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

Uchebnoe_posobie_GLAVA_7m3

.pdf
Скачиваний:
14
Добавлен:
17.03.2015
Размер:
465.69 Кб
Скачать

ГЛАВА 7 . РАБОТА С МАССИВАМИ.

До этой главы использовались, в основном, так называемые простые типы данных.

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

Но очень часто перед программистом стоит задача обработать множество однотипных элементов данных. Эту возможность обрабатывать набор однотипных элементов как единое целое в ISO/ANSI C++ обеспечивают массивы.

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

К элементам массива можно обратиться по имени массива и индексу самого элемента. То есть массивы не являются простыми типами данных. У каждого элемента массива есть индекс. Индексы в языке С начинаются с 0 и заканчиваются на N-1, где N количество элементов в массиве.

Указатель на начало массива (имя массива).

 

mas

 

 

 

 

 

Элементы массива

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

20

 

 

-8

12

 

0

 

45

 

67

 

1

 

-70

 

12

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mas[0]

mas[1]

 

mas[2]

 

mas[3]

mas[4]

mas[5]

mas[6]

mas[7]

 

mas[8]

Индексы элементов

На рис. 4.1 показан массив. Имя mas состоит из девяти элементов, каждый из которых содержит отдельное значение типа int. Поскольку имеется девять элементов, значение индекса находится в пределах от 0 до 8. Чтобы сослаться на определенный элемент, пишется имя массива, за которым идет значение индекса определенного элемента, заключенное в квадратные скобки. Так, например, чтобы сослаться на третий элемент, нужно написать mas[2]. Если представлять индекс в виде смещения от первого элемента, то легко увидеть, что значение индекса четвертого элемента будет равно 3.

Общий объем памяти, необходимый для хранения каждого элемента, определяется его типом, и все элементы массива сохраняются в одном непрерывном блоке памяти. Объѐм памяти необходимой для хранения массива определяется произведением размера памяти одного элемента на

количество элементов массива.

По сути, объявление массива ничем не отличается от объявления переменных простого типа. В объявлении массива необходимо указать тип его элементов и имя массива. Единственным отличием является то, что рядом с именем массива нужно поставить количество его элементов. Например, для объявления массива изображенного на рис. Необходимо написать:

int mas[9] ;

Поскольку каждое значение типа int занимает 4 байта памяти, весь массив потребует 36 байт. Массивы могут иметь любой размер — количество элементов ограничено лишь объемом доступной памяти компьютера, на котором выполняется программа.

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

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

int mas[9] ={ 20,-8,12,0,45,67,1,-70,12};

Нельзя специфицировать больше значений, чем объявлено элементов в массиве, но меньше – указать можно. Если задано меньше значений, то они присваиваются последовательно элементам массива, начиная с первого – то есть элемента с индексом 0. Элементы массива, для которых не указаны начальные значения, инициализируются нулями. Это не то же самое, что происходит, если вообще не указать список инициализации. Без списка инициализации элементы массива получают значения «мусора».

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

int value[] = { 2, 3, 4 }; определяет массив из трех элементов с начальными значениями 2, 3 и 4.

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

Первый путь достаточно прост. В нѐм используется статически выделенный массив с чрезмерно большим количеством элементов. В этом случае, пользователь вводит переменную для действительного размера массива, различную для каждого запуска программы. Это переменная должно быть заведомо меньше, чем константа, стоящая в выделении памяти под массив. В проведенном примере она должна быть меньше 9. Если используемый массив будет больше, произойдет ошибка переполнения массива. При этой реализации очень часто много памяти выделенной под массив не используется. Поэтому такой путь не является оптимальным с точки зрения выделения оперативной памяти. И его используют, если заранее (на этапе разработки) известна размерность массива, или это мало изменяемое число.

Общий вид оператора выделения статической памяти под массив имеет вид:

тип_элементов имя_массива[константа_размер_массива];

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

память

в

процессе работы

В этом

случае создается

После

ввода переменной с

В этом случае выделение памяти под массив будет иметь вид:

тип_элементов *имя_массива; //определене переменной размер_массива

имя_массива=new тип_элементов[размер_массива];

При статическом выделении памяти область, занимаемая переменными программы, освобождается автоматически в момент завершения работы программы. Для динамически выделенной памяти необходимо использовать оператор освобождения памяти.

Оператор освобождения памяти из-под массива примет вид: delete[] имя_массива.

Операторы выделения и удаления памяти те же самые, что и операторы работы с указателями, добавляется только [].

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

Для работы с элементами массива используются массивы. Наиболее удобен для этой работы оператор for().

Обращение к элементу представляется в виде имя_массива[индекс_элемента].

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

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

к несуществующим данным, которые могут содержать значения других переменных, или какой-то мусор, или даже код самой программы. Если обращение к такому элементу произойдет в выражении, то получится какието произвольные данные, которые, участвуя в вычислениях, породят некорректные результаты. Если же программист попытается записать значение в элемент массива, указанный неправильным индексом, то перезапишутся любые данные, которые окажутся в этом месте памяти. Если это будет программный код, результат окажется катастрофическим. К счастью подобная ситуация в Microsoft Visual Studio 2008 отслеживается в процессе выполнения программы. Рассмотрим пример для данной ситуации:

//Ошибка переполнения массива

#include <iostream> #include "rus.h" using namespace std; int main()

{

int A[10];

//статическое выделение памяти под массив А, размерности 10 элементов типа int

int i; // переменная для индекса

for(i=1;i<=10;i++) // цикл для работы с элементами массива /* Ошибка при индексации — начинается с 1 и заканчивается 10,

таким образом первый элемент массива не обработывается, а при =10 происходит переполнение массива.

Типичная ошибка программиста ранее изучающего Пачкаль*/ cin>>A[i]; // ввод элемента массива

for(i=0;i<10;i++) // правильно написанный цикл

/*индекс элемента массива начинаеися с 0 и заканчивается 9 (9<10), изменяется с шагом 1 .До этого цикла выполнение программы не доходит*/

cout<<A[i]<<"\t"; // вывод на экран i-ого элемента массива через табуляцию

cout<<endl;

} // Конец программы

Результат работы программы

Если в этом окне нажать кнопку «Пропустить», то окно консольного приложения примет вид:

Первый элемент массива не был введѐн с клавиатуры, поэтому содержит в себе «мусор», а цифра 10 оказалась за границей массива и не распечатывается в правильном цикле. Окно с ошибкой относится к сообщениям Microsoft Visual Studio 2008, и если программа выполняется из файла с расширением .exe без запуска среды, то этого окна не будет. В этом случае использования неправильного значения индекса не выдается никаких предупреждений во время выполнения. Единственный способ предохраниться от такой ситуации – соответствующим образом писать код программы.

Рассмотрим программу, которая работает с двумя массивами, считает сумму элементов каждого и помещает ее в третий массив.

//Работа с массивами

#include <iostream> #include "rus.h" using namespace std; int main()

{

float *A,*B,Sum[2]={0.f,0.f};

/*для работы потребуются два указателя для динамичкских массивов и статический массив размерностью 2 для содержания суммы элементов массива А и В соответственно, массив для суммы инициализируется нулевыми значениями */

int An,Bn,i;

//переменные для размерностей первого и второго массивав соответственно и переменная для индекса

cout<<R("Введите размерности массива А и В\n"); cin>>An>>Bn; // ввод размерностей

A=new float[An]; // выделение памяти под массив А B=new float[Bn]; // выделение памяти под массив В

cout<<R("\n введите элементы первого массива\n"); for(i=0;i<An;i++) // цикл для работы с элементами массива

cin>>A[i]; // ввод элементов массива А cout<<R("\n введите элементы второго массива\n");

for(i=0;i<Bn;i++) // цикл для работы с элементами массива cin>>B[i]; // ввод элементов массива В

cout<<R("\n первый массив \n");

for(i=0;i<An;i++) // цикл для работы с элементами массива

{

cout<<A[i]<<"\t";//вывод элемента массива А на экран

Sum[0]+=A[i];

// К первому элементу массива Sum добавляется элемент массива А

}

/* после окончания цикла в первом элементе массива Sum находится сумма всех элементов массива А, при добавлении A[0] важно что бы в Sum[0] находился 0, так как иначе А[0] добавится к мусору или другому числу, следовательно сумма элементов массива А не получится.*/

cout<<R("\n второй массив \n");

for(i=0;i<Bn;i++) // цикл для работы с элементами массива

{

cout<<B[i]<<"\t";// ввод элемента массива В Sum[1]+=B[i]; // Работа с массивами

//Ко второму элементу массива Sum добавляется элемент массива

В

}

//Результат тот же что и выше

cout<<R("\n сумма для 1-го и 2-го массивов \n"); cout<<Sum[0]<<"\t\t"<<Sum[1]<<endl; // вывод сумм для

массивов

cout<<endl;

}// конец программы

Выдача результата

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

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

В функцию можно передавать массивы, но в этом случае массив не копируется, несмотря на то, что при этом по-прежнему применяется передача аргумента по значению. Имя массива преобразуется в указатель, и копия указателя на начало массива передается в функцию по значению. Это достаточно выгодно, потому что копирование больших массивов требует значительных затрат времени. Однако элементы массива могут быть изменены внутри функции, и потому массив — единственный тип, который не может быть передан по значению.

Рассмотрим предыдущий пример, написанный в формате функций:

// Работа с массивами

#include <iostream> #include "rus.h" using namespace std;

int write_mas(float*mas,int size); // функция печати на экран массива int read_mas(float*mas,int size); // функция ввода с клавиатуры массива

float sum_mas(float*mas,int size); // функция расчета суммы элементов массива

int write_mas(float*mas,int size) {float sum=0.f;

cout<<R("\n массив \n"); for(int i=0;i<size;i++)

cout<<mas[i]<<"\t"; cout<<endl;

return 1;

}

int read_mas(float*mas,int size)

{

cout<<R("\n ввод элементов массива \n"); for(int i=0;i<size;i++)

cin>>mas[i]; return 1;

}

float sum_mas(float*mas,int size) {float sum=0.f;

/*при добавлении mas[0] важно что бы в sum находился 0, так как иначе mas[0] добавится к мусору или другому числу, следовательно сумма элементов массива не получится.*/

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

{

sum+=mas[i];

// В переменную sum добавляется элемент массива mas

}

/*после окончания цикла в sum находится сумма всех элементов массива mas,*/

return sum;

}

int main()

{

float *A,*B,Sum[2]={0.f,0.f}; int An,Bn,i;

cout<<R(" Введите размерности массивов А и В\n"); cin>>An>>Bn;

A=new float[An]; B=new float[Bn];

read_mas(A,An); // ввод элементов массива А с клавиатуры read_mas(B,Bn); // ввод элементов массива В с клавиатуры write_mas(A,An); // вывод элементов массива А на экран write_mas(B,Bn); // вывод элементов массива В на экран

Sum[0]=sum_mas(A,An);

//В первый элемент массива Sum помещается сумма элементов массива А

Sum[1]=sum_mas(B,Bn);

//Во второй элемент массива Sum помещается сумма элементов массива В

cout<<R("\n сумма для 1-го и 2-го массивов \n"); write_mas(Sum,2);

cout<<endl;

}

Результат работы программы можно увидет ниже.

Внешне результат один и тот же, но код программы стал более

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

Для наглядности в прототипах и заголовках функций можно использовать не указатели (*), а [], пример заголовка уже рассмотренной функции может иметь вид:

int write_mas(float mas[],int size); // функция печати на экран массива

Разницы никакой, выигрыш только в восприятии программиста, так как при такой записи не возникает вопроса работает функция с указателем или массивом.

Работу с массивами можно рассматривать как работу с указателями и реализовать смещение по элементам массива использую не индексацию а смещение по указателю. В этом случае предыдущий пример примет вид:

// Работа с массивами через указатель

#include <iostream> using namespace std;

int write_mas(float*mas,int size); // функция печати на экран массива int read_mas(float*mas,int size); // функция ввода с клавиатуры массива

float sum_mas(float*mas,int size); // функция расчета суммы элементов массива

int write_mas(float*mas,int size) {float sum=0.f;

float *p; //неинициализированный указатель для «проход по массиву»

cout<<"\n массив \n";

for(p=mas;p<mas+size;p++) // цикл для работы с элементами массива

cout<<*p<<"\t"; // значения элементов это *р cout<<endl;

return 1;

}

int read_mas(float*mas,int size) {float *p;

cout<<"\n ввод элементов массива \n";

for(p=mas;p<mas+size;p++) // цикл для работы с элементами массива

cin>>*p; return 1;

}

float sum_mas(float*mas,int size) {float sum=0.f;

float *p;

for(p=mas;p<mas+size;p++) // цикл для работы с элементами массива

{

sum+=*p;

//В переменную sum добавляется элемент массива mas

}

/*после окончания цикла в sum находится сумма всех элементов массива mas,*/

return sum;

}

int main()

{

float *A,*B,Sum[2]={0.f,0.f}; setlocale(NULL,"Russian"); int An,Bn,i;

cout<<" Введите размерности массива А и В\n"; cin>>An>>Bn;

A=new float[An]; B=new float[Bn];

read_mas(A,An); read_mas(B,Bn); write_mas(A,An); write_mas(B,Bn); Sum[0]=sum_mas(A,An); Sum[1]=sum_mas(B,Bn);

cout<<"\n сумма для 1-го и 2-го массивов\n"; write_mas(Sum,2);

cout<<endl;

}

Результат работа программы полностью совпадает с предыдущим результатом. Функция main() не была изменена. Изменились заголовки функции для более четкого визуального отображения особенностей работы с функциями. И изменились циклы для доступа к элементам массива в предыдущем примере for(int i=0;i<size;i++) для работы с индексом и for(p=mas;p<mas+size;p++) для работы с указателем. Доступ к значению элементов массива изменилмя с mas[i] на *p. В остальном логика решения задачи не изменилась. Можно использовать еще один вариант цикла для обработки массива (прмежуточный, использовать индексы, но доступ к значению получать по указателю). В этом случа цикл примет вид for(int i=0;i<size;i++) , а досту к значению -- *(mas+i). В указанном выражении сначала определяется смещение указателя от начала массива на i элементов, а потом берется значение по адресу.

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