数据和C
3.1 变量与常量数据
变量
- 在程序运行期间可能会改变或被赋值。这些称为变量(variable)
常量
- 在程序运行期间没有改变的值。这些被称为常量(constant)
3.2 数据:数据类型关键字
最初K&R给出的关键字 | C90标准添加的关键字 | C99标准添加的关键字 |
---|---|---|
int | signed | _Bool |
long | void | _Complex |
short | _Imaginary | |
unsigned | ||
char | ||
float | ||
double |
- C中用int关键字来表示基本的整数类型。后3个关键字(long , short和unsigned)和C90新增的signed用于提供基本整数类型的变式,如unsigned short int和long long int。
- char关键字用于指定字母和其他字符(如,#,$,%和*)。此外,char类型也可以表示较小的整数。
- float,double和long double表示带小数点的数。
- _Bool类型表示布尔值(true/false)。
- _Complex表示复数。
- _Imaginary表示虚数。
- 按照计算机的存储方式可以分为两大基本类型:整数类型和浮点数类型。
位,字节和节是描述计算机数据单元或存储单元的术语。主要指存储单元。
最小的存储单元是位(bit),可以存储0或1(或者说,位用于设置“开”或“关”)。位是计算机内存的基本构建块。
字节(byte)是常用的计算机存储单位。对于几乎所有的机器,1字节均为8位。既然1位可以表示0或1,那么8位字节就有256(2的8次方)种可能的0,1组合。通过二进制编码(仅用0和1便可以表示数字),便可表示0~255的整数或一组字符。
字(word)是设计计算机时给定的自然存储单位。对于8位的微型计算机,1个字长只有8位。从那以后,个人计算机字长增至16位,32位,直到目前的64位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。
3.2.1 整数
- 整数是没有小数部分的数。计算机以二进制数字存储整数,如:整数7以二进制写是111。因此,要在8位字节中存储该数字,需要把前5位都设置成0,后3位设置成1。
3.2.2 浮点数
- 在一个值后面加上一个小数点,该值就成为一个浮点值。如7是整数,7.00是浮点数。
- 浮点数的书写方式
- e计数法:如 3.16 E 7 3.16E7 3.16E7表示 3.16 ∗ 1 0 7 3.16*10^7 3.16∗107。
3.2.3 浮点数与整数的存储方式
-
浮点数
- 计算机将浮点数分成小数部分和指数部分来表示,并将两部分分开存储。(因此导致虽然7和7.00在数值上相同,但是他们的存储方式不同)
- 在十进制下,可以把 7.0 7.0 7.0写成 0.7 E 1 0.7E1 0.7E1。0.7是小数部分,1是指数部分。(计算机内部使用二进制)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
-
两种类型区别
- 整数没有小数部分,浮点数有小数部分。
- 浮点数可以表示的范围比整数大。
- 对于一些算术运算,浮点数损失的精度更多。(因为在任何两数区间内,都存在无穷多个实数,所以计算机的浮点数不能表示区间内所有的值。浮点数通常表示的只是近似值)。
- 之前,浮点运算比整数运算慢,不过,现在许多CPU都包含浮点处理器,缩小了速度上的差距。
3.3 基本数据类型
3.3.1 int类型
- C中整数类型可以表示不同的取值范围和正负值。一般情况下使用int类型即可。
- int类型是有符号整型,即int类型的值必须是整数,可以是正整数,负整数或零。
- ISO C规定int的取值范围最小为 − 32768 到 32767 -32768到32767 −32768到32767。不少于16位。
1.声明int变量
- 声明单个变量:int xxx;
- 声明多个变量:int xxx,yyy,zzz;
2.初始化变量
-
初始化(initialize)变量就是为变量赋一个初始值。
-
赋值方式
-
int hogs = 21;
-
int cows = 32, goats = 14;
-
3.int类型常量
- 上面的整数(21,32,14)都是整型常量和整型字面量。C中把不含小数点和指数的数作为整数。把大多数整型常量视为int类型,但是非常大的整数除外。
4.八进制与十六进制
-
十六进制数的每一位的数恰好由4位二进制数表示。
- 如:十六进制3的二进制数是0011。
- 十六进制数5的二进制是0101。
- 十六进制数35的位组合(bit pattern)是 00110101 00110101 00110101。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
- 十六进制53的位组合是 01010011 01010011 01010011。
-
C中用特定的前缀表示使用哪种进制。
- 0x或0X前缀表示十六进制值。如十进制16表示成0x10或0X10。
- 0前缀表示八进制。如十六进制数16表示成八进制是020。
5.显示八进制和十六进制
进制 | 符号 |
---|---|
十进制 | %d |
八进制 | %o |
十六进制 | %x |
进制前缀 | 符号 |
---|---|
八进制前缀 0 | %#o |
十六进制前缀 0x | %#x |
十六进制前缀 0X | %#X |
-
// // Created by ym on 2021/2/22. // /* * 输出10进制,8进制,16进制 */ #include <stdio.h> int main(void) { int x = 100; printf("x十进制为 %d \n 八进制为 %o \n 十六进制为 %x \n",x,x,x); printf("x十进制为 %d \n 八进制为 %#o \n 十六进制为 %#x,%#X \n",x,x,x,x); return 0; }
-
output
-
x十进制为 100 八进制为 144 十六进制为 64 x十进制为 100 八进制为 0144 十六进制为 0x64,0X64
3.3.2 其他整数类型
- C中提供了3个附属关键字修饰基本整数类型:short,long和unsigned。
- short int(简写为short)占用的存储空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型(正负)类型至少占16位。
- long int或long占用的存储空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。至少占32位。
- long long int或long long(C99标准加入)占用的存储空间可能比long多,适用于更大数值的场合。该类型至少占64位。与int类似,long long是有符号类型。
- unsigned int或unsigned只用于非负值的场合。这种类型与有符号类型表示的范围不同。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整型可以表示更大的数。
- 在C90中,添加了unsigned long int或unsigned long和unsigned short int或unsigned short类型。C99中添加了unsigned long long int 或unsigned long long。
- 在任何有符号类型前面添加关键字signed,可强调使用有符号类型的意图。如:short,short int,signed short,signed short int都表示同一种类型。
1. 使用多种整数类型的原因
- 为什么说short类型“可能”比int类型占用的空间少,long类型“可能”比int类型占用的空间多?
- 因为C语言只规定了short占用的存储空间不能多于int,long占用的存储空间不能少于int。这样的规定是为了适应不同的机器。
- 现在,个人计算机上最常见的设置是,long long占64位,long占32位,short占16位,int占16或32位(依计算机的自然字长而定)。
- C对基本数据类型只规定了允许的最小大小。
- 对于16位机,short和int的最小取值范围是[ − 2 15 -2^{15} −215, 2 15 − 1 2^{15}-1 215−1]。
- 对于32位机,long的最小取值范围是[ − 2 31 -2^{31} −231, 2 31 − 1 2^{31}-1 231−1]。
- 对于32位机,unsigned short和unsigned int的最小取值范围是[ 0 0 0, 65535 65535 65535]。
- 对于32位机,unsigned long的最小取值范围是[ 0 0 0, 2 32 − 1 2^{32}-1 232−1]。
- long long是为了支持64位的需求,最小取值范围为[ − 2 63 -2^{63} −263, 2 63 − 1 2^{63}-1 263−1]。
- unsigned long long的最小取值范围是[ 0 0 0, 2 64 − 1 2^{64}-1 264−1]。
2. 如何选择适合的int类型
-
首先,考虑unsigned类型。这种类型的数常用于计数,因为计数不用负数。而且,unsigned类型可以表示更大的正数。
-
如果一个数超出了int范围,但在long类型范围内,使用long类型。然而对于那些,long占用的空间比int大的系统,使用long类型会减慢运行速度。
-
如果在long类型和int类型占用空间相同的机器上编写代码,当确实需要32位的整数时,应使用long类型而不是int类型,以便把程序移植到16位机器后仍然可以正常工作。类似地,如果确实需要64位的整数,应使用long long类型。
-
如果在int设置为32位的系统中要使用16位的值,应使用short类型以节省存储空间。
- 通常,只有当程序使用相对于系统可用内存较大的整型数组时,才需要重点考虑节省空间的问题。
3. long常量和long long常量
-
超出int类型能表示的范围,编译器会将其视为long int类型(假设这种类型可以表示该数字)。如果数字超出long可表示的最大值,编译器则将其视为unsigned long类型。如果还不够大,编译器则将其视为long long或unsigned long long类型(前提是编译器能识别这些类型)。
-
八进制和十六进制常量被视为int类型。如果值太大,编译器会尝试使用unsigned int。如果还不够大,编译器会依次使用long,unsigned long,long long和unsigned long long类型。
-
有些情况下,需要编译器以long类型存储一个小数字。此外,一些C标准函数也要求使用long类型的值。要把一个较小的常量作为long类型对待,可以在值的末尾加上l(小写L)或L后缀。
-
在支持long long类型的系统中,也可以使用ll或LL后缀表示long long类型的值,如3LL。另外,u或U后缀表示unsigned long long,如5ull,10LLU。
整数溢出
-
如果整数超出了响应类型的取值范围会怎样?分别将有符号类型和无符号类型的整数设置为比最大值略大。
-
#include <stdio.h> int main(void) { int i = 2147483647; unsigned int j = 4294967295; printf("%d %d %d\n",i,i+1,i+2); printf("%u %u %u\n",j,j+1,j+2); return 0; }
-
output
-
2147483647 -2147483648 -2147483647 4294967295 0 1
-
可以将无符号整数j看作是汽车的里程表,当达到它能表示的最大值时,会重新从起始点开始。整数i也是类似的情况。它们的主要区别是,在达到最大值时,unsigned int类型的变量j从0开始;而int类型的变量i则从-2147483648。
4. 打印short,long,long long和unsigned类型
-
打印unsigned int类型的值使用%u转换说明。
-
打印long类型的值,使用%ld转换说明。
-
如果系统中int和long的大小相同,使用%d就行。
-
这样的程序被移植到其他系统(int和long类型的大小不同)中会无法正常工作。在x和o前面可以使用l前缀,%lx表示以十六进制格式打印long类型整数,%lo表示以八进制格式打印long类型整数。(虽然C允许使用大写或小写的常量后缀,但是在转换说明中只能使用小写)
-
打印short类型,可以使用h前缀,%hd表示以十进制显示short类型的整数。%ho表示以八进制显示short类型的整数。h和l前缀都可以和u一起使用,用于表示无符号类型。如:%lu表示打印unsigned long类型的值。
-
对于支持long long类型的系统,%lld和%llu分别表示有符号和无符号类型。
-
#include <stdio.h> int main(void) { unsigned int un = 3000000000; short end = 200; long big = 65537; long long verybig = 12345678908642; printf("un = %u and not %d\n",un,un); printf("end = %hd and %d\n",end,end); printf("big = %ld and not %hd\n",big,big); printf("verybig = %lld and not %ld\n",verybig,verybig); return 0; } /** *程序输出 * un = 3000000000 and not -1294967296 * end = 200 and 200 * big = 65537 and not 1 * verybig = 12345678908642 and not 1942899938 */
-
上面程序第3行中,把65537以二进制格式写成一个32位数是00000000000000010000000000000001。使用%hd,printf()只会查看后16位,所以显示的值是1。类似,输出的最后一行先显示verybig的完整值,然后由于使用了%ld,printf()只显示存储在后32位的值。
3.4.3 使用字符:char类型
- char类型用于存储字符(如:字母或标点符号),但是从技术层面看,char是整数类型,因为char类型实际上存储的是整数而不是字符。(如:在ASCII码中,整数65即代表大写字母A)。
- 标准ASCII码的范围是0~127,只需要7位二进制数(64,32,16,8,4,2,1)即可表示。通常char类型被定义为8位的存储单元。
- 商用的统一码(Unicode)创建了一个能表示世界范围内多种字符集的系统,目前包括的字符已超过110000个。国际标准化组织(ISO)和国际电工技术委员会(IEC)为字符集开发了ISO/IEC 10646标准。统一码标准也与ISO/IEC 10646标准兼容。
- C语言把1字节定义为char类型占用的位(bit)数,因此无论是16位还是32位系统,都可以使用char类型。
1. 声明char类型
-
char response; char itable, latan;
-
上面声明创建了3个char类型的变量;response,itable和latan。
2. 字符常量和初始化
-
在C中用单引号括起来的单个字符被称为字符常量(character constant)
-
char grade = 'A'; char broiled; /*声明一个char类型的变量*/ broiled = 'T'; /*为其赋值,正确*/ broiled = T; /*错误,此时T是一个变量*/ broiled = "T" /*错误,此时“T”是一个字符串*/
-
实际上,字符是以数值形式存储的,所以也可以使用数字代码来赋值。
-
char grade = 65;
-
不建议使用这种方式,能这样做的前提是系统使用ASCII码。
-
-
C将字符常量视为int类型而非char类型。如。在int为32位,char为8位的ASCII系统中,有如下代码:
-
char grade = 'B';
-
本来’B’对应的数值66存储在32位的存储单元中,现在却可以存储在8位的存储单元中(grade)。利用字符常量的这种特性,可以定义一个字符常量’FATE’,即把4个独立的8位ASCII码存储在一个32位存储单元中。如果把这样的字符常量赋给char类型变量grade,只有最后8位有效。因此,grade的值是‘E’。
-
#include <stdio.h> int main(void) { char grade = 'FATE'; printf("%c",grade); return 0; }
-
3. 非打印字符
-
有些ASCII字符打印不出来,如,一些代表行为的字符(如:退格,换行,终端响铃或蜂鸣等)。C提供了3种方法表示这些字符。
-
1.使用ASCII码,如:蜂鸣字符的ASCII码是7,因此表示为
-
char beep = 7;
-
-
2.使用特殊的符号序列表示一些特殊的字符,这些符号叫做转义序列(escape sequence),把转义序列给字符变量时,必须用单引号把转义序列括起来。如
-
char nerf = '\n';
-
-
3.用16进制表示字符常量,即反斜杠后面跟一个x或X,再加上1~3位16进制数字。
-
类型 十六进制 八进制 十进制 char \x41 \101 N.A. int 0x41 0101 65 unsigned int 0x41u 0101u 65u long 0x41L 0101L 65L unsigned long 0x41UL 0101UL 65UL long long 0x41LL 0101LL 65LL unsigned long long 0x41ULL 0101ULL 65ULL
-
-
使用ASCII码时,注意数字和数字字符的区别,如:字符4对应的ASCII码是52,'4’表示字符4,而不是数值4。
-
4. 打印字符
-
printf()函数用%c指明待打印的字符。如果用%d转换说明打印char类型变量的值,答应的是一个整数。而%c转换说明告诉printf()打印该整数值对应的字符。
-
//显示字符的代码编号 #include <stdio.h> int main(void) { char ch; printf("Please enter a character: "); scanf("%c",&ch); //用户输入字符 printf("The code for %c is %d. \n",ch,ch); return 0; }
-
output
-
Please enter a character:Q The code for Q is 81.
-
printf()函数中的转换说明决定了数据的显示方式,而不是数据的存储方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
5. 有符号还是无符号
- 有些C编译器把char实现为有符号类型,这表示char表示的范围是-128~127。而有些编译器把char实现为无符号类型,那么char可表示的范围是0-255。
- 根据C90标准,C语言允许在关键字char前使用signed或unsigned。
3.4.4 _Bool类型
- C99中添加了Bool类型,用于表示布尔类型,因为C语言用值1表示true,值0表示false,所以 Bool类型实际上也是一种整数类型。但原则上它仅占用1位存储空间。
3.4.5 可移植类型:stdint.h和inttypes.h
-
C中提供了很多有用的整数类型,但是,某些类型名在不同系统中的功能不一样。C99新增了两个头文件stdint.h和inttypes.h,以确保C语言的类型在各系统中的功能相同。
-
C中为现有类型创建了更多类型名。新的类型名定义在stdint.h头文件中。
- 如:int32_t表示32位的有符号整数类型。在使用32位int的系统中,头文件会把int32_t作为int的别名。不同的系统也可以定义相同的类型名。
- 如:int为16位,long为32位的系统会把int32_t作为long的别名。然后,使用int32_t类型编写程序,并包含stdint.h头文件时,编译器会把int或long替换成与当前系统匹配的类型。
-
上面说的类型别名是精确宽度整数类型(exact-width integer type)。int32_t表示整数类型的宽度正好是32位,但是计算机的底层系统可能不支持。
-
如果系统不支持精确宽度整数类型咋办?C99和C11提供了第2类别名集合。称为最小宽度类型(minimum width type)。
- 如:int_least8_t是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。
-
C99和C11定义了一组可使计算达到最快的类型集合。这组类型集合被称为最快最小宽度类型(fastst minimum width type)。
- 如:int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
-
C99定义了最大的有符号整数类型intmax_t,可存储任何有效的有符号整数值。uintmax_t表示最大的无符号整数类型。这些类型可能比long long和unsigned long类型更大,因为C编译器除了实现标准规定的类型外,还可利用C语言实现其他类型。
-
C99和C11提供了输入和输出,如果要打印int32_t类型的值,有些定义使用**%d**,而有些定义使用**%ld**,C标准针对这一情况,提供了一些字符串宏来显示可移植类型。
-
如:inttypes.h头文件中定义了PRId32字符串宏,代表打印32位有符号值的合适转换说明(如d或l)。
-
//可移植整数类型名 #include <stdio.h> #include <inttypes.h> //支持可移植类型 int main(void) { int32_t me32; //me32是一个32位有符号整型变量 me32 = 706540874; printf("First, assume int32_t is int: "); printf("me32 = %d\n",me32); printf("Next, let's not make any assumptions.\n"); printf("Instead, use a \"macro\" from inttypes.h: "); printf("me32 = %" PRId32 "\n",me32); return 0; /** * output * First, assume int32_t is int: me32 = 706540874 * Next, let's not make any assumptions. * Instead, use a "macro" from inttypes.h: me32 = 706540874 */ }
3.4.6 float,dobule和long double
- C中的浮点类型有float,double和long double类型。浮点类型能表示包括小数在内更大范围的数。
- float类型必须至少能表示6位有效数字,且取值范围至少是 1 0 − 37 10^{-37} 10−37~ 1 0 + 37 10^{+37} 10+37。通常,系统存储一个浮点数要占用32位。其中8位用于表示指数的值和符号,剩下的24位用于表示非指数部分(也称为尾数或有效数)及其符号。
- double类型和float类型的最小取值范围相同,但至少必须能表示10位有效数字。一般情况下,double占用64位而不是32位。一些系统将多出的32位全部用来表示非指数部分,这不仅能提高精度,还减少了舍入误差。一些系统把其中的一些位分配给指数部分,以容纳更大的指数,从而增加了可表示数的范围。无论采用哪种方式,double的值至少有13位有效数字,超过了标准的最低位数规定。
- long double用以满足比double类型更高的精度要求,C只保证long double类型至少与double类型的精度相同。
1. 声明浮点型变量
float noah,jonah;
double trouble;
float planck = 3.14e-12;
long double gnp;
2. 浮点型常量
- 浮点型常量的基本形式是:有符号的数字(包括小数点),后面紧跟e或E,最后是一个有符号数表示10的指数。
- 正号可以省略。
- 可以没有小数点或指数部分。不能同时省略两者。
- 可以小数部分或整数部分。不能同时省略两者。
- 在浮点数后面加上f或F后缀可覆盖默认类型(double为浮点型默认类型),编译器会将浮点型常量看作float类型。使用l或L后缀使得数字成为long double类型。没有后缀的浮点型常量是double类型。
C99添加了一种新的浮点型常量格式——使用十六进制表示浮点型常量,即在十六进制数前加上十六进制前缀(0x或0X),用p或P分别代替e或E,用2的幂代替10的幂(即,p计数法)。
如:0xa.1fp10
十六进制a等于十进制10,.1f是1/16加上15/256(十六进制f等于十进制15),p10是 2 10 2^{10} 210或1024。0xa.afp10表示的值是(10 + 1/16 + 15/256)x 1024(即,十进制10364.0)。
注:并非所有的编译器都支持C99这一特性
3. 打印浮点值
-
printf()使用%f转换说明打印十进制计数法的float和double类型浮点值,用**%e打印指数计数法的浮点数。如果系统支持十六进制格式的浮点数,可用a和A分别代替e和E**。打印long double类型要使用**%Lf**,%Le或**%La**转换说明。
-
//以两种方式显示float类型的值 #include <stdio.h> int main(void) { float aboat = 32000.0; double abet = 2.14e9; long double dip = 5.32e-5; printf("%f can be written %e\n", aboat,aboat); //下一行要求编译器支持C99或其中的相关特性 printf("And it's %a in hexadecimal, powers of 2 notation\n",aboat); printf("%f can be written %e\n",abet,abet); printf("%Lf can be written %Le\n",dip,dip); return 0; } /** * output * 32000.000000 can be written 3.200000e+04 * And it's 0x1.f4p+14 in hexadecimal, powers of 2 notation * 2140000000.000000 can be written 2.140000e+09 * 0.000053 can be written 5.320000e-05 */
3.4.7 复数和虚数类型
- C99标准支持复数类型和虚数类型,但是有所保留。C11标准把整个复数软件包都作为可选项。
- C有3种复数类型
- float_Complex
- double_Complex
- long double_Complex
- C有3种虚数类型
- float_Imaginary
- double_Imaginary
- long double_Imaginary
- 如果包含complex.h头文件,便可以用complex代替**_Complex,用imaginary代替_Imagimary**,还可以用I代替**-1**的平方根。
3.4.8 类型大小
//
// Created by ym on 2021/2/27.
//查看当前系统支持的指定类型的大小
#include <stdio.h>
int main(void) {
printf("int %zd bytes.\n", sizeof(int));
printf("char %zd bytes.\n", sizeof(char));
printf("short %zd bytes.\n", sizeof(short));
printf("long %zd bytes.\n", sizeof(long));
printf("long long %zd bytes.\n", sizeof(long long));
printf("double %zd bytes.\n", sizeof(double));
printf("long double %zd bytes.\n", sizeof(long double));
printf("float %zd bytes.\n", sizeof(float));
return 0;
}
/**
* output
* int 4 bytes.
* char 1 bytes.
* short 2 bytes.
* long 4 bytes.
* long long 8 bytes.
* double 8 bytes.
* long double 12 bytes.
* float 4 bytes.
*/
- sizeof是c内置运算符,以字节为单位给出指定类型的大小。C99和C11提供**%zd转换说明匹配sizeof的返回类型。一些不支持C99和C11的编译器可用%u或%lu代替%zd**。