
- •Арифметические операции с указателями и с указателями на массивы.
- •Ввод-вывод в символьные массивы.
- •Динамическая память. Указатели и массивы. Ссылочный тип.
- •Доступ к глобальным переменным, скрытым локальными переменными с тем же именем (оператор ::).
- •Доступ к элементам массива. Вычисление размера массива. Многомерные массивы.
- •Инициализация массивов по умолчанию. Явная инициализация массивов.
- •Использование new и delete на примере динамических массивов, стеков, очередей.
- •Указатели на структуру
- •Массивы структур
- •Совместное использование операторов if-else-if и switch
- •Указатели на массивы. Указатели на строки.
- •Использование указателя на символьную строку
- •Условный оператор ?. Оператор switch.
- •Формальные и фактические параметры. Массивы в качестве параметров. Аргумент типа void.
- •Способ передачи параметров в подпрограмму
- •Логические операции. Инкремент и декремент. Арифметические операции.
- •Объявление переменной массива
- •Объекты стандартного предопределенного потокового ввода-вывода cin, cout, cerr, clog.
- •Объявление переменных указателей. Простые операторы с указателями.
- •Оператор break. Оператор continue. Совместное использование операторов break и continue.
- •Оператор if. Оператор if-else. Вложенные операторы if-else. Оператор if-else-if.
- •If (условие_истинно) оператор; else оператор;
- •If (условие_истинно)
- •Операторы динамического распределения памяти (new, delete).
- •Операции динамического распределения памяти.
- •Определение переменных указателей. Инициализация указателей.
- •Передача значений параметров по умолчанию. Передача параметров по ссылке и ссылочные переменные.
- •Передача параметра по ссылке
- •Передача структур в функции. Создание массива структур.
- •Приоритет переменных с файловой и локальной областями действия. Операция уточнения области действия.
- •Циклы for. Циклы while. Циклы do-while. Разница между циклами.
- •Цикл while ("пока") с постусловием
- •Пространство имен. Операторы namespace и using. Пространство имен
- •Прототипы функций. Вызов функций по значению и вызов по ссылке. Область действия. Рекурсия.
Арифметические операции с указателями и с указателями на массивы.
Операции с указателями
С указателями можно выполнять следующие операции: разадресация (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (– –), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&). Операция разадресации, или разыменования, предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины (если она не объявлена как константа):
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* или если тип указателей справа и слева от операции присваивания один и тот же.
Присваивание указателей данных указателям функций (и наоборот) недопустимо.
Арифметические операции с указателями (сложение, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.
Указатели и массивы в языке Си++ тесно связаны. Имя массива можно использовать как указатель на его первый элемент, поэтому пример с массивом alpha можно записать так:
int main() { char alpha[] = "abcdefghijklmnopqrstuvwxyz"; char* p = alpha; char ch; while (ch = *p++) cout << ch << " = " << int (ch) << " = 0" << oct(ch) << '\n'; }
Можно также задать описание p следующим образом:
char* p = &alpha[0];
Эта эквивалентность широко используется при вызовах функций с параметром-массивом, который всегда передается как указатель на его первый элемент. Таким образом, в следующем примере в обоих вызовах strlen передается одно и то же значение:
void f() { extern "C" int strlen(const char*); // из <string.h> char v[] = "Annemarie"; char* p = v; strlen(p); strlen(v); }
Но в том и загвоэдка, что обойти это нельзя: не существует способа так описать функцию, чтобы при ее вызове массив v копировался ($$4.6.3).
Результат применения к указателям арифметических операций +, -, ++ или -- зависит от типа указуемых объектов. Если такая операция применяется к указателю p типа T*, то считается, что p указывает на массив объектов типа T. Тогда p+1 обозначает следующий элемент этого массива, а p-1 - предыдущий элемент. Отсюда следует, что значение (адрес) p+1 будет на sizeof(T) байтов больше, чем значение p. Поэтому в следующей программе
main() { char cv[10]; int iv[10]; char* pc = cv; int* pi = iv; cout << "char* " << long(pc+1)-long(pc) << '\n'; cout << "int* " << long(pi+1)-long(pi) << '\n'; }
с учетом того, что на машине автора (Maccintosh) символ занимает один байт, а целое - четыре байта, получим:
char* 1 int* 4
Перед вычитанием указатели были явной операцией преобразованы к типу long ($$3.2.5). Он использовался для преобразования вместо "очевидного" типа int, поскольку в некоторых реализациях языка С++ указатель может не поместиться в тип int (т.е. sizeof(int)<sizeof(char*)).
Вычитание указателей определено только в том случае, когда они оба указывают на один и тот же массив (хотя в языке нет возможностей гарантировать этот факт). Результат вычитания одного указателя из другого равен числу (целое) элементов массива, находящихся между этими указателями. Можно складывать с указателем или вычитать из него значение целого типа; в обоих случаях результатом будет указатель. Если получится значение, не являющееся указателем на элемент того же массива, на который был настроен исходный указатель (или указателем на следующий за массивом элемент), то результат использования такого значения неопределен. Приведем пример:
void f() { int v1[10]; int v2[10]; int i = &v1[5]-&v1[3]; // 2 i = &v1[5]-&v2[3]; // неопределенный результат int* p = v2+2; // p == &v2[2] p = v2-2; // *p неопределено }
Как правило, сложных арифметических операций с указателями не требуется и лучше всего их избегать. Следует сказать, что в большинстве реализаций языка С++ нет контроля над границами массивов. Описание массива не является самодостаточным, поскольку необязательно в нем будет храниться число элементов массива. Понятие массива в С является, по сути, понятием языка низкого уровня.