• 【黄色手套22】7话:预处理和文件操作


    预处理是在编译前所做的工作,编译器自动调用预处理程序对源码中以 ‘#’ 开头的预处理部分进行处理;处理完毕后,进入源码的编译阶段。

    宏定义、文件包含、条件编译三者都属于预处理部分。

    -------------------

    目录

    宏定义:

    常量的定义:

    文件操作:

    什么是文件?

    文件的概念:

    指向文件的指针:

    内存和外存的区别:

    文件指针的定义:

    文件的打开和关闭:

    文件操作函数:

    文件包含:

    C程序的分文件编写:

    条件编译:


    宏定义:

    宏定义,又称宏替换,自定义一个宏名(符合标识符的命名规则)
    用来替换任意数据、标识符或者表达式。

    即,使用某些定义的标识符,在编译之前对源代码进行替换处理

    1. 无参宏定义

        宏定义关键字:#define

    定义无参宏的基本格式:#define 宏名 宏替换

        // 末尾无分号

        // 可以没有宏名

    例如:

    1. #include
    2. #define I int //没有分号
    3. #define Pi 3.1415926
    4. #define N 10
    5. #define M (10+20)
    6. #define Enter putchar('\n')
    7. int main()
    8. {
    9. I a=1;
    10. printf("%d\n", a); // 输出1
    11. printf("%d\n",N*M); // 输出300
    12. Enter;
    13. return 0;
    14. }

    ■.  不能给宏定义的常量进行赋值操作:

         如:N=6;× (报错:表达式必须是可修改的左值)

    2. 带参宏定义

    定义带参宏的基本格式:#define 宏名(参数列表) 宏替换

      ■. 带参宏可以像函数一样调用:

    1. #include
    2. #define MAX(x,y) x>y?x:y
    3. #define ADD(x,y) (x+y)
    4. #define Cir_C(r) 2*Pi*r // 求圆的周长
    5. #define Cir_S(r) Pi*r*r // 求圆的面积
    6. int mian()
    7. {
    8. printf("%d\n", MAX(10, 20)); // 输出20
    9. printf("%d\n", ADD(20, 30)*ADD(30, 30)); // 输出3000
    10. printf("%f\n", Cir_C(4));
    11. printf("%f\n", Cir_S(4));
    12. return 0;
    13. }

      ■. 宏定义是一种替换操作,其在替换完成前并不会计算。

      ■. 宏定义尽量用大写,使其在程序中容易辨别区分。

      ■. typedef只给数据类型取别名(后期处理)

          define什么都可以(预先处理)

    常量的定义:

    关键字const

    定义常量的基本格式:

    const 数据类型 常量名 = 常量值;

     定义成常量后,其值不可被改变:

     如,const int a=10;

            a=100;(报错:表达式必须是可修改的左值)

    ___________

    文件操作:

    什么是文件?

    文件有不同的类型,在程序设计中,主要有两种文件。

    (1)程序文件:

                    包括源程序文件(.c)、目标文件(.obj)、可执行文件(.exe)等。

                    这一类型的文件主要用于存储程序代码。

    (2)数据文件:

                    此文件的内容不是程序,而是程序运行时读写的数据。

                    比如程序运行过程中输出到磁盘或其他设备上的数据,或在程序运行过程中供程序读                      取的数据。

    ■.C语言的文件操作主要是对数据文件的操作;

        之前,程序中所处理的数据输入和输出都是以终端为对象的,是从键盘输入数据,运行结果输出到终端显示器上;

        实际上,我们可以将一些数据(程序运行的最终结果或中间数据)保存起来,方便以后需要时再调用;而这时,就需要用到磁盘文件。

       

    文件的概念:

    (1)文件名:

                    每一个文件都需要一个唯一的文件标识,以便用户使用。

                    文件标识也称为文件名,它由三部分组成:

                    其一是文件路径----表示文件在外存设备中的存储位置,文件路径是唯一标识文件在外存中的位置;

                    其二是文件名主干----表示文件的名字,可由用户自定义,命名规则遵循标识符的命名规     则;

                    其三是文件后缀----表示文件的性质,也称为文件的格式,用于描述文件的类型。

    (2)文件的分类:

                    根据数据的组成形式,数据文件可分为ASCII文件和二进制文件。

                    数据在内存中是以二进制形式存储的,如果不加转换输出到外存中,就是二进制文件。

                         (可以认为它是存储在内存的数据的映像,所以又称为映像文件)

                    如果要求在外存上以ASCII码形式存储,就需要在存储前进行转换。

                         (每一个字节存放一个字符的ASCII码,ASCII文件又称为文本文件)

    (3)文件存储方式的区别:

                    一个数据在磁盘上存储,字符一律以ASCII形式存储,数值型数据可以兼用二进制形式存储(如,整数10,000,用ASCII形式存储在磁盘上占五个字节(一个字符一个字节);用二进制行书存储在磁盘上只占四个字节(00000000 00000000 00100111 00010000))。

                    用ASCII形式存储时字符与字节一一对应,一个字符一个字节,便于逐个处理,但占用存储空间较多,而且处理时要花费转换时间(ASCII码与二进制的转换)。

                    用二进制形式存储就相当于把内存中的数据内容直接存储在磁盘上,由于不需要转换,所以二进制文件便于计算机处理。

    ______________

    指向文件的指针:

            C语言要想操作内存,需要用到各种数据类型的指针。

          (如,int *p;char *p;float *p等)

      例如,整型数据类型的指针:

    1. #include
    2. void swap(int *x, int *y)
    3. {
    4. *x = *x + *y;
    5. *y = *x - *y;
    6. *x = *x - *y;
    7. }
    8. int main()
    9. {
    10. int a = 5, b = 6;
    11. swap(&a,&b);
    12. printf("%d %d\n",a,b); //输出 6 5
    13. return 0;
    14. }

            而文件也是需要调用到内存中才能够使用,

            所以就相应地需要用到“指向文件的指针”,

            即“文件类型的指针”,简称“文件指针”,(如,FILE *fp)

         

             每一个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息

           (如,文件的名字、文件的状态、文件的位置等),

         这些信息保存在一个结构体变量中,此结构体类型是由系统自动声明的,取别名为FILE

             其被包含在stdio.h头文件中。

           

    内存和外存的区别:

    内存和外存是两个概念

            CPU能直接访问的存储器称之为内存储器(内存),它包括cache和主存器;

            CPU不能直接访问的存储器称为外存储器(外存);

            CPU通过内存储器调入外存储器上的信息来间接访问外存储器;

            为什么CPU要有内外之分呢?保障CPU自身的高运行效率。

    文件指针的定义:

    ■. 文件的结构体类型别名为 FLIE

        文件类型在stdio.h头文件中已有声明,所以我们不需要另外单独声明,直接使用即可。

    ■. 文件类型变量的定义基本格式:

    FILE 文件变量名;

       例如:

    FLIE f1;

        定义了一个结构体变量f1,

        f1中存放一个文件的相关信息,

        这些信息在打开文件时由系统根据文件的实际情况自动放入。

        然而,

    ★ 我们一般不用文件变量来访问文件,而是使用文件指针来访问文件。

    ■. 文件指针的定义基本格式:

    FILE *fp;

       定义了一个文件类型指针fp,

       用来指向FILE中的数据,

       fp指向某一文件在内存中的文件信息区(结构体变量),

       通过此文件信息区能够访问此文件,

       即,

      ★ 文件指针变量fp可以找到并操作其指向的文件。   

       

    ________________

    文件的打开和关闭:

    fopen() 和fclose() 都包含在stdio.h头文件中

    ■. 文件打开函数:fopen()

    fopen()函数返回的是该文件的起始地址,我们通常将其返回值赋值给一个文件指针,用文件指针指向此文件的地址。

    基本格式:

    文件指针 = fopen("文件名""文件打开方式");

    文件的打开方式与含义:

     例如:

    1. int main()
    2. {
    3. FILE *fp;
    4. fp = fopen("test.txt","w+");
    5. fclose(fp);
    6. return 0;
    7. }

        打开一个文件时,会通知编译器以下三个信息:

        打开的文件名称、文件的打开方式、使用哪个文件指针指向被打开的文件。

    ■. 文件关闭函数:fclose()

        在使用完一个文件之后,为了防止它被误用,应该关闭它。

        “关闭”就是撤销文件信息区和文件缓冲区,使指针不再指向此文件,即无法再操作此文件。

         除非重新打开此文件,使指针指向此文件。

    基本格式:

    fclose(文件指针);

       养成在每次程序终止之前关闭所有打开的文件的良好习惯。

       当fclose()函数成功关闭文件时,返回0;否则返回EOF(-1)。

       最后,将指针指向空 :p=NULL;


    _____________  

    文件操作函数:

    文件操作函数都包含在stdio.h头文件里。

    文件操作函数
    fgetc()
    fputc()
    fgets()
    fputs()

    fscanf()

    fprintf()
    fread()
    fwrite()
    rewind()
    fseek()

     ■. 文件操作流程:

         定义文件指针 -》打开文件 -》操作文件 -》关闭文件

         文件打开完成后就可以对其进行读写操作,即可使用一些常用的文件操作函数。

    ■. 字符输入和输出函数:

    ①使用字符读取函数fgetc()从文件读取一个字符

    1. char ch;
    2. ch = fgetc(fp);
    3. putchar(ch);
    4. putchar('\n');

      从文件指针fp指向的位置读取一个字符存入字符变量ch中,

      读取成功返回所读的字符,失败则返回为文件结束标志EOF(-1)。

    ②使用字符写入函数fputc()从文件写入一个字符:

       基本格式:

     fputc('字符',文件指针);

    ■. 字符串输入和输出函数:

    ①字符串读取函数fgets()

    基本格式:

    fgets(字符数组,字符个数,文件指针);

    例如:

    1. char str1[10]="";
    2. fgets(str1,3,fp);
    3. puts(str1);

       从文件指针fp指向的位置读取一个长度为2的字符串

     (最后一位赋值为'\0',用作字符串结束标志),

       存放在字符数组str1中,读取成功返回地址str1,失败则返回NULL。

    ②字符串写入函数fputs()

       基本格式:

     fputs("字符串",文件指针);

      例如:

      fputs("123\n",fp);       

    ■. 格式化写入和读取函数:

    fprintf() 基本格式:

     fprintf(文件指针,"格式化占位符",写入列表);

      例如:

    1. fprintf(fp,"%s,%d,%c\n",str1,x,y);
    2. 将变量以相应的格式写入fp指向的位置

      fscanf()基本格式:

    1. fscanf(文件指针,"%s,%d,%c,%f",str2,x,y,z);
    2. 从fp指向的位置“读”相应类型的数据,“取”到相应的变量中

       fscanf()函数遇空格、回车、tab键停住  

       读取和写入的格式应该保持一致

    ■.  以二进制形式写入和读取数据:

    fread()函数:

    1. fread(buffer,size,count,fp);
    2. buffer是一个地址(数组),用于存储从文件读取出来的数据,
    3. size为需要读取的字节单位大小,
    4. count为需要取出的数据项的个数(每个数据项的大小为size)。

    fwrite()函数:

    1. fwrite(buffer,size,count,fp);
    2. 从fp指向的文件中位置读入count个size大小的数据,放入到数组butter中

    随机读写文件:

    (1)文件指针位置标记及定位

             文件指针打开文件后默认指向文件的开头,也就是第一个数据的位置,

             此后每读取或写入一次,文件指针自动向后移动到与数据大小相对应的文件位置。

    (2)强制使文件指针指向文件开头

             使用rewind()函数强制使文件指针fp指向文件开头的位置:

     rewind(fp);

    (3)使文件指针指向文件中任意位置

            使用fseek()函数使文件指针指向文件中任意位置

     fseek(fp,位移量,起始点);

            起始点用0、1、2代替表示,0为文件开头位置,2为文件末尾位置,1为文件当前位置;

            位移量是指以起始点为基点,向前(-)/后(+)移动的字节数,位移量为long型数据:

    1. fseek(fp,1000);
    2. 将文件指针fp向后移动到离文件开头100个字节处
    3. fseek(fp,501);
    4. 将文件指针fp向后移动到离当前文件位置50个字节处
    5. fseek(fp,-102);
    6. 将文件指针fp向前移动到离文件末尾100个字节处

           fseek()函数一般用于二进制文件。

           fseek()和rewind()函数可以实现文件的随机读写。

    ____________

     文件包含:

    如果,我们想要用库函数就需要包含其头文件,也就是文件包含,

    当然,我们也可以编写自定义头文件,包含自己编写的头文件。

    ■. 文件包含的基本格式

    #include<系统头文件>

      包含系统头文件用 < > ,只会在系统头文件中找

    #include"自定义头文件"

      包含自定义头文件用 " " ,会先在自定义头文件中找,找不到再到系统头文件中找

    ■. 头文件的重复包含

      --文件包含允许嵌套,即在一个(被包含)文件中可以包含其他文件。

      --头文件的嵌套包含可能会引起头文件的重复包含,从而出现函数和变量的重定义问题,

         所以需要避免头文件重复包含,某些宏定义语句可以防止头文件重复包含。

      例如:

    1.   #pragma once 
    2. //  防止头文件重复包含,不让文件的内容被包含两次,在头文件最前面添加

      #pragma once 是独有的,有使用平台(如vs)的限制,其他平台可能不存在

      通用方式:

    1. #ifndef 该头文件名
    2. #define 该头文件名
    3. ......
    4. #endif

    C程序的分文件编写:

    多文件编程就是把多个头文件(.h文件)和源文件(.c文件)组合在一起构成一个程序。
    1. 多文件编程一般用来开发中大型项目。
    2. 可以用头文件封装函数的声明,在源文件里完成函数功能的定义

     即,C语言头文件中只写函数声明和数据类型的声明,不在头文件中写函数的定义和变量的定义。

    ■. 分文件编写的好处

      1.代码的复用性:代码可以重复使用

      2.代码的封装性:保护代码不被他人盗取篡改

    ___________

    条件编译:

    在预处理阶段,编译之前,根据不同的条件来编译不同的代码段,

    可节省编译时间。

    ■. #if......#else的使用

    1.   #define M 1
    2.   #if M
    3.   int a=10
    4.   #else
    5.   int a=100
    6.   #endif

    ■. #ifdef.....#endif的使用

    1. #define B //宏定义可以只有宏名没有宏替换
    2. #ifdef B
    3. int b=10
    4. #else
    5. int b=100
    6. #endif

    ■. #ifndef......#endif的使用

    1. #define C
    2. #ifndef C
    3. int c=10
    4. #else
    5. int c=100
    6. #endif

    #ifndef......#endif可以用来处理头文件重复包含问题

    1. #ifndef xxxxx
    2. #define xxxxx
    3. .
    4. .
    5. .
    6. .
    7. .
    8. .
    9. #endif

    ________#

  • 相关阅读:
    JavaWeb项目案例(一)
    python --机器学习(基本算法详解)
    【博学谷学习记录】超强总结,用心分享|架构师-设计模式 1
    服务器(I/O)之多路转接
    Django实现音乐网站 (22)
    智源社区AI周刊No.101:DeepMind推出AlphaTensor登Nature封面;stateof.ai发布AI情况报告...
    RabbtiMQ的安装与使用
    JavaWeb搭建学生管理系统(手把手)
    ChatGPT如何训练自己的模型
    java基于ssm的汽车维修工时费快速估价系统
  • 原文地址:https://blog.csdn.net/m0_73973059/article/details/132877477