目录
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 string&s),拷贝构造函数;
- string(const char*s),用C-string来构造string类对象;
- string(size_t n, char c),string类对象中包含n个字符c;
int main()
{
char s[] = "abc";
string str1; //(1)默认无参数构造
string str2(s); //(4)复制以空结尾的字符序列(C字符串),类似string str2=s;
string str5(s, 2); //(5)复制字符串子串
string str3(str2); //(2)拷贝构造,即复制,或string str3=str2;
string str4(str2, 2); //(3)复制string类子串
string str6(5, 'a'); //(6)填充5个连续的a
string str7(str2.begin(), str2.end()); //(7)迭代器构造[begin,end)
string str8("abc");
string str9 = "abc"; //字符串字面量初始化,调用(4)类型构造函数(应先转换一临时对象在拷贝,编译器优化为直接构造,可使用explicit避免隐式类型转换)
string str10{ 'a','b','c' }; //(8)C++11引入的列表初始化,或string str10={'a','b','c'};
}
容量操作函数
size | 返回字符串有效字符长度,底层实现原理完全相同,引入size()的原因是与其他容器的接口保持一致,一般基本都使用size(); |
length | |
capacity | 返回空间总大小; |
reserve | 为字符串预留空间,对成员变量capacity操作; |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充,对成员变量size操作; resize(size_t n),resize(size_t n, char c)都是将字符串中有效字符个数改变为n个,不同的是当字符个数增多时,前者用0来填充,后者用字符c来填充; resize改变元素个数时,增多可能会改变底层容量的大小,减少空间大小不变(vs/g++); |
empty | 检测字符串是否为空串,是true,否false; |
clear | 清空有效字符,size为0,capacity不变; 只是将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位置的字符,重载运算符[ ],无边界检查比at快; |
at | 返回pos位置的字符,但会边界检查,超出会抛异常; |
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 | 追加字符串; |
assign | 赋新值以替代当前值; |
operator+= | 追加字符串或单个字符; |
c_str | 返回c格式字符串('\0'结尾)指针,const指针; |
substr | 在str中从pos位置开始,截取n个字符,并返回新对象; |
insert | 插入字符串或单个字符,少用,需挪动数据; |
replace | 替换字符串; |
swap | 交换字符串值; |
erase | 删除指定位置后的字符,npos(无符号-1,即最大值),也少用; |
find | 从字符串pos位置开始往后找字符c,返回该字符第一个位置; 或查找字符串; |
rfind | 从字符串pos位置开始往后找字符c,返回该字符最后一个位置; 或查找字符串; |
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共用同一块空间;
- 在释放时,会对同一块空间释放两次引起程序崩溃;
#include <iostream>
#include <assert.h>
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; }
private:
char* _str;
size_t _size;
size_t _capacity; //不包含'\0'
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;
};
//**********************************************
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);
}
}