C++ Primer Plus 学习笔记(六)

第九章 内存模型和名称空间

1. 单独编译

头文件(.h)不参与编译,只是通过 #include 指令,将头文件中的代码放到对应的源文件中。

2. 存储持续性、作用域和链接性

自动存储持续性:在代码块中声明定义的,出了大括号就释放。

静态存储持续性:在代码块外面定义的或代码块内使用 static 修饰的变量,在程序运行时一直存在。

线程存储持续性:关键字thread_local修饰的变量,持续到线程结束。

动态存储持续性:new 分配内存开始,delete 释放结束。

作用域指名称在多大范围可见,链接性指是否可以在不同文件中共享,自动变量没有链接性。

自动存储性的变量(自动变量)放在栈中。栈是LIFO(后进先出),栈由两个指针管理,一个指向栈底(栈开始的地址),另一个指向栈顶(下一此添加变量的地址),加入新的变量时,改变栈顶指针,释放变量时,栈顶指针回到原来的位置。

C++11中,寄存器存储关键字register变成只是显示指出变量是自动变量。

静态存储持续性的变量(静态变量)有三种链接性:无链接(作用域在代码块中)、内部链接(作用域在文件中)和外部链接(可在其他文件中访问)。所有静态变量由分配的固定内存块存储。静态变量默认初始化为0,即声明不初始化时,自动初始化为0,静态的数组和结构的每个成员,在不显示初始化时,也默认设置成0,称为零初始化。

在代码块外部声明的变量,是具有外部链接的静态变量;在代码块外部声明,并用 static 修饰的变量,具有内部链接;在代码块内部声明,并用 static 修饰的变量,无链接。

显式初始化的静态变量,也会先零初始化后,再根据实现具体初始化:

#include <cmath>
int x;  // 零初始化
int y = 5;  // 零初始化后,编译器编译阶段计算常量表达式,再初始化
int z = 13 * 13;  // 零初始化后,编译器编译阶段计算常量表达式,再初始化
const double pi = 4.0 * atan(1.0);  // 编译时先零初始化,链接且程序执行时再初始化
int main()
{
    ...
}

在代码块外部定义的变量,叫外部变量,也叫全局变量,作用域是声明后的所有地方。

C++ 有单定义原则,外部链接的变量只能定义一次,但可以通过引用声明 extern,跨文件使用。

double up;  // 定义,零初始化
extern int blem;  // 引用声明,blem已在其他文件定义
extern char gr = 'z';  // 定义,因为是初始化gr

使用外部链接的全局变量,只能定义一次,用extern 关键字跨文件使用,一定注意单定义原则。

在代码块中定义的静态变量只初始化一次,即在函数定义中定义并初始化的静态变量,在每次调用该函数时,静态变量只在第一次调用时初始化,以后调用时都不会再初始化,可以相当于再次调用时,跳过初始化那行代码。

int count()
{
    static total = 0;
    int i = 1;
    total += i;
    return total;
}
int main()
{
    for (int j = 0; j < 3; j++)
        std::cout << count() << std::endl;  // 输出1,2,3
    return 0;
}
    

cv限定符:const 、volatile。const 表示初始化后不能再对其进行修改。关键字 volatile 表示即使代码没有对内存进行修改,内存上的值也可能发生变化。例如,程序在几条语句内使用了某个变量的值两次,若没有 volatile 修饰,编译器可能会将该值放到寄存器中,而寄存器内的值不会被改变,而使用 volatile 则不会把该值放入寄存器,那么该值就可能会改变。一般是告诉编译器不要进行这种优化。

mutable关键字,用来指出即使结构体或者类的对象是由 const 限定的,其内的某个成员也可以被修改。

struct data
{
    char name[30];
    mutable int acc;
    ...
};
const data ve = {"John", 10, ...};
strcpy(ve.name, "Tom");  // 非法
ve.acc++;  // 合法

const 全局变量的链接性是内部的,也就是说 const 常量可以放在头文件中,即使被多个源文件包含也不会违反单定义原则。可以通过 extern 将其变为外部链接,但在定义外部链接的 const 常量时,不能省略 extern,而普通的外部链接变量,在定义时可以省略 extern,只不过其他文件在使用该变量时,声明时要加 extern。

函数的存储性是静态的,默认链接性是外部的,通过在原型和定义时都加上 static 关键字,来声明该函数是内部链接的。函数定义也遵循单定义原则。内联函数好像默认是内部链接的,它可以定义在头文件中。

动态内存可以初始化:

int * pi = new int (6);  // 也可以使用大括号,*pi的值是6
struct where {double x; double y;};
where *one = new where {2.5, 5.3}; 
int * ar = new int [3] {2, 3, 4};

new 运算符的另一个用法是,定位 new 运算符:

char buffer[512];
int *pi = new int;  // 动态内存分配
int * p2 = new(buffer) int;  // 定位new,将p2指向的内存放在buffer的内存中
int * p3 = new(buffer) int[10]  // p3 指向的地址会覆 p2 的地址

// 偏移后 p4会接在 p3 之后
int * p4 = new(buffer + 10 * sizeof(int)) int [3];

放在buffer中 new 的内存,不能使用 delete,即delete只能释放常规动态内存分配的堆内存。

3. 名称空间

名称可以是变量、函数(声明和定义)、结构、枚举、类以及类和结构的成员。名称空间不能位于代码块中,可以在另一个命名空间中。可以把名称加入到已有的名称空间中:

namespace Jill {
    double pail;
}
    ...
namespace Jill {  // 在Jill中加入一个char指针
    char * pt;
}

使用using 声明名称空间的变量后,该变量在声明的作用域中都指名称空间中的名称:using Jill::pail,后面的 pail 都指 Jill::pail。使用using编译指令同理(using namespace Jill;),声明的作用域中,名称空间中的所有名称都可用。在同一作用域下,使用using声明和另定义同名变量,会引发冲突。但是使用using编译指令引入整个名称空间,再定义另一个同名变量,后者会隐藏名称空间中的同名变量。

嵌套的名称空间可以这样使用:

#include <iostream>
namespace Jill {
    double pail;
}
namespace myth {
    using Jill::pail;
    using namespace std;
}
int main()
{
    myth::cin >> Jill::pail;
    std::cout << myth::pail;
    return 0;
}    

using namespace myth 相当于两条using编译指令:

using namespace myth;
using namespace std;

可以给名称空间创建别名:

namespace mt = myth;
using mt::pail;  

可以创建未命名的名称空间,这样的名称空间中的名称可以看做内部链接的静态变量,作用域为声明后的整个文件:

#include <iostream>
static int counts;  // 零初始化
int main()
{
    std::cout << counts;
    return 0;
}
// 与下面等价
#include <iostream>
namespace {
    int counts;  // 静态内部链接,零初始化
}
int main()
{
    std::cout << counts;
    return 0;
}

未命名的名称空间中的变量可以看作是内部链接的静态变量,名称空间内的名称可以在文件内使用,空间内定义的变量会零初始化。

命名的名称空间是内部链接的,此名称空间内的名称是外部链接的,所以会有下面的情况:

// 源1.cpp
#include <iostream>
void Change();
namespace Jill {
	int a;
}  
int main()
{
	Change();
	std::cout << Jill::a << std::endl;
	return 0;
}

// 源2.cpp
namespace Jill {
	int a;
}
void Change()
{
	Jill::a += 1;
	std::cout << Jill::a << std::endl;
}

这个程序会报错:链接错误,Jill::a 重定义。因为源1中已经定义了一个外部链接的静态变量a,而将源2的 Jill ,改为 extern int a ,就可以通过编译。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值