C++:关于复制构造函数与赋值运算符浅复制导致的问题解释

        如果类中包含了使用new初始化的指针成员,应当定义复制构造函数和重载赋值运算符,以复制的数据,而不是让多个对象的指针成员指向同一片空间(被称为浅复制)。浅复制会导致指针指向已被释放的空间,出现悬空指针。

        复制构造函数用于初始化过程,重载的赋值运算符用于常规赋值语句中。

        下面举一个实例来做有关说明,该实例声明并定义一个stringbad类(表示有问题的string)。

//stringbad.h
#include<iostream>
#ifndef STRNGBAD_H
#define STRNGBAD_H
class stringbad{
private:
    char*str;
    int len;
    static int num_strings;  //静态类成员,用于计数
public:
    stringbad(const char*s);
    stringbad();
    ~stringbad();
    friend std::ostream & operator<<(std::ostream&os,const stringbad&st);
};
#endif

        声明中静态类成员不可在声明中初始化(除非使用const限定),静态类成员归属于类,而不属于具体的对象。下面是成员函数定义

//string.cpp
#include<cstring>
#include"stringbad.h"
using std::cout;

int stringbad::num_strings=0;   //外部初始化静态成员为0

stringbad::stringbad(const char*s){     //构造函数
    len=std::strlen(s);
    str=new char[len+1];
    strcpy(str,s);
    num_strings++;
    cout<<num_strings<<":\""<<str<<"\"对象被创建\n";
}
stringbad::stringbad(){         //默认构造函数
    len=4;
    str=new char[len+1];
    strcpy(str,"C++");
    num_strings++;
    cout<<num_strings<<":\""<<str<<"\"对象创建失败\n";
}
stringbad::~stringbad(){
    cout<<"\""<<str<<"\"被删除";
    --num_strings;
    cout<<num_strings<<"left\n";
    delete [] str;      //释放new分配的空间
}

std::ostream&operator<<(std::ostream & os,const stringbad&st){      //重载运算符便于直接用<<打印对象存储的字符串
    os<<st.str;
    return os;
}

        下面是测试函数

//vegnews.cpp
#include<iostream>
#include<cstdlib>
using std::cout;
#include"stringbad.h"

void callme1(stringbad &);      //按引用传递
void callme2(stringbad);        //按值传递

int main(){
    using std::endl;
    {                           //析构函数在作用域结束时自动调用,若无该括号分隔,等main函数结束时看不见结果输出
        cout<<"首先开始初始化三个对象"<<endl;
        stringbad h1("CSM");
        stringbad h2("LP");
        stringbad sp("SLBD");
        cout<<"h1:"<<h1<<endl;
        cout<<"h2:"<<h2<<endl;
        cout<<"sp:"<<sp<<endl;
        //接下来调用callme函数,比较两种参数传递方式的不同
        callme1(h1);
        cout<<"h1:"<<h1<<endl;
        callme2(h2);
        cout<<"h2:"<<h2<<endl;
        cout<<"通过赋值初始化对象"<<endl;
        stringbad sla=sp;
        cout<<"sla:"<<sla<<endl;
        cout<<"把对象赋给对象(非初始化)"<<endl;
        stringbad kn;
        kn=h1;
        cout<<"kn:"<<kn<<endl;
        cout<<"退出代码块"<<endl;
    }
    cout<<"main函数结束"<<endl;
    getchar();

    return 0;
}

void callme1(stringbad& rsb){
    cout<<"stringbad对象按引用传递:\n";
    cout<<" \""<<rsb<<"\"\n";
}
void callme2(stringbad sb){
    cout<<"stringbad对象按值传递\n";
    cout<<" \""<<sb<<"\"\n";
}

        下面是运行结果

异常1 

                这里string对象按值传递,这里复制的并非字符串,而是指向字符串的指针。所以当h2按值传递给sb时,sb对象char*指针也指向h2对象指针指向的字符串。callme2函数结束,自动调用析构函数删除sb对象,指向字符串空间被释放,h2的char*指针变为悬空指针。 

                与此相区别的是按引用传递,函数操作的是对象本身,而非副本。所以在callme1函数运行结束时不会调用析构函数。

异常2

 

                这里我们把h1赋给kn,两个char*指针指向同一片new分配的空间。最后退出代码块时,用new分配了四次空间,却调用一共六次析构函数(我的运行结果有异常,最后main函数结束的语句未出现,实际最后应有5次调用),num_strings值为-2,也有空间被反复释放,可能导致异常退出。

问题解决

        解决该异常的方法是进行深度复制,即复制应把字符串副本赋给对象而非指针。

复制构造函数
stringbad::stringbad(const stringbad&st){
    num_strings++;
    len=st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    cout<<num_strings<<":\""<<str<<"\"对象被创建\n";
}
赋值运算符重载
stringbad&stringbad::operator=(const stringbad&st){
    if(this==&st)
        return *this;//避免自己给自己赋值导致new多余空间
    delete [] str;  //删除原本分配空间
    len=st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    return *this;
}

        修改后运行结果正确,如下

        因为对象在栈中存储,满足先入后出,所以一次释放kn,sla,sp,h2,h1. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值