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; // 输出:2
    cout << ::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++; // ok
    st.name = "Job"; // not allowed
    return 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; // 应替代为匿名空间形式
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值