一:枚举
枚举(Enumeration)是C语言中的一种用户自定义数据类型,用于定义一组相关的常量。枚举使我们可以为这些常量分配有意义的名字,从而提高代码的可读性和可维护性。
如果我们定义一个变量,用来表示一周中的一天,该变量的值只能从七个有效值中选择,枚举可以帮助我们限制变量的有效值。
(1)定义枚举
枚举使用 enum 关键字来定义,枚举名称和枚举元素名称都是标识符,定义一个枚举就是定义了一种枚举数据类型,语法如下:
enum 枚举名称
{
枚举元素1,
枚举元素2,
...
枚举元素N
};
枚举元素也称为枚举成员或枚举常量,具有如下特点:
- 枚举元素的值必须在同一枚举中是唯一的
- 枚举元素的值必须是整数类型,通常是int
- 如果没有为枚举元素指定值,编译器会自动为它们分配值,从0开始,依次递增。
- 定义枚举的时候也可以为枚举元素自定义值,需保证唯一性和整型类型。
(2)枚举变量
定义变量时指定类型是我们所定义的枚举类型,该变量称为枚举变量。枚举变量的值应该是枚举类型中的枚举常量中的一个,不能是其他值。
#include <stdio.h>
int main()
{
// enum 枚举名称
enum Week
{
// 枚举成员
MONDAY, // 周一
TUESDAY, // 周二
WEDNESDAY, // 周三
THURSDAY, // 周四
FRIDAY, // 周五
SATURDAY, // 周六
SUNDAY // 周日
} b; // 第二种:定义出类型以后紧随定义枚举类型变量
// 创建枚举类型的变量 第一种写法:先定义类型、在创建枚举变量
enum Week a = MONDAY;
// 枚举变量进行赋值
a = SUNDAY;
printf("枚举变量a=%d\n", a);
// 定义枚举变量写法第三种
// 定义枚举类型
enum
{
SPRING = 3,
SUMMER = 6,
AUTUMN = 9,
WINTER = 12
} siji;
siji = WINTER;
printf("%d\n", siji);
//第一种:定义枚举类型 在定义枚举变量
//第二种:定义枚举类型的同时,定义枚举类型的变量
//第三种:定义枚举类型的时候可以省略名字,同时定义枚举变量
return 0;
}
(3)枚举的应用
如果枚举常量的值是连续的,我们可以使用循环遍历;如果枚举常量的值不是连续的,则无法遍历。
#include <stdio.h>
int main()
{
// 定义枚举类型
enum Week
{
MONDAY = 1, // 周一
TUESDAY, // 周二
WEDNESDAY, // 周三
THURSDAY, // 周四
FRIDAY, // 周五
SATURDAY, // 周六
SUNDAY // 周日
};
// 循环遍历枚举
// 枚举在进行循环遍历的时候,成员的数值务必连续的,不是连续不能循环遍历!!!
for (enum Week day = MONDAY; day <= SUNDAY; day++)
{
printf("今天是周%d\n", day);
}
return 0;
}
枚举可以与switch结合使用,请看如下案例:
#include <stdio.h>
// 枚举
enum Season
{
SPRING = 1, // 春天1
SUMMER = 2, // 夏天2
AUTUMN = 3, // 秋天3
WINTER = 4 // 冬天4
};
int main()
{
// 定义一个枚举变量
enum Season season;
printf("请您输入一个数字[1-4]:");
scanf("%d", &season);
// 条件判断
switch (season)
{
case SPRING:
printf("春天比较好,因为春天不冷!!!");
break;
case SUMMER:
printf("夏天比较好,因为夏天不热!");
break;
case AUTUMN:
printf("秋天比较好,因为不冷不热!");
break;
case WINTER:
printf("冬天比较好,冬天能滑雪!!!!");
break;
default:
printf("对不起,不在地球服务区内!");
break;
}
return 0;
}
二:结构体
C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限。
C语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起,这种类型称为结构体(structure)类型。
C语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。
(1)声明结构体
格式如下:
struct 结构体名
{
数据类型1 成员名1;
数据类型2 成员名2;
……
数据类型n 成员名n;
};
声明结构体变量
#include <stdio.h>
// 需求:用结构体表示一个学生:编号、名字、性别(m|g)、手机号、分数、邮箱!!!!
int main()
{
// 先定义结构体(自定义一种数据的类型)
struct Stundets
{
// 数据类型 成员名字(标识符)
int id;
// 姓名
char name[50];
// 性别
char sex;
// 手机号
char phone[12];
// 分数
double score;
// 邮箱
char email[20];
} xiaohei;
// 创建 结构体类型 的变量
// 小明:自定义类型结构体 的变量(结构体变量)
// 第一种写法:先定义结构体类型、在声明变量
struct Stundets xiaoming;
// 第二种写法:在结构体的后面直接声明变量
// 第三种:狗结构体,省略结构体的名字
struct
{
// 狗狗年龄
int age;
// 狗狗的名字
char name[10];
// 狗狗性别
char sex;
} erha;
return 0;
}
注意:在C语言中,结构体(struct)和结构体变量是两个不同的概念。
结构体是一种自定义的数据类型,像一种模板,定义了数据的格式。
结构体变量是根据结构体类型创建的变量,代表了一个具体的对象,用于存储数据。
(2)成员的获取和赋值
成员是结构体的一个组成部分,一般是基本数据类型、也可以是数组、指针、结构体等。结构体的成员也可以称为属性。
结构体和数组类似,也是一组数据的集合,结构体使用点号 . 获取单个成员,可以进行赋值和取值。
结构体成员逐个赋值
#include <stdio.h>
int main()
{
// 学员的结构体
// 需求:用结构体表示一个学生:编号、名字、性别(m|g)、手机号、分数、邮箱!!!!
struct Students
{
// 结构体的成员id、结构体的属性ID
int id;
// 名字
char *name;
// 性别
char sex;
// 手机号
char *phone;
// 分数
double score;
// 邮箱
char *emial;
// 考试等级
char level[3];
};
// 创建结构体类型的变量
struct Students lhy;
// 成员进行赋值
lhy.id = 1;
lhy.name = "刘海洋";
lhy.sex = 'm';
lhy.phone = "17720125002";
lhy.score = 59.5;
lhy.emial = "110@163.com";
// 点语法获取成员的数值
printf("%s %lf %s\n", lhy.name, lhy.score, lhy.emial);
// 点语法操作成员
// 字符别写汉字
// lhy.level[0] = 'a';
// lhy.level[1] = 'b';
// printf("%s", lhy.level);
return 0;
}
使用大括号一次性对结构体所有成员赋值
#include <stdio.h>
int main()
{
// // 结构体Students
struct Students
{
// 编号
int id;
// 学生姓名
char *name;
// 学员的性别
char sex;
// 学员的手机号
char *phone;
// 学员的分数
double score;
// 学员的邮箱
char *emial;
char aaa[10];
} wangqun = {1, "王群", 'm', "18866390611", 59.9, "abc@qq.com", "aaaaa"};
// 访问成员的数据
printf("编号:%d,姓名:%s,性别:%c,手机号:%s,分数:%lf,邮箱:%s\n", wangqun.id, wangqun.name, wangqun.sex, wangqun.phone, wangqun.score, wangqun.emial);
return 0;
}
(3)结构体指针
结构体指针是指向结构体的指针变量,允许间接访问和操作结构体的成员,它提供了一种方便的方式来处理结构体数据。
声明结构体指针的语法格式:
struct 结构体名 *结构体指针变量名;
声明结构体指针
通常需要先定义一个结构体变量,再创建结构体指针,并取结构体变量的地址作为结构体指针的值。
#include <stdio.h>
int main()
{
// 结构体
struct Students
{
// 编号
int id;
// 名字
char *name;
// 性别
char sex;
// 手机号
char *phone;
// 邮箱
char *email;
};
// 第一步:结构变量
struct Students stu = {1, "程月", 'g', "110", "122@qq.com"};
// 结构体变量操作成员用的是点点点点.语法
printf("%s %d\n", stu.name, stu.id);
// 结构体指针:操作成员,用的是箭头(右边箭头)
struct Students *ptr = &stu;
// 访问成员的数据
ptr->name = "老哥";
ptr->id = 5;
// 指针操作结构体->
printf("%s %d\n", ptr->name, ptr->id);
(*ptr).id = 2;
(*ptr).name = "大海";
printf("%s %d\n", (*ptr).name, (*ptr).id);
return 0;
}
(4)案列
其一:
- 编写一个Dog结构体,包含name(char *)、age(int)、weight(double)属性。
- 编写一个say函数,返回字符串,方法返回信息中包含所有成员值。
- 在main函数中,创建Dog结构体变量,调用say函数,将调用结果打印输出。
#include <stdio.h>
struct Dog
{
// 成员
char *name;
int age;
double weight;
};
char *say(struct Dog dog1)
{
// 布局字符数据
static char info[100];
dog1.name = "泰迪";
sprintf(info, "狗名:%s,年龄:%d,体重:%lf", dog1.name, dog1.age, dog1.weight);
return info;
}
int main()
{
// 定义结构体
struct Dog dog = {"二哈", 18, 11.1};
// 这里提前定义指针,在代码执行到这里的时候,指针指向的地址还不确定
// 先指向NULL,别让str变为野指针
char *str = NULL;
str = say(dog);
printf("%s", str);
printf("dog===%s", dog.name);
return 0;
}
其二:
- 编程创建一个Box结构体,在其中定义三个成员表示一个长方体的长、宽和高,长宽高可以通过控制台输入。
- 定义一个函数获取长方体的体积(volume)。
- 创建一个结构体指针,打印给定尺寸的长方体的体积。
#include <stdio.h>
struct Box
{
// 长度
double length;
// 宽度
double width;
// 高度
double height;
};
// 计算体积
double Volume(struct Box *ptr)
{
ptr->height = 4;
return ptr->length * ptr->width * ptr->height;
}
int main()
{
// 结构体变量
struct Box box;
printf("请您输入长度:");
scanf("%lf", &box.length);
printf("请您输入宽度:");
scanf("%lf", &box.width);
printf("请您输入高度:");
scanf("%lf", &box.height);
double result = Volume(&box);
printf("体积为%lf\n", result);
//指针操作的还是box,因此15行代码影响到 box
printf("高度:%lf到底是多少?\n", box.height);
return 0;
}
其三:
景区门票案例
- 一个景区根据游人的年龄收取不同价格的门票。
- 请编写游人结构体(Visitor),根据年龄段决定能够购买的门票价格并输出。
- 规则:年龄>18,门票为20元,其它情况免费。
- 可以循环从控制台输入名字和年龄,打印门票收费情况,如果名字输入n,则退出程序。
#include <stdio.h>
#include <string.h>
/**
* (1)一个景区根据游人的年龄收取不同价格的门票。
(2)请编写游人结构体(Visitor),根据年龄段决定能够购买的门票价格并输出。
(3)规则:年龄>18,门票为20元,其它情况免费。
(4)可以循环从控制台输入名字和年龄,打印门票收费情况,如果名字输入n,则退出程序。
*
strcmp//比较字符串是否相等的函数
*
*/
// 定义游客的结构体
struct Visitor
{
// 成员
int age; // 游客的年龄
char name[20]; // 存储用户输入进来的名字
int pay; // 支付钱数
};
// 声明一个函数
void payMoney(struct Visitor *ptr)
{
// 结构体指针,操作成员用->
if (ptr->age > 18)
{
ptr->pay = 20;
}
else
{
ptr->pay = 0;
}
}
int main()
{
// 创建一个结构体变量
struct Visitor visitor;
// // 开启循环
while (1)
{
printf("请您输入您的大名:");
scanf("%s", visitor.name);
// 判断客户的名字是不是N,如果是N跳出循环语句
// 相等判断:最基本保证左右类型一致 visitor.name == 'n'
if (strcmp(visitor.name, "n") == 0)
{
break;
}
printf("请您输入您的芳龄:");
scanf("%d", &visitor.age);
// 用户输入年龄与名字
// 票件的判断单独封装到一个函数内部
payMoney(&visitor);
printf("亲爱的%s同志,您需要支付%d元\n", visitor.name, visitor.pay);
}
// printf("访问结束!\n");
// char str[] = "Hello";
// char str1[] = "Hello";
// 字符串,本质就是字符数组,字符数组比较内存地址
// printf("%d\n",str==str1);
// strcmp
// 相同为0 不同为-1
// printf("%d\n", strcmp(str, str1));
// char str[] = "Hello";
// char str1[] = "Hello";
// printf("%d\n", str == str1);
// C语言判断的时候当中字面量=>常量
// printf("%d\n", "Hello" == "Hello");
// int a;
// scanf("%d", &a);
// char * str = "哈哈哈";
return 0;
}
总结:
区分三个概念:结构体、结构体变量、结构体变量的成员。
结构体是自定义的数据类型,表示的是一种数据类型。结构体变量代表一个具体变量。
int num1; // int 是数据类型, 而num1是一个具体的int变量
struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量
Car就像一个“汽车图纸”,生成出来的具体的一辆辆汽车,就类似于一个个的结构体变量。这些结构体变量都含有相同的成员,将结构体变量的成员比作“零件”,同一张图纸生产出来的零件的作用都是一样的。
三:共用体
有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示学生的“成绩”,这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),有时是浮点数(80.5、60.5)。
C语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。它内部可以包含各种属性,但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,后面写入的属性会覆盖前面的属性。这样做的最大好处是节省空间。
“共用体”与“结构体”的定义形式相似,但它们的含义是不同的:
- 结构体变量所占内存长度,可以认为是各成员占的内存长度的叠加;每个成员分别占有其自己的内存单元。
- 共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。
(1)声明共用体
union 共用体类型名称
{
数据类型 成员名1;
数据类型 成员名2;
…
数据类型 成员名n;
};
union data
{
int m;
float x;
char c;
};
上例中,union命令定义了一个包含三个属性的数据类型data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性。
(2)声明共用体变量
#include <stdio.h>
int main()
{
// 共用体类型
// 第一种写法:先定义类型 在创建共用体变量
// 第二种写法:定义完类型以后,立马创建共用体变量
union data
{
// 成员:成员之间用分号隔开
char a;
int b;
double c;
} bj, hb;
// 创建共用体变量
union data cn, tw, sz;
printf("占用内存中字节数:%zu\n", sizeof(cn));
// 第三种:共用体名字省略....
union
{
int a;
short b;
unsigned int c;
} tj, wh;
return 0;
}
(3)成员的获取和赋值
#include <stdio.h>
int main()
{
// 定义共用体类型
union data
{
// 三个成员
char ch;
int a;
long c;
};
// 声明共用体变量
union data uni;
printf("字节数:%zu\n", sizeof(uni));
// 第一种写法:点语法操作成员
// 成员赋值
uni.ch = 'z';
printf("%c\n", uni.ch);
uni.a = 100;
printf("%d\n", uni.a);
uni.c = 97l;
printf("%ld\n", uni.c);
return 0;
}
#include <stdio.h>
int main()
{
// 共用体类型
union data
{
char a;
int b;
long c;
};
//{.成员1 = 'z',.成员2 = 100,.成员n='xxx'};
// union data jch = {.a = 'z', .b = 97, .c = 100L};
// printf("%ld\n", jch.c);
// 这种语法是给共用体变量的[首个成员] 进行初始化数据
// 不能用逗号隔开
union data china = {'z'};
printf("%c\n", china.a);
return 0;
}
(4)练习
现有一张关于学生信息和教师信息的表格。学生信息包括姓名、编号、性别、职业、 分数,教师的信息包括姓名、编号、性别、职业、教学科目:可以参考下面的表格。
请利用共用体,只使用一个结构体保存每个人的信息。
Name | Num | Sex | Profession | Score / Course |
孙二娘 | 501 | 女(f) | 学生(s) | 89.5 |
吴用 | 1011 | 男(m) | 老师(t) | math |
顾大嫂 | 109 | 女(f) | 老师(t) | English |
林冲 | 982 | 男(m) | 学生(s) | 95.0 |
#include <stdio.h>
#define TOTAL 2
// 定义结构体,将来结构体变量存储人员信息
struct People
{
// 成员姓名
char name[20];
// 编号
int num;
// 性别 女f 男m
char sex;
// 职业 学生s 老师 t
char profession;
// 老师 课程 学生 分数
union
{
double score; // 分数
char course[20]; // 课程
} st;
};
int main()
{
// 结构体类型 数组
struct People arr[TOTAL];
// 输入信息
for (int i = 0; i < TOTAL; i++)
{
printf("请您录入信息:");
// 输入信息
scanf("%s %d %c %c", arr[i].name, &(arr[i].num), &(arr[i].sex), &(arr[i].profession));
// s别加上双引号,字符!!!
// 学生
if (arr[i].profession == 's')
{
printf("亲爱的同学,请您输入您的分数:");
scanf("%lf", &(arr[i].st.score));
}
else
{
// 老师
printf("亲爱的老师,请您输入您教授的课程:");
scanf("%s", arr[i].st.course);
}
}
// 循环打印输出
for (int j = 0; j < TOTAL; j++)
{
// 判断
if (arr[j].profession == 's')
{
printf("姓名:%s,编号:%d,性别:%c,职业:%c,分数:%lf\n", arr[j].name, arr[j].num, arr[j].sex, arr[j].profession, arr[j].st.score);
}
else
{
printf("姓名:%s,编号:%d,性别:%c,职业:%c,课程:%s\n", arr[j].name, arr[j].num, arr[j].sex, arr[j].profession, arr[j].st.course);
}
}
return 0;
}
四:typedef
C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。
起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如,有一个结构体的名字是 student,定义一个结构体变量stu1。
struct student stu1;
struct 看起来就是多余的,但不写又会报错。如果为 struct student起了一个别名 Student,书写起来就简单了。
Student stu1;
这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。
(1)typedef 应用场景
用typedef声明数组类型、指针类型,结构体类型、共用体类型等,使得编程更加方便。
为某个基本类型起别名
typedef 命令用来为某个类型起别名
typedef 类型名 别名;
习惯上,常把用typedef声明的类型名的第1个字母用大写表示,以便与系统提供的标准类型标识符相区别。
#include <stdio.h>
// typedef:可以给类型起别名 [别整汉字]
typedef char Zifu;
typedef int Zhengshu; // 相当于给int类型起了一个"绰号"Zhengshu
typedef double Xiaoshu;
//一次起多个别名
typedef float Heizi, Gousheng, Erha;
int main()
{
Zhengshu a = 100;
Zifu ch = 'z';
Xiaoshu b = 3.14;
printf("%d %c %f\n", a, ch, b);
Gousheng p = 5.5f;
printf("%f",p);
return 0;
}
为结构体、共用体起别名
为 struct、union等命令定义的复杂数据结构创建别名,从而便于引用。
#include <stdio.h>
// 声明共用体,结构体类型的时候省略名字
typedef union
{
int a;
char b;
double c;
} Union_data;
// 结构体
typedef struct
{
char *name; // 姓名
int age; // 年龄
char sex; // 性别
} People;
int main()
{
// 没有起别名
// struct Students a = {"小明", 18, 'm'};
// 起别名
// People a = {"小明", 18, 'm'};
// 没有起别名
// union data a = {.a = 10};
Union_data a = {.c = 3.14};
return 0;
}
为指针起别名
typedef可以为指针起别名。
为数组类型起别名
typedef也可以用来为数组类型起别名。
#include <stdio.h>
// 指针类型进行起别名
// int *
typedef int *intPtr;
// 字符指针
typedef char *String;
// 数组起别名
typedef int Array[5];
int main()
{
// int a = 10;
// intPtr ptr = &a;
// printf("%d\n", *ptr);
// String str = "我爱塞北的大雪啊";
// printf("%s", str);
Array arr = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
(2)综合使用演示
#include <stdio.h>
// 给字符指针取别名
typedef char *String;
// 给结构体取别名
typedef struct
{
int id;
String name;
String profile;
} User;
int main()
{
// 声明一个结构体变量
User u;
// 初始化结构体变量的属性赋值
u.id = 101;
u.name = "张三";
u.profile = "一个纯粹的人,一个高尚的人,一个脱离了低级趣味的人。";
// 打印结构体变量的属性
printf("id:%d\n", u.id);
printf("name:%s\n", u.name);
printf("profile:%s\n", u.profile);
return 0;
}
本章内容到此结束,下一章为动态内存分配与预处理器
关注我,一起学习嵌入式