C语言——预处理

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 预处理

预处理

一.预处理

  编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是宏发挥作用的阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释、插入被#include进来的文件内容、定义和替换由#define 定义的符号以及确定代码部分内容是否根据条件编译(#if )来进行编译。”文本性质”的操作,就是指一段文本替换成另外一段文本,而不考虑其中任何的语义内容。宏仅仅是在C预处理阶段的一种文本替换工具,编译完之后对二进制代码不可见

二.宏定义用法

①宏常量

  我们最常使用到的#define的用法就是用#define来定义一个符号常量,而要修改时,只需修改#define这条语句就行了,不必每处代码都修改例:

#include"stdio.h"

#define PI 3.14

#define STR "圆周率约等于"

intmain()

{

   printf("%s %f",STR,PI); //预处理时会被替换为 printf("%s %f","圆周率约等于",3.14);

   return0;

}

运行结果:

②宏语句

  我们还可以用宏定义一条或多条语句例:

#include"stdio.h"

#define Print printf("hello world!")

intmain()

{

   Print;  //预处理时会被替换为 printf("hello world!");

   return0;

}

操作结果:

③宏函数

  我还可以用宏来定义函数,因为宏定义也可以带参数例:

#include"stdio.h"

#define Print(str) printf("%s",str)

intmain()

{

   Print("这是一个只有一条语句的宏函数!");

   //预处理时会被替换为 printf("%s","这是一个只有一条语句的宏函数!")

   return0;

}

④其它

1.#undef 是用来撤销宏定义的,用法如下:

#define PI 3.141592654

 

 

// code

#undef PI

//下面开始 PI 就失效了

2.使用ifndef防止头文件被重复包含和编译

  这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。 C语言在对程序进行编译时,会先根据预处理命令进行“预处理”。C语言编译系统包括预处理,编译和链接等部分。

#ifndef x //先测试x是否被宏定义过

#define x //如果没有宏定义下面就宏定义x并编译下面的语句

...

...

...

#endif //如果已经定义了则编译#endif后面的语句

条件指示符#ifndef检查预编译常量在前面是否已经被宏定义。如果在前面没有被宏定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行编译处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。  千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

所以还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

 #ifndef <标识>

 #define <标识>

 ......

 #endif

<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h

 #ifndef _STDIO_H_

 #define _STDIO_H_

 ......

   #endif

#ifndef xxx //如果没有定义xxx#define xxx //定义xxx#endif //结束如果这个用法主要是在头文件中,主要是为了防止类重复的include,所以在类的头文件之前加上前面两个,用类名替代xxx,在最后加上最后一句

三.宏定义相关作用符

①换行符 ""

  我们定义宏语句或者宏函数时不可能总是一条语句呀,那要是有很多条语句时怎么办?都写在一行吗?这样显然代码就不美观,可读性不好,所以有多条语句时,我们就在每行末尾(除了最后一行)加上"",代表换行的意思例:

#include"stdio.h"

#define Print   printf("这是第1条语句\n");\

               printf("这是第2条语句\n");\

               printf("这是第3条语句\n")

               

#define Show(str1,str2,str3)\

{\

   printf("%s\n",str1);\

   printf("%s\n",str2);\

   printf("%s\n",str3);\  

}

intmain()

{

   Print;  //无参数宏函数

   Show("first","second","third"); //带参数宏函数

   return0;

}

操作结果:

②字符串化符 "#"

  "#"是“字符串化”的意思,将出现在宏定义中的#是把跟在后面的参数转换成一个字符串例:

#include"stdio.h"

#define Print(str)\

{\

printf(#str"的值是%d",str);\

}

int main()

{

int x=3,y=4;

Print(x+y); //此处等价于printf("x+y""的值是%d",x+y);

            //#str等价于"x+y",所以#str不需要再用双引号引起来

return 0;

}

操作结果:

③片段连接符"##"

  “##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。在普通的宏定义中,预处理器一般把空格解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些##来替代空格。例:

#include"stdio.h"

#define Add(n,value)\

{\

num##n+=value;\

}

int main()

{

int num1=1;

int num2=10;

Add(2,10); //等价于num2+=10; 这里把num和2连接成了num2

printf(" num1=%d\n num2=%d",num1,num2);

return 0;

}

操作结果:

四.宏函数的巧用

①类型传递

  我们知道函数虽然可以传递参数,但是却不能把类型作为参数传递,有时我们为了实现函数的复用性,就要使用STL模板,但是我们这个时候还有另外一种选择,就是写成宏函数例:一个开辟内存的函数

#define Malloc(type,size) (type*)malloc(sizeof(type)*size)

这个时候,我们只有把类型,容量作为参数传递进行,就可以开辟各种类型的内存了

int *p=Malloc(int,100); //开辟int类型的内存

char *q=Malloc(char,100); //开辟字符类型的内存

②传递数组

  由数组作为函数参数传递时,会失去其数组特性,也就是无法使用sizeof()函数计算出数组的大小,比如我们写一个排序函数,排序时我们不仅需要知道数组的首地址,还需要知道数组的大小,但是仅仅把数组名作为参数传递时,无法得知其数组大小,这时我们的函数就需要传递第二个参数,也就是数组的大小,于是函数就要写成Sort(int *a,int size).但宏函数就可以解决这个问题例:下面用宏函数写一个插入排序算法

#include"stdio.h"

#define InsertSort(list)\

{\

int s=sizeof(list)/4;\

int i,j;\

for(i=2;i<=s;i++)\

{\

list[0]=list[i];\

for(j=i-1;list[j]>list[0];j--)\

  list[j+1]=list[j];\

list[j+1]=list[0];\

}\

}

int main()

{

int num[]={0,2,5,7,3,1,8,0,8,22,57,56,74,18,99,34,31,55,41,12,9,4};

InsertSort(num);

for(int i=1;i<sizeof(num)/4;i++)

printf("%d ",num[i]);

return 0;

}

操作结果:当然还有很多宏定义的巧妙用法,这里就不全部列举了

五.注意事项

① 运算符优先级问题

#define MULTIPLY(x, y) x * y

  这是一个很简单的乘法函数,当计算MULTIPLY(10, 10),结果是100,这个大家都知道,但是当你计算MULTIPLY(5+5, 10)时,你以为结果还是100吗?当然不是,MULTIPLY(5+5, 10)=5+5*10=55,所以结果是55,所以我们写宏函数时要特别注意运算符的优先级,这里稳妥一点的写法应该这样写

#define MULTIPLY(x, y) ((x)*(y))

②宏参数重复调用

#define MAX(a,b) ((a)>(b)?(a):(b))

int a=0;

int b =1;

int c =MAX(++a,++b);

这里很多人都以为是c=MAX(1,2)=2;而实际上上面代码等价于

int c =((++a)>(++b)?(++a):(++b));

可以看到实际上a b都各自加了两次,所以c=1>2?2:3=3,所以结果是3

③分号吞噬问题

#include"stdio.h"

#define FUN(n)\

{\

while(n>0)\

{\

if(n==3)\

  break;\

}\

}

int main()

{

int num=10;

if(num>0)

FUN(num);

else

num=-num;

return 0;

}

  看似代码没有问题,但是一编译就报错,编译器显示"error: ‘else’ without a previous ‘if’",原来是因为FUN函数是一个代码块,然后if(num>0) FUN(num); 就等价于if(num>0) {…}; 这不就是在大括号后面打分号了吗?这样else当然就缺少if了  这个时候我们可以用do{…}while(0)来解决这个问题,写成如下就没问题了,因为while()后面正好需要一个分号

#define FUN(n)\

do\

{\

while(n>0)\

{\

if(n==3)\

  break;\

}\

}while(0)

④递归调用问题

#define NUM (4 + NUM)

  按前面的理解,(4 + NUM)会展开成(4 + (4 + NUM)),然后一直展开下去,直至内存耗尽。但是,预处理器采取的策略是只展开一次。也就是说,NUM只会展开成(4 + NUM),而展开之后NUM的含义就要根据上下文来确定了。

⑤宏参数预处理

  宏参数中若包含另外的宏,那么宏参数在被代入到宏体之前会做一次完全的展开,除非宏体中含有#或##。

有如下宏定义:

#define A(y) X_##y

#define B(y) A(y)

#define SIZE 1024

#define S SIZE

A(S)会被展开成X_S。因为宏体中含有##,宏参数直接代入宏体。B(S)会被展开成X_1024。因为B(S)的宏体是A(S),并没有#或##,所以S在代入前会被完全展开成1024,然后才代入宏体,变成X_1024。

include指令

1、#include 命令介绍

  • #include 命令是预处理命令的一种,预处理命令可以将别的源代码内容插入到所指定的位置;可以标识出只有在特定条件下才会被编译的某一段程序代码;
  • 可以定义类似标识符功能的宏,在编译时,预处理器会用别的文本取代该宏。

2、插入头文件的内容

  • #include 命令告诉预处理器将指定头文件的内容插入到预处理器命令的相应位置。有两种方式可以指定插入头文件:

#include <文件名>

#include "文件名"

  • 如果需要包含标准库头文件或者实现版本所提供的头文件,应该使用第一种格式。如下例所示:

#include <math.h>               // 一些数学函数的原型,以及相关的类型和宏

  • 如果需要包含针对程序所开发的源文件,则应该使用第二种格式。采用 #include 命令所插入的文件,通常文件扩展名是 .h,文件包括函数原型、宏定义和类型定义。
  • 只要使用 #include 命令,这些定义就可被任何源文件使用。如下例所示:

#include "myproject.h"         // 用在当前项目中的函数原型、类型定义和宏

  • 你可以在 #include 命令中使用宏。如果使用宏,该宏的取代结果必须确保生成正确的 #include 命令。
  • 例 1 展示了这样的 #include 命令。
  • 【例1】在 #include 命令中的宏

#ifdef        _DEBUG_

 #define       MY_HEADER       "myProject_dbg.h"

#else

 #define       MY_HEADER       "myProject.h"

#endif

#include        MY_HEADER

  • 当上述程序代码进入预处理时,如果 DEBUG 宏已被定义,那么预处理器会插入 myProject_dbg.h 的内容;如果还没定义,则插入 myProject.h 的内容。

3、预处理器如何找到头文件

  • 由给定的 C 语言实现版本决定 #include 命令所指定文件的搜索路径。同时,也由实现版本决定文件名是否区分大小写。对于命令中使用尖括号指定的文件(),预处理器通常会在特定的系统路径下搜索,例如,在 Unix 系统中,会搜索路径 /usr/local/include 与 /usr/include。
  • 对于命令中用双引号指定的文件("文件名"),预处理器通常首先在当前目录下寻找,也就是包含该程序其他源文件的目录。如果在当前目录下没有找到,那么预处理器也会搜索系统的 include 路径。文件名中可以包含路径。但如果文件名中包含了路径,则预处理器只会到该目录下寻找。
  • 你也可以通过使用编译器命令行选项,或在环境变量(该变量通常称为 INCLUDE)中加入搜索路径,为 #include 命令指定自己的搜索路径。具体的做法请参考采用的编译器的说明文档。

4、嵌套的 #include 命令

  • #include 命令可以嵌套使用;也就是说,通过 #include 命令插入的源文件本身也可以包含另一个 #include 命令。预处理器最多允许 15 层的嵌套包含。
  • 因为头文件有时候会包含另一个头文件,很容易发生相同的一个文件被多次包含的情况。例如,假设文件 myProject.h 中包含如下代码:

#include <stdio.h>

  • 如果源文件包含下面的 #include 命令,就会两次包含 stdio.h,一次是直接包含,另一次是间接包含:

#include <stdio.h>

#include "myProject.h"

  • 然而,可以采用条件式编译的命令,方便地避免多次包含相同的文件。例 2 使用了这个技巧。
  • 【例2】避免多次包含

#ifndef INCFILE_H_

#define INCFILE_H_

/* ...实际的头文件incfile.h的内容写在这里... */

#endif  /* INCFILE_H_ */

  • 第一次出现包含 incfile.h 的命令时,INCFILE_H_ 宏是没有定义的。预处理器因此插入 #ifndef 和 #endif 之间的内容,这段内容包含了对 INCFILE_H_ 宏的定义。嵌入 incfile.h 文件之后,#ifndef 条件就会为 false,预处理器会忽略 #ifndef 和 #endif 之间的内容。

undef指令

C语言中#undef的语法定义是:#undef 标识符,用来将前面定义的宏标识符取消定义。

然而,在实际应用中,#undef到底可以用来做什么?

整理了如下几种#undef的常见用法。

1. 防止宏定义冲突

在一个程序块中用完宏定义后,为防止后面标识符冲突需要取消其宏定义。

例如:

#include <stdio.h>


int main()

{

#define MAX 200

printf("MAX = %d\n", MAX);

#undef MAX


   int MAX = 10;

   printf("MAX = %d\n", MAX);


   return 0;

}


/******** 例程1:main.c ********/

在一个程序段中使用完宏定义后立即将其取消,防止在后面程序段中用到同样的名字而产生冲突。

2. 增强代码可读性

在同一个头文件中定义结构类型相似的对象,根据宏定义不同获取不同的对象,主要用于增强代码的可读性。

例如:在头文件student.h中定义两个学生对象(小明和小红),两个对象互不干涉。

#ifdef MING

#define MING_AGE 20

#define MING_HEIGHT 175

#endif


#ifdef HONG

#define HONG_AGE 19

#define HONG_HEIGHT 165

#endif


/******** 例程2:student.h ********/

在源文件中使用这两个对象:

#include <stdio.h>


#define MING

#include "student.h"

#undef MING

#define HONG

#include "student.h"

#undef HONG


int main()

{

printf("Xiao Ming's age is %d.\n", MING_AGE);

printf("Xiao Hong's age is %d.\n", HONG_AGE);


return 0;

}


/******** 例程3:main.c ********/

在一个头文件里定义的两个对象与分别在两个头文件里定义效果相同,但如果将相似的对象只用一个头文件申明,可以增强源代码的可读性。

3. 自定义接口

将某个库函数包装成自定义接口,而只允许用户调用自定义接口,禁止直接调用库函数。

(此例来源于《C和指针》)

例如,自定义安全的内存分配器接口:

/*

** 定义一个不易发生错误的内存分配器

*/

#include <stdlib.h>


#define malloc                         //防止直接调用malloc!

#define MALLOC(num, type)   (type *)alloc((num) * sizeof(type))

extern void *alloc(size_t size);


/*********** 例程4:alloc.h ***********/

其中“#define malloc”是为了防止用户直接调用库函数malloc,只要包含了这个头文件alloc.h,就不能直接调用库函数malloc,而只能调用自定义函数MALLOC,如果用户要调用库函数malloc编译器会发生错误。

自定义安全的内存分配器的实现:

/*

** 不易发生错误的内存分配器的实现

*/

#include <stdio.h>

#include "alloc.h"

#undef malloc


void *alloc(size_t size)

{

   void *new_mem;

   new_mem = malloc(size);

   if(new_mem == NULL)

   {

       printf("Out of memory!\n");

       exit(1);

   }

   return new_mem;

}


/*********** 例程5:alloc.c ***********/

因为在实现中需要用到库函数malloc,所以需要用这一句“#undef malloc”取消alloc.h中对malloc的宏定义。

这种技巧还是比较有意思的,用于对已经存在的库函数进行封装。而且如果包含了自定义接口文件,就不能直接调用库函数,而只能调用自定义封装的函数。

4. 用于调试头文件中

用于调试头文件中,偶然看到这样一个代码用到了#undef,写于此作为记录:

#ifdef _DEBUG_

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#define new DEBUG_NEW

#endif


/*********** 例程6:debug.h ***********/

条件编译

使用与平台有关的C语言函数,可能会使得程序不具有可移植性。比如Socket编程、多线程编程等是与平台有关的。

若想将程序做成平台无关的就需要用到与平台相关的条件编译。

编译器

GCC

#ifdef GNUC

#if GNUC >= 3 // GCC3.0以上

Visual C++

#ifdef _MSC_VER(非VC编译器很多地方也有定义)

#if _MSC_VER >=1000 // VC++4.0以上

#if _MSC_VER >=1100 // VC++5.0以上

#if _MSC_VER >=1200 // VC++6.0以上

#if _MSC_VER >=1300 // VC2003以上

#if _MSC_VER >=1400 // VC2005以上

Borland C++

#ifdef BORLANDC

UNIX

UNIX

#ifdef __unix

or

#ifdef unix

Linux

#ifdef __linux

or

#ifdef linux

FreeBSD

#ifdef FreeBSD

NetBSD

#ifdef NetBSD

Windows

32bit

#ifdef _WIN32(或者WIN32)

64bit

#ifdef _WIN64

GUI  App

#ifdef _WINDOWS

CUI  App

#ifdef _CONSOLE

Windows的Ver … WINVER

※ PC机Windows(95/98/Me/NT/2000/XP/Vista)和Windows CE都定义了

#if (WINVER >= 0x030a) // Windows 3.1以上

#if (WINVER >= 0x0400) // Windows 95/NT4.0以上

#if (WINVER >= 0x0410) // Windows 98以上

#if (WINVER >= 0x0500) // Windows Me/2000以上

#if (WINVER >= 0x0501) // Windows XP以上

#if (WINVER >= 0x0600) // Windows Vista以上

Windows 95/98/Me的Ver … _WIN32_WINDOWS

MFC App、PC机上(Windows CE没有定义)

#ifdef _WIN32_WINDOWS

#if (_WIN32_WINDOWS >= 0x0400) // Windows 95以上

#if (_WIN32_WINDOWS >= 0x0410) // Windows 98以上

#if (_WIN32_WINDOWS >= 0x0500) // Windows Me以上

Windows NT的Ver … _WIN32_WINNT

#if (_WIN32_WINNT >= 0x0500) // Windows 2000以上

#if (_WIN32_WINNT >= 0x0501) // Windows XP以上

#if (_WIN32_WINNT >= 0x0600) // Windows Vista以上

Windows CE(PocketPC)

#ifdef _WIN32_WCE

Windows CE … WINCEOSVER

Windows CE

WCE_IF

Internet Explorer的Ver … _WIN32_IE

Cygwin

Cygwin

#ifdef CYGWIN

32bit版Cygwin(现在好像还没有64bit版)

#ifdef CYGWIN32

MinGW(-mno-cygwin指定)

#ifdef MINGW32

程序测试:

#include

int main()

{

printf("The OS :");

#ifdef __linux

printf("Linux\n");

#endif

#ifdef _WIN32

printf("win 32\n");

#endif

#ifdef _WIN64

printf("win 64\n");

#endif

printf("The Compiler : ");

#ifdef GNUC

printf("GCC\n");

#endif

#ifdef _MSC_VER

printf("VC\n");

#endif

printf("Test Over!!!");

return ;

}

执行结果:

win7_32 CodeBlocks

Ubuntu_32 gcc

()c语言条件编译#ifdef与#if defined

c语言条件编译#ifdef与#if defined defined NAME是用来判断NAME是否被定义了(被用define定义了). #ifdef NAME == #if defined(NAME) ...

c语言条件编译#ifdef与#if defined

c语言条件编译#ifdef与#if defined  c语言条件编译#ifdef与#if defined 摘自:https://www.cnblogs.com/zhangshenghui/p/566 ...

c语言条件编译和预编译

转自http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html 一.C语言由源代码生成的各阶段如下: C源程序->编译预处理-&gt ...

C语言条件编译及编译预处理阶段(转)

一.C语言由源代码生成的各阶段如下: C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件 其中 编译预处理阶段,读取c源程序,对其中的 ...

【转】C语言条件编译及编译预处理阶段

原文: http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html 1. 宏定义(宏代换,宏替换,宏: 宏定义是C语言提供的3中预处理功能 ...

C语言条件编译(#if,#ifdef,#ifndef,#endif,#else,#elif)

1.条件编译介绍 条件编译(conditional compiling)命令指定预处理器依据特定的条件来判断保留或删除某段源代码.例如,可以使用条件编译让源代码适用于不同的目标系统,而不需要管理该源代 ...

条件编译,C语言条件编译详解

条件编译是指预处理器根据条件编译指令,有条件地选择源程序代码中的一部分代码作为输出,送给编译器进行编译.主要是为了有选择性地执行相应操作,防止宏替换内容(如文件等)的重复包含.常见的条件编译指令如表 ...

C语言-条件编译使用分析

1.基本概念 条件编译的行为类似于C语言中的if…else… 条件编译是预编译指示命令,用于控制是否编译某段代码 2.实例分析 条件编译初探   22-1.c #include

C语言 条件编译(if )

#include #define NUM -1 int main(int argc, const char * argv[]) { #if NUM > 0 pri ...

随机推荐

csuoj 1351: Tree Counting

这是一个动态规划的题: 当初想到要用dp,但是一直想不到状态转移的方程: 题解上的原话: 动态规划,设 g[i]表示总结点数为 i 的方案种数,另设 fi表示各个孩子的总结点数为i,孩子的个 ...

mysql忘记密码解决的办法

[很管用]忘记mysql root密码解决办法 1.编辑MySQL配置文件: 首先停止mysql服务, 然后开始编辑mysql配置文件:vi /etc/my.cnf在[mysqld]配置段添加如下一行 ...

jquery自定义函数

/** *jquery 的拓展方法 *//** * 给btn 添加去除disabled */$.fn.disabled = function() { $(this).each(function(ind ...

centos找不到环境变量 -bash: ls: command not found

#在系统中输入命令,报如下错误: [root@a1 work]# ll-bash: ls: command not found #昨时解决办法:export PATH=/usr/local/sbin: ...

linux存储管理之磁盘阵列

磁盘阵列 RAID ====================================================================================RAID:廉 ...

分布式知识点总结(来自CS-Notes)

转载地址:https://github.com/CyC2018/CS-Notes/blob/master/notes/%E5%88%86%E5%B8%83%E5%BC%8F.md 注:如Paxos等的 ...

c语言中printf()函数中的参数计算顺序

今天看到了一个关于printf()函数计算顺序的问题,首先看一个例子: #include int main() { printf("%d---%d---%d&q ...

jvm的内存分配

java内存分配 A:栈 存储局部变量 B:堆 存储所有new出来的 C:方法区(方法区的内存中) 类加载时 方法信息保存在一块称为方法区的内存中, 并不随你创建对象而随对象保存于堆中; D:本地方法 ...

64位RHEL5系统上运行yum出现&quot;This system is not registered with RHN”的解决方法

在红帽EL5上运行yum,提示“This system is not registered with RHN”,意思是没有在官网上注册,不能下载RH的软件包,替代方案是采用centos源. 1.卸载r ...

内联函数

一、什么是内联函数

   在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。

   为了解决这个问题,特别的引入了inline修饰符,表示为内联函数

   栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

下面我们来看一个例子:

#include <stdio.h>  


//函数定义为inline即:内联函数  

inline char* dbtest(int a)

{  

return (i % 2 > 0) ? "奇" : "偶";  

}  

 

int main()  

{  

int i = 0;  

for (i=1; i < 100; i++)

{  

printf("i:%d    奇偶性:%s /n", i, dbtest(i));      

}  

}

  上面的例子就是标准的内联函数的用法,使用inline修饰带来的好处我们表面看不出来,其实在内部的工作就是在每个for循环的内部任何调用dbtest(i)的地方都换成了(i%2>0)?"奇":"偶"这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

  其实这种有点类似咱们前面学习的动态库和静态库的问题,使 dbtest 函数中的代码直接被放到main 函数中,执行for 循环时,会不断调用这段代码,而不是不断地开辟一个函数栈。

二、内联函数的编程风格

1、关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用

如下风格的函数Foo 不能成为内联函数:

inline void Foo(int x, int y); // inline 仅与函数声明放在一起

void Foo(int x, int y)

{

}

而如下风格的函数Foo 则成为内联函数:

void Foo(int x, int y);

inline void Foo(int x, int y) // inline 与函数定义体放在一起

{

}

   所以说,inline 是一种 “用于实现的关键字” ,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline 关键字,但我认为inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

2、inline的使用是有所限制的

inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

三、慎用内联

   内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。

****

总结:

   因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦.而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定.所以,最好将内联函数定义放在头文件中.

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
2月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
2月前
|
自然语言处理 编译器 Linux
【C语言篇】编译和链接以及预处理介绍(上篇)1
【C语言篇】编译和链接以及预处理介绍(上篇)
42 1
|
1月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
27 2
|
2月前
|
编译器 Linux C语言
【C语言篇】编译和链接以及预处理介绍(下篇)
【C语言篇】编译和链接以及预处理介绍(下篇)
36 1
【C语言篇】编译和链接以及预处理介绍(下篇)
|
2月前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
2月前
|
编译器 C语言
C语言预处理详解
C语言预处理详解
|
2月前
|
存储 C语言
【C语言篇】编译和链接以及预处理介绍(上篇)2
【C语言篇】编译和链接以及预处理介绍(上篇)
40 0
|
4月前
|
存储 自然语言处理 程序员
【C语言】文件的编译链接和预处理
【C语言】文件的编译链接和预处理
|
4月前
|
程序员 编译器 C语言
C语言中的预处理指令及其实际应用
C语言中的预处理指令及其实际应用
91 0