C语言:第20天笔记

内容提要

  • 构造类型
  • 枚举类型
  • typedef
  • 综合案例:斗地主
  • 预处理

构造类型:枚举类型

使用建议

  • 如果定义不相干的常量,使用宏定义(符号常量);
  • 如果需要定义一组相关联的常量(如月份011、星期06、方向03、男女01等),使用枚举,进行统一管理。
  • 正式开发中,switch的case后面访问的通常是枚举中的常量。

定义

  • 一般情况下,定义常量使用宏定义(#define 宏名称 宏值),宏定义适合无关联关系的常量;
  • 当需要对一组有关联关系的量(如月份011、星期06、方向0~3等)进行定义时,宏定义清晰度低、不便于统一管理且会增加代码量,此时需使用枚举;
  • 枚举的作用是将多个有关联关系的常量组合到一起,提高代码的可读性。

说明

  1. 枚举定义了一组常量,开发中可直接使用这些常量(常用);
  2. 枚举类型也可类似于结构体定义变量等操作(不常用);
  3. 枚举常量有默认值,从0开始依次+1;可在定义时指定默认值,若个别未赋值,可根据已赋值常量依次+1推导。

特点

  1. 定义了一组常量,类似于定义了多个符号常量(宏定义);
  2. 提高了代码的可读性。

语法

语法分类格式说明
先定义类型后定义变量enum 枚举类型名 变量列表;定义枚举类型名后,再定义该枚举类型的变量,枚举的元素是符号常量
定义类型同时定义变量enum 枚举类型名{枚举元素列表} 变量列表;在定义枚举类型的同时,定义该枚举类型的变量
直接定义枚举变量enum {枚举元素列表} 变量列表;不单独定义枚举类型名,直接定义枚举变量

案例(demo01.c)

#include <stdio.h>void test1()
{// 定义一个枚举类型// 注意:枚举类型名一般首字母大写,主要是跟枚举元素名区分enum Week{// 定义枚举元素,元素本质上就是常量,在编译期,会被替换为字面量// 枚举元素的命名和符号常量命名一致,都是大写+下划线// 多个枚举元素之间使用逗号分隔// SUN,MON,TUE,WED,THU,FRI,SAT // 此时,这7个常量的值依次为:0~6SUN = 10, MON, TUE, WED, THU, FRI, SAT // 此时,这7个常量的值依次为:10~16};// 1. 直接访问枚举元素,适合于switchprintf("%d,%d,%d\n", SUN, WED, SAT); // 10,13,16// 2. 定义枚举类型的变量,适合于函数传参enum Week week;// 初始化week = TUE; // 不能随便赋值,赋值一定是这个枚举中定义的元素printf("%d\n", week); // 12// 3. 定义枚举类型变量的同时赋值enum Week week1 = THU;printf("%d\n", week1);// 14// 4. 可以定义多个枚举变量enum THU{A, B, C} x, y;// 赋值x = B;y = C;printf("x=%d,y=%d\n", x, y);// 1,2
}void test2()
{// 定义枚举类型enum CaiQuan{SHI_TOU, JIAN_DAO, BU};printf("请输入0~2之间的整数:\n0-石头,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 long size_t;

使用案例(demo02.c)

#include <stdio.h>int main(int argc, char *argv[])
{// 方式1:先定义数据类型,再重命名// 定义一个结构体struct Student{int id;char *name;char sex;int age;};// 类型重命名typedef struct Student Stu; // 将 struct Student 重命名为Stu// 使用新类型名// 定义结构体实例Stu stu = {1,"张三",'w',21};printf("%d,%s,%c,%d\n", stu.id, stu.name, stu.sex, stu.age);Stu *p = &stu;printf("%d,%s,%c,%d\n", 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;
}

应用场景

数据类型复杂(结构体、共用体、枚举、结构体指针、无符号的长整型)时使用。

跨平台兼容性案例

C语言标准提供:
typedef signed long int_int64_t;
typedef unsigned Long intuint64_t;

常见跨平台类型重命名:

  1. size_ttypedef unsigned long size_t;
  2. unit_16:类型重命名后的数据类型。

进阶案例(demo03.c)

#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", {99, 98, 97}};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 主要功能
  1. 初始化牌组
    • 创建包含54张牌的牌组(52张普通牌+2张王牌);
    • 普通牌按花色(♠,♥,♣,♦)和点数(3-2)排列。
  2. 洗牌功能
    • 使用随机数对牌组进行随机排序;
    • 确保每次运行洗牌结果不同(基于时间种子)。
  3. 发牌功能
    • 将洗好的牌发给3个玩家;
    • 每个玩家17张牌;
    • 剩余3张作为底牌。
  4. 显示功能
    • 打印每个玩家的手牌;
    • 打印底牌。

3. 数据结构

  • suits[]:存储4种花色符号的字符串数组;
  • ranks[]:存储13个点数等级的字符串数组;
  • jokers[]:存储大小王描述的字符串数组;
  • Card结构体:表示单张牌的数据结构;
  • 牌组数组:deck[54]
  • 玩家手牌数组:player1[17]player2[17]player3[17]
  • 底牌数组:bottomCards[3]

4. 用户交互

程序运行后自动完成以下流程:

  1. 初始化牌组;
  2. 洗牌;
  3. 发牌;
  4. 显示发牌结果(3个玩家的手牌和底牌)。

5. 输出格式

  • 普通牌显示格式:花色+点数(如"♠ 3");
  • 王牌显示格式:“小王"或"大王”;
  • 玩家手牌按顺序显示,每张牌用空格分隔;
  • 底牌同样格式显示。

6. 源码


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.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; // 牌花色int rank; // 底数
} Card;/*** @brief 初始化扑克*/
void initCards(Card *deck);/*** @brief 洗牌*/
void shuffDeck(Card *deck);void dealCard(Card *deck, Card *bottomCards, Card players[3][17]);void printfCard(Card players[3][17]);int main(int argc, char *argv[])
{// 创建一个数组,存放一副牌(54张包括大小王)Card deck[LEN];// 创建三个玩家Card players[3][17];// 创建一个数组用来存放底牌Card bottomCards[3];// 初始化牌initCards(deck);// 洗牌shuffDeck(deck);// 发牌dealCard(deck,bottomCards,players);// 展示牌printfCard(players);return 0;
}/*** @brief 初始化扑克*/
void initCards(Card *deck)
{int index  = 0;for(int i = 0; i < 4; i++){for(int j = 0;i < 13; j++){deck[index].rank = j;deck[index].suit = i;index++;}}// 初始化大小王deck[index].suit = 4; // 小王deck[index].rank = -1;index++;deck[index].suit = 5; // 大王deck[index].rank = -1;	
}// 洗牌
void shuffDeck(Card *deck)
{srand((unsigned)time(NULL));// 洗牌for (int i = 0; i < LEN; i++){int j = rand() % LEN; // 0-53Card temp = deck[i];deck[i] = deck[j];deck[j] = temp;}
}void dealCard(Card *deck,  Card *bottomCards, Card players[3][17])
{int index = 0;for(int i = 0;i < 17; i++){for(int j = 0;j < 3; j++){players[j][i] = deck[index++];}}// 底牌for(int i=0;i< 3; i++){bottomCards[i] = deck[index++];	}}// 打印
void printfCard(Card players[3][17])
{for(int i = 0;i < 3; i++){printf("第%d个玩家的牌是:",i+1);for(int j = 0; j < 17; j++){Card card = players[i][j]; if (card.suit == 4 || card.suit == 5){// 大小王printf("%s ",jokers[card.suit - 4]);}printf("%s %s ",suits[card.suit], ranks[card.rank]);}}}

运行结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

预处理

一、C语言的编译步骤

C语言程序从源代码到可执行文件,需经历以下4个核心步骤,预处理是整个流程的第一步:

  1. 预处理:由预处理器处理源文件中的预处理指令(如#define#include等),生成预处理文件(.i格式);
  2. 编译:编译器对预处理文件进行词法分析、语法分析、语义分析及优化,生成汇编代码文件(.s格式);
  3. 汇编:汇编器将汇编代码转换为机器可识别的二进制目标文件(.o.obj格式);
  4. 链接:链接器将多个目标文件与标准库(如libc)组合,生成最终可执行文件(如Windows下的.exe、Linux下的无后缀可执行文件)。

编译流程示意图

源文件(.c) → 预处理(cpp) → 预处理文件(.i) → 编译(cc1) → 汇编文件(.s) → 汇编(as) → 目标文件(.o) → 链接(ld) → 可执行文件

二、什么是预处理

预处理是在源文件(.c文件)编译之前,由预处理器自动完成的预备操作。当编译器对源文件进行编译时,会先调用预处理器执行预处理操作,只有预处理解析完成后,才能进入后续的编译过程。

查看预处理结果

通过gcc编译器的-E选项可查看预处理后的结果,命令格式如下:

gcc 源文件 -E -o 预处理输出文件名

例如,对demo01.c进行预处理并输出到demo01.i,命令为:gcc demo01.c -E -o demo01.i

三、预处理核心功能:宏定义

宏定义是预处理阶段最常用的功能之一,用于将一个标识符(宏名)定义为一个字符串(宏值),在预处理阶段会将代码中所有宏名替换为对应的宏值(即“宏展开”)。

1. 不带参数的宏定义

语法
#define 宏名称 宏值(替换文本)
  • 预处理机制:仅做文本替换,不进行类型检查;
  • 内存特性:宏定义不占用内存空间,因为编译前宏名已被替换为宏值;
  • 宏展开:预处理阶段将宏名替换为宏值的过程称为“宏展开”。
案例(demo01.c)
#include <stdio.h>#define PI 3.1415926  // 定义宏PI,宏值为3.1415926int main(int argc, char *argv[])
{float l, s, r;printf("请输入圆的半径:\n");scanf("%f", &r);// 计算周长(宏PI会被替换为3.1415926)l = 2.0 * PI * r;// 计算面积(宏PI会被替换为3.1415926)s = PI * r * r;printf("l=%10.4f\ns=%10.4f\n", l, s);return 0;
}
宏展开后的效果

预处理后,代码中PI会被直接替换为3.1415926,关键代码展开如下:

l = 2.0 * 3.1415926 * r;
s = 3.1415926 * r * r;

2. 带参数的宏定义

带参数的宏定义允许宏名接收参数,替换时会将宏体中的参数占位符替换为实际传入的参数,类似函数但无函数调用开销(仅文本替换)。

语法
#define 宏名(参数列表) 替换表达式
注意事项(面试高频考点)

带参数的宏定义需注意括号的使用,若缺少括号可能导致运算优先级错误。例如:

  • 错误写法:#define MULTI(a,b) a * b
    当调用MULTI(7+2, 3)时,展开为7+2*3,结果为13(不符合预期);
  • 正确写法:#define MULTI(a,b) (a) * (b)
    当调用MULTI(7+2, 3)时,展开为(7+2)*(3),结果为27(符合预期)。
案例(demo02.c)
#include <stdio.h>// 带参数的宏定义,宏名一般小写(区分函数名)
#define MULTI_1(a,b) (a) * (b)  // 正确:参数加括号
#define MULTI_2(a,b) a * b      // 错误:参数无括号int main(int argc, char *argv[])
{// 调用MULTI_1:(7+2)*(3) = 27int result1 = MULTI_1(7+2, 3); printf("%d\n", result1);// 调用MULTI_2:7+2*3 = 13(结果不符合预期)int result2 = MULTI_2(7+2, 3); printf("%d\n", result2);return 0;
}

3. 宏定义的作用域

  • 默认作用域#define命令需写在函数外部,宏名的有效范围从定义处开始,到本源文件结束;
  • 终止作用域:可通过#undef命令主动终止宏定义的作用域,后续代码中该宏名不再生效。
案例(demo04.c)
#include <stdio.h>#define PI 3.14        // PI的有效范围:从定义处(第8行)到#undef处(第18行)
#define DAY 29         // DAY的有效范围:从定义处(第10行)到本源文件结束void func1()
{float r = 4;float s = PI * r * r;  // 预处理后:3.14 * r * r(PI有效)int day = DAY;         // 预处理后:29(DAY有效)
}#undef PI                 // 终止PI的作用域,后续代码中PI失效
#define PI 3.1415926      // 重新定义PI,作用域从第20行到本源文件结束void func2()
{float r = 4;float s = PI * r * r;  // 预处理后:3.1415926 * r * r(新PI有效)int day = DAY;         // 预处理后:29(DAY仍有效)
}int main(int argc, char *argv[])
{return 0;
}

4. 宏定义的嵌套引用

宏定义支持嵌套,即一个宏的宏值中可以引用其他已定义的宏名,预处理时会逐层展开所有宏。

案例(demo04.c)
#include <stdio.h>#define R 3.0          // 定义宏R(半径)
#define PI 3.14        // 定义宏PI(圆周率)
#define L 2 * PI * R   // 嵌套引用PI和R,计算周长
#define S PI * R * R   // 嵌套引用PI和R,计算面积// 错误示例:宏定义中不应加等号(会导致替换错误)
#define P_WIDTH = 800  
#define P_HEIGHT = 480
#define SIZE = P_WIDTH * P_HEIGHTint main(int argc, char *argv[])
{// 预处理后:L展开为2*3.14*3.0,S展开为3.14*3.0*3.0printf("L=%f\nS=%f\n", L, S);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94465.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94465.shtml
英文地址,请注明出处:http://en.pswp.cn/web/94465.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 vue3 和 vue2 中,v-for 和 v-if 可以一起用吗,区别是什么

在 Vue 2 和 Vue 3 中&#xff0c;v-for 和 v-if 可以一起使用&#xff0c;但两者在处理顺序和推荐用法上存在明显区别&#xff0c;主要体现在优先级和最佳实践上&#xff1a; 1. Vue 2 中的 v-for 与 v-if优先级&#xff1a;v-for 的优先级高于 v-if。 这意味着 Vue 会先循环渲…

Linux-进程相关函数

文章目录Linux-进程相关函数父子进程关系父子进程地址空间getpid函数 获取本进程号getppid函数 获取当前进程的进程的父进程号getpgid函数 获取进程组号示例fork函数 创建进程区分父子进程exit函数 进程退出wait函数 等待子进程退出waitpid函数Linux-进程相关函数 每个进程都由…

数据挖掘 6.1 其他降维方法(不是很重要)

6.1 Other dimensionality reduction methods 6.1 其他降维方法 其他降维方法前言问题答案流形3 降维大纲3.1 线性方法3.2 非线性方法3.2.1 流形学习方法&#xff08;Manifold Learning&#xff09;3.2.2 概率方法&#xff08;Probabilistic Approaches&#xff09;3.2.3 拓扑数…

Unity中的特殊文件夹

一.工程路径获取print(Application.dataPath);只用于游戏开发编辑器模式下&#xff0c;游戏发布后此路径就不存在了二.Resources 资源文件夹//路径获取: //一般不获取 //只能使用Resources相关API进行加载 //如果硬要获取 可以用工程路径拼接print(Application.dataPath "…

Seaborn数据可视化实战:Seaborn高级使用与性能优化教程

Seaborn最佳实践与技巧 学习目标 本课程将深入探讨Seaborn库的高级使用技巧&#xff0c;包括性能优化、常见问题解决方法等&#xff0c;旨在帮助学员掌握如何高效地使用Seaborn进行数据可视化&#xff0c;提升图表的美观度和信息传达效率。 相关知识点 Seaborn最佳实践与技巧 学…

分布式系统与单机系统的优劣势对比

近期有遇到一个本地部署的需求&#xff0c;他们希望用主备方案&#xff0c;这就涉及到了备用系统怎么收费的问题。我们是单机系统&#xff0c;其他友商是分布式系统&#xff0c;那20坐席的手拨需求到底是选单机系统好&#xff0c;还是选分布式系统好呢&#xff1f;了解了两者的…

深度学习:从手写数字识别案例认识pytorch框架

目录 一、PyTorch 核心优势与框架定位 二、实战基础&#xff1a;核心库与数据准备 1. 关键库导入与功能说明 2. MNIST 数据集加载与可视化 &#xff08;1&#xff09;数据集下载与封装 &#xff08;2&#xff09;数据集可视化&#xff08;可选&#xff09; 3. DataLoade…

二分|组合|旋转数组

lc1976dijk min_pathpq. min_wlcr187同lc1823.约瑟夫环class Solution { public:int iceBreakingGame(int num, int target) {int x0;for(int i2;i<num;i){x(xtarget)%i;} return x;} };lc2972计算数组中可移除的子数组数量先找最长递增前缀&#xff0c;再结合递增后缀…

【C语言16天强化训练】从基础入门到进阶:Day 10

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 &#x1f349;学习方向&#xff1a;C/C方向学习者…

云计算与云原生技术探索

&#x1f31f; Hello&#xff0c;我是蒋星熠Jaxonic&#xff01; &#x1f308; 在浩瀚无垠的技术宇宙中&#xff0c;我是一名执着的星际旅人&#xff0c;用代码绘制探索的轨迹。 &#x1f680; 每一个算法都是我点燃的推进器&#xff0c;每一行代码都是我航行的星图。 &#x…

STM32之ADC详解

一、ADC概述 ADC&#xff08;模拟量转数字量转换器&#xff09;&#xff0c;在 STM32 开发中&#xff0c;利用 ADC 端口的电压数据&#xff0c;转换为对应的具体数字量数据内容。可通过 ADC 方式获取常用数据内容有&#xff1a; 光敏电阻、电池电量、油箱油量 ADC 转换…

深入理解计算机网络:从基础到应用的全面解析

标题&#xff1a;深入理解计算机网络&#xff1a;从基础到应用的全面解析 引言 计算机网络已经渗透到我们生活的方方面面。从家庭Wi-Fi到全球互联网&#xff0c;我们每天都在通过各种设备进行数据交换。本文将带领你走进计算机网络的世界&#xff0c;深入探讨网络的基础知识、常…

以结构/序列/功能之间的关系重新定义蛋白质语言模型的分类:李明辰博士详解蛋白质语言模型

上海交通大学第三届「AI for Bioengineering 暑期学校」于 2025 年 8 月 8—10 日正式开启。本次暑期学校汇聚了自全球 70 余所高校、 10 余所科研机构及 10 余家行业领军企业的 200 余位青年才俊、科研学者和产业代表&#xff0c;共同聚焦于人工智能&#xff08;AI&#xff09…

【大语言模型 15】因果掩码与注意力掩码实现:深度学习中的信息流控制艺术

【大语言模型 15】因果掩码与注意力掩码实现&#xff1a;深度学习中的信息流控制艺术 关键词&#xff1a;因果掩码、注意力掩码、下三角掩码、Padding掩码、序列建模、GPT解码器、BERT编码器、批量处理优化、自回归语言模型、信息流控制 摘要&#xff1a;在Transformer架构中&a…

大型电动化工程机械设备智能施工试验场的网络设计方案

随着工程机械设备逐步迈向智能化、电动化和无人化&#xff0c;传统施工试验场已经难以满足现代化施工设备的研发、测试和验证需求。为了适应这一趋势&#xff0c;建设一个基于高性能网络架构的大型智能施工试验场成为关键。本文将从网络架构、设备选型和功能实现等方面&#xf…

SPMI总线协议(一)

1、简单说明 系统电源管理接口( System Power Management Interface简称SPMI)是一种双线串行接口,用于连接片上系统(SoC)处理器系统的集成电源控制器(PC)与一个或多个电源管理集成电路(PMIC)电压调节系统。SPMI 使系统能够使用单个 SPMI 总线动态调整 SoC 内部电压域的…

数据存储的思考——从RocketMQ和Mysql的架构入手

数据存储是后台服务系统永远绕不开的知识 笔者希望能够从宏观的角度出发&#xff0c;思考数据存储系统的共性和设计方案&#xff0c;尝试从Mysql和RocketMQ的角度去思考谈谈系统存储架构的设计哲学 前置的知识 什么是RocketMQ、什么是Mysql&#xff0c;他们对于后端系统的主用…

MySQL 面试题系列(二)

目录1: SQL 中常见的 JOIN 类型有哪些&#xff1f;请分别说明其连接逻辑和适用场景。2: UNION 和 UNION ALL 有什么区别&#xff1f;它们各自的适用场景是什么&#xff1f;3: 什么是视图 (View)&#xff1f;它的作用和优缺点是什么&#xff1f;4: 什么是索引 (Index)&#xff1…

PostgreSQL诊断系列(2/6):锁问题排查全攻略——揪出“阻塞元凶”

&#x1f517; 接上一篇《PostgreSQL全方位体检指南》&#xff0c;今天我们深入数据库的“神经系统”——锁机制&#xff0c;解决最令人头疼的“卡顿”问题。 你是否经历过&#xff1a; 某个SQL执行着就不动了&#xff1f;应用界面卡在“加载中”&#xff1f;UPDATE 语句迟迟不…

crc16是什么算法

核心概念​CRC16​ 是一种循环冗余校验算法&#xff0c;属于哈希函数的一种。它的核心目的是检测数据的错误&#xff0c;通常用于数字网络和存储设备中&#xff0c;来验证数据在传输或存储后是否依然完整、无误。你可以把它想象成一个数据的“指纹”或“摘要”。发送方计算出一…