- •Часть 2
- •18 Сентября 2012 г., протокол № 1
- •Предисловие
- •Глава 6 простые типы данных § 1. Целый тип
- •1.1. Битовые операции
- •Использование битовых операций
- •1.3. Упаковка и распаковка информации
- •§ 2. Логический тип
- •§ 3. Символьный тип
- •§ 4. Вещественный тип
- •§ 5. Преобразование типов
- •Преобразование типов в выражениях
- •Преобразование типов при присваивании
- •Г л а в а 7 введение в указатели
- •§ 1. Понятие указателя. Операции разыменования и разадресации
- •§ 2. Инициализация и присваивание указателей
- •§ 3. Распределение динамической памяти
- •Операция new
- •Операция delete
- •§ 4. Параметры-указатели. Функция ввода scanf
- •Упражнения, тесты
- •Г л а в а 8 одномерные массивы, указатели и функции
- •§ 1. Связь указателей и одномерных массивов. Передача массива в качестве параметра функции
- •§ 2. Сортировка одномерных массивов
- •§ 3. Сортировка массива по параметру числа
- •§ 4. Сортировка массива выбором.
- •§ 5. Сортировка массива вставками
- •§ 6. Динамические одномерные массивы
- •6.1. Порядок работы с динамическим массивом
- •6.2. Работа с динамическим массивом в классе. Деструктор
- •§ 7. Введение в строки
- •Глава 9 мАтрицы
- •§ 1. Объявление, способы определения матриц
- •§ 2. Вывод матриц
- •§ 3. Типы алгоритмов
- •3.1. Построчная обработка
- •Обработка матрицы по столбцам
- •3.3. Обработка всей матрицы
- •3.4. Обработка части матрицы
- •Преобразование матрицы
- •Построение матриц
- •§ 4. Матрицы, указатели и функции
- •Упражнения и тесты
- •Обработка матрицы по столбцам.
- •Список реКоМендуемой литературы
- •Сборники задач по программированию
- •Оглавление
- •Методы программирования:
- •Лекции, примеры, тесты
- •Пособие для студентов механико-математического факультета
- •В двух частях
- •Часть 2
Упражнения, тесты
Пусть int t=10; int *p=&t; Какие из следующих операторов допустимы и что они означают?
1) cin>>p; 2) cin>>(*p); 3) cout<< p; 4) cout<<(*p); 5) p=1000; 6)*p=1000; 7) t*=*p; 8) t*=p; 9) t=*p; 10) t=p; 11) cout <<(*p*=2)?
2. Дан код:
void fun2 (int *x, int &y) { (*x)++; y--; } //1
void main() { int a=5, b=2; fun2 (&a, b); //2
cout<< a<< “ “<< b; getch() ; }
Что будет выведено?
Варианты ответов: 1) 6 1; 2) 5 1; 3) 6 2; 4) 5 2; 5) Ошибка в //1; 6) Ошибка в //2; 7) Ошибки в //1 и //2.
3. Дан код:
void fun3 (int x, int *y) { *y=x+10; } //1
void main() { int a=5, b=2; int* p=&b; /*2 */
fun3 (a,*p); /*3*/
cout<< a<< “ “<< b; getch() ; }
Что будет выведено?
Варианты ответов: 1) 5 15 ; 2) 15 2; 3) 5 2; 4) Ошибка в //1; 5) Ошибка в /*2*/; 6) Ошибки в /*3*/ .
4. Дан код:
void fun4 (int x, int *y) { * y=x*10; } //1
void main() { int a=5, *b=new int (2); //2
fun4 (????????); //3
cout<< a<< “ “<< (*b); getch() ; } //4
Как вызвать функцию в //3, чтобы вывести 5 50?
Варианты ответов:
1) fun3(a,b); 2) fun3(a, &b); 3) fun3(&a, b); 4) fun3(&a, &b); 5) fun3(a, *b); 6) fun3(*a, b); 7) fun3(*a, *b); 8) ошибки (указать номера строк).
5. Дан код:
void fun5(int &x, int *y) { ????????????? ; } //1
void main() { int a=5, *b=new int; //2
fun5 (a,b); //3
cout<<endl<<a<<" "<<(*b)<<endl; //4
getch(); }
Что записать в тексте функции в строке //1, чтобы вывести 5 15?
Варианты ответов:
1) y=x+10; 2) *y=*x+10; 3) *y=x+10; 4) y=*x+10;
5) ошибка в //2; 6) ошибка в //3; 7) ошибка в //4.
6. Дан код:
int *p1= new int = 11; int *p2; *p2= new int =11;
int *p3= new int[11]; int *p4= new int (11);
int *p5= new (int) 11; int *p6= new int :11;
Для каких указателей (p1 — p6) правильно резервируется память для одной целочисленной переменной и выполняется её инициализация?
Г л а в а 8 одномерные массивы, указатели и функции
В этой главе показано, как передать одномерный массив в функцию с помощью указателей, изучаются алгоритмы сортировки одномерных массивов, рассматривается порядок создания и работы с динамическим одномерным массивом, начинаем изучать строки как обычный статический массив символов.
§ 1. Связь указателей и одномерных массивов. Передача массива в качестве параметра функции
Рассмотренное понятие указателя усложняется ещё и тем, что для массивов указатели имеют некоторые особенности. Пусть мы объявили статический массив, то есть массив с фиксированной размерностью: const n=10; int A[n]; Этим самым на этапе компиляции выделяется непрерывный участок памяти из 4*n =40 байт для размещения n целых чисел. Кроме этого, без какого-либо дополнительного объявления, без использования символа “*” мы объявили и переменную-указатель с именем массива A, в которой находится адрес начала массива, т. е. номер первого байта элемента A[0]. Этот адрес можно в программе обозначить ещё и как &A[0].
Идентификатор массива определяется не просто как указатель, а как константный указатель на первый элемент массива. Это означает, что нельзя изменить адрес начала массива, то есть нельзя, например, переменной А присвоить значение другого адреса. Если int *q; то запись A=q;недопустима. Но обратное присваивание q=A; или q=&A[0]; разрешено. После такого присваивания элементы одного и того же массива можно обозначать не только A[i], но и q[i], где i = 0, 1, 2, …, n-1. Более того, переменной указателю q можно присвоить адрес любого, не обязательно первого с нулевым номером элемента массива. Например, после присваивания q=&A[5]; q[0] и A[5] — это одна и таже ячейка, один и тот же элемент массива с разными именами, q[1] — это A[6], и т. д., q[4] — это A[9].
Сказанное выше используется при передаче массива в качестве параметра функции. Независимо от того, является массив входным, выходным, или и тем и другим, то есть преобразуется, он передаётся одинаково в качестве параметра функции с помощью указателей по следующим правилам.
Заголовок функции можно записать по-разному. Например, функцию сортировки одномерного массива можно объявить двумя способами:
a) void MySort(int x[], int size); или b) void MySort(int *x, int size);
В заголовке функции указываем тип элементов массива, его имя и пустые квадратные скобки или указатель на массив, что одно и то же. Более того, в прототипе можем записать int x[], а в заголовке при определении этой же функции — int * x, или наоборот. Чаще всего в качестве параметра указывается и его размерность (size в примере). Этим самым мы подчёркиваем, что работаем с массивом произвольной размерности. Отметим, что из заголовка ещё не видно, что x — адрес массива, а не простой переменной.
В тексте функции, как обычно, доступ к элементам массива осуществляется с помощью индексов. Кроме этого, во второй части книги будет рассмотрен другой способ доступа к элементам массива, основанный на явном использовании указателей и операций для работы с ними.
Вызов такой функции выполняется одинаково и не зависит от вариантов прототипа (a) или b)). В вызывающей функции, например, в main, объявляем статический массив обычным образом const n=10; int A[n]; При вызове функции в качестве фактического параметра надо записать адрес начала массива, если обрабатываем его с самого начала. Поэтому в скобках в таком случае записываем только имя массива A или адрес его первого элемента &A[0], что одно и то же: MySort(A,n); или MySort(&A[0],n); Будет ошибка, если при вызове функции в качестве фактического параметра запишем, например, A[i], или A[n] (так записываем при объявлении массива), или A[] (так записываем при определении формального, а не фактического параметра). То есть следующие вызовы компилятор не пропустит:
MySort(A[i],n); — ошибка, так как A[i] при i=0, 1, ... , n-1— это число или элемент массива, а не адрес;
MySort(A[n],n); — ошибка, так как A[n] это не адрес и, более того, n превосходит наибольшее значение индекса, так как нумерация элементов массива с нуля;
MySort(A[],n); — неправильно;
MySort(*A, n); — тоже неправильно;
При использовании массива в качестве параметра функции фактически передаётся не сам массив, а указатель на него, или адрес массива, то есть номер первого байта первой ячейки массива (с индексом 0). Другими словами, int x[] (или int *x) в заголовке функции означает, что ячейка x предназначена для хранения адреса массива. При вызове функции из объявления int A[n] следует, что A, или &A[0], — это адрес начала массива из n элементов. Как и для простых переменных, этот адрес (но не сам массив) копируется в ячейку x. В результате получается, что в A и в x хранятся адреса одной и той же области оперативной памяти. Другими словами, массив в памяти не дублируется, а его элементы по-разному называются: A[0] в main — это та же ячейка, что и x[0] в MySort, A[1] — это та же ячейка, что и x[1], и так далее. Поэтому если в функции элементы массива с именем x изменим, то этим самым мы изменим и элементы массива A. Поэтому с помощью указателя можно не только передать массив в функцию, но и вернуть из неё вновь полученный или изменённый массив.
Никакого ссылочного типа для возврата массива из функции не требуется.
Благодаря указателям составленную функцию для работы с одномерным массивом можно без изменений использовать для обработки части массива. Например, для сортировки n/2 первых элементов массива ту же функцию вызываем так: MySort (A, n/2); или MySort (&A[0], n/2); Заметим, что если n нечётное (например, n=15), то рассортируем первые n/2=7 элементов массива, то есть меньше половины.
Пусть надо рассортировать массив не с самого начала, а, например, с его середины и до конца. Тогда в качестве фактического параметра при вызове такой функции указываем адрес элемента a[n/2], а количество обрабатываемых элементов уменьшаем: MySort (&a[n/2], n%2 ? n/2+1 : n/2); Если n — нечётное (n=15), то, начав с a[n/2] (a[7]), мы рассортируем n/2+1=8 элементов, то есть больше половины. При чётном n n%2=0, что соответствует false, и рассортируем n/2 элементов.
