C++ 类类型转换

前言

在 C++ 中,对于一个类来说,根据接收的参数数量和类型不同,可以定义多个构造函数。如果在创建一个类的对象时,不需要输入任何实参作为初始值,此时调用的是默认构造函数。如果将同一个类的另外一个对象作为实参初始值,则调用的称为拷贝构造函数。有一种特殊的构造函数,该类构造函数只接收一个实参,他实际上不仅仅实现了一种初始化对象的方式,而且实现了一种转换为此类类型的转换机制,这种构造函数就被称为转换构造函数。在使用 C++提供的内置类型时,会接触到很多类型转换机制,其实用户自定义的类型也存在转换机制,而此篇文章就主要讨论接收一个参数的转换构造函数,以及其带来的类类型转换机制。同时还对类型转换运算符进行讨论,为我们的类自定义转换成其他类型的行为。


0x1 将其他类型转换为类类型

如下假设我们实现了一个 Person 类,该类有三个构造函数,分别是默认构造函数,接受两个参数 name 和 address 的构造函数,还有接受一个参数 name 的构造函数,对于第三个构造函数,由于其只接收一个构造函数,所以它给 Person 类带来了一种 string 到 Person 的转换机制。代码如下:

// g++环境:gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

#include <iostream>
#include <string>
using namespace std;

class Person
{
    
    
public:
    Person() : name("Unknown"), address("Unknown") {
    
     cout << "default constructor\n"; };
    Person(string n, string addr) : name(n), address(addr) {
    
     cout << "direct constructor 1\n"; }
    Person(string n) : name(n), address("Unknown") {
    
     cout << "direct constructor 2\n"; }

private:
    string name, address;
};

int main()
{
    
    
    string name = "Alice";
    Person Alice;
    Alice = name;
    return 0;
}

输出如下:
在这里插入图片描述
可见,main 函数中的第二行代码,通过默认构造函数初始化一个 Person 对象 Alice。而第三行将一个 string 对象赋值给一个 Person 对象 Alice,按道理来说,两个类型不同的对象是无法赋值的,而此处之所以可以赋值就是因为就发生了隐式类型转换,通过转换构造函数 direct constructor 2 ,编译器先调用该构造函数通过 string 对象 name 生成一个 Person 的临时对象,然后将该临时对象赋值给 Alice 对象。如果类设计者需要接收一个参数的构造函数,但同时又不希望向用户提供这种隐式的转换机制应该怎么办呢,仅仅需要将其构造函数声明成 explicit 即可(C++11),用户则不能通过赋值操作来将 string 对象赋值给 Person 对象,如下给转换构造函数 direct constructor 2 加上 explicit 声明:

explicit Person(string n) : name(n), address("Unknown") {
    
     cout << "direct constructor 2\n"; }

则编译的时候将报错:
在这里插入图片描述
g++ 显示没有合适的 operator= 函数匹配,其实就是因为没有合适的构造函数将 string 对象转换成 Person 临时对象。

注意,explicit 仅仅屏蔽掉了其隐式转换的属性,此时同样可以使用显式转换机制
隐式转换还可以发生在函数返回的时候。

将 main 函数改成如下则可以顺利编译通过:

int main()
{
    
    
    string name = "Alice";
    Person Alice; 
    Alice = static_cast<Person>(name);	// 通过static_cast将name显示转换成Person对象
    return 0;
}

0x2 将类类型转换成其他类型

通过类型转换运算符可以将类类型转换到其他的类型,其一般形式:operator type() const ,其中 type表示某种类型。假如我们希望 Person 类提供这样的机制,即我们可以通过其对象就能判断其是否具有名字,此时就需要用到类型转换运算符,将第一小节的代码扩展如下:

#include <iostream>
#include <string>
using namespace std;

class Person
{
    
    
public:
    Person() : name("Unknown"), address("Unknown") {
    
     cout << "default constructor\n"; };
    explicit Person(string n, string addr) : name(n), address(addr) {
    
     cout << "direct constructor 1\n"; }
    Person(string n) : name(n), address("Unknown") {
    
     cout << "direct constructor 2\n"; }
    
    /***************************************************************************/
    // 扩展部分
    operator bool() const
    {
    
    
        if (name == string("Unknown"))
            return false;
        return true;
    }
    /***************************************************************************/
private:
    string name, address;
};

int main()
{
    
    
    string name = "Alice";
    Person Alice;
    Alice = static_cast<Person>(name);

    bool isAliceHasName = Alice;
    cout << isAliceHasName << endl;	//输出为 1
	
	cout << (Alice << 2) << endl;	//输出为 4 ,如果将转换成 bool 的类型转换运算符声明为 explicit ,则编译失败
    return 0;
}

通过在 Person 类中定义个 operator bool() const 类型转换运算符即完成了该操作,在语句bool isAliceHasName = Alice; 中,就调用了成员函数 operator bool() const 发生隐式转换,因为 Alice 对象具有名字(成员变量 name 没被赋值为 “Unknown”),所以返回了 true ,输出为 1。注意该类型转换运算符有几个特点:

  • 没有显示的返回类型,但是有返回值,且返回值跟 type 必须是同一个类型。
  • 没有形参。
  • 必须定义成类的成员函数。
  • 一般都会用 const 修饰,因为其通常不应该改变待转换对象的内容。

同样的,这种隐式转换会带来很严重的问题,在上述的 Person 类中,以下代码也能通过编译,Alice << 2; ,即使我们没有重载 << 运算符。其能通过编译的原因就是编译器在识别到该行代码时,因为 Alice 对象可以隐式转换成 bool 类型,所以编译器先将其隐式转换成 bool 类型的 true,bool 类型可以进行移位运算,故被编译器进行整型提升后向左移位 2 位,所以输出的就是 4。显然这种结果是我们及其不愿意看到的,于是在 C++11 标准中,加入了关键字 explicit,将类型转换运算符声明为 explicit ,则可以屏蔽掉隐式转换。同样,类似于第一小节中的用法,即使屏蔽了隐式转换,仍然可以通过显式转换的方式来进行类型转换。但是对于类型转换运算符来说,有一个特殊的意外,就是当其被声明为 explicit 的时候,若表达式是被用作条件,则仍然可以发生隐式转换

  • if 、while 、do 语句的条件部分
  • for 语句头的条件表达式
  • 逻辑非运算符(!)、逻辑或运算符(||)、逻辑与运算符(&&)的运算对象
  • 条件运算符( ? : )的条件表达式

这就给我们带来了很多方便,比如我们在用 cin 作为循环控制的时候,经常会写这种代码:while(std::cin >> value)cin 在执行完 >> 运算后,会返回 cin 的引用,此时 cin 这个对象本身作为了 while 语句的条件判断。我们都知道 cin 是一种流对象,为什么这种用户定义的类的对象可以直接作为 while 语句的条件判断呢,我们可以深入源代码:

可见,在 cin 对应的类中,定义了一个转换成 bool 类型的类型转换运算符,该运算符就可以通过调用 fail() 函数,来判断流在进行输入后,状态是否还是正确的,并将结果返回。同时,其被声明成 explicit 来防止被用来作为一般的隐式转换,又因为其可以作为条件判断的特殊性,则可以将 cin 对象用于上述所提及的特例中了。

参考资料: 《C++ Prime》

猜你喜欢

转载自blog.csdn.net/qq_21746331/article/details/117638667
今日推荐