结构体
定义格式
//基本定义格式
struct 结构体类型名
{
成员1类型 成员1名;
成员2类型 成员2名;
成员3类型 成员3名;
....
成员n类型 成员n名;
};
例如:
struct Student_info
{
char Name[64];
char ClassID[7];
char Sex;
char Age;
};
//如何使用自定义的结构体类型去定义变量:
格式:
struct 结构体类型名 结构体变量名;
struct Student_info a; //定义一个结构体变量a
//定义结构体类型的同时定义变量
struct 结构体类型名
{
成员1类型 成员1名;
成员2类型 成员2名;
成员3类型 成员3名;
....
成员n类型 成员n名;
}变量名1,变量名2....,变量名n;//定义了若干个结构体变量
例如:
struct Student_info
{
char Name[64];
char ClassID[7];
char Sex;
char Age;
}a,b,c,d,e;
//定义结构体类型的同时定义变量,但是是一次性的,以后就不能再创建再结构体变量了。
struct
{
成员1类型 成员1名;
成员2类型 成员2名;
成员3类型 成员3名;
....
成员n类型 成员n名;
}变量名1,变量名2....,变量名n;//定义了若干个结构体变量
例如:
struct //匿名结构体
{
char Name[64];
char ClassID[7];
char Sex;
char Age;
}a,b,c,d,e;//该结构在这个语句后不能再定义新的结构体变量了,因为没名字。
//定义结构体类型的同时,为其取个类型别名
-----------------------------------------------------------------------------
C关键词: typedef 为C数据类型取别名
取别名格式:
typedef 原名 别名;
例如:
typedef unsigned int u32;
typedef给函数指针类型取别名
typedef void (*p)(int ,int) ;//给函数指针类型vod (*)(int,int) 取了个别名p(p是类型)
可以使用原版方法:
void (*p1)(int,int);
使用别名:
p p1;//定义一个函数指针变量p1
-----------------------------------------------------------------------------
对结构体定义并取别名
typedef struct 结构体类型名
{
成员1类型 成员1名;
成员2类型 成员2名;
成员3类型 成员3名;
....
成员n类型 成员n名;
}结构体类型别名;
例如:
typedef struct Student_info
{
char Name[64];
char ClassID[7];
char Sex;
char Age;
}STD,*pSTD;//结构体类型别名 STD ,同时为结构体指针类型取了别名pSTD
struct Student_info a;//定义一个结构体变量a
STD a;//使用别名定义一个结构体变量a
struct Student_info *p;//定义一个结构体指针变量p
pSTD p;//使用别名定义了一个结构体指针变量p
注意:以上定义结构体类型,使用了typedef后可以不给结构体类型名,但之后定义变量或指针只能使用别名!!!
例如:
typedef struct
{
char Name[64];
char ClassID[7];
char Sex;
char Age;
}STD,*pSTD;//结构体类型别名 STD ,同时为结构体指针类型取了别名pSTD
之后想要创建该结构体的变量就只能使用STD或pSTD了。
结构体定义时的初始化问题
typedef struct Bank_UserInfo
{
char name[32];
char Sex;
char PhoneNum[12];//电话
int Age;
char CardID[19];//银行卡号
float money;//存款
}BUI,*pBUI;
//全部初始化:按顺序填充初值
BUI a={"kkk",0,"18707694830",18,"xxxxx",99.8};
//部分初始化:
BUI a={"kkk",0};//后面未初始化默认为0 和数组很像;必须从头按顺序开始,不能从中间开始
//选择初始化--初始化部分成员:
BUI a={.name="kkk",.Age=18,.money=99.8};
结构体的自引用
struct Node
{
int a;
char b;
struct Node data;//这种写法是错误的
};
//正确写法
struct Node
{
int a;
char b;
struct Node *data;
};
-----------------------------------------------------------
typedef struct Node
{
int a;
char b;
Node *data;//这种写法是错误的,因为是先执行成员列表再执行到命名的Node,所有这里编译器是不知道Node是什么
}Node;
//正确写法:
typedef struct Node
{
int a;
char b;
struct Node *data;
}Node;
结构体的成员类别
基本数据类型
数组
结构体
指针
枚举
共同体
其它结构体(不能是自己的结构体变量,可以是自己的结构体指针)
…
结构体类型大小
typedef struct Bank_UserInfo
{
char name[32];//默认就是32byte
char Sex;
char PhoneNum[12];//电话
int Age;
char CardID[19];//银行卡号
float money;//存款
}BUI,*pBUI;
sizeof(BUI) = 76;//32+1+12+(3)+4+19+(1)+4 = 76-->括号括起来的是为了对齐而补上的,76刚好为4(成员中最大的字节)的倍数
对齐的意义:
字节对齐,增加CPU读取数据的速度!!!
为什么要对齐?
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取得某些特定类型的数据,否则抛出异常。
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
基本对齐规则
规则1 :结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
规则2:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则3。
结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。不是直接将结构体A的成员直接移动到结构体B中
规则3:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐.
结构体内含有数组,看成是连续若干个同类型数据处理即可
强制对齐规则
//结构体后面加:
__attribute((aligned (n))) //n:1 2 4 8
按照n字节对齐,如果结构体成员有大于n字节成员,使用默认基本规则,否则按照规定的对齐规则
__attribute__ ((packed))
取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,没有所谓的对齐(相当于以1字节对齐)
例如:
struct abc
{
char a;//1+空隙7Byte
double b;//8byte
char c;//1+空隙3Byte
int d;//4byte
char e;
}__attribute((aligned (4))) ;//32
struct abc
{
char a;//1
char b;//1
char c;
}__attribute((aligned (4))) ;//4
struct abc
{
char a;//1
char b;//1
char c;
};//3
struct abc
{
char a;//1
double b;//8
char c;//1
int d;//4
char e;//1
}__attribute__ ((packed));//15
//使用伪指令
#pragma pack(n)
C编译器将按照n个字节对齐--强制。
例如:
#pragma pack(1)
struct abc
{
char a;//1
double b;//8
char c;//1
int d;//4
char e;//1
};//15
//使用伪指令
#pragma pack ()
取消自定义字节对齐方式。
例如:
#pragma pack(1)
#pragma pack ()
struct abc
{
char a;//1
double b;//8
char c;//1
int d;//4
char e;//1
};//32
结构体的成员访问
结构体变量
点访问
例如:
struct abc
{
char a;
double b;
char c;
int d;
char e;
};
struct abc arg;
arg.a = 1;
arg.b = 1.2;
arg.d = 100;
结构体指针:
箭头访问 ->
前提是你的结构体指针必须是有指向一个有效的结构体空间
struct abc arg;
struct abc *p = malloc(sizeof(struct abc));
p->a = 1;
p->b = 1.2;
p->d = 100;
结构体嵌套访问:
struct abc
{
char a;//1+7
double b;//8
char c;//1+3
int d;//4
char e;//1+7
};//32
struct def
{
char a;//1
char b[10];//10+5(为后面的32字节补齐)
struct abc e;//32
char f;//1+7
};//56
struct def arg;
arg.e.a = 10;//多层嵌套 结构体变量用点.逐层访问
结构体传参
struct abc
{
int a;
char b;
}
struct abc data = {1,'a'};
//结构体传参
void print(struct abc data)
{
printf("%dn",data.a);
}
//结构体地址传参
void print2(struct abc *data)
{
printf("%dn",data->a);
}
int main()
{
print(data);
print2(&data);
return 0;
}
//上面两种传参形式,哪种更好?
一般都是首选print2函数;因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递的结构体对象是很大的,参数压栈的系统开销就会很大,导致性能下降。
位域结构体
//有时候我们使用数据类型来存储一些数据,但这些数据只需要占用该变量空间中很少的一部分,剩下的空间就浪费了(无法再被使用);使用位域,就可以让数据只占用该空间中的某几个位,剩下的位可以分配给其它数据使用
struct 位域结构名
{
类型说明符 位域名1:位域长度;
类型说明符 位域名2:位域长度;
...
};
//只能用于整数类型,char、int...;不能用于float、double
例如:
struct abc
{
unsigned char a:1;//0
unsigned char b:3;//1~3
char d:3;//4~6
};//占用空间大小为1
struct abc
{
unsigned char a:1;//0
unsigned char b:3;//1~3
char d:3;//4~6
char e:2;//0~1
};//占用空间大小为2
位域结构体的大小和访问
大小问题:
①连续的同种类型成员,其位域加起来不超过当前类型大小,算一个当前类型
②如果超过了1个当前类型大小,需要跨一个当前类型大小的空间
③位域长度不能超过当前类型
//例如上面的题目:a、b、c变量总共占用7个位,可以全部存在char(8位)中的,如果再加上一个e,就变成了9位,超出了char的位数,所以e就不能再放在第一个char中,只能放在第2个char中。
访问问题:
会因为宽度问题,把大的数据截断---按照位域长度操作结构体变量
例如:
struct abc arg;
arg.a = 2;//因为a只占1位,只能存放0和1,当存入2-->00000010时,会发生数据截断,只取第一位0
printf("%dn",arg.a);//0
arg.a = 3;//00000011-->截断取1
printf("%dn",arg.a);//1
//位段的内存分配
1、位段的成员可以是int 、unsigned int、signed int或者是char(属于整形家族)类型
2、位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3、位段的空间很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。(因为C语言没有给出明确的规定,所以位段有些标准是按照编译器自己的理解来执行的,每个编译器的方法可能不一样,所以不支持跨平台)
//位段的跨平台问题
1、int位段被当成有符号数还是无符号数是不确定的。
2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,在16位机器会出问题。)
3、位段的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
枚举
枚举的定义格式
enum 枚举类型名
{
枚举名1,
枚举名2,
枚举名3,
枚举名4,
....
枚举名n
};
例如:
enum week
{
Mon,
Tru,
Wen
};
枚举类型定义枚举变量:
enum week a;//定义一个枚举变量a-->相当于int a;
a = Mon;//0
a = Wen;//2
a = 100;//100
注意:枚举变量定义完成后,枚举类型里面的只是可选列表,枚举类型内部的可选列表标识符可以直接使用.
枚举类型的大小
sizeof(enum week) == 4;(不管枚举里有多少个成员,大小都是4)
枚举类型的本质
枚举类型的本质可以看作一个int类型,定义枚举变量时,选用那个选项,这个选项的数据就占据该枚举的空间
枚举类型内的可选列表参数,不给初值默认从0开始,后面的选项的值为前面选项的值+1;
如果某个选项给了初值后,该选项之前的选项仍然使用默认规则,后面的选项也是等于前面选项的值+1.
例如:
enum abc
{
a,//0
b,//1
c = 6,//6
d,//7
e//8
};//4
枚举的优点
我们可以使用#define定义常量,为什么非要使用枚举?枚举的优点:
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较枚举有类型检查,更加严谨
3、防止了命名污染(封装)
4、便于调试(用define,在调试时常量已经被替换了,调试看到的内容和编译器实际运行的内容不一样,可能发生一些错误而不容易被发现;但是使用枚举调试看到什么运行就是什么)
5、使用方便,一次可以定义多个常量
共用体/联合体
特性:多个成员变量共用一个内存 ,并且是互斥的
//共用体的定义格式
union 共用体名
{
数据类型 成员1名;
数据类型 成员2名;
.....
};
例如:
union Selection
{
char Job[32];
float C_Grade;
};
//共用体类型大小
共用体的类型大小按里面最大的成员大小计算
sizeof(union Selection) == 32
共用体顾名思义,里面所有的成员都共用同一块空间,每个成员都是从基地址开始,你修改了任何一个共用体成员的数据,其他的成员数据都会受到影响。
例如:
union abc
{
char a;//1
int b;//4
float c;//4
long e;//8
};//8
union abc data;
data.a = 1;
printf("%dn",data.a);//1
data.b = 10;
printf("%dn",data.a);//10
printf("%dn",data.b);//10
//共用体成员访问与结构体一样
共用体变量:点访问
共用体指针:箭头访问
//使用共同体来判断大小端
int check()
{
union u
{
char a;
int b;
}
union u un;
un.b = 1;
return (int)un.a;
}
int main()
{
int ret = check();
if(ret == 1)
{
printf("大端n");
}else
{
printf("小端n");
}
return 0;
}
//共同体的大小
共同体的大小就是成员里最大的那个成员的大小(需要对齐);
例如:
union abce
{
short c[9];//18
int i;
};//20--->最大成员是18,对齐后为20就是该结构体的大小
union abce
{
char c[9];//9
int i;
double a;
};//16--->最大成员是9,需要与8对齐,对齐后为16.
>>
指针、野指针、常量指针、指针常量
C语言实现24点游戏算法
C语言实现汉诺塔



