各位csdn的友友们肯定都掌握了c语言中char,short, int, long, float, double的类型,这些都是我们c语言中的一些内置类型,其实c语言是可以允许我们创造一些类型的,今天阿博就带领友友们一起掌握这些新的自定义类型😊😊😊
文章目录
- 结构体
- 1.结构体类型的声明
- 2.结构的自引用
- 3.结构体变量的定义和初始化
- 4.结构体内存对齐
- 5.结构体传参
- 6.结构体实现位段(位段的填充&可移植性)
- 枚举
- 1.枚举类型的定义
- 2.枚举的优点
- 3.枚举的使用
- 联合
- 1.联合类型的定义
- 2.联合的特点
- 3.联合大小的计算
结构体
1.结构体类型的声明
2.结构的自引用
3.结构体变量的定义和初始化
4.结构体内存对齐
5.结构体传参
6.结构体实现位段(位段的填充&可移植性)
枚举
1.枚举类型的定义
2.枚举的优点
3.枚举的使用
联合
1.联合类型的定义
2.联合的特点
3.联合大小的计算
结构体类型的声明
struct stu
{
char name[20]; //名字
int age; //年龄
char sex[5]; //性别
char id[20]; //学号
};
注意最后那个分号千万不能丢哦!!!
这里的struct stu就是定义一个学生类型,这里的name,age,sex,id就是成员变量,好了当我们了解这些后,就可以试着用结构体类型来创建变量了
struct stu
{
char name[20]; //名字
int age; //年龄
char sex[5]; //性别
char id[20]; //学号
}s4,s5,s6;
int main()
{
struct stu s1;
struct stu s2;
struct stu s3;
}
友友们注意这里s1,s2,s3,s4,s5,s6都是我们用结构体创建出来的结构体变量,但是是s1,s2,s3是局部变量,s4,s5,s6是全局变量
结构体特殊的声明
struct
{
char c;
int a;
double d;
}s1;
struct
{
char c;
int a;
double d;
}*ps;
int main()
{
/*ps = &s1;*/ //error
return 0;
}
以上两种结构体没有类型名,这就是传说中的匿名结构体了,友友们注意这种结构体定义出来的全局变量只能使用一次,还有虽然两个匿名结构体内容是相同的,但是我们也不能写*ps=&s1,因为在编译器看来,这两个地址类型是不一样的
结构体的自引用
/这是一个错误的示范
//struct Node
//{
// int data; //这里我们无法求得结构体的大小,它会无限循环下去
// struct Node n;
//};
//int main()
//{
// return 0;
//}
struct Node
{
int data; /*4*/
struct Node* next; /*4/8*/
};
int main()
{ //正确的自引用方式
struct Node n1;
struct Node n2;
n1.next = &n2;
return 0;
}
typedef struct
{
int data;
char c;
}s;
typedef struct
{
int data;
Node* next; //error
}Node;
typedef struct Node
{
int data;
struct Node* next; //correct
}Node;
注意typedef 是把struct 这个匿名结构体重新起名为s和Node,这里的s和Node是结构体类型名,不是定义的结构体变量!!!而且第二个typedef不能这样用,因为我们在没有重新命名Node之前,结构体变量中就使用了Node,这个顺序应放在命名之后使用!!!
结构体变量的定义和初始化
struct S
{
int a;
char c;
}s1; //全局变量
struct S s3; //全局变量
struct C
{
float f;
struct S s;
};
int main()
{
struct S s2={100,'q'}; //局部变量
struct C sc = { 3.14f,{200,'w'} }; //结构体嵌套初始化
struct S s3 = { .c = 'a',.a = 150 }; //注意这里我们也可以不按照顺序进行初始化
return 0;
}
友友们注意结构体初始化的时候没有我们想象的那么复杂,其实就是定义变量的同时赋初值,不同的结构体初始化的时候可能是不相同的,因为它们所包含的类型可能不一样,结构体嵌套初始化的时候就是在大括号里面在加一个大括号然后进行赋初值
结构体内存对齐
友友们,重点来了哈,来一起和阿博打起12.1分的精神拿捏它吧🦹♂️🦹♂️🦹♂️
struct S1
{
int a;
char c;
};
struct S2
{
char c1;
int a;
char c2;
};
struct S3
{
char c1;
int a;
char c2;
char c3;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
return 0;
}
这里我们惯性思维会告诉我们是5, 6,7,但是当我生成结果的时候,可能会大吃一惊,下面来一起跟阿博探秘吧.这里阿博先给大家传输一些内功😁😁
1.结构体的第一个成员永远都放在0偏移处
2.从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处,这个对齐数是成员自身大小和默认对齐数的较小值,在VS环境下默认对齐数为8,gcc环境下,没有默认对齐数,没有默认对齐数时,对齐数就是成员自身的大小.
3.当全部成员存放进去之后,结构体的大小必须是所有成员对齐数中最大对齐数的整数倍,如果不够,则浪费空间对齐.
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的成员中的最大对齐数)的整数倍.
这里可以给友友们测试下,在测试前先给友友们介绍一个函数offsetof
这里我们也可以看出它们的偏移量.
2.
3.
4.
友友们我们来个结构体嵌套来测试一下我们的实力,加油哦😊😊😊
这里友友们可能会想为什么存在内存对齐呢,这里阿博分两方面给友友们讲.
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常.
2.性能原因 :数据结构(尤其是栈)应该尽可能的在自然边界上对齐.原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存仅需要一次访问.
总体来说:结构体内存对齐是拿空间来换取时间的做法.这里阿博可以教大家一种既满足对齐,又节省空间的做法:让占用空间小的成员尽可能的集中在一起
这里给友友们看下图解哦
修改默认对齐数
#pragma pack(1) //设置默认对齐数为8
struct S1
{
char c1;//1,1,1
int i; //4,1,1
char c2; //1,1,1 //默认是1,就相当于没有对齐,结果是6
};
#pragma pack() //取消设置的默认对齐数,还原为默认
结论:结构在对齐方式不合适的时候,我们可以自己更改默认对齐数
结构体传参
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 print2(const struct S*ps) //防止被修改
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
这里友友们注意我们首选print2函数,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降.所以结构体在传参的时候,要传结构体的地址.
结构体实现位段
什么是位段呢,可能会有很多友友疑问,因为我们都只知道段位😄😄😄,下面阿博就给大家普及一下
1.位段的成员通常是 int ,unsigned int ,signed int
2.位段的成员名后边有一个冒号和一个数字.
这里A就是一个位段.
//位段--二进制位
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
}; //47 bit
int main()
{
struct A sa = { 0 };
printf("%d\n", sizeof(sa));
return 0;
}
这里一共有47个比特位,按常理说6个字节,但为什么是8个字节呢,下面来和阿博一起探索吧
位段的内存分配
1.位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
2.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
这里我们调试一下,发现和我们的假设一样,但是这只代表在vs上是这样,不代表其他编译器也是这样
位段的跨平台问题总结
1.int 位段被当成有符号数还是无符号数是不确定的
2.位段中最大的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器上就会出问题)
枚举类型的定义
enum Sex
{
//枚举的可能取值 ,默认是从0开始,递增1的
//枚举常量
MALE=5,
FAMALE,
SECRET
};
int main()
{
//enum Sex s=MALE; //enum Sex=1;这里在c语言中是支持的,但是在c++中就不支持了
printf("%d\n", MALE);
printf("%d\n", FAMALE);
printf("%d\n", SECRET);
return 0;
}
这里我们也可以改变它的初值,注意枚举常量是用逗号隔开的,最后一个逗号可以不加,枚举的好处可以让我们代码的可读性大大提高,枚举本身有类型检查,可以让我们的代码更加严谨,还便于我们代码的调试,一次可以定义多个常量
联合类型的定义
union Un
{
char c;//1
int i;//4
};
int main()
{
union Un u;
printf("%d\n", sizeof(u));
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
printf("%p\n", &u);
}
联合体的应用
#include<stdio.h>
union Un
{
char c;
int a;
};
int main()
{
union Un u;
u.a = 1;
if (u.c == 1)
{
printf("小端\n");
}
else
printf("大端\n");
return 0;
}
联合体的特点:联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小,(因为联合至少得有能力保存那个最大的成员)
联合体大小的计算
1.联合的大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un
{
char arr[5]; //5
int n; //4
};
int main()
{
printf("%d\n", sizeof(union Un));
return 0;
}
这里有5个char 的类型,大小是5,int 的大小是4,所以该联合体的大小至少是5,但是5不是4的整数倍,所以我们这里取了8个字节
这里给友友们出个题测试一下
union Un
{
short s[7];
int n;
};
int main()
{
printf("%d\n", sizeof(union Un));
return 0;
}
这里有7个短整型,大小是14,有一个整形,大小是4,所以该联合体大小至少是14,但是14不是4的整数倍,所以需要往后浪费2个字节,所以大小是16
好了友友们,本期就到此结束了,如果你们感觉对自己有帮助的话,可以给阿博点个关注哦,后续阿博会继续给大家带来一些干货,下期再见💕💕💕