C++学习 十四、类的进阶(3)自动类型转换,强制类型转换
前言
本篇继续类的进阶学习,自动类型转换和强制类型转换。
内置类型的转换
C++的内置类型具有自动转换(隐式)和强制转换(显式)。
只要类型兼容,就能自动转换:
int x = 1.66; // double to int
float y = 2; // int to float
long z = y; // float to long
否则,只能使用强制转换:
int* px = (int*) 100;
int p = long (&p);
// int p = int (&p); // error!
注意:int p = int(&p);
将报error: cast from 'int*' to 'int' loses precision [-fpermissive]
,我查了一下stack overflow,下面这个答案说的挺清楚,原因是将指针转换为int类型后丢失了精度,并且没法再重构这个指针,也就是出现了信息丢失,因此编译器不让过。
类的转换
C++设计者希望,自定义的类型也能够和内置类型一样能够与其它类型进行转换。
于是,类也能够进行自动转换(隐式)和强制转换(显式)。
其它类型转换为类
只接受一个参数的构造函数将成为其它类型转换为类,示例如下:
#include <cstdio>
#include <iostream>
using std::ostream;
class B{
private:
int a_;
double b_;
public:
friend class A;
B();
B(int, int);
};
B::B(){
a_ = 100;
b_ = 100;
}
B::B(int a, int b){
a_ = a;
b_ = b;
}
class A{
private:
int a_;
double b_;
public:
A();
A(int);
A(double);
A(const B&, bool flag=true);
friend ostream& operator<< (ostream&, const A&);
};
A::A(){
a_ = 0;
b_ = 0;
}
A::A(int a){
a_ = a;
}
A::A(double b){
b_ = b;
}
A::A(const B& cb, bool flag){
if(flag)
{
a_ = cb.a_;
b_ = cb.b_;
}
}
ostream& operator<< (ostream& os, const A& ca){
os << ca.a_ << " " << ca.b_ << '\n';
return os;
}
int main(){
A ca;
B cb;
ca = 1;
std::cout << ca;
ca = 1.1;
std::cout << ca;
ca = cb;
std::cout << ca;
ca = A(5);
std::cout << ca;
return 1;
}
/*
Program returned: 1
1 0
0 1.1
100 100
5 0
*/
上面的示例中自定了类A
,类B
,且A
是B
的友元,
类B
提供了一个默认构造函数,一个双参数构造函数。
类A
提供了一个默认构造函数,两个参数类型不同的单参数构造函数,一个带一个默认参数的双参数构造函数。
赋值语句ca = 1;
使用类A
的构造函数A::A(int)
根据int值创建一个临时对象,然后将该临时对象的数据赋值到ca
中。这就是类的自动转换,也称为隐式转换。
赋值语句ca = 1.1;
同理,只不过调用的构造函数是A::A(double)
。
赋值语句ca = cb;
使用的是带一个默认参数的A::A(const B&, bool flag=true)
,将类B
的对象转换为类A
。类与类之间也可以互相转换,并且用于类型转换的构造函数有且仅有一个需要从外部传递的参数,多余参数必须具有默认值。
赋值语句ca = A(5);
在转换中指出了构造函数名,称为类的强制转换,也被称为显式转换。
explicit关键字
类的自动转换容易出现意想不到的转换,因此通常需要禁用隐式转换。
关键字explicit
,英文意思为明确的
。C++中explicit
声明构造函数和转换函数不能用于隐式转换。
把上面类A
声明中的构造函数使用explicit
关键字声明:
class A{
private:
int a_;
double b_;
public:
A();
explicit A(int);
explicit A(double);
A(const B&, bool flag=true);
friend ostream& operator<< (ostream&, const A&);
};
由于explicit
禁止了这两个构造函数用于隐式转换,后面的赋值语句ca = 1; ca = 1.1;
将报error: no match for 'operator=' (operand types are 'A' and 'int')
。而显式转换ca = A(5);
则不会受到影响。
注意:explicit
关键字只能出现在类声明中,否则报error: 'explicit' outside class declaration
。
注意:explicit
关键字只能用于声明构造函数和转换函数(下面会提到),否则报error: only declarations of constructors and conversion operators can be 'explicit'
。
转换为类的二义性
如果一个类有多个可用于类型转换的构造函数,那么在转换时就可能发生二义性,例如:
#include <cstdio>
#include <iostream>
using std::ostream;
class B{
private:
int a_;
double b_;
public:
friend class A;
B();
B(int, int);
};
B::B(){
a_ = 100;
b_ = 100;
}
B::B(int a, int b){
a_ = a;
b_ = b;
}
class A{
private:
int a_;
double b_;
public:
A();
explicit A(int);
explicit A(double);
A(const B&, bool flag=true);
friend ostream& operator<< (ostream&, const A&);
};
A::A(){
a_ = 0;
b_ = 0;
}
A::A(int a){
a_ = a;
}
A::A(double b){
b_ = b;
}
A::A(const B& cb, bool flag){
if(flag)
{
a_ = cb.a_;
b_ = cb.b_;
}
}
ostream& operator<< (ostream& os, const A& ca){
os << ca.a_ << " " << ca.b_ << '\n';
return os;
}
int main(){
A ca;
ca = A(5l);
std::cout << ca;
return 1;
}
赋值语句ca = A(5l);
想将long类型转换为类,但是构造函数提供的是int类型或者double类型。将long
转为int
,double
的优先级相同,因此报error: call of overloaded 'A(long int)' is ambiguous
,即编译器不知道该使用哪个构造函数来进行显式转换。
常见的类转换
常见的类隐式转换总结如下:
- 将对象初始化为内置类型时;
- 将内置类型赋给对象时;
- 将内置类型传递给参数类型为对象的函数;
- 在返回类型为对象的函数中返回内置类型;
另外,如果内置类型与用于类转换的构造函数的参数类型不同,但可以转换,则编译器先将该内置类型转换为对应的参数类型,再使用类转换。
类转换为内置类型
如果要将类转换为内置类型,需要在类声明和定义转换函数。
C++中,转换函数是用户定义的强制类型转换。转换函数的形式是operator TypeName()
,并且是类成员函数。
示例如下:
class A{
private:
int a_;
double b_;
public:
A();
explicit A(int);
explicit A(double);
A(const B&, bool flag=true);
operator int();
friend ostream& operator<< (ostream&, const A&);
};
A::operator int(){
return a_;
}
int main(){
A ca;
ca = A(5);
std::cout << ca;
int a = int(ca);
int b = ca;
return 1;
}
转换函数名已经指出了返回类型,因此不能再指定返回类型;转换函数是类方法,对象调用转换函数,指定了隐式参数是对象本身,因此不能有其它参数。
总结起来,转换函数需要注意三点:
- 是类方法;
- 函数名即返回类型,无返回类型名;
- 调用对象即参数,参数列表无参数。
初始化语句int a = int(ca);
指明了转换函数名,是强制类型转换,也称为显式转换。
初始化语句int b = ca;
是自动类型转换,也称为隐式转换。
explicit关键字
C++98中,explicit关键字不能用于转换函数。
C++11中,转换函数也能够通过explicit
关键字禁止隐式转换:
class A{
private:
int a_;
double b_;
public:
A();
explicit A(int);
A(const B&, bool flag=true);
explicit operator int();
explicit operator double();
friend ostream& operator<< (ostream&, const A&);
};
A::operator int(){
return a_;
}
int main(){
A ca;
ca = A(5);
std::cout << ca;
int a = int(ca);
// int b = ca; // error!
return 1;
}
转换为内置类型二义性
如果一个类有多个转换函数,也可能出现二义性。
如果类中有两个转换函数:
// class A declaration
operator int();
operator double();
// usage
int a = ca;
// long b = ca; // error!
int a = ca;
能过编译,但long b = ca;
将报error: conversion from 'A' to 'long int' is ambiguous
。
与构造函数不同的是,构造函数由于可以重载,即使explicit
指定了必须进行显式转换,因为构造函数名相同,因此仍然可能出现二义性。
而转换函数不能重载,因此显式转换必然不会出现二义性:
// class A declaration
explicit operator int();
explicit operator double();
// usage
// int a = ca; //error!
long b = double(ca);
注意:由于上面所说的二义性问题、意料之外的转换等原因,因此在使用类的类型转换时,尽量将构造函数和转换函数声明为explicit,以禁止隐式转换。
后记
本篇记录了类的类型转换,用于类型转换的构造函数与转换函数。下篇将继续学习类的进阶内容,拷贝构造函数。