📄 subject_14537.htm
字号:
<p>
序号:14537 发表者:浪子 发表日期:2002-09-08 21:39:13
<br>主题:传引用的问题。
<br>内容:各位高手:在创建一个基类时,为什么要建立一个他的拷贝构造函数。例如:<BR>Class Amianl<BR>{<BR> public:<BR> Animal();<BR> Animal(Animal &);<BR> private:<BR> ....<BR>};<BR><BR>Animal::Animal(Animal &)<BR>{<BR> .....<BR>}<BR><BR><BR>在这个基类Animal中为什么要定义 Animal(Animal &);这句?<BR>它起什么作用?<BR>清高手指点。先谢了。
<br><a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p>
<hr size=1>
<blockquote><p>
回复者:史列因 回复日期:2002-09-08 21:51:49
<br>内容: 这么说吧,如果你的C++书上没有讲清楚,把它扔了换一本。
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:浪子 回复日期:2002-09-09 07:26:21
<br>内容:to 史列因 :我还是要谢谢你,我不敢说书好不好,或许本人的理解没到位,一直理解不通,才向各位高手请教。在问这个问题时,我顺便再穿插一个问题:<BR><BR>{class CDocument{<BR>public:<BR> CDocument(const CDocument&){cout<<"enter into CDocument\n"<<endl;}<BR> CDocument(){cout<<"enter into CDocument\n"<<endl;};<BR> ~CDocument(){cout<<"enter out CDocument\n";};<BR>};<BR><BR>class CMyDoc:public CDocument<BR>{<BR>public:CMyDoc(){cout<<"enter into CMyDoc\n"<<"\n";<BR><BR> ~CMyDoc(){cout<<"enter out CMyDoc\n";<BR>};<BR><BR>void main()<BR>{<BR><BR> ((CDocument)mydoc).func();// 1<BR> (CDocument)mydoc;//2<BR>}<BR><BR>在执行1时为什么会调用拷贝构造函数 CDocument(const CDocument&){cout<<"enter into CDocument\n"<<endl;} 是单单“((CDocument)mydoc)”这部分在起作用吗?<BR>请史列因 等朋友指点。先谢了。<BR>
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:凯凯 回复日期:2002-09-09 08:49:28
<br>内容:拷贝构造函数对于一个类来说并是必须的(当然大多数情况下没它不行)。如果你的类对象需要相互赋值,或是要做为函数的参数或返回值,且类中包含指针或是在赋值时你需要其他的特殊逻辑时才需要定义之。普通情况下需要时编译器会自动产生一个。<BR><BR>你上面贴出的代码不全,没有mydoc和func()的定义。
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:8888feige 回复日期:2002-09-09 09:54:18
<br>内容:在这个基类Animal中为什么要定义 Animal(Animal &);这句?<BR><BR>你可以不定义这种构造函数,也没有问题。<BR>其实定义这种构造函数是为了赋值方便比如说你可以用下面的方法代替。<BR><BR>Animal oA1 = new Animal;<BR>Animal oA2 = new Animal;<BR>oA2 = oA1;<BR><BR>但有了这种构造方法你可以直接做:<BR>Animal oA1 = new Animal;<BR>Animal oA2 = new Animal(oA1);<BR>
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
<font color=red>答案被接受</font><br>回复者:了了 回复日期:2002-09-09 10:16:46
<br>内容:《effective c++》<BR><BR><BR><BR>条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符<BR><BR>看下面一个表示string对象的类:<BR><BR>// 一个很简单的string类<BR>class string {<BR>public:<BR> string(const char *value);<BR> ~string();<BR><BR> ... // 没有拷贝构造函数和operator=<BR><BR>private:<BR> char *data;<BR>};<BR><BR>string::string(const char *value)<BR>{<BR> if (value) {<BR> data = new char[strlen(value) + 1];<BR> strcpy(data, value);<BR> }<BR> else {<BR> data = new char[1];<BR> *data = '\0';<BR> }<BR>}<BR><BR>inline string::~string() { delete [] data; }<BR><BR>请注意这个类里没有声明赋值操作符和拷贝构造函数。这会带来一些不良后果。<BR><BR>如果这样定义两个对象:<BR><BR>string a("hello");<BR>string b("world");<BR><BR>其结果就会如下所示:<BR><BR>a: data——> "hello\0"<BR>b: data——> "world\0"<BR><BR>对象a的内部是一个指向包含字符串"hello"的内存的指针,对象b的内部是一个指向包含字符串"world"的内存的指针。如果进行下面的赋值:<BR><BR>b = a;<BR><BR>因为没有自定义的operator=可以调用,c++会生成并调用一个缺省的operator=操作符(见条款45)。这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。赋值的结果如下所示:<BR><BR>a: data --------> "hello\0"<BR> /<BR>b: data --/ "world\0"<BR><BR>这种情况下至少有两个问题。第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。<BR><BR>string a("hello"); // 定义并构造 a<BR><BR>{ // 开一个新的生存空间<BR> string b("world"); // 定义并构造 b<BR><BR> ...<BR><BR> b = a; // 执行 operator=,<BR> // 丢失b的内存<BR><BR>} // 离开生存空间, 调用 <BR> // b的析构函数<BR><BR>string c = a; // c.data 的值不能确定!<BR> // a.data 已被删除<BR><BR>例子中最后一个语句调用了拷贝构造函数,因为它也没有在类中定义,c++以与处理赋值操作符一样的方式生成一个拷贝构造函数并执行相同的动作:对对象里的指针进行逐位拷贝。这会导致同样的问题,但不用担心内存泄漏,因为被初始化的对象还不能指向任何的内存。比如上面代码中的情形,当c.data用a.data的值来初始化时没有内存泄漏,因为c.data没指向任何地方。不过,假如c被a初始化后,c.data和a.data指向同一个地方,那这个地方会被删除两次:一次在c被摧毁时,另一次在a被摧毁时。<BR><BR>拷贝构造函数的情况和赋值操作符还有点不同。在传值调用的时候,它会产生问题。当然正如条款22所说明的,一般很少对对象进行传值调用,但还是看看下面的例子:<BR><BR>void donothing(string localstring) {}<BR><BR>string s = "the truth is out there";<BR><BR>donothing(s);<BR><BR>一切好象都很正常。但因为被传递的localstring是一个值,它必须从s通过(缺省)拷贝构造函数进行初始化。于是localstring拥有了一个s内的指针的拷贝。当donothing结束运行时,localstring离开了其生存空间,调用析构函数。其结果也将是:s包含一个指向localstring早已删除的内存的指针。<BR><BR>顺便指出,用delete去删除一个已经被删除的指针,其结果是不可预测的。所以即使s永远也没被使用,当它离开其生存空间时也会带来问题。<BR><BR>解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。<BR><BR>对于有些类,当实现拷贝构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。前面提到的那个遗漏了拷贝构造函数和赋值操作符的例子固然是一个糟糕的设计,那当现实中去实现它们又不切实际的情况下,该怎么办呢?很简单,照本条款的建议去做:可以只声明这些函数(声明为private成员)而不去定义(实现)它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。关于这个俏皮的小技巧的细节,参见条款27。<BR><BR>关于本条款中所用到的那个string类,还要注意一件事。构造函数体内,在两个调用new的地方都小心地用了[],尽管有一个地方实际只需要单个对象。正如条款5所说,在配套使用new和delete时一定要采用相同的形式,所以这里也这么做了。一定要经常注意,当且仅当相应的new用了[]的时候,delete才要用[]。<BR>
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:史列因 回复日期:2002-09-09 10:43:49
<br>内容: ((CDocument)mydoc).func();// 1<BR>因为这个转换是转换的对象而不是指针(或引用,其实本质还是指针),编译器会生成这种形式:<BR>CDocument temp( mydoc );<BR>temp.func();<BR><BR>这个问题不是理解到不到位的问题,而是书中必须写的一清二楚的,至少要能比我表达的更清楚。或许你没看到,或许看过但没了印象。<BR>
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:浪子 回复日期:2002-09-09 12:32:48
<br>内容:to 史列因 ,了了 ,8888feige ,凯凯 等朋友的热心解答。我现在总算对拷贝构造函数有点头绪了。谢谢诸位。
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -