You Know Nothing
  • 主页
  • 分类
  • 标签
  • 归档

C++ 中的类型转换

  • 1. 引言
  • 2. 类型提升和算术类型转换
  • 3. 转换构造函数
  • 4. 类型转换运算符
  • 5. 继承类到基类的类型转换
    • 5.1 静态类型和动态类型
    • 5.2 阻止向上类型转换
    • 5.3 多重继承与向上类型转换
  • 6. 其它隐式类型转换

1. 引言

不同的数据在计算机内存中的存储方式不同,导致了“类型”这一抽象概念的产生。

对于一个变量,需要回答三个问题:

  • 变量的起点(内存地址)
  • 变量的内存长度(类型决定)
  • 变量的解析方式(类型决定)

一般情况下不同类型的两个变量无法直接进行运算。这样就需要类型转换。存在两个概念:隐式类型转换和显式类型转换。

  • 隐式类型转换:不通过专门的类型转换操作,而是通过其他规定或代码上下文隐式发生的类型转换
  • 显式类型转换:通过专门的类型转换操作进行,具有强制性,不会受任何类型转换以外的因素影响,又被称为强制类型转换

2. 类型提升和算术类型转换

  • 对于同类算术类型,如 short 与 int、float 与 double,占用内存小的类型转换为另一类型。这称为类型提升
  • 整型转换为浮点型
  • 仅当无符号类型占用的内存小于有符号类型时,无符号类型才提升为有符号类型,否则,有符号类型转换为无符号类型
int main() {
    unsigned short a = 1;
    unsigned b = 1;
    cout << ( a > -1) << " " << (b > -1) << endl;
}
//将输出 1 0

3. 转换构造函数

  • 构造函数只有一个形参
  • 构造函数不止一个形参,但只有第一个形参无默认值
  • 构造函数不止一个形参,但都有默认值
  • 第一形参的类型不是类本身(此时为拷贝构造函数)或其附加类型(此时为移动构造函数)

若要避免隐式类型转换,可以利用 explicit 关键字。

4. 类型转换运算符

定义了与转换构造函数相反的操作,从类类型转换为其他类型。在 operator 之后跟着转换的目标类型。

struct A {
    operator int() const {
        return 0;
    }
};

void test(int) {}

int main() {
    test(A());
}

若要避免隐式类型转换,可以利用 explicit 关键字。

对于类型转换运算符与 explicit 有一个例外:operator bool() 在条件表达式或逻辑表达式中发生的隐式类型转换不受 explicit 的影响。

5. 继承类到基类的类型转换

继承类到基类的类型转换,称为向上类型转换。

struct A {};
struct B: A {};

int main() {
    A a1 =  B(); //值向上转换,切除继承类部分,实际调用拷贝复制构造函数
    A *a1 = new B; //指针向上转换
    A &a3 = a1; //左值引用向上转换
    A &&a4 = B(); //右值引用向上转换
}

5.1 静态类型和动态类型

  • 静态类型:变量声明的类型
  • 动态类型:变量实际存储的数据的类型

绝大多数情况下,静态类型与动态类型都是必须一致的,否则将发生隐式类型转换或引发编译错误。当且仅当使用基类的指针或引用存储继承类对象时,变量的静态类型与动态类型将不一致。此时,虽然看上去发生了向上类型转换,实际上并未发生,称为动态绑定。

一个变量的静态类型,决定了由此变量能够访问到的成员名称。当静态类型时基类指针或引用时,即使变量存放的是继承类对象,也只能访问到基类中声明的数据成员。

即:如果发生向上类型转换的类型是类的指针或引用,将以丢失继承类部分的成员名称为代价进行向上类型转换。

但是由于虚函数的存在,访问成员名称所得到的实际成员函数将不一定与静态类型保持一致,此性质是 C++ 多态 的核心。

5.2 阻止向上类型转换

为什么继承类可以访问基类的成员?

不难发现,“继承类可以访问基类成员”这一性质并不是天经地义的,因为继承类中并没有“复制粘贴”一个基类,而只有继承类本身的部分,故原则上继承类虽然继承了基类,但其本身仍然是没有能力访问基类的成员的。

继承类对象之所以能够访问基类成员,是因为在进行这样的访问时,继承类的 this 指针通过向上类型转换操作转换成了一个基类类型的指针,然后以基类指针的身份访问到了基类的成员。

如果希望阻止这种隐式的向上类型转换呢?

让我们认真考察 public、protected 与 private 这三个关键字。

  • public:当用于访问说明符时,表示对类的一切用户可见;用于继承时,表示继承时不修改基类的一切访问说明符
  • protected:当用于访问说明符时,表示仅对类的继承用户可见,对类的实例用户不可见;用于继承时,表示将基类的一切 public 访问说明符在继承类中修改为 protected
  • private:当用于访问说明符时,表示对一切类的用户均不可见;用于继承时,表示将基类的一切 public 和 protected 访问说明符在继承类中修改为 private

如果我们从向上类型转换这一角度思考,就能得出答案:

  • public:不阻止任何用户进行向上类型转换
  • protected:阻止类的实例用户进行向上类型转换
  • private:阻止一切用户进行向上类型转换
struct A {};

struct B: A {};            // 不阻止任何B类的用户向A进行类型转换
struct C: protected A {};  // 阻止C类的实例用户向A进行类型转换
struct D: private A {};    // 阻止D类的一切用户向A进行类型转换

struct E: B { void test() { static_cast<A *>(this); } };  // B类的继承类用户可以向A进行类型转换
struct F: C { void test() { static_cast<A *>(this); } };  // C类的继承类用户可以向A进行类型转换
struct E: D { void test() { static_cast<A *>(this); } };  // Error!D类的继承类用户不可以向A进行类型转换

int main()
{
    static_cast<A *>(new B);  // B类的实例用户可以向A进行类型转换
    static_cast<A *>(new C);  // Error!C类的实例用户不可以向A进行类型转换
    static_cast<A *>(new D);  // Error!D类的实例用户不可以向A进行类型转换
}

由此可见,public 继承将不阻止类的任何用户进行向上类型转换,而 private 继承将阻止类的一切用户进行向上类型转换,protected 继承只阻止类的实例用户进行向上类型转换,但不阻止类的继承类用户进行向上类型转换。

5.3 多重继承与向上类型转换

对于多重继承,其向上类型转换对于同一继承层的多个基类是全面进行的。

参考以下代码:

struct A { int i; };
struct B { int i; };
struct C: A, B { int i; };
struct D: A, B {};

int main()
{
    C().i;  // 访问C::i
    D().i;  // Error!存在二义性!
}

6. 其它隐式类型转换

  • 0 转换为空指针
  • 数组名退化为指针
  • 空指针或数字 0 转为 false,其它指针或数字转为 true
  • T 转换为 void
  • 非 const 转换为 const

RELATED

  • C++ Primer 第十七章 标准库特殊设施
  • C++ Primer 第十六章 模板与泛型编程
  • C++ Primer 第十五章 面向对象程序设计
  • C++ Primer 第十四章 重载运算与类型转换
  • C++ Primer 第十三章 拷贝控制

OLDER

  • 深入浅出 SQL
  • 统计学习方法 第十二章 统计学习方法总结
  • 统计学习方法 第十一章 条件随机场
  • 统计学习方法 第十章 隐马尔科夫模型
  • 统计学习方法 第九章 EM 算法及其推广

NEWER

  • Head first Java 笔记
  • 算法(第四版)
  • 量子信息和量子计算——量子计算部分
  • Python3 网络爬虫开发实践
  • 深度学习 第五章 机器学习基础

发布日期

2019-09-07 10:55:16

最后更新

2019-09-07 10:55:16

分类

C++

标签

  • C++ 18
  • Powered by Pelican. Theme: Elegant by Talha Mansoor