💦 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
▶ 联合体的大小至少是最大成员的大小,但不一定是最大成员的大小
▶ 当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
四、通讯录
[传送门正在抢修]