1. 数据类型

1.1 整型

和数学的概念一样,在C语言中,整数是没有小数部分的数。计算机以二进制数字储存整数,例如,整数 7 以二进制写是 111。因此,要在8位字节中储存该数字,需要把前 5 位都设置成 0,后 3 位设置成 1。

image-20230726091431777

1.1.1 整形家族

关于变量的定义方式前面已经详细讲过了,把具体的数据类型放到变量名前面即可,关于整形其实是一个很宽泛的概念,我们还可以再次进行细分,如下表:

数据类型 占用空间
short(有符号短整型) 2字节
int(有符号整形) 4字节
long(有符号长整形) Windows 为 4 字节,Linux 为 4 字节(32位),8字节(64位)
long long(有符号长长整形) 8字节
unsigned short(无符号短整型) 2字节
unsigned int(无符号整形) 4字节
unsigned long(无符号长整形) long 类型
unsigned long long(无符号长长整形) 8字节

C语言提供 3个附属关键字修饰基本整数类型:shortlongunsigned。我们应记住以下几点:

  • short int类型(简写为short)占用的存储空间可能比 int 类型少,常

    用于较小数值的场合以节省空间。

  • long int(简写为long)占用的存储空间可能比 int 多,适用于较大数值的场合。

  • long long int(简写为long long,C99标准加入)占用的储存空间可能比long多,适用于更大数值的场合,该类型至少占64位

  • unsigned为前缀的整数只适用于非负值的场合。

  • 当一个小的数据类型赋值给一个大的数据类型,不会出错,因为编译器会自动转化。但当一个大的类型赋值给一个小的数据类型,那么就可能丢失高位。

对于长整形来说它所对应的数值常量和非长整形是有区别的,如下表:

整型常量 所需类型
10 代表short/int类型
10l,10L 代表long类型
10ll,10LL 代表long long类型
10u,10U 代表unsigned short/int类型
10ul,10UL 代表unsigned long类型
10ull,10ULL 代表unsigned long long类型

对于长整形数值来说在描述的时候需要再后面添加L(或者小写的l)后缀,如果是无符号类型需要添加后缀U(u),这样看起来就非常直观了。

关于上述整形在使用 printf() 函数打印的时候对应的占位符如下:

占位符 描述
%d/%i 十进制int类型
%o 八进制整数
%x/%X 十六进制整数
%u 十进制unsigned int类型
%hd 十进制short类型
%ho 八进制 short int 类型
%hx 十六进制 short int 类型
%hu 十进制unsigned short类型
%ld 十进制long类型
%lo 八进制 long int 类型
%lx 十六进制 long int 类型
%lu 十进制unsigned long类型
%lld 十进制long long类型
%llo 八进制 long long int 类型
%llx 十六进制 long long int 类型
%llu 十进制unsigned long long类型
%% 输出一个百分号

1.1.2 sizeof() 关键字

sizeof不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节

  • sizeof 的返回值为size_t
  • size_t 类型在 32 位操作系统下是unsigned int,是一个无符号的整数
占位符 描述
%zd 使用 printf函数打印size_t类型的数据

C99 和 C11 提供%zd转换说明匹配sizeof的返回类型。一些不支持C99和C11的编译器可用%u%lu代替%zd

sizeof 的操作方式一共有两种:

  • 获取特定数据类型的大小,语法为:sizeof(类型)
  • 获取给定变量的大小,语法为:sizeof(变量名)或者sizeof 变量名

当 sizeof 运算符运算对象是类型时,圆括号必不可少,但是对于特定量(变量或常量)可有可无。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
int main()
{
short a = 10;
int b = 10;
long c = 10l; //或者10L
long long d = 10ll; //或者10LL
printf("sizeof(short) = %zd\n", sizeof(short));
printf("sizeof(int) = %zd\n", sizeof(int));
printf("sizeof(long) = %zd\n", sizeof(long));
printf("sizeof(long long) = %zd\n", sizeof(long long));
printf("=====================================\n");
printf("sizeof(a) = %zd\n", sizeof(a));
printf("sizeof(b) = %zd\n", sizeof(b));
printf("sizeof(c) = %zd\n", sizeof c);
printf("sizeof(d) = %zd\n", sizeof d);
printf("=====================================\n");
printf("short a = %hd\n", a);
printf("int b = %d\n", b);
printf("long c = %ld\n", c);
printf("long long d = %lld\n", d);
printf("=====================================\n");

unsigned short a2 = 20u;
unsigned int b2 = 20u;
unsigned long c2 = 20ul;
unsigned long long d2 = 20ull;
printf("unsigned short a = %hu\n", a2);
printf("unsigned int b = %u\n", b2);
printf("unsigned long c = %lu\n", c2);
printf("unsigned long long d = %llu\n", d2);

return 0;
}

程序输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(long long) = 8
=====================================
sizeof(a) = 2
sizeof(b) = 4
sizeof(c) = 4
sizeof(d) = 8
=====================================
short a = 10
int b = 10
long c = 10
long long d = 10
=====================================
unsigned short a = 20
unsigned int b = 20
unsigned long c = 20
unsigned long long d = 20

1.1.3 有符号和无符号区别

1.1.3.1 有符号整数

有符号整数是一种整数数据类型,可以表示正数负数。在C/C++中,常见的有符号整数类型有:

  1. signed short int:它是一个有符号的短整数类型,占用两个字节的存储空间,可简写为short int或者short
  2. signed int:它是一个有符号的整数类型,占用四个字节的存储空间,可简写为int
  3. signed long int:它是一个有符号的长整数类型,占用四个字节或八个字节的存储空间,根据编译器和平台的不同,表示的范围也会有所变化。可简写为long int或者long
  4. signed long long int:它是一个有符号的长长整数类型,占用八个字节的存储空间,表示的范围是很大的。可简写为long long int或者long long

这些有符号整数类型可以表示负数,因为它们的最高位(最左边的位)用于表示符号位,0表示正数,1表示负数。接下来举例说明:

现有一个十进制-1089474374,通过程序打印它对应的十六进制数值:

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
signed int a = -1089474374;
printf("%X\n", a);
return 0;
}

输出的结果为:

1
BF0FF0BA 

接下来我们将这个十六进制数转换为二进制进行分析:

十六进制 BF0FF0BA
补码 1011 1111 0000 1111 1111 0000 1011 1010
反码 1011 1111 0000 1111 1111 0000 1011 1001
原码 1100 0000 1111 0000 0000 1111 0100 0110

前面已经反复强调了,数值在计算机中一律都是采用补码来存储的,对于有符号整数来说,它们左侧的最高位是符号位,得到的结果符号位为1,证明这个整数是负数。

image-20230726175554693

如果忽略最高位的符号位,得到就是一个正整数,其对应的十六进制数值应该是40F00F46,也就是十进制的1089474374

image-20230726180039192

正整数的补码和原码相同,得到的二进制数和负数的二进制数相比较也只是最高位不同,正整数最高位为0,负整数最高位为1

1.1.3.2 无符号整数

无符号整数是一种整数数据类型,它只能表示非负数(包括零)无符号数最高位不是符号位,而就是数的一部分,无符号数不可能是负数。

在C/C++中,无符号整数需要使用关键字unsigned对整型进行修饰,常见的无符号整数类型有:

  1. unsigned short int:它是一个无符号的短整数类型,占用两个字节的存储空间,可写简写为unsigned short
  2. unsigned int:它是一个无符号的整数类型,占用四个字节的存储空间,可写简写为unsigned
  3. unsigned long int:它是一个无符号的长整数类型,占用四个字节或八个字节的存储空间,根据编译器和平台的不同,表示的范围也会有所变化。可写简写为unsigned long
  4. unsigned long long int:它是一个无符号的长长整数类型,占用八个字节的存储空间,表示的范围很大。可写简写为unsigned long long

现有一个十进制3236958022,通过程序打印它对应的十六进制数值:

1
2
3
4
5
6
7
#include <stdio.h>
int main()
{
unsigned int a = 3236958022;
printf("%X\n", a);
return 0;
}

程序输出的结果如下:

1
C0F00F46

接下来我们将这个十六进制数转换为二进制进行分析:

十六进制 C0F00F46
补码 1100 0000 1111 0000 0000 1111 0100 0110
反码 1100 0000 1111 0000 0000 1111 0100 0110
原码 1100 0000 1111 0000 0000 1111 0100 0110

image-20230726185020221

通过验证可以得知,无符号整形的左侧最高位不是符号位,而是数值范围的上限。因此,无符号整数具有更大的正数范围,但不能表示负数。

最后,通过一个表格来说明有符号和无符号整型的取值范围:

数据类型 占用空间 取值范围
short 2 字节 -32768 到 32767 (-215 ~ 215-1)
int 4 字节 -2147483648 到 2147483647 (-231 ~ 231-1)
long 4 字节 -2147483648 到 2147483647 (-231~ 231-1)
unsigned short 2 字节 0 到 65535 (0 ~ 216-1)
unsigned int 4 字节 0 到 4294967295 (0 ~ 232-1)
unsigned long 4 字节 0 到 4294967295 (0 ~ 232-1)

1.2 布尔类型

C99标准添加了_Bool类型,用于表示布尔值,即逻辑值truefalse。因为C语言用值 1 表示 true,值 0 表示 false,所以 _Bool 类型实际上也是一种整数类型。但原则上它仅占用1位存储空间,因为对 0 和 1 而言,1位的存储空间足够了。关于布尔类型,其对应的头文件为stdbool.h,该头文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// stdbool.h
// The C Standard Library <stdbool.h> header.
#ifndef _STDBOOL
#define _STDBOOL

#define __bool_true_false_are_defined 1

#ifndef __cplusplus

#define bool _Bool
#define false 0
#define true 1

#endif /* __cplusplus */

#endif /* _STDBOOL */

通过阅读上述代码(初学者不用读),可以得到如下信息:

  1. 数据类型 bool_Bool 是等价的,二者可以替换彼此
  2. 布尔类型有两个值:0或者1
    • 0 值对应的关键字是 false
    • 1 值对应的关键字是 true
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool var1 = true; // 假设为真
bool var2 = false; // 假设为假
// 修改布尔类型变量的值
var1 = false;
var2 = true;
printf("var1 = %d, var2 = %d\n", var1, var2);
return 0;
}

1.3 字符型

1.3.1 ASCII 对照表

字符型变量用于存储一个单一字符,在 C 语言中用 char 表示,其中每个字符变量都会占用 1 个字节。在给字符型变量赋值时,需要用一对英文半角格式的单引号 ('') 把字符括起来。

从技术层面看,char 的本质就是一个1字节大小的整型。因为 char 类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符,即用特定的整数表示特定的字符,最常用的编码就是ASCII 编码。

下面是 ASCII 对照表:

ASCII值 控制字符 ASCII值 字符 ASCII值 字符 ASCII值 字符
0 NUT 32 (space) 64 @ 96
1 SOH 33 ! 65 A 97 a
2 STX 34 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 BEL 39 , 71 G 103 g
8 BS 40 ( 72 H 104 h
9 HT 41 ) 73 I 105 i
10 LF 42 * 74 J 106 j
11 VT 43 + 75 K 107 k
12 FF 44 , 76 L 108 l
13 CR 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DCI 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 S 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 TB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92 / 124 |
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 `
31 US 63 ? 95 _ 127 DEL

标准ASCII码的范围是0~127,只需7位二进制数即可表示。通常,char 类型被定义为8位的存储单元,因此容纳标准 ASCII 码绰绰有余。

ASCII 码大致由以下两部分组成:

  • 非打印控制字符:
    • ASCII 表上的数字 0 ~ 31 分配给了控制字符,用于控制像打印机等一些外围设备。
    • 数字 127 代表 Del 命令。
  • 打印字符:数字 32 ~ 126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main()
{
char A = 'A';
char a = 'a';
printf("a = %d\n", a); // 97
printf("A = %d\n", A); // 65

printf("A = %c\n", 'a' - 32); // 小写a转大写A
printf("a = %c\n", 'A' + 32); // 大写A转小写a

char ch = ' ';
printf("空字符:%d\n", ch); // 空字符ASCII的值为32
printf("A = %c\n", 'a' - ' '); // 小写a转大写A
printf("a = %c\n", 'A' + ' '); // 大写A转小写a

return 0;
}

1.3.2 转义字符

转义字符是一种特殊的字符序列,用于表示在代码中无法直接表示或输入的字符。在 C/C++ 和许多其他编程语言中,转义字符以反斜杠(\)开头,后跟一个或多个字符。

下表中是一些常见的C/C++转义字符:

转义字符 含义 ASCII码值(十进制)
\a 警报 007
\b 退格(BS) ,将当前位置移到前一列 008
\f 换页(FF),将当前位置移到下页开头 012
\n 换行(LF) ,将当前位置移到下一行开头 010
\r 回车(CR) ,将当前位置移到本行开头 013
\t 水平制表(HT) (跳到下一个TAB位置) 009
\v 垂直制表(VT) 011
\\ 代表一个反斜线字符”" 092
\' 代表一个单引号(撇号)字符 039
\" 代表一个双引号字符 034
\? 代表一个问号 063
\0 数字0 000
\ddd 8进制转义字符,d范围0~7 3位8进制
\xhh 16进制转义字符,h范围0-9,a-f,A~F 3位16进制

温馨提示:注意:绿色字体标注的为不可打印字符。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main()
{
printf("abcxyz");
printf("\refg\n");
printf("abc");
printf("\befg\n");
printf("%d\n", '\123');
printf("%d\n", '\x23');
return 0;
}

程序输出的结果如下:

1
2
3
4
efgxyz
abefg
83
35
  • 第 5 行:输出一个字符串,但是并没有换行
  • 第 6 行:转义字符\r是将位置切换到行首, \n为换行,因此发生了数据覆盖
  • 第 7 行:输出一个字符串,但是并没有换行
  • 第 8 行:转义字符\b作用为退格,因此会删掉一个字符, \n为换行
  • 第 9 行:\1238进制转义字符,0123 对应 10 进制数为 83
  • 第 10 行:\x2316进制转义字符,0x23 对应 10 进制数为 35

1.3.3 数值溢出

当超过一个数据类型能够存放最大的范围时,数值会溢出。有符号位最高位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。

数据类型 占用空间 取值范围
char 1字节 -128 到 127(-27 ~ 27-1)
unsigned char 1字节 0 到 255(0 ~ 28-1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main()
{
//符号位溢出会导致数的正负发生改变
char ch = 0x7f + 2; // 127+2
printf("ch = %d\n", ch);
//最高位的溢出会导致最高位丢失
unsigned char ch1;
ch1 = 0xff + 1; // 255+1
printf("ch1 = %u\n", ch1);
ch1 = 0xff + 2; // 255+2
printf("ch1 = %u\n", ch1);
return 0;
}

程序执行的结果如下:

1
2
3
ch = -127
ch1 = 0
ch1 = 1
  1. 有符号字符变量ch的最大值是127,也就是二进制的0111 1111,如果在这个数值的基础上加 2,二进制数值会变成1000 0001,最高位符号位值是 1,也就意味着这是一个负数,并且计算机是基于补码进行数据运算的,所以这个二进制数应该是补码,我们需要计算原码:
    • 原码 = 补码的数据位取反 + 1,即:1111 1111
    • 1111 1111去掉符号位就是127,所以结果是-127
  2. 无符号字符变量ch1的最大值是255,也就是二进制的1111 1111,如果在这个数值的基础上加 1,二进制数值会变成1 0000 0000,最高位溢出,剩余的8个二进制位都为0,所以得到的结果为0
  3. 无符号字符变量ch1的最大值是255,也就是二进制的1111 1111,如果在这个数值的基础上加 2,二进制数值会变成1 0000 0001,最高位溢出,剩余的8个二进制位值为1,所以得到的结果为1

1.4 浮点型

浮点型变量是用来存储小数数值的。在C语言中, 浮点型变量分为两种: 单精度浮点数(float)、 双精度浮点数(double), 从语义上就可以得知 double 型变量所表示的浮点数比 float 型变量更精确。

数据类型 占用空间 有效数字范围
float 4 字节 7 位有效数字
double 8 字节 15~16 位有效数字

由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。有效位以外的数字将被舍去,这样可能会产生一些误差。

在使用过程中不以 f/F 结尾的常量是 double 类型,以 f/F 结尾的常量(如3.14f/3.14F)是 float 类型。

我们在使用一些浮点值的时候,可能会遇到一个数值非常大或者非常小的情形,为了将它们更加情形的描述出来,就可以使用科学计数法。

科学计数法(Scientific Notation)是一种表示非常大或非常小的数的常用方法。它基于数字的指数形式,由两部分组成:一个基数(在1到10之间)和一个指数(整数或有理数)。通常使用科学计数法来简化表示具有大量零的数或极大/极小的物理量。

科学计数法的一般形式如下:

a x 10b

其中,a 是一个在 1 到 10 之间的基数,10 是底数,b 是指数。例如:

  • 1,000,000 可以用科学计数法表示为 1 x 106
  • 0.000001 可以用科学计数法表示为 1 x 10-6

科学计数法的优点是简洁、易于阅读和表达极大/极小的数。它也便于进行数学运算,特别是在涉及非常大或非常小的数时。在编程语言中,科学计数法也被广泛使用。例如在C/C++中,可以使用科学计数法表示浮点数:

1
double num = 8e6; // 表示 8 x 10^6,等同于 8000000.0

在此示例中,变量 num 被赋值为 8e6,它表示 8 x 10^6,等同于数值 8000000.0。8是基数,e代表的就是底数10,6是指数, 此处的e和E可以互换

关于上述整形在使用 printf() 函数打印的时候对应的占位符如下:

占位符 描述
%f 小数(包含float类型和double类型)默认精度为6位
%g 6个有效数字的浮点数。整数部分一旦超过6位,就会自动转为科学计数法,指数部分的e为小写
%G 等同于%g,唯一的区别是指数部分的E为大写
%le 科学计数法表示的 long double 类型浮点数
%lf long double 类型浮点数
%.2f 打印浮点数,保留小数点后两位
%a 十六进制浮点数,字母输出为小写
%A 十六进制浮点数,字母输出为大写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main()
{
// 传统方式赋值
float a = 3140000.314f;
double b = 3.14;
printf("a = %le\n", a);
printf("b = %f\n", b);

// 科学法赋值
a = 3.2e3f; // 3.2*1000 = 32000,e可以写E
printf("a1 = %f\n", a);
a = 100e-3f; // 100*0.001 = 0.1
printf("a2 = %f\n", a);

double c = 0.31415926;
printf("c = %.9f\n", c);

return 0;
}

程序输出的结果如下:

1
2
3
4
5
a = 3.140000e+06      // 损失了精度
b = 3.140000
a1 = 3200.000000
a2 = 0.100000
c = 0.314159260

2. 数据的输入输出

2.1 字符串常量

字符串是内存中一段连续的char空间,以'\0'(数字0)结尾。字符串常量是由双引号括起来的字符序列,如 “china”“C program”“$12.5”等都是合法的字符串常量。字符串常量与字符常量的不同:

image-20230727163735392

每个字符串的结尾,编译器会自动的添加一个结束标志位'\0',即字符串 "a" 包含两个字符'a'’\0’

2.2 printf 和 putchar 函数

printf() putchar() 都是标准C的库函数,可以直接拿过来使用,它们都可以进行数据的输出,但二者还是有区别的:

  • printf 是输出一个字符串(char*)
  • putchar 输出一个字符(char)

这两个函数的函数原型如下:

1
2
3
#include <stdio.h>
int putchar(int c); // 参数为要输出的字符
int printf(const char *format, ...);

下面的表格是使用 printf 函数进行数据输出的时候常用的一下占位符:

占位符 数据类型 描述
%d int 接受整数值并将它表示为有符号的十进制整数
%hd short int 短整数
%hu unsigned short 无符号短整数
%o unsigned int 无符号8进制整数
%u unsigned int 无符号10进制整数
%x,%X unsigned int 无符号16进制整数,x 对应的是 abcdef,X 对应的是 ABCDEF
%f float/double 浮点数
%lf long double 长双精度浮点数
%e,%E double 科学计数法表示的数,此处”e”的大小写代表在输出时用的”e”的大小写
%c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%s char * 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
%p void * 以16进制形式输出指针
%% % 输出一个百分号

printf 函数附加格式(下表中的字符可以一并添加到上面的占位符中):

字符 含义
l(字母l) 附加在d,u,x,o前面,表示长整数
- 左对齐
m(代表一个整数) 数据最小宽度
0(数字0) 将输出的前面补上0直到占满指定列宽为止不可以搭配使用-
m.n(代表一个整数) m指域宽,即对应的输出项在输出设备上所占的字符数。n指精度,用于说明输出的实型数的小数位数。对数值型的来说,未指定n时,隐含的精度为n=6位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
int main()
{
int a = 100;
printf("a = %d\n", a);
printf("%p\n", &a);
printf("%%d\n");
printf("========================\n");
char c = 'a';
putchar(c);
printf("\n");
long a2 = 100;
printf("%ld, %lx, %lo\n", a2, a2, a2);
printf("========================\n");
long long a3 = 1000;
printf("%lld, %llx, %llo\n", a3, a3, a3);
printf("========================\n");
int abc = 10;
printf("abc = '%6d'\n", abc);
printf("abc = '%-6d'\n", abc);
printf("abc = '%06d'\n", abc);
printf("abc = '%-06d'\n", abc);
printf("========================\n");
double d = 12.3;
printf("d = \'%-10.3lf\'\n", d);

return 0;
}

程序输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = 100
000000B6A26FF544
%d
========================
a
100, 64, 144
========================
1000, 3e8, 1750
========================
abc = ' 10'
abc = '10 '
abc = '000010'
abc = '10 '
========================
d = '12.300 '

2.3 scanf 与 getchar 函数

通过使用 scanf 函数与 getchar 函数都可以从标准输入设备读取数据,它们都是标准C的库函数,二者的区别在于:

  • getchar 是从标准输入设备读取一个字符(char)
  • scanf 通过%转义的方式可以得到用户通过标准输入设备输入的数据

两个函数的函数原型如下:

1
2
3
#include <stdio.h>
int getchar(void); // 得到字符的 ascii 值
int scanf(const char *format, ...); // 用法和printf类似

需要注意的是,getchar函数每次只能读取一个字符。如果你输入了多个字符,getchar函数只会读取第一个字符,而将其他字符留在输入缓冲区中。你可以在需要的时候多次调用getchar函数来读取多个字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
int main()
{
char ch1;
char ch2;
char ch3;
int a;
int b;

printf("请输入ch1的字符:");
ch1 = getchar();
printf("ch1 = %c\n", ch1);
getchar();

printf("请输入ch2的字符:");
ch2 = getchar();
printf("\'ch2 = %ctest\'\n", ch2);
getchar();

printf("请输入ch3的字符:");
scanf("%c", &ch3);
printf("ch3 = %c\n", ch3);

printf("请输入整数a的值:");
scanf("%d", &a);
printf("a = %d\n", a);

printf("请输入整数b的值:");
scanf("%d", &b);
printf("b = %d\n", b);

return 0;
}

在上面的程序中有一下几点需要额外说明:

  1. 通过getchar()接收一个字符输出,输入完毕之后会按下回车健表示输入完毕,所以一共输入了两个字符,但是getchar()一次只能读一个字符,想要读出这个回车就必须再调用一次getchar(),程序中第13,18行的作用就是如此。
  2. 在使用scanf接收一个输入的时候,会把用户输入存储到一个变量中,所以需要对这个变量取地址(在变量名前加 &)也就是找到变量的家,这样才能够完成变量数据的写入,在后续的学习过程中会频繁接触到类似的操作。

2.4 缓冲区

缓冲区(Buffer)是指用于临时存储数据的一块内存区域。缓冲区在不同的上下文中有不同的用途,以下是一些常见的缓冲区的应用场景:

  1. 输入缓冲区:用于在从输入设备(如键盘、鼠标)读取数据之前,临时存储输入的数据。输入缓冲区可以收集并存储一定数量的输入数据,等待程序逐个处理。
  2. 输出缓冲区:用于在将数据发送到输出设备(如显示器、打印机)之前,临时存储要输出的数据。输出缓冲区可以收集一定数量的数据,然后一次性发送到输出设备,从而提高输出效率。
  3. 文件缓冲区:用于在文件的读取和写入过程中作为数据的中转站。文件缓冲区可以减少对物理文件的读写操作,通过在缓冲区内进行数据处理,提高文件操作的效率。
  4. 网络缓冲区:用于在网络通信中存储数据,以便发送和接收数据包。网络缓冲区可以帮助管理数据包的传输,并提供对数据包的排序、丢失检测和重传等处理。

在前面章节中为大家介绍了输入和输出函数,对于这些函数而言都是对应一块缓冲区,其好处在上文中已经提及了。虽然缓冲输入好处很多,但是某些交互式程序也需要无缓冲输入。例如,在游戏中,你希望按下一个键就执行相应的指令。因此,缓冲输入和无缓冲输入都有用武之地。

关于缓冲区我们可以将其分为两类:

  • 完全缓冲I/O
    • 当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中
    • 缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节
  • 行缓冲I/O
    • 在出现换行符时刷新缓冲区
    • 键盘输入通常是行缓冲输入,所以在按下Enter键后才刷新缓冲区

在C语言中,完全缓冲函数在后讲文件 I/O 的时候会接触到,有一个常用的行缓冲函数是printf。它用于将格式化的数据输出到标准输出(通常是显示器)并自动刷新缓冲区。当使用printf函数在屏幕上打印内容时,内容会被缓冲并在换行符\n出现时刷新缓冲区,将缓冲区中的内容显示到屏幕上。

通常情况下,当我们使用printf函数输出内容后,内容并非立即显示在屏幕上,而是先存储在缓冲区中,当以下条件之一满足时,缓冲区的内容才会被刷新到屏幕上:

  1. 缓冲区已满:当缓冲区已经达到一定大小时,会自动刷新缓冲区。
  2. 遇到换行符 \n:当printf函数的格式化字符串中包含换行符时,会触发缓冲区的刷新。
  3. 输入输出流的关闭:当程序结束时,缓冲区中的内容会被自动刷新。

除了printf函数,还有其他用于行缓冲的函数,例如puts函数和fputs函数。这些函数会自动在输出内容末尾添加换行符,以触发缓冲区的刷新。