iis 如何新建网站,学做网站难不难,免费企业网站开源系统,wordpress 嵌套回复前言 上一节我们了解了文件操作的相关内容#xff0c;本节我们来了解一下预处理指令#xff0c;那么废话不多说#xff0c;我们正式开始今天的学习
预定义符号
在C语言中#xff0c;设置了一些预定义的符号#xff0c;可以供我们直接使用#xff0c;预定义符号是在程序…
前言 上一节我们了解了文件操作的相关内容本节我们来了解一下预处理指令那么废话不多说我们正式开始今天的学习
预定义符号
在C语言中设置了一些预定义的符号可以供我们直接使用预定义符号是在程序的预处理过程中直接被处理的那么C语言中定义的符号有下面几种
1.__FILE__
表示当前正在编译的源文件的地址
2.__LINE__
表示文件当前所在的行号
3.__DATE__
表示文件被编译的日期
4.__TIME__
表示文件被编译的时间
5.__STDC__
如果编译器遵循ANSI C其取值为1若不是则它的取值未定义gcc编译器支持
下面我们来尝试使用一下以上预定义符号
#define _CRT_SECURE_NO_WARNINGS 1#include stdio.hint main(void)
{printf(%s\n, __FILE__);printf(%s\n, __DATE__);printf(%s\n, __TIME__);printf(%d\n, __LINE__);return 0;
} 这些符号都是在编译前的预处理阶段就进行了处理
#define 定义常量
#define 有两种不同的功能
1. #define 可以定义符号常量
2. #define 可以定义宏
#define 使用的基本语法如下
#define name stuff
例如
#define MAX 100
#define MIN 1
在程序的预处理阶段代码中的 name 变量就会被替换为数值 stuff
stuff 定义的数值的类型不一定全要为整数例如
#define STR hello world
我们还可以使用 #define 定义相对复杂的函数例如
#define forever for(;;)
在这个代码当中for 函数的初始化部分、调整部分和判断部分都被省略。由于该 for 函数判断条件没有写这样就意味着判断条件是恒为真的就会造成死循环
如果我们定义的 stuff 长度过长我们可以运用续行符 \ 除了程序的最后一行以外程序的每一行都可以添加换行符
#define DEBUG_PRINT printf(file:%s\tline:%d\t \date:%s\ttime:%s\n ,\__FILE__,__LINE__ ,\
__DATE__,__TIME__ )
那么此时我们肯定会存在一个疑问在 #define 定义标识符的时候需不需要在末尾加上一个呢
建议是最好不要加上分号举个简单的例子
#define MAX 1000;int main(void)
{int a MAX;return 0;
}
在此代码的预处理阶段原代码会将代码转变成如下这种形式
#define MAX 1000;int main(void)
{int a 1000;;return 0;
}
此时 int a 1000后面就有两个分号就会造成程序的错误
还有可能会出现这样的情况
#define MAX 1000;int main(void)
{printf(%d\n, MAX);return 0;
}
我们在打印 MAX 的时候由于其带了分号就会造成打印不成功
#define 定义宏
#define 允许把参数替换到文本中去这种实现通常被称作定义宏
宏的声明方式如下
#define name( parament-list ) stuff
parament-list 是一个由逗号隔开的符号表他们可能会出现在 stuff 中
其中需要注意
参数列表的左括号必须与 name 紧紧的挨在一起如果两者之间存在着空格那么参数列表就会被解释成 stuff 的一部分
下面来举一个例子
例如我们需要定义一个宏来求某个数的平方我们则可以这么写
#define SQUARE(x) x*xint main(void)
{int a 4;int ret SQUARE(a);printf(%d\n, ret);return 0;
}
局限性
但是这个程序存在一定的弊端若 SQUARE 里面的参数是 a 1:
#define SQUARE(x) x*xint main(void)
{int a 4;int ret SQUARE(a1);printf(%d\n, ret);return 0;
} 我们发现结果并不是我们想要的 25 而是 9 这是为什么呢
因为宏参数在进行替换的时候是直接采取替换的也就是说会被替换成a 1 * a 1由于乘法的优先级是大于加法的所以会采取 4 4 1 的运算模式所以算出来的结果就是 9
要解决这个问题我们需要在宏中添加括号 #define SQUARE(x) (x) * (x)
这样算出来的结果才是 25
在这个定义中我们使⽤了括号想避免之前的问题但是同时这个宏可能会出现新的错误
例如 int a 5;printf(%d\n ,10 * DOUBLE(a));
我们想要打印 100 到屏幕上但我们实际运行程序的时候打印的却是 55
这个问题的解决办法是在宏定义表达式两边加上⼀对括号 #define DOUBLE( x) ( ( x ) ( x ) )
带有副作用的宏参数
当宏参数在宏的定义中出现超过⼀次的时候如果参数带有副作⽤那么你在使⽤这个宏的时候就可 能出现危险导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果
例如我们要写一个宏其功能是求两个数的较大值
#define MAX(X,Y) ((X)(Y)?(X):(Y))int main(void)
{int a 1;int b 2;int m MAX(a, b);//int m (a,b) ((a)(b)?(a):(b));printf(m %d\n, m);printf(a %d\n, a);printf(b %d\n, b);return 0;
} 我们可以发现 的操作在程序中执行的次数不止一次这样就导致了结果存在问题
宏的替换规则
在程序中扩展#define定义符号和宏时需要涉及⼏个步骤
1.在调⽤宏时⾸先对参数进⾏检查看看是否包含任何由#define定义的符号。如果是它们⾸先 被替换
2.替换文本随后被插⼊到程序中原来⽂本的位置。对于宏参数名被他们的值所替换
3.最后再次对结果文件进⾏扫描看看它是否包含任何由#define定义的符号。如果是就重复上 述处理过程
我们需要注意
1.宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏不能出现递归
例如
int m MAX(a, MAX(2, 3));
2.当预处理器搜索 #define 定义的符号的时候字符串常量的内容并不被搜索
宏与函数的对比
我们来比较一下函数和宏
#define MAX(X,Y) ((X)(Y)?(X):(Y))int Max(int x, int y)
{return x y ? x : y;
}
宏通常被应⽤于执⾏简单的运算当执行较为简单的运算的时候宏相较于函数更加具有优势原因有两点
1.通过 函数栈帧的创建与销毁 的知识我们可以知道函数在调用函数、执行运算、返回函数的时候都需要花费时间宏在执行小型运算时所需要i的时间相较于函数使用的时间、空间会更少所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹
2.函数的参数必须要声明为特定的类型 和函数相⽐宏的劣势
1.每次使⽤宏的时候⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短否则可能⼤幅度增加程序的⻓度
2.宏是没法调试的
3.宏由于类型⽆关不够严谨
4.宏可能会带来运算符优先级的问题导致程容易出现错 宏有时候可以做函数做不到的事情。⽐如宏的参数可以出现类型但是函数做不到
例如
#define Malloc(n,type) (type*)malloc(n*sizeof(type))int main(void)
{int* p (int*)malloc(10 * sizeof(int));Malloc(10, int);return 0;
}
宏和函数的对比
属性#define定义宏函数代码长度每次使用的时候宏代码都会被插入程序程序长度会大幅增加函数代码只出现在一个地方每次使用的时候去那个地方调用对程序长度影响不大执行速度更快因为有函数的调用与返回相对较慢操作符优先级需要多加括号因为操作符优先级的缘故不加括号往往会造成无法预料的结果参数只在调用的时候求值一次他的结果直接传递给函数表达式的取值更容易预测带有副作用的参数参数可以被替换到函数的多个位置可能导致宏的参数被多次计算产生不可预料的结果参数只在调用的时候求值一次他的结果直接传递给函数表达式的取值更容易预测参数类型宏的参数与类型无关函数的参数与类型有关调试宏不方便调试函数可以逐语句调试递归不支持递归支持递归 拓展在C中有一个函数叫做内联函数inline这个函数既具有函数的特点又具有宏的特点
# 以及 ##
# 运算符
#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中
#运算符所执⾏的操作可以理解为“字符串化”
直接写出概念可能有点难以理解下面我们来举例说明
我们首先铺垫一下
int main(void)
{printf(helloworld\n);printf(helloworld\n);return 0;
}
如上面的代码这两个字符串本质上是相同的
再看一下这个代码
int main(void)
{int a 1;printf(the value of a is %d\n, a);int b 20;printf(the value of b is %d\n, b);float f 5.6f;printf(the value of f is %f\n, f);return 0;
}
这三个打印函数的格式是非常相似的那么我们此时就考虑到另一个问题我们能不能把它封装成一个函数呢或者说可不可以把它写成一个宏呢答案是可行的
#define Print(n,format) printf(the value of n is format\n, n)int main(void)
{int a 1;Print(a,%d);int b 20;Print(b,%d);float f 5.6f;Print(f,%f);return 0;
}
我们根据前置的铺垫可以写出这样的代码但当我们在运行程序的时候发现了这样的一个问题
我们可以观察到其中的 n 并没有被替换此时我们就需要用到 # 操作符了# 操作符可以让参数直接转换成字符串
#define Print(n,format) printf(the value of #n is format\n, n)int main(void)
{int a 1;Print(a,%d);int b 20;Print(b,%d);float f 5.6f;Print(f,%f);return 0;
} ## 运算符
## 可以把位于它两边的符号合成⼀个符号它允许宏定义从分离的⽂本⽚段创建标识符
## 被称为记号粘合这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的
我们同样的进行举例说明
如果我们想写⼀个函数求2个数的较⼤值的时候不同的数据类型就得写不同的函数
例如
int int_max(int x, int y)
{return x y ? x : y;
}
float float_max(float x, float y)
{return x y ? x : y;
}
但是这样操作下来程序会显得十分冗杂此时我们可以用##来处理换一个写法
#define GENERIC_MAX(type) \type type##_max(type x,type y) \{ \return xy?x:y; \}//定义函数
GENERIC_MAX(int);
GENERIC_MAX(float);int main(void)
{int r1 int_max(3, 5);printf(%d\n, r1);float r2 float_max(3.1f, 5.2f);printf(%f\n, r2);return 0;
}
此时我们用 ## 操作符把位于两端的符号合并成为了一个符号并由此来定义一个函数
命名约定
因为宏和函数的使用语法很相似所以在我们书写宏的时候通常会这样的习惯
1.将宏的名字全部大写
2.函数的名字不要全部大写
这样我们能更好的分辨宏和函数当然有时会存在着特殊情况例如 offsetof 是一个宏它是用来计算结构体成员相较于结构体起始位置的偏移量的宏他虽然是宏但是它全是小写
#undef 的使用
#undef 指令⽤于移除⼀个宏定义例如
#define MAX 100int main(void)
{printf(%d\n, MAX);
#undef MAXprintf(%d\n, MAX);return 0;
}
此时我们移除了 MAX 所以会报错
命令行定义
我们在 gcc 编译器上可以在命令行里指定变量的数据当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候这个特性有点⽤处假定某个程序中声明了⼀个某个⻓度的数组如果机器内存有限我们需要⼀个很⼩的数组但是另外⼀个机器内存⼤些我们需要⼀个数组能够⼤些因为我当前的环境是 VS 而且该内容并不是很重要所以仅仅作了解就行
条件编译
我们在编译一个程序的时候我们如果需要将一条语句或者一组语句编译或者放弃是很方便的因为我们有条件编译指令
对于某些调试性的代码直接删除很可惜但是保留下来又很碍事所以我们此时可以选择性的编译例如
#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;
}
假如我们不需要使用 printf 函数我们可以注释掉
#define __DEBUG__
这样当我们在运行代码的时候 printf 的指令就不会被执行
下面我们来盘点一下常见的条件编译指令
1.单分支的条件编译语句 #if 常量表达式 //... #endif
例如
int main(void)
{
#if 0printf(haha\n);
#endifreturn 0;
}
这样就不会执行打印 haha 的操作只有满足 if 后面的条件时里面的语句才会被执行
2.多分支的条件编译语句
使用 #elif 和 #else 来进行多分支的条件编译语句
#define M 2int main(void)
{
#if M0printf(haha\n);
#elif M1printf(hehe\n);
#elif M2printf(hello world\n);
#elseprintf(luelue);
#endifreturn 0;
} 3.判断是否被定义
#define MAX 0int main(void)
{
#if defined(MAX)printf(haha\n);
#endifreturn 0;
}
使用 #if defined( ) 可以判断是否被定义他还有另外一这种写法
#define MAX 0int main(void)
{
#ifdef MAXprintf(haha\n);
#endifreturn 0;
}
两者的逻辑都是一模一样的若是想写不被定义的代码只需要在 #define 前加上 或者写作 #ifndef
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
当我们代码的长度不长时我们很难用到条件编译指令当我们的代码很长且想要实现多平台的功能时就经常会用到条件编译指令
头文件的包含
我们知道我们可以自己创造头文件
#pragma onceint Add(int x, int y)
{return x y;
}
该头文件里面的内容可以在其他其他文件里被使用我们通常认为我们自定义的头文件需要用 包含 而不是使用
#define _CRT_SECURE_NO_WARNINGS 1#include stdio.h#include test.hint main(void)
{int a 1;int b 2;int c Add(a, b);printf(c %d\n, c);return 0;
}
其实不然 和 的不同仅仅在于他们的查找策略不同二者使用过程中并不存在绝对的错误 的查找策略先在源⽂件所在⽬录下查找如果该头⽂件未找到编译器就像查找库函数头⽂件⼀样在 标准位置查找头⽂件如果都找不到就会报编译错误
也就是说库文件可以用 查找。可是但是这样做查找的效率就低些当然这样也不容易区分是库⽂件还是本地⽂件了
嵌套文件包含
假设头文件被多次包含会怎么样
如果test.c⽂件中将test.h包含5次那么test.h⽂件的内容将会被拷⻉5份在test.c中。 如果test.h⽂件⽐较⼤这样预处理后代码量会剧增。如果⼯程⽐较⼤有公共使⽤的头⽂件被⼤家都能使⽤⼜不做任何的处理那么后果真的不堪设想。 如何解决头⽂件被重复引⼊的问题呢
答案是使用条件编译
#ifndef __TEST_H__
#define __TEST_H__int Add(int x, int y)
{return x y;
}#endif
这样做就只会被包含一次或者使用
#pragma once
结尾
条件编译指令的内容很多远远不止我上面归纳的这些感兴趣的朋友可以自行去了解我这里便不再作过多的讲解了谢谢您的浏览