C ++ - 字符串类的实现

时间:2015-07-30 01:07:23

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

我试图实现字符串类。这就是我所做的:

#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秒 按任意键继续。

虽然根据上面的结果看起来是正确的,但我想知道它是否真的正确?我不太熟悉C风格的字符串,所以我很担心 例如关于line:

content [length] = '\0';

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

EDIT1: 我也重载了opeartor +(我想加入&#34; MyStrings&#34;),这里是代码:

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();
   }

结果如下:

计算器

overflowstack

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

我希望这段代码没有问题:)

EDIT2: 这是使用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:)

3 个答案:

答案 0 :(得分:3)

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

这可以通过从两个构造函数中删除行content[length] = \0来修复,或者如果要追加\0,则应该length增加1的值

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

编辑:@Thane Plummer首先在评论中指出这一点!

答案 1 :(得分:2)

有两个错误。 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] = '\0';
}

如果你的字符串是空终止&#34; abc \ 0&#34;,strlen将返回3而不是4,所以你只会分配3个字符而不是4个字符(编辑:并且要完成,如前所述,你确实开始从0而不是1开始索引,因此即使你增加长度,内容[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;
}

答案 2 :(得分: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会有显着的性能提升。