结构体的意义
问题:学籍管理需要每个学生的下列数据:学号、姓名、性别、年龄、分数,请用 C 语言程序存储并处理一组学生的学籍。
单个学生学籍的数据结构:
- 学号(num): int 型
- 姓名(name) :char [ ] 型
- 性别(sex):char 型
- 年龄(age):int 型
- 分数(score):float 型
思考:如果有多个学生,该怎么定义,已学数据类型无法解决(已学的数据类型需要定义好多变量,不友好)。
概述
- 正式:
结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据被称为结构型数据的 “成员” ,其描述了一块内存区间的大小及解释意义。 - 通俗:
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
在C语言中,定义结构体的语法格式如下:
struct 结构体名 {
类型 成员1;
类型 成员2;
// ...
};
其中,结构体名
是您自定义的结构体类型名称,可以根据需求进行命名。成员1
、成员2
等表示结构体的成员变量,每个成员都有自己的类型和名称。
定义结构体后,可以使用该结构体类型创建结构体变量,并访问结构体的成员。访问结构体成员的语法是使用结构体变量名后跟成员名,中间使用点.
进行连接。
结构体的使用:
-
struct 结构体名 变量名
-
struct 结构体名 变量名 = {成员1值,成员2值…}
-
定义结构体时顺便创建变量(这时候创建几个变量都可以,中间用逗号隔开,直接在创建的时候赋值也可以,例如:)
struct student { int num; //学号 char name[16]; //姓名 float score; //成绩 }stu5 = {1002,"lihua",89},stu6;
-
如果只想给一部分数据赋值的话:
struct 结构体名 变量名 = { .name = "cuiyi", .num = 111, };
下面是一个更完整的示例:
#include <stdio.h>
// 定义一个结构体
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 创建一个结构体变量
struct Person person1;
// 访问结构体的成员
strcpy(person1.name, "John");
person1.age = 25;
person1.height = 1.75;
// 输出结构体的成员
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.2f\n", person1.height);
return 0;
}
在上述示例中,我们定义了一个名为Person
的结构体,它包含了姓名、年龄和身高三个成员变量。然后,我们创建了一个名为person1
的结构体变量,并给它的成员赋值。最后,使用printf
函数输出结构体的成员值。
结构体数组
- 作用:将自定义的结构体放入数组中方便维护
- 语法:
struct 结构体名 数组名[元素个数] = {{}, {}, …{}}
示例:
#include<stdio.h>
struct stu
{
char name[16];
int age;
float score;
}s[3];
int main()
{
struct stu s[3] = {{"zhangsan",18,500},{"lisi",18,530},{"wangwu",18,550}};
int i;
for (i = 0; i < 3; i++)
{
printf("name=%s, age=%d, score=%f\n",s[i].name,s[i].age,s[i].score);
}
return 0;
}
结构体指针
- 作用:通过指针访问结构体的成员
- 语法:
struct 结构体名 *指针名;
- 利用操作符->可以通过结构体指针访问结构体属性(比如s.name 有一个指针 ps 指向 s ,那么可以用 ps->name 代替 s.name)
示例:
结构体数组和结构体指针
#include<stdio.h>
struct stu
{
char name[16];
int age;
float score;
}s[3];
int main()
{
struct stu s[3] = {{"zhangsan",18,500},{"lisi",18,530},{"wangwu",18,550}};
struct stu *ps = s;//定义一个指针指向结构体数组
int i;
for (i = 0; i < 3; i++)
{
printf("name=%s, age=%d, score=%f\n",(*(ps+i)).name,(*(ps+i)).age,(*(ps+i)).score);
}
return 0;
}
在上面的代码中,一定要记得 (*(ps+i)) 才是一个大括号里面的值,这样才能 .name .age .score 。
结构体嵌套结构体
- 含义
结构体中的成员可以是另一个结构体 - 语法
struct 结构体名
{
struct 结构体名 成员名;
};
示例:
#include <stdio.h>
#include <string.h>
struct person
{
char name[16];
int age;
char sex;
};
struct student
{
struct person stu;
float score;
};
struct teacher
{
struct person tea;
char phone[12];
};
int main(int argc, const char *argv[])
{
struct student s;
strcpy (s.stu.name,"zhangsan");
s.stu.age = 12;
s.stu.sex = 'm';
s.score = 98;
printf("name = %s,age = %d, sex = %c, score = %f\n", s.stu.name, s.stu.age, s.stu.sex, s.score);
struct teacher t;
struct teacher *p = &t;
strcpy (p->tea.name, "lisi");
p->tea.age = 54; //注意这里操作符 -> 的用法
p->tea.sex = 'w';
strcpy (p->phone, "13112341234");
printf("name = %s,age = %d, sex = %c, score = %s\n", t.tea.name, t.tea.age, t.tea.sex, t.phone);
return 0;
}
上述代码中,有一个部分用到了 p->tea.age 这种形式,这是因为p是指针,而 tea 只是一个普通变量,所以从 tea 出发不能用 ->,只有指针才可以使用这个操作符。
结构体大小
字节对齐
- 含义
字节对齐主要是针对结构体而言的,通常编译器会自动对其成员变量进行对齐,以提高数据存取的效率。(因为如果按照类型实际的大小来判断的话,那么需要判断很多次,这样对齐了以后有规律就不用判断了) - 作用
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
计算方法
- 自身对齐 (这个数据类型大小是多少就是多少)
- 默认对齐 (4字节)
- 有效对齐 (在自身对齐和默认对齐之间选最小)
规则:(地址 / 有效地址) 必须是整数。
计算过程:
- 把结构体里每个变量的类型的自身对齐,默认对齐和有效对齐分别写出来;
- 以有效对齐为准写每个变量的地址,最开始那个变量的地址肯定是0,然后后面叠加,注意在这个过程中要遵从**<规则>**,比如图中的变量 b ,本来地址应该是 1 ,但是因为 1 / 4 不是整数,所以要扩充到 4 凑整,那么这个时候 变量 a 的地址浪费了 1 2 3 这三个地址,又因为 b 本身就是 4 个字节,所以它的地址是 4 5 6 7。 c 和 d 因为都可以整除有效对齐,所以每个都加 1 个字节就行;
- 最终看一下,有效对齐最大的是 4 ,所以每个都要以 4字节 对齐,则要在变量 d 的后面再补 2 个地址:10 和 11 (因为前面的 8 和 9 已经占了 2 个地址了,还差 2 个地址凑够 4 个地址)。
- 得出结果:结构体 A 的地址是 0~11 ,所以大小是 12 。
上述过程要注意:能不能整除只能决定每个变量开头的地址,具体要每一行的地址从开头的地址要写到几要看变量类型的 sizeof 是多少。比如有 double c,c的开头地址是 8 ,那么这个变量占的字节就是 8 9 10 11 12 13 14 15 这八个字节。