作者主页:paper jie的博客_CSDN博客
本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。
本文录入于《系统解析C语言》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将算法基础知识一网打尽,希望可以帮到读者们哦。
其他专栏:《算法详解》《C语言》《C语言-语法篇》等
内容分享:本期将对c语言中的自定义类型进行详细的讲解,各位看官姥爷快搬好小板凳坐好叭。
-------- 不要998,不要98,只要一键三连,三连买不了吃亏,买不了上当
目录
结构体
结构体的声明
特殊的声明
结构体的自引用
结构体的定义和初始化
结构体内存对齐
练习
内存对齐的原因
修改默认对齐数
结构体传参
位段
位段的含义
位段的内存分配
位段的跨平台问题
位段的应用
枚举
枚举类型的定义
枚举的优点
枚举的使用
联合体
联合体的定义
联合的特点
联合体大小的计算
结构体
结构体是一些值的集合,这些值是它的成员。它们可以是不同类型的变量。
结构体的声明
注意:它的分号不能丢了
//栗子:这里定义一个学生
struct Stu
{
int age;
char name[20];
char sex[5];
};
特殊的声明
在声明结构体的时候,还有一种特殊的声明,就是匿名结构体类型:
//栗子:
struct
{
int a;
char b;
float c;
}s1;
struct
{
int a;
char b;
float c;
}*p;
上面两个结构体省略了结构体的名字,这时有一个问题就是:
p = &s1在编译器上是编译不过去的。原因就是编译器把他们两个当成了不同的结构体类型。
结构体的自引用
在结构体中包含一个类型为该结构本身的成员是可以的:
struct Node
{
int a;
struct Node *next;
};
结构体的定义和初始化
声明类型的时候可以定义变量,也可以在声明完后定义。初始化可以在定义完后初始化,也可以定义的同时初始化。
struct point
{
int a;
int b;
}s1; //声明的同时定义
struct point s2; //先声明再定义结构体变量
//初始化:定义的同时初始化
struct point s3 = { 1,2 };
struct Stu
{
int age;
char name[20];
};
struct Stu s = { 20,"zhangsan" };
struct Node
{
int date;
struct Stu s1;
struct Node* next;
}n1 = {1, {19, "lishi"}, NULL}; //结构体嵌套初始化
struct Node n2 = { 2, {25,"wangwu"}, NULL };//结构体嵌套初始化
结构体内存对齐
对于结构体内存对齐有以下几点规则:
第一个成员放在与结构体变量偏移量为0的位位置
其他变量要对齐到对齐数的整数倍处的地址处
对齐数是编译器默认的一个对齐数与该成员大小的较小值(vs默认值为8,gcc,Linux没有默认对齐数,对齐数是成员自身大小)
结构体总大小必须是最大对齐数的倍数
如果是嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)
练习
//练习一
struct s1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct s1));
}
c1为首成员,对齐偏移量为0的位置,占1个字节。i为4个字节小于8,对齐数为4,从&c1+3的位置开始存放i。c2为1个字节小于8,对齐数为1,接在i后面存放。又因为它们加起来为9不是最大对齐数的倍数,所以要提升为12.
//练习二
struct s2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct s2));
}
//练习3
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
}
//练习4
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
}
内存对齐的原因
平台原因:
不是所有的硬件平台都能访问任意地址上的数据。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构应该尽量的在自然边界上对齐。原因就是为了访问未对齐的内存,处理器需要进行两次内存访问。而对齐的内存只需要访问一次。
总得来说就是内存对齐是用来拿空间换时间的做法。
所以在设计结构体的时候我们要尽量的满足对齐,又节约空间。做法就是让空间小的成员尽量在一起:
//错误的做法
struct S1
{
char c1;
int i;
char c2;
};
//正确的做法
struct S2
{
char c1;
char c2;
int i;
};
修改默认对齐数
修改对齐数我们需要用到一个预处理指令:#pragma,它可以改变我们的默认对齐数:
#pragma pack(8)
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()
#pragma pack(1)
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
在我们遇到对齐方式不合适的时候,我们就可以用pragma来改变默认对齐数。
结构体传参
结构体传参有两种方法:一种是直接传参,一种是地址传参。一般来说是地址传参比较好。
因为:函数传参需要压栈,会有时间和空间的开销。如果传递的结构体对象过大,那么参数压栈的开销就会比较大,所以会导致性能的下降。
struct Stu
{
char name[20];
char sex[4];
int age;
};
struct Stu s = { "zhangsan","nan", 20 };
//结构体传参
void print_f(struct Stu s)
{
printf("%s\n", s.name);
}
//结构体地址传参
void print_t(struct Stu* s)
{
printf("%d\n", s->age);
}
int main()
{
print_f(s);//传结构体
print_t(&s);//传地址
return 0;
}
位段
位段的含义
段位和结构体是类似的,但是有两点不同:
段位的成员必须是int,unsigned int, signed int
段位的成员名后面有一个冒号和一个数字
举个栗子:
struct S
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
位段的内存分配
段位的成员是int unsigned int signed int 或者是char类型
段位的空间是按照需要以4个字节或者1个字节的方式开辟的。
段位涉及很多不确定的因素,段位是不跨平台的,注重可移植的程序需要避免使用段位。
栗子:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
位段的跨平台问题
位段的应用
它可以极大的减少数据的空间,是数据可以高速传递
枚举
枚举就是可能的情况一一列举。比如:生活中的星期,月份,性别都可以一一列举
枚举类型的定义
enum day
{
mon,
tues,
wed,
thur,
fri,
sat,
sun,
};
enum color
{
red,
green,
blue
};
以上定义的day和color都是枚举类型,{}中的内容是枚举类型的可能取值,叫枚举常量。这些值都是有默认值的,默认从0开始,每次增加1,在定义的时候也是可以赋值的:
enum day
{
mon = 1,
sun = 7,
sat = 6
};
枚举的优点
增加代码的可读性和可维护性
和#define的标识符比较枚举有类型检查,更加严谨
防止了命名污染(封装)
便于调试
使用方便,一次可定义多个常量
枚举的使用
int main()
{
enum color str = red; //只有拿枚举类型给枚举常量赋值,才不会出现类型的差异
str = 6;
return 0;
}
联合体
联合体的定义
联合体是一种特殊的自定义类型。这种类型的变量包括了许多的成员,他的特点是这些成员共同使用一块空间。
举个栗子:
union un
{
char c;
int i;
};
//联合变量的定义
union un u;
联合的特点
联合体的成员公用一块空间,这样的联合体大小至少是最大成员的大小。
通过下面代码可以发现,它们公用一块空间,且还会改变对方的值
union un
{
int i;
char c;
};
int main()
{
union un u;
printf("%p\n", &(u.i));
printf("%p\n", &(u.c));
u.i = 0x11223344;
u.c = 0x55;
printf("%x\n", u.i);
return 0;
}
联合体大小的计算
联合的大小至少是最大成员的大小
当最大成员的大小不是最大对齐数的倍数的时候,就要对齐到最大对齐数的整数倍
栗子:
union un1
{
char c[5];
int i;
};
union un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union un1));
printf("%d\n", sizeof(union un2));
return 0;
}