C++ 数据存储有多种选择(可以 选择 数据保留在内存中的 时间长度 ,可以选择 程序中的那一部分代码可以访问数据——涉及到作用域和链接 ,可以使用new来动态的分配内存。C++ 名称空间是 一种 控制 访问权限的 方式。大型程序由多个源码文件组成——这些文件可能会共享一些数据,这些文件都需要单独进行编译)。
C++ 和 C 一样 ,鼓励将组件函数放在单独的文件中,这样单独的编译每个文件,最后将所有得到的目标文件进行链接 得到最终的可执行程序。(如果只修改了一个文件,那么只需要对修改的文件进行重新编译,并将得到的目标文件与其他目标文件进行链接即可——这使得大型程序的管理更加方便,并且编程环境会记录文件的修改时间,这样可以实现对只对修改后的文件进行编译。常用的编译工具 有 UNIX Linux中的 make程序 ,以及 其他集成开发环境)
不同系统对编译的具体不同,所以我们不需要过多关注编译的细节,重点关注更通用的方面——比如 程序设计。
将一个程序 分解放在多个文件中。(将 结构的声明 、 函数的原型 都放在头文件中;将函数定义放在一个单独的文件中;将主程序放在一个单独的文件中。以上的这种分解程序的方式,是一种非常有用的组织程序的策略。比如,当编写另一个程序是,只需将 头文件 和 包含函数定义的文件 复制到程序文件目录,并在主程序中添加对头文件的引用,就可以在主程序中使用 头文件中定义的类型和涉及的函数。像这样 一个头文件包含了 定义的数据类型 和 函数原型 ,另一个源代码文件 包含了函数的定义,这样的两个文件就组成了一个软件包,可以嵌入到各种程序当中)
不要将 函数定义 和 变量的声明 放到头文件中(因为,如果这么做了会引发其他问题)。头文件中常包含的内容有:函数原型、使用#define 或 const 定义的符号常量、结构声明、类声明、模板声明、内联函数。(结构神经、类声音、模板声明 都不创建变量,所以是可以放在头文件中。const数据 和 内联函数有特殊的链接属性,所以也可以放在头文件中)
在源代码文件中,引入头文件时,有两种语法:第一种:#include "corrd.h",这种语法,编译器会优先查找当前工作目录(源代码目录 或者 系统指定的其他目录),如果在当前工作目录没有找到该头文件,则会去系统的头文件目录中查找。(因此 这种语法,适用于 引入自定义的头文件的时候) 第二种:#include <corrd.h>,这种语法会优先在系统的头文件目录中查找,如果没找到,才会到当前工作目录中查找。(这种语法,适用于在引入 C++库中的头文件 或者 系统自带的头文件时 )
不要将 头文件 加入到项目类表中(只需将源代码文件加入到项目列表中,头文件使用#include语句引入。),不要在源代码文件中 引入(这里指的是#include)其他源代码文件(因为这样会导致多重声明)。
编译两个源代码文件的UNIX命令:CC file1.cpp file.cpp。
对于头文件有一个规则(就是 在一个同一个文件中 只允许出现一次——因为如果 重复的包含,则有可能出现结构定义重复,导致编译错误)。为了实现在同一个文件中只包含一次头文件,使用 预处理编译指令 #ifndef 指令 来 忽略 头文件重复包含的情况——这种方式并不能阻止代码包含的重复,而是通过忽略重复代码的形式 来使头文件中的代码不被重复执行而已,并且大多数 C/C++的头文件都采用这种形式来实现重复过滤。(#ifndef的语法为:
#ifndef COORDIN_H_
#define COORDIN_H_
;;;
#endif
。上述语法中,使用 #define 名称 这样的语句完成了名称的定义——#define 出定义名称之外,常用语创建符号常量,语法为,#define MAXIMUM 4096)
一般情况下 是 根据文件进行 单独编译(实际上,C++中是使用 翻译单元 这个术语,因为 文件 并不是计算机组织信息的唯一方式——但为了便于理解,我们一般 以文件 为组织信息的单元来 对一些概念进行讲解)
不同编译器 编译 得到的 目标文件 不能进行链接——这样会造成链接错误,因为 不同的编译器 底层 会根据自己的 名称修饰规则 来修改或替换函数名,这样就造成了 同一个函数 在使用不同的编译器 进行编译 的到的不同目标文件中 名字就不一致了,这样链接的时候 就无法 匹配 和 正常的链接。 (因此,在将目标文件 进行链接的时候 应确保所有目标文件 都是有一个编译器生成的——如果出现了由于编译器版本不一致 而导致的链接错误,可以使用自己的编译器对源码重新编译 并进行链接 来消除链接错误)
探究存储类别如何影响信息在文件之间的共享。(变量 的 链接性 决定了 那些信息 可以在文件之间共享)
自动变量:局部变量(包括函数参数),执行代码块的时候创建和分配内存,代码块执行结束后 销毁和释放内存。
静态变量:全局变量 和 static修饰的变量,它们在整个程序的运行期间都存在。(静态变量的作用域 也分为 局部 全局 外链 三种,但是每种静态变量的声明周期都是一样的——并且静态变量的数目在程序运行期间不会改变,所以 一般将 静态变量 存储在固定内存中,这些变量在程序执行期间一直存在,并且默认初始化为0值。静态数组的元素 和 静态结构的成员,默认情况下都会被自动初始化为0。)
线程变量:使用关键字thread_local 声明的变量,这种变量的生命周期和 它所属的线程一样长。
动态内存:也称自由内存(free store) 或 堆(heap),使用new关键字分配的内存,这种内存的释放有两种方法—— 使用delete运算符进行释放 或者 程序运行结束的时候自动释放。(若不释放,则这样的内存将一直处于被分配或占用的状态)
C++ 一个元素的存储方式 是通过 元素占用内存的生命周期、元素的作用域、元素的连接性 这三个方面来描述的。(函数原型中 变量的作用域 就是()以内——专业也就是为什么 函数原型中的 变量名字 可有可无的原因,因为出了这个() 这些变量也就失效了。在类中声明的成员,作用域是整个类。在名称空间中声明的变量的作用域时整个名称空间——全局作用域 其实是 名称空间作用域的特例。函数的作用域 可以是 整个类 或整个命名空间——这里的命名空间 包括全局作用域,函数的作用域不可能是局部的——因为 不能再代码块内 定义函数)
在C语言 和以前 的C++版本中,auto关键字,只是用来显示的 指出自动变量类型。(在C++11 中 auto 用于 对类型进行 推断)
register 关键字 在C语言和 和以前的C++ 版本中,将自动变量 标识为使用CPU寄存器存储——这样能够提高变量的访问速度。(但是在C++11 中该关键字 用来标识 变量是自动变量——这个功能 和 旧版中auto关键字的功能一样)
静态变量有三种 :定义在代码块之外的变量(不加 static关键字,它的作用域为 整个项目:当前文件 和 其他文件 都可以使用);定义在代码块之外的变量 并且 加 static关键字(它的作用域 为当前文件——其他文件不能使用该变量);定义在代码块内部的变量 并且加static关键字(这种变量,作用域就是当前代码块,除了代码块之外 就不能使用该变量)。(static 用于不同的静态变量时 的作用有所不同,satic关键字的含义取决于上下文,这种现象被称为关键字重载)
静态变量的初始化方式 有两种:静态初始化(就是 使用 常量 或者 表达式进行初始化,这种初始化方式 是 在编译阶段 就可以进行的);动态初始化(这一般 是 使用 调用函数 或者 包含调用哈数的表达式来初始化,这种初始化方式 在编译阶段无法完成,必须 在 编译 链接 之后 在程序执行的时候 才会调用函数 来实现 对静态变量的初始化)
对于外链接的静态变量,使用该变量的文件必须对其进行声明,才能正常使用。(并且对 这种静态变量的声明同样遵循 单定义的规则,就是 这种变量只能定义一次——也就是说,对这种变量的声明分为两种,一种是 进行定义的声明, 其他的都是进行引用的声明。引用声明的语法是:extern int blem; 如果将该语句 的blem 使用 = 5 进行赋值,那么该语句就不是引用生命了——就变成了定义声明,并且语句中的 extern也 变得可有可无 ,失去了意义)
但 外链接的静态变量(也称全局变量) 和 局部变量重名时,如果希望在函数中同时使用这两种变量,可以在 变量名前面 加上 ::(作用域解析运算符)——这样就表示 使用的是 我外链接的静态量,而不加 :: 运算符 的是 局部变量,这样就实现了在函数中同时使用这两种变量。
局部变量能够更好的实现程序 模块之间的隔离,因此更常用。全局变量 常用语特定的场景——比如 一些需要多个函数或文件共享的变量,会将其设置为全局变量,并且如果不希望对全局变量进行修改,则常用const关键字对其进行修饰。
对于静态局部变量,这种变量一般是在代码块中进行声明——这里以函数为例。静态局部变量会在函数第一次调用时,进行初始化,并且不会随着函数调用的结束而消失,在整个程序的运行过程中一直存在——也就是说,每次函数调用时,其他变量都是重新初始化,但是静态局部变量在第一次初始化之后,便不会再执行初始化,即使是重新调用函数,这个变量的值也是上一次调用函数结束后的值——本次调用对该变量的操作是建立在之前操作的基础上的,是具有连续性的。
像 static 、auto、extern、const、thread_local 等这些都被称为 存储说明符(或 cv说明符)。(在同一个声明语句中,不能同时使用多个存储说明符——也有特殊的情况,比如thread_local可以 和 static 或 extern 结合使用)
对于 静态外链接变量,如果 定义的时候使用了const 则 该变量就转变成了 内链接静态变量——相当于该变量 使用了 static const 进行了修饰(例如 const int figers =10;该语句相当于 static const int fingers=10; 这也就是为什么在头文中 使用const 来声明了变量,多个源文件同时引用该头文件时,不会出现变量重复定义错误——因为const 修饰的变量 作用域只是 局限于引用的源文件而已,这样不同的源文件中 的 这种变量是相互独立的,相当于staic变量,所以在链接阶段不会出现重复定义的错误)。如果 希望改变既使用 const关键字 又保持外链接的性质,需要在const 前面加上 extern 关键字。
函数默认是外链接的,但是如果在 函数原型 和 函数定义上 同时使用了static关键字 那么该函数就是变成了内链接函数(只能在当前文件中使用)
内联函数比较特殊,它可以直接在头文件中定义,并且链接的时候不会出现链接错误。
程序中如果调用了一个函数,如果该函数是静态函数,那么C++会首先在当前文件中查找 函数定义;如果该函数不是静态的,那么编译器会在整个程序的所有文件中查找该函数的定义——如果找到重复的函数定义,则报错;如果在程序文件中没找到,则会在库中继续搜索,这也就意味着,用户可以定义一个 与库 中的函数同名的函数,并且编译器会优先使用 用户 定义的函数。(对于 自定义函数名 和库 中的函数名 重名的情况,编译器 也可以通过 显示的指定 使用库中的函数)
在函数原型中 可以指定函数 在编译的时候 使用哪种 名称修饰方式。(比如。extern "C" void spiff(int); 该语句 表示 该函数在编译时 会使用 C语言的方式 对函数名称进行修改。如果将 “C” 去掉或者 换成“C++”,那么函数在编译时候就会使用C++的 名称修饰方式 对 函数名称进行修改)
通常编译器使用 三块独立的内存:固定内存(用于存储静态变量),栈(用于存储自动变量),堆(用于动态内存)
new运算符 可以从指定的空间来进行内存的分配。(例如:
char buffer[512];
double *pd4;
pd4=new(buffer) double[5];
上述语句 就是将 缓冲区 buffer 的5个double类型字符大小的空间 划分出来,并用指针pd4来存储该内存的地址。并且 这种内存分配,不能用 delete[] pd4;语句来进行释放——如果buffer本身是使用new来创建的,可以用delete 来删除 buffer——整个内存块。
)
new 和delete 关键字 底层也是以函数的形式进行定义,使用者两个关键字,本质也是相当于调用 new() 和 delete() 函数。
C++提供了 命名空间 这个工具,用来控制 名称的作用域。(C++ 通过创建 一个新的 声明区域 的方式来创建 命名空间——允许程序 的其他部分 使用 命名空间里 声明的东西;使用 namespace 来创建命名空间,语法为: namespace Jack{声明1;声明2;声明3;;;;}。基本概念:声明区域 就是 可以用来 进行声明的区域;而作用域 就是 声明的变量 实际可以发挥作用的区域。)
名称空间 内的 声明 和 该名称空间外(包括其它命名空间)的声明 是独立的、互不影响的——也就是说 是可以重名的。并且 命名空间是是可以 再次使用 namespace jack{} 这种语法对命名空间的内容进行任意的扩展。(对命名空间中 的 内容进行访问 通过 jack:: pail=1; 这种语法进行访问的。)
使用 using指令 可以 将命名空间的单个命名引入 (语法为 using Jack::a; 这个语句 将变量 a 引入当前区域,并且 不能再在当前区域 声明 a变量——这样会出现 变量名重复错误),也可以将整个命名空间全部引入 (语法为 using namespace Jack; 这个语句将 整个 命名空间的内容全部引入——这种情况下,在该区域是可以 声明 和命名空间中的变量 重名的变量的,这样优先生效的是局部变量,相当于将命名空间里的 同名变量进行了隐藏,并且这个被隐藏的命名空间 变量 可以通过 Jack::来实现显示的使用)
命名空间可以嵌套——也就是说,在一个命名空间里 可以 定义 另一个命名空间。也可以再一个命名空间里 使用 using指令 来引入其他命名空间的内容。
例如:
namespace myth
{
using Jill:fetch;
using namespace elemetns;
using std::cout;
namespace fire
{
int flame; ; ; ; ;
}
}
using namespace myth;该语句 将引入 命名空间里所用的声明——从包括其他命名空间引入的 内容 和 子命名的内容,所有这些都内容都可以直接使用。
也可以使用 using namespace myth :: fire;这种方式来 单独引入子命名空间的内容。
比如,对于fetch变量, myth::fetch 和Jill ::fetch 这两个语句是等效的。
可以通过语句 namespace m=myth; 这种语句 给 空间 myth 起一个别名m——这种方式 在命名空间 的名字 较长的时候,可以 简化书写。
如果 创建一个 没有名字的命命名空间,则该命名空间中的声明 只在当前文件可见——相当于创建了一堆静态内链接的变量。(例如。namespace{ int counts;} 主程序的其他语句; 上面这个程序 可以等效为 static int counts; 主程序的其他语句;)
如果一个函数 在命名空间中 进行了 原型的声明,那么它的定义 也必须在这个命名空间中才行。(因为该函数,的作用域 就是这个命名空间,所以它的 声明 和 定义 都必须在这个命名空间中)
命名空间中的声明 优先级 大于 静态全局变量 ,但是小于局部变量。
对于 new 分配的动态内存,程序是通过指针来 对着写内存单元进行跟踪的。
名称空间 的主要作用,是当程序非常大的时候,用于减少 声明的变量(或者其他内容)的名称的冲突。