C语言IO操作

文件的概念和类型

  概念:一组相关数据的有序集合

  文件类型:

  1. 常规文件 r
  2. 目录文件 d
  3. 字符设备文件 c
  4. 块设备文件 b
  5. 管道文件 p, 进程间通信的机制
  6. 套接字文件 s, 进程间通信的机制
  7. 符号链接文件 l

如何理解标准IO

  标准IO由ANSIC标准定义,就是用标准C语言定义好的一组用来输入和输出的API

  主流操作系统(Linux,Windows)上都实现了C库

  标准IO通过缓冲机制减少系统调用,实现更高的效率

流(FILE)的含义

  标准IO用一个结构体数据类型来存放打开的文件的相关信息

  标准IO的所有操作都围绕FILE来进行

  FILE又被称为流(stream)

  流分为两种流分别是

    文本流:Windows系统中文本流的换行符占用两个字节用“\r\n”表示,LInux中用‘\n’表示

    二进制流:Windows系统二进制流的换行符占用一个字节用“\n”表示,LInux中用‘\n’表示

流的缓冲

  全缓冲:当流的缓冲区无数据或无空间时才执行实际IO操作

  行缓冲:当在输入和输出中遇到换行符“\n”时,进行IO操作;当流和一个终端关联时,是典型的行缓冲

  无缓冲:数据直接写入文件,流不进行缓冲,一般在打印错误信息时使用

  标准IO预定义3个流,程序运行时自动打开

标准输入流 0 STDIN_FILENO stdin
标准输出流 1 STDOUT_FILENO stdout
标准错误流 2 STDERR_FILENO stderr

 

 

 

流的打开

  下列函数可用于打开一个标准IO流

   FILE *fopen(const char *path, const char *modle);

  成功时返回流指针;出错时返回NULL

  model参数

模式描述
r或rb 打开一个已有的文本文件,允许读取文件。
w或wb 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a或ab 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+或r+b 打开一个文本文件,允许读写文件。
w+或w+b 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+或a+b 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

  当给定b参数时,表示以二进制方式打来文件,但linux下忽略该参数

  fopen新建文件权限

    fopen()创建的文件访问权限是0666(rw-rw-rw),0表是8进制数

    Linux系统中umask设定会影响文件的访问权限,其规则为(0666 & (~umask)),可以通过umask命令查看,默认为0022

    0022  ----> 000 010 010

    取反   ----> 111 101 101

    0666  ----> 110 110 110

    结果:---> 110 100 100   ---> 0644(rw-r--r--)

    用户可以通过umask函数修改相关设定,将umask设置为0时,umask不影响文件访问权限

错误信息处理

  extern int errno;//存放错误号

  void perror(const char *s);//向输出字符串s,再输出错误号对应的错误信息

  char *strerror(int errno);//根据错误号返回对应的错误信息

流的关闭

  int fclose(FILE *stream)

  fclose()调用成功返回0,失败返回EOF,并设置errno

  流关闭时自动刷新缓冲中的数据并释放缓冲区

  当一个程序正常终止时,所有打开的流都会被关闭,但是为了安全期间,程序员要主动关闭

  流一旦关闭后就不能执行任何操作

程序中能够打开的文件或流的个数有限制,写程序测试:

程序结果是1021,因为启动一个程序,默认打开stdin、stdout、stderr三个流,所以一个是1024;

#include <stdio.h>

int main()
{
    int count;
    while (1)
    {
        if (fopen("mycp.c", "r") == NULL)
        {
            break;
        }
        count++;
    }
    printf("%d\n", count);//1021
    return 0;
}

读写流

  流支持不同的读写方式

    读写一个字符:fgetc()/fputc()一次读/写一个字符

    读写一行:fgets()/fputs()一次读/写一行数据,一般用于文本文件,一般不适用于二进制文件

    读写若干个对象:fread()/fwrite()每次读/写若干个对象,而每个对象具有相同的长度,效率高,推荐使用

  按字符输入

    下列函数用来输入一个字符

      #include <stdio.h>

      int fgetc(FILE *stream);//与getc()函数功能完全相同,成功时返回读取的字符,若到文件末尾或出错时返回EOF

      int getc(FILE *stream);

      int getchar(void);//从标准输入中获取字符等同于fgetc(stdin)

  按字符输出

    下列函数用来输出一个字符

      #include <stdio.h>

      int fputc(int c, FILE *stream);//与putc()函数功能完全相同,第一个参数是输出的字符,成功时返回写入的字符;出错时返回EOF

      int putc(int c, FILE *stream);

      int putchar(int c);//向标准输出流写入一个字符,等同于fputc(c, stdout);

  利用fgetc()/fputc()实现文件复制

#include <stdio.h>
#include <string.h> //提供strerror()函数
#include <errno.h>  //提供errno变量

int main(int argc, char *argv[])
{
    FILE *fps, *fpd; //定义两个流指针分别指向源文件和目标文件
    int ch; //保存读出的字符

    if (argc < 3) //检验命令行参数
    {
        printf("Usage : %s <src_file> <dst_file>\n", argv[0]);
        return -1;
    }

    /*
        打开源文件
    */
    if ((fps = fopen(argv[1], "r")) == NULL)
    {
        //perror("fopen src file");
        printf("fopen src file: %s\n",strerror(errno));
        return -1;
    }

    /*
        打开目标文件
    */
    if ((fpd = fopen(argv[2], "w")) == NULL)
    {
        perror("fopen dst file");
        return -1;
    }

    while ((ch = fgetc(fps)) != EOF)
    {
        fputc(ch, fpd);
    }
    fclose(fps);
    fclose(fpd);
    return 0;
}

  按行输入

    下列函数用来输入一行:

      #include <stdio.h>

      char *gets(char *s);//从标准输入读入一行数据,不推荐使用,因为没有执行缓冲区的大小,容易造成缓冲区溢出

      char *fgets(char *s, int size, FILE *stream);//成功时返回s,到文件末尾或出错时返回NULL,遇到"\n"或已输出size-1个字符时返回,总是包含"\0"

  按行输出

    下列函数用来输出一行字符串:

      #include <stdio.h>

      int puts(const char *s);//将缓冲区s中的字符串输出到stdout,并追加"\n"

      int fputs(const chars *s, FILE *stream);//将缓冲区s中的字符串输出到stream中,不追加"\n"

      成功时返回输出的字符个数;出错时返回EOF

  写程序统计一个文本中包含多少行?

#include <stdio.h>
#include <string.h>
#define SIZE 37

int main(int argc, const char *argv[])
{
    int count = 0;//统计文本行数
    FILE *fp;
    char buf[SIZE] = {};//如果buf不指定大小会报栈溢出错误
    if ((fp = fopen("b.txt", "r")) == NULL)
    {
        perror("fopen");
        return -1;
    }
    while (fgets(buf, SIZE+1, fp) != NULL)
    {
        if (buf[strlen(buf) -1] == '\n')
        {
            count ++;
        }
    }
    fclose(fp);
    printf("lines:%d\n", count);
    return 0;
}

  按指定对象输入/输出

    下列函数用来从流中读写若干个对象:

      #include <stdio.h>

      size_t fread(void *ptr, size_t size, size_t n, FILE *fp);

      size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);

      参数说明:

        ptr是缓冲区的首地址

        size是对象数据的大小,对象为字符时值为1,对象为数字时值为4.

        n是要读写的对象个数

        fp为流指针

      成功返回实际读写的对象个数;出错时返回EOF

      既可以读写文件,也可以读写数据文件

      效率高

  使用fread()/fwrite()函数实现文件拷贝

#include <stdio.h>
#include <string.h> //提供strerror()函数
#include <errno.h>  //提供errno变量

#define N 64

int main(int argc, char *argv[])
{
    FILE *fps, *fpd; //定义两个流指针分别指向源文件和目标文件
    char buf[N];
    int n;

    if (argc < 3) //检验命令行参数
    {
        printf("Usage : %s <src_file> <dst_file>\n", argv[0]);
        return -1;
    }

    /*
        打开源文件
    */
    if ((fps = fopen(argv[1], "r")) == NULL)
    {
        //perror("fopen src file");
        printf("fopen src file: %s\n",strerror(errno));
        return -1;
    }

    /*
        打开目标文件
    */
    if ((fpd = fopen(argv[2], "w")) == NULL)
    {
        perror("fopen dst file");
        return -1;
    }

    while ((n = fread(buf, 1, N, fps)) > 0)
    {
        fwrite(buf, 1, n, fpd);
    }
    fclose(fps);
    fclose(fpd);
    return 0;
}

流的刷新

  自动刷新

    全缓冲:当流的缓冲区满的时候将自动刷新,打开文件时默认是全缓冲

    行缓冲:流的缓冲区满的时候或遇到换行符"\n"将自动刷新

    关闭流的时候将会自动刷新

  手动刷新

    #include <stdio.h>

    int fflush(FILE *fp);

    成功返回0;出错时返回EOF

    将流缓冲区中的数据写入到实际的文件

    Linux下只能刷新输出缓冲区

流的定位

  #include <stdio.h>

  long ftell(FILE (stream);//成功的时候返回当前读写位置,出错时返回EOF

  long fseek(FILE *stream, long offset, int whence);//定位一个流,成功返回0,出错时返回EOF,whence参数:SEEK_SET(文件的开始)/SEEK_CUR(当前位置)/SEEK_END(文件的结尾)

  void rewind(FILE *stream);//将流定位到文件的起始位置

检测流结束和出错

  #include <stdio.h>

  int ferror(FILE *stream);//返回1表示流出错,否则返回0

  int feof(FILE *stream);//返回1表示文件已到末尾;否则返回0

格式化输出

  #include <stdio.h>

  int printf(const char *fmt,...);

  int fprintf(FILE *stream, const char *fmt,...);//向指定流中输出格式化后的数据

  int sprintf(char *s, const char *fmt,...);//向指定缓冲区中输出格式化后的数据

写程序实现一下功能:

  每个一秒向文件test.txt文件中写入系统时间,格式如下:

    1, 2020-02-19 15:30:20

    2, 2020-02-19 15:30:21

  该程序无限循环,直到ctrl+c结束程序,下次再执行该代码时接着之前的格式接续写入:3, 2020-02-19 15:31:21

#include <stdio.h>
#include <string.h> //strlen()
#include <time.h> //time()/localtime()
#include <unistd.h> //sleep()

#define SIZE 3
int main()
{
    int line = 0;//用于记录行号
    FILE *fp;
    char buf[SIZE];
    time_t t;//用于存放当前的时间
    struct tm *tp;//存放格式化之后的时间
    {
        
    };

    if ((fp = fopen("test.txt", "a+")) == NULL)
    {
        perror("fopen");
        return -1;
    }

    while (fgets(buf, SIZE, fp) !=NULL)
    {
        if (buf[strlen(buf) - 1] == '\n') line++;
    }

    while (1)
    {
        time(&t);
        tp = localtime(&t);
        fprintf(fp, "%02d, %d-%02d-%02d %02d:%02d:%02d\n", ++line, tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
        fflush(fp);//刷新缓冲区,将缓冲区的内容写入到文件中
        sleep(1);
    }

    return 0;
}

文件IO

什么是文件IO

  posix(可移植操作系统接口)定义的一组函数

  不提供缓冲机制,每次读写操作都引起系统调用

  核心概念是文件描述符

  访问各种类型文件(标准io一般只能访问普通文件和终端文件)

  Linux下,标准IO基于文件IO实现

文件描述符

  每打开的文件都对应一个文件描述符

  文件描述符是一个非负整数,Linux为程序中每个打开的文件分配一个文件描述符

  文件描述符从0开始分配,依次递增

  每个程序打开的文件描述符都是相互独立的

  文件IO操作通过文件描述符来完成

  0,1,2分别表示标准输入,标准输出,标准错误

open函数用来创建或打开一个文件

  #include <fcntl.h>

  int open(const char *path,  int oflag, ...);

  成功时返回文件描述符;出错时返回EOF

  打开文件时使用两个参数

  创建文件时第三个参数指定新文件的权限

  只能打开设备文件

原型 int open(const char *pathname, int flags, mode_t mode);
参数 pathname 被打开的文件名(可包括路径名)
flags O_RDONLY:只读方式打开文件 这三个参数互斥
O_WRONLY:可写方式打开文件
O_RDWR:读写方式打开文件
O_CREAT:如果文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_EXCL:如果使用O_CREAT是文件存在,则可返回错误信息。这一参数可测试文件是否存在
O_NOCTTY:使用本参数时,如文件为终端,那么终端不可以作为调用open()系统调用的哪个进程的控制终端
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据
O_APPEND:以添加方式打开文件,所以对文件的写操作都在文件的末尾进行
mode 被打开文件的存储权限,为8进制表示

 

 

 

 

 

 

 

 

 

以只写方式打开文件1.txt。如果文件不存在则创建,如果文件存在则清空:

  int fd;

  if ((fd = open("1.txt", O_WRONLY| O_CREAT|O_TRINC, 0666)) < 0) {

    perror("open");

    return -1;

  }

以读写方式打开文件1.txt,如果文件不存在则创建,如果文件存在则报错

  int fd;

  if((fd = open("1.txt", O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) {

    if (errno == EEXIST){

      perror("exist error");

    }else{

      perror("other error");

    }

  }

close 函数用来关闭一个打开的文件

  #include <unistd.h>

  int close(int fd);

  成功时返回0;出错时返回EOF

  程序结束时自动关闭所有打开的文件

  文件关闭后,文件描述符不再代表文件

读取文件

  read函数用来从文件中读取数据

    #include <unistd.h>

    ssize_t read(int fd, void *buf, size_t, count);

    成功时返回实际读取的字节数;出错时返回EOF

    读到文件末尾时返回0

    buf是接收数据的缓冲区

    count不应超过buf大小

  从指定的文件(文本文件)中读取内容并统计大小

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

int main(int argc, const char *argv[])
{
    int fd, n, total = 0;
    char buf[64];

    if (argc < 2)
    {
        printf("Usage: %s <file>\n", argv[0]); return -1;
    }

    if ((fd = open(argv[1], O_RDONLY)) < 0 )
    {
        perror("open");
        return -1;
    }

    while ((n = read(fd, buf, 64)) > 0)
    {
        total += n;
    }
    close(fd);
    printf("total: %d\n", total);
    return 0;
}

写入文件

  write函数用来向文件写入数据

    #include <unistd.h>

    ssize_t write(int fd, void *buf, size_t, count);

    成功时返回实际写入的字节数;出错时返回EOF

    buf是发送数据的缓冲区

    count不应超过buf的大小

  将键盘输入的内容写入文件,直到输入quit

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

#define N 20

int main(int argc, char *argv[])
{
    int fd;
    char buf[N];

    if (argc < 2)
    {
        printf("Usage: %s <file>\n", argv[0]);
        return -1;
    }

    if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0 )
    {
        perror("open");
        return -1;
    }

    while (fgets(buf, N + 1, stdin) != NULL)
    {
        if(strcmp(buf, "quit\n") == 0) break;
        write(fd, buf, strlen(buf));
    }
    close(fd);
    return 0;
}

定位文件

  lseek函数用来定位文件

    #include <unistd.h>

    off_t lseek(int fd, off_t offset, int whence);

    成功时返回当前文件读写位置;出错时返回EOF

    参数offset和参数whence同fseek完全一样

读取目录

  opendir函数用来打来一个目录文件

    #include <dirent.h>

    DIR *opendir(const char *name);

    DIR是用来描述一个打开的目录文件的结构体类型

    成功时返回目录流指针;出错时返回NULL

  readdir函数用来读取目录流中的内容

    #include <dirent.h>

    struct dirent *readdir(DIR *dirp);

    struct dirent是用来描述目录流中一个目录项的结构体类型

    包含成员char d_name[256] 参数帮助文档

    成功时返回目录流dirp中下一个目录项;出错或到末尾时返回NULL

  close函数用来关闭一个目录文件

    #include <dirent.h>

    int closedir(DIR *drip);

    成功返回0;失败返回EOF

  打印指定目录下所有文件名称(只会打印指定目录下的文件和目录,不会打印子目录下的文件)

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

int main(int argc, char *argv[])
{
    DIR *dirp;
    struct dirent *dp;

    if (argc < 2)
    {
        printf("Usage: %s <directory>\n", argv[0]);
        return -1;
    }

    if ((dirp = opendir(argv[1])) == NULL)
    {
        perror("opendir");
        return -1;
    }

    while ((dp = readdir(dirp)) != NULL)
    {
        printf("%s\n", dp->d_name);
    }
    closedir(dirp);
    return 0;

}

修改文件访问权限

  chmod/fchmod函数用来修改文件的访问权限

    #include <sys/stat.h>

    int chmod(const char *path, mode_t mode);

    int fchmod(int fd, mode_t mode);

    成功返回0;出错返回EOF

    root和文件所有者能修改文件的访问权限

获取文件属性

  stat/lstat/fstat函数用来获取文件的属性

    #include <sys/stat.h>

    int stat(const char *path, struct stat *buf);

    int lstat(const char *path, struct stat *buf);//推荐使用

    int fstat(int fd, struct stat *buf);

    成功返回0;出错返回EOF

    如果path是符号连接stat获取的是目标文件色的属性;而lstat获取的是连接文件的属性

  struct stat是存放文件属性的结构体类型

    mode_t st_mode:  类型和访问权限

    uid_t st_uid:    所有者id

    uid_t st_gid:    用户组id

    off_t st_size:    文件大小

    time_t st_mtime:  最后修改时间

  st_mode

    通过系统提供的宏来判断文件类型:

      st_mode & 0170000 (001 111 000 000 000 000,文件类型掩码)

      S_ISREG(st_mode)  0100000

      S_ISDIR(st_mode)   0040000

      S_ISCHR(st_mode)  0020000

      S_ISBLK(st_mode)  0060000

      S_ISFIFO(st_mode)    0010000

      S_ISLNK(st_mode)  0120000

      S_ISSOCK(st_mode)  0140000

    通过系统提供的宏来获取文件访问权限

      S_IRUSR  00400    8

      S_IWUSR     00200    7

      S_IXUSR  00100    6

      S_IRGRP  00040    5

      S_IWGRP     00020    4

      S_IXGRP   00010    3

      S_IROTH  00004    2

      S_IWOTH  00002    1

      S_IXOTH   00001    0

  获取并显示文件属性

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

int main(int argc, char *argv[])
{
    struct stat buf;
    int n;
    struct tm *tp;

    if (argc < 2)
    {
        printf("Usage %s <file>\n", argv[0]);
        return -1;
    }

    if (lstat(argv[1], &buf) < 0)
    {
        perror("lstat");
        return -1;
    }

    switch (buf.st_mode & S_IFMT)
    {
    case S_IFREG:
        printf("-");
        break;
    case S_IFDIR:
        printf("d");
        break;
    case S_IFCHR:
        printf("c");
        break;
    case S_IFBLK:
        printf("b");
        break;
    case S_IFIFO:
        printf("p");
        break;
    case S_IFLNK:
        printf("l");
        break;
    case S_IFSOCK:
        printf("s");
        break;
    }

    for (n=8; n>=0; n--)
    {
        if (buf.st_mode & (1 << n)) //1左移n位与一个数求&获得的是该数第n位的值
        {
            switch (n % 3)
            {
            case 2:
                printf("r");
                break;
            case 1:
                printf("w");
                break;
            case 0:
                printf("x");
                break;
            }
        }
        else
        {
            printf("-");
        }
    }
    printf(" %lu" , buf.st_size);
    tp = localtime(&buf.st_mtime);
    printf(" %d-%02d-%02d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday);
    printf(" %s\n", argv[1]);

    return 0;
}

 库

库的概念

  库是一个二进制文件,包含的代码可被程序调用

  标准C库、数学库、线程库......

  库有源码,可下载后编译;也可以直接安装二进制包

  库默认的安装路径在/lib 或/usr/lib

  库是事先编译好的,可以复用的代码

  在OS上运行的程序基本上都要使用库。使用库可以提高开发效率

  Windows和Linux下库文件的格式不兼容

  Linux下包含静态库和共享库

静态库

  编译(链接)时把静态库中相关代码复制到可执行文件中

    程序中已经包含代码,运行时不再需要静态库

    程序运行时无需加载库,运行速度更快

    占用更多磁盘和内存空间

    静态库升级后,程序需要重新编译链接

静态库的创建

  确定库中函数的功能、接口

  编写库源码hello.c

#include <stdio.h>

void hello(void)
{
    printf("hello world!\n");
}

  编译生成目标文件hello.o:gcc -c hello.c -Wall

  创建静态库hello:ar crs libhello.a hello.o(库文件名为libhell.a,库名为hello)

  查看库中符号信息:

    xdl@xdl-gj:~/C语言/lib$ nm libhello.a

    hello.o:
             U _GLOBAL_OFFSET_TABLE_
    0000000000000000 T hello
             U puts

链接静态库

  编写应用程序test.c

void hello(void);

int main()
{
    hello();
    return 0;
}

  编译test.c并链接静态库libhello.a

    gcc -o test test.c -L. -lhello

      -L:用来指定库的搜索路径,. 表示当前目录

      -l:指定要链接的库名

共享库

  编译(链接)时仅记录用到哪个共享库中的哪个符号(hs),不复制共享库中相关代码

    程序不包含库中代码,尺寸小

    多个程序可共享同一个库

    程序运行时需要加载库

    库升级方便,无需重新编译程序

    使用更加广泛

共享库的创建

  确定库中函数功能、接口

  编写库源码hello.c  bye.c

#include <stdio.h>

void hello(void)
{
    printf("hello world!\n");
}
/**********************************/
#include <stdio.h>

void bye(void)
{
    printf("bye!\n");
}

  编译生成目标文件:gcc -c -fPIC hello.c by2.c -Wall;

    -fPIC:告诉编译器要生成.o文件可以被任何位置的程序调用

  创建共享库common:gcc -shared -o libcommon.so.1 hello.o bye.o ;.1表示版本

  为共享库文件创建链接文件:ln -s libcommon.so.1 libcommon.so

链接共享库

  编写应用程序test.c

#include "common.h"

int main()
{
    hello();
    bye();
    return 0;
}
/*common.h*/
void hello(void);
void bye(void);

  编译test.c并链接共享库libcommon.so:

    gcc -o test test.c -L. -lcommon (默认先找共享库,其次才会寻找静态库,可以通过-static 参数直接连接静态库)

加载共享库

  执行程序:    

    xdl@xdl-gj:~/C语言/lib$ ./test
    ./test: error while loading shared libraries: libcommon.so: cannot open shared object file: No such file or directory

  添加共享库的加载路径

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

    xdl@xdl-gj:~/C语言/lib$ ./test
    hello world!
    bye!

如何找到共享库

  为了让系统能找到要加载的共享库,有三种方法:

    1、把库拷贝到/usr/lib  或 /lib目录下

    2、在LD_LIBRARY_PATH环境变量中添加库所在路径

    3、添加/etc/ld.so.conf.d/*.conf文件,执行ldconfig刷新

        sudo vim my.conf

          /home/xdl/C语言/lib

        sudo ldconfig

posted @ 2020-02-20 18:34  xdl_smile  阅读(3342)  评论(0编辑  收藏  举报