C++ - 字符串类的实现
Posted
技术标签:
【中文标题】C++ - 字符串类的实现【英文标题】:C++ - implementation of string class 【发布时间】:2015-10-21 05:07:23 【问题描述】:我正在尝试实现字符串类。这是我所做的:
#include <iostream>
#include <cstring>
using namespace std;
class MyString
private:
char * content;
int length;
public:
MyString ();
MyString ( const char * );
~MyString ();
MyString ( const MyString & );
void print ( void );
void operator = ( const MyString );
;
MyString :: MyString ()
content = 0;
length = 0;
MyString :: MyString(const char *n)
length = strlen (n);
content = new char [ length ];
for ( int i = 0 ; i < length ; i++ )
content [i] = n [i];
content [length] = '\0';
MyString :: ~ MyString ()
delete [] content;
content = 0;
MyString :: MyString ( const MyString & x )
length = x.length;
content = new char [length];
for( int i = 0 ; i < length ; i++ )
content [i] = x.content [i];
content [length] = '\0';
void MyString :: print( void )
cout <<""<< content << endl;
void MyString :: operator = ( const MyString x )
length = x.length;
content = new char [length];
for( int i = 0 ; i < length ; i++ )
content [i] = x.content [i];
content [length] = '\0';
int main()
MyString word1 ("***");
MyString word2;
word2 = word1;
word1.print();
word2.print();
我编译了它,这是我得到的:
堆栈溢出
堆栈溢出
进程返回 0 (0x0) 执行时间:0.050 s 按任意键继续。
虽然根据上面的结果看起来是正确的,但我想知道它真的正确吗?我对 C 风格的字符串不太熟悉,所以我很担心 例如关于行:
content [length] = '\0';
由于 C 风格的字符串末尾有空终止符,我想终止我的数组,但这是正确的方法吗? 我使用了动态内存分配,我也想知道我是否正确释放了资源? 是否有一些内存泄漏? 提前致谢。
编辑1: 我还重载了operator +(我想加入“MyStrings”),这里是代码:
MyString MyString :: operator + ( const MyString & x )
MyString temp;
temp.length = x.length + length;
temp.content = new char [ temp.length + 1 ];
int i = 0, j = 0;
while ( i < temp.length )
if (i < length )
temp.content [i] = content [i];
else
temp.content [i] = x.content [j];
j ++;
i ++;
temp.content [ temp.length ] = '\0';
return temp;
这里是主程序:
int main()
MyString word1 ( "stack" );
MyString word2 ( "overflow" );
MyString word3 = word1 + word2;
word3.print();
word3 = word2 + word1;
word3.print();
结果如下:
堆栈溢出
溢出堆栈
进程返回 0 (0x0) 执行时间:0.040 s 按任意键继续。
我希望这段代码没有问题:)
编辑2: 这是使用 for 循环而不是 while 的 + 运算符的实现:
MyString MyString :: operator + (const MyString & x)
MyString temp;
temp.length = x.length + length;
temp.content = new char [temp.length+1];
for( int i = 0 ; i < length ; i++ )
temp.content[i] = content[i];
for( int i = length , j = 0 ; i <temp.length ; i++, j++)
temp.content[i] = x.content[j];
content[temp.length] = '\0';
return temp;
现在可能更好,因为没有 if :)
【问题讨论】:
我希望这是一个作业。 C++ 世界最不需要的就是另一个字符串类。 别担心,这是作业:) 您对content [length] = '\0'
的分配超出了您的分配一个字符。如果要添加 NULL,则需要分配长度 + 1 个字符。
在我看来,BSTR 是一个很好的实现。如果我是你,我会寻找源头并仔细研究它。它在comutil.h
中声明,但我在这个系统上没有 C++,所以我不能更具体。
@etf:从 0 跑到 length。由于strlen
返回了一个有效值(或者您可以假设它确实如此),那么您知道末尾有一个 0。只需将其复制为字符串的终止符即可。
【参考方案1】:
您正在尝试为content[length]
分配一个值,但您没有为content[length]
分配足够的内存以供访问。如果length == 10
,那么您可以通过content[9]
访问content[0]
,但不能访问content[10]
。
当然,这可以通过从两个构造函数中删除行 content[length] = \0
来解决,或者如果您想附加 \0
,您应该将 length
的值增加 1
。
您是否考虑过只在内部使用std::string
?
编辑:@Thane Plummer 是第一个在 cmets 中指出这一点的人!
【讨论】:
【参考方案2】:还有一些其他的注意事项和建议,因为至少还有两个陷阱等着跳出来攻击。
#include <iostream>
#include <cstring>
// using namespace std; DANGER! namespace std is huge. Including all of it can
// have tragic, unforeseen consequences. Just use what you need.
using std::cout;
using std::endl;
class MyString
private:
char * content;
int length;
// will use clone to reduce duplication in the copy constructor and operator =
void copy(const MyString & source);
public:
MyString();
// it is nice to name the variables in the definition. The header may be the
// only documentation the user gets.
MyString(const char * source);
~MyString();
MyString(const MyString &source);
void print(void);
// changed prototype to match the expected format operator= format
MyString & operator =(const MyString &source);
//OP asked about this in a previous question.
friend std::ostream & operator<<(std::ostream & out,
const MyString& towrite);
;
MyString::MyString()
// content = 0;
//destructor needs something to delete[]. If content isn't set to something,
//you'll get a big ka-BOOM! when the MyString is destroyed
content = new char[1];
content[0] = '\0'; //this has the advantage of printing an empty MyString
// without crashing
length = 0;
MyString::MyString(const char *source) // Variable names should describe their purpose
//DANGER: strlen will fail horribly if passed an unterminated string. At a
// loss at the moment for a good, safe solution. Look into strnlen, but
// even it can't help you here.
length = strlen(source);
content = new char[length + 1]; //Needed one extra character to fit the NULL
/* If we got this far without dying, strcpy is no threat which makes this redundant:
for (int i = 0; i < length; i++)
content[i] = n[i];
content[length] = '\0';
*/
strcpy(content, source);
MyString::~MyString()
delete[] content;
// content = 0; string is gone. No need to clear this
void MyString::copy(const MyString & source)
length = source.length;
content = new char[length + 1];
// assuming that the source MyString is correctly formatted this is once again safe.
strcpy(content, source.content);
MyString::MyString(const MyString & source)
copy(source); // use the copy method
void MyString::print(void)
cout << "" << content << endl;
MyString &MyString::operator =(const MyString &source)
copy(source); // use the copy method again.
return *this; // allows chaining operations
std::ostream & operator<<(std::ostream & out,
const MyString& towrite)
out << towrite.content;
return out;
int main()
MyString word0;
MyString word1("***");
MyString word2;
word2 = word1;
MyString word3(word2); //testing copy constructor
word1.print();
word2.print();
cout << word3 << endl; //testing outstream overload
// test output of empty string
word0.print();
cout << word0 << endl;
编辑:
在发布后意识到,由于我们知道字符串的长度,因此使用 memcpy(content, source.content, length+1);
代替 strcpy
可以获得显着的性能提升。
【讨论】:
@etf 看到了。我上面提出的大部分观点也适用于它。研究 strcat 和 memcpy 而不是制作自己的复制循环。 再次感谢。我想我现在可以回到我之前的问题(之前的问题涉及学生和 Word 类):)【参考方案3】:有两个错误。 Thane Plummer 在 cmets 和 Tas 在答案中已经说明了这一点:
MyString :: MyString(const char *n)
length = strlen(n);
content = new char [length];
for( int i = 0 ; i < length ; i++ )
content [i] = x.content [i];
content [length] = '\0';
如果您的字符串是以空结尾的“abc\0”,strlen 将返回 3 而不是 4,因此您将只分配 3 个字符而不是 4(编辑:并且要完整,如前所述,您确实开始从 0 而不是 1 开始索引,所以 content[length] 总是会溢出,即使你增加了长度)
另一个错误不那么严重(实际上是合法的但奇怪的 c++):
void operator = ( const MyString );
复制赋值运算符应该采用 const 引用而不是 const 值(否则您可能会无用地调用复制构造函数),并返回引用而不是 void(以便您可以链接一些调用)。正确的声明是:
MyString& operator=(const MyString&);
正确的实现是:
MyString& MyString::operator=(const MyString& x)
length = x.length;
delete[] content;
content = new char [length];
for( int i = 0 ; i < length ; i++ )
content [i] = x.content [i];
// actually not needed since x.content should already be null-terminated
// content[length - 1] = '\0';
return *this;
【讨论】:
你的赋值操作符泄漏内存(删除之前的缓冲区失败) 你说得对,我没注意就复制了,我会改正的operator=
使用值很好,但正如您所说,返回引用比void
更好。要了解为什么使用值很好,请在此处检查“复制和交换”习语:***.com/questions/3279543/…
是的,但不是他的原始实现,在他理解为什么复制和交换工作正常之前,他必须了解值传递和引用传递之间的区别,以及它们的含义. (另外,它不适用于他最初指定的 const 值参数)
如果长度是字符串中的字符数,而不是实际分配的字符数(即多一个),则代码似乎是正确的。我会使用两个 for 循环(第二个使用“移位”索引)而不是使用 if 的一段时间,但这只是个人喜好,我不能肯定它会更有效。当然,如果您被允许使用它,2 memcpy 可能会更有效率(正如 user4581301 所说)。以上是关于C++ - 字符串类的实现的主要内容,如果未能解决你的问题,请参考以下文章