1程序的翻译与执行环境
执行是涉及硬件的,不是这系列的重点
- 编译部分是将源文件xxx.c通过编译器编译成xxx.obj
- 链接部分是将xxx.obj+链接库放到链接器中链接成可执行程序
- 在汇编阶段,把各个文件的全局符号生成对应的符号表xxx.o;在链接阶段,把各个符号表进行汇总,如果发现有无法解析的函数地址,则出现错误
2预处理!
2.1预定符号
- 这些是内置好的符号
- __FILE__;当前编译源文件的路径
- __LINE__;该代码当前所在的行号
- __DATE__;当前日期
- __TIME__;当前时间
- __FUNCTION__;当前的所在的函数名
- __LINE__;
- 以上通常是为了写log用的
2.2#define
- #define M 100
- #define reg register
- #define do_forever for(…;…;…)
- #define CASE break;case
- 就是可以自己定义一段东西,在预处理时直接替换
- 加;就是会在预处理时加上分号,所以要斟酌使用
2.3#define 定义宏
- #define SQUARE(X) X*X
宏的一个缺陷
由于宏#define是一个预编译的操作,相关的运算并不是在预编译中进行的,所以会出现如下情况
#define SQUARE(X) X*X
//#define SQUARE(X) ((X)*(X))//如果想要3*3的效果,得这么改,括号不能省
#include<iostream>
using namespace std;
int main()
{
cout<<SQUARE(1+2);//由于是完全替换,到时候上面语句在预编译时会转化成:1+2*1+2 =5
return 0;
}
2.4#(非define哪个)和##
-
将#x的内容替换成对应的字符串(而非变量值)
-
只能在宏中使用
#define PRINT(X,F) printf("the value of "#X" is "F,X);
int main()
{
int a =10;
PRINT(a,"%d");
int b = 20;
PRINT(b,"%d");
float f = 2.5f;
PRINT(f,"%f");
return 0;
}
##的使用
- 把两个字符串合在一起
2.5带副作用的宏参数
主要出现在a++,++a这种,因为他是完全替换。这是一种危险的编程行为。
2.6宏和函数的对比
宏的优势
- 宏:直接在预处理阶段完全替换,所需要的的汇编代码是更少的。他跟inline很像,但是inline实在编译期间展开的,且本质时函数。
- 函数:需要做相当多的准备工作,编译,才能进行核心的汇编代码。
- 如果调用+返回的过程比实际执行小型计算时更长,应该用宏。
- 此外,函数需要明确的类型,而宏允许其他类型,此时宏更为灵活,如:
#define MAX(X,Y) ((X)>(Y)?(X):(Y)) int Max(int x,int y) { return x>y?x:y; }
宏的劣势
- 由于是完全替换,即实际上会插入到程序中。因此一般建议宏是一个小巧的功能。而函数不是,函数是专门开辟了一片内存空间的,使用时就去那片空间找。
- 宏没办法debug!因为调试是在代码编译,链接后形成.exe时才能debug,而宏在预处理阶段已经全部替换了。
- 宏由于没有明确类型定义,缺乏严谨性
- 括号少括了,操作符的优先级,会导致未知错误
- 宏不允许递归
2.7#undef
取消一个定义
2.8条件编译,跟条件判断类似,预处理时跟注释类似
-
#define PRINT int main() { //如果PRINT定义了就编译 #ifdef PRINT printf("1"); #endif return 0; }
-
#if #elif #else #endif
-
//如果HEHE不定义,下面参与编译 #ifndef HEHE #endif
- 允许嵌套
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #endif
2.9文件包含
-
自己定义的头文件用#include “add.h”,来引入。称为本地文件的包含。他会在本地位置查找,如果找不到则去库函数的头文件目录下找
-
#include <stdio.h>,为库文件的包含。<>直接去库函数里面找。
-
防止头文件被重复包含用
:
第一种:#pragma once第二种:
#ifndef __TEST_H__
#define __TEST_H__
#endif