20、C语言输入输出与命令行参数全解析

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;

这个流程图清晰地展示了文件复制程序的步骤,包括参数检查、文件打开、数据读取和写入、错误处理以及最终的关闭文件和输出信息。通过这样的流程,可以确保程序的正确性和健壮性。

【四旋翼无人机】具备螺旋桨倾斜机构的驱动四旋翼无人机:建模控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的驱动四旋翼无人机展开研究,重点探讨其系统建模控制策略,结合Matlab代码Simulink仿真实现。文章详细分析了无人机的动力学模型,特别是引入螺旋桨倾斜机构后带来的驱动特性,使其在姿态位置控制上具备更强的机动性自由度。研究涵盖了非线性系统建模、控制器设计(如PID、MPC、非线性控制等)、仿真验证及动态响应分析,旨在提升无人机在复杂环境下的稳定性和控制精度。同时,文中提供的Matlab/Simulink资源便于读者复现实验并进一步优化控制算法。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真经验的研究生、科研人员及无人机控制系统开发工程师,尤其适合从事飞行器建模先进控制算法研究的专业人员。; 使用场景及目标:①用于驱动四旋翼无人机的动力学建模仿真平台搭建;②研究先进控制算法(如模型预测控制、非线性控制)在无人机系统中的应用;③支持科研论文复现、课程设计或毕业课题开发,推动无人机高机动控制技术的研究进展。; 阅读建议:建议读者结合文档提供的Matlab代码Simulink模型,逐步实现建模控制算法,重点关注坐标系定义、力矩分配逻辑及控制闭环的设计细节,同时可通过修改参数和添加扰动来验证系统的鲁棒性适应性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值