基础10:枚举+ 宏定义+类型重命名typedef

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

  1. const与#define区别
No比较项#defineconst
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)都可以用来定义常量,但它们之间有一些关键的区别:

  1. 类型安全:

    • 枚举是一种强类型的数据类型,它具有明确的类型信息,这意味着您不能将非枚举类型的值赋值给一个枚举变量。
    • 宏是简单的文本替换,没有类型检查,可能会导致类型不匹配的问题。
  2. 作用域:

    • 枚举具有块级作用域,这意味着它们只能在定义它们的块内使用。
    • 宏没有作用域限制,它们可以在整个文件或项目中使用。
  3. 内存占用:

    • 枚举在编译时被替换为其值,因此不会占用额外的运行时内存。
    • 宏会在代码中进行文本替换,这可能会增加程序的大小。
  4. 调试和错误检测:

    • 枚举可以通过编译器来确保它们没有被修改,这有助于防止错误。
    • 宏没有这样的机制,如果宏定义被错误地修改,可能不会在编译时被发现。
  5. 可读性和维护性:

    • 枚举使代码更易于阅读和理解,因为它们明确表示了变量是不可变的。
    • 宏可能会使代码难以理解,因为它们隐藏了实际的值。
  6. 性能:

    • 枚举通常比宏更快,因为它们在编译时就已经确定了值。
    • 宏可能会在运行时进行计算,这可能会引入额外的开销。

选择使用枚举还是宏取决于具体的应用场景和需求:

  • 如果需要定义一组相关的命名常量,并且希望它们具有明确的类型信息,那么应该使用枚举。
  • 如果需要定义一个简单的文本替换,并且不需要类型安全性,那么可以使用宏。

在现代 C++ 代码中,推荐使用枚举来定义常量,除非有特定的原因需要使用宏。

3 宏定义

3.1. 宏定义是什么?

宏是用来表示一段代码的标识符。
宏也是标识符,也要满足标识符的规则。但通常习惯使用大写字母和下划线命名。

3.2. 宏定义怎么用?

宏定义通常有三种用法:
当作常量使用。
当作函数使用。
编译预处理。

3.2.1 宏定义常量

3.2.1.1 预定义宏

ANSI C标准定义有些定义好的宏定义,称为预定义宏。这些宏定义以双下划线__开头结尾。

No.预定义宏作用
1LINE当前所在文件的行号
2FILE表示当前源文件
3DATE文件被编译的日期
4TIME文件被编译的时间

示例

printf("%s:%d",__FILE__,__LINE__);
printf("%s:%s",__DATE__,__TIME__);

试一试
多执行几次,多编译几次查看效果

3.2.1.2 自定义宏

除了使用标准定义的宏,可以使用#define指令用来定义一个宏。

  1. 语法
#define 标识符 值
示例
#define PI 3.1415926
说明
  1. 注意没有结尾的分号,因为不是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) x
x*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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值