C语言学习笔记《带你学C带你飞》P41-P61

41、内联函数和一些鲜为人知的技巧

  • 内联函数 解决程序中函数调用的效率问题。(但会增加编译时间)

    • 定义函数前加上inline关键字
    • 内联函数执行过程是在主函数中展开,而不是主函数-子函数-返回主函数。
    • 内联函数虽然节省了函数调用的时间消耗,但由于每一个函数出现的地方都要进行替换,因此增加了代码编译的时间。另外,并不是所有的函数都能够变成内联函数。
    • 现在的编译器很聪明,不写inline,也会自动将一些函数优化成内连函数
    • 总结:编译器比你更了解哪些函数应该内联哪些不能内联,所以这个知识点你只需要知道就好……
    #include<stdio.h>
    
    inline int square(int x);
    
    inline int square(int x)
    {
        return x*x;
    }
    int main()
    {
        int i=1;
        while(i<=100){
            printf("%d的平方是:%d\n",i-1,square(i++)); //提高编译效率,也可以避免像宏定义出现两次加的错误
        }
        return 0;
    }
    
  • #和## 是两个预处理运算符

    • 在带参数的宏定义中,#运算符后面应该跟一个参数,预处理会把这个参数转化为一个字符串。
    #include<stdio.h>
    #define STR(s) # s
    
    int main(){
         printf("%s\n",STR(FISHC));
         return 0;
    }
    
    • 会把多个空格转化为一个空格
    #include<stdio.h>
    
    #define STR(s) # s
    
    int main(){
         printf(STR(Hello   %s num=%d\n),STR(FISHC),520);
    
         return 0;
    }
    
    • ##运算符被称为记号连接运算符,比如可以使用##运算符连接两个参数
    #include<stdio.h>
    
    #define TOGETHER(x,y) x ## y
    
    int main(){
        printf("%d\n",TOGETHER(2,50));
    
        return 0;
    }
    
  • 可变参数

    • 带参数的宏定义也可以使用可变参数
    • #define SHOWLIST(…) printf(#__VA_ARGS__)
    • 其中…表示可变参数,__VA_ARGS__在预处理中被实际的参数集所替换(就像参数列表)(两边是两个下划线哦)
    #include <stdio.h>
    
    #define SHOWLIST(...) printf(#__VA_ARGS__)
    
    #define PRINT(format, ...) printf(#format, ##__VA_ARGS__)
    
    int main(void)
    {
        SHOWLIST(FishC, 520, 3.14\n);
    
        PRINT(num = % d\n, 520);
        PRINT(Hello FishC !\n); //这个里面可变参数是空的
    
        return 0;
    }
    

42、结构体

  • 结构体声明

    struct 结构体名  
    {
    	结构体成员1;
    	结构体成员2;
    	结构体成员3;
    	...
    };//这里有一个分号
    
  • 示例

    struct Book
    {
        char title[128];
        char author[40];
        float price;
        unsigned int date;
        char publisher[40];
    };
    
  • 定义结构体类型变量

    • struct 结构体名称 结构体变量名;
    • 或者:在声明结构体时定义
    struct 结构体名{
        ...
    } 变量名;//不过这里是全局变量
    
    
    //注意:如果
    typedef struct 结构体名称{
    	...
    }简称; //或typedef struct 结构体名 简称;
    
    //使用typedef给结构体定义了一个简称,并不是变量
    
     
    
    • 结构体可以放在函数外(全局),可以放在函数内(局部)
  • 访问结构体变量

    • 要访问结构体成员,我们需要引入一个新的运算符——点号( . )运算符。比如book.title就是引用book结构体的title成员,它是一个字符数组;而book.price则是引用book结构体的price成员,它是一个浮点型的变量。
    #include<stdio.h>
    
    struct Book
    {
    
        char title[128];
        char author[40];
        float price;
        unsigned int date;
        char publisher[40];
    } book;
    
    int main(){
        //struct Book book;
        printf("请输入书名:");
        scanf("%s",book.title); //字符数组名指向开头元素地址 不用&
    
        printf("请输入作者:");
        scanf("%s",book.author);
    
        printf("请输入售价:");
        scanf("%f",&book.price);
    
        printf("请输入出版日期:");
        scanf("%d",&book.date);
    
        printf("请输入出版社:");
        scanf("%s",book.publisher);
    
        printf("\n===========数据录入完毕========\n");
        printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d\n出版社:%s\n",book.title,book.author,book.price,book.date,book.publisher);
    }
    
  • 初始化一个结构体变量(按顺序)

    stuct Book book={
        "C语言",
        "小明",
        19.9,
        20220420,
        "出版社"
    };
    
  • c99新特性:初始结构体的指定成员值

    • 其语法和数组指定初始化元素类似,不过结构体指定初始化成员使用点号( . )运算符和成员名。

    • 比如可以让程序只初始化Book的price成员:struct Book book={.price = 19.9};

    • 还可以不按结构体声明的成员顺序进行初始化:

      struct Book book={ .price = 19.9, .title = “C语言” , .date = 20220420};

  • 结构体的长度与内存对齐

    #include<stdio.h>
    
    int main(){
        struct A
        {
            char a;
            int b;
            char c;
    
        } a={'x',520,'o'};
    
        printf("size of a=%d\n",sizeof(a));//结果结构体a长度为12 ,因为内存对齐(让CPU更快处理数据 )
    
        // 如果顺序为char a;char c;int b;则长度为8
    
        return 0;
    }
    

43、结构体数组和结构体指针

  • 结构体嵌套

    #include <stdio.h>
    
    struct Date
    {
        int year;
        int month;
        int day;
    } date;
    
    struct Book
    {
        char title[128];
        char author[40];
        float price;
        struct Date date; //结构体嵌套
        char publisher[40];
    } book = {
        "C语言",
        "小明",
        19.9,
        {2022, 4, 20},
        "出版社"};
    
    int main(void)
    {
    
        printf("书名:%s\n作者:%s\n售价:%.2f\n出版社:%s\n",
               book.title, book.author, book.price, book.publisher);
    
        printf("日期:%d-%d-%d", book.date.year, book.date.month, book.date.day);
    
        return 0;
    }
    
  • 定义结构体数组

    • 第一种方法是在声明结构体的时候进行定义:
    struct 结构体名称
    {
    	结构体成员;
    } 数组名[长度];
    
    • 第二种方法是先声明一个结构体类型(比如上面Book) ,再用此类型定义一个结构体数组:
    struct 结构体名称
    {
    	结构体成员;
    };
    
    struct 结构体名称 数组名[长度];
    
  • 初始化结构体数组

    struct Book book[3]={
        {"书1", "张三", 11.1, {2022, 1, 1}, "出版社1"},
        {"书2", "李四", 22.2, {2022, 2, 2}, "出版社2"},
        {"书3", "王五", 33.3, {2022, 3, 3}, "出版社3"}
    };
    
  • 结构体指针

    • struct Book *pt;

      pt=&book;

    • 通过结构体指针访问成员的两种方法

      • (*结构体指针).成员名 【例如: (*pt).title;注意: *优先级低于点. 不加括号会先运行 pt.title;】
      • 结构体指针->成员名 【如:pt->title,注意:箭头->用于指针;点. 用于对象】

44、传递结构体变量和结构体指针

  • 传递结构体变量

    • 两个相同结构体类型的结构体变量可以赋值。book1=book2;
    #include <stdio.h>
    
    struct Date
    {
        int year;
        int month;
        int day;
    };
    
    struct Book
    {
        char title[128];
        char author[40];
        float price;
        struct Date date;
        char publisher[40];
    };
    
    struct Book getInput(struct Book book)
    {
    
        printf("请输入书名:");
        scanf("%s", book.title);
    
        printf("请输入作者:");
        scanf("%s", book.author);
    
        printf("请输入售价:");
        scanf("%f", &book.price);
    
        printf("请输入出版日期:");
        scanf("%d-%d-%d", &book.date.year, &book.date.month, &book.date.day);
    
        printf("请输入出版社:");
        scanf("%s", book.publisher);
    
        return book;
    }
    
    void printBook(struct Book book)
    {
        printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n", book.title, book.author, book.price, book.date.year, book.date.month, book.date.day, book.publisher);
    }
    
    int main()
    {
        struct Book book1, book2;
    
        printf("请输入第一本书的信息");
        book1 = getInput(book1);
    
        printf("请输入第二本书的信息");
        book2 = getInput(book2);
    
        printf("打印第一本书的信息");
        printBook(book1);
        putchar('\n');
        printf("打印第二本书的信息");
        printBook(book2);
    
        return 0;
    }
    
    
    • 提高执行效率,函数可以不用结构体传址,而是传递指向结构体变量的指针。
    #include <stdio.h>
    //提高执行效率,函数可以不用结构体传址,而是传递指向结构体变量的指针。
    
    struct Date
    {
        int year;
        int month;
        int day;
    };
    
    struct Book
    {
        char title[128];
        char author[40];
        float price;
        struct Date date;
        char publisher[40];
    };
    
    void getInput(struct Book *book);
    void printBook(struct Book *book);
    
    //直接通过指针修改,不需要返回值
    void getInput(struct Book *book)
    {
    
        printf("请输入书名:");
        scanf("%s", book->title);
    
        printf("请输入作者:");
        scanf("%s", book->author);
    
        printf("请输入售价:");
        scanf("%f", &book->price);
    
        printf("请输入出版日期:");
        scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
    
        printf("请输入出版社:");
        scanf("%s", book->publisher);
    
    }
    
    void printBook(struct Book *book)
    {
        printf("书名:%s\n作者:%s\n售价:%.2f\n出版日期:%d-%d-%d\n出版社:%s\n", book->title, book->author, book->price, book->date.year, book->date.month, book->date.day, book->publisher);
    }
    
    int main()
    {
        struct Book book1, book2;
    
        printf("请输入第一本书的信息\n");
        getInput(&book1);
        putchar('\n');
        printf("请输入第二本书的信息\n");
        getInput(&book2);
    
        printf("打印第一本书的信息\n");
        printBook(&book1);
        putchar('\n');
        printf("打印第二本书的信息\n");
        printBook(&book2);
    
        return 0;
    }
    
    
  • 动态申请结构体

    • 使用malloc函数为结构体分配存储空间
    int main(){
        struct Book *book1,*book2;
        
        book1=(struct Book *)malloc(sizeof(struct Book));
        book2=(struct Book *)malloc(sizeof(struct Book));
    
        if(book1==NULL||book2==NULL){
            printf("内存分配失败!\n");
            exit(1);//需要stdlib.h
        }
    
        printf("请输入第一本书的信息");
        getInput(book1);
    
        printf("请输入第二本书的信息");
        getInput(book2);
    
        printf("显示第一本书的信息");
        printBook(book1);
    
        printf("显示第二本书的信息");
        printBook(book2);
        
        free(book1);
    
        free(book2);
    
        return 0;
    }
    

45、单链表1

  • 单链表

    • 链表是一种常见的基础数据结构(在《数据结构和算法》的系列课程中第一个讲的就是它)。根据需求,可以构造出单链表、双链表、循环链表和块状链表等。链表的出现很大程度上弥补了数组的先天不足。

    • 信息域 指针域

      信息域存储数据,指针域指向下一个地址

    • 最后一个指针域指向NULL

  • 单链表插入元素(头插法)

    #include <stdio.h>
    #include <stdlib.h>
    
    //单链表插入元素(头插法)
    
    struct Book
    {
        char title[128];
        char author[40];
        struct Book *next;
    };
    
    void getInput(struct Book *book)
    {
        printf("请输入书名:");
        scanf("%s", book->title);
        printf("请输入作者:");
        scanf("%s", book->author);
    }
    
    void addBook(struct Book **library)
    { //**library指向book结构指针的指针
    
        struct Book *book, *temp;
        book = (struct Book *)malloc(sizeof(struct Book));
    
        if (book == NULL)
        {
            printf("内存分配失败!\n");
            exit(1); //需要stdlib.h
        }
    
        getInput(book);
    
        if (*library != NULL)
        {                      //有书的情况,头指针 *library指向新插入书位置
            temp = *library;   //头指针原来书的位置
            *library = book;   //指向新插入书位置
            book->next = temp; // next指向下一个节点地址,即原来书的位置
        }
    
        else
        {                      //开始没有书的情况 *library=NULL的情况下添加书
            *library = book;   //第一个节点指针不是 NULL了是book节点的
            book->next = NULL; // next指向下一个节点地址,即NULL
        }
    }
    
    void printLibrary(struct Book *library)
    {
        struct Book *book;
    
        int count = 1;
        book = library;
        while (book != NULL)
        {
            printf("-----------Book%d-----------\n", count);
            printf("书名:%s\n", book->title);
            printf("作者:%s\n", book->author);
            book = book->next;
            count++;
        }
    }
    
    void releseLibrary(struct Book *library)
    { //释放资源
        struct Book *temp;
        while (library != NULL)
        {
            temp = library->next;
            free(library);
            library = temp;
        }
    }
    
    int main()
    {
        struct Book *library = NULL; //头指针 
        int ch;
        while (1)
        {
            printf("是否录入书籍信息(Y/N):");
            do
            {
                
                ch = getchar();
            } while (ch != 'Y' && ch != 'N');
            if (ch == 'Y')
            {
                addBook(&library);
            }
            else
            {
                break;
            }
        }
    
        printf("是否打印书籍信息(Y/N):");
        do
        {
            
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');
        if (ch == 'Y')
        {
            printLibrary(library);
        }
        releseLibrary(library);
    
        return 0;
    }
    

46、单链表2

  • 单链表插入元素(尾插法)

    • 只需修改addBook函数的有书情况下的插法
    void addBook2(struct Book **library)
    { //**library指向book结构指针的指针
    
        struct Book *book, *temp;
        book = (struct Book *)malloc(sizeof(struct Book));
        if (book == NULL)
        {
            printf("内存分配失败!\n");
            exit(1); //需要stdlib.h
        }
        getInput(book);
    
        if (*library != NULL)
        { //有书的情况
    
            temp = *library;
            while (temp->next != NULL)
            { //定位 单链表尾部位置
                temp = temp->next;
            }
    
            //插入数据
            temp->next = book;
            book->next = NULL;
        }
    
        else
        {                      
            *library = book;   
            book->next = NULL; 
        }
    }
    
    
    • 优化:定义一个指针始终指向尾部,提高效率
    void addBook2(struct Book **library)
    { //**library指向book结构指针的指针
    
        struct Book *book;
        static struct Book *tail; //记录单链表尾部的位置
        book = (struct Book *)malloc(sizeof(struct Book));
    
        if (book == NULL)
        {
            printf("内存分配失败!\n");
            exit(1); //需要stdlib.h
        }
    
        getInput(book);
    
        if (*library != NULL)
        { //有书的情况
    
            tail->next = book;
            book->next = NULL;
        }
    
        else
        {
            *library = book;
            book->next = NULL;
        }
    
        tail = book;
    }
    
    
  • 搜索单链表,比如输入书名或作者,就可以找到相关的节点数据。

    //搜索单链表
    struct Book *searchBook(struct Book *library, char *target)
    {
        struct Book *book;
    
        book = library;
        while (book != NULL)
        {
            if (!strcmp(book->title, target) || !strcmp(book->author, target))
            { // strcmp相等返回0 。需要string.h
                break;
            }
            book = book->next;
        }
        return book;
    }
    
    //打印查找到的书
    void printBook(struct Book *book)
    {
        printf("书名:%s", book->title);
    
        printf("作者:%s", book->author);
    }
    
    int main()
    {
        struct Book *library = NULL; //头指针
        struct Book *book;
        char input[128];
        int ch;
        while (1)
        {
            printf("是否录入书籍信息(Y/N):");
            do
            {
    
                ch = getchar();
            } while (ch != 'Y' && ch != 'N');
            if (ch == 'Y')
            {
                addBook(&library);
            }
            else
            {
                break;
            }
        }
    
        printf("是否打印书籍信息(Y/N):");
        do
        {
    
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');
        if (ch == 'Y')
        {
            printLibrary(library);
        }
    
        printf("请输入查找的书名或作者");
        scanf("%s", input);
    
        book = searchBook(library, input);
        if (book == NULL)
        {
            printf("很抱歉,未找到");
        }
        else
        {
            do
            {
                printf("已找到符合条件的图书...");
                printBook(book);
            } while ((book = searchBook(book->next, input)) != NULL); //多本图书都匹配的话可以重复找
        }
    
        releseLibrary(library);
    
        return 0;
    }
    
    

47、单链表3

  • 单链表插入节点(中间插入)

    #include <stdio.h>
    #include <stdlib.h>
    
    struct Node
    {
        int value;
        struct Node *next;
    };
    
    // 单链表插入节点(中间插入)
    void insertNode(struct Node **head, int value)
    {
        struct Node *previous; //上一个
        struct Node *current; //当前
        struct Node *new; ///待插入的结点
    
        current = *head;
        previous = NULL;
    
        while (current != NULL && current->value < value)
        {
            previous = current;
            current = current->next;
        }
    
        new = (struct Node *)malloc(sizeof(struct Node));
        if (new == NULL)
        {
            printf("内存分配失败!\n");
            exit(1);
        }
    
        new->value = value;
        new->next = current;
    
        if (previous == NULL)
        { //空链表,current为NULL即*head为NULL的情况下,不执行循环导致 previous为NULL
            *head = new;
        }
        else //不是空链表
        {
            previous->next = new;
        }
    }
    
    void printNode(struct Node *head)
    {
        struct Node *current;
        current = head;
        while (current != NULL)
        {
            printf("%d ", current->value);
            current = current->next;
        }
        printf("\n");
    }
    
    int main()
    {
        struct Node *head = NULL;
        int input;
    
        while (1)
        {
            printf("请输入一个整数(输入-1表示结束):");
            scanf("%d", &input);
            if (input == -1)
            {
                break;
            }
            insertNode(&head, input);
            printNode(head);
        }
        return 0;
    }
    
    
  • 单链表删除节点

    void deleteNode(struct Node **head, int value)
    {
        struct Node *previous;
        struct Node *current;
        current = *head;
        previous = NULL;
        while (current != NULL && current->value != value)
        {
            previous = current;
            current = current->next;
        }
        if (current == NULL)
        {
            printf("找不到匹配的节点");
            return;
        }
        else
        {
            if (previous == NULL)
            {
                *head = current->next;
            }
            else
            {
                previous->next = current->next;
            }
    
            free(current);
        }
    }
    
    int main()
    {
        struct Node *head = NULL;
        int input;
    
        while (1)
        {
            printf("请输入一个整数(输入-1表示结束):");
            scanf("%d", &input);
            if (input == -1)
            {
                break;
            }
            insertNode(&head, input);
            printNode(head);
        }
    
         printf("开始测试删除整数。。。\n");
         while(1){
                  printf("请输入一个整数(输入-1表示结束):");
                  scanf("%d",&input);
                  if(input==-1){
                          break;
                  }
                  deleteNode(&head,input);
                  printNode(head);
    
         } 
        return 0;
    }
    
    

48、内存池

  • 内存碎片

  • 时间上消耗 :应用层——>内核层——>应用层

  • 解决方法: 内存池 ,让程序额外维护的一个缓存区域

    • 使用单链表来维护一个简单地内存池。
    • 只需要将没有用的内存空间地址依次用一个单链表记录下来;当再次需要的时候,从这个单链表中获取即可。
  • 使用方法

    • 当申请内存时检查内存池有没有适合的垃圾内存块,重新使用

    • 想申请内存时

      如果内存池非空,则直接从里面获取空间

      如果内存池为空,则重新申请内存空间

    • 想释放内存时

      如果内存池有空位(小于内存池容量),那么将即将废弃的内存块插入内存池

      如果内存池没有空位,那么就用free等释放掉

49、基础typedef

  • typedef是C语言最重要的关键字之一

  • 世界上第一门高级编程语言 Fortran

  • typedef基本功能:给数据类型(包括结构体)起别名

    • typedef A B;//给A取别名B
    • typedef A B,C;//给A取别名B/C。可以取多个别名
  • define也可以 #define B A

  • 区别

    • define是直接替换
    • typedef是对类型的封装。用来起别名。可以对指针类型取别名。
  • 给结构体取别名

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct Date
    {
        int year;
        int month;
        int day
    } DATE, *PDATE;
    
    int main(void)
    {
        struct Date *date;
    
        date = (PDATE)malloc(sizeof(DATE));
        if (date == NULL)
        {
            printf("内存申请失败\n");
            exit(1);
        }
        date->year = 2022;
        date->month = 4;
        date->day = 22;
    
        printf("%d%d%d\n", date->year, date->month, date->day);
    
        return 0;
    }
    

50、进阶typedef

  • 使用typedef目的一般有两个

    • 给变量起一个容易记住且意义明确的别名
    • 简化一些比较复杂的类型声明。
  • 一些比较复杂的声明语句

    • int (*ptr)[3]; :数组指针, ptr是一个指针,指向三个整型元素的数组(数组起始地址)

      #include <stdio.h>
      
      typedef int (*PTR_TO_ARRAY)[3];
      
      int main(){
          int array[3] = {1,2,3};
          PTR_TO_ARRAY ptr_to_array = &array;
      
          int i;
          for(i=0;i<3;i++){
              printf("%d\n",(*ptr_to_array)[i]);
          }
          return 0;
      }
      
    • int (*fun)(void); :函数指针,指向一个参数是void,返回值为int 的函数

      #include <stdio.h>
      
      typedef int (*PTR_TO_FUN)(void);
      
      int fun(void)
      {
          return 123;
      }
      
      int main(void)
      {
          PTR_TO_FUN ptr_to_fun = &fun;
          printf("%d\n", (*ptr_to_fun)());
      
          return 0;
      }
      
    • int *(*array[3])(int); :

      • (*array[3])是一个指针数组并用A替换,则int *A(int)是一个指针函数
      #include <stdio.h>
      
      // int \*(*array[3])(int); :
      //   (\*array[3])是一个指针数组并用A替换,则int *A(int)是一个指针函数
      
      typedef int *(*PTR_TO_FUN)(int);
      
      int *funA(int num)
      {
          printf("%d\t", num);
          return &num; //返回无意义,只是测试
      }
      int *funB(int num)
      {
          printf("%d\t", num);
          return &num;
      }
      int *funC(int num)
      {
          printf("%d\t", num);
          return &num;
      }
      
      int main(void)
      {
          PTR_TO_FUN array[3] = {&funA, &funB, &funC}; //&可省略,函数就是指向这个函数的地址
          int i;
          for (i = 0; i < 3; i++)
          {
              printf("addr pf num: %p\n", (*array[i])(i));
          } 
      
          return 0;
      }
      
    • void (*funA(int, void (*funB)(int)))(int);

51、共用体

  • 在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。
union 共用体名称
{     
    共用体成员1;
    共用体成员2;
    共用体成员3;
    ...
};
  • 共用体所有成员共享同一个内存地址。地址长度足以容纳最大成员的长度。也会受内存对齐影响
#include <stdio.h>
#include <string.h>

typedef int *(*PTR_TO_FUN)(int);

union Test
{
    int i;
    double pi;
    char str[10];
};

int main()
{
    union Test test;
    test.i = 520;
    test.pi = 3.14;

    strcpy(test.str, "FishC.com");
    printf("addr of test.i:%p\n", &test.i);
    printf("addr of test.pi:%p\n", &test.pi);
    printf("addr of test.str:%p\n", &test.str); //输出结果几个地址%p相同
    printf("test.i:%d\n", test.i);
    printf("test.pi:%.2f\n", test.pi);
    printf("test.str:%s\n", test.str); //输出结果只有str正确 ,因为前两个被覆盖了
    printf("size of int:%d\n", sizeof(int));
    printf("size of double:%d\n", sizeof(double));
    printf("size of test.str:%d\n", sizeof(test.str));
    printf("size of test:%d\n", sizeof(test));

    return 0;
}
  • 定义共用体类型变量

    • 定义共用体跟定义结构体的语法相似,可以先声明一个共用体类型,再定义共用体变量:
    union data
    {
        int i;
        char ch;
        float f;
    };
    union data a,b,c;
    
    • 也可以在声明的同时定义共用体变量:
    union data
    {
        int i;
        char ch;
        float f;
    } a, b, c;
    
    • 共用体的名字不是必须的
    union {
        int a;
        char b;
    } a,b,c;
    
  • 初始化共用体

    union data a = {520}//初始化第一个成员
    union data b = a;//使用一个共用体初始化另一个共用体
    union data c = {.ch = 'c'};//C99新特性,指定初始化成员
    
  • 拓展:https://blog.csdn.net/xy010902100449/article/details/48292527

    • 共用体常用来节省内存,特别是一些嵌入式编程,内存是非常宝贵的!
    • 共用体也常用于操作系统数据结构或硬件数据结构!
    • union 在操作系统底层的代码中用的比较多,因为它在内存共享布局上方便且直观。所以网络编程,协议分析,内核代码上有一些用到 union 都比较好懂,简化了设计。
    • 共用体(union)是一种数据格式,它能够存储不同类型的数据,但是只能同时存储其中的一种类型。

52、枚举类型

  • 如果一个变量只有几种可能的值,那么就可以将其定义为枚举(enumeration)类型

  • 声明枚举类型

  • enum 枚举类型名称 {枚举值名称, 枚举值名称…};

  • 定义枚举变量

    • enum 枚举类型名称 枚举变量1,枚举变量2;
  • 代码实例

    #include <stdio.h>
    #include <time.h>
    
    //枚举类型
    
    int main()
    {
        enum Week
        {
            sun,
            mon,
            tue,
            wed,
            thu,
            fri,
            sat
        }; //默认第一个是0,此时sun=0,mon=1。。。 。也可以指定值,后面的会依次自动加1。enum Week {sun=1,mon,tue,wed,thu,fri,sat};
        enum Week today;
    
        struct tm *p; // tm结构体 包含了当地时间和日期,其中成员变量int tm_wday 表示星期几范围0-6
        time_t t;
    
        time(&t);          // time函数返回表示当前时间的time_t
        p = localtime(&t); // localtime函数将time_t类型的值转化为具体的本地时间和日期
    
        today = (enum Week)p->tm_wday;
    
        switch (today)
        {
        case mon:
        case tue:
        case wed:
        case thu:
        case fri:
            printf("工作日\n");
            break;
        case sat:
        case sun:
            printf("休息日\n");
            break;
        default:
            printf("Error\n");
        }
    
        return 0;
    }
    

53、位域

  • 单片机(Microcontrollers)是一种集成电路芯片

    • 是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完兽的微型计算机系统,在工业控制领域广泛应用。

    • 单片机 集成电路芯片,把CPU、RAM、ROM、I/O等集成到一块硅片上构成小而完善的微型计算机系统。

  • 位域,或称位段、位字段。

    • 位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据,节约空间
    • 允许把一个字节拆开来使用,把一个字节中的二进制位划分为一个区域并指定每一个区域的位数,每一个区域可以命名然后在程序中单独进行使用
    • 例如,对一个字节划分为几个部分并命名,这几部分就是位域
  • 使用位域的做法是 在结构体定义时,在结构体或成员后面使用冒号( : )和数字来表示该成员所占的位byte数。

    • 位域的宽度不能超过它所依附的数据类型的长度
    #include <stdio.h>
    //使用位域的做法是 在结构体定义时,在结构体或成员后面使用冒号( : )和数字来表示该成员所占的位byte数。
    int main()
    {
        struct Test
        {
            unsigned int a : 1;
            unsigned int b : 1;
            unsigned int c : 2; 
        };
        struct Test test;
        test.a = 0;
        test.b = 1;
        test.c = 2;
        printf("a=%d,b=%d,c=%d\n", test.a, test.b, test.c);//a=0,b=1,c=2
        printf("size of test=%d", sizeof(test)); //size of test=4
    
        return 0;
    }
    
  • 无名位域

    • 位域成员可以没有名称,只要给出数据类型和宽度即可。
    struct Test{
        unsigned int x:100;
        unsigned int  :100
    }
    

54、位操作

  • c语言并没有规定一个字节有几位(一般是8位),只是规定“可寻址的数据存储单位,其尺寸必须可以容纳运行环境的基本字符集的任何成员”。一般是由编译器规定在limits.h中

  • 逻辑位运算符
    在这里插入图片描述

  • 按位取反( ~ )

    • 逻辑位运算符中优先级最高的是按位取反运算符,它的运算符是一个~符号,作用是将1变成0,将0变成1:
  • 按位与( & )

    • 优先级第二高的是按位与运算符,它的运算符是一个&符号(逻辑与是两个&符号)
  • 按位异或 ( ^ )

    • 优先级排第三的是按位异或运算符,它的运算符是一个^符号,只有当两个操作数对应的二进制位不同时,它的结果才为1,否则为0:
  • 按位或( | )

  • 逻辑位运算符中优先级最低的是按位或运算符,它的运算符是一个|符号(逻辑或是两个|符号)

  • 和赋值号结合

    • 这四个运算符,除了按位取反只有一个操作数之外,其它三个都可以跟赋值号(=)结合到一块,使得代码更加简洁
  • 代码示例

    #include <stdio.h>
    
    //位运算
    int main()
    {
    
        int mask = 0xFF;   // 0x表示是16进制  FF是15 15即0000 0000 0000 0000 1111 1111
        int v1 = 0xABCDEF; // 10 11 12 13 14 15 即   1010 1011 1100 1101 1110 1111
        int v2 = 0xABCDEF;
        int v3 = 0xABCDEF;
    
        v1 &= mask; //即v1=v1&mask;
        v2 |= mask;
        v3 ^= mask;
    
        printf("v1=0x%X\nv2=0x%X\nv3=0x%X\n", v1, v2, v3);
    
        return 0;
    }
    
    
    //v1=0xEF         0000 0000 0000 0000 1110 1111        
    //v2=0xABCDFF     1010 1011 1100 1101 1110 1111
    //v3=0xABCD10     1010 1011 1100 1101 0001 0000
    

55、移位和位操作的应用

  • C语言除了提供四种逻辑位运算符之外,还提供了可以将某个变量中所有的二进制位进行左移或右移的运算符——移位运算符。

  • 左移位运算符 <<

  • 左边操作数是即将被移位的数据,右边是要移动的位数。移出的数据扔掉,移动后空位用0填充

  • 右移位运算符 >>

  • 左边操作数是即将被移位的数据,右边是要移动的位数。移出的数据扔掉,移动后空位用0填充

  • 和赋值号结合

    #include <stdio.h>
    
    int main()
    {
        int value = 1;
        printf("----------左移---------\n");
    
        while (value < 1024)
        {
            value <<= 1; // value = value << 1;
            printf("value=%d\n", value);
        }
        printf("\n");
        printf("----------右移---------\n");
        value = 1024;
        while (value > 1)
        {
            value >>= 2;
            printf("value = %d\n", value);
        }
        return 0;
    }
    
    /*
    -----左移---------
    value=2
    value=4
    value=8
    value=16
    value=32
    value=64
    value=128
    value=256
    value=512
    value=1024
    
    -----右移---------
    value = 256
    value = 64
    value = 16
    value = 4
    value = 1
    */
    
  • 一些未定义行为

    • 左移、右移运算符右边的操作数如果是为负数,或者右边的操作数大于左边操作数支持的最大宽度,那么表达式的结果均是属于“未定义行为”。
    • 左边的操作数是有符号还是无符号数其实也对移位运算符有着不同的影响。无符号数肯定没问题,因为这时候变量里边所有的位都用于表示该数值的大小。但如果是有符号数,那就要区别对待了,因为有符号数的左边第一位是符号位,所以如果恰好这个操作数是个负数,那么移动之后是否覆盖符号位的决定权还是落到了编译器上。

56、打开和关闭文件

  • 文件

    • “计算机文件(或称文件、电脑档案、档案),是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存。需要注意的是,存储于长期存储设备的文件不一定是长期存储的,有些也可能是程序或系统运行中产生的临时数据,并于程序或系统退出后删除。”——维基百科
  • linux/unix

    • 万物皆文件 Everything is a file
    • KISS原则 Keep it simple,stupid.
  • 文本文件和二进制文件

    • 文本文件:由一些字符的序列组成的文件
    • 二进制文件:通常指除了文本文件以外的
    • 从本质上来说,文本文件也是属于二进制文件的,只不过它存放的是相应的字符编码值。。
  • 打开和关闭文件

    • 读:从文件中获取数据
    • 写:将数据写入到文件里
    • 在完成读写操作后,必须将其关闭
  • fopen(路径,模式),用于打开文件并返回函数指针

    • 路径参数可以是相对路径(./a.txt),也可以是绝对路径(/home/user1/a.txt)。如果只给出文件名(a.txt)则表示该文件在当前文件夹。

    • 如果文件打开成功,返回一个指向FILE结构的文件指针;

    • 如果文件打开失败,返回NULL并设置errno为指定的错误

  • 文件的打开模式

    模式描述
    “r”1 、以只读的模式打开一个文本文件,从文件头开始读取; 2、 该文本文件必须存在
    “w”1、以只写的模式打开一个文本文件,从文件头开始写入 ;2、如果文件不存在则创建一个新的文件; 3、如果文件已存在则将文件的长度截断为 0(重新写入的内容将覆盖原有的所有内容)
    “a”1、以追加的模式打开一个文本文件,从文件末尾追加内容; 2、如果文件不存在则创建一个新的文件
    “r+”1、以读和写的模式打开一个文本文件,从文件头开始读取和写入; 2、该文件必须存在; 3、该模式不会将文件的长度截断为 0(只覆盖重新写入的内容,原有的内容保留)
    “w+”1、以读和写的模式打开一个文本文件,从文件头开始读取和写入; 2、如果文件不存在则创建一个新的文件; 3、如果文件已存在则将文件的长度截断为 0(重新写入的内容将覆盖原有的所有内容)
    “a+”1、以读和追加的模式打开一个文本文件; 2、如果文件不存在则创建一个新的文件; 3、读取是从文件头开始,而写入则是在文件末尾追加
    “b”1、与上面 6 中模式均可结合(“rb”, “wb”, “ab”, “r+b”, “w+b”, “a+b”); 其描述的含义一样,只不过操作的对象是二进制文件
    • 打开方式要区分开主要是因为换行符。C语言换行符\n,unix系统\n,windows用\r\n,mac用\r。如果在windows系统以文本文件打开,读会将\r\n自动转换为\n,写会\n转换为\r\n,但是以二进制模式打开则不会做转换。如果在unix系统二者是一样的。
  • 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
    
        FILE *fp;
        int ch;
        if ((fp = fopen("hello.txt", "r")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE);
        }
        while ((ch = getc(fp)) != EOF) // EOF通常是-1,表示文件结束
        {
            putchar(ch);
        }
        fclose(fp); //关闭,释放缓冲区,如果成功关闭,返回值是0,若失败返回值为EOF
    
        return 0;
    }
    

57、读写文件1

  • 顺序读写和随机读写

  • 读单个字符

    • fgetc:
    • getc
  • fgetc 函数返回来自stream(流)中的下一个字符

    • 函数用于从文件流中读取下一个字符并推进文件的位置指示器(用来指示接下来要读写的下一个字符的位置) 。

    • 函数原形:

      #include <stdio.h>
      
      int fgetc( FILE *stream );
      
    • 参数:stream,该参数是一个FILE对象的指针,指定一个待读取的文件流

    • 返回值:

      • 将读取到的unsigned char类型转换为int并返回
      • 如果到达文件尾或者发生错误时返回EOF.
  • 备注:

    • fgetc 函数和 getc 函数两个的功能和描述基本上是一模一样的,它们的区别主要在于实现上:fgetc 是一个函数;而 getc 则是一个宏的实现。
    • 一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。
    • 由于 getc 是由宏实现的,对其参数可能有不止一次的调用,所以不能使用带有副作用(side effects)的参数。所谓带有副作用的参数就是指 getc(fp++) 这类型的参数,因为参数在宏的实现中可能会被调用多次,所以你的想法是 fp++,而副作用下产生的结果可能是 fp++++++。
  • 写单个字符

    • fputc
    • putc
  • fputc 函数用于将一个字符写入到指定的文件中并推进文件的位置指示器(用来指示接下来要读写的下一个字符的位置)。

    • 函数原形

      #include <stdio.h>
      
      int fputc(int c, FILE *stream);
      
    • 参数:

      • c : 指定待写入的字符
      • stream : 该参数是一个 FILE 对象的指针,指定一个待写入的文件流
    • 返回值:

      • 如果函数没有错误,返回值是写入的字符;
      • 如果函数发生错误,返回值是EOF
  • 备注:

    • fputc 函数和 putc 函数两个的功能和描述基本上是一模一样的,它们的区别主要在于实现上:fputc 是一个函数;而 putc 则是一个宏的实现。
  • 代码示例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        FILE *fp1, *fp2;
        int ch;
    
        if ((fp1 = fopen("hello.txt", "r")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        if ((fp2 = fopen("world.txt", "w")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        while ((ch = fgetc(fp1)) != EOF) // EOF通常是-1
        {
            fputc(ch, fp2);
        }
    
        fclose(fp1);
        fclose(fp2);
    
        return 0;
    }
    
  • 读写整个字符串

    • fgets
    • fputs
  • fgets 函数用于从指定文件中读取字符串

    • fgets 函数最多可以读取 size - 1 个字符,因为结尾处会自动添加一个字符串结束符 ‘\0’。当读取到换行符(‘\n’)或文件结束符(EOF)时,表示结束读取(‘\n’ 会被作为一个合法的字符读取,EOF不会)。

    • 函数原型:

      #include <stdio.h>
      
      char *fgets(char *s, int size, FILE *stream);
      
    • 参数:

      • s :字符型指针,指向用于存放读取字符串的位置
      • size : 指定读取的字符数(包括最后自动添加的 ‘\0’)
      • stream : 该参数是一个 FILE 对象的指针,指定一个待操作的数据流
    • 返回值:

      • 如果函数调用成功,返回 s 参数指向的地址。
      • 如果在读取字符的过程中遇到 EOF,则 eof 指示器被设置;如果还没读入任何字符就遇到这种 EOF,则 s 参数指向的位置保持原来的内容(s不变),函数返回 NULL。
      • 如果在读取的过程中发生错误,则 error 指示器被设置,函数返回 NULL,但 s 参数指向的内容可能被改变。
  • fputs 函数用于将一个字符串写入到指定的文件中,表示字符串结尾的 ‘\0’ 不会被一并写入。

    • 函数原型

      #include <stdio.h>
      
      int fputs(const char *s, FILE *stream);
      
    • 参数

      • s : 字符型指针,指向用于存放待写入字符串的位置
      • stream: 该参数是一个 FILE 对象的指针,指定一个待操作的数据流
    • 返回值:

      • 如果函数调用成功,返回一个非 0 值;(此处错误。API文档里是成功时返回非负值, 失败时返回EOF)
      • 如果函数调用失败,返回EOF
  • feof 函数用于检测文件的末尾指示器(end-of-file indicator)是否被设置。

    • 函数原型:

      #include <stdio.h>
      
      int feof(FILE *stream);
      
    • 参数:stream, 该参数是一个 FILE 对象的指针,指定一个待检测的文件流

    • 返回值:

      • 如果检测到末尾指示器(end-of-file indicator)被设置,返回一个非 0 值;
      • 如果检测不到末尾指示器(end-of-file indicator)被设置,返回值为 0。
    • feof 函数仅检测末尾指示器的值,它们并不会修改文件的位置指示器。

    • 文件末尾指示器只能使用 clearerr 函数清除。

  • 代码示例:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAX 1024
    int main()
    {
    
        FILE *fp;
        char buffer[MAX];
    
        if ((fp = fopen("hello.txt", "w")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
    
        fputs("hello 1\n", fp);
        fputs("hello 2\n", fp);
        fputs("hello 3\n", fp);
        fclose(fp); //关闭,写入文件,释放缓冲区 。
        //需要fclose,不关的话,文件指示器指向文件末尾,影响后面操作 。而且还在缓冲区,还没写入文件。
    
        if ((fp = fopen("hello.txt", "r")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        while (!feof(fp)) // feof检测不到末尾,返回0 。所以这里是未到末尾 。
        {
            fgets(buffer, MAX, fp); //每次最多读取MAX-1个字符,因为结尾自动添加\0。 读取到\n或EOF会结束这一行,\n也会被作为合法字符读取,EOF不会 。
            printf("%s", buffer);
        }
        fclose(fp);
    
        return 0;
    }
    //hello 1
    //hello 2
    //hello 3
    //hello 3
    
    //出现问题 第四行打印第三行内容,但文件hello中并没有第四行 ,只有回车。
    //原因 fgets如果还没读入任何字符就遇到 EOF,则 s 参数指向的位置保持原来的内容(s不变),函数返回 NULL。
    //解决  if(!fgets(buffer,MAX,fp)==NULL) printf("%s",buffer);
    

58、读写文件2

  • 格式化读写文件

    • fscanf
    • fprintf
    • 和scanf、printf相似,只不过是从文件读取、输出到文件
    • 拓展 为什么scanf中用&取地址符,而printf不用。因为scanf本来就是一个函数,用取地址后就能将接受的数据存在这个地址里,在scanf函数外也能用。指针在函数内就是通过访问所指向地址的值来进行改写,并且能延续到函数外。
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main()
    {
        FILE *fp;
        struct tm *p;
        time_t t;
        time(&t);
    
        p = localtime(&t);
    
        if ((fp = fopen("date.txt", "w")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
    
        fprintf(fp, "%d-%d-%d", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
        fclose(fp);
    
        int year, month, day;
        if ((fp = fopen("date.txt", "r")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        fscanf(fp, "%d-%d-%d", &year, &month, &day);
        printf("%d-%d-%d", year, month, day);
        fclose(fp);
    
        return 0;
    }
    
    
  • 以二进制方式读写文本文件

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        FILE *fp;
        if ((fp = fopen("text.txt", "wb")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        fputc('5', fp);
        fputc('2', fp);
        fputc('0', fp);
        fputc('\n', fp);
        fclose(fp);
    
        return 0;
    }
    
    
  • 二进制读写文件

    • fread
    • fwrite
  • fread 函数用于从指定的文件中读取指定尺寸的数据。

    • 函数原型

      #include <stdio.h>
      
      size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 参数

      参数含义
      ptr指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
      size指定要读取的每个元素的尺寸,最终尺寸等于 size * nmemb
      nmemb指定要读取的元素个数,最终尺寸等于 size * nmemb
      stream该参数是一个 FILE 对象的指针,指定一个待读取的文件流
    • 返回值:

      • 返回值是实际读取到的元素个数(nmemb);
      • 如果返回值比 nmemb 参数的值小,表示可能读取到文件末尾或者有错误发生(可以使用 feof 函数或 ferror 函数进一步判断)。
  • fwrite 函数用于将指定尺寸的数据写入到指定的文件中。

    • 函数原型

      #include <stdio.h>
      
      size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
      
    • 参数

      参数含义
      ptr指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
      size指定要写入的每个元素的尺寸,最终尺寸等于 size * nmemb
      nmemb指定要写入的元素个数,最终尺寸等于 size * nmemb
      stream该参数是一个 FILE 对象的指针,指定一个待写入的文件流
    • 返回值:

      • 返回值是实际写入到文件中的元素个数(nmemb);
      • 如果返回值与 nmemb 参数的值不同,则有错误发生。
  • 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct Date
    {
        int year;
        int month;
        int day;
    };
    
    struct Book
    {
        char name[40];
        char author[40];
        char publisher[40];
        struct Date date;
    };
    
    int main()
    {
        FILE *fp;
        struct Book *book_for_write, *book_for_read;
        book_for_write = (struct Book *)malloc(sizeof(struct Book));
        book_for_read = (struct Book *)malloc(sizeof(struct Book));
        if (book_for_write == NULL || book_for_read == NULL)
        {
            printf("内存分配失败");
            exit(EXIT_FAILURE);
        }
        strcpy(book_for_write->name, "带你学c带你飞");
        strcpy(book_for_write->author, "小甲鱼");
        strcpy(book_for_write->publisher, "清华大学出版社");
    
        book_for_write->date.year = 2017;
        book_for_write->date.month = 11;
        book_for_write->date.day = 11;
    
        if ((fp = fopen("file.txt", "w")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        fwrite(book_for_write, sizeof(struct Book), 1, fp);
        fclose(fp);
    
        if ((fp = fopen("file.txt", "r")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE); // exit() 需要stdlib.h
        }
        fread(book_for_read, sizeof(struct Book), 1, fp);
    
        printf("书名:%s\n", book_for_read->name);
        printf("作者:%s\n", book_for_read->author);
        printf("出版社:%s\n", book_for_read->publisher);
        printf("出版日期:%d-%d-%d\n", book_for_read->date.year, book_for_read->date.month, book_for_read->date.day);
        fclose(fp);
    
        return 0;
    }
    

59、随机读写文件

  • ftell返回给定流 stream 的当前文件位置。

    #include <stdio.h>
    #include <stdlib.h>
    
    //ftell返回给定流 stream 的当前文件位置,返回自是long
    int main()
    {
        FILE *fp;
        if ((fp = fopen("hello.txt", "w")) == NULL)
        {
            printf("文件打开失败!\n");
            exit(EXIT_FAILURE);
        }
    
        printf("%ld\n", ftell(fp));  //0
        fputc('F', fp);
    
        printf("%ld\n", ftell(fp));  //1
        fputs("ishC\n", fp);
    
        printf("%ld\n", ftell(fp));  //7 【linux中是6,因为win系统换行 \r\n 所以1+6】
        fclose(fp);
    
        return 0;
    }
    
    
  • rewind移动到文件头

    • 上面程序添加
    rewind(fp);   //rewind将位置指示器移动到文件头
    fputs("AA",fp);//文件内容:AAshC
    
    • 会把原来的内容覆盖掉
  • fseek用于设置文件流的位置指示器

    • 函数原型

      #include<stdio.h>
      
      int fseek(FILE *stream,long int offset,int whence);
      
    • 参数

      参数含义
      stream该参数是一个FILE对象的指针,指定一个待操作的文件流
      offset指定从whence参数的位起置偏移多少个字节
      whence这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:【SEEK_SET: 文件的开头】;【SEEK_CUR: 文件指针的当前位置】;【SEEK_END :文件的末尾】
    • 返回值:如果成功,则该函数返回零,否则返回非零值。

  • 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    
    #define N 4
    
    struct Stu
    {
        char name[24];
        int num;
        float score;
    } stu[N], sb;
    
    int main()
    {
        FILE *fp;
        int i;
    
        if ((fp = fopen("score.txt", "w")) == NULL)
        {
            printf("打开文件失败!\n");
            exit(EXIT_FAILURE);
        }
    
        for (i = 0; i < N; i++)
        {
            printf("请开始录入成绩(格式:姓名 学号 成绩)");
            scanf("%s %d %f", stu[i].name, &stu[i].num, &stu[i].score);
        }
    
        fwrite(stu, sizeof(struct Stu), N, fp);
        fclose(fp);
    
        if ((fp = fopen("score.txt", "rb")) == NULL)
        {
            printf("打开文件失败!");
            exit(EXIT_FAILURE);
        }
    
        fseek(fp, sizeof(struct Stu), SEEK_SET);
        fread(&sb, sizeof(struct Stu), 1, fp);
        printf("%s(%d)的成绩是:%.2f\n", sb.name, sb.num, sb.score);
        fclose(fp);
    
        return 0;
    }
    
    
  • 可移植性问题

    • 对于以二进制模式打开的文件,fseek在某些操作系统中可能不支持SEEK_END位置。
    • 对于以文本模式打开的文件,feek函数的whence参数只能取SEEK_SET才是有意义的,并且传递给offset参数的值要么是0,要么是上一次对同一个文件调用ftell函数获取的返回值。

60、标准流和错误处理

  • 文件流

    • 标准输入 stdin
    • 标准输出 stdout
    • 标准错误输出 stderr
  • 标准错误输出 stderr 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        FILE *fp;
    
        if ((fp = fopen("压根都不存在的文件.txt", "r")) == NULL)
        {
            fputs("打开文件失败!\n", stderr); 
            exit(EXIT_FAILURE);
        }
        fclose(fp);
        return 0;
    }
    
  • 重定向

    • 由于标准输出和标准错误输出通常都是直接打印到屏幕上,为了区分它们,可以使用Linux shell的重定向功能:

      (1)重定向标准输入使用 <

      (2)重定向标准输出使用 >

      (3)重定向标准错误输出使用 2>

  • 错误处理

    • 检测错误指示器,使用ferror函数
    • 使用clearerr函数可以人为的清除文件末尾指示器和错误指示器状态
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        FILE *fp;
        int ch;
        if ((fp = fopen("0_hello.txt", "r")) == NULL)
        {
            fputs("打开文件失败!\n", stderr);
            exit(EXIT_FAILURE);
        }
        while (1)
        {
            ch = fgetc(fp);
            if (feof(fp))
            {
                break;
            }
            putchar(ch);
        }
    
        fputc('c', fp); //因为只读的,所以会失败,触发ferro
    
        if (ferror(fp))
        {
            fputs("出错了,这条消息输出到错误输出流\n", stderr);
        }
    
        clearerr(fp); 
        if (feof(fp) || ferror(fp))
        {
            printf("123456"); //不打印。末尾指示器和错误指示器被clear清除掉,不会触发
        }
        fclose(fp);
    
        return 0;
    }
    
    • ferror函数只能检测是否出错,但无法获取错误原因。不过,大多数系统函数在出现错误时会将错误原因就在errno中。
    • perror函数可以直观的打印出错误原因。会自己加"冒号空格错误原因"。例如perror(“错误原因是”);输出为"错误原因是: Bad file descriptor"
    • strerror函数直接返回错误码对应的错误消息,参数是errno。
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    
    int main()
    {
        FILE *fp;
    
        if ((fp = fopen("压根都不存在的文件.txt", "r")) == NULL)
        {
            printf("打开文件失败,原因(错误代码)是:%d\n",errno); // errno.h
            //打开文件失败,原因(错误代码)是:2
            
            perror("打开文件失败,原因是");//不需要errno.h
            //打开文件失败,原因是: No such file or directory
    
            fprintf(stderr,"出错,原因竟是 -> %s <- 这个!",strerror(errno));
            //出错,原因竟是 -> No such file or directory <- 这个!
    
            exit(EXIT_FAILURE);
        }
        fclose(fp);
        return 0;
    }
    
    

61、IO缓冲区

  • IO缓冲区

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        FILE *fp;
        if ((fp = fopen("12_output.txt", "w")) == NULL)
        {
            perror("打开文件失败,原因");
            exit(EXIT_FAILURE);
        }
        fputs("123456\n", fp); //写在了缓冲区里,并没有写入文件
        getchar(); 
        
        fclose(fp);
        return 0;
    }
    
  • 标准IO提供三种类型的缓冲模式

    • 按块缓存:也称为全缓存,在填满缓冲区后才进行实际的设备读写操作;
    • 按行缓存:是指在接收到换行符\n之前,数据都是先缓存在缓冲区的;
    • 不缓存:允许直接读写设备上的数据
  • fflush函数可以强制刷新缓冲区

  • setvbuf 函数定义流 stream 应如何缓冲。

    • 函数原型

      #include<stdio.h>
      
      int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
      
    • 参数

      参数含义
      stream该参数是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
      buf1、指定一个用户分配的缓冲区
      2、如果该参数为NULL,那么函数会自动分配一个指定尺寸的缓冲区
      mode指定数据流的缓存模式:【_IOFBF:按块缓存】【_IOLBF:按行缓存】【_IONBF:不缓存】
      size指定缓冲区的尺寸(字节)
    • 返回值:如果成功,则该函数返回 0,否则返回非零值。

  • 模式 描述

    • _IOFBF 全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。、
    • IOLBF 行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
    • _IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。
  • 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        char buff[1024];
    
        memset(buff, '\0', sizeof(buff)); // memset使用一个常量字节填充内存空间。需要string.h
    
        setvbuf(stdout, buff, _IOFBF, 1024); //修改为_IONBF 后直接输出
    
        fprintf(stdout, "welcome\n");
    
        fflush(stdout); // 刷新缓冲区会立即输出
    
        fprintf(stdout, "输入任意字符后才会显示该行字符\n");
    
        getchar();
    
        return 0;
    }
    
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Fly-ping

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

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

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

打赏作者

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

抵扣说明:

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

余额充值