Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции C++.doc
Скачиваний:
7
Добавлен:
01.05.2025
Размер:
1.44 Mб
Скачать

4.3 Локализация создания объектов

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

Для того чтобы увидеть преимущества, предоставляемые объявлением локальных объектов вблизи точки их первого использования, рассмотрим следующую версию программы timer. В ней два объекта b и c создаются, используя информацию, получаемую во время выполнения программы как раз перед первым использованием этих объектов. Программа также иллюстрирует преимуще­ства перегрузки конструкторов, что позволяет реализовать различные способы инициализации.

#include <iostream.h>

#include <stdlib.h>

#include <time.h>

class timer

{

//аналогично прошлому примеру

};

int main()

{

timer a(10);

a.run();

cout << "Enter number of seconds: ";

char str[80];

cin >> str;

timer b(str); // инициализация во время выполнения с помощью строки

b.run();

cout << "Enter minutes and seconds: ";

int min, sec;

cin >> min >> sec;

timer с(min, sec); /* инициализация во время выполнения с помощью минут и секунд */

c.run();

return 0;

}

Как можно видеть, объект а создан с использованием целой константы. В то же время объекты b и с созданы с использованием информации, введенной пользователем. Поэтому они не объявля­лись до тех пор, пока эта информация не была известна. Таким образом, объекты b и с созданы с использованием данных, которые были сформированы как раз перед точкой их создания. Для объекта b в качестве таких данных выступает строка, в которой записаны секунды. Для объекта с данными служат два целых числа, с помощью которых вводятся минуты и секунды. Поскольку допустимы различные форматы инициализации, то нет необходимости выполнять преобразова­ния данных от одного типа к другому при инициализации объекта. Из сказанного можно за­ключить, что целесообразно создавать объекты перед точкой их первого использования.

4.4 Перегрузка функций и неопределенность

При перегрузке функций возможно появление ошибок такого типа, какие нам раньше не встре­чались. Можно создать ситуацию, в которой компилятор не сможет выбрать между двумя или более перегруженными функциями. Когда такое происходит, говорят, что ситуация неопределенна, двусмысленна (ambiguous). Неопределенные инструкции являются ошибками, и программа, содержащая неопределенности, не будет откомпилирована.

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

int F(double d);

cout << F('с'); // ошибки не происходит из-за преобразования

Как указано в комментарии, данный код не является ошибочным, потому что С автоматически конвертирует символ с в его эквивалент типа double. В C++ только немногие из подобных преоб­разований запрещены. Хотя автоматическое преобразование типов является очень удобным, оно служит наиболее распространенной причиной неопределенности. Например, рассмотрим следу­ющую программу:

#include <iostream.h>

float F(float i) { return i; }

double F(double i) { return -i; }

int main()

{

cout << F(10.1) << " "; // неопределенности нет, вызов F(double)

cout << F(10); // неопределенность

return 0;

}

Здесь функция F() перегружена, так что она может иметь в качестве аргументов переменные типа float или double. Первая строка в функции main() не вызывает неопределенности, поскольку число 10.1 автоматически преобразуется в C++ к типу double и вызывается функция F() с аргументом типа double. Однако когда функция F() вызывается с использованием числа 10, возникает неопределенность, поскольку компилятор не может определить, приводить это число к типу float или типу double. Это вызывает сообщение об ошибке, и программа не компилируется.

Как показывает предыдущий пример, неопределенность вызвана не перегрузкой функции F, а вызовом этой функции с неопределенным типом аргумента, т.е. особенностями ее вызова.

Ниже приведен другой пример неопределенности, вызванной автоматическим преобразовани­ем типа в языке C++:

#include <iostream.h>

char F(unsigned char ch) { return ch-1; }

char F(char ch) { return ch+1; }

int main()

{

cout << F('c'); // вызов F(char)

cout << F(88) << " "; // неопределенность

return 0;

}

В C++ unsigned char и char не являются двусмысленными. Тем не менее, когда функция F() вызывается с числом 88, компилятор не знает, какую из функций вызывать. Иными словами, следует ли 88 преобразовывать к char или к unsigned char?

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

#include <iostream.h>

int F(int i) { return i; }

int F(int i, int j = 1) { return i*j; }

int main()

{

cout << F(4, 5) << " "; // неопределенности нет

cout << F(10); // неопределенность

return 0;

}

Здесь при первом вызове функции F() указываются два аргумента, так что неопределенность не возникает и вызывается функция F(int i, int j). Однако при втором вызове функции F() возникает двусмысленность, поскольку компилятор не знает, вызывать ли функцию F() с одним аргументом, или же использовать функцию с двумя аргументами, у которой второй аргумент принимает значение по умолчанию.