С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 54
Текст из файла (страница 54)
Указатель же в течение своейжизни способен адресовать разные объекты или не адресовать вообще.Поскольку указатель может содержать, а может и не содержать адрес какого-либо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 некоторое значение// ошибка: ссылка не может быть равной 0Type obj2 = operate( objl, 0 );объект. Например:}Если параметр должен ссылаться на разные объекты во время выполнения функции илипринимать нулевое значение (ни на что не ссылаться), нам следует использоватьуказатель.Одна из важнейших сфер применения параметров-ссылок – эффективная реализацияперегруженных операций.
При этом использование операций остается простым иинтуитивно понятным. (Подробнее данный вопрос рассматривается в главе 15.) Разбереммаленький пример. Представим себе класс Matrix (матрица). Хорошо бы реализоватьMatrix a, b, c;операции сложения и присваивания “привычным” способом:c = a + b;Эти операции реализуются с помощью перегруженных операторов – функций с немногонеобычным именем.
Для оператора сложения такая функция будет называтьсяMatrix// тип возврата - Matrixoperator+(// имя перегруженного оператораMatrix m1,// тип левого операндаMatrix m2// тип правого операнда){Matrix result;// необходимые действияreturn result;operator+. Посмотрим, как ее определить:}При такой реализации сложение двух объектов типа Matrix выглядит вполне привычно:a + b;но, к сожалению, оказывается совершенно неэффективным. Заметим, что параметры унас передаются по значению. Содержимое двух матриц будет копироваться в областьактивации функции operator+(), а поскольку объекты типа Matrix весьма велики,затраты времени и памяти на создание копий могут быть совершенно неприемлемыми.327С++ для начинающихПредставим себе, что мы решили использовать указатели в качестве параметров, чтобы// реализация с параметрами-указателями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 выглядит вполне привычно:328С++ для начинающих329a + 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";используется дополнительный параметр:}Другой способ сообщить функции размер массива-параметра – объявить параметр какссылку. В этом случае размер становится частью типа, и компилятор может проверитьаргумент в полной мере.330С++ для начинающих// параметр - ссылка на массив из 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 ){// так же, как и раньшемассивами разных типов и размеров:}Параметры шаблона заключаются в угловые скобки.