栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++隐式转换和显式转换 | explicit | static

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++隐式转换和显式转换 | explicit | static

参考:C++类型转换:隐式转换和显式转换

隐式转换

当一个值拷贝给另一个兼容类型的值时,隐式转换会自动进行。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。

隐式转换类型
  1. 基本数据类型:基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。【当然大到小的转换也可以,但编译器会 waring 窄转换】

    // 小到大
    int a = 1;
    float b = a;
    // 大到小
    float a = 1.1;
    int b = a;// 同样可以,但是编译器会发出narrow conversion窄转换的warning
    
  2. 类/对象:

    1. 构造函数:在自己定义的构造函数中,会出现隐式转换,通常,在只有一个参数的构造函数会默认定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。在多个参数的构造函数中不会出现隐式转换

      但这是在 C++98/03 时的情况,在 C++11 引入了列表初始化,因此多参数也可以被隐式转换。

      class A{
      	A(int i){}
          A(int i, int ){}
      }
      int main(){
          // 正常写法
          A a(1);
          A a = A(1);
          // 隐式转换,会看起来很奇怪,一个对象为什么会等于一个int类型的值呢?让人难以理解
          A a = 1;// 这就是隐式转换,把1会转换成A(1)
          A a = 'a';// 也是隐式转换,这里把char当作数字,且发生了基本类型的隐式转换char->int,又发生了对象的隐式转换A('a')
          --------------------
          // C++11引入列表初始化
          // 正常写法
          A a(1,2);
          A a = A(1,2);
          // 多参数隐式转换
          A a = {1,2};// 等价于A a = A(1,2)
      }
      
    2. 对象赋值:可以将子类对象赋值给父类对象(可能会发送对象切片,但这是可行的,直接隐式转换),但反过来,父类对象不能赋值给子类对象,因为子类对象多的成员,父类不能提供

      class A{};
      class B : public A{};
      int main(){
          // 子类赋值给父类√
          B b;
          A a(b);
          A a = b;
          -------------
          // 错误
          A a;
          B b(a);
      }
      
  3. 指针:在下面的显示转换中也可以看到,子类指针->父类指针是可以隐式转换的;同理有类型类指针转换成 void* 也是隐式可以的

消除隐式转换

C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

class A{
	explicit A(int i){}
    explicit A(int i, int ){}
}
int main(){
    // 正常写法
    A a(1);
    A a = A(1);
    // 隐式转换
    A a = 1;// error
    A a = 'a';// error
    --------------------
    // C++11引入列表初始化
    // 正常写法
    A a(1,2);
    A a = A(1,2);
    // 多参数隐式转换
    A a = {1,2};// error
}
显式转换

C++ 中引入 4 种强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast。使用格式 xxx_cast

C语言的转换方式很简单,可以在任意类型之间转换,但这也恰恰是缺点,因为极其不安全,这种转换很容易出bug,需要严格审查代码才能消除这种隐患,但是C这种转换方式不利于我们审查代码,且程序运行时也可能会出bug。

而C++引入的这几种类型转换可以解决上述问题,不同场景下不同需求使用不同的类型转换方式,有利于编译器帮助我们代码审查。

但真正在使用中,有时候图方便会直接使用 C 风格的强制类型转换,毕竟 C++ 兼容 C,正如使用 printf 还是 cout 一样,前者规定格式,后者只是输出流

参考:C++为什么非要引入那几种类型转换?

1. static_cast
class A{};
class B : A{};
int main(){
    // 1
    double a = 1.1;
    int c = static_cast(a);// C++中引入的强制转换
    int d = (int)a;// c语言风格的强制转换
    // 2
    void *empty;
    A *has_type = static_cast(empty);// void* -> A*
    empty = static_cast(has_type);// A* -> void*【事实上可以隐式转换】
	// 3
    A father;
    B *son_p = static_cast(&father);// 父类指针->子类指针【没有限制,可以成功,但运行时可能会出错,而下面的dynamic_cast向下转换时有两个要求】
    B son;
    A *father_p = static_cast(&son);// 子类指针->父类指针【事实上可以隐式转换,用父类声明子类】
}
适用场景
  1. 基本数据类型之间的转换使用
  2. 在有类型指针和void*之间转换使用
  3. 在继承关系的父类和子类指针中转换使用,如果不是继承关系,会报 which are not related by inheritance, is not allowed

非多态类型转换一般都使用static_cast【注意子类转成父类不一定有多态,继承不代表多态,只有父类中有虚函数时才是多态】

2. dynamic_cast
class A{};
class B : A{};
int main(){
    // 1
    void *empty = nullptr;
    A *has_type = dynamic_cast(empty);// void* -> A* 报错'void' is not a class type
    empty = dynamic_cast(has_type);// A* -> void* 【事实上就是隐式转换】
    // 2
    A father;
    B *son_p = dynamic_cast(&father);// 父类指针->子类指针【有限制:①dynamic_cast向下转换时要求有多态,即父类中必须有虚函数。②如果父类指针本来就指向父类,那么可以转换,但会转换成nullptr,地址为0】
    cout << son_p;// 为0
    //正确的转换:满足两个要求
    A *father_p = new B;// 父类指针指向子类对象
    B *son_p = dynamic_cast(father_p);
    cout << son_p// 不为0,成功转换
        
    B son;
    A *father_p = dynamic_cast(&son);// 子类指针->父类指针【事实上可以隐式转换,用父类声明子类】
}
适用场景
  1. 只能用于指向类的指针和引用(或void*),但只能类转 void* 不能 void* 转类指针
  2. 用于将父类的指针或引用转换为子类的指针或引用【但有两个条件】
3. const_cast
int main(){
	const int a = 1;// 原数据可以是const,也可以不是,结果都一样,但是如果cout << a,如果是const int a = 1,则会输出1,好像a没有被修改一样,事实上是编译器会将使用常量的地方直接替换成对应的值
    const int *p1 = &a;
    int *p2 = const_cast(&a);// const int* -> int*
    *p2 = 2;// 修改值
    cout << *p2;// 2
    const int *p3 = const_cast(p2);// int * ->const int*
}
  1. 用于常量指针或引用与非常量指针或引用之间的转换【不能是基本类型或者是直接对象 ,否则报Const_cast to 'int', which is not a reference, pointer-to-object, or pointer-to-data-member】,去除常量性是危险操作,还是要谨慎操作。
4. reinterpret_cast

和 C 中的强制转换一样,什么都可以转,一般在前面三种解决不了时才使用,允许所有的指针转换: **既不检查指针所指向的内容,也不检查指针类型本身。**正如名字一样,reinterpret 重新定义

5. 与 C 中强制转换的区别

static_cast(x) 和 int(x) 或 (int)x 相比,前者会进行检查,避免错误的转换,比如不同的有类型的指针之间的转换是不允许的,而后者则不会,不论是什么类型,只要是指针就能转换,如下

// 显式转换风格
y = int (x);    // functional notation
y = (int) x;    // c-like cast notation,C语言中只有这种方式
// 两种转换对比
auto *p1 = new int(1);
auto *p2 = new float (1.1);
p2 = static_cast(p1);// error,在编译时就制止这种行为,避免在运行时出错
p2 = (float*)p1;// 编译通过,但如果使用*p2,那么会把p1指向的整数1的32位2进制表示当成浮点数来解释
// 不同对象指针的转换问题
class A{
  int _a;  
};
class B{
  int _b;
  int _c;
public:
    int get_b(){
        return b;
    }
};
int main(){
    A a;
    B *b = (B*)&a;
    b->get_b();// 不报错,甚至确实能得到值,事实上A对象所占的内存为32bit,B对象所占内存为64bit,因此当把A的指针转换成B*时,会把_a所在内存当作_b,_a后面一块32bit没有被分配的内存当成_c【可能发生segmentation fault】,而类的方法都在静态区中,因此可以调用,通过调用B的方法,将_a返回
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/883742.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号