23.0、C语言——结构体的剖析与使用

23.0、C语言——结构体的剖析与使用

结构体的声明:

结构体的基础知识:

结构是一些值的集合,这些值称为成员变量,结构的每个成员与数组不同,结构体的每个成员可以是不同的类型变量【但是成员变量类型不可以是自己的结构体类型】;

比如声明一个学生结构体类型->

struct Student {
	char name[20];
	char tele[12];
	char sex[10];
	short age;
}

在main()函数中定义的结构体变量都是局部变量;

全局结构体变量,定义如下:

struct Student {
	char name[20];
	char tele[12];
	char sex[10];
	short age;
}s2,s3,s4;

struct Student s5;

特殊声明方式:

匿名结构体类型 ->

struct {
	int x;
	int y;
	int z;
}a;

        1. 向上面代码描述的是匿名结构体类型,那么这种类型由于没有结构体名字,所以只能在声明结构体类型的后面直接定义结构体变量,在其他地方无法定义结构体变量;
        2. 当然这种声明的方式不建议大家去使用,这里只做了解即可;
        3. 结构体类型的指针只能存放自己结构体类型的对象地址;

结构体的自引用方式 ->

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

        1. 其实就是数据结构中的链式存储方式;
        2. 结构体中一部分存储数据,一部分存储一个指向下一个结构体元素的指针;

结构体变量的定义和初始化:

typedef struct Student {
	char name[20];
	char tele[12];
	char sex[10];
	short age;
}Student;

int main() {
	struct Student s1 = {"小澜","1355841253","男",18};
	printf("%s%s%s%d",s1.name,s1.tele,s1.sex,s1.age);
	return 0;
}

当然结构体类型中也可以包含其他的结构体类型:

typedef struct Student {
	char name[20];
	char tele[12];
	char sex[10];
	short age;
}Student;

typedef struct People {
	Student s;
	int a;
	int b;
	int c;
}People;

int main() {
	People p = { {"小澜","1355841253","男",18},1,2,3 };
	printf("%s%s%s%d",p.s.name,p.s.tele,p.s.sex,p.s.age);
	return 0;
}

结构体内存对齐:

struct S1 {
	char c1;
	int a;
	char c2;
};
struct S2 {
	char c1;
	char c2;
	int a;
};
int main() {
	struct S1 s1 = { 0 };
	printf("%d\n",sizeof(s1));
	struct S2 s2 = { 0 };
	printf("%d\n", sizeof(s2));
	return 0;
}

计算一下这两个结构占用内存空间的大小 ->

考点 如何计算?首先得掌握结构体的对齐规则:

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

        2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;【对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值;】

        VS中默认的值为 8 ;【那么有些编译器没有默认对齐数(例如 gcc 编译器),这时候变量的大小就是每次的对齐数】

        3. 结构体总大小为最大齐数(每个成员变量都有一个对齐数)的整数倍;

        4. 如果嵌套了结构体的情况,嵌套的 结构体a 对齐到自己结构体中变量的最大对齐数的整数倍处【比如说:该 结构体a 中定义了 int a; 且这个 a 的对齐数是该 结构体a 中最大的,那么该 结构体a 就按照 4 的倍数来算】,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍;

计算s1的内存大小如下图所示: 

        1. s1 算出来的大小是 12;
        2. 那么结构体类型变量 s2 的大小计算方法也同理能算出来是 8;

        3. 可以看到 s1 和 s2 的成员变量都是一样的,只是定义的顺序不一样,最终导致结构体占用的内存空间大小不一样;
        4. 可以用 #pragma pack( int ) 去修改 默认的对齐数;如果int参数什么都不写就代表取消设置的默认对齐数;

offsetof ( )  宏

offsetof 其实是宏,这里先做了解即可,在之后宏的学习章节中再给大家详细介绍;

size_t  offsetof ( structName , memberName );

        1. 返回该变量在该结构体中的偏移量;【使用的时候需要引入头文件 #include <stddef.h>】
        2. 第一个参数为 -> 结构体类型,第二个参数为 -> 该结构体的变量;
        3. 实例代码如下所示:

struct S {
	char c;
	int i;
	double d;
};
int main() {
	printf("%d\n", offsetof(struct S ,c));
	printf("%d\n", offsetof(struct S, i));
	printf("%d\n", offsetof(struct S, d));
	return 0;
}

输出的结果为:0 , 4 , 8

为什么存在内存对齐?

大部分的参考资料都是这么说的:

        1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常;

        2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐;原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问;

总的来说:

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

结构体值传递:

struct S {
	int a;
	char b;
	short c;
};
void init(struct S tmp) {
	tmp.a = 1;
	tmp.b = 'a';
	tmp.c = 2;
}
int main() {
	struct S s = { 0 };
	init(s);
	printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
	return 0;
}

        如上代码,将对象 s 传递给 init()函数 并且修改结构体对象 s 的值,但是由于这是值传递,所以 tmp 是一个临时拷贝的变量,所以修改的也只是 tmp 临时拷贝变量中的值,并没有真正的修改到 s 的值;

结构体址传递 ->

struct S {
	int a;
	char b;
	short c;
};
void init(struct S* p) {
	(*p).a = 1;
	(*p).b = 'a';
	(*p).c = 2;
}
int main() {
	struct S s = { 0 };
	init(&s);
	printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
	return 0;
}

        1. 如上代码,传址方式将 s 的地址传递到 init() 函数中通过 s 的地址去修改 s 的值,是可以修改成功的;
        2. 如果我们要传递 结构体对象 给函数,尽量使用传址的方式,因为这样更加节省空间节省资源;
        3. 如果用传值的方式,当我们的变量很大的时候,会导致需要拷贝一个很大的临时变量,很占用栈的内存空间;

总结:

        1. 函数传参的时候,参数是需要压栈,会有时间和空间上的开销;
        2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降; 
        3. 所以当结构体传参的时候要传结构体的地址;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值