目录
结构体
结构体的定义
匿名结构体
结构体的自引用
结构体大小计算
位段
枚举
枚举概念
枚举的声明与使用
枚举大小计算
枚举与宏的区别
联合体
联合体的概念
联合体的声明与使用
联合体大小计算
结构体
结构体的定义
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
struct tag//标签{member-list;//成员列表}variable-list;//变量列表//注意变量列表后面的分号
//结构体struct Stu{char name[20];//姓名char sex[5];//性别char id[20];//学号int age;//年龄double grade;//成绩}student;//这里的student就是struct Stu类型创建的变量。struct Stu teacher;//这样创建变量的方法也是可以的。
其中struct Stu是自定义结构体类型,花括号内的一系列数据类型称为结构体成员,student为定义的结构体类型变量。
匿名结构体
//匿名结构体类型struct{int a;char b;float c; }x;struct{int a;char b;float c;}a[20], *p;//在上面代码的基础上,下面的代码合法吗?p = &x;
在声明结构的时候,可以不完全的声明,上面的两个结构在声明的时候省略掉了结构体标签。在上面代码的基础上,下面的代码是不合法的。因为,当两个结构体的类型相同时,才可以用同类型的结构体指针来接收同类型的结构体地址。但是,这两个是匿名结构体,特殊的声明,虽然结构体的成员都是一样的,但是在编译期看来,这两个结构体是不一样的。
结构体的自引用
//错误示例struct Node{int data;struct Node next;};
直接将结构体嵌套在相同结构体是不可取的,你可以想象一下,如果可以这么做,那这个结构体的大小是多少,他会无限制地嵌套下去,导致栈溢出,如果硬是需要嵌套自身,应该嵌套相同结构体类型的指针。
//正确示例struct Node{int data;//数据域struct Node* next;//指针域};
用typedef定义新类型名来代替已有类型名,即给已有类型重新命名。
举例说明:
typedef int Elem; typedef struct{int date;..........}STUDENT;STUDENT stu1,stu2;
使用typedef来命名一个结构体类型是可以省略的,因为typedef引入了标签;上面代码中的STUDENT不是结构体变量,而是创建了一个使用的标签。在这基础上,我们来看下面的代码。
typedef struct{int data;Node* next;}Node;
这个代码中,使用typedef将结构体重命名为Node,但是在结构体内部却先使用了Node这个结构体类型名,所以这个代码是错误的。
typedef struct Node{int data;struct Node* next;}Node;
这才是正确的代码。如果我们要用到自定义结构体的时候,我们需要把标签也就是结构体类型名先定义出来。
结构体的使用与传参
#include <stdio.h>int main(){struct Stu s = { "张三","男","0618",20,99 };//结构体声明与初始化printf("姓名:%s\n", s.name);//结构体成员访问方式1:结构体变量名.结构体成员名printf("性别:%s\n", s.sex);struct Stu* ps = &s;printf("学号:%s\n", ps->id);//结构体访问成员方式2:结构体指针->结构体成员名printf("年龄:%d\n", ps->age);printf("C语言考试成绩:%.2lf\n", ps->grade);return 0;}
输出结果
姓名:张三性别:男学号:06龄:20C语言考试成绩:99.00D:\gtee\C-learning-code-and-project\test_922\Debug\test_922.exe (进程 26272)已退出,代码为 0。按任意键关闭此窗口. . .
对于结构体传参,尽量使用结构体指针传参,因为直接传结构体,当结构体很大时,会占用大量栈区的空间,甚至导致栈溢出。
结构体大小计算
我们先介绍第一个概念,偏移量。
计算机汇编语言中的偏移量定义为:把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为"有效地址或偏移量"。
接下来开始讲解计算如何计算结构体大小。
结构体的大小不是将所有成员变量的大小加起来这么简单,实际上在内存中各成员是需要对齐的,在vs编译器中默认对齐值为8,在其他的编译环境中可能没有默认值,也可以默认值是其他的值。
当然,这个默认对齐值是可以修改的,可以使用"pragma pack()"修改。
如果对齐数为1,相当于结构体没有内存对齐。
#pragma pack(4)//设置默认对齐数为4#pragma pack()//无参数表示默认值为8(vs编译器)
结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
这里举两个例子。
struct S3{ //默认对齐数 自身大小 对齐数double d;//8 8 8char c; //8 1 1int i; //8 4 4};//结构体嵌套问题struct S4{char c1; //8 1 1struct S3 s3; double d;//8 8 8};
对于S3,第一个成员总是放在结构体偏移量为0的位置。所以偏移量0-7的位置放的是double类型的d成员。char c的对齐数位1。所以放在偏移量为8的地址处就可以了。int i的对齐数是4,除了第一个成员变量要对齐到某个数字(对齐数)的整数倍的地址处。int i就只能放在偏移量为12的地址处,并且占4个字节。如下图所示,S3共占16个字节。
对于结构体S4,char c1是第一个成员,所以它放在结构体的起始位置,也就是偏移量为0的位置,占一个字节。如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。S3的最大对齐数是8,所以S3只能放在偏移量为8的地址,且占16个字节。最后一个double d对齐数是8,放在偏移量为24的地址,占8个字节。S4的大小为32个字节。
那为什么需要对内存对齐呢?
1、平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
位段
位段,也称位域,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段"或称"位域"( bit field) 。利用位段能够用较少的位数存储数据。
位段的声明和结构是类似的,有两个不同
1、位段的成员必须是 int、unsigned int 或signed int 。
2、位段的成员名后边有一个冒号和一个数字。
struct A {int _a:2;int _b:5;int _c:10;int _d:30;};
那位段的在内存中占多少字节呢?
位段的内存分配
1、位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2、位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
在上面代码中的位段中,先开辟的是4个字节的大小空间,也就是32个bit位。前面的三个成员共占17个bit位,在一个字节里面,可以将这三个成员放下。但是还剩下15个bit位不够放下成员_d。所以需要再开辟四个字节,用来存放成员_d。所以这个位段的大小是8个字节。
我们已经知道了位段是如何开辟内存的,接下来再来看一段代码,了解位段是如何存放数据的。
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;}
内存中开辟的空间如图所示。s.a = 10;s.b = 12;a的二进制表示为1010,b的二进制表示为1100,但是a在位段中只分配了3个bit位。所以,只能存放后三位010。
同理,c和d在内存中如图所示。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用:
枚举
枚举概念
enum,枚举在C/C++/c#,还有Objective-C中,是一个被命名的整型常数的集合,枚举在日常生活中很常见。例如表示星期的SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY, 就是一个枚举。枚举的说明与结构和联合相似。
枚举的声明与使用
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,GREEN=5,BLUE};
枚举常量,默认第一个成员是0,从后面依此递增。但是,成员定义的时候可以赋初值,GREEN = 5,从这个成员之后,在这个GREEN的基础之上,依此递增,也就是BLUE = 6。
枚举的使用
enum Color//颜色{RED=1,GREEN=2,BLUE=4};enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。clr = 5;//错误,枚举常量不可被赋值
枚举大小计算
枚举变量的大小还是枚举成员的大小,都是存储一个整型的数据。所以枚举类型变量占4个字节。
#define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>enum Color//颜色{RED,GREEN = 5,BLUE};int main(){enum Color clr;printf("%d\n", sizeof(clr));printf("%d\n", sizeof(RED));printf("%d\n", sizeof(GREEN));printf("%d\n", sizeof(BLUE));return 0;}
4444F:\VS编程文件\CProject\9月\test_9_17\Debug\test_9_17.exe (进程 13880)已退出,代码为 0。按任意键关闭此窗口. . .
枚举与宏的区别
使用枚举定义的枚举常量是有类型的,为枚举类型,而使用#define宏是替换,并没有枚举类型这种性质。
我们可以使用 #define定义常量,为什么非要使用枚举?
枚举的优点:
增加代码的可读性和可维护性
和#define定义的标识符比较枚举有类型检查,更加严谨。
防止了命名污染(封装)。
便于调试。
使用方便,一次可以定义多个常量。
联合体
联合体的概念
在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作"共用体"类型结构,简称共用体,也叫联合体。
联合体的声明与使用
关键字 union
联合也是一种特殊的自定义类型.
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫公用体)。
//联合类型的声明union Un{char c;int i;};//联合变量的定义union Un un;
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
使用联合体判断大小端:
int is_bl(){union BL{char a;int b;}un;un.b = 1;//返回1为小端,返回0位大端if (un.a == 1){return 1;}else{return 0;}}int main(){int ret = is_bl();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;}
输出结果为:
小端F:\VS编程文件\CProject\9月\test_9_17\Debug\test_9_17.exe (进程 12580)已退出,代码为 0。按任意键关闭此窗口. . .
联合体大小计算
1、联合的大小至少是最大成员的大小。
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1{char c[5];int i;};union Un2{short c[7];int i;};//下面输出的结果是什么?printf("%d\n", sizeof(union Un1));printf("%d\n", sizeof(union Un2));
联合体Un1最大成员大小为5,char类型对齐数为1,int类型对齐数为4,所以最大对齐数为4,联合体大小应为最大对齐数的整数倍,大小为8。
联合体Un2最大成员大小为14,short类型对齐数为2,int类型对齐数为4,所以最大对齐数为4,联合体大小应为最大对齐数的整数倍,大小为16。
816F:\VS编程文件\CProject\9月\test_9_17\Debug\test_9_17.exe (进程 15160)已退出,代码为 0。按任意键关闭此窗口. . .