第四章 字符串和格式化输入/输出
4.1 前导程序
4.2 字符串简介
字符串(character string)是一个由一个或多个字符的序列,例如
"Good luck!"
其中双引号不是字符串的一部分,它用来告诉编译器它括起来的是字符串
4.2.1 char类型数组和null字符
C语言没有专门储存字符串的变量类型,字符串都被储存在char
类型的数组中。数组是同类型元素的有序序列。
例如
G | o | o | d | l | u | c | k | ! | \0 |
---|
数组末尾的字符\0
是空字符(null),C语言用它标记字符串的结束,它是非打印字符,它的ASCII码是0.C语言字符串一定以null结尾,意味着数组容量至少比待存储的字符数多1。
计算机在其中自动处理的细节有
- 创建数组
- 把字符串中的字符逐个放入数组
- 在末尾加上一个
\0
4.2.2 使用字符串
根据%s
转换说明,scanf()
在遇到第一个空白(空格,制表符或换行符)时就自动停止,它只能读取一个单词,而非一整句话。
字符串和字符
字符’x’是基本类型char
,由一个字符组成
x |
---|
字符串"x"是派生类型char
数组,由两个字符组成
x | \0 |
---|
4.2.3 strlen()
函数
strlen()
和sizeof
运算符的区别
sizeof
运算符,以字节为单位给出对象;strlen()
函数给出字符串中的字符长度,空字符不计入;strlen()
在空字符处自动停止,而sizeof
运算符的结果通常会更大,它会将空字符及后面的垃圾数据计入(即char
数组多余的存储单元里的数据)
#include <stdio.h>
#include <string.h>
int main(void)
{
char say[15];
printf("Please enter a string:\n");
scanf("%s", say);
printf("%s\n", say);
printf("sizeof = %zd\n", sizeof say);
printf("strlen(say) = %zd", strlen(say));
return 0;
}
运行结果
Please enter a string:
Good!
Good!
sizeof = 15
strlen(say) = 5
G | o | o | d | ! | \0 | 垃圾数据 |
---|
上述程序中
sizeof
没有使用圆括号,在运算对象是类型时必须加括号,如sizeof(int)
,对于特定量则可有可无。
4.3 常量和C预处理器
#define TAXRATE 0.2
在编译程序时,程序中所有的TAXRATE都会被替换成0.2,这一过程被称为编译时替换,这样定义的常量称为明示常量或符号常量。
格式 #define NAME value
,注意字符常量定义的末尾没有分号
优点
- 可读性:常量名比数字表达的信息更多
- 可维护性:在程序中多次使用一个常量,若想修改它的值只需改变常量的定义即可,而不用在程序中逐一修改。
我们约定符号常量的名称全为大写字母,便于识别。
#define
指令还可以定义字符和字符串常量,分别使用单引号和双引号。
4.3.1 const
限定符
const
关键字,用于限定一个变量为只读。
const int MONTHS = 12;
MONTHS
的值为12,在程序中不能更改。
const
用起来比#define
更灵活。
4.3.2 明示常量
limits.h
和float.h
分别提供了与整数类型和浮点类型大小限制的相关详细信息。每个头文件都定义了一系列供实现使用的明示常量,下面是limits.h
中的部分代码
#define INT_MAX +32767
#define INT_MIN -32767
详细头文件内容,在使用时查阅即可。
4.4 printf()
和scanf()
它们是输入/输出函数,简称I/O函数,它们工作原理几乎相同,都使用格式字符串和参数列表
4.4.1 printf()
函数
转换说明及其打印的输出结果
转换说明 | 输出 |
---|---|
%a /%A |
浮点数、十六进制数和p计数法 |
%c |
单个字符 |
%d /%i |
有符号的十进制整数 |
%e /%E |
浮点数,e计数法 |
%f |
浮点数,十进制计数法(在printf() 中float 类型自动转换为double 类型,默认打印小数点后六位) |
%g /%G |
根据值的不同,自动选择% f或%e .%e 用于指数小于-4或者大于等于精度时 |
%o |
无符号八进制整数 |
%p |
指针 |
%s |
字符串 |
%u |
无符号十进制整数 |
%x |
无符号十六进制整数,使用十六进制0f |
%X |
无符号十六进制整数,使用十六进制0F |
%% |
打印一个% |
4.2.2 使用printf()
printf()
函数的格式是printf( 格式字符串, 待打印项1, 待打印2, ...);
格式字符串是双引号括起来的内容,包含实际要打印的字符和转换说明两部分信息。
4.2.3 printf()
的转换说明修饰符
printf()
的修饰符
修饰符 | 含义 |
---|---|
标记 | 五种标记- ,+ ,空格 ,# 和0 的说明见下表。可以不使用或使用多个标记 |
数字 | 最小字段宽度,如果宽度不够,系统自动使用更宽字段。如%6d |
. 数字 |
精度。对于不同的转换说明,使用方法不同,详见下表。 |
h |
和整型转换说明一起使用,表示(unsigned) short int 类型的值。如%hu ,%hx ,%5.2hd |
l |
和整型转换说明一起使用,表示(unsigned) long int 类型的值,如%ld ,%8lu |
ll |
和整型转换说明一起使用,表示(unsigned) long long int 类型的值,如%lld ,%8llu |
hh |
和整型转换说明一起使用,表示unsigned/signed char 类型的值,如%hhu ,%hhx ,%6.4hhd |
L |
和浮点转换说明一起使用,表示long double 类型的值。如%Le |
z |
和整型转换说明一起使用,表示size_t 的值(即sizeof 的返回值类型)。如%zd |
j |
和整型转换说明一起使用,表示intmax_t 或uintmax_t 类型的值。这些类型定义在stdint.h 中。如%jd ,%jx |
t |
和整型转换说明一起使用,表示ptrdiff_t 类型的值,它是两个指针差值的类型。如%td ,%12ti |
printf()
中的标记
标记 | 含义 |
---|---|
- |
待打印项左对齐,无此标记则右对齐。如%-20d |
+ |
有符号值若为正,则在值前面加+ ;若为负,则在值前面加- 。如%+6.2i |
空格 |
有符号值若为正,则在值前面显示前导空格;若为负,则在值前面显示减号+标记覆盖一个空格。如% 6.2f |
# |
把结果转换为另一种形式。如果是%o ,则以0 开始;如果是%x 或%X ,则以0x 或0X 开始 |
0 |
对于数值格式,用前导0 代替空格填充字段宽度。对于整数格式,如果出现- 标记或指定精度,则忽略该标记。 |
测试程序见测试目录下
不同转换说明下,.数字
表示的精度
转换说明 | 含义 |
---|---|
%e ,%E ,%f |
表示小数点右边的数字的位数 |
%g ,%G |
表示有效数字的最大位数 |
%s |
表示打印的字符的最大数量 |
整型转换 | 表示打印数字的最小位数,如有必要用前导0以达到这个位数 |
只使用一个.
,而不加数字表示.0
,如%.f
和%.0f
一个意思。
4.4.4 转换说明的意义
转换说明把以二进制格式存储在计算机中的值转换成一系列字符(字符串)以便于显示。转换即翻译,把给定的值按说明翻译并打印出来
1.转换不匹配
short int
类型的336占用2字节,二进制表示如下:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
---|
如果用%c
打印336,它只会查看这2个字节的后一个字节:
0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
---|
相当于336除以256取余,专业术语称336以256为模。这时,336的余数是80,对应的ASCII值是字符P
,于是打印出来的就是字符P
2.简谈参数传递的原理*
函数调用如下:
float n1;
double n2;
long n3, n4;
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
该调用告诉计算机把变量n1, n2, n3, n4的值传递给程序,是一种常见的参数传递方式。
程序把传入的值放入被称为栈(stack)的内存区域,计算机根据变量类型(而不是根据转换说明)将这些值放入栈中。
n1
,float
类型自动转换为double
类型,被储存在栈中,占8字节;n2
,同样在栈中占8字节;n3
和n4
在栈中分别占4字节.
然后,控制转到printf()
函数,该函数根据转换说明(而不是变量类型),从栈中读取值。%ld
的转换说明表明printf()
应该读取4字节,所以printf()
就读取栈中的前4个字节,即n1
的前一半作为第1个值(n1
的前半部分被解释为一个long
类型整数);随后,n1
的后半部分作为第2个long
类型整数;类似地,第3,4个%ld
,printf()
读取了n2
的前一部分和后一部分。
于是,即使用对了转换说明,printf()
还是读错了字节。
2.printf()
的返回值
它返回打印的字符的个数,如果输出错误,printf()
则返回一个负值
例如:rv = printf(...)
的方式把它的返回值赋值给rv
3.打印较长的字符串
三种方法
- 使用多个
printf()
- 使用反斜杠
\
结合Enter
来断行,但是下一行代码不能缩进,必须从最左边开始(强迫症感觉这样就不太好用了: ) - 在两个用引号括起来的字符串之间用空白(包括换行)隔开,C编译器会把多个字符串看做一个字符串
4.4.5 使用scanf()
- 在了解指针之前需要先知道,如果用
scanf()
读取基本变量类型的值,在变量名前加&
;在把字符串读入字符数组时不要使用&
- 只要在每个输入项之间至少一个换行符,空格或制表符即可,可以在一行或多行输入
scanf()
函数的转换说明与printf()
函数基本相同,主要区别是,对于float
和double
类型,printf()
都使用%f
,%e
,%E
,%g
和%G
转换说明,而scanf()
只把它们用于float
类型,对于double
类型时要使用l
修饰符。
scanf()转换说明中的修饰符
转换说明 | 含义 |
---|---|
* |
抑制赋值,如%*d |
数字 | 最大字段宽度。输入达到最大字段宽度处,或第一次遇到空白字符时停止,如%10d |
hh |
把整数作为signed char 或unsigned char 类型读取,如%hhd ,%hhu |
ll |
把整数作为long long 或unsigned long long 类型读取,如%lld ,%llu |
h |
%hd ,%hi 表示把对应的值储存为short int 类型;%ho ,%hx 表示把对应的值储存为unsigned short int 类型 |
l |
%ld,%li 表示把对应的值储存为long 类型;%lo,%lx,%lu 表示把对应的值储存为unsigned long 类型;%le ,%lf ,%lg 表示把对应的值储存为long double 类型 |
L |
在e ,f ,g 前面使用L 而非l ,表示把对应的值储存为long double 类型 |
j ,z ,t |
和printf() 中类似 |
1.从scanf()
角度看输入
我们假设scanf()
函数根据一个%d
转换说明读取一个整数
scanf()
会跳过所有空白字符,直到遇到第1个非空白字符开始读取scanf()
每次读取一个字符,因为是读取整数,所以它想先读取到一个数字或符号(正负号)。如果第一个是非空白字符不是数字或符号(正负号),scanf()
将停在那里,不会把值赋给指定变量- 如果后面每次读取的是数字,它就保存这个数字并读取下一个字符。
scanf()
不断地读取和保存字符,直到遇到非数字字符(或到达字符宽度限制),这时它认为读到了整数的末尾,然后scanf()
把非数字字符放回输入scanf()
计算已读取的数字和符号,将计算后的值放入指定变量
如果使用%s
转换说明,scanf()
会读取除空白外的所有字符。scanf()
跳过空白开始读取第一个非空白字符并保存,直到再次遇到空白。
2.格式字符串中的普通字符
scanf()
函数允许把普通字符字符放到格式字符串中,除空格字符外的普通字符必须与输入字符串严格匹配,例如
scanf("%d, %d");
scanf()
函数将其解释为:用户将输入一个数字、一个逗号、再输入另一个数字,即像下面这样
37,38
格式字符串中的空白意味着将跳过下一项输入项前面的所有空白,这里的所有空白也包括没有空白的特殊情况。上面的示例中scanf()有一个空格,于是37, 38
,
37,
38
都可以正常输入。
除了%c
,其他转换说明都会自动跳过待输入值前面的所有空白。对于%c
,scanf(" %c", &ch)
会从第一个非空白字符开始读取,而scanf("%c", &ch)
会从第一个字符开始读取
3.scanf()
函数的返回值
返回成功读取的项数。如果没有读取任何值,且需要读取一个数字而用户输入了一个非数值字符串,返回0。当scanf()
检测到文件末尾时,会返回EOF
4.4.6 printf()
和scanf()
的*
修饰符
1.printf()
中的*
修饰符
可以用*
修饰符替代字段宽度,即不预先指定字段宽度,而是通过一个参数告诉printf()
字段宽度为多少,即参数列表中还应包含*
对应的值。这个方法也可用于浮点值的字段宽度及精度
printf("%*.*f", width, precision, weight);
*
,*
, f
对应的值在参数列表的顺序按照在格式字符串中的顺序即可。
2.scanf()
中的*
修饰符
把*
放在%
和转换说明之间,会使得scanf()
跳过相应的输入项
scanf("%*d, %*d, %d", &n);
表示scanf()
将跳过前两个输入项,把输入的第3个数赋给n
在程序需要读取文件中的特定列的内容时,这项跳过功能很有用。
4.4.7 printf()
用法提示
可以通过指定较大的固定字段宽度来使打印的数值更加整齐
4.5 关键概念
4.6 本章小结
4.7 复习题
挑选一个有意思的题目
请写出下面这行代码的打印结果
printf("%c%c%c", 'H', 105, '\41');
答案放在文末
4.8 编程练习
-
编写一个程序, 提示用户输入名和姓, 然后以“名,姓”的格式打印出
来。 -
编写一个程序, 提示用户输入名和姓, 并执行以下操作:
a. 打印名和姓, 包括双引号;
b. 在宽度为20的字段右端打印名和姓, 包括双引号;
c. 在宽度为20的字段左端打印名和姓, 包括双引号;
d. 在比姓名宽度宽3的字段中打印名和姓。
-
编写一个程序,读取一个浮点数,首先以小数点记数法打印,然后以
指数记数法打印。 用下面的格式进行输出(系统不同,指数记数法显示的位数可能不同) :a.输入21.3或2.1e+001;
b.输入+21.290或2.129E+001;
-
编写一个程序,提示用户输入身高(单位: 英寸)和姓名,然后以下面的格式显示用户刚输入的信息:
Dabney, you are 6.208 feet tall
使用float类型,并用/作为除号。如果你愿意,可以要求用户以厘米为
单位输入身高,并以米为单位显示出来。 -
编写一个程序,提示用户输入以兆位每秒(Mb/s)为单位的下载速度
和以兆字节(MB)为单位的文件大小。程序中应计算文件的下载时间。注
意,这里1字节等于8位。使用float类型,并用/作为除号。该程序要以下面的格式打印 3个变量的值(下载速度、 文件大小和下载时间),显示小数点236后面两位数字:At 18.12 megabits per second, a file of 2.20 megabytes
downloads in 0.97 seconds. -
编写一个程序,先提示用户输入名,然后提示用户输入姓。在一行打
印用户输入的名和姓,下一行分别打印名和姓的字母数。 字母数要与相应名
和姓的结尾对齐, 如下所示:Melissa Honeybee 7 8
接下来, 再打印相同的信息, 但是字母个数与相应名和姓的开头对齐,
如下所示:Melissa Honeybee 7 8
-
编写一个程序, 将一个
double
类型的变量设置为1.0/3.0,一个float
类型的变量设置为1.0/3.0。分别显示两次计算的结果各3次:一次显示小数点后面6位数字; 一次显示小数点后面12位数字;一次显示小数点后面16位数字。程序中要包含float.h
头文件, 并显示FLT_DIG和DBL_DIG
的值。1.0/3.0的值与这些值一致吗? -
编写一个程序,提示用户输入旅行的里程和消耗的汽油量。然后计算
并显示消耗每加仑汽油行驶的英里数,显示小数点后面一位数字。接下来,使用1加仑大约3.785升,1英里大约为1.609千米,把单位是英里/加仑的值转换为升/100公里(欧洲通用的燃料消耗表示法),并显示结果,显示小数点后面 1 位数字。
注意,美国采用的方案测量消耗单位燃料的行程(值越大越好) , 而欧洲则采用单位距离消耗的燃料测量方案(值越低越好)。使用#define 创建符号常量或使用 const 限定符创建变量来表示两个转换系数。
复习题答案
Hi!
第一个字符是字符常量,第二个字符是由十进制整数转化而来,第三个字符是八进制字符常量的ASCII表达。’\41’写作’\041’可以更好表示它是八进制ASCII编码。测试见测试目录下。
来源:CSDN
作者:给雨
链接:https://blog.csdn.net/RainGiving/article/details/104720219