6.1.1 数据类型
C语言是有类型的语言。C语言的变量,必须:
1.在使用前定义;
2.确定类型.
如果我们一开始直接说a=6:使用了未定义的标识符a。
C语言之后语言朝着两个方向发展:
- C++和Java更强调类型,对类型的检查更加严格
- JavaScript、Python、PHP不着重类型,甚至不需要事先定义。
支持强类型的观点认为明确的类型有助于尽早发现程序中的简单错误;
反对强类型的观点认为过于强调类型,迫使程序员面对底层,实现而非事务逻辑
总的来说,早期语言、面向底层的语言强调类型。
C语言需要类型,但是对类型的安全检查并不足够:
(以下斜体的是c99类型)
整数:char、short、int、long、long long;
浮点数:float,double,long double;
逻辑:bool;
指针;
以及自定义类型。
类型的不同之处在于:
- 类型名称:int,long,double……
- 输入输出时的格式化(占位符):%d,%ld,%lf……
- 所表达的数的范围:char<short<int<float<double
- 内存中占据的大小:1~16个字节
内存中的表达形式:二进制数(补码)、编码(浮点是编码形式)
编码形式不能直接进行运算,而两个整数可以。
Sizeof()是一个运算符,给出某个类型或变量在内存中所占据的字节数。如:sizeof(int)
sizeof(i)
(1个字节是8个比特,所以int
4个字节32个比特)
sizeof是一个静态运算符,在编译时其结果就已经决定了
比如上一行写sizeof(a++),但输出a之后会发现a其实没有++。
6.1.2 整数类型
sizeof(char)=1;//char1字节(8比特)
sizeof(short)=2;//short2字节
sizeof(int)=4;//由编译器决定,通常代表“1个字”
sizeof(long)=4;//由编译器决定,通常代表“1个字”
sizeof(long long)=8;//long long 8字节
说一台计算机的字长的时候,我们指的是寄存器RAM是多少字长(比特)的。
每一次从CPU中取数据取32个比特
所以说int就是用来表达寄存器的,不同计算机可能得到不一样的sizeof(int)
。
6.1.3 整数的内部表达
计算机内部,一切都是二进制的,只是说我们以不同的方式去看待它。
十进制:18→二进制:00100010
十进制用负号来表示负数,运算一般在结果上再加负号。
而二进制怎么表示负数呢?
一个字节(8位)可以表达的数:00000000 ~ 11111111(0~255)
三种方案:
- 像十进制一样,有一种特殊的标志(类似符号)来表示负数(缺陷:计算机做加减法的时候,要像判断十进制的负号一样,我们需要一个东西去控制加号还是减号。不过每次计算机都要特别地去判断这个符号的正负,这样就会比较复杂。)
- 从0000 0000到1111 11111,取中间的数为0,如1000 0000表示0,比他小的是负数,比他大的是正数,各一半(缺陷:所有数都要和这个数做减法来计算其值,会比较复杂)
- 补码
思路:本质上来看,(互为相反数的)负数+正数=0。这是提供思路的一种方法。
比如我们希望-1+1→0,如何能够做到?
如0→0000 0000,1→0000 0001,我们让一个数+1得到0。这个数字选谁?
全1的数字1111 1111。因为0000 0001+1111 1111→1 0000 0000多出来的一位(溢出)那一位被丢掉了,相加结果即是00000000。妙啊
或者换个角度:-1=0-1=(1)0000 0000-0000 0001→1111 1111
(1111 1111被当作纯二进制看待时是255,被当做补码看待时是-1)
所以对于-a来说,他的补码就是0-a,实际就是2^n^-a,n是该种类型的位数
补码的意义就是拿补码和原码可以加出一个溢出的0。
另一个好处是这样我们做计算的时候,不需要调整加减,全部都是加法(+补码就是-原码)。
6.1.4 整数的范围:如何推算整数类型所能表达的数的范围,越界了会怎样?
一个字节(8位):0000 0000~1111 1111
其中0000 0000→0
0000 0001 ~ 0111 1111→1~127(纯二进制数)
1000 0000 ~ 1111 1111→-128 ~ -1(用补码表示的数)
还是那句话,关键在于我们以什么样的方式去看待这个数。当成纯二进制数,1111 1111就是255;当成整数,就是-1
char c=255;
int i=255;
printf("%d%d",c,i);
输出结果:c=-1,i=255。
因为对于字符c来说,255→1111 1111
而对于整形变量i来说,266→0000 0000 0000 0000 0000 0000 1111 1111
|类型|大小|范围|
|--|--|--|
|char|1字节|-128~127|
|short|2字节|-2^15^~2^15^-1|
|int|取决于编译器|-2^31^~2^31^-1|
|long|4字节|-2^31^~2^31^-1|
|long long|8字节|-2^63^~2^63^-1|
比如char长度256,会被分成三部分:-128~-1,0,1~127
所有整数类型范围都是-2^(n-1)^~2^(n-1)^-1
如果我们希望一个数据从计算机中拿出来时我们将其视为纯二进制看待,我们要在其前面加上一个关键字unsigned.
unsigned char c=255;
unsigned使得这个类型在正整数表达部分范围扩大一倍,但是不能表达负数了。
如果一个字面量常数想要表达自己是unsigned,可以在后面加u/U:255U
Similarly,想表达自己是个long,后面加l
unsigned的初衷不是为了扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位。
整数是以纯二进制方式进行运算的。
补码就是顺时针-,逆时针+
char c=127;
c++;
然后c变成了-128。
Similarly,-128-1变成了127。
unsigned(无符号)则是直接加减。
但是
unsigned char c=255;
c++;
并不像预想中的得到256,我们得到c=0,因为256在8比特里是全0,第九位是1被丢掉。
c=0;
c--;
得到255。
所以对于unsigned,也是顺时针-,逆时针+
int a=0,b=1;
while(++a>0);
printf("int数据类型的最大数是:%d\n",a-1);
while((a/=10)!=0)b++;
printf("int数据类型最大的数的数位是:%d",b);
可以用这种方法推算int 的最大范围。while(++a>0)
使得a加到127→-128越界后变成负数,然后printf("%d",a-1);
再返回到127,得到int可以表达的最大数
同理,如果想得到unsigned的最大范围,参考上面的表,while(a!=0);
6.1.5 整数的格式化:如何格式化地输入输出整数,如何处理8进制/16进制
整数的输入输出只有两种形式:int和long long
%d:int
%u: unsigned
%ld: long long
%lu: unsigned long long
若int i=-1;储存在计算机中是二进制形式,即全1,若以%u即unsigned形式输出,得到的结果就是int 型的最大值(4294967295)
如此仍然可见,重点在于我们以什么样的正确的方式去看待这个结果。
8进制(octal):一个以0开始的数字字面量
16进制(Hexadecimal):一个以0x开始的数字字面量
char c=012;
int i=0x12;
printf("c=%d,i=%d\n",c,i);
得到结果(%d转化为十进制):c=10,i=18
(计算机内部同样还是二进制的形式)
想要输出8进制:%o
或%O
想输出16进制:%x
或%X
,小写的x输出小写的字母,大写的输出大写的
(但是这样输出的结果前面是没有0和0x的。所以可以写printf(“c=0%o,i=0x%x\n”,c,i);
8进制和16进制只是如何把数字表达为字符串,与内部如何表达数字无关。scanf
中也可以用%o,表示读进来的数我们把其当做8进制来读。
16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位(0001 0010→1 2)
八进制的一个数字正好表达3位二进制,因为早期计算机的字长是12的倍数,并非8。
6.1.6 整数类型的选择
char,short,int,long,long long
每种类型有不同大小,不同范围,内部是什么样,表达出来是什么样,……
C语言这些又多又复杂的原因:1.C语言的严谨性,为了准确表达内存,做底层程序的需要
2.C语言要和硬件打交道,要准确表达计算机里的东西(内存、寄存器、接口……)
建议:没有特殊需要,就选择int
原因 :
1) 现在CPU字长(CPU和内存之间的通道。如果char可能会从32位中先挑出来8个,可能会花费时间)普遍是32位/64位,一次内存读写、一次计算都是int,选择更短的类型不会更快,甚至可能会更慢
2) 现代编译器一般会设计内存对齐,(比如这个东西虽然占据8bit,在内存中仍然占据了一个int。这个事等到结构再讲)所以更短的类型实际在内存中也可能占据一个int的大小。除非在做顶层的硬件,告诉你硬件多大你就要用多大去运算
至于Unsigned与否,只是输出的形式不同,内部的计算还是一样的
6.1.7 浮点类型
float,double
32位,64位
10^38^,10^308^
7位有效数字,15
但是float靠近0的部分(0~10^38^)有很小一部分不能表达(这应该是之前的题目为什么if判断浮点数不写做差==0,而是写做差<1e-6)
float还能表达0,+-inf(无穷),nan(非有效数字)
%e/%E:科学计数法
1234.56789输出得到:1.234567e+03
-5.67E+16,科学计数法可选的有:+-号,e或E,小数点,E后面的符号+-
Double ff=1E-10;
输出精度:%.数字f
可以指定输出小数点后几位,这样输出的结果是做四舍五入的
%.3f,-0.0045→-0.004
%.30f→后面输出30位,-0.0049→-0.00489999……(计算机最终只能用离散的数字来表示数字,这就是浮点数的误差。Double更准确,但可能依然不精确)
%.3f,-0.00049→-0.000
6.1.8 浮点数的范围和精度
Inf无穷,nan输出不存在的浮点数
正数除0.0→inf
负数除0.0→-inf
0.0/0.0→nan
(如果是整数,12/0编译不过)
浮点数范围不包括无穷大,但它可以表示。
float a,b,c;
a=1.345f;
b=1.123f;
c=a+b;
if(c==2.468)printf("相等");
else printf("不相等,c=%.10f,或%f",c,c);
加f:表明身份,让它是float
结果:不相等!c=2.4679999352,或2.468000
f1==f2不一定成功,应该写fabs(f1-f2)<1e-12
所以浮点数不能做精确计算,最后误差会累积起来的。只能在一定范围内相信小数点
Android计算器低级错误?都是二进制惹的祸!
而传统计算器直接用整数做运算
而浮点数内部是编码形式
从左到右分别表示正数负数、指数、小数
如果没有特殊需要,就用double
现代cpu能直接对double做硬件计算,性能不会比float差,而且在64位的机器上数据存储的速度也不会比float慢
6.1.9 字符类型:char是整数也是字符
char是整数,也是字符。(character)
原因1:我们可以用单引号表示字符的字面量'a'
,'1'
''
也是字符
我们可以在printf和scanf里用%c来输入和输出字符。
计算机内部每一个值都有一个字符去表达它(ASCII)如1对应的码是49
输入1给计算机的方法:scanf("%c",&c);
输入1
或者scanf("%d",&c);
输入49
输出同样,因为49=='1'
注意空格的问题。
scanf("%d%c",&i,&c);
scanf("%d %c",&i,&c);
这样中间几个空格回车都没关系,中间的空格会全部被读掉
从A到Z的ASCII码都是连着的
一个字符加一个数字可以转到后面那个对应ASCII码的字符处int i='Z'-'A';
//两个字符相减,得到它们在表中的距离
字母在ASCII表中顺序排列,且大小写字母分开排列'a'-'A'
可以得到小写字母与大写字母之间的距离,所以'a'+('A'-'a')
可以转小写为大写
6.1.10 逃逸字符
用来表达无法印出来的控制字符或特殊字符,由一个反斜杠\开头,后面跟着一个字符
printf("请分别输入身高的英尺和英寸,""如输入\"5 7\"表示5英尺7英寸:");
字符 | 意义 | 字符 | 意义 |
---|---|---|---|
\b | 回退一格 | \" | 双引号 |
\t | 到下一个表格位 | \' | 单引号 |
\n | 换行 | \ | 反斜杠本身 |
\r | 回车 |
(作业时就会有最后结尾多个空格,使用/b来删掉的情况,但其实应该说是覆盖了吧,总之这样运行的时候是不正确的,还是要想办法控制输出的空格等的格式正确)
例子
printf("123\b\n456\n");
得到结果:
但是到了其他程序那边运行
123
456
原因:dev c++那个黑框框,是别人写的程序(shell)来帮助我们运行的;部分东西是经过它处理的,比如\b\n,不同shell对此处理不一样。如dev c++用BS字符来表示。
\b\n都是控制字符
BS是回到上一格,没输出东西就什么结果都没有,输出了就把他盖住了
也不能否认有的程序,输出的时候把\b翻译成删除
\t:到下一个表格位(在每一行中有一些固定的位置,\t代表输出的固定的位置)
比如
printf("123\t456\n");
printf("12\t456\n");
\n换行\r回车,源自打字机的动作
一直敲键盘,打字机的轴就往左移
敲一下回车就回到右边,这叫回车
然后往上调一下纸,这叫换行。
不过dev c++把回车就当做回车+换行了。
6.1.11 类型转换
自动类型转换:当运算符两边出现不一样的类型时,会自动转成较大的类型,即能表达的数范围更大的类型。char->short->int->long->long long
int->float->double
对于printf,任何小于int的类型都会被转换成int;float会被转换成double。所以printf输出double的时候写%f就也行。
但scanf不会,想输入short时,需要%hd。
强制类型转换:(类型)值
如:
(int)10.2;
(short)32;
但是注意安全性,小的变量不总能表达大的量。如(short)32768
,因为short最大范围是32767,所以会被转化为-32768
只是从那个变量计算出了一个新类型的值,他并不改变那个变量的值或类型。
int i=32768;
short s=(short)i;
printf("%d\n",i);
i输出仍然是32768。强制类型转换不会改变这个变量自身的类型或值。
如果想计算a,b的int型相除得到的i的int型的值:
double a=1.0;
double b=2.0;
int i=(int)a/b;
实际上是先int a,再/浮点数b,强制类型转换的优先级高于四则运算。
所以正确的写法是
int i=(int)(a/b);
(double)(a/b);