C语言:预处理

个人博客网址:https://ljsblog.com

预处理(十二-终)

翻译环境和执行环境

翻译环境

在这个环境中源代码被转换为可执行的机器指令,就是把(.c)文件翻译成(.exe)文件。
一个C语言程序需要经过的四个步骤:编辑(.c)、编译(.obj)、链接(.exe)、运行。
而其中编译又分为:预编译(预处理)、编译、汇编。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vuyDllJe-1625748038088)(过程.png)]
预编译(预处理)
源码中的所有预处理语句(#号开头的语句便是预处理语句,例如:#include)得到处理并删除注释。
编译
就是把C语言代码翻译成汇编代码。
汇编
把汇编代码翻译成二进制代码。
链接
但机器代码还是不能直接运行。所以链接器将处理合并目标代码,生成一个可执行目标文件,可以被加载到内存中,由系统执行。

执行环境

这个环境用于实际执行代码。

  1. 程序必须载入内存中。在有操作系统的环境中,一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储在静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

预编译(预处理)详解

预定义符号

__FILE__//源文件的绝对路径
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间

#include <stdio.h>
int main()
{
  printf("%s\n",__FILE__);
  printf("%d\n",__LINE__);
  printf("%s\n",__DATE__);
  printf("%s\n",__TIME__);
  return 0;
}
/*
d:\vc++2010\prac\prac\ss.c
5
Feb  7 2021
22:29:26
*/

预处理指令

#开头的就是预处理指令例如:  
#define,#include,#pragma pack(),#if,#endif,#ifdef,#line
#define定义标识符

#define name stuff
例:

#include <stdio.h>
#define MAX 100
#define STR "blog"
int main()
{
  int a=MAX;
  //预处理完后变为
  //int a=100;
  printf("%d\n",a);//100
  printf("%s\n",STR);//blog
  return 0;
}

:在define定义标识符时,最后不要加;

#define定义宏

#define可以把参数替换到文本中,这种实现称为宏(macro)或定义宏(define macro)。
宏的申明方式
#define name(parament-list) stuff
parament-list是一个由逗号隔开的符号表,这些符号可能出现在stuff中。
参数列表的左括号必须与name紧邻
例:

#include <stdio.h>
#define SQU(x) x*x
//接收一个参数x
int main()
{
  int a=SQU(3);
  //预处理器会将3*3代替SQU(3)
  printf("%d\n",a);//9
  return 0;
}

:宏的参数不是传参,而是替换
例:

#include <stdio.h>
#define SQU(x) x*x
int main()
{
  int a=SQU(3+1);
  //预处理将 3+1*3+1 代替SQU(3+1),并不是(3+1)*(3+1)
  //所以a的值为7,而不是16
  printf("%d\n",a);//7
}

所以为了避免这个问题,我们要给stuff加上括号(stuff)以及stuff中每个参数也加上括号。

#include <stdio.h>
#define SQU(x) ((x)*(x))
int main()
{
	int a=SQU(3+1);
	//预处理将 ((3+1)*(3+1)) 代替SQU(3+1)
	//所以这次a的值为16
	printf("%d\n",a);//16
	return 0;
}

:

  1. 宏参数和#define定义中可以出现其他#define的变量,但宏不能出现递归。
  2. 预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索,通俗点讲,就是字符串常量的内容不会被替换。

#和##的区别
#是将其后面的宏参数进行字符串化操作,并不替换。
例:

#include <stdio.h>
#define PRINT(x) printf(#x"的值为%d\n",x)
int main()
{
  int a=10;
  int b=20;
  PRINT(a);
  //printf(“a”“的值为%d\n”,x)替换PRINT(a)
  PRINT(b);
  //printf(“b”“的值为%d\n”,x)替换PRINT(a)
  return 0;
}
/*
a的值为10
b的值为20
*/

##是将它两边的符号合成一个符号。
例:

#include <stdio.h>
#define CON(x,y) x##y
int main()
{
  int myblog2021=100;
  printf("%d\n",CON(myblog,2021));//100
  //CON(myblog,2021)
  //myblog##2021
  //myblog2021
  return 0;
}
宏和函数

区别

  1. 宏的参数与类型是无关的,只要对参数的操作合法,就可以使用于任何类型;函数的参数类型是固定的,如果参数的类型不同,就需要使用不同的函数,即使他们执行的任务是相同的。
  2. 宏可能会带来运算优先级的问题,导致运算结果出错;
  3. 宏的参数替换是直接替换的;而函数调用时会将实参的值传给形参;
  4. 宏是不方便调试的,因为宏是在编译之前进行(先用宏体替换宏名,再进行编译);而函数是可以逐语句调试;
  5. 宏的参数是不占内存空间的,因为只做字符串的替换;而函数调用时参数之间的传递,所以占用内存;
  6. 宏的速度比函数速度快,因为函数有调用和返回时间的开销;
  7. 宏在传参时可以传类型,但是函数不能传类型;
  8. 宏不能递归,函数可以递归;
  9. 宏的代码长度很长(除去非常小的宏),每次使用时,宏代码都被插入到程序中,使得程序的长度大幅度增加;

命名约定
宏名全部大写,函数名不要全部大写。

#undef

用于移除一个宏定义。
#undef name
例:

#include <stdio.h>
#define MAX 100
int main()
{
  printf("%d\n",MAX);//100
#undef MAX
  return 0;
}
条件编译

选择性的按照某种条件编译代码。
常见的条件编译指令:

//1
#if 常量表达式
        //...
#endif
//常量表达式由预处理器求值。如:
#define __DEBUG__ 1
#if __DEBUG__
        //...
#endif

//2 多分支的条件编译
#if
        //...
#elif
        //...
#else
        //...
#endif

//3 判断是否被定义
#if defined(symbol)
//等同于
#ifdef symbol

#if !defined(symbol)
//等同于
#ifndef symbol
文件包含

#include指令可以使另外一个文件被编译。就像它实际出现于#include指令的地方一样。
替换方式:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。

头文件被包含方式

本地文件包含
#include "filename.h"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
库文件包含
#include <filename.h>
编译器直接在标准位置查找头文件。如果找不到就提示编译错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值