C 语言枚举、typedef 与预处理详解
枚举类型
建议:如果定义不相干的常量,使用宏定义(符号常量);如果需要定义一组相关联的常量,如月份0 ~ 11
,星期1~6
,方向0 ~ 3
,使用枚举,进行统一管理。以后正式开发中switch的case后面访问的就是枚举中的常量。
枚举的作用就是将多个拥有关联关系的常量组合到一起,提高代码的可读性。
说明
枚举定义了一组常量,我们在开发中直接使用这些常量。
枚举类型也可以类似于结构体一样定义变量等操作。
枚举常量有默认值,从0开始依次+1;定义是可以指定默认值,个别没有赋值的可以根据赋值依次+1推导。
特点
定义了一组常量,类似定义了多个符号常量(宏定义)
提高了代码的可读性
语法
①定义枚举类型名称后,就可以定义该类型的变量(先类型,后变量)
注意:枚举的类型是符号常量
enum 枚举类型名 变量列表;
②在定义枚举类型的同时,定义该枚举类型的变量(类型+变量)
enum 枚举类型名{枚举元素列表} 变量列表;
③直接定义枚举变量(变量)
enum {枚举元素列表} 变量列表;
案例
/*************************************************************************> File Name: > Author: doris> Description: > Created Time: 2025-08-04 09:53:38************************************************************************/
#include <stdio.h>
/*
定义一个枚举类型
枚举类型一般首字母大写,主要是和枚举元素区分
**/
void test1()
{enum Week{//定义枚举元素,元素本质上就是常量,在编译期间会被替换为字面量,命名和符号常量一样为:大写+下划线//多个枚举元素之间使用逗号分隔//SUN,MON,TUE,WED,THU,FRI,SAT 值依次为:0 ~ 6SUN = 10,MON,TUE,WED,THU,FRI,SAT //值依次为:10~16};//1.直接访问枚举元素,适合于switchprintf("%d,%d,%d\n",SUN,WED,SAT); //10,13,16//2.定义枚举类型的变量,适合于函数传参enum Week week;//初始化week = TUE;//不能随便赋值,赋值枚举中定义的元素printf("%d\n",week);//3.定义枚举类型变量的同时赋值enum Week week1 = THU;printf("%d\n",week1);//3.定义多个枚举类型变量enum THU{A,B,C } x, y;x = B;y = C;printf("x=%d,y=%d\n",x,y);
}void test2()
{enum CaiQuan{SHI_TOU,JIAN_DAO,BU};printf("请输入0~2之间的整数:\n 0-石头,1-剪刀,2-布\n");int choice;scanf("%d",&choice);switch (choice){case SHI_TOU: printf("石头\n");break;case JIAN_DAO:printf("剪刀\n");break;case BU:printf("布\n");break;}
}int main(int argc, char *argv[])
{test1();test2();return 0;
}
typedef
说明:给类型重命名,不会影响到类型本身。
作业:给已有的类型起别名。
格式:
typedef 已有类型名 重命后的类型名;
//typedef unsigned lnog size_t;
使用:
/*************************************************************************> File Name: demo01.c> Author: doris> Description: > Created Time: 2025-08-04 10:59:36************************************************************************/
#include <stdio.h>int main(int argc, char *argv[])
{//定义一个结构体struct Student{int id;char *name;char sex;int age;};//类型重命名typedef struct Student Stu;Stu stu = {1,"张三",'w',21};printf("%d,%s,%c,%d",stu.id,stu.name,stu.sex,stu.age);Stu *p = &stu;printf("%d,%s,%c,%d",p ->id,p->name,p->sex,p->age);//方式2:定义数据类型的同时重命名typedef struct PersonInfo{int a;double b;}Per;Per per = {2,4.5};printf("%d,%.2f\n",per.a,per.b);//定义指针Per *p1 = &per;printf("%d,%.2f\n",p1->a,p1->b);return 0;
应用场景
数据类型复杂(结构体、共用体、枚举、结构体指针、无符号的长整型)时使用
为了跨平台的兼容性,例如:
- size_t:类型重名后的数据类型:
typedef unsigned long size_t;
- unit_16:类型重命名后的数据类型。
- size_t:类型重名后的数据类型:
案例:
/*************************************************************************> File Name: demo02.c> Author: doris> Description: > Created Time: 2025-08-04 11:16:06************************************************************************/
#include <stdio.h>struct Student
{int age;char *name;double scores[3];
};
typedef struct Student Stu_t; // 对类型重命名
typedef Stu_t* pStu_t; // 结构体指针重命名void test1()
{Stu_t s1 = {21,"zhangsan",{90,23,67}};printf("%d,%s,%.2lf,%.2lf,%.2lf\n",s1.age,s1.name,s1.scores[0],s1.scores[1],s1.scores[2]);Stu_t *p;p = &s1;printf("%d,%s,%.2lf,%.2lf,%.2lf\n",p->age,p->name,p->scores[0],p->scores[1],p->scores[2]);
}int main(int argc, char *argv[])
{test1();return 0;
}
综合案例:斗地主
1. 程序概述
这是一个模拟斗地主游戏发牌过程的C语言程序,实现了扑克牌的初始化、洗牌和发牌功能。
2. 功能需求
2.1 扑克牌定义
- 使用结构体
Card
表示一张牌,包含:- 花色属性
suit
(0-3表示普通花色♥♠♦♣,4表示小王,5表示大王) - 点数属性
rank
(0-12对应3-A,2,-1表示大小王)
- 花色属性
2.2 主要功能
- 初始化牌组:
- 创建包含54张牌的牌组(52张普通牌+2张王牌)
- 普通牌按花色(♠,♥,♣,♦)和点数(3-2)排列
- 洗牌功能:
- 使用随机数对牌组进行随机排序
- 确保每次运行洗牌结果不同(基于时间种子)
- 发牌功能:
- 将洗好的牌发给3个玩家
- 每个玩家17张牌
- 剩余3张作为底牌
- 显示功能:
- 打印每个玩家的手牌
- 打印底牌
3. 数据结构
suits[]
: 存储4种花色符号的字符串数组ranks[]
: 存储13个点数等级的字符串数组jokers[]
: 存储大小王描述的字符串数组Card
结构体: 表示单张牌的数据结构- 牌组数组:
deck[54]
- 玩家手牌数组:
player1[17]
,player2[17]
,player3[17]
- 底牌数组:
bottomCards[3]
4. 用户交互
程序运行后自动完成以下流程:
- 初始化牌组
- 洗牌
- 发牌
- 显示发牌结果(3个玩家的手牌和底牌)
5. 输出格式
- 普通牌显示格式:花色+点数(如"♠ 3")
- 王牌显示格式:“小王"或"大王”
- 玩家手牌按顺序显示,每张牌用空格分隔
- 底牌同样格式显示
6. 源码
/*************************************************************************> File Name: 综合案例:斗地主> Author: doris> Description: > Created Time: 2025-08-04 14:23:07************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>#define LEN 54//定义扑克牌的花色和点数
const char *suits[] = {"黑","红","梅","方"}; //花色
const char *ranks[] = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
const char *jokers[] = {"大王","小王"};//定义牌的结构体
typedef struct
{int suit; //花色下标(0 ~3:普通牌, 4:小王,5:大王)int rank;// 点数下标(0~12:普通牌,-1:大小王)
}Card;//初始化一副牌
void initDeck(Card *deck);//洗牌(打乱牌序)
void shuffeDeck(Card *deck);//发牌(3个玩家各自17张牌,最后3张作为底牌)
void deelCards(Card *deck,Card *player1,Card *player2,Card *player3,Card *bottomCards);//打印牌,根据数组中提供的花色和点数下标,获取相应的
void printCard(Card card);int main(int argc,char *argv[])
{//创建一个数组,用来存放一副牌(54)Card deck[LEN];//创建三个数组,用来存放3个玩家Card player1[17],player2[17], player3[17];//创建一个数组,用来存放底牌Card bottomCards[3];//初始化牌initDeck(deck);//洗牌shuffeDeck(deck);//发牌deelCards(deck,player1,player2,player3,bottomCards);//打印玩家手牌和底牌int i;printf("玩家1的手牌:");for(i = 0 ; i< 17; i++) printCard(player1[i]); printf("\n");printf("玩家2的手牌:");for (i = 0; i < 17; i++) printCard(player2[i]); printf("\n");printf("玩家3的手牌:");for (i = 0; i < 17; i++) printCard(player3[i]); printf("\n");printf("底牌:");for (i = 0; i < 3; i++) printCard(bottomCards[i]); printf("\n");return 0;
}/*** @brief 初始化一副牌* * @param deck 一副牌*/
void initDeck(Card *deck)
{//定义一个下标(0~53)int index = 0;//初始化52张普通牌for (int suit = 0;suit < 4;suit++)//{for (int rank = 0;rank < 13;rank++){//记录每张牌花色和点数的下标deck[index].suit = suit; // 花色下标deck[index].rank = rank; // 点数下标index++;}}//初始化大小王deck[index].suit = 4; // 小王deck[index].rank = -1;index++;deck[index].suit = 5;deck[index].rank = -1;
}/*** @brief 洗牌(打乱牌序)* * @param deck */
void shuffeDeck(Card *deck)
{//设置随机种子srand((unsigned)time(NULL));//洗牌//遍历当前的有序牌for (int i = 0; i < LEN;i++){//随机生成0~53之间的数,作为交换牌的下标int j = rand() % LEN;//交换当前遍历牌跟随机牌的位置Card temp = deck[i];deck[i] = deck[j];deck[j] = temp;}
}/*** @brief 发牌(3个玩家各自17张牌,最后3张作为底牌)* * @param deck * @param player1 * @param player2 * @param player3 * @param bottomCards */
void deelCards(Card *deck,Card *player1,Card *player2,Card *player3,Card *bottomCards)
{//动态下标int index = 0;//给玩家发牌/**for(int i = 0;i < LEN;i++){if(i % 3 == 0)else if (i % 3 == 1)else if (i % 3 == 2)}**/for(int i = 0;i < 17;i++){player1[i] = deck[index++];player2[i] = deck[index++];player3[i] = deck[index++]; }//最后三张作为底牌for(int i = 0;i < 3;i++){bottomCards[i] = deck[index++]; }
}void printCard(Card card)
{if(card.suit == 4 || card.suit == 5 ){// 大小王printf("%s",jokers[card.suit - 4]);}else{// 普通牌printf("%s %s",suits[card.suit], ranks[card.rank]);}
}
预处理
C语言的编译步骤
- 预处理
- 编译
- 汇编
- 链接
什么是预处理
预处理就是在源文件(.c文件)编译之前,所进行的一部分预备操作,这部分操是由预处理器(预处理程序)
自动完成。当源文件在编译时,编译器会自动调用预处理程序来完成预处理执行的操作,预处理执行解析完成才能进入下一步的编译过程。
查看预处理结果:
gcc 源文件 -E -o 程序名
预处理功能
宏定义
不带参数的定义
语法:
#define 宏名称 宏值(替换文本)
**预处理机制:**此时的预处理只做数据替换,不做类型检查
**注意:**宏定义不会占用内存空间,因为在编译前已经将宏名称替换成了宏值
**宏展开:**在预处理阶段将宏名称替换成宏值的过程称之为“宏展开”。
案例:
/*************************************************************************> File Name: demo01.c> Author: 千夕> Description: > Created Time: 2025年05月29日 星期四 10时12分50秒************************************************************************/#include <stdio.h>#define PI 3.1415926int main(int argc,char *argv[]){float l,s,r,v;printf("请输入圆的半径:\n");scanf("%f",&r);// 计算周长l = 2.0 * PI * r;// 计算面积s = PI * r * r;printf("l=%10.4f\ns=%10.4f\n",l,s);return 0;}
/*************************************************************************> File Name: demo01.c> Author: 千夕> Description: > Created Time: 2025年05月29日 星期四 10时12分50秒************************************************************************/#include <stdio.h>#define PI 3.1415926int main(int argc,char *argv[]){float l,s,r,v;printf("请输入圆的半径:\n");scanf("%f",&r);// 计算周长l = 2.0 * PI * r;// 计算面积s = PI * r * r;printf("l=%10.4f\ns=%10.4f\n",l,s);return 0;}
带参数的定义
语法:
#define 宏名(参数列表) 替换表达式
面试题:
#define MULTI(a,b) (a)*(b) #define MULTI(a,b) a * b
实现:
/*************************************************************************> File Name: demo02.c> Author: 千夕> Description: > Created Time: 2025年05月29日 星期四 10时22分23秒************************************************************************/#include <stdio.h>// 带参数的宏定义,宏名一般小写 #define MULTI_1(a,b) (a) * (b) #define MULTI_2(a,b) a * bint main(int argc,char *argv[]) {int result1 = MULTI_1(7+2,3); // (7+2) * (3) = 27printf("%d\n",result1);int result2 = MULTI_2(7+2,3); // 7 + 2 * 3 = 13printf("%d\n",result2);return 0; }
宏定义的作用域
#define
命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。可以用
#undef
命令终止宏定义的作用域。案例:
/*************************************************************************> File Name: demo04.c> Author: 千夕> Description: > Created Time: 2025-08-04 17:34:58************************************************************************/ #include <stdio.h>#define PI 3.14 // PI的有效范围:10~18行 #define DAY 29void func1() {float r = 4;float s = PI * r * r; // 预处理后:float s = 3.14 * r * rint day = DAY; // 预处理后:int day = 29; }#undef PI// 终止了 PI的范围#define PI 3.1415926void func2() {float r = 4;float s = PI * r * r; // 预处理后:float s = 3.1415926 * r * rint day = DAY; // 预处理后:int day = 29; }int main(int argc, char *argv[]) {return 0; }
在宏定义中引用已定义的宏名
案例:
/*************************************************************************> File Name: demo04.c> Author: 千夕> Description: > Created Time: 2025年05月29日 星期四 10时38分29秒************************************************************************/#include <stdio.h>#define R 3.0 // 半径 #define PI 3.14 #define L 2 * PI * R // 周长 在宏定义的时候,引入已定义的宏名 #define S PI * R * R // 面积#define P_WIDTH = 800 #define P_HEIGHT = 480 #define SIZE = P_WIDTH * P_HEIGHTint main(int argc,char *argv[]) {printf("L=%f\nS=%f\n",L,S);// 预处理后:2 * 3.14 * 3.0, 3.14 * 3.0 * 3.0return 0; }