【问题标题】:C++ - implementation of string classC++ - 字符串类的实现
【发布时间】: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 ("stackoverflow");
    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。只需将其复制为字符串的终止符即可。

标签: c++ arrays string implementation dynamic-memory-allocation


【解决方案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("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 可以获得显着的性能提升。

    【讨论】:

    • @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 更好。要了解为什么使用值很好,请在此处检查“复制和交换”习语:stackoverflow.com/questions/3279543/…
    • 是的,但不是他的原始实现,在他理解为什么复制和交换工作正常之前,他必须了解值传递和引用传递之间的区别,以及它们的含义. (另外,它不适用于他最初指定的 const 值参数)
    • 如果长度是字符串中的字符数,而不是实际分配的字符数(即多一个),则代码似乎是正确的。我会使用两个 for 循环(第二个使用“移位”索引)而不是使用 if 的一段时间,但这只是个人喜好,我不能肯定它会更有效。当然,如果您被允许使用它,2 memcpy 可能会更有效率(正如 user4581301 所说)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-28
    • 1970-01-01
    • 2012-10-17
    • 2014-12-28
    • 1970-01-01
    相关资源
    最近更新 更多