string
是表示字符串
的字符串类
string
类的接口与常规容器的接口基本相同,在添加一些专门用来操作string
的常规接口basic_string
模板类的别名。typedef basic_string string
。string
类不能操作多字节或者变长字符序列
string
类的时候,必须包含头文件string
以及using namespace std
。string()
:构造空的string类对象,既空字符串string(const char* s)
:使用C语言的字符串构造string类对象string(size_t n,char c)
:构造后的string类对象包括n个字符cstring(const string& s)
:用string类对象s拷贝构造另一个对象string(const string& s, size_t n)
:使用对象s中的第n个字符开始构造新的string类对象int main()
{
string s1; // 构造空的string类对象s1
string s2("hello,world"); //使用C字符串构造
string s3(10, 'a'); // 10个a构造对象
string s4(s2); //拷贝构造
string s5(s2, 9);//从s2对象下标为9的位置开始构造
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
cout << s3.c_str() << endl;
cout << s4.c_str() << endl;
cout << s5.c_str() << endl;
return 0;
}
size_t size() const
:返回字符串有效字符长度size_t length() const
:返回字符串有效的长度size_t capacity() const
:返回空间总大小,既容量bool empty() const
:判断字符串是否为空串,是返回true,不是返回falsevoid clear()
:清空有效的字符void resize(size_t n,char c)
:将有效字符的个数改成n个,多出来的空间用0填充void reserve(size_t res_arg = 0)
:修改string类的容量,为字符串预留空间void test1()
{
string s("hello,world");
cout << s.length() << endl; // 11
cout << s.size() << endl; // 11
cout << s.capacity() << endl;
// 15 ,capacity是15但是实际上开辟了16个char的空间,最后还有`\0`
cout << s << endl;//string类对象支持直接用cin和cout进行输入输出
//将对象s中的字符串清空,只改变有效数据的大小,不改变容量的大小
s.clear();
cout << s.size() << endl; // 0
cout << s.capacity() << endl; // 15
// 将s中有效字符个数增加到10个,多出位置用'j'进行填充
s.resize(10, 'j');
cout << s.size() << endl; // 10
cout << s.capacity() << endl; // 15
cout << s << endl; // jjjjjjjjjj
//将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
s.resize(15);
cout << s.size() << endl; // 15
cout << s.capacity() << endl; // 15
cout << s << endl; // jjjjjjjjjj\0\0\0\0\0
//将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl; // 5
cout << s.capacity() << endl; // 15
cout << s << endl; // jjjjj
cout << "--------------" << endl;
//测试reserve
string s2("hehe");
//reserve不会改变string中有效元素个数
cout << s2.size() << endl;//4
cout << s2.capacity() << endl;//15
s2.reserve(100);
cout << s2.size() << endl;//4
cout << s2.capacity() << endl;//111
//当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小
s2.reserve(50);
cout << s2.size() << endl; // 4
cout << s2.capacity() << endl; // 111
}
关于容量操作需要注意的一些地方:
size()
与length()
方法底层实现原理完全相同,引入size()
的原因是为了与其他容器的接口保持一 致
,一般情况下基本都是用size()
clear()
只是将string
中有效字符清空,不改变底层空间大小
resize(size_t n)
与 resize(size_t n, char c)
都是将字符串中有效字符个数改变到n
个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间
。注意:resize
在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变reserve(size_t res_arg=0)
为string
预留空间,不改变有效元素个数
,当reserve的参数小于 string的底层空间总大小
时,reserve
不会改变容量大小char& operator[] (size_t pos)
:返回pos
位置的字符,也可以修改const char& operator[] (size_t pos)
:返回pos位置字符,只能访问不能修改void test1()
{
string s1("hello");
const string s2("world");
cout << s1[0] << " " << s2[0] << endl; //h w
s1[0] = 'x';
// s2[0] = 'x';这个不能修改,因为它是const修饰的对象不能修改
cout << s1[0] << endl;// x
}
void push_back(char c)
:在字符串尾插入字符c
string& append(const char* s)
:在字符串后边追加一个C语言字符串string& operator+=(const string& str)
:在字符串后边追加string类字符串string& operator+=(const char* s)
:在字符串后边追加C语言字符串string& operator+=(char c)
:在字符串后追加字符cconst char* c_str() const
:返回C格式的字符串,既char*
size_t find(char c,size_t pos=0) const
:从字符串pos
位置开始向后找字符c
,找到返回该字符在字符串中的位置,找不到返回-1
size_t rfind(char c, size_t pos = npos)
:从字符串pos位置开始向前查找字符c
,返回该字符在字符串中的位置string substr(size_t pos=0,size_t n=npos) const
:在str中从pos位置开始,截取n个字符,然后将其返回string& erase (size_t pos = 0, size_t len = npos)
:删除从pos开始向后的len个字符void test2()
{
string str("hello");
str.push_back('C');//在str后边追加字符C
cout << str << endl;
str.append(" world");//在str后边追加字符串
cout << str << endl;
str += 'X'; //str后边追加字符X
cout << str << endl;
str += "xxx";//str后边追加字符串xxx
cout << str << endl;
cout << str.c_str() << endl;//以C语言形式打印字符串
}
// npos是string里面的一个静态成员变量
// static const size_t npos = -1;
void test3()
{
string file("test.cpp");
size_t pos = file.rfind('.');
if (pos == string::npos)
{
cout << "文件不存在后缀" << endl;
}
else{
cout << file.substr(pos, file.size() - pos) << endl;
}
}
void test4()
{
string url("http://www.baidu.com/");
size_t start = url.find("://");
if (start == string::npos){
cout << "url error" << endl;
return;
}
//find找不到返回-1,但是用size_t接收finish是一个很大的数字
size_t finish = url.find('/', start + 3);
cout << url.substr(start + 3, finish - start - 3) << endl;
//删除url的前缀
int pos = url.find("://");
url.erase(0, pos + 3);
cout << url << endl;
}
如果存在多次插入的操作,我们可以利用reserve提高插入数据的效率,避免增容带来的开销。
//增容4次
void test5()
{
string s;
int sz = s.capacity();
for (int i = 0; i < 100; ++i)
{
s += 'x';
if (sz != s.capacity())
{
cout << s.capacity() << endl;
}
}
}
//没有增容
void test5_op()
{
string s;
s.reserve(100);
int sz = s.capacity();
for (int i = 0; i < 100; ++i)
{
s += 'x';
if (sz != s.capacity())
{
cout << s.capacity() << endl;
}
}
}
string
尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'
三种的实现方式差不多
,一般情况下string
类的+=
操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
string
操作时,如果能够大概预估到放多少字符
,可以先通过reserve
把空间预留好,这样可以减少调用扩容函数增加的额外开销
operator+
:一个字符串加一个字符串,需要要另一个字符串接收返回值s=s1+s2
operator>>
:输入运算符重载operator<<
:输出运算符重载getline
:获取一行的字符串relational operators
:比较两个字符串的大小迭代器--不常用,跟其他容器保持统一的访问方式
void test()
{
string num("1234");
/*迭代器,底层类似指针
用来遍历string,访问容器
迭代器是string类中的内部类型,是typedef出来的
begin指向第一个位置,end指向最后一个数据的下一个迭代器 [)左闭右开区间
迭代器给出一个统一的方式访问容器,不需要关心容器底层的细节*/
string::iterator it = num.begin();
int res = 0;
while (it != num.end()){
res *= 10;
res += (*it) - '0';
++it;
}
cout << res << endl;
//vector为类名,vector为类型
vector<int> v;
v.push_back(1);
v.push_back(2);
vector<int>::iterator vit = v.begin();
while (vit != v.end()){
cout << *vit << " ";
++vit;
}
cout << endl;
}
void test1()
{
//反向迭代器
string num("1234");
string::reverse_iterator rit = num.rbegin();//rbrgin--4,rend--1
while (rit != num.rend()){
cout << *rit << " ";
(*rit) += 1; //迭代器遍历可以修改
++rit;
}
cout << endl;
}
int StrToNum(const string& str)
{
//const修饰的迭代器只能读不能写
int num = 0;
string::const_iterator it = str.begin(); //begin存在重载函数-const修饰的迭代器
while (it != str.end())
{
num *= 10;
num += (*it) - '0';
++it;
//(*it) = 3;错误不能修改
}
cout << num << endl;
return num;
}
for+下标--更加常用 (sring\vector\双端队列支持operator[],可以像数组一样访问)
void test2()
{
//for + 下标
string s1("hello");
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
}
语法糖C++11,新式for循环
void test3()
{
string str("2345");
int res = 0;
for (auto e : str)
{
res *= 10;
res += (e - '0');
}
cout << res << endl;
}
浅拷贝
:也称值拷贝
,编译器只是将对象中的值拷贝过来
。如果对象中存在资源(堆上的内存)
,最后就会导致多个对象共享同一份资源
,当一个对象销毁时就会将该资源释放掉
,而此时另一些对象不知道该资源已经被释放
,以为还有效,所以当继续对资源操作时,就会发生发生了非法访问
。
深拷贝
:如果一个类中涉及到资源
的管理,我们必须给每个对象独立的分配资源
,保证多个对象之间不会因为共享资源造成多次释放的程序崩溃问题
。一个类中如果涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数
必须要显式给出。一般情况都是按照深拷贝
方式提供。
/**
* 模拟实现string类的构造、拷贝构造、赋值运算符重载
*
*/
class String
{
public:
//构造函数
String(char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言
if (str == nullptr)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
//拷贝构造(深拷贝)
String(const String& s)
:_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
//赋值运算符重载
String& operator=(const String& s)
{
if (this != &s)//防止自己给自己赋值
{
delete[] this->_str; // s2的空间比当前空间大,所以要先释放s1既当前对象,
//然后在开辟和s2一样大的空间进行拷贝
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;//连续赋值
}
private:
char* _str;
};
/**
* 模拟实现string类的构造、拷贝构造、赋值运算符重载
*
*/
cl
class String
{
public:
//构造函数
String(char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言
if (str == nullptr)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
//拷贝构造(深拷贝)
String(const String& s)
:_str(nullptr)
{
String tmp(s._str);//复用构造函数
swap(_str, tmp._str);
}
//赋值运算符重载
String& operator=(String s) //传值过程中存在一份拷贝构造
{
swap(_str, s._str);
return *this;
}
private:
char* _str;
};
写时拷贝
就是一种拖延症
,是在浅拷贝的基础之上增加了引用计数
的方式来实现的。如果以只读的方式使用资源,那么就直接在源资源上加引用。但是如果使用只写的方式使用资源,那么就拷贝一份资源,然后使用。
引用计数
:用来记录资源使用者
的个数。在构造
时,将资源的计数给成1
,每增加一个对象使用该资源,就给计数增加1
,当某个对象被销毁
时,先给该计数减1
,然后再检查是否需要释放资源
,如果计数为1
,说明该对象时资源的最后一个使用者,将该资源释放
,否则就不能释放
,因为还有其他对象在使用该资源
。