C++-字符串类的实现

C++ - implementation of string class

本文关键字:实现 字符串 C++-      更新时间:2023-10-16

我正在尝试实现字符串类。以下是我所做的:

#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] = '';
    }

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] = '';
}
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] = '';
}
int main() {
    MyString word1 ("stackoverflow");
    MyString word2;
    word2 = word1;
    word1.print();
    word2.print();
}

我编译了它,这就是我得到的:

堆叠式

堆叠式

进程返回0(0x0(执行时间:0.050秒按任意键继续。

虽然从上面的结果来看它看起来是正确的,但我想知道它真的正确吗?我对C型字符串不太熟悉,所以我很担心例如关于线路:

content [length] = '';

由于C样式字符串的末尾有null终止符,我想终止我的数组,但这是正确的方法吗?我使用了动态内存分配,我还想知道我是否正确地释放了资源?是否有内存泄漏?提前谢谢。

第1版:我还重载了opeartor+(我想加入"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 ] = '';
    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秒按任意键继续。

我希望这个代码没有问题:(

第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] = '';
    return temp;
}

现在可能更好了,因为没有if:(

您正试图为content[length]分配一个值,但尚未为访问content[length]分配足够的内存。如果是length == 10,则可以访问content[0]content[9],但不能访问content[10]

当然,这可以通过从两个构造函数中删除行content[length] = 来解决,或者如果要追加,则应将length的值增加1

您是否考虑过只在内部使用std::string

编辑:@Thane Plummer是第一个在评论中指出这一点的!

其他一些注意事项和建议,因为至少还有两个gotcha在等待跳出并罢工。

#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] = ''; //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] = '';
*/
    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("stackoverflow");
    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可以显著提高性能。

有两个错误。Thane Plummer在评论中和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] = '';
}

如果您的字符串是以null结尾的"abc\0",strlen将返回3而不是4,因此您将只分配3个字符而不是4(编辑:为了完整,如前所述,您确实从0而不是1开始索引,因此content[length]将始终溢出,即使您增加长度(

另一个错误不那么严重(实际上是合法的,但很奇怪(:

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] = '';
  return *this;
}