Бьерн Страуструп. Язык программирования С++



бет33/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   29   30   31   32   33   34   35   36   ...   124

4.6.3 Передача параметров


При вызове функции выделяется память для ее формальных параметров,

и каждый формальный параметр инициализируется значением

соответствующего фактического параметра. Семантика передачи

параметров тождественна семантике инициализации. В частности, сверяются

типы формального и соответствующего ему фактического параметра, и

выполняются все стандартные и пользовательские преобразования типа.

Существуют специальные правила передачи массивов ($$4.6.5).

Есть возможность передать параметр, минуя контроль типа ($$4.6.8),

и возможность задать стандартное значение параметра ($$4.6.7).

Рассмотрим функцию:
void f(int val, int& ref)

{

val++;



ref++;

}
При вызове f() в выражении val++ увеличивается локальная копия

первого фактического параметра, тогда как в ref++ - сам второй

фактический параметр увеличивается сам. Поэтому в функции


void g()

{

int i = 1;



int j = 1;

f(i,j);


}
увеличится значение j, но не i. Первый параметр i передается по

значению, а второй параметр j передается по ссылке. В $$2.3.10

мы говорили, что функции, которые изменяют свой передаваемый

по ссылке параметр, труднее понять, и что поэтому лучше их избегать

(см. также $$10.2.2). Но большие объекты, очевидно, гораздо

эффективнее передавать по ссылке, чем по значению. Правда можно

описать параметр со спецификацией const, чтобы гарантировать, что

передача по ссылке используется только для эффективности, и

вызываемая функция не может изменить значение объекта:
void f(const large& arg)

{

// значение "arg" нельзя изменить без явных



// операций преобразования типа

}
Если в описании параметра ссылки const не указано, то это

рассматривается как намерение изменять передаваемый объект:
void g(large& arg); // считается, что в g() arg будет меняться
Отсюда мораль: используйте const всюду, где возможно.

Точно так же, описание параметра, являющегося указателем, со

спецификацией const говорит о том, что указуемый объект не будет

изменяться в вызываемой функции. Например:


extern int strlen(const char*); // из

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); // ошибка: здесь нужно преобразовывать тип
}

4.6.4 Возвращаемое значение


Если функция не описана как 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).





Достарыңызбен бөлісу:
1   ...   29   30   31   32   33   34   35   36   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет