主题:[讨论]C++之拷贝构造函数问题
YaAnn [专家分:1630] 发布于 2009-07-10 16:11:00
最近在看C++的构造函数,析构函数,拷贝构造函数和赋值函数的相关知识,写了一个简单的string类
(1)、先请大家看看程序有什么不妥的地方没有?
(2)、程序中有些地方不明白,已经在程序中注释了,大家帮忙看看,主要是拷贝构造函数的问题?
(3)、关于拷贝构造函数,大家都是怎么理解的?
谢谢大家···
忘了,环境是 XP sp3 + VC2005
代码如下:
[code=c]
#include <iostream>
using namespace std;
class CSString
{
public:
//构造函数
CSString():str(NULL) //a
{
str = new char[1];
str[0] = '\0';
}
CSString(const char *strr):str(NULL) //b
{
if (strr == NULL)
{
str = new char[1];
str[0] = '\0';
}
else
{
size_t len = strlen(strr);
str = new char[len+1];
strcpy_s(str,len+1,strr);
}
}
//拷贝构造函数
CSString(const CSString &src) //c
{
size_t len = strlen(src.str);
str = new char[len+1];
strcpy_s(str,len+1,src.str);
}
//赋值函数
CSString &operator =(const CSString &src)
{
if (this == &src)
{
return *this;
}
delete []str;
size_t len = strlen(src.str);
str = new char[len+1];
strcpy_s(str,len+1,src.str);
return *this;
}
//析构函数
~CSString()
{
delete []str;
str = NULL;
}
//重载 + 运算符
CSString operator +(const CSString ss) const
{
CSString tmp;
delete []tmp.str;
size_t len = strlen(str) + strlen(ss.str) + 1;
tmp.str = new char[len];
strcpy(tmp.str,str);
strcat(tmp.str,ss.str);
return tmp; //调试到这里的时候调用了拷贝构造函数,这里不太明白???
}
private:
char *str;
};
int main()
{
CSString str0("西安"); //调用构造函数b
CSString str1("好地方"); //调用构造函数b
CSString str2; //调用构造函数a
str2 = str0 + str1; //先调用拷贝构造函数c(值是“好地方”,这里也不太明白??),再调用重载+,然后调用赋值函数
//这里调用了析构函数,析构赋值函数中间变量
return 0;//析构str2,str1,str0
}
[/code]
最后更新于:2009-07-10 16:14:00
回复列表 (共11个回复)
沙发
bruceteen [专家分:42660] 发布于 2009-07-10 16:43:00
代码基本能工作,但不好的地方有很多
先调用拷贝构造函数c(值是“好地方”,这里也不太明白??)
--- 因为你写的是 CSString operator +(const CSString ss) const
而不是 CSString operator +(const CSString& ss) const
板凳
bruceteen [专家分:42660] 发布于 2009-07-10 17:29:00
[code=c]
#include <iostream> // CSString用不到<iostream>,因此#include <iostream>不应该出现在CSString之前
using namespace std;
class CSString
{
public:
CSString() : str(0) // 将str(NULL)改为str(0),记住你现在用的是C++
{
//str = new char[1]; // 既然不能保存数据就别开辟空间
//str[0] = '\0';
}
CSString( const char* strr) // 将 const char *strr 改为 const char* strr,虽然含义一样。
// 在C中,TYPE *strr意图表现*strr为TYPE;在C++中,TYPE* strr意图表现strr为TYPE*
{
// if (strr == NULL) // 永远不要考虑strr为NULL的情况,如果你看strcpy的原型就能明白,C/C++契约就规定其应当为一个合法的字符串
// { // 因为你是无法健壮到支持任何错误的,比如你要不要检查strr指向的不是一个字符串?所以你只能约定好调用者给一个合法的字符串
// str = new char[1];
// str[0] = '\0';
//}
//else
//{
size_t len = strlen(strr);
str = new char[len+1];
//strcpy_s(str,len+1,strr); // strcpy_s是什么?str为len+1,那strcpy可能会出现错误吗?strcpy_s是M$的一个笑话。
strcpy(str,strr);
//}
}
//拷贝构造函数
CSString( const CSString& src ) // const CSString &src 改为 const CSString& src
{
if( !src.str )
str = 0;
else
{
size_t len = strlen(src.str); // 有太多类似的操作了
str = new char[len+1]; // ,因此可以考虑将它们写到一个私有函数中
strcpy(str,src.str); //
}
}
//赋值函数
CSString& operator=( const CSString& src )
{
//if (this == &src)
//{
// return *this; // 在不影响阅读的情况下,最好一个return
//}
if (this != &src)
{
delete[] str; // 将 delete []str 改为 delete[] str。很明显[]和str是没关系的
if( !src.str )
str = 0;
else
{
size_t len = strlen(src.str);
str = new char[len+1];
strcpy(str,src.str);
}
}
return *this;
}
//析构函数
~CSString()
{
delete[] str;
// str = NULL; // 这个是没必要的,是一种陋习。一个对象析构了就不应当再去使用
// ,你这个str=NULL是不是想说你很鼓励对方访问已经析构了的对象?^_^
// 一个原则是:绝不对错误行为进行健壮性设计,因为这只会掩盖错误从而造成更大的错误。
}
//重载 + 运算符
CSString operator+(const CSString ss) const // const CSString ss 改为 const CSString& ss
{
// 这下面的代码我就不改了
CSString tmp;
delete []tmp.str;
size_t len = strlen(str) + strlen(ss.str) + 1;
tmp.str = new char[len];
strcpy(tmp.str,str);
strcat(tmp.str,ss.str); // 从效率上不对,因为strcat需要对第一个参数做strlen(str),而你又已经做过了
// 差不多是 len1=strlen(str); len=len1+strlen(ss.str)+1; strcpy( temp.str+len1, ss.str );
return tmp; //调试到这里的时候调用了拷贝构造函数,这里不太明白??? --- 可以有,也可以没有(因为具名返回值优化)
// 建议不要使用 tmp,因为匿名返回值优化大部分编译器都能优化得很好
}
private:
char *str; // 建议改为 char* str_,就是对于非public变量和函数,后面加个下划线。别人看到最后有下划线就知道不可以直接调用它。
};
再说说其他的吧
你允许 const char* 隐式转化为 CSString 吗?从常理上讲,应该是允许的。所以构造函数前不加 explicit
,但看你的 CSString operator+(const CSString ss) const,别人可以 CSString + "abc",但 "abc"+CSString 就不行了,别人会很奇怪,所以+应该是友元函数。
字符串长度在很多地方常用,有没有考虑加进去过
如果加入长度,怎样保证……,这个这个太复杂了,不说了
int main()
{
CSString str0("西安");
CSString str1("好地方");
CSString str2;
str2 = str0 + str1; // 为什么不写成 CSString str2 = str0 + str1;?
return 0;
}
[/code]
3 楼
YaAnn [专家分:1630] 发布于 2009-07-11 11:03:00
[quote][code=c]
#include <iostream> // CSString用不到<iostream>,因此#include <iostream>不应该出现在
CSString之前
using namespace std;
class CSString
{
public:
CSString() : str(0) // 将str(NULL)改为str(0),记住你现在用的是C++
{
//str = new char[1]; // 既然不能保存数据就别开辟空间
//str[0] = '\0';
}
CSString( const char* strr) // 将 const char *strr 改为 const char* strr,虽然含义一样。
// 在C中,TYPE *strr意图表现*strr为TYPE;在C++中,TYPE* strr意图表现
strr为TYPE*
{
// if (strr == NULL) // 永远不要考虑strr为NULL的情况,如果你看strcpy的原型就能明白,
C/C++契约就规定其应当为一个合法的字符串
// { // 因为你是无法健壮到支持任何错误的,比如你要不要检查strr指向的不是一个
字符串?所以你只能约定好调用者给一个合法的字符串
// str = new char[1];
// str[0] = '\0';
//}
//else
//{
size_t len = strlen(strr);
str = new char[len+1];
//strcpy_s(str,len+1,strr); // strcpy_s是什么?str为len+1,那strcpy可能会出现错误吗?
strcpy_s是M$的一个笑话。
strcpy(str,strr);
//}
}
//拷贝构造函数
CSString( const CSString& src ) // const CSString &src 改为 const CSString& src
{
if( !src.str )
str = 0;
else
{
size_t len = strlen(src.str); // 有太多类似的操作了
str = new char[len+1]; // ,因此可以考虑将它们写到一个私有函数中
strcpy(str,src.str); //
}
}
//赋值函数
CSString& operator=( const CSString& src )
{
//if (this == &src)
//{
// return *this; // 在不影响阅读的情况下,最好一个return
//}
if (this != &src)
{
delete[] str; // 将 delete []str 改为 delete[] str。很明显[]和str是没关系的
if( !src.str )
str = 0;
else
{
size_t len = strlen(src.str);
str = new char[len+1];
strcpy(str,src.str);
}
}
return *this;
}
//析构函数
~CSString()
{
delete[] str;
// str = NULL; // 这个是没必要的,是一种陋习。一个对象析构了就不应当再去使用
// ,你这个str=NULL是不是想说你很鼓励对方访问已经析构了的对象?^_^
// 一个原则是:绝不对错误行为进行健壮性设计,因为这只会掩盖错误从而造成更
大的错误。
}
//重载 + 运算符
CSString operator+(const CSString ss) const // const CSString ss 改为 const CSString& ss
{
// 这下面的代码我就不改了
CSString tmp;
delete []tmp.str;
size_t len = strlen(str) + strlen(ss.str) + 1;
tmp.str = new char[len];
strcpy(tmp.str,str);
strcat(tmp.str,ss.str); // 从效率上不对,因为strcat需要对第一个参数做strlen(str),而你又已
经做过了
// 差不多是 len1=strlen(str); len=len1+strlen(ss.str)+1; strcpy(
temp.str+len1, ss.str );
return tmp; //调试到这里的时候调用了拷贝构造函数,这里不太明白??? --- 可以有,也可
以没有(因为具名返回值优化)
// 建议不要使用 tmp,因为匿名返回值优化大部分编译器都能优化得很好
}
private:
char *str; // 建议改为 char* str_,就是对于非public变量和函数,后面加个下划线。别人看到最
后有下划线就知道不可以直接调用它。
};
再说说其他的吧
你允许 const char* 隐式转化为 CSString 吗?从常理上讲,应该是允许的。所以构造函数前不加
explicit
,但看你的 CSString operator+(const CSString ss) const,别人可以 CSString + "abc",但
"abc"+CSString 就不行了,别人会很奇怪,所以+应该是友元函数。
字符串长度在很多地方常用,有没有考虑加进去过
如果加入长度,怎样保证……,这个这个太复杂了,不说了
int main()
{
CSString str0("西安");
CSString str1("好地方");
CSString str2;
str2 = str0 + str1; // 为什么不写成 CSString str2 = str0 + str1;?
return 0;
}
[/code][/quote]
恩,学习了,太感谢了···
友元函数还没有学到。
先解决部分问题吧,有下列疑问:
(1)、 NULL == 0 ---- 但用NULL表示指针不是更明确吗?
(2)、 strcpy_s是M$的一个笑话 ---- 是不是因为不是标准?
(3)、 关于析构函数的想法是这样的 ----
[code=c]//析构函数
~CSString()
{
delete []str;
str = NULL;
}[/code]
首先释放内存,再防止野指针。
(4)、 对于类似 + 的二元运算符重载,重载函数的变量使用一个好一些还是用两个好一些?书上
的解释不一样?
谢谢了!
4 楼
dsldon [专家分:360] 发布于 2009-07-12 08:17:00
路过,学习了。
5 楼
bruceteen [专家分:42660] 发布于 2009-07-12 11:59:00
1. 在C中规定:void* 可以隐式转化为任何 TYPE*;在C++中规定0可以隐式转化为任何 TYPE*。
所以在C++的NULL定义和C中是不一样的,C++中保留NULL这个宏只是为了兼容原有的C代码。
C++中推荐使用0,而不是NULL,因为NULL不但丑陋,而且不统一。
“用NULL表示指针不是更明确吗?”--- 如果为了明确的话,那么应该有 charNULL、intNULL 等无限多的NULL。这样一来,你一定会觉得麻烦,因为你只需要给其赋上零值,而不关心其类型。不需要关心类型也就是类型不依赖,这是优点。而C++规定0可以隐式转化为任何TYPE*,也就是出于这个目的,它不需要依赖对象类型是 TYPE 和 TYPE*。
当然,有的时候也需要明确类型,比如如下代码:
void foo( int )
{
puts( "int" );
}
void foo( int* )
{
puts( "int*" );
}
int main()
{
foo( NULL );
}
将输出 int 而不是 int*,这导致在C++0x中可以能会引入类似nullptr之类的东西。只是顺便提一下,和本问题无关。
2。strcpy_s 解决不了任何问题,不出错的用了strcpy也不会出错,该出错的用了strcpy_s还是一样出错。
使用strcpy时,有一个长度在调用者心中,而使用strcpy_s时将这个长度告诉函数,问题在于函数也不能确保你这个长度是正确的呀,如果错,则strcpy_s也错;如果不错,则strcpy也不错。
另外一点最严重的是,strcpy_s会掩盖错误,而成使得错误升级。前面说过,任何意图掩盖错误的行为都必然会带来更大的错误。假设目的区长度不足以容纳源串,那么目的串就和原串不一样,在银行就会将"张宝宝"的钱给了"张宝",这比"张宝宝"取不出钱更糟糕。如果目的区足以容纳源串,那么使用strcpy_s又是画蛇添足,多做了一件无意义的检查。
3。这其实和2是同样的问题。你只应该保证正确的行为能得到正确的结果,对于错误的行为,其结果无论是正确还是错误,你都不应该去管。在这里“正确的行为”就是指内存释放后就不应该再去访问它。
4。一种简单的做法是将 成员函数CSString operator+(const CSString& ss) const
改为友元 CSString operator+( const CSString& lhs, const CSString& rhs );
这就能保证 const char* + const CSString 可行,能保证 const CSString + const char* 可行
能保证 const CSString + const CSString 可行,同时能阻止 const char* + const char*。
这利用了 const char* 可以隐式转化为 CSString 的特性。
当然,一般为了避免const char*转化为CSString带来的效率影响,可以使用三个重载:
CSString operator+( const CSString& lhs, const CSString& rhs );
CSString operator+( const char* lhs, const CSString& rhs );
CSString operator+( const CSString& lhs, const char* rhs );
6 楼
YaAnn [专家分:1630] 发布于 2009-07-12 19:47:00
[code=c]
//赋值函数
CSString& operator=( const CSString& src )
{
//if (this == &src)
//{
// return *this; // 在不影响阅读的情况下,最好一个return
//}
if (this != &src)
{
delete[] str; // 将 delete []str 改为 delete[] str。很明显[]和str是没关系的
if( !src.str )
str = 0;
else
{
size_t len = strlen(src.str);
str = new char[len+1];
strcpy(str,src.str);
}
}
return *this;
}
[/code]
前面的语句是为了防止出现自赋值现象,改掉不合适吧?
7 楼
sarrow [专家分:35660] 发布于 2009-07-12 20:11:00
[quote]前面的语句是为了防止出现自赋值现象,改掉不合适吧?[/quote]
看别人的代码要用心——见这里:
[quote] if (this != &src)[/quote]
已经排除了自我赋值的情况。
说实话,要写好一个类是挺难的事情。
上面的这个版本,还是有问题——应该先分配新的,然后再释放原来的。
——如果你先就把原来的给释放掉了;若在此时,内存不够新内存分配——那么完蛋了,旧的值已经被破坏了。
正常的情况,发生这样的情况时,应用程序应该提示用户内存不足,是否需要保存当前的操作到硬盘——程序应当一切以用户数据的安全为首要考虑。
当然,如果自己本身内存够的话,还可以考虑直接利用之前的内存,而无须另行分配。
8 楼
YaAnn [专家分:1630] 发布于 2009-07-12 21:11:00
[em8][em8]
真是不好意思,我照着原来的一步一步的改,后面的没有细看
9 楼
Chipset [专家分:16190] 发布于 2009-07-12 21:27:00
std::string还是有点技术含量的,模拟写一个作为练习很不错。楼上bruceteen和sarrow真有耐心。赞一个!!
10 楼
YaAnn [专家分:1630] 发布于 2009-07-16 17:29:00
最新版本正在修改中,下面是部分代码,有些问题:
[code=c]
#include <string.h>
class CSString
{
public:
//构造函数
CSString():str_(0)
{}
CSString(const char *strr)
{
CScopy(strr);
}
//拷贝构造函数
CSString(const CSString& src)
{
if (!src.str_)
{
str_ = 0;
}
else
{
CScopy(src);
}
}
//赋值函数
CSString& operator=(const CSString&);
CSString& operator=(const char*);
//析构函数
~CSString()
{
delete[] str_;
}
private:
char *str_;
void CScopy(const CSString&);
void CScopy(const char*);
};
void CSString::CScopy(const CSString& src)
{
str_ = new char[strlen(src.str_)+1];
strcpy(str_,src.str_);
}
void CSString::CScopy(const char* strr)
{
str_ = new char[strlen(strr)+1];
strcpy(str_,strr);
}
CSString& CSString ::operator =(const CSString &src)
{
if (this != &src)
{
delete[] str_;
if (!src.str_)
{
str_ = 0;
}
else
{
CScopy(src);
}
}
return *this;
}
CSString& CSString::operator=(const char* strr)
{
if (this->str_ != strr)
{
delete[] str_;
if (!strr)
{
str_ = 0;
}
else
{
CScopy(strr);
}
}
return *this;
}
int main()
{
CSString str0("西安"); //普通构造函数
CSString str1(str0); //拷贝构造函数
// = 重载测试
CSString str2,str3;
str2 = str1;
str3 = "";
return 0;
}
[/code]
上面还只是有重载 = 的部分。
问题:调试str3,为什么析构后反而像是给分配了内存??
环境:xp sp2 + VC2005
我来回复