Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

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

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

double* p1 = &ad[0];

double* p2 = p1+7;

double* p3 = &p1[7];

if (p2 != p3) cout << "impossible!\n";

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

18.5.2. Указатели и массивы

Имя массива относится ко всем элементам массива. Рассмотрим пример.

char ch[100];

Размер массива

ch
, т.е.
sizeof(ch)
, равен 100. Однако имя массива без видимых причин превращается в указатель.

char* p = ch;

Здесь указатель

p
инициализируется адресом
&ch[0]
, а размер
sizeof(p)
равен 4 (а не 100). Это свойство может быть полезным. Например, рассмотрим функцию
strlen
, подсчитывающую количество символов в массиве символов, завершающимся нулем.

int strlen(const char* p) // аналогична стандартной

// функции strlen

{

int count = 0;

while (*p) { ++count; ++p; }

return count;

}

Теперь можем вызвать ее как с аргументом

strlen(ch)
, так и с аргументом
strlen(&ch[0]
). Возможно, вы заметили, что такое обозначение дает очень небольшое преимущество, и мы с вами согласны. Одна из причин, по которым имена массивов могут превращаться в указатели, состоит в желании избежать передачи большого объема данных по значению. Рассмотрим пример.

int strlen(const char a[]) // аналогична стандартной

// функции strlen

{

int count = 0;

while (a[count]) { ++count; }

return count;

}

char lots [100000];

void f

{

int nchar = strlen(lots);

// ...

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

strlen
,
но этого не происходит. Вместо этого объявление аргумента
char p[]
рассматривается как эквивалент объявления
char* p
, а вызов
strlen(lots)
— как эквивалент вызова
strlen(&lots[0])
. Это предотвращает затратное копирование, но должно вас удивить. Почему вы должны удивиться? Да потому, что в любой другой ситуации при передаче объекта, если вы не потребуете явно, чтобы он передавался по ссылке (см. разделы 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
становится массивом из шести символов, потому что компилятор добавляет завершающий нуль в конце строкового литерала.

<
Поделиться с друзьями: