Язык C++ позволяет выполнять преобразование значения одного типа в значение другого типа. Преобразование типа бывает явным и неявным. Явное преобразование называется приведением типов. При приведении типов перед преобразовываемой переменной в скобках указывается имя типа, к которому она приводится. Например:
int iVar;
float fVar;
fVar = 10*(float)iVar; /*перед умножением выполняется приведение типа int переменной iVar к типу float, однако тип переменной iVar после вычисления выражения останется неизменным, т.е. int*/
Преобразование типов выполняется для значений переменных при вычислении выражений и оказывает влияние на тип результата, но не изменяет типа самих переменных, участвующих в выражении. Преобразование типов между базовыми типами и преобразование типа для указателей, ссылок и указателей на члены производного типа называется стандартным преобразованием типов. В языке C++ автоматически выполняется стандартное преобразование типов в том случае, если выражение содержит переменные различных типов.
Основные правила для автоматического преобразования типов состоят в следующем:
• - типы всех переменных, используемых в выражении справа от оператора присваивания, сначала преобразовываются к наиболее «широкому» типу, а затем выполняется вычисление выражения;
• - тип результата будет преобразован к типу значения, расположенного слева от оператора присваивания.
При преобразовании типов char и int, а также типов, образуемых из них (таких как short, long, signed и unsigned), выполняются следующие правила:
• - при преобразовании целого типа signed к типу unsigned не про-исходит изменения значения битов и поэтому для отрицательных чисел значение переменной типа unsigned будет отличаться от значения переменной типа signed. Например:
short s = -3;
unsigned short us;
us = i; //переменная us будет равна 65533
• - при преобразовании целого типа unsigned к типу signed не происходит изменения значения битов и поэтому значения переменной типа unsigned могут отличаться от значения переменной типа signed. Например:
short s;
unsigned short us = 65533;
i = us; //переменная i будет равна -3
• - допускается автоматическое преобразование целого типа к более «короткому» типу, однако это может вызвать потерю части данных.
При преобразовании типов с плавающей точкой выполняются следующие правила:
• - допускается автоматическое преобразование типа float к
double и double к long double, значение переменной при этом не изменяется;
• - преобразование к более «короткому» типу с плавающей точкой (например, double к float) происходит корректно только в том случае, если нет потери значения, в противном случае результат будет неопределен;
• - преобразование типа с плавающей точкой к целому типу приводит к усечению дробной части (например, 3.5 преобразуется к 3).
Для арифметических выражений используются следующие правила преобразования типов, приведенные в табл. 3:
Таблица 3 Правила преобразования типов
Преобразование типа указателя может происходить при инициализации, присвоении, сравнении и других операциях.
Целое константное выражение, равное нулю, или выражение, приводимое к типу void*, преобразовывается к указателю, называемому
NULL-указателем. Такой указатель может использоваться для сравнения и оп-ределения существования объекта.
Указатель типа void* может быть преобразован к указателю любого другого типа только явным приведением типа. Обратно, указатель любого типа может быть неявно преобразован к указателю типа void*.
Это может быть использовано для передачи параметров функции, для которой формальный параметр может быть указателем различных типов, например int* или float*. В этом случае прототип функции будет выглядеть следующим образом:
void F1(void *pVar);
а вызов функции будет выполняться как:
int *piVar;
float *pfVar;
F1(piVar);
F1(pfVar);
В теле функции для работы с указателем надо будет использовать явное преобразование типа, например (int*) piVar.
Указатель на любой объект, не являющийся const или volatile, может быть неявно преобразован к указателю типа void*.
Указатель на функцию может быть преобразован к типу void*, если тип void* является достаточным для размещения этого указателя.
Указатель на класс может быть преобразован к указателю на базовый класс в том случае, если:
• - специфицируемый базовый класс является доступным и преобра-зование является недвусмысленным;
• - используется явное преобразование типа указателя (результатом этого является указатель на «подобъект» - часть объекта, описанная в базовом классе). Например:
class A
{
public:
int AComponent;
int AMemberF();
};
class B: public A
{
public:
int BComponent;
int BMemberF();
};
B bObject; //объект класса B
A *pA = &bObject; /*этот указатель ограничивает дос-туп к членам класса B:
BComponent и BMemberF() и допуска-ет только доступ к членам базового класса*/
B *pB = &bObject; /*разрешен доступ как к членам класса B, так и к членам класса
A*/
pA->AMemberF();//разрешено: функция-член класса A
pB->AMemberF();//разрешено: наследуется от класса A
pA->BMemberF();//ошибка: нет в классе A
Любое выражение, имеющее тип массива, может быть преобразовано к указателю того же типа. Результатом будет указатель на первый элемент масси-ва. Например:
char Path[MAX_PATH]; //массив типа char*
char *pPath = Path; //указатель на массив,
//эквивалентно &Path[0]
C++ не поддерживает стандартное преобразование данных типа const или volatile к любому типу, не являющемуся также const или volatile. Однако это может быть выполнено посредством явного приведения типов.
Отметим, что указатели на члены (класса, структуры) не могут рассмат-риваться как обычные указатели (в отличие от указателей на статические чле-ны) и для них не выполняется стандартное преобразование.
Вы можете определить функции-элементы, которые будут осуществлять явное преобразование типа класса к другому типу. Эти функции называют опе-раторами приведения или процедурами преобразования типа. Они имеют следующий вид:
operator имя_нового_типа();
Процедуры преобразования подчиняются следующим правилам:
• - процедура преобразования не имеет аргументов;
• - процедура преобразования не имеет явной спецификации типа возвращаемого значения, так как подразумевается тип, указанный после ключевого слова operator;
• - процедура преобразования может описываться как virtual;
• - процедура преобразования наследуется.
С одной стороны, легко предотвратить выполнение операции преобразо-вания, просто не определив этой операции. С другой – может потребоваться сконструировать объекты с использованием одного значения другого типа так, чтобы не осуществлялось скрытое преобразование с помощью конструктора с одним аргументом. В стандарт языка C++ включено ключевое слово explicit, предназначенное для решения этой проблемы. Оно просто помещается перед объявлением конструктора с одним аргументом. В результате чего конструктор, объявленный с ключевым словом explicit, не может быть использован в ситуации неявного преобразования данных.
Например, преобразуем ранее введенный класс Distance таким образом, чтобы можно было осуществлять преобразование типа Distance в тип float и наоборот:
#include <iostream>
using namespace std;
class Distance
{
private:
const float MTF;
int feet;
float inches;
public:
Distance():feet(0), inches(0.0), MTF(3.280833F){}
explicit Distance(float meters): MTF(3.280833F)
{
float fltfeet = MTF * meters;
feet = int ( fltfeet );
inches = 12 * ( fltfeet - feet );
}
void showdist()
{ cout << feet << "\'-" << inches << '\"'; }
};
int main()
{
void fancyDist(Distance);
Distance dist1(2.35F);
//инициализация не работает
// Distance dist1 = 2.35F;
cout << "\ndist1 = ";
dist1.showdist ( );
float mtrs = 3.0F;
cout << "\nDist1 ";
// fancyDist ( mtrs );
return 0;
}
void fancyDist ( Distance d )
{
cout << "(в футах и дюймах) = ";
d.showdist ( );
cout << endl;
}
В функции main сначала происходит преобразование фиксированной величины типа float – 2.35, представляющей собой метры, - в футы и дюймы с использованием конструктора с одним аргументом. Затем преобразуем тип Distance в тип float.
Хитрость этой программы в том, что вы можете вызвать функцию fancyDist, отвечающую за вывод на экран объекта класса Distance, передав ей в качестве аргумента переменную типа float. Компилятор поймет, что тип аргумента неверен и будет искать операцию преобразования. Найдя конструктор Distance, который принимает в качестве аргумента переменную типа float, компилятор приспособит этот конструктор для преобразования типа float в Distance и передаст значение типа Distance в функцию. Это неявное преобразование одно из тех, что вы можете упустить из виду. Однако, если мы сделаем конструктор явным, то мы предупредим неявные преобразования. Вы можете проверить это, удалив символ комментария из вызова функции fancyDist в программе: компилятор сообщит вам о том, что не может вы-полнить преобразование. Без ключевого слова explicit этот вызов работает. Отметим такой побочный эффект явного конструктора, как то, что вы не можете использовать инициализацию объекта, в которой присутствует знак равенства
Distance dist1 = 2.35F;
тогда как выражение со скобками
Distance dist1 ( 2.35F );
работает как обычно. |