基础知识 - 结构体
1、结构体类型与结构体变量
1.1 结构体的定义
结构体是一种自定义的数据类型,它把多个不同类型的变量封装在一起,形成一个新的复合数据类型。可以定义该结构体类型的变量,与使用 int 定义变量的方法相同
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
如:标量,指针,数组,甚至是其他结构体
1.2 结构体的创建
使用 struct 关键字创建结构体
例如,在描述一个学生信息时,我们可能需要姓名(字符串)、年龄(整数)、成绩(浮点数)等不同类型的数据,这时就可以定义一个结构体来整合这些信息:
struct Student
{char name[50]; //姓名int age; //年龄float grade; //成绩
}; //分号不能丢
1.3 结构体变量声明与初始化
定义结构体后,就可以声明该类型的变量。有多种方式可以声明结构体变量,并且可以在声明时进行初始化:
C 和 C++ 中结构体的区别:
C 环境下:struct + 标号名 才表示结构体类型(可以使用 typedef 起别名)
#include<stdio.h>//typedef - 起别名 //格式:typedef 类型名 别名typedef struct Student //定义一个学生结构体,并给结构体起别名为Stu {int num;char sex; }Stu;int main() {typedef int i; //给int起别名为ii m = 7;Stu a; //通过别名定义结构体类型变量return 0; }
C++ 环境下:结构体名就可以表示结构体的类型
#include<iostream> using namespace std;struct Student //定义一个学生结构体 {int num;char sex; };int main() {Student a; //通过结构体名定义结构体类型的变量return 0; }
本文代码使用 C 语言格式
// 单个结构体变量并初始化
struct Student student1 = {"Alice", 20, 85.5};// 结构体数组并初始化
struct Student class[3] =
{{"Bob", 21, 78.0},{"Charlie", 20, 90.0},{"David", 22, 88.5}
};
1.4 结构体的成员访问
- 结构体变量,可以使用点运算符(.)来访问其成员
例如,要输出student1的年龄,可以这样写:
printf("Student age: %d\n", student1.age);
- 如果结构体变量是指针类型,则需要使用箭头运算符(->)来访问成员
例如:我们有一个指向Student结构体的指针ptrStudent,要输出ptrStudent的名字,可以这样写:
struct Student *ptrStudent = &student1;
printf("Student name: %f\n", ptrStudent->name);
2、结构体字节对齐
2.1 对齐规则
- 找成员当中最大的类型来作为对齐数,因此结果一定是它的整数倍
- 要按照成员变量定义的顺序进行,不能自由组合分配空间
- 按照整数倍地址对齐
2.2 带有位域的对齐规则
位域:
- 成员:之后的数字
- 表示的是所占 bit 的大小
- 在内存要求苛刻的情况下可以使用位域。在不同系统(不同编译器也不同)不同效果(不要在可移植代码中使用)
- 分析相邻的两个成员是否是同种类型,如果是同种类型,可以考虑放置在同一个单位下
- 如果相邻的成员超出一个单位,那么就放两个单位里面,放置的时候不允许跨单位存储
2.3 带有指针的对齐规则
只与当前运行环境有关(默认 x86 运行环境)
- x86 - 32bit - 4字节
- x64 - 64bit - 8字节
typedef struct s1
{char a; //1int b; //4short c; //2
}s1;typedef struct s2
{char a : 1; //1 1bitint b : 5; //4 5bitchar* c; //4 (默认x86运行环境)short d; //2
}s2;typedef struct s3
{s1* a; //4 (默认x86运行环境)s2 b; //4 16 (s2的对齐数为4字节,s2的总字节数为16)char c; //1long d; //4
}s3;int main()
{printf("%d,%d,%d", sizeof(s1), sizeof(s2), sizeof(s3));//输出: 12 16 28return 0;
}
2.4 常用数据类型的字节数
数据类型 | 字节数 |
int | 4 |
char | 1 |
long | 8 |
short | 2 |
float | 4 |
double | 8 |
习题
1、求 sizeof(s) ( )
struct s
{
int x: 3;
int y: 4;
int z: 5;
double a;
}
A: 16
B: 32
C: 20
D: 24
解析:2*8=16
成员当中最大的类型为 double,所以以 8 字节作为对齐数。前3个是同种类型且没有超出一个单位,可以考虑放置在同一个单位下,故 2 * 8
2、在 32 位 cpu 上选择缺省对齐的情况下,有如下结构体定义则 sizeof(struct A) 的值为 ( )
struct A {
unsigned a : 19;
unsigned b : 11;
unsigned c : 4;
unsigned d : 29;
char index;
A: 9
B: 12
C: 16
D: 20
解析:4*4=16
成员当中最大的类型为 int,所以以 4 字节作为对齐数。前 4 个都是同种类型,可以考虑放置在同一个单位下,由于相邻成员超出一个单位就要放两个单位里面,故前 2 个放在一个单位里,第 3 个放在一个单位里,第 4 个放在一个单位里,故 4 * 4
3、结构体的自引用
3.1 结构体中直接包含同类型结构体变量不可行
struct Node
{int data;struct Node next;
};
这种定义是错误的。原因在于,如果一个结构体里包含同类型的结构体变量,会陷入无限嵌套的情况。假设要计算 sizeof(struct Node),为了确定 struct Node 的大小,就得先知道 next 成员的大小,而 next 又是 struct Node 类型,它里面又有 next 成员,如此循环往复,结构体大小就会无穷大,这显然是不合理的,编译器也无法为其分配确定大小的内存空间
3.2 结构体中包含同类型结构体指针是可行的
struct Node
{int data;struct Node* next;
};
这是正确的结构体自引用方式。因为指针在内存中占用固定大小的空间(在 32 位系统中通常是 4 字节,在 64 位系统中通常是 8 字节),不管 struct Node 具体是什么样子,struct Node* 类型的指针只是用来存储另一个 struct Node 结构体实例的地址,所以不会出现无限嵌套导致大小无法确定的问题
在这种情况下,计算 sizeof(struct Node) 就很明确了。假设 int 类型占 4 字节,指针在 32 位系统占 4 字节,那么 sizeof(struct Node) 的结果就是 4 + 4 = 8 字节;在 64 位系统中,如果指针占 8 字节,sizeof(struct Node) 就是 4 + 8 = 12 字节 (不考虑字节对齐的情况下)
3.3 关于 typedef 对匿名结构体类型重命名时的错误
typedef struct
{int data;Node* next;
}Node;
这里是错误的。typedef 的作用是给前面的匿名结构体类型取一个新名字 Node,但在匿名结构体内部却提前使用了 Node 这个还未定义完成的类型来创建 next 成员变量。在编译器处理到 Node* next; 这一行时,Node 还没有被定义,所以编译器无法识别 Node 类型,从而导致编译错误
3.4 正确的 typedef 重命名方式
typedef struct Node
{int data;struct Node* next;
}Node;
这种方式是正确的。首先,定义了一个名为 struct Node 的结构体,在结构体内部使用 struct Node* 来引用自身类型的指针,这是合法的。然后,使用 typedef 把 struct Node 类型重命名为 Node,这样之后就可以直接使用 Node 来声明变量了,例如:Node node1;
综上所述,结构体自引用时要使用指针,在使用 typedef 重命名结构体类型时要注意类型定义和使用的先后顺序,避免出现编译错误。