传送门:C语言-综合案例:通讯录
目录
第一节:什么是预处理
预处理是对代码进行的一些修改,从代码变成可执行程序,就先需要经过预处理。
之前出现的 #define、注释都与预处理有关。进行预处理时,会将 define 定义的符号替换成预设的符号,注释会被删除。
还有一些预处理符号,这些符号会被替换成相应的字符串,它们的写法和内容如下:
__FILE__ | 它所在的文件路径 |
__LINE__ | 它所在的行号 |
__DATE__ | 处理它时的日期 |
__TIME__ | 处理它时的时间 |
__FUNCTION__ | 它所在的函数名 |
请看以下代码:
#include <stdio.h>
int main()
{
printf("文件路径:%s\n",__FILE__);
printf("当前行号:%d\n",__LINE__);
printf("当前日期:%s\n",__DATE__);
printf("当前时间:%s\n",__TIME__);
printf("所在函数:%s\n",__FUNCTION__);
return 0;
}
第二节:#define
#define 定义的符号也需要经过预处理才能发挥作用,它会将代码中的符号替换成预设的符号,#define 有以下几种用法:
2-1.定义符号
#define M 100 // 预处理时,会将 M替换成100
#include <stdio.h>
int main()
{
int a = M;
printf("%d\n",a);
return 0;
}
2-2.给类型取别名
#define un_t unsigned int // 给 unsigned int 取了一个短的名字 un_t
#include <stdio.h>
int main()
{
un_t a = 100;
printf("%d\n",a);
return 0;
}
关键字 typedef 也有给类型取别名的作用,它们之间也有区别,请看以下代码:
#define char_ptr char* // 给 char* 取了一个的名字 char_ptr
#include <stdio.h>
int main()
{
char_ptr a,b;
printf("a的大小:%u\n",sizeof a);
printf("b的大小:%u\n",sizeof b);
return 0;
}
typedef char* char_ptr;
#include <stdio.h>
int main()
{
char_ptr a,b;
printf("a的大小:%u\n",sizeof a);
printf("b的大小:%u\n",sizeof b);
return 0;
}
它们都给char*取了别名,但是 #define 定义出来的变量类型却不一样,这是因为 #define 实行的是,直接替换,替换后的内容为:
char* a,b;
第一个 * 与 a 结合,a就是指向char的指针,而b没有 * ,它就是char类型的变量。
而 typedef 是类型重定义,它取的别名会作为一个整体给后面的变量,即:
char* a;char* b;
//或
char* a,* b;
这是它们之间的一些区别,需要注意。
2-3.定义一段代码
例如我们需要知道当前文件路径+行号+日期+时间+函数名,如果用5个 printf,当需要多次调用时就太麻烦了,而且也不能封装成函数,这样的话行号和函数名就是被封装的函数的了,可以用#define,请看以下代码:
#define Print_Info printf("文件路径:%s\n",__FILE__);\
printf("当前行号:%d\n", __LINE__);\
printf("当前日期:%s\n", __DATE__);\
printf("当前时间:%s\n", __TIME__);\
printf("所在函数:%s\n", __FUNCTION__)
#include <stdio.h>
#include <Windows.h>
int main()
{
Print_Info;
printf("\n");
Sleep(5); // 休眠5s
Print_Info;
return 0;
}
#define 中的 '\' 是续行符,在内容较多、需要换行时使用
虽然我们写的是 Print_Info,但在预处理时,它会自动被替换成上述的5个printf。
在上述代码和结果中,还需要注意的是:虽然程序休眠了5s再打印下一个Print_Info,但是它们的当前时间是一样的,这是因为预处理在程序执行前就完成了,即__TIME__在执行前就完成替换了,之后如何执行与它无关。
2-4.定义宏
宏是一种类似函数的结构,它也允许传参,一个简单的宏如下:
#define ADD(x,y) x+y // 宏
#include <stdio.h>
int main()
{
printf("%d\n",ADD(1,2));
return 0;
}
它也可以传参,但是与函数传参不同,在传参过程中不会计算,而是直接替换,请看以下代码:
#define MUL((x),(y)) x*y
#include <stdio.h>
int main()
{
printf("%d\n",MUL(1,2+3));
return 0;
}
如果MUL是个函数,传入2和2+3的参数,它会计算成2和5的参数传递进去,那么答案就是10,但是实际上的结果是7:
这是因为宏的参数不会计算,而是直接替换,这个过程如下:
那么怎么避免这种情况发送呢?,我们可以给宏中的x,y加上括号:
#define MUL(x,y) (x)*(y)
这就相当于传参时先计算再传入。但是这样还不够保险,情看以下代码:
#define ADD(x,y) (x)+(y)
#include <stdio.h>
int main()
{
printf("%d\n", ADD(2, 2 + 3)*2);
return 0;
}
这是一个加法宏,得到2与2+3的和再乘以2,结果似乎是14,但是实际上的答案是12:
这是因为虽然参数先计算后传入,但是替换后y被2抢占先机了,即ADD(2, 2 + 3)*2替换后为
(2)+(2+3)*2,先乘除后加减,结果就是12了。
又怎么避免它呢?我们可以给 (x)+(y) 又套上括号,让它们成为一个整体:
#define ADD(x,y) ((x)+(y))
这样的"双重保险"下就几乎不会出错了。
2-5.#define 的替换规则
如果有宏,会先替换宏中的参数;
其次再替换参数名、符号等;
最后检查是否还有没替换的符号,如果有,继续替换;
因为#define的替换规则是循环的,这代表着#define中可以嵌套其他#define的符号,请看以下代码:
#include <stdio.h>
#define ADD(x,y) ((x)+(y))
#define M 2
#define N 3
#include <stdio.h>
int main()
{
printf("%d\n", ADD(M, M + N)*M);
return 0;
}
我们在宏中嵌套使用了#define的M、N,这是被允许的。
但是宏不允许递归,因为没有结束条件,它会无限替换下去。
还需要知道的是#define不是遇到M、N就替换,它无法替换字符串中的符号:
#define M 2
#define N 3
#include <stdio.h>
int main()
{
printf("%s\n", "M,N");
return 0;
}
2-6.宏中的#
#的作用是将一个字符插入字符串中,例如:
#define PRINT(x) printf("the value of "#x" is %d\n",x);
#include <stdio.h>
int main()
{
int a = 10;
PRINT(a);
return 0;
}
它的替换过程如下:
2-7.宏中的##
##可以将两个部分连接成一个整体:
#define cat(x,y) x##y
#include <stdio.h>
int main()
{
int a101 = 10;
printf("%d\n",cat(a,101));
return 0;
}
它的替换过程如下:
#和##几乎用不上,至少在我的学习过程中从来没有用过。
2-8.#undef 移除一个宏定义
它的使用方法如下:
#define M 100
#undef M // 移除M的宏定义
#include <stdio.h>
int main()
{
int a = M;
printf("%d\n",a);
return 0;
}
第三节:条件编译
条件编译与选择语句很像,它需要满足条件才编译这部分代码,可以跳过一些不需要编译的代码,节约了时间。
3-1.#ifdef
#ifdef 是如果定义了这个宏,就执行代码,否则不执行:
#define M 100// 定义了M
#include <stdio.h>
int main()
{
#ifdef M
printf("%d\n",M);
#endif // 结束
return 0;
}
当然也可以不要后面预设的符号:
#define M// 定义了M
#include <stdio.h>
int main()
{
#ifdef M
printf("定义了M\n");
#endif // 结束
return 0;
}
3-2.#ifndef
ifndef与ifdef相反,不定义才执行,定义了反而不执行。
3-3.#if
#if后跟常量表达式,而不是宏。
3-3.#elif 和 #else
#elif 与之 #ifdef,如同 else if 与之 if,如何使用else if 就如何使用 #elif;#else 同理。
第四节:文件包含
4-1.库文件
文件包含使用 #include,它可以跟<>包含库文件,例如#include<stdio.h>;也可以跟""包含本地文件,例如我们写通讯录时的#include"Address.h"。
它们也有差异:
#include<stdio.h>会直接到标准库去查找,找不到就报错;
#include"Address.h"会先去本地库中查找,找不到再去标准库查找,还没有就报错。
虽然冒号可以查找本地库和标准库,但是库文件还是用<>,因为库文件查找就不需要去本地浪费时间了。
4-2.防止文件被多次包含
(1)#prama once
将它放在头文件的最前面,那么该头文件不会被重复包含;
(2)#ifndef 宏
#define 宏
#endif
第一次包含该文件时,没有文件中的宏,就编译该文件,如果再次包含这个文件,因为有了文件中的宏就不再编译,这样也可以防止文件被多次包含。
结语:
至此,我学到关于C语言的知识就已经完成了,但是我学习编程的道路还没有结束,实际上,我还欠很多博客没有写,比如数据结构、C++、Linux操作系统……接下来我也将努力完成它们。我不是计算机专业的学生,而是药学专业的学生,在大一下册的时候偶然接触到计算机编程,从此一发不可收拾,用了一万块报名了课外机构——比特就业课。至此每天上完课就写算法题,或者写博客,亦或者上编程课。
大三寒假我想试一下找实习,所以我需要尽快完成博客。
诸君,共勉。