一、学习内容
结构体
#### 概念
1. 引入:定义整数赋值为10 int a=10; 定义小数赋值为3.14 float b=3.14; 定义5个整数并赋值 int arr\[5\] = {1 , 2 , 3 , 4 ,5}; 定义一个学生并赋值学号姓名成绩 定义一个雪糕并赋值名称产地单价 问题:没有学生、雪糕 数据类型 解决:结构体------存储多个 类型相同或不同的数据 的容器 区别:数组 和 结构体的区别是什么?
#### 定义结构体的语法
1. struct 结构体名{ 成员变量1; 成员变量2; 成员变量3; ...... };
#### 结构体变量的定义 初始化 赋值
1.
##### 结构体变量的定义
1.
###### 第一种
1. struct student{ int id; char name\[20\]; float score; }a; //学生变量a
2.
###### 第二种
1. struct student{ int id; char name\[20\]; float score; }; struct student a; //学生变量a
2.
##### 结构体变量的初始化和赋值
1.
###### 第一种
1. 定义并初始化 struct student{ int id; char name\[20\]; float score; }a={1001,"孙文雅",90.5};
2.
###### 第二种
1. 定义并初始化 struct student{ int id; char name\[20\]; float score; }; struct student a={1001,"孙文雅",90.5};
3.
###### 第三种
1. 先定义、再赋值 struct studen a; //错误赋值 a={1001,"孙文雅",90}; //数组 和 结构体都是构造类型,能再分解,不能整体进行赋值、输入、输出。 //错误赋值 id = 1001; //原因:因为id在复合语句{}中,只能在{}中识别,不能在{}外直接识别 a.id = 1001; strcpy(a.name , "孙文雅"); a.score = 90.5;
4.
###### 第四种
1. 先定义、再输入 struct student a; scanf("%d %s %f" , \&a.id , a.name , \&a.score);
#### 结构体数组的定义 初始化 赋值
1.
#### 定义类型
1. struct student{ int id; char name\[20\]; float score; };
2. /struct student a , b , c , d , e; struct student a\[3\]; 分别是a\[0\] a\[1\] a\[2\] struct student a\[3\] = {{1001,"HQYJ",90.5},{1001,"HQYJ",90.5},{1001,"HQYJ",90.5}}
#### typedef和结构体的结合使用
1. typedef struct student{ int id; char name\[20\]; float score; }Stu , \*Stu_Ptr , Stu_Arr\[3\]; Stu a = {1001,"HQYJ",90.5}; //定义学生变量a Stu_ptr p = \&a; //定义学生指针变量p,指向学生变量a Stu_Arr arr = {{1001,"HQYJ",90.5},{1001,"HQYJ",90.5},{1001,"HQYJ",90.5}} printf("%d %s %.2f\\n" , a.id , a.name , a.score); printf("%d %s %.2f\\n" , p-\>id , p-\>name , p-\>score); for(int i=0; i\<3; i++){ printf("%d %s %.2f\\n" , arr\[i\].id , arr\[i\].name, arr\[i\].score); }
##### 结构体的嵌套
1.
##### 结构体的嵌套
1. 定义一个人的信息:姓名,性别,车(品牌,单价) struct CAR{ char brand\[20\]; float price; }; //定义 车 数据类型 struct people{ char name\[20\]; char sex; struct CAR car; }; struct people a = {"张三" , 'M' , {"奔驰",19.5}}; printf("姓名:%s 性别%c 车的品牌:%s 车的价格:%.2f万元" , a.name, a.sex, a.car.brand, a.car.price)
2.
##### 结构体嵌套结构体数组
1. 定义一个人的信息:姓名,性别,2车(品牌,单价) struct CAR{ char brand\[20\]; float price; }; //定义 车 数据类型 struct people{ char name\[20\]; char sex; struct CAR car\[2\]; }; struct people a = {"张三" , 'M' , {{"奔驰",19.5},{"本田",23.75}}}; printf("姓名:%s 性别%c 车的品牌:%s 车的价格:%.2f万元" , a.name, a.sex, \\ a.car\[0\].brand, a.car\[0\].price, a.car\[1\].brand, a.car\[1\].price)
3.
##### 结构体内部写动态函数
1. 在c语言中,结构体中不可以有函数
2. 在结构体中实现一个功能,则需要借助于函数指针
3. 定义一个学生:姓名,年纪,分数,爱好(唱歌) void song(){ printf("苍茫的天涯是我的爱......\\n"); } int fun(){ return 10086; } struct student{ char name\[20\]; int age; float score; void (\*hobby)(); int (\*p)(); }; struct student a={"张三",18,90.5,song}; printf("姓名:%s" , a.name); printf("年龄:%d" , a.age); printf("分数:%.2f" , a.score); a.hobby(); printf("积分:%d" , a.p());
4.
##### 结构体字节对齐
1. 结构体各个成员的地址是连续的
2. 结构体变量的地址是第一个成员的地址
3. 64位操作系统,8字节对齐,32位操作系统,4字节对齐
4. 计算步骤
1. 先找最大步长【int/float 4字节 double/long/指针 8字节】
2. 按照最大步长分布内存
3. 在一个步长内就放进去
4. 若跨步长了,就按照最大步长再开空间
5. 最后计算总和即可
脑图
二、作业
求 sizeof(name1)?(晟安信息)
struct name1{
char str;
short x;
int num;
};
解析:
char str:占 1 字节,起始地址是 0。后面需要对齐到 2 字节(short x 的对齐要求),因此会插入 1 个填充字节。short x:起始地址是 2,占 2 字节。int num:按 4 字节对齐,x 结束的地址是 4,因此不需要额外的填充。num 占用 4 字节。str + 填充字节(1 字节) + x(2 字节)+ num(4 字节) = 8 字节。
解答:
8
(电工时代)
typedef struct _a
{
char c1;
long i;
char c2;
double f;
}a;
typedef struct _b
{
char c1;
char c2;
long i;
double f;
}b;
sizeof(a) = _______;
sizeof(b) = _______;
解析:
sizeof(a):
char c1:占 1 字节,起始地址为 0。long i:需要按 8 字节对齐,因此从地址 8 开始,占用 8 字节。char c2:占 1 字节,从 c1 开始的地址为 16。由于 double f 需要 8 字节对齐,所以在 c2 后面将有 7 个填充字节。double f:占 8 字节,从地址 24 开始。
总大小:1 byte (c1) + 7 bytes (填充) + 8 bytes (long i) + 1 byte (c2) + 7 bytes (填充) + 8 bytes (double f) = 32 字节。
sizeof(b):
char c1 和 char c2:各占 1 字节,共 2 字节。为了满足 long i 的 8 字节对齐需求,后面会有 6 个填充字节。
long i:占 8 字节,从地址 8 开始。
double f:占 8 字节,从地址 16 开始。
总大小:1 byte (c1) + 1 byte (c2) + 6 bytes (填充) + 8 bytes (long i) + 8 bytes (double f) = 24 字节。
解答:
32 24
给了一个结构体,求 sizeof(struct A) = ________。 (鲁科安全,软通动力)
struct A{
char a;
char b;
char c;
short d;
int e;
short f;
}
解析:
char a
:占 1 字节,起始地址为 0。
char b
:占 1 字节,起始地址为 1。
char c
:占 1 字节,起始地址为 2。
short d
:占 2 字节,short
类型通常需要 2 字节对齐,所以地址 3 需要对齐到 4,插入 1 个填充字节。d
从地址 4 开始,占用 2 字节。
int e
:占 4 字节,int
需要 4 字节对齐。从 d
结束后的地址是 6,需要对齐到 8,插入 2 个填充字节。e
从地址 8 开始,占用 4 字节。
short f
:占 2 字节,short
需要 2 字节对齐。e
结束的地址是 12,因此 f
可以直接从地址 12 开始,占用 2 字节。
由于结构体整体对齐规则通常以最大成员对齐(这里 int
是 4 字节对齐),因此为了满足对齐要求,总大小要是 4 字节的倍数。f
结束时的地址是 14,因此需要插入 2 个填充字节,使结构体大小为 16 字节
解答:
16
有一个如下的结构体,请问在64位编译器下用 sizeof(struct A) 计算出的大小是多少?( ) (鲁科安全)
struct A{
long a1;
short a2;
int a3;
int *a4;
}
A. 24 B. 28 C. 16 D. 18
解析:
long a1:占 8 字节,从地址 0 开始。short a2:占 2 字节,从地址 8 开始。short 类型通常要求 2 字节对齐,但是后面的 int a3 需要 4 字节对齐。填充字节:由于 int a3 需要 4 字节对齐,a2 结束后地址是 10,因此需要 2 个填充字节,使得 a3 从地址 12 开始。int a3:占 4 字节,从地址 12 开始。int *a4:占 8 字节,指针类型在 64 位系统中是 8 字节,要求 8 字节对齐。由于 a3 结束后的地址是 16,符合 8 字节对齐的要求,因此 a4 从地址 16 开始,占 8 字节。
总大小:long a1(8 字节) + short a2(2 字节) + 2 个填充字节 + int a3(4 字节)+ int *a4(8 字节)= 24 字节。
解答:
A
有以下说明语句,则下列错误的引用 是( )。(山大华天)
struct stu
{
int no;
char name[21];
};
stu w, *p = &w;
A. w.no B*p.no C. p->no D. (*p).no
解析:
A. w.no
w 是结构体变量,可以直接使用点运算符 . 访问结构体成员 no。这是正确的引用方式。
B. *p.no
这是错误的语法,*p 是指针解引用,表示指向 w 的结构体,但是这里缺少括号。p.no 会被理解为 p 是结构体,试图访问其成员 no,这是不正确的,因为 p 是指针,而不是结构体。因此这个表达式应该写成 (*p).no。
C. p->no
p 是指向结构体的指针,箭头运算符 -> 用于通过指针访问结构体的成员。这是正确的引用方式。
D. (*p).no
通过 *p 解引用指针 p,访问结构体 w,再使用点运算符 . 来访问成员 no。这是正确的引用方式。
解答:
B
写出下述程序结果: (中维世纪)
typedef struct test
{
char x1;
short x2;
float x3;
char x4;
}TEST_T;
printf("%d", sizeof(TEST_T));
解析:
char x1
:占 1 字节,起始地址是 0。short x2
:short
类型通常需要 2 字节对齐,因此在 x1
后面插入 1 个填充字节,使得 x2
从地址 2 开始,占 2 字节。float x3
:float
类型需要 4 字节对齐,x2
结束后的地址是 4,符合 float
的对齐要求,因此 x3
从地址 4 开始,占 4 字节。char x4
:占 1 字节,x3
结束的地址是 8。由于 char
不需要对齐,x4
可以直接从地址 8 开始,占 1 字节。
总大小:为了满足结构体整体对齐,编译器通常会以最大的成员(这里是 float,即 4 字节)的对齐方式对齐整个结构体。因此,结构体的大小必须是 4 的倍数。x4 结束时的地址是 9,需要插入 3 个填充字节 以保证整体大小是 4 的倍数。
解答:
12
下面的代码输出是什么,为什么?(64位环境) (信雅达)
struct {
char *a1;
long a2;
short a3;
}A;
int main(void)
{
printf("%d", sizeof(A));
}
解析:
char *a1:指针类型在 64 位系统中占用 8 字节,起始地址是 0。long a2:long 类型在 64 位系统中占 8 字节,需要 8 字节对齐,所以 a2 可以从地址 8 开始,占 8 字节。short a3:short 类型占 2 字节,需要 2 字节对齐,a2 结束后的地址是 16,a3 可以从地址 16 开始,占用 2 字节。
填充字节:为了满足结构体整体对齐的要求,编译器通常以结构体中 最大对齐要求的成员 进行对齐。在这个例子中,最大的成员是 char *a1 和 long a2,它们的对齐要求是 8 字节。因此,为了保持结构体整体对齐,a3 后面需要插入 6 个填充字节,使得结构体的总大小是 8 字节的倍数。
总大小:8 bytes (a1) + 8 bytes (a2) + 2 bytes (a3) + 6 bytes (填充) = 24 字节
解答:
24
设有如下结构定义: struct student { int num; char name[20]; char sex; int age; char addr[30];} stud; 若用printf("%s\n", .....)访问该结构中name值的正确方法是 ( ) (杭州快越科技)
A. stud -> name B. &stud.name
C. stud.&name D. stud.name
解析:
A. stud -> name
-> 运算符用于指针访问结构体成员,stud 是结构体变量,不是指针。因此,这个选项是错误的。
B. &stud.name
&stud.name 获取的是 name 数组的地址,虽然它和 stud.name 表示的内容类似,但 &stud.name 实际上是指向整个数组的地址,不适合直接用作字符串指针。这个选项虽然编译器可能接受,但语义不太合适。
C. stud.&name
这是语法错误,不能在结构体变量名后直接使用 &name,因此这个选项是错误的。
D. stud.name
这是正确的方式。stud.name 是字符数组名,在需要时会自动转换为指向数组第一个元素的指针,因此可以正确用于 printf("%s", stud.name) 语句。
解答:
D
则sizeof(cs)的值是( ) (苏州特点电子科技)
struct
{
short a; char b; float c;
}cs;
A.4 B.5 C.7 D.8
解析:
short a:占 2 字节,起始地址为 0。short 类型需要 2 字节对齐,因此无需填充字节。char b:占 1 字节,从地址 2 开始。由于 float c 需要 4 字节对齐,因此需要插入 1 个填充字节,使得接下来的 float c 从 4 字节对齐的地址开始。float c:占 4 字节,从地址 4 开始,符合 4 字节对齐要求。总大小:2 bytes (a) + 1 byte (b) + 1 byte (填充) + 4 bytes (c) = 8 字节。
结论:
sizeof(cs)
的值是 8 字节。解答:
D
如下函数的输出结果是:【 】
struct node
{
char a; short b; char c; int d;
};
struct node s = { 3, 5, 6, 99 };
struct node *pt = &s;
printf("%X\n", *(int*)pt);
解析:
char a:1 字节short b:2 字节,起始地址在 a 后面,所以实际地址为 2填充字节:由于 int d 需要 4 字节对齐,在 short b 后面需要 1 个填充字节,使 d 从地址 4 开始char c:1 字节,实际地址为 4int d:4 字节,从地址 8 开始
假设结构体在内存中的布局如下(假设按小端字节序):
a = 0x03 (1 byte)
b = 0x05 (2 bytes)
c = 0x06 (1 byte)
d = 0x63 (4 bytes)
解引用指针
pt 是指向 struct node 的指针。(int*)pt 将指针转换为指向 int 的指针,实际上指向的是 d 成员,因为 d 是第一个 int 类型成员。*(int*)pt 读取 d 的值,即 99。
结果
d 的值是 99,按十六进制表示是 0x63。
解答:
63
编程题:定义学生结构体,存储学生的学号、姓名、分数,定义长度为5的结构体数组,实现:
①输入学生信息
②输出学生信息
③计算学生的成绩总分、平均分
④按照学生的分数进行排序
⑤输出排序后的数组
代码解答 :
cpp
#include <stdio.h>
#include <string.h>
// 定义学生结构体
typedef struct {
int id; // 学号
char name[50]; // 姓名
float score; // 分数
} Student;
// 函数声明
void inputStudents(Student students[], int count);
void printStudents(const Student students[], int count);
float calculateTotalScore(const Student students[], int count);
float calculateAverageScore(const Student students[], int count);
void sortStudentsByScore(Student students[], int count);
int main() {
const int numStudents = 5; // 学生数量
Student students[numStudents];
// 输入学生信息
printf("输入学生信息:\n");
inputStudents(students, numStudents);
// 输出学生信息
printf("\n学生信息:\n");
printStudents(students, numStudents);
// 计算并输出成绩总分和平均分
float totalScore = calculateTotalScore(students, numStudents);
float averageScore = calculateAverageScore(students, numStudents);
printf("\n成绩总分: %.2f\n", totalScore);
printf("平均分: %.2f\n", averageScore);
// 按分数排序
sortStudentsByScore(students, numStudents);
// 输出排序后的学生信息
printf("\n按分数排序后的学生信息:\n");
printStudents(students, numStudents);
return 0;
}
// 输入学生信息
void inputStudents(Student students[], int count) {
for (int i = 0; i < count; i++) {
printf("输入第 %d 位学生的信息:\n", i + 1);
printf("学号: ");
scanf("%d", &students[i].id);
printf("姓名: ");
scanf("%s", students[i].name);
printf("分数: ");
scanf("%f", &students[i].score);
}
}
// 输出学生信息
void printStudents(const Student students[], int count) {
for (int i = 0; i < count; i++) {
printf("学号: %d, 姓名: %s, 分数: %.2f\n", students[i].id, students[i].name, students[i].score);
}
}
// 计算总分
float calculateTotalScore(const Student students[], int count) {
float total = 0;
for (int i = 0; i < count; i++) {
total += students[i].score;
}
return total;
}
// 计算平均分
float calculateAverageScore(const Student students[], int count) {
float total = calculateTotalScore(students, count);
return total / count;
}
// 按分数排序(升序)
void sortStudentsByScore(Student students[], int count) {
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (students[i].score > students[j].score) {
// 交换
Student temp = students[i];
students[i] = students[j];
students[j] = temp;
}
}
}
}
三、总结
1. 学习内容概述
结构体定义与使用
了解了如何定义结构体,并通过实际例子学习了如何在结构体中使用各种数据类型。
学习了结构体变量的初始化方法,以及如何通过多种方式定义结构体数组。
学习了如何通过指针访问结构体成员,使用 `typedef` 简化结构体的声明和操作。
嵌套结构体
结构体内可以包含其他结构体,学习了如何定义一个嵌套结构体并初始化其成员变量。
结构体与函数结合
学习了如何将结构体与函数结合,通过函数传递结构体并返回结果。包括将结构体作为函数参数和返回值。
位字段的使用
位字段允许在结构体中节省内存空间,通过指定位数来定义变量的存储空间。
2. 学习难点
结构体指针的使用
结构体指针的使用较为复杂,尤其是在处理嵌套结构体时。理解如何通过指针访问和修改结构体成员是本次学习的难点。
结构体数组的初始化与访问
在结构体数组中,初始化每一个结构体成员以及使用指针遍历整个结构体数组需要较多练习,特别是对于较为复杂的结构体。
嵌套结构体的访问
嵌套结构体的成员访问更为复杂,需要通过多层次的点运算符和指针运算符。
3. 主要事项
结构体的内存对齐
结构体成员在内存中是按一定的对齐方式排列的,不同的机器和编译器可能会有不同的内存对齐策略,导致存储空间的浪费或性能下降。
结构体与动态内存分配
结构体中的数组成员如果长度不确定,可以通过动态内存分配来解决。在使用动态内存时,需要确保合理分配和释放内存。
结构体的拷贝与传递
结构体传递给函数时通常是按值传递的,可能会造成大量的内存拷贝,因此在函数中传递结构体指针更为高效。
位字段的限制
位字段可以节省空间,但由于其实现依赖于硬件和编译器,不同平台可能存在兼容性问题。
4. 未来学习的重点
深入理解结构体与内存的关系
未来应更加深入研究结构体的内存对齐方式,以及如何在不同平台下优化结构体的内存占用。
结构体与复杂数据结构结合
进一步学习如何使用结构体构建链表、树、图等复杂数据结构,并理解这些数据结构中的内存管理问题。
结构体与文件输入输出
研究如何将结构体与文件操作结合起来,例如将结构体数据写入文件或从文件读取结构体数据,确保文件读写的高效性和数据一致性。
提高结构体的性能优化
理解结构体优化对程序性能的影响,特别是在大型程序中如何通过优化结构体来减少内存占用和提高访问效率。