C++实现MyString的示例代码
作者:悲伤土豆拌饭
MyString的构造、析构、拷贝构造、赋值运算
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1];//strlen()计算至'\0'截至的字符数 strcpy(str, p); } else { str = new char[1]; //额外提供一个空间 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重载插入操作符 { if (str != NULL) { out << str; } return out; } String(const String& s):str(NULL) { //str = s.str; 浅拷贝 是同一个空间,会造成一个空间释放两次 //深拷贝 str = new char[strlen(s.str)+1]; strcpy(str, s.str); } String& operator=(const String& s) { if(this != &s) { delete[]str; str = new char[strlen(s.str)+1] strcpy(str,s.str); } return *this; } }; ostream& operator<<(ostream& out, const String& s) { s << out; //s.operator<<(cout); //operator<<(&s1,cout); return out; } int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); cout << s1 << endl; //operator<<(cout, s1); }
前面之所以对空指针构建对象提供一个空间的原因:使其在赋值重载中只有指向堆区一种情况进行处理
通过此方式进行等号运算符重载,然后调动拷贝构造对s2进行重写构造
输出流重写
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //额外提供一个空间 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重载插入操作符 { if (str != NULL) { out << str; } return out; } }; int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); }
在这里通过改写前的代码 operator<<(&s1,cout);
不难看出,将cout初始化out,随后将this.str输出至out
ostream& operator<<(ostream& out)const
此处只能使用引用,因为cout在ostream类中进行转移,该类将拷贝构造函数定义为保护访问属性,无法使用cout初始化out,继而只能使用引用;同样若我们不想使用实参去初始化形参,可以将拷贝构造函数定义为私有或保护类型
若希望输出符合cout << s1 << endl;
此种形式,需要再写一个全局函数
class String { char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //额外提供一个空间 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } //ostream& operator<<(const String* const this, ostream &out) ostream& operator<<(ostream& out)const //重载插入操作符 { if (str != NULL) { out << str; } return out; } }; ostream& operator<<(ostream& out, const String& s) { s << out; //s.operator<<(cout); //operator<<(&s1,cout); return out; } int main() { String s1("123"); s1 << cout; //s1.operator<<(cout); //operator<<(&s1,cout); cout << s1 << endl; //operator<<(cout, s1); }
通过此种形式进行翻转,继而达到符合 cout << s1 << endl;
的形式
MyString加号运算符重载
int main() { String s1("123"); String s2("456"); String s3; s3 = s1 + s2; S3 = s1 + "789"; s3 = "789" + s1; }
分别写三个加号运算符重载,来对应上面的三个情况(类+类、类+字符串、字符串+类)
String operator+(const String& s)const { char *p = new char(strlen(this->str) + strlen(s.str) + 1); strcpy(p, this->str); strcat(p, s.str); return String(p); }
第一个为成员函数,但是存在内存泄漏,需要进行下面的步骤
在私有成员变量中,创建一个新的构造函数,直接将p给到str,而没有创建新的空间;并且在加号运算符重载进行修改使其调用私有的构造函数
private: String(char*p,int)//两个参数与公有构造区分 { str = p; } public: String operator+(const String& s)const { char *p = new char(strlen(this->str) + strlen(s.str) + 1); strcpy(p, this->str); strcat(p, s.str); return String(p,1); }
这样就解决了原本内存泄漏的问题
接下来完成剩余两个等号运算符重载
String operator+(const char* s)const { char* p = new char(strlen(this->str) + strlen(s) + 1); strcpy(p, this->str); strcat(p, s); return String(p, 1); //return *this + String(s) //上面的方式更方便,但是会构造两个临时对象 }
此处需要写在类外,并且需要类内添加友元函数
friend String operator+(const char* t, const String s);
String operator+(const char* t, const String s) { char* p = new char(strlen(s.str) + strlen(t) + 1); strcpy(p, s.str); strcat(p, t); return String(p, 1); //return String(p) + s; 与上面同理,并且不需要友元函数 }
讨论一个衍生问题
class String { private: char* str; public: String(const char* p = NULL) :str(NULL) { if (p != NULL) { str = new char[strlen(p) + 1]; strcpy(str, p); } else { str = new char[1]; //额外提供一个空间 *str = '\0'; } } ~String() { if (str != NULL) { delete[] str; } str = NULL; } String(const String& s) { //str = s.str; 浅拷贝 是同一个空间,会造成一个空间释放两次 //深拷贝 str = new char[strlen(s.str)]; strcpy(str, s.str); } String& operator=(const String& s) { if (this != &s) { delete[]str; str = new char[strlen(s.str)]; strcpy(str, s.str); } return *this; } }; String fun() { String s2("456"); return s2; } int main() { String s1; s1 = fun(); return 0; }
讨论此程序执行的过程总共创建了多少个对象:
主函数运行首先开辟main函数栈帧,创建s1对象,默认构造只有大小为一的空间存放“\0”;之后执行fun()函数,分配fun栈帧,然后创建s2对象,创建一个堆区,str指向堆区空间;并且将按值返回,需要构建一个临时对象(将亡值);
将亡值概念:表达式过程中所产生的不具有名字的一个实体,叫做将亡值;将亡值的生存期仅在表达式的调用过程中,表达式调用结束,将亡值就会结束
构建临时对象调用拷贝构造,fun函数结束,s2生存期结束,调动析构函数;首先释放s2调用资源,再归还s2空间;回到主函数,把将亡值赋值给s1调用赋值语句,接着调用将亡值的析构函数进行释放
这个过程中总共创建了三个对象,分别是s1、s2、将亡值对象
那么如果我们对fun以引用进行返回
String& fun() { String s2("456"); return s2; } int main() { String s1; s1 = fun(); return 0; }
当以引用返回,就不会返回一个s2的备份,从引用底层来看会返回s2的地址;这样会从一个已死亡对象来获取数据,继而会得到随机值
随后介绍的右值拷贝构造与右值赋值语句可以解决这个问题
到此这篇关于C++实现MyString的示例代码的文章就介绍到这了,更多相关C++ MyString内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!