IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《C++ Primier Plus》笔记:内存模型部分(ch9 内存模型和名称空间 ch12类和动态内存分配) -> 正文阅读

[C++知识库]《C++ Primier Plus》笔记:内存模型部分(ch9 内存模型和名称空间 ch12类和动态内存分配)

Chapter9 内存模型和名称空间

9.1 单独编译

  • 如果只修改了一个文件,则可以只重新编译该文件,然后将其与其他文件的编译版本链接
  • 与其将结构声明加入到每一个文件中,步入将其放在头文件中,然后在每一个文件中包含头文件
  • 可以将程序分为三部分:
    • 头文件:结构声明,使用结构的函数原型
    • 源代码文件:结构相关函数代码
    • 调用文件:调用函数的代码
  • 不能再头文件中包含函数定义,再在其他两个源文件中包含该头文件
  • 头文件中应该包含的内容:
    • 函数原型
    • 使用#define或const定义的符号常量
    • 结构声明
    • 类声明
    • 模板声明
    • 内联函数
  • 可以将结构声明放在头文件里,因为不会创建变量,只是告诉编译器如何创建变量;模板声明不是将被编译的代码,而是指示编译器如何生成函数定义;被声明为const的函数和内联函数有特殊的链接属性。
  • 保护方案:
    #ifndef C
    #define C
    #endif
  • C++标准允许每个编译器设计人员实现不同的名称修饰,因此不用编译器生成的对象文件可能无法正确链接

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

  • C++中有四种方案存储数据,区别在于数据保留在内存里的持续时间
    • 自动存储持续性(在栈里):在函数定义中声明的变量和函数参数,在开始执行代码块时创建,结束执行时释放
    • 静态存储持续性:在函数外定义的变量,使用static定义的变量,在程序的整个运行过程都存在
    • 线程存储持续性:用关键词thread local声明,生命周期和所属线程一样长
    • 动态存储持续性(在堆里):用new分配的内存直到delete都不会被删除

9.2.1 作用域和链接

  • 作用域(scope)描述了名称在文件的多大范围内可见

    • 局部的变量只在定义它的代码块可用:自动变量的作用域是局部。
    • 全局的变量在定义位置到文件结尾都可用:静态变量的作用域是全局还是局部取决于它是如何被定义的。
    • 在函数原型作用域中使用的名称只在包含参数列表的括号内可用
    • 在类中声明的成员的作用域为整个类
    • 在名称空间中声明的变量的作用域
    • C++函数的作用域可以是整个类或整个名称空间,但不能是全局的
  • 链接性(linkage)

    • 链接性为外部的名称可在文件间共享
    • 链接性为内部的名称职能由一个文件中的函数共享
  • 内部代码块的局部变量隐藏了同名外部变量的定义

9.2.2 自动存储持续性

  • 关键字register最初是由C语言引入的,建议编译器使用CPU寄存器来存储自动变量来提高访问变量的速度。在C++11中,只是显式地指出变量是自动的

9.2.3 静态持续变量

  • C++为静态存储持续性变量提供了3种链接性,这3种链接型都能在程序执行期间存在
    • 外部链接性:可在其他文件中访问
    • 内部链接性:只在当前文件中访问
    • 无链接性:只能在当前函数或代码块中访问
  • 由于静态变量的数目在程序运行期间是不变的,不需要使用特殊的结构管理;编译器将分配固定的内存块来存储所有的静态变量
  • 如果没有显式初始化静态变量,编译器将把它们设置为0.静态数组和结构每个元素或成员变量都设置为0。
int glabal = 1000;      //静态持续性,外部链接性
static int one_file;    //静态持续性,内部链接性
void function(int n){
    static int count = 0;   //静态持续性,无链接性
}
//即使在func1没有被执行时,count也留在内存中

变量的5种存储方式

存储描述持续性作用域链接性声明方式
自动自动代码块在代码块中
寄存器自动代码块在代码块中,使用关键字register
静态,无链接性静态代码块在代码块中,使用关键字static
静态,外部链接性静态文件外部不在任何函数中
静态,内部链接性静态文件内部不在任何函数中,使用关键字static
  • 在C++代码中,空指针用0表示,但内部可能用非零表示,因此指针变量将被初始化相应的内部表示
  • 零初始化和常量表达式初始化统称为静态初始化,这意味着编译器在处理文件时初始化变量,而动态初始化意味着在编译后初始化
  • 首先所有静态变量被0初始化,接下来如果使用常量表达式初始了变量则编译器将根据文件内容计算表达式(如 int enough = 2sizeof(long)+1),否则将采用动态初始化(如:const double pi = 4.0atan(1.0))

9.2.4 静态持续性,外部链接性

  • C++有两种声明:定义声明(defining declaration)和引用声明(referencing declaration)
    • 定义声明,简称为定义,为变量分配内存空间
    • 引用声明,简称为声明,不为变量存储空间,因为是引用已有的变量:使用关键字extern,且不进行初始化
double up;              //定义声明
extern int blem;        //引用声明
extern char gr = 'z'    //定义声明
  • 如果要在多个文件使用外部变量,只需在一个文件中包含该变量的定义声明,但在使用该变量的其他所有文件中,都必须使用extern声明它
  • 作用域解释符::放在变量名前面时,该运算符表示使用变量的全局版本
  • 全局变量可以被所用函数访问,但程序会不可靠:解决方法是使用const避免不该的修改

const char* const months[12] = {
    "Jan",....
}

//第一个const防止字符串被修改,第二个const确保数组中的每一个指针始终指向它最初指向的字符串

9.2.5 静态持续性,内部链接性

  • 可以防止和其他文件中的同名静态变量冲突

9.2.6 静态持续性,无链接性

  • 它在两次函数调用之间仍然存在,值保持不变
  • 只在启动时进行一次初始化,以后调用时不执行初始化

9.2.7 说明符和限定符

  • 关键字thread_local指出变量的持续性与其线程的持续性相同。thread_local变量之于线程正如静态变量之于程序
  • const限定符
    • 内存被初始化后,程序不能对其进行修改
    • 默认情况下全局变量的链接性为外部的,但const全局变量的链接性是内部的(就像使用了static说明符一样)——这是因为通常把const放在头文件里。
    • 内部链接性还意味着每个文件都有自己的一组常量
    • 如果希望某个常量的链接性为外部的,可以用extern来覆盖内部链接性:extern const int states = 50;且必须在使用变量的外部文件中使用extern声明。
    • 在函数或代码块中声明const时,其作用域为代码块
  • mutable
    • 指出即使结构或类变量为const,其某个成员也可以声明为mutable,从而可被修改
  • volatile限定符
    • 即使程序没有对其修改,其值也可能发生变化(by硬件)。
    • 告诉编译器不要执行对这种类型执行调到寄存器的相关优化

9.2.8 函数和链接性

  • 所有函数的持续性都是静态的
  • 默认情况下函数的链接性为外部的,可以使用关键字static将函数的链接性设为内部的,必须同时在原型和函数定义中使用该关键字
  • 对于每一个非内联函数,程序只能包含一个定义
  • 对于链接性为外部的函数来说,在多文件程序中只能有一个文件包含该函数的定义,但使用函数的每个文件都该包含其函数原型
  • 内联函数不受单定义规则约束,因此程序员可以将内联函数的定义放在头文件中:不过同一个函数的所有内联定义都必须相同
  • C++查找函数定义的顺序:
    • 如果函数原型指出函数是静态的,则只在该文件中查找函数定义;否则将在所有的程序文件中查找
    • 如果找到两个定义,编译器将发出错误信息
    • 如果在文件中没有找到,将在库中搜索:这意味着文件中定义的函数会覆盖同名库函数

9.2.9 语言链接性

  • 由于C++有名称修饰,当使用C函数时可以用函数原型来指出:
extern "C" void spiff(int);     //用C的protocol来找函数
extern  void spiff(int);        //用C++的protocol来找函数
extern "C++" void spiff(int);   //用C++的protocol来找函数

9.2.10 存储方案和动态分配

  • 动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制:因此可以在一个函数中分配动态内存,而在另一个函数中释放
  • 编译器使用三块独立的内存空间:用于自动变量,用于静态变量,用于动态存储
  • 动态存储分配的内存返回地址赋给指针变量p,如果是外部链接性的需要在使用的地方声明: extern float* p;
  • 在程序结束时由new分配的内存通常将被释放,但在某些情况下请求大型内存块将导致程序结束不会自动释放
int *pi = new int (6);              //*p 设为 6
double* pd = new double (99.99)     //*pd 设为99.99

C++11支持:

struct where {double x; double y; double z};
where *one = new where {2.5, 6.3, 4.2};
int* ar = new int [5] {26,7,3,5,6};
  • new失败时,前十年C++返回空指针,但现在将引发异常std::bad_alloc
  • 运算符new和new[]分别调用两个分配函数,它们处于全局名称空间中:
    void * operator new(std::size_t);
    void * operator new[] (std::size_t);
    //存在以下转换过程
    int* pi = new int ---> int* pi = new(sizeof(int))
    
  • 与之相对的是释放函数
    void operator delete(void* )
    void operator delete[](void*)
    

9.3.1 传统的C++名称空间(补图9.6)

  • 几个基本概念:
    • 声明区域:可以在其中声明的区域
    • 潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾
    • 作用域:变量对于程序而言可见的区域
  • 变量并非在潜在作用域都可见,可能被隐藏

p267 图9.6

9.3.2 新的名称空间特性

  • 一个名称空间中的名称不会与另一个名称空间的相同名称冲突
  • 名称空间可以是全局的,也可以位于另一个名称空间中,但不能在代码块中
  • 名称空间中声明的名称的链接性默认为外部的(除非它引用了常量)
  • 全局名称空间,对应于文件级声明区域,全局变量位于全局名称空间中
  • using声明
    • 例using Jill::fetch可以用fetch代替Jill::fetch
    • 在函数外面使用using声明,将把名称添加到全局名称空间
    • 不能在函数中同时存在using Jill::fetch和局部的fetch;这比using编译指令更安全,后者会直接让局部的来覆盖
  • using编译指令
    • using namespace Jill可以使用名称空间中所有名称
    • 局部声明的函数名fetch将隐藏内部using namespace Jill的fetch;换句话说就是不能用fetch代替Jill::fetch了,需要显式使用Jill::fetch

9.3.4 名称空间及其前途

一些使用名称空间的建议:

  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量或静态全局变量
  • 不要再头文件中使用using编译指令,如果非要使用应该放在所有#include之后
  • 导入名称时首选使用作用域解析运算符或using声明
  • 对于using声明,首选将其作用域设为局部而不是全局

Chapter12 类和动态内存分配

12.1 动态内存和类

  • C++使用new和delete来动态控制内存,但在类中使用堆内存会带来问题,需要析构函数来消除这一问题
  • 将成员声明为静态存储类,这对对于所有类对象都有相同值的类私有数据是非常方便的,例如:记录所创建的该类对象数目
  • 不能在类声明中初始化静态成员变量,因为声明描述了如何分配内存,但并不分配内存。可以在类声明之外用单独的语句初始化,因为静态类成员是单独存储的,不是类的组成成分:
    int StringBad::num_strings = 0;
    
  • 初始化是在方法文件中,而不是在类声明文件中进行的。但如果静态成员是const整数类型或枚举型,则可以在类声明中初始化
  • 如果在头文件中进行初始化,将出现多个初始化语句副本引发错误
  • 在类构造函数中new/new[]的指针会在对象销毁的时候销毁,但其在堆中开辟的空间依然存在。因此必须在析构函数中用delete/delete[]删除
  • 当使用一个对象来初始化另一个对象时(例如,在函数中将对象作为参数且按值传递),编译器将自动生成复制构造函数,并且会调用析构函数

12.1.2 特殊成员函数

  • 如果用户没有自己定义,C++自动提供了以下成员函数:

    • 默认构造函数

      • 编译器将提供一个不接受任何参数,不执行任何操作的构造函数。
      • 使实例的成员值在初始化时是未知的
      • 构造函数的重载不能导致二义性
    • 默认析构函数

    • 复制构造函数

      • 用于将一个对象复制到新创建的对象中
      • 用于初始化过程(包括按值传递参数、返回对象),而不是常规赋值过程中
      • 原型通常是 ClassName(const ClassName &)
      • 新建一个对象并将其初始化为同类现有对象时将调用:
      StringBad ditto(motto);
      StringBad metoo = motoo;
      StringBad also = StringBad(motto);
      StringBad* pStringBad = new StringBad(motto); //初始化一个匿名对象,并将新对象地址赋给指针
      
      • 默认的复制构造函数逐个复制非静态类成员(成员复制也称为浅复制),复制的是成员的值
      • 如果成员是一个指向数据的指针,浅复制将导致两个对象指向同一数据
    • 赋值运算符

    • 地址运算符

  • 隐式地址运算符返回调用对象的地址(即this指针的值)

  • C++11提供了另外两个特殊成员函数:移动构造函数、移动赋值运算符

12.1.4 Stringbad的其他问题:赋值运算符

  • C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的:
    • ClassName & ClassName::operator= (const ClassName&)
  • 赋值运算符的隐式实现也是堆成员逐个复制,这也可能导致浅复制的问题

12.2.1 修改后的默认构造函数

  • C++11引入关键字nullptr表示空指针

12.2.3 使用中括号表示访问字符

  • 可以使用方法operator来重载访问运算符
  • 在city[0]中,city是第一个操作数,[]是操作符, 0是第二个操作数
  • 对于const对象,应该重载[]运算符,否则将不可写

12.2.4 静态类成员函数

  • 可以将成员函数声明为静态的,不能通过对象调用静态成员函数
  • 如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用
  • 静态成员函数只能使用静态数据成员

12.3 在构造函数中使用new时应该注意的事项

  • 如果有多个构造函数,必须用相同的方法使用new,因为只有一个析构函数,必须和它兼容
  • 复制构造函数应该:
    • 做到深复制,分配新空间复制数据
    • 更新所有受影响的静态
  • 赋值运算符应该
    • 做到深复制:检查自我赋值的情况,释放成员指针以前指向的内存,返回一个指向调用对象的引用

12.4.1 返回指向const对象的引用

  • 返回对象将调用复制构造函数,而返回引用不会
  • 引用指向的对象应该在调用函数执行时存在

12.4.2 返回指向非const对象的引用

  • 常见的返回非const对象引用的情景是:
    • 重载赋值运算符:用于连续赋值
    • 重载与cout一起使用的<<运算符:用于串接输出

12.4.3 返回对象

  • 如果被返回的对象是被调用函数中的局部变量,则不应该按引用的方式返回他,因为函数返回前将调用局部对象的析构函数

12.4.4 返回const对象

  • 如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须返回这种对象的引用

12.5 使用指向对象的指针

  • 如果ClassName是类,value的类型为TypeName,
    ClassName *pclass = new ClassName(value)
    //将调用以下构造函数, 可能包含到引用的转换或int到double的转换
    ClassName(TypeName)
    
    ClassName *ptr = new ClassName
    //将调用默认构造函数
    
  • 在下述情况下析构函数将被调用:
    • 如果对象是动态变量,则执行完定义该对象的程序块时将调用该对象的析构函数
    • 如果对象是静态变量,则在程序结束时调用对象的析构函数
    • 如果对象是new创建的,则只在显式调用delete时采调用析构函数

12.5.2 指针和对象小结

  • 使用对象指针时:
  • 可以将指针初始化为指向已有的对象
  • 可以使用new来初始化指针,这将创建一个新的对象
  • 对类使用new将调用相应的类构造函数来初始化新创建的对象
  • 用->访问类方法,用解除引用运算符*来获得对象

12.5.3 再谈定位new运算符

  • 对于使用定位new运算符创建的对象,应该以与创建顺序相反的顺序进行删除:因为晚创建的对象可能依赖于早创建的对象
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:34:21  更:2022-07-20 18:34:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 22:27:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码