C语言自定义类型
构造类型
数据类型
- 基本类型/基础类型
- 整数类型
- 短整型:short/short int(2字节)
- 整型:int(4字节)
- 长整型:long/long int(8字节(64位系统))
- 长长整型:long long /long long int(16字节)
- 浮点型
- 单精度:float(4字节)
- 双精度:double(8字节)
- 长双精度:long double(16字节(64位系统))
- 字符型:char(1字节)
- 整数类型
- 指针类型
- 数据类型*:
*int、char*、float*
(8字节) - void*:任意数据类型的指针(万能指针)(8字节)
- 数据类型*:
- 空类型
- void:没有返回值/没有形参(不能定义变量)
- 自定义类型/构造类型
- 结构体类型:struct
- 共用体/联合体类型:union
- 枚举类型:enum
结构体
结构体的定义
- 定义:自定义数据类型的一种,关键字struct,结构体类型的变量可以存储多个不同数据类型的数据。
- 定义格式:
struct 结构体名
{
数据类型1 成员名称1; //结构体中的变量叫做成员
数据类型2 成员名称2;
....
}
注意:结构体中定义的变量,我们称之为成员变量
- 格式说明
- 结构体名,合法的标识符,建议单词的首字母大写(所谓的结构体名,就是自定义类型的类型名称)
- 数据类型n:C语言支持的所有类型(包括函数,函数在这里用函数指针表示)
- 成员的名称:合法的标识符,就是变量的命名标准
- 数据类型n成员名称n:类似于定义变量,定义了结构体中的成员
标识符:
说明:变量名、数组名、函数名、常量名、结构体名、共用体名、枚举名等都是标识符
命名规则:
- 只能包含字母、数字、下划线
- 不能以数字开头
-
注意:
-
结构体在定义时,成员不能赋值
举例:
struct cat { int age = 5; //错误,定义时不能赋值 double hight; //正确 void (*run)(void);//正确 }
-
-
常见的定义格式
- 方式一:常规定义(命名结构体,只定义类型)推荐
struct Student { int num; char name[20]; char sex; int age; char address[100]; void (*info)(void); }
- 方式二:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog { char *name; int age; struct //结构体中嵌套的结构体不能有名字,故被称作匿名结构体 { int year; int month; int day; } }
注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为名一个结构体的成员
总结:
- 结构体可以定义在局部位置,也可以定义在全局位置(用的比较多)
- 全局位置的结构体名和局部位置的结构体名可以相同,就近原则
-
结构体类型的使用:
利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。
结构体变量的定义
-
三种形式定义结构体变量
结构体变量也成为结构体的实例。
-
第一种
- 先定义结构体(自定义数据类型)
- 然后使用
struct 结构体名 变量名 (实例); //先定义结构体(自定义数据类型) struct A { int a; char b; } //后定义结构体变量(使用自定义数据类型) struct A x; struct A y;
-
第二种
- 在定义结构体的同时,定义结构体变量
struct 结构体名 { 数据类型1 数据成员1; ... }变量列表; struct A { int a; char b; }x,y; struct A z;
-
第三种(不推荐)
- 在定义匿名结构体的同时,定义结构体变量
struct { int a; char b; }x,y; struct { int a; char b; }z;
此时定义了一个没有名字的结构体(匿名结构体);x,y是这个结构体类型的变量。
-
-
匿名结构体:弊大于利
- 优点:少些一个结构体名称
- 缺点:只能使用一次,定义结构体类型的同时必须定义变量。
- 应用场景:
- 当结构体的类型只需要使用一次,并且定义类型的同时定义变量
- 作为其他结构体的成员
-
定义结构的同时,定义结构体变量初始化
struct Cat
{
int age;
}cat;
- 结构体成员部分初始化时,大括号{}不能省略
- 结构体成员,没有默认值,是不确定的数
结构体变量的使用
-
结构体变量访问结构体成员
- 格式:
结构体变量名.成员名;
可以通过访问给这个成员赋值(存数据)
可以通过访问获取成员的值(取数据)
- 结构体变量未初始化,结构体的成员值随机(不确定)
-
结构体变量在定义是,可以初始化
- 建议用大括号表明数据的范围
- 结构体成员初始化,可以部分初始化,部分初始化时一定带大括号标明数据的范围
-
案例:
#include <stdio.h>
/*
全局结构体(数据类型)
*/
struct Dog
{
char *name; // 姓名
int age; // 年龄
char sex; // M:公,W:母
void (*eat)(void); // 吃饭
};
void eat()
{
printf("狗在吃狗粮\n");
}
/*
先定义在初始化
*/
void fun1()
{
// 定义结构体变量
struct Dog dog;
// 给结构体变量赋值,其实就是给成员赋值
dog.name = "旺财";
dog.age = 5;
dog.eat = eat;
// 访问结构体变量,其实就是访问其成员
printf("%s%d%c\n", dog.name, dog.age, dog.sex);
//访问函数
dog.eat();
}
void fun2()
{
// 定义结构体变量并初始化
struct Dog dog = {"招财", 23, 'M'};
// 修改成员的值
dog.name = "金宝";
printf("%s%d%c\n", dog.name, dog.age, dog.sex);
}
int main()
{
fun1();
fun2();
return 0;
}
结构体数组的定义
-
什么时候需要结构体数组
需要管理一个学生对象,只需要定义一个struct Student A;
若管理多个学生对象,此时需要一个结构体数组struct Student student[29];
-
四种形式定义结构体数组
-
先定义结构体类型,然后定义结构体变量,最后将变量储存到结构体数组
//定义一个学生类型的结构体 struct Student { char* name; int age; floar scores[3]; } //定义结构体对象 struct Student zhangsan = {"张三",21,{80,85,72}}; struct Student zhangsan = {"李四",21,{82,75,92}}; //定义结构体数组 struct Student student[3] = {zhangsan,lisi};
-
定义结构体类型,然后定义结构体数组并初始化
//定义一个学生类型的结构体 struct Student { int id; char* name; int age; float scores[3]; } //定义结构体数组并初始化 struct Student students[3] = { {1,"张三",21,{89,85,68}}, {2,"李四",20,{85,76,90}} };
-
定义结构体类型的同时定义结构体并完成初始化
//定义一个学生类型的结构体 struct Student { int id; char* name; int age; float scores[3]; } students[3] = { {1,"张三",21,{89,85,68}}, {2,"李四",20,{85,76,90}} };
-
定义结构体类型的同时定义结构体数组,然后通过索引给结构体成员赋值
//定义一个学生类型的结构体 struct Student { int id; char* name; int age; float scores[3]; } stus[3]; //赋值 stus[0].id = 1; stus[0].name = "张三"; stus[0].age = 19; stus[0].scores[0] = 89;
小贴士
结构体数组名访问结构体成员:
格式:结构体数组名->成员名
#include <stdio.h> void print_(char *str) { printf("%s", str); } void fun3() { // 定义学生类型的结构体 struct Student { int id; char *name; int age; float scores[3]; void (*print_)(char *) }; struct Student stu1 = {1, "张三", 21, {89, 20, 65}}; struct Student stu2 = {2, "李四", 21, {89, 20, 65}}; stu1.print_ = print_; stu2.print_ = print_; struct Student stus[] = {stu1, stu2}; int len = sizeof(stus) / sizeof(stus[0]); for (int i = 0; i < len; i++) { // printf("%-3d", stus->id); printf("%d,%s,%d,%.2f\n", stus[i].id, stus[i].name, stus[i].age, stus[i].scores[i]); stus->print_(stus->name); } } int main() { fun3(); return 0; } //指针写法 #include <stdio.h> void print_(char *str) { printf("%s", str); } void fun3() { // 定义学生类型的结构体 struct Student { int id; char *name; int age; float scores[3]; void (*print_)(char *); }; struct Student stu1 = {1, "张三", 21, {89, 20, 65}}; struct Student stu2 = {2, "李四", 21, {89, 20, 65}}; stu1.print_ = print_; stu2.print_ = print_; struct Student stus[] = {stu1, stu2}; int len = sizeof(stus) / sizeof(stus[0]); struct Student *p = stus; for (; p < stus + len; p++) { printf("%d,%s,%d", p->id, p->name, p->age); float *q = p->scores; int len_ = sizeof(p->scores) / sizeof(p->scores[0]); for (; q < p->scores + len_; q++) { printf(" %-6.2f", *q); } printf("\n"); } int main() { fun3(); return 0; }
-
构造体类型
构造体数组
案例
需求:对候选人得票的统计程序。设有3个候选人,每次输入一个的票的候选人名字,要求最后输出各人的票的结果
#include <stdio.h>
#include <string.h>
/*
需求:对候选人得票的统计程序。
设有3个候选人,每次输入一个的票的候选人名字,
要求最后输出各人的票的结果
定义一个候选人的构造体(对象)
*/
struct Person
{
char name[20];
int temp;
};
/*定义候选人数组*/
struct Person persons[3] = {
{"张三", 0},
{"李四", 0},
{"王五", 0}};
int main()
{
char leader_name[20];
// 使用循坏完成十次投票
for (int i = 0; i < 10; i++)
{
printf("请输入您要投票的候选人姓名:\n");
scanf("%s", leader_name);
// 给被投票的候选人加一票
for (int j = 0; j < 3; j++)
{
// 判断两个字符串结构是否相同
if (strcmp(leader_name, persons[j].name) == 0)
{
persons[j].temp++;
}
}
}
printf("投票的结果:\n");
// 下标法
// for (int i = 0; i < 3; i++)
// {
// printf("%s:%d\n", persons[i].name, persons->temp);
// }
struct Person *p = persons;
for (; p < persons + 3; p++)
{
printf("%s:%d\n", p->name, p->temp);
}
return 0;
}
构造体指针
-
定义:结构体类型的指针变量指向结构体变量或数组的起始地址
-
语法:
struct 结构体名 *指针变量列表; struct Dog { char name[20]; int age; }; struct Dog dog = {"富贵",5}; struct Dog *p = &dog;
构造体成员访问
-
结构体成员访问
-
结构体数组名访问结构体成员
- 格式:结构体数组名->成员名;
- 举例
for (; p < persons + 3; p++) { printf("%s:%d\n", persons->name, persons->temp); }
-
结构体成员访问符
- ==.==左侧是结构体变量(结构体对象/结构体实例),也可以叫做结构体对象访问成员符,右侧是结构体成员
- ==->==左侧是指针,也叫做结构体指针访问成员符,右侧是结构体成员
-
访问结构体成员的两种类型,三种方式
-
通过结构体对象访问成员
struct Stu { int age; char name[20]; }stu; stu.name;
-
通过结构体指针访问成员
-
指针引用访问成员
struct Stu { int age; char name[20]; }stu; struct Stu *p = &stu; p->name;
-
指针解引用间接访问成员
struct Stu { int age; char name[20]; }stu; struct Stu *p = &stu; (*p)->name;
-
-
-
结构体数组中元素的访问
struct Stu { int id; char name[20]; float scores[3]; } stu[3] = { {1,"张三",{75,85,95}}, {2,"李四",{85,84,95}}, {3,"王五",{88,97,77}} } printf("%s,%.2f",stu[1].name,stus[1].scores[1]);李四84 printf("%s,%.2f",stu->name,stus->scores[1]);张三85 printf("%s,%.2f",(stu+2)->name,(stus+2)->scores[2]);王五77 printf("%s,%.2f",(*(stu+2)).name,(*(stus+2)).scores[2]);王五77
小贴士
-
结构体类型的使用案例
结构体可以作为函数的返回类型、形式参数
-
构造体类型大小
-
规则:字节对齐(默认,数据在内存中存储在其类型大小的整数倍)
- 首先保证结构体中的成员储存在自身的对齐边界(类型大小的整数倍)
- 在满足条件1下,最终大小要满足最大成员所占存储单元的整数倍
-
为什么要字节对齐
节省内存,提高访问效率
-
在GNU标准中,可以在定义结构体时,指定对齐规则:
__attribute__((packed));//结构体所占内存大小时所哟成员所占内存大小之和 __attribute__((aligned(n)));//设置结构体占n个字节,若n比默认值小,n不起作用;n必须是2的次方
案例
#include <stdio.h> int main() { struct Cat { int id; char *name; char sex __attribute((aligned(2)));//设置结构体占n个字节 } __attribute__((packed));//结构体所占内存大小时所哟成员所占内存大小之和 printf("%ld\n", sizeof(struct Cat)); // 默认字节对齐24,使用packed后13,设置大小后14 return 0; }
-
柔性数组:
struct St { ... char arr[0]; }
柔性数组不占有结构体的大小
案例
#include <stdio.h> int main() { struct Cat { int id; char *name; char arr[0];//柔性数组不占用结构体大小 char sex __attribute((aligned(2)));//设置结构体占n个字节 } __attribute__((packed)); printf("%ld\n", sizeof(struct Cat)); // 默认字节对齐24 return 0; }
共用体/联合体类型
-
定义:使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,其首地址相同。
-
语法
union 共用体名称 { 数据类型 变量名; .... };
-
共用体的定义和结构体类似
-
可以有名字,也可以匿名
-
共用体在定义时也可以定义共用体变量
-
共用体在定义时也可以初始化成员
-
共用体也可以作为形参和返回值类型使用
-
共用体也可以定义共用体变量
…
结构体的语法,共用体都支持
-
-
注意:
- 共用体弊大于利,尽量少用,一般少用;
- 共用体变量在某一时刻只能存一个数据,也只能取一个数
- 共用体和结构体都是自定义数据类型,用法类似于基本数据类型
- 共用体可以使共用体的成员,也可以是结构体的成员
- 结构体可以是结构体的成员,也可以是共用体的成员