C++ Primer Plus读书笔记
内存与命名空间
1.头文件
一种组织程序的策略,就是:一个文件(头文件)包含了用户定义类型的定义,另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成一个软件包,可以应用在各种程序中。
不把函数定义放在头文件中,原因是:如果在头文件中包含了一个函数的定义,然后在其他两个文件中include这个文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则将会出错。
- 下面是头文件中常包含的内容:
函数原型
使用#define或const定义的符号变量
结构声明
类声明
模板声明
内联函数
- 头文件管理
//假设头文件名称为zmyyq.h
#ifndef ZMYYQ_H_
......
#endif
2.存储持续性
存储持续性(用来确定数据在内存中留存的时间)
1.自动存储持续性:在函数定义中声明的变量(包含函数参数)的存储持续性为自动的,他们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,他们使用的内存就被释放
2.静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态,他们在整个程序运行过程中都存在。
__static修饰的内容__ 1. 如果是局部变量,则在用static修饰局部变量后,该变量只在初次运行时进行初始化工作,且只进行一次。 局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在__静态数据区__ ,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。 2. 通常情况下对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。例如: file1.c int a=1; file2.c #include<stdio.h> extern int a; int main(void) { printf("%d\",a); return 0; } 则执行结果为 1 但是如果在file1.c中把int a=1改为static int a=1; 那么在file2.c是无法访问到变量a的。原因在于用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。 3. 用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。
extern 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。 注意extern声明的位置对其作用域也有关系,如果是在main函数中进行声明的,则只能在main函数中调用,在其它函数中不能调用。其实要调用其它文件中的函数和变量,只需把该文件用#include包含进来即可,为啥要用extern?因为用extern会加速程序的编译过程,这样能节省时间。
3.线程存储持续性:使用关键字thread_local声明的变量,则其生命周期与所属的线程是一样的,这部分属于并行编程。
4.动态存储持续性:用new运算符分配的内存将一直存在,知道使用delete运算符将其释放或程序结束为止,这种内存的存储持续性为动态的,有时候被称为自由存储或堆
作用域和链接
- 作用域描述了名称在文件的多大范围可见
- C++函数的作用域可以是整个类或者整个名称空间,但不可能是局部的,也就是说代码块中不可以定义函数
- 链接性描述了名称如何在不同单元间共享。
- 1.外部链接性:可在其他文件中访问
- 2.内部链接性:只能在当前文件中访问
- 3.无链接性:只能在当前代码块中访问
- 作用域描述了名称在文件的多大范围可见
自动存储持续性:
自动指的是在函数声明的函数参数和变量的存储性为自动,作用域为局部,没有链接性- 自动变量的初始化:可以使用任何在声明时其值为已知的表达式来初始化自动变量。由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。
- 栈
- 解决自动变量管理的常用方法是留出一段内存,将其视为栈,管理变量的增减。之所以被称为栈是因为新数据被象征性地放在原有数据的上面,当程序使用完之后从栈中删除.
- 跟踪栈的实现方法:程序中通常使用2个指针来跟踪栈,一个指向栈底(栈开始的位置),另一个指针指向堆顶——下一个可用的内存单元。当函数被调用时,该函数的自动变量将加入到栈中,栈顶指针指向变量后面的可用内存单元,当函数调用结束之后栈顶指针被重置为函数被调用之前的位置。
- 栈空间的大小,在linux中是使用ulimit来指定的,编译时确定。在windows中,栈的大小是在链接是确定的。一般都是十几兆到几十兆之间不会太大。
总结各个区
动态存储区(堆):(动态分配)
- malloc,new动态分配在heap堆区。
- 动态存储区(堆),程序员自己分配自己释放。
- 使用
delete 指针
释放完内存后,指针还是存在的,并且还是之前的位置,只是它指向的内容已经变了。
动态存储区(栈):(动态分配)
- 自动变量、const变量在stack栈区。
- 动态存储区(栈),系统自动分配释放。
静态存储区:(静态分配)
- extern,全局变量,在static静态存储区。
- 静态存储区,一旦分配,不会被回收,可读可写
程序代码区:(静态分配)
- main函数、其他函数在code程序代码区。
- 程序代码区,一旦分配,可读不可写,不可改变
说明符与限定符
- 下面的属于存储说明符
- auto
- register 寄存器
- static 静态
- extern
- thread_local C++11新增的
- mutable
- 这个说明符是const的一个延伸,在使用const对结构进行修饰时,该结构不可被改变,但是他的成员还是可以被修改的,用这个修饰结构中的成员,则该成员不可被修改
struct data
{
char a[30];
mutable int access;//该成员变量不可被修改
}
- 这个说明符是const的一个延伸,在使用const对结构进行修饰时,该结构不可被改变,但是他的成员还是可以被修改的,用这个修饰结构中的成员,则该成员不可被修改
cv限定符
c表示const,它表明内存被初始化后,程序便不能再对它进行修改。它对于默认存储类型稍有影响。在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性是内部的,也就是用了const就相当于加上了static。
v表示volatile,该关键字表明:即使程序代码没有对内存单元进行修改,其值也可能会发生变化(大多是硬件改变某些取值,也有可能是两个程序共享一块内存)这个关键字的作用是为了改善编译器的优化能力,假设编译器发现程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个量两次,而是将这个值缓存到寄存器中,假设变量的值在这两次使用之间都不会变化,如果不设置这个volatile则告诉编译器不进行这种优化。
- 下面的属于存储说明符
函数的链接性:
- 函数在存储的持续性上都自动为静态的,因为函数定义的时候不允许在一个函数中定义另一个函数。
- 函数加上一个static表示只允许在当前代码文件中运行,一般来说,如果一个函数只在某个文件中执行,则要把他定义成static。
- 使用extern来指出函数是在另一个文件中定义的。如果使用extern则该函数只能在一个地方定义。
查找函数的方式
- 假设在程序的某个文件中调用一个函数,则按照一下顺序进行查找
- 如果该文件中的函数原型指出该函数是static,则编译器只在该文件中查找函数定义,否则将在所有文件中查找。
- 如果找到两个定义,编译器将会发出错误消息,每个外部函数只能有一个定义。如果在文件中找不到,则会到库中查找。这就意味着如果定义了一个与库函数同名的函数,编译器将使用自己写的函数,如果没有才去找库函数。
- 假设在程序的某个文件中调用一个函数,则按照一下顺序进行查找
2.名称空间
- 名称空间可以是全局的,也可以位于另一个名称空间中,但是不能位于代码块中,因此他的链接性为外部的
using声明和using编译指令
- using声明使得特定的标识符可用;(局部性)
- using编译指令使得整个名称空间可用。(所有)
对比:
- 使用using编译指令导入一个名称空间中的所有名称与使用多个using声明是不一样的。如果某个名称已经在函数中被声明,则不能使用using声明导入相同的名称,
- 使用using编译指令时,如果导入了一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样,不过仍可以用作用域解析运算符来调用。
- 一般来说,使用using声明比使用using编译指令要安全,它只导入指定的名称,如果与局部的名称发生冲突,则编译器会发出指示。
#include<iostream> using namespace std; namespace ZhuMengYanYiQuan { double fetch = 3.14; } double fetch = 5.16; int main() { double fetch = 4.15; using namespace ZhuMengYanYiQuan; cout << fetch << endl; //4.15 cout << ::fetch << endl; //5.16 cout << ZhuMengYanYiQuan::fetch << endl; //3.14 return 0; }
- 名称空间的其他特性
- 可以将名称空间声明进行嵌套,调用的时候就按着嵌套的顺序使用::进行解析即可。
namespace ZhuMengYanYiQuan
{
namespace QuanQuanQuan
{
float ChunJie = 5.5;
}
double fetch = 3.14;
}
- 未命名的名称空间:表示在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾,与全局变量类似。因为它没有名字,所以不能让他在其他的地方使用。但是它提供了链接性为内部的静态变量的替代品。
namespace
{
int ice;
int hot;
}
- 可以将名称空间声明进行嵌套,调用的时候就按着嵌套的顺序使用::进行解析即可。
名称空间及其用途:下面是一些指导性的准则
- 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
- 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
- 如果开发了一个函数库或类库,将其放在一个名称空间中。
- 仅将编译指令作为一种将旧代码转换为使用名称空间的权宜之计。
- 在头文件中不要使用using编译指令,首先这样会掩盖要让那些名称可用哪些不可用,另外,包含头文件的顺序可能会影响程序的行为。如果非要加using编译指令,则应将其放在所有预处理编译指令#include之后。
导入名称时,首选使用作用域解析运算或者using声明的方法。
对于using声明,首选将其作用域设置为局部而不是全局。
3.总结
一种代码的组织策略:
使用头文件来定义用户类型、函数原型;
并将函数定义放在一个独立的源代码中;
- 头文件和源代码文件一起用来定义和实现用户定义的类型和方法;
- 调用这些函数的函数放在第三个文件之中。
- 也就是一个.h和.cpp用来定义和实现功能,对他们的调用放在另一个.cpp中
C++的存储方案
- 静态变量存在于整个程序执行期间,对于在函数外面定义的变量,其所属的文件中位于该变量定义之后的所有函数都可以使用,并且可以在程序的其他文件中使用(外部链接性),另一个文件要使用它,必须使用extern关键字来声明。
- 对于文件之间共享的变量,应在一个文件包含其定义声明,也可进行初始化。在其他文件中包含声明(使用extern且不初始化)
- 使用static修饰的在函数之外声明的变量,只能在当前的文件中使用,不能在其他文件中使用。