昨天师兄又出了道测试题,让我们实现类似于string类的没有MyString类,刚开始很头疼,可是真正在自己写代码的时候又很兴奋的发现,这个过程真的是个很宝贵的机会,让我又有机会可以很好的熟悉回顾C++的很多知识—类设计,构造析构函数,成员函数,友元函数,引用,重载,字符串操作,动态内存分布。。。。。于是昨天花了半天时间写了300多行代码,并认真的进行了相关测试、修改和总结。因为内容有点丰富,所以想分几次写出来,条理也清楚些。
类的空间分配:类给它的每个对象都分配了独立的空间去存储它的数据成员,所有的对象公共的访问类方法进行操作。同时在对象的独立空间中,不包括数据成员动态分配的空间,对象只是记录了动态分配空间的地址(所以在析构函数调用的时候只是删除了对像空间,同时需要用new来删除动态分配的地址)。
一、类声明—mystring.h:
1. 构造函数:
专门用于构建新对象,给成员数据分配必要的内存空间并将值赋给新对象的成员数据。
默认构造函数:
在未提供显式初始化值时,被用来创建对象的构造函数(所以它一般没有参数)
MyString();
复制构造函数:
用于将一个对象复制到新创建的对象中(当然这个被复制的对象必须已经存在)。
MyString(const MyString &str);
给定了一定初始化参数的构造函数:
参数列表中的值会一次赋给新创建对象的各个成员函数:
MyString(const char*str);
2.析构函数:
当对象过期时删除对象所占的内存空间,并且当对象创建时有用New请求的内存空时,在析构函数中同时要调用delete对原来分配的 内存空间进行释放,以防止内存泄露。
~MyString();
3.成员函数:
重载赋值成员函数:
MyString &operator=(const MyString &str); //利用已有的string对象通过=给一个对象进行赋值 MyString &operator=(const char*str); //直接用常量字符串进行赋值
一般赋值函数:
MyString &assign(const MyString&str); MyString &assign(const char*sstr);
几个处理字符串的成员函数:
size_t getsize()const; //返回字符串大小 void clear(); //把字符串清空 bool empty(); //判断字符串是否为空 void swap(MyString &str); //交换两个字符串 int compare(const MyString &str)const; //比较2个字符串的大小 //第一个const说明显式调用的字符串不可更改,括号外面的const说明隐式调用的字符串不可更改,只读数据 int compare(const char*str);
追加函数:
MyString &operator+=(const MyString&str); MyString &operator+=(const char*str); MyString &append(const MyString&str); MyString &append(const char *str);
生成字串:
MyString substr(size_t pos = 0,n=npos) const;生成字串,从第0个位置开始长度为n,若N超过长度,则为输出整个字符串的长度
4.友元函数(运算符重载):
友元函数一般都是在类得声明中进行定义,它不属于类得成员函数,但是它和类得成员函数一样同样的可以对类得所有数据成员进行访问。
friend bool operator==(const MyString &str1,const MyString &str2); friend bool operator==(const char *str,const MyString &str2); friend bool operator==(const MyString &str1,const MyString *str2); friend bool operator>(const MyString &str1,const MyString &str2); friend bool operator>(const char*str1,const MyString &str2); friend bool operator>(const MyString &str1,const char*str2);
同样还有<等各种比较。
friend MyString operator+(const MyString &str1,const MyString &str2); friend MyString operator+(const char*str1,const MyString &str2); //两个字符串进行相加 friend MyString operator+(const MyString &str1,const char*str2); friend ostream & operator<<(ostream &os,const MyString &str); //输出命令符的重载
5.成员数据变量:
char *string; //指向字符串的指针 int length; //字符串的长度 static const int string_number = 0; //计数创建的字符串的数目
二、实现.cpp文件:
1.构造函数和析构函数:
MyString::MyString() { length = 0; string = new char; char *s = "/0"; memcpy(string,s,1); ++string_number; } MyString::MyString(const char*str) { length = strlen(str); string = new char(length+1); memcpy(string,s,length); ++string_number; } MyString::MyString(MyString &str) { length = str.length; string = str.string; ++string_number; } MyString::~MyString() { delete[]string; --string_number; }
几个注意的问题:
1)构造函数中必须给所有的数据成员进行初始化。
2)注意在给指向字符串的指针赋值时,左右类型的对应。
char *s代表一个指向字符串的指针,所有右边必须是一个字符串常量“/0”,而不能是‘/0'.
3)一个指针只能指向一个地址,不能同时指向两个。
在给string分配了地址之后,下一步我们肯定是确定分配的地址中存放的具体内容,那么这个时候我们都是使用strcpy()或者是
memcpy()把对应的字符串存入地址中。
如果原来我们成这样实现:
MyString::MyString() { length = 0; string = new char; string = "/0"; ++string_number; }
那么我们在编译和实现的时候都不会发现有什么错,但是析构函数使用delete【】释放内存使执行结果会出现乱码,因为string=“/0”
让它指向了一个字符串,并没有分配内存空间,所以在释放的时候就会出现错误。
4)析构函数中的重要语句 delete【】不要忘
析构函数在使用的时候只会释放为对象分配的空间,但是对象的空间中只是存储了数据成员分配内存的地址,所以并没有释放数据成员
的内存空间,必须使用delete[]来进行释放,防止内存泄露
2.重载运算符的成员函数:
MyString &MyString::operator+=(const MyString&str) { char *dest; dest = new char[str.length+length+1]; memcpy(dest,string,length); memcpy(dest+length,str.string,str.length+1); delete[]string; length = length+str.length; string = dest; return*this; } MyString &MyString::operator+=(const char*str) { char *dest; dest = new char[strlen(str)+length+1]; memcpy(dest,string,length); memcpy(dest+length,str,strlen(str)+1); delete[]string; string = dest; return *this; } //字符串赋值 MyString &MyString::operator=(const MyString&str) { if(&str == this) return *this; delete[]string; string = new char[str.length]; memcpy(string,str.string,str.length); length = str.length; return *this; }
注意的幾個問題:
1)+=运算中,调用函数的对象最终字符串长度肯定大于其原来的长度,所以在这个函数调用过程中,我们必须给字符串重新分配一块
两个字符串长度和大小的内存区域。但是因为函数返回值又必须是原调用对象的引用,所以我们要定义一个中间变量指针来存储2个
字符串合并结果,最后释放string指针原来的内存区域后,再让它指向合并字符串。
2)“=”赋值运算肯定和构造函数的初始化不一样。
在使用这个方法以前,那么对象肯定至少已经通过调用构造函数数据成员有了一定的值。所以这个时候我们首先判断两个对象是否
相等。相等的话返回原对象,如果不等:
那么两个对象的字符串长度肯定不同。所以我们先释放原字符串内存空间,然后根据赋值对象的字符串长度分配内存空间并把字符
串内存拷贝过去。
3.字符串处理的几个函数:
size_t MyString::getsize(MyString &str) { return strlen(str.string); } void MyString::clear() { length = 0; while(string!='/0') *string ='/0'; } bool MyString::empty() { return strlen(string)==0; } int MyString ::compare(const MyString &str) { return compare(string,str.string); } void MyString::swap(MyString &str) { char *temp; temp = string; string = str.string; str = temp; } Mystring MyString::substr(sizez_t pos=0,size_t n )const { MyString string; delete[]string.string; if(n>length) { string.length = length; string.string = new char[length+1]; memcpy(string.string,string,length+1); return string; } length = n; string.string = new char[length+1]; memcpy(string.string,string,length+1); return string; }
注意的几个问题:
1)在这几个函数的实现中,我们可以直接调用c语言中几个对字符串处理的<string.h>函数进行实现
2)clear()函数中注意,只是把每个内存区域的字符置为0,并不能通过delete[]来释放内存空间,这样很容易和析构函数一起造成
两次释放内存空间引起错误。
3)swap()实现交换的这个函数中,我们可以直接定义中间变量指针实现,不用重新分配内存空间全部进行转存,这个对于析构函数
的析构没有影响。
4.友元函数:
friend bool operator==(const MyString &str1,const MyString &str2) return strcmp(str1.string,str2.string)==0; friend MyString operator+(const MyString &str1,const MyString &str2) { MyString mystring; char *dest; dest = new char[str1.length+str2.length+1]; memcpy(dest,str1.string,str1.length); memcpy(dest+str1.length,str2.string,str2.length+1); delete[]mystring.string; mystring.string = dest; mystring.length = str1.length+str2.length; return mystring; } friend ostream &operator<<(ostream &os,const MyString &str) { os<<str.string; return os; }
注意的问题和上面差不多,这里就不重复了~~~
其他几个函数实现都基本雷同~