结构体
目录
结构体
结构体类型的声明和变量的定义
结构的自引用
结构体初始化
结构体内存对齐
结构体传参
结构的基础知识:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体类型的声明和变量的定义
struct tag
{
member-list;//成员列表
}variable-list;//变量列表
简单例子:
创建学生s1,s2,s3,s4,s5
例如描述一个学生:
//声明一个结构体类型
//声明一个学生类型,是想通过学生类型来创建学生变量(对象)
//描述学生:名字+年龄+性别+学号
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4,s5,s6;//分号不能丢
//s4,s5,s6全局变量
struct Stu s3;//s3全局变量
int main()
{
//创建的结构体变量
//s1,s2局部变量
struct Stu s1;
struct Stu s2;
}
特殊的声明
在声明结构的时候,可以不完全的声明。
使用场景:通常在只需要一次的结构体使用
简单例子:
//匿名结构体类型:没有名字,省略掉了结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;//这能在这里创建变量
struct
{
int a;
char b;
float c;
}* px;//匿名结构体指针
int main()
{
px=&x;//警告: 编译器会把上面的两个声明当成完全不同的两个类型,是非法的
}
结构的自引用
数据结构:数据在内存中存储的结构
链表:1 -> 2 -> 3 -> 4 -> 5
|-------------------------------------------------------|
|数值域 指针域(存放下一个数据的地址) |
|-------------------------------------------------------|
//err代码1
结构中包含一个类型为该结构本身的成员是 错误 的
struct Node
{
int data;
struct Node next;
};
否则sizeof(struct Node)=无穷大
正确的自引用方式:
//代码2
struct Node
{
int data;
struct Node* next;
};
重命名:给struct Node起一个小名叫Node
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体变量初始化
//初始化:定义变量的同时赋初值。
#include <stdio.h>
struct Point
{
int x;
int y;
};
struct Node //类型声明
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL };//结构体嵌套初始化
struct Node n2 = { 10, {4,5}, NULL };//初始化
int main()
{
struct Node n3 = { 10, {4,5}, NULL };//初始化
printf("%d %d %d %s", n1.data, n1.p.x, n1.p.y, n1.next);
return 0;
}
//结果:10 4 5 (null)
结构体内存对齐
问题:请计算结构体的大小,4个printf的结果是多少?
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
struct S3
{
double d;
char c;
int i;
};
struct S4//结构体嵌套问题
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
return 0;
}
答案:
12
8
16
32
解析:求结构体大小标准步骤
struct S1//12
{
char c1;
int i;
char c2;
};
1.求出成员大小
char c1; //1字节
int i; //4字节
char c2; //1字节
2.求出对齐数
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc没有默认对齐数
以VS为例:
char c1; //1字节/8字节-->1字节
int i; //4字节/8字节-->4字节
char c2; //1字节/8字节-->1字节
3.求出结构体总大小
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
结构体总大小:4的整数倍
4.画出内存结构
第一个成员在与结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
char c1; //1的整数倍
int i; //4的整数倍
char c2; //1的整数倍
存完char c2后偏移量为9,不是4的整数倍,所以为12
| |
|...... |
|------------|<----假设从这里开始放(偏移量为0)
| char c1 |
|------------|偏移量为1
| 浪费 |
|------------|偏移量为2
| 浪费 |
|------------|偏移量为3
| 浪费 |
|------------|偏移量为4<----4的整数倍
| int i |
|------------|偏移量为5
| int i |
|------------|偏移量为6
| int i |
|------------|偏移量为7
| int i |
|------------|偏移量为8<----1的整数倍
| char c2 |
|------------|偏移量为9
| 浪费 |
|------------|偏移量为10
| 浪费 |
|------------|偏移量为11
| 浪费 |
|------------|偏移量为12
|...... |
| |
S2求法同上:
struct S2//8
{
char c1;
char c2;
int i;
};
struct S3//16
{
double d;
char c;
int i;
};
1.求出成员大小
double d; //8
char c; //1
int i; //4
2.求出对齐数
double d; //4
char c; //1
int i; //4
3.求出结构体总大小
4的整数倍
4.画出内存结构
double d; //4
char c; //1
int i; //4
| |
|...... |
|---------|<----假设从这里开始放(偏移量为0)
|double d |
|---------|偏移量为1
|double d |
|---------|偏移量为2
|double d |
|---------|偏移量为3
|double d |
|---------|偏移量为4
|double d |
|---------|偏移量为5
|double d |
|---------|偏移量为6
|double d |
|---------|偏移量为7
|double d |
|---------|偏移量为8<----1的整数倍
|char c |
|---------|偏移量为9
| 浪费 |
|---------|偏移量为10
| 浪费 |
|---------|偏移量为11
| 浪费 |
|---------|偏移量为12<----4的整数倍
|int i |
|---------|偏移量为13
|int i |
|---------|偏移量为14
|int i |
|---------|偏移量为15
|int i |
|---------|偏移量为16
struct S4//32
{
char c1;
struct S3 s3;
double d;
};
1.求出成员大小
char c1;//1
struct S3 s3;//16
double d;//8
2.求出对齐数
char c1;//1
struct S3 s3;//8
double d;//4
3.求出结构体总大小
8的整数倍
4.画出内存结构
char c1;//1
struct S3 s3;//8
double d;//4
| |
|...... |
|------------|<----假设从这里开始放(偏移量为0)
|char c1 |
|------------|偏移量为1
| 浪费 |
|------------|偏移量为2
| 浪费 |
|------------|偏移量为3
| 浪费 |
|------------|偏移量为4<----4的整数倍
| 浪费 |
|------------|偏移量为5
| 浪费 |
|------------|偏移量为6
| 浪费 |
|------------|偏移量为7
| 浪费 |
|------------|偏移量为8<----8的整数倍
|struct S3 s3|
.............
|struct S3 s3|
|------------|偏移量为24<----4的整数倍
| double d |
..............
| double d |
|--over------|偏移量为32<----8的整数倍
int main()
{
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//8
printf("%d\n", sizeof(struct S3));//16
printf("%d\n", sizeof(struct S4));//32
return 0;
}
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地对齐
访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
例如:win32平台,32根地址线,32根数据线-->一次读4字节
01 01 00 00 |00
char int |
一次读取 |二次读取
01 00 00 00 01 00 00 00
char --浪费-- int
一次读取
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
修改默认对齐数
#pragma 这个预处理指令可以改变我们的默认对齐数
例子:
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
offsetof
偏移量求取宏
offsetof简单例子
#include <stdio.h>
#include<stddef.h>//offsetof头文件
struct S
{
char c;
int i;
double d;
};
int main()
{
printf("%d\n",offsetof(struct S,c));//0
printf("%d\n",offsetof(struct S,i));//4
printf("%d\n",offsetof(struct S,d));//8
return 0;
}
百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察: offsetof 宏的实现
结构体传参
#include <stdio.h>
struct S
{
char c;
int i;
double d;
};
//初始化传地址
void Init(struct S* ps)
{
ps->c = 'w';
ps->i = 100;
ps->d = 3.14;
}
//结构体传值
void Print1(struct S tmp)
{
printf("%d %c %lf\n",tmp.c,tmp.i,tmp.d);
}
//传地址
void Print2(struct S* ps)
{
printf("%d %c %lf\n", ps->c ,ps->i ,ps->d);
}
int main()
{
struct S s;
Init(&s);
//不推荐:传结构体
//若结构体过大,临时拷贝的系统开销大,会导致性能的下降
Print1(s);
//推荐:传地址,4-8字节
Print2(&s);
return 0;
}