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

Лабы 1 курс 2 семестр / ЛР 5 Информатика 2 сем 2020

.pdf
Скачиваний:
11
Добавлен:
15.01.2021
Размер:
930.97 Кб
Скачать

6Лабораторная работа №5. Массивы и указатели

6.1Цели и задачи работы:

Цель данной лабораторной работы — научиться работать с одномерными и многомерными массивами.

Задача данной лабораторной работы — выполнить выданные типовые задания по обработке массива данных.

6.2Теоретическая часть

Когда компилятор обрабатывает объявление переменной в программном коде, он выделяет память в соответствии с еѐ типом. Все обращения в программе к переменной по ее имени он заменяет адресом области памяти, в которой хранится значение переменной. При необходимости работать в программном коде с адресами памяти, а не с конкретными значениями, используются указатели - переменные специальных типов, предназначенных для хранения адресов областей памяти.

type *pA; // указатель на переменную const type *pB; // указатель на константу type * const pC; // константный указатель на переменную

const type * const pD; // константный указатель на константу

Здесь «type» - тип переменной, на которую указывает указатель. Модификатор «const», находящийся между именем указателя и «звездочкой», относится к самому указателю и запрещает его изменение, а «const» слева от «звездочки» задает постоянство значения, на которое он указывает.

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

Чтобы узнать адрес переменной в памяти (для последующей инициализации указателя) нужно воспользоваться оператором получения адреса «&»:

80

int A = 5; // переменная

int *p = &A; // указатель на неё

A

 

p

5

address

address

Чтобы получить или изменить значение, хранимое в оперативной памяти, на которое указывает указатель, нужно воспользоваться оператором «*» (не путать с оператором умножения «*»):

int B = *p;

A

p

 

B

5

address

5

5

*p = 8;

A

p

 

 

8

address

8

8

Для того, чтобы выделить в оперативной памяти место для хранения не одного, а сразу нескольких (многих) однотипных значений используют массивы. В языках «C» и «C++» массив – упорядоченный набор элементов одного типа. Для того чтобы объявить статический массив (массив, под который размер места в оперативной памяти определяется на этапе компиляции программы) и проинициализировать его, нужно воспользоваться инструкцией следующего вида:

type mas[size] = {var1, var2, var 3, … };

81

переменная

массив

a

mas

size

Здесь type - тип элементов массива, mas – имя массива, size - его размер (должен быть задан литералом или константой), var1, var2, var 3 и т.д. - значения элементов массива. Если созданный массив не проинициализировать, в его элементах будет «мусор» (так же, как и в переменной).

Узнать размер статического массива в байтах, как и размер переменной, можно при помощи оператора sizeof(), в круглых скобках которого указывается имя массива. А разделив размер массива на размер одного элемента можно узнать количество элементов в массиве.

Имя статического массива по своей сути является константным указателем на первый его элемент:

float mas[5] = {1.1, 2.2, 3.3, 4.4, 5.5}; float f = *mas; // f == 1.1

mas

1.1 2.2 3.3 4.4 5.5

f

1.1= 1.1

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

Для динамического выделения памяти в языке «C» используется функция malloc(), описанная в файле stdlib.h. Эта функция имеет один аргумент размер выделяемой памяти в байтах. Возвращает функция указатель типа void * на выделенный блок памяти. Если память требуемого размера не может быть выделена, функция malloc возвращает «нулевой указатель», в языке «C» - это значение 0. При работе с указателями

82

(сравнении их с нулѐм) в языке «C» принято вместо «0» писать NULL (#define NULL 0).

Освобождается выделенная память при помощи функции free(), в которую передаѐтся указатель на ранее выделенный блок памяти.

#include <stdlib.h>

...

void main()

{

//Выделение памяти для хранения 100 значений типа int int *p = (int*) malloc(100 * sizeof(int));

if(p == NULL)

{

//Здесь можно обработать ошибку выделения памяти

}

...

// Освобождение памяти free(p);

p = NULL;

...

}

...

Так же в языке «C» для выделения памяти для массива можно использовать функцию calloc(). Первый еѐ аргумент – количество элементов массива, второй – размер одного элемента в байтах. Как и malloc(), calloc() возвращает указатель типа void * на выделенный блок памяти (NULL, если память требуемого размера не может быть выделена) .

// Выделение памяти для хранения 100 значений типа int int *p = (int*) calloc(100, sizeof(int));

Для изменения размера ранее выделенного участка памяти в языке «C» можно использовать функцию realloc(). Еѐ аргументы указатель на ранее выделенный блок памяти и новый размер блока памяти в байтах.

// Увеличение размера блока ранее выделенной памяти realloc(p, 200 * sizeof(int));

83

Функция realloc может, как использовать ранее выделенный участок памяти, так и создать новый независимо от того, увеличивается или уменьшается размер блока памяти.

Для динамического выделения памяти в языке «C++» используется оператор new. Оператор new имеет две формы. Первая форма выделяет место в оперативной памяти под хранение одного значения определенного типа, например:

int *p = new int(1024);

Здесь оператор new выделяет место в памяти под хранение значения типа int, записывает туда значение 1024 и возвращает адрес выделенного места. Этот адрес используется для инициализации указателя p. Дальнейшие действия над выделенным местом производятся с помощью указателя. Вторая форма оператора new выделяет память под массив заданного размера, состоящий из элементов определенного типа:

int *pa = new int[4];

В этом примере память выделяется под массив из четырех элементов типа int. В переменную-указатель pa будет записан адрес первого элемента массива.

Если память выделить не удалось, оператор new сгенерирует исключение std::bad_alloc.

unsigned int i = 1000;

try

{

while(true) // Бесконечный цикл для примера, так лучше не писать

{

// На каждой итерации пытаемся выделить в 10 раз больше памяти, чем на предыдущей

int *pa = new int[i];

std::cout<<"Доступно " << i * sizeof(int) << " байт памяти\n";

delete[] pa; i *= 10;

}

}

catch(std::bad_alloc)

{

84

std::cout<<"Не удалось выделить " << i * sizeof(int) << " байт памяти\n";

}

Если нужно, что бы new не генерировал исключение, после него можно записать «(std::nothrow)», тогда оператор new вернѐт нулевой указатель. В языке «C++» нулевой указатель обозначается ключевым словом nullptr.

unsigned int i = 1000;

while(true) // Бесконечный цикл для примера, так лучше не писать

{

// На каждой итерации пытаемся выделить в 10 раз больше памяти, чем на предыдущей

int *pa = new (std::nothrow) int[i]; if(pa != nullptr)

{

std::cout<<"Доступно " << i * sizeof(int) << " байт памяти\n";

delete[] pa; i *= 10;

}

else

{

std::cout<<"Не удалось выделить " << i * sizeof(int) << " байт памяти\n";

break;

}

}

Когда динамически выделенное место в оперативной памяти больше не нужно, еѐ следует явным образом освободить. Это делается с помощью оператора delete, имеющего, как и new, две формы – для единичного объекта

идля массива:

//освобождение памяти под переменную (объект) delete p;

//освобождение памяти под массив

delete[] p;

Если выделенная память не будет освобождена, произойдѐт «утечка памяти». Память будет расходоваться впустую – ни система, ни программа еѐ использовать не будут.

85

При попытке освобождения памяти по нулевому указателю ничего не произойдѐт.

Доступ к элементам массива можно получить разными способами. Что бы получить указатель на k-тый по счѐту элемент массива, нужно добавить k - 1 к указателю на начало массива, например:

float *pf = mas + 2; f = * pf; // f == 3.3

 

 

 

 

mas 1

2

3

4

 

 

 

2

1.1

2.2

3.3

4.4

5.5

 

 

 

 

f

 

 

 

+

 

 

 

 

 

 

 

 

 

 

 

 

3.3

3.3

*

 

address

 

 

 

 

Аналогичным образом к указателям можно применять оператор «-», а так же операторы ++ и --. Так же к указателям можно применять операторы сравнения. Например:

// вывод элементов массива в обратном порядке

pf = mas + 4; // указатель на последний элемент массива do

{

std::cout << *pf--; // помним, что у «--» приоритет

//выше, чем у «*», впрочем, в данном случае

//это ни на что не влияет

}while(pf != mas)

Для доступа к элементам массива можно также использовать оператор «[]» получения значения (точнее ссылки) по индексу. Предполагается, что элементы массива индексируются (нумеруются) целыми числами, начиная с 0. Последний элемент массива, соответственно, имеет индекс «размер массива – 1».

// вывод элементов массива в обратном порядке for(unsigned int index = 4; index >= 0; index--)

{

std::cout << massiv[index] << " ";

86

}

Строковый литерал – строка символов, заключенная в двойные кавычки. Такой литерал может занимать и несколько строк, в этом случае в конце строки ставится обратная косая черта. Специальные символы могут быть представлены своими escape-последовательностями. Вот примеры строковых литералов:

"" (пустая строка) "a"

"a multi-line \

string literal signals its \ continuation with a backslash"

Фактически строковый литерал представляет собой массив символьных констант, где по соглашению языков «C/C++» последним элементом всегда является специальный символ с кодом 0 ('\0'). Если литерал 'A' задает единственный символ 'А', то строковый литерал "А" – массив из двух элементов: 'А' и '\0' (пустого символа).

Следует помнить, что при считывании строки с консоли с использованием std::sin>>, она разбивается на отдельные слова по пробелам. Т.е. при попытке считать таким образом строку «slovo_1 slovo_2» прочитается только «slovo_1», после которого будет стоять '\0'. Для прочтения следующего слова нужно повторно использовать std::sin>>.

Альтернативный вариант использовать std::cin.getline(massiv, quantity),

где massiv – имя символьного массива, в который нужно записать считанную строку, а quantity – количество символов, которые нужно прочитать. Например, при чтении из консоли строки «slovo_1 slovo_2», используя std::cin.getline(mass, 12), в массиве mass окажется «slovo_1 slo», после которого будет стоять '\0'.

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

// статичекое выделение памяти int A[ 4 ][ 3 ];

или

//динамичекое выделение памяти

//(запись int** означает указатель на указатель на

//значение типа int)

int** A = new int*[4];

87

for(unsigned int i = 0; i < 4; i++)

{

A[i] = new int[3];

}

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

A[0][1] = 5; int a = A[3][2];

Так может выглядеть инициализация нулями двумерного массива с помощью вложенных циклов:

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

{

for ( int j = 0; j < 3; ++j )

{

A[ i ][ j ] = 0;

}

}

После завершения работы с динамическими массивами, нужно освободить выделенную память

for(unsigned int i = 0; i < 4; i++)

{

delete[] A[i];

}

delete[] A;

6.3Примеры решения задач

Задача А Вывести на экран введенную строку (не более 100 символов), предварительно удалив символы ‘*’ и удвоив ‘A’ и ‘a’.

Блок-схема алгоритма решения задачи будет включать цикл для последовательного перебора символов строки. Если очередной символ не является ‘*’ – выводим его на экран. Если этот символ - ‘A’ и ‘a’ – выводим его на экран ещѐ раз.

88

 

 

 

Начало

 

 

Символьный массив str из 101 элемента

 

 

 

Ввод str

 

 

Целое неотрицательное число i = 0

 

 

да

нет

 

 

 

str[i] != '\0'

 

да

нет

 

 

 

str[i] != '*'

 

 

Вывод str[i]

 

Конец

да

str[i] == 'A' ||

нет

 

 

 

 

 

str[i] == 'a'

 

 

Вывод str[i]

i++

Составим программу на основе блок-схемы

#include <iostream> #include <stdlib.h>

void main()

{

char str[101];

std::cout<<"input string: "; std::cin.getline(str, 101); std::cout<<"\n\n";

for(unsigned int i=0; str[i] != '\0'; i++)

{

if(str[i] != '*')

{

89