C语言-文件IO

news2024/10/9 2:56:37

文件IO

I :input 输入,从文件中读取数据到内存

O:output 输出,把数据写入到文件

Linux系统IO 和 c语言标准IO

1、linux系统IO

1.1 简介

linux操作系统把对文件的操作封装成了多个函数,统称为linux系统IO。

文件描述符(File descirptor) 是一个用来访问文件的抽象化概念

文件描述符是一个正整数(进程文件表项下标),一个被打开的文件对应着一个文件描述符

linux中,每个进程(暂时理解程序)都会自动打开3个文件:

功能                                                                            文件描述符           对应的宏

标准输入文件 从输入设备(鼠标,键盘等)中获取数据     0                      STDIN_FILENO

标准输出文件 输出数据到输出设备(屏幕等)                   1                      STDOUT_FILENO

标准出错文件 输出错误信息到输出设备(屏幕等)            2                      STDERR_FILENO

这个宏定义在头文件 /usr/include/unistd.h

/* Standard file descriptors. /

#define STDIN_FILENO 0 / Standard input. /

#define STDOUT_FILENO 1 / Standard output. /

#define STDERR_FILENO 2 / Standard error output. */

1.2 如何学习系统函数

ubuntu中,把所有系统函数都做了说明手册,我们需要学会去查询这个手册

man -f 标识符 //查询该标识符在手册中的所有功能简介

以 strcpy,printf为例

man 数字 标识符 //进入到对应手册

比如:

man printf //如果该标识符有多页,不加数字默认进入最前面那页

man 1 printf //进入printf手册第一页

man 3 printf //进入printf手册第三页

man strcpy

man 3 strcpy

可以安装中文手册

sudo apt install manpages-zh //安装

sudo apt remove manpages-zh //卸载

直接百度

1.3 具体函数

1.3.1 打开文件

   #include <sys/types.h>
   #include <sys/stat.h>
   #include <fcntl.h>
   //使用open函数,需要包含这三个头文件
   int open(const char *pathname, int flags);
   int open(const char *pathname, int flags, mode_t mode);
   //为什么有两个open函数,不会重复定义吗?
   //一个是用宏函数实现的,一个是普通函数
   pathname :文件的路径名(包含路径的文件名)
   flags:打开该文件的方式,有很多种
        O_RDONLY, O_WRONLY 或 O_RDWR  必选项,三选一
        (只读 read only   只写write only  读写 read write)
        以下是可选项,0项或多项,和上面的标识用 | 连接
        O_CREAT:如果文件不存在,则先创建,如果文件存在则直接打开
        O_EXCL:该标识一般和 O_CREAT配合使用,如果文件存在,则报错
        O_TRUNC:如果文件存在且是普通文件,并且有可写权限,那么打开文件时会把文件的内容清空
        O_APPEND:追加方式打开。如果没有该标志,打开文件后,读写指针(光标)在文件开头;如果有该标志
                读写指针(光标)在文件末尾。
        O_NONBLOCK 或 O_NDELAY :以非阻塞的方式打开
                阻塞(默认方式)打开的话,该文件中如果没有内容,你去read,read函数会阻塞/等待,直到文件中有内容
                非阻塞打开的话,该文件中如果没有内容,你去read,直接返回0,不会等待
                注意:  普通文件没有阻塞效果,只有特殊文件(如:设备文件,管道文件...)才有
    mode:该参数只有在第二个参数有 O_CREAT 标志时才有效
        用来指定创建文件后文件的权限,有两种指定方式:
        (1)用宏指定
            S_IRWXU
              00700 允许 文件 的 属主 读 , 写 和 执行 文件
            S_IRUSR (S_IREAD)
              00400 允许 文件 的 属主 读 文件
              .....
        (2)用八进制数字指定
            0777    // 111 111 111
            0764    // 111 110 100
            ....
            
    返回值:
        失败了返回-1,同时errno被设置
        成功返回文件描述符
    (可以打开目录,因为目录也是文件,只是只能以只读的方式打开目录)
        
 errno是系统中定义的一个全局变量,是一个整数,不同的值表示不同的系统错误,一般叫做错误码,当发生某个系统错
 误时,系统自动把errno设置为对应的错误码,并且提供了相关函数来解析这个错误码:
    perror  strerror
    
    printf("打开%s失败:%s\n","3.txt",strerror(errno));
    perror("打开失败");

1.3.2 关闭文件

#include <unistd.h>
int close(int fd);
    fd:要关闭的那个文件描述符
返回值:
    失败返回-1,同时errno被设置
    成功返回0
    
任何一个文件打开,操作完之后,必须要关闭。

1.3.3 读取文件内容

   #include <unistd.h>
   ssize_t read(int fd, void *buf, size_t count);
        从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中.
        fd:文件描述符
        buf:用来保存读取到的数据的内存首地址
        count:要读取的字节数
    返回值:
        失败返回-1,同时errno被设置
        成功返回实际读取到的字节数(返回0表示没有内容可读,一般是表示到了文件末尾了)

​ read函数进行读取,是从文件当前光标所在位置开始读的, ​ 读取成功之后,光标自动往后偏移。 ​ ​ 示例代码: ​

         01测试代码.c 中的 test1函数和test2函数

1.3.4 往文件写入数据

   #include <unistd.h>
   ssize_t write(int fd, const void *buf, size_t count);
        fd:文件描述符
        buf:要写入的数据的首地址
        count:要写入的字节数
    返回值:
        失败返回-1,同时errno被设置
        成功返回实际写入的字节数
        
    write函数进行写入,从光标位置开始写入,写入成功后,光标自动往后偏移
    
    示例代码:
        01测试代码.c 中的 test3函数
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
void test1()
{
    int fd ;
    fd = open("/mnt/hgfs/CS2415F/2阶段/1.txt",O_RDWR );
    if(fd == -1)
    {
        //printf("打开失败\n");
        printf("打开%s失败:%s\n","1.txt",strerror(errno));
        perror("打开失败");
        return;
    }

    char buf[100];//因为是文本文件,文件中的内容是字符串,所以读到的内容需要用字符数组来保存
    int r = read(fd,buf,20);//从打开的文件中读取20字节保存到buf数组中
    if(r == -1)
    {
        printf("读取失败:%s\n",strerror(errno));
        close(fd);
        return ;
    }
    buf[r] = '\0';
    printf("%s\n",buf);
    close(fd);
}

void test2()
{
    int fd;
    fd = open("/mnt/hgfs/CS2415F/交叉开发/1_gcc和gdb/code/main.o",O_RDONLY);
    if(fd == -1)
    {
        //printf("打开失败\n");
        printf("打开%s失败:%s\n","main.o",strerror(errno));
        return;
    }

    short data;//因为main.o是二进制文件,每2个字节为一个单位(整数),所以定义短整型变量来保存读取到的数据
    int r = read(fd,&data,2);
    if(r == -1)
    {
        printf("读取失败:%s\n",strerror(errno));
        close(fd);
        return ;
    }
    printf("0x%hx\n",data);
    r = read(fd,&data,2);
    printf("0x%hx\n",data);
    close(fd);
}

void test3()
{
    int fd ;
    fd = open("/mnt/hgfs/CS2415F/2阶段/1.txt",O_RDWR | O_APPEND);
    //if(fd == -1)
    if(-1 == fd)
    {
        //printf("打开失败\n");
        printf("打开%s失败:%s\n","1.txt",strerror(errno));
        perror("打开失败");
        return;
    }

    char buf[100];
    scanf("%s",buf);//从键盘输入数据
    
    //把从键盘输入的数据保存/写入到文件中
    int r = write(fd,buf,strlen(buf));
    //if(r == -1)
    if(-1 == r)
    {
        printf("写入失败:%s\n",strerror(errno));
    }

    close(fd);
}

int main()
{

    //test1();
    //test2();
    test3();
    return 0;
}

1.3.5 定位光标

   #include <sys/types.h>
   #include <unistd.h>
   off_t lseek(int fd, off_t offset, int whence);
        fd:文件描述符
        offset:偏移量,以字节为单位,正数往后偏移,负数往前偏移
        whence:偏移方式
            SEEK_SET    以文件开头位置为基准  
            SEEK_CUR    以当前光标位置为基准  
            SEEK_END    以文件末尾位置为基准  
        如:
        lseek(fd,10,SEEK_SET);//把光标定位到据文件开头10字节处
        lseek(fd,0,SEEK_SET);//把光标定位到文件开头处
        lseek(fd,0,SEEK_END);//把光标定位到文件末尾处
        lseek(fd,5,SEEK_CUR);//把光标从当前位置往后移5字节
    返回值:
        失败返回-1,同时errno被设置
        成功返回新光标距离文件开头的字节数

1.3.6 获取文件属性

这个结构体用来描述文件的属性/状态
   struct stat {
       dev_t     st_dev;         /* ID of device containing file */
                                文件的设备号
       ino_t     st_ino;         /* Inode number */
                                Inode 号
       mode_t    st_mode;        /* File type and mode */   
                                文件的类型及权限,详细介绍在下面
       nlink_t   st_nlink;       /* Number of hard links */
                                硬链接的数目
       uid_t     st_uid;         /* User ID of owner */
                                用户ID
       gid_t     st_gid;         /* Group ID of owner */
                                组用户ID
       dev_t     st_rdev;        /* Device ID (if special file) */
                                设备号
       off_t     st_size;        /* Total size, in bytes */
                                文件大小
       blksize_t st_blksize;     /* Block size for filesystem I/O */
                                块大小,一般来说一块是512字节
       blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */
                                有多少块
    struct timespec st_atim;  /* Time of last access */
                                最后访问文件的时间
    struct timespec st_mtim;  /* Time of last modification */
                                最后修改文件内容的时间
    struct timespec st_ctim;  /* Time of last status change */
                                最后修改文件状态的时间
    #define st_atime st_atim.tv_sec      /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
    };
    
    st_mode成员详细介绍
    该成员用来描述文件的类型及文件的权限
    strucr stat sb;//假设sb已经保存了某个文件的属性,那么下面代码就是用来判断该文件是什么类型的文件
    switch (sb.st_mode & S_IFMT) {
        case S_IFBLK:  printf("block device\n");            break;//块设备
        case S_IFCHR:  printf("character device\n");        break;//字符设备
        case S_IFDIR:  printf("directory\n");               break;//目录
        case S_IFIFO:  printf("FIFO/pipe\n");               break;//管道文件
        case S_IFLNK:  printf("symlink\n");                 break;//符号链接/软链接文件
        case S_IFREG:  printf("regular file\n");            break;//普通文件
        case S_IFSOCK: printf("socket\n");                  break;//套接字文件
        default:       printf("unknown?\n");                break;
    }
​
    下面的代码就是判断该文件的权限:
    if(sb.st_mode & S_IRUSR)
    {
        //条件成立,说明该文件用用户可读权限
    }
    if(sb.st_mode & S_IWUSR)
    {
        //条件成立,说明该文件用用户可写权限
    }
    ......
    
    详细代码可以参考 man 2 stat 
    
   #include <sys/types.h>
   #include <sys/stat.h>
   #include <unistd.h>
​
   int stat(const char *pathname, struct stat *statbuf);
        pathname:文件路径名
        statbuf:一个结构体变量的地址,stat函数执行成功后,该结构体就保存了文件的属性信息
    返回值:
        失败返回-1,同时errno被设置
        成功返回0
   int fstat(int fd, struct stat *statbuf);
        和 stat函数功能完全一样,区别在于第一个参数是文件描述符
   int lstat(const char *pathname, struct stat *statbuf);
        和 stat函数功能基本一样,区别在于如果 pathname 文件为软链接文件时,
        lstat获取的是软链接文件本身的属性信息
        stat获取的是软链接指向的源文件的属性信息
​
    示例代码:
        02测试代码.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
int main()
{
    //先定义 struct stat 结构体变量,用来保存文件的属性信息
    struct stat st;

    //调用 stat函数获取文件属性信息,并保存在 st中
    int r = stat("/home/china/1",&st);
    if(r == 0)
    {
        printf("文件大小:%ld\n",st.st_size);
            switch (st.st_mode & S_IFMT) {
            case S_IFBLK:  printf("block device\n");            break;//块设备
            case S_IFCHR:  printf("character device\n");        break;//字符设备
            case S_IFDIR:  printf("directory\n");               break;//目录
            case S_IFIFO:  printf("FIFO/pipe\n");               break;//管道文件
            case S_IFLNK:  printf("symlink\n");                 break;//符号链接/软链接文件
            case S_IFREG:  printf("regular file\n");            break;//普通文件
            case S_IFSOCK: printf("socket\n");                  break;//套接字文件
            default:       printf("unknown?\n");                break;
        }
        printf("最后修改文件内容的时间:%s\n",ctime(&(st.st_mtime)));
    }

    return 0;
}

1.3.7 目录操作

   (1)打开目录
   #include <sys/types.h>
   #include <dirent.h>
   DIR *opendir(const char *name);
        name:目录的路径
   DIR *fdopendir(int fd);
        fd:目录的文件描述符(先用 open 打开,返回文件描述符,再用 opendir打开)
   返回值:
        失败返回 NULL
        成功返回 DIR指针( 不需要关心DIR具体是什么,只需要知道后续对目录的操作要用到它)
    (2)读取目录
    #include <dirent.h>
    struct dirent *readdir(DIR *dirp);
        dirp:opendir的返回值
        
    目录中的内容(包括文件和子目录)有多少项是不固定的,readdir每次只读一项,需要循环调用readdir进行读取,
    直到返回NULL(表示读取完毕)
        
    返回值:
        成功返回 struct dirent 结构体指针
     struct dirent {
         ino_t          d_ino;       /* Inode number */
         off_t          d_off;       /* Not an offset; see below */
         unsigned short d_reclen;    /* Length of this record */
         unsigned char  d_type;      /* Type of file; not supported
         by all filesystem types */
         char           d_name[256]; /* Null-terminated filename */
     };
        其中最重要的就是 d_name,就是读取到的 子目录名字或者文件名
        
    The  only  fields  in the dirent structure that are mandated by POSIX.1
    are d_name and d_ino.  The other fields  are  unstandardized,  and  not
    present on all systems; see NOTES below for some further details.
    只有 d_name 和 d_ino 是所有linux系统中都支持的成员,其他成员并不是所有系统中都存在。
    为了代码有更好的兼容性,建议尽量不要使用其他成员。
        
    (3)关闭目录
    #include <sys/types.h>
    #include <dirent.h>
    int closedir(DIR *dirp);
    
    (4)创建目录
    int mkdir(const char *pathname, mode_t mode);
    
    示例代码:
        04目录操作.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
//name:目录的路径名
int get_dir_num(const char *name)
{
    DIR * pdir = opendir(name);
    if(NULL == pdir)
    {
        printf("打开%s目录失败,%s\n",name,strerror(errno));
        return -1;
    }
    int count = 0;
    struct dirent * p;
    struct stat st;
    int r;
    char pathname[100];
    while(1)
    {
        p = readdir(pdir);
        if(NULL == p)//读取完毕,跳出循环
        {
            break;
        }
        //printf("%s\n",p->d_name);//可以发现,包含两个特殊目录:  .  ..
        if(strcmp(p->d_name,".")==0 || strcmp(p->d_name,"..")==0)
            continue;
        //if(p->d_type == DT_REG)
            //count++;
        strcpy(pathname,name);
        strcat(pathname,"/");
        strcat(pathname,p->d_name);
        //printf("%s\n",pathname);
        r = stat(pathname,&st);
        if(-1 == r)
        {
            perror("");
        }
        if((st.st_mode & S_IFMT) == S_IFREG)
        {
            count++;
        }
        else if((st.st_mode & S_IFMT) == S_IFDIR)//是子目录
        {
            count += get_dir_num(pathname);
        }
    }
    
    closedir(pdir);
    return count;
}

int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        printf("输入有误,需要一个参数,指定目录\n");
        return -1;
    }
    
    int n = get_dir_num(argv[1]);
    //...
    //send(......);
    //write(......);
    printf("有%d个普通文件\n",n);
    return 0;
}

1.3.8 其他函数

	truncate
	int truncate(const char *path, off_t length);
		缩短/扩大 path文件 至 指定 length 长度
	unlink
	int unlink(const char *pathname);
		删除指定文件
	mkdir 创建目录
	rmdir 删除空目录
	
	remove	删除普通文件或者目录
	
	chdir	改变工作路径,工作路径默认是运行程序的那个路径,程序中的相对路径都是相对工作路径而言的
	int chdir(const char *path);

2、c语言标准IO

2.1 简介

系统IO是操作系统提供的函数,不同的操作系统提供的函数不一样。

而C语言标准IO是c语言标准库提供的,只要你用c语言进行开发,不管在什么系统中都可以使用。

c语言标准IO,用struct FILE类型来表示一个被打开的文件

三个特殊的 FILE 指针

/* Standard streams.  */
extern struct FILE  *stdin;		/* Standard input stream.  */
extern struct FILE  *stdout;		/* Standard output stream.  */
extern struct FILE  *stderr;		/* Standard error output stream.  */

stream 流
IO流  文件流		有缓冲区

参考 05测试代码.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void test1()
{
    int fd = open("../1.txt",O_WRONLY);
    if(-1 == fd)
    {
        perror("");
        return ;
    }

    int i;
    for(i=0;i<5;i++)
    {
        write(fd,"1\n",2);
        sleep(2);
    }

    close(fd);
}

void test2()
{
    FILE * fp = fopen("../1.txt","w");
    //...
    if(NULL == fp)
    {
        perror("");
        return ;
    }

    int i;
    for(i=0;i<5;i++)
    {
        fwrite("1\n",1,2,fp);
        sleep(2);
    }

    fclose(fp);
}

int main()
{

    //test1();
    test2();
    return 0;
}

标准IO中在合适的时机把缓冲区中的数据写入到文件中,合适的时机??

全缓冲:当缓冲区满了之后或者程序正常结束或者关闭文件才会写入到文件中。比如 fwrite

行缓冲:遇到‘\n’或者程序正常结束才会写入到文件中 ,比如 printf

注意,用 printf调试段错误时一定要换行

无缓冲:没有缓存直接输出,比如 stderr文件流, perror函数

2.2 文件操作相关函数

2.2.1 打开文件

   #include <stdio.h>
   FILE *fopen(const char *path, const char *mode);
   		path :要打开的文件的路径名
   		mode :打开方式,字符串
            r      打开文本文件,用于读。流被定位于文件的开始。
            r+     打开文本文件,用于读写。流被定位于文件的开始。
            w      将文件长度截短为零(清空),或者创建文本文件,用于写。流被定位于文件的开始。
            w+     打开文件,用于读写。如果文件不存在就创建它,否则将清空它。流被定
                  	位于文件的开始。
            a      打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。流被定
                  位于文件的末尾。
            a+     打开文件,用于追加 (在文件尾写)。如果文件不存在就创建它。读文件
                  的初始位置是文件的开始,但是写入总是被追加到文件的末尾。	
    	返回值:
    		失败返回NULL,同时errno被设置
    		成功返回被打开文件的 FILE指针
   FILE *fdopen(int fd, const char *mode);
   		fd:文件描述符。先open,再fopen
   		mode:和fopen一样
   		返回值:和fopen一样

2.2.2 关闭文件

   #include <stdio.h>
   int fclose(FILE *stream);
   		stream :fopen的返回值
   	返回值:
   		失败返回-1,同时errno被设置
   		成功返回0

2.2.3 读取文件内容

   #include <stdio.h>
   size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
   读取文本文本和二进制文件,并且想读多个字节都可以
   		ptr:用来保存读取到的数据的内存首地址
   		size:要读取的内容中单个元素的大小
   		nmemb:要读取的内容中元素的个数
   			读取的总字节数是 size*nmemb
   		stream:指定从哪个文件中读取
   	返回值:
   		失败返回-1,同时errno被设置
   		成功返回实际读到的元素个数

  读取文本文件中的一个字符 	
  getc/getchar/fgetc
  int getchar(void);
  	从标准输入文件流(stdin)中读取一个字符
  返回值:
  	失败返回-1
  	成功返回该字符的 ascii码
  		
  int fgetc(FILE *stream);
  int getc(FILE *stream);
  	从stream指定的文件中读取一个字符
  返回值:
	失败返回-1
  	成功返回该字符的 ascii码
	读取文本文件中的一行字符
	char *gets(char *s);//容易越界,不建议使用
	char *fgets(char *s, int size, FILE *stream);
		s:用来保存字符串的那块内存的首地址
		size:表示最多读 size-1个字节(最后一个字节给'\0')。
			一般来说,传入s指向的那块内存的大小,可以防止越界
			如果在size-1个字节前遇到换行符,也会结束
		stream:指定从哪个文件中读取
	返回值:
		失败返回-1,同时errno被设置
		成功返回字符串首地址(其实就是s)

2.2.4 往文件中写入数据

   size_t fwrite(const void *ptr, size_t size, size_t nmemb,
                 FILE *stream);
     	ptr: 要写入的数据的首地址
        size:要写入的内容中单个元素的大小
        nmemb:要写入的内容中元素的个数
        stream:指定写入到哪个文件中
   返回值:
   		失败返回-1,同时errno被设置
   		成功返回实际写入的元素个数
    
	往文件中写入一个字符
	int putchar(int c);//把字符c写入标准输出流 stdout ,效果就是输出字符c
		c:要写入的字符
		失败返回-1
		成功返回c
    int putc(int c, FILE *stream);
    int fputc(int c, FILE *stream);
    	c:要写入的字符
    	stream:指定写入到哪个文件流
    	失败返回-1
		成功返回c
    往文件中写入一行字符
    int puts(const char *s);//写入到stdout,自动加一个 '\n'
    int fputs(const char *s, FILE *stream);//输出到指定文件,不会加'\n'
    

2.2.5 定位光标

   #include <stdio.h>
   int fseek(FILE *stream, long offset, int whence);
   		stream:指定文件流
   		offset:偏移量,以字节为单位,正数往后偏移,负数往前偏移
		whence:偏移方式
			SEEK_SET	以文件开头位置为基准	
   			SEEK_CUR	以当前光标位置为基准	
   			SEEK_END	以文件末尾位置为基准	
   		返回值:
   			失败返回-1,同时errno被设置
   			成功返回0
   long ftell(FILE *stream);
   		返回 stream文件中光标距离文件的位置
练习:
(1)实现复制目录的函数
	把 src目录里面的所有文件都复制到 dest目录中,并且要保持文件的层次关系不变
	void copy_dir(const char *src,const char *dest)
	{}
	//需要用到 mkdir函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
int copy_file(const char *src,const char *dest)
{
    // 打开两个文件
    int src_fd = open(src,O_RDONLY);
    if(-1 == src_fd)
    {
        printf("打开源文件%s失败,%s\n",src,strerror(errno));
        return -1;
    }

    int dest_fd = open(dest,O_WRONLY | O_CREAT | O_TRUNC,0664);
    if(-1 == dest_fd)
    {
        printf("打开目标文件%s失败,%s\n",dest,strerror(errno));
        close(src_fd);
        return -1;
    }

    //进行文件内容复制
    char buf[100];
    int r ;
    while(1)
    {
        r = read(src_fd,buf,100);
        if(0 == r)
            break;//读取完毕
        write(dest_fd,buf,r);
    }

    //关闭文件
    close(src_fd);
    close(dest_fd);
    return 0;
}

/*
    功能:复制目录,把 src目录里面的所有文件(包括子目录)全部复制到 dest目录中
    失败返回-1,成功返回0
*/
int copy_dir(const char *src,const char *dest)
{
    int r = mkdir(dest,0777);//创建目的目录,如果目录已经存在, mkdir会失败
                            //但是这种情况并不影响我们后续的操作,所有不需要结束函数
                            //如果是其他原因导致的创建失败,那就需要结束函数
    if(-1 == r && errno != EEXIST)
    {
        printf("创建%s目录失败:%s\n",dest,strerror(errno));
        return -1;
    }
    DIR * psrc = opendir(src);
    if(NULL == psrc)
    {
        printf("打开%s文件失败:%s\n",src,strerror(errno));
        return -1;
    }
    DIR * pdest = opendir(dest);
    if(NULL == pdest)
    {
        printf("打开%s文件失败:%s\n",dest,strerror(errno));
        closedir(psrc);
        return -1;
    }
    struct dirent * p;
    //char srcpathname[100]; //可能会越界,最好是动态分配
    //char destpathname[100]; 
    int srclen;
    int destlen;
    char * srcpathname;
    char * destpathname;
    struct stat st;//用来保存获取到的文件属性信息
    while(1)//一项一项读取目录
    {
        p = readdir(psrc);
        if(NULL == p)//返回NULL,代表目录读取完毕
            break;

        //printf("%s\n",p->d_name);//可以发现,包含两个特殊目录:  .  ..
        if(strcmp(p->d_name,".")==0 || strcmp(p->d_name,"..")==0)
            continue;

        //连接原目录及该目录中的文件,组成一个完整的路径名
        srclen = strlen(src) + 1 + strlen(p->d_name) + 1;
        srcpathname = (char *)malloc(srclen);
        strcpy(srcpathname,src);
        strcat(srcpathname,"/");
        strcat(srcpathname,p->d_name);
        printf("%s\n",srcpathname);
        r = stat(srcpathname,&st);
        if(-1 == r)
        {
            perror("");
            continue;
        }
        destlen = strlen(dest) + 1 + strlen(p->d_name) + 1;
        destpathname = (char *)malloc(destlen);
        strcpy(destpathname,dest);
        strcat(destpathname,"/");
        strcat(destpathname,p->d_name);
        printf("%s\n",destpathname);

        if((st.st_mode & S_IFMT) == S_IFREG)//srcpathname是普通文件,进行复制
        {
            r = copy_file(srcpathname,destpathname);
            if(-1 == r)//该文件复制失败
            {
                printf("%s文件复制失败\n",srcpathname);
            }
        }
        else if((st.st_mode & S_IFMT) == S_IFDIR)//是子目录
        {
            //递归调用自己
            copy_dir(srcpathname,destpathname);
        }

        free(srcpathname);
        free(destpathname);
    }

//原目录  /mnt/hgfs/mnt/hgfs/CS2415F/2阶段
    // /mnt/hgfs/mnt/hgfs/CS2415F/2阶段/1文件IO
//目录目录  /home/china/test
    //  /home/china/test/1文件IO
    closedir(psrc);
    closedir(pdest);
    return 0;
}

int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        printf("输入有误,需要带两个参数,原目录路径和目的目录路径\n");
        return -1;
    }
        
    int r = copy_dir(argv[1],argv[2]);
    if(-1 == r)
    {
        printf("复制失败\n");
    }
    else
    {
        printf("复制成功\n");
    }

    return 0;
}

2.2.6 格式化输出

按照指定的格式进行输出

   #include <stdio.h>
   int printf(const char *format, ...);
        format:用于指定输出格式
            这个参数是一个字符串,字符串的内容分为三类字符
            (1)普通字符,原样输出
            (2)以\开头的转义字符,如: \n  \0  \t \r .....  
                如果想输出 \ 字符本身,  \\
            (3)以%开头的特色字符,如: %d %ld %x %hd %f %lf %s ... 
                会用后面的参数的值来替换它
        ... :表示后面的参数个数集和类型是不确定的,根据你的实际情况写,后面的参数个数要和
                第一个参数中 % 开头的特色字符个数相同,类型要匹配
        返回值:失败返回-1,成功返回实际输出的字符个数
    例如:
        int a = 10;
        float b = 1.5;
        char c = 'X';
        printf("a=%d,b=%f,c=%c\n",a,b,c);
        输出 a=10,b=1.5,c=X
        a=  ,  b= , c= 这些都是普通字符,原样输出, %d %f %c 由后面的参数的值替换输出
        \n 有换行的效果
   
   int fprintf(FILE *stream, const char *format, ...);
        该函数比 printf多了一个参数 stream,指定文件流
        printf固定向 STDOUT_FILENO文件流中输出
        而 fprintf可以指定向某个文件流中输出
        后面的参数和 printf完全一样
        如:
            FILE * fp = fopen("./1.txt","w+");
            fprintf(fp,"a=%d,b=%f,c=%c\n",a,b,c);//输出a=10,b=1.5,c=X\n到 1.txt文件中
        
   int dprintf(int fd, const char *format, ...);
        和 fprintf功能一样,输出到指定文件中,只不过是用 文件描述符来指定
        
   int sprintf(char *str, const char *format, ...);
        该函数比 printf多了一个参数 str,它是一块内存的首地址,该函数的功能不是输出到文件流中,
        而是保存到内存中。
        示例代码:
        之前有这3行代码,用来连接字符串,保存到 pathname数组中
        strcpy(pathname,name);
        strcat(pathname,"/");
        strcat(pathname,p->d_name);
        ->
        sprintf(pathname,"%s/%s",name,p->d_name);
        
   int snprintf(char *str, size_t size, const char *format, ...);
        在 sprintf函数的基础上,增加了一个参数 size,用来指定 str内存的大小,防止越界

2.2.7 格式化输入

按照指定的格式进行输入

   #include <stdio.h>
   int scanf(const char *format, ...);
        format:指定输入格式,是一个字符串,字符串中有3类字符串
        (1)普通字符,原样输入
            int data;
            scanf("请输入:%d",&data);//单纯从语句的角度,没有错
            //输入:100   错误
            //输入:请输入:100    正确    
            因为 "请输入:" 这些字符就是普通字符,需要原样输入
            
            int a,b;
            scanf("%d%d",&a,&b);
            //输入:10 20  错误
            //输入:10,20  正确
            因为","是普通字符,需要原样输入
        (2) 空白符
            空格 tab  \n 等
            也要输入空白符
        (3) 以%开头的特色字符
            如: %d %ld %x %hd %f %lf %s ...  
            分别对应要输入不同类型的数据
       ... 后面的参数个数及类型根据第一个参数中 %开头的特色字符决定的
            类型都是地址/指针
       
      返回值:
        返回正确匹配的变量数目(%开头的特色字符数目)
        
   int fscanf(FILE *stream, const char *format, ...);
        fscanf相比 scanf多了一个参数 stream, scanf只能从标准输入文件流中获取数据,
        而 fscanf可以从指定的文件流中获取数据
   int sscanf(const char *str, const char *format, ...);
        sscanf相比 scanf多了一个参数 str, scanf只能从标准输入文件流中获取数据,
        而 sscanf可以从指定的内存中获取数据
        
        char buf[] = "18 2.4 x dkljldk";
        r = sscanf(buf,"%d %f %c",&a,&b,&c);
        printf("r=%d,a=%d,b=%f,c=%c\n",r,a,b,c);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2197816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

笔试算法day01

目录 1.除2 2.Fibonacci数列&#xff08;Fib 数列&#xff09; 3.单词搜索 1.除2 除2&#xff01; (nowcoder.com) 算法思路&#xff1a; 只需要对最大的n个偶数进行/2即可。 将所有的偶数存进大根堆中&#xff0c;执行k次操作即可 #include <iostream> #include <…

2024年AI知识库哪家强?8款主流软件对比分析

在当今这个信息爆炸的时代&#xff0c;如何高效地管理、搜索和共享知识成为了一个重要的问题。AI知识库作为一种先进的解决方案&#xff0c;正受到越来越多企业和个人的青睐。本文将对比分析8款主流的AI知识库软件&#xff0c;帮助大家找到最适合自己的工具。 1. HelpLook AI知…

机器学习K近邻算法——回归问题K近邻算法示例

针对“数据4.1”&#xff0c;讲解回归问题的K近邻算法&#xff0c;以V1&#xff08;营业利润水平&#xff09;为响应变量&#xff0c;以V2&#xff08;固定资产投资&#xff09;、V3&#xff08;平均职工人数&#xff09;、V4&#xff08;研究开发支出&#xff09;为特征变量。…

Flutter 进阶:根据IP地址判断用户国家/地区

在应用开发中根据IP地址判断用户国家/地区的两种方法 引言 在开发国际化应用时&#xff0c;了解用户的地理位置至关重要。这不仅影响用户体验&#xff0c;还关系到内容展示和合规性。本文将介绍两种通过IP地址判断用户所在国家或地区的方法。 方法一&#xff1a;使用 ip-api…

redis高级(面试题二)

目录 一、redis的五大数据结构有哪些&#xff1f;zset底层是什么结构&#xff1f; 1、redis五大数据结构有哪些&#xff1f; 2、什么是skiplist&#xff1f; 3、zset底层是什么结构&#xff1f; 二、Redis的内存过期策略是什么&#xff1f;Redis的内存淘汰策略有哪些&#…

【专题】数据库系统的基本原理

1. 数据库系统概述 1.1. 数据库系统的应用 电信业、银行业、金融业、销售业、联机的零售商、大学、航空业、人力资源、制造业等等。 1.2 数据库系统的概念 (1) 数据&#xff08;Data&#xff09; 数据是数据库存储的基本对象。是描述现实世界中各种具体事物或抽象概念的、可…

Nuxt日志监控(服务端及客户端日志检测)

此文章主要讲解如何使用Nuxt进行日志监控&#xff0c;例如服务端请求日志&#xff0c;客户端请求日志&#xff0c;方便线上出现问题能及时排查问题所在 一、下载依赖 npm install winston winston-daily-rotate-file二、plugin下创建日志处理插件winston.js&#xff0c;对日志…

靠谱!有了它,微信自动统计报表轻松搞定!

当你需要定期统计多个微信号的数据时&#xff0c;每次都要逐一登录并手动统计各种数据&#xff0c;这不仅耗时&#xff0c;还容易出错。 好在&#xff0c;一个便捷的工具——个微管理系统能够帮助我们高效地管理这些繁杂的数据&#xff0c;让我们的工作事半功倍。 好友统计报…

安装Node.js环境,安装vue工具(最佳实践)

一、安装Node.js 去官网下载自己需求的安装包&#xff08;我提供的步骤是windows10 64x&#xff09; 下载 | Node.js 中文网 (nodejs.cn)https://nodejs.cn/download/ 下载好后&#xff0c;安装到默认路径就好了&#xff0c;所占用的内容很少。 一直点next就行了 安装好后&a…

python操作.docx、.pptx文件

python操作.docx、.pptx文件 .docx文件和.pptx文件是Microsoft Office套件中两种常见的文件格式&#xff0c;分别对应Word文档和PowerPoint演示文稿。WPS Office完美支持Microsoft Office文件格式。 使用 Python 操作 .docx 和 .pptx 文件是一项非常实用的技能&#xff0c;尤…

BlabkForestLabs 又放大招:“蓝莓”模型其实是 Flux1.1?!

神秘的 AI 生成模型 BlabkForestLabs 又放大招了&#xff1f;就在 AI 绘画圈还在训练 Flux.1 练的不亦乐乎的时候&#xff0c;黑森林工作室又推出了一个新的模型—— Flux1.1 &#xff01;这次升级后的 Flux1.1 性能直接完爆前版的 Flux.1 &#xff0c;再次将 AI 绘画的上限拉高…

如何给ppt增加新的一页?这2个ppt使用技巧值得推荐!

在当今讲究视觉表现力的时代&#xff0c;PPT已经成为职场中不可或缺的工具。无论是汇报工作、演示方案还是传递想法&#xff0c;一份精美的PPT都能让你的演讲&#xff08;演示&#xff09;更加出色。 然而&#xff0c;制作PPT并非易事&#xff0c;尤其是对于新手来说&#xff…

STM32-HAL库 驱动DS18B20温度传感器 -- 2024.10.8

目录 一、教程简介 二、驱动理论讲解 三、CubeMX生成底层代码 四、Keil5编写代码 五、实验结果 一、教程简介 本教程面向初学者&#xff0c;只介绍DS18B20的常用功能&#xff0c;但也能满足大部分的运用需求。跟着本教程操作&#xff0c;可在10分钟内解决DS18b20通信难题。…

windows认证

本地环境用户信息存储在%systemroot%/system32/SAM 域环境用户信息存储在ntds.dit 本地认证 windows系统下哈希结构&#xff1a;username:RID:LM-HASH:NT-HASH LM哈希 算法&#xff1a; 转大写&#xff0c;转二进制&#xff0c;补0补足14字节 二分获得两段字串&#xff…

算法:238.除自身以外数组的乘积

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;前缀和&#xff09; 这道题非常类似 724. 寻找数组的中心下标 在前一篇博客讲解了该题目 传送门:算法&#xff1a;724.寻找数组的中心下标 这道题目的区别在于&#xff0c;这道题是预处理前缀积和后缀积 另外&#x…

了解网页 blob 链接

blob 链接 自从 HTML5 提供了 video 标签&#xff0c;在网页中播放视频变得非常简单&#xff0c;只要在代码中插入一个 video 标签&#xff0c;再将 video 标签的 src 属性设置为视频的链接就可以了。由于 src 指向的是视频文件真实的地址&#xff0c;所以当我们通过浏览器的调…

如何绘制短剧产业链图谱?短剧产业前景如何?

绘制短剧产业链图谱是一个涉及多个环节的复杂过程。我们首先需要确定产业链的主要环节&#xff0c;包括内容创作、制作、发行、宣传和观众。每个环节都由不同的参与者组成&#xff0c;如编剧、导演、演员、制作公司、版权销售商、在线平台、电视台、广告公司和消费者等&#xf…

《CTF 特训营》:网络安全竞赛的进阶指南

在网络安全领域日益受到重视的今天&#xff0c;CTF&#xff08;Capture The Flag&#xff09;竞赛作为一种检验和提升网络安全技能的方式&#xff0c;受到了越来越多爱好者的关注。而《CTF 特训营》这本书&#xff0c;无疑是一本帮助读者深入了解 CTF 竞赛的优秀读物。 一、书籍…

Linux shell编程学习笔记86:sensors命令——硬件体温计

0 引言 同事们使用的Windows系统电脑&#xff0c;经常莫名其妙地装上了鲁大师&#xff0c;鲁大师的一项功能是显示系统cpu等硬件的温度。 在Linux系统中&#xff0c;sensors命令可以提供类似的功能。 1 sensors命令 的安装和配置 1.1 sensors命令 的安装 要使用sensors命…

INS淡绿色风格人像街拍Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色介绍 INS 淡绿色风格人像街拍通过 Lightroom 调色可以营造出清新、自然、时尚的视觉效果。这种风格以淡绿色为主色调&#xff0c;给人一种宁静、舒适的感觉。 预设信息 调色风格&#xff1a;INS风格预设适合类型&#xff1a;人像&#xff0c;街拍&#xff0c;自拍&#…