Освой самостоятельно С++ за 21 день - Джесс Либерти 26 стр.


Вопросы и ответы

В чем состоит преимущество работы с указателями?

На этом занятии вы узнали, насколько удобно использовать доступ к объекту по его адресу и передавать параметры как ссылки. На занятии 13 будет рассмотрена роль указателей в полиморфизме классов.

Чем удобно размещение объектов в динамической области памяти?

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

Зачем ограничивать права доступа к объекту, объявляя его константным?

Следует использовать все средства, позволяющие предотвратить появление ошибок. На практике достаточно сложно отследить, в какой момент и какой функцией изменяется объект. Использование спецификатора const позволяет решить эту проблему.

Коллоквиум

В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний, а также рассматривается ряд упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов.

Контрольные вопросы

1. Какой оператор используется для получения адреса переменной?

2. Какой оператор позволяет получить значение, записанное по адресу, содержащемуся в указателе?

3. Что такое указатель?

4. В чем различие между адресом, который хранится в указателе, и значением, записанным по этому адресу?

5. В чем различие между оператором разыменования и оператором получения адреса?

6. В чем различие между следующими объявлениями: const int * ptrOne и int * const ptrTwo?

Упражнения

1. Объясните смысл следующих объявлений переменных:

• int * pOne

• int vTwo

• int * pThree = &vTwo

2. Допустим, в программе объявлена переменная yourAge типа unsigned short. Как объявить указатель, позволяющий манипулировать этой переменной?

3. С помошью указателя присвойте переменной yourAge значение 50.

4. Напишите небольшую программу и объявите в ней переменную типа int и указатель на этот тип. Сохраните адрес переменной в указателе. Используя указатель, присвойте переменной какое-либо значение,

5. Жучки: найдите ошибку в следующем фрагменте программы.

#include <iostream,h>

int main()

{

int *pInt;

*pInt = 9;

cout << "The value at pInt: " << *pInt;

return 0;

}

6. Жучки: найдите ошибку в следующем фрагменте программы.

int main()

{

int SomeVariable = 5;

cout << "SomeVariable: " << SomeVariable << "\n";

int *pVar = & SomeVariable;

pVar = 9;

cout << "SomeVariable: " << *pVar << "\n";

return 0;

}

День 9-й. Ссылки

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

• Что представляют собой ссылки

• Чем ссылки отличаются ог указателей

• Как создать ссылки и использовать их

• Какие ограничения есть у ссылок

• Как по ссылке передаются значения и объекты в функции и из функций

Что такое ссылка

Ссылка - это то же, что и псевдоним. При создании ссылки мы инициализируем ее с помощью имени другого объекта, адресата. С этого момента ссылка действует как альтернативное имя данного объекта, поэтому все, что делается со ссылкой, в действительности происходит с этим объектом.

Для объявления ссылки нужно указать типа объекта адресата, за которым следует оператор ссылки (&), а за ним - имя ссылки. Для ссылок можно использовать любое легальное имя переменной, но многие программисты предпочитают со всеми именами ссылок использовать префикс "г". Так, если у вас есть целочисленная переменная с именем someInt, вы можете создать ссылку на эту переменную, написав

int &rSomeRef = someInt;

Это читается следующим образом: rSomeRef - это ссылка на целочисленное значение, инициализированная адресом переменной someInt. Создание и использование ссылок показано в листинге 9.1.

Примечание:Обратите внимание на то, что оператор ссылки (&) выглядит так же, как оператор адреса, который используется для возвращения адреса при работе с указателями. Однако это не одинаковые операторы, хотя очевидно, что они родственны.

Листинг 9.1. Создание и использование ссылок

1: // Листинг 9.1.

2: // Пример использования ссылок

3:

4: #include <iostream.h>

5:

6: int main()

7: {

8: int intOne;

9: int &rSomeRef = intOne;

10:

11: intOne = 5;

12: cout << "intOne: " << intOne << endl;

13: cout << "rSomeRef: " << rSomeRef << endl;

14:

15: rSomeRef = 7;

16: cout << "intOne: " << intOne << endl;

17: cout << "rSomeRef: " << rSomeRef << endl;

18: return 0;

19: }

Результат:

intOne: 5

rSomeRef: 5

intOne: 7

rSomeRef: 7

Анализ: В строке 8 объявляется локальная целочисленная переменная intOne, а в строке 9 - ссылка rSomeRef на некоторое целое значение, инициализируемая адресом переменной intOne. Если объявить ссылку, но не инициализировать ее, будет сгенерирована ошибка компиляции. Ссылки, в отличие от указателя, необходимо инициализировать при объявлении.

В строке 11 переменной intOne присваивается значение 5. В строках 12 и 13 выводятся на экран значения переменной intOne и ссылки rSomeRef - они, конечно же, оказываются одинаковыми.

В строке 15 ссылке rSomeRef присваивается значение 7. Поскольку мы имеем дело со ссылкой, а она является псевдонимом для переменной intOne, то число 7 в действительности присваивается переменной intOne, что и подтверждается выводом на экран в строках 16 и 17.

Использование оператора адреса (&) при работе со ссылками

Если использовать ссылку для получения адреса, она вернет адрес своего адресата. В этом и состоит природа ссылок. Они являются псевдонимами для своих адресатов. Именно это свойство и демонстрирует листинг 9.2.

Листинг 9.2. Взятие адреса ссылки

1: // Листинг 9.2.

2: // Пример использования ссылок

3:

4: #include <iostream.h>

5:

6: int main()

7: {

8: int intOne;

9: int &rSomeRef = intOne;

10:

11: intOne = 5;

12: cout << "intOne: " << intOne << endl;

13: cout << "rSomeRef: " << rSomeRef << endl;

14:

15: cout << "&intOne: " << &mtOne << endl;

16: cout << "&rSomeRef: " << &rSomeRef << endl;

17:

18: return 0;

19: }

Результат:

intOne: 5

rSomeRef: 5

&intOne: 0x3500

&rSomeRef: 0x3500

Примечание:Результаты работы программы на вашем компьютере могут отличаться от приведенных в последних двух строках.

Анализ: И вновь-таки ссылка rSomeRef инициализируется адресом переменной intOno. На этот раз выводятся адреса двух переменных, и они оказываются идентичными. В языке C++ не предусмотрено предоставление доступа к адресу самой ссылки, поскольку в этом нет смысла. С таким же успехом для этого можно было бы использовать указатель или другую переменную. Ссылки инициализируются при создании, и они всегда действуют как синонимы для своих адресатов, даже в том случае, когда применяется оператор адреса.

Например, если у вас есть класс с именем City, вы могли бы объявить объект этого класса следующим образом:

City boston;

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

City &beanTown = boston;

Существует только один класс City; оба идентификатора ссылаются на один и тот же объект одного и того же класса. Любое действие, которое вы предпримите относительно ссылки beanTown, будет выполнено также и над объектом boston.

Обратите внимание на различие между символом & в строке 9 листинга 9.2, который объявляет ссылку rSomeRef на значение типа int, и символами & в строках 15 и 16, которые возвращают адреса целочисленной переменной intOne и ссылки rSomeRef.

Обычно для ссылки оператор адреса не используется. Мы просто используем ссылку вместо связанной с ней переменной, как показано в строке 13.

Ссылки нельзя переназначать

Даже опытных программистов, которые хорошо знают правило о том, что ссылки нельзя переназначать и что они всегда являются псевдонимами для своих адресатов, может ввести в заблуждение происходящее при попытке переназначить ссылку. То, что кажется переназначением, оказывается присвоением нового значения адресату. Этот факт иллюстрируется в листинге 9.3.

Листинг 9.3. Присвоение значения ссылке

1: // Листинг 9.3

2: // Присвоение значения ссылке

3:

4: #include <iostream.h>

5:

6: int main()

7: {

8: int intOne;

9: int &rSomeRef = intOne;

10:

11: intOne: 5

12: cout << "intOne:\t" << intOne << endl;

13: cout << "rSomeRef:\t" << rSomeRef << endl;

14: cout << "&intOne:\t" << &intOne << endl;

15: cout << "&rSomeRef:\t" << &rSomeRef << endl;

16:

17: int intTwo = 8;

18: rSomeRef = intTwo; // не то что вы думаете

19: cout << "\nintOne:\t" << intOne << endl;

20: cout << "intTwo:\t" << intTwo << endl;

21: cout << "rSomeRef:\t" << rSomeRef << endl;

22: cout << "&intOne:\t" << &intOne << endl;

23: cout << "&intTwo:\t" << &intTwo << endl;

24: cout << "&rSomeRef:\t" << &rSomeRef << endl;

25: return 0;

26: }

Результат:

intOne: 5

rSomeRef: 5

&intOne: 0x213e

&rSomeRef: 0x213e

intOne: 8

int:Two: 8

rSomeRef: 8

&intOne: 0x213e

&intTwo: 0x2130

&rSomeRef: 0x213e

Анализ: Вновь в строках 8 и 9 объявляются целочисленная переменная и ссылка на целое значение. В строке 11 целочисленной переменной присваивается значение 5, а в строках 12-15 выводятся значения переменной и ссылки, а также их адреса.

В строке 17 создается новая переменная intTwo, которая тут же инициализируется значением 8. В строке 18 программист пытается переназначить ссылку rSomeRef так, чтобы она стала псевдонимом переменной intTwo, но этого не происходит. На самом же деле ссылка rSomeRef продолжает действовать как псевдоним переменной intOne, поэтому такое присвоение эквивалентно следующей операции:

intOne = intTwo;

Это кажется достаточно убедительным, особенно при выводе на экран значений переменной intOne и ссылки rSomeRef (строки 19- 21): их значения совпадают со значением переменной intTwo. На самом деле при выводе на экран адресов в строках 22-24 вы видите, что ссылка rSomeRef продолжает ссылаться на переменную intOne, а не на переменную intTwo.

Не рекомендуется:Не пытайтесь переназначить ссылку. Не путайте оператор адреса с оператором ссылки.

Рекомендуется:Используйте ссылки для создания псевдонимов объектов. Инициализируйте ссылки при объявлении.

На что можно ссылаться

Ссылаться можно на любой объект, включая нестандартные (определенные пользователем) объекты. Обратите внимание, что ссылка создается на объект, а не на класс. Нельзя объявить ссылку таким образом:

int & rIntRef = int; // неверно

Ссылку rIntRef нужно инициализировать, используя конкретную целочисленную переменную, например:

int howBig = 200: int & rIntRef = howBig;

Точно так же нельзя инициализировать ссылку классом CAT:

CAT & rCatRef = CAT; // неверно

Ссылку rCatRef нужно инициализировать, используя конкретный объект класса CAT:

CAT frisky;

CAT & rCatRef = frisky;

Ссылки на объекты используются точно так же, как сами объекты. Доступ к данным-членам и методам осуществляется с помощью обычного оператора доступа к членам класса (.), и, подобно встроенным типам, ссылка действует как псевдоним для объекта. Этот факт иллюстрируется в листинге 9.4.

Листинг 9.4. Ссылки на объекты класса

1: // Листинг 9.4.

2: // Ссылки на объекты класса

3:

4: #include <iostream.h>

5:

6: class SimpleCat

7: {

8: public:

9: SimpleCat(int age, int weight);

10: ~SimpleCat() {}

11: int GetAge() { return itsAge; }

12: int GetWeight() { return itsWeight; }

13: private:

14: int itsAge;

15: int itsWeight;

16: }

17:

18: SimpleCat::SimpleCat(int age, int weight)

19: {

20: itsAge = age;

21: itsWeight = weight;

22: }

23:

24: int main()

25: {

26: SimpleCat Fnsky(5,3);

27: SimpleCat & rCat = Fnsky;

28:

29: cout << "Frisky: ";

30: cout << Frisky.GetAge() << " years old. \n";

31: cout << "И Frisky весит: ";

32: cout << rCat.GetWeight() << " kilograms. \n";

33: return 0;

34: }

Результат:

Frisky: 5 years old.

И Frisky весит: 3 kilograms.

Анализ: В строке 26 объявляется переменная Frisky в качестве объекта класса SimplcCat. В строке 27 объявляется ссылка rCat на некоторый объект класса SimpleCat, и эта ссылка инициализируется с использованием уже объявленного объекта Frisky. В строках 30 и 32 вызываются методы доступа к членам класса SimpleCat, причем сначала это делается с помощью объекта класса SimpleCat (Frisky), а затем с помощью ссылки на объект класса SimpleCat (rCat). Обратите внимание, что результаты идентичны. Снова повторюсь: ссылка - это всего лишь псевдоним реального объекта.

Объявление ссылок

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

Пример 1:

int hisAge;

int &rAge = hisAge;

Пример 2:

CAT boots;

CAT &rCatRef = boots;

Нулевые указатели и нулевые ссылки

Когда указатели не инициализированы или когда они освобождены, им следует присваивать нулевое значение (0). Это не касается ссылок. На самом деле ссылка не может быть нулевой, и программа, содержащая ссылку на нулевой объект, считается некорректной. Во время работы некорректной программы может случиться все что угодно. Она может внешне вести себя вполне пристойно, но при этом удалит все файлы на вашем диске или выкинет еще какой-нибудь фокус.

Большинство компиляторов могут поддерживать нулевой объект, ничего не сообщая по этому поводу до тех пор, пока вы не попытаетесь каким-то образом использовать этот объект. Однако не советую пользоваться поблажками компилятора, поскольку они могут дорого вам обойтись во время выполнения программы.

Передача аргументов функций как ссылок

На занятии 5 вы узнали о том, что функции имеют два ограничения: аргументы передаются как значения и теряют связь с исходными данными, а возвращать функция может только одно значение.

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

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

На занятии 5 вы узнали, что параметры, передаваемые функции, помешаются в стек. Если функции передается значение как ссылка (с помощью либо указателей, либо ссылок), то в стек помещается не сам объект, а его адрес.

В действительности в некоторых компьютерах адрес хранится в специальном регистре, а в стек ничего не помещается. В любом случае компилятору известно, как добраться до исходного объекта, и при необходимости все изменения производятся прямо над объектом, а не над его временной копией.

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

Вспомните, что в листинге 5.5 (см. Занятие 5) демонстрировалось, что после обращения к функции swap() значения в вызывающей функции не изменялись. Исключительно ради вашего удобства этот листинг воспроизведен здесь еще раз (листинг 9.5).

Листинг 9.5. Демонстрация передачи по значению

1: // Листинг 9.5. Передача параметров как значений

2:

3: #include <iostrearn.h>

4:

5: void swap(int x, int у);

6:

7: int main()

8: {

9: int x = 5, у = 10;

10:

11: cout << "Main. Before swap, x: " << x << " у: " << у << "\n";

12: swap(x,у);

13: cout << "Main. After swap, x: " << x << " у: " << у << "\n";

14: return 0;

15: }

16:

17: void swap(int x, int у);

18: {

19: int temp;

20:

21: cout << "Swap. After swap, x; " << x << " у: " << у << "\n";

22:

23: temp = x;

24: x = у;

25: у = temp;

26:

Назад Дальше