以下内容为Hughen个人学习C++ Primer Plus(第6版)后所写,欢迎一起探讨学习。
看到这个标题,就觉得有点恶心,不管什么编程语言,只要一涉及到内存问题,都表现的十分令人咂舌,因为毕竟计算机在内存上的发展还是很机械式的,没有你我平时候用高级语言那样随便,一个new,一个变量声明内存空间就来了,实际上在底层操作系统做了很多事,简短的吹嘘之后,开始正式进入主题——内存模型和名称空间(namespace)
首先,需要温习一下单独编译。
单独编译
单独编译的目的无非是,当我们的项目很大的时候(整个项目由很多模块组成,代码估计得上万行吧),节省整个项目编译的时间,因为有些部分我们可能根本没有修改过,所以没必要重新再编译一次,希望编译器是直接的链接起来就行了。要做到单独编译的前提是,需要将代码模块化。模块化,这点就不给大家扯了,肯定都知道。只是一个良好的编程格式是在头文件中放置如下内容:
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数(inline)
讲到头文件,有一个地方一直被潜移默化的约束了很久,不太明白它的意思,现在终于整明白了,和大家分享一下。
#ifndef MYHEADER_H
#define MYHEADER_H
......
#endif
上述代码大家一定都很熟悉,其中“MYHEADER_H”就是我要向大家说明的,之所以会这样做是因为编译器在遇到该头文件时要试着检查你自己定义的MYHEADER_H这个是否有被定义,没有被定义就创建MYHEADER_H(编译器内部需要的),然后呢,我就想是否可以给这个定义换一个名字,结果证实这是当然的。采用这种下划线的方式,目的是创建一个在其它地方不太可能被定义的名称。C++ Primer Plus上面用的是MYHEADER_H_的格式,其实都差不多的,只是怎么看着更有利于理解而已。
存储连续性
在自动变量的存储上,采用的方式都是存储在栈中,之所以称为栈主要,主要是因为新数据被象征性地放在原有数据的上面。
我们平时使用的new操作符分配的存储空间是放在堆(heap)中,但是这里这里需要注意的是,其指向堆的指针本身是不在堆中的,它也是在栈中的,比如:
int *p = new int(0);
int *q = new int(10);
其中p,q都是在堆中,但是&p,&q是在栈中的。这就验证了为什么使用
delete操作符之后p和q都还可以继续指向的缘故,因为
delete释放的只是指针指向的堆内存。这里的p和q指针只有离开它的作用域之后才会被系统释放,这就是栈内存的管理方式。
这里的
new操作符,还有一种形式叫做定位
new运算符,使用情况如下:
struct stc
{
......
};
char buffer[1000];
int main()
{
stc *p;
p = new (buffer) stc; // 表示从buffer中new出一个大小为stc的存储区域
......
}
围绕变量的存储,一定就有一个生存期的存在,每个变量都有一个生存期。先看如下代码:
int global = 100; // 静态持续变量,外部链接性,可以在本文件外访问使用
static int x = 50; // 静态持续变量,内部链接性,只能在包含本段代码的文件中访问使用
int main()
{
......
}
void func()
{
static int op = 0; // 静态持续变量,无链接性,只能在func()中使用,只会初始化一次
int opi = 0;
......
}
变量global,x,op它们都是静态的,不过可以被使用的范围不一样,其中范围最大的是global,可以在外部文件,比如tmp.cpp文件中用
extern关键字标注来使用;x只能在本文件中使用,op也是,不过op的范围更小,因为它只能在func()函数内才能使用。
下面这个表列出了五种变量的存储方式上的规则和差别:
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
在使用链接性为外部的静态变量时需要注意,在定义声明(简称定义)的时候不能添加关键字
extern,当加上
extern的话同时就必须的给予初始值,否者视为引用声明(简称声明)。
// file1.cpp
double gx; // 一个定义声明
extern double gy = 1.5; // 一个定义声明,并同时初始化了
// file2.cpp
extern double gx; // 引用了一个没有初始化的gx
extern double gy; // 不是定义声明,这是一个引用声明
变量在定义的时候就会得到系统分配的空间,而引用声明,它是不会分配存储空间的,它只是引用已有的变量。
但是,这里还有一个意外,使用了
const标记的全局变量,链接性是内部的。
const int ft = 404;
int main()
{
......
这里的 ft 变量是全局的,使用了
const修饰,原本的外部链接性因为有了
const的修饰变成了内部的。
假如全局
const声明是外部的,结果会是什么呢?
比如下面这串代码在程序的其他几个文件中都包含进去了,那么就会在这些文件中存在如下定义:
const int ft = 404;
const char * cotent = "警告!服务器错误!";
但是根据单
定义规则(
变量只有一次定义),这些文件中的该处代码应该改为
extern const形式,但实际上不是这样的,所以就会导致重复定义出错。
所以
const全局变量的链接性是内部的。
如果你非要使用
const类型的外部链接性变量,则需要如下的定义:
// file1.cpp
extern const int state = 101; // 定义声明
// file2.cpp
extern const int state; // 引用声明
在语言链接性方面,主要表现的形式就是下面这样的:
extern "C" void func1(int); // 使用C库中预编译的函数func1
extern "C++" void func2(int); // 使用C++库中预编译的函数func2
当然,在默认的情况下是使用C++库中预编译的函数。
名称空间
namespace SomeSpace
{
double func(int n) {...}
int x;
using namespace elements;
}
在使用定义好的名称空间的时候,只有两种途径:
using SomeSpace::var; // using声明,效果是使单条名称可用
using namespace SomeSpace; // using编译指令,效果是使整个名称空间的名称都可用
当然下面的代码也是合法的:
using namespace SomeSpace;
using namespace elements;
要使用名称空间中的函数,可以用以下using声明:(如果func被重载了,则一个using声明将导入所有的func函数版本)
using SomeSpace::func;
namespace
{
......
}
它的作用范围有限,仅限于包含它的本文件使用。