C语言 结构体
创始人
2024-03-07 02:27:23
0

C语言 结构体

  • 一、结构体的声明和初始化
    • 1. 结构体声明
    • 2. 结构体初始化
  • 二、typedef 重定义结构体
  • 三、结构体成员的类型
  • 四、结构体成员的访问
  • 五、结构体传参
  • 六、结构体的自引用
  • 七、结构体的内存对齐
    • 对齐规则
    • 程序清单1
    • 程序清单2
    • 程序清单3
    • 程序清单4
    • 修改默认对齐数

一、结构体的声明和初始化

1. 结构体声明

程序清单:

#include struct Student {char name[20];		// 姓名int age;			// 年龄int studentID;		// 学号
}student1, student2;int main() {struct Student student3;return 0;
}

2. 结构体初始化

程序清单:

#include struct Student {char name[20];			// 姓名int age;				// 年龄int studentID;			// 学号
}student1 = {"Rose", 19, 06}, student2={"小红", 17, 03};int main() {struct Student student3 = {"Jack", 18, 02};return 0;
}

注意事项:

上面是结构体声明和初始化的标准格式,随着 Student 类型被创建出来,那么后面就可以根据其来创建相应的结构体变量:student1,student2,student3.

student1,student2 为全局变量,student3 为局部变量。

二、typedef 重定义结构体

#include typedef struct Student {char name[20];			// 姓名int age;				// 年龄int studentID;			// 学号
}Student;int main() {Student student = { "Rose", 17, 03 };// struct Student student = ...return 0;
}

我们平时写类型的时候,都需要在自定义类型前加上 struct,才能够表示一个完整的结构体类型。而经 typedef 关键字重定义后, " struct Student " 和 " Student " 就是等价的了,这可以让我们后续更加简单地创建 Student 变量了。这里只需要注意写法即可。

三、结构体成员的类型

结构的成员可以是标量、数组、指针,甚至是其他结构体。如下所示:

程序清单:

#include struct Grade {int chinese;				// 语文成绩int math;					// 数学成绩int english;				// 英语成绩
};struct Student {char name[20];				// 姓名int age;					// 年龄int studentID;				// 学号struct Grade grade;			// 成绩
};int main() {struct Student student = { "Rose", 17, 03, {90, 95, 93} };printf("学生信息:%s %d %d\n", student.name, student.age, student.studentID);printf("学生成绩:%d %d %d\n", student.grade.chinese, student.grade.math, student.grade.english);return 0;
}

输出结果:

1-1

四、结构体成员的访问

在访问结构体成员时,可以采用 . 或者 -> ,前者对应结构体变量,后者对应结构体指针变量。

程序清单:

#include struct Student {char name[20];  // 名字int age;		// 年龄int studentID;  // 学号
};int main() {struct Student student1 = {"Jack", 18, 32};  struct Student student2 = {"Bruce", 20, 05};printf("%s %d %d\n", student1.name, student1.age, student1.studentID);struct Student* ps1 = &student1;printf("%s %d %d\n", (*ps1).name, (*ps1).age, (*ps1).studentID);printf("%s %d %d\n", ps1->name, ps1->age, ps1->studentID);return 0;
}

输出结果:

1-2

五、结构体传参

程序清单:

#include struct Student {char name[20];		// 姓名int age;			// 年龄int studentID;		// 学号
};void print1(struct Student student) {printf("%s %d %d\n", student.name, student.age, student.studentID);
}void print2(struct Student* ps) {printf("%s %d %d\n", ps->name, ps->age, ps->studentID);
}int main() {struct Student student1 = {"Jack", 18, 02};print1(student1);	// 1. 传值print2(&student1);	// 2. 传地址return 0;
}

输出结果:

1-3

注意事项:

① 函数在定义时,应该写在结构体之后。由于 main 函数作为程序的入口,之后在 main 函数中调用 print1 和 print2 函数时,程序就需要自上到下扫描,如果两个打印函数都写在了结构体之前,就会产生找不到结构体类型的现象。

② 上面在使用结构体传参时,采用了两个传参类型。
第一,传值调用 print1 函数;第二,传地址调用 print2 函数。然而,从调用函数的效率角度看,后者更加高效。

前者在传值时,传的是整个结构体过去的,如果结构体过大,形参在接收实参传来的结构体时,也要同样开辟一个与原结构体一样大的内存空间,后续才能接收结构体中各种各样的成员变量。然而,后者在传整个结构体的地址时,形参只需要拿一个结构体指针变量接收即可,因为一个指针变量占内存的大小要么是 4,要么是 8 ,所以整体系统开销较小。

此外,这里结构体传参应该与数组传参区分开来,如果将一个结构体变量作为函数的参数,那么传递的就是整个结构体;然而,如果将一个数组作为函数的参数,那么传递的就是数组首元素的地址。

综上所述,结构体在传参时,应该尽可能传它的地址,而不是结构体变量本身。

六、结构体的自引用

结构体的自引用常用于表示数据结构中的链表,结构体中包含数据域和指针域。

#include struct Node {int data;				// 数据域struct Node* next;		// 指针域
};int main() {struct Node node1;return 0;
}

七、结构体的内存对齐

对齐规则

1. 第一个结构体成员在处于偏移量为 0 的地址处。

2. 其他结构体成员需要从对齐数的整数倍的偏移量地址处开始继续偏移。
( 对齐数 = 编译器默认的对齐数与该结构体成员大小两者比较后的较小值 )
在 VS 编译器中,默认值为 8;在 Linux 环境下,没有默认对齐数,此时结构体成员的自身大小就是它的对齐数。

3. 结构体占用内存的总大小为最大对齐数的整数倍,单位字节。
( 最大对齐数 = 所有结构体成员对齐数中的最大值 )

4. 如果出现了结构体嵌套结构体的情况,那么内部的结构体对齐到自己的最大对齐数的整数倍处。最终,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

程序清单1

#include 
#include struct S1 
{char c1;		// 1int i;			// 4char c2;		// 1
};int main() {printf("%d\n", sizeof(struct S1)); // 12printf("%u ", offsetof(struct S1, c1)); // 0printf("%u ", offsetof(struct S1, i)); // 4printf("%u \n", offsetof(struct S1, c2)); // 8return 0;
}

注意事项:

offsetof 可以用来计算结构体成员的偏移量。

S1 所占内存的计算过程:

1-4

程序清单2

#include 
#include struct S2
{char c1;		// 1char c2;		// 1int i;			// 4
};int main() {printf("%d\n", sizeof(struct S2)); return 0;
}// 输出结果:8

S2 所占内存的计算过程:

1-5

程序清单3

#include 
#include struct S3
{double d;		// 8char c;			// 1int i;			// 4
};int main() {printf("%d\n", sizeof(struct S3));return 0;
}// 输出结果:16

S3 所占内存的计算过程:

1-6

程序清单4

#include 
#include struct S3			// 16
{double d;		// 8char c;			// 1int i;			// 4
};struct S4
{char c1;		// 1struct S3 s3;	// 最大对齐数 8double d;		// 8
};int main() {printf("%d\n", sizeof(struct S4)); // 32printf("%u ", offsetof(struct S4, c1)); // 0printf("%u ", offsetof(struct S4, s3)); // 8printf("%u \n", offsetof(struct S4, d)); // 24return 0;
}

S4 所占内存的计算过程:

1-7

修改默认对齐数

#include 
#include struct S1 {char c;			// 1double d;		// 8
};#pragma pack(4)
struct S2 {char c;			// 1double d;		// 8 (取对齐数为 4)
};
#pragma pack()#pragma pack(1)
struct S3 {char c;			// 1double d;		// 8 (取对齐数为 1)
};
#pragma pack()int main() {printf("%d\n", sizeof(struct S1)); // 16printf("%d\n", sizeof(struct S2)); // 12printf("%d\n", sizeof(struct S3)); // 9return 0;
}

1-8

注意事项:

在 VS 编译器中,对齐数默认值为 8;在 Linux 环境下,没有默认对齐数,此时结构体成员的自身大小就是它的对齐数。当我们为程序设计对齐数时,也应该考虑到硬件的访问情况,通常应该将对齐数设置为 2 的次方。

此外,在对齐数相同的情况下,我们应该尽可能地将占用空间小的结构体成员集中在一起。对比上面程序清单1 和 程序清单2,同样的对齐数,同样的程序,后者将 char 类型的结构体成员放在了一起,结果既满足了对齐,又节省了空间。

相关内容

热门资讯

三部门:不断完善学前教育成本分... 观点网讯:12月23日,国家发展改革委、教育部、财政部联合发布《关于完善幼儿园收费政策的通知》,要求...
汪清林区法院:化解未成年人纠纷... 近日,吉林省汪清林区法院审结了一起涉未成年人在校遭受人身损害案件,法院充分考量了未成年人的行为特点及...
北京市长城保护条例 北京市人民代表大会常务委员会公告 〔十六届〕第45号 《北京市长城保护条例》已由北京市第十六届人民代...
棒杰股份(002634)披露关... 截至2025年12月23日收盘,棒杰股份(002634)报收于5.28元,较前一交易日下跌4.52%...
高盛再度唱多!预计中国股市到2... 来源:视觉中国 界面新闻编辑 | 江怡曼 近日,高盛发布名为《中国策略:2025年中国股市十大...
尤文身价变化:共10人身价下降... 在意甲联赛的激烈竞争中,尤文图斯的球员身价变化引发了广泛关注。根据最新的德转数据,尤文队内有10名球...
美国发布H-1B签证新规,优先... 当地时间12月23日,美国国土安全部发布新规,正式以“加权选择”机制取代H-1B签证原有的随机抽签制...
悉尼恐袭事件,意外替德国默茨政... 刚刚过去的一周,发生在澳大利亚的悉尼邦迪海滩恐怖袭击事件引起全球关注。 对于万里之外的德国而言,这场...
视频丨“粤车南下”驶入香港市区... 今天(12月23日),“粤车南下”驶入香港市区政策正式实施,符合条件的广东私家车可直接驶入香港市区,...
立白回应与经销商解约纠纷:个别... 12月23日,红星新闻报道《多地代理商称与立白集团解约后 对方未按约交接市场致严重损失,律师解读》一...