C++字符串类的封装你真的了解吗
目录
- 字符串类的封装
- 常规代码
- 头文件
- 函数实现文件
- Test文件
- 重载左移>>
- 重载右移<<
- 重载赋值=
- 重载中括号[ ]
- 重载加号+
- 重载==
- 总结
字符串类的封装
常规代码
头文件
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class MyString//字符串应该是维护着一个字符数组的,在堆区 { public: MyString(const char* str);//有参构造函数 MyString(const MyString& str);//拷贝构造函数 ~MyString();//析构函数(是需要的,因为这个类中还维护着一个指针,需要自己释放) private: char* pString;//维护在堆区开辟的字符数组 int m_Size;//字符串长度(不统计\0) };
在头文件中定义了MyString类中应该有的属性和三个函数。(有参构造函数接收字符串并创建对象)(拷贝构造函数可以接收同类型的对象并且复制出一个新的对象)(析构函数负责在释放掉本对象的同时释放掉对象中维护的指针)
函数实现文件
#define _CRT_SECURE_NO_WARNINGS 1 #include"myString.h"; //MyString str = "123"; MyString::MyString(const char* str)//有参构造函数,传进去的参数是一个字符串,返回的是MyString类型的对象。 { cout<<"MyString的有参构造函数调用"<<endl; this->pString = new char[strlen(str) + 1];//给本对象的pString准备空间。 strcpy(this->pString, str);//将传进来带的字符串的地址也传给本对象的pString处,str是字符串,不能调用pString属性。 this->m_Size = strlen(str); } MyString::MyString(const MyString& str)//拷贝构造函数 { //这个需要深拷贝,因为类中有个指针。注意:深拷贝是对指针进行的深拷贝,也就是会传进来个对象,然后给这个对象中的指针 分配传进来的对象中的指针的大小的空间,然后将这个值也赋值到本指针中。 cout << "MyString的拷贝函数调用" << endl; this->pString = new char[strlen(str.pString)+1]; strcpy(this->pString , str.pString); this->m_Size = str.m_Size; } MyString::~MyString()//析构(是需要的,因为这个类中还维护着一个指针,需要自己释放) { cout << "MyString的析构函数调用" << endl; if (this->pString != NULL) { delete[] this->pString; this->pString = NULL; } }
拷贝构造和有参构造的区别就是:
1,有参构造传进去的是一个字符串,返回成一个对象。所以要的是对象的各个属性与字符串的属性的匹配。
2,拷贝构造传进去的是一个对象,返回的也是一个对象,所以要的是传进来的对象和本对象(要创建的对象)之间属性的对应。
3,如果拷贝构造的时候发现需要拷贝个指针,那么就不能直接使用编译器的拷贝构造函数了,因为编译器的拷贝构造函数构造出来的对象的指针是和传进来的对象的指针是指向同一块地方的,那么等到需要释放指针的时候就会出现重释放的错误。**(这也是为什么需要自己重新创建拷贝构造的原因)**如果类中不需要维护指针,那么就不需要自己写拷贝构造(自己为指针再创建空间)。
Test文件
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include "myString.h"; int main() { MyString str = "abc";//调用默认构造 //和后面的一样MyString str("abc"); MyString str2 = str;//调用拷贝构造 return 0; }
注意,这里的“abc”就是传进来的字符串str,然后这里的str就是创建出来的MyString类型的对象。
运行的结果:
MyString的有参构造函数调用
MyString的拷贝函数调用
MyString的析构函数调用
MyString的析构函数调用
重载左移>>
如果想进行
cout<<str<<endl;
代码肯定会报错,因为str是MyString类型的对象,<<不认识这个。
所以,这个时候就需要重载一下<<左移运算符,可以在函数文件中实现。(需要用全局函数配合友元进行重载)
实现函数:
ostream& operator<<(ostream& cout, MyString& str) //重载左移运算符 { cout << str.pString;//这里的pString是类中的私有属性,所以需要在原类(在头文件中)中给整个重载函数设置友元 return cout; }
头文件:
class MyString { friend ostream& operator<<(ostream& cout, MyString& str);//设置的友元 public: private: };
这样cout << str << endl;
这行代码就可以调用了。
重载右移<<
如果想进行
cin>>str;
代码肯定也会报错,因为str是MyString类型的对象,>>不认识这个。
所以,这个时候就要重载一下>>右移运算符,可以在函数文件中实现。(需要用全局函数配合友元进行重载)
实现函数:
istream& operator>>(istream& cin, MyString& str)//重载右移运算符 { //应该先清空原来的堆区数据 if (str.pString)//这里的pString是对象中的私有属性,所以需要在原类中加上这个重载函数的友元声明 { delete[] str.pString; str.pString = NULL; } //不用急着直接将输入的内容传给pString,可以先开辟临时数组,记录着输入内容。 char buf[1024]; cin >> buf; //因为是自己重载的函数,刚才将str的pString删除了,现在需要重新申请空间。 str.pString = new char[strlen(buf) + 1]; strcpy(str.pString, buf); str.m_Size = strlen(buf);//别忘了还要把大小考进str的size中,因为传进来一串字符以后,对象中的长度还保持着原先的长度,所以需要进行修改。 cout << str.m_Size << endl; return cin; }
头文件:
class MyString//字符串应该是维护着一个字符数组的,在堆区 { friend istream& operator>>(istream& cin, MyString& str); public: private: };
然后就能给str的pString赋值了。
重载右移运算符的时候的清空原来字符串中的内容好像不太重要,删除了也能正常运行。创建临时数组记录(数组大小够大即可),然后将赋值,最后别忘了更改对象中的size。
重载赋值=
如果想进行:
str2 = str1
直接将两个对象进行=运行起来代码肯定会崩,因为:全拷贝了,删除对象的时候会出现浅拷贝的问题。
如果想进行:
str2 = “abc"
直接将字符串赋值给字符串肯定也是不行的。
所以需要重载两个不同参数的 = 运算符。(一种参数是对象,一种参数是字符串)
MyString& operator=(const MyString& str); MyString& operator=(const char* str);
返回值必须要是MyString& ,因为使用完=运算符要返回的是自身(str2). 注意在头函数中声明完了以后到实现文件中去实现的时候要写范围MyString::,而且这个类的范围需要写在返回值类型的后面,函数名的前面。
重载=运算符,与重载左移和右移运算符不同,不用再像<<和>>一样使用全局函数重载了,需要使用成员函数
头文件:
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class MyString { friend ostream& operator<<(ostream& cout, MyString& str); friend istream& operator>>(istream& cin, MyString& str); public: MyString(const char* str);//有参构造函数 MyString(const MyString& str);//拷贝构造函数 ~MyString();//析构(是需要的,因为这个类中还维护着一个指针,需要自己释放) //重载两个=运算符 MyString& operator=(const MyString& str); MyString& operator=(const char* str); private: char* pString;//维护在堆区开辟的字符数组 int m_Size;//字符串长度(不统计\0) };
实现文件:
#define _CRT_SECURE_NO_WARNINGS 1 #include"myString.h"; MyString & MyString::operator=(const MyString & str) { //先判断原堆区有没有内容,如果有先释放。 if (this->pString) { delete[]this->pString; this->pString = NULL; } //进行深拷贝 this->pString = new char[strlen(str.pString) + 1]; strcpy(this->pString, str.pString); this->m_Size = strlen(str.pString); return *this; } MyString & MyString::operator=(const char* str) { //先判断原堆区有没有内容,如果有先释放。 if (this->pString) { delete[]this->pString; this->pString = NULL; } //进行深拷贝 this->pString = new char[strlen(str) + 1]; strcpy(this->pString, str); this->m_Size = strlen(str); return *this; }
Test文件:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include "myString.h"; int main() { MyString str = "abc";//调用默认构造 MyString str2 = "bcd"; str = str2; MyString str3 = "abc"; cout << str << endl;//bcd cout << str3 << endl;//abc return 0; }
重载中括号[ ]
如果项进行
str2[0] = 'a';
是不可以的,因为[ ]不认识str。
所以需要重载中括号[]。直接使用成员函数进行重载。
//在头文件中: char operator[](int index); //在实现函数中: char MyString::operator[](int index)//重载中括号 { return this->pString[index]; }
正常来说就返回char类型的数值就行了,这样就可读了。
但是如果想将str2p[1]作为运算左值来修改,那么就需要返回本体char&
//在头文件中: char& operator[](int index); //在实现函数中: char& MyString::operator[](int index)//重载中括号 { return this->pString[index]; } //在Test文件中 int main() { MyString str2 = "bcd"; cout << str2[1] << endl;//c str2[1] = 'z'; cout << str2[1] << endl;//z return 0; }
重载加号+
如果想实现:
MyString str3 = "abc"; MyString str4 = "def"; MyString str5 = str3 + str4; MyString str6 = str5+"abc";
这样肯定会报错,因为+不认识对象,也不认识这样的字符串
所以,需要对+进行重载,使用的还是成员函数,只有一个参数。
从题意得,传进去一个对象,然后返回出一个对象,或者是传进去一个字符串,返回出一个对象。(前提是将第一个传进去的看作是调用对象)
头文件:
MyString operator+(const MyString& str); MyString operator+(const char* str);
实现文件:
//重载+运算符 MyString MyString:: operator+(const MyString& str) { //本身abc,传入的是def,刚开始应该先计算一下需要开辟的内存空间。 int newSize = this->m_Size + strlen(str.pString) + 1; char* temp = new char[newSize];//然后将这块空间开辟出来,temp指针指向它。 memset(temp, 0, newSize);//将空间里面的内容全部清空。 strcat(temp, this->pString);//将this->pString扔进了temp中。 strcat(temp, str.pString);//然后再将str字符串扔进去,这样它们就自己结合了。 //但是创建好的新的字符串不能直接返回,因为需要返回一个对象 //所以就创建一个新的对象,然后通过构造函数将字符串赋给新对象,最后再返回新对象。 MyString newString = temp; //还有一点,创建的类是空间temp用完了需要释放 delete[]temp; return newString; } MyString MyString:: operator+(const char* str) { //本身abc,传入的是def,刚开始应该先计算一下需要开辟的内存空间。 int newSize = this->m_Size + strlen(str) + 1; char* temp = new char[newSize];//然后将这块空间开辟出来,temp指针指向它。 memset(temp, 0, newSize);//将空间里面的内容全部清空。 strcat(temp, this->pString);//将this->pString扔进了temp中。 strcat(temp, str);//然后再将str字符串扔进去,这样它们就自己结合了。 //但是创建好的新的字符串不能直接返回,因为需要返回一个对象 //所以就创建一个新的对象,然后通过构造函数将字符串赋给新对象,最后再返回新对象。 MyString newString = temp; //还有一点,创建的类是空间temp用完了需要释放 delete[]temp; return newString; }
两种+重载函数几乎一样
TEST文件
int main() { MyString str3 = "abc"; MyString str4 = "def"; MyString str5 = str3 + str4; MyString str6 = str5 + "abc"; cout << str5 << endl;//abcdef cout << str6 << endl;//abcdefabc return 0; }
重载==
ps补充:strcmp函数中,如果两个字符串相等,那么就返回0,如果不相等,那么就返回1。
也是提供两种重载函数:
//头文件: //重载==运算符 bool operator==(const MyString& str); bool operator==(const char* str); //实现文件: //重载==运算符 bool MyString::operator==(const MyString & str) { if (strcmp(this->pString, str.pString) == 0) return true; else return false; } bool MyString::operator==(const char* str) { if (strcmp(this->pString, str) == 0) return true; else return false; } //Test文件 int main() { MyString str3 = "abc"; MyString str4 = "def"; MyString str5 = str3 + str4; cout << str5 << endl;//abcdef if (str5 == str5) { cout << "是相等的" << endl; } else { cout << "是不相等的" << endl; } //结果是相等的。 return 0; }
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!