前言

好久没更新了,主要是最近有点摆了,不过还是得更新的嘛,顺便也是我的复习了......这一章的内容是内存模型和名称空间,比较简短,主要介绍单独编译与名称空间。关于存储持续性那些也会涉及一些。


单独编译

我们知道,在写一个cpp程序之前我们都要使用预处理器拷贝一些头文件(.h/.hpp),而我们在进行多文件编译的时候,时常需要我们自己写头文件,接下来就介绍如何写头文件。

首先,头文件中要写些什么呢,一般只可以包括这些:

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

不要将函数定义(函数模板、类模板定义除外)或变量声明放到头文件中。这样做对于简单的情况可能是可行的,但通常会引来麻烦。例如,如果在头文件包含一个函数定义,然后在其他两个文件(属千同一个程序)中包含该头文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则这将出错。

我们在cpp中引用我们自己的头文件时,要和库头文件分别开,需要用“”来写,例如:#include "example.h"

在同一个文件中只能将同一个头文件包含一次。这个规则很容易,但很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的C/C++技术可以避免多次包含同一个头文件。

#ifndef COORDIN_H_
#define COORDIN_H_
//something ......
#endif

其中COORDIN_H一般是头文件名字的大写,用_隔开符号。

注意:我们在编译的时候,不需要把头文件一起写出,因为在cpp文件中已经用#include连接了。但是头文件要和cpp在同一个目录下。

例如有两个cpp文件:file1.cpp, file2.cpp, example.h,其中cpp文件中链接了头文件,编译时只需写:

g++ file1.cpp file2.cpp

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

这一章的内容大多和C语言中的相同,接下来介绍一些C++中特殊的。C语言这部分:

【C语言学习之路】第十五节课——字符串存储、字符串转换函数以及存储类别

静态存储持续性:在函数定义外定义的变量和使用关键字static。全局静态持续性则需使用extern。

关于动态分配,C++中使用了newdelete进行动态分配。

使用new运算符初始化

如果要为内置的标量类型(如int 或double) 分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起:

int *pi = new int (6) ; / / *pi set to 6, 此时new返回的是存储6的地址。

在C++11中,还可以初始化常规结构或数组,需要使用大括号的列表初始化。

struct where {doubl e x; double y; double z;};
where  *one = new where (2 .5, 5.3, 7.2 }; // C++11
int *ar = new int [4] {2,4,6,7}; // C++11

当new失败,即分配内存失败,会返回std::bad_alloc状态,或者空指针(以前的版本)。

分配函数

运算符new 和new[]分别调用如下函数:

void * operator new (std::size_t) ; // used by new
void * operator new [] (std::size_t); // used by new[]

这些函数被称为分配函数,它们位于全局名称空间中. 同样,也有由delete 和delete[] 调用的释放函数。

void operator delete(void * ) ;
void operator delete[] (void*) ;

通常, new 负责在堆(heap)中找到一个足以能够满足要求的内存块。它的基础语句是:

int* pi = new int;
int* pa = new int[40];

名称空间

在C++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加,使用多个不同的人写的成员时,可能导致名称冲突。C++标准提供了名称空间工具,以便更好地控制名称的作用域。

我们在自己创建新的名称空间的时候,允许程序的其他部分使用该名称空间中声明的东西。我们使用关键字namespace 创建名称空间。例如:

namespace example{
double pail;//variable declar ation
void fetch();//function prototype
struct Well {... } ;//structure declaration
}

需要有一种方法来访问给定名称空间中的名称。最简单的方法是,一通过作用域解析运算符::使用名称空间来限定该名称,例如:

example::pail = 3.14159;//变量定义
example::fetch(){...}//函数定义

我们并不希望每次使用名称时都对它进行限定,我们可以使用using编译指令使整个名称空间在某个指定区域可用。例如:

namespace example{
double pail;//variable declar ation
void fetch();//function prototype
struct Well {... } ;//structure declaration
}
int main()
{
    using namespace example;
    pail=3.14159;
    return 0;
}

当然,如果名称空间中定义的名字不与其他空间中重合,还可以使用using声明,例如:

namespace example{
double pail;//variable declar ation
void fetch();//function prototype
struct Well {... } ;//structure declaration
}
using namespace::double pail;// a using declaration
int main()
{
    pail=3.14159;
    return 0;
}

一般说来,使用using声明比使用using编译指令更安全,这是由于它只导入指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using 编译指令导入所有名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味若名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。

当然,名称还具有其他的特性,例如:可以将名称空间声明进行嵌套,未命名的名称空间。

namespace example2{
    namespace example3{
    ...
    }
    ...
}
namespace  //unnamed namespace
{
    int cnt;
}

这就像后面跟着using 编译指令一样,也就是说, 在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾。从这个方面看,它们与全局变里相似。然而,由于这种名称空间没有名称,因此不能显式地使用using编译指令或using 声明来使它在其他位置都可用。所以其实相当于链接性为内部的静态变量的替代品。

即可以替换为static int cnt;


后记

这一章就讲到这里了,接下来的类才是面向对象编程的重点。

这里的一切都有始有终,却能容纳所有的不期而遇和久别重逢。
最后更新于 2024-01-14