C语言学习笔记---3.字符串格式化输入输出
阅读原文时间:2022年04月17日阅读:1

1.C语言字符串

字符串(character string)是一个或多个字符的序列,例如:"Zing went the strings of my heart!"

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

注意图4.1中数组末尾位置的字符\0这是空字符(null character),C语言用它标记字符串的结束。空字符不是数字0,它是非打印字符,其ASCII码值是(或等价于)0。C中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。一个40个储存单元的字符串,只能储存39个字符,剩下一个字节留给空字符。

字符和字符串

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

2.C语言格式化输入输出

2.1 printf函数

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

printf()的转换说明修饰符

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

如果要插入多个字符,其书写顺序应该与下表相同

1.使用修饰符和标记的示例

#include
#define PAGES 959
int main(void)
{
printf("*%d*\n", PAGES);
printf("*%2d*\n", PAGES);
printf("*%10d*\n", PAGES);
return 0;
printf("*%-10d*\n", PAGES);
}

结果:

*959*
*959*
*   959*
*959   *

第一个与默认情况相同;第二个转换说明是%2d输出结果应该是两个字段,因为待打印的整数有3位数字,字段宽度自动扩大为3位;

第三个转换说明是%10d,对于输出宽度为10位,即在输出的3位数字前加7个空格;

第四个转换说明为%-10d,输出结果也是10个空格,-标记数字位于字段左侧。

浮点型修饰符示例

示例1:

#include
int main(void)
{
const double RENT = 3852.99; // const变量
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;
}

结果:

*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
* 3852.990*
* 3.853E+03*
*+3852.99*
*0003852.99*

示例2:

#include
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;
}

输出

1f 1F 0x1f
**42** 42**-42**
**  6** 006**00006** 006**

示例3:

#include
#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);
return 0;
}

结果:

[Authentic imitation!]
[  Authentic imitation!]
[          Authe]
[Authe          ]

示例1:

第1个转换说明是%f。在这种情况下,字段宽度和小数点后面的位数均为系统默认设置,即字段宽度是容纳带打印数字所需的位数和小数点后打印6位数字。

第2个转换说明是%e。默认情况下,编译器在小数点的左侧打印1个数字,在小数点的右侧打印6个数字。这样打印的数字太多!解决方案是指定小数点右侧显示的位数,程序中接下来的 4 个例子就是这样做的。请注意,第4个和第6个例子对输出结果进行了四舍五入。另外,第6个例子用E代替了e。

第7个转换说明中包含了+标记,这使得打印的值前面多了一个代数符号(+)。0标记使得打印的值前面以0填充以满足字段要求。注意,转换说明%010.2f的第1个0是标记,句点(.)之前、标记之后的数字(本例为10)是指定的字段宽度。

示例2:

第1行输出中,1f是十六进制数,等于十进制数31。第1行printf()语句中,根据%x打印出1f,%F打印出1F,%#x打印出0x1f。

第 2 行输出演示了如何在转换说明中用空格在输出的正值前面生成前导空格,负值前面不产生前导空格。这样的输出结果比较美观,因为打印出来的正值和负值在相同字段宽度下的有效数字位数相同。

第3行输出演示了如何在整型格式中使用精度(%5.3d)生成足够的前导0以满足最小位数的要求(本例是3)。然而,使用0标记会使得编译器用前导0填充满整个字段宽度。最后,如果0标记和精度一起出现,0标记会被忽略。

示例3:

注意,虽然第1个转换说明是%2s,但是字段被扩大为可容纳字符串中的所有字符。还需注意,精度限制了待打印字符的个数。.5告诉printf()只打印5个字符。另外,-标记使得文本左对齐输出。

printf()函数返回值

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

打印较长的字符串

在字符串中,可以使用\n来表示换行字符,但是不能通过按下Enter(或Return)键产生实际的换行符。

给字符串断行有3种方法

方法1:

使用多个printf()语句。因为第1个字符串没有以\n字符结束,所以第2个字符串紧跟第1个字符串末尾输出。

方法2:

用反斜杠(\)和Enter(或Return)键组合来断行。

方法3:

ANSI C引入的字符串连接。

示例:

#include
int main(void)
{
printf("Here's one way to print a ");
printf("long string.\n");
printf("Here's another way to print a \
long string.\n");
printf("Here's the newest way to print a "
"long string.\n");  /* ANSI C */
return 0;
}

输出:

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.

2.2 scanf函数

scanf()把输入的字符串转换成整数、浮点数、字符或字符串,使用指向变量的指针作为参数列表

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

示例:

#include
int main(void)
{
int age;      // 变量
float assets;   // 变量
char pet[30];   // 字符数组,用于储存字符串
printf("Enter your age, assets, and favorite pet.\n");
scanf("%d %f", &age, &assets); // 这里要使用&
scanf("%s", pet);        // 字符数组不使用&
printf("%d $%.2f %s\n", age, assets, pet);
return 0;
}

交互结果:

Enter your age, assets, and favorite pet.
38
92360.88 llama
38 $92360.88 llama

scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白。
scanf转换说明如下表:

scanf()函数修饰符:

示例:

#include
#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;
}

输出:

What's your name? Angela Plains
Hello, Angela.You are an extraordinary being.

在上述示例中scanf()只读取了Angela Plains中的Angela,它在遇到第1个空白(空格、制表符或换行符)时就不再读取输入。一般而言,根据%s转换说明,scanf()只会读取字符串中的一个单词,而不是一整句。

如果使用带多个转换说明的scanf(),C规定在第1个出错处停止读取输入。

格式字符串中的普通字符

scanf()函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。例如,假设在两个转换说明中添加一个逗号:

scanf("%d,%d", &n, &m);

用户必须像下面这样进行输入两个整数:

88,121

除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。因此,scanf("%d%d", &n, &m)与scanf("%d %d", &n, &m)的行为相同。

对于%c,在格式字符串中添加一个空格字符会有所不同。例如,如果把%c放在格式字符串中的空格前面,scanf()便会跳过空格,从第1个非空白字符开始读取。也就是说,scanf("%c", &ch)从输入中的第1个字符开始读取,而

scanf(" %c", &ch)则从第1个非空白字符开始读取。

scanf()的返回值

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

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

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

printf()的*修饰符:

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

示例:

#include
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()跳过相应的输出项.

2.3 strlen()函数

strlen()函数给出字符串中的字符长度

strlen()和sizeof 区别:

示例:

#include
#include   /* 提供strlen()函数的原型 */
181
#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;
}

结果:

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个存储单元。但是,只有前11个单元用来储存Serendipity,所以strlen()得出的结果是11。name数组的第12个单元储存空字符,strlen()并未将其计入。图4.4演示了这个概念。

对于 PRAISE,用 strlen()得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并未明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。
%zd转换说明对strlen()和sizeof都适用。
sizeof使用对象是类型时必须加括号,是特点量时可以加括号也可以不加
例如:

sizeof name;
sizeof 6.28;
sizeof(char);
sizeof(int);

3.常量和预处理

C语言预处理格式:

#define NAME value

用大写表示符号常量是 C 语言一贯的传统。这样,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。大写常量只是为了提高程序的可读性

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

#define BEEP '\a'
#define TEE 'T'
#define ESC '\033'
#define OOPS "Now you have done it!"

const限定符:

C90标准新增了const关键字,用于限定一个变量为只读。

const int MONTHS = 12;

明示常量:

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

limits.h中的明示常量:

float.h中的一些明示常量: