C语言——结构体自定义类型,C语言中的结构体自定义类型详解

马肤

温馨提示:这篇文章已超过465天没有更新,请注意相关的内容是否还可用!

摘要:,,C语言中,结构体是一种自定义类型,用于组合多个不同类型的数据项。通过结构体,可以创建复杂的数据结构,将多个变量组合成一个整体。结构体中的每个成员可以具有不同的数据类型,如整型、浮点型、字符型等。通过结构体,可以实现数据的封装,提高代码的可读性和可维护性。在C语言中,结构体的应用十分广泛,可以用于表示各种复杂的数据结构,如人员信息、坐标点等。

C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第1张

目录

结构体类型

声明结构体

结构体的特殊声明

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

结构体的自引用

结构体内存对齐

对齐规则

内存对齐存在意义

默认对齐数的修改

结构体传参

结构体实现位段

了解位段是什么

位段的内存分配

位段有跨平台的问题及使用注意事项


C语言中有内置的类型,内置类型如下:

内置类型
char

short

int
long
long long
float
double
long double

这些都是C语言本身支持的现场类型,但是仅仅有内置类型是不够的。

比如,我们要定义一个人的变量,

人:3.14   ——   这种就是不行的

人是一个复杂的对象,有身高、体重、名字等。所以这就要用到一个自定义类型——结构体。

C语言中也有自定义类型的。

结构体类型

声明结构体

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

//结构体的声明
struct tag  //tag就是标签名
{
    member-list;  //成员列表:1个或者多个,但是不能没有
}variable_list;  //变量列表

我们知道结构体的声明之后,我们就可以自定义一个学生练习一下:

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

结构体的特殊声明

在声明结构体的时候,可以不完全声明:匿名结构体类型

//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x;
		
struct
{
	int a;
	char b;
	float c;
}a[20], * p;

这两个结构体对比前面的,看出tag省略了

在使用这种形式的时候,要注意两点:

  1. 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次,第二次往后基本不能用了。
  2. 编译器会把上面的两个声明当成完全不同的两个类型,所以下面操纵是非法的。
p = &x;

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

我们定义了这个类型,要怎么创建这种类型的变量并怎么初始化它呢?

局部结构体变量创建和初始化有两种:

一种按照结构体成员的顺序初始化的,如下:

//按照结构体成员的顺序初始化
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);

                        C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第2张

注:   ·   和  ->都是用来访问结构体内的变量用的,· 是用来取的这个结构体中的元素,->是取得这个结构体中元素的地址所对应的元素。

例如:
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};             
*p=Stu;
Stu.name==(*p).name==p->name。

第二种初始化按照指定顺序初始化的(乱序):

//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "李四", .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);

                                C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第3张

全局变量的定义有两种,初始化方式也跟局部变量相似,下面就只举例全局变量的创建:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}b2,b3; //全局变量           
struct Stu b1;//全局变量

结构体的自引用

先介绍typedef相关知识, typedef为C语言的关键字:作用是为一种数据类型定义一个新名字。

typedef可以声明新的类型名来代替已有的类型名,不能增加新的类型。这里的数据类型有前面所说的内置数据类型(int,float等),还有自定义的数据类型(struct等)。

结构体自引用对于数据结构上的链表是非常有用的,

数据结构——其实是数据在内存种的存储和组织的结构,数据结构有多种

  • 线性数据结构:顺序表、链表、栈、队列。
  • 树形数据结构:二叉树
  • 等等……

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第4张

    结构体自引用的正确方式(链表形式):

    struct Node
    {
    	int data;			//数据
    	struct Node* next;  //指针——自己里包含一个自己同类型的指针
    };
    

    我们用typedef重命名一下:

    typedef struct Node
    {
    	int data;			//数据
    	struct Node* next;  //指针——自己里包含一个自己同类型的指针
    }Node;    //将struct Node 类型重命名为 Node
    //也可也写成
    struct Node
    {
    	int data;			//数据
    	struct Node* next;  //指针——自己里包含一个自己同类型的指针
    };
    typedef struct Node Node;  //typedef在后面重命名

    注意,千万不能写成以下这些形式:

    struct Node
    {
     int data;
     struct Node next;
    };
    一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大。
    typedef struct   //匿名结构体类型
    {
     int data;
     Node* next;  //这里面还有struct不能省略掉
    }Node;

    这个Node是对前面的匿名结构体类型的重命名产生的,但在匿名结构体内部提前使用Node类型来创建变量成员变量是不可以的。所以匿名结构体类型是不能实现这种自引用的。

    所以,定义结构体尽量不要使用匿名结构体。

    结构体内存对齐

    这个牵扯到计算结构体的大小,并且还有它特有的对齐规则。

    对齐规则

    1. 结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处。(后面举例介绍)
    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。                                             对齐数=编译器默认的一个对齐数与该成员变量大小的较小值                                                   VS中默认的一个对齐数为8                                                                                                       Linux中gcc没有默认对齐数,对齐数就是成员变量自身大小。
    3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
    4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

    例如下列例子,并了解这个规则怎么用:

    struct S1
    {
     char c1;
     int i;
     char c2;
    };
    printf("%d\n", sizeof(struct S1));
    struct S2
    {
     double d;
     char c;
     int i;
    };
    printf("%d\n", sizeof(struct S2));
    //结构体嵌套问题
    struct S3
    {
     char c1;
     struct S2 s2;
     double d;
    };
    printf("%d\n", sizeof(struct S3));

    第一个:

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第5张

    所以会输出:12.

    注:若遇到数组,如char[5] 则就是存了五个char。

    第二个:C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第6张

    所以这个输出16.

    第三个:

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第7张

    所有会输出:32.

    内存对齐存在意义

    结构体内存对齐是牺牲空间来换取时间的做法。

    1. 平台原因 (移植原因) C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第8张 2. 性能原因 C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第9张 若又想节省一些空间,那么就让空间小的成员尽量集中在一起。
    struct S1
    {
        char c1;
        int i;
        char c2;
    };  //占12
    struct S2
    {
        char c1;
        char c2;
        int i;
    };  //占8

    默认对齐数的修改

    我们前面说VS中默认对齐数是8,那么我们可以修改吗?

    我们可以用一个预处理指令修改,并且另一个指令可以取消修改,如下:

    #pragma pack(1)//设置默认对⻬数为1
    #pragma pack()//取消设置的对⻬数,还原为默认

    根据自己所需,可以用这个预处理指令修改对齐数。

    结构体传参

    我们函数传参,可以传整型变量、数组、指针变量等。结构体变量也可以传。

    实现具体如下列程序:

    struct S
    {
    	int data[1000];
    	int num;
    };
    struct S s = { {1,2,3,4}, 1000 };
    //结构体传参
    void print1(struct S s)  //这样拷贝开创了一个相同大的内存空间
    {
    	printf("%d\n", s.num);
    }
    //结构体地址传参
    void print2(struct S* ps)
    {
    	printf("%d\n", ps->num);
    }
    int main()
    {
    	print1(s); //传结构体变量  //传值调用 拷贝
    	print2(&s); //传结构体变量的地址   //传址调用
    	return 0;
    }

    不过上面两个函数,print2的效率高一些。

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第10张C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第11张所以,结构体传参的时候,尽量传结构体的地址。

    结构体实现位段

    了解位段是什么

    位段实现是基于结构体的

    位段的声明和结构体相似,有两个不同:

    1. 位段的成员必须是int、unsigned int 或者signed int,C99中位段成员的类型可以选择其他类型。
    2. 位段成员名后边有一个冒号和一个数字。
      struct A
      {
       int _a:2;
      };

    位段的内存分配

    1. 位段的成员可以是int、unsigned int、signed int 或者char类型等
    2. 位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
    3. 位段很多不确定因素,因此不能跨平台的,可移植程序应避免使用位段。

    我们看下一段代码:

    struct A
    {
    	int _a : 2;
    	int _b : 5;
    	int _c : 10;
    	int _d : 30;
    };
    int main()
    {
    	printf("%d\n", sizeof(struct A));
    	return 0;
    }

    A所占内存是多少?

    输出结果:

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第12张

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第13张

    位段有跨平台的问题及使用注意事项

    int位段被当成有符号还是无符号不确定

    位段最大位的数目不确定。

    位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

    当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃 剩余的位还是利用是不确定的。

    所以位段可以达到结构体同样的效果,并且可以很好的节省空间,但有跨平台问题。

    注意事项:位段内存中每个节分配一个地址,一个字节内部的bit位是没有地址的。所以不能对位段成员使用&操作符,不能使用scanf直接输入值,只能先输入放在一个变量中,然后赋值给位段的成员(使用位段结构体里面的类型尽量要一样,否则可控性就会比较差)。

    struct A
    {
    	int _a : 2;
    	int _b : 5;
    	int _c : 10;
    	int _d : 30;
    };
    int main()
    {
    	struct A sa = { 0 };
    	//scanf("%d", &sa._b);//错误
    	
    	int b = 0;
    	scanf("%d", &b);
    	sa._b = b;
    	return 0;
    }

    制作不易,求各位大佬三连qwq,若有不足的地方,请大佬们多多指点!

    C语言——结构体自定义类型,C语言中的结构体自定义类型详解 第14张


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码