第 1 章 快速上手
# 第 1 章 快速上手
# 简介 -- 程序1.1 重排字符
rearrange.c
#pragma region comment
/*
** 这个程序从标准输入中读取输入行并在标准输出中打印这些输入行,
** 每个输入行的后面一行是该行内容的一部分。
**
** 输入的笫 1 行是一串列标号,串的最后以一个负数结尾。
** 这些列标号成对出现,说明需要打印的输入行的列的范围.
** 例如, 0 3 10 12 -1 表示笫 0 列到第 3 列,第 10 列到第 12 列的内容将被打印.
*/
#pragma endregion comment
/*
#pragma region input
4 9 12 20 -1
abcdefghijklmnopqrstuvwxyz
Hello there, how are you?
I am fine, thanks.
See you!
Bye
#pragma endregion input
*/
/*
#pragma region output
Original input : abcdefghijklmnopqrstuvwxyz
Rearranged line: efghijmnopqrstu
Original input : Hello there, how are you?
Rearranged line: o ther how are
Original input : I am fine, thanks.
Rearranged line: fine,hanks.
Original input : See you!
Rearranged line: you!
Original input : Bye
Rearranged line:
#pragma endregion output
*/
#pragma region preprocessor_directives
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20 /* 所能处理的最大列号 */
#define MAX_INPUT 1000 /* 每个输入行的最大长度 */
#pragma endregion preprocessor_directives
#pragma region function_prototype
int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input, int n_columns, int const columns[]);
#pragma endregion function_prototype
#pragma region main1
int main(void)
{
#pragma endregion main1
#pragma region main2
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
#pragma endregion main2
#pragma region main3
/*
** 读取该串列标号
*/
n_columns = read_column_numbers(columns, MAX_COLS);
#pragma endregion main3
#pragma region main4
/*
** 读取、处理和打印剩余的输入行
*/
while (gets(input) != NULL)
{
printf("Original input : %s\n", input);
rearrange(output, input, n_columns, columns);
printf("Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
#pragma endregion main4
#pragma region read_column_numbers1
/*
** 读取列标号, 如果超出范围则不予理会。
*/
int read_column_numbers(int columns[], int max)
{
#pragma endregion read_column_numbers1
#pragma region read_column_numbers2
int num = 0;
int ch;
#pragma endregion read_column_numbers2
#pragma region read_column_numbers3
/*
** 取得列标号,如果所读取的数小于 0 则停止.
*/
while (num < max && scanf("%d", &columns[num]) == 1 && columns[num] >= 0)
num += 1;
#pragma endregion read_column_numbers3
#pragma region read_column_numbers4
/*
** 确认已经读取的标号为偶数个,因为它们是以成对的形式出现的。
*/
if (num % 2 != 0)
{
puts("Last column number is not paired.");
exit(EXIT_FAILURE);
}
#pragma endregion read_column_numbers4
#pragma region read_column_numbers5
/*
** 丢弃该行中包含最后一个数字的那部分内容
*/
while ((ch = getchar()) != EOF && ch != '\n')
;
#pragma endregion read_column_numbers5
#pragma region read_column_numbers6
return num;
}
#pragma endregion read_column_numbers6
/*
** 处理输入行,将指定列的字符连接在一起,输出行以 NUL 结尾。
*/
void rearrange(char *output, char const *input, int n_columns, int const columns[])
{
int col; /* columns 数组的下标 */
int output_col; /* 输出列计数器 */
int len; /* 输入行的长度 */
len = strlen(input);
output_col = 0;
/*
** 处理每对列标号
*/
for (col = 0; col < n_columns; col += 2)
{
int nchars = columns[col + 1] - columns[col] + 1;
/*
** 如果输入行结束或输出行数组已满,则结束任务
*/
if (columns[col] >= len || output_col == MAX_INPUT - 1)
break;
/*
** 如果输出行数据空间不够,只复制可又容纳的字符数
*/
if (output_col + nchars > MAX_INPUT - 1)
nchars = MAX_INPUT - output_col - 1;
/*
** 复制相关数据
*/
strncpy(output + output_col, input + columns[col], nchars);
output_col += nchars;
}
output[output_col] = '\0';
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
输入 & 输出
- 输入
4 9 12 20 -1
abcdefghijklmnopqrstuvwxyz
Hello there, how are you?
I am fine, thanks.
See you!
Bye
2
3
4
5
6
- 输出
Original input : abcdefghijklmnopqrstuvwxyz
Rearranged line: efghijmnopqrstu
Original input : Hello there, how are you?
Rearranged line: o ther how are
Original input : I am fine, thanks.
Rearranged line: fine,hanks.
Original input : See you!
Rearranged line: you!
Original input : Bye
Rearranged line:
2
3
4
5
6
7
8
9
10
# 空白和注释
空白
- 空行将程序的不同部分分隔开来;
- 制表符(tab)用于缩进语句,更好地显示程序的结构。
C是一种自由格式的语言,并没有规则要求必须怎样书写语句
- 但如果在编写程序时能够遵守一些约定可以使代码更加容易阅读和修改
rearrange.c
中的注释/* ** 这个程序从标准输入中读取输入行并在标准输出中打印这些输入行, ** 每个输入行的后面一行是该行内容的一部分。 ** ** 输入的笫 1 行是一串列标号,串的最后以一个负数结尾。 ** 这些列标号成对出现,说明需要打印的输入行的列的范围. ** 例如, 0 3 10 12 -1 表示笫 0 列到第 3 列,第 10 列到第 12 列的内容将被打印. */
1
2
3
4
5
6
7
8
注释
- 注释的目的是告诉读者程序能做些什么以及怎样做
- 注释以符号
/*
开始,以符号*/
结束 - 在C程序中,凡是可以插入空白的地方都可以插入注释
- 注释不能嵌套
提示
- 如今软件开销的最大之处并非在于编写,而是在于维护
- 在修改一段代码时所遇到的第1个问题就是要搞清楚代码的功能
- 要注意书写正确的注释,并且在修改代码时要注意注释的更新
- 注释如果不正确那还不如没有
逻辑上删除一段C代码,最好使用 #if 指令
#if 0
statements
#endif
2
3
# 预处理指令
rearrange.c
中的预处理指令#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_COLS 20 /* 所能处理的最大列号 */ #define MAX_INPUT 1000 /* 每个输入行的最大长度 */
1
2
3
4
5
- 这5行称为预处理指令(preprocessor directives)
- 它们由预处理器(preprocessor)解释
- 预处理器读入源代码,根据预处理指令对其进行修改,然后把修改过的源代码递交给编译器
# #include
指令
- 预处理器用名叫
stdio.h
的库函数头文件的内容替换第1条#include
指令语句- 其结果就仿佛是
stdio.h
的内容被逐字写到源文件的那个位置stdio.h
头文件使程序可以访问标准I/O库(Standard I/O Library)中的函数- 这组函数用于执行输入和输出
- 第2、3条指令的功能类似,只是它们所替换的头文件分别是
stdlib.h
和string.h
stdlib.h
定义了EXIT_SUCCESS
和EXIT_FAILURE
符号string.h
头文件提供了用来操纵字符串的函数
- 其结果就仿佛是
提示
- 如果有一些声明需要用于几个不同的源文件
- 可以在一个单独的文件中编写这些声明,然后用
#include
指令把这个文件包含到需要使用这些声明的源文件中 - 这样操作,便用不着在许多不同的地方进行复制,避免了在维护这些代码时出现错误的可能性
- 可以在一个单独的文件中编写这些声明,然后用
# #define
指令
- 预处理指令是
#define
把名字MAX_COLS
定义为20
,把名字MAX_INPUT
定义为1000
- 当这个名字以后出现在源文件的任何地方时,它就会被替换为定义的值
- 由于它们被定义为字面值常量,所以这些名字不能出现于有些普通变量可以出现的场合(比如赋值符的左边)
- 这些名字一般都大写,用于提醒它们并非普通的变量
# 函数原型
rearrange.c
中的函数原型int read_column_numbers(int columns[], int max); void rearrange(char *output, char const *input, int n_columns, int const columns[]);
1
2
函数原型(function prototype)告诉编译器这些以后将在源文件中定义的函数的特征
- 当这些函数被调用时,编译器就能对它们进行准确性检查
- 每个原型以一个类型名开头,表示函数返回值的类型
- 关键字
void
表示函数并不返回任何值,在其他语言里,这种无返回值的函数被称为过程(procedure)
- 关键字
- 跟在返回类型名后面的是函数的名字
- 再后面是函数期望接受的参数
- 函数原型中参数的名字并非必需
# main
函数
int main(void)
{
2
main 函数
- 每个C程序都必须有一个
main
函数,它是程序执行的起点 - 关键字
int
表示函数返回一个整型值 - 关键字
void
表示函数不接受任何参数 main
函数的函数体包括左花括号和与之相匹配的右花括号之间的任何内容
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
2
3
4
变量声明
- 声明了4个变量
- 一个整型标量,一个整型数组以及两个字符数组
- 所有
4
个变量都是main
函数的局部变量,其他函数不能根据它们的名字访问它们 - 但它们可以作为参数传递给其他函数
/*
** 读取该串列标号
*/
n_columns = read_column_numbers(columns, MAX_COLS);
2
3
4
- 这条语句调用函数
read_column_numbers
。数组columns
和MAX_COLS
所代表的常量20
作为参数传递给这个函数
所有传递给函数的参数都是按值传递的
但当数组名作为参数时就会产生按引用传递的效果
/*
** 读取、处理和打印剩余的输入行
*/
while (gets(input) != NULL)
{
printf("Original input : %s\n", input);
rearrange(output, input, n_columns, columns);
printf("Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
while 循环
- 在
C
语言中,while
循环的功能和它在其他语言中一样- 它首先测试表达式的值,如果是假的
(0)
就跳过循环体 - 如果表达式的值是真的(非
0
),就执行循环体内的代码 - 然后再重新测试表达式的值
- 它首先测试表达式的值,如果是假的
gets 函数
gets
函数从标准输入读取一行文本并把它存储于作为参数传递给它的数组中- 一行输入由一串字符组成,以一个换行符
(newline)
结尾 gets
函数丢弃换行符,并在该行的末尾存储一个NUL
字节- 一个
NUL
字节是指字节模式为全0
的字节,类似'\0'
这样的字符常量
- 一个
- 一行输入由一串字符组成,以一个换行符
gets
函数返回一个非NULL
值,表示该行已被成功读取- 当
gets
函数被调用但事实上不存在输入行时,它就返回NULL
值,表示它到达了输入的末尾(文件尾)。
- 当
字符串与字符串常量
- 字符串就是一串以NUL字节结尾的字符
- 字符串常量(string literal)就是源程序中被双引号括起来的一串字符
- 字符串常
"hello"
在内存中占据6
个字节的空间,按顺序分别是H
、e
、l
、l
、o
和NUL
- 字符串常
printf 函数
printf
函数执行格式化的输出- 格式常常以字符串常量的形式出现
- 常用
printf
格式代码
格式 | 含义 |
---|---|
%d | 以十进制形式打印一个整型值 |
%o | 以八进制形式打印一个整型值 |
%x | 以十六进制形式打印一个整型值 |
%g | 打印一个浮点值 |
%c | 打印一个字符 |
%s | 打印一个字符串 |
\n | 换行 |
# read_column_numbers
⏳
/*
** 读取列标号, 如果超出范围则不予理会。
*/
int read_column_numbers(int columns[], int max)
{
2
3
4
5
6
- 这几行构成了
read_column_numbers
函数的起始部分
这个声明和早先出现在程序中的该函数原型的参数个数和类型以及函数的返回值完全匹配
- 如果出现不匹配的情况,编译器就会报错
在函数声明的数组参数中,并未指定数组的长度
- 这种格式是正确的,因为不论调用函数的程序传递给它的数组参数的长度是多少,这个函数都将照收不误
- 它允许单个函数操纵任意长度的一维数组
- 这个特性不利的一面是函数没法知道该数组的长度
- 如果确实需要数组的长度,它的值必须作为一个单独的参数传递给函数
和绝大多数语言一样,C语言中形式参数的名字和实际参数的名字并没有什么关系
- 可以让两者相同,但这并非必须
int num = 0;
int ch;
2
- 这里声明了该函数的两个局部变量。
- 第2个变量并未初始化
- 它的初始值将是一个不可预料的值,也就是垃圾
- 第2个变量并未初始化
# 问题
#
问题 1
C是一种自由形式的语言,也就是说并没有规则规定它的外观究竟应该怎样。但本章的例子程序遵循了一定的空白使用规则。你对此有何想法?
答案
按照一定的规则使用空白可以更好显示程序的结构,使得代码更加容易阅读和修改,增强程序的可维护性
#
问题 2
把声明(如函数原型的声明)放在头文件中,并在需要时用 #include
指令把它们包含于源文件中,这种做法有什么好处?
答案
- 声明只需要编写一次
- 维护和修改声明会更加容易
- 消除了在多份拷贝中出现写法不一致的可能性
#
问题 3
使用 #define
指令给字面值常量取名有什么好处?
答案
- 如果命名得当,
#define
指令比字面值常量更能表示变量的含义 - 如果需要修改,只需修改
#define
指令对应的值即可,不会出现漏改,错改的情况
#
问题 4
依次打印一个十进制整数、字符串和浮点值,你应该在printf函数中分别使用什么格式代码?试编一例,让这些打印值以空格分隔,并在输出行的末尾添加一个换行符
答案
"%s %s %g\n"
#
问题 5
编写一条 scanf
语句,它需要读取两个整数,分别保存于 quantity
和 price
变量,然后再读取一个字符串,保存在一个名叫 department
的字符数组中
答案
scanf("%d %d %s", &quantity, &price, department);
#
问题 6
C语言并不执行数组下标的有效性检查。你觉得为什么这个明显的安全手段会从语言中省略?
答案
- 程序员可以在需要地方自行加入下标检查
- 在已经知道下标正确的前提下,没有必要再次进行检查,可以节省下标检查的开销
#
问题 7
本章描述的 rearrange
程序包含下面的语句, strcpy
函数只接受两个参数,所以它实际上所复制的字符数由第 2
个参数指定。在本程序中,如果用 strcpy
函数取代 strncpy
函数会出现什么结果?
strncpy( output + output_col,
input + columns[col], nchars );
2
答案
- 每次会复制多余的字符到
output
中, 但由于output_col
会正确更新,所以结果与使用strncpy
一致。但是复制次数增多,会导致额处的性能消耗 - 还有可能会导致复制的字符超过
output
的范围
#
问题 8
rearrange
程序包含下面的语句, 你认为这段代码可能会出现什么问题?
while( gets( input ) != NULL ) {
答案
- 输入较长时,可能会导致
input
数组的溢出 - 而且在较新的编译器中,使用
gets
会出现警告 (opens new window),最好用fgets
(opens new window) 函数替代,因为其参数需要输入数组的长度
# 编程练习
#
编程练习 1
“Hello world!”程序常常是C编程新手所编写的第1个程序。它在标准输出中打印Hello world!,并在后面添加一个换行符。当你希望摸索出如何在自己的系统中运行C编译器时,这个小程序往往是一个很好的测试例。
答案
- 代码
#include <stdio.h>
int main(void)
{
printf("hello world!\n");
return 0;
}
2
3
4
5
6
7
- 输出
hello world!
#
编程练习 2
编写一个程序,从标准输入读取几行输入。每行输入都要打印到标准输出上,前面要加上行号。在编写这个程序时要试图让程序能够处理的输入行的长度没有限制。
答案
- 代码
/*
** 读取字符串并按行输出
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ch;
int line = 0;
int at_begining = 1;
while ((ch = getchar()) != EOF)
{
// 检查是否处于行首
if (at_begining)
{
at_begining = 0;
line += 1;
printf("%d ", line);
}
// 检查是否处于行尾
if (ch == '\n')
at_begining = 1;
// 打印字符
putchar(ch);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 输入
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 输出
1 The Zen of Python, by Tim Peters
2
3 Beautiful is better than ugly.
4 Explicit is better than implicit.
5 Simple is better than complex.
6 Complex is better than complicated.
7 Flat is better than nested.
8 Sparse is better than dense.
9 Readability counts.
10 Special cases aren't special enough to break the rules.
11 Although practicality beats purity.
12 Errors should never pass silently.
13 Unless explicitly silenced.
14 In the face of ambiguity, refuse the temptation to guess.
15 There should be one-- and preferably only one --obvious way to do it.
16 Although that way may not be obvious at first unless you're Dutch.
17 Now is better than never.
18 Although never is often better than *right* now.
19 If the implementation is hard to explain, it's a bad idea.
20 If the implementation is easy to explain, it may be a good idea.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
编程练习 3
写一个程序,从标准输入读取一些字符,并把它们写到标准输出上。它同时应该计算checksum值,并写在字符的后面。
checksum
(检验和) 用一个singed char
类型的变量进行计算,它初始为-1
。当每个字符从标准输入读取时,它的值就被加到checksum
中。如果checksum
变量产出了溢出,那么这些溢出就会被忽略。当所有的字符均被写入后,程序以十进制整数的形式打印出checksum
的值,它有可能是负值。注意在checksum
后面要添加一个换行符
答案
- 代码
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ch;
signed char checksum = -1;
while ((ch = getchar()) != EOF)
{
putchar(ch);
checksum += ch;
}
printf("%d\n", checksum);
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 输入
Hello world!
- 输出
Hello world!
102
2
#
编程练习 4
编写一个程序,一行行地读取输入行,直至到达文件尾。算出每行输入行的长度,然后把最长的那行打印出来。为了简单起见,你可以假定所有的输入行均不超过1000个字符
答案
代码
#include <stdio.h>
#include <stdlib.h>
#define MAX_LEN 1001
int main(void)
{
int len;
char input[MAX_LEN];
int longest_len = -1;
char longest[MAX_LEN];
while ((gets(input) != NULL))
{
len = strlen(input);
if (longest_len < len)
{
longest_len = len;
strcpy(longest, input);
}
}
if (longest_len > 0)
puts(longest);
return EXIT_SUCCESS;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 输入
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 输出
There should be one-- and preferably only one --obvious way to do it.
#
编程练习 5
rearrange
程序中的下列语句, 当字符的列范围超出输入行的末尾时就停止复制。这条语句只有当列范围以递增顺序出现时才是正确的,但事实上并不一定如此。请修改这条语句,即使列范围不是按顺序读取时也能正确完成任务
if( columns[col] >= len ... )
break;
2
答案
- 代码(高亮的为修改的部分)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20 /* 所能处理的最大列号 */
#define MAX_INPUT 1000 /* 每个输入行的最大长度 */
int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input, int n_columns, int const columns[]);
int main(void)
{
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
/*
** 读取该串列标号
*/
n_columns = read_column_numbers(columns, MAX_COLS);
/*
** 读取、处理和打印剩余的输入行
*/
while (gets(input) != NULL)
{
printf("Original input : %s\n", input);
rearrange(output, input, n_columns, columns);
printf("Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
/*
** 读取列标号, 如果超出范围则不予理会。
*/
int read_column_numbers(int columns[], int max)
{
int num = 0;
int ch;
/*
** 取得列标号,如果所读取的数小于 0 则停止.
*/
while (num < max && scanf("%d", &columns[num]) == 1 && columns[num] >= 0)
num += 1;
/*
** 确认已经读取的标号为偶数个,因为它们是以成对的形式出现的。
*/
if (num % 2 != 0)
{
puts("Last column number is not paired.");
exit(EXIT_FAILURE);
}
/*
** 丢弃该行中包含最后一个数字的那部分内容
*/
while ((ch = getchar()) != EOF && ch != '\n')
;
return num;
}
/*
** 处理输入行,将指定列的字符连接在一起,输出行以 NUL 结尾。
*/
void rearrange(char *output, char const *input, int n_columns, int const columns[])
{
int col; /* columns 数组的下标 */
int output_col; /* 输出列计数器 */
int len; /* 输入行的长度 */
len = strlen(input);
output_col = 0;
/*
** 处理每对列标号
*/
for (col = 0; col < n_columns; col += 2)
{
int nchars = columns[col + 1] - columns[col] + 1;
/*
** 如果输入行结束,则读入下一对列标号
*/
if (columns[col] >= len)
continue;
/*
** 如果输出行数组已满,则结束任务
*/
if (output_col == MAX_INPUT - 1)
break;
/*
** 如果输出行数据空间不够,只复制可又容纳的字符数
*/
if (output_col + nchars > MAX_INPUT - 1)
nchars = MAX_INPUT - output_col - 1;
/*
** 复制相关数据
*/
strncpy(output + output_col, input + columns[col], nchars);
output_col += nchars;
}
output[output_col] = '\0';
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
- 输入
12 20 0 4 -1
abcdefghijklmnopqrstuvwxyz
Hello there, how are you?
I am fine, thanks.
See you!
Bye
2
3
4
5
6
- 输出
Original input : abcdefghijklmnopqrstuvwxyz
Rearranged line: mnopqrstuabcde
Original input : Hello there, how are you?
Rearranged line: how are Hello
Original input : I am fine, thanks.
Rearranged line: hanks.
Original input : See you!
Rearranged line: See y
Original input : Bye
Rearranged line: Bye
2
3
4
5
6
7
8
9
10
提示
- 列范围以递归的顺序出现是指
- 对于
- 需要保证
- 需要保证
- 对于
- 列范围不以递增的顺序出现是指成对的列范围
- 对于
可以小于
- 但仍需要保证两个成列的项目,是按递增的顺序出现的
- 即
,
- 即
- 对于
#
编程练习 6
修改rearrange
程序,去除输入中列标号的个数必须是偶数的限制。如果读入的列标号为奇数个,函数就会把最后一个列范围设置为最后一个列标号所指定的列到行尾之间的范围。从最后一个列标号直至行尾的所有字符都将被复制到输出字符串
答案
- 代码(高亮的为修改的部分)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20 /* 所能处理的最大列号 */
#define MAX_INPUT 1000 /* 每个输入行的最大长度 */
int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input, int n_columns, int const columns[]);
int main(void)
{
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
/*
** 读取该串列标号
*/
n_columns = read_column_numbers(columns, MAX_COLS);
/*
** 读取、处理和打印剩余的输入行
*/
while (gets(input) != NULL)
{
printf("Original input : %s\n", input);
rearrange(output, input, n_columns, columns);
printf("Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
/*
** 读取列标号, 如果超出范围则不予理会。
*/
int read_column_numbers(int columns[], int max)
{
int num = 0;
int ch;
/*
** 取得列标号,如果所读取的数小于 0 则停止.
*/
while (num < max && scanf("%d", &columns[num]) == 1 && columns[num] >= 0)
num += 1;
#if 0
/*
** 确认已经读取的标号为偶数个,因为它们是以成对的形式出现的。
*/
if (num % 2 != 0)
{
puts("Last column number is not paired.");
exit(EXIT_FAILURE);
}
#endif
/*
** 丢弃该行中包含最后一个数字的那部分内容
*/
while ((ch = getchar()) != EOF && ch != '\n')
;
return num;
}
/*
** 处理输入行,将指定列的字符连接在一起,输出行以 NUL 结尾。
*/
void rearrange(char *output, char const *input, int n_columns, int const columns[])
{
int col; /* columns 数组的下标 */
int output_col; /* 输出列计数器 */
int len; /* 输入行的长度 */
int nchars;
len = strlen(input);
output_col = 0;
/*
** 处理每对列标号
*/
for (col = 0; col < n_columns; col += 2)
{
if (col + 1 == n_columns)
nchars = len - columns[col];
else
nchars = columns[col + 1] - columns[col] + 1;
/*
** 如果输入行结束或输出行数组已满,则结束任务
*/
if (columns[col] >= len || output_col == MAX_INPUT - 1)
break;
/*
** 如果输出行数据空间不够,只复制可以容纳的字符数
*/
if (output_col + nchars > MAX_INPUT - 1)
nchars = MAX_INPUT - output_col - 1;
/*
** 复制相关数据
*/
strncpy(output + output_col, input + columns[col], nchars);
output_col += nchars;
}
output[output_col] = '\0';
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
- 输入
4 6 12 14 18 -1
abcdefghijklmnopqrstuvwxyz
Hello there, how are you?
I am fine, thanks.
See you!
Bye
2
3
4
5
6
- 输出
Original input : abcdefghijklmnopqrstuvwxyz
Rearranged line: efgmnostuvwxyz
Original input : Hello there, how are you?
Rearranged line: o t hore you?
Original input : I am fine, thanks.
Rearranged line: fihan
Original input : See you!
Rearranged line: you
Original input : Bye
Rearranged line:
Original input :
Rearranged line:
2
3
4
5
6
7
8
9
10
11
12