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

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

1 前导程序

#include <stdio.h>
#include <string.h>
#define DENSITY 62.4

int main(){
    float weight, volume;
    int size, letters;
    char name[40];

    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 leeters,\n", letters);
    printf("and we have %d bytes to store it.\n", size);

    return 0;

}
[root@hans ~]# ./talkback 
Hi! What's your first name?
Chirstine
Chirstine,What's your weight in pounds?
154
Well, Chirstine, your volume is 2.47 cubic feet.
Also, your first name has 9 leeters,
and we have 40 bytes to store it.

该程序包含以下新特性:

  • 用数组储存字符串
  • 使用%s转换说明来处理字符串的输入和输出。注意scanf()中name没有&前缀,而weight有前缀
  • 用C预处理器把字符常量DENSITY定义为62.4
  • 用C函数strlen()获取字符串的长度

2 字符串简介

字符串是一个或多个字符的序列。

"Zing went the strings of my heart!"

双引号不是字符串的一部分,它只是告诉编译器它括起来的是字符串,正如单引号用于标识单个字符一样。

2.1 char类型数组和null字符

C语言中没有专门用于存储字符串的变量类型,字符串都被储存在char类型的数组中,数组由连接的存储单元组成。字符串中的字符被储存在相邻的存储单元中,每个单元储存一个字符。

|Z|i|n|g| |w|e|n|t| |t|h|e| |s|t|r|i|n|g|s| |o|f| |m|y| |h|e|a|r|t|!|\0|

在数组末尾位置的字符 \o 为空字符,C语言用它标记字符串的结束,空字符不是数字0,它是非打印字符。其ASCII码值是0,C中字符串一定以空字符结束,这就意味着数组的容量必须至少比待存储字符串的字符数多1.40个存储单元的字符串只能储存39个字符,剩下一个字节留给空字符。

数组,可以以把数组看作是一行连续的多个存储单元。

image

2.2 使用字符串

[root@hans ~]# cat praise1.c 
#include <stdio.h>
#define PRAISE "You are an extraordinary being."

int main(void){
    char name[40];
    printf("What's your name?");
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE);

    return 0;
}

[root@hans ~]# ./praise1 
What's your name?Angela Plains
Hello, Angela. You are an extraordinary being.

在打印时有两个%s,第一个是name,第二个是PRAISE.但打印name时scanf()只读取了Angela Plains中的Angela,它是遇到第1个空白(空格,制表符或换行符)时就不再读取输入。所以在读到Angela Plains之间的空格时就停止了,根据%s转换说明,scanf()只会读取字符串中的一个单词,而不是一整句。

字符串和字符:

字符串常量“x”和字符常量‘x’不同,区别之一在于'x'是基本类型(char),而“X”是派生类型(char 数组);区别之二是“x”实际上由两个字符组成:

'X'和空字符\0.

image

2.3 strlen()函数

sizeof它以字节为单位给出对象的大小。strlen()函数给出字符串中的字符长度。

[root@hans ~]# cat praise2.c 
#include <stdio.h>
#include <string.h>
#define PRAISE "You are an extraordinary being."

int main(void){
    char name[40];

    printf("What's your name?");
    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;
}
[root@hans ~]# ./praise2 
What's your name?Serendipity Chance
Hello, Serendipity. You are an extraordinary being.
Your name of 11 letters occupies 40 memory cells.
The phrase of praise has 31 letters and occupies 32 memory cells.

sizeof运算符报告,name数组有40个存储单元,但strlen()得出来的结果是11.name数组的第12个单元存储空字符并未将其计入。

image

对了PRAISE,用strlen()得出的是字符串中的字符数(包括空格和标点符号)。但sizeof把PRAISE字符串末尾不可见的空字符也计算在内。是因为程序并未明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。

C99和C11标准专门为sizeof运算符的返回类型添加了%zd转换说明。

还有注意一点:上一章的sizeof使用了圆括号,但本例中没有。何时使用圆括号取决于运算对象是类型还是特定量,运算对象是类型时圆括号必不可少,但对于特定量,可有可无。对于类型应写成sizeof(char)sizeof(float);对于特定量可写成 sizeof namesizeof 6.28.尽管如此,还是建议所有情况下都使用圆括号。

3 常量和C预处理器

有些时候程序中要使用常量,比如计算圆的周长。这里3.14159代表著名的常量pi.

如何创建常量,方法之一是声明一个变量,然后将该变量设置为所需的常量,但变量在程序中可能会无意间改变它的值,所以使用C语言提供的C预处理器。只需在程序顶部加一行:

# define PI 3.14159

编译程序时所有的PI都会替换成3.14159,这个过程被称为编译时替换。在运行程序时,程序中的替换均已完成。通常这样定义的常量也称为明示常量。

格式:首先是#define ,接着是符号常量名(PI),然后是符号常量的值(3.14159)

#define NAME value (其中没有=符号,而且末尾不用加分号)
NAME一般为大写,这是C语言一贯传统。

符号常量的命名规则与变量相同,可以使用大小写字母、数字、和下划线字符,首字符不能为数字。

image

[root@hans ~]# cat pizza.c 
#include <stdio.h>
#define PI 3.14159

int main(viod){
    float area, circum, radius;

    printf("What is the radius of your pizza?\n");
    scanf("%f",&radius);

    area = PI * radius * radius;
    circum = 2.0 * PI * radius;

    printf("Your basic pizza parameters are as follows:\n");
    printf("circumference = %1.2f, area = %1.2f\n", circum, area);


    return 0;

}

[root@hans ~]# ./pizza 
What is the radius of your pizza?
6.0
Your basic pizza parameters are as follows:
circumference = 37.70, area = 113.10

#define指令还可以定义字符和字符串常量,前者使用单引号,后者使用双引号。

#define BEEP '\a'
#define TEE 'T'
#define ESC '\033'
#define OOPS 'NOW you have done it!'
符号常量名后面的内容被用来替换符号常量。
/*错误的格式*/
#define TOES = 20 #这么写TOES为= 20 不是20.

3.1 const 限定符

C90标准新增了const关键字,用来限定一个变量为只读。(用const类型限定符声明的是变量,不是常量)

const int MONTHS = 12; //MONTHS在程序中不可更改。

3.2 明示常量

C头文件limits.h和float.h分别提供了与整数类型和浮点类型大小限制相关的详细信息。每个头文件都定义了一系列实现使用的明示常量。(明示常量相当于符号常量)

image

image

如何使用limits.h和float.h中的数据

[root@hans ~]# cat defines.c 
#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("Smallest 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 precision = %d digits\n", FLT_DIG);
    printf("float epsilon = %e\n", FLT_EPSILON);

    return 0;
}
[root@hans ~]# ./defines 
Some number limits for this system
Biggest int:2147483647
Smallest long long: -9223372036854775808
One byte = 8 bits on this system.
Largest double: 1.797693e+308
Smallest normal float: 1.175494e-38
float precision = 6 digits
float epsilon = 1.192093e-07

4 printf()和scanf()

printf()和scanf()它们是输入/输出函数,或简称I/O函数。能让用户可以与程序交流。

4.1 printf()函数

请求printf()函数打印数据的指令要与待打印数据的类型相匹配。如打印整数时使用%d,打印字符时用%c,这些符号被称为转换说明,它们指定了如何把数据转成可显示的形式。

转换说明 输出
%a,%A 浮点数、十六进制数和p计数法(C99/C11)
%c 单个个字符
%d 有符号十进制数
%e,%E 浮点数,e计数法
%f 浮点数,十进制计数法
%g 根据数值不同自动选择%f或%e,%e格式在指数小于-4或者大于等于精度时使用
%G 根据数值不同自动选择%f或%E,%E格式在指数小于-4或者大于等于精度时使用
%i 有符号十进制整数(与%d相同)
%o 无符号八进制整数
%p 指针
%s 字符串
%u 无符号十进制数
%x 无符号十六进制整数,使用十六进制数0f
%X 无符号十六进制整数,使用十六进制数0F
%% 打印一个百分号
[root@hans ~]# cat printout.c 
#include <stdio.h>
#define PI 3.141593

int main(viod){
    int number = 7;
    float pies = 12.75;
    int cost = 7800;

    printf("The %d contestants 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;
}
[root@hans ~]# ./printout 
The 7 contestants ate 12.750000 berry pies.
The value of pi is 3.141593. 
Farewell! thou art too dear for my possessing, 
$15600

printf()函数的格式

printf(格式字符串,待打印项1,待打印项2,......)
格式字符串是双引号括起来的内容。

image

4.2 printf()的转换说明修饰符

修饰符 含义
标志 五种标志(-、+、空格、非和0),可以不使用标记或使得多个标记,示例: “%-10d”
数字 字段宽度的最小值。如果字段不能容纳要打印的数或者字符串,系统会使用更宽的字段示例: “%4d”
.数字 精度 对于%e,%E和%f转换,是将要在小数点的右边打印的数字的位数。对于%g和%G转换,是有效数字的最大位数。对于%s转换,是将要打印的字符的最大数目。对于整数转换,是将要打印的数字的最小位数。如果必要,要使用前导0来达到位数。只使用"."表示其后跟随一个0,所以%.f和%.0f相同。示例: “%5.2f”表示打印一个浮点数,它的字段宽度为5个字符,小数点后有两个数字
h 和整数转换说明符一起使用,表示一个short int或unsigned short int类型数值。示例: “%hu”, “%hx”, “%6.4hd”
hh 和证书转换说明符一起使用,表示一个signed char或unsigned char类型数值
j 和整数转换说明符一起使用,表示一个intmax_t或uintmax_t值示例: “%jd”,"%8jx"
l 和整数转换说明符一起使用,表示一个long int或unsigned long int类型值
ll 和整数转换说明符一起使用,表示一个long long int或unsigned long long int类型值(C99)示例: “%lld”,"%8llu"
L 和浮点数转换说明符一起使用,表示一个long double值示例: “%Lf”, “%10.4Le”
t 和整数转换说明符一起使用,表示一个ptrdiff_t值(与两个指针之间的差相对应的类型)(C99)。示例: “%td”, “%1ti”
z 和整数转换说明符一起使用,表示一个size_t值(sizeof返回的类型)(C99)。示例: “%zd”,"%12zd"

printf()中的标记

标志 意义
- 打印项左对齐,即,从字段的左侧开始打印该项。示例: “%-20s”
+ 有符号的值若为正,则显示带加号的符号;若为负,则显示带减号的符号。示例: “%+6.2f”
空格 有符号的值若为正,则显示时带前导空格(不显示符号);若为负,则在前面显示减号。+标志会覆盖一个空格。示例: “% 6.2f”
# 使用转换说明的另一种形式。若为%o格式,则以0开始;若为%x和%X格式,则以0x或0X开始。对于所有的浮点形式,#保证了即使不跟任何数字,也打印一个小数点字符。对于%g和%G格式,它防止尾随0被删除。示例: “%#o”, “%#8.0f”, “%+#10.3e”
0 对于数值格式,用前导零代替空格填充字段宽度。对于整数格式,如果出现-标志或者指定了精度(对于整数)则忽略该标志。示例: “%010d”, “%08.3f”

示例:

[root@hans ~]# cat width.c 
#include <stdio.h>
#define PAGES 959

int main(void){
    printf("*%d*\n", PAGES);
    printf("*%2d*\n", PAGES);  
    printf("*%10d*\n", PAGES);
    printf("*%-10d*\n", PAGES);

    return 0;
}
[root@hans ~]# ./width 
*959*
*959*   #转换说明是%2d,其对应输出结果应该是2字段宽度,因为打印的整数有3位数字,所以字段宽度自动扩大以符合整数的长度。
*       959*
*959       *

浮点格式示例:

[root@hans ~]# cat floats.c 
#include <stdio.h>

int main(void){

    const double RENT = 3852.99;
    printf("*%f*\n", RENT);
    printf("*%e*\n", RENT);
    printf("*%4.2f*\n", RENT);
    printf("*%3.1f*\n", RENT);
    printf("*%10.3f*\n", RENT);
    printf("*%10.3E*\n", RENT);
    printf("*%+4.2f*\n", RENT);
    printf("*%010.2f*\n", RENT);

    return 0;
}
[root@hans ~]# ./floats               
*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
*  3852.990*
* 3.853E+03*
*+3852.99*
*0003852.99*

格式标记:

[root@hans ~]# cat flags.c
#include <stdio.h>

int main(void){
    printf("%x %X %#x\n",31, 31, 31);
    printf("**%d**% d**% d**\n", 42, 42, -42);
    printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);

    return 0;
}
[root@hans ~]# ./flags 
1f 1F 0x1f
**42** 42**-42**
**    6**  006**00006**  006**

字符串格式:

[root@hans ~]# cat stringf.c 
#include <stdio.h>
#define  BLURB "Authentic imitation!"

int main(void){
    printf("[%2s]\n", BLURB);
    printf("[%24s]\n", BLURB);
    printf("[%24.5s]\n", BLURB);
    printf("[%-24.5s]\n", BLURB);  #.5告诉printf()只打印5个字符,-标记使得文本左对齐输出

    return 0;

}
[root@hans ~]# ./stringf 
[Authentic imitation!]
[    Authentic imitation!]
[                   Authe]
[Authe                   ]

4.3 转换说明的意义

转换说明把二进制格式储存在计算机中的值转换成一系列字符(字符串)以便于显示。

4.3.1 转换不匹配

转换说明应该与待打印值的类型相匹配。如:打印int类型的值,可以使用%d,%x,%o,打印double类型可使用%f,%e或%g

[root@hans ~]# cat intconv.c   #一些不匹配的整型转换。
#include <stdio.h>
#define PAGES 336
#define WORDS 65618

int main(void){
    short num = PAGES;
    short mnum = -PAGES;
    
    printf("num as short and unsigned short: %hd %hu\n", num, num);
    printf("-num as short and unsigned short: %hd %hu\n", mnum, mnum);
    printf("num as int and char: %d %c\n", num, num);
    printf("WORDS as int, short, and char: %d %hd %c\n", WORDS, WORDS, WORDS);

    return 0;
}

[root@hans ~]# ./intconv 
num as short and unsigned short: 336 336
-num as short and unsigned short: -336 65200
num as int and char: 336 P
WORDS as int, short, and char: 65618 82 R

第一行,num对应转换说明%hd和%hu输出结果为336,没有问题
第二行,mnum对应的转换说明%u输出结果却为65200,并非期望的336.这是因为有符号short int 类型的值在我们的参考系统中的表示方式所致。首先,short int的大小是2字节;其次,系统使用二进制补码来表示有符号整数。这种方法数字0~32767代表它们本身,而数字32768~65535则表示负数。其中65535表示-1,65534表示-2.以此类推。因此,-336表示65200.拟被解释成有符号int时,65200代表-336,而被解释成无符号Int时65200代表65200.一个数字可以被解释成两个不同的值。
第三行,演示了把一个大于255的值转换成字符会发生什么?在系统中short int是2字节,char是1字节,当pirntf()使用%c打印336时,它只会查看存储336的2字节中的后1字节。这种截断相当于用一个整数除以256只保留其余数。336除以256余数是80,对应的ASCII值是P。
第四行,WORDS为65618使用int输出原值,打印%hd转换说明时printf()只使用最后2个字节。这相当于65618除以65536的余数,这里余数是82.%hd为82,82对应的ASCII码为R,%C输出为R

image

混淆整型和浮点型。

cat floatcnv.c 
#include <stdio.h>
int main(void)
{
     float n1 = 3.0;
    double n2 = 3.0;
    long n3 = 2000000000;
    long n4 = 1234567890;

    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;
}
[root@hans_tencent_centos82 ~]# ./floatcnv 
3.0e+00 3.0e+00 0.0e+00 3.2e-319
2000000000 1234567890
2000000000 1234567890 0 0

参数传递:
分析参数传递的原理(参数传递机制因实现而异):
    printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
该调用告诉计算机把变量n1、n2、n3、和n4的值传递给程序。程序把传入的值放入被称为栈的内存区域。计算机根据变量类型把这些值放入栈中。因此,n1被储存在栈中,占8字节(float类型转换double类型)。同样n2也在栈中占8字节,而n3,n4在栈中分别占4字节。然后控制转到pritnf()函数,该函数根据转换说明从栈中读取值。%ld转换说明表明printf()应该读取4字节,所以printf()读取栈中前4字节作为第1个值。这就是n1的前半部分,将被解释成一个long类型的整数。根据下一个%ld转换说明,printf()再读取4字节,这是n1的后半部分,将被解释成第2个long类型的整数。类似地,根据第3个和第4个%ld,printf()读取n2的前半部分和后半部分,并解释成两个long类型的整数。因此,对于n3和n4,虽然用对了转换说明,但printf()还是读错了字节。

image

4.3.2 printf()的返回值

大部署C函数都有一个返回值,这是函数计算并返回给主调程序的值。可以把返回值像其他值一样使用。printf()函数也有一个返回值,它返回打印字符的个数。如果有输出错误,printf()则返回一个负值。printf()的返回值是其打印输出功能的附带用途,通常很少用到,但在检查输出错误时可能会用到。

[root@hans ~]# cat  printval.c                 
#include <stdio.h>

int main(void){
    int bph2o = 212;
    int rv;

    rv = printf("%d F is water's boiling point.\n", bph2o);
    printf("The printf() function printed %d  characters.\n", rv);

    return 0;

}
[root@hans ~]# ./printval 
212 F is water's boiling point.
The printf() function printed 32  characters.

首先,程序用rv = printf()的形式把printf()的返回值赋给rv.因此该语句执行了两项任务:打印信息和给变量赋值。其次,注意计算针对所有字符数,包括空格和不可见的换行符(\n)。
4.3.3 打印较长的字符串

有时printf()语句太长,在屏幕上不方便阅读。如果空白(空格、制表符、换行符)仅用于分隔不同的部分,C编译器会忽略它们。一条语句可以写成多行,只需在不同部分之间输入空白即可。但不能在以引号括起来的字符串早间断行。

printf("The printf() function printed %d  characters.\n", 
		rv);   #没有问题
printf("The printf() function printed %d  
		characters.\n", rv);  #错误

断行的3种方法:

[root@hans ~]# cat longstrg.c 
#include <stdio.h>

int main(){
    printf("Here's one way to print a ");
    printf("long string.\n");   //1
    printf("Here's another way to print a \
long string.\n"); //2
    printf("Here's the newest way to print a "
            "long string.\n"); /*3, ANSI C */

    return 0;
}
[root@hans ~]# ./longstrg 
Here's one way to print a long string.
Here's another way to print a long string.
Here's the newest way to print a long string.
三种形式是等效的。

4.4 使用scanf()

scanf()可以读取不同格式的数据,从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号。

scanf()把输入的字符串转换成整数、浮点数、字符或字符串,而printf()正好相反,它把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。

scanf()和printf()类似,也使用格式字符串和参数列表。scanf()中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在参数列表中。printf()函数使用变量、常量和表达式,而scanf()函数使用指向变量的指针。在这里只需记住以下两条简单的规则:

  • 如果用scanf()读取基本变量类型的值,在变量名前加一个&
  • 如果用scanf()把字符串读入字符数组中,不要使用&

示例:

[root@hans ~]# cat input.c 
#include <stdio.h>

int main(void){
    int age;  			//变量

    float assets;		//变量
    char pet[5];		//变量

    printf("Enter your age, assets, and favorite pet.\n");
    scanf("%d %f", &age, &assets); /* use & */
    scanf("%s", pet); /* char arrary not use &*/
    printf("%d $%.2f %s\n", age, assets, pet);

    return 0;
}
[root@hans ~]# ./input 
Enter your age, assets, and favorite pet.
38
92360.88 llama
38 $92360.88 llama

**ANSI C中scanf()的转换说明 **

转换说明 含义
%c 把输入解释成一个字符
%d 把输入解释成一个有符号十进制整数
%e,%f,%g,%a 把输入解释成一个浮点数
%E,%F,%G,%A 把输入解释成一个浮点数
%i 把输入解释成一个有符号十进制整数(C99标准新增了%a)
%o 把输入解释成一个有符号八进制整数(C99标准新增了%A)
%p 把输入解释成一个指针(地址)
%s 把输入解释成字符串。从第1个非空白字符开始,到下一个空白字符之前的所有字符都是输入
%u 把输入解释成一个无符号十进制整数
%x,%X 把输入解释成一个有符号十六进制整数

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

转换说明 含义
* 抑制赋值, 示例:"%*d"
数字 最大字段宽度。输入达到最大字段宽度处,或第1次遇到空白字符时停止 示例:"%10s"
hh 把整数作为 signed char 或 unsigned char 类型读取 示例:"%hhd"、"%hhu"
ll 把整数作为 long long 或 unsigned long long 类型读取(C99) 示例:"%lld"、"%llu"
h、l或L "%hd"和"%hi"表明把对应的值存储为 short int 类型 "%ho"、"%hx"和"%hu"表明把对应的值存储为 unsigned short int 类型 "%ld"和"%li"表示把对应的值存储为 long 类型 "%lo"、"%lx"和"%lu"表明把对应的值存储为 unsigned long 类型 "%le"、"%lf"和"%lg"表明把对应的值存储为 double 类型 在e、f和g前面使用L而不是l,表明把对应的值存储为 long double 类型。如果没有修饰符,d、i、o和x表明对应的值被存储为 int 类型,f和g表明把对应的值存储为 float 类型
j 在整形转换说明后面时,表明使用 intmax_t 或 uintmax_t 类型(C99) 示例:"%jd"、"%ju"
z 在整形转换说明后面时,表明使用 sizeof 的返回类型(C99) 示例:"%zd"、"%zo"
t 在整形转换说明后面时,表明使用表示两个指针差值的类型(C99) 示例:"%td"、"%tx"
4.4.1 从scanf()角度看输入

scanf()怎么读取输入:

假设scanf()根据一个%d转换说明读取一个整数。scanf()函数每次读取一个字符,跳过所有的空白字符,直至遇到第1个非空白字符才开始读取。因为要读取整数,所以scanf()希望发现一个数字字符或者一个符号(+或-)。如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。scanf()不断地读取和保存字符,直至遇到非数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后,scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。
最后,scanf()y计算已读取数字(可能还有符号)相应的数值,并将计算后的值放入指定的变量中。

如果使用字段宽度,scanf()会在字段结尾或第1个空白字符处停止读取(两个条件满足一个便停止)。

如果第1个非空白字符是A而不是数字,scanf()将停在那里,并把A放回输入中,不会把值赋给指定变量。程序在下一次读取输入时,首先读到的是字符是A。如果程序只使用%d转换说明,scanf()就一直无法越过A读下一个字符。另外,如果使用带多个转换说明的scanf(),C规定在第1个出错处停止读取输入。

其他数值匹配的转换说明读取输入和用%d的情况相同,区别在于scanf()会把更多字符识别成数字的一部分。
4.4.2 格式字符串的普通字符

scanf()函数允许把普通字符放在格式字符串中,除空格字符外的普通字符必须与输入字符串严格匹配。如:

scanf("%d,%d", &n,&m)
用户输入时必须这样进行输入两个整数:
88,121
由于格式字符串中,%d后面紧跟逗号,所以必须在输入88后面再输入一个逗号,但由于scanf()会跳过整数前面的空白,所以下面的输入都可以:
88, 121

88,
121

除了%c其他转换说明都会自动跳过待输入值前面所有的空白。因此,scanf("%d%d",&n, &m)scanf("%d %d",&n, &m)的行为相同。对于%c在格式字符串中添加一个空格字符会有所不同。scanf("%c", %ch)从输入中的第1个字符开始读取,而scanf(" %c", %ch)则从第1个非空白字符开始读取。

4.4.3 scanf()的返回值

scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测到“文件结尾”时,会返回EOF(通常#define指令把EOF定义为-1),

4.5 printf()和scanf()的*修饰符

printf()和scanf()都可以使用*修饰符来修改转换说明的含义。

printf()中的*修饰符

如果你不想预先指定字段宽度,希望通过程序指定,那么可以用*修饰符代替字段宽度。但还是要用一个参数告诉函数,字段宽度应该是多少。也就是说,如果转换说明是%d,那么参数列表中应包含*d对应的值(也可用于浮点值指定精度和字段宽度)。

[root@hans ~]# cat varwid.c 
#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;
}
[root@hans ~]# ./varwid 
Enter a field width:
6
The number is :   256
Now enter a width and a precision:
8 3
Weight =  242.500
Done!

scanf()中*的用法

*放在%和转换字符之间时,会使得scanf()跳过相应的输出项。

[root@hans ~]# cat skip2.c 
#include <stdio.h>
int main(void){
   int n;

   printf("Please enter three integers:\n");
   scanf("%*d %*d %d", &n);
   printf("The last integer was %d\n", n);

    return 0;
}
[root@hans ~]# ./skip2 
Please enter three integers:
2013 2014 2015
The last integer was 2015

//在程序需要读取文件中特定列的内容时,这项跳过功能很有用。
4.5.1 printf()的用法提示

想把数据打印成列,指定固定字段宽度很有用。因为默认的字段宽度是待打印数字的宽度。

printf("%d %d %d\n", val1, val2, val3);打印3次出来的结果是:
12 234 1222
5 0 23
22334 2322 10001

printf("%9d %9d %9d\n", val1, val2, val3);打印3次出来的结果是:
       12       234      1222
        5         0        23
    22334      2322     10001

在文字中嵌入一个数字,通常指定一个小于或等于该数字宽度的字段会比较方便。

float distance = 10.22
printf("Count Beppo ran %.2f miles in 3 hours.\n", distance);

Count Beppo ran 10.22 miles in 3 hours.

如果转换说明改为%10.2f:
float distance = 10.22
printf("Count Beppo ran %10.2f miles in 3 hours.\n", distance);

Count Beppo ran      10.22 miles in 3 hours.

本地化设置

美国和世界上的许多地区都使用一个点来分隔十进制值的整数部分和小数部分。printf()和scanf()都没有提供逗号的转换说明,C语言考虑了这种情况。C程序可以选择特定的本地化设置。

C 标准有两个本地化设置:"C"和“”(空字符串)。默认情况下,程序使用"C"本地化设置,而“ ”本地化设置可以替换当前系统中使用的本地语言环境

5 关键概念

C语言用char类型表示单个字符,用字符串表示字符序列。字符常量是一种字符形式,即用双引号把字符括起来。可以把字符串储存在字符数组中,字符串无论是表示成字符常量还是储存在字符数组中都以一个叫做空字符的隐藏字符结尾。

在程序中,最好用#define定义数值常量,用const关键字声明的变量为只读变量。

C语言的标准输入函数(scanf())和标准输出函数(printf())都使用一种系统。在该系统中第1个参数中的转换说明必须与后续参数中的值相匹配。

空白字符(制表符、空格和换行符)在scanf()处理输入时起着至关秣的作用,除了%c模式(读取下一个字符),scanf()在读取输入时会跳过非空白字符前的所有空白字符。然后一直读取字符。直到遇到空白字符或与正在读取字符不匹配的字符。

posted on 2021-12-19 10:14  Hans_Wang  阅读(409)  评论(0编辑  收藏  举报

回到顶部