读书笔记1--C Primer Plus:C基本数据类型

一、基本数据类型

C基本数据类型分为整型浮点型两类。两者区分也很简单,有小数部分的就是浮点数,否则就是整数。比如-3为整数,而-3.0就是浮点数。同样的各种浮点数表示-3.14E3就是-3.14x10³,2E-8表示2.0x10-8。这些只是书写上的区别,方便我们人本身取区别,这两类数据在内存中使用二进制存储,在计算机内存中的区别在于存储方式的不同:
整型内存存储方式
浮点型内存存储法师

1、整形

C数据类型
斜体样式如上图所示,在C中规定了int整型和修饰int的short、long、unsigned。一般short int也基座short,long int 也基座long,C99标准加入了long long int 也基座long long。同时C标准只规定了这些类型的大小关系,int要不小于short,同时int不能大于long,而具体大小上int占多少字节还与系统的字长有关,如果是16位系统那么int可能就是16位的,如果是32位系统那么int可能就是32位的。目前,最常见的设置是,short占16位int占16位或32位long占32位long long占64位
由于int类型会在不同系统上不同,所以编程时若确实需要32位,应该使用long而不是int,以便程序移植到16位系统上能正常工作。如果确实需要16位空间是,应该使用short而不是int,这样就算移植到别的系统也可以节省空间。
unsignedsigned关键字用来修饰这四种类型,前者表示无符号整数,后者只是强调这是有符号整数,为啥后者是强调,因为这四种类型本身就是表示整数,包括负整数、0、正整数。
有符号整型取值范围
上图是C规定的数据有符号整型取值范围,8位的(-128,127),16位的(-32768,32767),32位的(-2147483648,-2147483647),64位的(-9223372036854775808,9223372036854775807),这计算很简单,用二进制计算一下就明白了,这些数值就是各类型数据在计算机内存以二进制形式存储时所能表示的范围大小。
无符号整型取值范围
上图是无符号整型取值范围,也就是没有负数,最高的位不用来表示±了,也用来表示数据大小,所以8位的(0,255)、16位的(0,65535)、32位的(0,4294967295)、64位的(0,0xfffffffffffffff)。
关于有符号无符号数据溢出和二进制表示,请自学源码、反码、补码等知识。
在格式化输出时,如%hd、%lx等符号表示以某种格式查看内存里的数据并以某种进制显示在屏幕上,比如%hd就表示把内存的数据当成short类型来只查看该数据的前16字节内容,并以d(10进制)形式打印。而%ld则是查看32位,并以x(8进制)形式打印显示。

long num = 65537;
printf("%ld",num);
printf("%hd",num);

![](https://img-blog.csdnimg.cn/direct/5089ff02c2b24a6ca44e47b0442fd5d6.png
如上代码,%ld打印出来的结果会是65537,因为在long的32位表示范围内。而%hd打印出来就是1,因为此时代码只会按short类型(16位)来查看低16位数据,高16位被自动忽略了。也及时所谓的截断式读取。类似的还有对unsigned int =3000000000 定义的数据使用%u和%d,虽然都是按照32位来读取完整了,但是读取显示的时候格式方式不同,%u把最高位的1当做2的31次方来处理,%d把最高位的1当做±符号来处理,所以打印出来的结果就也会不同。

short  num = 32767;
printf("%%d格式:%d\n", num);
printf("%%hd格式:%hd\n", num);

在这里插入图片描述
这种往大了读就不会有截断式读取的漏洞。
总的来说,不管怎么,都应该按照数据原来是什么大小的就按照什么大小去读取显示(比如short的就对应使用%h),另外如果是unsigned的就要加上u后缀,不是unsigned的就不要加此后缀。这样去格式化显示才是会正确和真实反映内存中声明定义了的数据。

还有一种整数类型叫char,虽然它经常用于存储字符或标点符号,但内存里是一样是当做整型去存储的(我在开头就说了基本数据类型就两大块,整型和浮点型),只不过所谓的字符标点啥的也是用二进制编码表示的归根结底还是数字,比如ASCII码使用整数65表示字母A。所以,不例外,char也是整型之一。C规定,char整型占用的大小为1字节(8位),也就是一个char变量实际就是存储了一字节大小的整数值。

char ch = 'C';
printf("%%c=%c \n",ch);
printf("%%d=%d \n", ch);

在这里插入图片描述

这里的%c就是把char变量存储的整数值读出来当做一个字符去打印显示,而%d就是把整数值按10进制打印显示出来。不管以什么样的格式去打印去显示,都改变不了它在内存里是整型的事实。
在这里插入图片描述
最后注意,有些编译器的实现是把char当做有符号类型,那么范围是(-128,127),而有些编译器是将其实现为无符号类型,那么范围是(0,255)。这具体要看编译器是如何实现的,同时C标准也允许关键字char前面使用unsigned 和 signed来修饰,不管编译器默认char是什么类型,unsigned char都表示无符号类型,signed char都表示有符号类型。如下图,我用的VS IDE里面stdint.h头文件对两者做了重定义,有符号的定义为int8_t,无符号的定义为uint8_t,这样使用的使用就直接使用重定义后的来声明变量就方便多了,这种情况主要是用来处理小整数的。如果只是处理字符,那就无所谓了直接使用char即可,不需要修饰。
在这里插入图片描述

至此,5种基本数据类型(char、short、int、long、long long ),以及它们的signed和unsigned的表示范围就都讲完了。关于整数类型的声明类型、打印格式的匹配,C标准提供了头文件stdiint.h和inttypes.h供我们使用和移植。

附加:C99标添加了_Bool类型,用于表示布尔类型,即TRUE和FALSE,因为C语言使用1表示true,0表示false,所以布尔类型其实还是整型之一,只不过它只占用1位的存储空间。

2、浮点型

C语言的浮点数类型有float、double、long double,其表示方式类似于科学计数法。注意浮点常量一般会默认为double型。程序中三种写法:常规(如,23.456)、指数形式(45.02E10)、C99和C11(0xa.1fp10,用十六进制和2的幂表示)
在这里插入图片描述
在这里插入图片描述

float类型为单精度浮点数,占用4个字节,也即32位。其中8位用于表示指数的值和±符号,剩余24位用于有效尾数值和±符号。为什么有精度一说?因为任意两小数之间有无穷无尽的小数,比如0.1和0.2之间的小数无穷无尽,可以小到0.1E-100那都是可以的,没有尽头,但是数据存储在内存中是按照二进制表示的,比如二进制11.11就是十进制3.75,二进制11.10就是十进制3.50,那么我想表示十进制小数3.61怎么办?二进制11.10和11.11之间已经无法找出空位了来表示中间值3.61,所以只能提高精度,把小数点往后移,二进制11.101表示十进制3.625,可见已经很接近3.61了,但是还是不相等。那没办法,继续提高精度,二进制11.1001表示十进制3.5625,还是不等于3.61,所以精度还等继续提升以去逼近。事实上,十进制3.61换成二进制应该为11.10011100001010001111010111000010100011110101110001。这就说明,要想在计算机任意或者表示更多的小数,就得把精度不断提高,精度越高,能表示的浮点数越多,即便表示不了,那也要最逼近要表示的小数,减少误差。

double类型为双精度浮点数,占用8个字节,也即64位。这里多出的32位全部也用来表示有效尾数值和±符号,这样一来,提高了精度,减少了舍入误差。
long double类型为双精度浮点数。C90新增的,只不过内存更大,以实现更高的精度。但是C标准只保证它至少与double相同,具体看系统能不能做到,现有64位系统基本无法实现比double更高精度了,所以一般long double 和double一样。

编写C代码时浮点数常量(默认为double,除非加后缀f表示float,L表示long double)的写法多种多样:3.14159,.231,3.e9,4e16,3e-15,.8E-5,100.,等等,有时候小数点不能省略,有时候可以。
在这里插入图片描述
打印浮点数时,%f表示按照10进制计数法打印float和double精度类型的浮点数,%e表示按照指数计数法去打印。若到打印精度为long double的则要添加L后缀,%Lf或**%Le**。(要区分的是这只是按照某种格式去打印,并不是内存中的样子)
在这里插入图片描述
在这里插入图片描述

计算机内存上如上图所示,指数部分为整数,表示小数点的位置,尾数部分为小数表示小数点后面的具体值。如-3.75存到float里面就是:1 10000000 11100000000000000000000,直观点就是-1x2*?x(1+0.5+0.25+0.125)=-1x2*?x(1+0.875) = -1x21x1.875 = -3.75。很简单,因为存到内存里是用二进制表示的,所以,先不断除以2直到尾数部分可以用科学计数法表示可,-3.75/2=-1.875=-1.875e+0。
存储和计算方式如下图所示:注意关于指数部分,8位为0~255数值范围,其中0-7f表示-127到0,而80到ff则依次表示1到128,这和平常我们表示计算机数字有些不同。
在这里插入图片描述
同理,-59.375在内存中1 10000100 11011011000000000000000,方法也是先不断除2(本质就在在计算机的二进制移动小数点),
-59.375/2 》-29.6875》14.84375》7.421875》3.7109375》1.85546875,所以-59.375=-1.85546875x2^(+5)=-2
(+5)x(1+0.5+0.25+0.0625+0.03125+0.0078125+0.00390625) = -(2*+5)x(1+0.85546875)。其中指数部分+5就写成132(10000100),尾数部分就正常计算小数即可。

讲了那么多除以2的,当然也有乘以2的,反正只要能将尾数变成1.xxx这种就行,比如0.85546875在内存中为0 01111110 10110110000000000000000。原理就是0.85546875x2=1.7109375=(1+0.7109375)。所以0.856875=+2^-1x(1+0.7109375),所以指数部分-1写成01111110(即126),尾数部分0.7109375就写成10110110000000000000000(即2*-1+2*-3+2*-4+2*-6+2*-7)。

同整型数一样,浮点数也有溢出(overflow),分为上溢和下溢,前者表示数值超过所声明的类型的内存存储范围了,就是装不下怎么大的数,这样的话printf会打印inf,这是一个表示无穷大的特定值。后者表示数值太小了,以至于现有精度无法表示出来(前面将float时有介绍过),比如,3.1452e-15,保持现有精度不变,此数再除以10,为0.3145e-15,因为精度就只到小数点后4位,所以原本尾数2就溢出(下溢)了,同理换算到二进制里也一样,不管时float还是double或者long double其溢出都类似如此。
对于float浮点数,8位用于指数表示小数点的位置,尾数部分24位,其中1位用作表示±符号,剩余23位用来表示有效数字,2*-23 = 0.00000011920928955078125>10*-7,但<10*-6所以精度在6位或者7位,再小就不能表示了。23位二进制最小能表示的小数精度就到此,其他情况都会比此值大。比如2.01e10 + 2.0 = 2.01e10 + 0.0000000002e10 = 2.010000e10,原因就在于0.0000000002e10 溢出精度而在尾数右移中下溢了。

在这里插入图片描述
又比如说,10.0+0.0123e-10 = 1.00e1+ 0.000000000000123e1 = 1.000000e1,出现这种情况同样是有效尾数为6位,而内存里要存下0.000000000000123属实为难了,float的话顶多存到第六位,所以上述这些加法都会错误,这种精度就是浮点数在计算机里进行运算的痛点。不过double型和long double型增加的位数基本都用来添加尾数长度了,所以精度方面基本都用了,long double还增加了指数部分,所以能表示的范围也越大了(尾数位越多则小数精度越高;指数位越多则能表示的数范围越大,毕竟小数点能移动的更远,能表示的数就越大,大到甚至能用来计算天体运动等等)。
在这里插入图片描述

3、C99和C11附加复数和虚数型

其中复数型包括float _Complexdouble _Complexlong double _Complex型,虚数型包括float _Imaginarydouble _Imaginarylong double _Imaginary型。这些数据每个都包含两个对应浮点型的数据,比如float_Complex,存储了两个float类型数据,一个表示实部数据,一个表示虚部数据。若有额外使用可参考头文件complex.h详情。因为这块主要作为可选项,有些并没有直接实现。

4、总结

至此,所有C基本数据类型都已经整理完毕。剩余的叫做其他数据类型的,包括指针、数组、结构体、联合,都是从基本数据类型中构造而来的。
声明基本数据类型的关键为:charshortintlongunsignedsignedfloatdouble_Bool_Complex_Imaginary。组成的基本数据类型如下:
1、整型:(注意,单独使用unsigned 声明变量会被默认为unsigned int
unsigned char … … … (signed)char (1Byte)(C标准规定)
unsigned short … … … (signed)short >=16位(C标准规定)
unsigned int … … … … (signed)int >=short 、<=long(C标准规定)
unsigned long … … … . (signed)long >=32位 (C标准规定)
unsigned long long … … (signed)long long >>64位(C标准规定)
_Bool 1位存储0和1即可
2、浮点型:(范围和精度依次递增
float
double
long double
3、复数型:(可选项
float _Complex
double _Complex
long double _Complex
4、虚数型:(可选项
float _Imaginary
double _Imaginary
long double _Imaginary
在这里插入图片描述
其中上图的%zd转换说明用来匹配sizeof的返回类型,这样就不用手动写%hd,%ld等等。这些数据类型在声明和定义数据中,为数据在内存中的存储格式做了规划,这样编译器在将这些常量或者变量翻译成二进制存储到内存中时,就会按照对应的数据类型进行处理。当然,编写代码比如去赋值时,

float num1 = 2.012;
int a;
a = num1;

在这里插入图片描述
编译器这边就会给个警告,当然内存里不管是什么数据类型存的都是一串二进制数据,什么说明都没有。处理这些的都有编译器来进行,上图的例子,float num1变量在内存里是0 10000000 (指数段) 00000001100010010011100 (尾数段),而当将其赋值给int a变量时,编译器发现声明类型不匹配,就只取整数a=2,小数部分去掉,则a内存里就是0x00 00 00 02,虽然浮点型和整型存储格式完全不同,但不用担心,这些操作都有编译器自己搞定,在整个程序都编译完成后(包括头文件库等等),也就是都转化成二进制后在一起写进内存进行运行。
所以,所谓的编程,其实就是在和编译器打交道,写一些编译器能看懂的东西,然后编译器再把这些逻辑翻译成芯片能看懂的指令集(机器码)。也就是,所谓的数据类型声明就是声明给编译器看的,告诉编译器该开多大内存,以什么格式存储,以及在编译程序时可根据相应规则转换这些数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值