Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 182 183 184 185 186 187 188 189 190 ... 337
удивиться? Да потому, что в любой другой ситуации при передаче объекта, если вы не потребуете явно, чтобы он передавался по ссылке (см. разделы 8.5.3–8.5.6), этот объект будет скопирован.

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

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) {

    // что-то

1 ... 182 183 184 185 186 187 188 189 190 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий