自定义类型:结构体,枚举,联合(上)
创始人
2025-05-30 14:44:24
0

一、结构体

1.结构体类型的声明

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
struct tag { member-list; }variable-list;

1)定义学生类型

//*************  定义学生类型
struct Stu
{//成员变量char name[20];int age;float weight;
}s4,s5,s6;//全局变量
int main()
{struct Stu s1;//局部变量struct Stu s2;struct Stu s3 = { "akai",20,50.6 };return 0;
}

2) 特殊的声明 - 匿名结构体类型

在声明结构的时候,可以不完全的声明。没有名字的结构体

正常定义结构体会命名 例如: strcut Stu{...};
但是匿名结构体只能用这一次,相当于一次性用品,用一次,之后就不能用了


struct
{char c;int a;double d;
}s1,s2;

2.1)错误示范:有的编译可能没有报错,可能是因为编译器优化了,

但本身这种写法是错误的

//错误例子:
//创建一个匿名结构体
struct
{char c;int a;double d;
}s1;
//再创建一个匿名结构体
struct
{char c;int a;double d;
}* ps;//创建结构体指针
int main()
{//在上面代码的基础上,下面的代码合法吗?ps = &s1;//error//编译器会把上面的两个声明当成完全不同的两个类型。//所以是非法的。return 0;
}

3)易错点(初始化之后的修改赋值)

结构体里的 char xx[20];在初始化之后,是无法直接赋值的!!!

s1.name = "aaaa"; //error - 左值无法改变
name是一个是一个地址,地址是一个常量值,无法被改变

struct S
{char name[20];char ch;int n;
};
int main()
{struct S s1 = { "haha",'z',3 };s1.ch = 'k';s1.n = 6;//s1.name = "aaaa"; //error - 左值无法改变//name是一个是一个地址,地址是一个常量值,无法被改变return 0;
}

2.结构的自引用

1)错误示范:

原因:自己类型的结构体不能包含自己类型的结构体变量 正在创建结构体Node时(其实还没有创建),在结构体成员里写了Node(不存在)
无限循环下去了,这个结构体节点大小无法求出来
//错误示范:
//struct Node
//{
//	int data;
//	struct Node n;
//	//错误	C2079	“n”使用未定义的 struct“Node”	
//
//};

 

2)正确使用

正确使用:自己类型的结构体是可以包含自己类型的结构体指针(结构体自引用)
当有一些数据不是连续存放的时候(数据结构里的链表存储方式),
可以通过指针可以使得一个数据找到下一个数据


struct Node
{int data;//4struct Node* next;//4/8
};
int main()
{struct Node n1;//创建结构体类型struct Node的变量 n1struct Node n2 = { 10 ,NULL};n1.next = &n2;//n1的成员变量next指针指向了下一个数据 n2printf("%d\n", n1.next->data);//打印得到10//通过结构体变量n1里的成员变量next找到所指向的数据n2的地址,//从而通过 指针->成员变量 ,找到结构体变量n2里的成员变量data的值,10return 0;
}

2.1)typedef 自定义类型名

正确使用

//*************  自定义类型名typedef
typedef struct
{int data;char c;
}S; //给匿名结构体类型 重命名为 Stypedef struct Student
{char name[20];int age;
}Stu;//给struct Student结构体类型 重命名为 Stu

错误示范

//错误示范
typedef struct
{int data;Node* next;//error - 报错 C2061 语法错误:标识符"Node"//此时Node还没被重命名,在这里使用会有问题
}Node; 

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

1)
struct Stu
{char name[20];int age;float weight;
}s4,s5;
struct B
{float f;struct Stu s;
};
struct Stu s6;
struct Stu s3 = { "lisi",27,75.8f };//定义且初始化
int main()
{struct Stu s4 = { "hehe",20,34.5f };struct Stu s1;struct Stu s2 = {"zhangsan",20,66.6f};//定义且初始化struct B sb = { 3.14f,{"wifi",33,55.3f} };结构体嵌套初始化 - 结构体里包含结构体类型的时候,用{}包含struct Stu s7 = { .weight=45.6f,.name="haha",.age=30};//不按结构体成员顺序初始化结构体变量printf("%f,%d,%s\n", sb.f, sb.s.age, sb.s.name);//3.140000,33,wifireturn 0;
}

 2)

struct S
{char name[10];int* ptr;
};
int main()
{int a = 100;struct S s1 = { "abcdef",NULL };struct S s2 = { "abcdef",&a };printf("%s,%d", s2.name, *(s2.ptr));//abcdef,100return 0;
}

 

4.结构体内存对齐 - 计算结构体的大小

1)结构体的对齐规则:

1.结构体的第一个成员永远都放在0偏移处。

2.从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处。
这个对齐数是: 成员自身大小和默认对齐数的较小值。
*备注:
VS 环境下 默认对齐数是8
gcc 环境下 没有默认对齐数,没有默认对齐数时,对齐数就是成员自身的大小

3.当成员全部存放进去后,
结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。
如果不够,则浪费空间对齐。

4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍处。
整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含中嵌套的结构体成员中的对齐数。

2)例子

//例1
struct S
{char s[9];//0 1 2 3 4 5 6 7 8 占9字节+补7字节(9 10 11 12 13 14 15 )double d;//16 17 18 19 20 21 22 23 占8字节int n;//24 25 26 27 占4字节//16+8+4=28字节//最大为double 8字节,结构体的总大小是8的倍数,往后面补4个字节,28+4=32字节
};
int main()
{printf("%d\n", sizeof(struct S));return 0;
}//例2
struct S2
{char s[9];//0 1 2 3 4 5 6 7 8 占9字节+补3字节(9 10 11 )=12int n; //12 13 14 15 占4字节double d;//16 17 18 19 20 21 22 23 占8字节//12+4+8=24字节//最大为double 8字节,结构体的总大小是8的倍数,24刚好
};
int main()
{printf("%d\n", sizeof(struct S2));return 0;
}

 3)练习

多练几遍就很容易了

//练习1
struct S1
{char c1;//1 - 从(偏移量:0)开始int i;//4	- 从4的倍数(偏移量:4)开始 4-7 ,到此一共8字节char c2;//1 - 从(偏移量:8 )开始//此时一共9字节,但由于,最大的对其数为4//根据规则:结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。如果不够,则浪费空间对齐。//得到 12 字节
};
int main()
{printf("%d\n", sizeof(struct S1));//12return 0;
}
//练习2
struct S2
{char c1;//0char c2;//1int i;//4 -7//一共8字节,正好为最大对齐数4的倍数
};
int main()
{printf("%d\n", sizeof(struct S2));//8return 0;
}

 结构体嵌套:结构体里有一个结构体类型,求结构体的总大小

(稍微难了一点点,多分析一下就出来了)

struct S3
{double d;// 两个结构体中,最大对齐数 8char c;int i;//一共16字节
};
struct S4
{char c1;//0struct S3 s3;//包含了://double d;	//8 - 15//char c;//16//int i;//20 - 23double d;//24 - 31//共32个字节,两个结构体最大对齐数 8 的整数倍
};
int main()
{printf("%d\n", sizeof(struct S4));//32//求S4,从S4内的成员开始计算return 0;
}

 

 4)为什么要浪费空间来对齐呢?

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

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

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

 让占用空间小的成员尽量集中在一起(从而节省空间)

//让占用空间小的成员尽量集中在一起(从而节省空间)
//例如:
struct S1
{char c1;//0int i;//4-7char c2;//8//一共9个字节,最大对齐数4 的倍数为 12,所以占12个字节的空间
};
struct S2
{char c1;//0char c2;//1int i;//4-7//一共8个字节,占8个空间//比较 S1 和 S2,同样的成员变量,因为顺序不同,其实所占空间也会不同,//在这里我们创建时可以适当思考成员变量的创建顺序,//使占用空间小的成员尽量集中在一起,从而节省空间,提高效率
};

5)offsetof - 求数据类型成员变量的位偏移量


#include
struct S
{char c;//0int a;//4//一共8字节
};
int main()
{struct S s = {0};printf("%d\n", sizeof(struct S));//8printf("%d\n", offsetof(struct S, c));//0printf("%d\n", offsetof(struct S, a));//4return 0;
}

 

6)修改默认对齐数 

#pragma 这个预处理指令,可以改变我们的默认对齐数
#pragma pack(1)      //将默认对齐数设置修改为 1

#pragma pack()        //恢复为默认对齐数:vs默认对齐数为8

结论: 结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
//************   修改默认对齐数
#pragma //这个预处理指令,可以改变我们的默认对齐数
#pragma pack(1)//将默认对齐数设置修改为 1
struct S
{char c1;int i;char c2;//此时,没有对齐,直接往后创建空间,6
};
#pragma pack()//恢复为默认对齐数:vs默认对齐数为8
int main()
{printf("%d\n", sizeof(struct S));//6return 0;
}

 

 

5.结构体传参

以下print1 和 print2 函数哪个好些? 原因:
        函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
    如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
    而传地址,最多最多就是4/8字节,不会消耗太多空间。如果觉得通过地址可能会改变原来的值,可以在前面加上 const 使得 指针指向的内容无法被改变

结论:
  结构体传参的时候,要传结构体的地址。
 
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)
{printf("%d\n", ps->num);
}
int main()
{//上面的 print1 和 print2 函数哪个好些?//答案是:首选print2函数。print1(s); //传结构体print2(&s); //传地址return 0;
}

6.结构体实现位段(位段的填充&可移植性)

1)位段的声明

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是整型家族: int、unsigned int 或signed int 或者char等
2.位段的成员名后边有一个冒号一个数字
 

//位段 - 二进制比特位
struct A
{int _a : 2; int _b : 5;int _c : 10;int _d : 30;//首先int _a,一进来开辟4字节空间,也就是32个比特位//其次,_a占 2bit, _b占 5bit, _c占 10bit  - 此时32位里占用了17个bit,剩余15个bit//最后,_d占 30个bit,15bit是不够存的,所以继续开辟了一个 int 8字节 ,32位来存30bit//一共用掉了8个字节
};
int main()
{//A就是一个位段类型。那位段A的大小是多少?printf("%d\n", sizeof(struct A));//8字节return 0;
}

 2)位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段


//一个例子
struct S
{char a : 3;//开辟1个字节 - 8bit 开始存 3bitchar b : 4;//存4bit ,剩余 1bit不够存下一个变量了char c : 5;//又开辟了1字节 - 8bit 存 5bit,剩余3bit,不够了char d : 4;//再开辟1字节 - 8bit 存 4bit//一共开辟3字节
};
int main()
{struct S s = { 0 };//假设:(vs就是这种情况)//1、分配到的内存的比特位是从低位开始存(从右往左)//2、分配的内存剩余的比特位不够使用时,浪费掉s.a = 10;//1010 -3bit   010s.b = 12;//1100 -4bit	1100s.c = 3;//0011	-5bit	00011s.d = 4;//0100  -4bit	0100//01100010  00000011  00000100//十六进制:62 03 04 (调试查看:62 03 04 ,与设想一致)printf("%d\n", sizeof(struct S));//3字节return 0;
}

3)空间是如何开辟的?- 位段的跨平台问题:

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结
    跟结构相比,位段可以达到同样的效果,可以很好的节省空间,但是位段有跨平台的问题存在。尽量别用,用就要考虑好移植后平台问题。

vs的位段分配空间的方式:

1、分配到的内存的比特位是从低位开始存(从右往左)
2、分配的内存剩余的比特位不够使用时,浪费掉

 

 

二、枚举

1.枚举类型的定义

枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中: 一周的星期一到星期日是有限的7天,可以一一列举。 性别有:男、女、保密,也可以一一列举。 月份有12个月,也可以一一列举 这里就可以使用枚举了。

 

 

2.枚举的优点

3.枚举的使用

enum Sex
{//枚举的可能取值,	默认从 0开始递增1,当然在定义的时候也可以赋初值。//枚举常量MALE, FEMALE, SECRET//如果赋初值 MALE= 5从修改的位置往后递增1//FEMALE 6//SECRET 7//MALE 0//如果赋初值 FEMALE=10, 从修改的位置往后递增1,之前的不变//SECRET 11};
int main()
{enum Sex s = 1;//在C语言中可以这样修改值,但是在c++里不行//如果语法检测不严格时可以这样的,但是最好按照枚举常量来改//例如:enum Sex s = MALE;printf("%d\n", MALE);//0printf("%d\n", FEMALE);//1printf("%d\n", SECRET);//2return 0;
}

 

相关内容

热门资讯

中证A500ETF摩根(560... 8月22日,截止午间收盘,中证A500ETF摩根(560530)涨1.19%,报1.106元,成交额...
A500ETF易方达(1593... 8月22日,截止午间收盘,A500ETF易方达(159361)涨1.28%,报1.104元,成交额1...
何小鹏斥资约2.5亿港元增持小... 每经记者|孙磊    每经编辑|裴健如 8月21日晚间,小鹏汽车发布公告称,公司联...
中证500ETF基金(1593... 8月22日,截止午间收盘,中证500ETF基金(159337)涨0.94%,报1.509元,成交额2...
中证A500ETF华安(159... 8月22日,截止午间收盘,中证A500ETF华安(159359)涨1.15%,报1.139元,成交额...
科创AIETF(588790)... 8月22日,截止午间收盘,科创AIETF(588790)涨4.83%,报0.760元,成交额6.98...
创业板50ETF嘉实(1593... 8月22日,截止午间收盘,创业板50ETF嘉实(159373)涨2.61%,报1.296元,成交额1...
港股异动丨航空股大幅走低 中国... 港股航空股大幅下跌,其中,中国国航跌近7%表现最弱,中国东方航空跌近5%,中国南方航空跌超3%,美兰...
电网设备ETF(159326)... 8月22日,截止午间收盘,电网设备ETF(159326)跌0.25%,报1.198元,成交额409....
红利ETF国企(530880)... 8月22日,截止午间收盘,红利ETF国企(530880)跌0.67%,报1.034元,成交额29.0...