Создание нового проекта консольного типа, страница 5

Константные параметры

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

printf (const char* format,...);

означает, что не разрешено изменять значение строки format внутри тела printf. Заодно отметьте, что многоточие при объявлении прототипа функции означает, что количество параметров не определено и может быть произвольным. Прототип известной функции копирования символьных строк

char* strcpy (char* dest, const char* source);

ясно указывает, какую строку и куда она собирается копировать. Обратите внимание, как компактно выглядит оператор копирования константной строки символов buff в динамически выделенную область памяти и отныне адресуемую переменной char* FileName.

char buff[256], *FileName;

gets(buff);

FileName = strcpy(new char[strlen(buff)+1],buff);

Функция strlen возвращает количество значащих символов в строке buff. Эта и другие функции работы со строками символов определены в файле string.h.

Многомерные массивы

Большая свобода в выборе средств манипуляции элементами массивов, предоставляемая языками С и C++, иногда вносит путаницу в понимание основных положений, которые позволяют эффективно работать с массивами более высоких размерностей. Действительно, надо затратить некоторые усилия, чтобы осознать эквивалентность следующих выражений: arr[2] и 2[arr] при условии, что был объявлен массив, например, double arr[6];. Или эквивалентность a[i][j] и *(*(a+i)+j) при условии, что был объявлен двухмерный массив, например, int a[3][4];. Следует отметить, что можно никогда не использовать выражения типа 2[arr] и, тем не менее, создавать работоспособные программы, но нужно уметь производить анализ выражений такого типа, чтобы понять как компилятор интерпретирует переменные с индексами.

Как было отмечено выше, если объявлен одномерный массив, например, float arr[6];, то имя массива arr (без последующего индекса) может быть использовано как константный указатель на его первый элемент. Особенностью языков С и C++ является тот факт, что выражение arr[5] трактуется компилятором, как *(arr+5). Действительно, так как arr — адрес начала массива, то arr+5 означает (с учетом правил адресной арифметики) адрес шестого, последнего элемента массива. Следовательно, *(arr+5) — это содержимое по адресу arr+5, то есть равенство arr[5]==*(arr+5) истинно. Теперь проанализируем, как компилятор трактует выражение 5[arr]. Сначала он преобразовывает 5[arr] в *(5+arr), после чего очевидно:

  5[arr] Ы *(5+arr) Ы *(arr+5) Ы arr[5]

Таким образом, имя массива и его индекс можно менять местами, а результат при этом остается тем же. Неслыханная вещь для других языков программирования. Рассмотрим теперь двухмерный массив int a[2][3];. Встретив описание такого типа, компилятор отводит в памяти место для линейного размещения массива в виде последовательности ячеек.

1-й элемент

Последний эл-т

1-й элемент

Последний элемент

1-й строки

1-й строки

2-й строки

последней строки

Ї

Ї

Ї

Ї

a[0][0]

a[0][1]

a[0][2]

a[1][0]

a[1][1]

a[1][2]

Двухмерный массив рассматривается как массив массивов. Элементами главного массива из двух элементов являются одномерные массивы, каждый из трех элементов типа int. В языке имеют смысл такие объекты:

¨  a[0][0] — первый элемент массива типа int;

¨  a[0] — адрес первого элемента массива типа int*;

¨  a — адрес первой строки массива типа int**.

Выражение a+1 означает адрес второй строки массива, то есть адрес, сдвинутый на один элемент массива массивов, а таким элементом является строка двухмерного массива. Выражение a+1 подразумевает сдвиг от a на размер одной строки, (а не на размер числа типа int). Адресная арифметика всегда осуществляется в единицах базового типа данных. Теперь такой единицей является строка двухмерного массива или массив целых из трех элементов. Имеют место следующие равенства:

a==&a[0],     a[0]==&a[0][0]

Если вывести содержимое этих адресов: a, &a[0], a[0], &a[0][0], (в формате %p), то мы, может быть с удивлением, обнаружим, что все они представляют собой одно и то же число, являющееся адресом первого элемента. Но, несмотря на численное равенство, объекты из первого равенства и объекты из второго равенства, например a[0] и a, принадлежат к разным типам и их не следует смешивать в одном выражении, так как, несмотря на численное равенство, это объекты разной природы. Поучительно в режиме пошагового выполнения просмотреть следующую программу:

void main()

{

int a[2][3] = { {1,2,3}, {4,5,6}};

printf("\n **a  = %d\t\t a[0][0] = %d"

   "\n a    = %p\t a[0]    = %p"

    "\n &a[0]= %p\t &a[0][0]= %p"

  "\n a+1  = %p\t a[0]+1  = %p\n",

   **a,a[0][0],a,a[0], &a[0],&a[0][0],a+1,a[0]+1);

printf("\na   == &a[0]    = %d\na[0]== &a[0][0] = %d\n\n",

  a==&a[0],a[0]==&a[0][0]);

}

Массив a[2][3] можно инициализировать в точке его определения так, как показано в примере. Первым параметром функции printf всегда является строка символов. Несмотря на переносы в тексте, все части одной строки склеятся при компиляции. Результат работы первой printf занимает четыре строки на экране. Первая строка вывода будет такой: **a=1 a[0][0]=1. Так как a является адресом адреса, то понадобилась двойная разадресация (**a) для того, чтобы добраться до первого элемента массива. Таким образом, выражение **a==a[0][0] является истинным. Вторая и третья строки дают одинаковые численные результаты. Все числа являются адресом первого элемента массива. Они могут быть, например: 0012FF68. Четвертая строка вывода дает два разных числа: первое — адрес первого элемента второй строки, второе — адрес второго элемента первой строки, например: