7 共用体的使用
7.1 共用体的内存布局
共用体使不同的类型变量共享同一段内存单元,所以共用体只能存储一个数据成员的值,
共用体的大小等于最大成员的大小。在程序中改变共用体的一个成员值,其它成员值也随之改变
7.2 判断系统字节序
大小端,大小字节序,高低字节序,或Big&Little Endian,表示数据在内存中的存放模式
大端:网络字节序 小端:主机字节序
低字节序:低字节保存在内存的低地址中
高字节序:低字节保存在内存的高地址中
#include <iostream> //共用体: int checkByteOrder(){ union { int data; char flag; }subject; subject.data = 1; return (subject.flag == 1); }; int main(){ if (checkByteOrder() == 1){ printf("当前系统为低字节序\n"); } else{ printf("当前系统为高字节序\n"); } system("pause"); return 0; }
8 枚举的使用
8.1 枚举&枚举变量
枚举,本质上是一组被命名的常数集合
枚举变量,由枚举类型定义的变量
// 定义枚举类型kWeek // 包括枚举和const常量:名称前加小写k,且除去开头k外每个单词开头字母均大写 enum Week {kSun, kMon, kTue, kWed, kThu, kFri, kSat};
// 定义枚举变量 Week weekday1, weekday2; enum {kSun,kMon,kTue,kWed,kThu,kFri,kSat} weekday1, weekday2;
注意:枚举变量大小,等于枚举类型所占内存大小
8.2 枚举的内存布局
枚举类型本身是不占内存的,但是枚举值占用内存的。枚举默认是用int类型来存储的,32位系统下占4个字节。#define 定义的常量,在预编译时替换。而enum定义的常量,是在运行时替换,根据标识去常量区获取对应的值
可以通过继承方式改变枚举的大小,例如:
//TypeChar 类型变量大小占用1字节 enum TypeChar : unsigned char {};
具体实现:
enum EnumInt{ kMon = 1, kTue = 2, kWed = 3 }; enum EnumChar : unsigned char { kRed = 0x00, kYellow, kBule = 0xff }; //sizeof(EnumInt) = 4 //sizeof(EnumChar) = 1
扩展:枚举的范围
如果某个枚举符的值均为非负值,该枚举的取值范围就是[0~2^k-1]
如果存在负的枚举符值,则该枚举的取值范围就是[-2k~2k-1]
k 是能使所有枚举值位于此范围内最小2的幂
举例1:enum kE1{ a=2, b=4 };kE1均为正值,能使所有枚举值位于此范围内最小2的幂为 3 ,因此k的值为2,于是kE1的取值范围为[0,7]
举例2:enum kE2{ a=-2, b=4 };kE2有正有负,那么满足条件的k的值为3,kE1的取值范围是[-8,7]
9 指针与引用
9.1 指针&引用
名称 | 内存大小 | 布局 | 说明 |
指针 | 四个字节 | 二进制数据储存 | 是用来控制内存地址的变量,它指向单个对象的地址 |
引用 | 四个字节 | 二进制数据储存 | 引用是一个指针常量 |
指针数组 | 元素所占内存的大小 | 与元素的储存结构有关 | 是一个只包含指针元素的数组 |
数组指针 | 四个字节 | 二进制数据储存 | 是一个指针变量,它指向一个数组 |
指向指针的指针 | 四个字节 | 二进制数据储存 | 是一个指针链。第一个指针包含了第二个指针的地址,第二个指针指向实际值 |
说明:以上内存大小均为 32 bit 位系统
9.2 深拷贝&浅拷贝
深拷贝:被访问的数据,在每个单元中都有自己的一份
浅拷贝:两个函数通过拷贝它们共享的数据指针来工作
#include <iostream> //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题 class Person { public: explicit Person(const int height) { p_height_ = new int(height); } Person(const Person& p) { //p_height_ = p.p_height_; p_height_ = new int(*p.p_height_); } ~Person() { if (p_height_ != NULL){ delete p_height_; } } private: int* p_height_; }; int main() { Person p1(180); Person p2(p1); system("pause"); return 0; }
explicit关键字:被修饰的类构造函数,不能进行隐式类型转换,如下图所示:
扩展:new/delete 和 malloc/free 区别
1、malloc/free是C/C++标准库的函数,new/delete是C++操作符
2、返回值类型不同:new操作符内存分配成功时, 返回的是对象类型的指针,而malloc返回的是void*指针, 需要强转将指针转换为所需类型
3、定制内存大小不同:malloc/free需要手动计算类型大小,而new/delete编译器可以自己计算类型大小
4、分配失败时返回值:new内存分配失败时会直接抛bac_alloc异常, 它不会返NULL, malloc分配内存失败时返回NULL
5、malloc/free只是动态分配内存空间/释放空间,而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)
10 存储分类说明符
static、register、extern都是用来变量存储说明的关键字,选择适当的存储分类符,不仅能够提高变量的访问效率,而且还能使内存的存储空间更有效
10.1 auto
auto 中文释义:自动的
作用:使变量在离开作用域自动销毁,一般情况下可省略
#include <iostream> int main(){ auto int a = 2; //离开main函数,变量a自动销毁 return 0; }
10.1 static
static 中文释义:静态的
作用:定义的变量只能接受一次初始化,不能接受第二次
#include <iostream> void func(){ static int a = 10; a++; std::cout << a << std::endl; } int main(){ func(); //初始化a为10,输出a为11 func(); //不接受初始化,a为11,输出a++,等于12 system("pause"); return 0; }
10.2 register
register 中文释义:寄存器
寄存器类型,寄存器是计算机内距离CPU最近的容器,将变量尽可能定义在寄存器里,使变量读取更加快速,提高程序效率。缺点是寄存器空间有限, 常用于频繁使用的变量
是否一定会放在寄存器里
register int often_use_num = 0;
10.3 extern
extern 中文释义:外部变量
它属于变量声明,extern int n和int n的区别就是,告知编译器,int类型变量n定如有调用去其它文件中查找定义
//call.cpp #include <iostream> void func(){ extern int n; std::cout << n << std::endl; } int main(){ func(); system("pause"); return 0; } //definition.cpp int n = 101;
11 存取限定符
11.1 const
const 中文释义:常量
指定一个语义约束,编译器会强制实施这个约束,使某值保持不变的
场景:修饰普通变量,指针变量,参数传递和函数返回值
const int val = 5; //const修饰的是指针,指针指向可以改,指针指向的值不可以更改 const int * p1 = &a; p1 = &b; //正确 //*p1 = 100; 报错 //const修饰的是常量,指针指向不可以改,指针指向的值可以更改 int * const p2 = &a; //p2 = &b; //错误 *p2 = 100; //正确
11.2 volatile
volatile 中文释义:不稳定的
使用 volatile 声明的变量,系统总是重新从它所在内存读取数据,简单地说就是防止编译器对代码进行优化
场景:多任务环境下各任务之间的共享标志
#include <stdio.h> void main() { volatile int i = 10; int a = i; // 下面汇编语句的作用就是改变内存中 i 的值 // 但是又不让编译器知道 printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); }
Release 模式下,变量i声明是否声明volatile对结果有显著影响
变量未声明volatile时:编译器对代码进行了优化,输出结果不正确
变量声明volatile时:关键字发挥了作用
volatile的意义在于多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程可见
12 inline关键字
12.1 引入inline关键字
在c/c++中,引入inline修饰符,为了解决一些频繁调用的小函数大量消耗栈内存问题,表示为内联函数
#include <stdio.h> //函数定义为inline即:内联函数 inline char* dbtest(int a) { return (i % 2 > 0) ? "奇" : "偶"; } int main() { int i = 0; for (i=1; i < 100; i++) { printf("i:%d 奇偶性:%s /n", i, dbtest(i)); } }
内联函数作用就是在每个for循环内部任何调用dbtest(i)的地方都换成了(i%2>0)?”奇”:”偶”,避免了频繁调用函数对栈内存重复开辟带来的消耗
12.2 inline内联优缺点
相比普通函数,内联函数效率更高,因为内联函数通过复制代码,节省了函数调用的时间
inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且部分编译器不支持递归内联
以下情况使用inline不会起作用:
函数体内代码长,使用内联将导致内存消耗代价较高
函数体出现循环,执行函数体内代码的时间要比函数调用的开销大
12.3 宏函数&内联的区别
内联函数可以被调试,而宏函数不可以
宏定义没有类型检查,无论对错都是直接替换
内联函数在编译时会进行类型检查
宏定义不是函数,而是在预编译的时候把宏名用宏体替换,本质就是字符串替换
而内联函数本质上是一个函数,有参数列表和返回值,它在编译进行代码插入,编译器会在调用内联函数的地方,把函数内容展开,省去函数调用开销,来提高效率