【C/C++】中的隐式类型转换 ~ 关于整数除以整数,商仍为整数的问题

在下述三种情况下,C/C++会进行隐式类型转换(implicit type cast):①变量初始化或者赋值时,值与变量的类型不同;②表达式中不同类型的变量/值进行运算时;③函数参数传递▲时。

本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

3.5.1 赋值/变量初始化

我们通过对下述C++程序及其执行结果的分析来解释赋值/变量初始化时的隐式类型转换及其影响:

//Project - AssignType
#include <iostream>
using namespace std;
   
int main(){
    bool a = 3.0;            //转换为bool类型:0为false,非0为true
    float b = -99999.2301;   //double转float,精度降低,可能超出储值范围
    int c = b;               //float转int, 小数部分丢失,可能超出储值范围
    unsigned int d = c;      //int转unsigned int,负值被错误解释
    short e = d;             //unsigned int转short,可能超出储值范围
    double f = b;            //float转double,安全

    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << "a = " << a << "\nb = " << b << "\nc = " << c
         << "\nd = " << d << "\ne = " << e << "\nf = " << f;
    return 0;
    }

上述代码的执行结果为:

a = 1
b = -99999.226562
c = -99999
d = 4294867297
e = 31073
f = -99999.226562

🚩第6行:任意值赋值给布尔型变量,按非零即真原则,0值转换为false, 非零转换为true。double类型的字面量3.0赋值给布尔类型的变量a,被转换成true。true输出给cout,结果为1。

🚩第7行:double转换为float时,可能发生精度损失。因为double类型对象由64个比特组成,而float类型只有32个比特。如果double类型的值超过float的储值范围,转换结果将是不确定的。本行进行了此类转换,可以看到转换后的float类型变量b丢失了精度(值不等于-99999.2301)。如果把b的类型改为double,可以保持更高的精度。

🚩第8行:float转换为int时,小数部分丢失。当储值范围超出时,结果不确定。输出结果证实,这种转换只保留了整数部分-99999。

🚩第9行:int(有符号)转换为unsigned int时,如果原值为负数,则结果不确定,因为无符号整数只能存储非负值。输出结果中,可以看到c值-99999被错误转换成了4294867297。

🚩第10行:unsigned int转成short(有符号),是从32位无符号整数到16位有符号整数的转换,如果超出储值范围,则结果不确定。输出结果中,可以看到无符号整数d值4294867297被错误转换成了有符号短整数31073。

🚩第11行:float转换为double则是安全的,因为double的储值范围更大,精度也更高。输出结果证实,double类型变量f完美复制了float类型变量b的值。同理,short到int,int到long long也是安全的。

🚩第13行:对cout输出浮点数的格式进行了设置:按定点小数(相对于科学计算法)输出,保留6位小数。具体细节请参考本书2.3.4节中的扩展阅读内容。

**将储值范围和精度较低的对象赋值给储值范围更大,精度更高的对象是安全的,反之则不然。**程序员应尽量避免将储值范围大/精度高的对象赋值给储值范围小/精度低的对象。如果因为某些原因不得不这么做,则需要反复确认两件事:①精度的损失在可接受范围内;②源对象的值不会超过目标对象的储值范围。

使用**列表初始化(list initialization)【C++ 11】**可以避免变量初始化过程中不恰当的隐式类型转换所带来的差错。如下述C++代码所示:

//Project - ListInit
#include <iostream>
using namespace std;
   
int main(){
    char c = 712;         //允许,但会溢出
    char d {66};          //允许,66在char的储值范围内
    char e {712};         //不允许,712超过char的储值范围
    unsigned int f {-1};  //不允许,unsigned int只能存非负整数
    int g {3.12};         //不允许,收窄会导致精度丢失
    
    return 0;
}

与传统的赋值初始化不同,列表初始化不允许收窄(narrowing),当被初始化的变量无法准确表达{ }内的字面量或者常量▲时,编译器会报错,拒绝编译。

🚩第6行:char类型对象c只有8个比特的存储空间,由于是有符号字符,其储值范围为-128 ~ +127。显然,字面量712超过了c的储值范围。但由于这是传统的赋值初始化,编译器将放任这种情况的发生,至多给出一个警告。

🚩第7行:字面量66在d的储值范围内,该行代码会被编译器所接受。

🚩第8 ~ 10行的变量不能准确表达{ }内的字面量,编译器将报错拒绝。

3.5.2 表达式

在表达式a/b中,a,b称为操作数(operand),/称为操作符(operator)。当算术运算发生时,如果参与运算的操作数具有不同的类型,则编译器会生成额外的代码将其转变成相同的类型后再进行运算。因为,绝大多数的CPU指令集均只支持同类型对象之间的算术运算,比如两个有符号32位整数之间的运算。显然,算术运算的结果类型也受上述操作数类型转换的影响。

下述C++代码以除法运算为例,研究算术运算过程中的类型转换:

//Project - DivisionOperator
#include <iostream>
using namespace std;
   
int main(){
    int i = 3;                     //有符号整数
   
    auto a = 10 / i;               //整数 / 整数 = 整数
    cout << "10/3 = " << a << ", type = " << typeid(a).name() << endl;
   
    auto b = double(10.0) / i;     //浮点数 / 整数 = 浮点数
    cout << "10.0/3 = " << b << ", type = " << typeid(b).name() << endl;
   
    auto c = i / double(10.0);     //整数 / 浮点数 = 浮点数
    cout << "3/10.0 = " << c << ", type = " << typeid(c).name() << endl;
   
    auto d = double(10.0) / 3.0f;  //双精度浮点数 / 单精度浮点数 = 双精度浮点数
    cout << "10.0/3.0f = " << d << ", type = " << typeid(d).name() << endl;
   
    auto e = (unsigned int)10 / i; //无符号整数 / 有符号整数 = 无符号整数
    cout << "unsigned 10/3 = " << e << ", type = " << typeid(e).name() << endl;
   
    auto f = 10 / (unsigned int)3; //有符号整数 / 无符号整数 = 无符号整数
    cout << "10/unsigned 3 = " << f << ", type = " << typeid(f).name() << endl;
   
    return 0;
}

上述代码的执行结果为:

10/3 = 3, type = i
10.0/3 = 3.33333, type = d
3/10.0 = 0.3, type = d
10.0/3.0f = 3.33333, type = d
unsigned 10/3 = 3, type = j
10/unsigned 3 = 3, type = j

🚩第6行:为了便于解释,此处显式定义了一个值为3的整数i,根据前述章节的描述,它事实上是一个有符号整数(signed int)。接下来的代码里,我们使用auto【C++ 11】来推断商的结果类型,并使用typeid()操作符及其name成员函数▲将商的类型打印出来。

🚩第8 ~ 9行:输出结果证实,整数/整数的结果类型为整数,商的小数部分被舍弃。请学习过Python语言的读者注意,这与Python语言不同。

🚩第11 ~ 12行:输出结果证实,浮点数/整数的结果类型为浮点数,此处的浮点数类型为double。请读者注意,字面量10.0的类型也是double。作者在这里有意写成double(10.0),通过一个显式的double类型构造函数▲将10.0“转换”成一个double,是因为担心读者无法正确识别10.0字面量的类型而产生疑惑。

🚩第14 ~ 15行:输出结果证实,整数/浮点数的结果类型为浮点数。

🚩第17 ~ 18行:输出结果证实,双精度浮点数/单精度浮点数的结果类型为双精度浮点数。3.0f中的后缀f表明该字面量为float类型。

🚩第20 ~ 24行:输出结果证实,有符号整数与无符号整数进行除法运算的结果为无符号整数。输出结果type = j中的j指无符号整数。

**当表达式中两种不同类型的对象进行算术运算时,编译器总是将较小的类型转换为较大的类型再进行计算。**第11行中,一个double除以一个int,编译器会先将整数i转换成double,再进行除法运算。两个double相除,其结果自然是double。

需要注意的是,这种形式的隐式类型转换只是创建一个被转换对象的副本,不会改变被转换对象自身。比如第11行的i被转成double,编译器只是创建了一个double类型的用完即弃的临时对象,其值与i相同。第11行代码执行前后,对象i不会有任何变化。

为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

简洁的C及C++
由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
Python编程基础及应用
由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造

如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

Python编程基础及应用

Python编程基础及应用实验教程
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值