C语言输入输出与命令行参数全解析
1. 字符编码与
printf
函数
在C语言的输入输出体系中,字符有不同的编码表示。例如:
| 字符 | 十进制 | 十六进制 | 八进制 |
| ---- | ---- | ---- | ---- |
| $ | 36 | 0x24 | 0044 |
| % | 37 | 0x25 | 0045 |
| & | 38 | 0x26 | 0046 |
| ’ | 39 | 0x27 | 0047 |
| ( | 40 | 0x28 | 0050 |
printf
函数是C语言输入输出系统的核心,它能帮助我们将各种类型的数据打印到控制台。不过,输出的位置并非仅限于控制台。
2. 预定义文件操作
2.1 预定义文件
程序启动时,操作系统会打开三个预定义文件:
-
stdin
:标准输入,是程序的常规输入。
-
stdout
:标准输出,用于程序的常规输出。
-
stderr
:标准错误,用于输出错误信息。
默认情况下,这些文件与控制台相连,但命令行解释器可以将它们连接到磁盘文件、管道或其他设备。
2.2
fprintf
函数
fprintf
函数用于向指定文件发送数据,示例如下:
fprintf(stdout, "Everything is OK\n");
fprintf(stderr, "ERROR: Something bad happened\n");
printf
函数实际上是
fprintf(stdout, ...)
的便捷替代。
3. 数据读取
3.1
scanf
函数问题
scanf
函数是
printf
函数的对应读取函数,例如:
// Reads two numbers (do not use this code)
scanf("%d %d", &aInteger, &anotherInteger);
需要注意的是,
scanf
函数的参数前要加
&
,因为它需要修改参数,所以参数必须通过地址传递。然而,
scanf
函数处理空白字符的方式难以预测,除非是专家,否则不建议使用。
3.2
fgets
和
sscanf
函数
推荐使用
fgets
函数从输入中读取一行数据,再用
sscanf
函数解析字符串:
fgets(line, sizeof(line), stdin); // Read a line
sscanf(line, "%d %d", &aInteger, &anotherInteger);
fgets
函数的一般形式为:
char* result = fgets(buffer, size, file);
其中,
result
是指向刚读取字符串的指针,若到达文件末尾则为
NULL
;
buffer
是存放读取行的字符数组;
file
是文件句柄,当前我们仅使用
stdin
。
sscanf
函数与
scanf
函数类似,只是第一个参数是字符串。它返回成功转换的项目数。
3.3 错误检查
在实际使用中,需要对读取操作进行错误检查:
if (fgets(line, sizeof(line), stdin) == NULL) {
fprintf(stderr, "ERROR: Expected two integers, got EOF\n");
return (ERROR);
}
if (sscanf(line, "%d %d", &aInteger, &anotherInteger) != 2) {
fprintf(stderr, "ERROR: Expected two integers.\n");
return (ERROR);
}
4. 危险的
gets
函数
gets
函数是
fgets
函数从
stdin
读取数据的简写形式:
result = gets(buffer);
但
gets
函数非常危险,不建议使用。它在计算机早期被创建,当时人们认为使用计算机的人都很聪明和诚实。然而现在,黑客会利用它进行栈溢出攻击。当前的GCC编译器也会尽力阻止使用
gets
函数,编译和链接时都会给出警告。例如编译使用
gets
的程序时:
$ gcc -Wall -Wextra -o gets gets.c
Agets.c: In function 'main':
gets.c:17:5: warning: 'gets' is deprecated [-Wdeprecated-declarations]
gets(line);
^~~~
In file included from gets.c:11:0:
/usr/include/stdio.h:577:14: note: declared here
extern char *gets (char *__s) __wur
__attribute_deprecated__;
^~~~
/tmp/cc5H1KMF.o: In function `main':
gets.c:(.text+0x1f): warning: the `gets' function is
dangerous and should not be used.
5. 文件打开与操作
5.1 打开文件
预定义文件
stdin
、
stdout
和
stderr
是文件句柄,
fopen
函数可用于创建新的文件句柄。示例代码如下:
#include <stdio.h>
int main()
{
FILE* outFile = fopen("hello.txt", "w");
if (outFile == NULL) {
fprintf(stderr, "ERROR: Unable to open 'hello.txt'\n");
exit(8);
}
if (fprintf(outFile, "Hello World!\n") <= 0) {
fprintf(stderr, "ERROR: Unable to write to 'hello.txt'\n");
exit(8);
}
if (fclose(outFile) != 0) {
fprintf(stderr, "ERROR: Unable to close 'hello.txt'\n");
exit(8);
}
return (0);
}
fopen
函数的一般形式为:
result = fopen(filename, mode);
mode
可以是以下几种:
-
r
:只读
-
w
:只写
-
r+
:读写
-
a
:追加(从文件末尾开始写)
-
b
:与其他模式结合用于二进制文件
5.2 文件名注意事项
在处理文件名时,要注意微软的命名约定使用反斜杠
\
作为分隔符。在Linux或macOS中,使用
/
作为路径分隔符,例如:
FILE* fopen("/root/file.txt", "w");
在微软系统中,直接使用
\
会有问题,需要转义:
// Right
FILE* fopen("\\root\\file.txt", "w");
6. 二进制文件操作
6.1
fread
和
fwrite
函数
C语言的输入输出系统可以通过
fread
和
fwrite
函数处理二进制文件。
fread
函数的一般形式为:
result = fread(buffer, elementSize, size, inFile);
其中,
buffer
是存放数据的缓冲区指针,
elementSize
通常为1,
size
是缓冲区大小,
inFile
是要读取的文件。函数返回读取的字节数,文件结束返回0,出现I/O错误返回负数。
fwrite
函数的结构类似:
result = fwrite(buffer, elementSize, size, inFile);
只是它用于写入数据。
6.2 文件复制示例
以下是使用
fread
和
fwrite
函数复制文件的示例代码:
/**
* Copy infile.bin to outfile.bin.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main()
{
// The input file
FILE* inFile = fopen("infile.bin", "rb");
if (inFile == NULL) {
fprintf(stderr, "ERROR: Could not open infile.bin\n");
exit(8);
}
// The output file
FILE* outFile = fopen("outfile.bin", "wb");
if (outFile == NULL) {
fprintf(stderr, "ERROR: Could not create outfile.bin\n");
exit(8);
}
char buffer[512]; // A data buffer
while (true) {
// Read data, collect size
ssize_t readSize = fread(buffer, 1, sizeof(buffer), inFile);
if (readSize < 0) {
fprintf(stderr, "ERROR: Read error seen\n");
exit(8);
}
if (readSize == 0) {
break;
}
if (fwrite(buffer, 1, readSize, outFile) != (size_t)readSize) {
fprintf(stderr, "ERROR: Write error seen\n");
exit(8);
}
}
fclose(inFile);
fclose(outFile);
return (0);
}
在这个过程中,
fread
返回
ssize_t
类型,
fwrite
返回
size_t
类型。为避免编译器警告,在比较写入字节数时需要进行类型转换。
7. 缓冲与刷新
7.1 缓冲问题
C语言的输入输出系统采用缓冲I/O,这意味着
printf
或
fwrite
操作的数据可能不会立即发送到输出设备,而是先存储在内存中,直到有足够数据以提高效率。
例如,下面的代码会出现问题:
#include <stdio.h>
int main()
{
int zero = 0;
int result;
printf("Before divide ");
result = 5 / zero;
printf("Divide done\n");
printf("Result is %d\n", result);
return (0);
}
预期输出是
Before divide Floating point exception (core dumped)
,但实际只看到
Floating point exception (core dumped)
。这是因为
printf
的数据进入了缓冲区,程序异常终止时数据还在缓冲区。
7.2 刷新操作
使用
fflush
函数可以解决这个问题,示例如下:
printf("Before divide "); fflush(stdout);
不过,不建议每次写入都刷新,因为这会降低缓冲的效率。
8. 文件关闭
使用完文件后,需要使用
fclose
函数关闭文件:
int result = fclose(file);
如果关闭成功,
result
为0,否则为非零值。
9. 命令行参数与原始I/O
9.1 命令行参数
操作系统允许用户在运行程序时提供多个命令行选项,C语言通过
argc
和
argv
两个参数将这些参数传递给
main
函数:
int main(const int argc, const char* const argv[])
argc
包含参数的数量,
argv
是一个字符串数组,存储实际的参数。例如运行
./prog first second third
时:
| 变量 | 值 |
| ---- | ---- |
| argc | 4 |
| argv[0] | ./prog |
| argv[1] | first |
| argv[2] | second |
| argv[3] | third |
以下是一个打印命令行参数的示例代码:
/**
* Echo the command line arguments.
*/
#include <stdio.h>
int main(const int argc, const char* argv[])
{
for (int i = 0; i < argc; ++i) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return (0);
}
9.2 原始I/O
C语言程序员可使用的两种主要文件I/O系统是缓冲I/O和无缓冲I/O。标准I/O系统(如
printf
)使用缓冲区,而原始I/O是无缓冲的。
为了说明两者的区别,举个清理衣柜扔电源线的例子:
-
无缓冲I/O
:
1. 拿起一根电源线。
2. 走到外面垃圾桶。
3. 扔掉它。
4. 重复500次。这种方式的吞吐量很低。
-
缓冲I/O
:
1. 将一根电源线放入垃圾袋。
2. 持续放入电源线,直到袋子装满(可装100根)。
3. 走到外面垃圾桶。
4. 扔掉袋子。
5. 重复五次。这种方式效率更高。
综上所述,C语言的输入输出系统功能强大,通过合理使用各种函数和处理命令行参数,可以实现高效、安全的程序开发。同时,要注意避免使用危险的函数,如
gets
,并正确处理文件的打开、读写和关闭操作。
10. 编程问题实践
10.1
printf
参数问题
可以尝试在
printf
语句中放入过多、过少或错误类型的参数,观察程序的行为。例如,当放入错误类型(如用
double
代替
int
)时,程序可能会输出错误的结果或产生未定义行为。
10.2 温度转换程序
编写一个程序,要求用户输入摄氏温度,并将其转换为华氏温度。转换公式为:$F = C \times 1.8 + 32$。以下是示例代码:
#include <stdio.h>
int main() {
double celsius, fahrenheit;
printf("请输入摄氏温度: ");
scanf("%lf", &celsius);
fahrenheit = celsius * 1.8 + 32;
printf("华氏温度为: %.2lf\n", fahrenheit);
return 0;
}
操作步骤如下:
1. 定义两个
double
类型的变量
celsius
和
fahrenheit
,分别用于存储摄氏温度和华氏温度。
2. 使用
printf
函数提示用户输入摄氏温度。
3. 使用
scanf
函数读取用户输入的摄氏温度。
4. 根据转换公式计算华氏温度。
5. 使用
printf
函数输出计算得到的华氏温度。
10.3 单词计数程序
编写一个程序,统计文件中的单词数量。首先需要明确“单词”的定义,这里定义为以空格、制表符或换行符分隔的连续字符序列。以下是示例代码:
#include <stdio.h>
#include <ctype.h>
#define MAX_LENGTH 1000
int main() {
FILE *file;
char line[MAX_LENGTH];
int wordCount = 0;
int inWord = 0;
file = fopen("input.txt", "r");
if (file == NULL) {
fprintf(stderr, "无法打开文件\n");
return 1;
}
while (fgets(line, MAX_LENGTH, file) != NULL) {
for (int i = 0; line[i] != '\0'; i++) {
if (isspace(line[i])) {
inWord = 0;
} else if (!inWord) {
wordCount++;
inWord = 1;
}
}
}
fclose(file);
printf("文件中的单词数量为: %d\n", wordCount);
return 0;
}
操作步骤如下:
1. 定义一个最大长度
MAX_LENGTH
,用于存储读取的行。
2. 打开指定的文件
input.txt
,如果文件打开失败,输出错误信息并终止程序。
3. 使用
fgets
函数逐行读取文件内容。
4. 遍历每行的字符,根据字符是否为空白字符判断是否进入一个新的单词。
5. 关闭文件并输出统计的单词数量。
10.4 文件比较程序
编写一个程序,逐行比较两个文件,并输出不同的行。以下是示例代码:
#include <stdio.h>
#include <string.h>
#define MAX_LENGTH 1000
int main() {
FILE *file1, *file2;
char line1[MAX_LENGTH], line2[MAX_LENGTH];
file1 = fopen("file1.txt", "r");
file2 = fopen("file2.txt", "r");
if (file1 == NULL || file2 == NULL) {
fprintf(stderr, "无法打开文件\n");
return 1;
}
int lineNumber = 1;
while (fgets(line1, MAX_LENGTH, file1) != NULL && fgets(line2, MAX_LENGTH, file2) != NULL) {
if (strcmp(line1, line2) != 0) {
printf("第 %d 行不同:\n", lineNumber);
printf("文件1: %s", line1);
printf("文件2: %s", line2);
}
lineNumber++;
}
fclose(file1);
fclose(file2);
return 0;
}
操作步骤如下:
1. 定义一个最大长度
MAX_LENGTH
,用于存储读取的行。
2. 打开两个要比较的文件
file1.txt
和
file2.txt
,如果文件打开失败,输出错误信息并终止程序。
3. 使用
fgets
函数逐行读取两个文件的内容。
4. 使用
strcmp
函数比较每行的内容,如果不同则输出该行的信息。
5. 关闭文件。
11. 高速度文件复制程序
结合命令行参数和原始I/O系统,可以编写一个高速文件复制程序。以下是示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFER_SIZE 4096
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "用法: %s <源文件> <目标文件>\n", argv[0]);
return 1;
}
int source_fd = open(argv[1], O_RDONLY);
if (source_fd == -1) {
perror("无法打开源文件");
return 1;
}
int dest_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dest_fd == -1) {
perror("无法创建目标文件");
close(source_fd);
return 1;
}
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {
if (write(dest_fd, buffer, bytes_read) != bytes_read) {
perror("写入错误");
close(source_fd);
close(dest_fd);
return 1;
}
}
if (bytes_read == -1) {
perror("读取错误");
close(source_fd);
close(dest_fd);
return 1;
}
close(source_fd);
close(dest_fd);
printf("文件复制成功\n");
return 0;
}
操作步骤如下:
1. 检查命令行参数的数量是否为3(程序名、源文件名和目标文件名),如果不是则输出使用说明并终止程序。
2. 使用
open
函数以只读模式打开源文件,如果打开失败则输出错误信息并终止程序。
3. 使用
open
函数以只写模式创建或截断目标文件,如果创建失败则关闭源文件并输出错误信息。
4. 定义一个缓冲区
buffer
,使用
read
函数从源文件读取数据。
5. 使用
write
函数将读取的数据写入目标文件,如果写入失败则输出错误信息并关闭两个文件。
6. 关闭源文件和目标文件,输出复制成功的信息。
12. 总结
C语言的输入输出系统为开发者提供了丰富的功能和灵活的操作方式。从基本的字符编码和
printf
函数,到文件的打开、读写和关闭,再到命令行参数的处理和原始I/O的应用,每个部分都有其独特的作用。
在实际编程中,需要注意以下几点:
- 避免使用危险的函数,如
gets
,以防止安全漏洞。
- 正确处理文件操作中的错误,确保程序的健壮性。
- 合理使用缓冲和刷新机制,提高程序的效率。
- 利用命令行参数和原始I/O系统,实现更高效、灵活的程序。
通过不断实践和探索这些编程问题,开发者可以更好地掌握C语言的输入输出系统,编写出高质量的程序。
下面是一个简单的流程图,展示了文件复制程序的主要流程:
graph TD;
A[开始] --> B{检查参数数量};
B -- 数量正确 --> C[打开源文件];
B -- 数量错误 --> D[输出用法说明并退出];
C -- 打开成功 --> E[打开目标文件];
C -- 打开失败 --> F[输出错误信息并退出];
E -- 打开成功 --> G[读取源文件数据];
E -- 打开失败 --> H[关闭源文件并输出错误信息];
G -- 读取成功 --> I[写入目标文件];
G -- 读取失败 --> J[关闭文件并输出错误信息];
I -- 写入成功 --> K{是否读完};
I -- 写入失败 --> J;
K -- 未读完 --> G;
K -- 读完 --> L[关闭文件];
L --> M[输出复制成功信息];
M --> N[结束];
D --> N;
F --> N;
H --> N;
J --> N;
这个流程图清晰地展示了文件复制程序的步骤,包括参数检查、文件打开、数据读取和写入、错误处理以及最终的关闭文件和输出信息。通过这样的流程,可以确保程序的正确性和健壮性。
超级会员免费看
431

被折叠的 条评论
为什么被折叠?



