C++ (week4):Linux系统编程1:文件

一、文件:Linux文件操作

1.基于文件指针的文件操作

跳转链接:https://blog.csdn.net/Edward1027/article/details/138673572


2.Linux目录操作

(1)目录路径
0.error

NAME: glibc error reporting functions
SYNOPSIS:

#include <error.h>
#include <errno.h>
void error(int status, int errnum, const char *format, ...)

①第一个参数:为0不退出程序;非0退出程序,相当于exit(1)。
②第二个参数:为0,则不设置errno。若非0,则设置errno。
③第三个参数:相当于perror(" ")

举例:

//Window下的错误处理
perror("getcwd");
exit(1);

//linux下的错误处理
error(1, errno, "getcwd");    //非0退出,不需要换行符,会自动添加\n
error(1, 0, "prefix:");  	  //不设置errno

1.getcwd

(1)cwd:current working directory
(2)作用:获取当前工作目录的绝对路径
(3)参数
(4)返回值:成功 、失败
(5)特例:getcwd(NULL,0)

查阅man手册:name、synopsis、return value

笔记代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2.chdir

在这里插入图片描述

惯用法(Idiom):切换当前工作目录

if(chdir(argv[1]) == -1){
	error(1, errno, "chdir %s", argv[1]);
}

3.创建目录:mkdir

在这里插入图片描述
惯用法(Idiom):

if(mkdir(argv[1]) == -1){
	error(1, errno, "mkdir %s", argv[1]);
}
mode_t mode;
int err = mkdir(argv[1], mode);
if(err){
	error(1, errno, "mkdir %s", argv[1]);
}

如何查看mode_t究竟是什么类型?


4.删除目录:rmdir

在这里插入图片描述
惯用法(Idiom):

if(rmdir(argv[1]) == -1){
	error(1, errno, "rmdir %s", argv[1]);
}

5.删除文件:unlink(路径名)
#include <unistd.h>

int unlink(const char *pathname);

(1)参数
pathname: 要删除的文件的路径

(2)返回值
①成功,返回 0。
②失败,返回 -1,并设置 errno 以指示错误


(2)目录流 DIR*

目录项:directory entry -> dirent

0.模型

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


1.opendir:打开目录流

在这里插入图片描述


2.closedir:关闭目录流

惯用法(Idiom):

if(closedir(pdir) == -1) {
    error(1, errno, "closedir %s failed.", src);
}

在这里插入图片描述


3.readdir:从目录流中读取下一个目录项

在这里插入图片描述


目录项(directory entry, dirent) 结构体:inode编号、名字

#include <dirent.h>
#include <sys/types.h>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

工作原理:
在这里插入图片描述



练习1:使用 readdir 函数读取并打印目录中的所有文件名:

#include <stdio.h>
#include <dirent.h>

int main() {
    // 打开目录
    DIR* dir = opendir("./test1");
    if (dir == NULL) {
    	error(1, errno, "opendir %s", path);
    }

    // 读取目录中的每一项
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    // 关闭目录
    closedir(dir);

    return 0;
}


4.seekdir:可以移动目录流中的位置
5.telldir :返回目录流中现在的位置。
6.rewinddir :重置目录流,即移动到目录流的起始位置。

(3)练习:递归处理目录
①青春版tree命令

在这里插入图片描述

//youth_tree.c
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>

int directories = 0, files = 0;

void dfs_print(const char* path, int width){
    //打开目录流
    DIR* stream = opendir(path);
    if(stream == NULL){
        error(1, errno, "opendir %s failed.\n",path);
    }

    //遍历每一个目录项
    errno = 0;
    struct dirent* pdirent;
    while((pdirent = readdir(stream)) != NULL){
        char* filename = pdirent->d_name;
        //忽略.和..
        if(strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0){
            continue;
        }
            
        //打印目录项的名字
        for(int i = 0; i < width; i++){
            putchar(' ');
        }
        puts(filename);

        if(pdirent->d_type == DT_DIR){
            //拼接路径
            char subpath[128];
            sprintf(subpath, "%s/%s", path, filename);
            directories++;
            dfs_print(subpath, width + 4);
        }else{
            files++;
        }
    } //pdirent == NULL 

    //关闭目录流
    closedir(stream);
    if(stream == NULL){
        error(1, errno, "closedir failed.");
    }
}

int main(int argc, char* argv[]){
    //参数校验
    if(argc != 2){
        error(1, 0, "参数错误。正确用法: ./youth_tree  dir");    
    }

    //输出根目录名
    puts(argv[1]);
    //递归打印目录项
    dfs_print(argv[1], 4);   //缩进4格
    //打印统计信息
    printf("\n%d directories, %d files.\n",directories, files);
    
    return 0;
}

②递归复制目录

在这里插入图片描述

// copyDir.c
#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>

// 复制文件
void copyFile(const char* src, const char* dst) {
    //1.打开文件
	FILE* stream_src = fopen(src, "rb");
	if(stream_src == NULL) {
        error(1 , errno, "open src file %s failed.",src);
	}

    FILE* stream_dst = fopen(dst, "wb");
    if(!stream_dst){
        fclose(stream_src);
        error(1, errno, "open dst file %s failed.",dst);
    }

	//2.确定文件大小
	fseek(stream_src, 0, SEEK_END);
	long len = ftell(stream_src);
	char* content = (char*)malloc((len + 1) * sizeof(char));  // 1 for '\0'

	//3.读取文件
	rewind(stream_src);  //回到文件开头
	int bytes;
    while(bytes = fread(content, 1, len, stream_src) > 0){
        fwrite(content, 1, len, stream_dst);
    }
    content[bytes] = '\0';

	//4.关闭文件
	fclose(stream_src);
	fclose(stream_dst);
}

void copyDir(const char* src, const char* dst) {
    // 创建dst目录
    mkdir(dst,0777);
    // 打开src目录
    DIR* pdir = opendir(src);
    if(pdir == NULL){
        error(1, errno, "opendir %s failed.",src);
    }
    // 遍历目录流
    errno = 0;
    struct dirent* pdirent;
    while((pdirent = readdir(pdir)) != NULL) {
        // 忽略.和..
        char* filename = pdirent->d_name;
        if(strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0){
            continue;
        }
        // 如果该目录项是目录,则调用copyDir递归复制
        char subsrc[128];
        char subdst[128];
            sprintf(subsrc, "%s/%s", src, filename);
            sprintf(subdst, "%s/%s", dst, filename);
        if(pdirent->d_type == DT_DIR){
            copyDir(subsrc, subdst);
        }
        // 如果该目录项是文件,则调用copyFile复制文件
        else{
            copyFile(subsrc, subdst);
        }
    }
    // 关闭目录流
    if(closedir(pdir) == -1){
        error(1, errno, "closedir %s failed.", src);
    }
}

int main(int argc, char* argv[]) {
    //参数校验  ./copyDir src dst
     if(argc != 3){
         error(1, 0, "参数错误。正确用法:%s src dst",argv[0]);
     }
     //递归复制目录
    copyDir(argv[1], argv[2]);
    return 0;
}

③递归删除目录

在这里插入图片描述

#include <func.h>
#include <string.h>

void deleteDir(const char* path) {
	// 打开目录
    DIR* pdir = opendir(path);
    if(pdir == NULL){
        error(1, errno, "opendir %s", path);
    }
     
    errno = 0;
    struct dirent* pdirent;
    // 遍历目录流,依次删除每一个目录项
    while ((pdirent = readdir(pdir)) != NULL) {
        // 忽略.和..
        if(strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0){
            continue;
        }
        // 如果该目录项是目录,则调用deleteDir递归删除
        char subpath[1024];
        sprintf(subpath, "%s/%s", path, pdirent->d_name);  //拼接路径:上级路径/文件名
        if(pdirent->d_type == DT_DIR){
            deleteDir(subpath);
        }
        // 如果该目录项是文件,则调用unlink删除文件
        else {
            unlink(subpath);
        }
    } //pdirent == NULL

    if(errno){
        error(1, errno, "readdir");
    }
    
    // 目录为空了,可以删除该目录了
    if(rmdir(path) == -1){
        error(1, errno, "rmdir %s", path);
    }
    
    // 关闭目录流
    closedir(pdir);
    if(pdir == NULL){
        error(1, errno, "closedir failed.");
    }
}

int main(int argc, char *argv[])
{
    //参数校验
    if(argc != 2){
        error(1, 0, "参数错误。正确用法: ./rmdir_r  dir");
    }
    
    //递归删除目录
    deleteDir(argv[1]);

    return 0;
}


3.基于文件描述符的文件操作

(1)文件描述符

1.文件描述符 (file descriptor,fd) ,非负整数

2.内核用于管理文件的数据结构
①文件描述表:下标是文件描述符fd
②打开文件表:文件位置,即偏移量。有一个指针,指向vnode。
③vnode:是磁盘inode的复制

在这里插入图片描述


(2)打开文件、创建文件、关闭文件
打开文件、创建文件:open()

1.open系统调用的函数原型

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

int open(const char *pathname, int flags);				//打开文件:文件名,打开方式
int open(const char *pathname, int flags, mode_t mode);	//创建文件:文件名,打开方式,权限

(1)参数:[open:两个参数是打开文件,三个参数是创建文件]
pathname: 要打开的文件的路径。
flags: 标志位 (是32bit的位图)
打开文件的方式和行为。常见的标志包括:
O_RDONLY: 以只读方式打开文件
O_WRONLY: 以只写方式打开文件
O_RDWR: 以读写方式打开文件
O_CREAT: 如果文件不存在,则创建该文件。需要第三个参数 mode 来指定文件的权限。
O_EXCL: 与 O_CREAT 一起使用,确保调用创建文件时文件不存在。
O_TRUNC: 如果文件存在并且是只写或读写模式,打开文件时将其长度截断为 0。
O_APPEND: 以追加模式打开文件。所有写入操作都将追加到文件的末尾。
mode: 创建文件时设置的权限(当使用 O_CREAT 标志时需要)。这个参数使用八进制表示文件权限,例如 0644 表示文件所有者有读写权限,组用户和其他用户只有读取权限。
在这里插入图片描述

(2)返回值:
①执行成功,返回一个文件描述符,表示已经打开的文件
②执行失败,返回-1,并设置相应的errno

(3)举例:

int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666); //核心
if (fd == -1) {
    error(1, errno, "open %s", argv[1]);
}

// 获取了文件描述符
printf("fd = %d\n", fd);

②关闭文件:close()
int close(int fd);//fd表示文件描述词,是先前由open或creat创建文件时的返回值。

将reference count 减1,若引用计数为0,则释放数据结构。


(3)读写文件

使用read和write来读写文件,它们统称为不带缓冲的IO。读写到内核缓冲区。

读文件:read()

1.函数原型

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);//文件描述符、缓冲区(起始地址)、长度(最多读取长度)

(1)参数
①文件描述符(file descriptor):这是一个整数,用于标识已经打开的文件。
②缓冲区(buffer):这是一个指向内存位置的指针,数据将被读取到这个缓冲区中。需要定义一个缓冲区buff来接收数据。
③字节数(count):这是要读取的字节数。

(2)返回值

int n = read(fd, recvline, MAXSIZE);  //n为成功读取的字节数

①成功,返回读写的字节数目
读到文件末尾EOF,read立刻返回0
②失败,返回-1,并设置errno


写文件:write()

1.系统调用 write的函数原型

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);   //count是要写的字节数目

(1)参数
①fd: 文件描述符,表示要写入的文件。可以是一个打开的文件、管道、套接字等。
②buf: 指向要写入数据的缓冲区。
③count: 要写入的字节数。

(2)返回值
①成功时,返回实际写入的字节数(可能小于count)
读到文件末尾EOF,返回0
②失败时,返回-1,并设置errno



在这里插入图片描述

#include <func.h>

int main(int argc, char* argv[])
{
    // ./copy src dst
    if (argc != 3) {
        error(1, errno, "Usage: %s src dst", argv[0]);
    }
    // 1. 打开src和dst
    int src = open(argv[1], O_RDONLY);
    if (src == -1) {
        error(1, errno, "open %s", argv[1]);
    }
    int dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (dst == -1) {
        error(1, errno, "open %s", argv[2]);
    }
    // 2. 复制文件
    char buf[4096];
    int bytes;
    while ((bytes = read(src, buf, sizeof(buf))) > 0) {
        // 实际读了bytes个字节,所以写入bytes字节
        write(dst, buf, bytes);
    }
    // 3. 关闭文件
    close(src);
    close(dst);
    return 0;
}

补图。内核缓冲区



(4)改变文件大小:ftruncate

1.作用:
改变文件的大小:截断文件,扩大文件大小

2.头文件、参数

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

int ftruncate(int fd, off_t length);

3.返回值:
执行成功则返回0,失败返回-1

在这里插入图片描述

文件空洞,全为’\0’的页,不会分配磁盘空间


(5)内存映射:mmap

1.作用
①mmap:内存映射(memory map),零拷贝复制技术
②将文件的一部分,映射到内存
③使用场景:复制大文件

优势:标准文件操作通常需要将文件内容从磁盘读入内核缓冲区,然后再从内核缓冲区拷贝到用户缓冲区。而使用 mmap 时,文件内容直接映射到用户空间,可以直接访问,避免了多次数据拷贝,从而提高了效率

mmap 是一种内存映射文件的方法,用于将文件内容映射到内存中,从而可以通过内存地址直接访问文件内容。它在处理大文件时非常有用,因为它不需要将整个文件一次性加载到内存中,而是按需加载部分内容。这种方法提高了文件访问的效率,并且可以通过内存操作来读写文件内容。


2.函数原型

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

(1)参数
①addr:映射的起始地址
②length:映射的内存长度

③prot:内存保护标志
1> PROT_READ:页内容可以读取
2> PROT_WRITE:页内容可以写入
3> PROT_EXEC:页内容可以执行
4> PROT_NONE:页内容不能被访问

④flags:指定映射的特性
1> MAP_SHARED:共享映射,对映射区域的修改会写入到文件中,并且对映射了该文件的其他进程可见。
2> MAP_PRIVATE:私有映射,对映射区域的修改不会写入到文件中,并且对其他进程不可见。
3> MAP_ANONYMOUS:匿名映射,不涉及文件,通常与 MAP_PRIVATE 一起使用。
在这里插入图片描述

⑤fd:文件描述符,表示要映射的文件。对于匿名映射,应将其设为 -1
⑥offset:文件映射的起始偏移量,必须是页面大小的整数倍

(2)返回值
①成功,返回映射的内存地址
②失败,返回 MAP_FAILED,并设置 errno

off_t 用于表示文件偏移量,实际类型是 long 或 long long
size_t 用于表示对象大小或内存块大小,实际类型是 unsigned int 或 unsigned long


(6)文件定位:lseek

lseek,系统调用,改变文件的位置。一个系统调用,实现三个库函数。

在这里插入图片描述

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);//fd文件描述词
//whence 可以是下面三个常量的一个
//SEEK_SET 从文件头开始计算, SEEK_CUR 从当前指针开始计算, SEEK_END 从文件尾开始计算

(7)获取文件的元数据信息:fstat

1.stat、fstat函数原型

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

int stat(const char *file_name, struct stat *buf); //文件名 stat结构体指针
int fstat(int fd, struct stat *buf); //文件描述词 stat结构体指针

2.举例:

//获取图片的长度
void test1_stat(){
    struct stat file_state;
    stat("./1.png", &file_state);
    printf("file size = %ld\n", file_state.st_size);
}

void test2_fstat(){
    int fd = open("./1.png",O_RDONLY);
    struct stat st;
    fstat(fd, &st);
    printf("file size = %ld\n", st.st_size);
}

3.stat与fstat的区别:
stat 接受一个文件路径(pathname)作为参数。用于获取指定路径的文件或目录的信息。
fstat 接受一个文件描述符(fd)作为参数。用于获取已经打开的文件的信息,文件描述符是通过 open、pipe、dup、socket 等系统调用获得的。


4.stat与fstat的共同点:使用struct stat 结构体来存储文件的详细信息

struct stat {
    dev_t     st_dev;     // 文件所在设备的ID
    ino_t     st_ino;     // 文件的节点号
    mode_t    st_mode;    // 文件的类型和模式
    nlink_t   st_nlink;   // 硬链接数量
    uid_t     st_uid;     // 文件所有者的用户ID
    gid_t     st_gid;     // 文件所有者的组ID
    dev_t     st_rdev;    // 设备ID(如果是特殊文件)
    off_t     st_size;    // 文件大小(以字节为单位)
    blksize_t st_blksize; // 块大小(文件系统I/O操作的基本单位)
    blkcnt_t  st_blocks;  // 分配的块数
    time_t    st_atime;   // 最后访问时间
    time_t    st_mtime;   // 最后修改时间
    time_t    st_ctime;   // 最后状态改变时间
};

(8)文件描述符的复制:dup、dup2
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

1.系统调用:dup

2.dup:返回最小可用的文件描述符
dup2:指定新的文件描述符

3.文件描述符表,fd复制,引用计数+1

在这里插入图片描述

4.作用:实现重定向


(9)将修改页写入磁盘:fsync

1.作用
①确保数据持久性:fsync 确保所有写入的数据在调用后已经实际写入到物理存储设备中,避免数据丢失。
②同步文件状态:不仅数据本身被写入磁盘,文件的元数据(如文件大小、修改时间等)也会同步到磁盘上。

fsync 是 Linux 和 UNIX 操作系统中的一个系统调用,用于将文件的所有已修改数据刷新到磁盘上,确保数据持久化。以下是对 fsync 的详细介绍:
适用于需要保证数据安全写入磁盘的场景,如数据库、文件系统等关键应用。

2.函数原型

#include <unistd.h>
#include <fcntl.h>

int fsync(int fd);

(1)参数
fd:文件描述符,是一个打开文件的标识符。

(2)返回值
①成功,返回 0。
②失败,返回 -1,并设置errno


(10)文件描述符 vs 文件流(文件指针)

文件描述符:传输文件
文件流:对文件进行操作

补图11:02

1.fopen是库函数,open是系统调用
1.文件描述符(file descriptor)

int fd = open("file.txt", O_RDONLY);
read(fd, buffer, sizeof(buffer));
write(fd, buffer, sizeof(buffer));
close(fd);

2.文件指针(file pointer)

FILE *fp = fopen("file.txt", "r");
fread(buffer, sizeof(char), sizeof(buffer), fp);
fwrite(buffer, sizeof(char), sizeof(buffer), fp);
fclose(fp);

2.区别:文件描述符更底层,更高效。但没有缓冲机制,导致要进行多次I/O。

在这里插入图片描述

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以回答您的问题。以下是在Linux下配置C++14开发环境的步骤: 1. 安装编译器 在Linux下,我们可以使用GCC编译器来编译C++14代码。如果您的Linux系统中没有安装GCC编译器,可以使用以下命令进行安装: ```bash sudo apt-get update sudo apt-get install gcc sudo apt-get install g++ ``` 2. 配置编译器 在Linux下,我们可以使用以下命令来检查GCC编译器是否支持C++14: ```bash g++ --version ``` 如果您的GCC编译器版本较低,可以使用以下命令来更新到支持C++14的版本: ```bash sudo apt-get install software-properties-common sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install gcc-6 g++-6 ``` 3. 配置开发环境 在Linux下,我们可以使用任何文本编辑器来编写C++14代码。如果您想使用集成开发环境(IDE),可以使用以下命令来安装Code::Blocks IDE: ```bash sudo apt-get update sudo apt-get install codeblocks ``` 安装完成后,您可以打开Code::Blocks IDE并创建一个新的C++14项目。 4. 编写和编译代码 现在您已经配置好了C++14开发环境,可以开始编写和编译代码了。以下是一个简单的C++14程序示例: ```cpp #include <iostream> int main() { std::cout << "Hello, C++14!" << std::endl; return 0; } ``` 使用以下命令来编译并运行程序: ```bash g++ -std=c++14 main.cpp -o main ./main ``` 以上就是在Linux下配置C++14开发环境的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值