4.6.C语言宏定义与预处理、函数和函数库
详解C语言的宏定义
详解C语⾔的宏定义宏定义介绍假设我们有⼀个 C 源⽂件 main.c,那么只需要通过 gcc main.c -o main.exe 即可编译成可执⾏⽂件(如果只写 gcc main.c,那么 Windows 上会默认⽣成 a.exe、Linux 上会默认⽣成 a.out ),但是这⼀步可以拆解成如下步骤:预处理:gcc -E main.c -o main.i,根据 C 源⽂件得到预处理之后的⽂件,这⼀步只是对 main.c 进⾏了预处理:⽐如宏定义展开、头⽂件展开、条件编译等等,同时将代码中的注释删除,注意:这⾥并不会检查语法;编译:gcc -S main.i -o main.s,将预处理后的⽂件进⾏编译、⽣成汇编⽂件,这⼀步会进⾏语法检测、变量的内存分配等等;汇编:gcc -c main.s -o main.o,根据汇编⽂件⽣成⽬标⽂件,当然我们也可以通过 gcc -c main.c -o main.o 直接通过 C 源⽂件得到⽬标⽂件;链接:gcc main.o -o main.exe,程序是需要依赖各种库的,可以是静态库也可以是动态库,因此需要将⽬标⽂件和其引⽤的库链接在⼀起,最终才能构成可执⾏的⼆进制⽂件。
⽽这⾥我们主要来介绍⼀下预处理中的宏定义,相信很多⼈都觉得宏定义⾮常简单,但其实宏定义有很多⾼级⽤法。
我们先来看看简单的宏定义:#include <stdio.h>// 宏定义的⽅式为:#define 标识符常量// 然后会将所有的 PI 替换成 3.14#define PI 3.14int main() {printf("%f\n", PI);}我们⽣成预处理之后的⽂件:gcc -E main.c -o main.i我们看到 PI 被替换成了 3.14,当然除了浮点型之外,也可以是其它的类型:#include <stdio.h>#define NAME "satori"#define AGE 17#define GENDER 'f'int main() {printf("%s %d %c\n", NAME, AGE, GENDER); // satori 17 f}我们再来查看⽣成的预处理⽂件:我们看到确实只是简单替换,除此之外,没有做任何的处理。
c 宏定义 用法
c 宏定义用法
C语言中的宏定义是一种预处理指令,用于在编译之前将一些常量、函数、代码块等定义为一个标识符,方便代码的编写与管理。
宏定义的格式为:#define 标识符替换文本。
其中,标识符可以是任何合法的C语言标识符,替换文本可以是任何合法的C语句,包括常量、表达式、函数等。
宏定义有以下几种用法:
1. 定义常量:可以用宏定义来定义一些常量,例如#define PI 3.1415926,这样在代码中使用PI就相当于使用了常量3.1415926。
2. 定义函数:可以用宏定义来定义一些简单的函数,例如
#define ADD(a,b) (a+b),这样在代码中使用ADD(x,y)就相当于使用了函数x+y。
3. 定义代码块:可以用宏定义来定义一些代码块,例如#define SQUARE(x) (x*x),这样在代码中使用SQUARE(a)就相当于使用了a*a。
4. 定义条件编译:可以用宏定义来定义一些条件编译语句,例如#define DEBUG,这样在代码中使用#ifdef DEBUG和#endif就可以编写一些只在调试模式下才执行的代码。
需要注意的是,宏定义是一种简单的文本替换,因此可能会导致一些不可预期的问题,例如宏定义中的参数没有被正确使用时,就可能会导致编译错误或者逻辑错误。
同时,宏定义也不适用于一些复杂的操作,例如大量的循环、递归等,因为这可能会导致代码体积变大、可读性变差等问题。
因此,在使用宏定义时需要谨慎。
C语言宏定义
C语言宏定义C语言宏定义C语言既具有高级语言的功能,又具有低级语言的许多功能。
那么大家知道C语言宏定义是怎样的呢?下面一起来看看!宏定义是预处理命令的一种,它允许用一个标识符来表示一个字符串。
先看一个例子:#include#define N 100int main(){ int sum = 20 + N; printf("%d ", sum); return 0;}运行结果:120该示例中的语句int sum = 20 + N;,N被100代替了。
#define N 100就是宏定义,N为宏名,100是宏的内容。
在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。
宏定义是由源程序中的宏定义命令#define完成的,宏代换是由预处理程序完成的。
宏定义的一般形式为:#define 宏名字符串#表示这是一条预处理命令,所有的预处理命令都以#开头。
define是预处理命令。
宏名是标识符的一种,命名规则和标识符相同。
字符串可以是常数、表达式等。
这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号。
程序中反复使用的表达式就可以使用宏定义,例如:#define M (n*n+3*n)它的作用是指定标识符M来代替表达式(y*y+3*y)。
在编写源程序时,所有的(y*y+3*y)都可由M代替,而对源程序编译时,将先由预处理程序进行宏代换,即用(y*y+3*y)表达式去替换所有的宏名M,然后再进行编译。
将上面的例子补充完整:#include#define M (n*n+3*n)int main(){ int sum, n; printf("Input a number: "); scanf("%d", &n); sum = 3*M+4*M+5*M; printf("sum=%d ", n); return 0;}运行结果:Input a number: 10↙sum=1560上面的程序中首先进行宏定义,定义M来替代表达式(n*n+3*n),在sum=3*M+4*M+5*M中作了宏调用。
C语言预处理的相关知识
C语言预处理的相关知识C语言预处理的相关知识导语:在C语言编译的时候,会经历以下几个步骤:预处理,编译,汇编,链接,然后生成可执行文件。
整个过程是一连串动作完成的。
而预处理阶段呢,也是在最先执行的一个步骤。
相对来说,也是比较重要的一个步骤。
那么C语言预处理的相关知识呢?一起来学习下吧:概念:以“#”号开头的预处理指令如包含#include,宏定义制定#define等,在源程序中这些指令都放在函数之外,而且一般放在源文件的前面,所谓预处理其实就是在编译的第一遍扫描之前的所作的工作,预处理是c语言的一个重要的功能,它由预处理程序单独完成,当对一个源文件进行编译时,系统自动引用预处理程序,预处理在源代码编译之前对其进行的一些文本性质的操作,对源程序编译之前做一些处理,生成扩展的C源程序预处理阶段做了任务:1:将头文件中的内容(源文件之外的文件)插入到源文件中2:进行了宏替换的过程,定义和替换了由#define指令定义的符号3:删除掉注释的过程,注释是不会带入到编译阶段4:条件编译预处理指令:gcc -E bin/helloworld.i src/helloworld.c预处理生成的是.i的文本文件,这个文本文件是可以直接通过cat 命令进行文本文件查看的宏定义在C语言中允许用一个标识符来表示一个字符串;称为宏,在预处理时,对程序的宏进行替换,其中宏定义是由源程序中的#define来完成,而宏的替换,主要是由预处理程序完成的#define PI 3.1415宏定义的规则:#表示一条预处理的指令,以#开头的均是预处理指令#define是宏定义的指令,标识符是所定义的宏名宏名一般都是大写的字母表示,以便和变量名区别宏定义其实并不是C语言的语句,所以后面是不用去加;号宏体可以是常数,表达式,格式化字符串等,为表达式的时候应该用括号阔起来宏替换不分配内存空间,也不做正确性的检查宏的范围是从定义后到本源文件的结束,但是可以通过#undef来进行提前取消宏定义分为有参宏定义和无参宏定义:无参宏定义:语法:#define 标识符(宏名)[字符串]宏体可缺省:#define YES 1#define NO 0#define OUT printf("Hello world")#define WIDTH 80#define LENGTH (WIDTH+40)宏的移除语法#undef 宏名功能:删除前面定义的宏事例:#undef PI#undef OUT#undef YES#undef NO带参宏定义:带参宏定义的语法结构#define 宏名(形参列表) 字符串(宏体)带参数宏定义调用:宏名(实参表);C语言中允许宏带有参数,在宏定义的参数中称为形式参数,形式参数不分配内存单元,没有类型定义;#define S(a,b) a*b;area = S(3,2);宏展开 area = 3 * 2;注意事项:带参数宏定义中,宏名和形式参数列表之间不能有空格出现。
c语言的预处理指令分3种 1宏定义 2条件编译 3文件包含
c语⾔的预处理指令分3种 1宏定义 2条件编译 3⽂件包含宏简介1.C语⾔在对源程序进⾏编译之前,会先对⼀些特殊的预处理指令作解释(⽐如之前使⽤的#include⽂件包含指令),产⽣⼀个新的源程序(这个过程称为编译预处理),之后再进⾏通常的编译所有的预处理指令都是以#开头,并且结尾不⽤分号2.预处理指令分3种 1> 宏定义 2> 条件编译 3> ⽂件包含3.预处理指令在代码翻译成0和1之前执⾏4.预处理的位置是随便写的5.预处理指令的作⽤域:从编写指令的那⼀⾏开始,⼀直到⽂件结尾,可以⽤#undef取消宏定义的作⽤6.宏名⼀般⽤⼤写或者以k开头,变量名⼀般⽤⼩写 宏定义可以分为2种:不带参数的宏定义和带参数的宏定义。
⼀、不带参数的宏定义1.⼀般形式#define 宏名字符串⽐如#define ABC 10右边的字符串也可以省略,⽐如#define ABC2.作⽤它的作⽤是在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常⽤来定义常量.3.使⽤习惯与注意1> 宏名⼀般⽤⼤写字母,以便与变量名区别开来,但⽤⼩写也没有语法错误2> 对程序中⽤双引号扩起来的字符串内的字符,不进⾏宏的替换操作。
3> 在编译预处理⽤字符串替换宏名时,不作语法检查,只是简单的字符串替换。
只有在编译的时候才对已经展开宏名的源程序进⾏语法检查4> 宏名的有效范围是从定义位置到⽂件结束。
如果需要终⽌宏定义的作⽤域,可以⽤#undef命令5> 定义⼀个宏时可以引⽤已经定义的宏名#define R 3.0#define PI 3.14#define L 2*PI*R#define S PI*R*R举例1 #include <stdio.h>2#define COUNT 434int main()5 {6char *name = "COUNT";78 printf("%s\n", name);910int ages[COUNT] = {1, 2, 67, 89};1112#define kCount 41314for ( int i = 0; i<COUNT; i++) {15 printf("%d\n", ages[i]);16 }1718// 从这⾏开始,COUNT这个宏就失效19#undef COUNT2021//int a = COUNT 写这个报错2223return0;24 }⼆、带参数的宏定义1.⼀般形式#define 宏名(参数列表) 字符串2.作⽤在编译预处理时,将源程序中所有宏名替换成字符串,并且将字符串中的参数⽤宏名右边参数列表中的参数替换3.使⽤注意1> 宏名和参数列表之间不能有空格,否则空格后⾯的所有字符串都作为替换的字符串2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进⾏任何计算操作。
C语言 数据类型与宏定义
6
注意: 注意:
1)宏替换时仅仅是将源程序中与宏名相同的标识符替换 成宏的内容文本,并不对宏的内容文本做任何处理。 2)C语言程序员通常用大写字母来定义宏名,以便与变 量名相区别。 3 3)宏定义时,如果字符串太长,需要写多行,可以在行 尾使用反斜线“\”续行符。例如: #define LONG_STRING "this is a very long \ string that is used as an example" 注意双引号包括在替代的内容之内。
11
(9)在定义宏时,如果宏是一个表达式,那么一定要将这个表 )在定义宏时,如果宏是一个表达式, 达式用() ()括起来 达式用()括起来
#define X 10 #define Y 20 #define Z X+Y void main() { int a=2,b=3; a*=Z; b=b*Z; printf("a=%d,b=%d\n", a,b); }
19
文件包含两种格式
1)使用尖括号<>:直接到系统指定的“文件包含目 录去查找被包含的文件。 2)使用双引号” ”:系统首先到当前目录下查找被 包含文件,如果没找到,再到系统指定的“文件包 含目录”去查找。一般情况下,使用双引号比较保 险。 注意 ” ”之间可以指定包含文件的路径。如 #include”c:\\prg\\p1.h”表示将把C盘prg目录下的 文件p1.h的内容插入到此处(字符串中要表示‘\’, 必须使用‘\\’)。
22
void main() { 程序实例如下 float price1,price2,sumprice; 源程序 scanf("%f%f",&price1,&price 2); #define USA 0 sumprice=price1+price2; #define ENGLAND 1 printf("sum=%.2f%s",sumpri #define FRANCE 2 #define ACTIVE_COUNTRY ce,currency); } USA #if ACTIVE_COUNTRY==USA char *currency="dollar"; //有效 #elif ACTIVE_COUNTRY==ENG LAND char *currency="pound"; #else char *currency="france"; #endif
C语言中的宏定义
下面是预处理后的这条语句:
1. n=((i)>(((j)>(k)?(j):(k)))?(i):(((j)>(k)?(j):(k))));
2) 、宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们是 否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个出错信 息。预处理器不会检查宏参数的类型,也不会进行类型转换。 3) 、无法用一个指针来指向一个宏。如在 17.7 节中将看到的,C 语言允许指针指向函数。 这一概念在特定的编程条件下非常有用。 宏会在预处理过程中被删除, 所以不存在类似的 “指 向宏的指针”。因此,宏不能用于处理这些情况。 4) 、宏可能会不止一次地计算它的参数。函数对它的参数只会计算一次,而宏可能会计算 两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。考虑下面 的例子,其中 MAX 的一个参数有副作用:
1. #define getchar() getc(stdin)
空的参数列表不是一定确实需要, 但可以使 getchar 更像一个函数。 (没错, 这就是<stdio.h> 中的 getchar,getchar 的确就是个宏,不是函数 ——虽然它的功能像个函数。) 使用带参数的宏替代实际的函数的优点: 1) 、 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销—— 存储上下文 信息、复制参数的值等。而一个宏的调用则没有这些运行开销。 2) 、 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序 依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用 MAX 宏从两个数中选出 较大的一个,数的类型可以是 int,long int,float,double 等等。 但是带参数的宏也有一些缺点。
C语言中的宏定义用法
C语言中的宏定义用法宏定义是C语言中一种重要的预处理指令,通过宏定义可以为一些常用的代码片段或数值指定名称,方便程序开发和维护。
本文将介绍C语言中宏定义的用法和注意事项。
首先,在C语言中,宏定义使用“#define”关键字进行定义,其语法格式为:```#define 宏名称值```其中,宏名称是自定义的标识符,可以是任意有效的变量名或符号;值可以是任意的表达式、常量或代码片段。
通过宏定义,我们可以将一些重复使用的代码片段定义为宏,以提高代码的重用性和可读性。
在使用宏定义时,需要注意以下几点:1. 宏定义不需要分号结尾,直接写在宏定义行即可。
2. 宏名称一般使用大写字母表示,以区分于普通变量。
3. 宏定义的值可以是任意合法的C语句,但最好使用括号将其括起来,防止优先级问题。
4. 宏定义中可以使用参数,以实现不同场景下的值替换。
除了定义普通的宏之外,C语言中还有一种特殊的宏定义“#define MAX(a, b) ((a) > (b) ? (a) : (b))”,这种宏定义被称为宏函数,可以实现简单的函数功能。
宏函数通常使用括号将参数括起来,以确保表达式的正确性。
另外,C语言中还有一些系统预定义的宏,如“__FILE__”表示当前文件名,“__LINE__”表示当前行号,“__FUNCTION__”表示当前函数名等。
这些宏可以在调试和错误提示时起到一定的作用,方便程序员定位问题。
在使用宏定义时,需要注意一些潜在的问题,如:1. 宏定义的替换是简单的文本替换,可能会产生一些意外的结果。
2. 宏定义带来的代码重复可能会增加代码的长度,降低代码的可读性。
3. 在调试时,宏定义会隐藏实际代码逻辑,导致调试困难。
综上所述,C语言中的宏定义是一种方便而强大的工具,可以提高代码的可维护性和可读性。
在使用宏定义时,需要注意语法规范和潜在的问题,以充分发挥其优势。
通过合理地运用宏定义,可以使程序更加简洁高效,提升开发效率。
C语言预处理命令--宏定义
C语⾔预处理命令--宏定义⼀、宏讲解1、宏定义宏(Macro),是⼀种的称谓。
⾥的宏是⼀种(Abstraction),它根据⼀系列预定义的规则替换⼀定的⽂本模式。
或在遇到宏时会⾃动进⾏这⼀模式替换。
2、C语⾔宏定义的常规⽤法1) 定义符号常量#define PI 3.1415926#define MAX_N 100002) 定义傻⽠表达式(注意,定义的这种表达式⼀不⼩⼼很容易出现bug,下⽂会讲)#define S(a, b) a * b#define MAX(a, b) (a) > (b) ? (a) : (b)3) 定义代码段#define P(a) {\ printf("%d\n", a);\} ps:编译器对于宏的解析是很严谨的,只能⽀持⼀⾏解析,\是起连接作⽤,表⽰当⾏的宏代码与下⼀⾏宏连接在⼀起,使得编译器当成⼀⾏看待。
3、编译器预定义的宏在C语⾔中,我们有很多预定义的宏,就是C语⾔帮程序员预先定义好的宏,可以让我们使⽤。
宏说明__DATE__ ⽇期:Mmm dd yyyy__TIME__ 时间:hh:mm:ss__LINE__ 当前源⽂件的代码⾏号__FILE__ ⽂件名__func__ 函数名/⾮标准__FUNC__ 函数名/⾮标准__PRETTY_FUNCTION__ 更详细的函数信息/⾮标准4、预定义命令-条件式编译函数说明#ifdef DEBUG 是否定义了DEBUG宏#ifndef DEBUG 是否没有定义DEBUG宏#if MAX_N == 5 宏MAX_N是否等于5#elif MAX_N == 4 否则宏MAX_N是否等于4#else#endif5、预定义命令从上图可以看到: 预编译 将.c ⽂件转化成 .i⽂件 使⽤的gcc命令是:gcc –E 对应于预处理命令cpp 编译 将.c/.h⽂件转换成.s⽂件 使⽤的gcc命令是:gcc –S 对应于编译命令 cc –S 汇编 将.s ⽂件转化成 .o⽂件 使⽤的gcc 命令是:gcc –c 对应于汇编命令是 as 链接 将.o⽂件转化成可执⾏程序 使⽤的gcc 命令是: gcc 对应于链接命令是 ld 总结起来编译过程就上⾯的四个过程:预编译、编译、汇编、链接。
C语言预处理命令总结大全:宏定义
C语⾔预处理命令总结⼤全:宏定义C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。
虽然它们实际上不是C语⾔的⼀部分,但却扩展了C程序设计的环境。
本节将介绍如何应⽤预处理程序和注释简化程序开发过程,并提⾼程序的可读性。
ANSI标准定义的C语⾔预处理程序包括下列命令:#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。
⾮常明显,所有预处理命令均以符号#开头,下⾯分别加以介绍。
⼀ #define命令#define定义了⼀个标识符及⼀个串。
在源程序中每次遇到该标识符时,均以定义的串代换它。
ANSI标准将标识符定义为宏名,将替换过程称为宏替换。
命令的⼀般形式为:#define ID string注意:1该语句没有分号。
在标识符和串之间可以有任意个空格,串⼀旦开始,仅由⼀新⾏结束。
2宏名定义后,即可成为其它宏名定义中的⼀部分。
3 宏替换仅仅是以⽂本串代替宏标识符,前提是宏标识符必须独⽴的识别出来,否则不进⾏替换。
例如:#define XYZ this is a tes使⽤宏printf("XYZ");//该段不打印"this is a test"⽽打印"XYZ"。
因为预编译器识别出的是"XYZ"4如果串长于⼀⾏,可以在该⾏末尾⽤⼀反斜杠' \'续⾏。
#defineLONG_STRING"this is a very long\string that is used as an example"5 C语⾔程序普遍使⽤⼤写字母定义标识符。
6 ⽤宏代换代替实在的函数的⼀⼤好处是宏替换增加了代码的速度,因为不存在函数调⽤的开销。
但增加速度也有代价:由于重复编码⽽增加了程序长度。
c 宏定义 用法
c 宏定义用法C语言中的宏定义是一种将代码中的标识符替换为所定义的文本的预处理指令。
宏定义通常被用来维护代码的可读性和可维护性,在编写C程序时是非常有用的。
C宏定义的基础语法非常简单,使用#define指令来定义一个宏,例如:#define PI 3.14159这条指令的意思是将PI这个标识符替换为3.14159,这样在代码中使用PI时就会被视作3.14159。
在这之后,我们就可以在代码的任何地方使用PI这个宏定义。
宏定义是C语言中的一种预处理指令,它在编译时被翻译为它所对应的代码。
在这种情况下,引用宏定义的每个地方都被替换为其面值(即所定义的文本)。
这可能会导致代码的一些小问题,例如:#define MAX 100...int array[MAX+1];...这里MAX+1被替换为101,因此使用了101个位置而不是100。
为了避免这种情况,我们可以使用括号来完整限制宏定义的范围:#define MAX 100...int array[(MAX)+1];此时,因为MAX被包含在括号中,它与+1结合而不是100+1结合。
这样便可以得到正确的数组大小。
另外一个常见的宏定义用法是使用条件编译。
条件编译是一种技术,它允许我们根据条件选择性地编译代码,以便在不同情况下使用不同的代码。
条件编译通常用于编写可移植的代码,或在不同的操作系统或编译器上编写代码的变体。
以下是一个条件编译的示例:#ifdef DEBUGprintf("Debugging information: x = %d\n", x);#endif在上面的代码中,#ifdef指令用来测试一个变量(在这个例子中是DEBUG)是否已经被定义。
如果已经定义,那么代码将被编译。
否则,它会被忽略。
最后,宏定义的一个不错的用途是创建递归宏。
递归宏是指在宏的定义中调用该宏。
这可以通过在宏定义中使用递归调用来实现,并可以用来简化一些特定的计算。
c语言 宏定义 运算
c语言宏定义运算在C 语言中,宏定义是一种预处理指令,可以用来创建简单的代码替换。
宏定义允许你使用宏名称来表示一系列的代码或表达式,并在预处理阶段将其替换为实际的代码。
下面是一些关于宏定义和运算的基本示例:1. 简单宏定义:```c#include <stdio.h>// 定义一个简单的宏,将MAX 定义为两个数中的较大者#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {int x = 10, y = 20;printf("Max of %d and %d is %d\n", x, y, MAX(x, y));return 0;}```在这个例子中,`MAX` 宏接受两个参数`a` 和`b`,并返回它们中较大的一个。
2. 使用宏进行运算:```c#include <stdio.h>// 定义宏,计算一个数的平方#define SQUARE(x) ((x) * (x))int main() {int num = 5;printf("Square of %d is %d\n", num, SQUARE(num));return 0;}```这个例子中,`SQUARE` 宏用于计算一个数的平方。
3. 使用`#define` 定义常量:```c#include <stdio.h>// 使用#define 定义常量#define PI 3.14159int main() {double radius = 2.5;double area = PI * radius * radius;printf("Area of the circle with radius %lf is %lf\n", radius, area);return 0;}```在这个例子中,`PI` 被定义为常量,用于表示圆周率。
c语言宏定义用法规则
c语言宏定义用法规则C语言宏定义是一种C语言中最常使用的技术,它可以将经常使用的长句子等缩短,可以将复杂的语句也变得更加简单。
使用宏定义,可以提高程序的可读性,使程序更加便于维护和修改,并且可以更加高效地使用程序。
一、宏定义的语法形式C语言的宏定义的语法有以下几种格式:1. #define:#define宏定义用于定义字符串宏或符号宏,本质上它就是把特定的字符串或符号,映射到一个相应的宏名称。
例如:#define PI 3.14159293表示宏定义一个PI,其值为3.141592932. #undef:#undef用于取消宏定义,例如:#undef PI表示取消之前定义流程中的PI宏定义;3. #ifdef:#ifdef可以根据宏定义的存在与否,在程序编译时有选择性的执行一段代码,例如:#ifdef PIprintf("PI is define\n");#endif上述代码表示:如果PI的宏定义存在的话,则编译执行printf("PI is define\n"),否则不执行。
C语言宏定义可以使用参数,这些参数可以是函数、符号、字符串或者表达式,它们都可以在宏定义中扮演角色,使用参数可以提高宏的可扩展性,从而提高程序的复用性,简化程序的结构。
1. 宏定义参数的表示参数的格式使用形式参数名称来表示,一般使用字母a~z表示参数,形式参数可以使用多次,但参数名必须是唯一的。
例如:#define MIN(x, y) ((x) < (y))? (x): (y)这是一个使用参数的宏定义示例,其中参数x,y是表示形式参数的名称,宏定义的意思是返回x和y的较小值。
使用宏定义参数需要在宏定义时明确参数的含义,每个参数都必须有明确的含义,有利于后续的维护和修改。
例如:三、C语言宏定义书写规范1. #define是注释符号,使用时要在一行的开头,以#开头,表示此行的内容是宏定义,且宏定义的关键词必须全大写。
考研c语言知识点总结
考研c语言知识点总结在计算机科学与技术的学习中,C语言是非常重要的一门语言。
它是一种中级语言,同时也是一种通用结构化语言,具有高效的机器级操作能力。
在计算机领域中,C语言广泛应用于系统软件的开发,大型应用程序的编写以及各种编程环境的构建。
一、C语言基础知识点1. 变量和数据类型在C语言中,变量是程序中用于存储数据值的一种占位符。
变量的类型决定了变量的存储方式,以及该存储空间内可以存储的数据的类型。
C语言中的数据类型包括整型、浮点型、字符型、指针和布尔型等。
对于不同的数据类型,在内存中会分配不同长度的存储空间。
2. 运算符和表达式C语言中的运算符包括算术运算符、关系运算符、逻辑运算符、赋值运算符等。
对于不同的运算符,它们具有不同的优先级以及结合性。
表达式是由变量、常量、运算符和函数调用等组成的。
C语言中的表达式会被编译器解析,并生成相应的指令来执行表达式的计算。
3. 控制结构C语言中的控制结构包括顺序结构、选择结构和循环结构。
顺序结构是程序中的基本结构,程序中的语句按照它们出现的顺序依次执行。
选择结构用于根据条件来确定程序执行的路径,包括if语句、switch语句等。
循环结构用于重复执行一段程序代码,包括for循环、while循环和do...while循环等。
4. 函数在C语言中,函数是一段可重复使用的代码块。
函数能够接受参数,在函数体内对参数进行处理,并返回一个值。
C语言中还可以使用指针作为函数的参数,从而可以修改函数外面的变量。
函数的调用过程是根据栈结构来完成的,函数的递归调用是通过栈实现的。
5. 数组和指针数组是由相同类型的元素组成的数据集合,C语言中的数组是一种静态数据结构,数组的元素在内存中是连续分配的。
指针是一个存储变量地址的变量,能够存储其他变量的地址,并能对其他变量进行间接访问。
指针与数组有天然的联系,可以通过指针进行数组元素的访问。
6. 字符串和结构体字符串是一串字符的集合,C语言中的字符串可以使用字符数组或指针来表示,并且在字符串的末尾会有一个'\0'表示字符串的结束。
C语言中的预编译宏定义
C语言中的预编译宏定义C语言中的预编译宏定义导语:C初学者可能对预处理器没什么概念,这是情有可原,下面是C中的预编译宏定义,一起来学习下吧:(一) 预处理命令简介预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:#define 定义一个预处理宏#undef 取消宏的定义#include 包含文件命令#include_next 与#include相似, 但它有着特殊的用途#if 编译预处理中的条件命令, 相当于C语法中的if语句#ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句#ifndef 与#ifdef相反, 判断某个宏是否未被定义#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif 之后的语句, 相当于C语法中的else-if#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else 之后的语句, 相当于C语法中的else#endif #if, #ifdef, #ifndef这些条件命令的结束标志.defined 与#if, #elif配合使用, 判断某个宏是否被定义#line 标志该语句所在的行号# 将宏参数替代为以参数值为内容的字符窜常量## 将两个相邻的标记(token)连接为一个单独的标记#pragma 说明编译器信息#warning 显示编译警告信息#error 显示编译错误信息(二) 预处理的文法预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.预处理语句格式: #command name(...) token(s)1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).3, 语句中可以利用""来换行.e.g.# define ONE 1 /* ONE == 1 */等价于: #define ONE 1#define err(flag, msg) if(flag)printf(msg)等价于: #define err(flag, msg) if(flag) printf(msg)(三) 预处理命令详述1, #define#define命令定义一个宏:#define MACRO_NAME(args) tokens(opt)之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.对象宏不带参数的宏被称为"对象宏(objectlike macro)"#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.e.g.#define MAX 100int a[MAX];#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endif#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.函数宏带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.函数宏的参数是固定的情况函数宏的定义采用这样的方式: #define name( args ) tokens其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.e.g.#define mul(x,y) ((x)*(y))注意, 函数宏之后的参数要用括号括起来, 看看这个例子:e.g.#define mul(x,y) x*y"mul(1, 2+2);" 将被扩展为: 1*2 + 2同样, 整个标记串也应该用括号引用起来:e.g.#define mul(x,y) (x)*(y)sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:e.g.mul (f(a,b), g(c,d));e.g.#define (stmt) stmt( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符.((a=1, b=2;)) 可解决上述问题.在定义和调用函数宏时候, 要注意一些问题:1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.example_3.7:#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp};明显后面的;是多余的, 我们应该这样调用: swap(1,2)虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:#define swap(x,y)do { unsigned long _temp=x; x=y; y=_tmp} while (0)swap(1,2); 将被替换为:do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.eg_3.8:#define incr(v, low, high)for ((v) = (low),; (v) <= (high); (v)++)只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */函数宏中的参数包括可变参数列表的情况C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.#define name(args, ...) tokens#define name(...) tokens"..."代表可变参数列表, 如果它不是仅有的'参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃).通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.e.g.#ifdef DEBUG#define my_printf(...) fprintf(stderr, __VA_ARGS__)#else#define my_printf(...) printf(__VA_ARGS__)#endiftokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表.注意在使用#define时候的一些常见错误:#define MAX = 100#define MAX 100;=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";".注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍.关于定义宏的另外一些问题(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...e.g.#define NULL 0#define NULL /* null pointer */ 0上面的重定义是相同的, 但下面的重定义不同:#define fun(x) x+1#define fun(x) x + 1 或: #define fun(y) y+1如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.(2) 在gcc中, 可在命令行中指定对象宏的定义:e.g.$ gcc -Wall -DMAX=100 -o tmp tmp.c相当于在tmp.c中添加" #define MAX 100".那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?---若-DMAX=1, 则正确编译.---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc 赋予该宏默认值(1), 如: -DVAL == -DVAL=1(3) #define所定义的宏的作用域宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别e.g.#define ONE 1sum = ONE + TWO /* sum = 1 + TWO */#define TWO 2sum = ONE + TWO /* sum = 1 + 2 */#undef ONEsum = ONE + TWO /* sum = ONE + 2 */char c[] = "TWO" /* c[] = "TWO", NOT "2"! */(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.e.g.# define ONE NUMBER_1# define NUMBER_1 1int a = ONE /* a = 1 */2, #undef#undef用来取消宏定义, 它与#define对立:#undef name如够被取消的宏实际上没有被#define所定义, 针对它的#undef 并不会产生错误.当一个宏定义被取消后, 可以再度定义它.3, #if, #elif, #else, #endif#if, #elif, #else, #endif用于条件编译:#if 常量表达式1语句...#elif 常量表达式2语句...#elif 常量表达式3语句......#else语句...#endif#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.e.g.#if 0{一大段代码;}#endif常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.#if MACRO_NON_DEFINED == #if 0在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.4, #ifdef, #ifndef, defined.#ifdef, #ifndef, defined用来测试某个宏是否被定义#ifdef name 或 #ifndef name它们经常用于避免头文件的重复引用:#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endifdefined(name): 若宏被定义,则返回1, 否则返回0.它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:#if defined(VAX) && defined(UNIX) && !defined(DEBUG)和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.5, #include , #include_next#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.#include "headfile"#include#include 预处理标记前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include 包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.关于#include "headfile"和#include 的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.相对于#include, 我们对#include_next不太熟悉. #include_next 仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.可参考cpp手册进一步了解#include_next6, 预定义宏标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:__LINE__ 当前语句所在的行号, 以10进制整数标注.__FILE__ 当前源文件的文件名, 以字符串常量标注.__DATE__ 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.__TIME__ 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.__STDC__ 如果当前编译器符合ISO标准, 那么该宏的值为1__STDC_VERSION__ 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L.我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?__STDC_HOSTED__ 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.gcc定义的预定义宏:__OPTMIZE__ 如果编译过程中使用了优化, 那么该宏被定义为1.__OPTMIZE_SIZE__ 同上, 但仅在优化是针对代码大小而非速度时才被定义为1.__VERSION__ 显示所用gcc的版本号.可参考"GCC the complete reference".要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null7, #line#line用来修改__LINE__和__FILE__.e.g.printf("line: %d, file: %s ", __LINE__, __FILE__);#line 100 "haha"printf("line: %d, file: %s ", __LINE__, __FILE__);printf("line: %d, file: %s ", __LINE__, __FILE__);显示:line: 34, file: 1.cline: 100, file: hahaline: 101, file: haha8, #pragma, _Pragma#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:#pragma GCC name token(s)#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.(1) #pragma GCC dependencydependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.e.g.在demo.c中给出这样一句:#pragma GCC dependency "temp-file"然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息: warning: current file is older than temp-file如果当前文件比指定的文件新, 则不给出任何警告信息.还可以在在#pragma中给添加自定义的警告信息.e.g.#pragma GCC dependency "temp-file" "demo.c needs to be updated!"1.c:27:38: warning: extra tokens at end of #pragma directive1.c:27:38: warning: current file is older than temp-file注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.(2) #pragma GCC poison token(s)若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.e.g.#pragma GCC poison scanfscanf("%d", &a);warning: extra tokens at end of #pragma directiveerror: attempt to use poisoned "scanf"注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.(3) #pragma GCC system_header从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用#warning显式指明).(这条#pragma语句还没发现用什么大的用处)由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:e.g.#define PRAGMA_DEP #pragma GCC dependency "temp-file"由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:#define PRAGMA_DEP _Pragma("GCC dependency "temp-file"")注意, ()中包含的""引用之前引该加上转义字符.9, #, ###和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.e.g.#define TEST(a,b) printf( #a "<" #b "=%d ", (a)<(b));注意: #只针对紧随其后的token有效!##用于将它前后的两个token组合在一起转换成以这两个token 为内容的字符串常量. 注意##前后必须要有token.e.g.#define TYPE(type, n) type n之后调用:TYPE(int, a) = 1;TYPE(long, b) = 1999;将被替换为:int a = 1;long b = 1999;(10) #warning, #error#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:#warning tokens#error tokense.g.#warning "some warning"注意, #error和#warning后的token要用""引用起来!(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.【C语言中的预编译宏定义】。
C语言中的宏定义
C语言中的宏定义1. 简单宏定义简单的宏定义有如下格式:[#define指令(简单的宏)] #define 标识符替换列表替换列表是一系列的C语言记号,包括标识符、关键字、数、字符常量、字符串字面量、运算符和标点符号。
当预处理器遇到一个宏定义时,会做一个“标识符”代表“替换列表”的记录。
在文件后面的内容中,不管标识符在任何位置出现,预处理器都会用替换列表代替它。
不要在宏定义中放置任何额外的符号,否则它们会被作为替换列表的一部分。
一种常见的错误是在宏定义中使用 = :1.#define N = 100 /*** WRONG ***/2.int a[N]; /* 会成为 int a[= 100]; */在上面的例子中,我们(错误地)把N定义成一对记号(= 和100)。
在宏定义的末尾使用分号结尾是另一个常见错误:1.#define N 100; /*** WRONG ***/2.int a[N]; /* become int a[100;]; */这里N被定义为100和;两个记号。
在一个宏定义中,编译器可以检测到绝大多数由多余符号所导致的错误。
但不幸的是,编译器会将每一处使用这个宏的地方标为错误,而不会直接找到错误的根源——宏定义本身,因为宏定义已经被预处理器删除了。
简单的宏主要用来定义那些被Kernighan和Ritchie称为“明示常量”(manifest constant)的东西。
使用宏,我们可以给数值、字符和字符串命名。
1.#define STE_LEN 802.3.#defineTRUE 14.5.#defineFALSE 06.7.#definePI 3.141598.9.#defineCR '\r'10.11.#defineEOS '\0'使用#define来为常量命名有许多显著的优点:1) 、程序会更易读。
一个认真选择的名字可以帮助读者理解常量的意义。
否则,程序将包含大量的“魔法数”,使读者难以理解。
C语言宏定义与预编译详解
C语言宏定义与预编译详解/findaway123/article/details/6994203 20111.宏定义和函数的区别-------------------------------------------------------------------------------------------------------------------宏:宏定义是C提供的三种预处理功能的其中一种,这三种预处理包括:(1)宏定义(2)文件包含(3)条件编译1.不带参数的宏定义:格式:#define 标识符字符串标示符就是可以替换字符串的宏名称,编译器在进行预处理过程中对使用宏替换的地方展开,用“字符串”替换宏名称,这样做的好处就是可以对数字或者随机数值指定一个有代表意义的名称,提高程序的可读性和通用性,在后期维护中可以方便的修改宏定义中字符串的数值大小或者其他数值类型,从而可以控制整个代码中所有引用宏名的地方。
从占用资源的角度看,编译器对宏替换不会占用或者分配内存,和函数比较,调用子函数需要分配内存,增加系统开销,但子函数可以把程序结构模块化,提高程序的可读性和聚合度,对比之下,宏也可以有参数,如果在程序中为了不调用子函数而减小开销,那么所有过程都写在一个函数中,并且没有自注释的名称,程序的可读性就会降低,毕竟代码是处理现实世界中事务数据关系的一种抽象,但不是一个人的,应该是像一首简洁,优美的诗,描述了美好的事务,所以折中之下,宏替换是个不错的选择。
虽然宏替换占用了编译器的时间,所谓“有得必有失”,减小了程序执行的资源消耗,这未尝不是一种平衡。
宏的一些特点(引用):(1)宏名一般用大写(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。
例如:数组大小常用宏定义,可以理解数组大小代表具体含义,便于二次维护。
(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
宏定义(无参宏定义和带参宏定义),C语言宏定义详解
宏定义(⽆参宏定义和带参宏定义),C 语⾔宏定义详解1、宏定义说明宏定义是⽐较常⽤的预处理指令,即使⽤“标识符”来表⽰“替换列表”中的内容。
标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容。
常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。
2、⽆参宏定义⽆参数宏定义的格式为:#define 标识符替换列表替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使⽤标识符表⽰⼀常量,或称符号常量。
说明:1. 可以不在⾏⾸,但只允许它前⾯有空格符。
例如:#define PI 3.1416 //正确,该⾏#前允许有空格int a;#define N 5 //错误,该⾏#前不允许有空格外的其他字符2. 标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号#define N =5 //虽语法正确,但预处理器会把N替换成=5int a[N]; //错误,因为宏替换之后为 int a[=5];宏定义不是语句,是预处理指令,故结尾不加分号。
如果不⼩⼼添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。
例如:#define N 5; //虽语法正确,但会把N替换成5;int a[N]; //语法错误,宏替换后,为int a[5;];错误3. 由于宏定义仅是做简单的⽂本替换,故替换列表中如有表达式,必须把该表达式⽤括号括起来,否则可能会出现逻辑上的“错误”。
例如:#define N 3+2int r = N * N;宏替换后为:int r=3+2*3+2; //r=11如果采⽤如下形式的宏定义:#define N (3+2)int r=N*N;则宏替换后,为:int r=(3+2)*(3+2); //r=254. 当替换列表⼀⾏写不下时,可以使⽤反斜线\作为续⾏符延续到下⼀⾏。
例如:#define USA "The United \States of \America"该宏定义中替换列表为字符串常量,如果该串较长,或为了使替换列表的结构更清晰,可使⽤续⾏符 \ 把该串分若⼲⾏来写,除最后⼀⾏外,每⾏⾏尾都必须加续⾏符 \。
C语言函数和宏定义资料
函数和宏定义一.目的和要求1、掌握函数的定义方法和调用规则。
2、掌握C语言程序中主调函数和被调用函数之间进行数据传递的规则。
3、了解函数的返回值及它的类型,并正确使用它。
4、了解局部变量和全局变量的作用域及它们与存储分类的关系,理解变量的存在性和可见性的概念。
5、练习递归函数的使用。
6、理解宏的概念,掌握定义无参宏和带参宏的方法。
了解文件包含的概念,掌握其用法。
7、理解内部函数和外部函数,了解外部函数的编译和连接的方法。
二.相关知识1、函数的概念函数是一个可以反复使用的程序段,从其它的程序段中均可以通过调用语句来执行这段程序,完成既定的工作。
从程序设计的角度来看,函数可以分为两种:①标准函数,即库函数。
②自定义函数。
从函数形式的角度来看,函数也可分为无参函数和有参函数两种。
2、函数的定义一个函数在被调用之前必须先定义,函数定义的一般形式:存储类型数据类型函数名(形式参数表){ 数据定义语句序列;执行语句序列;}3、函数的调用函数调用按函数是否有返回值分为有返回值的函数调用和无返回值的函数调用。
其格式如下:函数名(实参表)[;]无返回值的函数调用格式,最后要有一个语句结束符“分号”。
4、函数的声明函数声明是指在主调函数中,对在本函数中将要被调用的函数提前作的必要的声明。
函数声明的一般格式为:存储类型数据类型函数名(形式参数表);注意:①当函数定义在前,主调函数的定义在后时,可以缺省函数声明。
②如果被调的用户函数和主调函数不在同一编译单位,则在定义函数的编译单位应将该函数定义成extern,在主调函数的函数体中将该函数说明为extern。
5、函数调用中的数据传递方法C语言规定在函数间传递数据有四种方式:值传递方式、地址传递方式、返回值方式、全局变量传递方式。
①值传递方式所传递的是参数值,其特点是“参数值的单向传递”。
②地址传递方式所传递的是地址,其特点是“参数值的双向传递”。
③返回值方式不是在形式参数和实际参数之间传递数据,而是通过函数调用后直接返回一个值到主调函数中。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
4.6.3.宏定义1
4.6.3.1、宏定义的规则和使用解析
(1)宏定义的解析规则就是:在预处理阶段由预处理器进行替换,这个替换是原封不动的替换。
(2)宏定义替换会递归进行,直到替换出来的值本身不再是一个宏为止。
《C语言高级专题第6部分-4.6.C语言宏定义与预处理、函数和函数库》
第一部分、章节目录
4.6.1.C语言预处理理论
4.6.2.C语言预处理代码实战
4.6.3.宏定义1
4.6.4.宏定义2
4.6.5.函数的本质
4.6.6.函数的基本使用
4.6.7.递归函数
4.6.8.函数库
4.6.9.字符串函数
4.6.4.2、内联函数和inline关键字
(1)内联函数通过在函数定义前加inline关键字实现。
(2)内联函数本质上是函数,所以有函数的优点(内联函数是编译器负责处理的,编译器可以帮我们做参数的静态类型检查);但是他同时也有带参宏的优点(不用调用开销,而是原地展开)。所以几乎可以这样认为:内联函数就是带了参数静态类型检查的宏。
(2)""包含的头文件,编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后再到系统指定目录去寻找,如果还没找到则提示文件不存在。
总结+注意:规则虽然允许用双引号来包含系统指定目录,但是一般的使用原则是:如果是系统指定的自带的用<>,如果是自己写的在当前目录下放着用"",如果是自己写的但是集中放在了一起专门存放头文件的目录下将来在编译器中用-I参数来寻找,这种情况下用<>。
(3)一个正确的宏定义式子本身分为3部分:第一部分是#dedine ,第二部分是宏名 ,剩下的所有为第三部分。
(4)宏可以带参数,称为带参宏。带参宏的使用和带参函数非常像,但是使用上有一些差异。在定义带参宏时,每一个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可。
4.6.3.2、宏定义示例1:MAX宏,求2个数中较大的一个
(2)源码.c->(编译)->目标文件.o->(链接)->elf可执行程序
(3)源码.c->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序
(4)源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序
(2)gcc -E xx.c -o xx.i可以实现只预处理不编译。一般情况下没必要只预处理不编译,但有时候这种技巧可以用来帮助我们研究预处理过程,帮助debug程序。
总结:宏定义被预处理时的现象有:第一,宏定义语句本身不见了(可见编译器根本就不认识#define,编译器根本不知道还有个宏定义);第二,typedef重命名语言还在,说明它和宏定义是有本质区别的(说明typedef是由编译器来处理而不是预处理器处理的);
#define MAX(a, b) (((a)>(b)) ? (a) : (b))
关键:
第一点:要想到使用三目运算符来完成。
第二点:注意括号的使用
4.6.3.3、宏定义示例2:SEC_PER_YEAR,用宏定义表示一年中有多少秒
#define SEC_PER_YEAR (365*24*60*60UL)
4.6.2.3、条件编译
(1)有时候我们希望程序有多种配置,我们在源代码编写时写好了各种配置的代码,然后给个配置开关,在源代码级别去修改配置开关来让程序编译出不同的效果。
(2)条件编译中用的两种条件判定方法分别是#ifdef 和 #if
区别:#ifdef XXX判定条件成立与否时主要是看XXX这个符号在本语句之前有没有被定义,只要定义了(我们可以直接#define XXX或者#define XXX 12或者#define XXX YYY)这个符号就是成立的。
(2)代码和数据需要彼此配合,代码是为了加工数据,数据必须借助代码来起作用。拿现实中的工厂来比喻:数据是原材料,代码是加工流水线。名词性的数据必须经过动词性的加工才能变成最终我们需要的产出的数据。这个加工的过程就是程序的执行过程。
4.6.5.4、函数的实质是:数据处理器
(1)程序的主体是数据,也就是说程序运行的主要目标是生成目标数据,我们写代码也是为了目标数据。我们如何得到目标数据?必须2个因素:原材料+加工算法。原材料就是程序的输入数据,加工算法就是程序。
4.6.2.C语言预处理代码实战
本节向大家演示几种常见的预处理,通过代码实例让大家明白这几种预处理技巧的使用方法和使用环境,目的是使大家真正学会使用这些预处理技巧。
4.6.3.宏定义1
本节首先讲解宏定义的一般规则和使用方法,然后用两个宏定义向大家演示宏定义中的关键点和易错点,这两个宏定义都是面试题中非常常见的典型题目。
4.6.4.宏定义2
本节深入对比带参宏、带参函数的相同相异点,并且借此引入内联函数,让大家明白这三者之间的联系,知道在什么情况下编程应该选择哪种技术来达到最好的效果。
4.6.5.函数的本质
本节首先引入函数,讲解函数出现的原因,函数书写的一般规则,同时扩展了函数在面向对象领域出现的形式(方法),最后向大家揭示了函数的本质:数据处理器。
预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这几个工具再加上其他一些额外的会用到的可用工具,合起来叫编译工具链。gcc就是一个编译工具链。
4.6.1.2、预处理的意义
(1)编译器本身的主要目的是编译源代码,将C的源代码转化成.S的汇编代码。编译器聚焦核心功能后,就剥离出了一些非核心的功能到预处理器去了。
关键:
第一点:当一个数字直接出现在程序中时,它的是类型默认是int
第二点:一年有多少秒,这个数字刚好超过了int类型存储的范围
4.6.4.宏定义2
4.6.4.1、带参宏和带参函数的区别(宏定义的缺陷)
(1)宏定义是在预处理期间处理的,而函数是在编译期间处理的。这个区别带来的实质差异是:宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行,执行完后再跳转回来。
4.6.5.函数的本质
4.6.5.1、C语言为什么会有函数
(1)整个程序分成多个源文件,一个文件分成多个函数,一个函数分成多个语句,这就是整个程序的组织形式。这样组织的好处在于:分化问题、便于编写程序、便于分工。
(2)函数的出现是人(程序员和架构师)的需要,而不是机器(编译器、CPU)的需要。
(3)当我们的函数内函数体很短(譬如只有一两句话)的时候,我们又希望利用编译器的参数类型检查来排错,我还希望没有调用开销时,最适合使用内联函数。
4.6.4.3、宏定义来实现条件编译(#define #undef #ifdef)
(1)程序有DEBUG版本和RELEASE版本,区别就是编译时有无定义DEBUG宏。
注:用函数的时候程序员不太用操心类型不匹配因为编译器会检查,如果不匹配编译器会叫;用宏的时候程序员必须很注意实际传参和宏所希望的参数类型一致,否则可能编译不报错但是运行有误。
总结:宏和函数各有千秋,各有优劣。总的来说,如果代码比较多用函数适合而且不影响效率;但是对于那些只有一两句话的函数开销就太大了,适合用带参宏。但是用带参宏又有缺点:不检查参数类型。
(2)预处理器帮编译器做一些编译前的杂事。
4.6.1.3、编程中常见的预处理
(1)#include(#include <>和#include ""的区别)
(2)注释
(3)#if #elif #endif #ifdef
(4)宏定义
4.6.1.4、gcc中只预处理不编译的方法
(1)gcc编译时可以给一些参数来做一些设置,譬如gcc xx.c -o xx可以指定可执行程序的名称;譬如gcc xx.c -c -o xx.o可以指定只编译不连接,也可以生成.o的目标文件。
第四:尽量少碰全局变量:函数最好用传参返回值来和外部交换数据,不要用全局变量。
4.6.5.3、函数是动词、变量是名词(面相对象中分别叫方法和成员变量)
(1)函数将来被编译成可执行代码段,变量(主要指全局变量)经过编译后变成数据或者在运行时变成数据。一个程序的运行需要代码和数据两方向的结合才能完成。
(5)整个程序的运行其实就是很多个函数相继运行的连续过程。
4.6.6.函数的基本使用
4.6.6.1、函数三要素:定义、声明、调用
(1)函数的定义就是函数体、函数声明是函数原型、函数调用就是使用函数
4.6.6.函数的基本使用
4.6.7.递归函数
4.6.8.函数库
4.6.9.字符串函数
3.6.10.数学库函数
3.6.11.自己制作静态链接库并使用
3.6.12.自己制作动态链接库并使用
第三部分、随堂记录
4.6.1.C语言预处理理论
4.6.1.1、由源码到可执行程序的过程
(1)源码.c->(编译)->码实战
4.6.2.1、头文件包含
(1)#include <> 和 #include""的区别:<>专门用来包含系统提供的头文件(就是系统自带的,不是程序员自己写的),""用来包含自己写的头文件;更深层次来说:<>的话C语言编译器只会到系统指定目录(编译器中配置的或者操作系统配置的寻找目录,譬如在ubuntu中是/usr/include目录,编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下),如果找不到就会提示这个头文件不存在。