结构体

结构体类型的声明

结构体是一种用户自定义的数据类型,用于存储不同类型的数据,可以包含多个不同的成员变量。

1
2
3
4
5
struct 结构体名 {
类型 成员变量1;
类型 成员变量2;
//...
};

注意:
1、{ }内部是结构体的成员变量
2、结构体结尾时,最后的分号不能丢

特殊声明

匿名结构体声明

可以在定义一个结构体类型的同时,直接定义变量,可以不给结构体起名字

使用场景:一次性使用的结构体类型

1
2
3
4
5
6
7
8
9
10
struct
{
int age;
char name[20];
} person;
struct
{
int age;
char name[20];
} person[20];

结构体成员也是结构体类型

结构体可以作为另一个结构体的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
struct Date
{
int year;
int month;
int day;
};
struct Person
{
char name[20];
struct Date birthday;
};

int main()
{
struct Person p[3] = {
{"Ethaniel", 2000, 10, 1},{"李四", 2001,5,1},{"王五",{2023, 1, 1}}
};
printf("%s, %d-%d-%d\n", p[1].name, p[1].birthday.year, p[1].birthday.month, p[1].birthday.day);

printf("%lu\n", sizeof(struct Person));//32
printf("%lu\n", sizeof(p[0]));//32

return 0;
}

结构体变量的定义、初始化和成员访问

结构体的变量是普通变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <string.h>
//结构体的声明
struct Student
{
char s_name[20];//字符数组存放字符串
char s_id[20];
int score[3];
};
int main()
{
//定义结构体变量
struct Student s1;//没有对s1进行初始化
//结构体变量s1的成员可以直接使用,运算符访问
//赋值操作:
strcpy(s1.s_id,"s001");//s1.s_id[0]='s'; s1.s_id[1]='0';s1.s_id[2]='0';s1.s_id[3]='7';s1.s_id[4]='\0';
strcpy(s1.s_name,"Ethaniel");
s1.score[0] = 90;
s1.score[1] = 95;
s1.score[2] = 60;

//访问(输出)
printf("姓名:%s\n", s1.s_name);
printf("学号:%s\n", s1.s_id);
printf("语文成绩:%d,数学成绩:%d,英语成绩:%d\n", s1.score[0],s1.score[1],s1.score[2]);

struct Student s2 = {"李四","s002", 99, 88, 77};//初始化了s2
//访问(输出)
printf("姓名:%s\n", s2.s_name);
printf("学号:%s\n", s2.s_id);
printf("语文成绩:%d,数学成绩:%d,英语成绩:%d\n", s2.score[0],s2.score[1],s2.score[2]);

strcpy(s2.s_name, "王五");
printf("姓名:%s\n", s2.s_name);
return 0;
}

结构体变量是指针变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <string.h>

//结构体的声明
struct Student
{
char name[20];
int age;
};
int main()
{
//定义结构体变量
struct Student s;
//定义结构体指针并赋值
struct Student *ptr = &s;
//赋值
//ptr->name="张三";
//->结构体中指针赋值
strcpy(ptr->name,"王五");
ptr->age=18;
(*ptr).age = 19;
printf("姓名:%s\n", ptr->name);
// printf("年龄:%d\n", ptr->age);
printf("年龄:%d\n", (*ptr).age);

strcpy(ptr->name,"如花");
(*ptr).age = 23;

return 0;
}

注意事项:
1、使用正确的运算符:普通变量用点运算符(.),指针变量用箭头运算符(->)来访问结构体的成员2、正确的成员名:结构体成员的名称是区分大小写的,需要确保大小写正确。

其他初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<string.h>
struct Person
{
char name[20];
int age;
};
int main()
{
//逐个成员赋值
struct Person p1 = {};
strcpy(p1.name, "Tom");
p1.age = 20;
printf("姓名:%s,年龄:%d\n",p1.name,p1.age);

struct Person p2;
strcpy(p2.name, "Jerry");
p2.age = 25;
printf("姓名:%s,年龄:%d\n",p2.name,p2.age);

//整体赋值
struct Person p3=p2;
printf("姓名:%s,年龄:%d\n",p3.name,p3.age);

//初始化赋值
struct Person p4={"lisi",18};
printf("姓名:%s,年龄:%d\n",p4.name,p4.age);
struct Person p5={
.name ="wangwu",
.age = 19
};
printf("姓名:%s,年龄:%d\n",p5.name,p5.age);
return 0;
}

结构体传参

值传递

将整个结构体变量的值复制一份,传递给函数。在函数内部对结构体的修改不会影响原始的结构体类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
struct Point
{
int x;
int y;
};
void printPoint(struct Point p)
{
p.x=30;
p.y=50;
printf("函数中:x: %d, y: %d\n", p.x, p.y);
}
int main()
{
struct Point p = {3, 5};
printPoint(p);
printf("main函数中:x: %d, y: %d\n", p.x, p.y);
return 0;
}

注意事项:
1、优点:保护原始数据不被修改。
2、缺点:若结构体大,可能消耗大量内存。

指针传递

将结构体的指针传递给函数,通过指针可以直接修改原始的结构体变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
struct Point
{
int x;
int y;
};
void modifyPoint(struct Point* p)
{
p->x += 10;
p->y += 10;
printf("函数中:x: %d, y: %d\n", p->x, p->y);
}
int main()
{
struct Point p = {3, 5};
modifyPoint(&p);
printf("main函数中:x: %d, y: %d\n", p.x, p.y);
return 0;
}

注意事项:
1、优点:内存使用高效,大型结构体传递快速。
2、缺点:可能导致原始数据不安全,因为可以通过指针直接修改结构体内的数据。

typedef用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
#include<string.h>
//一、typedef(typedef 原数据类型 数据新类型的别名)
// typedef int INT; 起个别名
// INT main()
// {
// INT data = 777;
// printf("%d\n", data);
// return 0;
// }

//结构体定义变量也可以起别名
typedef struct st_student//st_student结构体名称
{
char name[20];
int age;
}Stu;//Stu结构体别名

int main()
{
Stu student = {};//直接写别名Stu了
strcpy(student.name,"ww");
student.age=16;
printf("姓名:%s\n", student.name);

return 0;
}

简单链表

结构体的自引用就是指在结构体内部,包含指向自身类型结构体的指针。

链表是由一个个节点通过指针域链接而成的表,所以,我们只需要定义一个节点,就可以定义整个表

1
2
3
4
5
typedef struct Node
{
int data;
struct Node* next;
}*linklist;

首元结点:链表中有有效(非头节点)的第一个节点称为:”首元结点”

头节点:”首元结点”前额外增设的节点,特点:数据域内一般不放数据,也可以放链表长度等信息。

头指针:指向链表的第一个节点的指针;作用,他用来识别链表的起始位置,并通过它可以遍历整个链表

空表:头指针或者头节点的指针域为null的链表

C11_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>//动态内存
// 定义一个结构体
struct Node {
int data;//数据域
struct Node* next;//指针域
};
int main()
{
//创建结构体节点
// struct Node node1, node2, node3;

// 初始化节点数据
// node1.data = 10;
// node2.data = 20;
// node3.data = 30;

// 构建节点之间的关系
// node1.next = &node2;
// node2.next = &node3;
// node3.next = NULL;

// 遍历并打印节点数据
// struct Node* p = &node1;//p为头指针
// while (NULL != p)
//{
// printf("%d\n", p->data);
// p = p->next;
//}


// //在链表中添加一个新节点
// struct Node node4 = {40, NULL};
// node3.next = &node4;


// //遍历并打印节点数据
// p = &node1;
// while(NULL!= p)
// {
// printf("%d\n", p->data);
// p = p->next;
// }

//创建一个有100个节点的带有头指针的单链表:头插法
struct Node *head = NULL;
for (int i = 0; i<100; i++)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));//
newNode->data = i;
newNode->next = head;
head = newNode;
}
struct Node* p = head;
while(NULL!= p)
{
printf("%d ", p->data);//99 98 97 .... 3 2 1 0
p = p -> next;
}
printf("\n");
return 0;
}

两种不合法案例

1
2
3
4
5
6
//案例1
struct Node
{
int data;
struct Node next;
};
1
2
3
4
5
6
//案例2
typedef struct
{
int data;
Node* next;
}Node;

注意事项:结构体自引用,成员定义只能是指针。

结构体内存对齐

是指编译器在分配结构体变量的内存空间时,按照一定的规则将结构体成员按照一定的字节对齐方式进行排列。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
struct Test {
int a;
char b;
int c;
};
int main()
{
struct Test test = {1,'a',3};
printf("%lu\n", sizeof(test));//12
return 0;
}

注:以下都是在32位操作系统上讨论

寄存器只能从整除以4的地址开始读取数据

内存对齐的目的:提高内存访问效率和处理器的数据访问速度

注意:
结构体的内存对齐是由编译器根据一定的规则来进行的,可以提高内存访问的效率和速度。对于特定的应用场景,可以根据需要进行适当的对齐优化。

枚举

枚举是一种用户自定义的数据类型,用于定义一组具体的常量。枚举常量被称为枚举值,它们可以是整数、字符或字符串类型。

枚举类型的定义及使用

枚举定义:

enum 枚举类型名
{
枚举值1,
枚举值2,

};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
//声明一个枚举
enum Weekday
{
MONDAY,
TUESDAY,
};
int main()
{
enum Weekday today = TUESDAY;
printf("%d\n", today);//2
switch(today)
{
case MONDAY:
printf("今天是星期一\n");
break;
case TUESDAY:
printf("今天是星期二\n");
break;
default:
printf("无效的枚举值\n");
}
return 0;
}

枚举的使用场景

场景 说明 案例
状态/类型 当程序中有一组固定的状态或类型需要使用时 enum season
{
spring,
Summer,
};
错误码的表示 枚举来表示不同的错误码,提高代码的可读性和可维护性 enum ErrorCode
{
SUCCESS,
NOT_FOUND,
};
选项的表示 当某个变量有一组固定的选项时 enum Gender
{
MAN,
UNMAN,
};
常量集合的定义 当程序需要定义一组常量时 enum Color
{
MAN,
UNMAN,
};

注意:
1、枚举类型变量的值其实就是整数值
2、枚举类型的变量赋值尽可能使用枚举变量,而不是整数值

共用体(联合)

共用体(union)是一种特殊的数据类型,它允许不同的数据类型共享同一块内存空间。只能同时存储其中一个成员的值。

共用体类型的定义

定义格式(声明):

union 联合名称
{
类型 成员变量1;
类型 成员变量2;
//…
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
union MyUnion
{
int a;
char b;
int c;
};
int main()
{
union MyUnion u;
u.a = 1;
u.b = 'a';
u.c = 3;
printf("%lu\n",sizeof(u));//4
//联合体只有一个内存空间;
//内存空间的大小为最大成员变量空间,但要注意内存对齐,可能要调整这个大小;
return 0;
}

共用体的特点

由于共用体的成员共享同一块内存空间,因此对一个成员的赋值会影响到其他成员的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
union MyUnion 
{
int num;
char c;
};
int main()
{
union MyUnion u = {};
u.num = 97;
printf("%d\n", u.num);
printf("%c\n", u.c);

u.c = 'A';
printf("%d\n", u.num);
printf("%c\n", u.c);
return 0;
}

注意事项:
联合体的使用需要谨慎,因为它对数据的存储和访问方式较为复杂,容易引发错误。