赞
踩
大家好,我是熊猫,今天我们来认识一下C语言中的自定义数据类型,
C语言中的char,short,int,long,float,double这些类型我们大家肯定已经非常熟悉了,
这些都属于C语言自身所带的类型,但是在我们的日常生活中只具有单一属性的事物少之又少,
更多的是同时具有各种各样的不同属性,
比如作为在校大学生的我:“姓名”,“年龄”,“班级”,“学号”,“身高”等等等,
再比如一本书:“书名”,“作者”,“编号”,“价格”等等等
这些都不是用一个简单的数据类型就可以表示的,那么,我们就需要用到这些自定义类型
来定义我们需要的数据类型。下面就让我们从结构体开始认识吧!
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
struct tag{
member—list;
};
member—list是成员列表
例如描述一本书籍:
struct book{
char title[20];//书名
char writer[20];//作者
float price;//价格
};
这里的struct是结构体关键字,book是结构体标签,struct book才是一个完整的结构体名。
在创建结构体变量是必须写完全。
创建结构体有两种方法:
一种是直接在声明结构时直接创建,这种创建出来的是全局变量,
另一种是通过结构体类型创建。
例如:
struct book{
char title[20];//书名
char writer[20];//作者
float price;//价格
}b1,b2;
struct book b3,b4;
我们在声明一个结构体时,可以不给它“起名字”,这个称为不完全声明
例如:
struct {
char ch;
char str[20];
int num;
}d1,d2;//只能在声明结构体的同时创建变量
//struct d3,d4; 这种是错误的,因为我们只有结构体关键字,而并不知道这个结构体的名字
struct {
char ch;
char str[20];
int num;
}d3,d4;
//这里我们需要注意:这里的d3和d4是相同的,
//但是d3和d1、d2是不同的,虽然它们的类型看起来“完全一样”
//但是编译器还是会判定为不同的类型
在结构中包含一个为结构体本身的类型的变量
例如:
struct str{
int data;
struct str* ps;
};
//在这里我们可以设置一个指向自身类型的指针,这属于数据结构中的链表的用法.
//struct str{
//int data;
//struct str s;
//};
//这里的用法是不行的,这里s变量里还有一个结构体变量s,s变量里面还有一个s,属于无限套娃,无法计算内存大小.
看代码:
//结构体的初始化
struct str{
char ch;
int data;
}s1={'a',10}; // 1
struct str s2={'b',20}; // 2
struct str s3={.data=30,.ch='c'}; // 3
//结构体的赋值
struct str s4;
scanf("%c %d",&s4.ch,&s4.data);
//如果是结构体指针,那就有两种赋值方式
struct *p=s4;
scanf("%c %d",&(*p).ch,&(*p).data);// *p等同于s4
scanf("%c %d",&p->ch,&p->data);//->为箭头运算符,可以令结构体指针直接指向结构体成员
结构体的内存对齐是结构体的一个很重要的知识,这个与结构体在内存中的存储方式有关
下面我们先来计算一下下面这两个结构体的大小:
struct str1
{
char ch;
short sh;
int num;
};
struct str2
{
char ch;
int num;
short sh;
};
这里如果我们不知道结构体的内存对齐规则,那么肯定有很多朋友会认为这两个结构体的大小都是7,
sizeof(str1)=1+2+4=7,
sizeof(str2)=1+4+2=7;
那么既然我们专门讲了这个例子的话那就说明这是错误的结果了,
那么到底是第一个错了还是第二个错了还是两个大小都不对呢,
下面看实际运行结果:
这里为什么会出现这样的结果呢?
这里我们先来了解一下结构体内存对齐的规则。
- 第一个成员在与结构体变量偏移量为0的地址处。
2.从第二个成员开始,偏移量必须是 对齐数(默认对齐数与它自身大小中的较小者) 的整数倍。
3.结构体总大小为最大对齐数的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们通过画图进行详细了解:
我们也可以通过offsetof()函数来得到哥哥成员的偏移量来进行验证:
代码如下:
#include<stdio.h> #include<stdlib.h> struct str1 { char ch; short sh; int num; }; struct str2 { char ch; int num; short sh; }; int main() { printf("offsetof(struct str1, ch) = \t%d\n", offsetof(struct str1, ch)); printf("offsetof(struct str1, sh) = \t%d\n", offsetof(struct str1, sh)); printf("offsetof(struct str1, num) = \t%d\n", offsetof(struct str1, num)); printf("offsetof(struct str2, ch) = \t%d\n", offsetof(struct str2, ch)); printf("offsetof(struct str2, num) = \t%d\n", offsetof(struct str2, num)); printf("offsetof(struct str2, sh) = \t%d\n", offsetof(struct str2, sh)); return 0; }
为什么存在内存对齐?
大部分的参考资料都是如是说的:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
我们上面的两个例子中成员是完全相同的,但是一个大小为8,一个却为12;
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
修改对齐数需要用到预处理指令#pragma
代码实现:
#include<stdio.h> #pragma pack(1)//设置默认对齐数为1 struct str1 { char ch; short sh; int num; }; struct str2 { char ch; int num; short sh; }; int main() { printf("%d\n", sizeof(struct str2)); return 0; }
还原默认对齐数:
#include<stdio.h> #pragma pack(1)//设置默认对齐数为1 #pragma pack()//还原默认对齐数 struct str1 { char ch; short sh; int num; }; struct str2 { char ch; int num; short sh; }; int main() { printf("%d\n", sizeof(struct str2)); return 0; }
我们在进行函数传参时既可以进行传值传参也可以进行传址传参
结构体也同样可以使用以上两种方法
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(struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s); //传结构体 print2(&s); //传地址 return 0; }
如上面这种情况,
结构体非常大,如果我们进行传址传参的话形参是实参的一份临时拷贝,
编译器就会在内存中开辟一块和实参一样大的区域存放形参,这样做会浪费很大的空间,
而使用传址传参就只是传出一个指针,而一个指针大小无非是4/8个字节,
因此,我们在进行结构体传参时更建议使用传址传参。
结构体讲完就得讲讲结构体实现 位段 的能力。
我想,大多数同学都没有听说过位段这个概念吧,所以接下来我们就不卖关子,
直接通过下面的实例来了解它。
位段的声明和结构体是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 、signed int 或 char 的整形家族。
2.位段的成员名后边有一个冒号和一个数字。
代码实例:
#include<stdio.h>
struct str
{
int a:4;
int b:10;
int c:20;
int d:8;
};
int main()
{
printf("%d\n",sizeof(struct str));
return 0;
}
运行结果:
这里关于位段的知识不进行过多赘述,我们知道有这个知识点就好,
当然他也有自己的使用场景:计算机网络里对数据的分段传输时需要加上描述信息,这时就可以使用位段,
可以对空间进行合理地使用。
枚举顾名思义就是–一一列举,把可能的情况全部都列举出来
一周有七天,可以一一列举,
一天有二十四个小时,可以一一列举,
英文字母有二十六个,也可以一一列举。
enum Day//星期
{
MON,
TUES,
WED,
THUR,
FRI,
SAT,
SUN
};
这里枚举类型默认从0开始,既:
MON == 0 , TUES == 1 , WED == 2 ……
在初始化时可以更改他们的值,eg:
MON = 3,
那么TUES就会变为4,往后依次增大1
为什么使用枚举?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1.增加代码的可读性和可维护性
2.和#define定义的标识符比较枚举有类型检查,更加严谨。
3.防止了命名污染(封装)
4.便于调试
5.使用方便,一次可以定义多个常量
enum Day { MON, TUES, WED THUR, FRI, SAT, SUN }; int main() { enum Day d; scanf("%d",&d); switch(d) { case MON: printf("星期一\n"); break; case TUES: printf("星期二\n"); break; case WED: printf("星期三\n"); break; case THUR: printf("星期四\n"); break; case FRI: printf("星期五\n"); break; case SAT: printf("星期六\n"); break; case SUN: printf("星期日\n"); break; } return 0; }
联合体我们也顾名思义一下那就是–站在一起,共同使用。
联合体也是一个特殊的自定义类型,可以包含不同的成员,而这些成员共同使用同一块内存空间。(所以也叫公用体)
union un
{
int num;
float fa;
char str[10];
};
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
经典例题:判断该计算机是大端存储还是小端存储
int main()
{
int a=0x1;
//p存放的是变量a的首地址(也就是低地址),
//因为小端存储时低位放在低地址处,所以当*p为1是则为小端存储,*p为0则是大端存储
char*p=(char*)&a;
printf("%d\n",*p);
return 0;
}
上面我们是使用了强制类型转换的方法取得了a的地址,但是根据今天我们讲的共用体,我们就可以设计一种更巧妙的方法进行判断
如下:
union un
{
int a;
char ch;
};
int main()
{
union un d = { 0 };
scanf("%d", &d.a);
printf("%d\n", d.ch);
return 0;
}
联合体也有对齐数,
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union un1 { char ch1; char ch2; int data; }; union un2 { char ch1; char str[10]; int data; }; int main() { printf("%zu\n",sizeof(union un1)); printf("%zu\n",sizeof(union un2)); return 0; }
运行结果:
我是在VS下测试的,VS的默认对齐数为8
下面看图解:
以上就是关于结构体、枚举、以及联合的知识,这里我再写几点熊猫自己的总结:
- 结构体和联合体都需要内存对齐,设计时尽量将小变量放在一起,内存对齐有时会造成内存的浪费,但是却可以提高成员访问速度,
也就是我们常说的用内存换时间。- 结构体位段的存在就是为了节省空间,所以位段不需要内存对齐,使用位段时要注意成员后面的 “:” 以及分配的比特位。
- 枚举类型各个成员之间是通过 “,” 连接的,也就是说枚举类型实际上只有一个变量,因此:sizeof(enum day)== 4。
- 在定义自定义类型时要注意大括号后面的 “;” ,这是一条语句结束的标志,如果有的编译器没有自动给出我们也不能忘记。
那么今天的内容就写到这里,感谢大家的支持,欢迎大家来评论区一起探讨,大家的鼓励是继续更新的巨大动力。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。