3.7 C++高级编程_自己实现智能指针

指针未释放引起的内存泄漏

先定义一个Person类,在Person类中定义一个构造函数,一个析构函数,一个普通的成员函数。

Person类

class Person {
public:
    Person()
    {
        cout << "Person()" << endl;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

    void printInfo(void)
    {
        cout << "printInfo()" << endl;
    }
};

然后编写一个测试函数test_func,从内存中申请了一块Person类大小的空间。

test_func函数

void test_func(void)
{
    Person *p = new Person();

    p->printInfo();
}

最后,在main函数中调用test_func函数。

main函数

int main(int argc, char **argv)
{
    test_func();

    return 0;
}

最终,函数源码如下:

#include <iostream>
#include <string.h>
#include <unistd.h>

using namespace std;

class Person {
public:
    Person()
    {
        cout << "Person()" << endl;
    }

    ~Person()
    {
        cout << "~Person()" << endl;
    }

    void printInfo(void)
    {
        cout << "printInfo()" << endl;
    }
};

void test_func(void)
{
    Person *p = new Person();

    p->printInfo();
}

int main(int argc, char **argv)
{
    test_func();

    return 0;
}

这个代码有什么问题呢?

我们编译执行一下,可以看到Person类的构造函数被调用了,但是在程序运行完毕退出后Person类的析构函数没有调用

也就是说,在程序运行退出后,没有将之前申请的内存释放掉,这时候,就发生了内存泄漏

增加内存释放

要想防止这个问题,可以这样做:

1、在函数退出时增加delete操作

 

2、或者使用局部变量也可以防止这个问题。

今天讲第 3 种方法,那就是使用智能指针

智能指针

定义一个sp类,在sp类中,有一个私有成员p,它是一个指向Person类的指针

然后定义两个构造函数,一个构造函数的传参为一个指向Person类的指针,另一个构造函数的传参为,主要是在没有申请Person类空间的时候将私有成员指空

定义一个析构函数,在析构函数中,如果指针p不为空,则表示有申请过内存空间,此时需要将空间释放。

class sp {
private:
    Person* p;

public:
    sp () : p(0)
    {
        cout << "sp () : p(0)" << endl;
    }

    sp (Person *other)
    {
        cout << "sp (Person *other)" << endl;
        this->p = other;
    }

    ~sp ()
    {
        cout << "~sp ()" << endl;
        if (p) {
            cout << "delete this->p" << endl;
            delete this->p;
        }
    }
};

修改test_func函数,在函数中创建了一个sp类的对象s。

void test_func(void)
{
    sp s = new Person;
}

此时,会调用构造函数 sp (Person *other)。

编译测试,可以看到虽然没有delete,但是申请的内存空间在函数调用结束后,还是会自动释放。

这是因为创建的对象p实际上是一个局部变量,局部变量的生命周期随函数调用结束而终止

重定向->

此时,如果想要使用Person类里面的成员要怎么做?

显然直接使用“->”是不行的,因为 s 实际上还是一个sp类型的对象。

此时需要重定向“->”,在sp类的定义中添加下面的重定向函数,返回sp类中指向Person类的指针成员。

Person* operator->(void)
{
    return this->p;
}

修改test_func函数,增加调用Person类的成员函数printInfo。

编译测试,可以看到成员函数printInfo被成功调用。

增加传参为sp类本身的构造函数

继续修改代码,增加一个拷贝构造函数,传参为sp类本身。

修改测试函数,传入一个sp类对象,使用传入的对象,初始化函数内部定义的局部变量。

修改main函数,创建一个sp类对象,并且把创建的对象作为参数传给test_func函数。

编译测试,发现第71行有报错。

报错信息说:不可以把sp转换为sp引用。

先分析一下第71行,实际上,编译第71行的代码时,编译器实际上是通过三行代码实现的:

sp other = new Person();
相当于:
Person *p = new Person();
sp tmp(p); ==> 对应的构造函数:sp (Person *other)
sp other(tmp); ==> 对应的构造函数:sp(sp &other)

问题出在第三行:sp &other = tmp; 这是错误的语法,不能引用临时变量。

修改为:const sp &other = tmp; 正确的语法。

修改构造函数,增加const属性。

此时可以编译成功,但是执行时有报错。

这里其实有一个疑惑。

我的理解是sp other = new Person(); 这行代码其实传入的就是一个指向Person类的指针,调用的应该就是sp (Person *other),没有用到sp (const sp &other)才对;

用到sp (const sp &other)函数的,应该是 sp s = other;

从执行来看也是sp other = new Person(); 也是没有调用到 sp (const sp &other) 的,但是编译器确实在71行报了类型错误,费解...

显然,报错的原因是申请的空间被释放了两次:

  1. test_func函数,退出函数,清除临时变量时调用了一次;
  2. 程序执行完毕退出时,又调用了一次;

 

当空间已经释放后,不应该再对这个空间进行二次释放。

修改代码,在Person类中增加一个私有成员count,用来记录sp类的使用次数,当这个次数不为0时,就表示没有其他程序使用该对象,此时可以将申请的空间释放掉。

 在Person类的构造函数中将计数值初始化为0。

 

在sp类的构造函数中对计数值加1。

 

在析构函数中对计数值减1,当减为0时,表示没有其他程序要操作这个类,此时才调用delete释放空间。

 

编译测试,程序可以正常执行。

重定向*

使用智能指针的目的是:少用"Person *"; 用"sp"来代替"Person *"

防止出现内存未释放引起的内存泄漏。

Person *per,有2种操作:per->xxx, (*per).xxx;

那么,sp也应该有这两种操作:sp->xxx, (*sp).xxx;

其中,sp->xxx之前已经实现了,现在来增加 (*sp).xxx。

修改sp类,增加一个成员函数 Person& operator*(void),重定向*。

修改main函数,测试一下。

程序可以正常执行。

增加基类

前面为了解决空间重复释放的问题,我们在Person类中加了一个count变量,还有与这个变量相关的一系列操作函数。

这部分的内容,如果定义了一个Cat类,Dog类时,也有可能会使用。

那么,可以把这部分内容单独拿出来,单独定义一个基类,后续如果有其他类需要使用计数的功能时,就可以直接继承这个基类。

修改代码,增加一个基类RefBase。

需要注意的是,要将之前在Person类的构造函数初始化count值,改为在RefBase类的构造函数初始化count值。

Person类继承基类RefBase即可。

编译测试,程序执行成功。

使用模板

对于当前的代码,可以使用sp代替Person *p,但是如果有Cat类,或者Dog类,要使用sp代替Cat *c,Dog *d,还需要重新定义sp类。

但是,如果将sp类定义为一个模板,则可以免去重新定义的繁琐操作。

将sp类修改为模板,将之前sp类中的Person全部换成T。

template <typename T>
class sp {
private:
    T* p;

public:
    sp () : p(0)
    {
        cout << "sp () : p(0)" << endl;
    }

    sp (T *other)
    {
        cout << "sp (T *other)" << endl;
        this->p = other;
        p->incStrong();
    }

    sp (const sp &other)
    {
        cout << "sp (const sp &other)" << endl;
        this->p = other.p;
        p->incStrong();
    }

    ~sp ()
    {
        cout << "~sp ()" << endl;
        if (p) {
            cout << "delete this->p" << endl;
            p->decStrong();
            if (0 == p->getStrongCount()) {
                delete this->p;
                this->p = NULL;
            }
        }
    }

    T* operator->(void)
    {
        return this->p;
    }

    T& operator*(void)
    {
        return *p;
    }
};

测试函数也修改为模板函数。

template <typename T>
void test_func(sp<T> &other)
{
    sp<T> s = other;

    s->printInfo();
}

main函数中,创建sp对象时,将T声明为Person。

编译测试,执行成功。

这样,以后如果需要使用Person *时,就可以使用sp<Person>来代替。

此时,new出来了一个对象,但是不需要去delete它,由系统来自动delete这个对象。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 1.2.2 采用渐进的学习方式 4 1.2.3 运行效率 4 1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺什么 10 1.3.6 方法应当提供什么 10 1.4 起草:最小的方法 12 1.4.1 前提 13 1.4.2 高概念 14 1.4.3 论述(treatment) 14 1.4.4 结构化 14 1.4.5 开发 16 1.4.6 重写 17 1.4.7 逻辑 17 1.5 其他方法 17 1.5.1 Booch 18 1.5.2 责任驱动的设计(RDD) 19 1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 什么是非正常 29 2.5 基本对象 30 2.6 什么是对象 34 2.7 抽象数据类型 35 2.8 对象细节 35 2.9 头文件形式 36 2.10 嵌套结构 37 2.11 小结 41 2.12 练习 41 第3章 隐藏实现 42 3.1 设置限制 42 3.2 C++的存取控制 42 3.3 友元 44 3.3.1 嵌套友元 45 3.3.2 它是纯的吗 48 3.4 对象布局 48 3.5 类 48 3.5.1 用存取控制来修改stash 50 3.5.2 用存取控制来修改stack 51 3.6 句柄类(handle classes) 51 3.6.1 可见的实现部分 51 3.6.2 减少重复编译 52 3.7 小结 54 3.8 练习 54 第4章 初始化与清除 55 4.1 用构造函数确保初始化 55 4.2 用析构函数确保清除 56 4.3 清除定义块 58 4.3.1 for循环 59 4.3.2 空间分配 60 4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 70 5.1.2 安全类型连接 70 5.2 重载的例子 71 5.3 缺省参数 74 5.4 小结 81 5.5 练习 82 第6章 输入输出流介绍 83 6.1 为什么要用输入输出流 83 6.2 解决输入输出流问题 86 6.2.1 预先了解操作符重载 86 6.2.2 插入符与提取符 87 6.2.3 通常用法 88 6.2.4 面向行的输入 90 6.3 文件输入输出流 91 6.4 输入输出流缓冲 93 6.5 在输入输出流中查找 94 6.6 strstreams 96 6.6.1 为用户分配的存储 96 6.6.2 自动存储分配 98 6.7 输出流格式化 100 6.7.1 内部格式化数据 101 6.7.2 例子 102 6.8 格式化操纵算子 106 6.9 建立操纵算子 108 6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 7.1.4 与C语言的区别 126 7.2 指针 127 7.2.1 指向const的指针 127 7.2.2 const指针 127 7.2.3 赋值和类型检查 128 7.3 函数参数和返回值 128 7.3.1 传递const值 128 7.3.2 返回const值 129 7.3.3 传递和返回地址 131 7.4 类 133 7.4.1 类里的const和enum 133 7.4.2 编译期间类里的常量 134 7.4.3 const对象和成员函数 136 7.4.4 只读存储能力 139 7.5 可变的(volatile) 140 7.6 小结 141 7.7 练习 141 第8章 内联函数 142 8.1 预处理器的缺陷 142 8.2 内联函数 144 8.2.1 类内部的内联函数 145 8.2.2 存取函数 146 8.3 内联函数和编译器 150 8.3.1 局限性 150 8.3.2 赋值顺序 150 8.3.3 在构造函数和析构函数里隐藏行为 151 8.4 减少混乱 152 8.5 预处理器的特点 153 8.6 改进的错误检查 154 8.7 小结 155 8.8 练习 155 第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 C++中的静态成员 166 9.3.1 定义静态数据成员的存储 166 9.3.2 嵌套类和局部类 168 9.3.3 静态成员函数 169 9.4 静态初始化的依赖因素 171 9.5 转换连接指定 174 9.6 小结 174 9.7 练习 174 第10章 引用和拷贝构造函数 176 10.1 C++中的指针 176 10.2 C++中的引用 176 10.2.1 函数中的引用 177 10.2.2 参数传递准则 178 10.3 拷贝构造函数 179 10.3.1 传值方式传递和返回 179 10.3.2 拷贝构造函数 182 10.3.3 缺省拷贝构造函数 187 10.3.4 拷贝构造函数方法的选择 188 10.4 指向成员的指针
目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 1.2.2 采用渐进的学习方式 4 1.2.3 运行效率 4 1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺什么 10 1.3.6 方法应当提供什么 10 1.4 起草:最小的方法 12 1.4.1 前提 13 1.4.2 高概念 14 1.4.3 论述(treatment) 14 1.4.4 结构化 14 1.4.5 开发 16 1.4.6 重写 17 1.4.7 逻辑 17 1.5 其他方法 17 1.5.1 Booch 18 1.5.2 责任驱动的设计(RDD) 19 1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 什么是非正常 29 2.5 基本对象 30 2.6 什么是对象 34 2.7 抽象数据类型 35 2.8 对象细节 35 2.9 头文件形式 36 2.10 嵌套结构 37 2.11 小结 41 2.12 练习 41 第3章 隐藏实现 42 3.1 设置限制 42 3.2 C++的存取控制 42 3.3 友元 44 3.3.1 嵌套友元 45 3.3.2 它是纯的吗 48 3.4 对象布局 48 3.5 类 48 3.5.1 用存取控制来修改stash 50 3.5.2 用存取控制来修改stack 51 3.6 句柄类(handle classes) 51 3.6.1 可见的实现部分 51 3.6.2 减少重复编译 52 3.7 小结 54 3.8 练习 54 第4章 初始化与清除 55 4.1 用构造函数确保初始化 55 4.2 用析构函数确保清除 56 4.3 清除定义块 58 4.3.1 for循环 59 4.3.2 空间分配 60 4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 70 5.1.2 安全类型连接 70 5.2 重载的例子 71 5.3 缺省参数 74 5.4 小结 81 5.5 练习 82 第6章 输入输出流介绍 83 6.1 为什么要用输入输出流 83 6.2 解决输入输出流问题 86 6.2.1 预先了解操作符重载 86 6.2.2 插入符与提取符 87 6.2.3 通常用法 88 6.2.4 面向行的输入 90 6.3 文件输入输出流 91 6.4 输入输出流缓冲 93 6.5 在输入输出流中查找 94 6.6 strstreams 96 6.6.1 为用户分配的存储 96 6.6.2 自动存储分配 98 6.7 输出流格式化 100 6.7.1 内部格式化数据 101 6.7.2 例子 102 6.8 格式化操纵算子 106 6.9 建立操纵算子 108 6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 7.1.4 与C语言的区别 126 7.2 指针 127 7.2.1 指向const的指针 127 7.2.2 const指针 127 7.2.3 赋值和类型检查 128 7.3 函数参数和返回值 128 7.3.1 传递const值 128 7.3.2 返回const值 129 7.3.3 传递和返回地址 131 7.4 类 133 7.4.1 类里的const和enum 133 7.4.2 编译期间类里的常量 134 7.4.3 const对象和成员函数 136 7.4.4 只读存储能力 139 7.5 可变的(volatile) 140 7.6 小结 141 7.7 练习 141 第8章 内联函数 142 8.1 预处理器的缺陷 142 8.2 内联函数 144 8.2.1 类内部的内联函数 145 8.2.2 存取函数 146 8.3 内联函数和编译器 150 8.3.1 局限性 150 8.3.2 赋值顺序 150 8.3.3 在构造函数和析构函数里隐藏行为 151 8.4 减少混乱 152 8.5 预处理器的特点 153 8.6 改进的错误检查 154 8.7 小结 155 8.8 练习 155 第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 C++中的静态成员 166 9.3.1 定义静态数据成员的存储 166 9.3.2 嵌套类和局部类 168 9.3.3 静态成员函数 169 9.4 静态初始化的依赖因素 171 9.5 转换连接指定 174 9.6 小结 174 9.7 练习 174 第10章 引用和拷贝构造函数 176 10.1 C++中的指针 176 10.2 C++中的引用 176 10.2.1 函数中的引用 177 10.2.2 参数传递准则 178 10.3 拷贝构造函数 179 10.3.1 传值方式传递和返回 179 10.3.2 拷贝构造函数 182 10.3.3 缺省拷贝构造函数 187 10.3.4 拷贝构造函数方法的选择 188 10.4 指向成员的指针
C++编程思想 目 录 译者序 前言 第1章 对象的演化 1 1.1 基本概念 1 1.1.1 对象:特性+行为 1 1.1.2 继承:类型关系 1 1.1.3 多态性 2 1.1.4 操作概念:OOP程序像什么 3 1.2 为什么C++会成功 3 1.2.1 较好的C 3 1.2.2 采用渐进的学习方式 4 1.2.3 运行效率 4 1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺什么 10 1.3.6 方法应当提供什么 10 1.4 起草:最小的方法 12 1.4.1 前提 13 1.4.2 高概念 14 1.4.3 论述(treatment) 14 1.4.4 结构化 14 1.4.5 开发 16 1.4.6 重写 17 1.4.7 逻辑 17 1.5 其他方法 17 1.5.1 Booch 18 1.5.2 责任驱动的设计(RDD) 19 1.5.3 对象建模技术(OMT) 19 1.6 为向OOP转变而采取的策略 19 1.6.1 逐步进入OOP 19 1.6.2 管理障碍 20 1.7 小结 21 第2章 数据抽象 22 2.1 声明与定义 22 2.2 一个袖珍C库 23 2.3 放在一起:项目创建工具 29 2.4 什么是非正常 29 2.5 基本对象 30 2.6 什么是对象 34 2.7 抽象数据类型 35 2.8 对象细节 35 2.9 头文件形式 36 2.10 嵌套结构 37 2.11 小结 41 2.12 练习 41 第3章 隐藏实现 42 3.1 设置限制 42 3.2 C++的存取控制 42 3.3 友元 44 3.3.1 嵌套友元 45 3.3.2 它是纯的吗 48 3.4 对象布局 48 3.5 类 48 3.5.1 用存取控制来修改stash 50 3.5.2 用存取控制来修改stack 51 3.6 句柄类(handle classes) 51 3.6.1 可见的实现部分 51 3.6.2 减少重复编译 52 3.7 小结 54 3.8 练习 54 第4章 初始化与清除 55 4.1 用构造函数确保初始化 55 4.2 用析构函数确保清除 56 4.3 清除定义块 58 4.3.1 for循环 59 4.3.2 空间分配 60 4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 70 5.1.2 安全类型连接 70 5.2 重载的例子 71 5.3 缺省参数 74 5.4 小结 81 5.5 练习 82 第6章 输入输出流介绍 83 6.1 为什么要用输入输出流 83 6.2 解决输入输出流问题 86 6.2.1 预先了解操作符重载 86 6.2.2 插入符与提取符 87 6.2.3 通常用法 88 6.2.4 面向行的输入 90 6.3 文件输入输出流 91 6.4 输入输出流缓冲 93 6.5 在输入输出流中查找 94 6.6 strstreams 96 6.6.1 为用户分配的存储 96 6.6.2 自动存储分配 98 6.7 输出流格式化 100 6.7.1 内部格式化数据 101 6.7.2 例子 102 6.8 格式化操纵算子 106 6.9 建立操纵算子 108 6.10 输入输出流实例 111 6.10.1 代码生成 111 6.10.2 一个简单的数据记录 117 6.11 小结 123 6.12 练习 123 第7章 常量 124 7.1 值替代 124 7.1.1 头文件里的const 124 7.1.2 const的安全性 125 7.1.3 集合 126 7.1.4 与C语言的区别 126 7.2 指针 127 7.2.1 指向const的指针 127 7.2.2 const指针 127 7.2.3 赋值和类型检查 128 7.3 函数参数和返回值 128 7.3.1 传递const值 128 7.3.2 返回const值 129 7.3.3 传递和返回地址 131 7.4 类 133 7.4.1 类里的const和enum 133 7.4.2 编译期间类里的常量 134 7.4.3 const对象和成员函数 136 7.4.4 只读存储能力 139 7.5 可变的(volatile) 140 7.6 小结 141 7.7 练习 141 第8章 内联函数 142 8.1 预处理器的缺陷 142 8.2 内联函数 144 8.2.1 类内部的内联函数 145 8.2.2 存取函数 146 8.3 内联函数和编译器 150 8.3.1 局限性 150 8.3.2 赋值顺序 150 8.3.3 在构造函数和析构函数里隐藏行为 151 8.4 减少混乱 152 8.5 预处理器的特点 153 8.6 改进的错误检查 154 8.7 小结 155 8.8 练习 155 第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 C++中的静态成员 166 9.3.1 定义静态数据成员的存储 166 9.3.2 嵌套类和局部类 168 9.3.3 静态成员函数 169 9.4 静态初始化的依赖因素 171 9.5 转换连接指定 174 9.6 小结 174 9.7 练习 174 第10章 引用和拷贝构造函数 176 10.1 C++中的指针 176 10.2 C++中的引用 176 10.2.1 函数中的引用 177 10.2.2 参数传递准则 178 10.3 拷贝构造函数 179 10.3.1 传值方式传递和返回 179 10.3.2 拷贝构造函数 182 10.3.3 缺省拷贝构造函数 187 10.3.4 拷贝构造函数方法的选择 188 10.4 指向成员的指针 ......

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值