赞
踩
该篇博客是对上一篇博客【C/C++】程序环境,探索程序的执行过程的衍生,这里的一些知识需要用到这篇博客,如果对此不了解的可以在此查看了解。
在预处理阶段有一些预定义符号是可以直接使用的,如下:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
//当写代码时想要获得文件位置、行号、日期和时间可以使用以上命令
示例1:
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
return 0;
}
我们可以清晰的看到这些预定义符号的作用。
示例2:
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
printf("%s\n", __STDC__);
return 0;
}
//报出如下错误
error C2065: “__STDC__”: 未声明的标识符
#define的作用为于以下两点
- #define 定义标识符
- #define 定义宏
语法:#define name stuff
示例:
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现//替换后为死循环
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
提问:
在define定义标识符的时候,要不要在后面加上”;“
可以通过一个例子来解答此事:
#define MAX 100;
#define MAX 100
//建议不要加符号
//在以下场景下会报错
if(condition)
max = MAX;
//添加分号,替换后为:max = MAX;;表示有两条语句
//在if下如果有两条或两条以上语句需要使用用大括号,否则报错
else
max = 0;
#define机制包括一个规定,允许把参数替换到文本中,这种实现通常称为宏或宏定义
下面事宏的声明方式:
#define name(parament-list) stuff
示例:
#define Max(a,b) ((a)>(b))?(a):(b)
int main()
{
int a = 10;
int b = 20;
int c = Max(a, b);
//替换后为:int c = ((a)>(b))?(a):(b);
printf("%d\n", c);
return 0;
}
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被释放为stuff的一部分。
如:
#define AQUARE(x) x*x
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
注意:
#define M 10
printf("hello M\n")
//在上诉字符串中有M,但不识别。
如何把参数传入到字符串
我们先来看看下面的代码:
int main()
{
char* p = "hello ""world\n";
printf("hello"" world\n");
printf("%s", p);
return 0;
}
#define PRINT(value,format) printf("the "#value" is "format"\n", value)
int main()
{
int a = 10;
PRINT(a, "%d");
int b = 20;
PRINT(b, "%d");
float c = 3.2f;
PRINT(c, "%f");
return 0;
}
##可以把位于两端的符号合成为一个符号。
它允许宏定义从分离的文本片段创建标识符。
#define CAT(A,B) A##B
int main()
{
int AAABBB = 100;
printf("%d\n", CAT(AAA, BBB));
return 0;
}
//方法1
int a = 1;
int b = a + 1;
//方法2
int a = 1;
int b = ++a;
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
int x = 4;
int y = 5;
int z = MAX(x++, y++);
printf("z=%d x=%d y=%d\n", z, x, y);
return 0;
}
宏通常被应用于执行简单的运算。
比如在两个数中找出最大值。
#define MAX(a,b) ((a)>(b)?(a):(b))
为什么不用函数完成这个任务?
对于简单的运算,函数的调用需要进行压栈和销毁操作,真正的运行操作是很少的。
而宏的操作是直接进行操作,不需要进行其它的操作,相对于函数更加简便。
函数只有一份,每次调用都是在哪一个地方使用。
宏的参数可以出现类型,而函数做不到。
#define CALLOC(num,type) (type*)calloc(num,sizeof(type))
int main()
{
int* a = CALLOC(10, int);
return 0;
}
#define定义的宏:每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。
函数:函数代码只出现于一个地方;每次使用这个函数时,都会调用那个地方的同一份代码。
#define定义的宏:更快
函数:存在函数的调用和返回的额外开销,所以会相对慢一些。
但对于比较大的程序,这点时间并不影响。
#define定义的宏:宏参数的求值时在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏书写的时候多些括号。
函数:函数参数只在函数调用的时候求值一次,它的结果值传递给函数。
#define定义的宏:参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。
函数:函数参数只在传参的时候求值一次,结果更容易控制。
#define定义的宏:宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。
函数:函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
int x = 10;
int y = 20;
ADD(x + y);//宏——将括号内的三个元素作为参数传递
add(x + y);//函数——将x和y相加后作为一个参数传递
#define定义的宏:宏是不方便调试的
函数:函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
#define定义的宏:宏是不能递归的
函数:函数是可以递归的
一般来讲函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。
我们平时的习惯是:
把宏名全部大写
函数名不要全部大写
但是有例外:
offset —— 宏
getchar —— 有些编译器上,也是宏
这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重定义,那么它的旧名字首先要被移除。
#include <stdio.h> int main() { int array[ARRAY_SIZE]; int i = 0; for (i = 0; i < ARRAY_SIZE; i++) { array[i] = i; } for (i = 0; i < ARRAY_SIZE; i++) { printf("%d ", array[i]); } printf("\n"); return 0; }
许多编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性就有点用处。
看上述代码,假定某个程序中声明一个某个很长的数组,如果机器内存有限,我们需要一个很小的数组,但是另一个机器内存大些,我们需要一个数组能够大些。
//linux环境演示:
gcc test.c -D ARRAY_SIZE=10
//test.c为代码所在源文件
在此目录下之前有一个test.c所以将文件名命名为test1.c
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
调试性的代码(为方便调试写的代码),删除可惜(万一下次遇到同样的问题),保留又碍事,所以我们可以选择性的编译。
示例:
#include <stdio.h> #define __DEBUG__ int main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i < 10; i++) { arr[i] = i; //如果标识符被定义,则执行条件语句。 #ifdef __DEBUG__ printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 #endif //__DEBUG__ } return 0; }
示例2:
#include <stdio.h> //#define __DEBUG__ int main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i < 10; i++) { arr[i] = i; #ifdef __DEBUG__ printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 #endif //__DEBUG__ } 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 4.嵌套指令 #if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
我们已经知道,#include指令可以使另一个文件被编译。就像它实际出现于#include指令的地方一样。
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,就实际被编译10次。
#include"filename"
查找策略:先在源文件所在目录下查找,如果该头文件为找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就报错。
#include<filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
是不是可以说,对于库文件也可以使用“”的形式包含?
答案:可以
但是这样查找的效率就低些,当然这样也不容易区分是库函数还是本地文件。
comm.h和comm.c是公共模块
test1.h和test1.c使用了公共模块
test2.h和test2.c使用了公共模块
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。
方法1:
使用条件编译,判断头文件是否被多次包含。
方法2:
#pragma once
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。