bilibiliclass54-59_C语言_自定义类型详解(结构体+枚举+联合)

自定义类型:结构体,枚举,联合

本章重点

结构体
    结构体类型的声明和变量的定义
    结构的自引用
    结构体初始化
    结构体内存对齐
    结构体传参
    结构体实现位段(位段的填充&可移植性)
枚举
    枚举类型的定义
    枚举的优点
    枚举的使用
联合
    联合类型的定义
    联合的特点
    联合大小的计算


结构体

结构的基础知识:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

结构体类型的声明和变量的定义

struct tag
{
member-list;//成员列表
}variable-list;//变量列表

简单例子:

创建学生s1,s2,s3,s4,s5

例如描述一个学生:
      声明一个结构体类型
      声明一个学生类型,是想通过学生类型来创建学生变量(对象)
      描述学生:名字+年龄+性别+学号

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}s4,s5,s6;//分号不能丢
//s4,s5,s6全局变量

    struct Stu s3;//s3全局变量

int main()
{
    //创建的结构体变量
    //s1,s2局部变量
    struct Stu s1;
    struct Stu s2;    
}

特殊的声明

在声明结构的时候,可以不完全的声明。

使用场景:通常在只需要一次的结构体使用
 

简单例子:

//匿名结构体类型:没有名字,省略掉了结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;//这能在这里创建变量

struct
{
int a;
char b;
float c;
}* px;//匿名结构体指针

int main()
{
px=&x;//警告: 编译器会把上面的两个声明当成完全不同的两个类型,是非法的!!!
}

 

结构的自引用

数据结构:数据在内存中存储的结构
链表:1 -> 2 -> 3 -> 4 -> 5

|-------------------------------------------------------|
|数值域  指针域(存放下一个数据的地址)    |
|-------------------------------------------------------|

//err代码1
结构中包含一个类型为该结构本身的成员是 错误 的

struct Node
{
    int data;
    struct Node next;
};

否则sizeof(struct Node)=无穷大


正确的自引用方式:

//代码2
struct Node
{
    int data;
    struct Node* next;
};


重命名:给struct Node起一个小名叫Node
 

typedef struct Node
{
    int data;
    struct Node* next;
}Node;


结构体变量初始化

//初始化:定义变量的同时赋初值。
#include <stdio.h>
struct Point
{
    int x;
    int y;
};

struct Node    //类型声明
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = { 10, {4,5}, NULL };//结构体嵌套初始化

struct Node n2 = { 10, {4,5}, NULL };//初始化

int main()
{
    struct Node n3 = { 10, {4,5}, NULL };//初始化
    printf("%d %d %d %s", n1.data, n1.p.x, n1.p.y, n1.next);
    return 0;
}
//结果:10 4 5 (null)

 

结构体内存对齐

问题:请计算结构体的大小,4个printf的结果是多少?

struct S1
{
char c1;
int i;
char c2;
};

struct S2
{
char c1;
char c2;
int i;
};

struct S3
{
double d;
char c;
int i;
};

struct S4//结构体嵌套问题
{
char c1;
struct S3 s3;
double d;
};

int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
printf("%d\n", sizeof(struct S4));
return 0;
}

答案:
12
8
16
32


解析:求结构体大小标准步骤

struct S1//12
{
char c1;
int i;
char c2;
};


1.求出成员大小

char c1;            //1字节
int i;              //4字节
char c2;            //1字节

2.求出各自对齐数
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc没有默认对齐数
以VS为例:
char c1;          //1字节/8字节-->1字节
int i;                 //4字节/8字节-->4字节
char c2;          //1字节/8字节-->1字节

以gcc为例:

char c1;            //1字节
int i;                  //4字节
char c2;            //1字节

3.求出结构体总大小
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
结构体总大小:4的整数倍

4.画出内存结构
第一个成员在与结构体变量偏移量为0的地址处
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
char c1;       //1的整数倍
int i;             //4的整数倍
char c2;       //1的整数倍

5.算出结构体总大小

结构体总大小:4的整数倍
存完char c2后偏移量为9,不是4的整数倍,所以为12

|            |
|......      |
|------------|<----假设从这里开始放(偏移量为0)
| char c1    |
|------------|偏移量为1
|  浪费      |
|------------|偏移量为2
|  浪费      |
|------------|偏移量为3
|  浪费      |
|------------|偏移量为4<----4的整数倍
|  int i     |
|------------|偏移量为5
|  int i     |
|------------|偏移量为6
|  int i     |
|------------|偏移量为7
|  int i     |
|------------|偏移量为8<----1的整数倍
| char c2    |
|------------|偏移量为9
|  浪费      |
|------------|偏移量为10
|  浪费      |
|------------|偏移量为11
|  浪费      |
|------------|偏移量为12
|......      |
|            |


S2求法同上:

struct S2//8
{
char c1;
char c2;
int i;
};

 

struct S3//16
{
double d;
char c;
int i;
};


1.求出成员大小
double d;    //8
char c;        //1
int i;        //4
2.求出对齐数
double d;    //4
char c;        //1
int i;        //4
3.求出结构体总大小
    4的整数倍
4.画出内存结构
double d;    //4
char c;        //1
int i;        //4

|         |
|......   |
|---------|<----假设从这里开始放(偏移量为0)
|double d |
|---------|偏移量为1
|double d |
|---------|偏移量为2
|double d |
|---------|偏移量为3
|double d |
|---------|偏移量为4
|double d |
|---------|偏移量为5
|double d |
|---------|偏移量为6
|double d |
|---------|偏移量为7
|double d |
|---------|偏移量为8<----1的整数倍
|char c   |
|---------|偏移量为9
|  浪费   |
|---------|偏移量为10
|  浪费   |
|---------|偏移量为11
|  浪费   |
|---------|偏移量为12<----4的整数倍
|int i    |
|---------|偏移量为13
|int i    |
|---------|偏移量为14
|int i    |
|---------|偏移量为15
|int i    |
|---------|偏移量为16


 

struct S4//32
{
char c1;
struct S3 s3;
double d;
};

1.求出成员大小
char c1;//1
struct S3 s3;//16
double d;//8
2.求出对齐数
char c1;//1
struct S3 s3;//8
double d;//4
3.求出结构体总大小
    8的整数倍
4.画出内存结构
char c1;//1
struct S3 s3;//8
double d;//4

|            |
|......      |
|------------|<----假设从这里开始放(偏移量为0)
|char c1     |
|------------|偏移量为1
|  浪费      |
|------------|偏移量为2
|  浪费      |
|------------|偏移量为3
|  浪费      |
|------------|偏移量为4<----4的整数倍
|  浪费      |
|------------|偏移量为5
|  浪费      |
|------------|偏移量为6
|  浪费      |
|------------|偏移量为7
|  浪费      |
|------------|偏移量为8<----8的整数倍
|struct S3 s3|
.............
|struct S3 s3|
|------------|偏移量为24<----4的整数倍
| double d   |
..............
| double d   |
|--over------|偏移量为32<----8的整数倍

int main()
{
printf("%d\n", sizeof(struct S1));//12
printf("%d\n", sizeof(struct S2));//8
printf("%d\n", sizeof(struct S3));//16
printf("%d\n", sizeof(struct S4));//32
return 0;
}

 
为什么存在内存对齐?

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地对齐
访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
例如:win32平台,32根地址线,32根数据线-->一次读4字节

01   01 00 00 |00
char int      |
一次读取       |二次读取
01   00 00 00 01 00 00 00
char --浪费-- int      
一次读取

总体来说:
结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起

 

修改默认对齐数

#pragma 这个预处理指令可以改变我们的默认对齐数

例子:
 

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认


结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

offsetof
偏移量求取宏

offsetof简单例子
 

#include <stdio.h>
#include<stddef.h>//offsetof头文件
struct S
{
    char c;
    int i;
    double d;
};
int main()
{
    printf("%d\n",offsetof(struct S,c));//0
    printf("%d\n",offsetof(struct S,i));//4
    printf("%d\n",offsetof(struct S,d));//8
    return 0;
}

百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察: offsetof 宏的实现

 

结构体传参

#include <stdio.h>
struct S
{
    char c;
    int i;
    double d;
};

//初始化传地址
void Init(struct S* ps)
{
    ps->c = 'w';
    ps->i = 100;
    ps->d = 3.14;
}

//结构体传值
void Print1(struct S tmp)
{
    printf("%d %c %lf\n",tmp.c,tmp.i,tmp.d);
}

//传地址
void Print2(struct S* ps)
{
    printf("%d %c %lf\n", ps->c ,ps->i ,ps->d);
}

int main()
{
    struct S s;
    Init(&s);
    //不推荐:传结构体
    //若结构体过大,临时拷贝的系统开销大,会导致性能的下降
    Print1(s);

    //推荐:传地址,4-8字节
    Print2(&s);
    return 0;
}

 

位段

节省空间的结构体

什么是位段?

结构体实现 位段

位段的声明和结构是类似的,有两个不同
1.位段的成员必须是 int、unsigned int 或signed int   (其实char也可以)   (通常位段的类型是相同或相似的成员)
2.位段的成员名后边有一个冒号和一个数字。
 
位段简单例子

struct A
{
//类型 变量名:所占的bit位
//所占的bit位<=类型bit位(win32:int-32bit)
int a:2;
int b:5;
int c:10;
int d:30;
};


位段的内存分配

位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的


例子1:

struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};

共计47bit
1字节==8bit


由于位段类型为int,所以一次开辟4字节==32bit


开辟32bit
{
    a-2bit
    b-5bit
    c-10bit
    剩余32-17=15bit    <   d所需要的30bit
    在VS编译器下浪费掉15bit
    再开辟32bit
}
再开辟32bit
{
    d-30bit
    剩余2bit被浪费
}

printf("%d\n", sizeof(struct A));//共计8字节


例子2
 

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;
}

调试->窗口->内存
 


位段的跨平台问题

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:
跟结构相比,位段可以达到同样的效果,

但是可以很好的节省空间,但是有跨平台的问题存在。

位段的应用:网络数据的传输

 

枚举    即为一 一列举

枚举类型的定义

//颜色
enum Color//枚举类型
{
    //枚举常量
    RED,
    GREEN,
    BLUE
};
int main()
{
    //枚举类型 变量名 = 赋值;
    enum Color c =BLUE;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
    
    printf("%d %d %d\n",RED,GREEN,BLUE);
    //结果0 1 3

    return 0;
}

枚举常量取值都是有值的,默认从0开始,一次递增1

当然在定义的时候也可以赋初值,下一个默认为前一个加1

例如

enum Color//颜色
{
    RED = 1,
    GREEN,//(默认为2)
    BLUE = 4
};

枚举的大小为4字节,枚举类型无法自己指定

枚举的优点

我们可以使用 #define 定义常量,为什么非要使用枚举?
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名冲突(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量

#define 定义常量,例如:
 

#define    RED 0
#define    GREEN 1
#define    BLUE 2

 


 
联合(共用体)

联合类型的定义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,
特征是这些成员公用同一块空间(所以联合也叫共用体)。


简单例子

//联合类型的声明
union Un
{
    char c;
    int i;
};

//联合变量的定义
union Un un;

//计算连个变量的大小
printf("%d\n", sizeof(un));//4字节


联合的特点

联合的成员是共用同一块内存空间的,&u == &(un.i) == &(un.c)
一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
 

 

百度面试题:
判断当前计算机的大小端存储


方法一:强制类型转化

#include<stdio.h>
int check_sys()
{
    int a=1;
    return *(char*)&a;
}
int main()
{
    if(check_sys())
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}


方法二:联合

#include<stdio.h>
int check_sys()
{
    union Un
    {
        char c;
        int i;
    }u;
    u.i=1;
    return u.c;
}

int main()
{
    if(check_sys())
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}

 

联合大小的计算

联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,
就要对齐到最大对齐数的整数倍


简单例子:
 

#include <stdio.h>
union Un
{
    int i;
    //自身大小4字节
    //默认对齐数8
    //min{自身大小,默认对齐数}==4
    //对齐数==4
    char arr[5];
    //自身大小5字节
    //元素类型大小1字节
    //默认对齐数8
    //min{元素类型大小,默认对齐数}==1
    //对齐数==1

    //最大成员大小(5)不是最大对齐数(4)的整数倍
    //对齐到最大对齐数的整数倍(8)
};
int main()
{
    printf("%d\n", sizeof(union Un));
}
/*
结果:8
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值