1、宏定义
1.1、不带参数的宏定义
#define 标识符 字符串 例如:#define PI 3.1415926
说明:
(1)宏名一般习惯用大写字母表示,但并非规定,也可以用小写
(2)使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量
(3)宏定义是用宏名代替一个字符串,也就是做简单的置换,并不做正确性检查。 比如如果写成 :
#define PI 3.l4l5926(把1错写成了l),
在预编译时并不会做任何语法检查,只有在编译已经把宏展开后的源程序时才会发现语法错误并报告
(4)宏定义不是C语句,不必在行末加分号,如果加了分号将会连同分号一起进行置换。 比如如果写成:
#define PI 3.1415926; area=PI*r*r;
则宏展开后该语句为:area=3.1415926;*r*r
(5)#define命令出息在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束,通常宏定义写在文件开头,在此文件范围内有效
(6)可以用#undef命令来终止宏定义的作用域,这样可以灵活控制宏定义的作用范围
(7)在进行宏定义时,可以应用已经定义的宏名,可以层层置换
(8)对程序中用引号括起来的字符串内的字符,即使与宏名相同,也不进行置换
(9)宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只做支付替换,不分配内存空间
1.2、带参数的宏定义
#define 宏名(参数表) 字符串 例如:#define S(a,b) a*b · · ·
area=S(3,2);
说明:
(1)对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替#define命令行中的形参
(2)在宏定义时,在宏名与带参数的括号之间不应加空格,否则将空格以后的字符都作为代替字符串的一部分。
#define S (r) PI*r*r --------- 加红色部分不能有空格
1.3、对于带参数的宏定义与函数的区别
(1)函数调用时,先求出实参表达式的值,然后代入形参,而使用带参数的宏只是进行简单的字符替换
(2)函数调用是在程序运行时处理的,为形参分配临时的内存空间,宏展开是在编译前进行的,在展开时并不分配内存单元,不进行值的传递,也没有“返回值”的概念
(3)对函数中的市场和形参都要定义类型,二者的类型要求一致,如不一致则要进行类型转换,而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符合代替,展开时代入指定的字符串即可,宏定义时,字符串可以是任何类型的数据
(4)调用函数只可得到一个返回值,而用宏可以设法得到几个结果.
得到几个结果,怎么理解呢?
#define PI 3.1415926
#define CIRCLE(R,L,S,V) L=2*PI*R; S=PI*R*R; V=4.0/3.0*PI*R*R*R
void main()
{
float r,l,s,v;
scanf("%f",&r);
CLE(r,l,s,v);
printf("r=%6.2f,l=%6.2f,s=%6.2f,v=%6.2f\n",r,l,s,v );
}
---------------------------
真正意义上的返回值, 应该是可以直接等于的.
Variable = fun();
如果宏能实现一个"="号赋给多个, 这才叫做多个返回值.但是实际上,这里并不能严格意义上进行多个结果的返回值,展开后可以发现其等价形式多个变量进行赋值操作,而不是严格意义上的通过返回值赋值"Variable = fun()".
(5)宏使用次数多是,宏展开后源程序变长,因为每次展开都使程序增长,而函数调用不会
(6)宏替换不占用运行时间,只占编译时间,而函数调用则占用运行时间用于分配单元、保存现场、值传递、返回等
注:
-
多层#define TURE 或 FALSE 或 其他 一定要明确,不然可能会被优化掉
-
宏定义出现在结构体内
预编译与编译两者的区别:宏定义是在预编译时就会去找到相应的常量去替换,就是在编译成目标文件之前已经进行了替换,所以这里在结构体中有宏定义不会被重复编译,也就不可能为结构体中的宏定义分配内存。
作用域还是开始于#define,结束于#undef,不会存在只作用于结构体之内的可能。把宏定义放在结构体中,主要是为了代码更加直观,让人一看就知道改结构体中的个标志位是什么,被定义在哪一位 ,以便于理解运算。
2、结构体
struct 结构体标识名
{
类型名1 结构成员表1;
类型名2 结构成员表2;
类型名n 结构成员表n;
};
注意:struct是关键字,是结构体类型的标志。“结构体标识名”和“结构体成员名”都是用户定义的标识符,“结构体标识名”是可选项,在说明中可以不出现。每个“结构成员表”中都可以含有多个同类型的成员名,它们之间用逗号隔开。结构体中的成员名可以和程序中的其他变量名相同,不同结构体中的成员也可以同名。要记住:结构体说明要以分号结尾。
定义结构体类型的变量、数组、和指针变量可用4种方式:
a:紧跟在结构体类型说明之后进行定义;
b:在说明一个无名结构体类型的同时直接进行定义;
c:先说明结构体类型,再单独进行变量定义;
d:使用typedef说明一个结构体类型名,再用新类型名来定义变量。
如果已经定义了一个结构体变量和基类型为同一结构体类型的指针变量,并使该指针指向同类型的变量,则引用结构体变量中的成员可以用以下三种形式:
形式1:结构体变量名.成员名 形式2:指针变量名->成员名 形式3:(*指针变量名).成员名
注意:结构体变量名也可以是已经定义的结构体数组中的数组元素。
说明:点号(.)称为成员运算符;箭头(->)称为结构指向运算符,它由减号和大于号两部分组成,它们之间不能有空格;在形式三中,一对圆括号不可少。这两个运算符与圆括号、下标运算符的优先级相同,在c语言的运算符中优先级最高。
..............................................................................
初始化成员:
顺序初始化
顺序初始化struct必须要按照成员的顺序进行,缺一不可,如果结构体比较大,很容易出现错误,而且表现形式不直观,不能一眼看出各个struct各个数据成员的值。
乱序初始化
乱序初始化是C99标准新加的,比较直观的一种初始化方式。相比顺序初始化而言,乱序初始化就如其名,成员可以不按照顺序初始化,而且可以只初始化部分成员,扩展性较好。linux内核中采用这种方式初始化struct。
乱序初始化有两种方式,一种是用点(.)符号;一种是用冒号(:)。方式一是C99标准,方式二是GCC的扩展,强烈建议使用第一种方式。
未被初始化的成员默认为0(指针类型的成员默认为NULL)。两种乱序初始化方法,即可以用在C代码中,也可以用在C++代码
..............................................................................
3、枚举
在程序中,可能需要为某些有限的整数定义一个别名,我们可以利用预处理指令#define来完成这项工作,更普遍采用的是枚举类型。
enum 枚举类型名 {常量1,常量2,常量3,.......};
enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN};
(1) 枚举型是一个集合,枚举成员是一些命名的整型常量,元素之间用逗号“,”隔开。
(2) DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
(3) 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
(4) 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
(5) 枚举型是预处理指令 #define 的替代。
(6) 类型定义以分号“;”结束。
声明变量
enum DAY yesterday;
enum DAY today;
enum week { Mon=1, Tue, Wed, Thu, Fri Sat, Sun} days; // 变量days的类型为枚举型enum week
typedef enum { Mon=1, Tue, Wed, Thu, Fri Sat, Sun} WEEK;
typedef enum WEEK { Mon=1, Tue, Wed, Thu, Fri Sat, Sun};
WEEK Monday;
WEEK Tuesday;
- 同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的命名常量。
- 定义、声明、赋初值可以同时进行。
#include <stdio.h>
/* 定义枚举类型,同时声明该类型的三个变量,并赋初值。它们都为全局变量 */
enum DAY
{
MON=1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} yesterday = MON, today = TUE, tomorrow = WED;
- 对枚举型的变量赋整数值时,需要进行类型转换。
#include <stdio.h>
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
void main()
{
enum DAY yesterday, today, tomorrow;
yesterday = TUE;
today = (enum DAY) (yesterday + 1); //类型转换
tomorrow = (enum DAY) 30; //类型转换
//tomorrow = 3; //错误
printf("%d %d %d \n", yesterday, today, tomorrow); //输出:2 3 30
}
- 枚举类型与sizeof运算符(可以量enum当做一个类似的 int 变量类型)
enum escapes
{
BELL = '\a',
BACKSPACE = '\b',
HTAB = '\t',
RETURN = '\r',
NEWLINE = '\n',
VTAB = '\v',
SPACE = ' '
};
enum BOOLEAN { FALSE = 0, TRUE } match_flag;
void main()
{
printf("%d bytes \n", sizeof(enum escapes)); //4 bytes
printf("%d bytes \n", sizeof(escapes)); //4 bytes
printf("%d bytes \n", sizeof(enum BOOLEAN)); //4 bytes
printf("%d bytes \n", sizeof(BOOLEAN)); //4 bytes
printf("%d bytes \n", sizeof(match_flag)); //4 bytes
printf("%d bytes \n", sizeof(SPACE)); //4 bytes
printf("%d bytes \n", sizeof(NEWLINE)); //4 bytes
printf("%d bytes \n", sizeof(FALSE)); //4 bytes
printf("%d bytes \n", sizeof(0)); //4 bytes
}
- 枚举类型作为函数的参数
#include "stdio.h"
typedef enum { e0 = 0, e1, e2, e3, e4, eMax }testEnum;
void fun(testEnum te)
{
printf("%d\r\n", te);
}
int main(void)
{
fun(e3);
fun((testEnum)8);
//fun(8); // ERROR:invalid conversion from 'int' to 'testEnum '
return 0;
}
输出: 3 8