自定义类型:结构体详解,位段,如何传参,如何分配空间

1.结构体声明和定义,初始化

  1. 首先对结构体的定义是什么?  结构体用来描述一个复杂对象,里面可以包含多个属性(类型)  char,int,float,double,short,long 等等
  2. 结构体是一些值的集合,是成员变量,可以是很多种类型 标量 数组 指针,甚至可以是结构体
  3. 假如是一个人,人是一个复杂对象,单单用一个类型很难描述,所有有了结构体,让我们自己来定义类型

1.2结构体的声明和定义

  1. 这个是基本结构解释 
struct tag //第一个参数是表示结构体,第二个结构体的名字
 {
 member-list;//结构体成员,一个或者多个
 }variable-list;//创建结构体同时声明了一个变量,是全局变量

    2. 那么如何创建一个结构体的类型?下面创建的就是一个结构体类型,描述着各种属性

struct student
{
    char name[20];//名字
    int age;//年龄
    int high;//身高
    float weight;//体重
};

  3.竟然结构体类型创建好了,接下来就是结构体初始化,还有声明变量

  1. s1 和 s2 是在创建结构体的同时一同创建的全局变量   s2 是正好初始化了值
  2. s3 和 s1 是一个意思 都是全局变量
  3. LocalA 是结构体的初始化,按照顺序的,是局部变量的初始化
  4. 还有LocalB 另外一种,不用按照顺序初始化。不过要使用 .age等操作才可以访问到结构里的成员
struct student
{
    char name[20];//名字
    int age;//年龄
    int high;//身高
    float weight;//体重
}s1 s2 = { {"yyy"}, 18 ,170 ,75.5f};//全局变量初始化
struct student s3;//这里和s1创建的都是全局变量
int main()
{
    struct student LocalA = { {"ddd"}, 18 ,170 ,75.5f};
    struct student LocalB = { .age = 19,.high = 177,.weight = 68.8f, .name = "jiangwen" };//局部结构体变量
    //68.8 确实是编译器默认是double类型的值,假如赋值给float的,68.8f才是正确的

    printf("%s %d %d %f", LocalA.name, LocalA.age, LocalA.high, LocalA.weight);
    
    LocalA.age = 25;

    return 0;
}

1.3结构体的访问

  1. 如何获取到访问到?,并且对其进行修改和输出
  2. LocalA.age = 25;像这样的 " . "操作符也可以放到结构体并且对其修改
  3. 就是上面的printf 函数输出,注意哈:对于字符串的输出仅仅用字符的首地址就够了,至于首元素的概念以及理解。可以去看看我主页的博客

  4. 对于前面的结构体,不做过多的做解释重点还是后面内容

1.4结构体的间接访问

struct student
{
    char name[20];//名字
    int age;//年龄
    int high;//身高
    float weight;//体重
}sd = {.name = "wer"};//创建变量全局变量的同时进行赋值
{
    struct student* p1= &sd;
    printf("%s", p1->name);
	return 0;
}
  1. 结构体的地址,通过 “->”,并且用(“->”)间接引用符号对它进行访问。然后输出 wer

2.匿名结构体和结构体的自引用

  1. 匿名结构体,就是省去了结构体的名字,没有了名字就意味着只能用一次
  2. p1不用初始化也行,用 p1.a = 12;这样 赋值也可以
  3. struct
    {
    	int a;
    	int b;
    }p1 = {14, 145};//这样用
    int main()
    {
    	printf("%d %d", p1.a, p1.b);
    	return 0;
    }

  4. 你看这个结构体变量都没有都不能初始化

2.1结构体自引用

	//第一部分
    typedef struct node
	{
		int a;
        int b;
	}Node;//对这个结构体类型,重新定义类型,这个结构体还没有给值呢


    //第二部分
	struct Node //下面的两步操作等价与上面的操作
	{
		int a;
        int b;
	};
    //对这个结构体类型重新定义类型
	typedef struct node ber;
int main()
{
	Node str = { 12,10 };
	printf("%d %d", str.a, str.b);
	return 0;
}
  1. typedef 对这个类型重新命名,意思就是说重命名的这个node有了这个结构体的这个类型

  2. 使用的方法就是main函数这样

  3. 那么啥是结构体自引用?就像是链表一样全部给你链接起来通过这个地址。

  4. 为什么要链起来在结构体里面再创建一个结构体不好吗?,因为用sizeof的时候不好判断大小,假如嵌套了很多结构体怎么办?

  5. 至于怎么使用我也不太清楚,鄙人知识有限,等后面学数据结构我再来解释,这里先给个伏笔

    struct node
	{
		int a;
        struct node* be//自己包含了自己的相关信息
	};

3.结构体内存对齐

    1. 首先把第一个数放到起始位位置为0的地址处
    2. 然后去比对齐数,取小的对齐数 —>接下来就是放到对齐数的倍数处
    3. 结构体总大小就是,成员最大对齐数的整数倍
    4. 假如嵌套了结构体,嵌套结构体最大对齐数和默认对齐数比,然后放(放的是嵌套结构体)在对齐数的倍数上
    5. 结构体嵌套结构体的大小是所有最大对齐数的整数倍,当然也包括嵌套结构体里的最大对齐数
    6. 下面是例子,s2,左边是嵌套了结构体的情况,s1是结构体结算过程

3.1为什么有内存对齐

  1. 平台原因(移植原因):有些硬件平台不能随便访问地址上的数据,有些硬件平台只能在特定的地方访问特定的数据,否则会有硬件异常
  2. 性能原因:在访问数据时,如果没有内存对齐,假如是32位他可以一次访问4个字节,假如前面还放了一个char类型的,访问后面是就会访问不全,导致还要访问一次,举个例子

这样做其实就是拿空间换时间的做法

3.2修改默认对齐数

  1. #pragma 这个预处理指令,可以用来修改默认对齐数
  2. 没有修改前的总大小12,修改了默认对齐数,之后总大小6
  3. #prama pack(1),意思是把默认对齐修改为1           #prama pack()关闭修改
  4. #pragma pack(1)
    struct S1
    {
        char c1;
        int i;
        char c2;
    }s1;
    #pragma pack()
    int  main()
    {
        printf("%zd", sizeof(s1));
    	return 0;
    }

4.结构体传参

  1. 这里先介意不用先看代码,主要想表达的意思是,当函数调用结构体的时候,是结构体拷贝过去更好呢? ,还是结构体传地址更好呢?
  2. 个人解释:可以看到,结构体里面有个int数组,假如是传值调用也就是拷贝过去,传递数组的过程中由于数组太大了,就会导致及浪费时间也浪费空间
  3. 不像地址,地址大小无非就是4/8个字节大小,往往这样会更加节约空间和时间
  4. 还有一种情况就是假如,传过去的值被修改怎么办,不用慌 就用个const修饰一下嘛
  5. 偏专业一点的解释:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  6. 传参的时候结构体过大,就会导致压榨开销大,导致性能下降
  7. 所以 传地址更好,地址只有4/8个字节
  8. struct str
    {
    	int a[1000];
    	char* name;
    	int height;
    };
    struct str pe = { {1,2,3,4,5},"yyw",170};
    //结构体传参
    void print1(struct str pe)
    {
    	for (int i = 0; i < 5; i++)
    	{
    		printf("%d ", pe.a[i]);
    	}
    	printf("%s\n", pe.name);
    	printf("%d\n", pe.height);
    }
    //结构体传地址
    void print2(const struct str *p)//const在 * 左边修饰指针指向的值不可以被更改
    {
    	for (int i = 0; i < 5; i++)
    	{
    		printf("%d ", p->a[i]);
    	}
    	printf("%s\n", p->name);
    	printf("%d\n", p->height);
    }
    int main()
    {
    	print1(pe);
    	print2(&pe);
    	return 0;
    }

5.结构体实现位段(位域)

1. 什么是位段

  1. 位段的成员必须是int unsigned int 或者 signed int ,C99中位段的成员的类型也可以是其他的
  2. 尾段成员名后面有一个冒号和一个数字 ,_a : 7;表示开辟7个bit位节空间
  3. 下面例子大小是8,位段:假设int类型的,先给你4个字节用,用完再申请4个字节的空间 ,下面计算的总大小是47个字节,因此需要两个int类型的大小
  4. struct A
    {
    	int _a : 7;
    	int _b : 3;
    	int _c : 15;
    	int _d : 22;
    };

2. 位段的内存分配

  1. 位段成员 int,unsigned int ,signed int 或者是 char 等类型
  2. 位段是需要以4个字节(int)或者1个字节(char)的方式来开辟
  3. 位段的不确定因素太多,不适合跨平台,要避免使用
struct a
{
	char a : 5;
	char b : 3;
	char c : 7;
	char d : 1;
};
int main()
{
	struct a Wd = { 0 };
	Wd.a = 31;
	Wd.b = 7;
	Wd.c = 2;
	Wd.d = 1;
	printf("%zd\n", sizeof(struct a));
	return 0;
}
  1. 先对代码进行解释:a 开辟的空间是 5个bit位,下面b c d开辟方式都是一样的
  2. 竟然只开辟这么些空间,那就必定会导致溢出,怎么溢出的呢,来看图
  3. 因为内存是怎么开辟的具体没有定义,在vs中是以从后向前的开辟的
  4. 橙色是a开辟的空间,红色是b开辟的,那黄色和粉色就不用说了吧
  5. 当给a赋值的时候,就发现31的二进制是11111 巧了刚好能存下来,但是到b这里就不行了b的二进制是1000,可是我只给b开辟了三个bit位的空间啊,这个时候就导致了溢出,因为存不下了,后面字节分析吧,粉色是c。

3. 位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数,到底怎么样不知道
  2. 位段中的最大位的的数目无法确定,假如在16位机器上可能会出问题的,因为16位机器上int 是2个字节,32位和64位是int长度是64个字节
  3. 位段在内存里面分配空间时,不知道是从左向右还是从右向左分配,标准没有定义
  4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,没办法容纳第一个位段的剩余位时,舍弃还是保留呢?不确定
  5. 总结:跟结构相比,位段也可以达到同样的效果,还可以节省空间,但是有跨平台问题

4. 位段的应用:IP数据报,严格划分了空间,对网络的通常有帮助

5. 位段使用的注意事项

  1. 不能对位段成员取地址&谁都不可以,因为有可能位段成员共用一块空间,共用一块地址
  2. 位段的成员尽量是同一种类型
  3. 可以先把值给变量,然后再赋值给位段,不能直接取位段的成员

加油!!!

  • 30
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值