赞
踩
在C语言当中,除了我们常用的几个基本的数据类型之外,还有一种类型叫自定义类型。比如:我们要描述一个学生。这个学生有姓名,性别,年龄,身高等。单独用基本的数据类型是不能完全描述的。这个时候就要使用我们自定义的类型来进行描述。自定义的类型有结构体,枚举和联合体。
一、结构体
结构体时一些值得集合,这些值被称为成员变量,每一个成员变量可以有不同的类型。
1,声明
struct tag//这里的tag表示标签,不是变量名
{
member-list;//成员变量
}variable-list;//变量列表,用来定义变量。注意这里的分号
比如,描述一个人:
- struct person
- {
- char name[20];
- char sex[5];
- int age;
- char nation[20];
- };
这里的变量列表可以没有。在变量列表里面定义的变量是全局变量。
2,定义且初始化
要定义,可以直接在变量列表里面,也可以在main函数里面定义。
- struct person
- {
- char name[20];
- char sex[5];
- int age;
- char nation[20];
- }person1;//结构体变量person1
-
- struct person person2;//结构体变量person2
-
- int main(void)
- {
- struct person person3;//结构体变量person3
- struct person person4[2];//结构体数组变量person4,有两个元素,都是结构体类型
- return 0;
- }

要初始化也很简单。
- #include<stdio.h>
-
- struct person
- {
- char name[20];
- int age;
- }person1 = {"zhangsan", 18};//结构体变量person1
-
- struct person person2 = {"lisi", 18};//结构体变量person2
-
- int main(void)
- {
- struct person person3 = {"wangwu", 18};//结构体变量person3
- struct person person4[2] =
- {"abc", 18, {"def", 18}};//加不加{}都可以
-
- return 0;
- }

有的时候,结构体还会出现嵌套的情况:
- struct person
- {
- char name[20];
- int age;
- }person1 = {"zhangsan", 18};//结构体变量person1
-
- struct people
- {
- struct person person;
- char nation[20];
- };
对其进行初始化:
struct people people = { {"zhangsan",18}, "XXX" };//嵌套结构体的初始化
我们在声明结构体的时候,也可以不完全声明,这个时候省略掉标签。
- struct
- {
- int a;
- char b;
- float c;
- }a;
有一种情况是不可以省略标签的,即使用typedef关键字的时候。typedef关键字能够为一种数据类型定义一个新名字。如果省略,就会报错。
- typedef struct S
- {
- int data[1000];
- int num;
- }s;
- s s1 = { {1,2,3,4}, 1000 };
3,结构体成员的访问
结构体成员的访问需要通过点(.)操作符来进行访问的。下面我们来打印这几个初始化的变量。
- #include<stdio.h>
-
- struct person
- {
- char name[20];
- int age;
- }person1 = {"zhangsan", 18};//结构体变量person1
-
- struct people
- {
- struct person person;
- char nation[20];
- };
-
- struct person person2 = {"lisi", 18};//结构体变量person2
-
- int main(void)
- {
- struct person person3 = { "wangwu", 18 };//结构体变量person3
- struct person person4[2] =
- {"abc", 18, {"def", 18}};//加不加{}都可以
-
- struct people people = { {"zhangsan",18}, "XXX" };//嵌套结构体的初始化
-
- printf("%s\n", person1.name);
- printf("%d\n", person1.age);
- printf("--------------\n");
- printf("%s\n", person2.name);
- printf("%d\n", person2.age);
- printf("--------------\n");
- printf("%s\n", person3.name);
- printf("%d\n", person3.age);
- printf("--------------\n");
- printf("%s\n", person3.name);
- printf("%d\n", person3.age);
- printf("--------------\n");
- printf("%s\n", person4[1].name);
- printf("%d\n", person4[1].age);
- printf("--------------\n");
- printf("%s\n", people.person.name);
- printf("%s\n", people.nation);
-
- return 0;
- }

现在,我们来看看结构体指针访问指向的变量的成员。有以下代码:
- struct Stu
- {
- char name[20];
- int age;
- };
-
- void print(struct Stu* ps)
- {
- printf("name = %s age = %d\n", (*ps).name, (*ps).age);
- //使用结构体指针访问指向对象的成员,为了简化,使用->操作符来替代(*).操作。
- printf("name = %s age = %d\n", ps->name, ps->age);
- }
- int main()
- {
- struct Stu s = { "zhangsan", 20 };
- print(&s);//结构体地址传参
- return 0;
- }

- #include<stdio.h>
-
- struct person
- {
- char name[20];
- int age;
- };
-
- struct people
- {
- struct person* person;
- char nation[20];
- };
-
- int main(void)
- {
- struct person person = { "zhangsan", 18 };
- struct people people = { &person ,"XXX"};
-
- printf("%s\n", people.person->name);
- printf("%d\n", people.person->age);
- printf("%s\n", people.nation);
-
- return 0;
- }

4,结构体传参
- 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;
- }

在传参的时候,参数需压栈。传递一个结构体对象的时候,由于结构体过大,参数在压栈的时候的系统开销比较大,导致性能的下降。另外,在计算结构体的大小的时候,如果是传的结构体的值,会导致结构体的大小无限的增大。所以,结构体在传参要传地址。
5,结构体内存对齐
现在,我们来讨论结构体的内存大小。
5.1,来看下面这段代码,计算它的大小(*):
- struct S1
- {
- char c1;
- int i;
- char c2;
- };
-
- int main(void)
- {
- printf("%d\n", sizeof(struct S1));
- return 0;
- }
我们发现,这个结构体的大小并不是我们认为的6个字节,而是12个字节。
结构体内存的计算如下:
1,第一个成员在与结构体变量偏移量为0的地址处。
2,其他的成员变量要对齐到对齐树的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8,有的默认为4。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
简单来说,就是:在内存里面,第一个变量放在开始的0地址处,其他的要放在什么地址处,要看对齐数。对齐数是默认对齐数和成员大小的最小值。按对齐数放了之后,这个结构体的大小是最大的对齐数的整数倍。
计算下面嵌套结构体的大小:
- struct S1
- {
- char c1;//1
- int i;//4
- };//大小为8
-
- struct S2
- {
- char c1;//1
- struct S1 s1;//8
- int d;//4
- };
-
-
- int main(void)
- {
- printf("%d\n", sizeof(struct S2));
- return 0;
- }

在计算的时候,不可以直接把成员变量的大小直接加起来,在取对齐数的最大值倍数处。比如第一个计算(*)处:加起来是6,在取对齐数(4)的整数倍,结果是8,这显然和计算的结果不符合。
在来看最后一道:
- struct S
- {
- short s1;//2
- char c1;//1
- int s2;//4
- };
-
-
- int main(void)
- {
- printf("%d\n", sizeof(struct S));
- return 0;
- }
如果做的熟练了,也不必要画出图来,直接进行计算就可以了。有的时候会遇到short和char在一起的情况,这个时候,可以直接看成4个大小。
5.2,存在内存对齐的原因:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说: 结构体的内存对齐是拿空间来换取时间的做法。这种做法是很常见的。
我们在设计结构体的时候,既要满足对齐,又要节省空间,那么就让占用空间小的成员尽量集中在一起。如果我们将(*)处的顺序变一下,比如两个char型放在一起,这个结构体的大小就会发生变化。
5.3,修改默认对齐数
- #pragma pack(1)//设置默认对齐数为1
- struct S2
- {
- char c3;
- int i2;
- char c4;
- };
- int main()
- {
- printf("%d\n", sizeof(struct S2));//6
- return 0;
- }
还原为默认:
#pragma pack()//取消设置的默认对齐数,还原为默认
二、位段
1,定义和声明
结构体可以实现位段。位段的声明和结构体类似。位段是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。采用位段结构既能够节省空间,又方便于操作。 比如位段A:
- struct A
- {
- int _a:2;
- int _b:5;
- int _c:10;
- int _d:30;
- };
位段的成员名后边有一个冒号和一个数字。后面数字的单位是比特。位段的成员必须是整型家族。
2,位段的内存分配
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。前面的位段A种,_a是int类型,是以4个字节开辟的。那么A的大小是多少(32位)?
- struct A
- {
- int _a : 2;
- int _b : 5;
- int _c : 10;
- int _d : 30;
- };
-
- int main()
- {
- //输出的结果是什么?
- printf("%d\n", sizeof(struct A));
- return 0;
- }
这是如何计算的?
在这里,_a开辟了4个字节的大小,但是_a只需要两个比特就够了。在32位下,还有30个比特。_b需要5个比特,还剩下25比特,_c需要10个比特,这样就剩下15个比特,_d需要30个比特,剩下的15个比特不够,就在开辟4个字节大小的空间来存放_b。这里的图假设是从左开始放。
我们来看这一道题(在VS上是从右开始放):
- struct S
- {
- char a : 2;
- char b : 3;
- char c : 4;
- char d : 5;
- };
-
- int main(void)
- {
- struct S s = { 0 };
- s.a = 10;
- s.b = 12;
- s.c = 3;
- s.d = 4;//在内存中是什么样的
- return 0;
- }

3,位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
三、枚举
枚举,就是一一列举。将可能的值进行一一列举。
1,定义:
- enum Day//星期
- {
- Mon,
- Tues,
- Wed,
- Thur,
- Fri,
- Sat,
- Sun
- };
- enum Day//星期
- {
- Mon,
- Tues,
- Wed,
- Thur,
- Fri,
- Sat,
- Sun
- };
-
- int main(void)
- {
- printf("%d\n", Mon);
- printf("%d\n", Tues);
- printf("%d\n", Wed);
- printf("%d\n", Thur);
- printf("%d\n", Fri);
- printf("%d\n", Sat);
- printf("%d\n", Sun);
-
- return 0;
- }

以上定义的 enum Day是枚举类型。{}中的内容是枚举类型的可能取值,也叫枚举常量 。这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
- enum Day//星期
- {
- Mon,
- Tues,
- Wed,
- Thur = 6,
- Fri,
- Sat,
- Sun
- };
2,优点
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
3,使用
在switc语句当中,如果我们的分支太多,case后面的标签又是数字,这个时候,要知道这个数字代表什么,就需要去找。但是如果我们使用enum,就可以把数字换成让人一眼便知的常量名,大大提高代码的可读性。
- enum Game
- {
- EXIT,
- GAME_BEGIN,
- GAME_DISCONTINUE
- };
-
- int main(void)
- {
- switch (1)
- {
- case GAME_BEGIN:
- printf("你开始了游戏!\n");
- case GAME_DISCONTINUE:
- printf("你中止游戏!\n");
- case EXIT:
- printf("你退出了游戏!\n");
- }
-
- return 0;
- }

四、联合体(共用体)
1,定义:
联合体也是一种特殊的自定义类型,这种类型定义的变量包含一系列的成员。这些成员是公用一块空间,所以也叫共用体。联合体的定义和结构体类似。
- //联合类型的声明
- union Un
- {
- char c;
- int i;
- };
- //联合变量的定义
- union Un un;
2,特点:
联合体是共用一块空间的,这样,这个联合变量的大小,至少是最大成员的大小,才能存放最大的那个成员。
- #include<stdio.h>
-
- union Un
- {
- int i;
- char c;
- };
- union Un un;
-
- int main(void)
- {
- // 下面输出的结果是一样的吗?
- printf("%p\n", &(un.i));
- printf("%p\n", &(un.c));
-
- //下面输出的结果是什么?
- un.i = 0x44;
- un.c = 0x55;
- printf("%x\n", un.i);
-
- return 0;
- }

从这里可以看出联合体的特点:共用一块空间。正因为如此,所以在打印un.i的时候,会发现结果是0x55。这里的0x44被覆盖了。由联合体的特点,可以用来判断当前计算机的大小端存储。
- union Un
- {
- int i;
- char ch;
- };
-
- int main(void)
- {
- union Un un;
- un.i = 1;
- printf("%d\n", un.ch);//1,是小端
-
- return 0;
- }
3,联合体大小的计算
联合的大小至少是最大成员的大小。 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
- union Un1
- {
- char c[5];//共5个元素,每个占1个字节,总的大小为5
- int i;//4
- };
-
- int main(void)
- {
- printf("%d\n", sizeof(union Un1));//8,是最大对齐数4的倍数。注意不是5,这里的5是数组总的大小
- return 0;
- }
- union Un2
- {
- short c[7];//2*7
- int i;//4
- };
-
- int main(void)
- {
- printf("%d\n", sizeof(union Un2));//16
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。