目录
1.编译;
编译:预编译;编译;汇编;
预编译:注释的删除;#include头文件的包含;#define的替换; 这些都是文本操作;#include 和#define 都是预处理指令,所有的预处理指令都是在预编译阶段处理的;
编译:把C语言代码翻译成汇编指令; 语法分析,语义分析,词法分析,符号汇总;
汇编:生成二进制指令,全是汇编代码,把汇编代码翻译成二进制指令,生成目标文件;形成符号表;
2.预处理详解;
预定义符号:
代码举例:
#include<stdio.h>
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
return 0;
}
代码分析:
__FILE__ :进行编译的源文件;
__LINE__ :文件当前的行号;
__DATE__:文件被编译的日期;
__TIME__ :文件被编译的时间。
#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时给常量MAX赋值;第二行是把register变成一个简短的名字;第三行是把for循环写作do_forever;第四行是把break;case写作CASE,这样就可以防止忘记写break;在使用define时,如果stuff过长,可以在每一行行后加续行符,这样就可以重启一行写;注意define定义后面不加“ ;”;
#define定义宏:
简要介绍:
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中
的操作符或邻近操作符之间不可预料的相互作用。
#define 替换规则:
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
#:
把参数插入到字符串:
代码举例:
#include<stdio.h>
#define print(x,y) printf("the value of "#x" ="y"\n",x)//这样就可以改变字符串中 的参数
int main()//
{
//printf("hahaha""\n");//可以这样写,打印两个字符串;
int a = 3;
print(a, "%d");
int b = 3;
print(b, "%d");
float c = 3.2f;
print(c, "%f");
return 0;
}
代码分析:
#可以把宏的参数以字符串的形式插入宏定义的字符串中;把一个宏参数变成对应的字符串;#x把x变成相应的字符串,y本身是字符串,不用#y;
##:
简要介绍:
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。
#define定义宏注意事项:
不要用带副作用的宏参数,例如:x++;
#undef:
这条指令用于移除一个宏定义。
#define定义宏举例:
交换奇偶位:
简要介绍:
交换一个数二进制位的奇偶位;
代码举例:
#include<stdio.h>
#define SwapIntBit(n) (((n) & 0x55555555) << 1 | ((n) & 0xaaaaaaaa) >> 1)
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
for (i = 31;i >= 0;i--)
{
printf("%d ", (n >> i) & 1);
}
printf("\n");
n = SwapIntBit(n);
for (i = 31;i >= 0;i--)
{
printf("%d ", (n >> i) & 1);
}
}
代码思路:
先给n与上01010101010101010101010101010101,拿出奇数位;再给n与上10101010101010101010101010101010,拿出偶数;奇数位左移,奇数位为0,偶数位占满;偶数位右移,偶数位为0,奇数位占满;两个相或,交换完毕;
运行结果:
offsetof:
简要介绍:
求出结构体成员的偏移量;
代码举例:
struct S
{
int x;
char a;
double m;
};
#include<stdio.h>
#define offsetof(StructType, MemberName) (size_t)&(((StructType *)0)->MemberName)
int main()
{
printf("%d\n", offsetof(struct S, a));
printf("%d\n", offsetof(struct S, m));
return 0;
}
代码思路:
先将0强制转换成结构体的起始地址,通过这个指针访问到他的成员,取地址,再强制转换成整数,就得到了偏移量;
运行结果:
3.条件编译;
简要介绍:
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。适用于调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
常见的条件编译指令:
1.#if 常量表达式//...#endif如:#define __DEBUG__ 1#if __DEBUG__//..#endif2.多个分支的条件编译#if 常量表达式//...#elif 常量表达式//...#else//...#endif3.判断是否被定义#if defined(symbol)#ifdef symbol#if !defined(symbol)#ifndef symbol4.嵌套指令#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif#endif
4.文件包含;
#include "filename":
本地文件包含;先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
#include <filename.h>:
库文件包含;查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。