对标题和序号稍加修改。
4.1 示例程序
本章仍从一个程序开始。
#include <stido.h>
#include <string.h> //提供strlen()函数原型
#define DENSITY 62.4 //人体密度(单位:磅/立方英尺)
int main(void)
{
float weight,volume;
int size,letters;
char name[40]; //声明一个可容纳40个字符的数组name
printf("Hi! What's your first 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("Well, %s, your volume is %2.2f cubic feet.\n",name,volume);
printf("Also, your first name has %d letters,\n",letters);
printf("and we have %d bytes to store it.\n",size);
return 0;
}
运行后输出结果:
Hi! What's your first name?
UnicornBeetle
UnicornBeetle, what's your weight in pounds?
121
Well, UnicornBeetle, your volume is 1.94 cubic feet.
Also, your first name has 13 letters,
and we have 40 bytes to store it.
该程序包含以下新特性:
- 用数组(array)存储字符串(character string)。
- 用
%s
转换说明处理字符串的输入和输出。 - 用
#define
预处理指令定义明示常量。 - 用
strlen()
获取字符串长度。
4.2 字符串简介
字符串是一个或多个字符的序列。如下所示:
"Zing went the strings of my heart!"
双引号不是字符串的一部分,它告知编译器双引号内部是字符串。
4.2.1 char类型数组和null字符
C语言没有专门存储字符串的变量类型,字符串都被存储在char类型数组中。数组是由同类型、连续的存储单元(元素)组成的有序序列,字符串中的字符被储存在char数组相邻的存储单元中,每个单元储存一个字符。
注意储存字符串的char数组末尾的存储单元中储存的是空字符(null character)即null。它标记字符串的结束。空字符不是数字0,是非打印字符,ASCII码值是0。C中的字符串一定以空字符结尾,这意味着数组的容量必须至少比待储存字符串中的字符数多1。例如上一示例中字符串在计算机内储存形式实际是这样的:
Zing went the strings of my heart!\0
示例程序中char数组的声明char name[40];
表达了以下信息:
[]
表明声明了一个数组。[]
中表明该数组的元素数量,即数组长度。char
表明每个元素的类型为字符类型。- 可储存40个字符,可储存字符串最大长度为39。
4.2.2 使用字符串
/*字符串输入输出*/
#include <stdio.h>
#define PRAISE "You are am extraordinary being."
int main(void)
{
char name[40];
printf("What's your name?\n");
scanf("%s",name);
printf("Hello, %s. %s\n",name,PRAISE);
}
运行后输出结果:
What's your name?
UnicornBeetle Liyifan
Hello, UnicornBeetle. You are am extraordinary being.
程序中出现了两次%s
:第一次是scanf()
函数要读取一个字符串,根据输入的字符串UnicornBeetle Liyifan,程序只读取了UnicornBeetle。这是因为scanf()
遇到第1个空白(空格、制表符或换行符)时不再读取输入。第二次是pintf()
中要输出两个字符串,其中一个是字符串常量PRAISE。
字符和字符串
字符常量和字符常量是不同的。例如,'x'
和"x"
是不同的,区别之一在于'x'
是基本类型(char),"x"
是派生类型(char数组)。区别之二在于"x"
实际上由'x'
和'\0'
组成。
4.2.3 strlen()函数
strlen()
用于返回字符串的长度,即除空字符外的字符个数,返回类型为%zd
,早期C中sizeof
和strlen()
实际返回类型通常为unsigned或unsigned long。
/*使用strlen()*/
#include <stdio.h>
#include <string.h> /*提供strlen()函数原型*/
#define PRAISE "You are am extraordinary being."
int main(void)
{
char name[40];
printf("What's your name?\n");
scanf("%s",name);
printf("Hello, %s. %s\n",name,PRAISE);
printf("Your name of %zd letters occupies %zd memory cells.\n",
strlen(name),sizeof name);
printf("The phrase of praise has %zd letters, ",
strlen(PRAISE));
printf("and occupies %zd memory cells.\n",sizeof PRAISE);
return 0;
}
运行后输出结果:
What's your name?
UnicornBeetle Liyifan
Hello, UnicornBeetle. You are am extraordinary being.
Your name of 13 letters occupies 40 memory cells.
The phrase of praise has 31 letters, and occupies 32 memory cells.
对于name数组,我们使用了13个元素,因此字符串长度为13,占用40个字节。对于字符串常量PRAISE,包含空格一共有31个字符,但是它储存在字符数组中以空字符结尾,尽管空字符是字符串的一个组成部分,但是却不算入字符串的长度,而算入占用空间中。因此PRAISE占用32字节。
4.3 常量和C预处理器
符号常量
使用符号常量(symbolic constant)的意义:
- 表达信息更多。
- 方便一改全改。
示例:
s=3.14*r;/*使用字面常量*/
s=PI*r;/*使用符号常量PI,其值为3.14*/
第二行比第一行的含义更加清楚。
声明方式:声明一变量作为符号名,再赋常量值。
float PI;
PI=3.14;
虽然PI形式上是符号常量,但其实际是一个变量,所以程序运行期间可能会不经意间改变PI的值,因此建议使用C预处理器来定义明示常量。
明示常量
用#define
预处理指令定义明示常量(manifest constant),格式:
#define NAME value
编译前会把所有NAME替换成value,这一过程被称为编译时替换(compile-time substitution),预处理器进行替换不进行计算。
注意,末尾不加;
,这是由预处理器处理的替换机制。大写常量既是传统又是为了提高可读性。命名规则与变量相同。
4.3.1 const限定符
C90新增关键字const,用于限定一个变量为只读,即不可更改。第12章详述。
4.3.2 limits.h和float.h明示常量
limits.h提供整数类型的明示常量
明示常量 | 含义 |
---|---|
CHAR_BIT | char类型的位数 |
CHAR_MAX | char类型的最大值 |
CHAR_MIN | char类型的最小值 |
SCHAR_MAX | signed char类型的最大值 |
SCHAR_MIN | signed char类型的最小值 |
UCHAR_MAX | unsigned char类型的最大值 |
SHRT_MAX | short类型的最大值 |
SHRT_MIN | short类型的最小值 |
USHRT_MAX | unsigned short类型的最大值 |
INT_MAX | int类型的最大值 |
INT_MIN | int类型的最小值 |
UINT_MAX | usigned int类型的最大值 |
LONG_MAX | long类型的最大值 |
LONG_MIN | long类型的最小值 |
ULONG_MAX | unsigned long类型的最大值 |
LLONG_MAX | long long类型的最大值 |
LLONG_MIN | long long类型的最小值 |
ULLONG_MAX | usigned long long类型的最大值 |
float.h提供浮点类型的明示常量
明示常量 | 含义 |
---|---|
FLT_MANT_DIG | float类型的位数尾数 |
FLT_DIG | float类型的最少有效数字位数 |
FLT_MIN_10_EXP | 带有全部有效数字的float类型的最小负指数(以10为底) |
FLT_MIN_10_EXP | float类型的最大正指数(以10为底) |
FLT_MIN | 保留全部精度的float类型最小正数 |
FLT_MAX | float类型的最大正数 |
FLT_EPSILON | 1.00和比1.00大的最小float类型值之间的差值 |
示例程序
/*使用limits.h和float.h定义的明示常量*/
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void)
{
printf("Some number limits for this system:\n");
printf("Biggest int: %d\n",INT_MAX);
printf("Samllest long long: %lld\n",LLONG_MIN);
printf("One Byte = %d bits on this system.\n",CHAR_BIT);
printf("Largest double: %e\n",DBL_MAX);
printf("Smallest normal float: %e\n",FLT_MIN);
printf("float precisio = %d digits.\n",FLT_DIG);
printf("float essilon = %e\n",FLT_EPSILON);
return 0;
}
运行后输出结果:
Some number limits for this system:
Biggest int: 2147483647s
Samllest long long: -9223372036854775808
One Byte = 8 bits on this system.
Largest double: 1.797693e+308
Smallest normal float: 1.175494e-038
float precisio = 6 digits.
float essilon = 1.192093e-007
4.4 printf()和scanf()
输入/输出函数,简称I/O函数
4.4.1 printf()函数
转换说明(conversation specification)指定了如何把数据转换成可显示的形式。下表为printf()
转换说明。
转换说明 | 输出 |
---|---|
%a | 浮点数、十六进制数和p计数法(C99/C11) |
%A | 浮点数、十六进制数和p计数法(C99/C11) |
%c | 单个字符 |
%d | 有符号十进制数 |
%e | 浮点数,e计数法 |
%E | 浮点数,e计数法 |
%f | 浮点数,十进制计数法 |
%g | 根据值不同,自动选择%f或%e。%e用于指数小于-4或者大于或等于精度时 |
%G | 根据值不同,自动选择%f或%E。%E用于指数小于-4或者大于或等于精度时 |
%i | 有符号十进制数(同%d) |
%o | 无符号8进制数 |
%p | 指针 |
%s | 字符串 |
%u | 无符号十进制数 |
%x | 无符号十六进制数,使用十六进制数0f |
%X | 无符号十六进制数,使用十六进制数0F |
%% | 打印一个% |
4.4.2 使用printf()
#include <stdio.h>
#define PI 3.141593
int main(void)
{
int number=7;
float pies=12.75F;
int cost=7800;
printf("The %d contestans ate %f berry pies.\n",number,pies);
printf("The value of pi is %f.\n",PI);
printf("Farewell! thou art too dear for my possessing,\n");
printf("%c%d\n",'$',2*cost);
return 0;
}
运行后输出结果:
The 7 contestans ate 12.750000 berry pies.
The value of pi is 3.141593.
Farewell! thou art too dear for my possessing,
$15600
printf()
输出格式:printf("格式字符串",输出列表);
。
4.4.3 printf()的转换说明修饰符
在%
和转换字符之间插入合法的修饰符可以修饰基本的转换说明。下表为合法的修饰符以及标记。如果要插入多个修饰符,要以下表列出的顺序为准。
修饰符 | 含义 |
---|---|
标记 | 表2 |
数字 | 最小字段宽度,不够宽度用空格填充 |
.数字 | 精度。对于%e 、%E 和%f ,表示小数点右边数字的位数;对于%g 和%G ,表示有效数字最大位数;对于%s ,表示待打印字符的最大数量;对于整型转换说明,表示待打印数字的最小位数。如有必要,使用前导0达到这个位数;只使用. 表示其后跟随一个0,%.f 和%.0f 相同 |
h | 和整型转换说明一起使用,表示short int或unsigned int值 |
hh | 和整型转换说明一起使用,表示short char或unsigned char值 |
j | 和整型转换说明一起使用,表示intmax_t或uintmax_t值 |
l | 和整型转换说明一起使用,表示long int或unsigned long int值 |
ll | 和整型转换说明一起使用,表示long long int或unsigned long long int值(C99) |
L | 和浮点类型转换说明一起使用,表示long double值 |
t | 和整型转换说明一起使用,表示ptrdiff_t值,是两个指针类型差值的类型(C99) |
z | 和整型转换说明一起使用,表示size_t值,是sizeof和strlen()的返回类型(C99) |
标记 | 含义 |
---|---|
- | 待打印项左对齐,从字段左侧开始打印 |
+ | 有符号值为正则显示+,为负则显示- |
空格 | 有符号值为正则显示前导空格(+覆盖1空格),为负显示- |
# | 把结果转换成另一种形式。对于%o 以0开始;对于%x 或%X 以0x或0X开始;对于所有浮点格式,#保证即使没有任何数字也打印一个小数点;对于%g 或%G ,#防止结果后面的0被删除 |
0 | 用前导0代替空格填充字段宽度。对于整数格式,如果出现-标记或指定精度,则忽略该标记。 |
打印整型示例
#include <stdio.h>
#define VALUE 959
int main(void)
{
printf("[%d]\n",VALUE);
printf("[%2d]\n",VALUE);
printf("[% d]\n",VALUE);
printf("[% d]\n",-VALUE);
printf("[%10d]\n",VALUE);
printf("[%-10d]\n",VALUE);
printf("%o %#o\n",VALUE,VALUE);
printf("%x %X %#x %#X\n",VALUE,VALUE,VALUE,VALUE);
printf("[%6d]\n",VALUE);
printf("[%6.4d]\n",VALUE);
printf("[%06d]\n",VALUE);
printf("[%06.4d]\n",VALUE);
return 0;
}
运行后输出结果:
[959]
[959]
[ 959]
[-959]
[ 959]
[959 ]
1677 01677
3bf 3BF 0x3bf 0X3BF
[ 959]
[ 0959]
[000959]
[ 0959]
打印浮点型示例
#include <stdio.h>
#define VALUE 3852.99
int main(void)
{
printf("[%f]\n",VALUE);
printf("[%e]\n",VALUE);
printf("[%4.2f]\n",VALUE);
printf("[%3.1f]\n",VALUE);
printf("[%11.3f]\n",VALUE);
printf("[%11.3E]\n",VALUE);
printf("[%+4.2f]\n",VALUE);
printf("[%010.2f]\n",VALUE);
return 0;
}
运行后输出结果:
[3852.990000]
[3.852990e+003]
[3852.99]
[3853.0]
[ 3852.990]
[ 3.853E+003]
[+3852.99]
[0003852.99]
打印字符串示例
#include <stdio.h>
#define STRING "Authentic imitation!"
int main(void)
{
printf("[%2s]\n",STRING);
printf("[%24s]\n",STRING);
printf("[%24.5s]\n",STRING);
printf("[%-24.5s]\n",STRING);
return 0;
}
运行后输出结果:
[Authentic imitation!]
[ Authentic imitation!]
[ Authe]
[Authe ]
4.4.4 转换说明的意义
转换说明把以二进制格式储存在计算机中的值转换成一系列字符串以便于显示,例如十进制数76在计算机内部以二进制数0100110储存,%d
将其转换成字符7和6并显示为字符串76。它实际上还是一种翻译说明。例如,%d
的意思是“把给定的值翻译成是十进制整数文本并打印出来”。
除此以外,使用printf()
还要注意以下三个问题。
转换不匹配
- 系统使用二进制补码存储有符号整数。
- 截断现象。
- 参数传递机制
计算机根据变量类型把传入的值放入被称为栈(stack)的内存区域,调用printf()
时,它根据转换说明从栈中读取值。
printf()返回值
printf()
返回打印字符的个数,如果有输出错误则返回一个负值。其返回值经常被用来检查输出错误。示例程序:
#include <stdio.h>
int main(void)
{
int rv;
rv=printf("212 F is water's boiling point.\n");
printf("The printf() funciton printed %d characters.\n",rv);
return 0;
}
运行后输出结果:
212 F is water's boiling point.
The printf() funciton printed 32 characters.
计算所有字符数时还包括空格和不可见的换行符。
打印较长字符串
可以用空白(空格、制表符、换行符)分隔不同的部分,编译器会忽略。示例:
printf("The printf() funciton printed %d characters.\n"
,rv);
以上示例中,换行符以及制表符会被编译器忽略。但是不能在双引号之间断行。给字符串断行的3种方法:
- 使用多个
printf()
连续输出。 \
组合Enter使用,下一行左对齐无缩进。- 用空白连接两个字符串。
示例:
printf("Hello, young lovers, where you are?\n");
printf("Hello, young lovers");
printf(", where you are?\n");
printf("Hello, young lovers\
, where you are?\n");
printf("Hello, young" " lovers" ", where you are?\n");
4.4.5 scanf()函数
scanf()
把输入的字符串转换成指定的类型,它的参数列表使用的是指向变量的指针(&
创建指针指向变量位置,第9章详解)。记住两点规则即可:
- 读取基本类型值在变量名前加
&
。 - 把字符串读入字符数组中不加
&
。
示例程序;
#include<stdio.h>
int main(void)
{
int age; /*变量*/
float assets; /*变量*/
char insect[30];/*字符数组,储存字符串*/
printf("Enter your age, assets, and farvorite insect.\n");
scanf("%d %f",&age,&assets);//使用&
scanf("%s",insect); //不使用&
printf("%d $%.2f %s\n",age,assets,insect);
return 0;
}
运行后输出结果:
Enter your age, assets, and farvorite insect.
22
101.88 UnicornBeetle
22 $101.88 UnicornBeetle
scanf()
用空白把输入分成多个字段,在依次把转换说明和字段匹配时跳过、不读取空白。但是读取字符时用%c
转换说明无法跳过空白,它会读取每个字符,稍后详述。
scanf()的转换说明
转换说明 | 含义 |
---|---|
%c | 把输入解释成字符 |
%d | 把输入解释成有符号十进制整数 |
%e、%f、%g、%a | 把输入解释成浮点数(C99新增%a) |
%E、%F、%G、%A | 把输入解释成浮点数(C99新增%A) |
%i | 把输入解释成有符号十进制整数 |
%o | 把输入解释成有符号八进制整数 |
%p | 把输入解释成指针(地址) |
%s | 把输入解释成字符串,从第一个非空白字符开始到写一个非空白字符之间都是输入 |
%u | 把输入解释成无符号十进制整数 |
%x、%X | 把输入解释成有符号十六进制整数 |
scanf()转换说明修饰符
使用多个修饰要按下表顺序书写。
修饰符 | 含义 |
---|---|
* | 抑制赋值 |
数字 | 最大字段宽度。输入达到最大字段宽度处或第1次遇到空白字符时停止。 |
hh | 把整数作为signed int或unsigned int类型读取 |
ll | 把整数作为long long int或unsigned long long int类型读取(C99) |
h | %hd 和%hi 把对应的值储存为int类型;%ho 、%hx 和%hu 把对应的值储存为unsigned short int类型 |
l | %ld 和%li 把对应的值储存为long类型;%lo 、%lx 和%lu 把对应的值储存为unsigned long类型;%le 、%lf 和%lg 把对应的值储存为double类型 |
L | %Le 、%Lf 和%Lg 把对应的值储存为long double类型 |
j | 表明使用intmax_t或uintmax_t类型(C99) |
z | 表明使用sizeof返回类型(C99) |
t | 表明使用表示两个指针差值的类型(C99) |
如果没有修饰符,d、i、o和x表明把对应的值储存为int类型,e、f、g表明把对应的值储存为float类型。
从scanf()角度看输入
scanf()
用%d
读取一个整数,希望发现一个数字或正负号,从第一个非空白字符开始不断保存和读取数字字符,直至遇到非数字字符(含空白),它便认为读到了整数结尾。然后把非数字字符放回输入,下一次读取输入时首先读到上次读取丢弃的非数字字符。最后scanf()
计算已读取数字相应的数值并放入指定的变量中。示例:
#include<stdio.h>
int main(void)
{
int input;
scanf("%d",&input);
printf("%d\n",input);
return 0;
}
运行后输出结果:
456awds547
456
注意,scanf()
读取指定形式的输入失败就把无法读取的输入放回输入队列中供下次读取。
如果使用字段宽度,scanf()
会在字段结尾或第1个空白处停止读取(满足两者之一便停止)。示例:
#include<stdio.h>
int main(void)
{
int input1,input2;
scanf("%4d",&input1);
printf("input1:%5d\n",input1);
scanf("%4d",&input2);
printf("input2:%5d\n",input2);
return 0;
}
运行后输出结果:
4648454 4wdsa8
input1: 4648
input2: 454
如果使用%s
转换说明,scanf()
会读取除空白以外的所有字符。scnaf()
从跳过空白开始读取第1个非空白字符,并保存非空白字符直到再次遇到空白。如果使用字段宽度,会在字段末尾或第1个空白字符处停止读取。当scanf()
把字符串放入指定数组中时,会在字符序列的末尾加上'\0'
,让数组成为一个C字符串。
格式字符串中的普通字符
scanf()
允许把普通字符放在格式字符串中。除空格以外的普通字符必须与输入字符串严格匹配。格式字符串中的空白意味着跳过下一输入项前的所有空白(包括无空格特殊情况)。对于语句scanf("%d ,%d",&n,&m);
以下输入都没问题:
88,121 //无空格的特殊情况
88 ,121
88 , 121
88 , 121
88,
121
除了%c
转换说明,其他转换说明都会自动跳过、不读取待输入值前的所有空白。但是在%c
前加空格会有不同的效果。例如,scanf("%c",&ch);
从第一个字符开始读取,scanf(" %c",&ch);
从第一个非空白字符开始读取。
scanf()返回值
scanf()
返回成功读取的项数。没有读取任何项返回0,检测到“文件结尾”返回EOF(第6章详解)。可用返回值检测和处理不匹配输入问题。
4.4.6 printf()和scanf()的*修饰符
printf()用法
可用*
代替字段宽度,使用参数指定*
值即可,也可用于精度。
示例程序:
/*使用变宽输出字段*/
#include<stdio.h>
int main(void)
{
unsigned width,precision;
int number=256;
double weight=242.5;
printf("Enter a field width:\n");
scanf("%d",&width);
printf("The number is:%*d:\n",width,number);
printf("Now enter a width and a precision:\n");
scanf("%d %d",&width,&precision);
printf("Weight=%*.*f\n",width,precision,weight);
printf("Done!\n");
return 0;
}
运行后输出结果:
Enter a field width:
6
The number is: 256:
Now enter a width and a precision:
8 3
Weight= 242.500
Done!
scanf()用法
scanf()
用*
修饰符跳过相应的输入项。示例:
/*使用变宽输出字段*/
#include<stdio.h>
int main(void)
{
int n;
printf("Please enter three integers:\n");
scanf("%*d %*d %d",&n);
printf("The last integers was:%d\n",n);
return 0;
}
运行后输出结果:
Please enter three integers:
2019 2020 2021
The last integers was:2021
读取文件特定列的内容时该功能很有效。