作者 | 梁唐
大家好,我是梁唐。
这是EasyC++系列第8篇,我们来聊聊C++中的算术运算符。
C++当中提供5种基础的算术运算符:加法、减法、乘法、除法和取模。
我们来看下代码:
int a = 10, b = 3;
cout << a + b << endl; // 13
cout << a - b << endl; // 7
cout << a * b << endl; // 30
cout << a / b << endl; // 3
cout << a % b << endl; // 1
前面三个都非常简单,着重讲下最后两种。
对于除法来说,我们要注意的是它是区分类型的。当我们的除数和被除数都是整数的时候,得到的结果也会是一个整数。所以10 ➗ 3得到的结果就是3,它的小数部分会被抛弃。想要得到小数结果,只需要除数或者被除数当中有一个是浮点型即可。
取模运算符求的就是一个数除以另外一个数之后的余数。这里要注意,在其他语言当中并没有对取模运算的限制,而在C++当中,严格限制了取模运算的对象只能是整数。否则编译的时候会报错:
C++当中算术运算符的优先级和我们从小数学课本里是一样的,先乘除再加减。
如:
3 + 4 * 5; // 23
120 / 4 * 5; // 150
20 * 5 + 4 * 6; // 124
即当乘除法和加减法同时出现时,先算乘除后算加减。如果有多个运算符同样优先级,那么先左后右。
前面说了,同样是除法,根据除数和被除数类型的不同,得到的结果也不同。这样固然非常灵活,但是除了更加复杂给学习、使用者带来负担之外,也会使得计算机的操作更加复杂。
比如我们一共有11种整型和3种浮点型,那么我们在计算的时候就会出现大量不同的情况。比如short + short,short + int,short + double等等,那么编译器就需要对这么多种情况都进行处理,这显然是非常麻烦的。为了解决这个问题,C++会自动执行许多类型转换。
下面我们对这些情况进行一一讨论。
当我们对某个值进行初始化或者赋值的时候,C++会自动将赋予的值转化成接收者的类型。比如:
float a = 3.5f;
double b = a;
在上面这个例子当中,我们将一个float
类型的变量a赋值给了double
类型的b。那么编译器会将a的值拓展成64位的double
再赋值给b。也就是说不会影响b的类型。
这样将长度更短的变量转化成更长变量的类型转换除了多占用一点内存之外,不会导致什么问题。但反向操作可能就会出错,比如:
long long a = 0x3f3f3f3f3f3f3f;
int b = a;
在上面的例子当中,我们将一个long long
赋值给了int
,由于a的数值非常大超过了int
能够承载的范围,进行这样的赋值之后,编译器并不会报错(甚至不会有警告),但将会导致结果错误。b变量将不可能再和a变量相等。
再比如将float
变量赋值给int
的时候,同样也会有类似的问题,所以在进行赋值的时候,当两个变量的类型不同时,千万要当心。
这是C++ 11的新特性,使用大括号进行初始化,这种操作被称为列表初始化。
这种方式的好处和坏处都很明显,好处是它不允许变量长度缩窄的情况,坏处则是又增加了学习的成本。例如,不允许将浮点型转换成整型。在不同的整型之间以及整型转化成浮点型的操作可能被允许,取决于编译器知道目标变量能够正确地存储赋给它的值。比如可以将int
类型赋值给long
,因为long
总是至少与int
一样长,反向操作则会被禁止。
int a = 0x3f3f3f3f;
long b = {a}; // 允许
long a = 0x3f3f3f3f;
int b = {a}; // 禁止
关于列表初始化,C++ primer当中还列举了一个非常有意思的case:
const int x = 55;
char c = {x}; // 允许
int x = 55;
char c = {x}; // 禁止
const int x = 1255;
char c = {x}; // 禁止
const int x = 1255;
char c = x; // 允许会警告
这是为什么呢?因为我们加了const修饰之后,编译器就明确知道了x的值,就等于55,它在char
类型的范围内,所以允许将它转化成char
。如果不加const,那么在编译器看来x是一个int
型的变量,它的范围要大于char
,所以会禁止。即使我们加了const修饰,如果x的值过大,超过char
的范围,也同样会被禁止。
当一个表达式当中出现多个变量类型的时候,C++也会进行转换。由于可能涉及的情况非常多,使得这个转换的规则也会比较复杂。
bool
、char
、unsigned char
、signed char
和short
全部转换为int
对于bool类型来说,true
会被转化成1,false
转换成0,其他类型的转换应该都很好理解,都是将范围更小的变量转化成范围更大的int
,这种转换称作整型提升。因为通常int
类型都是计算机最自然的类型,也意味着计算机在处理int
的时候,处理的速度最快。
将不同类型进行运算的时候,也会做一些转换。比如将int
和float
相加的时候,由于涉及到两种类型,其中范围较小的那个会被转换成较大的类型。比如如果我们计算9.0 / 5
,那么编译器会先将5转化成5.0,再进行除法运算,这样得到的结果自然也是一个double
。
C++11的规范中除了一个类型转换的校验表,我们可以参考一下校验表理解一下类型转换的过程。
long double
,则将另外一个数也转成long double
double
,则将另外一个数也转成double
float
,则将另外一个数也转成float
C++当中允许开发者手动强制对变量的类型进行转换,这也是C++的设计思路,规则严谨,但也允许推翻规则追求灵活度。
强制类型转换的方式有两种写法:
int a;
(long) a;
long (a);
这两行代码都是将一个int
型的a转换成long
型的,上面的是C语言的写法,底下一行是C++的写法。
还有一点要注意就是转换的顺序,我们来看一个例子:
int a = 11.99 + 19.99;
cout << a << endl;
int b = int(11.99) + int(19.99);
cout << b << endl;
在这段代码当中a和b输出的结果是不同的,a输出的结果是31,而b是30。
这是因为第一行代码是先计算的加法,得到31.98,再通过类型转换将31.98转换成int
。对于浮点数向整型的转换,C++会直接抹掉小数部分,所以得到的结果是31。而第二行代码当中,我们是先进行的类型转换,11.99和19.99分别被转换成了11和19,相加得到的结果也就是30了。
这里的一点差别很多新人经常踩坑,千万注意。