【C语言】自定义类型全家桶(值得收藏)

【C语言】自定义类型全家桶(值得收藏),第1张

【C语言】自定义类型全家桶(值得收藏)

目录

前言

结构体类型

结构体的声明

结构体变量的定义与初始化

结构体的自引用

结构体的访问

结构体的传参

 传结构体

传地址

结构体的内存对齐

位段

位段的声明

位段的内存管理

位段的跨平台性

 枚举类型

枚举类型的定义

枚举类型赋予初始值

枚举类型的优点

联合体类型

联合体的定义

联合体的特点

 联合体内存大小的计算


前言

初学C语言

我们先接触的都是内置的类型

比如说int char short float double long等等

这一期就来聊一聊自定义类型的知识

结构体类型

首先我们要知道什么是结构体

结构体就是各种值集合

这些值被称作结构体成员,这些成员可包括各种不同的类型

struct tag         //这里的struct是结构体的关键字,tag是结构体标签,也就是结构体的名称
{
	number - list; //结构体成员列表
}veriable-list;    //结构体的变量 
结构体的声明

如果结构体的标签是student,我拿student来举例子

结构体的完整声明

struct Student
{
    char name[20];//姓名
    char sex;//性别
    int age;//年龄
    int num;//学号
};   //这里的分号不能丢

结构体的不完全声明(匿名结构体类型)

struct
{
	int a;
	char b;
	double c;
}s;   //这s不能省略

匿名结构体的特点就是没有结构体标签

但这样写用户使用时只能使用一次,也就是说只能在结构体声明时就定义变量

因为你找不到结构体标签,就相当于找不到门牌号一样,无法再对其定义一个变量

结构体变量的定义与初始化

结构体的定义大致分为三种情况

<1>结构体声明的同时定义

struct Student 
{
    char name[20];//姓名
    char sex[20];//性别
    int age;//年龄
}s={"zhangsan","nan",20};

<2>结构体先声明,再定义

#include
struct Student
{
    char name[20];//姓名
    char sex;//性别
    int age;//年龄
    int num;//学号
};


int main()
{
    struct Student s = { "zhangsan",'w',20,111 };
	return 0;
}

<3>匿名结构体定义

struct 
{
    char name[20];//姓名
    char sex[20];//性别
    int age;//年龄
} s = { "zhangsan","nan",20};

注意:结构体初始化与数组相同,都必须整体进行赋值。

结构体的自引用
struct Node //初始话链表
{
	int a;
	struct Node next;
};

结构体的自引用就是结构体再套用自己

学过数据结构的朋友应该知道这是初始化链表

不过这一个代码有问题的

问题在于无法求出这个结构体的大小,不清楚这个结构体有多大,因为无法求出自引用的结构体有多大

所有自引用的结构体要用指针来访问

struct Node //初始话链表
{
	int a;
	struct Node* next;
};

故就可以通过指针来访问每一个结点

结构体的访问

当结构体定义且变量初始化完成后,就可以通过 *** 作符来访问变量中的成员

当然,这里给出了两个 *** 作符

分别是  . *** 作符和 -> *** 作符

当使用结构体变量时,就用点 *** 作符,当访问结构体指针变量就用箭头 *** 作符

(1)通过结构体变量进行访问:

printf("%sn",s.name);

(2)通过结构体指针进行访问:

printf("%sn",ps->name);
结构体的传参

函数的调用有时候需要传一些参数

参数的类型可以是不同的类型,可以是数组,也可以是指针

同样结构体的传参也可通过传结构体或者传指针

 传结构体
#include  
struct tag
{
	int a;
	char b[20];
}s1 = { 100,"abcdef" };
void print()
{
	printf("%d", s1.a);
}
int main()
{
	print(s1);
	return 0;
}
传地址
#include
struct tag
{
	int a;
	char b[20];
}s2 = { 100,"abcdef" };
void print(struct tag*s2)
{
	printf("%d", s2->a);
}
int main()
{
	print(&s2);
	return 0;
}

我们要知道函数传参是形参就是实参的临时拷贝

参数是要压栈的(向系统申请空间),既然是临时拷贝,就会再次再栈上开辟空间,当实参足够大时,显然会浪费一定的空间和时间

相比较与传结构体,传指针会更好 

结构体的内存对齐(强烈建议观看)

在另外一篇文章详细讲过——【C语言系列】-结构体中的内存对齐

位段

可能有人没有听过什么是位段

位段的结构类型跟结构体有些类似可以类似结构体去学习

也可以说

位段是结构体特殊的实现

位段的声明

相较于结构体,位段的声明有两点不同

<1>规定位段的成员的必须是int,unsigned int ,signed int (但是写成char类型也没什么大的问题)

<2>位段的成员后面有一个冒号和一个数字

struct A  //位段的声明
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
位段的内存管理
#include
struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};
int main()
{
	
	printf("大小是%d字节", sizeof(struct A));
	return 0;
}

为什么位段的大小是八个字节呢?

成员内包含的数字代表的是这个成员需要利用的内存,单位是bit。

位段成员申请内存时都是以四个字节或者一个字节单位(当成员是char类型时)

    int a : 2;    //申请4个字节,也就是32个bit位,利用两个还剩30个
	int b : 5;    //利用5个,还剩25个
	int c : 10;   //利用10个,还剩15个
	int d : 30;   //这里的十五不够,所以再申请了4个字节

最终的结果就是八字节

但问题是,变量d利用的空间是留下的15个bit加上重新申请的空间呢

这个结果在不同的环境的结果是不同的,所以位段的跨平台性比较差

位段使用的前提是你知道存储的内存大概有多大

就比如说年龄

十个bit位0111111111,最大值就可以达到1023

就不需要再申请一次利用一个int类型的空间大小

这就达到了节省内存的作用,存在即合理

位段的应用 

struct A
{
	char a : 3;
	char b : 4;
	char c : 5;
};
main()
{
	struct A s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	return 0;
}

              

位段的跨平台性

1.int位段被当成有符号数还是无符号数是不确定的,有时候系统会自动转化为无符号整形。


 2.位段中最大位的数目不能确定。(因为在早期的16位机器int最大16,而32位机器int最大32)


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

意思当内存一个字节00000000,存入01010,可能会出现00001010或者01010000


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

 枚举类型

枚举类型适用于可以一一列举的类型

比如说星期、性别

枚举类型的定义
enum Day   //星期
{
	//枚举的可能取值
	Mon,
	Tues,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};
enum Sex  //性别
{
	MALE,//0
	FEMALE,//1
	SECRET//2
};

枚举类型是有初始值的

如果你没赋予它初始值,就会默认从零开始,依次加一

枚举类型赋予初始值
#include
enum Sex  //性别
{
	MALE = 4,
	FEMALE=10,
	SECRET//
};
main()
{
	printf("%d %d %d", MALE,FEMALE,SECRET);
	return 0;
}

可以看到,其实默认的值是可以改的

当某个成员被赋予某个值的时候,后续的成员就在此基础上加一

枚举类型的优点

1.相较于数字,枚举增加代码的可读性和可维护性


2.和#define定义的标识符比较枚举有类型检查,更加严谨。


3.防止了命名污染(封装)


4.便于调试


5.使用方便,一次可以定义多个常量

联合体类型

联合体类型也叫共用体

联合体的定义
union Un  
{
	int a;
	char b;
};

union是联合体关键字,Un是联合体的标签

联合体的特点

共用体,顾名思义,这里的共用就是公用内存

内存的也可被折叠

#include
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u = {0};
	printf("%dn", sizeof(u));
	printf("%pn", &u);
	printf("%pn", &(u.c));
	printf("%pn", &(u.i));
	return 0;
}

 他们有相同的地址

c和i存放在同一块内存空间上,修改c或者i都会影响到另一个成员。

 联合体内存大小的计算

 <1>联合体内存大小是最大成员的大小

<2>最大成员的大小如果不是最大对齐数的整数倍,就会对齐到最大对齐数的整数倍

(联合体也存在内存对齐)

#include
union Un1
{
	char c[5];  
	int i;
};          
//Un1成员最大成员大小5,最大对齐数是4,所以Un1的大小是8;
union Un2
{
	char c[7];
		int i;
};
//Un2成员最大成员大小7,最大对齐数是4,所以Un2的大小是8;
int main()
{
	printf("%dn", sizeof(union Un1));
	printf("%dn", sizeof(union Un2));
	return 0;
}

  欢迎点赞收藏加关注,如若有问题可以提出来 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5714567.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存