C 结构体
数组是一个存储多个相同类型值的结构;但如果我们希望存储一同学的信息,他包含:姓名、性别、年龄、家庭住址等;这时数组就不能够胜任了。于是本篇需要引出“结构体”类型,来实现不同类型数据的存储。
结构体是一种用户自定义类型;
结构体类型声明;变量创建和初始化
- 声明结构体类型
struct 类型名 {多个成员变量声明语句}; - 结构体变量声明
struct 类型名 结构体变量名 ; - 结构体变量初始化类似数组,使用参数列表进行初始化;
- 结构体变量与普通变量类似,若未显示进行初始化,在全局位置自动初始化、在函数内部在不会自动初始化,内存中存储的是随机值
示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student {
char sex;
int age;
char level;
char name[20];
};
int main(void) {
struct student stu = {'w', '1', 33, "bluce"};//参数列表
struct student st;
st.age = 8;
st.level = '1';
st.sex = 'f';
// st.name="bluce";//数组名类似指针常量 不可再次对其赋值
strcpy(st.name, "bluce");
return 0;
}
结构体的内存对齐
CPU按字长(一次处理32位或者64位)将内存数据送入寄存器处理的;为充分利用CPU,编译器会在结构体成员之间插入“空洞”,以实现高效访问。
从下图可以看到student类型成员的位置不同,在内存中的分布也会有差异:
情况1:

char类型只占一个字节,由于后面跟着int类型,对齐后int类型存储位置前便产生了三个字节的空洞;level后面跟着name数组,对齐时name指针按照4字节对齐,于是前面又产生3字节空洞
情况2:

可以看到在设计结构体时,从上到下,先声明空间占用较小的成员、再声明空间占用较大的成员,更有利于降低内存占用。
结构体的内存占用
- 从上面的图中可以看到,结构体的内存占用不是简单的成员大小之和,而是内存对齐后的总大小;
- 上面结构体的成员中含有一个字符数组,它占用空间最多;因为:在结构体内存分布中,数组长度声明了多长,就占用多少字节空间;
- 结构体作为函数形参传递时,因是值拷贝,对于体积较大的结构体来说拷贝工作较为繁重;
实用技巧
指针替代数组成员
以student结构体为例
- 字符数组的好处时:值拷贝,内存占用随结构体消失而消失;
- 字符数组的坏处是大数组拷贝工作繁重;
- 在有些场景字符数组的长度无法确定
使用字符指针来代替可解决这些问题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student {
char sex;
char level;
int age;
char* name;
};
int main(void) {
struct student st;
st.age = 8;
st.level = '1';
st.sex = 'f';
st.name="bluce";//第一处错误
st.name[0]='g';//第二处错误
return 0;
}
使用字符指针也有新的注意事项,即上面在main函数中有两处错误:
1、 方法内部的st是局部变量,不会自动初始化,故st中的name是存储的是随机内容,即:name是野指针
2、 将字符串字面量赋值给name,name此时指向了只读区,使用name修改字符串,程序会崩溃
故,上面的代码,正确的方法可以写成:使用malloc手动申请内存,将返回的地址赋值给name,然后使用strcpy将字符串值拷贝到name指向的地址处。
int main(void) {
struct student st;
st.age = 8;
st.level = '1';
st.sex = 'f';
char *newPtr = malloc(strlen("bluce") + 1);
if (newPtr != NULL) {
st.name = newPtr;
strcpy(st.name, "bluce");
}
return 0;
}
对于手动申请的内存,不使用时记得释放;如果不使用后,忘记释放,这块空间就只能等到程序退出后,被系统回收。
作形参时使用结构体指针替代结构体变量
使用结构体指针当函数参数,比普通的结构体变量的好处自不必多说;
下面演示了一下,在不同位置使用const关键字,达到对形参保护的目的:
struct Student
{
char sex;
int age;
char level;
char name[20];
};
void func(struct Student* stu)
{
stu->name[0] = 'b';//ok
stu = malloc(sizeof(struct Student));//ok
}
void func1(const struct Student* stu)
{
stu->name[0] = 'b';//error
stu = malloc(sizeof(struct Student));//ok
}
void func2(struct Student* const stu)
{
stu->name[0] = 'b';//ok
stu = malloc(sizeof(struct Student));//error
}
void func3(const struct Student* const stu)
{
stu->name[0] = 'b';//error
stu = malloc(sizeof(struct Student));//error
}
结构体指针
这里主要介绍的是使用malloc动态分配内存的方式;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student {
char sex;
char level;
int age;
char *name;
};
int main(void) {
struct student *stdptr = malloc(sizeof(struct student));
stdptr->name = malloc(strlen("嘿嘿") + 1);
stdptr->level = '1';
stdptr->age = 30;
stdptr->sex = 'm';
strcpy(stdptr->name, "嘿嘿");
if (stdptr != NULL) {
if (stdptr->name != NULL) {
free(stdptr->name);
stdptr->name = NULL;
}
free(stdptr);
stdptr = NULL;
}
return 0;
}
可以看到,结构体内部若含有指针,则需单独对指针成员再进行内存分配;释放时,要先释放指针成员,再释放结构体指针。
使用typedef省去“多余的struct关键字”
声明结构体类型后,每次使用结构体时,都要带上struct关键字;你是否也觉得这个有些多余呢?现在有typedef帮助我们简化结构体类型的使用:
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct student
{
char sex;
int age;
char level;
char name[20];
} Student, * StudentPtr;
int main() {
Student stu;
StudentPtr st = &stu;
return 0;
}
使用typedef定义新类型后,便可使用新类型,像声明普通类型变量那样;每次不必再带上“多余的struct关键字”。

浙公网安备 33010602011771号