C语言自定义类型变量——结构体(一)

本文详细介绍了C语言中结构体的定义,包括声明、变量创建和初始化,以及内存对齐规则。特别强调了结构体成员变量的内存布局和匿名结构体的使用限制。
摘要由CSDN通过智能技术生成

前言:我们知道,C语言本身有许多变量类型,例如char,short,int,long,long long,float,double,long double等,这些C语言本身支持的现成的类型称为内置类型,然而仅仅使用这些内置变量无法一些复杂对象进行描述,就一本书而言,如何去描述它呢?它有名字,编号,作者,日期等等....这时我们发现仅仅用内置类型是不够的,事实上,C语言中不仅仅有内容变量,也允许有自定义类型,包括:结构体—struct,枚举—enum,联合体—union。今天我们的主角便是自定义类型之一:结构体变量。

 目录:

一.结构体定义

  1.1结构体的声明

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

  1.3结构体的特殊声明

  1.4结构体的自引用

二.结构体内存对齐

  2.1对齐规则

  2.2有关内存对齐的例题介绍

一.结构体定义

 何为结构体?事实上,结构就是是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.1结构体的声明

struct tag
{
     member-list;//成员变量
}variable-list;//切记分号不能丢

struct tag称为结构体标签,相当于结构体变量类型,类似与int,char等

而variable-list是你给这个结构体指定的变量名称,类似于int a=0;中的a,它不一定要出现在结构体的声明中,也可以在使用它的时候再创建名称。

例如:试想如何描述一个学生,他有名字,年龄,性别,学号等。

struct Stu
{
	char name[20];//姓名
	int age;//年龄
	char gender[5];//性别
	int id[10];//学号
};

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

以上文学生为例,我们已经声明了这样一个描述他具体信息的结构体变量,那如何给它初始化赋值呢?

这里就需要引入一个新操作符,"."操作符,称为结构体成员变量引用操作符,通过使用.可以找到结构体中的成员变量.

.        结构体.成员变量名

法一:按照结构体成员的顺序初始化

#include <stdio.h>
struct Stu
{
     char name[20];//名字 
     int age;//年龄 
     char sex[5];//性别 
     char id[20];//学号 
};
int main()
{
     //按照结构体成员的顺序初始化 
     struct Stu s = { "张三", 20, "男", "20230818001" };
     printf("name: %s\n", s.name);
     printf("age : %d\n", s.age);
     printf("sex : %s\n", s.sex);
     printf("id : %s\n", s.id);
     return 0;
}

法二:按照指定顺序初始化

#include <stdio.h>
struct Stu
{
    char name[20];//名字 
    int age;//年龄 
    char sex[5];//性别 
    char id[20];//学号 
};
int main()
{
    //按照指定顺序初始化
    struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
"⼥" };//乱序
    printf("name: %s\n", s2.name);
    printf("age : %d\n", s2.age);
    printf("sex : %s\n", s2.sex);
    printf("id : %s\n", s2.id);
    return 0;
}

最终我们都如愿以偿地得到了想要初始化的结果:

1.3结构体的特殊声明

在声明结构体的时候,有一种特殊的声明方法,就是不完全声明。

例如:

//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c
}a[20],*p;

上面两个结构体在声明的时候都省略掉了结构体标签(tag),那么问题来了:

在上⾯代码的基础上,下⾯的代码合法吗?

p = &x;

 警告: 编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。也就是说,匿名结构体可以使用,但它是一次性的,不可二次操作。

1.4结构体的自引用

在结构体的定义中我们了解到,结构体的成员变量可以是任意类型的,那么在一个结构体当中是否可以包含一个类型为该结构体本身的成员呢?

例如,定义一个链表(一种数据结构)的节点:

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

上述代码是否正确?如果正确,那么试想sizeof(struct Node)是多少?仔细分析就会发现这个代码是不行的,若是一个结构体再包含一个同类型的结构体变量,这样结构体大小就会无穷大,像是陷入了一个死循环,是不合理的。那是不是结构体无法自引用呢?也不是,需要正确的方式。

正确的自引用方式:

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

与上一代码不同,正确的自引用方式应是借助指针。

在结构体⾃引⽤使⽤的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引⼊问题,看看 下⾯的代码,可⾏吗?

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

答案是不⾏的,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使 ⽤Node类型来创建成员变量,这是不⾏的。(简要说就是人家没创建好你就提前用了,当然不可以了)那如何解决呢?一句话:定义结构体时不要使用匿名结构体!

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

二.结构体内存对齐

在上文我们提及了一次sizeof(struct Node),你是否思考过一个结构体的大小是多少呢?简单来想,一个结构体有多大难道不是各个成员变量所占内存大小的总和吗,事实并非如此,例如:

按照我们的设想,这个结构体大小应该是1+4+1=6个字节,然而事实上它占了12个字节,这是为什么呢?这就要引出一个热门考点:结构体的内存对齐规则。

2.1对齐规则

首先让我们了解一下结构体对齐规则的理论:

1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。

        对齐数=编译器默认的一个对齐数与该成员变量大小的较小值

-VS中的默认值为8

-Linux中的gcc没有默认对齐数,对齐数就是成员自身大小。

3.结构体总大小为最大对齐数(结构体中的每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍数。

4.如果是嵌套了结构体的情况,嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数的整数倍)。

2.2有关内存对齐的例题介绍 

理论成立,下面我们具体分析一下:

再拿出刚才的例子:

struct Stu
{

        //   成员变量大小   编译器默认对齐数   最终对齐数(二者较小值)      
        char a;//        1  8   1
        int b;//           4   8   4
        char c;//        1   8    1
};

 

在内存中观察可知,第一个成员先对齐到初始偏移量为0的地址处,剩下的成员要对齐到各自对齐数的整数倍地址处,例如int b的对齐数为4,所以它要跳过三个字节从4(是4的整数倍)处开始存储,同理对于char c,它的对齐数是1,可以紧挨着int b存储,因为任何整数都是1的整数倍,最终结构体还要对齐到最大对齐数(即所有成员变量的对齐数中最大的,即4),所以最终结构体还要跳过3个字节指向12(4的整数倍处),这样才算开辟了一个完整的结构体空间,实现了结构体的内存对齐。 这样我们便不难理解上述例子结构体大小为何是12而不是6了。

练习一下吧:

#include<stdio.h>
struct S3
{
	double d;//8 8 8
	char c;//1 8 1
	int i;//4 8 4
};
//结构体嵌套问题
struct S4
{
	char c1;//1 8 1
	struct S3 s3;//对齐到自身成员变量的最大对齐数处
	double d;//8 8 8
};
int main()
{
	printf("%zd\n", sizeof(struct S3));//16
	printf("%zd\n", sizeof(struct S4));//32
	return 0;
}

如何计算的和上述例子是一样滴,望同学们仔细观察! 

  • 33
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C语言中,可以使用typedef关键字来定义自己习惯的数据类型名称。这样可以简化代码并增加可读性。例如,可以使用typedef定义一个自定义的数据类型名称,比如BOOL类型,可以将其定义为int类型,并使用#define定义True为1。这样,在代码中就可以直接使用BOOL作为数据类型,并赋值为True或False。另外,结构体也可以使用typedef来定义,这样在声明结构体变量时就不需要再加上struct关键字了。例如,可以使用typedef定义一个结构体类型为lept_value,然后在声明结构体变量时就可以直接使用lept_value而不需要加上struct关键字。结构体变量在赋值的同时也可以对结构体中的变量进行赋值。例如,在定义一个结构体Dog时,可以在声明结构体变量dog1时同时对其进行赋值,如dog1={4,'m'}。这样可以方便地初始化结构体变量的值。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [C语言结构体用到的typedef](https://blog.csdn.net/weixin_44477424/article/details/122796704)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [C语言——结构体与typedef](https://blog.csdn.net/fmj2801876977/article/details/129068033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值