C++拷贝函数的作用
最近学习了C++的拷贝构造函数,可是我不明白拷贝构造函数究竟有什么用,有构造函数了为什么还要拷贝构造函数,谁能给我讲讲吗?...
最近学习了C++的拷贝构造函数,可是我不明白拷贝构造函数究竟有什么用,有构造函数了为什么还要拷贝构造函数,谁能给我讲讲吗?
展开
展开全部
拷贝构造函数,经常被称作X(X&),是一种特殊的构造函数,他由编译器调用来完成一些基于同一类的其他对象的构件及初始化。它的唯一的一个参数(对象的引用)是不可变的(因为是const型的)。这个函数经常用在函数调用期间于用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。
展开全部
首先纠正你的一个特大理解错误
=本来和默认拷贝构造函数相同
等号不会和任何拷贝(或称复制,后面就叫复制构造函数,因为贝字超难打)构造函数相同。下面讲解原因:
复制初始化与复制构造函数:复制初始化是创建一个新对象,并且其初值来自于另一个已存在的对象,复制初始化总是调用复制构造函数来初始化的,复制初始化时首先使用指定的构造函数创建一个临时对象,然后用复制构造函数将临时对象的每个非static成员依次的复制到新创建的对象。复制构造函数执行的是逐个成员初始化。注意这里是用一个已存在的对象创建另一个新对象,与用构造函数直接创建一个新对象不一样,使用构造函数初始化时不会使用另一个对象。比如有类hyong,则语句hyong m(1,2)调用构造函数直接初始化,而语句hyong n=m则是用已存在的对象m去初始化一个新对象n,属于复制初始化。
赋值:赋值是在两个已存在的对象间进行的,也就是用一个已存在的对象去改变另一个已存在对象的值。赋值将调用赋值操作符对对象进行操作,比如有类hyong,有语句hyong x(1);hyong y(1,2)则x=y;这就是赋值,因为对象x和y是已经存在的对象,而语句hyong x=y;则是复制初始化,是用一个已存在的对象y去创建一个新对象x,所以是复制初始化。
所以你补允所问的后面两个问题根本没有任何意义,重载的=和复制构造函数根本就是不相同的,他们在不同的场合使用,如果两个对象已经存在,就会使用赋值操作符,如果是使用一个对象去创建(注意是创建)另一个不存在的对象,则会使用复制构造函数。
如果还有不明白的地方,就去本人的文库下载关于构造函数的文章,里面有详细的讲解。
=本来和默认拷贝构造函数相同
等号不会和任何拷贝(或称复制,后面就叫复制构造函数,因为贝字超难打)构造函数相同。下面讲解原因:
复制初始化与复制构造函数:复制初始化是创建一个新对象,并且其初值来自于另一个已存在的对象,复制初始化总是调用复制构造函数来初始化的,复制初始化时首先使用指定的构造函数创建一个临时对象,然后用复制构造函数将临时对象的每个非static成员依次的复制到新创建的对象。复制构造函数执行的是逐个成员初始化。注意这里是用一个已存在的对象创建另一个新对象,与用构造函数直接创建一个新对象不一样,使用构造函数初始化时不会使用另一个对象。比如有类hyong,则语句hyong m(1,2)调用构造函数直接初始化,而语句hyong n=m则是用已存在的对象m去初始化一个新对象n,属于复制初始化。
赋值:赋值是在两个已存在的对象间进行的,也就是用一个已存在的对象去改变另一个已存在对象的值。赋值将调用赋值操作符对对象进行操作,比如有类hyong,有语句hyong x(1);hyong y(1,2)则x=y;这就是赋值,因为对象x和y是已经存在的对象,而语句hyong x=y;则是复制初始化,是用一个已存在的对象y去创建一个新对象x,所以是复制初始化。
所以你补允所问的后面两个问题根本没有任何意义,重载的=和复制构造函数根本就是不相同的,他们在不同的场合使用,如果两个对象已经存在,就会使用赋值操作符,如果是使用一个对象去创建(注意是创建)另一个不存在的对象,则会使用复制构造函数。
如果还有不明白的地方,就去本人的文库下载关于构造函数的文章,里面有详细的讲解。
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
我来专业回答你的问题吧。
拷贝构造函数就是把一个对象的值赋给另一个对象的一种简便方法。也可以说是用一个对象初始化另外一个对象的方法 。
详细的说就是:
拷贝构造函数就是把一个对象复制给另一个对象时调用的构造函数,当类中没有指针类型的数据成员时可以通过调用默认的构造函数(是按位拷贝,所以两个对象指针所指向相同的内存,当对象被销毁时,会调用各自的析构函数,这样也就把这块内存释放了两次,引发错误)就可以
而类中有指针类型的数据成员事就要调用自己写的拷贝构造函数 避免指针所指向的内存被释放两次。
拷贝构造函数就是把一个对象的值赋给另一个对象的一种简便方法。也可以说是用一个对象初始化另外一个对象的方法 。
详细的说就是:
拷贝构造函数就是把一个对象复制给另一个对象时调用的构造函数,当类中没有指针类型的数据成员时可以通过调用默认的构造函数(是按位拷贝,所以两个对象指针所指向相同的内存,当对象被销毁时,会调用各自的析构函数,这样也就把这块内存释放了两次,引发错误)就可以
而类中有指针类型的数据成员事就要调用自己写的拷贝构造函数 避免指针所指向的内存被释放两次。
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
我不是专业 说说我自己的想法
拷贝构造函数可以理解为把一个对象的内容全部复制并生成另一个对象
拷贝构造函数与构造函数最大的区别是参数 构造函数的参数对应着生成对象的一个成员 而拷贝构造函数的参数则对应整个对象。如:
class_name(int member); 编译器只需要将member赋值给一个成员
class_name(class_name a); 编译器需要分析a 后分别赋值给新对象的成员
只要你不从编译器如何实现的角度来看 应该没有区别, 但是拷贝构造函数的参数很复杂(一个对象), 我们用这种参数创建的新对象会很麻烦 比如会跟新对象共用一块内存。。 如果我们变了其中任何一个对象的值 可能其他的对象也跟着完。。。 所以有时拷贝构造函数要我们自己来写(需要分配新内存来以示区别)。
如果不对请指正 谢谢
拷贝构造函数可以理解为把一个对象的内容全部复制并生成另一个对象
拷贝构造函数与构造函数最大的区别是参数 构造函数的参数对应着生成对象的一个成员 而拷贝构造函数的参数则对应整个对象。如:
class_name(int member); 编译器只需要将member赋值给一个成员
class_name(class_name a); 编译器需要分析a 后分别赋值给新对象的成员
只要你不从编译器如何实现的角度来看 应该没有区别, 但是拷贝构造函数的参数很复杂(一个对象), 我们用这种参数创建的新对象会很麻烦 比如会跟新对象共用一块内存。。 如果我们变了其中任何一个对象的值 可能其他的对象也跟着完。。。 所以有时拷贝构造函数要我们自己来写(需要分配新内存来以示区别)。
如果不对请指正 谢谢
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
class Name {
const char* s;
// ...
};
class Table {
Name* p;
size_t sz;
public:
Table(size_t s = 15) { p = new Name[sz = s]; }
~Table() { delete[] p; }
Name* lookup(const char*);
bool insert(Name*);
};
如果t1和t2都是类Table的对象,t1 = t2的默认含义就是将t1按成员逐个复制到t2。对于赋值的这种解释方式,在应用到具有指针成员的类的对象时,就可能产生一种出人意料的(通常也是人们所不希望)的作用。对于包含了由构造函数/析构函数管理的资理的对象而言,按成员复制的主义通常是不正确的。例如,
void h()
{
Table t1;
Table t2 = t1; // 复制初始化:麻烦
Table t3;
T3 = t2; // 复制赋值:麻烦
}
在这里,Table的默认构造函数为t1和t3各调用了一次,一共两次。然而Table的析构函数则被调用了3次:对t1、t2和t3各一次!由于赋值的默认解释是按成员赋值,所以,在h()结束时,t1、t2和t3中将各包含一个指针,它们都指向建立t1时从自由存储中分配的那个名字数组。在建立t3时所分配的数组的指针并没有保留下来,因为它被赋值t3 = t2覆盖掉了。这样,如果没有自动废料收集,对这个程序而言,该数组的存储就将永远丢掉了。而在另一方面,为t1的创建而分配的数组因为同时出现在t1、t2和t3里,将被删除3次。这种情况所导致的结果是无定义的,很可能是灾难性的。
这类反常情况可以避免,方式就是将Table复制的意义定义清楚:
Table(const Table&); // 复制构造函数
Table& operator=(const Table&); // 复制赋值
程序员可以为这些复制操作定义自己认为最合适的任何意义,但对于这类容器,传统上就是复制那些被包含的元素(或至少是让用户以为做了复制)。例如,
Table::Table(const Table& t) // 复制构造函数
{
p = new Name[sz = t.sz];
for (int i = 0; i<sz; i++) p[i] = t.p[i];
}
Table& Table::operator=(const Table& t)
{
if (this != &t) { // 当心自赋值:t = t
delete[] p;
p = new Name[sz = t.sz];
for (int i = 0; i<sz; i++) p[i] = t.p[i];
}
return *this;
}
情况几乎总是如此,复制构造函数与复制赋值通常都不一样。究其根本原因,复制构造函数是去完成对未初始化的存储区的初始化,而复制赋值运算符则必须正确处理一个结构良好的对象。
在某此情况下,可以对赋值做一些优化,但赋值运算符的一般性策略非常简单:防止自赋值,删除那些老元素,初始化,复制那些新元素。通常每个非静态的成员都必须复制。可以通过异常去报告复制中出错的情况。
const char* s;
// ...
};
class Table {
Name* p;
size_t sz;
public:
Table(size_t s = 15) { p = new Name[sz = s]; }
~Table() { delete[] p; }
Name* lookup(const char*);
bool insert(Name*);
};
如果t1和t2都是类Table的对象,t1 = t2的默认含义就是将t1按成员逐个复制到t2。对于赋值的这种解释方式,在应用到具有指针成员的类的对象时,就可能产生一种出人意料的(通常也是人们所不希望)的作用。对于包含了由构造函数/析构函数管理的资理的对象而言,按成员复制的主义通常是不正确的。例如,
void h()
{
Table t1;
Table t2 = t1; // 复制初始化:麻烦
Table t3;
T3 = t2; // 复制赋值:麻烦
}
在这里,Table的默认构造函数为t1和t3各调用了一次,一共两次。然而Table的析构函数则被调用了3次:对t1、t2和t3各一次!由于赋值的默认解释是按成员赋值,所以,在h()结束时,t1、t2和t3中将各包含一个指针,它们都指向建立t1时从自由存储中分配的那个名字数组。在建立t3时所分配的数组的指针并没有保留下来,因为它被赋值t3 = t2覆盖掉了。这样,如果没有自动废料收集,对这个程序而言,该数组的存储就将永远丢掉了。而在另一方面,为t1的创建而分配的数组因为同时出现在t1、t2和t3里,将被删除3次。这种情况所导致的结果是无定义的,很可能是灾难性的。
这类反常情况可以避免,方式就是将Table复制的意义定义清楚:
Table(const Table&); // 复制构造函数
Table& operator=(const Table&); // 复制赋值
程序员可以为这些复制操作定义自己认为最合适的任何意义,但对于这类容器,传统上就是复制那些被包含的元素(或至少是让用户以为做了复制)。例如,
Table::Table(const Table& t) // 复制构造函数
{
p = new Name[sz = t.sz];
for (int i = 0; i<sz; i++) p[i] = t.p[i];
}
Table& Table::operator=(const Table& t)
{
if (this != &t) { // 当心自赋值:t = t
delete[] p;
p = new Name[sz = t.sz];
for (int i = 0; i<sz; i++) p[i] = t.p[i];
}
return *this;
}
情况几乎总是如此,复制构造函数与复制赋值通常都不一样。究其根本原因,复制构造函数是去完成对未初始化的存储区的初始化,而复制赋值运算符则必须正确处理一个结构良好的对象。
在某此情况下,可以对赋值做一些优化,但赋值运算符的一般性策略非常简单:防止自赋值,删除那些老元素,初始化,复制那些新元素。通常每个非静态的成员都必须复制。可以通过异常去报告复制中出错的情况。
参考资料: 《C++程序设计语言(特别版)》
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询