【无标题】

预处理指令:
    程序员所编写的代码并不能被编译器真正编译,需要一段程序把代码翻译一下
    翻译的程序叫预处理器,翻译的过程叫预处理,被翻译的语句叫预处理指令,以#开头的语句都是预处理指令


    查看预处理结果:
    gcc -E xxx.c    把预处理的结果显示到终端
    gcc -E xxx.c -o xxx.i 把预处理的结果存储到.i预处理文件中

    预处理指令的分类:
#include 导入头文件
#incllude <> 从系统指定的路径下查找并导入头文件
#include""   先从当前路径下查找头文件,如果找不到再从系统指定路径下查找并导入
编译参数 -I /path 也可以指定头文件查找路径
系统通过设置环境变量来指定头文件的系统查找路径

#define 定义宏
    宏常量:
        #define MAX 50
        优点:提高了可读性和可扩展性,方便批量修改,安全性更高,还可以与case配合使用
            注意:宏变量建议全部大写,末尾不要加分号
            [函数名、局部变量小写加下划线,全局变量首字母大写、指针变量+p、数组arr、字符串str]
        注意 typedef int num; 用法和含义上的区别
        预定义好的宏常量:
            __func__  获取函数名%s
            __FILE__  获取宏所在文件名%s
            __LINE__  获取宏所在行数%d
            __DATE__  获取当前日期(年月日)%s
            __TIME__  获取当前时间(时分秒)%s
            
    宏函数:
#define SUM(a,b) a+b
            宏函数其实就是带参数的宏
            宏函数不是真正的函数,不检查参数类型,没有传参和返回值
            ·如何实现宏函数:
            1、先把代码中使用到宏函数的地方替换为宏函数后面的代码
            2、再把宏函数代码中使用的参数替换为调用者提供的参数
    宏的二义性:
            由于宏的位置不同、参数不同导致宏有不同的功能,叫宏的二义性
        如何避免宏的二义性:
            宏函数整体加小括号,每个参数都加小括号保护
            在使用宏函数的时候不要提供带自变运算符的变量作为参数
            容易出选择题,提供宏函数问结果

            运算符:
            # 把宏函数的参数变成字符串
            ## 合并两个参数变成一个标识符

    
        注意:宏函数可以用大括号保护代码,定义宏常量、宏函数时不能换行的,但是可以在一行的末尾使用续行符\换行


常考的笔试面试题:
1、#define和typedef有什么区别?
#define 是宏替换
typedef 是类型重定义
如果是普通类型时,从功能上没有任何区别

#define INT int
typedef int INT;
INT num;

·指针类型有区别时:

#define INT int*
INTP p1, p2, p3;    //p1是指针 p2p3都是 int

typedef int* INTP;
INTP p1,p2,p3;    //p1p2p3都是指针

2、sizeof和strlen有什么区别?
    sizeof是一个计算变量、类型的字节数的运算符
    strlen是一个计算字符串长度的函数

3、宏函数和普通函数有什么区别?
    是什么?
    宏函数:带参数的宏替换,不是真正的函数,只是用起来像函数
    函数:是一段具有某项功能的代码的集合,会被编译成二进制指令存储在代码段中,
        函数名就是该段代码的首地址,有独立的栈内存、命名空间
    有什么不同:
        函数:返回值  参数类型检查  安全  传参    速度慢    跳转
        宏函数:运算结果  通用        危险  替换    速度快    冗余

    条件编译
        根据条件决定让哪些代码参与编译,哪些代码不参与
    版本控制
    #if
    #elif
    #else
    #endif
    注意:还可以使用#if 0 #endif 注释整段代码
    
    头文件卫士:防止头文件重复包含
        #ifndef 宏名(头文件名大写)(HEAD H)
        #define 宏名
        #endif//宏名

    判断、调试
        #if        宏名
        #else    
        #endif    
    注意:可以在gcc编译时通过参数 -D宏名,为此次编译的结果添加宏

    调试代码:
        #ifdef DEBUG
        #define debug(...) printf(__VA_ARGS__)
        #else
        #define debug(...)
        #endif
    提示错误信息:
    #define error(...)  printf("%s %s [%d] %s:%m %s %s\n",
        __FILE__,__func__,__LINE__,__VA_ARGS__,__DATE__,__TIME__)

4、条件编译
根据条件决定哪些代码参与最终的编译
#if/#elif/#else/#endif/#ifdef/#ifndef
编译器控制:
#if __cpluseplus
printf("C++编译器\n");
#else
printf("C编译器\n");
#endif

操作系统位数:
#if __x86_64__
    printf("64位操作系统\n");
#elif __i386__
    printf("32位操作系统\n");
#endif

5、常考笔试面试
    定义一个199年有多少秒的宏,忽略闰平年
    #define SEC (3600*24*364*199u)
    在类型重定义时,#define和typedef区别?
    在定义常量时,#define和const有什么区别?
    宏函数和普通函数的区别?

头文件中应该有什么?
    问题:一个头文件可能被任何的源文件包含,意味着头文件内容可能出现在
    多个目标文件中,链接合并时就可能会冲突
    ·重点:头文件中不能有定义语句(变量和函数),只能编写声明语句
        全局变量声明
        函数声明
        宏常量
        宏函数
        typedef 类型
        结构、联合、枚举的类型声明
    头文件的编写要求:
        1.为每个.c文件写一份.h文件,因为.h文件是对.c文件的说明
        2.如果需要使用到某个.c文件中的变量、函数、宏时,只需要把他的头文件导入即可
        3.每个.c文件都要导入自己的.h,目的是让定义和声明一致
    头文件的相互包含:
    假如a.h中包含了b.h的内容,b.h又包含了a.h,这种情况编译时就会出现
    把a.h和b.h中要相互使用的东西都提取出来,再额外放入编写的c.h(可以没有c.c)
    注意:unknown type name "未知类型名",如果已经确认过该类型、并正确导入,但是依然报为止类型名,一般是头文件互相包含
        可以用a.c包含 b.h , b.c 包含 a.h
        复制头文件卫士时粗心大意也可能会导致未知类型名

    Makefile
        Makefile 是由一系列的编译指令组成的可执行文本文件,也叫做编译脚本
        在终端输入Makefile命令就可以自动执行Makefile脚本中的翻译指令,它可以根据脚本中的
        编译指令的设置来编译整个工程,它还能决定编译先后顺序、哪些需要编译、哪些不需要编译,可以提高编译效率。

        ·编译规则:
            1. 如果此项目没有编译过,则会编译全部的.c文件,并链接成可执行文件
            2. 如果这个工程的某几个.c文件被修改,那么我们只编译被修改的 c 文件,并重新链接成可执行文件
            3. 如果这个工程的.h文件被改变了,那么依赖它的所有.c文件都要重新编译,并重新链接成可执行文件

上一篇:C语言杂谈01---如何理解条件编译


下一篇:3D打印机硬件驱动-马林固件最新版本2.0.X中文注释(3)marlin 2.0.9.2 截至发稿时间2021年12月16日