C++--类型转换详解(隐式类型转换、强制类型转换)

C++类型转换

C++类型转换主要分为两种:隐式类型转换和显式类型转换(强制类型转换)。

(type)expression //C风格转换格式
static_cast<type>(expression) //C++风格转换格式

1. 隐式类型转换

所谓隐式类型转换,是指不需要用户干预,编译器默认进行的类型转换行为(很多时候用户可能都不知道到底进行了哪些转换)。

隐式类型转换一般分为两种:内置数据类型、自定义数据类型。

1.1 内置数据类型

C++的隐式类型转换,都满足一个基本原则:由低精度向高精度的转换。若不满足该原则,编译器会提示编译警告

举例说明:
例1:混合类型的算术运算表达式中

int int_value = 8;
double dou_value = 10.7;
double dou_Sum = int_value + dou_value; // int_value会被自动转换为double类型,用转换的结果再与dou_value相加.

例2:不同类型的赋值操作时

int value = true; // bool类型被转换为int类型

例3:函数参数传值时

void func(double dArg); // 声明函数
func(1); // 调用函数。整型数值1被默认转换为double类型数值1.0

例4:函数返回值时

double add(int num1, int num2)
{
     
   return (num1 + num2); // 运算结果会被隐式转换为double类型返回
}

若不满足该原则,编译器会提示编译警告。如下:

double num1 = 100.66;
int num2 = num1; // : warning C4244: “初始化”:从“double”转换到“int”,可能丢失数据

如果我们不想看到警告,可以选择强制类型转换。如下:

double num1 = 100.66;
int num2 = (int)num1;
1.2 自定义数据类型

隐式类型转换的风险一般存在于自定义类型转换间。尤其需要注意自定义类的构造函数。例如:

 class MyString
{
     
public:
    MyString(int n) {
     }; // 本意:预先分配n个字节给字符串
    MyString(const char* p) {
     }; // 用C风格的字符串p作为初始化值 
    // ......
};

void main()
{
     
    MyString s1 = "China"; //OK 隐式转换,等价于MyString s1 = MyString(”China”) 
    MyString s2(10); // OK 分配10个字节的空字符串
    MyString s3 = MyString(10); // OK 分配10个字节的空字符串

    MyString s4 = 10; // OK,编译通过。也是分配10个字节的空字符串
    MyString s5 = 'A'; // 编译通过。分配int('A')个字节的空字符串
    // s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
}

如上例,要想禁止此种隐式类型转换,可以使用C++关键字explicit。

2. 强制类型转换

C++为了规范C中的类型转换,加强类型转换的可视性,引入了四种强制类型转换操作符:static_cast, reinterpret_cast,const_cast和dynamic_cast,他们本质上都是模板类。
C++--类型转换详解(隐式类型转换、强制类型转换)_第1张图片

2.1 static_cast

它用于非多态类型的转换(静态转换),对应于C中的隐式类型转换,但不能用于两个不相关类型的转换,如整形和整形指针之间的转换,虽然二者都是四个字节,但他们一个表示数据,一个表示地址,类型不相关,无法进行转换。

该转换在编译时完成,和C风格的类型转换相似,不过要注意下面几点:

(1)不能在没有派生关系的两个类类型之间转换;

(2)不能去除掉原有类型的类型修饰符,例如const,volatile,__unaligned;

(3)转换对象时由于没有动态类型检查,所以由基类对象转换成派生类对象的时候存在安全隐患

2.2 reinterpret_cast

有着与C风格的强制转换同样的能力。

它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。

它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。不到万不得已绝对不用。

2.3 const_cast

(1)常量指针 被强转为 非常量指针,且仍然指向原来的对象;

(2)常量引用 被强转为 非常量引用,且仍然指向原来的对象;

(3)常量对象 被强转为 非常量对象。

2.4 dynamic_cast

(1)其他三种都是编译时完成的。dynamic_cast是运行时处理的,运行时要进行类型检查

(2)不能用于内置基本数据类型间的强制转换。例如:

double dValue = 12.12;
int nDValue = dynamic_cast<int>(dValue); // error C2680 : “int” : dynamic_cast 的目标类型无效。目标类型必须是指向已定义类的指针或引用

(3)使用dynamic_cast进行转换时,基类中一定要有虚函数,否则编译不通过。

需要有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的必要,此时转换才有意义。

由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。

class A
{
     };

class B : public A
{
     };

class C
{
     };

void main()
{
     
    A *pA = new A;
    B *pB = dynamic_cast<B*>(pA); // 编译错误error C2683: “dynamic_cast”:“A”不是多态类型
}

(4)dynamic_cast转换若成功,返回的是指向类的指针或引用;若失败则会返回NULL。

(5)在类的转换时,在类层次间进行上行转换(upcast)时,dynamic_cast和static_cast的效果是一样的

在进行下行(downcast)转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

向上转换即为指向子类对象的向上转换,即将子类指针转化父类指针

向下转换的成败取决于将要转换的类型,即要强制转换的指针所指向的对象实际类型与将要转换后的类型一定要相同,否则转换失败。

关于(4)、(5)两条的代码示例如下:

#include  
#include 
using namespace std;

class A
{
     
public:
    virtual void f()
    {
     
        cout << "A::f()" << endl;
    }
};

class B : public A
{
     
public:
    void f()
    {
     
        cout << "B::f()" << endl;
    }

    void bf()
    {
     
        cout << "B::bf()" << endl;
    }
};

class C
{
     
    void pp()
    {
     
        return;
    }
};

int fun()
{
     
    return 1;
}

void main()
{
     
    A* pAB = new B; // pAB是A类型的指针指向一个B类型的对象
    A* pAA = new A; // pAA是A类型的指针指向一个A类型的对象
    B* pB = nullptr;
    C* pC = nullptr;
    pB = dynamic_cast<B*>(pAB); // 结果为not nullptr,向下转换成功,pAB指向的就是B类型的对象,所以可以转换成B类型的指针。 
    if (nullptr == pB)
    {
     
        cout << "dynamic_cast :: nullptr" << endl;
    }
    else
    {
     
        cout << "dynamic_cast :: not nullptr" << endl;
    }
    // 等价于static_cast
    pB = static_cast<B*>(pAB); // 结果为not nullptr,向下转换成功,pAB指向的就是B类型的对象,所以可以转换成B类型的指针。 
    if (nullptr == pB)
    {
     
        cout << "static_cast :: nullptr" << endl;
    }
    else
    {
     
        cout << "static_cast :: not nullptr" << endl;
    }

    pB = dynamic_cast<B*>(pAA); // 结果为nullptr,向下转换失败。pAA指向的是A类型的对象,所以无法转换为B类型的指针。
    if (nullptr == pB)
    {
     
        cout << "dynamic_cast :: nullptr" << endl;
    }
    else
    {
     
        cout << "dynamic_cast :: not nullptr" << endl;
    }

    // static_cast的不安全性测试
    pB = static_cast<B*>(pAA); // 结果为not nullptr,向下转换成功。pAA指向的是A类型的对象,竟然转换为B类型的指针!
    if (nullptr == pB)
    {
     
        cout << "static_cast :: nullptr" << endl;
    }
    else
    {
     
        cout << "static_cast :: not nullptr" << endl; // 不安全性
        pB->f();  // A::f()
        pB->bf();  // B::bf()
    }

    pC = dynamic_cast<C*>(pAB); // 结果为nullptr,向下转换失败。pAB指向的是B类型的对象,所以无法转换为C类型的指针。
    if (nullptr == pC)
    {
     
        cout << "dynamic_cast :: nullptr" << endl;
    }
    else
    {
     
        cout << "dynamic_cast :: not nullptr" << endl;
    }

//  pC = static_cast(pAB); 
//  error C2440: “static_cast”: 无法从“A *”转换为“C *” 与指向的类型无关;转换要求 reinterpret_cast、C 样式转换或函数样式转换

    delete pAB;
    delete pAA;
    
    system("pause");
}
// run out:
/*
dynamic_cast :: not nullptr
static_cast :: not nullptr
dynamic_cast :: nullptr
static_cast :: not nullptr
A::f()
B::bf()
dynamic_cast :: nullptr
*/

由程序运行结果分析:static_cast的不安全性显而易见

pB = static_cast<B*>(pAA); 

向下转换结果为not nullptr。pAA指向的是A类型的对象,竟然可以转换为B类型的指针!相当危险!

(6)使用dynamic_cast的类型转换,其转换结果几乎都是执行期定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。

参考博客

C++类型转换

PS:小米面试问到了关于C++类型转换,特此学习一下~~

机会总是留给有准备的人,加油!

你可能感兴趣的:(C++学习之路,c++)