C语言隐式/显式类型转换 | C++四种强制类型转换、类的隐式转换、explicit

文章目录

  • C语言类型转换
    • 隐式类型转换
    • 显式类型转换
  • C++ 强制类型转换
    • static_cast
    • reinterpret_cast
    • const_cast
    • dynamic_cast
  • 类的隐式类型转换
    • 概念
    • 只允许一步类类型转换
    • explicit 抑制构造函数定义地隐式转换
    • 可以通过显式转换使用explicit构造函数


C语言类型转换

隐式类型转换

编译器在编译阶段自动进行,通常适用于相近的类型,如果不能转换则会编译失败。

何时发生隐式类型转换:

  • 在大多数表达式中,比int类型小的整数值提升为较大的整数类型
  • 在条件中,非布尔值转换成布尔类型
  • 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转化成左侧运算对象的类型
  • 算术运算或关系运算对象有多种类型,需要转换成同一种类型
  • 函数调用时也会发生类型转换

显式类型转换

需要用户自己处理,通常用于不相近类型的转换。

int main()
{int i = 9;int* p = &i;//将指针转换为地址int addr = (int)p;cout << addr;
}

C语言的类型转换使用起来很简单,但是也有很大的缺点:

  • 隐式类型转换 可能会因为整形提升或者数据截断导致 精度的丢失,并且有时候会因为 忽略隐式类型转换导致错误发生
  • 显示类型转换 代码不够清晰,没有很好的将各种情况划分开,而是全部混在一起使用。

C++ 强制类型转换

C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast

static_cast

static_cast 用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast ,但它不能用于两个不相关的类型进行转换。(即对应C语言中的隐式类型转换)

int main()
{double d = 1.9;int i = static_cast<int>(d);cout << i;
}

reinterpret_cast

reinterpret_cast 是一种较为危险的类型转换,通常为操作数的位模式提供较低层次的重新解释,用于 将一种类型转换为另一种不同的类型 ,通常适用于指针、引用、以及整数之间的类型转换。

int main()
{int i = 9;int* p = &i;double* p2 = reinterpret_cast<double*>(p);cout << *p2 << ' ' << *p;
}

在这里插入图片描述
如上面所说,这种转换十分容易导致错误的发生,因为指针类型其实是其指向的地址的类型,决定了指针看待这段地址的方式,它该如何读取数据。这里我把 int 改成了 double,使得他原本应该读取 4个字节,而变成了 8个字节,就导致了数据的变化。如果使用不当很容易会造成越界访问导致程序崩溃。

这篇文章也很有意思。


const_cast

const_cast 通常用于删除变量的const属性。

如果想要修改 const变量的值,就需要用 volatile 来取消编译器优化。因为 const变量 创建后会被放入寄存器中,只有我们取 const变量 的地址时,他才会在内存中申请空间。通常我们想要修改的是 const变量在内存中的值 ,但是由于 编译器优化,当使用 const变量 时就会到 寄存器 中去取值,所以需要用 volatile 取消编译器优化,让其每次在内存中取值。

不加 volatile 时:

int main()
{const int ci = 10;int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)*pi = 20;cout << ci << ' ' << *pi;
}

在这里插入图片描述
可以看出 ciconst属性 仍在,其值并未更改,只改了 *pi 的值。

volatile 时:

int main()
{volatile const int ci = 10;int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)*pi = 20;cout << ci << ' ' <<  *pi;
}

在这里插入图片描述


dynamic_cast

dynamic_cast 是一种动态的类型转换,是C++新增的概念,用于将一个 父类对象的指针/引用转换为子类对象的指针/引用

派生类 可以赋值给 基类 的对象、指针或者引用,这样的赋值也叫做对象切割。

例如Human类派生出的Student类:
在这里插入图片描述
当把 子类 赋值给 父类 时,可以通过切割掉多出来的成员 _stuNum 的方式来完成赋值。

但是 基类 对象如果想赋值给 派生类 ,则不可以,因为他不能凭空多一个 _stuNum 成员出来。

但是基类的 指针或者引用 却可以强制类型转换赋值给派生类对象。

  • 这个过程有可能成功,也有可能会因为越界导致出现问题。 如果使用C语言的强制类型转换,很可能就会出现问题,因为其没有安全保障。
  • 而如果使用 dynamic_cast ,则能够保证安全,因为其会先检查转换是否能够成功,如果不能成功则返回 0,能则直接转换。但是 dynamic_cast 的向下转换只支持继承中的 多态 类型,也就是 父类之中必须包含虚函数
int main()
{Human h1;Student s1;Human* hPtrs = &s1; // 指向派生类对象Human* hPtrh = &h1; // 指向基类对象//传统方法Student* pPtr = (Student*)hPtrs; // 没问题Student* pPtr = (Student*)hPtrh; // 有时候没有问题,但是会存在越界风险//dynamic_castStudent* pPtr = dynamic_cast<Student*>(hPtrh);return 0;
}

dynamic_cast 是如何识别父类的指针指向的是父类对象还是子类对象的呢?

其原理就是在运行时通过查找虚函数表上面的标识信息,来确认其指向的到底是父类还是子类,这也就是为什么只能用于含有虚函数的类。

这种在 运行中进行类型识别的方法 ,也叫做 RTTI ,C++中有很多支持 RTTI 的方法,如 dynamic_casttypeiddecltype


类的隐式类型转换

概念

类的隐式类型转换: 如果类有需要 一个实参 的构造函数(或者构造函数虽然有 N 个参数,但这些参数都有缺省值),那么可以使用 该实参类型 构造一个 具有常量属性的临时类。这样的行为看起来像从 实参类型类类型 的隐式转换。

示例如下:

class People{int age;string name;
public:People(int a){age = a;name = "zhangsan";}People(string b){age = 10;name = b;}People(){age = 21;name = "lihua";}People& add(const People &temp){age += temp.age;return *this;}People& revise(const People &temp){name = temp.name;return *this;}int getage(){return this->age;}string getname(){return this->name;}
};
int main()
{int num = 30;string who = "zhaoliu";People p1,p2(12);p1.add(num);cout << p1.getage() << endl;p2.revise(who);cout << p2.getname() << endl;
}

输出结果:
在这里插入图片描述
People 类中,接收 intstring 的构造函数分别运用了People类类型的隐式转换机制(即分别定义了从 intstring 类型向 People 隐式转化的规则)。换言之,在需要People的地方,我们可以使用string或者int作为替代

在上述代码main函数中:

int num = 30;
p1.add(num);
// 用num构造一个临时的People对象,该对象的name为空串,age等于30

我们用int实参充当了add的成员。该操作是合法的,编译器用给定的int自动创建了一个People对象。新生成的这个临时People对象被传递给add。因为add的参数是一个常量引用,所以我们可以给该参数传递一个临时量。(因为临时量具有常量属性)

那如果add的参数不是常量引用,以上操作还可以进行吗?

答案是否定的,因为num构造临时的people经过了形如下式的转化:

const People temp = People(num);

之后是将temp临时量作为实参传入add函数

p1.add(temp)

而如若add的形参只是一个普通引用的话,是无法绑定具有常量属性的临时量temp的。


只允许一步类类型转换

编译器只会自动地执行一步类型转换。

下面的调用是错误的:

p2.revise("huangba");
// error:隐式地使用了两种转换规则
// (1)把"huangba"转换成string
// (2)再把临时的string转换成People

如果想完成上述调用,可以显式地将字符串转换为string或者People对象:

  // 显式地转换成string,隐式地转化成Peoplep2.revise(string("huangba"));// 显式地转换成People,隐式地转化成stringp2.revise(People("huangba"));

explicit 抑制构造函数定义地隐式转换

我们之前提到,如果构造函数有多个参数,但这些参数都有默认实参,那么同样遵循类的隐式转换规则:

class Date
{
public:Date(int year, int month = 4, int day = 24):_year(year),_month(month),_day(day){}int _year;int _month;int _day;
};int main()
{Date d1(2020, 4, 24);Date d2 = 2020;//C++98Date d3 = { 2020, 5 }; //C++11Date d4 = { 2020, 5, 26 }; //C+11
}

这里其实是先用这个整型值来调用了缺省的构造函数来创建了一个临时对象,再使用这个对象来为 d2、d3、d4 赋值

如果想要禁止隐式转换,可以通过将构造函数声明为 explicit 加以阻止:

在这里插入图片描述
在这里插入图片描述
此时,没有任何构造函数能用于隐式地创建 Date 对象,隐式转换就不会发生了。

  1. 关键字 explicit 只对需要一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为 explicit
  2. 只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。
  3. explicit 构造函数只能用于 直接初始化 ,而且,编译器不会在自动转换过程中使用该构造函数。如下:
    在这里插入图片描述

可以通过显式转换使用explicit构造函数

编译器不会将具有 explicit 属性的构造函数用于隐式转换过程,但是我们可以显式地进行转换(强制类型转换):
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443828.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数重载、引用再探、内联函数

文章目录函数重载为什么C支持重载&#xff0c;C语言不支持呢&#xff1f;extern “C”引用再探引用的特性引用的使用场景引用和指针引用和指针的不同点:内联函数什么是内联函数&#xff1f;内联函数的特性内联函数的好处类的内联成员函数的声明内联函数的使用constexpr函数概念…

类的概念、成员函数的定义方式、类的访问控制和封装、类的大小、this指针

文章目录类的概念structclassclass和struct的区别是什么呢&#xff1f;类中成员函数的两种定义方式声明和定义都在类中声明和定义分离类的访问控制和封装类的封装特性类的大小结构体内存对齐规则类的存储方式this指针类的概念 在C中&#xff0c;类可以说是最重要的东西&#x…

jQuery实现两个列表框的值之间的互换:

jQuery实现两个列表框的值之间的互换&#xff1a; 截图如下&#xff1a; 代码如下&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <%String path request.getContextPath();String basePath reque…

类的6个默认成员函数:构造函数、析构函数、拷贝构造函数、重载运算符、三/五法则

文章目录6个默认成员函数构造函数概念默认构造函数的类型默认实参概念默认实参的使用默认实参声明全局变量作为默认实参某些类不能依赖于编译器合成的默认构造函数第一个原因第二个原因第三个原因构造函数初始化构造函数里面的“”是初始化吗&#xff1f;为什么要使用列表初始化…

C++ 类的知识 | 构造函数再探、匿名对象、友元函数、内部类、类的const成员、类的static成员

文章目录构造函数再探以下代码共调用多少次拷贝构造函数委托构造函数概念形式匿名对象友元友元的声明友元类令成员函数作为友元函数重载和友元注意内部类特性类的const成员可变数据成员类的static成员概念关于static静态成员的类内初始化静态成员能用于某些普通成员不能的场景构…

顺序容器(vector、list、string、deque、forward_list)及迭代器、容器适配器

文章目录概述所有容器都支持的操作迭代器迭代器支持的操作迭代器支持的算术运算容器类型size_typeiterator 和 const_iterator容器定义和初始化拷贝初始化顺序容器独有的构造函数&#xff08;array除外&#xff09;array的初始化与内置数组类型的区别6种初始化方法&#xff08;…

jQuery实现表格隔行换颜色:

jQuery实现表格各行换颜色&#xff1a; 截图如下&#xff1a; 代码如下&#xff1a; <span style"font-family:Microsoft YaHei;font-size:14px;"><% page language"java" import"java.util.*" pageEncoding"UTF-8"%> &…

用stack处理中缀表达式【+、-、*、/、()】

文章目录题目描述思路使用getline()存储输入的字符串边读取边压栈完整代码题目描述 使用stack处理括号化的表达式。当你看到一个左括号&#xff0c;将其记录下来。当你在一个左括号之后看到一个右括号&#xff0c;从stack中pop对象&#xff0c;直至遇到左括号&#xff0c;将左括…

二维数组的查找

文章目录题目描述思路注意代码题目描述 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整数…

Springmvc,Spring MVC文件上传

Springmvc文件上传&#xff1a; 1.代码截图如下&#xff1a; 2.UploadController.java: package cn.csdn.controller;import java.io.File;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller; import org.springframework.ui.…

插入迭代器、流迭代器、反向迭代器、移动迭代器

文章目录前言插入迭代器inserterfront_inserterback_inserteriostream迭代器istream_iterator 读取输入流istream_iterator允许使用懒惰求值ostream_iterator操作反向迭代器reverse_iterator的base成员函数前言 除了为每个容器定义的迭代器之外&#xff0c;标准库在头文件iter…

泛型算法(lambda表达式、function类模板、bind函数适配器、迭代器类别、链表数据结构独有的算法)

文章目录概念find()函数迭代器令算法不依赖于容器但算法依赖于元素类型的操作算法永远不会执行容器的操作只读算法accumulate()函数从两个序列中读取元素&#xff08;equal函数为例&#xff09;迭代器作为参数形成两个序列equal()写容器元素的算法概念fill()fill_n()插入迭代器…

jsp,div 限制字数,超出部分用省略号代替

1.我是用struts2标签做的&#xff1a;如下&#xff1a; <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <% taglib prefix"s" uri"/struts-tags"%> <%String path request.getContext…

C++之关联容器

文章目录概述及类型mapsetpair类型概念初始化默认初始化提供初始化器允许的操作可以创建一个pair类的函数可以作为容器的类型关联容器迭代器概念map的迭代器set的迭代器是const的初始化map and setmultimap and multiset关联容器的操作额外的类型别名关联容器和算法删除元素添加…

动态内存、智能指针(shared_ptr、unique_ptr、weak_ptr)、动态数组

文章目录三种对象的分类三种内存的区别动态内存概念智能指针允许的操作智能指针的使用规范new概念内存耗尽/定位new初始化默认初始化直接初始化值初始化delete概念手动释放动态对象空悬指针shared_ptr类格式独有的操作make_shared函数shared_ptr的计数器通过new用普通指针初始化…

动态数组的简单应用

文章目录连接两个字符串字面常量题目注意代码输出结果处理输入的变长字符串题目注意代码连接两个字符串字面常量 题目 连接两个字符串字面常量&#xff0c;将结果保存在一个动态分配的char数组中。重写&#xff0c;连接两个标准库string对象。 注意 使用头文件cstring的str…

二分查找算法实现

文章目录思路代码以二分区间作为while判定条件以给定值作为while判定条件主函数思路 while判定条件的选择&#xff0c;注意最外层的return的内容就好。下面提供了两个代码版本。计算 mid 时需要防止溢出&#xff08;对应类型【如本例中的int】可能存不下&#xff09;&#xff…

根据中序、前序遍历重建二叉树

文章目录题目递归思路细节易错代码复杂度分析迭代思路细节易错代码复杂度分析题目 输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 例如&#xff0c;给出 前序遍历 preorder [3,9,20,15,7] 中…

深搜+剪枝

文章目录题目思路注意代码复杂度分析题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c…

搜索+回溯问题(DFS\BFS详解)

文章目录题目思路DFS思路代码复杂度分析BFS思路代码复杂度分析题目 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动&#xff0c;它每次可以向左、右、上、下移动一格&#xff08;不能移动到方格外&#xff09;&am…