预处理指令
预处理指令分为三种:宏定义、条件编译、文件包含
1) 所有的预处理指令都是以井号’#’开头
2) 预处理指令在代码编译前执行
3) 预处理指令的位置可以随便写
4) 预处理指令的作用域:从编写指令的那一行开始,一直到文件结尾,或者人为结束
宏定义
定义:#define 宏名 值(结束宏:#undef 宏名)
宏名一般大写,变量名一般小写
宏定义分为三种:不带参数的宏、带参数的宏、空宏
- 不带参数的宏定义
#define NUM 5
- 带参数的宏定义
#define NUM(a,b) ((a)*(b)) //a,b为变量
注意:定义带参数的宏时,参数跟结果都要加小括号,否则可能会出错,如:a=c+d,b=c-d,则NUM(a,b)为c+d*c-d结果会出错
原因:因为宏定义执行时仅仅是将宏名替换为后面的值,并不运算,运算在编译运行时发生,如果不加小括号会导致运算出错
- 定义空宏
#define NUM
使用宏定义替换时,双引号内""的宏不能被替换
#define COUNT 5
int main()
{
char *name="COUNT";
}
//字符串name中的COUNT不能被替换
条件编译
基本用法
#if 条件1
...code1...
#elif 条件2
...code2...
#else
...code3...
#endif
1> 如果条件1成立,那么编译器就会把#if 与 #elif之间的code1代码编译进去(注意:是编译进去,不是执行,和平时用的if-else是不一样的)
2> 如果条件1不成立、条件2成立,那么编译器就会把#elif 与 #else之间的code2代码编译进去
3> 如果条件1、2都不成立,那么编译器就会把#else 与 #endif之间的code3编译进去
4> 注意,条件编译结束后,要在最后面加一个#endif,不然后果很严重
5> #if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
应用举例
#include <stdio.h>
#define MAX 11
int main()
{
#if MAX==0
printf("MAX为0");
#elif MAX>0
printf("MAX大于0");
#else
printf("MAX小于0");
#endif
return 0;
}
在第2行定义了一个宏MAX,当然在开发中这个MAX可能被定义在其他头文件中,现在只是为了方便演示,就写到main函数上面了。注意第6到第12行的条件编译语句。由于MAX为11,所以#elif的条件成立,第9行代码将会被编译进去,其实编译预处理后的代码是这样的:
/*stdio.h文件中的内容将会代替#include <stdio.h>的位置*/
int main()
{
printf("MAX大于0");
return 0;
}
输出结果:MAX大于0
其他用法
#if defined()和#if !defined()的用法
#if 和 #elif后面的条件不仅仅可以用来判断宏的值,还可以判断是否定义过某个宏。比如:
#if defined(MAX)
...code...
#endif
如果前面已经定义过MAX这个宏,就将code编译进去。它不会管MAX的值是多少,只要定义过MAX,条件就成立。
条件也可以取反:
#if !defined(MAX)
...code...
#endif
如果前面没有定义过MAX这个宏,就将code编译进去。
#ifdef和#ifndef的使用
#ifdef的使用和#if defined()的用法基本一致
#ifdef MAX
...code...
#endif
如果前面已经定义过MAX这个宏,就将code编译进去。
#ifndef又和#if !defined()的用法基本一致
#ifndef MAX
...code...
#endif
如果前面没有定义过MAX这个宏,就将code编译进去。
文件包含
一般形式
第1种形式#include <文件名>
第2种形式 #include "文件名"
<>尖括号表示系统自带的文件,””双引号表示自定义的文件
使用注意
- 文件包含不允许循环包含(互相包含)
- 使用#include指令可能导致多次包含同一个头文件,降低编译效率
解决方法:#ifndef TEMP
#define TEMP
此处为防止重复的声明
#endif
解释:如果没有定义TEMP,则进入条件编译,定义TEMP并执行“防止重复的声明”一次,然后结束条件编译,下次再调用时TEMP宏定义已存在,将不会进入条件编译,从而防止被重复声明
typedef
作用:给已经存在的类型起一个新的名称
使用场合:
-
基本数据类型
-
指针
-
结构体
-
枚举
-
指向函数的指针
#include <stdio.h>
typedef int Integer;
typedef unsigned int UInteger;
typedef float Float;
int main()
{
Integer a=-1; //此时Integer即相当于int
UInteger b=2; //此时UInteger即相当于unsigned int
Float c=3; //此时Float即相当于float
printf("%d,%d,%.2f",a,b,c);
return 0;
}
在第3、第4、第5行分别给int、unsigned int、float起了个别名,然后在main函数中使用别名定义变量,用来跟原来的基本类型是完全一样的。输出结果:-1,2,3.00
给类型起别名后,原来的int、float还是可以正常使用的:
int a=9;
float b=1.25;
也可以在别名的基础上再起一个别名
typedef int Integer;
typedef Integer MyInteger;
typedef与指针
除开可以给基本数据类型起别名,typedef也可以给指针起别名
#include <stdio.h>
typedef char *String;
int main()
{
String str="itcast"; //此时String即相当于char *
}
typedef与结构体
给结构体起别名可以使代码更加简洁明
默认情况下结构体变量的使用
//定义结构体类型
struct Point{
float x;
float y;
};
int main()
{
//定义结构体变量
struct Point p;
p.x=1.2;
p.y=1.5;
return 0;
}
默认情况下,定义结构体变量需要带struct关键字
使用typedef给结构体起别名
//定义结构体类型
struct Point{
float x;
float y;
};
//起别名
typedef Point MyPoint;
int main()
{
MyPoint p; //此时MyPoint相当于struct Point
p.x=1.2;
p.y=1.5;
return 0;
}
在第7行给结构体Point起了个别名叫做MyPoint,然后在11行使用MyPoint定义了一个结构体变量p,不用再带上struct关键字了
其实第1~第7行的代码可以简写为:
//定义结构体类型的同时起别名
typedef struct Point{
float x;
float y;
} MyPoint;
甚至可以省略结构体名称:
//可省略结构体类型名
typedef struct{
float x;
float y
} MyPoint;
typedef与指向结构体的指针
typedef可以给指针、结构体起别名,当然也可以给指向结构体的指针起别名
#include <stdio.h>
//定义结构体类型并起别名
typedef struct{
float x;
float y;
} MyPoint;
//给结构体指针起别名
typedef MyPoint *PP;
int main()
{
MyPoint p={10.0,20.0};
//利用别名定义结构体指针a
PP a;
//结构体指针a指向结构体p
a=&p;
printf("%f,%f",a->x,a->y);
return 0;
}
在第4行定义了一个结构体,顺便起了个别名叫MyPoint,第10行为指向结构体的指针定义了别名PP。然后在main函数中使用这2个别名。
输出结果:10.000000,20.000000
typedef与枚举类型
使用typedef给枚举类型起别名也可以使代码简洁。
//定义枚举类型
enum Season{
spring,
summer,
autumn,
winter
};
//起别名
typedef enum Season Season;
int main()
{
//定义枚举变量
Season s=spring;
return 0;
}
在第2行定义了枚举类型,在第9行起了别名为Season,然后在第11行直接使用别名定义枚举变量,不用再带上enum关键字了。
第1行~第9行代码可以简化为:
typedef enum Season{
spring,
summer,
autumn,
winter
} Season;
甚至可以省略枚举名称,简化为:
typedef enum{
spring,
summer,
autumn,
winter
} Season;
typedef与指向函数的指针
先来回顾下函数指针的知识
#include <stdio.h>
//定义一个求和函数
int sum(int a,int b)
{
return a+b;
}
int main()
{
//定义一个指向函数的指针同时指向sum函数
int (*p)(int,int)=sum;
//利用指针p调用sum函数
int a=(*p)(2,3);
printf("sum=%d",a);
return 0;
}
在第4行定义了一个sum函数,第12行定义了一个指向sum函数的指针变量p,可以发现,这个指针变量p的定义比一般的指针变量看来复杂多了,不利于理解。
第15行调用了p指向的sum函数,输出结果:sum=5
为了简化代码和方便理解,我们可以使用typedef给指向函数的指针类型起别名
#include <stdio.h>
//定义一个求和函数
int sum(int a,int b)
{
return a+b;
}
//起别名
typedef int (*MySum)(int,int);
int main()
{
//利用别名定义一个指向函数的指针同时指向sum函数
Mysum p=sum;
//利用指针p调用sum函数
int a=(*p)(2,3);
printf("sum=%d",a);
return 0;
}
看第9行,意思是:给指向函数的指针类型,起了个别名叫MySum,被指向的函数接收2个int类型的参数,返回值为int类型。
在第14行直接用别名MySum定义一个指向sum函数的指针变量p,这样看起来简单舒服多了。第17行的函数调用是一样的。
typedef与#define
用#define 代替typedef时,定义指针是不能被替代的
#include <stdio.h>
//用typedef给char *起别名
typedef char *String;
//用#define替换char *
#define String2 char *
int mian()
{
//用typedef起的别名定义
String str1,str2; //定义了两个字符串指针str1、str2
//用#define替换定义
String2 str3,str4 //表示定义了字符串指针str3,定义了字符变量str4.因为#define仅仅是替换,并不是类型别名定义,替换后为char * a,b;而*只跟a结合,并不会同时与b结合。
}
第12行表示定义了两个字符串指针str1、str2
第15行表示定义了字符串指针str3,定义了字符变量str4
因为#define仅仅是替换,并不是类型别名定义,替换后即为:char *str3,str4;
static与extern
static和extern对函数的作用
外部函数(extern):定义的函数能被本文件和其他文件访问。
1. 默认情况下所有函数都是外部函数。
2. 不允许有同名的外部函数。
内部函数(static):定义的函数只能被本文件访问,其他文件不能访问。
1. 允许不同文件中有同名的内部函数。
static对函数的作用:
1. 完整的定义一个内部函数
2. 完整的声明一个内部函数
extern 对函数的作用:
1. 完整的定义一个外部函数
2. 完整的声明一个外部函数
(extern 可以省略,默认情况下声明和定义的函数都是外部函数)
static和extern对变量的作用
static和extern对全局变量的作用
static对全局变量的作用:
定义一个内部变量
extern对全局变量的作用:
声明一个外部变量,完整的变量声明需要用extern关键字
(注意:extren 只能声明外部变量,不能完整定义外部变量,extern修饰的全局变量只是声明,需再定义后方能使用)
全局变量分2种:
外部变量:定义的全局变量能被本文件和其它文件访问。
1. 默认情况下,所有的全局变量都是外部变量
2.全局变量可以重复定义,同一文件中的同名全局变量代表同一变量
3.不同文件中的同名外部变量,也都代表同一个变量。
4.可以将全局变量声明为局部变量后再使用
内部变量:定义的全局变量只能被本文件访问,不能被其他文件访问。
1. 不同文件中的同名内部变量,互不影响。
static 对局部变量的作用
- static 修饰局部变量:
1. 延长局部变量的生命周期:直到程序结束的时候,局部变量才会被销毁。
2. 不改变局部变量的作用域。
举例:
//被static修饰的局部变量一直存在,直到程序结束 void test() { int a=0; a++; printf(“a的值是%d\n”,a); //static修饰局部变量b static int b=0; b++; printf(“b的值是%d\n”,b); } int mian() { test(); test(); return 0; }
输出结果为:
a的值是1 b的值是1
a的值是1 b的值是2
- static修饰局部变量的使用场合:
1. 如果某个函数的调用频率很高。
2. 这个函数内部的某个变量值是固定不变的。
递归
递归的两个条件:
1) 函数自己调用自己(return 函数自身)
2) 必须有个明确的返回值