Новости
Главная / 2ИСиП / Лекция 6. Массивы. Одномерные и многомерные массивы.

Лекция 6. Массивы. Одномерные и многомерные массивы.

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

#include <iosteam>

#include <string>

int main()
{
std::string students[10] = {
«Иванов», «Петров», «Сидоров»,
«Ахмедов», «Ерошкин», «Выхин»,
«Андеев», «Вин Дизель», «Картошкин», «Чубайс»
};
std::cout << students << std::endl; // Пытаемся вывести весь массив непосредственно
return 0;
}
Скомпилируйте этот код и посмотрите, на результат работы программы. Готово? А теперь запустите программу еще раз и сравните с предыдущим результатом. В моей операционной системе вывод был следующим:
• Первый вывод: 0x7ffff8b85820
• Второй вывод: 0x7fff7a335f90
• Третий вывод: 0x7ffff847eb40
Мы видим, что выводится адрес этого массива в оперативной памяти, а никакие не «Иванов» и «Петров».
Дело в том, что при создании переменной, ей выделяется определенное место в памяти. Если мы объявляем переменную типа int, то на машинном уровне она описывается двумя параметрами — ее адресом и размером хранимых данных.
Массивы в памяти хранятся таким же образом. Массив типа int из 10 элементов описывается с помощью адреса его первого элемента и количества байт, которое может вместить этот массив. Если для хранения одного целого числа выделяется 4 байта, то для массива из десяти целых чисел будет выделено 40 байт.
Так почему же, при повторном запуске программы, адреса различаются? Это сделано для защиты от атак переполнения буфера. Такая технология называется рандомизацией адресного пространства и реализована в большинстве популярных ОС.
Попробуем вывести первый элемент массива — фамилию студента Иванова.

#include <iosteam>

#include <string>

int main()
{
std::string students[10] = {
«Иванов», «Петров», «Сидоров»,
«Ахмедов», «Ерошкин», «Выхин»,
«Андеев», «Вин Дизель», «Картошкин», «Чубайс»
};
std::cout << students[0] << std::endl;
return 0;
}
Смотрим, компилируем, запускаем. Убедились, что вывелся именно «Иванов». Заметьте, что нумерация элементов массива в C++ начинается с нуля. Следовательно, фамилия первого студента находится в students[0], а фамилия последнего — в students[9].
В большинстве языков программирования нумерация элементов массива также начинается с нуля.
Попробуем вывести список всех студентов. Но сначала подумаем, а что если бы вместо группы из десяти студентов, была бы кафедра их ста, факультет из тысячи, или даже весь университет? Ну не будем же мы писать десятки тысяч строк с cout?
Вывод элементов массива через цикл

#include <iosteam>

#include <string>

int main()
{
std::string students[10] = {
«Иванов», «Петров», «Сидоров»,
«Ахмедов», «Ерошкин», «Выхин»,
«Андеев», «Вин Дизель», «Картошкин», «Чубайс»
};
for (int i = 0; i < 10; i++) {
std::cout << students[i] << std::endl;
}

return 0;

}
Если бы нам пришлось выводить массив из нескольких тысяч фамилий, то мы бы просто увеличили конечное значение счетчика цикла — строку for (…; i < 10; …) заменили на for (…; i < 10000; …). Заметьте что счетчик нашего цикла начинается с нуля, а заканчивается девяткой. Если вместо оператора строгого неравенства — i < 10 использовать оператор «меньше, либо равно» — i <= 10, то на последней итерации программа обратится к несуществующему элементу массива — students[10]. Это может привести к ошибкам сегментации и аварийному завершению программы. Будьте внимательны — подобные ошибки бывает сложно отловить. Массив, как и любую переменную можно не заполнять значениями при объявлении. Объявление массива без инициализации string students[10]; // или string teachers[5]; Элементы такого массива обычно содержат в себе «мусор» из выделенной, но еще не инициализированной, памяти. Некоторые компиляторы, такие как GCC, заполняют все элементы массива нулями при его создании. При создании статического массива, для указания его размера может использоваться только константа. Размер выделяемой памяти определяется на этапе компиляции и не может изменяться в процессе выполнения. int n; cin >> n;
string students[n]; /* Неверно */
Выделение памяти в процессе выполнения возможно при работе с динамическими массивами. Но о них немного позже.
Заполним с клавиатуры пустой массив из 10 элементов.
Заполнение массива с клавиатуры

#include <iosteam>

#include <string>

using std::cout;
using std::cin;
using std::endl;

int main()
{
int arr[10];

    // Заполняем массив с клавиатуры
    for (int i = 0; i < 10; i++) {
        cout << "[" << i + 1 << "]" << ": ";
        cin >> arr[i];
    }

    // И выводим заполненный массив.
    cout << "\nВаш массив: ";

    for (int i = 0; i < 10; ++i) {
        cout << arr[i] << " ";
    }

    cout << endl;

    return 0;

}
Скомпилируем эту программу и проверим ее работу.
. Многомерные массивы
Можно создать массивы, которые имеют более чем одну размерность. Каждая размерность представляется в виде индекса массива. Следовательно, двухмерные массивы имеют два индекса; трехмерные массивы имеют три индекса; и т.д. Массивы могут иметь любую размерность, хотя наиболее вероятно, что большинство массивов, которые используются в программе, будут одно или двухмерными
Хорошим примером двухмерного массива является шахматная доска. Одна размерность представляет 8 строк, другая размерность представляет восемь столбцов.
Предположим, что имеется класс с названием SQUARE. Тогда можно следующим образом описать массив с названием Board, который соответствует шахматной доске
SQUARE Board[8][8];
. Инициализация многомерных массивов
Многомерные массивы также можно инициализировать. При этом нужно учитывать, что при инициализации последующий индекс массива изменяется быстрее, чем предыдущий. В случае двухмерного массива, сначала устанавливается номер строки 0 (первый индекс), а затем номер столбца будет меняться от начального значения до конечного, и только потом номер строки будет увеличен. Например, для массива:
int Array[3][2];
первые три элемента при инициализации попадут в первую строку Array[0]; следующие три — во вторую Array[1] и т.д. Этот массив можно инициализировать таким образом:
int Array[3][2] = {1,2,3,4,5,6};
Для ясности можно сгруппировать значения по строкам с помощью фигурных скобок. Например,
int Array[3][2] = {{1,2},{3,4},{5,6}};
Такая запись более понятна, и без нее не обойтись, если нужно инициализировать только часть элементов в каждой строке.
Размещение массивов в свободной памяти
Можно поместить весь массив в свободную память. Для этого надо использовать операцию new и операцию индексирования. В результате этого получается адрес участка свободной памяти, который будет хранить значения массива. Например, в строке:
Dog *Family = new Dog[500];
объявляется указатель Family, который содержит адрес первого элемента массива размером 500. Другими словами, Family указывает на нулевой элемент Family[0].
Преимущество использования указателя Family состоит в том, что можно использовать ссылочную арифметику, чтобы получить доступ к каждому члену массива Family. Например, можете написать
Dog *Family = new Dog [500];
Dog *pDog = Family;//pDog указывает на Family[0]
pDog->SetAge(10); //задаем возраст объекту Family[0]
pDog++; //переходим к Family[1]
pDog->SetAge(20); //задаем возраст объекту Family[1]
Здесь объявляется новый массив из 500 объектов класса Dog и указатель хранит адрес первого элемента массива. Используя этот указатель, для первого объекта вызывается функция SetAge(). Затем указатель инкрементируется, для того чтобы он указывал на следующий элемент массива. Затем для второго элемента массива также вызывается функция SetAge().
Отметим, что для доступа к элементам этого массива можно будет использовать и операцию доступа по индексу. Например, можно записать следующую инструкцию:
Family[2].SetAge(10);

Для того чтобы удалить массив из свободной памяти, нужно использовать операцию delete, но при этом нужно указать, что удаляется не одно значение, а массив. Например:
delete [] pDog;
Квадратные скобки сообщают компилятору, что будет удаляться массив значений. Если скобки опустить, то из памяти будет удален только первый элемент массива.
Связь указателей и массивов
В C++ имя массива является константным указателем на первый элемент массива. Следовательно, в объявлении
Dog Family[50];
имя Family является константным указателем, хранящим значение &Family[0], что является адресом первого элемента массива Family.
Разрешается использовать имена массивов в качестве константных указателей и указателей в качестве имен массивов. Следовательно, Family+4 является допустимым способом доступа к значению в элементе массива Family[4].
Компилятор выполняет все арифметические преобразования, когда к указателю добавлятся значение, например, увеличивается на единицу. Адрес, который получит указатель, когда пишется Family+4 не будет соответствовать адресу на 4 байта больше чем адрес, который хранится в переменной Family, это будет адрес четвертого объекта массива. Если каждый объект длиной 4 байта, то при записи Family+4 к адресу в Family будет добавлено 16 байт. Если объектами массива являются объекты класса Dog, которые имеют четыре члена-данных типа long (длиной по 4 байта), и два члена-данных типа short (длиной по 2 байта), то каждый объект данного класса Dog имеет длину в 20 байт. В этом случае значением Family+4 является адрес на 80 байт больше чем адрес начала массива. Такое изменение адресов называется адресной арифметикой.
Массивы символов
Строка это последовательность символов. До сих пор мы работали только со строками, которые были текстовыми константами, заключенными в двойные кавычки («). Например:
cout << «hello world.\n»;
В C++ строки можно хранить в массивах типа char. Последним элементом строки обязательно должен быть код 0 (не символ, а число). Такие массивы называются «С-строками”. Можно описать и инициализировать строку точно так же, как объявляется любой массив. Например:
char Greeting[] = {’H’,’e’,’l’,’l’,’o’,’ ’,’W’, ’o’,’r’,’l’,’d’, 0 };
Последним элементом в списке инициализации стоит число (код символа) 0, с помощью которого многие функции в языке C++ находят конец строки. Хотя выше приведенный способ инициализации символьного массива работает, но он достаточно трудоемок для записи. В языке имеется другой, более удобный, способ инициализации символьного массива, пример которого показан ниже.
char Greeting[] = «Hello World»;
Следует сделать два замечания по используемому здесь синтаксису:
• вместо использования символьных констант, выделенных одиночными кавычками (‘), здесь используется символьная строка, выделенная двойными кавычками («) и она не заключена в фигурные скобки;
• в конце строки не добавляется число 0, так как компилятор добавит его сам.
Размер массива Greeting должен быть не менее 12 элементов, чтобы сохранить эту строку: 11 элементов для хранения букв и один элемент для хранения завершающего строку нуля.
. Функции для работы с массивами символов
Язык C++ наследовал от языка C набор функций для работы с массивами символов. Все эти функции объявлены в заголовочном файле cstring. Если программа использует такие функции, то этот файл нужно включить в программу:

include

Примерами таких функций являются:
• int strlen(str) — определяет количество символов в массиве, не включая нулевой код конца строки;
• char* strcpy(str1,str2) – копирует все символы до нуля из символьного массива str2 в символьный массив str1;
• char* strcat(str1,str2) – добавляет содержимое символьного массива str2 к концу строки в символьном массиве str1 (конкатенаця); массив str1 должен иметь достаточно места для хранения всех символов;
• int strcmp(str1,str2)- сравнивает поэлементно строки в символьных массивах str1 и str2 в соответствии с алфавитным порядком; если очередной символ в str1 по алфавитному порядку следует ранее, чем соответствующий символ в str2, то функция возвращает отрицательное значение; если строки совпадают, возвращается 0; если очередной символ в str1 следует по алфавиту после соответствующего символа в str2 , то функция возвращает положительное значение;
• и т.п.
В примере 11.2 показано использование стандартных функций для работы с С-строками.
Пример 11.2. Использование функций для работы со строками.
1:#include // для потоков ввода-вывода
2:#include // для работы с С-строками
3:using namespace std;
4:int main()
5:{
6: char first_name[20], last_name[40], full_name[70];
7: cout << «Enter your first name — «; // имя 8: cin >> first_name; // например, Bart
9: cout << «Enter your last name — «; // фамилия 10: cin >> last_name; // например, Simpson
11: strcpy(full_name, last_name);// копируем фамилию
12: strcat(full_name, «, «); // добавляем запятую и пробел
13: strcat(full_name, first_name); // добавляем имя
14: int len = strlen(full_name); // длина строки
15: cout<<«Your full name is » << full_name << endl;
16: cout<<«which contains ”<<len<<» characters.»<<endl;
17: return 0;
18:}
Результат работы программы:
Enter your first name — Bart
Enter your last name – Simpson
Your full name is Simpson, Bart
which contains 13 characters.
В строке 2 в программу включен заголовочный файл cstring, в котором объявлены функции для работы с символьными массивами. В строке 6 объявляются три символьных массива размерами в 20, 40 и 70 символов. Программист должен следить за тем, чтобы эти размеры не были превышены при работе программы. Такая проверка в данной программе не делается для того, чтобы сделать ее проще. В строках 11-14 вызываются функции стандартной библиотеки, для выполнения операций над строками. Конечно такая запись операций не очень понятная и удобная.
Данный пример с использованием нового типа данных string, переписан в разделе 12.4. Из сравнения этих программ можно увидеть, насколько могут быть полезными новые пользовательские типы данных.

В заголовочном файле определены полезные функции для преобразования строк, представляющих численные значения в сами численные значения:
double atof(const char* p);// преобразует строку в p в double
int atoi(const char* p);// преобразует строку в p в int
long atol(const char* p);// преобразует строку в p в long

Например:
char p[] = “12.85”; // строка текста
double d = atof(p); // d получает числовое значение 12.85