文章目录
1. 常量符号化
程序中的数字有时含义不明,被称为魔术数字。通常使用符号来表示。
常用的方式有解决这种问题
(1)
const
const double PI = 3.1415926;
// 星期
const int SUM = 0;
const int MON = 1;
const int TUES = 2;
const int WED = 3;
const int THUR = 4;
const int FRI = 5;
const int SAT = 6;
(2)
#define
#define PI 3.1415926
// 星期
#define SUM 0
#define MON 1
#define TUES 2
#define WED 3
#define THUR 4
#define FRI 5
#define SAT 6
- const与#define区别
No | 比较项 | #define | const |
---|---|---|---|
1 | 编译处理 | 预处理阶段 | 编译、运行阶段 |
2 | 工作原理 | 简单的字符串替换 | 有对应的数据类型 |
3 | 存储方式 | 展开,在内存中有若干个备份 | 只读变量在内存中只有一份 |
4 | 类型检查 | 没有类型安全检查 | 在编译阶段进行类型检查 |
5 | 作用域 | 从定义开始,任何位置都可访问 | 只能在变量作用域内 |
作用域
#include <stdio.h>
void func (){
#define N 12
const int n = 12;
}
void main(){
printf("%d\n",N);
printf("%d\n",n);
}
2. 枚举
2.1. 枚举是什么?
枚举是一种用户定义的数据类型,枚举可以看作是一组宏定义。
2.2 枚举怎么用?
(1)enum 枚举类型名{名字0,名字1,名字2,…,名字n};
枚举大括号里面的名字是常量符号,类型为int,值依次从0到n。
枚举就是给这些常量值,规定一个名字。
枚举量可以直接作为值使用。
枚举类型可以直接作为类型使用。
#include <stdio.h>
enum WeekDay
{
MonDay,TuesDay,WednesDay,ThursDay,FriDay,SaturDay,SunDay
};
int main(void)
{
//int day; //day定义成int类型不合适
enum WeekDay day = WednesDay; //赋值不能直接写成数字2,因为不符合逻辑
printf("%d\n",day); //这里输出的值是2,如果是上面赋值为SunDay,则为6
return 0;
}
(2)声明枚举时可以指定值
// 常用进制
enum Radix{Bin=2,Oct=8,Dec=10,Hex=16};
(3)也可以其中一个值,后续值依次加1
enum Mouth{Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sept,Oct,Nov,Dec};
2.3 为什么用枚举?
(1)一组常量定义语义。
限制值的可用范围。
(2 )枚举的优缺点
代码更安全
书写麻烦
(3)枚举和宏的选择;
在C++中,枚举(enum)和宏(macro)都可以用来定义常量,但它们之间有一些关键的区别:
-
类型安全:
- 枚举是一种强类型的数据类型,它具有明确的类型信息,这意味着您不能将非枚举类型的值赋值给一个枚举变量。
- 宏是简单的文本替换,没有类型检查,可能会导致类型不匹配的问题。
-
作用域:
- 枚举具有块级作用域,这意味着它们只能在定义它们的块内使用。
- 宏没有作用域限制,它们可以在整个文件或项目中使用。
-
内存占用:
- 枚举在编译时被替换为其值,因此不会占用额外的运行时内存。
- 宏会在代码中进行文本替换,这可能会增加程序的大小。
-
调试和错误检测:
- 枚举可以通过编译器来确保它们没有被修改,这有助于防止错误。
- 宏没有这样的机制,如果宏定义被错误地修改,可能不会在编译时被发现。
-
可读性和维护性:
- 枚举使代码更易于阅读和理解,因为它们明确表示了变量是不可变的。
- 宏可能会使代码难以理解,因为它们隐藏了实际的值。
-
性能:
- 枚举通常比宏更快,因为它们在编译时就已经确定了值。
- 宏可能会在运行时进行计算,这可能会引入额外的开销。
选择使用枚举还是宏取决于具体的应用场景和需求:
- 如果需要定义一组相关的命名常量,并且希望它们具有明确的类型信息,那么应该使用枚举。
- 如果需要定义一个简单的文本替换,并且不需要类型安全性,那么可以使用宏。
在现代 C++ 代码中,推荐使用枚举来定义常量,除非有特定的原因需要使用宏。
3 宏定义
3.1. 宏定义是什么?
宏是用来表示一段代码的标识符。
宏也是标识符,也要满足标识符的规则。但通常习惯使用大写字母和下划线命名。
3.2. 宏定义怎么用?
宏定义通常有三种用法:
当作常量使用。
当作函数使用。
编译预处理。
3.2.1 宏定义常量
3.2.1.1 预定义宏
ANSI C标准定义有些定义好的宏定义,称为预定义宏。这些宏定义以双下划线__开头结尾。
No. | 预定义宏 | 作用 |
---|---|---|
1 | LINE | 当前所在文件的行号 |
2 | FILE | 表示当前源文件 |
3 | DATE | 文件被编译的日期 |
4 | TIME | 文件被编译的时间 |
示例
printf("%s:%d",__FILE__,__LINE__);
printf("%s:%s",__DATE__,__TIME__);
试一试
多执行几次,多编译几次查看效果
3.2.1.2 自定义宏
除了使用标准定义的宏,可以使用#define指令用来定义一个宏。
语法
#define 标识符 值
示例
#define PI 3.1415926
说明
注意没有结尾的分号,因为不是C的语句。
(1)名字必须是一个单词,值可以是各种东西。在C语言的编译器开始之前,编译预处理程序会把程序中的名字换成值。是完全的文本替换。
(2)如果一个宏的值有其他宏的名字,也会被替换。
#define PI_2 2*PI
(3)如果一个宏的值超过一行,最后一行之前行末需要加\。
#define PI_2 2 \
* \
PI
(4)宏的值后面出现的注释不会被当做宏的值的一部分。
#define PI_2 2*PI // 二倍的PI
3.2.2 带参数的宏
宏可以带参数,使用上有些像函数。这种宏称为带参数的宏。
语法
#define 标识符(参数...) 代码
示例
#define square(x) ((x)*(x))
#define cube(x) ((x)*(x)*(x))
试一试
是否可以不带括号?
#define square(x) xx
#define cube(x) xx*x
square(10);
cube(10);
square(10+1);
cube(10+1);
说明
上面因为缺少括号导致错误,称为宏定义边际效应,所以带参数的宏需要在一下两个位置加上括号:
参数出现的每个地方都要加括号。
整个值要加括号。
参数的宏也可以有多个参数
#define MIN(a,b) ((a)<(b)?(a):(b))
练习
变量值交换SWAP(a,b)
最大值MAX(a,b),最小值MIN()
获取数组元素个数SIZEOF(arr)
尽量避免使用宏定义。
3.2.3编译预处理
有时我们会使用没有值的宏,这种宏用于条件编译的,#ifdef #ifndef用于检查宏是否被定义过。控制代码的编译。
编译预处理指令
以#开头的都是编译预处理指令。除了宏定义,还有文件包含#include和条件编译指令#if、#ifdef #ifndef、#else、#elif;
根据编译条件,选择编译或者编译某段代码。
编译预处理指令不是C语言的成分,但是C语言程序离不开它们。
#define _DEBUG
示例
#ifdef TEST
printf("Test\n");
#else
printf("No Test\n");
#endif
3.3 宏展开
宏的本质是指编译前(编译预处理阶段),用定义中的值或者代码完全替换宏的标识符。
在gcc中可以使用-E或者–save-temps,查看替换后的结果。
4. 类型重命名typedef
4.1. 类型重命名是什么?
给一个已有的数据类型声明一个新名字。新名字是数据类型的别名。
为现有类型创建别名,定义易于记忆的类型名。
简化代码。
便于批量修改具体类型。
4.2.类型重命名怎么用?
(1)基本类型重命名
类型重命名用法与变量定义相似,只是在前面加上typedef。
语法
typedef 类型 新名字;
实例
typedef unsigned char Byte;
Byte b = 0x11;
typedef char* Str;
Str str = "ABCDEFG";
创建平台无关的数据类型,比如:time_t、size_t、uint8_t、int8_t等。
(2)结构体/联合体类型重命名
a.我们使用结构体类型时,需要使用struct关键字。typedef可以省略这个关键字。
语法
typedef struct {
成员;
} 类型名;
typedef struct Point3D{
int x;
int y;
int z;
} Point3D;
Point3D p = {1,2,3};
b.有时结构体的类型名可以省略
typedef struct{
int x;
int y;
int z;
} Point3D;
c 在typedef定义结构体同时,可以定义结构体指针。
typedef struct{
int x;
int y;
int z;
} Point3D,*pPoint3D;
Point3D p = {1,2,3};
pPoint3D q = &p;
练习
1.重新定义结构体类型
struct student{
char name[32]; //姓名
int age; //年龄
float score; //成绩
} student;
(3) 函数指针类型重命名
语法
typedef 返回类型 (* 函数指针类型)(参数)
实例
int add(int a,int b){return a+b;}
typedef int (*opt)(int,int); // 定义函数指针类型
opt fpadd = &add; // 定义函数指针并赋值
printf("%d\n",(*fpadd)(1,3));
4.3 小结
操作 | 语法 | 示例 |
---|---|---|
定义变量 | 类型 变量名; | int n; |
定义类型 | typedef 类型 类型名; | typedef int num;num n; |
定义指针 | 类型* 指针名; | int* p; |
定义指针类型 | typedef 类型* 指针类型名; | typedef int* pointer;pointer p; |
定义函数 | 返回值类型 函数名(参数类表) | void func(int n){} |
定义函数指针 | 返回值类型 (*函数指针名)(参数类表) | void (*pfunc)(int);pfunc=&func; |
定义函数指针类型 | typedef 返回值类型 (*函数指针类型)(参数类表) | typedef void (*func_t)(int);func_t pfunc=&func; |