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++ - 字符串类的实现的主要内容,如果未能解决你的问题,请参考以下文章

C++——类的模拟实现

C++ string类的模拟实现

C++ string类的模拟实现

C++类的浅拷贝深拷贝以及写时拷贝问题

C++ String 类的简单实现

[ C++ ] string类常见接口及其模拟实现