字符串和格式化输入输出

人盡茶涼 提交于 2020-03-07 22:18:06

第四章 字符串和格式化输入/输出

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.hfloat.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_tuintmax_t类型的值。这些类型定义在stdint.h中。如%jd,%jx
t 和整型转换说明一起使用,表示ptrdiff_t类型的值,它是两个指针差值的类型。如%td,%12ti

printf()中的标记

标记 含义
- 待打印项左对齐,无此标记则右对齐。如%-20d
+ 有符号值若为正,则在值前面加+;若为负,则在值前面加-。如%+6.2i
空格 有符号值若为正,则在值前面显示前导空格;若为负,则在值前面显示减号+标记覆盖一个空格。如% 6.2f
# 把结果转换为另一种形式。如果是%o,则以0开始;如果是%x%X,则以0x0X开始
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)的内存区域,计算机根据变量类型(而不是根据转换说明)将这些值放入栈中。

n1float类型自动转换为double类型,被储存在栈中,占8字节;n2,同样在栈中占8字节;n3n4在栈中分别占4字节.

然后,控制转到printf()函数,该函数根据转换说明(而不是变量类型),从栈中读取值。%ld的转换说明表明printf()应该读取4字节,所以printf()就读取栈中的前4个字节,即n1的前一半作为第1个值(n1的前半部分被解释为一个long类型整数);随后,n1的后半部分作为第2个long类型整数;类似地,第3,4个%ldprintf()读取了n2的前一部分和后一部分。

于是,即使用对了转换说明,printf()还是读错了字节。

2.printf()的返回值

它返回打印的字符的个数,如果输出错误,printf()则返回一个负值

例如:rv = printf(...)的方式把它的返回值赋值给rv

3.打印较长的字符串

三种方法

  • 使用多个printf()
  • 使用反斜杠\结合Enter来断行,但是下一行代码不能缩进,必须从最左边开始(强迫症感觉这样就不太好用了: )
  • 在两个用引号括起来的字符串之间用空白(包括换行)隔开,C编译器会把多个字符串看做一个字符串

4.4.5 使用scanf()

  • 在了解指针之前需要先知道,如果用scanf()读取基本变量类型的值,在变量名前加&;在把字符串读入字符数组时不要使用&
  • 只要在每个输入项之间至少一个换行符,空格或制表符即可,可以在一行或多行输入
  • scanf()函数的转换说明与printf()函数基本相同,主要区别是,对于floatdouble类型,printf()都使用%f,%e,%E,%g%G转换说明,而scanf()只把它们用于float类型,对于double类型时要使用l修饰符。

scanf()转换说明中的修饰符

转换说明 含义
* 抑制赋值,如%*d
数字 最大字段宽度。输入达到最大字段宽度处,或第一次遇到空白字符时停止,如%10d
hh 把整数作为signed charunsigned char类型读取,如%hhd,%hhu
ll 把整数作为long longunsigned 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转换说明读取一个整数

  1. scanf()会跳过所有空白字符,直到遇到第1个非空白字符开始读取
  2. scanf()每次读取一个字符,因为是读取整数,所以它想先读取到一个数字或符号(正负号)。如果第一个是非空白字符不是数字或符号(正负号),scanf()将停在那里,不会把值赋给指定变量
  3. 如果后面每次读取的是数字,它就保存这个数字并读取下一个字符。
  4. scanf()不断地读取和保存字符,直到遇到非数字字符(或到达字符宽度限制),这时它认为读到了整数的末尾,然后scanf()把非数字字符放回输入
  5. 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 编程练习

  1. 编写一个程序, 提示用户输入名和姓, 然后以“名,姓”的格式打印出
    来。

  2. 编写一个程序, 提示用户输入名和姓, 并执行以下操作:

    a. 打印名和姓, 包括双引号;

    b. 在宽度为20的字段右端打印名和姓, 包括双引号;

    c. 在宽度为20的字段左端打印名和姓, 包括双引号;

    d. 在比姓名宽度宽3的字段中打印名和姓。

  3. 编写一个程序,读取一个浮点数,首先以小数点记数法打印,然后以
    指数记数法打印。 用下面的格式进行输出(系统不同,指数记数法显示的位数可能不同) :

    a.输入21.3或2.1e+001;

    b.输入+21.290或2.129E+001;

  4. 编写一个程序,提示用户输入身高(单位: 英寸)和姓名,然后以下面的格式显示用户刚输入的信息:

    Dabney, you are 6.208 feet tall

    使用float类型,并用/作为除号。如果你愿意,可以要求用户以厘米为
    单位输入身高,并以米为单位显示出来。

  5. 编写一个程序,提示用户输入以兆位每秒(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.

  6. 编写一个程序,先提示用户输入名,然后提示用户输入姓。在一行打
    印用户输入的名和姓,下一行分别打印名和姓的字母数。 字母数要与相应名
    和姓的结尾对齐, 如下所示:

    Melissa Honeybee
    
          7        8
    

    接下来, 再打印相同的信息, 但是字母个数与相应名和姓的开头对齐,
    如下所示:

    Melissa Honeybee
    
    7       8
    
  7. 编写一个程序, 将一个double类型的变量设置为1.0/3.0,一个float类型的变量设置为1.0/3.0。分别显示两次计算的结果各3次:一次显示小数点后面6位数字; 一次显示小数点后面12位数字;一次显示小数点后面16位数字。程序中要包含float.h头文件, 并显示FLT_DIG和DBL_DIG的值。1.0/3.0的值与这些值一致吗?

  8. 编写一个程序,提示用户输入旅行的里程和消耗的汽油量。然后计算
    并显示消耗每加仑汽油行驶的英里数,显示小数点后面一位数字。接下来,使用1加仑大约3.785升,1英里大约为1.609千米,把单位是英里/加仑的值转换为升/100公里(欧洲通用的燃料消耗表示法),并显示结果,显示小数点后面 1 位数字。

注意,美国采用的方案测量消耗单位燃料的行程(值越大越好) , 而欧洲则采用单位距离消耗的燃料测量方案(值越低越好)。使用#define 创建符号常量或使用 const 限定符创建变量来表示两个转换系数。


复习题答案

Hi!

第一个字符是字符常量,第二个字符是由十进制整数转化而来,第三个字符是八进制字符常量的ASCII表达。’\41’写作’\041’可以更好表示它是八进制ASCII编码。测试见测试目录下。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!