
- •Аннотация
- •Предисловие
- •Г л а в а 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) правильные?
Массивы указателей
Как и для простых типов данных, можно создавать как статический массив указателей, размерность которого фиксируется в виде константы и не меняется в процессе выполнения функции или всей программы, так и динамический. Такие массивы указателей, их использование и основанные на них массивы строк, полудинамические и динамические матрицы рассматриваются в этой главе
§1. Статический массив указателей
Статический массив указателей, например, на объекты типа int размерности 5, объявляется следующим образом:
const n=5; int * ap[n];
Память для такого массива выделяется на этапе компиляции. При таком объявлении массив, который называется ap, состоит из n элементов, каждый из которых имеет тип int *. Другими словами, ap[i], где i=0, 1, 2, …, n-1, представляет собой не целое обрабатываемое в программе число (оценку студента, план выпуска продукции и т. п.), а адрес ячейки оперативной памяти, в которой будет храниться целое число. Можно использовать операцию разыменования “*”. Содержимое ячейки памяти, адрес которой находится в ap[i], обозначается в программе *ap[i].
По аналогии с обычными указателями, i-й элемент объявленного таким образом массива указателей ap[i] можно проинициальзировать одним из следующих способов:
1) с помощью адреса предварительно объявленной простой целочисленной переменной. Например,
int v; ap[i]=&v;
2) с помощью объявленного указателя. Например,
int * p; p= new int; ap[i]=p;
3) непосредственно с помощью операции new. Например,
a) … ap[i]=new int; резервируем память для размещения одного целого числа и его адрес засылаем в i-й элемент массива указателей. Значение i должно быть определено.
б) резервируем память для размещения массива из m целых чисел и адрес его начала засылаем в i-й элемент массива указателей
unsigned m; cin>>m;…ap[i]=new int[m];
В качестве m можно использовать как переменную (см. в примере), так и константу. Переменная i может принимать значение от 0 до n-1;
4) для инициализации элемента массива ap можно использовать адрес предварительно объявленного одномерного массива фиксированной размерности:
const m=10; int a[m]; ap[i]=a;
где i по-прежнему может принимать значение от 0 до n-1 (а не до m-1).
Напомним, что при таком объявлении a — адрес начала одномерного массива. Поэтому последнее присваивание будет корректным.
Логическим продолжением последнего способа является использование массива указателей при работе со статической числовой матрицей. Пусть объявлена матрица
const п=5, m=10; int matr[n][m];
и надо переставить к1-ю строку с k2-й строкой.
Один из способов, при котором элементы двух строк физически переставляются в памяти, был показан в первом семестре. Так как matr[k1] и matr[k2] — адреса начала k1-й и k2-й строк, то возникает вопрос: как для такой перестановки строк ограничиться изменением нескольких адресов, не перемещая элементы в памяти? Объявим для этого ещё один указатель int *p. Казалось бы, можно изменить адреса следующим образом:
p=matr[k1]; matr[k1]=matr[k2]; matr[k2]=p; // ошибка
Но при компиляции второго и третьего присваиваний будут ошибки, так как matr[k1] и matr[k2] — константные указатели на начало строк, то есть их значения нельзя менять. Заметим, что если бы такая возможность разрешалась, то мы потеряли бы доступ к строкам матрицы.
Выход из этой ситуации следующий. Объявляем дополнительно массив указателей размерности n, где n — количество строк матрицы: int * ap[n]; Инициализируем этот массив:
for (int i=0; i<n; i++) ap[i]=matr[i];
Почему присваивание, записанное в цикле, будет корректным? С одной стороны, ap[i] — это указатель, так как ap объявлен не как массив целых чисел (int ap[n];) и не как один указатель (int * ap;) , а как массив указателей. Напомним, что с другой стороны, объявив статическую матрицу, мы этим самым объявили и константый массив указателей, то есть matr[i] — это адрес начала i-й строки матрицы. И тогда меняем не matr[k1] и matr[k2], а ap[k1] и ap[k2] аналогичным образом:
int *p; p=ap[k1]; ap[k1]=ap[k2]; ap[k2]=p;
Вместо m*3 присваиваний, которые необходимо было выполнить при физическом перемещении элементов строк, достаточно выполнить всего три операции присваивания для адресов. При необходимости такие “перестановки” строк можно выполнять несколько раз в цикле. Это имеет место, например, в алгоритмах сортировки. Понятно, что в этом случае эффективность такого метода возрастает.
После таких “перестановок” матрица matr не изменилась. В этом легко убедиться, если вывести её (matr[i][j]) на экран. Доступ к преобразованной матрице осуществляется с помощью идентификатора ap. Например, вывод изменённой матрицы можно выполнить так:
for(int i=0; i<n; i++)
{ cout<< endl;
for(int j=0; j<m; j++)
printf ( “%5d”, ap[i][j]);
}
Заметим, что несмотря на то, что, казалось бы, переменную ap не объявляли как двумерный массив (как матрицу), а как одномерный массив, но массив указателей, мы имеем возможность использовать эту переменную с двумя индексами (ap[i][j]).
Пример (+). Сортировка строк целочисленной матрицы по возрастанию некоторого параметра строки (например, по возрастанию максимальных элементов строк). Порядок чисел в каждой строке не меняется.
const m=4;
int main()
{ // 1) Статическую матрицу не создаём. Её достаточно объявить
const n=5; int matr[n][m];
// Cтатический массив указателей на строки матрицы
int*u[n];
// 2) Получаем матрицу…
for(int i=0;i<n;i++)
{ for(int j=0;j<m;j++)
matr [i][j]=random(15)-6;
/* … и формируем статический массив указателей на строки матрицы */
u[i]= matr [i];
}
/* 3) Получаем одномерный массив параметров s, каждый элемент которого —максимальное число в строке. Поэтому размерность этого массива совпадает с количеством строк матрицы */
int s[n],Mymax,Mymin,nmin;
for(int i=0;i<n;i++)
{ Mymax= matr [i][0];
for(int j=0;j<m;j++)
if (matr [i][j]>Mymax)
Mymax= matr [i][j];
s[i]=Mymax;
}
// 4) Вывод матрицы matr и вектора s
for(int i=0;i<n;i++)
{ cout<<endl;
for(int j=0;j<m;j++)
printf("%5d", matr [i][j]);
printf(" => %5d",s[i]);
}
cout<<endl;
// 5) Сортировка методом выбора минимального элемента
for(int start=0;start<=n-2;start++)
{
Mymin=s[start]; nmin=start;
for(int i=start;i<n;i++)
if(s[i]<Mymin)
{ Mymin=s[i];
nmin=i;
}
cout<< Mymin<< " "<<nmin<<endl;
// Комментарии в конце
int *p; p=u[start]; u[start]=u[nmin]; u[nmin]=p;
/* Переставляем элементы одномерного массива s с номерами start и nmin, не используя никакие указатели */
int t; t=s[start]; s[start]=s[nmin]; s[nmin]=t;
}
/* 6) Выводим рассортированную матрицу u (а не matr) и вектор s. */
for(int i=0;i<n;i++)
{ cout<<endl;
for(int j=0;j<m;j++)
printf("%5d",u[i][j]);
printf(" => %5d",s[i]);
}
cout<<endl;
/* 7)Убеждаемся, что матрица matr осталась не рассортированной! Матрицы u и matr занимают одну и туже оперативную память, но u[i] и matr [i] имеют, вообще говоря, разные адреса. */
for(int i=0;i<n;i++)
{ cout<<endl;
for(int j=0;j<m;j++)
printf("%5d",matr[i][j]);
}
getch(); return 0;
}
В первом семестре без использования указателей две строки матрицы c номерами start и nmin переставляли бы “физически” следующим образом:
int t1;
for(int j=0;j<m;j++)
{ t1= matr [start][j];
matr [start][j]= matr [nmin][j];
matr [nmin][j]=t1; }
Так как matr [i] – это константные адреса строк статической матрицы, их нельзя менять. Т. е. нельзя написать matr [start]= matr [nmin]. Поэтому переставляли полученные “новые” адреса строк матрицы с номерами start и nmin, то есть соответствующие элементы массива указателей u. Физически элементы этих строк остаются на старых местах, меняли только значения двух адресов в массиве u.