November 20, 2019

C++ ч.2


Имена

Имя(идентификатор) является последовательностью букв и цифр. Первый символ должен быть буквой.


Описания

Имя(идентификатор) следует описать прежде, чем оно будет использоваться в программе на с++. В программе на с++ должно быть только одно определение каждого имени, но описаний может быть много. Однако все описания должны быть согласованы по типу вводимого в них объекта.


Области видимости

Описание определяется область видимости имени. Это значит, что имя может использоваться только в определённой части текста программы.

int x;    // глобальный х
void f()
{
  int = x;    // локальное х скрывает глобальное х
  x = 1;    // присвоить локальному х
  {
    int = x;    // скрывает первое локальное х
    x = 2;    // присвоить второму локальному х
  }
  x = 3;    // присвоить первому локальному х
}
int* p = &x;    // взять адрес глобального х

Переопределение имён стоит свести к минимому

Операция разрешениея области видимости :: даёт возможность получить доступ к скрытому глобальному имени.

int x;
void f()
{
  int x = 1;    // скрывает глобальное х
  ::x = 2;     // присвоение глобальному х
}

Объекты и адреса

Любой объект — это некоторая область памяти, а адресом называется выражение, ссылающееся на объект или функцию.


Связывание

В программе может быть только 1 нелокальный тип, значение, функция, или объект с данным именем.


Время жизни объекта

Объект будет создан при появление его определения и уничтожен, когда исчезнет из области видимости. Объекты с глобальными именами существуют до конца программы. Если локальные объекты описаны со служебными словом static, то они то же существуют до конца программы.


Свободная память

Именованный объект является либо статическим, либо автоматическим. Статический объект размещается в памяти в момент запуска программы и существует там до её завершения. Автоматический объект размещается в памяти всякий раз, как управление попадает в блок, содержащий определение объекта, и существует до тех пор, пока управление остаётся в этом блоке.
Тем не менее, удобно создавать новые объекты, которые существуют до тех пор, пока он не станет ненужным.

Операция new - помогает создавать объект, который можно использовать после возврата из функции, где он был создан.

Что бы не было утечки памяти нужно использовать:
delete - удаляет такие объекты
nullptr - очищение указателя в памяти


Типы

Типы задают операции, которые могут применяться к именам.

Производные типы:

Исходя из основных типов, можно задать производные типы, с помощью определения структур и следующих операций описаны:
* указатель [ ] массив
& ссылка ( ) функция

int *a;    // описание указателя
char *p[20];    // массив из 20 символьных указателей

Ключевая идея состоит в том, что описание объекта производного типа должно отражать его использование

int v[10];    // описание вектора
i = v[3];    // использование элемента вектора

& - является префиксным
[ ] ( ) - являются постфиксными


Тип Void

Синтаксически эквивалентен основным типам, но использовать его можно только в производном типе. С его помощью задаются указатели на объекты неизвестного типа или функции, не возвращающие значение.

void f();    // f не возвращает значение
void* pv;    // указатель на объект неизвестного типа

Пользовательские типы. Структуры и объединения

Удобно задавать используемым типам новые мнемонические имена, отражающие их суть в разрабатываемой программе.

Директива typedef позволяет задать синоним для встроенного или пользовательского типа данных. Имена, определенные с помощью директивы typedef, можно использовать точно так же, как обычные спецификаторы типов.

Синтаксис директивы typedef имеет два варианта:

typedef тип новое_имя;
typedef тип новое_имя[размерность];

Второй вариант используется при определении нового имени типа для массива, а квадратные скобки являются элементом синтаксиса.

typedef unsigned int UINT; //UINT - беззнаковое целое
typedef char msg[100]; //msg - массив из ста символов типа char

Объявленные таким образом имена типов используются так же, как и имена стандартных типов:

UINT i, j; //две переменных типа unsigned int
UINT А[10]; //массив переменных типа unsigned int
msg m; //массив (строка) из ста символов типа char
msg strs[10]; //массив из 10 строк по сто символов каждая

Перечисления (enum)

При написании программ часто возникает потребность определить несколько именованных констант, для которых требуется, чтобы все они имели различные значения (при этом конкретные значения могут быть не важны). Для этого удобно воспользоваться перечисляемым типом данных, все возможны�� значения которого задаются списком целочисленных констант.

enum[имя типа] { список_констант }; 

Здесь квадратные скобки указывают на необязательность использования.

Имя типа задается в том случае, если в программе требуется определять переменные этого типа. Компилятор обеспечивает, чтобы эти переменные принимали значения только из списка констант. Константы должны быть целочисленными и могут инициализироваться обычным образом. При отсутствии инициализатора первая константа будет рана нулю, а каждой следующей присваивается на единицу большее значение, чем предыдущей.

Имена перечисляемых констант должны быть уникальными, а значения могут совпадать. Преимущество применения перечисления перед описанием именованных констант и директивой typedef состоит в том, что связанные константы нагляднее; кроме того, компилятор при инициализации констант может выполнять проверку типов.

Структуры (struct)

Структура – это объединенное в единое целое множество поименованных элементов в общем случае разных типов. В отличие от массива, все элементы которого однотипны, структура может содержать элементы разных типов.

Каждая структура включает в себя один или несколько объектов (переменных, массивов, указателей, структур и т. д.), называемых элементами структуры (компонентами).

Элементы структуры также называются полями структуры и могут иметь любой тип, кроме типа этой же структуры, но могут быть указателями на него.

Структуры, так же, как и массивы относятся к структурированным типам данных. Они отличаются от массивов тем, что, во-первых, к элементам структуры необходимо обращаться по имени, во-вторых, все поля структуры необязательно должны принадлежать одному типу.

struct[имя_типа]
{ 
 тип_1 элемент_1; 
 тип_2 элемент_2;
 ...
 тип_n злемент_n; 
}[список_описателей];

Такое определение вводит новый производный тип, который называется структурным типом.

Если список описателей отсутствует, описание структуры определяет новый тип, имя которого можно использовать в дальнейшем наряду со стандартными типами.

struct Worker // описание нового типа Worker
{
 char fio[30];
 int age, code;
 double salary;
}; // описание заканчивается точкой с запятой 
// определение переменной, массива типа Worker и указателя на тип Worker
Worker y, staff[100], *ps;

Если отсутст������ет имя типа, должен быть указан список описателей переменных, указателей или массивов. В этом случае описание структуры служит определением элементов этого списка.

// Определение переменной, массива структур и указателя на структуру
struct {
 char fio[30];
 int age, code;
 double salary; 
} x, staff[100], *ps;

Имя структуры можно использовать сразу после его объявления (определение можно дать позднее) в тех случаях, когда компилятору не требуется знать размер структуры.

struct List; // объявление структуры List 
struct Link
{
 List *р; // указатель на структуру List
 Link *prev, *succ; // указатели на структуру Link
}
struct List { /* определение структуры List */}; 

Это позволяет создавать связные списки структур.

Для инициализации структуры значения ее элементов перечисляют в фигурных скобках в порядке их описания.

struct Worker
{
 char fio[30];
 int age, code;
 double salary;
};
Worker ivanov = {"Иванов И.И.", 31, 215, 5800.35};

При инициализации массивов структур следует заключать в фигурные скобки каждый элемент массива.

struct complex
{
 float re, im; 
} compl [3] = { {1.3, 5.2}, {3.0, 1.5}, {1.5, 4.1}};

Для переменных одного и того же структурного типа определена операция присваивания, при этом происходит поэлементное копирование. Но присваивание – это и все, что можно делать со структурами целиком. Другие операции, например сравнение на равенство или вывод, не определены. Структуру можно передавать в функцию и возвращать в качестве значения функции.

Доступ к полям структуры выполняется с помощью операций выбора . (точка) при обращении к полю через имя структуры и –> при обращен��и через указатель.

Ввод/вывод структур, как и массивов, выполняется поэлементно.

Worker worker, staff[100], *ps;

worker.fio = "Петров С.С.";
staff[3] = worker;
staff[8].code = 123;
ps->salary = 4350.00;

Переменные структурного типа можно размещать и в динамической области памяти, для этого надо описать указатель на структуру и выделить под нее место

Worker *ps = new Worker; //создает переменную структурного типа
Worker *pms = new Worker[5]; //создает массив структурного типа

//обращение через операцию косвенного доступа
ps->age = 55;
//обращение через разыменовывание указателя
(*ps).code = 253;

//обращение к 0 элементу созданного массива через индекс
pms[0].salary = 5800;

//обращение к 1 элементу созданного массива через указатель
(*(pms + 1)).salary = 4800;

//очистка занимаемой памяти
delete ps;
delete []pms;

Если элементом структуры является другая структура, то доступ к ее элементам выполняется через две операции выбора.

struct А
{
 int а;
 double х;
};

struct В
{
 А а;
 double х;
};

B x[2];
х[0].а.а = 1;
х[0].а.x = 35.15;
х[1].х = 0.1;

Как видно из примера, поля разных структур могут иметь одинаковые имена, поскольку у них разная область видимости.

Объединения (union)

Объединение (union) представляет собой частный случай структуры, все поля которой располагаются по одному и тому же адресу. Формат описания такой же, как у структуры, только вместо ключевого слова struct используется слово union.

union [имя_типа]
{ 
 тип_1 элемент_1; 
 тип_2 элемент_2;
 ...
 тип_n злемент_n; 
} [список_описателей];

Длина объединения равна наибольшей из длин его полей. Когда используется элемент меньшей длины, то переменная типа объединения может содержать неиспользуемую память. Все элементы объединения хранятся в одной и той же области памяти, начиная с одного адреса.

В каждый момент времени в переменной типа объединение хранится только одно значение, и ответственность за его правильное использование лежит на программисте.

Объединение применяется для следующих целей:
- экономия памяти в тех случаях, когда известно, что больше одного поля одновременно не требуется;
- интерпретация одного и того же содержимого области памяти объединения с точки зрения различных типов данных.

Доступ к элементам объединения осуществляется тем же способом, что и к структурам.

Заносить значения в участок памяти, выделенный для объединения, можно с помощью любого из его элементов. То же самое справедливо и относительно доступа к содержимому участка памяти, выделенного для объединения.

При определении конкретных объединений разрешена их инициализация, причем инициализируется только первый элемент объединения.

Например, пусть в магазине имеется возможность использования различных способов оплаты (кредитная карта, чек, наличные). Тогда в программе учета одновременно нам необходимо будет хранить только одно из значений, тогда как остальные нас не интересуют.

#include <iostream>
using namespace std;
int main()
{
 enum paytype {СНЕСК, CARD, CASH};
 paytype ptype;
 union payment{
 long check;
 char card[25];
 float sum;
 }
 //инициализация возможна только через первый элемент объединения
 payment info = 24557695;
 /* присваивание значений info и ptype */
 ptype = CASH;
 cin >> info.summ;
 switch (ptype)
 {
 case CHECK: cout << "Оплата чеком: " << info.check; break;
 case CARD : cout << "Оплата по карте: " << info.card; break;
 case CASH : cout << "Оплата наличными: " << info.sum; break;
 };
 return 0;
}

При определении объединений без явного указания имени объединяющего типа разрешено не вводить даже имени объединения. В этом случае создается анонимное или безымянное объединение.

Объединение часто используют в качестве поля структуры, при этом в структуру удобно включить дополнительное поле, определяющее, какой именно элемент объединения используется в каждый момент. Имя объединения можно не указывать, что позволяет обращаться к его полям непосредственно.

#include <iostream>
using namespace std;
int main()
{
 enum paytype {CARD, СНЕСК, CASH};
 struct payment
 {
 paytype ptype;
 union{
 char card[25];
 long check;
 float sum;
 };
 };
 payment info;
 /* присваивание значений info и ptype */ 
 ptype = CASH;
 cin >> info.summ;
 switch (info.ptype)
 {
 case CARD : cout << "Оплата по карте: " << info.card; break;
 case CHECK: cout << "Оплата чеком: " << info.check; break;
 case CASH : cout << "Оплата наличными: " << info.sum; break;
 };
 return 0;
}

Битовые поля

Битовые поля – это особый вид полей структуры. Они используются для плотной упаковки данных, например, флажков типа «да/нет». Минимальная адресуемая ячейка памяти – 1 байт, а для хранения флажка достаточно одного бита. При описании битового поля после имени через двоеточие указывается длина поля в битах (целая положительная константа).

struct Options
{
 bool centerX:1;
 bool centerY:1;
 unsigned int border_type:2;
 unsigned int color_index:4;
}

Битовые поля могут быть любого целого типа. Имя поля может отсутствовать, такие поля служат для выравнивания на аппаратную границу. Доступ к полю осуществляется обычным способом – по имени. Адрес поля получить нельзя, однако в остальном битовые поля можно использовать точно так же, как обычные поля структуры. Следует учитывать, что операции с отдельными битами реализуются гораздо менее эффективно, чем с байтами и словами, так как компилятор должен генерировать специальные коды, и экономия памяти под переменные оборачивается увеличением объема кода программы. Размещение битовых полей в памяти зависит от компилятора и аппаратуры.


Указатели

Указатели предназначены для хранения адресов областей памяти. Указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом.

Переменная Тип Т* может хранить адрес объекта Т. Указатели на массивы и функции требуют более сложной записи:

int* pi;
char** cpp;    // указатель на указатель на char
int(*fp) (char, char*)    // указатель на функцию с параметрами
                          // сhar и char*, возвращают int

Звездочка относится непосредственно к имени, поэтому для того, чтобы объявить несколько указателей, требуется ставить ее перед именем каждого из них. Например, в операторе:

int *a, b, *c;
описываются два указателя на целое с именами а и с, а также целая переменная b. 

Существует специальная операция взятия адреса, обозначаемая символом &. Ее результатом является адрес объекта.
Главная операция над указателями — это косвенное обращение, т.е обращение к объекту, на который настроен указатель. Эту операцию называют косвенностью. (Префиксные унарные операции)

char c1 = 'a';
char* p = &c1;    // p содержит адрес к с1
char c2 = *p;    // с2 = 'a'

Указатель может быть константой или переменной, а также указывать на константу или переменную. Рассмотрим примеры:

int j; // целая переменная 
const int ci = 1; // целая константа 
int *рi; // указатель на целую переменную 
const int *pci; // указатель на целую константу 
int * const ср = &i; // указатель-константа на целую переменную
const int * const срс = &ci // указатель-константа на целую константу

Указатель на константу

С указателями дело обстоит так, поскольку приходится учитывать два значения: адрес и содержимое памяти по этому адресу. В следующем примере р - это указатель на константу; находящийся в указателе адрес может измениться, но содержимое памяти по этому адресу - нет.

const int* p;
int i = 17;
p = &i; // Можно
*p = 29; // Нельзя

Константный указатель

С константными указателями все наоборот: адрес изменять нельзя, но зато можно изменять содержимое памяти по этому адресу.

int i = 17;
int j = 29;
int* const p; // Нельзя! Должно быть задано начальное значение
int* const p1 = &i; // Порядок
*p1 = 29; // Можно; величина, на которую ссылается указатель, может изменяться
p1 = &j; // Нельзя

Константный указатель на константу

Константный указатель на константу (попробуйте-ка трижды быстро произнести это вслух!) изменить вообще нельзя. Это неизменяемый адрес неизменяемой величины.

int i = 17;
int j = 29;
const int* const p; // Нельзя. Должен быть задан начальный адрес
const int* const p1 = &i; // Можно
*p1 = 29; // Нельзя
p1 = &j; // Нельзя

Читаем справа налево:

const char* pTr; // pTr является указателем (*) на констатный char
char* const pTr; // pTr является констатным (const) указателем (*) на char

Слово const делает неизменяемым тип слева от него.

Существуют следующие способы инициализации указателя:

  1. Присваивание указателю адреса существующего объекта:

• с помощью операции получения адреса:

int а = 5; // целая переменная 
int* p = &a; // в указатель записывается адрес а 
int* р (&а); // то же самое другим способом 

• с помощью значения другого инициализированного указателя:

int* r = р;

• с помощью имени массива или функции, которые трактуются как адрес:

int b[10]; // массив
int* t = b; // присваивание адреса начала массива

void f(int а){ /* ... */ } // определение функции 
void(*pf)(int); // указатель на функцию
pf = f; // присваивание адреса функции

2. Присваивание указателю адреса области памяти в явном виде:

char* cp = (char *) 0хВ8000000; 

Здесь 0хВ8000000 — шестнадцатеричная константа, (char *) – операция приведения типа: константа преобразуется к типу «указатель на char».

3. Присваивание пустого значения:

int* rulez = 0;

4. Выделение участка динамической памяти и присваивание ее адреса указателю:

• с помощью операции new:

int* n = new int; // 1 
int* m = new int (10); // 2
int* q = new int [10]; // 3 

В операторе 1 операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала этого участка в переменную n. Память под саму переменную n (размера, достаточного для размещения указателя) выделяется на этапе компиляции. В операторе 2, кроме описанных выше действий, производится инициализация выделенной динамической памяти значением 10.
В операторе 3 операция new выполняет выделение памяти под 10 величин типа int (массива из 10 элементов) и записывает адрес начала этого участка в переменную q, которая может трактоваться как имя массива. Через имя можно обращаться к любому элементу массива.

Для описанных выше переменных освобождение памяти будет выглядеть так:

delete n; // 1 
delete m; // 2 
delete []q; // 3

Операции с указателями

Разыменование(*) предназначение для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины:

char а; // переменная типа char
// выделение памяти под указатель (char *р) и под динамическую переменную типа char (new char)
char *р = new char;
*р = 'Ю'; // присваивание значения динамической переменной
// копирование содержимого ячейки памяти на которую указывает p в переменную a
а = *р;

Разыменование(*), можно использовать в левой части оператора присваивания. С ней допустимы все действия, определенные для величин соответствующего типа (если указатель инициализирован). На одну и ту же область память может ссылаться несколько указателей различного типа.

Арифметические операции с указателями (сложение с константой, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.

Унарная операция получения адреса & применима к величинам, имеющим имя и размещенным в оперативной памяти. Таким образом, нельзя получить адрес скалярного выражения, неименованной константы или регистровой переменной.

int а = 5; // переменная a
int *p = &a; // в указатель записывается адрес а

Ссылки

Ссылка представляет собой синоним имени, указанного при инициализации ссылки. Ссылку можно рассматривать как указатель, который всегда разыменовывается. Формат объявления ссылки:

тип & имя;

где тип — это тип величины, на которую указывает ссылка, & — оператор ссылки, означающий, что следующее за ним имя является именем переменной ссылочного типа, например:

int kol; 
int& раl = kol: // ссылка раl — альтернативное имя для kol 

Переменная-ссылка должна явно инициализироваться при ее описании, кроме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса

После инициализации ссылке не может быть присвоена другая переменная.

Тип ссылки должен совпадать с типом величины, на которую она ссылается.

Не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки.

Ссылки применяются чаще всего в качестве параметров функций и типов возвращаемых функциями значений. Ссылки позволяют использовать в функциях переменные, передаваемые по адресу, без операции разыменовывания, что улучшает читаемость программы.

Ссылка, в отличие от указателя, не занимает дополнительного пространства в памяти и является просто другим именем величины. Операция над ссылкой приводит к изменению величины, на которую она ссылается.

Ссылки и const

Ссылка сама по себе является неизменяемой.

int a = 10; 
int & const b = a; // ошибка 
int const & c = a; // ссылка на константу

Использование константных ссылок позволяет избежать копирования объектов при передаче в функцию.

Point midpoint ( Segment const & s ); 

По константной ссылке можно передавать rvalue.

Point p = midpoint(Segment(Point(0, 0), Point(1, 1))); 


Массивы

Массив – это упорядоченная последовательность переменных одного типа. Каждому элементу массива отводится одна ячейка памяти. Элементы одного массива занимают последовательно расположенные ячейки памяти. Все элементы имеют одно имя – имя массива и отличаются индексами – порядковыми номерами в массиве.

тип_данных имя_массива[количество элементов];

float v[3];    // массив из трёх чисел с плавающей точкой
int a[2][5];    // два массива из пяти целых каждый

Все инструкции по выделению памяти формирует компилятор до выполнения программы.

При описании массив можно инициализировать, то есть присвоить его элементам начальные значения:

базовый_тип имя_массива[размерность] = {начальные_значения};

Если инициализирующих значений меньше, чем элементов в массиве, остаток массива обнуляется, если больше – лишние значения не используются.

Многомерные массивы

Многомерные массивы задаются указанием каждого измерения в квадратных скобках. В памяти многомерные массивы располагаются в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее всего изменяется последний индекс.
Для доступа к элемен��у многомерного массива указываются все его индексы.

При инициализации многомерного массива он представляется либо как массив из массивов, при этом каждый массив заключается в свои фигурные скобки, либо задается общий список элементов в том порядке, в котором элементы располагаются в памяти.

int mass3[3][2] = {1,1,0,2,3,0} 
//для удобства чтения каждая строка массива может находиться на отдельной строчке

Динамические массивы

Если до начала работы программы неизвестно, сколько в массиве элементов, в программе следует использовать динамические массивы. Память под них выделяется с помощью операции new. Адрес начала массива хранится в переменной, называемой указателем. Например:

int n = 10
int *а = new int[n];

Если динамический массив в какой-то момент работы программы перестает быть нужным и мы собираемся впоследствии использовать эту память повторно, необходимо освободить ее с помощью операции delete[].

Таким образом, время жизни динамического массива, как и любой динамической переменной, с момента выделения памяти до момента ее освобождения. Область действия зависит от места описания указателя, через который производится работа с массивом. Локальная переменная при выходе из блока, в котором она описана, «теряется». Если эта переменная является указателем и в ней хранится адрес выделенной динамической памяти, при выходе из блока эта память перестает быть доступной, однако не помечается как свободная, поэтому не может быть использована в дальнейшем. Это называется утечкой памяти и является распространенной ошибкой.


Указатели и Массивы

Имя массива можно использовать, как указатель на его первый элемент.

Доступ к произвольному элементу массива обеспечивается по имени массива и индексу – целочисленному смещение от начала:

имя_массива[индекс]

Имя массива является указателем-константой. К нему приемлемы все правила адресной арифметики, связанные с указателями. Для любого массива соблюдается равенство:

имя_массива == &имя_массива == &имя_массива[]

Доступ к элементам массива возможен по его имени как указателю. Если описан массив

int z[3];
то *z аналогично z[0], а *(z+1) == z[1]

z[0] == *(z) == *(z + 0)
z[1] == *(z + 1)
z[2] == *(z + 2)
z[i] == *(z + i)

Так как имя массива есть не просто указатель, а указатель константа, то значение имени невозможно изменить. Получить доступ ко второму элементу массива int x[4] c помощью выражения *(++z) будет ошибкой, а выражение
*(x + 1) до��устимо.


Структуры

Структуры являются совокупностью элементов произвольных типов.

struct address {
  char* name;    // имя
  long number;    // номер
  int zip;    // индекс
}

С помощью операции точка(.) можно обращаться к отдельным членам структуры.


Эквиваленты типов

Два структурных типа считаются различными даже тогда, когда они имеют одни и те же члены.
В описании начинающемся служебным typedef, описывается не переменная указанного типа, а вводится новое имя для типа.


Функция

возвращаемое_значение имя_функции (параметр)
{
  блок_повторяющегося_кода(тело);
}

Шаблоны функции

Цель введения шаблонов функций – автоматизация создания функций, которые могут обрабатывать разнотипные данные. В отличие от механизма перегрузки, когда для каждой сигнатуры определяется своя функция, шаблон семейства функций определяется один раз. Шаблон располагается перед main.

template <type name T>
T имя_функции(параметр)
{
  тело_функции;
}
#include <iostream>
#include <math.h>
using namespace std;
template <class array_type>
array_type max(array_type *a, const int N)
{
 array_type m = a[0];
 for (int i = 1; i < N; i++)
 if (a[i] > m)
 { 
 m = a[i];
 }
 return m;
}

int main(int argc, char* argv[])
{
 setlocale(LC_ALL, "Russian");
 double a[] = {2.5, 8.3, 6};
 int b[] = {3, 5, -1, 2};
 char c[] = {'A', 'b', 'Z', 'r'};
 cout << max(a, sizeof(a)/sizeof(a[0])) << endl;
 cout << max(b, sizeof(b)/sizeof(b[0])) << endl;
 cout << max(c, sizeof(c)/sizeof(c[0])) << endl;
 return 0;
}

При использовании шаблонов уже нет необходимости готовить заранее все варианты функций с перегруженным именем. Компилятор автоматически, анализируя вызовы функций в тексте программы, формирует необходимые определения именно для таких типов параметров, которые использованы в обращениях. Дальнейшая обработка выполняется так же, как и для перегруженных функций.

Основные свойства параметров шаблона:

1. Имена параметров шаблона должны быть уникальными во всем определении шаблона.

2. Список параметров шаблона функции не может быть пустым, так как при этом теряется возможность параметризации и шаблон функции становиться обычным определением конкретной функции.

3. В списке параметров шаблона функции может быть несколько параметров. Каждый из них должен начинаться со служебного слова class.

template <class type1, class type2>

4. Недопустимо использовать в заголовке шаблона параметры с одинаковыми именами, т.е. ошибочен такой заголовок:

template <class type1, class type1, class type1>

Функции с переменным количеством параметров

В C++ допустимы функции, у которых количество параметров при компиляции определения функции не определено. Кроме того, могут быть неизвестными и типы параметров. Количество и типы параметров становятся известными только в момент вызова функции, когда явно задан список фактических параметров. При определении и описании таких функций спецификация формальных параметров заканчивается многоточием:

тип имя (список_явных_параметров, ...);

Каждая функция с переменным списком параметров должна иметь один из двух механизмов определения их количества и типов.

Первый подход предполагает добавление в конец списка реально использованных, необязательных, фактических параметров специального параметра-индикатора с уникальным значением которое будет сигнализировать об окончании списка. В теле функции параметры перебираются, и их значение сравнивается с заранее известным концевым признаком.

Второй подход предусматривает передачу в функцию значения реального количества используемых фактических параметров, которые передаются с помощью одного из обязательных параметров.

В обоих случаях переход от одного фактического параметра к другому выполняется с помощью указателей (с использованием адресной арифметики).

#include <iostream>
using namespace std;
long summa(int k, ...)
{
 int *pik = &k;
 long total = 0;
 for (; k; k--)
 total += *(++pik);
 return total;
}

int main(int argc, char* argv[])
{
 setlocale(LC_ALL, "Russian");
 
 cout << "\n summa(2, 6, 4) = " << summa(2, 6, 4);
 cout << "\n summa(6, 1, 2, 3, 4, 5, 6) =" <<
 summa(6, 1, 2, 3, 4, 5, 6);
 return 0;
}

Результат
summa(2, 6, 4)=10
summa(6, 1, 2, 3, 4, 5, 6)=21

Протатип функции

возвращаемое_значение имя_функции(параметр);

Ссылки

Ссылка должна быть инициализирована, т.е должно быть нечто, что она может обозначать. После инициализации значение ссылки не может быть изменено: она всегда указывает на тот, объект, к которому была привязана при инициализации.


Строка

Это последовательность символов заключённая в двойные кавычки(" "):

const int len_str = 100;
char msg[len_str] = "Новая строка";


Если строка при определении инициализируется, ее размерность можно опускать:

char msg[ ] = "Новая строка"; //13 символов

Для размещения строки в динамической памяти надо описать указатель на

char, а затем выделить память с помощью new.

char *p = new char[len_str];

Для ввода-вывода строк используются как уже известные нам объекты cin и cout, так и функции, унаследованные из библиотеки С.

#include <iostream>
using namespace std;
int main()
{
 const int n = 80; 
 char s[n]; 
 cin >> s;
 cout << s << endl;
 return 0;
}

При вводе строки из нескольких слов, программа выведет только первое слово. Это связано с тем, что ввод выполняется до первого пробельного символа (то есть пробела, знака табуляции или символа перевода строки '\n')

Если требуется ввести строку, состоящую из нескольких слов, в одну строковую переменную, используются методы getline или get класса istream, объектом которого является cin.

#include <iostream>
using namespace std;
int main()
{
 const int n = 80; 
 char s[n]; 
 cin.getline(s, n);
 cout << s << endl;
 return 0;
}

Если в программе требуется ввести несколько строк, метод

getline удобно использовать в заголовке цикла, например:

#include <iostream>
using namespace std;
int main()
{
 const int n = 80; 
 char s[n]; 
 while (cin.getline(s, n))
 {
 cout << s << endl;
 };
 return 0;
}

Операции со строками

Для строк не определена операция присваивания, поскольку строка является не основным типом данных, а массивом. Присваивание выполняется с помощью функций стандартной библиотеки или посимвольно «вручную». Например, чтобы присвоить строке p строку a, можно воспользоваться функциями strcpy или strncpy, а для определения длинны строки – strlen.

#include <iostream>
#include <string.h>
using namespace std;
int main()
{
 char a[100] = "Working with a strings";
 size_t m = strlen(a) + 1; //добавим 1 для учета нуль-символа
 char *p = new char [m];
 strcpy(p, a);
 strncpy(p, a, strlen(a) + 1); 
 return 0;
}

Для использования этих функций к программе следует подключить заголовочный файл <string.h>.
Для преобразования строки в целое число используется функция atoi(str). Если преобразование не удалось, возвращает 0.
Аналогичные функции преобразования строки в длинное целое число (long) и в вещественное число с двойной точностью (double) называются atol и atof соответственно.

//Пример применения функций преобразования
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
 char a[] = "15) Кол-во - 249 шт. Цена - 499.99 руб.";
 int num;
 long quantity;
 double price;
 num = atoi(a);
 quantity = atol(&a[12]);//12 - смещение начала кол-ва
 price = atof(&a[27]); //27 - смещение начала цены
 cout << num << ' ' << quantity << ' ' << price;
 return 0;
}

Замечание. При переводе вещественных чисел разделитель целой и дробной части зависит от настроек локализации. По умолчанию используется символ точка. При изменении локализации (функция setlocale(LC_ALL, "Russian")), разделитель меняется на принятый в России, т.е. символ запятая.

Некоторые Стандартные функции работы со троками

<string.h> (<cstring>) – функции работы со строками в стиле C

Складывает строки
Функция добавляет s2 к s1 и возвращает s1. В конец результирующей строки добавляется нуль-символ

char *strcat(char *s1, char *s2);

Ищет символ в строке
Функция возвращает указатель на первое вхождение символа ch в строку s, если его нет, то возвращается NULL.

char *strchr(char *s, int ch);

Сравнивает строки
Функция сравнивает строки и возвращает отрицательное (если s1 меньше s2), нулевое (если s1 равно s2) или положительное (если s1 больше s2) значение.

int strcmp(char *s1, char *s2);

Копирует одну строку в другую
Функция копирует s2 в s1 и возвращает s1.

char *strcpy(char *s1, char *s2);

Копирует первые n символов одной строки в другую
Функция копирует не более n символов из s2 в s1 и возвращает s1. Если длина исходной строки превышает или равна n, нуль-символ в конец строки s1 не добавляется. В противном случае строка дополняется нуль-символами до n-го символа. Если строки перекрываются, поведение не определено.

char *strncpy(char *s1, char *s2, size_t n);

Ищет символ в строке
Функция возвращает указатель на первое вхождение символа ch в строку s справа, если его нет, возвращает NULL.

char *strrchr(char *s,int ch);

Ищет подстроку в строке
Функция выполняет поиск первого вхождения подстроки s2 в строку s1. В случае удачного поиска, возвращает указатель на элемент из s1, с которого начинается s2, и NULL в противном случае.

char *strstr(char *s1, char *s2);

Преобразует строку в число
Функция преобразует строку символов в числовое значение и возвращает его. При переполнении возвращает +/-HUGE_VAL При невозможности выполнить преобразование или исчезновении порядка возвращает 0. В обоих последних случаях errno устанавливается в ERANGE. end указывает на символ, на котором преобразование завершается.

double strtod(const char *str, char **end);

<ctype.h> (<cctype>) – функции классификации и преобразования типов

Возвращает символ в нижнем регистре
Функция получает параметр ch и возвращает его в нижнем регистре. В параметре ch используется только младший байт.

int tolower(int ch);

Возвращает символ в верхнем регистре
Функция получает параметр ch и возвращает его в верхнем регистре. В параметре ch используется только младший байт.

int toupper(int ch);

Проверяет, является ли символ буквой или цифрой
Функция выделяет младший байт параметра ch и возвращает значение true, если символ ch является буквой или цифрой, или false в противном случае.

int isalnum(int ch);

Проверяет, является ли символ буквой
Функция выделяет младший байт параметра ch и возвращает значение true, если символ ch является буквой, или false в противном случае.

int isalpha(int ch);

Проверяет, является ли символ цифрой
Функция выделяет младший байт параметра ch и возвращает значение true, если символ ch является цифрой, или false в противном случае.

int isdigit(int ch);

Таблица имён

Есть функция поиска в таблице имён:

name* look(char* p, int ins=0);

Второй её параметр показывает, была ли символьная строка, обозначающее имя, ранее занесена в таблицу.

Занесение в таблицу:

inline name* insert(const char* s) {return look(s,1)}

istrtream - это функция istream, которая считывает символы из строки, являющейся её первым параметром.

Включить в программу файлы <strstream.h>


Поразрядные логические операции

& | ^ ~ >> << Применяются к целым, тоесть к объектам типа char, short, int, long и к их безнаковым аналогам.
Чаще всего используются для работы с небольшим по величие множеством данных


Комментарии

Рекомендуется такой стиль ведения комментариев:

  • Начинать с комментария каждый файл программы: указать в общих чертах, что в ней определяется, дать ссылки на справочные руководства, общие идеи по сопровождению программы.
  • Снабжать комментарием каждое определение класса или шаблона типа.
  • Комментировать каждую нетривиальную функцию, указав: её назначение, используемый алгоритм.
  • Комментировать определение каждой глобальной переменной.
  • Давать некоторое число комментариев в тех местах где алгоритм неочевиден или непереносим.

Заголовочные файлы

Типы одного объекта или функции должны быть согласованны во всех их описаниях. Способ добиться согласованности описаний в различных файлах это: включить во входные файлы, содержащи�� операторы и определения данных, заголовочные файлы, которые содержат интерфейсную информацию.
Средством включения текстов служит макрокоманда #include, которая позволяет собрать в один файл несколько исходных файлов программы.

#include "включаемый_файл"

Команда заменяет строку, в которой она бала задана, на содержимое файла включаемый_файл.


Работа с файлами

Для работы с файлами необходимо подключить заголовочный файл <fstream>. В <fstream> определены несколько классов и подключены заголовочный файлы <ifstream> - файловый ввод и <ofstream> - файловый вывод.

Файловый ввод/вывод аналогичен стандартному вводу/выводу, единственное отличие – это то, что ввод/вывод выполнятся не на экран, а в файл. Если ввод/вывод на стандартные устройства выполняется с помощью объектов cin и cout, то для организации файлового ввода/вывода достаточно создать собственные объекты, которые можно использовать аналогично операторам cin и cout.

Например, необходимо создать текстовый файл и записать в него строку Работа с файлами в С++.

#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
	ofstream fout("cppstudio.txt"); // создаём объект класса ofstream и связываем его с файлом cppstudio.txt
	fout << "Работа с файлами в С++"; // запись строки в файл
	fout.close(); // закрываем файл
	system("pause");
	return 0;
}

Для того чтобы прочитать файл понадобится выполнить те же шаги, что и при записи в файл с небольшими изменениями:

#include <fstream>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
	setlocale(LC_ALL, "rus"); // корректное отображение Кириллицы
	char buff[50]; // буфер промежуточного хранения считываемого из файла текста
	ifstream fin("cppstudio.txt "); // (ВВЕЛИ НЕ КОРРЕКТНОЕ ИМЯ ФАЙЛА)

	if (!fin.is_open()) // если файл не открыт
		cout << "Файл не может быть открыт!\n"; // сообщить об этом
	else
	{
		fin >> buff; // считали первое слово из файла
		cout << buff << endl; // напечатали это слово
		fin.getline(buff, 50); // считали строку из файла
		fin.close(); // закрываем файл
		cout << buff << endl; // напечатали эту строку
	}
	system("pause");
	return 0;
}

В программе показаны два способа чтения из файла, первый – используя операцию передачи в поток, второй – используя функцию getline(). В первом случае считывается только первое слово, а во втором случае считывается строка, длинной 50 символов.

Режим открытия файла

Устанавливает характер использования файлов. Для установки режимов в классе ios_base предусмотренны константы, которые определяют режим открытия файла.

ios_base::in - открыть файл для чтения.
ios_base::out - открыть файл для записи.
ios_base::ate - при открытии файла переместить указатель в конец.
ios_base::app - открыть файл для записи в конец файла.
ios_base::trunc - удалить содержимое файла, если он существует.
ios_base::binary - открытие файла в двоичном режиме.

ate иapp по описанию очень похожи, они оба перемещают указатель в конец файла, но флаг app позволяет производить запись, только в конец файла, а флаг ate просто переставляет флаг в конец файла и не ограничивает места записи.

Режимы открытия файлов можно устанавливать непосредственно при создании объекта или при вызове функции open().

ofstream fout("cppstudio.txt", ios_base::app); // открываем файл для добавления информации к концу файла fout.open("cppstudio.txt", ios_base::app); // открываем файл для добавления информации к концу файла

Режимы открытия файлов можно комбинировать с помощью поразрядной логической операции или |, например: ios_base::out | ios_base::trunc — открытие файла для записи, предварительно очистив его.

Объекты класса ofstream, при связке с файлами по умолчанию содержат режимы открытия файлов ios_base::out | ios_base::trunc. То есть файл будет создан, если не существует. Если же файл существует, то его содержимое будет удалено, а сам файл будет готов к записи. Объекты класса ifstream связываясь с файлом, имеют по умолчанию режим открытия файла ios_base::in — файл открыт только для чтения.


Препроцессор

Некая программа, которая проводит некоторые манипуляции с кодом ещё до того, как он скомпилируется. Это своего рода предварительная обработка кода.

#define - директива находит в коде символы, которые мы ей укажим, и заменяет их на то, что мы ей зададим. Срабатывает до ко��пиляции кода.


Argc, Argv

Argc - колличество аргументов, говворит сколько параметров мы передали.
Argv - массив, который является указателем на char

(int argc, char* argv[])

Методы Get и Set

Get - получает данные.

Set - устаналивает значение.

Пример:
int GetX(){}
void SetX(int valueX)

Константные методы

Методы классов могут быть объявлены как const.

struct IntArray 
{ 
    size_t size () const;
};

Такие методы не могут менять поля объекта (тип this — указатель на const).

IntArray const * p = foo (); 
p - > resize (); // ошибка 

Внутри константных методов можно вызывать только константные методы.
Слово const является частью сигнатуры метода.

size_t IntArray :: size () const { return size_ ;}

Можно определить две версии одного метода:

struct IntArray 
{ 
    void foo () const 
    { // нарушение логической константности 
        data_ [10] = 1; 
    } 
private : 
    size_t size_ ; 
    int * data_ ; 
}; 

Ключевое слово mutable позволяет определять поля, которые можно изменять внутри константных методов:

struct IntArray 
{ 
   size_t size () const 
   { 
       ++ counter_ ; 
       return size_ ; 
   } 
private : 
   size_t size_ ; 
   int * data_ ; 
   mutable size_t counter_ ; 
}; 

Наследование

Это механизм создания нового класса на основе уже существующего. При этом к существующему классу могут быть добавлены новые элементы, либо существующие функции могут быть изменены. Основное назначение - повторное использование кодов.

Для порождения нового класса не основе существующего используется следующая форма:

class Имя : Модификатор_Доступа Имя_Базового_Класса
{Объявление членов;};

Модификаторы Доступа

Private: член класса Х могут использовать только функции-члены и друзья Х

Protected: член класса Х могут использовать только функции-члены и друзья класса Х, а так же функции-члены и друзья всех производных от Х классов.

Public: член можно использовать в любой функции.


Классы

Это пользовательский тип.

Для того, чтобы порождённый класс имел доступ к некоторым скрытым членам базового класса, в базовом класса их необходимо объявить со спецификацией доступа protected.

Неявные преобразования между пораждённым и базовым классами называются преопределёнными стандартными преобразованиями:

  • Объект порождённого класса неявно преобразуется к объекту базового класса.
  • Ссылка на порождённый класс неявно преобразуется к ссылке на базовый класс
  • Указатель на порождённый класс неявно преобразуется к указателю на базовый класс.

Множественные вхождения:

Возможность иметь более одного базового класса влечёт за собой возможность неоднократного вхождения класса, как базового.

Множественное наследование:

Класс наследние унаследует поведение всех своих предков. При одинаковых методах нужно использовать приведение типов.

((Тип)Объект).Метод();

Производные классы:

Это средство определения класса. Новые возможности добавляются к уже существующему классу, не требуя его перепрограммирования или пере трансляции. С помощью производных классов можно организовать общий интерфейс с несколькими различными классами так, что в других частях программы можно будет единообразно работать с объектами этих классов.

Производный класс наследует базовый класс. Объект производного класса содержит объект своего базового класса.

Параметры для конструктора базового класса задаются в определении конструктора производного класса. В этом смысле базовый класс выступает, как класс являющийся членом производного класса.

Производный класс сам в свою очередь может быть базовым классом.

Шаблонные классы:

Используются, когда мы хотим, чтобы класс работал с разными типами данных, и чтобы для каждого типа данных не приходилось писать личную реализацию

template <typename T>

Абстрактные классы:

Абстрактный класс можно использовать только в качестве базового для другого класса. Абстрактные классы нужны для задания интерфейса без уточнения каких-либо конкретных деталей реализации.

В Абстрактном классе присутствует чистая функция. Это виртуальная функция - она пустая, в ней не описана реализация.


Шаблоны Типа

Задаёт способ построения отдельных классов, подобно тому, как описание класса задаёт способ построения его отдельных объектов.

имя_шаблонного_класса <тип> является именем класса(определеяемым шаблоном тип), и его можно использовать, как все имена класса.


Функции

Функция – самостоятельная единица программы, спроектированная для реализации конкретной задачи, представляющая именованную группу операторов и выполняющая законченное действие. К функции можно обратиться по имени, передать ей значения и получить из нее результат. Функции избавляют нас от повторного программирования, позволяют использовать их в различных программах, повышают уровень модульности программы.

Описание функций:

Объявление функции (прототип, заголовок, сигнатура) задает ее имя, тип возвращаемого значения и список передаваемых параметров. Формат простейшего заголовка (прототипа) функции:

[класс] тип имя([ список_формальных_параметров ]);

Определение функции содержит, кроме объявления, тело функции, представляющее собой последовательность операторов и описаний в фигурных скобках:

[класс] тип имя([ список_формальных_параметров ]);
{
    тело функции;
}

Выражение, записанное в квадратных скобках, является опциональным и может быть опущено.

Cоставные части определения:

  1. С помощью необязательного модификатора класс можно явно задать область видимости функции, используя ключевые слова extern и static:
    - extern – глобальная видимость во всех модулях программы (по умолчанию);
    - static – видимость только в пределах модуля, в котором определена функция.
  2. Тип возвращаемого функцией значения может быть любым, кроме массива и функции (но может быть указателем на массив или функцию). Если функция не должна возвращать значение, указывается тип void.
  3. Список формальных параметров определяет данные, которые требуется передать в функцию при ее вызове. Элементы списка параметров разделяются запятыми. Для каждого параметра, передаваемого в функцию, указывается его тип и имя (в объявлении имена можно опускать), а также значение параметра по умолчанию (при этом все параметры правее данного должны также иметь значения по умолчанию).
    Вызов функции приводит к выполнению операторов, составляющих тело функции, и выглядит следующим образом:
имя([ список_фактических_параметров ]);


В определении, в объявлении и при вызове одной и той же функции типы и порядок следования параметров должны совпадать.

Перед вызовом компилятор должен знать прототип функции, поэтому одновременное объявление и определение должно следовать перед вызовом функции.

Функцию можно определить как встроенную с помощью модификатора inline, который рекомендует компилятору вместо обращения к функции помещать ее код непосредственно в каждую точку вызова. Модификатор inline ставится перед типом функции. Он применяется для коротких функций, чтобы снизить накладные расходы на вызов (сохранение и восстановление регистров, передача управления).
Все величины, описанные внутри функции, а также ее параметры, являются локальными. Областью их действия является функция.
При выходе из функции соответствующий участок стека освобождается, поэтому значения локальных переменных между вызовами одной и той же функции не сохраняются. Если этого требуется избежать, при объявлении локальных переменных используется модификатор static.
��озврат вычисленного значения организуется следующим образом. В теле функции должен присутствовать оператор return после которого следует выражение, вычисляющее возвращаемое значение, совместимое с типом функции.

Параметры функции:

Механизм параметров является основным способом обмена информацией между вызываемой и вызывающей функциями.
Параметры, перечисленные в заголовке описания функции, называются формальными параметрами, или просто параметрами, а записанные в операторе вызова функции – фактическими параметрами, или аргументами.

Существует два основных способа передачи параметров в функцию: по значению и по адресу.
При передаче по значению в стек заносятся копии значений аргументов, и операторы функции работают с этими копиями. Доступа к исходным значениям параметров у функции нет, а, следовательно, нет и возможности их изменить.
При передаче по адресу в стек заносятся копии адресов аргументов, а функция осуществляет доступ к ячейкам памяти по этим адресам и может изменить исходные значения аргументов. Данный способ подразделяется на передачу параметров через указатель и по ссылке.

#include <iostream>
using namespace std;
void f(int a, int* b, int& c)
{
    a++;
    (*b)++;
    c++;
}

void main()
{
    int a=1,b=1,c=1;
    cout<<"abc"<<endl;
    cout<<a<<''<<b<<''<<c<<endl;
    f(a,&b,c);
    cout<<a<<''<<b<<''<<c<<endl
}

Результат работы программы:
abc
111
122

Первый параметр (a) передается по значению. Его изменение в функции не влияет на исходное значение. Второй параметр (b) передается по адресу с помощью указателя, при этом для передачи в функцию адреса фактического параметра используется операция взятия адреса, а для получения его значения в функции требуется операция разыменования. Третий параметр (c) передается по адресу с помощью ссылки.
При передаче по ссылке в функцию передается адрес указанного при вызове параметра, а внутри функции все обращения к параметру неявно разыменовываются. Поэтому использование ссылок вместо указателей улучшает читаемость программы, избавляя от необходимости применять операции получения адреса и разыменования. Использование ссылок вместо передачи по значению более эффективно, поскольку не требует копирования параметров, что имеет значение при передаче структур данных большого объема.
Если требуется изменить значение параметра внутри функции, то он передается, либо через ссылку, либо через указатель.
Если требуется запретить изменение параметра внутри функции, используется модификатор const:

int f(const char*);

Таким образом, исходные данные, которые не должны изменяться в функции, предпочтительнее передавать ей с помощью константных ссылок.

По умолчанию параметры любого типа, кроме массива и функции (например, вещественного, структурного, перечисление, объединение, указатель), передаются в функцию по значению.

Если в определении функции присутствует параметр со значением по умолчанию, то данный параметр можно опустить при вызове функции. При этом все параметры левее данного должны присутствовать. Значение данного параметра внутри функции будет совпадать со значением параметра по умолчанию.

Передача массивов в функцию:

Параметрами функции могут быть массивы, и функции могут возвращать указатель на массив в качестве результата. При использовании массивов в качестве параметров в функцию передается указатель на его первый элемент, т.е. массив всегда передается по адресу. При этом информация о количестве элементов теряется, поэтому следует передавать размерность массива как дополнительный параметр.
Рассмотрим пример функции, формирующей вектор, состоящий из максимальных элементов двух исходных векторов.

#include <iostream>
using namespace std;

//функция формирует вектор(одномерный массив),
//состоящий из максимальных элементов исходных массивов
//массивы передаются через указатели
void max_vect(int n, int *x, int *y, int *z)
{
    for(int i=0;i<n;i++)
    z[i] = (x[i]>y[i] ? x[i]:y[i]);
}

void main()
{
    //определим размерность массива
    const int N = 7;
    int a[N] = {1,4,3,-1,5,6,1};
    int b[N] = {7,6,-2,4,3,2,4};
    int c[N];

    //получим вектор, состоящий из максимальных элементов исходных массивов
    max_vect(N,a,b,c);
    
    //выведем на экран элементы массива
    for(int i=0;i<N;i++)
    cout<<"\t"<<c[i];
}

Результат:
7 6 3 4 5 6 4

При передаче многомерных массивов, все размерности, если они не известны на этапе компиляции, должны передаваться в качестве параметров. Внутри функции массив интерпретируется как одномерный, а его индекс пересчитывается в программе.

Для работы с двумерным массивом естественным образом необходимо применить альтернативный способ выделения памяти под массив. При этом память выделяется в два этапа: сначала под столбец указателей на строки матрицы, а затем в цикле под каждую строку. Освобождение памяти должно выполняться в обратном порядке.

#include <iostream>
#include <stdlib.h>
using namespace std;
int sum(int **x, const int n, const int m)
{
 int s = 0;
 for (int i=0; i < n; i++)
 for (int j=0; j < m; j++)
 s += x[i][j];
 return s;
}
void main()
{
 setlocale(LC_ALL, "Russian");
 const int N=4, M=3;
//создаем одномерный массив указателей размером N элементов
//(то что это массив указывают квадратные скобки, а то что указателей-*)
 int **a = new int* [N]; 
for(int i=0; i < N; i++)
 //динамически создаем одномерный массив размерностью M
 //и присваиваем его адрес элементу массива указателей
 a[i] = new int[M];
 //заполним массив случайными числами и выведем его на экран
 for(int i=0; i < N; i++)
 {
 for(int j=0; j < M; j++)
 {
 //путем получения остатка (%) зададим числа в диапазоне от 0 до 9
 a[i][j] = rand()%10;
 //здесь по индексу i выбирается указатель на одномерный массив
 //а индексу j - элемент в этом массиве
 cout << a[i][j] << "\t";
 }
 cout << endl;
 }
 cout << sum(a, N, M);
 //освобождаем выделенную память в обратном порядке
 for(int i=0; i < N; i++)
 //сначала удаляем одномерные массивы
 delete [] (a[i]);
 //а затем и сам массив указателей
 delete [] a;
}

Указатели на функцию

Возможны только две операции с функциями: вызов и взятие адреса. Указатель полученный с помощью последней операции, можно впоследствии использовать для вызова функции.

Тип_функции(*имя_указателя)(спецификация_параметров)

Это фактически переменная, которой мы можем присвоить ссылку на функцию.

float (*my_func_ptr)(int, char*);
// Для большей удобочитаемости, рекомендуется использовать typedef.
// Особенно можно запутаться, когда указатель на функцию является аргументом функции.
// Тогда бы объявление выглядело так:
typedef float (*MyFuncPtrType)(int, char*);
MyFuncPtrType my_func_ptr

Для того чтобы указатель на функции указывал на вашу функцию, необходимо выполнить следующую конструкцию:

my_func_ptr = some_func;

Для вызова функции через указатель:

(*my_func_ptr)(7, "Arbitrary string");

Виртуальные функции

В базовом классе описываются функции, которые могут переопределяться в любом производном классе.

С помощью таких функций можно описать класс, который будет исполнять определённый код. Объекты классов будут сами определять своё поведение, в зависимости от реализации.

Что переопределить метод в классе наследнике, нужно использовать ключевое слово virtual.

virtual тип имя_функции()

Ключевое слово override нужно использовать для того, чтобы компилятор контролировал правильность описания сигнатур метода, который подлежит переопределению.

тип имя() override

Индексация

Операторная функция operator[ ] задаёт для объектов классов интерпретацию индексации. Второй параметр этой функции(индекс) может иметь произвольный тип. Это позволяет определять, например ассоциативный массив.


Конструкторы и деструкторы

Если у класса есть конструктор, он вызывается всякий раз при создании объекта этого класса. Если у класса есть деструктор, он выз��вается всякий раз, когда уничтожается объект этого класса.

Объект может создаваться как:

  • Автоматический, который создаётся каждый раз, когда его описание встречается при выполнении программы, и уничтожается по выходу из блока, в котором он описан.
  • Статический, который создаётся один раз при запуске программы и уничтожается при её завершении.
  • Объект в свободной памяти, который создаётся операцией new и уничтожается операцией delete.
  • Объект-член, который создаётся в процессе создания другого класса или при создании массива, элементом которого он является.

Конструктор локальной переменной вызывается каждый раз, когда при выполнении программы встречается её описание. Деструктор локальной переменно вызывается по выходу из блока, где она была описана.

Конструкторы класса вызываются в том порядке, в котором они заданы в описании класса.

Конструктор копирования:

Если не определить конструктор копирования, то он сгенерируется компилятором.

struct IntArray 
{ 
    IntArray ( IntArray const & a ) 
        : size_ ( a . size_ ) , data_ (new int [ size_ ]) 
    { 
        for ( size_t i = 0; i != size_ ; ++ i ) 
             data_ [ i ] = a . data_ [ i ];
    } 
    ... 
private : 
      size_t size_ ; 
      int * data_ ; 
};

Компилятор генерирует четыре метода:
1. конструктор по умолчанию,
2. конструктор копирования,
3. оператор присваивания,
4. деструктор.
Если потребовалось переопределить конструктор копирования,
оператор присваивания или деструктор, то нужно
переопределить и остальные методы из этого списка.

Виртуальный деструктор:

Нужен для того, чтобы правильно освобождать ресурсы в классах наследниках,
в случае если будет использоваться указатель базового класса, для хранения ссылок на классы наследники.


Операция преобразования

Конструктор удобно использовать для преобразования типа, но возможно нежелательные последствия:

  • Неявные преобразования от пользовательского типа к основному невозможны (поскольку основные типы не являются классами)
  • Нельзя задавать преобразование из нового типа в старый, не изменяя описание старого типа.
  • Нельзя определить конструктор с одним параметром, не определив тем самым и преобразование типа.

Ключевое слово this

Указатель объекта на самого себя. Это знание объекта о том, где он в памяти находится.

this -> т.к this хранит ссылку на самого себя, то через него можно получить доступ ко всему функционалу, который есть в объекте.


Интерфейсы

Это некая договорённость о том как можно с чем-то взаимодействовать. Это как абстрактный класс, в котором все методы чисто виртуальные, в интерфейсе не может быть никакой реализации.


Итераторы

Итераторы предназначены для перебора элементов контейнеров. Их универсальность позволяет писать код независящий от типа контейнера.


Множества

Это математическая структура, которая позволяет хранить в себе уникальные элементы. Каждый элемент может входить в множество только один раз.


Строки и символы

Вызов библиотеки отвечающей за работу со строками:

#include <string>

getline( ) - функция для считывания строки целиком, а не пословно.

Для хранения одного символа используется тип char.

Можно обращаться к отдельным символам строки, написав после её имени в квадратных скобках номер символа.

Узнать длину строк�� можно с помощью метода size.

По аналогии с переводом вещественных чисел в целые, мы можем сделать перевод из типа char в тип int, чтобы узнать код символа.

Метода find работает следующим образом: если подстрока нашлась, то возвращается число, равное номеру символа, с которого началось первое вхождение подстроки в строку. Если подстрока не нашлась, то метод возвращает -1.


Словарь

Ассоциативный массив, часто называется словарём или таблицей map.

Имея одно из значений, называемое ключём, можно получать доступ к другому, называемому просто значение.

Вызов библиотеки отвечающей за работу со словарём:

#include <map>

Создание словаря:

map<ключ, значение> имя;

Создать элемент в словаре:

имя_словаря[ключ]

При создании элемента в словаре, нужно сразу прировнять элемен к какому-либо значению.


Обработка ошибок

Функция, которая обнаружила ошибку и не может справиться с нею, запускает особую ситуацию, расчитывая, что устранить проблему можно в той функции, которая прямо или опосредственно вызывала первую. Если функция расчитана на обработку ошибок некоторого вида, она может указать это явно, как готовность перехватить данную особую ситуацию.


Особые ситуации

Особые ситуации дают средство сигнализировать о происходящих в конструкторе ошибках. Есть следующие способы сигнализации:

  • Возвратить объект в ненормальном состоянии в расчёте, что пользователь проверит его состояние.
  • Установить значение нелокальной переменной, которое сигнализирует, что создать объект не удалось.