c语言tips-结构体

1. 结构体定义和使用

#include <stdio.h>

struct stu {
    int age;
    int height;
    int weight;
};


int main(int argc, char *argv[]) {
    struct stu stu1;
    stu1.age = 18;
    stu1.height = 172;
    stu1.weight = 62;
    printf("年龄为:%d, 身高为:%d, 体重为:%d", stu1.age, stu1.height, stu1.weight);
    return 0;
}

定义结构体,然后把它当作一个类型初始化,并对其赋值和获取

#include <stdio.h>

struct stu {
    int age;
    int height;
    int weight;
} stu1;


int main(int argc, char *argv[]) {
    stu1.age = 18;
    stu1.height = 172;
    stu1.weight = 62;
    printf("年龄为:%d, 身高为:%d, 体重为:%d", stu1.age, stu1.height, stu1.weight);
    return 0;
}

在定义结构体的时候就初始化一个结构体变量

2. 结构体的存储空间

结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。

我们看看以下例子:

#include <stdio.h>

struct stu {
    int age;
    int height;
    int weight;
} stu1;


int main(int argc, char *argv[]) {
    stu1.age = 18;
    stu1.height = 172;
    stu1.weight = 62;
    printf("年龄为:%d, 身高为:%d, 体重为:%d\n", stu1.age, stu1.height, stu1.weight);
    printf("结构体所占的内存空间为:%d", sizeof(stu1));
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UhFTJM7O-1669625987454)(img/image-20221128111618814.png)]

可以看到当前结构体的内存空间为12刚好等于三个int类型的内存大小

3. c语言结构体的内存填充

加上我自己的理解

3.1 计算机自然对齐和内存寻址

计算机内存是以字节( Byte)为单位划分的,理论上 CPU 可以访问任意编号的字节,但实际情况并非如此

CPU 通过地址总线来访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据。 32 位的CPU 一次可以处理 4 个字节的数据,那么每次就从内存读取 4 个字节的数据;少了浪费主频,多了没有用。 64位的处理器也是这个道理,每次读取 8 个字节。

以 32 位的 CPU 为例,实际寻址的步长为 4 个字节,也就是只对编号为 4 的倍数的内存寻址,例如 0、 4、 8、12、 1000 等,而不会对编号为 1、 3、 11、 1001 的内存寻址。如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0NJYwVy-1669625987456)(img/image-20221101161741561.png)]

这样做可以以最快的速度寻址:不遗漏一个字节,也不重复对一个字节寻址

对于程序来说,一个变量最好位于一个寻址步长的范围内,这样一次就可以读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了

例如一个 int 类型的数据,如果地址为 8,那么很好办,对编号为 8 的内存寻址一次就可以。如果编号为 10,就比较麻烦, CPU 需要先对编号为 8 的内存寻址,读取 4 个字节,得到该数据的前半部分,然后再对编号为12 的内存寻址,读取 4 个字节,得到该数据的后半部分,再将这两部分拼接起来,才能取得数据的值。如下图所示

[(img-mrd1PJER-1669625987456)(img/image-20221128152832263.png)]

将一个数据尽量放在一个步长之内,避免跨步长存储,这称为内存对齐。 在 32 位编译模式下,默认以 4 字节对齐;在 64 位编译模式下,默认以 8 字节对齐 。如果一个变量在内存中的地址刚好是他的长度的整数倍,那就叫自然对齐

为了提高存取效率,编译器会自动进行内存对齐,请看下面的代码:

#include <stdio.h>

struct stu {
    int age;
    char height;
    int weight;
} stu1;


int main(int argc, char *argv[]) {
    stu1.age = 18;
    stu1.weight = 62;
    printf("年龄为:%d 体重为:%d\n", stu1.age,stu1.weight);
    printf("结构体所占的内存空间为:%d", sizeof(stu1));
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RTINJA7u-1669625987457)(img/image-20221128152938645.png)]

如果不考虑内存对齐,结构体变量 t 所占内存应该为 4+1+4 = 9 个字节。考虑到内存对齐,虽然成员 b 只占用 1 个字节,但它所在的寻址步长内还剩下 3 个字节的空间,放不下一个 int 型的变量了,所以要把成员 c 放到下一个寻址步长。剩下的这 3 个字节,作为内存填充浪费掉了。请看下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iN0F5e2M-1669625987458)(img/image-20221101162705657.png)]

编译器之所以要内存对齐,是为了更加高效的存取成员 c,而代价就是浪费了 3 个字节的空间

3.2 字节对齐规则

  • 结构体变量的首地址是最长成员长度的整数倍。
  • 每个成员相对结构体首地址的偏移量,一定是该成员长度的整数倍(个人认为这个规则最重要)
  • 如果结构体的总长度是最长成员长度的整数倍。
  • 如果结构体内有成员长度大干处理器的位数那么就以处理器的位数作为对齐单位。

4. c语言结构体数组

结构体数组,是指数组中的每个元素都是一个结构体。

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
} dates[] = {
        2022, 1, 1,
        2021, 6, 12,
        2011, 10, 10
};

int main(int argc, char *argv[]) {
    for(int i = 0; i < 3; i++)
    {
        printf("%d-%d-%d\n", dates[i].year, dates[i].month, dates[i].date);
    }
    return 0;
}

5.结构体嵌套结构体

结构体的成员里面有结构体

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
};

struct People
{
    struct Date born_date;
    char[20] name;
    int age;
    int height;
    int weight;
}people1 = {{2011, 2, 1}, "jack", 18, 172, 62};

int main(int argc, char *argv[]) {
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
           people1.name, people1.born_date.year, people1.born_date.month, people1.born_date.date, people1.age, people1.height, people1.weight);
    return 0;
}

6.结构体指针

指向结构体的指针称为结构体指针

编写格式:struct xxx* xxx

例子如下:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
};

struct People
{
    struct Date born_date;
    char[20] name;
    int age;
    int height;
    int weight;
}people1 = {{2011, 2, 1}, "jack", 18, 172, 62};

int main(int argc, char *argv[]) {
    struct People* pt = &people1;
/*    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
           people1.name, people1.born_date.year, people1.born_date.month, people1.born_date.date, people1.age, people1.height, people1.weight);*/
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d\n",
           pt->name, pt->born_date.year, pt->born_date.month, pt->born_date.date, pt->age, pt->height, pt->weight);
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d\n",
           (*pt).name, (*pt).born_date.year, (*pt).born_date.month, (*pt).born_date.date, (*pt).age, (*pt).height, (*pt).weight);
    return 0;
}

pt->xxx(*pt).xxx是一样的,那为什么要这个结构体指针呢,初始化这么麻烦,取值赋值都这么麻烦,这在我们后面函数赋值再来探讨

7.结构体的赋值传递和函数传递

直接赋值传递

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
};

struct People
{
    struct Date born_date;
    char[20] name;
    int age;
    int height;
    int weight;
}people1 = {{2011, 2, 1}, "jack", 18, 172, 62};

int main(int argc, char *argv[]) {
    struct People people2 = people1;
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
       people2.name, people2.born_date.year, people2.born_date.month, people2.born_date.date, people2.age, people2.height, people2.weight);
    return 0;
}

函数传递

结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。

请看第一个例子:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
};

struct People
{
    struct Date born_date;
    char* name;
    int age;
    int height;
    int weight;
}people1 = {{2011, 2, 1}, "jack", 18, 172, 62};

void print_struct(struct People people2){
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
           people2.name, people2.born_date.year, people2.born_date.month, people2.born_date.date, people2.age, people2.height, people2.weight);
}

int main(int argc, char *argv[]) {
    struct People people2 = people1;
    print_struct(people2);
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Jzt4Dke-1669625987459)(img/FireShot Capture 001 - Python Tutor code visualizer_ Visualize code in Python, JavaScript, C_ - pythontutor.com.png)]

通过代码的可视化展示我们可以看到,当结构体变量传到函数时会新开辟一个内存来存储传过来的结构体变量,有些结构体变量存储的数据过大调用函数会造成极大的时间和空间资源的浪费

我们再来看第二个例子:

#include <stdio.h>

struct Date {
    int year;
    int month;
    int date;
};

struct People
{
    struct Date born_date;
    char* name;
    int age;
    int height;
    int weight;
}people1 = {{2011, 2, 1}, "jack", 18, 172, 62};

void print_struct(struct People *pt){
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
           pt->name, pt->born_date.year, pt->born_date.month, pt->born_date.date, pt->age,pt->height, pt->weight);
}

int main(int argc, char *argv[]) {
    struct People people2 = people1;
    print_struct(&people2);
    return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fonICsY7-1669625987460)(img/FireShot Capture 002 - Python Tutor code visualizer_ Visualize code in Python, JavaScript, C_ - pythontutor.com.png)]

可以看到还是节省不少资源实现了相同的功能

8.typedef关键字在结构体中的使用

通过前面的例子我们发现在定义结构体变量时需要struct 结构体名称 结构体变量名,定义时还需要定义struct就很烦,有没有什么办法在定义时可以省略掉这个strcut呢?这就需要用到typedef

格式: typedef 原类型名 新类型名;

请看以下例子:

#include <stdio.h>

typedef struct Date {
    int year;
    int month;
    int date;
}Date;

typedef struct People
{
    Date born_date;
    char* name;
    int age;
    int height;
    int weight;
}People;

void print_struct(struct People *pt){
    printf("名字为:%s, 出生日期为:%d-%d-%d, 年龄:%d, 身高:%d, 体重:%d",
           pt->name, pt->born_date.year, pt->born_date.month, pt->born_date.date, pt->age,pt->height, pt->weight);
}

int main(int argc, char *argv[]) {
    People people1 = {{2011, 2, 1}, "jack", 18, 172, 62};
    People people2 = people1;
    print_struct(&people2);
    return 0;
}

这样我们就可以少写一个struct,例如struct People people1 --> People people1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值