结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量
下面的代码是结构体的基本框架
struct tag
{
member-list;
}variable-list;
假设描述一个学生,学生的信息:姓名,年龄,性别,学号
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
就以上面的学生为例,struct Stu属于结构体类型,下面定义的,s1,s2,s3,s4就是结构体变量
创建结构体类型的时候,并不开辟内存,只有定义完变量的时候才会开辟空间。
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2; //分号不能丢
int main()
{
struct Stu s3;
struct Stu s4;
return 0;
}
结构体的特殊声明就是在创建结构体类型的时候,省略了结构体标签名,也是不完全声明,这也称为匿名结构体类型。
struct //匿名结构体类型
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
而我们在给匿名结构体类型定义变量的时候只能在结构体类型上,不能在函数内定义结构体变量
struct //匿名结构体类型
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s1,s2; //分号不能丢,应该在这里定义变量
int main()
{
struct Stu s3;//在这里定义变量是error
struct Stu s4;//在这里定义变量是error
return 0;
}
定义s3,s4变量就会报下面的错误,匿名结构体只能用一次,之后再想定义新的变量是不行的。
接下来下面有一个匿名结构体类型问题,来看一下
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
//问题:在上面的代码基础上,下面的代码正确吗?
p=&x;
结论:因为是匿名结构体类型,所以编译器会把他们认定为两个不同的结构体类型,
所以p=&x,是错误的
结构体的自引用就是,在结构中包含一个为该结构体本身的成员
struct Node
{
int data;
struct Node next;//error
};
上面的这段代码是错误的,因为这样就类似于套娃,也就不能知道结构体类型的大小
应该改为下面这段代码
struct Node
{
int data;
struct Node* next;//true
};
如果嫌每次输入这个结构体类型太长,也可以选用typdef
下面的是正确写法
typedef struct Node
{
int data;
struct Node* next;
}Node;
int main()
{
Node s1;
return 0;
}
下面是错误示范
typedef struct Node
{
int data;
Node* next;//这里是不可以这样写的,error
}Node;
int main()
{
Node s1;
return 0;
}
typedef struct Node
{
int data;
struct Node* next;
}Node;//也就不能在这个地方定义变量了
int main()
{
Node s1;//用了typdef是方便了很多,但是也有另外的一种声音就说
//用完typdef之后并不能直观的知道这是什么类型
return 0;
}
整体初始化
下面s1,s2,s3这两种初始化的方法是都可以的,而在定义s3时,可以顺序颠倒用这种方式
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
}s1={ "zhangsan",15 };
int main()
{
struct Stu s2 = {"lisi",20};
struct Stu s3={.age=25,.name="wangwu"};
printf("%s %d",s1.name,s1.age);
printf("%s %d",s2.name,s2.age);
printf("%s %d",s3.name,s3.age);
return 0;
}
单个初始化
字符串并不能直接初始化,需要借用到strcpy
struct Stu //类型声明
{
char name[15];//名字
int age;//年龄
char a;
}s1;
int main()
{
//struct Stu s2 = { "lisi",20 };
//struct Stu s3 = { .age = 25,.name = "wangwu" };
struct Stu s4;
//s4.name = "lili";//error
strcpy(s4.name, "lili");
s4.age = 46;
s4.a = 'a';
printf("%s %d %c", s4.name, s4.age,s4.a);
return 0;
}
在开始之前我们先来了解一个宏offsetof
它是用来计算结构体成员相对于起始位置的偏移量
#include //头文件
size_t offsetof(type, member);
//type为结构体类型,member为结构体成员名
#include
struct S1
{
char a;
int b;
char c;
}s1;
int main()
{
printf("%zd\n",offsetof(struct S1,a));//0
printf("%zd\n", offsetof(struct S1, b));//4
printf("%zd\n", offsetof(struct S1, c));//8
return 0;
}
我们先来看一段代码,来看看这个结构体的内存大小
可能有人会猜结果是:6,但是输出的结果为:12
struct S1
{
char a;
int b;
char c;
}s1;
int main()
{
printf("%d",sizeof(struct S1));//12
return 0;
}
结构体内存对齐的规则
1.结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处
2.从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处
对齐数:结构体成员自身大小和默认对齐数的较小值
VS编译器的默认对齐数:8
Linux环境默认不设对齐数(对齐数是结构体成员的自身大小)
3.结构体的总大小,必须是最大对齐数的整数倍
每个结构体成员都有体格对齐数,其中最大对齐数就是【最大对齐数】
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们来看一段代码
struct S2
{
double d;
char c;
int i;
};
struct S1
{
char c1;
struct S2 s2;
double d;
};
int main()
{
printf("%d",sizeof(struct S1));//结果:32
return 0;
}
修改默认对齐数
//设置默认对齐数为1
#pragma pack(1)
//恢复默认对齐数
#pragma pack()
struct s1
{
char a;
int b;
char c;
}s;
int main()
{
printf("%d",sizeof(struct s1));//6
return 0;
}
为什么要内存对齐
可以将结构体变量直接传过去,也可以将结构体地址传过去
struct s
{
int a;
int b;
};
void print(struct s s1)
{
printf("%d",s1.a);
}
void print1(struct s* p)
{
printf("%d",(*p).b);//这两种方式都可以
printf("%d",p->b);//
}
int main()
{
struct s s1 = {1,2};
print(s1);
print1(&s1);
}
传值和传址哪个好
传址好!
原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
下降。
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字(数字的单位是比特位)
下面这段代码就属于位段
struct s
{
int a : 2;
int b : 10;
int c : 20;
};
接下来我们来看下面的代码,具体的看一下位段的内存是怎么分配的
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;
}
在这里我们也可以看出位段的内存是怎么分配的了,在VS当中每个字节的八个比特位是从右往左用的
可以参考一下下面的代码,同样都是存入1,2,3,4,而用的字节却不一样,这也就是位段的优势。
struct S
{
char a : 1;
char b : 2;
char c : 3;
char d : 4;
};
struct S1
{
int _a;
int _b;
int _c;
int _d;
};
int main()
{
struct S s = { 0 };
struct S1 s1;
s.a = 1;
s.b = 2;
s.c = 3;
s.d = 4;
s1._a = 1;
s1._b = 2;
s1._c = 3;
s1._d = 4;
printf("%d\n",sizeof(struct S));//2
printf("%d\n",sizeof(struct S1));//4
return 0;
}
枚举的意思就是一一列举
enum Color//颜色
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color color1 = RED;//true
enum Color color2 = 5;//在Vs当中是能通过的,c++是通不过的
printf("%d",RED);//0
printf("%d", GREEN);//1
printf("%d", BLUE);//2
return 0;
}
也可以对枚举类型里的成员进行定义
在后面还进行了对枚举类型内存大小进行了打印,结果是4,因为枚举类型在定义变量的时候,一次只能定义一个。
enum Color
{
RED = 2,
GREEN,
BLUE = 5
};
int main()
{
enum Color color1 = RED;
printf("%d", RED);//2
printf("%d", GREEN);//3
printf("%d", BLUE);//5
printf("%d",sizeof(enum Color));//4
return 0;
}
通过这些代码我们也可以看出枚举和宏定义有一些类似
但是宏定义的内容并没有类型,就只是在预处理阶段全部替换了,并且如果数据变大之后,宏定义会很麻烦
#define RED 2
#define GREEN 3
#define BLUE 4
枚举的优点
联合体和结构体也有一些类似,联合体是只能存在一个成员,而结构体是要求每个成员必须都存在
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
union Un
{
char a;
int b;
double c;
};
int main()
{
union Un un;
printf("%d",sizeof(union Un));//8
printf("%d",sizeof(un));//8
return 0;
}
union Un
{
char a;
int b;
double c;
};
int main()
{
union Un un;
printf("%p\n",&un);
printf("%p\n", &un.a);
printf("%p\n", &un.b);
printf("%p\n", &un.c);
return 0;
}
int ret()
{
union Un
{
char a;
int b;
}un;
un.b = 1;
return un.a;
}
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un1
{
char c[5];//自身对齐数是1,默认对齐数是8,它为最大成员5,
//因为要取到最大对齐数的整数倍所以结果为8
int i;//自身对齐数是4,默认对齐数8,取较小值为4
};
union Un2
{
short c[7];
int i;
};
printf("%d\n", sizeof(union Un1));//8
printf("%d\n", sizeof(union Un2));//16