结构体
格式:
结构体关键字+类型
{};
结构体声明:
struct Student
{
char name[10];
int age;
char sex[3];
float weight;
};
第二种写法:
struct Student
{
char name[10];
int age;
char sex[3];
float weight;
}s1,s2; //创建结构体的同时创建了两个全局变量s1,s2
结构体的自引用:
struct Node
{
int data;
struct Node* next;
};
对变量的创建
typedef struct Student
{
char name[10];
int age;
char sex[3];
float weight;
}Student;
上述代码的含义是:
typedef将struct Student这个结构体重新定义为Student.
struct point
{
int x;
int y;
}p;//声明结构体类型的同时创建变量p
struct Node
{
int data;
struct point p;
struct NOde* next;
}n={10,{4,5},NULL}//结构体嵌套初始化
结构体内存对齐
1.自身类型有一个对齐值
2.自定义类型有一个对齐值(内部成员最大的一个)
3.程序有一个指定的对齐值(#pragma pack(n) 2^x)
4.程序有一个有效对齐值(指定对齐值和将要对齐值中的较小值)
//练习
struct S1
{
char c1; //1 +3
int i; //4
char c2; //1 +3
};
printf("%d\n", sizeof(struct S1)); //12
c1所占空间大小为1字节,i为4,c2为1,首先将c1与i对齐,对齐值为4,所以c1要补一个3,此时c1与i一共占8个字节,再与c2对齐,8是1的整数倍,所以直接加c2的大小,总共占9个字节,不是自定义类型的对齐值(4)的整数倍,c2需要补3个,最后总共就是12
结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void pirnt2(struct s* ps)
{
printf("%d\n",ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是print2函数,因为如果参数是结构体的话,需要开辟一个空间存储结构体的,性能上会造成浪费,因此需要传结构体地址。
位段
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
限制条件:
1.位段不能跨字节存储
2.位段不能跨类型存储
练习:
typedef struct Test
{
char a : 1; //1
int b : 4; //4
}Test;
上述结构体所占内存大小:8
原因:两个变量的类型不同,不能共占一个字节。char为1字节,int为4字节,char要向int对齐,总共为8字节。
联合体
联合体声明:
typedef union Test
{
char a;
double b;
int c;
}Test;
联合体大小为内部最大类型的大小(该联合体大小为8)
union Un1
{
char c[5]; //5
int i; //4
};
c[5]的大小为5字节,i的大小为4字节,该联合体大小至少为5字节,再者该联合体对齐值为4,所以最终联合体所占空间大小为8字节。
枚举
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
默认Mon从0开始, 一次递增1
枚举的优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
enum Color
{
RED=1;
GREEN=2;
BLUE=4;
};
enum Color clr=GREEN//yes 只能用枚举常量给枚举变量赋值
clr=5; //error 不能用常量给枚举变量赋值