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

可以看到在设计结构体时,从上到下,先声明空间占用较小的成员、再声明空间占用较大的成员,更有利于降低内存占用。

结构体的内存占用

  • 从上面的图中可以看到,结构体的内存占用不是简单的成员大小之和,而是内存对齐后的总大小;
  • 上面结构体的成员中含有一个字符数组,它占用空间最多;因为:在结构体内存分布中,数组长度声明了多长,就占用多少字节空间;
  • 结构体作为函数形参传递时,因是值拷贝,对于体积较大的结构体来说拷贝工作较为繁重;

实用技巧

指针替代数组成员

以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关键字”。

posted @ 2025-11-17 10:08  BigBosscyb  阅读(7)  评论(0)    收藏  举报