结构体
1.定义:结构体是一些值的集合,这些值称为成员变量,每个成员可以是不同类型的变量。(是聚合类型)
2.结构体的声明:
struct tag
{
member-list;
}variable-list;
c中结构体不能为空(即成员变量不能省略)
c中结构体关键字不能省略
tag(标签)的注意:
- 见名知义;
- 可以省略;(匿名结构体)
- 不建议省略。
此处的variavle-list(变量名)建议省略,在使用时再定义变量名
struct Stu
{
char name[20];
int age;
char sex[5];
};
struct Stu obj;//定义全局变量int main(){ struct Stu obj1;//定义局部变量}
结构体之间是不能相互赋值的,例如下面的代码是非法的:
struct A
{
int a;
char b;
}x;
struct B
{
int a;
char b;
}*p;
p=&x;
3.结构体的成员:
结构体的成员可以是标量,数组,指针,结构体等任意类型
结构体的地址和第一个成员的地址在数值上是相等的
结构体成员的访问:
- 通过点操作符(.)
struct Stu
{
int age;
};
int main()
{
struct Stu A;
A.age=20;
}
- 当结构体指针访问成员变量时通过指向操作符(->)
struct A
{
int a;
char b;
};
int main()
{
struct A *p;
p->a=10; //p->a等同于(*p).a
p->b='B';
}
4.结构体的自引用:
struct test
{
int a;
struct test b;
};
这是不允许的,因为定义变量需要开辟空间,在这里大小是未知的
正确的做法如下:
struct test
{
int a;
struct test* b;
};
typedef struct node
{
int data;
struct node* next;
}node_t;
int main()
{
node_t n;
}
typedef对当前结构体类型重命名
在上一段代码中,如果不加typedef是定义了一个结构体变量,而加上是定义了一个结构体类型
5.结构体的不完整声明:
struct A
{
int a;
struct B *p;
};
struct B
{
int b;
struct A *q;
};
在A里使用还未定义的B是不可以的,需对B先进行不完整声明(理论上是这样的,但对某些编译器来说还是可以编译通过的)
struct B;
6.结构体变量的定义和初始化
能够定义变量的地方基本都可以定义结构体变量
- 结构体的初始化和数组初始化是一样的(花括号套花括号)
- 结构体和数组一样不能被整体赋值
struct Node
{
int data;
struct Node *next;
}n1={10,NULL};
struct Node n2={20,NULL};
7.结构体内存对齐(计算结构体的大小)
- 为什么存在结构体对齐?
平台原因(移植原因)
性能原因(内存未对齐,处理器需做两次内存访问,对齐后仅需一次访问)
- 对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处(即第一个成员变量默认对齐)
- 其他成员变量要对齐到某个数字(对齐数)的整数倍地址处(对齐数=编译器默认的一个对齐数与该成员大小的较小值, vs 中的默认值为8,Linux中的默认值为4)
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数包括第一个)的整数倍
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
总的原则就是自己的偏移量要整除自己的对齐数
总结:结构体的内存对齐是拿空间来换取时间的做法
例如:
struct S2{ //4
char a;//1+3
int b;//4->1+3+4=8
};
struct S4{
char a; //1
struct S3{ //8
double d;//8
char b[3];//1->3->8+3,b的偏移量是8,自身对齐数是1,8可以整除1,所以b是对齐的,成员变量是数组时,数组先看一个是否对齐,然后再放其他的
int i;//4->8+3+1+4=16
};
//1+7+16=24
char h[3];//1->3->24+3=27+1
char* c[2];//4->8->27+1+8=36+4
double e;//8->36+4+8=48
struct S2 f[2];//4->16->48+16=64
char g;//1->64+1=65
};
//s4的最大对齐数是8,65不是8的倍数,所以S4的总体大小是72
8.结构体传参
- 结构体传参不发生降维
- 结构体传参尽量不要传结构体,而是传结构体指针
struct S{
int data[1000];
int num;
};
struct S s={{1,2,3,4},1000};
void print(struct S* ps)
{
printf("%d\n",ps->num);
}
位段
1.位段的声明:
位段的声明和结构体是类似的,有两个不同
- 位段的成员必须是int,unsigned int,signed int ,unsigned char,signed char;
- 位段的成员名后边有一个冒号和一个数字 (数字为允许使用的比特位数)
struct A
{
int _a:2;
int _b:5;
int _c:10;
int -d:30;
}
2.位段的存储与内存分配:
在上一段代码中,位段是这样存储的
先开辟一个整形字节的大小,a占2个bit位,b占5个bit,c占10个bit,剩下15个bit不够放d,在开辟4个字节放入d的30bit,其中放d的时候可能有两种情况:
- 将d的30bit先拿出15bit放入第一个开辟的空间,再将剩下的15bit放入第二个整形字节中
- 浪费掉第一个整形字节中剩余的15bit,直接将d的30bit放入第二个整形字节中
这是不确定的,要看编译器
在内存分配上:
- 位段的空间是按照需要以4个字节或者1个字节的方式来开辟的
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
3.位段的应用:
在网络协议中经常使用,充当网络报头
枚举
枚举的顾名思义就是一一列举
1.枚举类型的定义
enum Color
{
RED,
GREEN,
BLACK
};
enum Color叫枚举类型
{ }中的内容叫枚举常量,枚举常量是有值得,默认从0开始,一次递增1,在定义的时候也可以赋值
枚举类型与结构体不同的是成员之间是以逗号分隔的
2.枚举的使用
enum Color
{
RED,
GREEN,
BLACK
};
enum Color clr=GREEN;
用枚举常量给枚举变量赋值,不建议给枚举变量赋其他的值(clr=5;)
枚举和宏相似,且宏用的会更多一点
联合
1.联合类型的声明
union Un
{
char c;
int i;
};
2.联合的特点:
- 联合的成员是共用同一块内存空间的
- 所有的联合成员的地址是一样的
3.联合大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(对齐数规则和结构体是一样的)
4.用联合体判断当前计算机的大小端存储:
union Un
{
char c;
int i;
}x;
int main()
{
x.i = 1;
printf("%d\n", x.i);
printf("%d\n", x.c);
system("pause");
return 0;
}
在这段代码中,在联合体开辟的4个字节中,char占的是最低字节的地址
给i赋值1,如果输出的的结果是c为1,则说明计算机为小端,否则为大端
5.联合体和结构体的使用:
将整形的ip地址转为点分十进制的表示形式
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
int main()
{
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1);
system("pause");
return 0;
}