目录
一、结构体
接之前的【初始结构体】。
1.1 结构体类型的声明
struct tag //tag - 自己设定
{
member-list; //成员列表
}variable-list; //变量列表,可有可无
基于此,我们可以对复杂对象进行描述,代码如下:
#include<stdio.h>
struct Student
{
char name[20];
int age;
char sex[5];
float score;
}s1,s2,s3;//s1,s2,s3是三个结构体变量 - 全局变量
struct Book
{
char name[20];
char author[12];
float price;
};
int main()
{
struct Student s4, s5, s6;//s4,s5,s6也是三个结构体变量 - 局部变量
return 0;
}
1.2 特殊声明
就是在声明结构体的时候,可以把结构体的名字省略掉,即匿名结构体类型。
写法1:
struct Book
{
char name[20];
char author[12];
float price;
}b1; //使用结构体类型的时候创建了b1
写法2:
struct//在创建结构体类型的时候顺带创建了b1,这时就可以把book删掉,就是匿名结构体类型,但只能用一次
{
char name[20];
char author[12];
float price;
}b1;
注意:在创建匿名结构体的时候,不能省略变量名。
易混淆的点:
在刚开始学习结构体的时候,总是认为结构体 struct 后边已经加上 Book ,误认为 Book 是变量名,后边 b1 又重命名。是因为之前形成了固有思维,例如:认为创建整型变量int a,就是类型+变量名,然后带入到结构体,就认为struct Book也是类型+变量名,这是一个误区。实际上,struct是声明结构体的关键字,结构体类型名是Book,b1是结构体类型变量。
所以,我们可以写一个代码证明:
#include<stdio.h>
struct
{
char name[20];
char author[12];
float price;
}b;
struct
{
char name[20];
char author[12];
float price;
}*p;
int main()
{
p = &b;
return 0;
}
这种写法正确吗?虽然结构体一模一样,但是编译器仍然认为是两种结构体类型,当放在等号两边时,编译器认为结构体的类型不一样。
1.3 结构的自引用
在结构中包含一个类型为该结构本身的成员是不可以的。例如:
struct Node
{
int data;
struct Node next;
}; //如果可以,那sizeof(struct Node)是多少
那么结构体是如何自引用呢?比如:链式数据,在内存上是不连续的,但是可以通过n1找到n2的地址,n2->n3,n3->n4...
正确的自引用方式如下:
struct Node
{
int data;
struct Node* next;
};
但是,值得注意的是:
typedef struct
{
int data;
Node* n;
}Node;
这样也是不行的!
因为对上述的结构体重命名为Node,也就是说这个结构体类型是完整的可用的,才能产生Node,而这个Node还没有产生就要用。
1.4 结构体变量的定义和初始化
#include<stdio.h>
struct Point
{
int x;
int y;
}p1 = { 1, 2 };
struct Point p3 = { 4, 5 };
struct Stu
{
char name[15];
int age;
};
int main()
{
int a = 10;
int b = 5;
struct Stu p2 = { a, b };
struct Stu s1 = { "zhangsan", 18 };
//struct Stu s1 = { 18, "zhangsan" };//err
struct Stu s2 = { .age = 18, .name = "zhangsan" };
printf("%s %d", s.age, s.name);
return 0;
}
当结构体比较复杂时:
#include<stdio.h>
struct Point
{
int x;
int y;
}p1 = { 1, 2 };
struct Node
{
int data;
struct Point p;
struct Node* next;
};
int main()
{
struct Node n = { 100,{20,21},NULL };
printf("%d x=%d y=%d\n", n.data, n.p.x, n.p.y);//空指针指向的内容无法访问,所以也无法打印
return 0;
}
1.5 结构体内存对齐
我们在1.3中抛出一个问题,sizeof(struct Node)是多少?
#include<stddef.h>
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1)); //12
printf("%d\n", sizeof(struct S2)); //8
printf("%d\n", offsetof(struct S1, c1)); //0
printf("%d\n", offsetof(struct S1, i)); //4
printf("%d\n", offsetof(struct S1, c2)); //8
//offsetof//宏 - 计算结构体成员相较于起始位置的偏移量
return 0;
}
结构体对齐规则如下:
- 第一个成员在与结构体变量偏移量为0的地址处;
- 其他成员变量要对其到某个数字(对齐数)的整数倍处(vs中默认值是8);注:对齐数=编译器默认的一个对齐数与该成员大小的较小值
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数,所有变量类型最大者与默认对齐数参数取最小)的整数倍;
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在设计结构体时,我们既要满足对齐,又要节省空间,那么就要让占用空间小的成员尽量集中在一起。
1.6 修改默认的对齐数
#pragma这个预处理指令,可以修改默认的对齐数。
#pragma pack(1)//设置对齐数为1
struct S2
{
char c1; //1
int i; //4
char c2; //1
};
#pragma pack()//取消对齐数设置,并恢复到8
int main()
{
printf("%d\n", sizeof(struct S2));//6
return 0;
}
1.7 结构体传参
struct S
{
int data[1000];
int num;
};
void print1(struct S t)
{
printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}
void print2(const struct S* ps)//整体效率高一些,但为了防止ps对原结构体的修改造成的不安全,需要加const并放在*左边
{
printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}
int main()
{
struct S s = { {1,2,3},100 };
print1(s);//传值调用
print2(&s);//传址调用
return 0;
}
注意:结构体传参要传结构体的地址。
函数传参时,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象时,结构体过大,参数压栈的系统开销比较大,所以导致性能的下降。
二、位段
2.1 位段的定义
位段的出现就是为了节省空间,位段的位就是二进制位。位段的声明和结构相似,有两个不同:
- 位段的成员必须时int、 unsigned int、 signed in
- 位段的成员名后边有一个冒号和一个数字
例如:
struct A
{
int _a : 2; //:2 - a占用2个bit位的空间
int _b : 5; //:5 - b占用5个bit位的空间
int _c : 10;
int _d : 30;
};
结构体创建时,有一些结构体成员的取值范围可能有限,我们假设 _a 成员的赋值只可能是0,1,2,3,0的二进制序列是00、1的二进制序列是01、2的二进制序列是10、3的二进制序列是11,我们发现只需要两个bit位就能放下来。而我们没有采用位段的形式,而是用 int,我们发现一个整型是 4 个字节,32个bit位,但 _a 只能用两个bit位,那么剩下的30个bit位就闲置了。
上述代码如果不适用位段,sizeof(struct A) == 16 个字节,使用位段占多少字节呢?
2.2 位段的内存分配
位段的内存分配规则
- 位段的成员可以是int、unsigned int、signed int或者char(属于整型家族)类型;
- 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟;
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应避免使用位段。
以下代码主要是VS对位段的处理:
#include<stdio.h>
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;
printf("%d\n", sizeof(struct S));//3
return 0;
}
C中没有规定,所以我们假设从小端开始存储,即从左往右:
0 | 0000 | 000 0000 | 0000 0000 | 0000
| b | a c d
10 - 0000 1010 - a占3个bit位,所以拿出010放在上边的a处
12 - 0000 1100 - b占4个bit位,所以拿出1100放在上边的b处
3 - 0000 0011 - c占5个bit位,所以拿出00011放在上边的c处
4 - 0000 0100 - d占4个bit位,所以拿出0100放在上边的d处
放上以后对应的是:
6 2 0 3 0 4
运行结果如下:
三、枚举
3.1 枚举类型的定义
enum Day//enum是枚举的关键字 Day是我们定义枚举类型的名字
{
//枚举的可能取值
Mon,
Tues,
wed,
thur,
fri,
sat,
sun
};
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
printf("%d\n", RED); //默认值为0.但在最开始时可以根据需求进行设定的
printf("%d\n", GREEN); //默认值为1
printf("%d\n", BLUE); //默认值为2
enum Color color = BLUE;//color是枚举变量
color = (Color)0; //想赋值0,需要强转为枚举类型
printf("%zd\n", sizeof(color));//4 zd打印sizeof
return 0;
}
枚举只能在给定的参数列表中赋值。
3.2 枚举的优点
- 可以增加代码的可读性和可维护性。
- 和#define定义的标识符比较,枚举有类型检查,更加严谨。
- 便于调试。
- 使用方便,一次可定义多个常量。
四、联合体(共用体)
4.1 联合体的定义
联合体是一种特殊的自定义类型,关键字:union。这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间。证明如下:
union Un
{
char c;//1
int i;//4
};
int main()
{
union Un un;
//printf("%d\n", sizeof(un));//4
printf("%p\n", &un);//0055F944
printf("%p\n", &(un.c));//0055F944
printf("%p\n", &(un.i));//0055F944
//所以同一时间只能使用一个
return 0;
}
基于联合体这个性质,可以将判断当前计算机是大端还是小端存储模式的代码进行改写:
#include<stdio.h>
int check_sys()
{
Union Un
{
char c;
int i;
}u;
u.i = 1;
return n.c;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
4.2 联合体大小的计算
联合体的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。比如:
#include<stdio.h>
union Un
{
char c[5];//5,但他的对齐数应该按char来算,所以相比最大对齐数是5
int i;//4
};
int main()
{
printf("%zd\n", sizeof(union Un));//8
return 0;
}
4.3 联合体的应用
某些成员不会在同一时间使用。比如:礼品兑换单有三种商品:图书、杯子、衬衫,每种商品都有库存量、价格、商品类型和相关的其他信息。
图书:书名、作者、页数
杯子:设计
衬衫:设计、可选颜色和尺寸
不管描述哪一个,都会有空间浪费了;此外,不可能一个商品既是图书又是杯子,所以在同一时间只有一个信息使用。
struct gift_list
{
//公共属性
int stock_number;
double price;
int item_type;
//特殊属性
char title[20];
char author[20];
int num_page;
char design[30];
int colors;
int sizes;
};
struct gift_list
{
int stock_number;
double price;
int item_type;
union
{
struct
{
char title[20];
char author[20];
int num_page;
}book;
struct
{
char design[30];
}mug;
struct
{
char design[30];
int colors;
int sizes;
}shirt;
}item;
};