Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

int *&v1;

должно читаться справа налево: v1 является ссылкой на указатель на объект типа int. Модифицируем функцию main(), которая вызывала rswap(), для проверки работы

#include <iostream>

void ptrswap( int *&vl, int *&v2 );

int main() { int i = 10; int j = 20;

int *pi = &i; int *pj = &j;

cout << "Перед ptrswap():\tpi: " << *pi << "\tpj: " << *pj <<

endl;

ptrswap( pi, pj );

cout << "После ptrswap():\tpi: "

<< *pi << "\tpj: " << pj << endl;

return 0;

ptrswap():

}

Вот результат работы программы:

 

Перед

ptrswap():

pi:

10

pj:

20

 

 

 

 

После

ptrswap():

pi:

20

pj:

10

 

 

 

 

 

 

 

 

 

7.3.2. Параметры-ссылки и параметры-указатели

Когда же лучше использовать параметры-ссылки, а когда – параметры-указатели? В конце концов, и те и другие позволяют функции модифицировать объекты, эффективно передавать в функцию большие объекты типа класса. Что выбрать: объявить параметр ссылкой или указателем?

Как было сказано в разделе 3.6, ссылка может быть один раз инициализирована значением объекта, и впоследствии изменить ее нельзя. Указатель же в течение своей жизни способен адресовать разные объекты или не адресовать вообще.

Поскольку указатель может содержать, а может и не содержать адрес какого-либо объекта, перед его использованием функция должна проверить, не равен ли он нулю:

class X;

void manip( X *px )

{

// проверим на 0 перед использованием if ( px != 0 )

// обратимся к объекту по адресу...

}

Параметр-ссылка не нуждается в этой проверке, так как всегда существует именуемый

class Type { };

void operate( const Type& p1, const Type& p2 );

int main() { Type obj1;

//присвоим objl некоторое значение

//ошибка: ссылка не может быть равной 0 Type obj2 = operate( objl, 0 );

ею объект. Например:

}

Если параметр должен ссылаться на разные объекты во время выполнения функции или принимать нулевое значение (ни на что не ссылаться), нам следует использовать указатель.

Одна из важнейших сфер применения параметров-ссылок – эффективная реализация перегруженных операций. При этом использование операций остается простым и интуитивно понятным. (Подробнее данный вопрос рассматривается в главе 15.) Разберем маленький пример. Представим себе класс Matrix (матрица). Хорошо бы реализовать

Matrix a, b, c;

операции сложения и присваивания “привычным” способом: c = a + b;

Эти операции реализуются с помощью перегруженных операторов – функций с немного необычным именем. Для оператора сложения такая функция будет называться operator +. Посмотрим, как ее определить:

Matrix

//

тип

возврата - Matrix

operator+(

//

имя

перегруженного

оператора

Matrix m1, // тип левого операнда Matrix m2 // тип правого операнда

)

{

Matrix result;

// необходимые действия return result;

}

При такой реализации сложение двух объектов типа Matrix выглядит вполне привычно:

a + b;

но, к сожалению, оказывается совершенно неэффективным. Заметим, что параметры у нас передаются по значению. Содержимое двух матриц будет копироваться в область активации функции operator+(), а поскольку объекты типа Matrix весьма велики, затраты времени и памяти на создание копий могут быть совершенно неприемлемыми.

Представим себе, что мы решили использовать указатели в качестве параметров, чтобы

// реализация с параметрамиуказателями

operator+( Matrix *ml, Matrix *m2 )

{

Matrix result;

// необходимые действия return result;

избежать этих затрат. Вот модифицированный код operator+():

}

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

&a + &b; // допустимо, хотя и плохо

Хотя такая форма не может не вызвать критику, но все-таки два объекта сложить еще

//а вот это не работает

//&a + &b возвращает объект типа Matrix

удается. А вот три уже крайне затруднительно:

&a + &b + &c;

Для того чтобы сложить три объекта, при подобной реализации нужно написать так:

// правильно: работает, однако ...

&( &a + &b ) + &c;

Трудно ожидать, что кто-нибудь согласится писать такие выражения. К счастью, параметры-ссылки дают именно то решение, которое требуется. Если параметр объявлен как ссылка, функция получает его l-значение, а не копию. Лишнее копирование исключается. И тип фактического аргумента может быть Matrix – это упрощает операцию сложения, как и для встроенных типов. Вот схема перегруженного оператора

// реализация с параметрами-ссылками operator+( const Matrix &m1, const Matrix

&m2 )

{

Matrix result;

// необходимые действия return result;

сложения для класса Matrix:

}

При такой реализации сложение трех объектов Matrix выглядит вполне привычно:

a + b + c;

Ссылки были введены в С++ именно для того, чтобы удовлетворить двум требованиям: эффективная реализация и интуитивно понятное применение.

7.3.3. Параметры-массивы

Массив в С++ никогда не передается по значению, а только как указатель на его первый, точнее нулевой, элемент. Например, объявление

void putValues( int[ 10 ] );

рассматривается компилятором так, как будто оно имеет вид

void putValues( int* );

Размер массива неважен при объявлении параметра. Все три приведенные записи

// три эквивалентных объявления putValues()

void putValues( int* ); void putValues( int[] );

эквивалентны:

void putValues( int[ 10 ] );

Передача массивов как указателей имеет следующие особенности:

изменение значения аргумента внутри функции затрагивает сам переданный объект, а не его локальную копию. Если такое поведение нежелательно, программист должен позаботиться о сохранении исходного значения. Можно также при объявлении функции указать, что она не должна изменять значение параметра, объявив этот параметр константой:

void putValues( const int[ 10 ] );

размер массива не является частью типа параметра. Поэтому функция не знает реального размера передаваемого массива. Компилятор тоже не может это

void putValues( int[ 10 ] ); // рассматривается как int* int main() {

int i, j [ 2 ];

putValues( &i ); // правильно: &i is int*;

// однако при выполнении возможна ошибка putValues( j ); // правильно: j - адрес 0-го элемента -

int*;

проверить. Рассмотрим пример:

// однако при выполнении возможна ошибка

При проверке типов параметров компилятор способен распознать, что в обоих случаях тип аргумента int* соответствует объявлению функции. Однако контроль за тем, не является ли аргумент массивом, не производится.

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

void putValues( int[], int size );

int main() {

int i, j[ 2 ]; putValues( &i, 1 ); putValues( j, 2 ); return 0;

параметр функции. Например:

}

putValues() печатает элементы массива в следующем формате:

( 10 )< 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >

где 10 – это размер массива. Вот как выглядит реализация putValues(), в которой используется дополнительный параметр:

#include <iostream>

const lineLength =12; // количество элементов в строке void putValues( int *ia, int sz )

{

cout << "( " << sz << " )< "; for (int i=0;i<sz; ++i )

{

if ( i % lineLength == 0 && i )

cout << "\n\t"; // строка заполнена

cout << ia[ i ];

//разделитель, печатаемый после каждого элемента,

//кроме последнего

if ( i % lineLength != lineLength-1 && i != sz-1 )

cout << ", ";

}

cout << " >\n";

}

Другой способ сообщить функции размер массива-параметра – объявить параметр как ссылку. В этом случае размер становится частью типа, и компилятор может проверить

// параметр - ссылка на массив из 10 целых void putValues( int (&arr)[10] );

int main() {

int i, j [ 2 ]; putValues(i); // ошибка:

// аргумент не является массивом из 10

целых putValues(j); // ошибка:

// аргумент не является массивом из 10

целых return 0;

аргумент в полной мере.

}

Поскольку размер массива теперь является частью типа параметра, новая версия putValues() способна работать только с массивами из 10 элементов. Конечно, это ограничивает ее область применения, зато реализация значительно проще:

#include <iostream>

void putValues( int (&ia)[10] )

{

cout << "( 10 )< ";

for ( int 1 =0; i < 10; ++i ) { cout << ia[ i ];

//разделитель, печатаемый после каждого элемента,

//кроме последнего

if ( i != 9 ) cout << ", ";

}

cout << " >\n";

}

Еще один способ получить размер переданного массива в функции – использовать абстрактный контейнерный тип. (Такие типы были представлены в главе 6. В следующем подразделе мы поговорим об этом подробнее.)

Хотя две предыдущих реализации putValues() правильны, они обладают серьезными недостатками. Так, первый вариант работает только с массивами типа int. Для типа double* нужно писать другую функцию, для long* – еще одну и т.д. Второй вариант производит операции только над массивом из 10 элементов типа int. Для обработки массивов разного размера нужны дополнительные функции. Лучшим решением было бы использовать шаблон – функцию, или, скорее, обобщенную реализацию кода целого семейства функций, которые отличаются только типами обрабатываемых данных. Вот как можно сделать из первого варианта putValues() шаблон, способный работать с

template <class Type>

void putValues( Type *ia, int sz )

{

// так же, как и раньше

массивами разных типов и размеров:

}

Параметры шаблона заключаются в угловые скобки. Ключевое слово class означает, что идентификатор Type служит именем параметра, при конкретизации шаблона функции putValues() он заменяется на реальный тип – int, double, string и т.д. (В главе 10 мы продолжим разговор о шаблонах функций.)

Параметр может быть многомерным массивом. Для такого параметра должны быть заданы правые границы всех измерений, кроме первого. Например:

putValues( int matrix[][10], int rowSize );

Здесь matrix объявляется как двумерный массив, который содержит десять столбцов и неизвестное число строк. Эквивалентным объявлением для matrix будет:

int (*matrix)[10]

Многомерный массив передается как указатель на его нулевой элемент. В нашем случае тип matrix – указатель на массив из десяти элементов типа int. Как и для одномерного