C++类和对象——(对象的赋值拷贝构造函数)

2023-10-30

目录

对象的赋值

目录

对象的赋值

1、提出问题:

2、解决办法:

拷贝构造函数

1、拷贝构造函数的原型:

2、调用机制:

3、使用例程代码

总代码工程:

对象的赋值

1、提出问题:

能否使用一个已经构造好的对象去初始化另一个对象,C++编译器又是如何处理这些操作的呐。

类似于:

A a(10);
A b = a;

先来运行一下下边这一段程序:

#include <iostream>

using namespace std;

class Test
{
	public:
		int *sum;
		int x;
		int y;

	Test()
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
		sum = new int[4];
	}

	Test(int a, int b):x(a), y(b)
	{
		cout << "Test(int a, int b)" << endl;
		sum = new int[4];
	}
};

int main()
{
	Test t1(10, 20); //Test t1 = (10, 20);
	t1.sum[0] = 1;
	t1.sum[1] = 2;
	t1.sum[2] = 3;
	t1.sum[3] = 4;
	Test t2 = t1;
	cout << t1.x << " " << t1.y << endl;
	cout << t2.x << " " << t2.y << endl;

	cout << t1.sum[2] << " " << t2.sum[2] << endl;
	cout << t1.sum << " " << t2.sum << endl;
	return 0;
}

打印结果如上图所示,对象t1和t2不止成员变量的值相同 ,地址也相同。也就是类似于两个对象名称共用一个内存空间。

这样的坏处是当只改变t2的成员变量值的时候相应t1的成员变量也会改变。

如果以下边一种写法:

#include <iostream>

using namespace std;

class Test
{
	public:
		int *sum;
		int x;
		int y;

	Test()
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
		sum = new int[4];
	}

	Test(int a, int b):x(a), y(b)
	{
		cout << "Test(int a, int b)" << endl;
		sum = new int[4];
	}

	~Test()
	{
		cout << "~Test" << endl;
		delete[] sum;
	}
};

int main()
{
	//通过new操作符 构造对象(申请空间)
	Test *t1 = new Test(10, 20);

	t1->sum[0] = 10;
	t1->sum[1] = 20;
	t1->sum[2] = 30;
	t1->sum[3] = 40;
 
	Test *t2 = t1;

	cout << t2->sum[1] << endl;
	delete t1;
	cout << t2->sum[1] << endl;
	//cout << t1->sum << " " << t2->sum << endl;
	return 0;
}

发现:在执行delete t1语句前和执行后t2->sum[1]的值是不一样的,因为在t1->sum和t2->sum指向的是同一块内存空间当执行delete t1语句的时候会调用析构函数,在析构函数中之前申请的sum空间被释放了,所以s2->sum[1]的值就和原来的不一样了。

由上边两个问题得知:

用一个构造好的对象使用=可以构造一个新的对象,而且新的对象中的成员变量的值和构造好的对象中的成员变量的值是相等的(实现对象的拷贝)。但是这样做带来的危害是:如果两个类中有成员变量是指针类型(对于其他普通成员变量不会这样{第二种代码也会修改普通成员变量的值}),一旦其中一个对象被销毁调用析构函数将指针变量指向的堆空间进行了释放,另外一个对象所操作的指针所执行的堆空间是非法的!!(也就是后边要讲的浅拷贝)。

那我们想通过使用一个已经构造好的对象去初始化另一个对象而且对于指针类型成员变量不收影响该怎么办呐。

2、解决办法:

法子1:直接分开赋值

Test t1(10, 20); //Test t1 = (10, 20);
t1.sum[0] = 1;
t1.sum[1] = 2;
t1.sum[2] = 3;
t1.sum[3] = 4;

Test t2;
//普通成员变量的赋值
t2.x = t1.x;
t2.y = t1.y;
//指针类成员变量的赋值使用下面的memcpy函数
memcpy(t1.sum, t2.sum, 10*sizeof(int));

法子2:引出拷贝构造函数

依然使用“=”

原因:当我们使用“=”使用一个构造好的对象初始化一个新的对象的时候时,没有实现拷贝函数,编译器自动生成拷贝函数调用

拷贝构造函数

1、拷贝构造函数的原型:

类名(const 类名 &变量名);//此处变量名相当于一个形参的变量名。

2、调用机制:

使用一个构造好的对象初始化一个新的对象。

3、使用例程代码

如下:

#include <iostream>

using namespace std;

class Test
{
	public:
		int *sum;
		int x;
		int y;

	Test()
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
		sum = new int[4];
	}

	Test(int a, int b):x(a), y(b)
	{
		cout << "Test(int a, int b)" << endl;
		sum = new int[4];
	}

	Test(const Test &t) //拷贝构造函数
	{
		cout << "Test(const Test &t)" << endl;
		//将t对象中的成员变量的值拷贝给新的对象
		x = t.x;
		y = t.y;
		sum = new int[4];//重新另给sum分配空间
		memcpy(sum, t.sum, 4*sizeof(int));//将数据拷贝过去
/*
        for(int i=0;i<4;i++)
            sum[i] = t.sum[i];//将数据拷贝过去
*/
	}

	~Test()
	{
		cout << "~Test" << endl;
		delete[] sum;
	}
};

int main()
{
	Test t1;
	t1.sum[0] = 1;
	t1.sum[1] = 2;
	t1.sum[2] = 3;
	t1.sum[3] = 4;

	Test t2 = t1;
	cout << t1.sum[2] << " " << t2.sum[2] << endl; // 3 3

	t1.sum[2] = 100;
	cout << t1.sum[2] << " " << t2.sum[2] << endl;// 100 3
	return 0;
}

注意:

1、拷贝构造函数当中:t 引用的是 = 右值  const:只读 防止t类内变量被修改(深拷贝)

拷贝构造函数为什么要这样写?

//-------引用作为函数的形参---------
//直接作为函数的形参
#if 0
void func2(Test t)//Test t = t1;触发拷贝构造函数的调用,而且func函数的调用并且函数结束t需要销毁
{

}
#endif
#if 1
void func2(const Test &t)
{

}
//等价与指针void func2(Test *t)
// 形参是个引用不会调用构造函数,
// 也不会引起中间对象(t)的产生和销毁,
// 使用const使其成为一个常量指针指向地址无法被修改
#endif

总代码工程

/*
 * 《对象的析构》
 * 定义:在C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
 * 析构函数没有参数也没有任何返回对象‘
 * 析构函数在对象自动销毁的时候自动被调用
 * 析构函数调用机制<<C++编译器自动调用
 * */
#include <iostream>
using namespace std;
#if 0
class A
{
public:
//    char name[20];
//    int age;
    int a1;
    int a2;
    int a3;
    //假如类中成员变量为指针
    int *p;
    A()//无参数构造函数
    {
//        p=(int *)malloc(40);//在堆上生成一块空间(C中常用在堆上创建空间的方法)
        cout << "A()." << endl;
    }
    A(int a,int b,int c):a1(10),a2(20),a3(30)
    {
        cout << "A(" << a1 << a2 << a3 << " ):a1(10),a2(20),a3(30)." << endl;
        cout << "a="<< a << endl;
        cout << "b="<< b << endl;
        cout << "c="<< c << endl;
    }
    //析构函数
    ~A()
    {
        //在A对象销毁的时候,我们需要手动释放堆上的空间,放在析构函数当中
//        free(p);//(C中常用释放堆空间的方法)
        cout << "~A()" <<endl;
    }
};
//C++中对象的动态申请和释放
/*
 * new和delete的基本用法
 *
 * new 运算符动态分配堆内存
 * 使用形式:指针变量 = new类型(常量);
 * eg:     int *p =new int(10);
 *explain:
 *         指针变量 = new类型[表达式];
 * 作用:从堆上分配一块《类型》大小的存储空间
 * 其中:“常量”是初始化值,可缺省
 * 创建数组对象时,不能为对象指定初始值
 *
 * delete 运算符释放已分配的内存空间
 * 使用形式:delete 指针变量;
 *         delete[]指针变量;
 * 其中指针变量必须是一个new返回的指针
 *\
 * new与delete和malloc与free的区别:
 * 注意:new和delete是运算符,而不是函数,因此执行效率高。
 * 虽然为了与C语言兼容,C++仍保留malloc和free函数,
 * 但建议用户不用malloc和free函数,
 * 而用new和delete运算符。
 * new/delete 和 malloc/free有何取别呢?
 * 1、malloc/free为C的标准库函数,new、delete则为C++的操作运算符
 * 2、new能自动计算需要分配的内存空间,而malloc需要手工计算字节数
 * 3、new与delete直接带具体类型的指针,malloc和free返回void类型的指针。
 * 4、new类型是安全的,而malloc不是。例如int *p = new float[2];就会报错;
 * 而int *p = malloc(2 *sizeof(int))编译时编译器就无法指出错误来。
 * 5、new调用构造函数,malloc不能;delete调用析构函数,而free不能
 * 6、new/delete是操作符可以重载,malloc/free则不能
 * 总结:更安全,便利,能重载
 * */
//对象动态建立和释放
void Build_release(void)
{
    //在堆上申请一个int类型大小的空间(4Bytes),并将申请的堆空间内容初始化为10
    int *p = new int(10);
    delete p;
//在堆上申请4个int类型大小的空间(4*sizeof(int)==16Bytes) 4:数组的长度
    int *p2 = new int[4];
    for (int i = 0; i < 4; i++)
    {
        cout << *(p2 + i) << endl; //cout << p2[i] << endl;
    }
    delete[] p2;
//在堆上申请一个Box类型大小的空间
    A *p3 = new A; //调用无参的构造函数
    delete p3;
//在堆上申请四个Box类型大小的空间
    A *p4 = new A[4]; //调用无参的构造函数
    delete[] p4;
//在堆上申请一个Box类型大小的空间
    A *p5 = new A(10, 10, 10); //调用带参数的构造函数
    delete p5;


}

int main() {
    Build_release();
    return 0;
}
#endif

/*
 * 使用已构造的对象初始化新的对象
 * 1、可以使用初始化好的对象,初始化另一个对象
 * */
#if 1
class Test
{
public:
    int *sum;
    int x;
    int y;

    Test()
    {
        cout << "Test()" << endl;
        x=0;
        y=0;
        sum = new int[4];
    }

    Test(int a,int b):x(a),y(b)
    {
        cout << "Test(int a,int b):x(a),y(b)" << endl;
        sum = new int[4];
    }

    //拷贝构造函数
    /*
     * 1、拷贝构造函数的原型
     * 类名(const 类名 &变量名)
     * 2、拷贝构造函数的调用时机:使用一个构造好的对象初始化一个新的对象
     * 深拷贝和浅拷贝的应用:当类的变量中含有指针类型的时候必须使用深拷贝,
     * */
    Test(const Test &t)//t 引用的是 右值  const:只读 防止t类内变量被修改(深拷贝)
    {
        cout << "Test(const Test &t)" << endl;
        x = t.x;
        y = t.y;
        sum = new int(4);//重新给sum分配空间
        for(int i=0;i<4;i++)
            sum[i] = t.sum[i];
        }

    //析构函数释放申请的空间
    ~Test()
    {
        cout << "~Test()" << endl;
        delete[] sum;
    }
};

//无构造函数
void func(void)
{
    Test *t1 = new Test(10,20);//调用构造函数:Test(int a,int b):x(a),y(b)

    t1->sum[0]=100;
    t1->sum[1]=101;
    t1->sum[2]=102;
    t1->sum[3]=103;

    Test t2 = *t1;//没有实现拷贝函数,编译器自动生成拷贝函数调用
    cout << t2.sum[0] <<endl;
    cout << t2.sum[1] <<endl;
    cout << t2.sum[2] <<endl;
    cout << t2.sum[3] <<endl;

    cout << "t2.x="<<t2.x<<endl;
    cout << "t2.y="<<t2.y<<endl;

    cout << "t1.sum="<<t1->sum<<endl;//两个对象的指针指向同一个内存空间
    cout << "t2.sum="<<t2.sum<<endl;

    //手动释放t1所指的申请的堆上的空间,意味着销毁 *t1这个对象,会自动调用析构函数
    //顺带手动释放成员变量sum所申请的空间
    delete t1;

    cout << t2.sum[0] <<endl;
    cout << t2.sum[1] <<endl;
    cout << t2.sum[2] <<endl;
    cout << t2.sum[3] <<endl;
    cout << "t2.sum="<<t2.sum<<endl;
    /*
     * 如果在类里边用一个对象给另一个对象赋值的时候如果一个对象为指针的话,那就回出问题
     * */
    //同一块堆空间将被释放两次会导致程序卡死
    //how to solve...
    //挨个赋值、
    // ==  /  memcpy(x,y,size);将y的值付给x空间大小为size
    //拷贝构造函数(自己实现)
    //

}

//有拷贝构造函数
void func1(void)
{
    Test t1(10,20);//调用构造函数:Test(int a,int b):x(a),y(b)
    t1.sum[0]=100;
    t1.sum[1]=101;
    t1.sum[2]=102;
    t1.sum[3]=103;
//使用已经构造好对象的t1 初始化一个对象
    Test t2 = t1;//调用拷贝构造函数 t2.Test(t1)
    cout << "t2.sum="<<t2.sum<<endl;
    cout << "t1.sum="<<t1.sum<<endl;//在拷贝构造函数当中重新给sum分配空间使

    for(int i = 0;i<4;i++)
        cout << t2.sum[i] << endl;

}

//-------引用作为函数的形参---------
//直接作为函数的形参
#if 0
void func2(Test t)//Test t = t1;触发拷贝构造函数的调用,而且func函数的调用并且函数结束t需要销毁
{

}
#endif
#if 1
void func2(const Test &t)
{

}
//等价与指针void func2(Test *t)
// 形参是个引用不会调用构造函数,
// 也不会引起中间对象(t)的产生和销毁,
// 使用const使其成为一个常量指针指向地址无法被修改
#endif

int main(){

    Test t1(10,20);//调用构造函数:Test(int a,int b):x(a),y(b)
    t1.sum[0]=100;
    t1.sum[1]=101;
    t1.sum[2]=102;
    t1.sum[3]=103;
/*
    //使用已经构造好的对象t1,初始化一个新的对象
    Test t2 = t1;
    cout << "t2.x="<<t2.x<<endl;
    cout << "t2.y="<<t2.y<<endl;

    cout << "t2.sum="<<t2.sum<<endl;
    cout << "t1.sum="<<t1.sum<<endl;//两个对象的指针指向同一个内存空间
    //注意:
*/

//    func();
//    cout << "func():No illness" << endl;


//    func1();
//    cout << "func1():Copied function" << endl;

    func2(t1);//现象调用拷贝构造函数



    return 0;
}
#endif

#if 0

#include <iostream>

using namespace std;

class Test
{
public:
    int *sum;
    int x;
    int y;

    Test()
    {
        cout << "Test()" << endl;
        x = 6;
        y = 0;
        sum = new int[4];
    }

    Test(int a, int b):x(a), y(b)
    {
        cout << "Test(int a, int b)" << endl;
        sum = new int[4];
    }
};

int main()
{
    Test t1(10, 20); //Test t1 = (10, 20);
    t1.sum[0] = 1;
    t1.sum[1] = 2;
    t1.sum[2] = 3;
    t1.sum[3] = 4;
    Test *t2 = &t1;
    cout << t1.x << " " << t2->x << endl;
    cout << t1.y << " " << t2->y << endl;

    cout << t1.sum[2] << " " << t2->sum[2] << endl;
    cout << t1.sum << " " << t2->sum << endl;
    t2->x=111;
    cout << t1.x << " " << t2->x << endl;

    return 0;
}

#endif

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++类和对象——(对象的赋值拷贝构造函数) 的相关文章

随机推荐

  • spark学习2:spark运行基本架构

    各个名词介绍 1 RDD 弹性分布式数据集 2 DAG 有向无环图 反应各RDD之间关系 即把第一个RDD和最后一个RDD 串联起来的关系图 根据DAG 能够找到每个RDD的父RDD 3 executor executor是 驻守在各个工作
  • 三维重建可以考虑光线跟踪的方法

    如题
  • LeetCode 之 剑指 Offer 12. 矩阵中的路径(Java)

    文章目录 LeetCode 之 剑指 Offer 12 矩阵中的路径 Java 一 题目 二 解题思路 三 代码 LeetCode 之 剑指 Offer 12 矩阵中的路径 Java 一 题目 剑指 Offer 12 矩阵中的路径 给定一个
  • Nacos搭建集群

    复制三份Nacos服务端文件 配置相同的数据库 参考前面nacos上线文档 修改每一个的application properties文件内的 server port 然后在每个conf文件夹内新增 cluster conf文件 文件内容为
  • python写ecxel。多个条件对Excel中的数据进行筛选并处理

    在第很多行中 D列不空 C列不空 那么A列B列E列F列不空 D列空 C列不空 那么A列B列E列F列空 import pandas as pd def process data filename 读取Excel文件 df pd read ex
  • Android 反编译三种方式

    首先反编译别人的APK 主要是学习别人的实现逻辑 给自己一个思路 比较商业的APK也不会给你留下这个漏洞 一些核心的业务处理会在后台进行操作 本地的 APK 也会进行混淆加密等 好了废话不多说 步入正题 方法一 在线反编译 很简单方便的一个
  • 思科路由器配置

    1 路由表 show IP route 用户模式 特权模式 enable 全局配置模式 configure terminal config 配置主机名称 enable configure terminal config hostname t
  • RuntimeError: cuDNN error: CUDNN_STATUS_INTERNAL_ERROR

    torch backends cudnn benchmark True 加在开头
  • MySQL基础学习(四)——SQL语句之DML语句

    前言 上一篇博客介绍了一下SQL语句中的DDL和DCL语句 主要涉及通过SQL赋予或者撤回相关的用户权限 创建数据表 创建索引等内容 这一篇博客开始总结DML语句 就是我们开发中常用的一些select insert update delet
  • C#常见编程

    1 编写一段程序 运行时向用户提问 你考了多少分 0 100 接受输入后判断其等级并显示出来 判断依据如下 等级 优 90 100分 良 80 89分 中 60 69分 差 0 59分 static void Main string arg
  • 合并两个有序链表(easy)

    将两个升序链表合并为一个新的 升序 链表并返回 新链表是通过拼接给定的两个链表的所有节点组成的 示例 1 输入 l1 1 2 4 l2 1 3 4 输出 1 1 2 3 4 4 示例 2 输入 l1 l2 输出 示例 3 输入 l1 l2
  • 【Golang源码学习】chromedp篇

    GitHub https github com chromedp chromedp chromedp go RunResponse 官方注释 func RunResponse ctx context Context actions Acti
  • WKWebView之离线加载以及遇到的问题

    目录 前言 一 离线包是什么 二 方案调研 NSURLProtocol WKURLSchemeHandler 三 具体实施 1 离线包的分发 2 服务器对请求接口处理 3 客户端下载离线包 4 webview设置拦截 5 WKURLSche
  • Typcho反序列化漏洞分析

    Typcho反序列化漏洞分析 文章首发 https xz aliyun com t 9428 影响范围 2017年10月24日之前的所有版本 环境搭建 下载地址 http typecho org 这里主要是说下 在intall之前 需要我们
  • Linux Ubuntu搭建Git服务器

    之前介绍过如何在Windows上搭建Git仓库服务器 不过服务器用的比较多的还是Linux 因为便宜 同一个VPS商一般来说Linux比Windows便宜 没有图形界面 低配置VPS的也可以跑动Linux 开源免费 我感觉比较灵活 下载源也
  • 创建对象的五种方式

    1 使用new关键字 gt 调用构造函数 2 使用Class的newInstance方法 gt 调用构造函数 3 使用Constructor的newInstance方法 gt 调用构造函数 4 使用clone方法 gt 没有调用构造函数 5
  • Oracle中的触发器(trigger)

    1 触发器的定义 数据库触发器是一个与表相关联 存储PL SQL语句的 东西 每当一个特定的数据操作语句 insert update delete 在指定的表上发出时 Oracle自动执行触发器中定义的语句序列 例如 当员工信息插入后 自动
  • java基于winbox 工具下使用 api获取映射表api数据

    Winbox 是基于 windows下远程管理 ROS的软件 提供直观方便的图形界面 用它能登陆路由器 这个路由器是软路由ROUTEOS制作的 用Winbox登陆后 就可以配置路由器了 用这个软件便于配置路由器 Winbox控制台使用TCP
  • 如何利用今日头条极速版挣点小钱

    红包 1元现金速撸 红包 下载 今日头条极速版 进入 任务 填邀请码 1386552161 即可立即提现1元到支付宝 每天阅读 睡觉 签到 走路都有钱领
  • C++类和对象——(对象的赋值拷贝构造函数)

    目录 对象的赋值 目录 对象的赋值 1 提出问题 2 解决办法 拷贝构造函数 1 拷贝构造函数的原型 2 调用机制 3 使用例程代码 总代码工程 对象的赋值 1 提出问题 能否使用一个已经构造好的对象去初始化另一个对象 C 编译器又是如何处