02. Linux嵌入式系统学习笔记(二)(编写C程序进行文件操作、模块化编程makefile的使用)
目录
一.使用系统低级文件读写访问接口进行文件的读写练习。
1. 创建 test.txt 文件
2. 编写 modify.c 程序
(0). 头文件
(1). open函数
(2). read函数
(3). write函数
(4). close函数
(5). rename函数
3. 编译并运行程序
二. 编写一个计算 5+(9/3)的程序
1. 创建目录并进入
2. 创建头文件 myhead.h
3. 创建加法实现文件 myadd.c
4. 创建除法实现文件 mydiv.c
5. 创建主程序文件 result.c
6. 创建 Makefile 文件
7. 项目运行
一.使用系统低级文件读写访问接口进行文件的读写练习。
要求:
1. 创建一个文件 test.txt,文件内容为:
2. 编写一个 c 程序 modify.c,读写这个文件,修改其内容,添加一行,将文件内容变成。
提示:可以把正确的内容写入一个临时文件,然后把临时文件重命名。
1. 创建 test.txt
文件
首先,创建一个名为 test.txt
的文件,并写入以下内容:
echo -e "1\n2\n3\n5" > test.txt
2. 编写 modify.c
程序
接下来,编写一个C程序 modify.c
,该程序将读取 test.txt
文件的内容,修改内容,并将修改后的内容写入一个临时文件,最后将临时文件重命名为 test.txt
。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define TEMP_FILE "temp.txt"
#define BUF_SIZE 1024
int main()
{
int fd_input, fd_output;
char buffer[BUF_SIZE];
ssize_t bytes_read, bytes_written;
char *target_char = "3"; // 要查找的字符
// 打开原始文件
fd_input = open("test.txt", O_RDONLY); // 只读模式打开
if(fd_input == 1)
{
perror("Error opening test.txt\n");
return 1;
}
// 创建临时文件
fd_output = open(TEMP_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);// 只写模式打开文件|创建文件并清空文件
if(fd_output == -1)
{
perror("Error creating temp file\n");
close(fd_input);
return 1;
}
// 读取原始文件内容并写入临时文件
bytes_read = read(fd_input,buffer, sizeof(buffer));
if(bytes_read == -1)
{
perror("Error reading from test.txt\n");
close(fd_input);
close(fd_output);
return 1;
}
// 在‘5’前面添加‘4’
for(int i = bytes_read - 1;i >= 0; i--)
{
buffer[i + 3] = buffer[i];
if(buffer[i] == '5')
{
buffer[i] = '4';
buffer[i+1] = '\n';
buffer[i+2] = '\n';
break;
}
}
buffer[strlen(buffer)] = '\0'; // 添加字符串结束符
// 将原文件内容拷贝到临时文件中
bytes_written = write(fd_output, buffer, strlen(buffer));
// 关闭文件描述符
close(fd_input);
close(fd_output);
// 重命名临时文件名为源文件名
if(rename(TEMP_FILE, "test1.txt") == -1)
{
perror("Error renaming temp file\n");
return 1;
}
printf("File modified successfully.\n");
return 0;
}
(0). 头文件
头文件 | 功能描述 | 在本程序中的作用 |
---|---|---|
<stdio.h> | 提供标准输入输出函数,如 printf 和 perror 。 | 使用 perror 打印错误信息。 |
<stdlib.h> | 提供通用工具函数,如内存管理(malloc、free)和程序控制(exit)。 | 未直接使用,但通常包含在C程序中。 |
<fcntl.h> | 提供文件控制选项的常量定义,如
| 定义 open 系统调用中使用的标志。 |
<unistd.h> | 提供POSIX操作系统API,如
| 使用这些系统调用来操作文件。 |
<string.h> | 提供字符串操作函数,如
| 使用 strlen 计算要写入的新行 "4\n" 的长度。 |
(1). open函数
功能:打开或创建一个文件,并返回文件描述符 | |
函数原型:#include <fcntl.h | |
参数 | |
| 要打开或创建的文件路径。 |
打开文件的标志,指定文件的访问模式和行为。常用标志: |
|
| 创建文件时指定文件的权限(仅在 O_CREAT 标志时有效)。权限通常用八进制表示,如 0644 。 |
返回值: | 成功:返回文件描述符(一个非负整数)。 失败:返回 |
(2). read函数
功能:从文件描述符对应的文件中读取数据。 | |
函数原型:#include <unistd.h> | |
参数 | |
| 文件描述符。 |
| 存储读取数据的缓冲区。 |
| 要读取的字节数。 |
返回值: | 成功:返回实际读取的字节数(可能小于 失败:返回 文件结束:返回 |
(3). write函数
功能:向文件描述符对应的文件中写入数据。 | |
函数原型:#include <unistd.h> | |
参数 | |
| 文件描述符。 |
| 存储读取数据的缓冲区。 |
| 要读取的字节数。 |
返回值: | 成功:返回实际写入的字节数(可能小于 失败:返回 |
(4). lseek函数 (调整文件的偏移量-文件指针的位置)
功能:调整文件的偏移量。 | |
函数原型:#include <unistd.h> | |
参数 | |
| 文件描述符。lseek调用后,文件描述符随之而改变 |
offset | 偏移量。 |
whence :基准位置,可以是 |
|
返回值: | 成功:返回新的偏移量。 失败:返回 |
注意:O_APPEND 标志:如果文件是以 O_APPEND 标志打开的,write 会始终将数据追加到文件末尾,忽略 lseek 设置的偏移量。 |
lseek的使用
场景 | 说明 |
---|---|
配合 read 使用 | 调整文件的读取位置。 |
配合 write 使用 | 调整文件的写入位置。 |
获取当前偏移量 | 使用 lseek(fd, 0, SEEK_CUR) 。 |
获取文件大小 | 使用 lseek(fd, 0, SEEK_END)。 |
(4). close函数
功能:关闭文件描述符,释放相关资源。 | |
函数原型:#include <unistd.h> | |
参数 | |
| 要关闭的文件描述符。 |
返回值: | 成功:返回 失败:返回 |
(5).
rename函数
功能:打开或创建一个文件,并返回文件描述符 | |
函数原型:#include <stdio.h> | |
参数 | |
| 原文件或目录的路径。 |
| 新文件或目录的路径。 |
返回值: | 成功:返回 失败:返回 |
3. 编译并运行程序
编译 modify.c
并运行它:
gcc modify.c -o modify
./modify
二. 编写一个计算 5+(9/3)的程序
要求:分成 4 个文件,一个头文件 myhead.h,一个进行加法运算的 myadd.c 代码文件、一个进行除法的文件 mydiv.c 和一个reslut.c 文件。编写一个 Makefile,使它们在 make 工具下生成可执行文件myresult。整个项目文件都放在一个目录 cal 下面
1. 创建目录并进入
mkdir -p cal && cd cal
mkdir -p
:
mkdir
:创建目录的命令(make directory)
-p
参数:自动创建父目录(如果父目录不存在),但在此例中cal
是直接创建的,所以-p
的作用是防止因目录已存在而报错。
&&
:逻辑运算符,表示前一条命令成功执行后,再执行后面的命令。
cd cal
:进入新创建的cal
目录。
2. 创建头文件 myhead.h
#ifndef MYHEAD_H
#define MYHEAD_H
int myadd(int a, int b);
int mydiv(int a, int b);
#endif
作用:
头文件保护:
#ifndef
和#endif
防止同一个头文件被多次包含(避免重复定义错误)。声明函数:告诉编译器
myadd
和mydiv
的存在,具体实现在.c
文件中。
3. 创建加法实现文件 myadd.c
int myadd(int a, int b) {
return a + b;
}
关键点:
函数名和参数必须与头文件
myhead.h
中的声明完全一致。
4. 创建除法实现文件 mydiv.c
int mydiv(int a, int b) {
return a / b;
}
注意:
如果
b=0
会导致除零错误,但此例中b=3
是安全的。
5. 创建主程序文件 result.c
#include <stdio.h>
#include "myhead.h"
int main() {
int a = 5;
int b = mydiv(9, 3);
int result = myadd(a, b);
printf("Result: %d\n", result);
return 0;
}
6. 创建 Makefile
文件
CC = gcc
CFLAGS = -I.
all: myresult
myresult: myadd.o mydiv.o result.o
$(CC) -o $@ $^ $(CFLAGS)
myadd.o: myadd.c myhead.h
$(CC) -c $< $(CFLAGS)
mydiv.o: mydiv.c myhead.h
$(CC) -c $< $(CFLAGS)
result.o: result.c myhead.h
$(CC) -c $< $(CFLAGS)
clean:
rm -f *.o myresult
Makefile 关键语法:
变量:
CC
和CFLAGS
是自定义变量,简化命令的重复输入。目标依赖:
myresult: myadd.o mydiv.o result.o
表示生成myresult
需要依赖三个.o
文件。如果某个
.o
文件不存在或对应的.c
文件被修改,Makefile 会自动重新编译。自动变量:
$@
:当前目标名(如myresult
)。
$^
:所有依赖文件(如myadd.o mydiv.o result.o
)。
$<
:第一个依赖文件(如myadd.c
)。
clean
规则:用于清理生成的文件,需手动执行make clean
。
-I.
的作用是什么?
告诉编译器在当前目录(.
)查找头文件myhead.h
。Makefile 中的
-c
参数:
表示只编译生成目标文件(.o
),不进行链接。为什么要用头文件?
头文件声明函数,实现代码分离,方便多人协作和模块化管理。为什么用
make
而不是手动编译?
自动化编译流程,避免重复输入命令。
只重新编译修改过的文件,提高效率。
7. 项目运行
(1)项目结构:
cal/
├── myhead.h
├── myadd.c
├── mydiv.c
├── result.c
└── Makefile
(2)编译运行:
make
./myresult
(3)输出结果:
Result: 8
(4)删除 .o 文件和 可执行文件
make clean # 执行 Makefile 中的 clean 规则