• 当你敲完Hello World后的第一步——C


    “江山如画,一时多少豪杰——时二二年五月六日”

    这里是目录

    前言

    了解敲完hello world后,编译器是怎么处理代码的第一步的呢,这是学习C和C++的基础。
    Hello World代码如下。放错了,重来。

    在这里插入图片描述
    代码如下
    在这里插入图片描述
    当你敲完Hello World这串代码时。编译器会对这些代码进行编译链接的操作。

    编译: 又分为 预处理编译汇编
    所以说 当你敲完C代码后的第一步,编译器会对C代码进行预处理.

    那么预处理主要做了那些事情呢?

    预处理大致做了以下事情
    1.定义和替换由 #define指令定义的符号
    2.删除注释
    3.确定代码部分内容是否应该根据一些 条件编译指令 进行编译
    4.插入被 #include指令包含的内容

    所以本章详解预处理指令 #define、#include、条件编译指令

    一、#define指令

    1.#define定义宏

    什么是宏?
    宏的定义:#define 允许把参数替换到文本中,这种实现通常称为定义宏

    宏的声明格式

    #define NAME stuff

    解释:没当有符号name出现在#define NAME stuff这条语句后面时,预处理器就会把它替换为 stuff。
    NAME:
    1.NAME是宏的名字。在这里 name 相当于变量,或者也可以相当于函数但不等于函数
    2.一般NAME都是大写,因为宏和函数语法很相似,语言本身我们无法区分,所以宏名要全部大写
    stuff:可以是常量。可以是表达式。也可以是一段程序

    例如:
    以下代码在预处理后是什么样子呢?

    //定义声明宏
    //定义中我们使用了括号,这是一个好习惯,避免优先级的错误
    #define SQUARE(x)  (x)*(x)
    int main()
    {
    	printf("%d ", SQUARE(5));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    预处理后的代码,以下你看到的代码是编译器实实在在的处后的代码。

    #define SQUARE(x)  (x)*(x)
    int main()
    {
    	//将SQUARE(5)替换为(5)*(5)
    	printf("%d ", (5)*(5));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    你是否还对#define 替换迷惑?请继续往下看

    2.#define 替换 宏

    到底上面的代码是怎么替换的 宏?
    1.再调用宏时,首先对参数检查,看是否包含了#define定义的符号,比如SQUARE(5),然后将它的x * x替换为5 * 5.
    2.对于宏,参数名被他们的值所替代。
    3.最后,再次对文本扫描,看是否包含了热任何由#define定义的符号。如果是,就重复上述处理过程。

    为什么会有第3步的重复呢?
    因为有时候#define定义可以包含其他#define定义的符号。但是宏不可以递归

    3.带副作用的宏参数

    什么是带副作用的宏参数?
    副作用:就是表达式求值的时候出现的永久性效果

    例如:

    x+1;//不带副作用
    x++;//带有副作用
    
    • 1
    • 2

    下面代码输出结果是什么?

    #include <stdio.h>
    #define ADD(a, b) (a)+(b)
    int main()
    {
    	int x = 2; 
    	int y = 3; 
    
    	int z = ADD(x++, y++);
    	//输出的结果是什么?
    	//x=3 y=4 z=5
    	printf("x=%d y=%d z=%d
    ", x, y, z);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    因为被替换的代码是int z = ADD(x++, y++);
    替换后为:int z = (x++)+(y++);

    这样结果就一目了然。

    4.#undef撤销宏定义

    #undef:这条指令用于移除一个宏定义
    例如:移除MAX这个宏。
    在这里插入图片描述

    5.宏和函数对比(重点)

    属性

    #define定义宏

    函数

    代码长度

    每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长

    函数代码只出现于一个地方。每次使用函数时,都调用同一个地方的代码

    执行速度

    更快

    存在函数的调用和返回 的格外开销,所以相对慢一些

    操作符优先级

    宏参数求值需要加上括号,否则容易造成不可以预料的后果

    只在函数调用事求值一次,不会带副作用

    带有副作用的参数

    参数可能被替换到宏的多个位置,有的可能带有副作用

    函数参数只在传参的时候求值一次,结果更容易控制

    参数类型

    宏的参数与类型无关,可以是任何类型的的参数

    函数参数与类型有关,参数类型不同就需要不同的函数,因为C语言没有C++的重载

    调试

    宏是不可以调试的,因为在程序运行前就已经替换的宏

    可以逐语句调试

    二、条件编译指令

    什么是条件编译?
    意思就是我们可以选择性的编译。
    条件编译:你可以选择代码的一部分是被正常编译还是完全忽略。用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令。

    1.单分支

    常量表达式expression,由预处理器求值。
    如果expression为真,那么statements将被执行,否则预处理器就安静的删除它们。

    #if expression
     statements;
    #endif
    //常量表达式expression,由预处理器求值。
    
    • 1
    • 2
    • 3
    • 4

    2.多分支条件编译

    同if else语句,为真则执行。

    #if expression
     //...
    #elif expression
     //...
    #else
     //...
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.判断某个符号是否被定义

    为了测试一个符号是否已经被定义。在条件编译中完成这个任务更方便。

    以下两条语句功能想通过。

    1.#if defined(symbol)
    
    2.#ifdef symbol
    
    • 1
    • 2
    • 3

    4.嵌套指令

    某个程序既要在windows系统下能够运行,也需要在Linux系统下运行,这就要条件编译来解决跨平台问题。这时候嵌套指令很容易解决。

    #if defined(OS_UNIX)
    	 #ifdef OPTION1
    			 unix_version_option1();
    	 #endif
    	 #ifdef OPTION2
     			unix_version_option2();
    	 #endif
    #elif defined(OS_MSDOS)
    	 #ifdef OPTION2
     			msdos_version_option2();
    	 #endif
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、#include指令

    #include在预处理时会被展开。
    这种展开的方式很简单:
    1.预处理器先删除这条指令,并用**#include**所包含文件的内容替换。
    2.这样一个源文件被包含10次,那就实际被编译10次。

    1.本地文件包含

    #include "Add.h"
    
    • 1

    查找方法
    1.先在源文件所在目录下查找
    2.如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。

    2.库文件包含

    #include <stdio.h>
    
    • 1

    查找方法:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

    这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
    答案是肯定的,可以。
    但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

    3.嵌套文件包含

    有时候会重复包含头文件,以前为了解决这个方法,人们用了条件编译。代码如下
    每个头文件的开头写:

    例如有个test.h的头文件。用下划线分开头文件。全大写。

    #ifndef __TEST_H__
    #define __TEST_H__
    //这里面写头文件的内容
    #endif  
    
    • 1
    • 2
    • 3
    • 4

    上面这种写法比较古老。
    现在一般用这个写法

    #pragma once
    
    • 1

    #pragma once也是是用来防止头文件被包含的。

  • 相关阅读:
    Pull Request 开源pr实战,过程详细,修改vite-plugin-cdn-import; cdn只导入css
    通讯录(C语言版)
    15:00面试,15:08就出来了,问的问题有点变态。。。
    Java 异常处理详解(如果想知道Java中有关异常处理的知识点,那么只看这一篇就足够了!)
    A-深度学习面试题
    【无标题】
    Alian解读SpringBoot 2.6.0 源码(十):启动流程之自动装配原理
    [激光原理与应用-17]:《激光原理与技术》-3- 激光的产生原理:微观粒子、能级、电子、光子、受激辐射
    C++中的各种函数及用法(4)
    uniapp微信小程序嵌入页面web-view配置
  • 原文地址:https://blog.csdn.net/segegefe/article/details/125402210