本章的主题是C语言的字符串和格式化输入/输出。
C语言没有专门用于存储字符串的变量类型,字符串都被存储在
char
类型的数组中。-
数组是同类型数据元素的有序序列。
- 以
char name[40]
为例,name
后面的方括号表明这是一个数组,方括号中的40
表明该数组中的元素数量,char
表明每个元素的类型。 - 字符串可以理解为字符数组,每个单元存储一个字符,数组末尾用一个空字符(null character)标识结束。
- 以
空字符
\0
是非打印字符,其ASCII码值为0。由于空字符的存在,因此字符串实际能存储的字符数要减1,剩下一个字节留给空字符。上例中字符串name
实际能存储的字符数为39。转换字符
%s
用于读写字符串,%zd
用于处理sizeof()
和strlen()
的返回值。scanf()
在遇到第一个空白(空格、制表符或换行符)时就不再读取输入。C语言还有其他的输入函数(如,fgets()
),用于读取一般字符串,后面会详谈。-
使用符号常量(symbolic constant)或 明示常量(manifest constant)的好处:
- 常量名比数字表达的信息更多;
- 复用性更好。
-
使用C预处理器来定义符号常量:
#define NAME value
。- 例如,
#define PI 3.14159
。编译程序时,程序中所有的PI
都会被替换成3.14159
。这一过程被称为编译时替换(compile-time substitution)。 - 为什么
PI
要大写?因为用大写表示符号常量是C语言一贯的传统,这样做是为了提高程序的可读性,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。
- 例如,
-
C90标准新增了
const
关键字,用于限定一个变量为只读。const int MONTHS = 12; // MONTHS在程序中不可更改,值为12
const
用起来比#define
更灵活,后续会详谈。 printf()
和scanf()
是标准输入/输出函数,简称为I/O函数。printf()
把整数、浮点数、字符和字符串转换成显示在屏幕上的文本,而scanf()
正好与它相反,把输入的字符串转换成整数、浮点数、字符和字符串存储在内存中。printf()
的格式为:printf(格式字符串, 待打印项1, 待打印项2, ...)
其中,格式字符串是双引号括起来的内容,包含两种形式不同的信息:(1)实际要打印的字符;(2)转换说明。待打印项1、待打印项2等都是要打印的项,可以是变量、常量或表达式。printf()
的转换说明
转换说明 | 含义 |
---|---|
%a |
浮点数、十六进制数和p记数法(C99/C11) |
%A |
浮点数、十六进制数和p记数法(C99/C11) |
%c |
单个字符 |
%d |
有符号十进制整数 |
%e |
浮点数,e记数法 |
%E |
浮点数,e记数法 |
%f |
浮点数,十进制记数法 |
%g |
根据值的不同,自动选择%f 或%e 。 |
%G |
根据值的不同,自动选择%f 或%E 。 |
%i |
有符号十进制整数(和%d 相同) |
%o |
无符号八进制整数 |
%p |
指针 |
%s |
字符串 |
%u |
无符号十进制整数 |
%x |
无符号十六进制整数,使用十六进制数0f |
%X |
无符号十六进制整数,使用十六进制数0F |
%% |
打印一个百分号 |
-
printf()
的转换说明修饰符:在%
和转换字符
之间插入修饰符可修饰基本的转换说明。
修饰符 | 含义 |
---|---|
标记 |
有6种标记(- 、+ 、空格 、# 、* 和0 ),可以不使用标记或使用多个标记- :待打印项左对齐+ :有符号值若为正,则在值前面显示加号;若为负,则在值前面显示减号空格 :有符号值若为正,则在值前面显示前导空格;若为负,则在值前面显示减号# :把结果转换为另一种类型。如果是%o 格式,则以0 开始;如果是%x 或%X 格式,则以0x 或0X 开始;对于所有的浮点格式,# 保证了即使后面没有任何小数,也打印一个小数点字符;对于%g 和%G 格式,# 防止结果后面的0 被删除* :抑制赋值(详见后面解释)0 :对于数值格式,用前导0 代替空格填充字段宽度。对于整型格式,如果出现- 标记或指定精度,则忽略该标记 |
数字 |
最小字段宽度 如果该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段 |
.数字 |
精度 对于 %e 、%E 和%f 转换,表示小数点右边数字的位数对于 %s 转换,表示待打印字符的最大数量对于整型转换,表示待打印数字的最小位数 如有必要,使用前导 0 来达到这个位数只使用 . 表示其后跟随一个0 ,所以%.f 和%.0f 相同 |
h |
和整型转换说明一起使用,表示short int 或unsigned short int 类型的值 |
hh |
和整型转换说明一起使用,表示signed char 或unsigned char 类型的值 |
j |
和整型转换说明一起使用,表示intmax_t 或uintmax_t 类型的值。这些类型定义在stdint.h 中 |
l |
和整型转换说明一起使用,表示long int 或unsigned long int 类型的值 |
ll |
和整型转换说明一起使用,表示long long int 或unsigned long long int 类型的值 |
L |
和浮点转换说明一起使用,表示long double 类型的值 |
t |
和整型转换说明一起使用,表示ptrdiff_t 类型的值。ptrdiff_t 是两个指针差值的类型 |
z |
和整型转换说明一起使用,表示size_t 类型的值。size_t 是sizeof() 返回的类型 |
printf()
有一个返回值,它返回打印字符的个数。如果有输出错误,则返回一个负值。给字符串断行有3种方法。
// longstrg.c --打印较长的字符串
#include <stdio.h>
int main(void)
{
// 方法1:使用多个printf()语句
printf("Here's one way to print a ");
printf("long string.\n");
// 方法2:使用反斜杠和Enter键组合
printf("Here's another way to print a \
long string.\n");
// 方法3:字符串连接
printf("Here's the newest way to print a "
"long string.\n");
return 0;
}
-
scanf()
函数的格式为:scanf(格式字符串, 地址列表)
其中,地址列表
是指向变量的指针,此处不必了解如何使用指针,只需记住以下两条简单的规则:- 如果用
scanf()
读取基本变量类型的值,则在变量名前加上一个&
; - 如果用
scanf()
把字符串读入字符数组中,不要使用&
。
- 如果用
scanf()
函数所用的转换说明与printf()
函数几乎相同。主要的区别是,对于float
类型和double
类型,printf()
都使用%f
、%e
、%E
、%g
和%G
转换说明,而scanf()
只把它们用于float
类型,对于double
类型要用l
修饰符。在
scanf()
中,除了%c
,其他转换说明都会自动跳过待输入值前面的所有空白。scanf()
函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。scanf()
函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()
便返回0
。-
printf()
和scanf()
都可以使用*
修饰符来修改转换说明的含义,但用法不一样。-
printf()
:如果不想预先指定字段宽度或精度,希望通过程序来指定,可以用*
修饰符代替字段宽度或精度,但需要参数告诉函数,字段宽度或精度应该是多少。 -
scanf()
:把*
放在%
和转换字符之间时,会使得scanf()
跳过相应的输入项。在程序需要读取文件中特定列的内容时,这项跳过功能很有用。
-
使用
scanf()
输入分数时,不能直接使用以下语句
/* 这是错误的分数输入 */
scanf("%f", &x); // 在实际输入的时候不能输入类似"3/7"的数值,因为scanf()并不能识别"/"
/* 这是正确的分数输入 */
scanf("%d/%d", &x, &y);
- 使用
scanf()
可以实现长数据的截取与跳过
/* 截取身份证号的出生年月日 */
#include <stdio.h>
int main(void)
{
int year, month, day;
printf("请输入您的身份证号:");
scanf("%*6d%4d%2d%2d%*4d", &year, &month, &day);
printf("您的生日是:%d年%d月%d日\n", year, month, day);
return 0;
}
下一章的主题是C语言的运算符、表达式和语句。