408学习笔记-15-C-结构体

本文详细介绍了C语言中结构体的基础概念,包括结构体的声明、变量的创建和初始化,以及结构体类型重命名、匿名结构体、结构体指针、内存对齐、成员变量访问和结构体传参。着重讨论了内存对齐对性能的影响和结构体传值与传址的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、结构体

结构体是一种自定义类型。

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。




2、结构体的声明

struct Student
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};//分号不能丢

以上声明的结构体的数据类型是:struct Student,这是一个整体,不能分开。




3、结构体变量的创建和初始化

#include <stdio.h>
#include <string.h>

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex;//性别
    char id[20];//学号
}s0 = {"zero", 0, 'M', "0"},s2;
//结构体全局变量的定义方式1
//结构体全局变量的初始化方式1:按照结构体成员变量的默认顺序初始化


//结构体全局变量的定义方式2
//结构体全局变量的初始化方式1:按照结构体成员变量的默认顺序初始化
struct Stu s1 = {"one", 1, 'M', "1"};


int main()
{

    printf("name: %s\n", s0.name);
    printf("age : %d\n", s0.age);
    printf("sex : %c\n", s0.sex);
    printf("id : %s\n", s0.id);

    printf("name: %s\n", s1.name);
    printf("age : %d\n", s1.age);
    printf("sex : %c\n", s1.sex);
    printf("id : %s\n", s1.id);


    //结构体全局变量的初始化方式2:按照结构体成员变量的指定顺序初始化
    strcpy(s2.name, "two");
    s2.age = 2;
    s2.sex = 'F';
    strcpy(s2.id, "2");

    printf("name: %s\n", s2.name);
    printf("age : %d\n", s2.age);
    printf("sex : %c\n", s2.sex);
    printf("id : %s\n", s2.id);


    //-------------------------------------------------------


    //结构体局部变量的定义方式
    //结构体局部变量的初始化方式1:按照结构体成员变量的默认顺序初始化
    struct Stu s3 = {"three", 3, 'M', "3"};

    printf("name: %s\n", s3.name);
    printf("age : %d\n", s3.age);
    printf("sex : %c\n", s3.sex);
    printf("id : %s\n", s3.id);


    //结构体局部变量的初始化方式2:按照结构体成员变量的指定顺序初始化
    struct Stu s4 = {.age = 4, .name = "four", .id = "4", .sex = 'F'};

    printf("name: %s\n", s4.name);
    printf("age : %d\n", s4.age);
    printf("sex : %c\n", s4.sex);
    printf("id : %s\n", s4.id);


    //结构体局部变量的初始化方式2:按照结构体成员变量的指定顺序初始化
    struct Stu s5;

    strcpy(s5.name, "five");
    s5.age = 5;
    s5.sex = 'F';
    strcpy(s5.id, "5");

    printf("name: %s\n", s5.name);
    printf("age : %d\n", s5.age);
    printf("sex : %c\n", s5.sex);
    printf("id : %s\n", s5.id);


    return 0;
}



4、结构体类型的重命名

typedef struct Student
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}Stu;//将结构体数据类型struct Student重命名为Stu




5、匿名结构体

struct 
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}s1;

1、匿名结构体类型就是在声明时,省略了标签的结构体数据类型。

2、匿名的结构体类型,如果没有对结构体类型使用typedef进行重命名的话,就只能在声明时定义结构体变量。

3、匿名结构体类型不能进行自引用。




6、结构体类型指针

#include <stdio.h>

typedef struct Node
{
    int data;
}Node, *pNode;



int main()
{
    struct Node *p1 = NULL;//结构体指针定义方法1
    Node *p2 = NULL;//结构体指针定义方法2
    pNode p3 = NULL;//结构体指针定义方法3

    return 0;
}

以上为三种结构体指针局部变量的方法。




7、结构体的自引用

结构体的自引用涉及到数据结构。

匿名的结构体类型是不能进行自引用的。

错误的自引用:

struct Node
{
 int data;
 struct Node next;
};

以上代码是错误的,因为一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大,无法用sizeof()操作符获得其大小,是不合理的。



正确的自引用:

struct Node
{
	int data;
	struct Node *next;
};


结构体的自引用时,结构体内部不能使用typedef产生的新命名,即:
错误代码:

typedef struct Node
{
	int data;
	Node *next;
}Node;

正确代码:

typedef struct Node
{
	int data;
	struct Node *next;
}Node;

原因:因为typedef的重命名作用是从新命名Node出现才开始生效的,可是在新命名Node出现前,也就是在结构体内部需要提前使用结构体类型,此时的typedef产生的新命名还未生效,所以在结构体内部不能使用typedef产生的新命名。



结构体的自引用常用于数据结构中对链表节点的构建。

struct Node
{
	int data;//数据域
	
	struct Node *next;//指针域
};

链表节点内部划分为数据域指针域数据域存放本节点所包含的信息,指针域存放指向下个节点的指针。




8、结构体内存对齐

结构体的对齐规则:

1、结构体的第一个成员对齐到和结构体变量起始位置偏移量0的地址处。

2、其他成员变量要对齐到偏移量自身对齐数的整数倍的地址处。(这里的整数倍指的是在不覆盖前驱成员变量情况下的最小整数倍)

每个成员变量都有自己的对齐数,自身对齐数计算规则是:

(1)成员变量为基本数据类型:编译器的默认对齐数与该成员变量量级大小的较小值,即:min[编译器默认对齐数,成员变量量级]

(2)成员变量为构造数据类型/自定义数据类型(数组、结构体、位段、枚举、联合):由于构造数据类型复合了一系列子数据类型,而这些子数据类型也都有自己的对齐数,他们的对齐数里的最大值(最大对齐数)被作为当前这个构造数据类型的对齐数。

64位操作系统下编译器的默认对齐数为832位操作系统下编译器的默认对齐数为4。Linux操作系统中,编译器没有默认对齐数,那么成员变量的对齐数就是成员变量自身的大小。

3、结构体总大小为成员变量中的最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。(这里的整数倍是指能够容下所有成员变量前提下的最小整数倍)

4、如果嵌套了结构体的情况,子结构体成员变量的对齐数为子结构体的成员变量中的最大对齐数,父结构体的整体量级大小就是最大对齐数的整数倍。(这里的整数倍是指能够容下所有成员变量前提下的最小整数倍)



内存对齐的原因:

(1)平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

(2)性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对齐是拿空间来换取时间的做法。



可以通过让占用空间小的成员变量尽量挨在一起来节省空间:

struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

以上代码成员变量都一样,但是所定义出来的结构体变量的大小不一样。




9、结构体成员变量的访问

由结构体的内存对齐我们可知,结构体变量内部是具有框架结构的。

对结构体成员变量的访问就是找到对应框格里的数据。

9.1、直接访问

struct Student
{
    char name[20];//名字
    int age;//年龄
    char sex;//性别
    char id[20];//学号
}s1;

结构体成员变量直接访问操作符:.

格式:结构体变量名.成员变量名

9.2、间接访问

struct Student
{
    char name[20];//名字
    int age;//年龄
    char sex;//性别
    char id[20];//学号
};

struct Student *ps;

结构体成员变量间接访问操作符:->

格式:结构体指针变量名->成员变量名

还有一种方式是:(*结构体指针变量名).成员变量名




10、结构体传参

结构体有传值传址两种传参方式。

与数组传参一样,传址这种传参方式更好。因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。若采用传址这种传参方式,如果结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值