C语言-文件IO

文件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);
相关推荐
嵌入式科普30 分钟前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
小林熬夜学编程2 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Jackey_Song_Odd2 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
A懿轩A3 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
半盏茶香4 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
字节高级特工5 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白5 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
XH华11 小时前
初识C语言之二维数组(下)
c语言·算法
Uu_05kkq14 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普16 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长