C++学习 十四、类的进阶(3)自动类型转换,强制类型转换

本文详细介绍了C++中类的自动类型转换(如隐式转换 int to float 等)和强制类型转换(如 explicit A(int))。讲解了如何实现其他类型到类的转换,以及如何使用explicit关键字防止意外隐式转换,还讨论了转换函数的声明与使用,包括可能遇到的二义性问题。
摘要由CSDN通过智能技术生成

前言

本篇继续类的进阶学习,自动类型转换和强制类型转换。

内置类型的转换

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,且AB的友元,

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转为intdouble的优先级相同,因此报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,以禁止隐式转换

后记

本篇记录了类的类型转换,用于类型转换的构造函数与转换函数。下篇将继续学习类的进阶内容,拷贝构造函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值