- •Алфавит языка
- •Идентификаторы
- •Ключевые слова
- •Знаки операций
- •Константы
- •Комментарии
- •Концепция типа данных
- •Простые типы данных
- •Целый тип (int)
- •Символьный тип (char)
- •Расширенный символьный тип (wchar_t)
- •Логический тип (bool)
- •Типы с плавающей точкой (float, double и longdouble)
- •Предварительные замечания о функциях ввода/вывода
- •Переменные
- •Операции
- •Выражения
- •Оператор "выражение"
- •Операторы ветвления Условный оператор if
- •Оператор switch
- •Цикл с предусловием (while)
- •Цикл с постусловием (dowhile)
- •Цикл с параметром (for)
- •Оператор goto
- •Оператор break
- •Оператор continue
- •Оператор return
- •Инициализация указателей
- •Операции с указателями
- •Переименование типов (typedef)
- •Глобальные переменные
- •Возвращаемое значение
- •Передача массивов в качестве параметров
- •Передача имен функций в качестве параметров
- •Параметры со значениями по умолчанию
- •Функции ввода/вывода
- •Открытие потока
- •Ввод/вывод в поток
- •Закрытие потока
- •Функции работы со строками и символами
- •Математические функции
- •Директива #include
- •Директива #define
Инициализация указателей
Указатели чаще всего используют при работе с динамической памятью. Доступ к выделенным участкам динамической памяти, называемым динамическимипеременными, производится только через указатели.
Время жизни динамических переменных — от точки создания до конца программы или до явного освобождения памяти.
В С++ используется два способа работы с динамической памятью. Первый использует семейство функций malloc и достался в наследство от С, второй использует операции new и delete.
Существуют следующие способы инициализации указателя:
1. Присваивание указателю адреса существующего объекта:
с помощью операции получения адреса:
int a = 5;//целая переменная
int* p = &a;//в указатель записывается адрес a
int* p (&a);//то же самое другим способом
значения другого инициализированного указателя:
int* r = p;
имени массива или функции, которые трактуются как адрес:
int b[10];//массив
int* t = b;// Присваивание имени массива
...
void f(int a ){ /* … */ }// Определение функции
void (*pf)(int);// Указатель на функцию
pf = f;// Присваивание имени функции
2. Присваивание указателю адреса области памяти в явном виде:
char* vp = (char *)0xB8000000;//шестнадцатиричная константа
3. Присваивание пустого значения:
int* suxx = NULL;
int* rulez = 0;
4. Выделение участка динамической памяти и присваивание ее адреса указателю:
с помощью операции new:
int* n = new int;// 1
int* m = new int (10);// 2
int* q = new int [10];// 3
с помощью функции malloc:
int* u = (int*)malloc(sizeof(int));// 4
Освобождение памяти, выделенной с помощью операции new, должно выполняться с помощью delete, а памяти, выделенной функцией malloc — посредством функции free. При этом переменная-указатель сохраняется и может инициализироваться повторно. Приведенные выше динамические переменные уничтожаются следующим образом:
delete n; delete m; delete [] q; free (u);
ВНИМАНИЕ
Если переменная-указатель выходит из области своего действия, отведенная под нее память освобождается. При этом память из-под самой динамической переменной не освобождается.
Операции с указателями
С указателями можно выполнять следующие операции: разадресация (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (– –), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).
Операция разадресации, или разыменования, предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины (если она не объявлена как константа):
char a;//переменная типа char
char * p = new char;/*выделение памяти под указатель и под динамическую переменную типа char */
*p = 'Ю'; a = *p;//присваивание значения обеим переменным
На одну и ту же область памяти может ссылаться несколько указателей различного типа. Примененная к ним операция разадресации даст разные результаты. Например, программа
#include <stdio.h>
int main()
{unsigned long int A=0Xсс77ffaa;
unsigned int* pint =(unsigned int *) &A;
unsigned char* pchar =(unsigned char *) &A;
printf(" | %x | %x |", *pint, *pchar);}
на IBM PC выведет на экран строку:
| ffaa | aa |
В примере при инициализации указателей были использованы операции приведения типов. Синтаксис операции явного приведения типа прост: перед именем переменной в скобках указывается тип, к которому ее требуется преобразовать.
При смешивании в выражении указателей разных типов явное преобразование типов требуется для всех указателей, кроме void*. Указатель может неявно преобразовываться в значение типа bool.
Присваивание без явного приведения типов допускается только указателям типа void* или если тип указателей справа и слева от операции присваивания один и тот же.
Присваивание указателей данных указателям функций (и наоборот) недопустимо.
Арифметические операции с указателями (сложение, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.
Инкремент перемещает указатель к следующему элементу массива, декремент — к предыдущему.
Фактически значение указателя изменяется на величину sizeof(тип).
Разность двух указателей — это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.
При записи выражений с указателями следует обращать внимание на приоритеты операций. В качестве примера рассмотрим последовательность действий, заданную в операторе
*p++ = 10;
То же самое можно записать подробнее:
*p = 10; p++;
Выражение (*p)++, напротив, инкрементирует значение, на которое ссылается указатель.
Унарная операция получения адреса & применима к величинам, имеющим имя и размещенным в оперативной памяти. Нельзя получить адрес скалярного выражения, неименованной константы или регистровой переменной.
Массивы
При использовании простых переменных каждой области памяти для хранения данных соответствует свое имя. Если с группой величин одинакового типа требуется выполнять однообразные действия, им дают одно имя, а различают по порядковому номеру.
Конечная именованная последовательность однотипных величин называется массивом. Описание массива в программе отличается от описания простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размерность):
float a [10]; //Пример описания массива из 10 вещественных чисел
Элементы массива нумеруются с нуля. Инициализирующие значения для массивов записываются в фигурных скобках.:
int b[5] = {3, 2, 1};// b[0]=3, b[1]=2, b[2]=1, b[3]=0, b[4]=0
Размерность массива может быть задана только целой положительной константой или константным выражением.
Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.
Пример #1. |
#include <iostream.h> int main(){ const int n = 10; int marks[n] = {3, 4, 5, 4, 4}; for (int i = 0, sum = 0; i<n; i++) sum += marks[i]; cout << "Сумма элементов: " << sum;} |
Размерность массивов предпочтительнее задавать с помощью типизированных констант.
Динамические массивы создают с помощью операции new, при этом необходимо указать тип и размерность, например:
float *p = new float [100];
В этой строке создается переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес ее начала записывается в указатель p. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.
Преимущество динамических массивов состоит в том, что их размерность может не быть константой.
Память, зарезервированная под динамический массив с помощью new [], должна освобождаться оператором delete [], а память, выделенная функцией malloc — посредством функции free, например:
delete [] p; free (q);
Размерность массива не указывается, но квадратные скобки обязательны.
Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор
int matr [6][8];
задает описание двумерного массива из 6 строк и 8 столбцов. В памяти такой массив располагается в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее всего изменяется последний индекс.
Для доступа к элементу многомерного массива указываются все его индексы, например, matr[i][j].
Инициализация многомерного массива:
int mass2 [][]={ {1, 1}, {0, 2}, {1, 0} };
int mass2 [3][2]={1, 1, 0, 2, 1, 0};
Для создания многомерного массива в динамической памяти необходимо указать все его размерности (первая из них может быть переменной):
matr = new int [a] [b];
Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete []. Указатель на константу удалить нельзя.
Пример #2. |
Программа определяет в целочисленной матрице номер строки, которая содержит наибольшее количество элементов, равных нулю. #include <stdio.h> int main(){ const int nstr = 4, nstb = 5;//Размерности массива int b[nstr][nstb]; int i, j; for (i = 0; i<nstr; i++)//Ввод массива for (j = 0; j<nstb; j++) scanf("%d", &b[i][j]); int istr = -1, MaxKol = 0; for (i=0; i<nstr; i++){//Просмотр массива по строкам int Kol = 0ж for (j = 0; j<nstb; j++)if (b[i][j]==0)Kol++; if (Kol>MaxKol){istr = i; MaxKol = Kol;} } printf(" Исходный массив:\n"); for (i = 0; i<nstr; i++){ for (j = 0; j<nstb; j++)printf("%d ", b[i][j]); printf("\n");} if (-1 == istr)printf("Нулевых элементов нет"); else printf("Номер строки: %d", istr); return 0;} |
Строки
Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ — это символ с кодом, равным 0, что записывается в виде управляющей последовательности '\0'. По положению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литералом:
char str[10] = "Vasia";
В этом примере под строку выделяется 10 байт, 5 из которых занято под символы строки, а шестой — под нуль-символ. Если строка при определении инициализируется, ее размерность можно опускать (компилятор сам выделит соответствующее количество байт):
char str[] = "Vasia";//Выделено и заполнено 6 байт
Оператор
char *str = "Vasia"
создает не строковую переменную, а указатель на строковую константу.
Операция присваивания одной строки другой не определена и может выполняться с помощью цикла или функций стандартной библиотеки.
Библиотека предоставляет возможности копирования, сравнения, объединения строк, поиска подстроки, определения длины строки и т.д. (возможности библиотеки описаны в разделе «Функции работы со строками и символами»), а также содержит специальные функции ввода строк и отдельных символов с клавиатуры и из файла.
Пример #3. |
Пример (программа запрашивает пароль не более трех раз). #include <stdio.h> #include <string.h> int main(){ char s[5], passw[] = "kuku";/*passw – эталонный пароль. Можно описать как *passw = "kuku";*/ int i, k = 0; for (i = 0; !k && i<3; i++){ printf("\nвведите пароль:\n"); gets(s);//функция ввода строки if (strstr(s,passw))k = 1;// функция сравнения строк } if (k) printf("\nпароль принят"); else printf("\nпароль не принят");} |
При работе со строками часто используются указатели.
Пример #4. |
Рассмотрим процесс копирования строки srcв строку dest. #include <iostream.h> int main(){ char *src = new char [10]; char *dest = new char [10], *d = dest; cin << src; while ( *d++ = *src++); cout << dest;} |
Типы данных, определяемые пользователем
В реальных задачах информация, которую требуется обрабатывать, может иметь достаточно сложную структуру. Для ее адекватного представления используются типы данных, построенные на основе простых типов данных, массивов и указателей.
Язык С++ позволяет программисту определять свои типы данных и правила работы с ними.