C_Primer_Plus04.String
字符串和格式化输入输出
- 要点
函数
strlen()
关键字const
字符串
如何创建、存储字符串
如何使用strlen()函数获取字符串的长度
用 C 预处理器指令#define和 ANSIC 的const修饰符创建符号常量
示例程序
// talkback.c -- 与用户交互
#include <stdio.h>
#include <string.h> // 提供 strlen() 函数原型
#define DENSITY 62.4 // 人体密度
#define PRAISE "You are an extraordinary being."
int main(void){
float weight, volume;
int size, letters;
char name[40]; // name 是一个能容纳40个字符的数组
printf("Hi! What is your name?\n");
scanf("%s", name);
printf("%s, what's your weight in pounds?\n", name);
scanf("%f", &weight);
size = sizeof name;
letters = strlen(name);
volume = weight / DENSITY;
printf("Oh, %s, %s\n", name, PRAISE);
printf("Your volume is %2.2f cubic feet.\n",
volume);
printf("Also, your name has %d letters, ", letters);
printf("and we have %d bytes to store it.\n", size);
printf("The phrase of praise has %lu letters ", strlen(PRAISE));
printf("and occupies %lu memory cells.\n", sizeof(PRAISE));
return 0;
}
output:
Hi! What is your name?
li磊磊
li磊磊, what's your weight in pounds?
21
Oh, li磊磊, You are an extraordinary being.
Your volume is 0.34 cubic feet.
Also, your name has 8 letters, and we have 40 bytes to store it.
The phrase of praise has 31 letters and occupies 32 memory cells.
utf-8 中,每个汉字占3个字节
用数组存储字符串,数组占用内存中40个连续的字符,每个字节存储一个字符值。使用 %s 转换符来处理字符串的输入和输出。
scanf() 中 name 没有 & 前缀,而 weight 有。&weight 和 name 都是地址。
使用 #define 定义预处理常量,在编译时,会将该常量替换为真实值,这一过程称为编译时替换(compile-time substitution)
使用 strlen() 获取字符串的长度(字节数)。使用 sizeof() 获取占据的字节数。
对于 PRAISE,用 strlen() 得出的是字符数,而 sizeof() 给出的结果更大,因为它包含了末尾的不可见的 null 字符。因为程序中未告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。
sizeof 函数的运算对象是类型时,圆括号必不可少,对于特定量,可有可无
sizeof(int), sizeof name, sizeof 62.8
建议所有情况下都使用圆括号
每个汉字占3个字符(3个字节)
字符串
字符串是一个数组
- C 语言没有专门用于存储字符串的变量类型,字符串都被存储在 char 类型的数组中,数组由连续的存储单元组成
- 字符串中的字符字符串中的字符被存储在相邻的存储单元中,每个单元存储一个字符
举例说明:"Zing went the strings of my heart!"

- 末尾的字符
\0是空字符(null character),是非打印字符(ascii 码为0),而不是数字 0. 用来标记字符串的结束 - C 中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1
- C 中的数组是同类型数据元素的有序序列
strlen() 函数
获得一个字符串中的字符数(除去null字符和未被占用的空间)
预处理器
预处理器在程序真正编译之前会对程序进行预处理,会执行一些预处理指令。
#include 是一种预处理指令,它表示在程序中包含其他文件的信息;而 #define 表示定义常量,这这样定义的常量在程序执行时不会发生变化,因为它在编译时将所有常量替换为了真实常量。这样定义的常量称为 明示常量(manifest constant)(有的地方叫 符号常量)
使用预处理指令定义变量时,一般写在程序开头,#include 之后,且不写等号和分号,一般使用全大写字母。
const 限定符
C90 标准新增了 const 关键字,用于限定一个变量为只读,不可更改。但它仍然是个变量。
const 用起来比 #define 更灵活
const int MONTHS = 12;
明示常量
C 头文件 limits.h 和 float.h 分别提供了与整数类型和浮点类型大小限制相关的详细信息。每个头文件都定义了一系列明示常量。例如,limits.h 头文件包含以下明示常量:
#define INT_MAX +32767
#define INT_MIN -32768
它们代表 int 类型的最大最小值。如果系统使用32位的 int,该头文件会为这些明示常量提供不同的值。如果程序中包含 limits.h 头文件,就可编写如下代码:
printf("Maximum int value on this system is %d\n", INT_MAX);


printf() 和 scanf()
最初 C 语言并未定义输入输出,而是把它们的实现留给了编译器的作者,以满足不同机器的不同需求。后来考虑到兼容性后,各编译器都提供不同版本的输入输出,但它们之间偶尔有一些差异。C90 和 C99 规定了这些函数的标准版本。
printf() 和 scanf() 工作原理几乎相同,它们都使用格式字符串和参数列表。
printf() 函数

在%和转换字符之间插入修饰符可以修饰基本的转换说明

sizeof 运算符以字节为单位返回类型或值的大小。C 标准只规定了该值是无符号整数,所以在实现中,它可以是 unsigned int, unsigned long 甚至是 unsigned long long。不同的系统可能使用 %u,%lu 或 %llu。鉴于此,C 提供了可移植性更好的类型 size_t,表示 sizeof 返回的类型,这被称为底层类型(underlying type)。printf() 函数使用 z 修饰符表示打印相应的类型。另外 C 还定义了 ptrdiff_t 类型,表示两个地址差值的底层有符号整数类型。
C 并未规定 float 类型的转换符,而是在 printf() 函数传递参数时,自动将 float 转换成 double

参数传递
#include <stdio.h>
int main(void){
float n1 = 3.0;
double n2 = 4.0;
long n3 = 2000000000;
int n4 = 123456;
printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
printf("%ld %ld\n", n3, n4);
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
return 0;
}
output:
3.0e+00 4.0e+00 0.0e+00 0.0e+00
2000000000 123456
2000000000 123456 2147483626 0
第一行显示,%e 转换符没有把整数转换成浮点数。
- 4字节的 float 类型在打印时会扩展成8字节的 double
- 当 printf() 查看 n4 时,除了查看 n4 的4个字节,还会查看它相邻的4个字节,共8个字节,然后将这8个字节解释成浮点数
- 由此看出,即使位数正确,转换符不匹配,输出的值也不同
第二行显示,只要使用正确的转换符,就可以得到正确的输出
第三行显示,如果 printf() 语句中有其他地方不匹配,即使用对了转换符,也会得到虚假的结果
参数传递机制因操作系统而异。分析如下:
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
该调用告诉计算机把四个变量的值传递给程序。程序把传入的值放入栈(stack, 一块内存区域),计算机根据变量类型(真实类型,不是转换符规定的类型)把这些值放入栈中。n1先入栈,占8个字节(float转换为double),n2和n3各占8个字节,n4是4个字节。然后控制转到 printf() 函数,它根据转换符从栈中读数据,%ld 表示读8个字节,所以 printf() 读取栈中前8个字节,作为第一个值,将其解释成一个long类型的整数,依次读取。
printf() 返回值
返回打印字符的个数。如果有输出错误,printf() 返回一个负值。比如一张已满的CD或DVD拒绝写入时,程序应该采取相应行动,此时就用到了该返回值。
打印较长的字符串:
printf("The printf() function printed %d \
characters.\n", rv);
printf("Here is a way to print a ");
printf("long string.\n");
printf("This is another way to print a "
"long string.\n");
scanf() 函数
从键盘输入字符:字母、数字和标点符号。scanf() 函数可以把输入到屏幕的字符转换成整数、浮点数、字符或字符串。
与 printf() 函数类似,也使用格式字符串和参数列表。scanf() 中的格式字符串表明字符输入流的目标数据类型。两个函数的主要区别在参数列表中,printf() 函数使用变量、常量和表达式,而 scanf() 函数使用指向变量的指针。使用方法:
- 如果用 scanf() 读取基本类型的值,在变量名前加一个 &
- 如果用 scanf() 把字符串读入字符数组中,不使用 &
#include <stdio.h>
int main(void){
int a;
float b;
char s[30];
printf("Enter an integer and a float number:");
scanf("%d %f", &a, &b);
printf("Enter a string:");
scanf("%s", s);
printf("integer:%d, float:%f\n", a, b);
printf("string:%s\n", s);
return 0;
}
如果字符串长度超过规定的30个字节,程序会报错栈崩溃:
*** stack smashing detected ***: ./03scanf terminated
Aborted (core dumped)
当有多个输入时,scanf() 函数使用空白(换行符,制表符和空格)把输入分成多个字段,再依次把转换符和字段匹配。
唯一的例外是 %c 转换符,scanf() 会读取每个字符,包括空白。
假设 scanf() 根据一个 %d 读取一个整数。scanf() 函数每次读取一个字符,跳过所有空白字符,直到遇到第一个非空白字符。因为要读取整数,所以它希望发现一个数字字符或一个符号(+,-)。如果找到一个数字或符号,它变保存,并读取下一个字符。如果下一个字符是数字,它边保存该数字并读取下一个字符,如此不断读取,直到遇到一个非数字字符,它便认为读到了整数的末尾。然后 scanf() 把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。然后 scanf() 计算已读的数字(可能还有符号)相应的数值,并将计算后的值放入指定变量中。
如果第一个非空白字符是A而不是数字,则 scanf() 将停在那里,并把A放回输入中,不会把它赋值给指定变量。程序在下一次读取输入时,首先读到的是A。如果程序只使用 %d 转换说明,则 scanf() 就一直无法越过A对下一个字符。如果使用带多个转换符的 scanf(),C 规定在第一个出错处停止读取输入。
如果使用其他转换符,比如%x,则可读字符增加了 a-f,A-F,e计数法,小数点和新增的p计数法。
如果使用 %s 读取字符串,scanf() 会读取除空白意外所有字符。
事实上,scanf() 并不是最常用的输入函数,只不过它能读取不同类型的数据。其他输入函数:getchar(), fgets()。这两个函数更适合处理一些特殊情况,比如读取单个字符或包含空格的字符串。
scanf() 对格式化比较敏感,比如 scanf("%d,%d", &a, &b),则在输入时,必须要有逗号,而对于空白,它会跳过空白(读char类型时除外)
scanf() 的返回值:
- 如果没有读任何项,返回0
- 当 scanf() 检测读到“文件结尾”,会返回 EOF(stdio.h 中定义的特殊值,通常用 #define 将其定义为-1)
printf() 和 scanf() 的 * 修饰符
printf() 和 scanf() 都可以用 * 来修改转换符的含义。
- printf()
#include <stdio.h>
int main(void){
unsigned int width, precision;
int a = 256;
float b = 345.6;
printf("Enter a field width:\n");
scanf("%d", &width);
printf("a:%*d\n", width, a);
printf("Enter a width and a precision:\n");
scanf("%d %d", &width, &precision);
printf("b:%*.*f\n", width, precision, b);
return 0;
}
output:
Enter a field width:
10
a: 256
Enter a width and a precision:
12 8
b:345.60000610
- scanf()
scanf() 函数中的 * 与 printf() 完全相反,用在 % 和 转换符之间,它会跳过输入。
#include <stdio.h>
int main(void){
int n;
printf("Please enter 3 integers:\n");
scanf("%*d %*d %d", &n);
printf("n:%d\n", n);
return 0;
}
output:
Please enter 3 integers:
3 4 5
n:5
ex01:
Write a program that sets a type double variable to 1.0/3.0 and a type float variable to 1.0/3.0. Display each result three times—once showing four digits to the right of the decimal, once showing 12 digits to the right of the decimal, and once showing 16 digits to the right of the decimal. Also have the program include float.h and display the values of FLT_DIG and DBL_DIG . Are the displayed values of 1.0/3.0 consistent with these values?
#include <stdio.h>
#include <float.h>
int main(void){
double a = 1./3.;
float b = 1./3.;
printf("6 double:%.6f, float:%.6f\n", a, b);
printf("12 double:%.12f, float:%.12f\n", a, b);
printf("16 double:%.12f, float:%.12f\n", a, b);
printf("FLT_DIG:%d, DBL_DIG:%d\n", FLT_DIG, DBL_DIG);
return 0;
}
output:
6 double:0.333333, float:0.333333
12 double:0.333333333333, float:0.333333343267
16 double:0.333333333333, float:0.333333343267
FLT_DIG:6, DBL_DIG:15
ex02:
Write a program that asks the user to enter the number of miles traveled and the number of gallons of gasoline consumed. It should then calculate and display the miles-per-gallon value, showing one place to the right of the decimal. Next, using the fact that one gallon is about 3.785 liters and one mile is about 1.609 kilometers, it should convert the mile-per-gallon value to a liters-per-100-km value, the usual European way of expressing fuel consumption, and display the result, showing one place to the right of the decimal. Note that the U. S. scheme measures the distance traveled per amount of fuel (higher is better), whereas the European scheme measures the amount of fuel per distance (lower is better). Use symbolic constants (using const or #define ) for the two conversion factors.
#include <stdio.h>
#define GALLON2LITER 3.785
int main(void){
const float MILE2KM = 1.609;
float miles, gallons;
float consume;
printf("Enter miles and gallons:\n");
scanf("%f %f", &miles, &gallons);
float kms = miles * MILE2KM;
float liters = gallons * GALLON2LITER;
consume = liters / (kms+0.0001) / 100.;
printf("Your petrol consume is %.2f liters per 100 kilometers.\n", consume);
return 0;
}
output:
Enter miles and gallons:
20
10
Your petrol consume is 0.01 liters per 100 kilometers.

浙公网安备 33010602011771号