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

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

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

К элементам массива можно обратиться по имени массива и индексу самого элемента (рис. 65). То есть массивы не являются простыми типами данных. У каждого элемента массива есть индекс. Индексы в языке С начинаются с 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]

 

 

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

Рис. 65. Наглядное представление обращения к элементам одномерного массива

 

На рис. 65 показан массив. Имя 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().

Обращение к элементу представляется в следующем виде:

имя_массива[индекс_элемента]

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

Если значение индексного выражения окажется вне допустимого диапазона, соответствующего элементам массива, то произойдет обращение к несуществующим данным, которые могут содержать значения других переменных, или какой-то мусор, или даже код самой программы. Если обращение к такому элементу произойдет в выражении, то получится какие-то произвольные данные, которые, участвуя в вычислениях, породят некорректные результаты. Если же программист попытается записать значение в элемент массива, указанный неправильным индексом, то перезапишутся любые данные, которые окажутся в этом месте памяти. Если это будет программный код, результат окажется катастрофическим. К счастью подобная ситуация в M S VS 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;

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

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

Рис. 66. Сообщение об ошибке при запуске программы

 

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

 

Рис. 67. Внешний вид окна консольного приложения при выполнении программы

 

Первый элемент массива не был введён с клавиатуры, поэтому содержит в себе «мусор», а цифра 10 оказалась за границей массива и не распечатывается в правильном цикле. Окно с ошибкой относится к сообщениям M S VS 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;

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

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

 

Рис. 68. Результат работы программы обработки двух массивов

и подсчета суммы их элементов

 

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

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

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

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

#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;

}

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

 

Рис. 69. Результат работы программы обработки элементов массивов,

реализованной с помощью функций

 

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

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

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 элементов, а потом берется значение по адресу.

 

Строки

Как уже упоминалось, массивы могут быть любого типа, но массивы типа char в ISO/ANSI C++ имеет некоторые встроенные особенности, которых нет у массивов других типов. Это связано с тем, что массив типа char – это строка. Символьная строка это последовательность символов, дополненная специальным символом «конца строки» – ‘\0’. Этот символ иногда называют нулевым, так как все его биты заполнены значением 0. Строки, организованные подобным образом, часто называют строками в стиле С, поскольку такое определение строк впервые было представлено этом языке. В С++ существует класс String, но в ISO/ANSI C++ нет типа данных string. Возможно, это не очень удобно, но дополнительные возможности при работе с массивами типа char позволяют не очень страдать из-за его отсутствия.

Инициализация символьного массива может быть такой же, как и у других массивов:

char str[9]={‘п’,’р’,’и’,’в’,’е’,’т’,’ ’,’я’,’\0’};

или быть своей собственной:

char str[9]={"привет я"};

Расположение элементов такого массива показано на рис. 70. Следует обратить внимание, что при втором способе инициализации не добавлен символ конца строки, ограничивающий символ '\0' добавляется компилятором автоматически. Если его включить в строчный литерал явно, то в строке будут два нулевых символа. При работе со строковыми необходимо всегда учитывать дополнительный размер для нулевого символа.

 

str

Элементы строки

 

 

  п р и в е т   я \0

 

  str[0] str[1] str[2] str[3] str[4] str[5] str[6] str[7] str[8]

 

 

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

Рис. 70. Наглядное представление обращения к элементам строки

 

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

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

char str[]={"привет я"};

В ISO/ANSI C++ существуем много достаточно много функций обработки строковых. С некоторыми, наиболее распространёнными можно познакомиться в таблице 8. Для работы с ними необходимо подключить заголовочный файл string.h

Таблица 8

Функции C++ работы со строками

Заголовок функции Примечание
char* strcat(char* str1, char* str2); К строке str1 добавляется строка str2, результат str1+ str2
char* strchr(char* str1, int sim); Возвращается указатель на первое вхождение символа sim в строке str1, NULL если не найден
int strcmp(char* str1, char* str2); Сравниваются 2 строки, если str1> str2, результат >0, если str1== str2, то=0, иначе <0
char* strcpy(char* str1, char* str2); Копирует str2 в str1 и его возвращает
int stricmp(char* str1, char* str2); Делает то же, что и strcmp, но считает заглавные и прописные буквы эквивалентными
int strlen(char* str); Возвращает длину строки str без ‘\0’
char* strlwr(char* str); Переводит заглавные буквы в строчные
char* strstr(char* str1, char* str2); Возвращает указатель на первое вхождение подстроки str2 в строку str1
double strtod(char* nprt, char** end); Преобразует строку nprt в значение типа double, очень часто end== NULL, а строка nprt имеет вид: [пробелы][знак][цифры][,цифры][{d|D|e|E}[знак][цифры]. Первый символ, не соответствующий форме прерывает преобразование
long strtol(char* nprt, char** end, int base); Преобразует строку nprt в значение типа long, очень часто end== NULL, а строка nprt имеет вид: [пробелы][знак][0][x][цифры]. Первый символ, не соответствующий форме прерывает преобразование. Если значение 2<= base<=32, оно используется как система счисления, если ==0, то интерпретация идет по первым двум символам 0+(1-7) 8-миричная, 0х–16-тиричная.
char* strtok(char* str1, char* str2); Находит первое вхождение разделителя из строки str2 в строке str1, если надо найти следующее слово, необходимо снова вызвать функцию str1== NULL
char* strupr(char* str); Переводит строчные буквы в заглавные


 

Пример реализации приведенных в таблице функций:

#include <iostream>

#include <string.h>

using namespace std;

int main(void)

{ setlocale(NULL,"Russian");

char str1[80]={"Мама мыла раму\0"};

char str4[80]={""};

char str2[80]={"А роза упала на лапу Азора\0"};

char str3[80]={"Аргентина манит негра\0"};

char *str_r;

char prob[]={".,?! \0"};

char dw[80]={"+1234,978E-2\0"};

char in[80]={"0x56435fc\0"};

long i,l;

double d;

cout<<"Исходные строки \n";

cout<<"str1=\t"<<str1<<endl;

cout<<"str2=\t"<<str2<<endl;

cout<<"str3=\t"<<str3<<endl;

cout<<"str4=\t"<<str4<<endl;

cout<<"prob=\t"<<prob<<endl;

cout<<"dw=\t"<<dw<<endl;

cout<<"in=\t"<<in<<endl;

cout<<"Копирование строки str1 в str3, результат указатель на str4\n";

str_r=strcpy(str4,str1);

cout<<str_r<<endl<<str4<<endl;

cout<<"Добавление в строку str4 str3, результат указатель на str4\n";

str_r=strcat(str4,str2);

cout<<str_r<<endl<<str4<<endl;

str_r=strchr(str1,'ы');

cout<<"с этого начинаеися строка для ы="<<str_r<<endl;

cout<<"Демонстрация разного сравнения 2 строк\n";

if(strcmp(str1, str1)==0)

cout<<"Совпадают\n";

if(strcmp(str1, str2)>0)

cout<<str1<<">"<<str2<<endl;

if(strcmp(str2, str1)<0)

cout<<str2<<"<"<<str1<<endl;

cout<<"Возврат номера для любых совпадающих символов из строк str1 и str3\n";

i=strcspn(str1,str3);

cout<<"совпавший символ\t="<<str1[i]<<" №= "<<i<<endl;

i=strlen(str1);

cout<<"длина строки\t"<<str1<<"="<<i<<endl;

cout<<"поиск совпадения подстроки str2, начиная с 5 позиции в строке str4\n";

str_r=strstr(str4,str2+5);

cout<<"в строке\t"<<str4<<"\nесть подстрока "<< str_r<<endl;

d=strtod(dw,NULL);

cout<<"\nиз строки\t"<<dw<<"\tполучилось число="<< d<<endl;

l=strtol(in,NULL,0);

cout<<"\nиз строки\t"<<in<<"\tполучилось число="<< l<<endl;

cout<<"Поиск указателя на разделитель из prob в str1\n";

str_r=strtok(str1, prob);

cout<<str_r<<endl<<str1<<endl;

cout<<"str1 необратимо испорчена, но можно найти следующие слова\n";

str_r=strtok(NULL, prob);

cout<<str_r<<endl<<str1<<endl;

cout<<"st1 необратимо испорчена, но можно найти следующие слова\n";

str_r=strtok(NULL, prob);

cout<<str_r<<endl<<str1<<endl;

cout<<"Перевод в заглавные буквы\n";

str_r=strupr(str2);

cout<<str_r<<endl<<str2<<endl;

cout<<"Перевод в маленькие буквы\n";

str_r=strlwr(str2);

cout<<str_r<<endl<<str2<<endl;

}

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

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

 

Массивы, которые определялись до сих пор, имеют один индекс и называются одномерными массивами. Но массив может иметь и более одного индексного значения — в этом случае он называется многомерным массивом. Аналогом двухмерного массива можно рассматривать матрицы (тензоры).

Объявление двухмерного статического массива имеет вид:

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

где конст_1 – это размер по строкам (количество строк), а конст_2 – количество элементов в строке, то есть размер по столбцам или количество столбцов.

По аналогии можно объявить трех- и более мерные массивы. В M S VS 2008 даже объявление 10-ти мерного массива не вызывает проблемы, в чем можно убедиться самостоятельно, проверив на практике.

Массивы хранятся в памяти так, что самый правый индекс растет быстрее всего. Пример массива data[2][4] рассмотрен на рис. 72.

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

 

data[0][0]
 
data[0][1]
data[0][2]
data[0][3]
 
 
 
data[1][0]
 
data[1][1]
data[1][2]
data[1][3]
 
 
 

Рис. 72. Организация массива data[2][4]

 

Следует обратить внимание на то, что двумерный массив в «родном» C++ – на самом деле одномерный массив, состоящий из одномерных массивов. Массив с тремя измерениями в «родном» C++ – это одномерный массив элементов, в котором каждый элемент представляет собой одномерный массив одномерных массивов. Большую часть времени программисту не придется об этом беспокоиться. Из сказанного следует, что для массива, показанного на рис. 72, выражения data[0], data[1] представляют одномерные массивы.

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

long data[2][4] = {{1,2,3, 5}, {7, 11, 13, 17} };

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

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

long data[2] [4] = { { 1, 2, 3},{ 7, 11 } };

В этом примере списки инициализации не полны. Элементы data[0][3], data[1][2] и data[1][3] не получают инициализирующих значений и потому равны нулю.

Если есть желание инициализировать весь массив нулевыми значениями, его можно реализовать просто:

long data[2] [4] = {0};

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

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

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

Объявление двухмерного динамического массива выглядит следующим образом: !!! а двумерный – одномерный???

тип_элемента **имя_массива;

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

1. Можно представить его как одномерный массив длинной n*m, где n – количество строк, а m – количество столбцов. Сложность только в соотношении привычного представления индексации элементов массива Aij, использовать A[i][j] не получится, так как эта запись применима только к двойным указателям, но можно использовать зависимость глубины расположения элементов такого вида: A[i*m+j]. Здесь один индекс зависит от i-строки и j-го столбца.

Рассмотрим пример программы с такой реализацией массива.

#include <iostream>

#include <stdlib.h> // заголовочный файл функций для генерации чисел

#include <time.h>// заголовочный файл для функции time()

using namespace std;

// прототипы функций

void zap_mas(int mas[],int n,int m,int min,int max);

// функция заполнения массива случайными числами в диапозоне от min до max

void print_mas(int mas[],int n,int m); // функция вывода массива на экран

int sun_d_mas(int mas[],int n,int m);

// функция расчет суммы элементов, стоящих на главной диагонали i==j

// описания функций

void zap_mas(int mas[],int n,int m,int min,int max)

{int i,j;

// для работы с 2-мерным масивом требуются 2 вложенных

// цикла: 1 изменяет индекс для строк, 2 – для столбцов

for(i=0;i<n;i++)

for(j=0;j<m;j++)

mas[i*m+j]=rand()%(max-min+1)+min;

// заполнение элемента случайным числом

}

void print_mas(int mas[],int n,int m)

{int i,j;

{// вывод на экран, между элементами одной строки выводится ‘\t’, строки разделяются ‘\n’

for( i=0;i<n;i++)

{

for(j=0;j<m;j++)

cout<<mas[i*m+j]<<"\t";

cout<<endl;

}

}

int sun_d_mas(int mas[],int n,int m)

{

int i,sum=0; // переменная для суммы

int k=n;

/* Необходимо учитывать, что считать элементы можно пока i<n и j<m, так как для главной диагонали i==j, то используемый индекс должен быть k<n и k<m, k будет минимальным из этих чисел */

if(k<m) k=m;

/* нужно использовать один индекс, так как другие элементы нас не интересуют, следовательно используется один цикл*/

for( i=0;i<k;i++)

sum+=mas[i*m+i];

return sum;

}

int main(void)

{

setlocale(NULL,"Russian"); // подключение кодировочной таблицы

int *A,n,m;

srand(time(NULL)); // подготовка работы генератора случайных чисел

cout<<"введите разметность по строкам и столбцам\n";

cin>>n>>m;

A=new int[n*m]; // выделение памяти под массив

zap_mas(A,n,m,-10,10); // заполнение массива от 10 до 10

print_mas(A,n,m); // вывод на экран

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

cout<<"сумма длавной диогонали = "<<sun_d_mas(A,n,m)<<endl;

delete[] A; // очистка памяти

}

Результат работы программы имеет вид (рис. 73):

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

как одномерного

 

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

srand(time(NULL)); и mas[i*m+j]=rand()%(max-min+1)+min;

srand() подготавливает генерацию случайных чисел, без этой функции генератор будет выдавать одни и те же числа при разных запусках программы. Параметром генератора может быть 1 или любое достаточно большое положительное число, в данном примере используется результат работы функции time(NULL) который возвращает число секунд прошедших с момента 00:00:00 по Гринвичу 1 января 1970 года. Параметр NULL говорит о том, что этот результат не надо сохранять в переменную.

Функция rand() возвращает случайные числа в диапазоне [0, MAX_INT]. Операция % позволяет сузить этот диапазон до [0, max-min]. Потом этот диапазон сместится на [min, max], так как к обоим его частям приплюсуется min (0+min=min, max-min+min=max). В результате этого элементы массиваы будут принимать значания от min до max.

2. Второй способ требует усилий при выделении памяти, необходимо вначале выделить память под массив указателей на строки, а потом для каждого указателя строки выделить память для элементов каждой строки. Это даст возможность обращаться к элементам массива A[i][j], но кроме выделения памяти потребует в конце работы программы удалить выделенную память (количество использованных операторов new должно соответствовать количеству использованных delete). В этом методе удобно написать функции удаления и выделения памяти под массив. Но они потребуют обязательно вернуть как результат указатель на измененную память, в процессе работы программы.

#include <iostream>

#include <stdlib.h>

#include <time.h>>

using namespace std;

// прототипы функций

int** new_mas(int n,int m); // выделения памяти под массив

int** del_mas(int *mas[],int n); // освобождения памяти

void zap_mas(int *mas[],int n,int m,int min,int max);

void print_mas(int *mas[],int n,int m);

int sun_d_mas(int *mas[],int n,int m);

// описания функций

int** new_mas(int n,int m)

{ int **mas;

/* для выделеня требуется локальный указатель, так как в параметрах указателя нет, MS VS 2008 не позволяет использовать неинициализированный указатель*/

mas=new int*[n]; // выделение памяти под массив строк

for(int i=0;i<n;i++) // для каждой строки

mas[i]=new int [m]; // выделение памяти под массив элементов

return mas; // возврат на начало выделенной памяти

}

int** del_mas(int *mas[],int n)

{

for(int i=0;i<n;i++) // для каждой строки

{

delete[] mas[i]; // удаление памяти? выделенной на строку

mas[i]=0;

}

delete[] mas; // удаление памяти, выделенной под массив

mas=0;

return mas; // возврат указателя, он равен 0

}

void zap_mas(int *mas[],int n,int m,int min,int max)

{int i,j;

for( i=0;i<n;i++)

for(j=0;j<m;j++)

mas[i][j]=rand()%(max-min+1)+min;

}

void print_mas(int *mas[],int n,int m)

{int i,j;

for( i=0;i<n;i++)

{

for(j=0;j<m;j++)

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

cout<<endl;

}

}

int sun_d_mas(int *mas[],int n,int m)

{

int i,k=n,sum=0;

if(k>m) k=m;

for( i=0;i<k;i++)

sum+=mas[i][i];

return sum;

}

 

int main(void)

{

setlocale(NULL,"Russian");

int **A,n,m;

srand(time(NULL));

cout<<"введите разметноcть по строкам и столбцам\n";

cin>>n>>m;

A=new_mas(n,m); // выделение памяти под массив

zap_mas(A,n,m,-10,10);

print_mas(A,n,m);

cout<<"сумма элементов главной диагонали = "<<sun_d_mas(A,n,m)<<endl;

A=del_mas(A,n); // очистка памяти

}

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

 

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

как массива указателей

 

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

Следует объяснить некоторые моменты, не очевидные в коде программы.

Первое: заголовок функции для выделения памяти под массив имеет вид: int** new_mas(int n,int m); не имеет в параметрах указателя на начало массива. В этой функции существует локальный указатель для динамичсеского выделения памяти. Это не создает проблемы, так как динамическая память остается выделенной пока не отработает оператор delete. Если ввести указатель в параметры функции, то M S VS 2008 сообщит об ошибке использования неинициализированного указателя. В этом случае необходимо в main() строку int **A,n,m; заменить на int **A=0,n,m; Внутри функции new_mas() станет ненужна локальная переменная mas, а заголовок функции примет вид:

int** new_mas(int* mas[], int n,int m);

Второе: после работы оператора delete[] сиавится оператор присваения удаленному указателю значения. 0. Дело в том, что M S VS 2008 при окрнчании работы программы числит выделенную плд нее память. То есть ко всем указателям применяется оператор delete. Ситуация, когда оператор delete применяется у указателю два раза подряд приводит к ошибке выделения памяти. Поэтому присваивание указателю значения 0 исключает подобную ситуацию. Использование оператора delete необходимо, так как при работе программы из командной строки без управления M S VS 2008 очистки памяти не происходит и, запуская приложение большое количество раз, можно исчерпать всю свободную оператовную память.

 

Контрольные вопросы и задания

 

1) Дайте определение массиву.

2) Каким образом в памяти расположены данные массива?

3) Опишите синтаксис объявления одномерного массива в языке С.

4) Как происходит инициализация массива? В чем заключается особенность инициализации массивов?

5) Каким образом можно определить объём памяти, необходимой для хранения массива?

6) Что такое индекс элемента массива? Как получить доступ к значению элемента массива, зная его индекс?

7) Какие виды выделения памяти под массивы Вам известны? Поясните особенности выделения памяти и высвобождения памяти для каждого вида.

8) Приведите общий синтаксис записи оператора выделения статической памяти под массив.

9) Каким образом происходит динамическое выделение памяти под массив? Приведите примеры.

10) Как представляются строки в языке C++?

11) Каким образом происходит инициализация строк? В чем заключается особенность инициализации строк? Приведите пример.

12) Как располагаются в памяти элементы двумерного массива?

13) Опишите синтаксис объявления двумерного массива в программе. Каким образом производится индексация элементов двумерного массива?

14) Каким образом можно инициализировать многомерный массив?

15) Как изменяются индексы элементов матрицы, лежащих:

- на главной диагонали;

- на побочной диагонали;

- выше главной диагонали;

- ниже главной диагонали;

- выше побочной диагонали;

- ниже побочной диагонали?

16) Напишите фрагменты программ для решения следующих задач:

- подсчет нулевых элементов в заданном одномерном массиве;

- вычисление суммы положительных элементов в заданном одномерном массиве;

- поиск первого максимального элемента и его индекса в заданном одномерном массиве;

- поиск последнего минимального элемента и его индекса в заданном одномерном массиве.

17) Напишите фрагменты программ для решения следующих задач:

- вычисление произведения отрицательных элементов в каждом столбце двумерного массива;

- вычисление суммы положительных элементов в каждой строке двумерного массива;

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

 

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

 

1. Аляев Ю. А. Алгоритмизация и языки программирования: Pascal, C++, Visual Basic / Ю. А. Аляев, О. А. Козлов. – Финансы и статистика, 2002. – 320 с.

2. Вдовенко В.В. Программирование на языке С++ / В.В. Вдовенко. – Красноярск, 2006. – 212 с.

3. Либерти Дж. Освой самостоятельно С++ за 21 день / Дж. Либерти. – 3 издание. – Киев: изд–во "Вильямс". 2006. – 784 с.

4. Пахомов Б.И. CC++ и MS Visual C++ 2008 для начинающих / Б.И. Пахомов. – СПб.: "БХВ–Петербург". 2009. – 624 с.

5. Страуструп Б. Язык программирования С++ / Страуструп Б. – 3 издание. – М.: Бином, 2011. – 1104 с.

6. Харбисон С.П. Язык C с примерами / С.П. Харбисон, Г.Л. Стил; пер. с англ. – М.: Бином, 2011. – 528 с.

7. Хортон А. Visual C++ 2005: базовый курс / А. Хортон. – М.: ООО "И.Д. Вильямс", 2007. – 1152 с.

СПИСОК ИСПОЛЬЗОВАННЫХ СОКРАЩЕНИЙ

 

API (Application Programming Interface) – Интерфейс программирования приложений

ASCII (American Standard Code for Information Interchange) – Американский стандартный код обмена информацией

CLI (Common Language Infrastructure) – Спецификация общеязыковой инфраструктуры

CLR (Common Language Runtime) – Общеязыковая исполняющая среда

IDE (Integrated Development Environment) – Интегрированная среда разработки

IEEE (Institute of Electrical and Electronics Engineers) – Институт инженеров по электротехнике и электронике

GUI (Graphical User Interface) – Графический интерфейс пользователя

MFC (Microsoft Foundation Classes) – Одна из библиотек на языке C++

MSDN (Microsoft Development Network) – Подразделение компании Microsoft, ответственное за взаимодействие фирмы с разработчиками. Подразделение работает как информационный сервис для разработчиков программного обеспечения

MS VS (Microsoft Visual Studio) – Система программных средств, используемая программистами для разработки программного обеспечения, разработанная Microsoft

ВЦ – Вычислительный центр

ОС – Операционная система

ПК – персональный компьютер

ПО – Программное обеспечение

ЭВМ – Электронно-вычислительная машина

ПРИЛОЖЕНИЯ

Приложение 1

Таблица символов 866 ( ASCII , OEM , DOS )

  .0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .A .B .C .D .E .F
0. 012 ☺ 1 ☻ 2 ♥ 3 ♦ 4 ♣ 5 ♠ 6 • 7 ◘ 8 ○ 93 ◙ 10 ♂ 11 ♀ 12 ♪ 13 ♫ 14 ☼ 15
1. ► 16 ◄ 17 ↕ 18 ‼ 19 ¶ 20 § 21 ▬ 22 ↨ 23 ↑ 24 ↓ 25 → 26 ← 27 ∟ 28 ↔ 29 ▲ 30 ▼ 31
2. 32 ! 33 " 34 # 35 $ 36 % 37 & 38 ' 39 ( 40 ) 41 * 42 + 43 , 44 - 45 . 46 / 47
3. 0 48 1 49 2 50 3 51 4 52 5 53 6 54 7 55 8 56 9 57 : 58 ; 59 < 60 = 61 > 62 ? 63
4. @ 64 A 65 B 66 C 67 D 68 E 69 F 70 G 71 H 72 I 73 J 74 K 75 L 76 M 77 N 78 O 79
5. P 80 Q 81 R 82 S 83 T 84 U 85 V 86 W 87 X 88 Y 89 Z 90 [ 91 \ 92 ] 93 ^ 94 _ 95
6. ` 96 a 97 b 98 c 99 d 100 e 101 f 102 g 103 h 104 i 105 j 106 k 107 l 108 m 109 n 110 o 111
7. p 112 q 113 r 114 s 115 t 116 u 117 v 118 w 119 x 120 y 121 z 122 { 123 | 124 } 125 ~ 126 ⌂ 127
8. А 128 Б 129 В 130 Г 131 Д 132 Е 133 Ж 134 З 135 И 136 Й 137 К 138 Л 139 М 140 Н 141 О 142 П 143
9. Р 144 С 145 Т 146 У 147 Ф 148 Х 149 Ц 150 Ч 151 Ш 152 Щ 153 Ъ 154 Ы 155 Ь 156 Э 157 Ю 158 Я 159
A. а 160 б 161 в 162 г 163 д 164 е 165 ж 166 з 167 и 168 й 169 к 170 л 171 м 172 н 173 о 174 п 175
B. ░ 176 ▒ 177 ▓ 178 │ 179 ┤ 180 ╡ 181 ╢ 182 ╖ 183 ╕ 184 ╣ 185 ║ 186 ╗ 187 ╝ 188 ╜ 189 ╛ 190 ┐ 191
C. └ 192 ┴ 193 ┬ 194 ├ 195 ─ 196 ┼ 197 ╞ 198 ╟ 199 ╚ 200 ╔ 201 ╩ 202 ╦ 203 ╠ 204 ═ 205 ╬ 206 ╧ 207
D. ╨ 208 ╤ 209 ╥ 210 ╙ 211 ╘ 212 ╒ 213 ╓ 214 ╫ 215 ╪ 216 ┘ 217 ┌ 218 █ 219 ▄ 220 ▌ 221 ▐ 222 ▀ 223
E. р 224 с 225 т 226 у 227 ф 228 х 229 ц 230 ч 231 ш 232 щ 233 ъ 234 ы 235 ь 236 э 237 ю 238 я 239
F. Ё 2404 ё 241 Є 242 є 243 Ї 244 ї 245 Ў 246 ў 247 ° 248 ∙ 249 · 250 √ 251 № 252 ¤ 253 ■ 254 2555

 

Учебное издание

 

 

Гуменникова Александра Викторовна

Орешкина Ольга Анатольевна

Зотин Александр Геннадьевич

 

ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ С++

В СРЕДЕ MICROSOFT VISUAL STUDIO

Часть 1

 

Учебное пособие

 

Редактор Е. Г. Некрасова

Оригинал-макет и верстка М. А. Белоусовой

 

 

Подписано в печать . Формат 60×84/16. Бумага офисная.

Печать плоская. Усл. п. л. . Уч.-изд. л. .

Тираж 100 экз. Заказ . С .

 

Санитарно-эпидемиологическое заключение

№ 24.04.953.П.000032.01.03. от 29.01.2003 г.

 

Редакционно-издательский отдел Сиб. гос. аэрокосмич. ун-та.

Опечатано в отделе копировально-множительной техники

Сиб. гос. аэрокосмич. ун-та.

660014, Красноярск, просп. им. газ. «Красноярский рабочий», 31.