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

news2024/12/23 11:09:17

目录

对象的赋值

目录

对象的赋值

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/708863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java——《面试题——网络篇》

前文 java——《面试题——基础篇》 Java——《面试题——JVM篇》 Java——《面试题——多线程&并发篇》 Java——《面试题——Spring篇》 Java——《面试题——SpringBoot篇》 Java——《面试题——MySQL篇》​​​​​​ Java——《面试题——SpringCloud》 Java…

Java——《面试题——设计模式篇》

前文 java——《面试题——基础篇》 Java——《面试题——JVM篇》 Java——《面试题——多线程&并发篇》 Java——《面试题——Spring篇》 Java——《面试题——SpringBoot篇》 Java——《面试题——MySQL篇》​​​​​​ Java——《面试题——SpringCloud》 Java…

QThread

QThread的执行从run()函数的执行开始; 在Qt中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作&#xff0c;从而让主界面能及时响应用户的请求操作。 QThread的使用 链接 work对象 1&#xff0c;继承Qobject&#xff1b; 2&#xff0c;dowork()&#xff1b; 使…

(stm32)Unix时间戳

时间戳计时系统和常用的年月日时分秒计时系统有很大差别 时间戳&#xff1a;只计算从1970年1月1日0点整为0秒&#xff0c;只用最基本的秒来计时&#xff0c;用不进位。 常用计时系统&#xff1a;60秒进位1分钟&#xff0c;60分钟进位1小时&#xff0c;24小时进位1天&#xff…

Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库

介绍 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库&#xff0c;基于 ECMA-376&#xff0c;ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式&a…

电子器件系列44:IGBT的内部电容

对两款igbt进行比较&#xff0c;确定两者是否可以互换 A: B: 一、 从电路图上分析&#xff0c;在启动的时候&#xff0c;电路会提供一个15v的电压&#xff0c;两者都能顺利导通&#xff0c;可以替换 二、 开关时间 从实际使用电路上分析&#xff0c;这些参数对于本电路的功能…

随机数检测(二)

随机数检测&#xff08;二&#xff09;- 扑克检测、重叠子序列、游程总数、游程分布 3.4 扑克检测方法3.5 重叠子序列检测方法3.6 游程总数检测3.7 游程分布检测 3.4 扑克检测方法 扑克检测方法如下图。 以下实现代码供参考。 def alterOffsetBit(m, offset, listSub:list)…

HoloLens2与Unity的Socket通信踩坑日记

最近想让Hololens中获取电脑中更新的数据&#xff0c;打算使用套接字的方式进行通信&#xff0c;明明在unity中模拟测试可以进行正常通信&#xff0c;但是将软件部署到HoloLens上通信总是失败&#xff0c;找了很久的原因&#xff0c;找了很久的原因&#xff0c;终于能够正常通信…

国产自研开源大数据管理平台DataSophon

【背景】 几天在朋友圈看到开源社区Datavane发布了一个新开源项目DataSophon&#xff1b;一个致力于快速实现部署、管理、监控以及自动化运维大数据云原生平台&#xff0c;帮助快速构建起稳定、高效、可弹性伸缩的大数据云原生平台&#xff1b;从介绍内容来看非常优秀&#xff…

事务管理-@TransActional

事务 概念&#xff1a;一组操作集合&#xff0c;是一个不可分割的单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 事务操作 开启事务&#xff08;一组操作开始前&#xff0c;开启事务&#xff09;&#xff1a;start transaction / begin;提交事务(操作全部成…

AutoSAR系列讲解(入门篇)4.8-BSW的OS功能

一、OS架构和术语解释 OS&#xff1a; 操作系统。就仅仅位于系统服务层&#xff0c;由于OS是需要直接操作硬件的一些功能的&#xff0c;所以系统服务层才会贯穿三层结构至达硬件。AutoSAR OS是基于OSEK OS开发出来的&#xff0c;OSEK也是早期ECU上的OS标准了&#xff0c;现在也…

matplotlib 区域填充

import matplotlib.pyplot as plt import numpy as npx np.linspace(0, 8 * np.pi, 1000) siny np.sin(x) cosy np.cos(x / 2) / 2 plt.plot(x, siny, cskyblue, labelsin-X) plt.plot(x, cosy, corangered, label1/2 cos 1/2x )# 填充颜色 plt.fill_between(x, cosy, siny,…

TiDB 多租户方案和原理

作者&#xff1a; li_zhenhuan 原文来源&#xff1a; https://tidb.net/blog/a55c1d14 使用多租户 有很多文章介绍技术上如何使用多租户&#xff0c;在这里简单说明&#xff1a; CREATE RESOURCE GROUP rg_oltp RU_PER_SEC4000; ALTER USER app1 RESOURCE GROUP "r…

java——Collection、Map 深度理解

Collection <类型>&#x1f349; List(Conllection的实现类) 可以重复&#xff0c;有顺序 可存放多个null值&#x1f95d; ArrayList 主选 &#x1f353; 结构&#xff1a;数组 特点&#xff1a;查找快&#xff0c;增删慢 线程不安全&#xff0c;效率高 Vector&#…

第十三章 原理篇:SWIN-transformer

累了&#xff0c;没写完 面试完了再来写 参考教程&#xff1a; swin-transformer/model.py 文章目录 概述transformer blockswindow_partionW-MSAwindow-reversePatch Merging 概述 在前面介绍了vision transformer的原理&#xff0c;加入transformer的结构后&#xff0c;这种…

HCIP(HCIA回顾)

OSI/RM 七层 应用层 表示层 会话层 传输层 区分不同的流量&#xff0c;定义传输方式。 端口号由16位二进制构成&#xff0c;范围为0~65535(其中0不作为传输层的端口使用)&#xff0c;所以真实取值范围为1~65535&#xff1b;其中&#xff0c;1~1023称为知名端口号。 1、可靠…

React Native 集成到iOS原有的项目上

1.官方说明地址&#xff1a; 集成到现有原生应用 2. 主要步骤说明 把 React Native 组件集成到 iOS 应用中有如下几个主要步骤&#xff1a; 配置好 React Native 依赖和项目结构。了解你要集成的 React Native 组件。使用 CocoaPods&#xff0c;把这些组件以依赖的形式加入到项…

【推式子 二项式定理】Wannafly挑战赛 B

B-求值2_Wannafly挑战赛17 (nowcoder.com) 题意&#xff1a; 思路&#xff1a; Code&#xff1a; #include <bits/stdc.h>#define int long longusing namespace std;const int mxn2e610; const int mxe2e510; const int Inf0x3f3f3f3f; const int mod998244353;int N;…

碳排放预测模型 | Python实现基于RF随机森林的碳排放预测模型

文章目录 效果一览文章概述研究内容源码设计参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于RF随机森林的碳排放预测模型 研究内容 碳排放被认为是全球变暖的最主要原因之一。 该项目旨在提供各国碳排放未来趋势的概述以及未来十年的全球趋势预测。 其方法是分析这…

sql统计某一字段不同状态的数量,时间戳转日期格式、按月统计使用

背景 1、在sql语句中统计一个字段的不同状态时&#xff0c;需要将每个状态的数量查出来&#xff0c;在进行统一输出&#xff0c;涉及表多次查询&#xff0c;下面用一个聚合函数的方式进行查询&#xff0c;比较方便&#xff0c;容易理解。 2、有时候数据表中的时间字段存储的是…