соответствующего фактического параметра. Семантика передачи
параметров тождественна семантике инициализации. В частности, сверяются
выполняются все стандартные и пользовательские преобразования типа.
Существуют специальные правила передачи массивов ($$4.6.5).
и возможность задать стандартное значение параметра ($$4.6.7).
фактический параметр увеличивается сам. Поэтому в функции
значению, а второй параметр j передается по ссылке. В $$2.3.10
(см. также $$10.2.2). Но большие объекты, очевидно, гораздо
эффективнее передавать по ссылке, чем по значению. Правда можно
изменяться в вызываемой функции. Например:
extern char* strcpy(char* to, const char* from);
extern int strcmp(const char*, const char*);
Значение такого приема растет вместе с ростом программы.
Отметим, что семантика передачи параметров отличается от семантики
присваивания. Это различие существенно для параметров, являющихся
const или ссылкой, а также для параметров с типом, определенным
пользователем ($1.4.2).
Литерал, константу и параметр, требующий преобразования,
можно передавать как параметр типа const&, но без спецификации
const передавать нельзя. Допуская преобразования для параметра типа
const T&, мы гарантируем, что он может принимать значения из того же
множества, что и параметр типа T, значение которого передается
при необходимости с помощью временной переменной.
float fsqrt(const float&); // функция sqrt в стиле Фортрана
void g(double d)
{
float r;
r = fsqrt(2.0f); // передача ссылки на временную
// переменную, содержащую 2.0f
r = fsqrt(r); // передача ссылки на r
r = fsqrt(d); // передача ссылки на временную
// переменную, содержащую float(d)
}
Запрет на преобразования типа для параметров-ссылок без спецификации
const введен для того, чтобы избежать нелепых ошибок, связанных
с использованием при передаче параметров временных переменных:
void update(float& i);
void g(double d)
{
float r;
update(2.0f); // ошибка: параметр-константа
update(r); // нормально: передается ссылка на r
update(d); // ошибка: здесь нужно преобразовывать тип
}
Если функция не описана как void, она должна возвращать значение.
Например:
int f() { } // ошибка
void g() { } // нормально
Возвращаемое значение указывается в операторе return в теле функции.
Например:
int fac(int n) { return (n>1) ? n*fac(n-1) : 1; }
В теле функции может быть несколько операторов return:
int fac(int n)
{
if (n > 1)
return n*fac(n-1);
else
return 1;
}
Подобно передаче параметров, операция возвращения значения функции
эквивалентна инициализации. Считается, что оператор return
инициализирует переменную, имеющую тип возвращаемого значения.
Тип выражения в операторе return сверяется с типом функции, и
производятся все стандартные и пользовательские преобразования
типа. Например:
double f()
{
// ...
return 1; // неявно преобразуется в double(1)
}
При каждом вызове функции создается новая копия ее формальных
параметров и автоматических переменных. Занятая ими память после
выхода из функции будет снова использоваться, поэтому неразумно
возвращать указатель на локальную переменную. Содержимое памяти,
на которую настроен такой указатель, может измениться непредсказуемым
образом:
int* f()
{
int local = 1;
// ...
return &local; // ошибка
}
Эта ошибка не столь типична, как сходная ошибка, когда тип функции -
ссылка:
int& f()
{
int local = 1;
// ...
return local; // ошибка
}
К счастью, транслятор предупреждает о том, что возвращается ссылка
на локальную переменную. Вот другой пример:
int& f() { return 1; } // ошибка
4.6.5 Параметр-массив
Если в качестве параметра функции указан массив, то передается
указатель на его первый элемент. Например:
int strlen(const char*);
void f()
{
char v[] = "массив";
strlen(v);
strlen("Николай");
}
Это означает, что фактический параметр типа T[] преобразуется к типу T*,
и затем передается. Поэтому присваивание элементу формального
параметра-массива изменяет этот элемент. Иными словами,
массивы отличаются от других типов тем, что они не передаются
и не могут передаваться по значению.
В вызываемой функции размер передаваемого массива неизвестен.
Это неприятно, но есть несколько способов обойти данную трудность.
Прежде всего, все строки оканчиваются нулевым символом, и значит их
размер легко вычислить. Можно передавать еще один параметр,
задающий размер массива. Другой способ: определить
структуру, содержащую указатель на массив и размер массива, и
передавать ее как параметр (см. также $$1.2.5). Например:
void compute1(int* vec_ptr, int vec_size); // 1-ый способ
struct vec { // 2-ой способ
int* ptr;
int size;
};
void compute2(vec v);
Сложнее с многомерными массивами, но часто вместо них можно
использовать массив указателей, сведя эти случаи к одномерным
массивам. Например:
char* day[] = {
"mon", "tue", "wed", "thu", "fri", "sat", "sun"
};
Теперь рассмотрим функцию, работающую с двумерным массивом - матрицей.
Если размеры обоих индексов известны на этапе трансляции, то
проблем нет:
void print_m34(int m[3][4])
{
for (int i = 0; i<3; i++) {
for (int j = 0; j<4; J++)
cout << ' ' << m[i][j];
cout << '\n';
}
}
Конечно, матрица по-прежнему передается как указатель, а размерности
приведены просто для полноты описания.
Первая размерность для вычисления адреса элемента неважна
($$R.8.2.4), поэтому ее можно передавать как параметр:
void print_mi4(int m[][4], int dim1)
{
for ( int i = 0; i
for ( int j = 0; j<4; j++)
cout << ' ' << m[i][j];
cout << '\n';
}
}
Самый сложный случай - когда надо передавать обе размерности.
Здесь "очевидное" решение просто непригодно:
void print_mij(int m[][], int dim1, int dim2) // ошибка
{
for ( int i = 0; i
for ( int j = 0; j
cout << ' ' << m[i][j];
cout << '\n';
}
}
Во-первых, описание параметра m[][] недопустимо, поскольку для
вычисления адреса элемента многомерного массива нужно знать
вторую размерность. Во-вторых, выражение m[i][j]
вычисляется как *(*(m+i)+j), а это, по всей видимости, не то, что
имел в виду программист. Приведем правильное решение:
void print_mij(int** m, int dim1, int dim2)
{
for (int i = 0; i< dim1; i++) {
for (int j = 0; j
cout << ' ' << ((int*)m)[i*dim2+j]; // запутано
cout << '\n';
}
}
Выражение, используемое для выбора элемента матрицы, эквивалентно
тому, которое создает для этой же цели транслятор, когда известна
последняя размерность. Можно ввести дополнительную переменную,
чтобы это выражение стало понятнее:
int* v = (int*)m;
// ...
v[i*dim2+j]
Лучше такие достаточно запутанные места в программе упрятывать.
Можно определить тип многомерного массива с соответствующей
операцией индексирования. Тогда пользователь может и не знать, как
размещаются данные в массиве (см. упражнение 18 в $$7.13).
Достарыңызбен бөлісу: