C++. Бархатный путь. Часть 1 - Массивы и параметры
ОГЛАВЛЕНИЕ
Массивы и параметры
В C++ возможно лишь поэлементное копирование массивов. Этим объясняется то обстоятельство, что в списке объявлений параметров не объявляются параметры-массивы. В Borland С++ 4.5 транслятор спокойно реагирует на объявление одномерного массива в заголовке функции, проверяет корректность его объявления (размеры массива должны быть представлены константными выражениями), однако сразу же игнорирует эту информацию. Объявление одномерного массива-параметра преобразуется к объявлению указателя. Подтверждением этому служит тот факт, что "массив"-параметр невозможно проинициализировать списком значений, что совершенно нормально для обычных массивов:
void ff(int keyArr[ ] = {0,1,2,3,4,5,6,7,8,9});// Ошибка объявления.
void ff(int keyArr[10] = {0,1,2,3,4,5,6,7,8,9});// Ошибка объявления.
Оба варианта прототипа функции будут отвергнуты. При этом транслятор утверждает, что указателю (и это несмотря на явное указание размеров массива!) можно присваивать значение адреса, либо NULL.
int keyArr[100]; // Глобальный массив.
int xArr[5]; // Ещё один глобальный массив.
int XXX; // Простая переменная.
void ff(int keyArr[ 1] = keyArr, //Объявление одноименного параметра.
int pArr1 [10] = xArr,
int pArr2 [ ] = &XXX, // Адрес глобальной переменной.
int pArr3 [ ] = &xArr[10], //Адрес несуществующего элемента.
int pArr4 [50] = NULL);
/* Допустимые способы инициализации массивов в прототипе функции
свидетельствуют о том, что здесь мы имеем дело с указателями. */
Следующий пример подтверждает тот факт, что объявление одномерного массива в списке параметров оказывается на самом деле объявлением указателя.
#include <iostream.h>
void fun(int *, int[], int qwe[10] = NULL);
/* Все три объявления параметров на самом деле являются объявлениями указателей. */
void main()
{
int Arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *pArr = Arr;
/* В функции main определены массив и указатель.*/
cout << Arr << " " << &Arr << " " << &Arr[0] << endl;
cout << pArr << " " << &pArr << " " << &pArr[0] << endl;
/* Разница между массивом и указателем очевидна: значение выражения,
представленного именем массива, собственный адрес массива и адрес
первого элемента массива совпадают. */
fun(Arr, Arr, Arr);
}
void fun(int* pArr1, int pArr2[], int pArr3[100])
{
cout << sizeof(pArr1) << endl;
cout << sizeof(pArr2) << endl;
cout << sizeof(pArr3) << endl;
cout << pArr1 << " " << &pArr1 << " " << &pArr1[0] << endl;
cout << pArr2 << " " << &pArr2 << " " << &pArr2[0] << endl;
cout << pArr3 << " " << &pArr3 << " " << &pArr3[0] << endl;
/* Все параметры проявляют свойства указателей. */
}
Так что размеры массива в объявлении параметра, подобно имени параметра в прототипе, являются лишь украшением, которое предназначается для напоминания программисту о назначении параметра.
При вызове функции передаются либо отдельные элементы массива и тогда мы имеем тривиальный список параметров, либо адреса, которые воспринимаются как адреса начальных элементов массивов. В последнем случае неизвестными оказываются размеры массива, однако, эта проблема решается благодаря введению дополнительного целочисленного параметра, задающего размеры массива, представленного указателем.
Следующий пример демонстрирует возможный вариант решения проблемы передачи в вызываемую функцию переменного количества однотипных значений. Подлежащие обработке данные в вызывающей функции располагаются в непрерывной области памяти (в нашем примере это целочисленный массив Arr). При этом обрабатывающая функция имеет два параметра, один из которых является указателем на объект обрабатываемого типа (в определении функции закомментированы альтернативные варианты объявления этого параметра), второй - целочисленного типа. В выражении вызова значением первого параметра оказывается адрес первого элемента массива, значением второго параметра - количество обрабатываемых элементов массива. Таким образом, функция с постоянным количеством параметров позволяет обрабатывать заранее неизвестное количество значений.
#include <iostream.h>
void fun(int * = NULL, int = 0);
void main()
{
int Arr[10] = {0,1,2,3,4,5,6,7,8,9};
fun(Arr, 10);
fun(Arr, sizeof(Arr)/sizeof(Arr[0]));
}
void fun(int* pArr /* int pArr[] */ /* int pArr[150] */, int key)
{
for ( key--; key >= 0; key--) cout << pArr[key] << endl;
}
Фактическое тождество одномерного массива и указателя при объявлении параметров определяет специфику объявления многомерных массивов-параметров. В C++ многомерный массив - понятие условное. Как известно, массив размерности n является одномерным массивом множества объектов производного типа - массивов размерности n-1. Размерность массива является важной характеристикой производного типа. Отсюда - особенности объявления многомерных массивов как параметров функций.
В следующем примере определена функция fun с трёхмерным параметром размерности 5*5*25. Транслятор спокойно реагирует на различные варианты прототипов функции fun в начале программы. Если последовательно комментировать варианты объявлений функции, ошибка будет зафиксирована лишь тогда, когда будут закомментированы все объявления, у которых характеристика второй и третьей размерности совпадает с аналогичной характеристикой многомерного параметра-массива в определении функции.
#include <iostream.h>
#define DIM1 3
#define DIM2 5
// void fun(int rrr[][][]);
/* Такой прототип неверен! Квадратные скобки в объявлении параметра,
начиная со второй, обязательно должны содержать константные выражения,
значения которых должны соответствовать значениям в квадратных скобках
(начиная со второй!) в объявлении параметра в определении функции. Эти
значения в контексте объявления параметров являются элементами
спецификации ТИПА параметра, а не характеристиками его РАЗМЕРОВ. Типы
составляющих одномерные массивы элементов в прототипе и заголовке
определения функции должны совпадать. */
//void fun(int rrr[5][DIM1][DIM2]);
void fun(int rrr[][3][5]);
void fun(int rrr[15][DIM1][5]);
void fun(int *rrr[3][DIM2]);
/* Во всех этих случаях параметр rrr является указателем на двумерный
массив из 3*5 элементов типа int. "Массив из трёх по пять элементов типа
int" - такова спецификация типа объекта. */
/* Следующие два прототипа, несмотря на одно и то же имя функции,
объявляют ещё пока неопределённые фунции. Одноимённые функции с
различными списками параметров называются перегруженными функциями. */
void fun(int *rrr[25][250]);
void fun(int rrr[50][100][DIM1]);
void main()
{
int Arr[2][DIM1][DIM2] = {
{
{1 ,2 ,3 ,4 ,5 },
{10,20,30,40,50},
{11,12,13,14,15},
},
{
{1,},
{2,},
{3,},
}
};
fun(Arr); // Вызов fun. Значение параметра - адрес начала массива.
}
void fun(int pArr[75][DIM1][DIM2])
{
cout << sizeof(pArr) << endl;
cout << pArr << " " << &pArr << " " << &pArr[0][0] << endl;
/* Параметр проявляет свойства указателей. */
cout << sizeof(*pArr) << endl;
cout << *pArr << " " << &*pArr << " " << &*pArr[0][0] << endl;
/* Если применить к указателю операцию разыменования, можно убедиться в том,
что параметр указывает на массив. При этом о топологии многомерного массива
можно судить исключительно по косвенной информации (в данном случае - по
значениям константных выражений DIM1 и DIM2) или по значениям дополнительных
параметров. */
}
При работе с параметрами-массивами мы имеем дело с указателями. Это немаловажное обстоятельство позволяет непосредственно из вызываемой функции изменять значения объектов, определённых в вызывающей функции.