
- •Аннотация
- •Предисловие
- •Г л а в а 1. Введение в указатели
- •§1. Понятие указателя. Операции разыменования и разадресации
- •§2. Инициализация и присваивание указателей
- •§3. Передача параметров функций с помощью указателей.
- •§4. Распределение динамической памяти.
- •4.1. Операция new
- •4.2. Операция delete
- •Упражнения, тесты.
- •Указатели и массивы
- •§1. Связь указателей и массивов
- •1.1. Указатели и одномерные массивы
- •1.2. Указатели и матрицы
- •Int MyFun (int *X, int n)
- •§3. Операции над указателями при работе с массивами.
- •3.1. Арифметические операции
- •3.2. Операции сравнения.
- •§4. Использование операций над указателями при работе с одномерными массивами
- •4.1. Использование индексов
- •4.2. Указатель в качестве параметра цикла
- •4.3. Использование указателя и индекса
- •§5. Строки.
- •5.1. Общая характеристика строк.
- •5.2. Примеры алгоритмов работы со строками.
- •5.3. Анализ строковых функций.
- •§6. Использование операций над указателями при работе со статической матрицей.
- •Упражнения, тесты.
- •Массивы указателей
- •§1. Статический массив указателей
- •§2. Частично динамическая матрица.
- •Int arr_of_size[n];
- •§3. Статический массив строк
- •§4. Динамический массив указателей
- •4.1. Указатель на указатель
- •4.2. Динамические “матрицы”.
- •Int *arr_of_size;
- •4.3. Передача матрицы в функцию
- •Int a[10]; FunArr1(a, 10,…);
- •Упражнения, тесты.
- •Задачи второго среднего уровня.
- •Структуры и другие типы, определяемые пользователем
- •§1. Структуры
- •Объявление структуры
- •1.2. Работа со структурой.
- •1.3. Вложенные структуры и статические массивы в структурах
- •1.4. Статический массив структур
- •§2. Cтруктуры и указатели
- •2.1. Указатели в структуре.
- •2.2. Указатели на структуру
- •2.3. Динамический массив структур
- •2.4. Ссылка на структуру.
- •2.5. Указатели и вложенные структуры
- •§3. Cтруктуры и функции
- •3.1. Передача полей структуры в функцию.
- •Void MyFun1 (int X, float &y, int *u1, float *u2, char *s);
- •3.2. Передача всей структуры в функцию
- •Void Fun1 (tst s,…);
- •Void Fun2 (tst & s,…);
- •Void Fun3 (tst* s,…);
- •§4. Cтруктуры и классы.
- •§5. Объединения.
- •Представление вещественных чисел в памяти компьютера.
- •§6. Поля битов (битовые поля)
- •Ввод в ы в о д
- •Symbol Code16 Code10 Code2
- •§7. Перечисления
- •Какие из строк (//1 – //9) правильные?
§3. Операции над указателями при работе с массивами.
В главе 1 были рассмотрены следующие операции, связанные с указателями: операция разыменования или доступ по адресу (*), разадресация или взятие адреса (&), присваивание указазателю адреса другой простой переменной или значения другого указателя, операции new и delete. Приведенные в этом параграфе операции имеет смысл использовать при работе с массивами, как одномерными, так и двумерными.
3.1. Арифметические операции
Из арифметических операций к указателям могут применяться только сложение и вычитание, которые имеют ряд особенностей и ограничений.
Пусть объявлен массив и указатель, в котором хранится адрес начала массива: const n=10; float a[n]; float * p=a, *q; unsigned short k=3;
В результате операции сложения адреса с целым числом ( p+k) для целого положительного к получается адрес ячейки, которая находится на k*sizeof(тип) байт “правее” ячейки с адресом p, где тип — тип элементов массива (здесь float). Другими словами, при использовании указателей для работы с массивами используется другая, отличная от известной с детского сада арифметика. Значение переменной указателя увеличивается не на k, как для обычных переменных, а на k*sizeof(тип). То есть записанное нами выражение система корректирует. Например, пусть p содержит значение адреса, условно равное 5000. Тогда в результате операции p+3 получим не 5003, а 5000+3*4=5012, где 4 =sizeof(float)— количество байт, занимаемое одним вещественным числом типа float. Другими словами, если в p был адрес начала массива (p=a), то p+3 — это адрес элемента a[3] (обозначается &a[3]), a *(p+3) — элемент массива a[3]. Если это изменённое значение адреса присвоить другой переменной, то есть выполнить q=p+3, то, как и для обычных переменных, адрес в p не изменится, а в q будет адрес элемента a[3]. Если выполнить p+=3, то значение переменной p изменяется и в ней будет &a[3], а доступ к a[3] можно осуществить с помощью операции разыменование. Поэтому cout<<(*p); выведет значение 3-го элемента массива (a[3]).
При к=1 получаем операцию инкремент для адреса (p++;), которая выполняется аналогично. В результате численное значение указателя увеличивается не на один байт, а на величину sizeof(тип) байт, где тип — тип элементов массива, а тип * — тип указателя. В нашем случае мы сместимся на 4 байта вправо к соседнему элементу массива с большим адресом . При организации цикла эта операция играет ту же роль, что и увеличесие индекса на единицу, например, i++;.
Определена и операция вычитание целого положительного числа из указателя, например, p-k. Числовое значение указателя уменьшается на величину k*sizeof(тип). Поэтому для нашего примера после выполнения p-=3; мы “сместимся” на 3 элемента влево, то есть адрес в переменной p уменьшится на 3*4 =12 байт. После операции декремент (p--;) указатель “перемещается” влево к соседнему элементу с меньшим адресом.
Операции p+k, p-k, p++, p-- теоретически разрешены не только для массивов. Изменяя значение указателя, можно перемещаться по участкам памяти и получать доступ к разным переменным. Но не следует надеяться, что они будут размещаться в памяти подряд в соответствии с размещением их описаний в тексте программы. Потому эти операции могут дать непредсказуемые результаты, если их использовать не для массива. Полную уверенность в последовательном размещении элементов можно получить, если используем массив.
Кроме вычитания целого числа из указателя можно вычитать один указатель из другого. Эта операция имеет смысл только тогда, когда оба указателя указывают на элементы одного и того же типа. Более того, эти элементы должны находиться в одном и том же массиве. В результате получаем количество элементов указанного типа (а не количество байт), находящихся между указателями. При этом может получиться как положительное, так и отрицательное значение. Пусть в p находится адрес начала массива (например, 5000), а в q — &a[3], то есть, 5012. Тогда cout<<(q-p); выведет не 12, а число 3 ((5012-5000)/4), а cout<<(p-q); выведет отрицательное число -3, так как p указывает на элемент с меньшим адресом.
В случае указателей на символы при работе со строками рассмотренные выше арифметические операции упрощаются, так как sizeof(char)=1, и мы имеем дело с обычной арифметикой. В этом случае, например, операция p+k “перемещает нас” на k байт, или на k символов вправо, если k>0, и влево, если k<0.
Другие арифметические операции к указателям неприменимы. Нельзя умножать, делить и складывать указатели, нельзя умножать и делить их не только на вещественное, но и на целое число. Нельзя применять битовый сдвиг и другие битавые операции к указателям, нельзя к указателю добавлять или вычитать из указателей вещественные значения.