21、C语言中的I/O操作与浮点数处理

C语言中的I/O操作与浮点数处理

1. 无缓冲I/O的使用场景

缓冲通常会让重复操作更高效,但在某些情况下,无缓冲I/O更适用。就像扔五个冰箱,你不会把它们都塞进垃圾袋再扔掉,而是一个一个单独扔掉。

2. 使用原始I/O复制文件

若要复制文件,可使用缓冲I/O系统,但这意味着要让其选择缓冲区大小。我们可自行设置缓冲区大小,以下是使用1024字节缓冲区的原始I/O复制文件的程序:

/** 
 * Copy one file to another. 
 * 
 * Usage: 
 *     copy <from> <to> 
 */ 

#include <stdio.h> 
#include <stdbool.h> 
#include <stdlib.h> 

#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 

#ifndef O_BINARY 
#define O_BINARY 0      // Define O_BINARY if not defined. 
#endif // O_BINARY 

int main(int argc, char* argv[]) 
{ 
  if (argc != 3) { 
        fprintf(stderr, "Usage is %s <infile> <outfile>\n", 
argv[0]); 

        exit(8); 
    } 

    // The fd of the input file 
  int inFd = open(argv[1], O_RDONLY|O_BINARY); 

  if (inFd < 0) { 
        fprintf(stderr, "ERROR: Could not open %s for 
input\n", argv[1]); 
        exit(8); 
    } 

    // The fd of the output file 
  int outFd = open(argv[2], O_WRONLY|O_CREAT|O_BINARY, 
0666); 
    if (outFd < 0) { 
        fprintf(stderr, "ERROR: Could not open %s for 
writing\n", argv[2]); 
        exit(8); 
    } 

    while (true) 
    { 
        char buffer[1024];      // Buffer to read and write 
        size_t readSize;        // Size of the last read 

      readSize = read(inFd, buffer, sizeof(buffer)); 
      if (readSize < 0) { 
            fprintf(stderr, "ERROR: Read error for file 
%s\n", argv[1]); 
            exit(8); 
        } 
      if (readSize == 0) 
            break; 

      if (write(outFd, buffer, readSize) != readSize) { 
            fprintf(stderr, "ERROR: Write error for %s\n", 
argv[2]); 
            exit(8); 
        } 
    } 
  close(inFd); 
    close(outFd); 
    return (0); 
}

使用该程序时,需指定输入文件和输出文件,命令如下:

$ ./copy input-file output-file

程序执行步骤如下:
1. 检查参数数量是否正确。
2. 打开输入文件, open 函数的一般形式为 file-descriptor = open(filename, flags) O_RDONLY 表示只读, O_BINARY 表示二进制文件。
3. 检查打开输入文件是否出错。
4. 打开输出文件,使用 O_WRONLY (只写)和 O_CREAT (若文件不存在则创建)标志, 0666 参数指定文件的保护模式。
5. 进行文件复制, read 函数读取数据, write 函数写入数据。
6. 检查读取和写入是否出错。
7. 关闭文件描述符。

3. 保护模式参数说明

0666 是一个八进制数,每个数字代表不同用户组的权限,每一位代表不同的权限类型:
| 权限类型 | 数值 |
| ---- | ---- |
| 读 | 4 |
| 写 | 2 |
| 执行 | 1 |

数字顺序为 <用户>, <组>, <其他> 0666 表示用户、组和其他用户都有读写权限。若使用 0640 ,则用户有读写权限,组只有读权限,其他用户无权限。

4. 二进制模式的使用

不同操作系统使用不同字符表示行尾,文本文件在不同操作系统间不具有可移植性。例如,在Linux上使用 write(fd, "Hi\n", 3) 写入文件,文件包含三个字符 48 69 0a ;在Windows上执行相同代码,会写入四个字符 48 69 0d 0a

为避免这种转换,可使用 O_BINARY 标志。Linux不区分二进制和文本文件,没有 O_BINARY 标志,因此在代码中添加如下定义:

#ifndef O_BINARY 
#define O_BINARY 0      // Define O_BINARY if not defined. 
#endif // O_BINARY

若操作系统已定义 O_BINARY ,则 #define 不会被编译;若使用无 O_BINARY 的类Linux操作系统, O_BINARY 将被赋值为0,不产生任何影响。

5. ioctl函数

原始I/O系统还提供了 ioctl 函数用于I/O控制,其一般形式为:

result = ioctl(fd, request, parameter);

其中, fd 是文件描述符, request 是设备特定的控制请求, parameter 是请求的参数。大多数请求下,操作成功返回0,失败返回非零值。可使用 ioctl 函数弹出可移动介质、倒带或快进磁带驱动器、设置串行设备的速度和其他参数,以及设置网络设备的地址信息。

6. 浮点数的基本概念

浮点数是小数点位置可变的数,如 1.0 0.1 0.0001 1000.0 。严格来说,小数点后不一定要有数字,例如 1.0 1. 是同一个数,但小数点两边都有数字时更易读。也可使用指数表示法,如 1.0e33 表示 1.0 × 10^33

7. 浮点数类型

在C语言中,浮点数类型有 float double long double double 类型的精度和范围是 float (单精度)类型的两倍, long double 的精度和范围更大。所有浮点常量默认为 double 类型,可通过添加 F 后缀将其变为单精度 float ,添加 L 后缀变为 long double

8. 自动转换

C语言会自动进行一些转换。若表达式的一个操作数是浮点数,C会自动将另一个操作数转换为浮点数。例如:

f = 1.0 / 3;    // Bad form

这里的 3 会在除法运算前转换为 3.0 。若将浮点数赋值给整数,会将其转换为整数。

9. 浮点数的问题

浮点数存在不精确的问题,例如十进制浮点数中 1/3 0.333333 ,无论使用多少位数字,都无法精确表示。计算机使用二进制浮点数,与十进制浮点数类似,只是指数和小数部分用二进制表示。

10. 舍入误差

1 + 1 等于 2 ,但 1/3 + 1/3 不等于 2/3 。例如:

+3.333e-01    // 1/3 in our notation 
+3.333e-01    // 1/3 in our notation 
+6.666e-01

2/3 +6.667e-01 ,这就是舍入误差的例子。可通过添加保护位来减少舍入误差。

11. 精度位数

单精度浮点数( float )理论上约有6.5位精度,但实际并非总是如此。例如计算 2/3 – 1/3 – 1/3

+6.667e-01    // 2/3 
-3.333e-01    // 1/3 
-3.333e-01    // 1/3 
+0.001e-01    // Result unnormalized 
+1.000e-04    // Result normalized

结果的第一位数字是 1 ,而正确的第一位数字应该是 0

12. 特殊值

IEEE浮点数格式有一些特殊的位模式,代表特殊值:
- 无穷大 float f = 1.0 / 0.0; 结果为 INFINITY float f = -1.0 / 0.0; 结果为 -INFINITY
- 非数字(NaN) :当操作无法产生结果时会生成 NaN ,例如 float f = sqrt(-1.0);

13. 最小可表示数

在浮点数方案中,最小可表示数并非 +1.0000e-99 ,还可以更小,如 +0.1000e-99 +0.0001e-99

综上所述,原始I/O系统能让我们更好地控制I/O操作,但需要我们自己处理更多细节;浮点数在使用时存在不精确等问题,需要我们在编程时格外注意。

14. 编程问题实践

为了更好地掌握上述知识,下面给出几个编程问题及分析。

14.1 输出问候语程序

编写一个程序,接受一个参数,即运行程序的人的名字,然后输出 Hello <name> ,若未提供参数,则输出 Hello stranger 。以下是示例代码:

#include <stdio.h>

int main(int argc, char* argv[]) {
    if (argc == 2) {
        printf("Hello %s\n", argv[1]);
    } else {
        printf("Hello stranger\n");
    }
    return 0;
}

操作步骤如下:
1. 编写上述代码并保存为 hello.c 文件。
2. 编译代码: gcc hello.c -o hello
3. 运行程序:
- 提供参数: ./hello Fred ,输出 Hello Fred
- 不提供参数: ./hello ,输出 Hello stranger

14.2 模式判断程序

编写一个程序,扫描参数列表,若存在 -d 参数,则输出 Debug mode ,若 -d 缺失,则输出 Release mode 。还可以添加其他选项。示例代码如下:

#include <stdio.h>
#include <stdbool.h>

int main(int argc, char* argv[]) {
    bool debug = false;
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-d") == 0) {
            debug = true;
            break;
        }
    }
    if (debug) {
        printf("Debug mode\n");
    } else {
        printf("Release mode\n");
    }
    return 0;
}

操作步骤如下:
1. 编写代码并保存为 mode.c 文件。
2. 编译代码: gcc mode.c -o mode
3. 运行程序:
- 包含 -d 参数: ./mode -d ,输出 Debug mode
- 不包含 -d 参数: ./mode ,输出 Release mode

14.3 复制程序性能测试

测试前面提到的复制程序在不同缓冲区大小下的性能。我们可以通过修改缓冲区大小(如1、16384、17000),并使用系统自带的时间命令来测试程序运行时间。

# 测试原始缓冲区大小
time ./copy large-input-file large-output-file

# 修改缓冲区大小为1
# 修改代码中buffer数组大小为1
gcc copy.c -o copy
time ./copy large-input-file large-output-file

# 修改缓冲区大小为16384
# 修改代码中buffer数组大小为16384
gcc copy.c -o copy
time ./copy large-input-file large-output-file

# 修改缓冲区大小为17000
# 修改代码中buffer数组大小为17000
gcc copy.c -o copy
time ./copy large-input-file large-output-file

由于几乎所有磁盘以512字节块进行读写,当缓冲区大小接近512的倍数时,程序性能可能更好,因为可以减少磁盘读写次数。

14.4 使用 getopt 函数解析参数

getopt 函数可用于解析命令行参数。以下是使用 getopt 函数改写前面模式判断程序的示例:

#include <stdio.h>
#include <getopt.h>

int main(int argc, char* argv[]) {
    int opt;
    bool debug = false;
    while ((opt = getopt(argc, argv, "d")) != -1) {
        switch (opt) {
            case 'd':
                debug = true;
                break;
            default:
                fprintf(stderr, "Usage: %s [-d]\n", argv[0]);
                return 1;
        }
    }
    if (debug) {
        printf("Debug mode\n");
    } else {
        printf("Release mode\n");
    }
    return 0;
}

操作步骤如下:
1. 编写代码并保存为 getopt_mode.c 文件。
2. 编译代码: gcc getopt_mode.c -o getopt_mode
3. 运行程序:
- 包含 -d 参数: ./getopt_mode -d ,输出 Debug mode
- 不包含 -d 参数: ./getopt_mode ,输出 Release mode

15. 总结

本文主要介绍了C语言中原始I/O系统和浮点数的相关知识,总结如下:
| 类别 | 特点 | 注意事项 |
| ---- | ---- | ---- |
| 原始I/O系统 | 提供对I/O操作的最佳控制,减少操作系统的编辑和干扰 | 需要自己处理更多细节,容易出错 |
| 浮点数 | 小数点位置可变,有 float double long double 类型 | 存在不精确、舍入误差等问题,使用时需谨慎 |

在实际编程中,我们可以根据具体需求选择合适的I/O方式和数据类型。例如,当需要精确控制I/O操作时,可使用原始I/O系统;当对精度要求不高且数据处理量较小时,可使用浮点数,但要注意其可能带来的问题。

16. 流程图总结
graph TD
    A[开始] --> B{参数数量是否正确}
    B -- 是 --> C[打开输入文件]
    B -- 否 --> D[输出错误信息并退出]
    C --> E{输入文件打开是否出错}
    E -- 是 --> D
    E -- 否 --> F[打开输出文件]
    F --> G{输出文件打开是否出错}
    G -- 是 --> D
    G -- 否 --> H[进行文件复制]
    H --> I{读取是否出错}
    I -- 是 --> D
    I -- 否 --> J{是否到达文件末尾}
    J -- 是 --> K[关闭文件描述符]
    J -- 否 --> L{写入是否出错}
    L -- 是 --> D
    L -- 否 --> H
    K --> M[结束]

该流程图展示了文件复制程序的执行流程,帮助我们更好地理解程序的逻辑。

通过本文的学习,相信大家对C语言中的I/O操作和浮点数处理有了更深入的了解,在实际编程中能够更加熟练地运用这些知识。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值