Шрифт:
Интервал:
Закладка:
Обратите внимание на то, что указатель, образованный из имени массива, установлен на его первый элемент и не является переменной, т.е. ему ничего нельзя присвоить.
char ac[10];
ac = new char [20]; // ошибка: имени массива ничего присвоить нельзя
&ac[0] = new char [20]; // ошибка: значению указателя ничего
// присвоить нельзя
И на десерт — проблема, которую компилятор может перехватить!
Вследствие неявного превращения имени массива в указатель мы не можем даже скопировать массивы с помощью оператора присваивания.
int x[100];
int y[100];
// ...
x = y; // ошибка
int z[100] = y; // ошибка
Это логично, но неудобно. Если необходимо скопировать массив, вы должны написать более сложный код. Рассмотрим пример.
for (int i=0; i<100; ++i) x[i]=y[i]; // копируем 100 чисел типа int
memcpy(x,y,100*sizeof(int)); // копируем 100*sizeof(int) байт
copy(y,y+100, x); // копируем 100 чисел типа int
Поскольку в языке C нет векторов, в нем интенсивно используются массивы. Вследствие этого в огромном количестве программ, написанных на языке C++, используются массивы (подробнее об этом — в разделе 27.1.2). В частности, строки в стиле C (массивы символов, завершаемые нулем; эта тема рассматривается в разделе 27.5) распространены очень широко.
Если хотите копировать, то используйте класс, аналогичный классу vector. Код копирования объектов класса vector, эквивалентный приведенному выше, можно записать следующим образом:
vector<int> x(100);
vector<int> y(100);
// ...
x = y; // копируем 100 чисел типа int
18.5.3. Инициализация массива
Массивы имеют одно значительное преимущество над векторами и другими контейнерами, определенными пользователями: язык С++ предоставляет поддержку для инициализации массивов. Рассмотрим пример.
char ac[] = "Beorn"; // массив из шести символов
Подсчитайте эти символы. Их пять, но ac становится массивом из шести символов, потому что компилятор добавляет завершающий нуль в конце строкового литерала.
Строка, завершающаяся нулем, является обычным явлением в языке С и многих системах. Такие массивы символов, завершающиеся нулем, мы называем строками в стиле языка С (C-style string). Все строковые литералы являются строками в стиле языка C. Рассмотрим пример.
char* pc = "Howdy"; // указатель pc ссылается на массив из шести
// символов
Графически это можно изобразить следующим образом.
Переменная типа char, имеющая числовое значение 0, — это не символ '0', не буква и не цифра. Цель этого завершающего нуля — помочь функции найти конец строки. Помните: массив не знает своего размера. Полагаясь на использование завершающего нуля, мы можем написать следующий код:
int strlen(const char* p) // похоже на стандартную функцию strlen()
{
int n = 0;
while (p[n]) ++n;
return n;
}
На самом деле мы не обязаны определять функцию strlen(), поскольку это уже стандартная библиотечная функция, определенная в заголовочном файле <string.h> (разделы 27.5 и Б.10.3). Обратите внимание на то, что функция strlen() подсчитывает символы, но игнорирует завершающий нуль; иначе говоря, для хранения n символов в строке в стиле языка С необходимо иметь память для хранения n+1 переменной типа char.
Только символьные массивы можно инициализировать с помощью литеральных констант, но любой массив можно инициализировать списком значений его элементов соответствующего типа. Рассмотрим пример.
int ai[] = { 1, 2, 3, 4, 5, 6 }; // массив из шести чисел
// типа int
int ai2[100] = { 0,1,2,3,4,5,6,7,8,9 }; // остальные 90 элементов
// инициализируются нулем
double ad[100] = { }; // все элементы инициализируются нулем
char chars[] = { 'a', 'b', 'c' }; // нет завершающего нуля!
Обратите внимание на то, что количество элементов в массиве ai равно шести (а не семи), а количество элементов в массиве chars равно трем (а не четырем), — правило “добавить нуль в конце” относится только к строковым литералам. Если размер массива не задан явно, то он определяется по списку инициализации. Это довольно полезное правило. Если количество элементов в списке инициализации окажется меньше, чем количество элементов массива (как в определениях массивов ai2 и ad), остальные элементы инициализируются значениями, предусмотренными для данного типа элементов по умолчанию.
18.5.4. Проблемы с указателями
Как и массивами, указателями часто злоупотребляют. Люди часто сами создают себе проблемы, используя указатели и массивы. В частности, все серьезные проблемы, связанные с указателями, вызваны обращением к области памяти, которая не является объектом ожидаемого типа, причем многие из этих проблем, в свою очередь, вызваны выходом за пределы массива. Перечислим эти проблемы.
• Обращение по нулевому указателю.
• Обращение по неинициализированному указателю.
• Выход за пределы массива.
• Обращение к удаленному объекту.
• Обращение к объекту, вышедшему из области видимости.
На практике во всех перечисленных ситуациях главная проблема, стоящая перед программистом, заключается в том, что внешне фактический доступ выглядит вполне невинно; просто указатель ссылается на неправильное значение. Что еще хуже (при записи с помощью указателя), проблема может проявиться намного позднее, когда окажется, что некий объект, не связанный с программой, был поврежден. Рассмотрим следующий пример.
Не обращайтесь к памяти с помощью нулевого указателя.
int* p = 0;
*p = 7; // Ой!
Очевидно, что в реальной программе это может произойти, если между инициализацией и использованием указателя размещен какой-то код. Чаще всего эта ошибка возникает при передаче указателя p функции или при получении его в результате работы функции. Мы рекомендуем никуда не передавать нулевой указатель, но, уж если вы это сделали, проверьте указатель перед его использованием. Например,
int* p = fct_that_can_return_a_0();
if (p == 0) {
// что-то делаем
}
else {
// используем р
*p = 7;
}
и
void fct_that_can_receive_a_0(int* p)
{
if (p == 0) {
// что-то