计算机程序基础教程(06):C语言 - 基础数据类型
【基础数据类型】
C语言将基础数据分为多种类型,使用不同的关键词表示,可以分为以下几大类:整数型、浮点型、字符型、布尔型。
● 整数型
整数类型又分为多种长度,每种长度又分为有符号数和无符号数,定义关键词如下:
unsigned char,无符号数,长度1字节,表示范围0-255。
unsigned short,无符号数,长度2字节,表示范围0-65535。
unsigned int,无符号数,长度4字节,表示范围0-4294967295。
unsigned long long,无符号数,长度8字节,表示范围0-18446744073709551615。
signed char,有符号数,长度1字节,表示范围 -127 - 128。
signed short,有符号数,长度2字节,表示范围 -32768 - 32767。
signed int,有符号数,长度4字节,表示范围 -2147483648 - 2147483647。
signed long long,有符号数,长度8字节,表示范围 -9223372036854775808 - 9223372036854775807。
● 浮点型
float,单精度浮点型,长度4字节。
double,双精度浮点型,长度8字节。
● 字符型
字符型其实就是char数据,用于存储字符编码的char数据称为字符型数据,为char类型数据赋值时可以使用一个英文字符赋值,编译器会转换为此字符的字符编码。
ASCII字符集如下:
● 布尔型
布尔型也是一个char数据,但是它只用来存储0和1,用于表示两种逻辑运算的结果,1表示对(true),0时表示错(false)。
在VC编译器中使用bool定义,使用true、false赋值,示例:bool a = true;
在GCC编译器中使用_Bool定义,使用0和1赋值,示例:_Bool a = 1;
若将布尔数据赋值为大于1的数据,则统一表示对(true),将其当做1处理。
● 常量与变量
变量是可以修改的数据,常量是不能修改的数据,常量需要在定义时赋值,并且之后不能再修改它的值,定义数据时默认是变量,添加const关键词指定为常量。
【运算符】
● 数学运算符
+,加法
-,减法
*,乘法
/,除法
%,取余
++,自加1
--,自减1
C语言除法运算不保留余数,对于计算结果为小数的情况需要进行取整操作,C语言使用向0取整的规则,3.5取整为3,-3.5取整为-3,除法结果直接丢弃余数即为向0取整的结果。
● 数学关系运算符
用于判断数据之间的大于、小于、等于关系,运算结果为一个布尔值,关系正确返回1,错误返回0。
>,大于
<,小于
==,等于
!=,不等于
>=,大于或者等于
<=,小于或者等于
● 逻辑关系运算符
用于对布尔数据进行运算,运算结果是一个布尔值。
&&,与运算,两个布尔数据都为1则结果为1,否则为0。
||,或运算,两个布尔数据有一个为1则结果为1,否则为0。
!,非运算,将一个布尔数据取反值,1变0,0变1。
● 移位运算符
将一个数据进行算数移位操作。
<<,算数左移
>>,算数右移
使用算数右移代替除以2的操作应该慎用,当除法运算无法整除时会涉及到向0取整操作,而使用算数右移会直接丢弃低位,导致右移结果与除法结果可能会不同。
● 按位逻辑运算符
将一个数据按照二进制位进行逻辑运算,得出一个新的数据。
&,按位与运算
|,按位或运算
^,按位异或运算,判断两个数字是否不同,若不同则运算结果此位为1,否则为0。
~,按位取反运算,将一个数据的每个二进制位取反值得出运算结果。
● 赋值运算符
=,赋值
+=,加法运算并赋值,A += B,等同于 A = A+B
-=,减法运算并赋值
*=,乘法运算并赋值
/=,除法运算并赋值
%=,取余运算并赋值
<<=,左移运算并赋值
>>=,右移运算并赋值
&=,按位与运算并赋值
|=,按位或运算并赋值
^=,按位异或运算并赋值
【数学运算优化】
当进行数学运算时,编译器在特定情况下会进行优化,比如两个常量之间的运算,编译器会在编译期间计算出结果,无需在程序执行期间计算,若变量与常量之间进行乘法、除法运算,编译器会使用执行速度更快的指令代替乘法或除法,得出相同结果。
其中除法指令最为耗时,优化也最为复杂,若除数为2的乘方,则转换为右移,之后进行向0取整规则调整,若除数不是2的乘方,则编译器转换为乘法,转换原理与减法转加法类似,也是先扩大再缩小,这里的缩小使用移位的方式实现。
使用10进制说明转换原理:
115 / 5 = 23
可以转换为
115 * 2 = 230,230右移1次等于23
因为除以5与乘以2的结果相差10倍,所以除以5转换为乘以2之后只需要再除以10即可,而除以10可以无需计算,右移1次即可。
● 有符号变量除以9
编译器会将除法转换为如下指令:
除法结果无法整除时需要进行向0取整,上述运算后,若被除数为负数,则计算结果需要额外加1才能满足向0取整的规则,末尾的两条指令用于实现加1运算。
首先将eax中的被除数算数右移31位,之后计算结果减eax,若被除数为负,则减-1,等同于加1,若被除数为正,则减0。
● 有符号变量除以7
编译器会将除法转换为如下指令:
上面的优化方案中,被除数乘以一个数之后又执行了一个加法,之后右移2次得出除法结果,原因是编译器进行除以7的优化时,无法计算出误差较小、长度又不超过32位的乘数,所以编译器使用将被除数缩小再右移的方式进行优化。
右移2位等于除以4,这里将除以7转换为除以4,两者若要相等的话,被除数必须首先减去自身的3/7,这样才能保证其1/4等于原值的1/7。
而计算被除数的3/7却又产生了另一个除法,实际上这个除法也会被编译器优化为乘法,首先 被除数 × 92492493h,之后使用rdx,等于乘法结果右移32位,这个结果不算符号位的话,就是被除数的3/7。
之后被除数与rdx相加,0x92492493转换为二进制是一个负数的补码,若被除数为负,则乘法结果为正,被除数会加一个正数,等于被除数减自身3/7,若被除数为正,则乘法结果为负,被除数会加一个负数,等于被除数减自身3/7。
最后右移2位,得出除以7的结果。
● 无符号变量除以7
编译器会将除法转换为如下指令:
这里除数为7,也会有之前的问题,就是编译器无法计算出误差较小、长度又不超过32位的乘数。
其实第三条乘法指令之后,使用存储高32位结果的edx就是除法结果,只不过这个结果有时候会不太精确,可能会比正确值大1,使用之后的4条指令进行调整将会消除误差。
下面解释后4条指令的作用: