桃猿三结义:结构、枚举、联合 下

简介: 桃猿三结义:结构、枚举、联合

💦 offsetof 宏

⭕ 函数原型和头

⭕ 函数的返回值

⭕ 函数的功能

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

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

点我跳转

💦 结构体传参

❓❔ 结构体传参可以传值也可以传址,问print1(传值)和print2(传址)哪种方式更好

#include<stdio.h>
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个字节

从功能的角度来分析(这是相对的):

▶ 结构体传值:假设我们希望在对结构体的内容做一些修改,那么传值就有一定的局限性

▶ 结构体传址:而传址可以有权限去对结构体的内容做一些修改;当然从某方面看,它又是不安全的,如果不希望修改其内容,我们可以加上const来限定

小结:

▶ 结构体传参时,要传结构体的地址

💦 结构体实现位段(位段的填充&可移植性)

❓❔ 什么是位段

🔑 位段的声明和结构是类似的

▶ 位段的成员必须是int、unsigned int 或 signed int (注:经测试位段的成员也能是char类型)

▶ 位段的成员名后面有一个冒号和一个数字

❓❔ 观察分析以下包含位段成员的结构体大小是多大

#include<stdio.h>
struct A
{
  int _a : 2;
  int _b : 5;
  int _c : 10;
  int _d : 30;
};
int main()
{
  printf("%d\n", sizeof(struct A));//8
  return 0;
}

📝 分析:

int _a : 2; 代表_a这个变量占2个bit位

int _b : 5; 代表_b这个变量占5个bit位

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

📝 小结:

位段涉及很多不确定的因素,位段是不跨平台的,注重可移植程序应该避免使用位段


🔎 探讨位段里的内存数据是如何开辟的

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

🧷 假设在VS中:当一块空间剩余的空间不够下一个成员使用时,它会浪费掉。且先使用低位的内容再使用高位的内容

📝 小结:

经分析验证:在VS中位段的存储模式正如我们上述的假设(注意仅适用于VS)


⚠ 位段的跨平台问题

▶ int位段被当成有符号数还是无符号数是不确定的

▶ 位段中最大位的数目不能确定(假设是int位段,接着为a分配30个bit的空间 -> int _a : 30,这样写可能有问题,因为在16位平台下,一个int占16bit)

▶ 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义

▶ 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

📝 小结:

▶ 位段可以很好的节省空间,但是有跨平台的问题


🔎 位段的应用

以上是一个IP数据包的格式:

数据以上的信息都是为了正确的传输信息,如果这些信息在传输时大小不以限制(比如4位版本号给个整型、4位首部长度给个整型…),🧑发了一百条变态给👦,虽然就两字,但是浪费的资源却是庞大的;如果使用位段(比如4位版本号给4位,4位首部长度给4位…),那么效率会大大的提高

二、枚举

💦 什么是枚举

🔑 官方来说:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。

简单来说就是一一列举,把可能的取值一一列举

💦 枚举类型的声明和定义

------------------------枚举的声明样式------------------------

enum tag

{

  possible values1,

  possible values2,

  possible values3

} variable-list;

------------------------解释------------------------

▶ enum是枚举关键字

▶ tag是枚举的标签名,是自定义的

▶ enum tag就是枚举类型

▶ {}里面放的是枚举可能取值,也叫枚举常量

▶ variable-list是变量

enum Day
{
  Mon,
  Tues,
  Web,
  Thur,
  Fri,
  Sat,
  Sun
}a,b;//创建全局变量
int main()
{
  //创建局部变量
  enum Day d = Mon;
  return 0;    
}

🎗 上面说到枚举是常量,那么打印它们的值

#include<stdio.h>
enum Color 
{
  RED,
  GREEN,
  BLUE
};
int main()
{
  printf("%d\n", RED);//0
  printf("%d\n", GREEN);//1
  printf("%d\n", BLUE);//2
  return 0;
}

❓❔既然枚举可能取值都有对应的常量值,那能否赋常量值呢

enum Color 
{
  RED,
  GREEN,
  BLUE
};
int main()
{
  enum Color c = 2;//?
  return 0;
}

📝 分析:

2是一个整型,而c是enum Color类型,所以是err。 发现在.c下执行代码并没有err,而.cpp中err

📝 小结:

这里只能说明C++对语法的检查更严格,虽然在C语言中并没有报错,但还是要避免这种写法


❓❔ 枚举常量值能否被修改,为什么常量能被修改

#include<stdio.h>
enum Color 
{
  RED = 5,
  GREEN,
  BLUE
};
int main()
{
  printf("%d\n", RED);//5
  printf("%d\n", GREEN);//6
  printf("%d\n", BLUE);//7
  return 0;
}

📝 分析:

枚举常量值是可以修改的,且会按照最后修改的值往后递增。在定义枚举时是赋初值(默认是从0开始),当然出了枚举外部去修改时是err

💦 枚举的优点

❓❔ 我们可以使用#define定义常量,为什么还要使用枚举

#define RED 5
#define GREEN 8
#define BLUE 9
//==========================================
enum Color
{
  RED = 5,
  GREEN = 8,
  BLUE //9
};

⚠ 注意:

▶ 增加代码的可读性和可维护性

▶ 和#define定义的标识符比较枚举有类型检查,更加严谨

▶ 防止命名污染(封装)

▶ 便于调试(而#define定义的常量在调试时不能看到标识符)

▶ 使用方便,一次可定义多个常量

➰ 对比

📝 小结:

▶ 代码1:可读性非常差(需要不断的向上翻阅代码来匹配功能)

▶ 代码2:可读性得到了改善(使用枚举)

💦 枚举的使用

enum Color 
{
  RED = 1
  GREEN = 2,
  BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异

三、联合体

💦什么是联合体

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

💦 联合类型的声明和定义

------------------------联合的声明样式------------------------

union tag

{

  member1;

  member2;

}variable-list;

------------------------解释------------------------

▶ union是联合体关键字

▶ tag是联合标签名,是自定义的

▶ union tag就是联合类型

▶ {}里面放的是成员列表

▶ variable-list是变量

#include<stdio.h>
union Un
{
  char c;
  int i;
};
int main()
{
  //定义联合体变量
  union Un n;
  printf("%d\n", sizeof(n));//4
  return 0;
}

💨 结果:

❓❔ 为什么是4呢,接下来我们就来探讨一下联合体的特点

💦 联合的特点

#include<stdio.h>
union Un
{
  char c;
  int i;
};
int main()
{
  union Un n;
  printf("%p\n", &n);
  printf("%p\n", &n.c);
  printf("%p\n", &n.i);
  return 0;
}

💨 结果:

📝 分析:

▶ 成员共用同一块空间

▶ 联合体的大小至少是最大成员的大小


🎗 回顾大小端

🎗 判断当前机器的大小端

只要判断第一个字节的内容即可:把1的地址取出来强制类型转换为(char*),再解引用就可以访问第一个字节

#include<stdio.h>
int check_sys()
{
  int i = 1;
  char* p = (char*) &i;
  return *p;
}
int main()
{
  int ret = check_sys();
  if(1 == ret)
  {
    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()
{
  int ret = check_sys();
  if(1 == ret)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
  return 0;
}

💦 联合大小的计算

#include<stdio.h>
union Un
{
  char a[5];
  int i;
}
int main()
{
  union Un u;
  printf("%d\n", sizeof(u));//8
  return 0;
}

⚠ 注意:

▶ 联合体也是有内存对齐的

▶ 这里的最大成员不是数组,因为char a[5] 等同于写5个char a

▶ 联合体的大小至少是最大成员的大小,但不一定是最大成员的大小

▶ 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍

四、通讯录

[传送门正在抢修]

相关文章
|
弹性计算 JavaScript 前端开发
常见类型-1:空,联合,枚举
本实验将介绍TypeScript中空,联合,枚举类型的用法。
|
2月前
|
编译器
联合和枚举
联合和枚举
39 8
|
2月前
|
编译器
自定义类型:联合和枚举
自定义类型:联合和枚举
23 0
|
7月前
|
编译器 C语言 C++
结构体,枚举,联合大小的计算规则
结构体,枚举,联合大小的计算规则
49 7
|
7月前
|
存储 编译器 Linux
自定义数据类型:结构体+枚举+联合
自定义数据类型:结构体+枚举+联合
|
存储 编译器 C语言
自定义数据类型:结构体,枚举,联合
自定义数据类型:结构体,枚举,联合
|
编译器 C语言 C++
结构体、枚举、联合详解(上)
结构体、枚举、联合详解(上)
36 0
结构体、枚举、联合详解(下)
结构体、枚举、联合详解(下)
44 0
|
存储 编译器 C语言
【自定义类型】带你走进结构体、枚举、联合(下)
【自定义类型】带你走进结构体、枚举、联合(下)
|
存储 编译器 C语言
【自定义类型】带你走进结构体、枚举、联合(上)
【自定义类型】带你走进结构体、枚举、联合(上)