2.1 结构体传参⭕
函数压栈问题
结构体传参时,结构体压栈所需要的空间开销就会大一些
若是传地址,无非就是4/8byte,开销会大大减少
例子1:
struct S { int arr[100]; int n; }; void print1(struct S ss) { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", ss.arr[i]); } printf("\n%d\n", ss.n); } void print2(struct S* ps) { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf("\n%d\n", ps->n); } int main() { struct S s = { {1,2,3,4,5}, 100 }; print1(s); print2(&s); return 0; }
例子2:
struct S { int data[1000]; int num; }; struct S s = {{1,2,3,4}, 1000}; //结构体传参 void print1(struct S s)//形参是实参的一份临时拷贝,不会改变实参 { printf("%d\n", s.num); } //结构体地址传参 void print2(struct S* ps)//这里加一个const避免不小心改掉实参 { printf("%d\n", ps->num); } int main() { print1(s); //传结构体 print2(&s); //传地址 return 0; }
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
结论:
结构体传参的时候,要传结构体的地址。
二.位段
2.1 什么是位段⁉️
位段 :二进制位
位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int 、 unsigned int 或 signed int 。
2. 位段的成员名后边有一个冒号和一个数字。
比如:
//位段 - 二进制位 struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };//47bit --> 6*8=48 --> 6byte ? int main() { struct A sa = { 0 }; printf("%d\n", sizeof(sa)); return 0; }
A就是一个位段类型。以上结果猜测为47bit,可是结果却是8byte -->64bit,还浪费了空间?
注意:
struct A
{
int _a;
int _b;
int _c ;
int _d;
};//这里面是结构体成员
这里每一个int 占4byte,总共16byte -->128bit对比64bit都节省了一半的空间
2.2 位段的内存分配❗
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
空间是如何开辟的?
列出下列代码
struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main() { struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0; }
假设:
内存开辟如下:
整体思路:先开辟一个字节的空间,如果放不下就逐渐向高地址开辟,证实了假设是正确的
2.3 位段的跨平台问题➰
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。( 16 位机器最大 16 , 32 位机器最大 32 ,写成 27 ,在 16 位机
器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
2.4 位段的应用✅
三. 枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举
性别有:男、女、保密,也可以一一列举。
月份有 12 个月,也可以一一列举
这里就可以使用枚举了
3.1 枚举类型的定义❎
enum Day//星期
{
Mon ,
Tues ,
Wed ,
Thur ,
Fri ,
Sat ,
Sun
};
enum Sex // 性别
{
MALE ,
FEMALE ,
SECRET
} ;
enum Color // 颜色
{
RED ,
GREEN ,
BLUE
};
- 以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
- {}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0 开始,一次递增 1 ,当然在定义的时候也可以赋初值
enum Color // 颜色
{
RED = 1 ,
GREEN = 2 ,
BLUE = 4
};
#include<stdio.h> enum Sex { MALE, FEMALE, SECRET }; int main() { enum Sex s = FEMALE;//1,若赋值成1在c语言中是没问题的,在c++会出现错误,就拿上面定义的可能取值给它赋值 printf("%d\n", MALE); printf("%d\n", FEMALE); printf("%d\n", SECRET); }
3.2 枚举的优点❗
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
enum OPTION { EXIT,//表示0 PLAY,//1 ADD,//2 DEL//3 };
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
#define EXIT 0 #define PLAY 1
- #define宏常量是在预编译阶段进行简单替换,枚举常量则是在编译的时候确定其值。
3. 防止了命名污染(封装)
4. 便于调试
- 一般在编译器里,可以调试枚举常量,但是不能调试宏常量
5. 使用方便,一次可以定义多个常量
- 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个
- 枚举可以自增1,这样不用每一个值都定义,而宏必须每个值都定义
- 枚举是一个集合,代表一类值,像你代码中的颜色归为一类,方便使用,而宏不能形成集合。
四. 联合(共用体)
4.1 联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
union Un { char c; int i; }; int main() { union Un u; printf("%d\n", sizeof(u)); printf("%p\n", &u); printf("%p\n", &(u.i)); printf("%p\n", &(u.c)); return 0; }
4.2 联合的特点⭕
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
面试题: 判断当前计算机的大小端存储
union Un { char c; int i; }; int main() { union Un u; u.i = 1; if (u.c == 1) printf("小端\n"); else printf("大端\n"); return 0; }
4.3 联合大小的计算 ⭕
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
先放一个char数组,再放一个int变量
union Un { char arr[5];//1 4 1 -->共5个字节 int n;// 4 8 4 }; int main() { printf("%d\n",sizeof(union Un)); return 0; }
先放一个short数组,再放一个int变量
union Un { short s[7]; int n; }; int main() { printf("%d\n", sizeof(union Un)); return 0; }
同理:结果必须也是4的倍数,short数组已经占了14个byte,所以联合体大小为16byte
本章完。欢迎各位大佬补充!