高级议题
1、预处理
预处理指的是编译器在编译程序之前对源程序处理的过程
<1>去掉程序中的注释
/*
多行注释
*/
//单行注释
<2>头文件包含
//预处理命令:#include
//头文件包含指的是将头文件中的内容拷贝到当前的源文件中
①包含系统头文件
#include<头文件名称.h> //直接去系统头文件目录中找头文件
②包含自己写的头文件
#include"头文件名称.h" //现在当前目录中找,如果找不到再去系统目录中找
<3>宏替换
//将源文件中所有的宏的符号用后面的字符串替换
//宏指的是用#define定义的符号
①预定义宏——系统已经定义好的宏,直接在程序中可以使用
FILE 正在编译的源文件的名称
LINE 文件当前的行号
FUNCTION 当前所在的函数名
DATE 预编译文件的日期
TIME 预编译文件的时间
STDC 判断编译器是否遵循ANSI C,是则为1
其中,_LINE_和_STDC_是整型常量,其余为字符串常量。
例如:
#include <stdio.h>
int main(void)
{
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
printf("%s\n",__FUNCTION__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
printf("%d\n",__STDC__);
return 0;
}
②不带参数的宏
例如:
#define PI 3.14
#define MIN 60
③带参数的宏
例如:
#define MAX(a,b) a>b?a:b
//带参宏可以在程序中被调用,例如
MAX(3,5) = 5
MAX(3.4,2.1) = 3.4
MAX("hello","world") = "world"
#define ADD(a,b) a*b+a
ADD(3+4,2+5) --> 3+4 * 2+5 +3+4 = 23 //这个结果和想要的结果不一致,所以在定义带参宏时,需要给参数加(),保证结果一致
#define ADD(a,b) (a)*(b)+(a)
ADD(3+4,2+5) --->(3+4) * (2+5) + 3+4 = 56
<4>条件编译
根据条件选择性的编译程序中的某一部分代码
①第一种形式
#ifndef 宏 //如果定义了宏,则编译代码段A,否则,编译代码段B
代码段A
#else
代码段B
#endif
//用于测试代码中
//#define DEBUG
#include <stdio.h>
float my_power(float a, int n)
{
float b = 1.0;
int i;
if(n == 0)
return b;
else if(n > 0){
for(i = 0; i < n; i++)
b *= a;
return b;
}else{
for(i = 0; i < -n;i++)
b *= a;
return 1/b;
}
}
#ifdef DEBUG //通过定义这个宏,控制下面的测试代码是否被编译
int main(void)
{
float a;
int n;
printf("请输入浮点数和指数:");
scanf("%f%d",&a,&n);
printf("%f\n",my_power(a,n));
}
#endif
②第二种形式
#ifndef 宏//如没有定义宏,则编译代码段A,否则,编译代码段B
代码段A
#else
代码段B
#endif
//用于头文件中——作用:防止头文件被重复包含
③第三种形式
#if 表达式 //如表达式为真,则编译代码段A,否则,编译代码段B
代码段A
#else
代码段B
#endif
//用于注释中
例如:
int main(void)
{
#if 0
printf("hello world\n");
#else
printf("farsight\n");
#endif
return 0;
}
2、字节序
<1>概念
字节序指的是处理器在对字(大于一个字节的数据)取值时,解释其中各个字节的顺序
<2>大端序和小端序
大端序:低字节数据保存在高地址位置,其他字节数据依次保存在低地址位置,这种字节序称为大端序
小端序:低字节数据保存在低地址位置,其他字节数据依次保存在高地址位置,这种字节序称为小端序
<3>如何判断当前的机器属于哪种字节
nt main(void)
{
unsigned int a = 0x12345678;
unsigned char b;
b = *(unsigned char*)&a;
if(b == 0x78)
printf("小端序\n");
else
printf("大端序\n");
return 0;
}
3、地址对齐
<1>概念
为了提高机器访问内存中的数据的效率,给数据在内存中分配空间时,将数据的空间分配在某些特殊的地址位置
这种分配空间的方式称为地址对齐
地址对齐分为自然对齐和适当对其
<2>自然对齐
如果某个数据的地址能够被它的长度整除,则这种分配内存的方式称为自然对齐。
<3>适当对齐
数据的M值——每一个数据都存在一个M值
如果数据的长度<机器字长,M值取数据的长度
如果数据的长度>=机器字长,M取机器字长
适当对齐:如果某个数据的地址能够被它的M值整除,则这种分配内存的方式称为适当对齐
<4>C语言中的数据对齐——32bit机器
基本类型数据——按数据的M值对齐
例如:
–|--
char|1个字节对齐
short|2个字节对齐
int|4个字节对齐
float|4个字节对齐
double|4个字节对齐
数组——按数组元素的M值对齐
联合体和结构体——成员中最大的M值对齐
联合体,举例:
union A{
short a; M值:2
float b; M值:4
double d; M值:4
}; //按4个字节对齐
结构体,举例:
#include <stdio.h>
#include <string.h>
#pragma pack(push) //将默认对齐方式压栈
//指定1个字节对齐
#pragma pack(1)
struct A{
char a;
int b;
short c;
};
#pragma pack(pop) //还原默认的对齐方式
struct B{
char a;
short c;
int b;
};
int main(void)
{
struct A st1;
struct B st2;
printf("sizeof(st1) = %d\n",sizeof(st1));
printf("sizeof(st2) = %d\n",sizeof(st2));
return 0;
}
4、特殊函数
<1>递归函数
如果一个函数中,调用了自身,则这样的函数称为递归函数,例如:
void fun(void )
{
printf("hello world\n");
fun();
}
一个正确的递归函数,必须具备以下几个条件:
递推阶段,回归阶段,地推终止条件
所以,上面的递归函数可以修改为:
void fun(int n)
{
printf("hello world\n");
if(n > 0) //递推终止条件
fun(n-1);
}
int main(void)
{
fun(5);
return 0;
}
<2>变参函数
调用函数时,如果可以传递不同类型或者不同个数的参数,这样的函数称为变参函数
变参函数的函数头中,变参用…表示,例如:
int fun(int n,...)
{
}
//最典型的变参函数就是:printf
int printf(const char *format, ...);
<3>回调函数
把简介调用的函数称为回调函数,例如:
void fun(void) //回调函数
{
printf("hello world\n");
}
void fun1(void(*p)(void))
{
p(); //在fun1中间接调用fun,此时fun就是回调函数
}
int main(void)
{
void fun1(fun);
return 0;
}
<4>内联函数
定义函数时,在返回值类型的前面如果加一个关键字:inline,此时该函数称为内联函数。
把一个函数定义为内联函数,就是要求系统尽可能快的调用该内联函数
此时,内联函数的调用过程就会与普通函数的调用过程不同,它就会以带参宏的调用过程被系统调用
内联函数原则:
①函数体中的代码不易过多
②函数体中不能洗循环语句
③一般为系统中比较重要的代码,希望它能够快速的执行
④内联函数一般会定义在头文件中