1.实现string类的构造、拷贝构造、赋值运算符重载以及析构函数
一、string类的文档介绍
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>
string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
二、string类的常用接口说明
1.string类对象的常见构造
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
2.string类对象的容量操作
//1.string类对象支持cin cout进行输入输出
// size/clear/resize
void Teststring1()
{
// 注意:string类对象支持直接用cin和cout进行输入和输出
string s("hello, world!!!");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
s.clear();// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
cout << s.size() << endl;
cout << s.capacity() << endl;
s.resize(10, 'a'); // 将s中有效字符个数增加到10个,多出位置用'a'进行填充
cout << s.size() << endl; // “aaaaaaaaaa”
cout << s.capacity() << endl;
s.resize(15); // 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
cout << s.size() << endl; // "aaaaaaaaaa\0\0\0\0\0" 注意此时s中有效字符个数已经增加到15个
cout << s.capacity() << endl;
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl; }
void Teststring2()
{
string s;
// 测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl; }
// 利用reserve提高插入数据的效率,避免增容带来的开销
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
void TestPushBackReserve()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3. string类对象的访问及遍历操作
void Teststring()
{
string s1("hello World");
const string s2("Hello World");
cout<<s1<<" "<<s2<<endl;
cout<<s1[0]<<" "<<s2[0]<<endl;
s1[0] = 'H';
cout<<s1<<endl;
// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
void Teststring3()
{
string s("hello World");
// 3种遍历方式
// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
// 另外以下三种方式对于string而言,第一种使用最多
// 1. for + operator[]
for(size_t i = 0; i < s.size(); ++i)
cout<<s[i]<<endl;
// 2.迭代器
string::iterator it = s.begin();
while(it != s.end())
{
cout<<*it<<endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while(rit != s.rend())
cout<<*rit<<endl;
// 3.范围for
for(auto ch : s)
{
cout<<ch<<endl;
}
4. string类对象的修改操作
void Teststring()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'w'; // 在str后追加一个字符'w'
str += "orld"; // 在str后追加一串字符串"orld"
cout<< str <<endl;
cout<< str.c_str() <<endl; // 以C语言的方式打印字符串
// 获取file的后缀
string file1("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size()-pos));//substr取出字符串的子串
cout << suffix << endl;
// npos是string里面的一个静态成员变量
// static const size_t npos = -1; //相当于特别大的值 代表一直到结束
// 取出url中的域名
sring url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://"); //返回:前的位置
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);//从某个位置开始找 这里是从start的位置开始找 /
string address = url.substr(start, finish - start);
cout << address << endl;
// 删除url的协议前缀
pos = url.find("://");
url.erase(0, pos+3);
cout<<url<<endl;
}
5. string类非成员函数
6.string的使用
1.构造/拷贝
2.增->operator+= /insert
3.删->earse
4.查->find/rfind
5.改->operator[]
6.遍历-> size() + operator[] 迭代器遍历 范围for
7.输入输出 cin>> getline(当输入字符串包含空格时用它) cout<<
8.容量,增容规则 -> VS基本1.5倍增 g++基本2倍增 reserve+resize
9.c_str()会返回对象中指向字符数组的指针
10.重载了 > < == 的比较 按ascll码去比较
三、练习
1.反转字母
class Solution {
public:
string reverseOnlyLetters(string s)
{
//类似快排的单趟排序
int left = 0;
int right = s.size()-1; //因为right代表下标 所以要-1
while(left < right) //相遇之前结束
{
while(left < right && !isalpha(s[left])) //isalpha是判断谁不是一个字符的接口
{
++left; //不为字符的时候 往后继续走
}
while(left < right && !isalpha(s[right]))
{
--right; //不为字符的时候 往后继续走
}
swap(s[left] ,s[right] );
++left;
--right;
}
return s;
}
};
2.字符串中第一个唯一字符
class Solution {
public:
int firstUniqChar(string s)
{ //时间复杂度为O(N) 空间复杂度尾O(1)
//统计字符出现的次数
int CountArray[26] = {0} ; //记录字符出现的次数
int len = s.size();
for(size_t i = 0 ; i < len ; i++ )
{
CountArray[s[i] - 'a']++; //s[i] - 'a'的意思是找到字符在数组中的的相对位置
}
for(size_t i = 0 ; i < len ; i++ )
{
if(CountArray[s[i] - 'a'] == 1)//找第一个只出现一次的字符
{
return i ;
}
}
return -1;//表示没有找到
}
};
3.验证一个字符串是否是回文
class Solution {
public:
bool isLetterOrNumber(char ch)
{
return (ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z');
}
bool isPalindrome(string s) {
// 先小写字母转换成大写,再进行判断
for(auto& ch : s)
{
if(ch >= 'a' && ch <= 'z')
ch -= 32;
}
int begin = 0, end = s.size()-1;
while(begin < end)
{
while(begin < end && !isLetterOrNumber(s[begin]))
++begin;
while(begin < end && !isLetterOrNumber(s[end]))
--end;
if(s[begin] != s[end])
{
return false;
}
else
{
++begin;
--end;
}
}
return true;
}
};
4.字符串相加
class Solution {
public:
string addStrings(string num1, string num2)
{
string retStr;
int end1 = num1.size()-1;
int end2 = num2.size()-1;
int next = 0; // 进位
while(end1 >= 0 || end2 >= 0)
{
int val1= 0 ;
if(end1 >= 0)
val1 = num1[end1] - '0';//原本这个位置上的这个值时一个字符中的0-9 会对应相应的
//ASCLL 要转换成数组中的0-9所以-‘0’
int val2 = 0;
if(end2 >= 0 )
val2 = num2[end2] - '0';
int ret = val1 + val2 + next;
if(ret > 9)
{
ret -= 10 ;
next = 1;
}
else
{
next = 0;
}
retStr += ('0'+ret);
//retStr.insert (retStr.begin() , '0' + ret );
--end1;
--end2;
}
if(next == 1)
retStr += '1';
reverse(retStr.begin(),retStr.end());
//retStr.insert (retStr.begin() , '1' );
return retStr;
}
};
四、string类的模拟实现
1.实现string类的构造、拷贝构造、赋值运算符重载以及析构函数
class string
{
public:
/*string()
:_str(new char[1])
{*_str = '\0';}
*/
//string(const char* str = "\0") 错误示范
//string(const char* str = nullptr) 错误示范
string(const char* str = "") //注意这里要给上缺省值 很重要
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if(nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void Teststring()
{
string s1("hello !!!");
string s2(s1);
}
这里引入了深浅拷贝的概念
2.浅拷贝:
string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构
造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块
空间被释放多次而引起程序崩溃
3.深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情
况都是按照深拷贝方式提供。
4.传统版本和现代版本的string类
class string
{
public:
string(const char* str = "") //const的构造函数
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if(nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s) //非const的构造函数
: _str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
string& operator=(const string& s)
{
if(this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
char& operator[](size_t pos)
{
return _str[pos];
}
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
class string
{
public:
string(const char* str = "")
{
if(nullptr == str)
str = "";
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
string(const string& s)
: _str(nullptr)
{
string strTmp(s._str);
swap(_str, strTmp);
}
// 对比下和上面的赋值那个实现比较好?
string& operator=(string s)
{
swap(_str, s._str);
return *this;
}
/*
string& operator=(const string& s)
{
if(this != &s)
{
string strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
5.string类增删查改的模拟实现
namespace hdzc
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// s1(s2)
string(const string& s)
{
// +1是开空间时永远保持给\0多开一个
_str = new char[s._size + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._size;
/*_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;*/
}
// s1 = s2
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._size + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._size;
}
return *this;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* newstr = new char[n + 1];
strcpy(newstr, _str);
delete[] _str;
_str = newstr;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n < _size) // 删除数据
{
_size = n;
_str[_size] = '\0';
}
else // 插入数据
{
// 空间不够先增容
if (n > _capacity)
reserve(n);
for (size_t i = _size; i < n; ++i)
_str[i] = ch;
_size = n;
_str[_size] = '\0';
}
}
// s1.push_back('x');
void push_back(char ch)
{
// 增容
if (_size == _capacity)
{
if (_capacity == 0)
{
reserve(2);
}
else
{
reserve(_capacity * 2);
}
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
// s1.append("xxxxxxxxxx");
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
reserve(_size + len);
strcpy(_str + _size, str);
_size += len;
}
// s1 += 'x'
string& operator+=(char ch)
{
this->push_back(ch);
return *this;
}
// s1 += "xxxxx"
string& operator+=(const char* str)
{
this->append(str);
return *this;
}
string& insert(size_t pos, char ch)
{
assert(pos < _size);
if (_size == _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos < _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size;
while (end >= pos)
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (_size - pos <= len)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
size_t find(char ch, size_t pos = 0)//找单个字符
{
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)//找一个字符串
{
const char* p = strstr(_str + pos, str);
if (p == nullptr)
return npos;
else
return p - _str;
}
// s1 < s2
// 实现这两个,其他的比较复用实现
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;//比较当前字符的ASCLL码
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;//比较当前字符的ASCLL码
}
bool operator<=(const string& s)
{
return *this < s || *this == s;//比较当前字符的ASCLL码
}
bool operator>(const string& s)
{
return !(*this <= s);//比较当前字符的ASCLL码
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator!=(const string& s)
{
return !(*this == s);
}
private:
char* _str;
size_t _size;
size_t _capacity;
static size_t npos;
};
size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator >> (istream& in, string& s)
{
while (1)
{
char ch = in.get();
if (ch == ' ' || ch == '\n')
break;
else
s += ch;
}
return in;
}
}