c++基础学习:析构函数&拷贝构造函数

本文深入解析C++中拷贝构造函数与析构函数的使用,包括字符串函数、拷贝构造函数的定义与作用、深浅拷贝的区别、析构函数的特点与必要性,以及具体实例展示。

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

拷贝构造函数与析构函数

字符串函数

  • 当构造函数参数是数组名时,需要用strcpy()函数给数组成员数组赋值。
string类是c++提供的字符串类,其主要功能是对字符串进行操作。string类定义的变量称为字符串对象,该对象可以直接用字符串常量赋值,也可调用string类中定义的成员函数。
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值不能被更新。c++ 中引入const的目的是为了取代宏这个预编译指令,消除它不能进行语法检查的缺点,同时继承宏替换的优点。c++中定义const常量具有不可变性。const可以保护被修饰的东西。防止意外的修改,增强程序的健壮性。

拷贝构造函数

  • 用一个对象创建并初始化另一个对象。
Date a1(2012, 1, 23);
Date a2(a1);

特点:

  1. 拷贝构造函数名字与类同名,没有返回类型;
  2. 拷贝构造函数只有一个形参,该参数是该类的对象的引用;
    如果一个类中没有定义拷贝构造函数,则系统自动生成一个缺省拷贝构造函数,其功能通常是将已知对象的所有数据成员的值拷贝给对应对象的数据成员。
定义
拷贝构造函数的格式如下:
      <类名>::<拷贝构造函数名>(<类名>&<引用名>)
       {<函数体>}
      其中, <拷贝构造函数名>与该类名相同;

好处:

  1. 拷贝构造函数用于使用已知对象的值创建一个同类的新对象。
  2. 把对象作为实参进行函数调用时,系统自动调用拷贝构造函数实现把对象值传递给形参对象;
  3. 函数的返回值为对象时,系统自动调用拷贝构造函数对返回对象值创建一个临时对象,然后再将这个临时对象值赋给接收函数返回值的对象。
  4. 拷贝构造函数需要定义的情况是数据成员是指针
  • 拷贝构造函数实例:
class CStuScore
{
public:
    void show()
    {
        cout<<strName<<"的平均成绩为:"<<GetAverage()<<endl;
    }
    CStuScore(char *pName, int no, float s1, float s2, float s3)
    {
        strName = pName;
        iStuNo  = no;
        fScore[0] = s1;
        fScore[1] = s2;
        fScore[2] = s3;
    }
private:
    float fScore[3];
    char *strName;
    int iStuNo;
    float GetAverage();
};

float CStuScore::GetAverage() //计算均值
{
    return (float)((fScore[0] + fScore[1] + fScore[2]) / 3.0);
}


int main()
{
    CStuScore one("xiaoming", 121212, 89, 99, 78),two(one);
    one.show();
    two.show();
    return 0;
}

  • 缺省的拷贝构造函数,只是指针值的复制(浅拷贝),即one ,two对象的指针成员指向同一个内存空间的问题。
分析

1. strName=pName;使数据成员指向外部c数组(例如:main函数)地址,也会被改,无法封装

int main()
{
CStuScore oOne("LiMing",21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  return 0;
}

int main()
{
  char c[10]="LiMing";
  CStuScore oOne(c,21020501,80,90,65),b(oOne); 
  oOne.Show();  
  b.Show();
  strcpy(c,"wangbo"); //修改数组c的内容 
  oOne.Show();    
  return 0;
}

在这里插入图片描述


  1. strName=pName;使数据成员指向外部c数组(例如:main函数)地址,c被释放,strName成为野指针
int main()
{
    char *c = new char[10];
    strcpy(c, "xiaoming");
    CStuScore one(c, 22132, 89, 90, 91), two(one);
    one.show();
    two.show();
    delete []c;       //释放动态数组c
    one.show();   //发生内存错误
    two.show();
    return 0;
}

析构函数

不是必须的,通常默认析构函数即可(无惨,函数体为空)

  • 必须定义析构函数的情况:
  1. 构造函数打开一个文件,使用完文件时,不要关闭文件。
  2. 从堆中分配了动态内存区,在对象消失之前必须释放。

特点:

  • 无返回类型
  • 无参数,因此不存在析构函数重载,只有一个析构函数。在对象释放时由系统自动调用。
  • 如果程序中未声明,则系统自动产生出一个缺省形式的析构函数。
  • 析构函数与构造函数的功能相对应,所以析构函数名时构造函数名前加一个逻辑反运算符“~”
  • 析构函数以调用构造函数相反的顺序被调用。
析构函数实例

该类定义的构造函数在对象之外分配一段堆内存空间,撤销时,由析构函数收回堆内存。通过动态数组申请和释放,解决了指针成员指向外部(main函数)地址的问题。

#include <iostream>
#include <cstring>

using namespace std;

class CStuScore
{
public:
    void show()
    {
        cout<<strName<<"的平均成绩为:"<<GetAverage()<<endl;
    }
    CStuScore(char *pName, int no, float s1, float s2, float s3)
    {
        strName = new char[12];  //动态数组申请
        strcpy(strName, pName);
        iStuNo  = no;
        fScore[0] = s1;
        fScore[1] = s2;
        fScore[2] = s3;
    }
    ~CStuScore()
    {
        delete []strName;    //动态数组释放
    }
private:
    float fScore[3];
    char *strName;
    int iStuNo;
    float GetAverage();
};

float CStuScore::GetAverage() //计算均值
{
    return (float)((fScore[0] + fScore[1] + fScore[2]) / 3.0);
}

int main()
{
    CStuScore one("xiaoming", 23213, 89, 23, 34);
    one.show();
    return 0;
}

在这里插入图片描述


拷贝构造函数的深浅拷贝问题

造函数的深浅拷贝问题

strName=r.strName;
//浅拷贝:对逐个成员的指针值的依次复制
//存在2个或2个以上对象姓名成员指向同一个内存空间的问题(即2个或2个以上对象共用一个姓名),如果释放其中一个对象,将导致另外的对象可能没有地方存姓名。这就发生内存错误。

CStuScore(CStuScore &r){strName= new char[12];
strcpy(strName,r.strName);}
//深拷贝
//重新定义拷贝构造函数。学生类中姓名是一个指针,指向各自由new创建存放姓名字符串的内存空间首地址。

构造函数与析构函数实例
#include <iostream>
using namespace std;
class point
{
public:
    point(int xp,int yp)
	{ x=xp; y=yp; }
    point(point& p); 
    ~point() {cout<<"析构函数被调用"<<endl;}
    int getx() {return x;}
    int gety() {return y;}
private:
        int x,y;
};
point::point(point& p)
{   x=p.x;   y=p.y;
    cout<<"拷贝构造函数被调用"<<endl;
}
point fun(point q);//函数声明
//不要在返回指向局部变量或者临时对象的引用。
函数执行完毕之后,局部变量和临时对象将消失,
引用将指向不存在的数据。

int main()
{
    point M(12,20),P(0,0),S(0,0);//调用3次带参数的构造函数创建对象M、P、S
    point N(M);//调用拷贝构造函数创建对象N
    P=fun(N);//函数调用完成,调用析构函数2次,释放局部对象R以及形参对象q
    S=M;
    cout<<"P="<<P.getx()<<","<<P.gety()<<endl; 
    cout<<"S="<<S.getx()<<","<<S.gety()<<endl;
    return 0;
}//程序结束前调用4次析构函数,释放对象N、S、P、M
point fun(point q)//形参是对象,参数传递时调用拷贝构造函数创建对象q
{
    cout<<"OK"<<endl;
    int x=q.getx()+10;
    int y=q.gety()+15;
    point R(x,y);//创建对象R,调用带参数的构造函数
    return R;
}


运行结果:
拷贝构造函数被调用
拷贝构造函数被调用
OK
析构函数被调用
析构函数被调用
P=22,35
S=12,20
析构函数被调用
析构函数被调用
析构函数被调用
析构函数被调用

注意,析构函数以调用构造函数相反的顺序被调用。

参考:https://blog.youkuaiyun.com/PattyAndSmith/article/details/84330773

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值