啃书《C++ Primer Plus》 面向对象部分 深拷贝与浅拷贝问题 拷贝构造函数 赋值函数

本文详细探讨了C++中拷贝构造函数和赋值函数的概念,解释了默认拷贝构造函数和赋值函数的行为,特别是关于深拷贝和浅拷贝的区别。通过实例展示了如何自定义拷贝构造函数和赋值函数以满足特定需求,防止自赋值行为,并解决了引用类型成员带来的问题。此外,文章强调了禁止对象拷贝的情况及其影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

干货长文预警


这一篇,我们来解决对象的赋值与拷贝问题。赋值与拷贝,是程序设计过程中不可或缺的部分,对于基础类型,这两个概念相信大家早已熟知,这里不多赘述。
但是对于自定义类型的对象,拷贝与赋值就有这不同的含义:

拷贝发生在一个对象诞生的时候,基于另一个同类对象进行构造
而赋值则是将一个已存在的对象的内容传递给另一个已存在的对象。

虽然编译器提供了默认的拷贝与赋值函数,又由于对象的数据成员在被赋值或是拷贝时会有着诸如指针指向新内存这些默认函数不能实现的不同行为需求,因此赋值与拷贝操作有时需要重新定义其行为,以满足程序的具体要求。这也就引出了本篇的第二个关键问题:深拷贝与浅拷贝

本文思维导图:
在这里插入图片描述


拷贝构造函数

根据名称,不难知道拷贝构造函数是一种特殊的构造函数,它的任务就是用来拷贝对象。

默认拷贝构造函数

拷贝构造函数享有编译器赋予默认添加的vip特权。当程序中没有拷贝构造函数时,编译器会自动提供一个缺省的拷贝构造函数。这个拷贝构造函数的访问级别是公有的。

在进行拷贝时,默认的拷贝构造函数的行为是固定的,它有两种行为:

  • 普通成员(内置类型,指针等)按位拷贝。
  • 对象成员 执行其拷贝构造函数

对于普通成员,程序将按位进行复制进行拷贝,执行“浅复制”
对于对象成员,程序将调用它的拷贝构造函数进行复制,这个成员的复制的具体行为由其拷贝构造函数决定

对于对象成员拷贝的解读,来看个粟子:

class A{
   
   
public:
    A(){
   
   }
    A(const A& a){
   
   cout << "A类拷贝构造函数" << endl << endl;}
};
class B
{
   
   
public:
    B(){
   
   }
    A a;	//对象成员,在默认拷贝构造函数中将被调用其拷贝构造函数
};
int main()
{
   
   
    B b;
    B c = b; //产生拷贝情况
}

运行结果:
在这里插入图片描述
表示在拷贝B类型对象过程中调用到了A类型拷贝构造函数

另外,我们还漏掉了一类情况:引用类型!!
将上述代码稍作修改,用来观察引用类型成员在默认拷贝构造函数中的行为:

class A{
   
   
public:
    A(){
   
   }
    A(const A& a){
   
   cout << "A类拷贝构造函数" << endl << endl;}
};
class B
{
   
   
public:
    B():aa(*new A()){
   
   }	//引用类型必须进行初始化!!!
    A a;	//对象成员,在默认拷贝构造函数中将被调用其拷贝构造函数
    A& aa;
};
int main()
{
   
   
    B b;
    B c = b; //产生拷贝情况
    cout << "对象成员 " << &b.a << " " << &c.a << endl;
    cout << "引用成员 " << &b.aa << " " << &c.aa << endl;
}

在主函数中打印两个对象的成员地址,用以区分其是否为同一对象。同时,该代码保留了上一份代码对A类拷贝构造函数的提示功能。其运行结果如下:
在这里插入图片描述
不难发现的是,对于引用型的成员,初始化拷贝时直接引用了被拷贝对象的引用,并没有创建新的引用。
考虑到引用在底层是使用指针实现的,相当于一个 A * const类型指针,所以引用成员的拷贝行为就也可以看作是按位复制的结果,仍然是一种“浅拷贝”。

自定义拷贝构造函数

当默认的拷贝构造函数不能满足程序设计需求时,就需要程序员自己来实现拷贝构造函数了。
实现自定义的拷贝构造函数的第一步,就理解拷贝构造函数的书写形式。

拷贝构造函数的形式

重点放在前面
拷贝构造函数声明形式如下:

类名(cosnt 类名& );

实现形式如下:

类名 (const 类名& 引用对象名):初始化列表{构造函数方法体}

作为一种特殊的构造函数,拷贝构造函数具有与构造函数相同的特点。但是不同于一般构造函数的是,拷贝构造函数的

<think>嗯,用户的问题是关于用C++实现一个计算剩余完整书籍数量的算法,涉及到老鼠啃书的情况。首先,我需要明确问题的具体要求。题目中提到“老鼠每小时啃书”,可能是指老鼠每小时会啃食一定数量的书籍,需要计算经过一定时间后剩余的完整书籍数量。不过用户提供的输入输出样例没有具体给出,所以需要根据常见的类似问题进行推测。 首先,可能的输入包括初始书籍数量、老鼠每小时啃食的数量以及经过的时间。输出则是剩余的完整书籍数量。例如,输入可能是初始书籍N,每小时啃食量K,时间H小时,那么剩余量应该是N - K*H,但不能小于零。所以剩余书籍是max(N - K*H, 0)。这看起来像是一个简单的数学问题,但需要处理负数的情况。 接下来,我需要考虑如何用C++实现这个算法。首先,读取输入数据,可能是三个整数:N(初始数量)、K(每小时啃食量)、H(小时数)。然后计算剩余数量,注意结果不能为负数。使用std::max函数来处理这种情况。 然后,用户可能需要的不仅仅是代码,还有算法思路的讲解。需要分步骤说明,比如输入处理、计算公式、输出结果。同时,代码部分要符合C++语法,并且高效。例如,使用cin读取输入,cout输出结果,确保类型正确,避免溢出。 另外,用户提到的参考引用中有关于C++的内容,比如引用[4]提到泛型算法,但这个问题可能不需要复杂的算法,简单的算术运算即可。不过可能需要考虑边界情况,比如当K或H为零的情况,或者输入无效的情况,但用户可能希望保持简单,假设输入都是有效的正整数。 测试样例方面,假设输入是比如100本书,每小时啃5本,经过20小时,那么剩余是100 -5*20=0。另一个例子,比如初始50,每小时3,经过10小时,剩余50-30=20。如果时间过长,比如初始10,每小时4,经过3小时,则剩余10-12=负数,所以取0。 最后,生成相关问题需要考虑算法优化、应用场景、边界条件处理等。例如,如何处理大数溢出,或者动态变化的老鼠啃食率等扩展问题。</think>### C++实现计算剩余完整书籍数量的算法 #### 问题分析 假设老鼠每小时啃食固定数量的书籍,给定初始书籍数量、啃食速率和时间,计算剩余完整书籍数量。公式为: $$剩余量 = \max(初始数量 - 每小时啃食量 \times 时间, 0)$$ #### 实现步骤 1. **输入处理**:读取初始书籍数$N$、每小时啃食量$K$、时间$H$ 2. **计算剩余量**:应用公式避免负数结果 3. **输出结果** #### 示例代码 ```cpp #include <iostream> #include <algorithm> // 用于max函数 int main() { int N, K, H; std::cin >> N >> K >> H; int remaining = std::max(N - K * H, 0); std::cout << remaining << std::endl; return 0; } ``` #### 输入输出样例 输入: ``` 100 5 20 // 初始100本,每小时啃5本,持续20小时 ``` 输出: ``` 0 ``` 输入: ``` 50 3 10 ``` 输出: ``` 20 ``` #### 算法优化 1. **溢出处理**:当$K \times H$超过int范围时,使用`long long`类型 2. **输入验证**:添加负数输入的检测逻辑 3. **动态速率**:如果啃食速率随时间变化,需使用积分计算[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值