1结构体
1.1结构体的声明
结构是一些值的集合,这些值被称为成员变量,结构的每个成员可以是不同类型的变量
数组:是一组相同类型的集合
1.2结构体声明
struct tag
{
memeber-list;
} variable-list;
例如描述一个学生
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
};//分号不能丢
1.3特殊的声明
在声明结构时,可以不完全声明。
比如
struct {
int x;
int y;
}b1;
struct {
/* data */
int a;
}arr[20],*P;
在以上代码的基础上下列代码是否合法?
int main(){
p = &b1;
return 0;
}
err:编译器会上面两个当成两个完全不同的类型。所以是非法的。
1.4结构体的自引用
在结构体中包含一个自己本身类型的结构是否可以呢?
struct Node
{
int data;
struct Node node;
};
int main(){
printf("%zd",sizeof(struct Node));
return 0;
}
一个Node的结构体由一个四字节的data和一个Node类型的结构体构成,如此下去无限嵌套,所以不可以这样写。
struct Node
{
int data;
struct Node* node;
};
int main(){
printf("%zd",sizeof(struct Node));
return 0;
}
正确的写法是存储下一个节点的地址,因为指针的类型是固定的,不会造成无限嵌套。
这样写是否可行?
typedef struct
{
int data;
Node node;
}Node;
答案是不行,因为typedef是将整体的struct定义为Node,在struct中,还没有完成定义,所以会报错。
正确的写法是结构体中用结构体类型,在外部定义可以用Node定义
typedef struct Node
{
int data;
struct Node* node;
}Node;
int main(){
Node n;
return 0;
}
1.5结构体变量的定义和初始化
struct point{
int x;
int y;
}p1;//声明类型的同时创建变量,p1为全局变量
struct point p2; //定义结构体类型变量p2
struct point p3 = {1,2};//定义结构体类型变量p3并赋值
//p1 p2 p3 都为全局变量
struct Stu
{
char name[20];
int age;
};
struct Stu s = {"张三",29};//初始化
struct Node
{
int data;
struct point p1;
struct Node* next;
}n1 = {10,{1,2},NULL};
int main(){
// struct Node node = {.data=1,.next=NULL,.p1={1,2}};
printf("data=%d,x=%d,y=%d",n1.data,n1.p1.x,n1.p1.y);
// printf("data=%d,x=%d,y=%d",node.data,node.p.x,node.p.y);
return 0;
}
1.6结构体内存对齐
结构体的对齐规则
- 结构体的第一个成员变量从便宜量为0的位置开始
- 其他成员变量的便宜量要对齐到某个数字(对齐数)的整数倍
- 对齐数 = 编译器默认对齐数与该成员变量的最小值
- 结构体的总体的大小等于最大对齐数的整数倍
- 如果存在嵌套结构体的情况(A的一个成员变量为B),那么被嵌套结构体B的最大对齐数就是他的成员变量中的最大对齐数,A的大小为所有最大对齐数(包含B中的最大对齐数)的整数倍
在设计结构体时,我们尽量让空间小的成员集中在一起
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别
1.7修改默认对齐数
#include<stdio.h>
struct s1{
char c1;
int c2;
char c3;
};//12
// struct s2
// {
// char c1;
// char c2;
// int c3;
// };
#pragma pack(1)
struct s2{
char c1;
int c2;
char c3;
};
int main(){
printf("%d\n",sizeof(struct s1));
printf("%d\n",sizeof(struct s2));
return 0;
}
我们可以手动修改对齐数
1.8结构体传参
假设结构体所占内存很大,那么以结构体作为形参,每次都会创建一份临时拷贝,会耗费很多时间和空间,所以可以将结构体的地址作为参数传过去,这样会减少性能损耗。
struct arr
{
int q[10000];
int data;
};
void print1(struct arr a){
printf("%d %d %d %d\n",a.q[0],a.q[1],a.q[2],a.q[3],a.data);
}
void print2(struct arr* a){
printf("%d %d %d %d\n",a->q[0],a->q[1],a->q[2],a->q[3],a->data);
}
int main(){
struct arr a={{1,2,3},4};
print1(a);
print2(&a);
}
2.位段
2.1什么是位段
- 位段的成员必须为int us int sint
- 位段的成员后边有一个冒号和一个数字
比如
struct A
{
int _a:2;
int b:5;
int c_:10;
int d:30;
};
A就是一个位段类型
2.2位段内存的分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
从低向高,不足向后
2.3 位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题 - 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在
3.枚举常量
3.1枚举的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。例如
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
3.2枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试使用方便,一次可以定义多个常量
4联合体
4.1 联合类型的定义
联合体是一种特殊的自定义类型,联合体的成员变量公用一块内存空间
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
结果为4
union Un
{
int i;
char c;
};
union Un un;
int main(){
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));//从同一地址开始
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);//小端存储11223355
return 0;
}
4.3 联合大小的计算
- 联合体的大小至少是最最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大整数倍的倍数
union Un1
{
char c[5];//类型为char 对齐数为1
int i;
};
union Un2
{
short c[7];//类型为short 对齐数为2
int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));//8
printf("%d\n", sizeof(union Un2));//16