前言
对于C语言的学习,我们知道有基本数据类型,指针类型,自定义类型等。今天一起来探讨一下自定义类型。
结构体
结构体的声明
结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构的声明
struct tag
{
member_list; //成员列表
}variable_list; //变量列表也称类型名,属于全局变量
注意:程序应尽可能少定义全局变量,在程序中到处引用全局变量,会导致程序难以控制,容易出错。且声明不占用内存
例如描述一个学生
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//姓名
char telp[11];//电话号
char id[20];//学号
};//分号不能丢
特殊的声明
特殊声明就是不完全声明又叫匿名结构体类型声明
//匿名结构体类型声明
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
对于上面声明,均省略了结构体标签,而对于匿名结构体类型声明,编译器会把上面两个声明当成完全不同的 两个类型,所以对于p = &x是非法的
结构的成员
结构的成员可以是标量,数组,指针,甚至是其他结构体。
结构体成员的访问
- 结构体变量访问成员: 结构变量成员通过点操作符(.)访问,点操作符接收两个操作数。
- 结构体访问指向变量的成员: 有时候我们得到不是一个结构体变量,而是一个指向结构体的指针,就应该用(->)操作符。
struct Stu
{
char name[20];//名字
int age;//年龄
}s;
void print(struct Stu* pc)
{
printf("%s,%d\n",(*pc).name,(*pc).age);
printf("%s,%d\n", pc->name, pc->age);
}
结构自引用
简而言之就是在结构中包含一个类型为结构本身的成员
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构的不完整声明
struct B;
struct A
{
int a;
struct B* pb;
};
struct B
{
int b;
struct A* pa;
};
结构体变量定义及初始化
有了结构体类型,定义及初始化就很简单了
struct Stu
{
int x;
int y;
}p1; //声明类型同时定义变量p1
struct Stu p2;//定义结构体变量p2
struct Stu P3 = { x, y };//定义变量同时初始化
struct Po
{
char name[20];
int age;
};
struct Po s = { "zhangsan", 20 };//定义变量同时初始化
结构体内存对齐
目前,我们已经掌握了结构体的基本使用了,现在来开始讨论一个问题:计算结构的大小。当然也是一个热门考点:结构体内存对齐
如何计算?
结构体内存对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处;
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认对齐数 与该成员自身大小中的最小值。VS默认值为8,linux中gcc默认值为4.
- 结构体总大小为最大对齐数的整数倍;
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的大小就是所有对齐数中最大对齐数的整数倍。
注意:
#pragma pack(n) //n表示系统默认最大对齐数
...
#pragma pack() //使用完应立即取消自定义设置
为什么存在内存对齐?
平台原因(移植原因):
* 不是所有硬件平台都能访问任意地址上的数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则肯呢个抛出硬件异常。性能原因:
* 数据结构(尤其是栈)应该尽可能的在自然边界上对齐,原因在于:访问未对齐的内存,处理器需要两次访问,而访问对齐内存,处理器只需访问一次。
总的来说:结构体内存对齐就是用空间换取时间的做法,而我们在设计的时候既要满足内存对齐,还要满足节省空间,那么唯一做法:让占用空间小的成员尽可能集中在一起。
//练习一
struct S1
{
double d;
//0-7
char c;
//8
int i;
//11-15
};
printf("%d\n",sizeof(struct S1)); //16
//练习二
struct S2
{
char a;
//0
struct S1 S;
//8-23
double f;
//24-31
};
printf("%d\n",sizeof(struct S2)); //32
附加:
//宏,表示结构体成员变量在内存中偏移量包含于头文件#include <stddef.h>
offsetof(struct S, A);
宏实现:(size_t)&(((S*)0)->m)
结构体传参
讲解函数栈桢的时候,我们知道函数传参是需要参数压栈的,如果传递的是一个结构体对象,而结构体过大,参数压栈的系统开销就会很大,所以导致系统性能下降。
结论
结构体传参的时候要传结构体指针。
位段
什么是位段?
位段声明与结构体类型相似,有两个不同:
- 位段成员必须int 、unsigned int 或 signed int。
- 位段成员名后面有一个冒号和一个数字。
位段内存分配
- 位段成员可以是int 、unsigned int、signed int、或者char(整形家族)类型。
- 位段的空间上按四个字节(int)或者一个字节(char)方式开辟
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植性程序因避免使用位段。
eg:
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};
printf("%d",sizeof(struct A)); //8
位段跨平台问题
- int位段当成有符号还是无符号是不确定的
- 位段中最大位数目不确定的
- 位段中成员是从左到右还是从右到左是不确定的
- 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的还是利用,是不确定的
总结
跟结构相比,位段可以达到同样的效果,还节省空间,但是因为存在跨平台问题,所以运用的就不是很多了。
枚举
枚举顾名思义就是一一列举。
比如:星期、颜色、性别等;
枚举类型定义
enum Color
{
RED,
GRENE,
BLUE
};
以上定义的enum Color 就是枚举类型,{}中内容就是可能取值,也叫枚举常量,默认从零开始一次递增1,当然有时候也可以自定义赋值:
enum Color
{
RED = 1,
GREEN = 4,
BLUE = 8
};
枚举优点
- 增加代码可读性与可维护性
- 与#define相比有类型检查,更加严谨
- 防止命名污染
- 便于调试
- 使用方便,一次可定义多个常量
联合(共用体)
联合定义
联合也是一种特殊自定义类型,该类型定义变量也包含一系列成员,特征是这些成员公用一块空间。
eg
//联合类型声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算变量大小
printf("%d",sizeof(un)); //4
联合特点
联合成员公用一块内存空间,这样一个联合变量大小,至少是最大成员大小。
经典应用
判断当前计算机大小端存储:
int CheckSystem()
{
union Un
{
int a;
char i;
}un;
un.a = 1;
//返回为1,小端存储
//返回为0,大端存储
return nu.i;
}
联合大小的计算
- 联合大小至少是最大成员大小
- 当最大成员大小不是最大对齐数整数倍时,就要对齐到最大对齐数的整数倍
eg
union UN1
{
char arr[5];
int i;
};
union UN2
{
short c[7];
int i;
};
printf("%d",sizeof(union UN1)); //8
printf("%d",sizeof(union UN2)); //16
结构体与联合巧妙使用:
//将long类型ip地址,转换为电分十进制形式
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d",my_ip.ip.c4,my_ip.ip.c3,my_ip.ip.c2,my_ip.ip.c1,);
结语
通过自定义类型学习,掌握了内存对齐等相关运用,以及如何检验大小端问题。
好好学习,天天编程