Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмизация и программирование. Учебное пособие для студентов направления подготовки 09.03.02 Информационные системы и технологии

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
1 Мб
Скачать

Глава 7.УКАЗАТЕЛИ И МАССИВЫ ДАННЫХ

§ 1. Указатели.

Переменная в программе – это объект, имеющий имя и значение. При обращении по имени к переменной можно получить её значение. Имя переменной соответствует адресу участка памяти, выделенного для переменной, и явно не связанно с адресом. Значение переменной соответствует содержимому участка памяти. Адресом участка оперативной памяти является номер байта, с которого начинается этот участок. Можно определить собственные переменные для хранения адресов участков памяти. Такие переменные называются указателями.

Указатель – это переменная, содержащая информацию о расположении в памяти другой переменной, т.е. адрес другой переменной.

Чтобы получить адрес в явном виде, необходимо использовать унарную операцию взятия адреса (&). Она применима к объектам, имеющим имя и размещенным в памяти. Применять операцию «&» к выражениям, константам, регистровым переменным или внешним объектам (файлам) нельзя. Значением указателя может быть и нулевой адрес. Для его обозначения определена специальная константа NULL.

Структура объявления указателя: <тип_данных> *<имя_указателя>;

Пример.

char *z; // объявление указателя на объект символьного типа; int *k,*i; // объявление указателей на объекты целого типа; float *f; // объявление указателя на объект вещественного типа.

Переменные z, k, i, f – указатели; *z, *k, *i, *f обозначает объекты типов char, int, float соответственно, на которые указывают z, k, i, f.

Например, оператор *z = ‘ ’ отправляет символ « » (пробел) в тот участок памяти, адрес которого определяет указатель z.

Символ «*» – знак унарной операции косвенной адресации (разыменования) раскрытия ссылки (обращения по адресу), результатом которой является объект, адресуемый указателем.

81

Указатель может ссылаться на объекты того типа, который присутствует в его объявлении. Исключением являются указатели типа void (тип значения отсутствует). Такие указатели могут ссылаться на объекты любого типа, но к ним нельзя применять операцию разыменования.

Пример. Объявление переменных: int a, x; // целые переменные

int *p; // указатель на другую целую переменную

Пример. Возможные операции присваивания объявленным переменным: a=2000;

p=&a; // указатель p содержит адрес переменной a.

В данном примере в переменной p не значение, а только адрес.

Без операции «&» использование указателя для косвенного обращения является недопустимым.

Пример. Использование косвенного обращения, предполагающего, что действие производится над переменной, адрес которой находится в данный момент в указателе:

x=x+*p; /* при косвенном обращении по указателю p берется значение переменной a, что эквивалентно: x=x+a */

§ 2. Массивы данных.

Массив представляет собой набор данных одного типа. Для объявления массива необходимо указать его тип, имя и размер. Доступ к элементам массива осуществляется при помощи индексов, являющихся порядковыми номерами элементов в последовательности. В оперативной памяти компьютера элементы массива размещаются последовательно в соответствии с ростом индекса. Начинается индексирование с нулевого элемента.

Если для получения доступа к элементам массива требуется один индекс, такой массив называют одномерным. Размер массива задаётся числом и записывается в квадратных скобках. Математический аналог одномерного массива – это вектор.

82

Структура объявления одномерного массива: <тип_элемента_массива><имя_массива>[<количество_элементов>];

Инициализация массива – присвоение начальных значений его элементам при объявлении. Массив можно инициализировать перечислением его значений или выражений, заключенных в фигурные скобки, при этом они отделяются запятыми.

Пример.

int b[ ] = {1, 2, 3, 4, 5}; float w[7] = {2};

Если в квадратных скобках не указано количество элементов массива, то длина массива определяется компилятором по количеству значений, перечисленных в фигурных скобках. Если размер массива задан, то указать значений больше, чем объявлено в квадратных скобках, нельзя, это приведет к синтаксической ошибке. Однако меньшее количество значений указать можно (необходимо определить хотя бы один элемент).

Пример. Копирование 10 элементов одного массива в другой можно организовать следующим образом:

int v1[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, v2[10]; // объявление переменных

// копирование элементов одного массива в другой for (int i=0; i<10; i++) // для i, начиная с 0 до 9,

v2[i]=v1[i]; /* копировать i-й элемент массива v1 в i-й элемент массива v2, после чего i увеличивается на 1 */

При обращении к элементу массива происходит обращение по его адресу, поэтому предыдущий фрагмент программы можно представить следующим образом, используя указатели:

83

Пример.

int v1[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, v2[10], *p1, *p2; // объявление переменных

p1=&v1[0]; // в указатель p1 заносится адрес первого элемента массива v1 p2=&v2[0]; // в p2 – адрес первого элемента массива v2

// копирование элементов одного массива в другой

for (int i=0; i<10; i++)

{

*p2=*p1;

p2++; /* увеличение адреса на единицу – переход к следующему элементу массива v2 */

p1++; // переход к следующему элементу массива v1

}

Элементами массива могут быть переменные любого типа.

Пример. Рассмотрим объявление переменных: int a[5]={2, 4, 8, 16, 32};

int *p[5]={a, a+1, a+2, a+3, a+4};

Первая строка задаёт одномерный массив, состоящий из пяти элементов – чисел 2, 4, 8, 16 и 32.

Вторая строка – это массив из указателей на элементы массива а.

Если для получения доступа к элементам массива требуется более одного индекса, такой массив называется многомерным.

В памяти элементы многомерного массива, как и в одномерном, расположены последовательно друг за другом (строка за строкой). Математический аналог многомерного массива – матрица.

Структура объявления двумерного массива: <тип_элементов_массива><имя_мас>[кол-во_строк] [кол-во_столб];

84

Пример.

int m[3][4]; /* объявление двумерного массива, содержащего 3 строки и 4 столбца */

Определить номер массива размера m на n можно по формуле:

" · [ + \ + 1

,

где i – номер строки (0 ≤i<n), j – номер столбца (0 ≤ j<m).

Над адресами и указателями можно производить некоторые действия. Этот процесс называют адресной арифметикой. Основными действиями над указателями являются:

сравнение (имеет смысл, только если оба указателя адресуют общий для них объект);

увеличение или уменьшение значения указателя на размер объекта, который он адресует;

Пример.

int x; // переменная целого типа

int *iter; // указатель на объект целого типа

iter = &x; // занесение в iter адрес переменной x

iter++; // увеличение адреса на некое количество байт (зависит от компьютера) iter--; // уменьшение адреса на некое количество байт (зависит от компьютера)

• сложение (или вычитание) производится по следующей схеме: (адрес в указателе) + (значение int выражения) *sizeof(<тип>),

где <тип> – это тип указателя.

Пример.

float x; // переменная вещественного типа int n = 3; // переменная целого типа

float *pr; // указатель

pr = &x; // занесение в pr адрес переменной x

pr = pr + n; // в pr записывается новое значение согласно формуле

85

индексация <указатель>[<индекс>] (не изменяет значения указателя, к которо-

му применяется).

Приведем примеры некоторых действий над указателями.

Пример.

 

p+i;

// установить указатель на i-ю переменную

*(p+i); /* получить значение i-й переменной; эквивалентно значе-

нию p[i] */

 

p++;

// установить указатель на следующую переменную

p--;

//установить указатель на предыдущую переменную

p+=i;

// переместить указатель на i переменных вперед

p-=i;

// переместить указатель на i переменных назад

*p++; /* получить значение переменной, на которую указывает p, затем указатель установить на следующую переменную */

*(--p); /* переместить указатель на предыдущую переменную, затем получить ее значение */

(*p)++; /* получить значение переменной, на которую указывает p, затем увеличить ее значение на 1 */

++(*p); /* получить значение переменной, на которую указывает p, увеличенное на 1 */

Указатели и массивы имеют много общего. Работа с областью памяти, как с обычным массивом, так и через указатель, одинакова. Например, для массива A[i] и указателя p транслятор интерпретирует следующие конструкции:

A[i] как *(p+i);

&A[i] как p+i;

A+i как &p[i] или &(*(A+i));

*(A+i) как p[i].

Различие между указателем и именем массива аналогично различию между переменной и константой: указатель – это ссылочная переменная, а имя массива – ссылочная константа, привязанная к конкретному адресу памяти.

86

§ 3. Линейный поиск и сортировка в массивах данных.

Задачи линейного поиска подразумевают нахождение некоторого элемента или нескольких элементов массива данных, которые удовлетворяют заданным условиям, при этом сортировка массива не производится.

Под сортировкой массива понимают такое преобразование исходного массива, при котором с помощью перестановки его элементов получают определенную упорядоченность по заданному признаку порядка.

Признаки порядка различают по соотношениям между элементами массива. Одномерный массив &["] из n элементов для любого " = %%%%%%%%%%1, $ − 1 называется упорядоченным:

по возрастанию элементов, если &[" − 1] < &["];

по убыванию элементов, если &[" − 1] > &["].

Обратите внимание, индексирование массива начинается с нулевого элемента, поэтому для прохождения по всему массиву необходимо указывать начальное значение " = 1, тогда " − 1 будет соответствовать элементу с индексом 0, а значение " = $ − 1 – номер последнего элемента массива (всего $ элементов: от нулевого до $ − 1).

В языке программирования С++ нет автоматического контроля выхода индекса за границу массива. Поэтому, если ссылка будет сделана на несуществующий элемент, это будет источником ошибок.

Рассмотрим два классических способа сортировки элементов массива, которые при реализации их алгоритма предполагают использование не менее двух вложенных циклов.

1. Пузырьковая сортировка (способ попарных перестановок).

В данном способе сортировки повторяется процесс многократных перестановок между элементами, которые находятся по соседству, но нарушают порядок. Как только упорядоченность массива достигается, процесс завершается.

Такое необычное название сортировка получила из-за поведения элементов при перестановках. Например, при сортировке по возрастанию после первой итерации (прохождению по внешнему циклу) элемент с наибольшим значением в конце вложенного цикла попадает в конец массива – «всплывает, как пузырек». На следующем прохождении внешнего цикла внутренний цикл достигает предпоследнего значения. И так продолжается до тех пор, пока массив не станет упорядоченным по возрастанию: на каждом проходе внешнего цикла следующий по размеру элемент «всплывает» в конце и добавляется к уже отсортированным, а проход внутреннего цикла уменьшается на одну итерацию.

Блок-схема реализации пузырьковой сортировки в случае упорядочивания по возрастанию показана на рис. 67.

87

 

i = 0, n-2, 1

 

 

j = 0, n-2-i, 1

 

+

A[j]<A[j+1]

 

 

x = A[j]

 

 

A[j] = A[j+1]

 

 

A[j+1] = x

 

 

Рис. 67 Блок-схема пузырьковой сортировки

2. Сортировка простого выбора минимума (или максимума).

Этот способ сортировки подразумевает перестановки каждого элемента с минимальным (или максимальным) среди следующих за ним элементов.

Блок-схема реализации сортировки простого выбора в случае упорядочивания по возрастанию показана на рис. 68.

§ 3. Генерация случайных чисел.

Для создания массива с произвольными значениями используют специальную программу, которая формирует псевдослучайное число. Это программа – генератор случайных чисел (ГСЧ).

Простейший ГСЧ работает по следующей схеме:

1)выбираем K – некоторое большое число и 4 – произвольное число из интервала [0,1];

2)обозначаем:

– дробная часть числа (4),– дробная часть числа ( ) и так далее.

3)Такое построение дает последовательность произвольных чисел 4, , , … .

88

i = 0, n-2, 1

x = A[i]

k = i

 

j = i+1, n-1, 1

 

+

A[j]<x

 

 

x = A[j]

 

 

k = j

 

 

 

 

+

A[k] = A[i]

A[i] = x

k ≠ i

Рис. 68 Блок-схема сортировки простого выбора

В языке программирования С++ генератор случайных чисел задается функцией rand из стандартной библиотеки:

i = rand ();

Функция rand генерирует целое число из интервала от 0 до RAND_MAX=32 767 (символическая константа, определенная в заголовочном файле <stdlib.h>). В качестве RAND_MAX выбрано максимальное положительное значение двухбайтового целого числа.

Если необходимо, например, сгенерировать целые числа в диапазоне от 0 до 9, то используется операция вычисления остатка (%) в сочетании с функцией rand.

89

i = rand () % 10; // генерирует случайное число от 0 до 9

Такой процесс называют масштабированием, а число 10 – масштабирующим коэффициентом. Рассмотрим пример использования ГСЧ.

Задача 20. Создайте массив с помощью ГСЧ и напечатайте его.

Математическая модель и описание алгоритма решения. Пользователю предлагается указать размер массива $, после чего программа генерирует элементы массива заданного размера.

Текст программы к задаче 20.

------------------------------------------------------------------------------------------------

#include<stdio.h>

#include<math.h>

int main ()

{

int A[100], n, i;

printf("\n Enter array size n = "); scanf("%d", &n);

printf("Array elements A: ", A[i]); for (i=0; i<n; i++)

{

A[i] = rand()%100-50;

printf(" %d,\ ", A[i]); /* вывод на экран */

}

return 0;

}

------------------------------------------------------------------------------------------------

В компиляторе языка С++ этот компьютерный код представлен на рис. 69.

90

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]