【C语言】且听我慢慢分析 自定义类型

【C语言】且听我慢慢分析 自定义类型,第1张

目录

结构体

结构体的声明 定义 及初始化

特殊的结构体

结构体的自引用

结构体的内存对齐

结构体的对齐规则:

offsetof 

修改默认对齐数

#pragma pack( ) 

结构体传参

 位段

声明方法

注意事项

位段的内存分配

枚举

联合体(共用体)

声明与定义

特点

联合体的大小


结构体

为什么会出现结构体?结构体能做什么?有什么优点?

且听我慢慢分析。


当我们想了解一本书的基本信息时,我们往往需要这样做:

我们需要敲出四个scanf来输入基本信息,很麻烦;又或者是一个scanf里跟了一堆需要输入的变量, 不美观;此时我们就会想,有没有即简单又美观的方法呢?结构体便应运而生了。


结构体的声明 定义 及初始化

当然我们也可以这样创建变量: 

但是请记住,这样创建的是全局变量,而刚才是在函数内创建的bk1,是局部变量。


 

如果我们觉得struct book bk1这种创建结构体变量的方法太麻烦,总带有struct book,也可以用typedef进行类型重命名:

特殊的结构体

因为它没有标签,所以这是匿名结构体,只能用一次。


 

 请问在匿名结构体成员变量相同的情况下,所创建的两个结构体相同吗?

p = &person;

合法吗? 

答案是非法的,原因是在编译期看来这是两个不同的类型。


结构体的自引用

struct Node
{
    int age;
    struct Node next;
};

int main()
{
    int size = sizeof(struct Node);     //?????能计算出来吗?多大呢?
    return 0;
}

我们发现, 结构体struct Node中又嵌套了一个struct Node,而struct Node中还会有,这样下去就无限递归下去了,这没办法运算呀!所以,我们在自引用的时候就发生了错误。


正确方法为:

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

通过地址找到下一个,不想继续找就放入空指针,才为正确的自引用方式。


 

结构体的内存对齐

 请问,输出的结果是什么呢?

为什么会是8和12呢?为什么只是成员变量先后顺序变了大小就变了呢???这是为什么呢??

答案是,结构体存在内存对齐。


(用空间换取时间)

结构体的对齐规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。



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



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



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



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


 

接下来我们来讲解上图中的原因:

因此,按照相同的算法,第二个结构体:

和大家说一下,Linux环境下没有默认对齐数,其自身大小便为默认对齐数。


offsetof 

计算结构体成员相对于首地址的偏移量

size_t offsetof( structName, memberName );

头文件:

求嵌套结构体的大小: 

 讲解:

首先温习一下规则四:

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


 

修改默认对齐数
#pragma pack( ) 

 

结构体传参

struct inform
{
    int i;
    char a;
    char b;
};
struct inform s1 = { 1,'a','b' };

void print1(struct inform* s1)
{
    ;
}

void print2(struct inform s1)
{
    ;
}

int main()
{
    print1(&s1);
    print2(s1);

    return 0;
}

是传参还是传址呢?? 

答案是:print1,传址。


原因:

函数传参的时候,参数会压栈,会有时间和空间上的系统开销。


如果传递的结构体过大,参数压栈的系统开销比较大,会造成性能下降。


 位段 声明方法

//位段
struct inform
{
    int a : 3;
    int b : 4;
    int c : 5;
};

注意事项

①成员必须为int ,unsigned int,signed int 

             或char,unsigned char, signed char。


②成员名后加冒号和数字

提问:

printf("%d\n", sizeof(struct inform));

位段的内存分配
//位段
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
struct S s = { 0 };


int main()
{
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

说明在VS2019中,字节内部的分配方式是从右向左的 

注:位段的存在就是为了节省空间,所以不存在内存对齐

枚举

默认值起始值为0,我们也可以给其初始化。


enum week
{
	MONDAY = 1,
	TUSDAY,
	WEDNESDAY,
	THUSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
};

 

但是我们不能这样:

enum week day = 2;//2是int类型的 而day是enum week类型的

类型不兼容

同时我们也不能这样:

TUSDAY = 3;

联合体(共用体)

关键字:union

大家看到名字有没有联想到什么呢?为什么叫共用体呢???共用是不是体现在内存中呢?是否有这种感觉呢?

没错,联合体的特点就是成员变量共用同一块内存空间。


声明与定义
union on//联合类型声明
{
	int i;
	char c;
};

int main()
{
	union on cn;//联合变量定义
	return 0;
}
特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。


因为共用同一块空间,因此在更改其中一个变量时,会将另一个变量也改掉,因为应用场景往往是使用其中一个变量时,其余变量不使用。


我们先证明一下他们共用同一块空间吧:

因此我们可以利用这个特点,改造一下判断机器大小端的代码:

void check()
{
	union ck
	{
		char c;
		int i;
	}k;
	k.i = 1;
	if (k.c == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	
}
int main()
{
	check();
	return 0;
}

 运行结果:

联合体的大小
union size
{
	int i;//对齐数为4
	char c;//对齐数为1
	//最大对齐数为4  刚好为4的整数倍 
};
union size sz;
int main()
{
	printf("%d\n", sizeof(sz));//4
	return 0;
}
union size
{
	short arr[7];//对齐数为数组元素类型 short为2 对齐数为2
	int a;//对齐数为1  因此最终大小应该是最大对齐数4的整数倍 为16
};

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

原文地址: http://www.outofmemory.cn/langs/563946.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-03
下一篇 2022-04-03

发表评论

登录后才能评论

评论列表(0条)

保存