当前位置: 首页 > news >正文

C++ Primer Plus 学习笔记(四)—— 内存模型和名称空间

1 单独编译

C++允许将组件函数放在独立的文件即头文件中,头文件中可以包含以下内容:

  • 函数原型;
  • 使用#define或const定义的符号常量;
  • 结构声明;
  • 类声明;
  • 模板声明;
  • 内联函数。

注意,在包含自定义的头文件时应使用 #include “test.h”,这样编译器才会优先从当前工作目录或其他目录查找;如果是#include <test.h>形式,则编译器将在存储标准头文件的主机系统的文件系统中查找。

在同一个文件中只能将同一个文件包含一次。但是在实际开发时很可能会使用包含了另一个头文件的头文件,从而造成头文件被重复包含的问题,因此一般都需要在头文件的头尾加上预处理器编译指令#ifndef,意味着仅当之前未使用预处理器编译指令#define定义名称CODE_TEST_H时,才处理#ifndef和#endif之间的语句。

#ifndef CODE_TEST_H
#define CODE_TEST_H
// ...
// 头文件具体内容
// ...
#endif

2 存储持续性

根据数据在内存中保留时间的不同,可以将存储数据方案分为:

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完成后它们使用的内存被释放。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性为静态的。它们在程序的整个运行过程中都存在。
  • 动态存储持续性:用new操作符分配的内存将一直存在,直到使用delete操作符将其释放或程序结束为止。

2.1 自动变量

可以使用关键字auto来显式地指出存储类别,由于局部变量默认状态下就是自动的,因此程序员几乎不使用它。

auto int num;

2.2 寄存器变量

可以使用关键字register来声明局部变量,表示该变量使用频率很高,寄存器变量会提醒编译器,用户希望它通过使用CPU寄存器来处理特定变量,从而提供对变量的快速访问(理念是CPU访问寄存器中的值的速度比访问堆栈中内存快)。

注意,编译器不一定会用寄存器来存储register变量,因为寄存器可能已经全被占用,或者寄存器无法存储所请求的类型。

如果变量被存储在寄存器中,则没有内存地址,因此不能将地址操作符用于寄存器变量。

int num1;
register int num2;
cout << &num1 << endl; // ok
cout << &num2 << endl; // not allowed

2.3 静态持续变量

C++为静态存储持性变量提供了3种链接性:外部链接性、内部链接性和无链接性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWhEwhSN-1677946789635)(C:\Users\LYJ\AppData\Roaming\Typora\typora-user-images\image-20230304220011282.png)]

所有的静态持续变量都有下面的两个初始化特征:

  • 未被初始化的静态变量的所有位都被设置为0。
  • 只能使用常量表达式来初始化静态变量。
#include <iostream>using namespace std;int global = 1; // 外部链接性,可在其他文件使用static int one_file = 2; // 内部链接性,被限定在当前文件内使用void test()
{static int count = 0; // 无链接性,仅在函数定义内使用
}int main()
{return 0;
}

C++支持使用关键字 extern 来重新声明以前定义过的外部变量,以及使用作用域解析操作符(::)来访问被隐藏的外部变量。

#include <iostream>using namespace std;int global = 1; // 外部全局变量void test1()
{extern int global; // 引用声明,重新声明在外部已定义的变量cout << global << endl; // 输出:1
}void test2()
{int global = 2; // 定义声明,定义与全局变量同名的局部变量后,局部变量将隐藏全局变量cout << global << endl; // 输出:2cout << ::global << endl; // 输出:1 // 表示使用变量的全局版本
}int main()
{test1();test2();return 0;
}

2.4 说明符和限定符

存储说明符包括auto、register、static、extern、mutable,cv-限定符包括const、volatile。

2.4.1 volatile

volatile 关键字表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,在两个程序共享数据场景下,当A程序在第一次访问结束后,B程序可能会在这之后修改数据值,后面A程序再一次访问时就是新数据值了。

该关键字的作用是为了改善编译器的优化能力,因为编译器在发现程序在几条语句中两次使用了某个变量的值时,编译器为了能让查找效率更高,可能会将该变量值缓存到寄存器中,而不是让程序查找这个值两次,这种优化假设变量的值在这两次使用之间不会变化。将变量声明为volatile,相当于告诉编译器,不要进行这种优化。

2.4.2 mutable

mutable 是用来修饰一个 const 结构(或类)实例的部分可变的数据成员的。

#include <iostream>using namespace std;struct ST {char name[20];mutable int num;
};int main()
{const ST st = { "Tom", 1 };st.num++; // okst.name = "Job"; // not allowedreturn 0;
}

2.4.3 const

在C++中,在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。

const int num = 1; // same as static const int num = 1;

C++修改了常量类型的规则是为了能让程序员更便于使用。例如,假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件,那么所有的源文件都将包含该定义。

  • 如果const声明的链接性像常规变量那样是外部的,这将出错,因为不能在多个文件中定义同一个全局变量。也就是说,其他引用文件必须都要使用extern关键字来提供引用声明。
  • 如果const声明是内部的,那每个源文件都有自己的一组常量,而不是所有文件共享一组常量。

如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用关键字extern来覆盖默认的内部链接性。

extern const int num = 1;

2.5 语言链接性

在C++中,由于同一个名称可能会对应多个函数,因此,C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号。例如,可能将spiff(int)转换为_spiff_i,而将spiff(double)转换为_spiff_d,这种方法被称为C++语言链接。

如果要在C++程序中使用C库中预编译的函数,由于spiff(int)在C库文件中符号为spiff_,而C++查找约定是查找符号名称_spiff_i,此时需要使用函数原型来指出要使用的约定:

extern "C" void spiff(int); // C
extern void spiff(int); // C++
extern "C++" void spiff(int); // C++

3 布局new操作符

布局new操作符能够在使用new申请内存时指定要使用的内存位置。

与常规new操作符的区别在于,布局new操作符使用传递给它的地址,它不跟踪哪些内存单元已被使用,也不查找未使用的内存块,因此这要求程序员自己去管理内存。

#include <iostream>
#include <new>using namespace std;const int N = 3;
char buffer[512];int main()
{double *pd1, *pd2;pd1 = new double[N];pd2 = new (buffer) double[N];for (int i = 0; i < N; i++) {pd1[i] = pd2[i] = i * 0.2 + 1000;}cout << "Memory address: \n" << "heap: " << pd1 << ", buffer: " << (void*)buffer << endl;for (int i = 0; i < N; i++) {cout << "pid1[" << i << "] in address " << &pd1[i] << ", ";cout << "pid2[" << i << "] in address " << &pd2[i] << endl;}return 0;
}

输出:

Memory address: 
heap: 0x7a1760, buffer: 0x407040
pid1[0] in address 0x7a1760, pid2[0] in address 0x407040
pid1[1] in address 0x7a1768, pid2[1] in address 0x407048
pid1[2] in address 0x7a1770, pid2[2] in address 0x407050

4 名称空间

C++中可以通过关键字namespace定义一种新的声明区域来创建命名的名称空间。

  • 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。
  • 可以通过作用域解析操作符::,使用名称空间来限定该名称。

4.1 using声明和using编译指令

using声明使指定的标识符可用,using编译命令使整个名称空间可用。

namespace Test1 {int num = 1;
}namespace Test2 {int num = 2;
}using Test1::num; // using声明
using namespace Test2; // using编译命令

另外,可以给名称空间创建别名,以简化对嵌套名称空间的使用。

namespace Out {namespace Inner {int num = 1;}
}namespace OI = Out::Inner;

4.2 未命名的名称空间

可以通过省略名称空间的名称来创建未命名的名称空间。

由于这种名称空间没有名称,因此不能在匿名空间所属文件外的其他文件中使用该名称空间中的名称,因此这种方法可以替代链接性为内部的静态变量。

static int count; // 内部静态变量namespace {int count; // 应替代为匿名空间形式
}
http://www.lryc.cn/news/31080.html

相关文章:

  • 详解基于 Celestia、Eclipse 构建的首个Layer3 链 Nautilus Chain
  • 列表与数组的转化
  • docker 运行花生壳实现内外网穿透
  • 操作系统——16.时间片轮转、优先级、多级反馈队列算法
  • Python3.8.8-Django3.2-Redis-连接池-数据类型-字符串-list-hashmap-命令行操作
  • Android kotlin 系列讲解(进阶篇)高级项目架构模式 - MVVM
  • 8. 查找
  • 二分查找算法
  • Git(3)之远程服务器
  • Javalin解构
  • yolov5算法,训练模型,模型检测
  • linux系统防火墙开放端口
  • CSAPP第九章 虚拟内存
  • numpy数组与矩阵运算(二)
  • Dubbo 中 Zookeeper 注册中心原理分析
  • 素数产生新的算法(由筛法减法改为增加法)--哥德巴赫猜想的第一次实际应用
  • 递归-需要满足三个条件
  • 【剑指Offer-Java】两个栈实现队列
  • Allegro如何将Waived掉的DRC显示或隐藏操作指导
  • MATLAB——数据及其运算
  • 【微信小程序】-- 页面导航 -- 声明式导航(二十二)
  • gdb查看汇编代码的例子
  • 第四讲:如何将本地代码与服务器代码保持实时同步
  • cuda调试(一)vs2019-windows-Nsight system--nvtx使用,添加nvToolsExt.h文件
  • 向Spring容器中注入bean有哪几种方式?
  • 如何用 Python采集 <豆某yin片>并作词云图分析 ?
  • Python装饰器的具体实用示例
  • 谈谈我对Retrofit源码的理解
  • 八股文(三)
  • 2023最新实施工程师面试题