数据类型
1. C语言基础数据类型
C语言是一种有类型的语言
- C语言的变量必须:
- 在使用前定义,并且
- 确定类型
通过一个例子来看:
#include "stdio.h"
int main(){
a = 10;
printf("a=%d", a);
return 0;
}
上述代码运行会报错,结果如下:
=> C语言定义变量时必须指定类型!!!
C语言以后的语言向两个方向发展:
- C++ / Java更强调类型,对类型的检查更严格
- JavaScript、Python、PHP不看重类型,甚至不需要事先定义
类型安全
- 支持强类型的观点认为明确的数据类型有助于尽早发现程序中的简单错误
- 反对强类型的观点认为过于强调类型迫使程序员面对底层、实现而非事务逻辑
- 总的来说,早期语言强调类型,面向底层的语言强调类型
- C语言需要类型,但是对类型的安全检查并不足够
C语言的类型
- 整数
- char、short、int、long、long long
- 浮点数
- float、double、long double
- 逻辑
- bool
- 指针
- 自定义类型
注:蓝色的是C99的类型
类型有何不同
- 类型名称:int、long、double
- 输入输出时的格式化:%d、%ld、%lf
- 所表达数的范围:char < short < int < float < double
- 内存中所占据的大小:1个字节到16个字节
- 内存中的表达形式:二进制数(补码)、编码
sizeof
- 是一个运算符,给出某个类型或变量在内存中所占据的字节数
- eg:sizeof(int)、sizeof(i)
举例代码如下:
#include "stdio.h"
int main(){
int a = 6;
printf("sizeof(int)=%ld\n", sizeof(int));
printf("sizeof(int)=%ld\n", sizeof(double));
printf("sizeof(a)=%ld\n", sizeof(a));
return 0;
}
结果如下:
可以看到,int
类型在内存中占用4个字节,1个字节等于8个比特,所以int
类型在内存中占用32个比特。double
类型在内存中占用8个字节。
注:通过
sizeof()
可以了解每个类型在内存中占据多大的空间。
sizeof
sizeof
是静态运算符,它的结果在编译时刻就决定了- 不要在
sizeof()
的括号里做运算,这些运算不会做的
情看下面代码:
#include "stdio.h"
int main(){
int a = 6;
printf("sizeof(int)=%ld\n", sizeof(int));
printf("sizeof(int)=%ld\n", sizeof(a++));
printf("a=%d\n", a);
return 0;
}
结果如下:
可以看出:
a++
根本就没有做,sizeof()
是静态的,只会返回括号内部的数据类型在内存中占据多大的空间。
2. 整数类型
首先,我们可以通过sizeof()
看看整数类型是怎么回事,代码如下:
#include "stdio.h"
int main(){
printf("sizeof(char)=%ld\n", sizeof(char));
printf("sizeof(short)=%ld\n", sizeof(short));
printf("sizeof(int)=%ld\n", sizeof(int));
printf("sizeof(long)=%ld\n", sizeof(long));
printf("sizeof(long long)=%ld\n", sizeof(long long));
return 0;
}
结果如下:
不同的编译器结果可能不同,32位和64位电脑结果也不同。
整数
- char:1字节(8比特、8位)
- short:2字节
- int:取决于编译器(CPU),通常的意义是“1个字”
- long:取决于编译器(CPU),通常的意义是“1个字”
- long long:8字节
什么是计算机的字长呢?
当我们在说一台计算机的字长的时候,我们指的是这台计算机里面的寄存器是几个比特的,比如32bit,说明每个寄存器能表达32bit的数据,同时也可以说CPU和RAM之间传输数据时,每次都是32bit。现在计算机更常见的是64bit。我们把这个32bit或64bit叫作一个字长,是因为它的寄存器一次可以处理的数据就是32bit或64bit,它的总线上一次可以传输的数据就是32bit或64bit,这个字长在C语言中反映为一个int
,也就是说int
想表达的就是一个寄存器的大小,所以在不同的平台上面、不同的CPU上面int
会变得不一样大。
3. 整数的内部表达
整数的内部表达
- 计算机内部一切都是二进制!
- 18 → 00010010
- 0 → 00000000
- -18 → ?
计算机内部如何表示负数?
十进制用“-”来表示负数:
12 + (-18) -> 12 -18 -> -6
12 - (-18) -> 12 +18 -> 30
12 * (-18) -> -(12 *18)
12 / (-18) -> -(12 /18)
我们在做十进制计算的时候,这个负号是特别的,是在数字之外的。
二进制负数
- 1个字节可以表达的数:
- 00000000 —— 11111111(0-255)
- 三种方案:
1. 仿照十进制,有一个特殊的标志表示负数
2. 取中间的数为0,如10000000表示0,比它小的是负数,比它大的是正数
3. 补码
前两种方法在计算机中实现较为麻烦,计算机中用补码表示负数
补码
- 考虑-1,我们希望-1+1=0。如何做到?
- 0 → 00000000
- 1 → 00000001
- 11111111 + 00000001 → 10000000
- 因为0 - 1 = -1,所以,-1 =
- (1)00000000 - 00000001 → 11111111
- 11111111被当作纯二进制看待时,是255,被当作补码看待时是-1
- 同理,对于 − a -a −a,其补码就是 0 − a 0-a 0−a,实际就是 2 n − a 2^{n}- a 2n−a, n n n是这种类型的位数,如:-2 补码就是0-2,实际就是 2 8 − 2 = 254 2^{8}- 2=254 28−2=254,254→11111110,补码11111110表示二进制-2。
注:补码的意义就是拿补码和原码可以加出一个溢出的“零”。
4. 整数的范围
数的范围
- 对于一个字节(8位),可以表达的是:
- 00000000 - 11111111
- 其中
- 00000000 → 0
- 11111111 ~ 10000000 → -1 ~ -128 当作补码时
- 00000001 ~ 01111111 → 1 ~ 127 纯二进制数
11111111作为纯二进制时就是255,作为补码看待就是-1。
我们可以通过如下代码测试:
#include "stdio.h"
int main(){
char c = 255;
int i = 255;
printf("c=%d, i=%d", c, i);
// 11111111
// 00000000 00000000 00000000 1111111
return 0;
}
结果如下:
为什么出现这样的结果?
首先应该知道计算机中数值都用补码存储!!!正数的补码是正数本身。
由于计算机中的二级制数据是以补码形式存在的,而 char
是1个字节(8bit),int
是4个字节(32bit)。所以 char c = 255
, 刚好是 -1 的补码,于是结果是-1 , int i = 255
, 只是低八位为1,高位全部为0,也就是正数(正数的补码也就是正数本身),于是结果为255。
整数的范围
- char:1字节:-128 ~ 127 ( − 2 8 − 1 -2^{8-1} −28−1 ~ 2 8 − 1 − 1 2^{8-1}-1 28−1−1)
- short:2字节:-32768 ~ 32767 ( − 2 16 − 1 -2^{16-1} −216−1 ~ 2 16 − 1 − 1 2^{16-1}-1 216−1−1)
- int:取决于编译器(CPU),通常的意义是“1个字”
- long:4字节
- long long:8字节
为什么负数要比正数多一个,如下图所示:
一个字节能表达的数是256个,中间的0
占据了一个,还剩下255个数,按照补码的设计少一个的正好在负数这边,所以负数比正数多一位,正数的范围:00000001~01111111(1~127),负数的范围:10000001~11111111(-1~-128)。
所以,整数的范围是 − 2 n − 1 -2^{n-1} −2n−1 ~ 2 n − 1 − 1 2^{n-1}-1 2n−1−1,其中, n n n取决于数据的类型,即比特数,1字节=8比特。
unsigned
- 如果一个字面量常数想要表达自己是unsigned,可以在后面加u或U
如:255U- 用l或L表示long(long)
- *unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
定义变量时,前面加unsigned
,表示这个变量不以补码表示,这个变量没有负数部分,只有0和正整数部分,想要用变量表达纯二进制时用unsigned
。但是unsigned
有个副作用,使得变量所表达的数在正数部分扩大一倍,但不能表达负数。
代码如下:
#include "stdio.h"
int main(){
unsigned char c = 255;
int i = 255;
printf("c=%d, i=%d", c, i);
// 00000000-11111111 -> 0-255
return 0;
}
结果如下:
unsigned char c = 255
,c = 255
,unsigned char
表示的范围是00000000 ~ 11111111,即0 ~ 255。
char c = 255
,c = -1
,char
表示的范围是10000000-01111111,即-128~127。
整数越界
- 整数是以纯二进制方式进行计算的,所以:
- 11111111 + 1 → 100000000 → 0
- 01111111 +1 → 10000000 → 128
- 10000000 - 1 → 01111111 → 127
------如上图所示,对于char
,顺时针0 - 1 = -1
、-128 + 1 = 127
,逆时针-1 + 1 = 0
、127 + 1 = -128
;对于unsigned char
,顺时针0 - 1 = 255
,逆时针255 + 1 = 0
。
可以通过如下代码验证一下:
#include "stdio.h"
int main(){
char c = 127;
char i = -128;
c = c + 1;
i = i - 1;
printf("c=%d,i = %d\n", c, i);
unsigned char a = 0;
unsigned char b = 255;
a = a - 1;
b = b + 1;
printf("a=%d,b = %d\n", a, b);
return 0;
}
结果如下:
所以下面计算成立
char c = -128
,c-1=127
;
unsigned char c = 127
,c+1=128
;
导致这种看似不正常的结果,就是整数的越界!
5. 整数的格式化
整数的输入输出
- 只有两种形式:int或long long
- %d:int
- %u:unsigned
- %ld:long long
- %lu:unsigned long long
不管在计算机内部是什么数,重点是我们以什么方式看待它!
看下面这个例子:
#include "stdio.h"
int main(){
char c = -1;
int i = -1;
printf("c=%u, i=%u\n", c, i);
return 0;
}
该输出结果如下:
是非常大的两个数,而且这两个数是一样的,这两个数是unsigned
的int
所能表达的最大的数。因为-1
在计算机内部表示的是全1。
char
只有1个字节,全1也不会有这么大,这是为什么呢?
因为当把所有小于int
的变量传给printf
的时候,编译器会把这些变量转换为int
传进去,因为char c = -1;
是有符号的,所以当它在转换的过程当中传进去的时候被扩展到所有位都是1
,最后作为unsigned
的结果就是4294967295
。
注:我们在计算机内部还是只有那么一个东西,以不同的方式看待它,就会有不同的结果,这和它在计算机内部是什么没有关系,取决于你是不是以正确的方式来用它,以正确的方式把它格式化成人能读懂的东西来看。
8进制和16进制
- 一个以0开始的数字字面量是8进制
- 一个以0x开始的数字字面量是16进制
%o
用于8进制,%x
用于16进制- 8进制和16进制只是如何把数字表达为字符串,与内部如何表达数字无关
举个例子:
#include "stdio.h"
int main(){
char c = 012;
int i = 0x12;
printf("c=%d, i=%d\n", c, i);
return 0;
}
结果如下:
注: 1、当用
%d
输出的时候,会输出10进制的结果。
2、这个进制只是我们怎么去看它,并不表示在计算机内部它会被表示成8进制或16进制,在计算机内部永远只有2进制。你在程序里面写了一个8进制,编译器会替你转成对应10进制的形式,再变成2进制交给计算机。
printf
中%o
输出8进制,%x
输出16进制:
#include "stdio.h"
int main(){
char c = 012;
int i = 0x12;
printf("c=%o, i=%x\n", c, i);
return 0;
}
结果如下:
8进制和16进制
- 16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位
- 8进制的一位数字正好表达3位二进制
eg: 二进制:0001 0010
十六进制: 1 2
16进制的两位可以表达一个字节 → char
所以用十六进制表达二进制很方便。
6. 选择整数类型
C语言这么多整数类型,一方面是早期C语言习惯,另一方面是为了准确表达硬件,和硬件打交道的时候要用到。
选择整数类型
- 为什么整数要有那么多种
- 为了准确表达内存,做底层程序的需要
- 没有特殊需要,就选择int
- 现在的CPU字长普遍是32位或64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
- 现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中也可能占据一个int的大小(虽然sizeof告诉你更小)
- unsigned与否只是输出的不同,内部计算是一样的
注:选择整数类型,没有特殊需要,就用
int
。
有的教科书上可能会说,“用short
可以省一点”,“short
可能会快一点”,“不超过127的就用char
”等等,但是今天的计算机不一样了,今天的计算机字长普遍都是32位或64位。
字长指的是CPU和内存之间的数据通道每次传输多少数据,CPU每次从内存中读一个数据,每次往内存当中写一个数据,它就是32位,它就是一个int
,如果你要它做一个char
8个比特,实际上,计算机是把32个比特全读进来,然后想办法拿出你要的8比特,所有现代计算机中,用short
、char
不见得比int
更快,甚至要慢。现代的编译器往往还会做内存对齐,结果就是在内存中放一个8比特的char
变量,有可能它实际占据的仍然是一个int
,所以再去用一个比int小的数据类型去做运算,已经没有什么价值了。除非做的是底层的程序,做的程序就是要面对硬件的,硬件要求用那种类型就用哪种类型,不然都用int
。
unsigned
与int
区别只是在输出的时候,只是在做某些位运算的时候,如果不是迫不得已,没有必要用unsigned
。
7. 浮点类型
类型 | 字长 | 范围 | 有效数字 |
---|---|---|---|
float | 32 | ± ( 1.20 × 1 0 − 38 ∼ 3.40 × 1 0 38 ) , 0 , ± i n f , n a n \pm \left ( 1.20\times 10^{-38} \sim ~ 3.40\times 10^{38}\right ) ,0,\pm inf,nan ±(1.20×10−38∼ 3.40×1038),0,±inf,nan | 7 |
double | 64 | ± ( 2.2 × 1 0 − 308 ∼ 1.79 × 1 0 308 ) , 0 , ± i n f , n a n \pm \left ( 2.2\times 10^{-308} \sim ~ 1.79\times 10^{308}\right ) ,0,\pm inf,nan ±(2.2×10−308∼ 1.79×10308),0,±inf,nan | 15 |
有效数字:对于float
来说只有7位数字是有效的,第8位数字是不准确的,对于double
来说只有15位数字是有效的,第16位数字是不准确的。
浮点数的输出:
类型 | scanf | printf |
---|---|---|
float | %f | %f,%e |
double | %lf | %f,%e |
注:
%e
输出科学计数法。
科学计数法:
相当于
−
5.67
∗
1
0
16
-5.67*10^{16}
−5.67∗1016
输出精度
- 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入的
- eg:
printf("%.3f\n", -0.0049);
输出-0.005- eg:
printf("%.30f\n", -0.0049);
输出-0.0048999999999841
在计算机内部0.0049不能被精确的表达为0.0049。
从数学的角度来看,所有数都是连续的,在数轴上任意取两点可以得到无穷多的数。但是对于计算机来说,最终只能用离散的数字去表达数字。对于double
来说,我们可以表达剪头指的两个数,但是位于这两个数之间的数都是double
所不能表达的,假如0.0048在两个剪头之间,计算机用离0.0048近的剪头表示0.0048,但是它和实际的0.0048之间是有距离的,这个距离就是浮点数的误差,这个误差和精度有关,double
能表达的数更多,就意味着double
的精度比float
高。我们在计算机中无法准确的表达一个数,这是天生的,除非不使用double
、float
这样的基础类型。比如说2/3是无法准确表达的,如果在计算机里面就把它记录为2和3两个整数的话,那么还是可以准确表达2/3的。
8. 浮点数的范围与精度
超过范围的浮点数
- printf输出
inf
表示超过范围的浮点数: ± ∞ \pm \infty ±∞- printf输出
nan
表示不存在的浮点数
例子如下:
#include "stdio.h"
int main(){
printf("%f\n", 12.0 / 0.0);
printf("%f\n", -12.0 / 0.0);
printf("%f\n", 0.0 / 0.0);
return 0;
}
输出如下:
其中,1.#INF00
相当于inf
;-1.#INF00
相当于-inf
;-1.#IND00
相当于nan
。不同的编译器结果会有差异。
浮点运算的精度:
浮点数的运算是没有精度的。
可以试下这个代码:
#include "stdio.h"
int main(){
float a, b, c;
a = 1.345f;
b = 1.123f;
c = a + b;
if (c == 2.468)
{
printf("相等\n");
}else{
printf("不相等!c=%.10f,或%f\n", c, c);
}
return 0;
}
结果如下:
注:1、带小数点的字面量是
double
而非float
;
2、float
需要用f
或F
后缀来表明身份;
3、对于浮点数的判断f1 == f2
可能会失败,可以用fabs(f1 - f2) < 1e-12
代替;
4、计算机中的浮点数只有一定范围内的有效数字,只能在一定范围内相信它。不能用浮点数做一些精确的运算,比如算钱的时候,不能用1.23元,应该用123分做计算。
浮点数的内部表达
整数内部是纯二进制数,所以两个整数可以直接运算。但浮点数内部是一种编码形式,前面1比特表达它是正数还是负数,中间11比特表达它的指数部分是多少,后面的位数表示它的分数部分是多少。
- 浮点数在计算时是由专用的硬件部件实现的,计算机会把这个编码交给这个专门的硬件做计算,该硬件会把这个数解开再做运算,算完后再编码成数字给你
- 计算double和float所用的部件是一样的
选择浮点类型
- 如果没有特殊需要,只使用double
- 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的机器上,数据存储的速度也不比float慢
9. 字符类型
字符类型
char
是一种整数,也是一种特殊的类型:字符。这是因为:
- 用单引号表示的字符字面量:‘a’,‘1’
- ''也是一个字符
- printf和scanf里用%c来输入和输出字符
如何将字符’1’输入给char c呢?代码如下:
#include "stdio.h"
int main(){
char c;
char d;
c = 1;
d = '1';
if (c == d)
{
printf("相等\n");
}else{
printf("不相等!\n");
}
// 将c和d以整数形式输出
printf("c=%d\n", c);
printf("d=%d\n", d);
return 0;
}
结果如下:
上面代码说明数字1和字符’1’在计算机内部是不一样的,每一个字符在计算机内部都有一个值去表达它,也就是ASCII码,后面会介绍。
字符的输入输出
- 如何输入’1’这个字符给char c?
- scanf(“%c”, &c); → 1
- scanf(“%d”, &i); c = i; → 49
代码如下:
#include "stdio.h"
int main(){
char c;
scanf("%c", &c);
printf("c=%d\n", c);
printf("c='%c'\n", c);
return 0;
}
结果如下:
同一个变量用%c
输出是'1'
,用%d
输出是49
。
下面用%d
输入看看:
#include "stdio.h"
int main(){
char c;
int i;
scanf("%d", &i);
c = i;
printf("c=%d\n", c);
printf("c='%c'\n", c);
return 0;
}
结果如下:
'1'
的ASCII编码是49,所以当c==49时,它代表'1'
。我们来试试49和'1'
是否相等:
#include "stdio.h"
int main(){
if (49 == '1')
{
printf("OK");
}
return 0;
}
结果如下:
从结果可以看出,整数49和字符“'1'
是相等的!
混合输入
- 下面两条语句有何不同(相差一个空格)?
- sanf(“%d %c”, &i, &c);
- sanf(“%d%c”, &i, &c);
代码如下:
#include "stdio.h"
int main(){
int i;
char c;
scanf("%d %c", &i, &c);
printf("i=%d, c=%d, c='%c'\n", i, c, c);
return 0;
}
结果如下:
以上结果可以看出,无论输入变量时带不带空格,带几个空格,结果都是正确的。
我们来看看下面一种情况(scanf("%d%c", &i, &c);
不带空格):
#include "stdio.h"
int main(){
int i;
char c;
scanf("%d%c", &i, &c);
printf("i=%d, c=%d, c='%c'\n", i, c, c);
return 0;
}
结果如下:
从上面结果可以看出,在%d
后面没有空格,它的意思是说我的整数只读到整数结束为止,后面的字符给下一个变量。所以输入时有没有空格是不一样的!
字符计算
- 一个字符加一个数字得到ASCII码表中那个数之后的字符
- 两个字符的减,得到它们在ASCII码表中的距离
第一种情况:
#include "stdio.h"
int main(){
char c = 'A';
c++;
printf("%c\n", c);
return 0;
}
结果如下:
第二种情况:
#include "stdio.h"
int main(){
int i = 'Z' - 'A';
printf("%d\n", i);
return 0;
}
结果如下:
大小写转换
- 字母在ASCII码表中是顺序排列的
- 大写字母和小写字母是分开排列的,并不在一起
- ‘a’ - 'A’可以得到两段之间的距离,于是,
- a + ‘a’ - 'A’可以把一个大写字母变成小写字母,而
- a + ‘A’ - 'a’可以把一个小写字母变成大写字母
10. 逃逸字符
- 用来表达印出来的控制字符或特殊字符,它由一个反斜杠“\”开头,后面跟上另一个字符,这两个字符合起来,组成一个字符,如
printf("请分别输入身高的英尺和英寸,如输入\"5 7\"表示5英尺和7英寸:");
,像\"
就是逃逸字符,它是两个字符表示一个字符。
逃逸字符有哪些呢?请看下表:
字符 | 意义 | 字符 | 意义 |
---|---|---|---|
\b | 回退一格 | \" | 双引号 |
\t | 到下一个表格位 | \’ | 单引号 |
\n | 换行 | \\ | 反斜杠本身 |
\r | 回车 |
下面看一个例子:
#include "stdio.h"
int main(){
printf("123\b\n456\n");
return 0;
}
结果如下:
而翁恺老师课程中的输出却如下所示:
这里有个字符BS
,为什么在不同的地方运行程序看到的东西会不一样呢?
我们在程序运行的时候会出现一个黑色的窗口,这个黑色的窗口本身就是一个别人写的程序,叫作shell。shell会在背后执行我们写的程序,所以shell接管了我们的键盘,我们眼镜看到的运行结果也是shell发送给我们的。我们的程序和shell“打交道”,shell把键盘上的输入给我们的程序,程序输出的东西交给shell,shell再显示给我们的眼镜看。在这个“打交道”的过程中, shell会做一些“翻译”的事情,比如说,键盘上面去做一些回退←
、回车 ←┘
,shell会把这些处理后传给程序,而程序中的\n
,\b
会经过shell变成你能够看得见的东西,而不同的shell就会做出不同的翻译,也就导致了显示上的差异。
我们将上面代码改一下:
#include "stdio.h"
int main(){
printf("123\bA\n456\n");
return 0;
}
结果如下:
可以看到回退了一个字符,原来的3
被替换成了A
。所以\b
的作用是让下一个输出回到上一个位置上去,如果下一个输出没有的话就没有结果,如果输出的话,就会替代原有的东西。
制表位
- 每行的固定位置
一个\t
使得输出从下一个制表位开始
用\t
才能使得上下两行对齐
可以使用\t
进行输出的对齐:
#include "stdio.h"
int main(){
printf("123\t456\n");
printf("12\t456\n");
return 0;
}
结果如下:
11. 类型转换
自动类型转换
- 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
- 大的意思是能表达的数的范围更大
- char → short → int → long → long long
- int → float → double
- 对于printf,任何小于int的类型都会被转换成int;float会被转换成double
- 但是scanf不会,比如要输入short,需要%hd
强制类型转换
- 要把一个量强制转换成另一个类型(通常是较小的类型),需要:
- (类型)值
- 比如:
- (int)10.2
- (short)32
- 注意这时候的安全性,小的变量不总能表达大的量
- (short)32768
小的变量不总能表达大的量,具体例子如下:
#include "stdio.h"
int main(){
printf("%d\n", (short)32768);
return 0;
}
结果如下:
short
最大只能表示32767,所以程序越界导致输出错误。
如果把强制转换位char
呢:
#include "stdio.h"
int main(){
printf("%d\n", (char)32768);
return 0;
}
结果如下:
为什么出现这种情况呢?32768二进制是10…0(15个0),转换成char
后,只保留了最后8个比特,而32768最后8比特都是0,所以转为char
后输出结果为0**大的类型转换成小的类型会出现越界!不理解的可以看本章第4节。
注:强制转换只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论是值还是类型都不变。
可以看如下代码:
#include "stdio.h"
int main(){
int i = 32768;
short s = (short)i;
printf("%d\n", i);
return 0;
}
结果如下:
强制类型转换只是用这个变量i
的值去算出那个新的short
,并不会去改变这个变量。
注:强制类型转换的优先级高于四则运算
下面代码做的事是把a
转换为int
后再除以b
:
double a = 1.0;
double b = 2.0;
int i = (int)a / b;
如果想要先计算a / b
的值要加括号:int i = (int)(a / b)
。
12. 逻辑类型
C语言原本是没有bool
类型的,C99后开始确定有一个新的类型bool
,但是这个bool
又不是真正的原生类型,使用前首先要包含一个头文件stdbool.h。
bool
- #include <stdbool.h>
- 之后就可以使用bool和true、false
示例代码如下:
#include "stdio.h"
#include "stdbool.h"
int main(){
bool b = 6 > 5;
bool t = true;
t = 2;
printf("%d\n", b);
return 0;
}
注:我们并没有真正的
bool
量的类型,实际上bool
类型的变量都是整数,在输输出时我们也没有一个特别的方式去输出bool
量,我们只能把他输出为一个整数,我们没有办法让printf
给我们输出一个true
。
13. 逻辑运算
逻辑运算
- 逻辑运算是对逻辑量进行的运算,结果只有0或1
- 逻辑量是关系运算或逻辑运算的结果
逻辑运算如下:
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
! | 逻辑非 | !a | 如果a是true结果就是false,如果a是false结果就是true |
&& | 逻辑与 | a&&b | 如果a和b都是true,结果就是true,否则就是false |
|| | 逻辑或 | a||b | 如果a和b有一个是true,结果就是true,两个都是false,结果为false |
- 如果要表达数学中的区间,如: x ∈ ( 4 , 6 ) x \in (4, 6) x∈(4,6)或 x ∈ [ 4 , 6 ] x \in [4, 6] x∈[4,6],应该如何写C表达式?
像4 < x <6这样的式子不是C能正确计算的式子,因为4 < x的结果是一个逻辑值(0或1),再判断这个0或1是否小于6,结果是恒定的。正确的写法应该是:x > 4 && x < 6
。
- 如何判断一个字符c是否是大写字母?
c >= 'A' && c <= 'Z'
下面的表达式应该如何理解?
age > 20 && age < 30
age在20和30之间index < 0 || index > 99
index不在0~99的范围内! age < 20
先计算! age
部分(!
是单目运算符,优先级高于比较运算符),结果不是0就是1,必小于20,该表达式结果为1
优先级
!
>&&
>||
- eg:
! done && (count > MAX)
先计算! done
,再计算括号里面
优先级 | 运算符 | 结合性 |
---|---|---|
1 | () | 从左到右 |
2 | ! + - ++ – | 从右到左(单目的+和-) |
3 | * / % | 从左到右 |
4 | + - | 从左到右 |
5 | < <= > >= | 从左到右 |
6 | == != | 从左到右 |
7 | && | 从左到右 |
8 | || | 从左到右 |
9 | = += -= *= /= %= | 从右到左 |
短路
- 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算,如果
a == 6
不成了那么b == 1
就不会计算,这可以提高运算的速度。
a == 6 && b == 1
a == 6 && b += 1
- 但是,如果
&&
右边是有赋值的或者需要产生一些结果的,那么右边的计算是不会做的。
- 对于
&&
,左边是false时就不做右边了- 对于
||
,左边是true时就不做右边了
这就是短路。
通过下面的例子,看一下什么是短路:
#include "stdio.h"
int main(){
int a = -1;
if (a > 0 && a++ > 1)
{
printf("OK\n");
}
printf("%d\n", a);
return 0;
}
结果如下:
结果是-1,这就说明在if语句中a++
就没有执行。
注:平时写代码时不要把赋值,包括复合赋值组合进表达式!!!
14. 条件和逗号运算
条件运算符
count = (count > 20) ? count -10 : count + 10;
- 条件、条件满足时的值和条件不满足时的值
上述语句就相当于:
if (count > 20)
count = count - 10;
else
count = count + 10;
表面上看起来,把一个条件语句改成了一行语句,简单了很多,但是在复杂情况下会带来很大麻烦。
优先级
- 条件运算符的优先级高于赋值运算符,但是低于其他运算符
eg:1.m < n ? x : a + 5;
2.a++ >= 1 && b -- > 2 ? a : b;
3.x = 3 * a > 5 ? 5 : 20 ;
这已经很复杂了。
嵌套条件表达式
- eg:
count = (count > 20) ? (count < 50) ? count - 10 : count - 5 : (count < 10) ? count + 10 : count + 5;
- 条件运算符是自右向左结合的
eg:w < x ? x + w : x < y ? x : y;
注:说了这么多,就是想说明条件表达是复杂易出错,不建议使用嵌套的条件表达式。
逗号运算符
逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有运算符中最低的,所以它两边的表达式会先计算;逗号的组合关系是自左向右,所有左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果。
例子如下:
#include "stdio.h"
int main(){
int i;
i = 3 + 4, 5 + 6;
printf("%d\n", i);
return 0;
}
结果如下:
该程序编译时会报一个warning,表达式5 + 6
的值没有被用到。如果非要看i
的值,是7
,因为赋值的优先级高于逗号,先算i = 3 + 4
,得到i = 7
,再算5 + 6
,这个表达式的结果没有交给任何变量,所以会报错。
那如果加一对括号呢?代码如下:
#include "stdio.h"
int main(){
int i;
i = (3 + 4, 5 + 6);
printf("%d\n", i);
return 0;
}
结果如下:
这时依旧会报一个warning,但报的错是表达式3 + 4
的值没有被用到。因为这时候括号里面是一个表达式,括号提升了逗号的优先级,括号里面的结果是逗号右边的结果,即5 + 6
,再赋值给i
,程序输出i
的结果为11
。
所以这个逗号表达式到底有什么作用呢?
- 逗号表达式主要是在for中使用
eg:for (i = 0, j = 10; i < j; i++, j--) ...
目前来说逗号表达式基本只有这一个用处!