浅谈C++中对象的复制与对象之间的相互赋值
C++对象的复制
有时需要用到多个完全相同的对象,例如,同一型号的每一个产品从外表到内部属性都是一样的,如果要对每一个产品分别进行处理,就需要建立多个同样的对象,并要进行相同的初始化,用以前的办法定义对象(同时初始化)比较麻烦。此外,有时需要将对象在某一瞬时的状态保留下来。
C++提供了克隆对象的方法,来实现上述功能。这就是对象的复制机制。
用一个已有的对象快速地复制出多个完全相同的对象。如
1
|
Box box2(box1); |
其作用是用已有的对象box1去克隆出一个新对象box2。
其一般形式为:
1
|
类名 对象2(对象1); |
用对象1复制出对象2。
可以看到,它与定义对象的方式类似,但是括号中给出的参数不是一般的变量,而是对象。在建立对象时调用一个特殊的构造函数——复制构造函数(copy constructor)。这个函数的形式是这样的:
1
2
3
4
5
|
//The copy constructor definition. Box::Box( const Box& b) { height=b.height; width=b.width; length=b.length; } |
复制构造函数也是构造函数,但它只有一个参数,这个参数是本类的对象(不能是其他类的对象), 而且采用对象的引用的形式(一般约定加const声明,使参数值不能改变,以免在调用此函数时因不慎而使对象值被修改)。此复制构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。
复制对象的语句
1
|
Box box2(box1); |
这实际上也是建立对象的语句,建立一个新对象box2。由于在括号内给定的实参是对象,因此编译系统就调用复制构造函数(它的形参也是对象), 而不会去调用其他构造函数。实参box1的地址传递给形参b(b是box1的引用),因此执行复制构造函数的函数体时,将box1对象中各数据成员的值赋给box2中各数据成员。
如果用户自己未定义复制构造函数,则编译系统会自动提供一个默认的复制构造函数,其作用只是简单地复制类中每个数据成员。C++还提供另一种方便用户的复制形式,用赋值号代替括号,如
1
|
Box box2=box1; //用box1初始化box2 |
其一般形式为
1
|
类名 对象名1 = 对象名2; |
可以在一个语句中进行多个对象的复制。如
1
|
Box box2=box1,box3=box2; |
按box1来复制box2和box3。可以看出,这种形式与变量初始化语句类似,请与下面定义变量的语句作比较:
1
|
int a=4,b=a; |
这种形式看起来很直观,用起来很方便。但是其作用都是调用复制构造函数。
请注意对象的复制和对象的赋值在概念上和语法上的不同。对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。而对象的复制则是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。
可以对例子程序中的主函数作一些修改:
1
2
3
4
5
6
7
8
|
int main( ) { Box box1(15,30,25); //定义box1 cout<< "The volume of box1 is " <<box1.volume( )<<endl; Box box2=box1,box3=box2; //按box1来复制box2,box3 cout<< "The volume of box2 is " <<box2.volume( )<<endl; cout<< "The volume of box3 is " <<box3.volume( )<<endl; } |
执行完第3行后,3个对象的状态完全相同。
下面说一下普通构造函数和复制构造函数的区别。
1) 在形式上
类名(形参表列); //普通构造函数的声明,如Box(int h,int w,int len);
类名(类名& 对象名); //复制构造函数的声明,如Box(Box &b);
2) 在建立对象时,实参类型不同
系统会根据实参的类型决定调用普通构造函数或复制构造函数。如
1
2
|
Box box1(12,15,16); //实参为整数,调用普通构造函数 Box box2(box1); //实参是对象名,调用复制构造函数 |
3) 在什么情况下被调用
普通构造函数在程序中建立对象时被调用。复制构造函数在用已有对象复制一个新对象时被调用,在以下3种情况下需要克隆对象:
① 程序中需要新建立一个对象,并用另一个同类的对象对它初始化,如上面介绍的那样。
② 当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参完全相同的值。如
1
2
3
4
5
6
7
8
|
void fun(Box b) //形参是类的对象 { } int main( ) { Box box1(12,15,18); fun(box1); //实参是类的对象,调用函数时将复制一个新对象b return 0; } |
③ 函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如
1
2
3
4
5
6
7
8
9
10
|
Box f( ) //函数f的类型为Box类类型 { Box box1(12,15,18); return box1; //返回值是Box类的对象 } int main( ) { Box box2; //定义Box类的对象box2 box2=f( ); //调用f函数,返回Box类的临时对象,并将它赋值给box2 } |
以上几种调用复制构造函数都是由编译系统自动实现的,不必由用户自己去调用,读者只要知道在这些情况下需要调用复制构造函数就可以了。
C++对象之间相互赋值
如果对一个类定义了两个或多个对象,则这些同类的对象之间可以互相赋值,或者说,一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是指对象中所有数据成员的值。
对象之间的赋值也是通过赋值运算符“=”进行的。本来,赋值运算符“=”只能用来对单个的变量赋值,现在被扩展为两个同类对象之间的赋值,这是通过对赋值运算符的重载实现的。
实际这个过程是通过成员复制来完成的,即将一个对象的成员值一一复制给另一对象的对应成员。
对象赋值的一般形式为:
1
|
对象名1 = 对象名2; |
注意对象名1和对象名2必须属于同一个类。例如
1
2
|
Student stud1,stud2; //定义两个同类的对象 stud2=stud1; //将stud1赋给stud2 |
通过下面的例子可以了解怎样进行对象的赋值。
[例] 对象的赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <iostream> using namespace std; class Box { public : Box( int =10, int =10, int =10); //声明有默认参数的构造函数 int volume( ); private : int height; int width; int length; }; Box::Box( int h, int w, int len) { height=h; width=w; length=len; } int Box::volume( ) { return (height*width*length); //返回体积 } int main( ) { Box box1(15,30,25),box2; //定义两个对象box1和box2 cout<< "The volume of box1 is " <<box1.volume( )<<endl; box2=box1; //将box1的值赋给box2 cout<< "The volume of box2 is " <<box2.volume( )<<endl; return 0; } |
运行结果如下:
1
2
|
The volume of box1 is 11250 The volume of box2 is 11250 |
说明:
对象的赋值只对其中的数据成员赋值,而不对成员函数赋值。数据成员是占存储空间的,不同对象的数据成员占有不同的存储空间,赋值的过程是将一个对象的数据成员在存储空间的状态复制给另一对象的数据成员的存储空间。而不同对象的成员函数是同一个函数代码段,不需要、也无法对它们赋值。
类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果 ,答案如下:
问题本质是,如果某个类的对象a里面有动态申请的数据,当你把a直接复制给同一个类的对象b的时候,a中的动态指针也给了b,这样a,b中的指针指向同一块内存.这样无论a或者b释放内存都会导致另外一个访内违例崩溃.
解决这个问题需要自己重载赋值运算符和拷贝构造函数.如果不想重载,并且也不喜欢出现错误,那么就把这两类函数声明为私有。
#include <cstdlib>
#include <iostream> using namespace std; class Box {
public:
Box(int = , int = , int = ); //声明有默认参数的构造函数
int *pN;
}; Box::Box(int h, int w, int len) {
height = h;
width = w;
length = len;
} int main(int argc, char** argv) {
Box box1(, , ), box2; //定义两个对象box1和box2
int a=;
box1.pN=new int();
cout << "The pN of box1 is " << *box1.pN << endl;
box2 = box1; //将box1的值赋给box2
cout << "The pN of box2 is " << *box2.pN << endl;
delete box1.pN;
cout << "The pN2 of box2 is " << *box2.pN << endl;
return ;
}
这也是参数内存泄漏的原因:
C++类对象之间赋值产生内存泄漏的一种情况
在用C++ 中定义的类,如果类成员中涉及到了指针以及开辟内存的问题,这样的类对象之间的赋值很容易造成内存的泄漏,见下面的一种情况。代码如下:
类的定义和相关的构造函数如下:
- class image
- {
- public:
- int width;
- int height;
- int* pImg;
- image(int w, int h, int value);
- };
- image::image(int w, int h, int value)
- {
- width = w;
- height = h;
- pImg = new int[w*h];
- for(int i=0;i<w*h;i++)
- pImg[i] = value;
- }
主函数的代码如下:
- image A(100, 100, 100);
- cout << A.pImg << endl;
- image B(100, 100, 10);
- cout << B.pImg << endl;
- cout << B.pImg[10] << endl;
- B =A;
- cout << B.pImg << endl;
- cout << B.pImg[10] << endl;
- return 0;
代码中,对象A和B都在构造函数中开辟了内存,但是后来将A直接赋值给B,运行结果如下:
结果显示:A和B对象创建后,分别调用构造函数开辟了内存,且A和B指针成员的内存的地址不同,以及开始的数据也不同。但是在将对象A赋值给对象B之后,对象B的指针成员的地址和A的一样,即:对象B的指针成员指向了A的对象所开辟的内存。这样导致了内存泄漏,对象B原始的内存开辟了,但是一直没有使用,即使想释放也无法释放,因为丢失了其起始的地址。
转自:http://www.jb51.net/article/72383.htm
http://zhidao.wangchao.net.cn/detail_4078607.html
浅谈C++中对象的复制与对象之间的相互赋值的更多相关文章
- 浅谈Java中的对象和引用
浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...
- 浅谈Java中的对象和对象引用
浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...
- 浅谈JavaScript中的闭包
浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...
- 浅谈Java中的深拷贝和浅拷贝(转载)
浅谈Java中的深拷贝和浅拷贝(转载) 原文链接: http://blog.csdn.net/tounaobun/article/details/8491392 假如说你想复制一个简单变量.很简单: ...
- 浅谈Java中的深拷贝和浅拷贝
转载: 浅谈Java中的深拷贝和浅拷贝 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bool ...
- 浅谈 .NET 中的对象引用、非托管指针和托管指针 理解C#中的闭包
浅谈 .NET 中的对象引用.非托管指针和托管指针 目录 前言 一.对象引用 二.值传递和引用传递 三.初识托管指针和非托管指针 四.非托管指针 1.非托管指针不能指向对象引用 2.类成员指针 五 ...
- 浅谈Java中的equals和==(转)
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...
- 浅谈Java中的equals和==
浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...
- 浅谈C中的malloc和free
转自http://bbs.bccn.net/thread-82212-1-1.html非常感谢作者 浅谈C中的malloc和free 在C语言的学习中,对内存管理这部分的知识掌握尤其重要!之前对C中的 ...
随机推荐
- c 常见错误
."c" not an argument in function sum 该标识符不是函数的参数2.array bounds missing ] in function main ...
- Shell脚本大量示例
Shell基础之控制流结构 一.控制结构 几乎所有的脚本里都有某种流控制结构,很少有例外.流控制是什么?假定有一个脚本,包含下列几个命令: #!/bin/sh # make a directory ...
- Android adb 命令速查表
常用命令 devices # 查看已连接的设备 start-server # 启动 adb server kill-server # 停止 adb server logcat # 查看日志 insta ...
- windows下bash终端--git-bash总汇
git bash是一个比较好用的终端(win下),但有一些细节需要调整后才好用. 1.比如,登陆后终端的表格,以及个别符号显示为乱码. 解决方法: 1).点击git bash左上角,选择“option ...
- 发布Web应用程序时发生的“xx.aspx.cs文件不存在”错误
(注意:这里说的Web应用程序并不是Web网站) 在VS里调试.IIS里浏览含.CS源码的项目都是没有问题的.但是发布后,所有的.CS文件都被编译到bin文件夹里去了.在IIS里浏览,发生“分析器错误 ...
- 每日英语:Six Ways to Modernize Your Car
AS AUTO MAKERS ADD far-out features to the latest cars at warp speed--everything from futuristic hea ...
- git命令(10):git 常用操作,撤销修改(2)
一. 创建与合并分支 1. 从master分支创建dev分支并切换到dev分支: git checkout master git checkout -b dev 其中,git checkout -b ...
- Java自动创建多层文件目录
// 创建文件上传路径 public static void mkdir(String path) { File fd = null; try { fd = new File(path); if (! ...
- 【GitHub】常用命令
克隆项目 git clone http://*git 把本地项目提交到远程 git add --all git commit -m "Initail commit" git pus ...
- Python基本语法_强制数据类型转换
目录 目录 前言 软件环境 Python数据类型的显式转换 显式转换的多种类型 Non-String转换为String str函数 repr str和repr的区别 eval Non-int转换为in ...