标准库中的string

字符串容量头文件:<string>

string本质上就是字符顺序表;

class string
{
private:
	char* str;
	size_t _size;
	size_t capacity;
};

1. string类对象的常见构造 

(constructor) 函数名称
功能说明
string() (重点)
构造空的 string 类对象,即空字符串
string(const char* s) (重点)
C-string 来构造 string 类对象
string(size_t n, char c)
string 类对象中包含 n 个字符 c
string(const string&s) (重点)
拷贝构造函数

 无参:空的string

string s1;

带参:用一个常量字符串初始化string

string s1("hello world");

拷贝构造是深拷贝

string s3(s2);

2. string类对象的容量操作

C++为string提供了许多接口

函数名称
功能说明
size (重点)
返回字符串有效字符长度
length
返回字符串有效字符长度
capacity
返回空间总大小
empty (重点)
检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点)
清空有效字符
reserve (重点)
为字符串预留空间(提前开辟)
resize (重点)
将有效字符的个数该成 n 个,多出的空间用字符 c 填充
size: 

返回字符串的长度(以字节为单位);
这是符合字符串内容的实际字节数,不一定等于其存储容量;(省去了"\0")

int main()
{
	string s("hello world");
	cout << s.size() << endl;//11,不算"\0"
	return 0;
}
length:

和size基本一样,但是size能包含的内容更大;

max_size

最大的size大小(不同容器,不同平台不同);

capacity:

(容量:开的空间)

返回已分配存储的大小

返回当前为字符串分配的存储空间的大小,以字节为单位表示;
容量不一定等于字符串长度。它可以相等或更大,当向字符串添加新字符时,额外的空间允许对象优化其操作;(显示的是有效字符个数,不算"\0")

接下来,我们看看在VS下是如何扩容的:

void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "first_capacity:" << sz << endl;
	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 << endl;
		}
	}
}

在VS中设计:当数据个数小于16时,会先存在内部的一个_buff里边 (VS防止小块内存在堆上)

微软工程师实现:

class string
{
private:
	char _buff[16];//n<16,这也是为什么第一次按2倍扩的原因
	char* str;//n>=16时,_buff就废弃了,全部开在了str指向的堆上
	size_t _size;
	size_t capacity;
};

 但是在不同平台有着不同的实现方式:

g++string的结构(标准的2倍扩容,没有_buff)

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

空间总大小

字符串有效长度

引用计数

指向堆空间的指针,用来存储字符串

struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};

 频繁的扩容是代价的,拷贝数据-释放空间,会降低效率,那我们如何避免扩容,减少扩容呢?

解决:reserve,resize

reserve:

请求将字符串容量调整为计划的大小更改,则该函数会导致容器将其容量增加到 n 个字符(或更大);

在所有其他情况下,它被视为一个非约束性请求,以缩小字符串容量:容器实现可以自由地进行优化,并使大于 n;(节省空间,但一般不会缩容(根据不同平台:VS不缩容(其实_buff就可以看出来了),g++缩容),因为缩容是要付出代价的,因为待会需要空间的话还要继续扩)

参考:典型的缩容:shrink_to_fit :capacity减到size

此函数对字符串容量没有影响,并且不能更改其内容;

reserve开的大小不包含\0,实际上最少要开n+1;

作用:提前开空间,避免扩容,提高效率

void TestPushBack()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();
}
resize:

(扩容插入字符) 

void resize (size_t n);void resize (size_t n, char c);

将字符串的大小调整为 n 个字符的长度;

如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除第 n个字符之外的字符;

如果 n 大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展当前内容,以达到 n 的大小。如果指定了 c,则新元素将初始化为 c 的副本,否则,它们是值初始化的字符(null 字符); 

size_t _sz = s.size();
s.resize(_sz + 2, '+');
clear:
void clear();

清除字符串

擦除字符串的内容,该字符串将变为空字符串长度为 0 个字符)。

clear一般是只清理内容,不清除掉容量的(怕白干了),但个别少数会连容量(空间)一起清除的

s.clear();
empty:

(判空)

bool empty() const;

测试字符串是否为空

返回字符串是否为空(即其长度是否为 0);
此函数不会以任何方式修改字符串的值;

cout << s.empty() << endl;

注意:

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, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变;

4. reserve(size_t  res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小;

3. string类对象的访问及遍历操作

函数名称
功能说明
operator[ ] (重点)
返回 pos 位置的字符, const string 类对象调用
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位
置的迭代器
范围 for
C++11 支持更简洁的范围 for 的新遍历方式

string::iterator(正向迭代器)(string::const_iterator)

begin:

返回一个迭代器,该迭代器指向字符串的第一个字符;

如果字符串对象是 const 限定的,则该函数返回const_iterator。否则,它将返回一个迭代器;
成员类型 iterator 和 const_iterator 是随机访问迭代器类型(分别指向字符和 const 字符);

string::iterator it = s2.begin();

end:

返回一个迭代器,该迭代器指向字符串的末尾字符("\0")

string::iterator it = s2.end();

string::reverse_iterator(反向迭代器) (string::const_reverse_iterator)

rbegin:

返回反向迭代器以反向开始

返回一个反向迭代器,指向字符串的最后一个字符(即其反向开头)(有效字符);
反向迭代器向后迭代:增加迭代器会使它们朝向字符串的开头;(++倒着走)
rbegin 指向 member end 将指向的字符之前的字符;

string::iterator it = s2.rbegin();

rend;

将反向迭代器返回到反向端

返回一个反向迭代器,指向字符串的第一个字符(被视为其反面端)之前的理论元素;(也就是第一个字符的前一个位置);

string::rbegin 和 string::rend 之间的范围包字符串的所有字符(顺序相反)(左(rbegin)闭右(rend)开);

operator[ ]

获取字符串的字符

返回字符串中位置 pos 处的字符的引用

1.pos是size_t类型; 

2.pos在字符串中是从0开始,即:第一个字符位置用0表示;

3.如果字符串对象是 const 限定的,则该函数返回 const char&,否则,它将返回 char&;

因此,我们可以去访问字符串中的字符:(适用于数组)

s2[0] = 'x';
//下标+[]
for (size_t i = 0; i < s2.size(); i++)
{
	cout << s2[i] << " ";
}

string是支持迭代器的方式进行遍历,迭代器是STL的六大组件之一,迭代器是用来遍历访问容器的;(所有的容器都可以用迭代器访问)

//迭代器
string::iterator it = s2.begin();
while (it != s2.end())
{
	cout << *it << " ";
	it++;
}

//范围for:所有容器都支持
//从s2中取字符给ch
//本质是迭代器
for (auto ch : s2)
{
	ch += 2;
	cout << ch << " ";
}

反向迭代器:(反向访问)

4.string提供的库---Function

库 (点击进入详情页)

stoi:
int stoi (const string&  str, size_t* idx = 0, int base = 10);
int stoi (const wstring& str, size_t* idx = 0, int base = 10);

将字符串转换为整数

解析 str,将其内容解释为指定基数的整数,该整数作为 int 值返回;
如果 idx 不是 null 指针,则该函数还会将 idx 的值设置为 str 中第一个字符在数字之后的位置;

str:

表示整数的 String 对象

idx:

指向 size_t 类型的对象的指针,该对象的值由函数设置为 str 中下一个字符在数值之后的位置;
此参数也可以是 null 指针,在这种情况下,不会使用它;

base:

确定有效字符及其解释的数字基数(基数);
如果此值为 0,则使用的基数由序列中的格式确定;

请注意,默认情况下,此参数为 10,而不是 0

类似的:

long long x=stoll(num1);
to_string:
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);

将数值转换为字符串

返回一个表示为 val 的字符串

 类似:

5. string类对象的修改操作

函数名称
功能说明
在字符串后尾插字符 c
在字符串后追加一个字符串
operator+= ( 重点)
在字符串后追加字符串 str
c_str ( 重点 )
返回 C 格式字符串(兼容C)
find + npos ( 重点)
从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的
位置
从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的
位置
str 中从 pos 位置开始,截取 n 个字符,然后将其返回(比copy好点,还要开buffer)
push_back:

将字符追加到字符串

将字符 c 追加到字符串的末尾,使其长度增加 1

append:

追加到字符串

通过在字符串的当前值末尾追加其他字符来扩展字符串

operator+=:

就是push_back和append的结合,很香的,可以丢掉前俩个(底层是按扩容的逻辑走的)

以后尾插用+=就对了

void test_string5()
{
	string s("hello world");
	s.push_back(' ');
	cout << s << endl;
	s.append("!!!");
	cout << s << endl;
	s += "1314";
	cout << s << endl;
	s += '!';
	cout << s << endl;
}
补充:
insert

说到尾插,还有头插,实际string没有头插,但是可以用insert接口实现头插:

s.insert(0, "hello everyone");

但头部,中间插入应该谨慎使用,有效率的流失

还有:

//插入一个
s.insert(0, "h");
s.insert(0, 1, 'h');//区别(小细节)
s.insert(0, 'h');//报错

由于历史的原因,整个string设计比较冗余,但是我们要尊重历史

语言都是向前兼容的,但python比较大胆~~~👍

erase

erase就比较好(指定位置删除字符)

从字符串中删除字符

擦除字符串的一部分,减小其长度:(头删就不用提供其他的接口)

void test_string6()
{
	string s("hello aaworld");
	s.erase(6, 2);//想要全删,默认给缺省值就行
	cout << s << endl;
}

其迭代器(接口)也支持头,尾删除(pop_back也有尾删功能):

//头删
s.erase(s.begin());
//还可以
s.erase(0, 1);
//尾删
s.erase(--s.end());
//还可以
s.erase(s.size() - 1, 1);
//都是间接实现
replace:

如果想把字符串的某一个位置替换成另外一个字符/字符串,可以用replace(本质:插入删除)(效率也不高)

string s("hello world");
s.replace(5, 1, "%%");//换一个就行,换两个就把1换成2
find and npos:

查找字符:(返回类型:size_t)(找到返回下标,没找到返回npos(npos是静态成员变量,要制定类域))

(将所有空格转换成'!'):

void test_string7()
{
	string s("hello world hello everyone nice to meet you   ");
	size_t pos = s.find(' ');
	while (pos != string::npos)
	{
		s.replace(pos, 1, "!");
		pos = s.find(' ', pos + 1);//防止对空格的从头开始查找
	}
	cout << s << endl;
}

但是面对大量数据是效率极低的,不可行的

void test_string7()
{
	string s("hello                       world ");
	size_t pos = s.find(' ');
	while (pos != string::npos)
	{
		s.replace(pos, 1, "!");
		cout << s << endl;
		pos = s.find(' ', pos + 1);//防止对空格的从头开始查找
	}
	cout << s << endl;
}

那该如何解决呢,我们可以拿空间换时间,又简单又高效:

void test_string7()
{
	string s("hello                       world ");
	string tmp;//牺牲空间
	tmp.reserve(s.size());//减少/避免 扩容
	for (auto ch : s)
	{
		if (ch == ' ')
		{
			tmp += "!";
		}
		else
		{
			tmp += ch;
		}
	}
	swap(s, tmp);//swap更高效
	cout << s << endl;
}

 5. string类非成员函数

函数名称
功能说明
尽量少用,因为传值返回,导致深拷贝效率低c
operator>> (重点)
输入运算符重载
operator<<  (重点)
输出运算符重载
getline (重点)
获取一行字符串
relational operators (重点)
大小比较

operator+

string+字符串:

string s1("hello ");
string s2 = s1 + "world";

字符串+string: (这样就不能重载为成员函数了,左操作数被string牢牢占用了)(只能重载为全局的,跟流插入一样)

string s1("hello ");
string s2 = "world" + s1;
getline:

在做一些题目的时候,我们时时需要输入空格,但是cin会将空格/换行默认为输入分割的标志,因此,getline很好的为我们解决了这个问题

getline:遇到空格不终止,还是从缓冲区拿数据,默认是换行才是分割的标志

string str;
getline(cin,str);

也可以遇到自定的符号作为结束的标志:(现在不默认了)

string str;
getline(cin,str,'*');

补充:引用计数的写时拷贝(博弈论设计)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的;
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1 ,每增加一个对象使用该资源,就给计数增加1 ,当某个对象被销毁时,先给该计数减 1 ,然后再检查是否需要释放资源,如果计数为1 ,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源
  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值