【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<stdio.h>
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("%s\n",s.name);

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

printf("%s\n",ps->name);

📢结构体的传参

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

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

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

 📢传结构体

#include<stdio.h>  
struct tag
{
	int a;
	char b[20];
}s1 = { 100,"abcdef" };
void print()
{
	printf("%d", s1.a);
}
int main()
{
	print(s1);
	return 0;
}

📢传地址

#include<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u = {0};
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

 他们有相同的地址

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

 📢联合体内存大小的计算

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

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

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

#include<stdio.h>
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("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

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

评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值