目录
- 结构体是一些值的集合,称为成员变量;
- 每个成员可以是不同类型;
一,结构体struct
结构体声明
1,一般声明
- 结构体关键字,结构体标签,结构体成员;
//结构体声明
struct Stu
{
//结构体成员
char name[20];
int age;
char sex[5];
double id[20];
}; //封号不可丢
2,特殊声明(不完全声明)
- 以上均为完全声明,不完全声明即声明时省略标签;
- 此为匿名声明,但只有声明时才能定义变量;
//结构体声明
struct
{
//结构体成员
char name[20];
int age;
char sex[5];
double id[20];
} stu; //只有声明时才能定义变量
struct
{
char name[20];
int age;
char sex[5];
double id[20];
} stu;
struct
{
char name[20];
int age;
char sex[5];
double id[20];
} *p;
int main()
{
//警告: 编译器会把上面的两个声明当成完全不同的两个类型, 所以是非法的;
p = &stu;
}
3,结构体自引用
- 结构体可以包含同类型结构体指针;
- 如链表;
struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
struct Stu stu; //报错,不可包含结构体本身成员
};
//正确写法,可开辟确定的空间
struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
struct Stu* p;
};
//匿名声明,并重定义为Stu
typedef struct
{
char name[20];
int age;
char sex[5];
double id[20];
Stu* stu; //报错,因为需先有结构体类型,才能重定义
}Stu;
//正确写法
typedef struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
struct Stu* stu; //struct不可省略
}Stu;
结构体变量的定义和初始化
1,结构体变量定义
- 声明时定义变量;
- 声明后定义变量(不可为匿名声明);
struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
}s1, s2; //声明时定义变量
int main()
{
struct Stu s3, s4; //声明后定义变量(不可为匿名声明)
return 0;
}
//通过重定义类型来简化定义
typedef struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
}Stu;
int main()
{
Stu stu;
return 0;
}
2,结构体初始化
- 定义的同时赋初值;
- 可嵌套初始化;
struct Stu
{
char name[20];
int age;
char sex[5];
double id[20];
}s1 = { "zhangsan",20,"male",3.14 }; //初始化
struct Book
{
char bname[20];
struct Stu s2;
};
int main()
{
struct Stu stu = { "lisi",21,"male",4.28 }; //初始化
struct Book book = { "C",{ "wangwu",22,"male",7.68 } }; //嵌套初始化
return 0;
}
struct S
{
char name[20];
int age;
}s1;
int main()
{
//两结构体可直接传值
struct S s1 = { "zhangsan",10 };
struct S s2 = { "lisi",20 };
s1 = s2;
printf("%s %d", s1.name, s1.age);
return 0;
}
//结果:lisi 20
//注意:
//数组不可通过数组名,直接传值(数组名表示首元素地址了);
//枚举可以通过变量名,直接传值;
//联合体可以通过变量名,直接传值;
结构体内存对齐
- 结构体类型的尺寸大小,需遵循内存对齐规则;
1,对齐规则
- 结构体第一个成员变量,在结构体中内存偏移量为0;
- 其他成员变量,需对齐到该变量对齐数整数倍的地址处;
- 即偏移量为该成员变量对齐数的整数倍;
- 若为嵌套结构体,则对齐到该结构体内最大对齐数的整数倍处;
- 结构体总大小,为所有成员变量中最大对齐数的整数倍(含嵌套结构体中的对齐数);
注:
- 成员变量对齐数=min(编译器默认的对齐数,该成员大小);
- VS默认为8,Linux无默认对齐数概念;
//默认编译器对齐数为8
struct S1
{
char a; //min(1,8)=1
int b; //min(4,8)=4
};
struct S2
{
int b; //min(4,8)=4
char a; //min(1,8)=1
};
struct S3
{
char a; //min(1,8)=1
int b; //min(4,8)=4
char c; //min(1,8)=1
};
struct S4
{
char a; //min(1,8)=1
int b; //min(4,8)=4
struct S3 s; //min(4,8)=4
};
int main()
{
printf("%d ", sizeof(struct S1)); // 8
printf("%d ", sizeof(struct S2)); // 8
printf("%d ", sizeof(struct S3)); // 12
printf("%d ", sizeof(struct S4)); // 20(嵌套)
return 0;
}
2,存在内存对齐的原因
- 平台原因(移植原因),不是所有的硬件平台都能访问任意地址上的任意数据;某些平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常;
- 性能原因,数据结构(尤其是栈)应该尽可能地在自然边界上对齐;原因在于,为了访问未对齐内存,处理器需要作两次内存访问,而对齐内存访问仅需要一次访问;
- 结构体内存对齐是拿空间换取时间的做法;
设计结构体时,既要满足对齐又要节省空间,可让占空间小的成员尽量集中在一起;
//默认编译器对齐数为8
struct S1
{
char a; //min(1,8)=1
int b; //min(4,8)=4
char c; //min(1,8)=1
};
struct S2
{
char a; //min(1,8)=1
char c; //min(1,8)=1
int b; //min(4,8)=4
};
int main()
{
printf("%d ", sizeof(struct S1)); // 12
printf("%d ", sizeof(struct S2)); // 8
return 0;
}
3,修改默认对齐数
结构体在对齐方式不合适的时候, 可以自己更改默认对齐数;
- #pragma pack(8) 设置默认对齐数;
- #pragma pack() 取消设置默认对齐数;
//默认编译器对齐数为8
#pragma pack (2)
struct S1
{
char a; //min(1,8)=1
int b; //min(4,8)=4
char c; //min(1,8)=1
};
#pragma pack()
int main()
{
printf("%d ", sizeof(struct S1)); // 8(对齐数为8时值为12)
return 0;
}
4,offsetof 宏
offsetof ( struct s,name )
- 返回成员变量相对起始位置的偏移量;
- 注,不对位段使用;
//(类型说明符) (表达式) ,是把变量、数值、表达式的运算结果强制转换成类型说明符所表示的类型;
//模拟实现offsetof宏
#define offsetof(type, member) (int)&((type*)0)->member)
//默认编译器对齐数为8
#include <stddef.h>
struct S1
{
char a; //min(1,8)=1
int b; //min(4,8)=4
char c; //min(1,8)=1
};
int main()
{
printf("%d ", sizeof(struct S1)); // 12
printf("%d ", offsetof(struct S1, a)); //偏移量0
printf("%d ", offsetof(struct S1, b)); //偏移量4
printf("%d ", offsetof(struct S1, c)); //偏移量8
return 0;
}
结构体传参
- 直接传结构体的值,即传值;
- 传结构体的地址,即传址(函数传参时,建议传址);
函数传参时,参数需压栈;如结构体过大,传值会参数压栈系统开销过大,导致性能下降;
详情请见《函数栈帧的创建和销毁》;
struct Book
{
char name[20];
float price;
int num;
};
void print1(struct Book book) //传参-传值
{
printf("%s\n", book.name);
}
void print2(const struct Book* pbook) //传参-传址
{
printf("%s\n", pbook->name);
}
int main()
{
struct Book book = { "C语言", 99.5, 10 };
struct Book* pbook = &book;
print1(book); //结果:C语言
print2(pbook); //结果:C语言
return 0;
}
附:参数压栈
- 栈,是一种数据结构,遵循“先进后出,后进先出”;
- 函数调用,会在内存的栈区开辟一块空间(内存可分为栈区、堆区、静态区);
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 2;
int b = 4;
int sum = 0;
sum = Add(a, b);
return 0;
}
位段
1,位段的声明
和结构体是类似的,但有两个不同:
- 位段的成员必须是 int,unsigned int,signed int,或char;
- 位段的成员名后边有一个冒号和一个数字(占比特位数);
struct S
{
//冒号后的数字,表示该成员所占比特位数
int a:3;
int b:4;
int c:30;
};
2,位段的内存分配
- 成员是int,unsigned int/signed int,或char类型;
- 空间上是按照以4个字节int或一个字节char的方式开辟的;
- 涉及好多不确定因素,位段是不跨平台的,注重可移植性的程序应该避免使用位段;
struct S
{
//冒号后的数字,表示该成员所占比特位数
int a:3;
int b:4;
int c:30;
};
int main()
{
struct S s = { 30,40,50 };
printf("%d ", sizeof(struct S)); //8
return 0;
}
- 在VS内,从右向左存储(如是int);
- 剩余不够的空间,浪费掉;
3,位段的跨平台问题
- int位段被当成有符号还是无符号数不确定;
- 位段中最大位的数目不确定(int在16位机器最大16,32位机器最大32);
- 位段中的成员在内存中从左向右,还是从右向左分配标准尚未定义;
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一位段剩余的位时,时舍弃剩余位段还是利用,不确定;
跟结构相比,位段可达到同样的效果,可以很好的节省空间,但是有跨平台的问题存在;
二,枚举enum
- 即可以一 一 列举可能的值;
- 值,即枚举常量,默认从0开始;
- 也可设置初值,那么其后的值依次递增;
//枚举类型
enum sex
{
//可能取的值
male, // 0
female, // 1
sceret // 2
};
enum color
{
red = 2, // 2
green, // 3
blue // 4
};
enum days
{
mon, // 0
tues, // 1
wed, // 2
thur, // 3
fri, // 4
sat, // 5
sun // 6
};
枚举类型的定义
//声明枚举类型
enum color
{
red,
green,
blue
};
int main()
{
enum color c = green;
printf("%d ", c); // 1
c = red;
printf("%d ", c); // 0
return 0;
}
枚举的使用
//枚举类型
enum color
{
red = 1,
green = 2,
blue = 4
};
int main()
{
//只能拿枚举常量给枚举变量赋值,才不会出现类型差异
enum color c = green; //c = 2,这样书写c++会报错;
printf("%d ", c);
return 0;
}
枚举的优点
- 增加代码的可读性和可维护性;
- 和#define定义的标识符比较,枚举有类型检查,更加严谨;
- 防止了命名污染(命名冲突);
- 使用方便,一次可以定义多个常量(define一次只可定义一个);
- 便于调试(define在预编译时已替换);
三,联合union
- 是一种特殊的自定义类型,也称共用体;
- 此类型定义的变量包含一系列的成员,但这些成员公用同一块空间;
联合类型的定义
//联合类型
union U
{
char c;
int i;
long long l;
};
联合的特点
- 联合成员公用同一块空间;
- 联合成员变量的大小,至少是最大成员的大小;
//联合类型
union U
{
char c;
int i;
long long l;
};
int main()
{
union U un;
//大小至少是最大成员的大小
printf("%d ", sizeof(union U)); //8
//成员变量的地址相同
printf("%p ", &(un.c)); //00EFF7CC
printf("%p ", &(un.i)); //00EFF7CC
printf("%p ", &(un.l)); //00EFF7CC
return 0;
}
联合大小的计算
- 联合大小至少是最大成员的大小;
- 当最大成员大小不是最大对齐数的整数倍时,就要对齐最大对其数的整数倍;
//联合类型
union U
{
char c; //大小1,对齐数1(1/8)
int i; //大小4,对齐数4(4/8)
long long l; //大小8,对齐数8(8/8)
int arr[3]; //大小12,对齐数8(12/8)
};
int main()
{
union U un;
//大小至少是最大成员的大小(>=12)
//且要是最大对齐数的整数倍(即8的倍数)
printf("%d ", sizeof(union U)); //16
}
//计算大小端
//方法一
int check_sys()
{
int a = 1;
return *((char*)&a);
}
//方法二
int check_sys()
{
union U
{
char a;
int b;
}un;
un.b = 1;
return un.a;
}
四,案例