学习目标
1.【掌握】枚举
2.【掌握】typedef关键字
3.【理解】预处理指令
4.【掌握】#define宏定义
5.【掌握】条件编译
6.【掌握】static与extern关键字
一、枚举
当我们要描述方向、四季、性别、学历、婚配情况等等事物的时候,我们知道这些事物的取值范围是非常有限的。比如,性别取值就男、女,四季取值就春、 夏、秋、冬。类似这样的需求,C语言提供了一种构造类型枚举专门针对此类需求,由程序员自己声明一种新的数据类型,并给这个新的数据类型声明几个固定枚举 值。同时,声明这个新的数据类型的变量时,给变量赋值的取值范围就只能赋值我们类型里声明的某个固定枚举值。
枚举类型的声明
语法:enum 枚举名 {枚举值1,枚举值2,...};
1
|
enum
Gender
{
Genderwomen
,
Genderman
}
;
//声明一个枚举类型,他的取值是women和man
|
枚举变量的声明
语法:enum 枚举名 变量名;
1
2
|
enum
Gender
{
Genderwomen
,
Genderman
}
;
//声明一个枚举类型,他的取值是women和man
enum
Gender
gender
;
//声明一个enum Gender类型的变量gender
|
变量的初始化
语法:enum 枚举名 变量名 = 枚举值;
1
2
|
enum
Gender
{
Genderwomen
,
Genderman
}
;
//声明一个枚举类型,他的取值是women和man
enum
Gender
gender
=
Genderwomen
;
//声明一个enum Gender类型的变量gender并初始化为women
|
枚举值对应的整型数值
1
2
3
4
5
6
|
enum
Gender
{
Genderwomen
,
Genderman
}
;
//声明一个枚举类型,他的取值是women和man
enum
Gender
gender
=
Genderwomen
;
//声明一个enum Gender类型的变量gender并初始化为women
printf
(
"gender = %d\n"
,
gender
)
;
//打印出gender = 0
gender
=
Genderman
;
//重新把枚举变量赋值man
printf
(
"gender = %d\n"
,
gender
)
;
//打印出gender = 1
gender
=
0
;
//这样赋值也是可以的,相当于赋值为women,但是不直观不好看.不推荐这种赋值
|
注意:
1.枚举也是一种数据类型,类型名是(enum 枚举名),必须要加上enum关键字啊。
2.给枚举变量初始化或者赋值的时候,取值只能取枚举类型的枚举值中的某个值,不能随意赋值。
3.每一个枚举值对应都有一个整型数值的,从第一个枚举值开始从0依次递增。
4.声明一个枚举变量的时候,这个变量里面存的实际上是这个枚举值对应的整型,而不是枚举值本身。
5.枚举值命名最好能加上区分这个枚举值属于哪个枚举类型的标示,比如在枚举值前面加上枚举类型名。
实际应用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
//性别枚举类型
enum
Gender
{
Genderwomen
,
Genderman
}
;
struct
Person
{
char
*name
;
//姓名
int
age
;
//年龄
enum
Gender
gender
;
//性别
}
;
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
struct
Person
laoWang
=
{
"老王"
,
18
,
Genderman
}
;
printf
(
"name = %s\nage = %d\ngender = %s\n"
,
laoWang
.
name
,
laoWang
.
age
,
laoWang
.
gender
==
0
?
"男"
:
"女"
)
;
/*打印出
name = 老王
age = 18
gender = 女
*/
return
0
;
}
|
二、typedef关键字
如果你感觉有些数据类型太长,难以记忆难以书写,我们可以使用typedef关键字为各种数据类型定义一个新名字(别名)。
语法:typedef 数据类型 别名;
typedef与普通数据类型
1
2
|
typedef
int
Integer
;
Integer
num
=
10
;
//等同于int num = 10;
|
typedef与指针
1
2
3
|
typedef
char
*
String
;
String
str
=
"字符串"
;
//等同于char *str = "字符串";
printf
(
"str = %s\n"
,
str
)
;
//打印 str = 字符串
|
typedef与结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//1.先声明结构体,再给结构体定义一个别名
struct
Person
{
char
*name
;
int
age
;
}
;
typedef
struct
Person
Person
;
//给struct Person定义一个别名
Person
person
=
{
"老王"
,
18
}
;
//等同于 struct Person person = {"老王",18};
printf
(
"姓名:%s\t年龄:%d\n"
,
person
.
name
,
person
.
age
)
;
//2.声明的结构体的同时给结构体定义一个别名
typedef
struct
Person
{
char
*name
;
int
age
;
}
Person
;
Person
person
=
{
"老王"
,
18
}
;
//等同于 struct Person person = {"老王",18};
printf
(
"姓名:%s\t年龄:%d\n"
,
person
.
name
,
person
.
age
)
;
//3.声明的结构体的同时给结构体定义一个别名,可以省略结构体名
typedef
struct
{
char
*name
;
int
age
;
}
Person
;
Person
person
=
{
"老王"
,
18
}
;
//等同于 struct Person person = {"老王",18};
printf
(
"姓名:%s\t年龄:%d\n"
,
person
.
name
,
person
.
age
)
;
|
typedef与指向结构体的指针
1
2
3
4
5
6
7
8
9
10
11
12
|
//声明一个结构体并定义别名
typedef
struct
{
char
*name
;
int
age
;
}
Person
;
typedef
Person
*
PP
;
//给指向结构体的指针取别名
Person
laoWang
=
{
"老王"
,
18
}
;
//声明结构体变量
PP
p
=
&
laoWang
;
// 声明指针变量指向结构体变量
//利用指针变量访问结构体成员
printf
(
"name = %s age = %d\n"
,
p
->
name
,
p
->
age
)
;
|
typedef与枚举
1
2
3
4
5
6
7
8
9
10
11
12
|
//1.先声明枚举类型,再给枚举类型定义别名
enum
Gender
{
Genderman
,
Genderwomen
}
;
typedef
enum
Gender
Gender
;
//给枚举类型起别名
Gender
gender
=
Genderman
;
//声明枚举变量
//2.声明枚举类型的同时定义别名
typedef
enum
Gender
{
Genderman
,
Genderwomen
}
Gender
;
Gender
gender
=
Genderman
;
//使用别名声明枚举变量
//3.声明枚举类型的同时定义别名,枚举名也是可以省略的
typedef
enum
{
Genderman
,
Genderwomen
}
Gender
;
Gender
gender
=
Genderman
;
//使用别名声明枚举变量
|
typedef与指向函数的指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 定义一个sum函数,计算a跟b的和
int
sum
(
int
a
,
int
b
)
{
int
c
=
a
+
b
;
return
c
;
}
typedef
int
(
*MySum
)
(
int
,
int
)
;
//这里别名是MySum
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
MySum
p
=
sum
;
//声明一个指向sum函数的指针变量p
int
result
=
p
(
4
,
5
)
;
// 利用指针变量p调用sum函数
printf
(
"result = %d\n"
,
result
)
;
//打印 result = 9
return
0
;
}
|
三、预处理指令
让我们来回顾一下C程序从编写源代码到执行需要的步骤。先编写符合C语言语法规范的源代码文件,然后编译成二进制代码的目标文件,再然后会进行链 接,最终生成可执行文件。其实在编译之前,还有一个很重要的步骤,系统会自动执行,那就是执行预处理指令。预处理指令是在编译之前执行的,我们已经学 过#include文件包含指令,今天我们再来整几发指令。
四、#define宏定义
在程序编译之前,会把使用宏名的地方替换成宏值的内容。注意这里的替换是纯洁的替换,无论宏值是表达式还是数值,甚至是错误代码,都会原模原样的替换。
语法:#define 宏名 宏值
无参数的宏:使用无参数宏的时候,只是纯粹的文本替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <stdio.h>
#define LOG printf("这里是LOG\n")
#define SUM a+b
#define AREA PI*r*r //宏值可以使用宏名,在使用地方的后面也可以
#define PI 3.14
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
double
num
=
PI
*
3
*
3
;
//等同于double num = 3.14 * 3 * 3;
printf
(
"num = %.2lf\n"
,
num
)
;
LOG
;
//等同于printf("这里是LOG\n");
int
a
=
1
,
b
=
1
;
//如果宏值中有变量,必须先声明再使用
int
result
=
SUM
*
2
;
//等同于int result = a + b * 2; 注意替换后的运算符优先级
printf
(
"result = %d\n"
,
result
)
;
int
r
=
2
;
//半径为2
float
area
=
AREA
;
printf
(
"area = %.2f\n"
,
area
)
;
#undef PI //取消宏 PI ,在之后就不能替换宏值了
return
0
;
}
|
有参数的宏:使用有参数宏的时候,需要调用宏的人传入一个值作为宏值的一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <stdio.h>
#define Log(str) printf(str)
#define SUM(a,b) a+b //这里的参数就是宏名和宏值里相同的部分
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
Log
(
"有参数宏\n"
)
;
//等同于printf("有参数宏");
int
num1
=
10
,
num2
=
20
;
//这里传入的参数是num1和num2,并不是10和20。代码执行的时候才会为num1,num2赋值10,20
int
sum
=
SUM
(
num1
,
num2
)
;
//等同于int sum = a + b;
printf
(
"sum = %d\n"
,
sum
)
;
return
0
;
}
|
注意:
1.当宏值是一个表达式,宏值的语法错误不会报错,因为检查语法是在编译的时候干的。
2.当宏值是一个表达式,替换宏名也是替换源代码中使用宏名的地方,所以特别注意替换后的运算符优先级问题。
3.宏值当中如果有变量,使用宏值之前必须要先声明这个变量。
4.如果双引号中出现了宏名,其实这个不是宏名,只是和宏名很像的字符串。
5.宏可以定义在任何地方,能被使用的作用域是从定义开始到文件结束。
五、条件编译
在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译,这就是条件编译。
语法:
1
2
3
4
5
6
7
|
#if 条件1
代码
1
#elif 条件2
代码
2
#else
代码
3
#endif
|
执行顺序:如果条件1满足,则代码1参与编译。如果条件1不满足,条件2满足,则代码2参与编译。如果条件1和条件2都不满足,则执行代码3。
第一种情况,判断宏值是否满足条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <stdio.h>
#define Flag 1
//这里Score只能使用宏,不能使用变量
#if Flag == 1
#define SUM(a,b) a+b
#else
#define SUM(a,b) a-b
#endif
int
main
(
)
{
int
result
=
SUM
(
20
,
10
)
;
//如果Flag为1则相加,否则相减
printf
(
"result = %d\n"
,
result
)
;
return
0
;
}
|
第二种情况,判断一个宏有被定义
1
2
3
4
5
6
7
8
9
10
11
|
#include <stdio.h>
#define Score 5
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
#ifdef Score //等同于 #if defined(Score)
printf
(
"已经定义了\n"
)
;
#else
printf
(
"没有定义\n"
)
;
#endif
return
0
;
}
|
第三种情况,判断一个宏没有被定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#ifndef ___AAAA____ //等同于 #if !defined(Score)
#define ___AAAA____
//#include <stdio.h>
//#include "xxxxx.h"
//#include "aaaaa.h"
//#include "zzzzz.h"
//#include "vvvvv.h"
//这里是一百多个头文件。。
/*
这里表示如果没有定义宏名为___AAAA____的宏,就定义一个,然后包含进来若干头文件。
然并卵?
当其他文件包含本文件多次的时候,每次都会判断是否定义了___AAAA____。如果没有定义才包含,定义过了就算了
*/
#endif
|
注意:
1.条件编译一定要使用宏进行条件判断,不能使用变量,因为变量的值是在程序执行的时候才赋值的。
2.#endif表示这个条件编译结束,一定不能少,不然会发生一些不可预料的事情。
3.条件编译只会编译符合条件的那一个分支编译成二进制代码。
六、static与extern关键字
static与函数
如果一个函数被static关键字修饰,只能在当前模块中使用,就相当于内部函数咯。
extern与函数
如果一个函数被extern关键字修饰,可以在当前模块中使用,也能被其他模块共享,不过默认函数就是被extern修饰的,可以省略不写。
static与变量
被static修饰的局部变量会声明在常量区中,函数执行完毕后不会被释放,只有程序执行结束才会释放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
void
test
(
)
{
static
int
num
=
10
;
num
++
;
printf
(
"num = %d\n"
,
num
)
;
}
int
main
(
)
{
for
(
int
i
=
0
;
i
<
5
;
i
++
)
{
test
(
)
;
//执行5次test函数
}
/*打印出
num = 11
num = 12
num = 13
num = 14
num = 15
*/
return
0
;
}
|
被static关键字修饰的全局变量只能在当前模块中使用,不能被其他模块共享,相当于私有全局变量。
main.c文件
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h>
#include "test.h"
void
test
(
)
{
num
++
;
printf
(
"num = %d\n"
,
num
)
;
}
int
main
(
)
{
for
(
int
i
=
0
;
i
<
5
;
i
++
)
{
test
(
)
;
}
return
0
;
}
|
test.h文件
1
|
static
int
num
;
|
test.c文件
1
2
|
#include "test.h"
static
int
num
=
100
;
|
extern与变量
extern不能修饰局部变量,被extern修饰的全局变量可以在当前模块中使用,也能被其他模块共享。不过默认全局变量就是extern修饰 的,所以我们可以省略(Xcode6.3前是默认加extern的,Xcode6.3后必须自己在声明里加上extern,但定义的地方可以不写)。
main.c文件
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <stdio.h>
#include "test.h"
void
test
(
)
{
num
++
;
printf
(
"num = %d\n"
,
num
)
;
}
int
main
(
)
{
for
(
int
i
=
0
;
i
<
5
;
i
++
)
{
test
(
)
;
}
return
0
;
}
|
test.h文件
1
|
extern
int
num
;
|
test.c文件
1
2
|
#include "test.h"
extern
int
num
=
100
;
|