目录
C语言中,字符串是以“\0”结尾的字符集合;C标准库提供了一些str库函数,但库函数与字符串是分开的,不符合OOP思想,且底层空间需要用户自己管理,容易越界访问;
注:编码格式,ASCII、UTF-8/16/32等;
一,标准库中的string类
string类
- string是利用动态数组实现的,是在堆上开辟的空间;
- string类是基于basic_string模板类的一个实例,使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数;
- string是标准的字符串类,其接口类似于标准字符容器的接口;提供了一系列成员函数和操作符函数,用于对字符串进行赋值、比较、查询、修改等操作;
- 不能操作多字节或变长字符的序列;
注:在使用string类时,必须包含#include<string>头文件及using namespace std;
二,string类的常用接口
构造函数
- string(),构造空的string类对象,即空字符串;
- string(const char*s),用C-string来构造string类对象;
- string(const string&s),拷贝构造函数;
- string(size_t n, char c),string类对象中包含n个字符c;
int main()
{
char str[] = "aba";
string s1; //默认无参数构造
string s2(str); //复制以空结尾的字符序列(C字符串)
string s3(s2); //拷贝构造,即复制
string s4(5, 'a'); //填充5个连续的a
}
容量操作函数
- size,返回字符串有效字符长度;
- length,返回字符串有效字符长度;
- capacity,返回空间总大小;
- reserve,为字符串预留空间,对成员变量capacity操作;
- resize,将有效字符的个数改成n个,多出的空间用字符c填充,对成员变量size操作;
- empty,检测字符串释放为空串,是true,否false;
- clear,清空有效字符,size为0,capacity不变;
注:
- size()与length()方法底层实现原理完全相同,引入size()的原因是与其他容器的接口保持一致,一般基本都使用size();
- resize(size_t n)与resize(size_t n, char c)都是将字符串中有效字符个数改变为n个,不同的是当字符个数增多时,前者用0来填充,后者用字符c来填充;resize改变元素个数时,增多可能会改变底层容量的大小,减少空间大小不变(vs/g++);
- clear()只是将string中有效字符清空,不改变底层空间大小;
int main()
{
string s("abcd");
cout << s.size() << endl;
cout << s.length() << endl;
s.resize(6);
cout << s.size() << endl;
s.resize(10, 'c');
cout << s << endl;
s.reserve(20);
cout << s.capacity() << endl;
}
访问及遍历操作函数
- operator[],返回pos位置的字符,const string类对象调用;
- begin+end,begin获取一个字符的迭代器,end获取最后一个字符下一个位置的迭代器;
- rbegin+rend,rbegin获取反向第一个字符的迭代器,rend获取反向最后一个字符下一个位置的迭代器;
- 范围for,C++11支持更简洁范围for的新遍历方式,支持迭代器就支持范围for;
int main()
{
string s("abcdefg");
for (int i = 0; i < s.size(); i++)
{
cout << s[i];
}
cout << endl;
//迭代器遍历
//string::iterator it = s.begin();
auto it1 = s.begin();
while (it1 != s.end())
{
cout << *it1;
it1++;
}
cout << endl;
//反向遍历
auto it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2;
it2++;
}
cout << endl;
//范围for遍历
for (auto i:s)
{
cout << i;
}
return 0;
}
修改操作函数
- push_back,字符串尾插字符c;
- append,字符串后追加一个字符串;
- operator+=,字符串后追加字符串str;
- c_str,返回c格式字符串('\0'结尾)指针,const指针,;
- find + pos,从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置;
- rfind,从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置;
- substr,在str中从pos位置开始,截取n个字符,将其返回;
- insert,插入字符串,少用,需挪动数据;
- erase,删除指定位置后的字符,npos(无符号-1,即最大值),也少用;
int main()
{
string s("abc");
s.push_back('d');
s.append("efg");
s += "hijk";
cout << s.c_str() << endl;
cout << s.find('c') << endl;
cout << s.find("hij", 2) << endl;
cout << s.substr(2, 3) << endl;
cout << s.insert(0, "ab") << endl;
s.reserve(20);
cout << s.erase() << endl; //size为0,capacity不变,类似clear
return 0;
}
非成员函数(即全局函数)
- operator+,尽量少用,因传值返回导致深拷贝效率低;
- operator>>,输入运算符重载;
- operator<<,输出运算符重载;
- getline,获取一行字符串;
- relational operators(<、>、=...),大小比较;
int main()
{
string s1;
cin >> s1;
cout << s1 << endl;
getline(cin, s1);
string s2 = "abcd";
getline(cin, s2); //会清除和覆盖之前内容
cout << s2 << endl;
return 0;
}
注:
- npos类静态成员变量,值为无符号-1,string::npos;
- vs编译器string类大小(即sizeof)为28, 因为默认有个buff数组(char _buf[16]),所以12+16;
- g++编译器string类大小(即sizeof)为8;
三,深/浅拷贝
经典string类问题
- s1调用默认拷贝构造,会导致是s1、s2共用同一块空间;
- 在释放时,会对同一块空间释放两次引起程序崩溃;
class string
{
public:
//构造函数
string(const char* str = "")
{
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;
};
int main()
{
string s1("abcd");
string s2(s1); //调用默认拷贝构造,值拷贝或浅拷贝
return 0;
}
浅拷贝
- 又称位拷贝,编译器会直接将对象的值拷贝过来;
- 如对象中管理资源,会导致多个对象共用一份资源,销毁释放时就会造成重复释放的问题;
深拷贝
- 会给每个对象独立分配资源,保证多个对象间不会因共享资源而造成多次释放;
- 如一个类中涉及到资源管理,其拷贝构造、赋值运算符重载、及析构函数都必须显示给出,一般按深拷贝方式提供;
//传统保守写法
//拷贝构造
string(const string& s)
:_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
//赋值运算符重载
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[]_str;
_str = tmp;
}
return *this;
}
//析构函数,因为新开了空间
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
}
}
//现代写法
//拷贝构造
string(const string& s)
:_str(nullptr)
{
//利用默认构造函数,申请新空间,然后交换
string strtmp(s._str);
swap(_str, strtmp._str);
}
//赋值运算符重载
string& operator=(string s)
{
//直接传值传参调用拷贝构造,然后交换
swap(_str, s._str);
return *this;
}
//赋值运算符重载
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// string strtmp(s._str);
// swap(_str, strtmp._str);
// }
// return *this;
//}
注:数组越界读一般检测不出来,越界写是抽查可能会检测出来;string都会被检测出来;
写时拷贝(了解)
- 即延时拷贝,在浅拷贝的基础上增加了引用计数的方式来实现;某个对象写入时,在深拷贝对象;
- 引用计数是用来记录资源使用者的个数,构造时计数加1,每增加一个对象使用该资源计数就在加1,当某个对象销毁时,计数就减1,如计数为1,说明该对象是资源的最后使用者,将资源释放;
注:vs是深拷贝,不是写时拷贝,g++是写时拷贝;
四,string类的模拟实现
namespace mystring
{
class string
{
public:
//迭代器iterator
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() { return _str; }
iterator end() { return _str + _size; }
const_iterator begin()const { return _str; }
const_iterator end()const { return _str + _size; }
public:
//默认构造
string(const char* str = "") //也可为"\0"
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//拷贝构造
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
//赋值运算符重载
string& operator=(string s)
{
swap(s);
return *this;
}
//析构函数
~string()
{
delete[]_str;
_str = nullptr;
}
//Capacity
void reserve(size_t newcapacity)
{
if (_capacity < newcapacity)
{
char* str = new char[newcapacity + 1];
strcpy(str, _str);
delete[] _str;
_str = str;
_capacity = newcapacity;
}
}
void resize(size_t newsize, char c = '\0')
{
if (newsize > _capacity)
reserve(newsize);
for (size_t i = _size; i < newsize; i++)
_str[i] = c;
_size = newsize;
_str[_size] = '\0';
}
//访问
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index) const
{
assert(index < _size);
return _str[index];
}
const char* c_str()const
{
return _str;
}
//修改
void push_back(char c)
{
//if (_size == _capacity)
//{
// size_t newcapacity = _capacity ? _capacity * 2 : 4;
// reserve(newcapacity);
//}
//_str[_size++] = c;
//_str[_size] = '\0';
insert(_size, c);
}
void append(const char* str)
{
//size_t len = strlen(str);
//if (_size + len > _capacity)
//{
// reserve(_size + len);
//}
//strcpy(_str + _size, str);
//_size += len;
insert(_size, str);
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(const string& s)
{
*this += s._str; //append(s._str);
return *this;
}
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity ? _capacity * 2 : 4;
reserve(newcapacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[end] = c;
++_size;
_str[_size] = '\0';
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 + 1;
while (end > pos)
{
_str[end + len - 1] = _str[end - 1];
--end;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
//查找
size_t find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
//kmp方法,bm方法
size_t find(char* substr, size_t pos = 0) const
{
const char* p = strstr(_str + pos, substr);
if (p == nullptr)
return npos;
else
return p - _str;
}
void clear()
{
_size = 0;
}
//交换
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
const static size_t npos;
private:
char* _str;
size_t _size;
size_t _capacity; //不包含'\0'
};
const size_t string::npos = -1;
//非成员函数重载
//+尽量少用
string operator+(const string& s, const char c)
{
string tmp = s;
tmp += c;
return tmp;
}
string operator+(const string& s, const char* str)
{
string tmp = s;
tmp += str;
return tmp;
}
string operator+(const string& s1, const string& s2)
{
string tmp = s1;
tmp += s2;
return tmp;
}
std::istream& operator>>(std::istream& in, string& s)
{
//in >> ch; //获取不到' '或'\0'
s.clear();
char c = in.get();
while (c != ' ' && c != '\n')
{
s += c;
c = in.get();
}
return in;
}
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
std::istream& getline(std::istream& in, string& s)
{
s.clear();
char c = in.get();
while (c != '\n')
{
s += c;
c = in.get();
}
return in;
}
bool operator>(const string& s1, const string s2)
{
size_t i1 = 0, i2 = 0;
while (i1 < s1.size() && i2 < s2.size())
{
if (s1[i1] > s2[i2])
return true;
else if (s1[i1] < s2[i2])
return false;
else
{
++i1;
++i2;
}
}
if (i1 == s1.size())
return false;
else
return true;
}
bool operator==(const string& s1, const string s2)
{
size_t i1 = 0, i2 = 0;
while (i1 < s1.size() && i2 < s2.size())
{
if (s1[i1] != s2[i2])
return false;
else
{
++i1;
++i2;
}
}
if (i1 == s1.size() && i2 == s2.size())
return true;
else
return false;
}
inline bool operator!=(const string& s1, const string s2)
{
return !(s1 == s2);
}
inline bool operator>=(const string& s1, const string s2)
{
return (s1 > s2 || s1 == s2);
}
inline bool operator<(const string& s1, const string s2)
{
return !(s1 >= s2);
}
inline bool operator<=(const string& s1, const string s2)
{
return !(s1 > s2);
}
}