一、预处理器指令
#define、#include、#ifdef、#else、#endif、#ifndef、#if、#elif、#line、#error、#pragma
指令可出现在源文件的任何地方,指令定义的作用域从定义出现的位置开始直到文件结尾。
1、#define
1)类对象宏
比如#define TWO 2
一般而言,预处理器发现程序中的宏后,会用它的等价替换文本代替宏,如果该字符串中还包括宏,则继续替换这些宏,例外情况是双引号中的宏,
常量宏可以用来指定标准数组的大小并作为const值的初始化值。
#define LIMIT 203
const int LIM = 30;
static int data1[LIMIT ] //合法
static int data2[LIM ] //非法
const int LIM2 = 2 * LIMIT //合法
const int LIM2 = 2 * LIM //非法
2)类函数宏
# define SQUARE(x) x*x
SQUARE(2) 的值是4,SQUARE(4+2)的值是36吗?其实值是4+2*4+2=14;预处理器不进行计算,只是简单地字符替换。
所以使用必须的足够多的圆括号来保证以正确的顺序进行运算和结合
# define SQUARE(x) (( x )*( x ))
不要在宏里进行增量运算。(++、--)
#运算符
# define PSQR(x)printf("The squqre of x is %d .\n",(( x )*( x )))
如果PSQR(8) 的The squqre of x is 64
可以使用#号,把语言符号转化为字符串
# define PSQR(x)printf("The squqre of #x is %d .\n",(( x )*( x )))
如果PSQR(8) 的The squqre of 8 is 64
##运算符
##运算符可以用于类函数宏的替换部分,另外,##还可以用于类对象宏的替换部分,这个运算符把两个语言符号组合成单个语言符号。
## define XNAME(n) x##n
这样使用宏 XNAME(4) 则就是x4
可变宏: ...和_ _VA_ _ARGS_ _
# define PR(...) printf(_ _VA_ _ARGS_ _)
#undef重定义常量
ANSI标准只允许新定义的与旧定义的完全相同。向同意味着主体具有相同的顺序的语言符号
2、#include
该指令有两种使用形式
#include <stdio.h>
文件名放在尖括号中,告诉预处理器在一个或多个标准系统目录中寻找文件
#include “mystuff.h”、#include “/user/mystuff.h”
文件名放在双引号中,告诉预处理器先在当前目录(或文件名中指定的其他目录)中寻找文件,然后在标准位置寻找文件 .
使用头文件来声明多个文件共享的外部变量,例如,如果开发共享某个变量的一系列函数,该变量报告某种类型的状态,此时,可以在包含函数声明的源代码文件中定义一个具有文件作用域、外部链接的变量
int status = 0; //文件作用域,源代码文件中
接着,可以在于源代码文件相关的头文件中进行引用声明:
extern int staus ; //头文件中
该行代码会出现在包含该头文件的任何地方。
包含头文件的另一种情况是:
使用具有文件作用域、内部链接和const 限定词的变量或数组
。使用const可以避免值被意外的改变。使用static 后,包含该头文件的文件都获得一份该常量的副本
3、#ifdef、#else、#endif指令
#ifdef MAVIS
# //如果已经定义了一个宏MAVIS 则执行这里的代码
#else
#//否则执行这里的代码
#endif //结束条件编译
4、#ifndef指令
#ifndef SIZE
#define SIZE 100 //如果SIZE没有定义
#endif //别忘了#endif
许多新的实现提供另一种方法来判断一个名字是否已经定义,不需要使用
#ifdef VAX
而是采用下面的形式
#if defined (VAX)
5、#if和#elif 指令
#if SYS == 1
#include “ibm.h”
#elif SYS == 2
#include "vax.h"
#elif SYS == 3
#include "mac.h"
#else
#include "general.h"
#endif
6、预定义宏
_ _DATE_ _ 进行预处理的日期(“Mmm dd yyyy”形式的字符串文字,如May 27 2006)
_ _FILE_ _ 代表当前源代码文件名的字符串文字 ,包含了详细路径,如G:/program/study/c+/test1.c
_ _LINE_ _ 代表当前源代码中的行号的整数常量
_ _TIME_ _ 源文件编译时间,格式微“hh:mm:ss”,如:09:11:10;
_ _func_ _ 当前所在函数名,在编译器的较高版本中支持
_ _FUNCTION_ _ 当前所在函数名
7、#line和#error
#line此命令主要是为强制编译器按指定的行号,开始对源程序的代码重新编号,在调试的时候,可以按此规定输出错误代码的准确位置。其作用在于编译的时候,准确输出出错代码所在的位置(行号),而在源程序中并不出现行号,从而方便程序员准确定位。
# line constant
1 #include <iostream>
2 using std::cout;
3 int main()
4 {
5 #line 1 "kevin"
6 int a=1;
7 aa=1;
8 cout<<a;
9 return 0;
10 }
#error当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息。
#if _ _STDC_VERSION_ _ ! =199901L
#error Not C99
#endif
二、内联函数
C语言中内联函数到底有什么作用?
试想一下,每当我们在假设就在主函数中调用另外一个函数的时候,那么这个函数就要入栈或者出栈,比如说下面的一个例子:
- Void myprint()
- {
-
- Printf("%d",3);
-
- }
-
- Void main()
- {
-
- Int i;
-
- For(i=0;i<100;i++)
-
- Myprint(2);
- }
- Void main()
- {
- Int i;
- For(i=0;i<100;i++)
- Void myprint()
- {
- Printf("%d",3);
- }
- }
- 此时相当于
- static inline void myprint(int n);
- static inline void myprint(int n)
- {
- printf("%d",n);
- }
- void main()
- {
- int i;
- for(i=0;i<100;i++)
- myprint(3);
-
- }
此时我们就不需要进栈出栈了,直接打印2就可以了,当然无论任何事,得到了一定的方便之后就必须得付出一定的代价,即此时的空间的问题。打个比方,现在我们要去一个超市买东西,而超市距离我们家有好几里路,此时你要话费一定的时间在去超市的路上,可是现在呢超市已经搬到家里了,我们就没有必要去超市了,因为超市已经在家里了,当然你的家有那么大?能容得下一个超市?所以此时就存在一个空间的问题,即你必须扩大你家,才能够容纳的下一个超市。
对于内存来说也是一样的,减少了对栈的进出时间的开销,我们却扩大了主存的空间来容纳本来在栈里的函数,在C语言中实现这一功能是用内联函数inline来实现的。这就是inline函数的作用!
在内联函数中如果有复杂操作将不被内联。如:循环和递归调用。
将简单短小的函数定义为内联函数将会提高效率。
内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?
如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。
一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。