目录
6. 数据类型
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 是一个运算符,给出某个类型或变量在内存中所占据的字节数:sizeof(int)、sizeof(i)。
静态的,编译时就决定了sizeof的结果,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;
}
整数类型
—— char: 1字节(8比特(8个二进制的位)).
—— short: 2字节
—— int:取决于计算机和编译器(CPU),通常的意义是“1个字”
—— long:取决于计算机和编译器(CPU),通常的意义是“1个字”.
—— long long: 8字节
说计算机的字长时,指的是CPU中的寄存器是多少宽的,如32bit,每个寄存器可以表达32个bit的数据,同时CPU与RAM每次传递数据为32bit。现在64bit更常见。
int 就是用来表达一个寄存器的大小。
计算机内部一切都是二进制的。
—— 18—>00010010
—— 0—> 00000000
—— -18—>?
二进制负数:
——1个字节可以表达的数: 00000000 —11111111(0-255)·
——三种方案:
1. 仿照十进制,有一个特殊的标志表示负数
2. 取中间的数为0,如1000000表示0,比它小的是负数,比它大的是正数
3. 补码
补码:
—— 考虑-1,我们希望-1+1—>0。如何能做到?
0—>00000000
1—> 00000001
11111111 + 00000001—>100000000(8位,多出来的1删掉)
—— 因为0-1—>-1,所以,-1=
(1)p0000000 - 00000001—>11111111
11111111被当作纯二进制看待时,是255,被当作补码看待时是-1
同理,对于-a,其补码就是0-a,实际是2 -a,n是这种类型的位数
补码的意义就是拿补码和原码可以加出一个溢出的“零”。
数的范围
—— 对于一个字节(8位),可以表达的是:. 00000000 - 11111111
—— 其中
00000000—>0
11111111 ~10000000—> -1 ~ -128.
00000001 ~01111111—> 1~127
#include <stdio.h>
int main()
{
char c =255;
int i =255;
unsigned char a = 255;
printf("c=%d , i=%d, a = %d\n", c,i,a);
//char c :11111111 范围 00000000~11111111 表达:-128~127
//int i :00000000 00000000 00000000 11111111
//unsigned char a: 11111111 范围 00000000~11111111 表达:0~255
return 0;
}
// 整数越界
int main()
{
char a =127;
unsigned char b = 127;
char c = -128;
unsigned char d = 255;
unsigned char e = 0;
a = a + 1; //+1 右移,-1 左移
b = b + 1;
c = c -1;
d = d + 1;
e = e - 1;
printf("a = %d,b = %d,c = %d,d = %d,e = %d",a,b,c,d,e);
return 0;
}
整数的范围
char:1字节: -128 ~127.
short:2字节:-32768 ~32767
int:取决于编译器(CPU),通常的意义是"1个字”.
long:4字节
long long:8字节
unsigned:表示此整数无补码部分,无负数部分,表达纯二进制。
—— 如果一个字面量常数想要表达自己是unsigned,可以在后面加u或U,如:255U
—— 用l或L表示long(long)
—— *unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位。
表达整数范围大一倍,无法表达负数。
整数越界
整数是以纯二进制方式进行计算的,所以︰
11111111+1—>100000000—>0
01111111+1—>10000000—>-128
10000000 - 1—>01111111—> 127
整数的输入输出
只有两种形式:int或long long.(所有小于int的char、short、int都用%d做输入输出。比int大用%ld)
—— %d:int
—— %u:unsigned.
—— %ld:long long
—— %lu:unsigned long long
不管在计算机内部是什么数,重点是用什么方式看。
#include <stdio.h>
int main()
{
char a = -1;
int b = -1;
char c = 012; //8进制
int d = 0x12; //16进制
int e = 0x2a;
printf("a = %u,b = %u\n",a,b); //char int 数 用unsigned来看;
printf("c = %d,d = %d\n",c,d); //用10进制看结果
printf("c = %o,d = %x,e = %x\n",c,d,e); //用对应进制看
printf("c = 0%o,d = 0x%x,e = 0x%X\n",c,d,e); //用对应进制看
return 0;
}
// 把小于int的变量传给printf时,编译器会把其转为int传进去,-1 有符号 变为所有位都是1.
// 这个进制只是人怎样去看它,在计算机内部都是2进制,%d 就输出10进制,%o输出8进制,%x输出16进制(字母小写)%X输出16进制(字母大写)。
8进制和16进制:
—— 16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位
—— 8进制的一位数字正好表达3位二进制
—— 因为早期计算机的字长是12的倍数,而非8
选择整数类型
—— 为什么整数要有那么多种:为了准确表达内存,做底层程序的需要·
—— 没有特殊需要,就选择int
—— 现在的CPU的字长普遍是32位或64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
—— *现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小(虽然sizeof告诉你更小)
—— unsigned与否只是输出的不同,内部计算是一样的
浮点类型
浮点数的输入输出
float——scanf:%f——printf:%f、%e
double——scanf:%lf——printf:%f、%e
在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入的。
#include <stdio.h>
int main()
{
double a = 1234.56789;
double b = 1E-10;
printf("%e, %E, %f\n",a,a,a);
printf("%e, %E, %f, %.16f\n",b,b,b,b);//%.16f,指定输出小数点后几位,四舍五入
printf("%.3f\n",-0.0049); //三位 ,四舍五入 九进一,-0.005
printf("%.30f\n",-0.0049); // 计算机不能精确表达任意数,无法连续,只能离散数表达。
printf("%.3f\n",-0.00049); //4舍去,输出-0.000
printf("%.3f\n",-0.0045); //输出结果为-0.004,5舍去了?
return 0;
}
超过范围的浮点数
printf 输出inf表示超过范围的浮点数:±∞
printf 输出nan表示不存在的浮点数
浮点数范围不包括无穷大,但它可以表示。整数12/0则无法编译通过。
#include <stdio.h>
int main()
{
printf ("%f\n",12.0/0.0);//整数不能除以0,无穷大不能用整数表达,可以用浮点表达(如果是整数,12/0编译不过)
printf("%f\n",-12.0/0.0);
printf("%f\n",0.0/0.0);
float a,b,c;
a=1.345f; //不带f为double,需要f或F表示是float
b=1.123f;
c=a+b;
if(c==2.468)printf("相等");
else printf("不相等,c=%.10f,或%f",c,c);
return 0;
}
带小数点的字面量是double而非float
float需要用f或F后缀来表明身份。
f1==f2不一定成功,应该写fabs(f1-f2)<1e-12
所以浮点数不能做精确计算,最后误差会累积起来的。只能在一定范围内相信小数点
传统计算器直接用整数做运算,浮点数内部是编码形式。
浮点数的内部表达
—— 浮点数在计算时是由专用的硬件部件实现的
—— 计算double和float所用的部件是一样的
—— 如果没有特殊需要,只使用double
—— 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的机器上,数据存储的速度也不比float慢
char 字符类型
char是一种整数,也是一种特殊的类型:字符(character)。这是因为:
—— 用单引号表示的字符字面量: 'a', '1'。
—— " 也是一个字符
—— printf和scanf里用 %c 来输入输出字符
计算机内部用 49 来表达 '1'
字符的输入输出
如何输入'1'这个字符给char c?
scanf("%c" , &c);—>1
scanf(" %d" , &i); c=i;—>49
'1'的ASCII编码是49,所以当c==49时,它代表'1'
#include <stdio.h>
int main()
{
char c;
char d;
char e;
c = 1;
d = '1';
e = 'E';
if ( c== d ){
printf("相等\n");
} else{
printf("不相等\n");
}
printf("c = %d\n",c); // 结果:c = 1
printf("d = %d\n",d); // 结果:d = 49
printf("e = %d\n",e); // 结果:e = 69
printf("e = %c\n",e); // 结果:e = E
char a;
scanf("%c",&a); // 键入 1
printf("a = %d a = '%c'\n",a,a);//结果:a = 49 a = '1'
int i;
scanf("%d",&i); //scanf只能处理int,不能处理char; 键入1
char b;
b = i;
printf("b = %d b = '%c'\n",b,b);//结果:b = 1 b = ''
scanf("%d",&i); //键入 49
char bb;
bb = i;
printf("bb = %d bb = '%c'\n",bb,bb); //结果:bb = 49 bb = '1'
if(49=='1'){
printf("49=='1'?————OK");
}
return 0;
}
混合输入
scanf("%d%c",&i,&c); //键入:1 1 ———— 结果读到 1 和 空格的字符
scanf("%d %c",&i,&c); //键入:1 1 ———— 结果读到1 49,和中间有无、有多少空格无关系
字符计算
—— 一个字符加一个数字得到ASCII码表中那个数之后的字符
—— 两个字符的减,得到它们在表中的距离
字母的大小写转换
—— 字母在ASCII表中是顺序排列的
——大写字母和小写字母是分开排列的,并不在一起.
—— 'a' - 'A' 可以得到两段之间的距离,于是:
X+'a'-'A' 可以把一个大写字母X变成小写字母x,而x+'A'-'a'可以把一个小写字母x变成大写字母X
逃逸字符
用来表达无法印出来的控制字符或特殊字符,它由一个 反斜杠" \ " 开头,后面跟上另一个字符,这两个字符合起来,组成了一个字符
printf("请分别输入身高的英尺和英寸,""如输入\"5 7\"表示5英尺7英寸:");
字符 | 意义 | 字符 | 意义 |
---|---|---|---|
\b | 回退一格 | \" | 双引号 |
\t | 到下一个表格位 | \' | 单引号 |
\n | 换行 | \ | 反斜杠本身 |
\r | 回车 |
printf("123\b\n456\n");
有些程序:
123
456
dev c++下方的黑框框,是别人写的程序(shell)来帮助我们运行的;部分东西需要经过它处理,如\b\n,不同shell的处理不一样。
制表位
—— 每行的固定位置
—— 一个 \t 使得输出从下一个制表位开始
—— 用 \t 才能使得上下两行对齐
回车和换行源于打字机的动作,回车为回左边,换行为上移纸张
dev c++把回车当做回车+换行了。
数据类型转换
自动类型转换
—— 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
—— 大的意思是能表达的数的范围更大
—— char > short —> int—-> long —> long long
—— int—> float —> double
—— 对于printf,任何小于int的类型会被转换成int; float会被转换成double(%f足够了)
—— 但是scanf不会,要输入short,需要%hd
强制类型转换
—— 要把一个量强制转换成另一个类型〈通常是较小的类型),需要:(类型)值
—— 比如:(int)10.2; (short)32
—— 注意这时候的安全性,小的变量不总能表达大的量。
—— 比如:(short)32768。(short最大只能表达到32767,结果只能是-32768)
——(char)32768结果为0,因为32768 = 10...0(15个0),char取最低那个bit。
只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论是值还是类型都不改变。
#include <stdio.h>
int main()
{
printf("%d\n", (short)32767);
printf("%d\n", (short)32768);
printf("%d\n", (char)32768);
int i = 32768;
short s = (short)i;//强制类型转换只是用这个i的值算了一个新的short类型的s,而不改变i。
printf("%d\n",i);
printf("%d\n",s);
return 0;
}
强制类型转换的优先级高于四则运算
double a=5.0;
double b=6.0;
int c =(int)a/b;//先将a整型化,再与double的b相除,最后赋给整型的c。
int d =(int)(a/b);//先做a/b除法,再将结果整型化。
逻辑类型
bool:C语音本没有bool类型,添加头文件#include<stdbool.h>后可使用bool和true、false。
仍是个整数。
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool b = 6>5;
bool c = 5>6;
bool t = true;
t = 2; //能编译通过
printf("%d\n",b);//结果为1
printf("%d\n",c);//结果为0
printf("%d\n",t);//结果为1
return 0;
}
逻辑运算
逻辑运算是对逻辑量进行的运算,结果只有0或1
逻辑量是关系运算或逻辑运算的结果
运算符 | 描述 | 示例 | 结果 |
---|---|---|---|
! | 逻辑非 | !a | a的true或false反转 |
&& | 逻辑与 | a && b | 只有a&b都是true时结果才是true,否则false |
|| | 逻辑或 | a || b | ab有一个true结果true,全false结果才false |
—— 表达数学区间时,(4,6)或[4,6]:
不可以写4<x<6这种式子。它会先判断4<x的结果是一个逻辑值(0或1),不论x为多少,4<x<6结果都是true,如x = 5;4<x,结果为true 即1,接后面,1<6,结果为true。
应该写成:x>4 && x<6;
—— 判断一个字符是大写字母:c >= 'A' && c <= 'Z';
—— !age<20:逻辑运算符优先级低于比较运算符,但!单目运算符,优先级高于双目,先做!,!age的结果不是0就是1,结果必为1.
—— 优先级:!>&&>||
!done && (count > MAX):这里()可以不要。
优先级 | 运算符 | 结合性 |
---|---|---|
1 | () | 从左到右 |
2 | !,+,-,++,-- | 从右到左(单目的+和-) |
3 | *,/,% | 从左到右 |
4 | +,- | 从左到右 |
5 | <,<=,>,>= | 从左到右 |
6 | ==,!= | 从左到右 |
7 | && | 从左到右 |
8 | || | 从左到右 |
9 | =,+=,-=,*=,/=,%= | 从右到左 |
短路:逻辑运算自左向右,如果左边足以决定结果,就不会进行右边的计算了
a==6&&b==1:如果左边a!=6,就终止了。就算右边是赋值的话右边也不会做了。
&&左边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为假,则OK没输出,而a=-1,表面&&后的a++没进行。
条件运算符的优先级高于赋值运算符,但是低于其他运算符。
条件运算符自右向左结合,尽量不使用嵌套条件表达式,太复杂,不好理解。
逗号运算符
逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。
逗号的优先级是所有的运算符中最低的,所以它两边的表达式会先计算;
逗号的组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果。
主要在for中使用:
for(i=0,j=10;i<j;i++,j--)......:分号;分开三个区域,区域中要放多个计算,则用逗号分开。