前言
从0开始记录我的学习历程,我会尽我所能,写出最最大白话的文章,希望能够帮到你,谢谢。
1.结构体类型的概念及定义
1.1、概念:
1.2、 结构体类型的定义方法
咱们在使用结构体之前必须先有类型,然后用类型定义数据结构,这个类型相当于一个模具
(1).先定义结构体类型,再去定义结构体变量
struct 结构体类型名{
成员列表(允许我们组合多个基本数据类型(如整数、浮点数、字符等))
};
例1:
struct stu{
int num;
char name[20];
char sex;
};
我们看这个例子。
我们定义了一个名为stu
的结构体类型。这个结构体类型包含了三个成员:一个整数num
,一个字符数组name
,和一个字符sex
。这些成员代表了学生的学号、姓名和性别。
struct stu lucy, bob, lilei;
一旦我们定义了这个结构体类型,就可以创建变量来存储实际的学生信息了。在这个例子中,我们创建了三个变量:lucy
、bob
和lilei
。每个变量都是一个结构体类型的实例,它们都有三个成员:num
、name
和sex
。
在定义结构体类型的时候顺便定义结构体变量,以后还可以定义结构体变量struct 结构体类型名 {成员列表 ;} 结构体变量 1, 变量 2;struct 结构体类型名 变量 3 ,变量 4 ;
例如看下面这个例子,实际上是和上面的例子是一个意思的。
struct stu{
int num;
char name[20];
char sex;
}lucy;
struct stu bob, lilei;
在定义结构体类型的时候,没有结构体类型名,顺便定义结构体变量,
因为没有类型名,所以以后不能再定义相关类型的数据了。也就是不可以往成员列表里面添加东西了。struct {成员列表 ;} 变量 1 ,变量 2;
2 结构体变量的定义初始化及使用
1、结构体变量的定义和初始化
结构体变量,是个变量,这个变量是若干个数据的集合
注:(1): 在定义结构体变量之前首先得有结构体类型,然后在定义变量(2): 在定义结构体变量的时候,可以顺便给结构体变量赋初值,被称为结构体的初始化(3): 结构体变量初始化的时候,各个成员顺序初始化
struct stu{
int num;
char name[20];
char sex;
};
struct stu boy;
struct stu lucy={101,"lucy",'f'};
我们创建了两个变量来存储实际的学生信息。第一个变量boy它
是一个未初始化的结构体变量,它没有任何成员的值被赋定。第二个变量lucy
是一个已经初始化的结构体变量。在声明时,我们直接给它赋初值。这里,我们给了学号、姓名和性别的值。
在这个初始化中,我们按照成员的顺序给了值:
101
是num
成员的值。"lucy"
是name
成员的值。由于name
是一个字符数组,我们使用引号括起来来表示字符串。'f'
是sex
成员的值。
1、结构体变量的使用
定义了结构体变量后,要使用变量
(1). 结构体变量成员的引用方法结构体变量. 成员名
struct stu{
int num;
char name[20];
int age;
}bob;
int main(int argc, char *argv[])
{
lihua={10,"bob",10};
printf("%d\n",bob.num);
printf("%s\n",bob.name);
printf("%d\n",bob.age);
return 0;
}
可以看到 通过.
操作符,我们可以访问结构体变量的各个成员。上面的代码分别打印出了bob
的学号、姓名和年龄。
2、结构体成员多级引用
首先,我们有一个名为date
的结构体,它包含了三个整数字段:year
、month
和day
。这些字段代表了一个特定的日期。
struct date{
int year;
int month;
int day;
};
接下来,我们有一个名为stu
的结构体,它包含了四个字段:一个整数num
、一个字符数组name
、一个字符sex
和一个子结构体birthday
。子结构体birthday
是我们之前定义的date
结构体。
struct stu{
int num;
char name[20];
char sex;
struct date birthday;
};
现在,我们在主函数中创建了一个名为lilei
的变量,它是一个stu
类型的变量。我们通过赋值来初始化这个变量的字段。
struct stu lilei={101,"lilei",'m'};
lilei.birthday.year=1986;
lilei.birthday.month=1;
lilei.birthday.day=8;
在这里,我们通过.birthday.year
, .birthday.month
, 和 .birthday.day
来访问和设置子结构体中的日期字段。
最后,我们使用printf
函数来打印出变量
printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);
printf("%d %d %d\n",lilei.birthday.year,lilei.birthday.month,lilei.birthday.day);
这里,我们通过多级结构体成员的引用来访问和打印出各个字段的值。可以看到就是通过点来访问的 嘎嘎简单哈。
3.结构体数组
结构体数组是一种数据结构,它允许我们存储多个相同类型的结构体变量。这是非常有用的,因为它可以帮助我们组织和管理大量的相关数据。
让我们来看看一个例子,以便更好地理解结构体数组:
首先,我们有一个名为stu
的结构体,它包含了三个字段:一个整数num
、一个字符数组name
和一个字符sex
。
struct stu{
int num;
char name[20];
char sex;
};
接下来,我们定义了一个名为edu
的结构体数组,它包含了三个元素:edu[0]
、edu[1]
和edu[2]
。
struct stu edu[3];
这个数组 edu
可以被视为三个 stu
类型的结构体变量的集合。我们可以通过数组的下标来访问每个元素,并对它们进行操作。
例如,我们可以将值 101
给 edu[0]
数组中的第一个结构体变量的 num
字段赋值:
edu[0].num = 101;
这里,.num
是指 edu[0]
结构体变量中的 num
字段。
同样,我们可以使用 strcpy
函数将字符串 “lucy” 给 edu[1]
数组中的第一个结构体变量的 name
字段赋值:
strcpy(edu[1].name, "lucy");
这里,.name
是指 edu[1]
结构体变量中的 name
字段。
4.结构体指针
结构体指针是一种在C语言中用于存储和操作结构体变量的特殊类型的指针。它允许我们通过指针来访问和修改结构体变量的字段,而无需直接使用变量的名称。
让我们来看看一个例子,以便更好地理解结构体指针:
首先,我们有一个名为stu
的结构体,它包含了两个字段:一个整数num
和一个字符数组name
。
struct stu{
int num;
char name[20];
};
接下来,我们定义了一个名为p
的结构体指针变量,它可以存储 stu
类型的结构体变量的地址。
struct stu * p;
这个指针变量 p
可以被视为一个存储结构体变量地址的“指针”,它占用了相同数量的内存空间,用于保存这个地址。
现在,我们创建了一个名为boy
的结构体变量,并使用 &
指针运算符将其地址赋值给指针变量 p
。
struct stu boy;
p = &boy;
这里,&boy
是 boy
结构体变量的地址,我们将其赋值给指针变量 p
。
接下来,我们可以通过以下方式访问和修改 boy
结构体变量的字段:
-
直接访问:
boy.num = 101; // 直接访问并修改 boy 的 num 字段
-
通过指针访问:
(*p).num = 101; // 使用指针来访问并修改 boy 的 num 字段
-
使用指针运算符:
p->num = 101; // 使用指针运算符来访问并修改 boy 的 num 字段
在这三种方法中,最后两种都是通过指针来访问和修改结构体变量的字段。前提是指针必须先指向一个结构体变量。如果指针没有正确地指向一个结构体变量,那么尝试访问或修改其字段可能会导致程序出错。
结构体指针经常用到的地方:
(1):保存结构体变量的地址
typedef struct stu{
int num;
char name[20];
float score;
}STU;
int main()
{
STU *p,lucy;
p=&lucy;
p->num=101;
strcpy(p->name,"baby");
//p->name="baby";//错误,因为 p->name 相当于 lucy.name 是个字符数组的名字,是个常量
}
(2):传结构体变量的地址
#include<stdio.h>
#include<string.h>
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p->num=101;
(*p).score=87.6;
strcpy(p->name,"lucy");
}
int main()
{
STU girl;
fun(&girl);
printf("%d %s %f\n",girl.num,girl.name,girl.score);
return 0;
}
(3): 传结构体数组的地址
#include<stdio.h>
#include<string.h>
typedef struct stu{
int num;
char name[20];
float score;
}STU;
void fun(STU *p)
{
p[1].num=101;
(*(p+1)).score=88.6;
}
int main()
{
STU edu[3];
fun(edu);
printf("%d %f\n",edu[1].num,edu[1].score);
return 0;
}
注意: 结构体变量的地址编号和结构体第一个成员的地址编号相同,但指针的类型不同
#include <stdio.h>
struct stu{
int num;
char name[20];
int score;
};
int main(int argc, char *argv[])
{
struct stu bob;
printf("%p\n",&bob);
printf("%p\n",&(bob.num));
return 0;
}
注意:结构体数组的地址就是结构体数组中第 0 个元素的地址.
#include <stdio.h>
struct stu{
int num;
char name[20];
int score;
};
int main(int argc, char *argv[])
{
struct stu edu[3];
printf("%p\n",edu);//struct stu *
printf("%p\n",&(edu[0]));//struct stu *
printf("%p\n",&(edu[0].num));//int *
return 0;
}
原本不想把这个代码放上来的 但是我觉得虽然废话多 但是如果光看话的话感觉总是有点离谱。
5、结构体内存分配
内存分配与对齐规则
分配单位
- 定义:结构体中最大成员变量的长度。
- 例如,如果结构体中最大成员是
int
(占4字节),则分配单位为4字节;如果最大成员是double
(占8字节),则分配单位为8字节。
- 例如,如果结构体中最大成员是
成员变量偏移
- 定义:成员变量的起始地址必须是其自身长度的整数倍。
- 例如,一个
int
类型变量(4字节)必须放在一个地址是4的倍数的位置上。 - 一个
short
类型变量(2字节)必须放在一个地址是2的倍数的位置上。
- 例如,一个
结构体总大小
- 定义:结构体总大小必须是分配单位的整数倍。
- 如果最终计算出的结构体大小不是分配单位的整数倍,需要填充(padding)到分配单位的整数倍。
内存分配与对齐示例
假设我们有以下结构体:
struct Example {
char a; // 1字节,偏移0
short b; // 2字节,偏移2(1 + 1对齐到2)
int c; // 4字节,偏移4
double d; // 8字节,偏移8
};
1. 确定分配单位
char
:1字节short
:2字节int
:4字节double
:8字节
最大成员变量是double
,占8字节,因此分配单位是8字节。
2. 计算每个成员变量的偏移量
按照结构体中成员变量的顺序,并根据它们自身长度的整数倍来计算偏移:
- char a:
- 起始地址:0(分配结构体的起始地址)
- 长度:1字节
- 偏移量:0(0是1的倍数)
- short b:
- 上一个成员
a
结束地址:1 - 为满足2字节对齐,b的起始地址:2
- 长度:2字节
- 偏移量:2(2是2的倍数)
- 上一个成员
- int c:
- 上一个成员
b
结束地址:4 - 为满足4字节对齐,c的起始地址:4
- 长度:4字节
- 偏移量:4(4是4的倍数)
- 上一个成员
- double d:
- 上一个成员
c
结束地址:8 - 为满足8字节对齐,d的起始地址:8
- 长度:8字节
- 偏移量:8(8是8的倍数)
- 上一个成员
3. 计算结构体总大小
- 最后一个成员
d
结束地址:16(8 + 8) - 结构体总大小必须是分配单位(8字节)的整数倍。
最终大小为16字节(已经是8的整数倍,无需额外填充)。
成员 | 类型 | 偏移量 |
---|---|---|
a | char | 0 |
b | short | 2 |
c | int | 4 |
d | double | 8 |
结构体总大小为16字节,满足对齐规则。
就是这样存的。