欢迎来到博主的专栏——c++编程
博主ID:代码小豪
文章目录
- <string>
- string类的接口
- 构造、析构、与赋值重载
- 构造函数
- 赋值重载运算符
- 元素访问
- operator[]
- 容量
- 修改器
- 对string对象的操作
- 迭代器
std::string是定义在c++标准的一个类,定义在标准库<string>中,其由类模板basic_string实例化而来。这一点我们可以在c++手册当中了解到。
不同的编译器之间对于string类的成员定义并不相同。(比如vs和g++),如果想要看看vs2022当中basic_string模板的源代码,可以在
D:\visual studio\VC\Tools\MSVC\14.35.32215\include
找到string库的源代码,博主这里是将vs安装到了D盘,如果你们安装到了其他位置,可以通过查看vs的属性找到文件位置。
请原谅博主能力有限,实在是看不懂string库的源代码,关于string类的成员对象有什么,我们可以在vs的调试当中看到
其成员可分为4个,分别是_buf、_Mysize、_Myres、_Ptr。
_buf是一个存储字符串的缓冲区,其类型是char[16],可最多存储16个字符。存储在栈区间上
_ptr是一个char*类型的指针,指向开辟在堆区的一块内存,当string类的对象存储的字符大于16个时,_buf不会再存储数据,而是改为在_ptr上存储数据
_Mysize是当前的字符个数
_Myres是最大的字符存储个数
当string对象的字符个数超过16个数,存储数据的内存会从buf改为_ptr指向的内存空间,这个空间是开辟在堆区间上的。
std::string s1("0123456789abcdef\0");
当_Mysize的个数超过_Myres时,string类会进行扩容,这个扩容操作不需要用户手动进行,当然,string类也提供了一个接口让用户手动的扩容,这在后边会讲到。
string类的接口
由于string类的成员对象都是私有权限(private),用户不能访问这些成员,于是c++提供了众多接口函数让用户通过接口对string类的对象进行操作。这些操作可以在c++使用手册当中看到。
c++使用手册
手册当中对string的接口(成员函数与友元函数)分为7大类,分别是:初始化和赋值、迭代器、容量、元素访问、修改器、字符操作和非成员重载函数。涉及的接口多达50几个,想要在这个博客当中这么多就不符合本专栏的目的了。博主这里简单讲解每个大类当中的常用接口。(博主打算在未来开一个新专栏,目的是精讲STL,如果大家想看请多多关注博主哦。)
构造、析构、与赋值重载
想要学会<string>和STL库。那么学习如何看懂c++使用手册是一个重要的步骤,这里我们将结合使用手册的内容对string类的接口进行讲解。
构造函数
在c++98的标准当中有7种重载形式,c++11新增了两种重载形式,由于c++11标准新增的两个重载不太常用,因此以c++98标准来讲解。
在后续的博客当中将不展示手册当中的参数原文,因为同名参数代表的意义基本一致
从使用手册可以看到,string类的拷贝构造函数重载七种,官方还分别给这七种构造方式进行了命名。
(1)empty constructor ——空构造
empty constructor是string的默认构造函数。其作用是构造一个空字符串。
std::string s1;//空字符串
(4)from c-string constructor——以字符串构造
C-string指的是C语言当中常用的字符串类型,即char*类型的字符串,这类字符串都是以’\0’作为结束标志。该构造会根据传递的C-string来构造string类型的对象。
std::string s2("hello world");//s2为"hello world";
std::cout << s2 << std::endl;//打印"hello world"
(2)copy constructor——拷贝构造
在声明string类的对象时,以引用传递的形式,向拷贝构造传递一个string类的引用参数。该对象会拷贝这个引用对象。
std::string s2("hello world");//string为"hello world";
std::string s3(s2);//s2作为实参以引用传递的方式调用构造函数,s3会拷贝s2
std::cout << s3 << std::endl;//打印"hello world"
(3)substring constructor——子串拷贝构造
这个构造函数有三个参数,分别string引用类型的str,size_t的pos和len。其len还存在一个缺省值_npos。
其作用是复制从str当中的pos位置开始,覆盖len个长度的字符的子串。如果len的长度超过了这个str的字符串长度,那么只复制到str的末尾。
缺省_npos是一个常量,其值以为unsigned int类型的最大值,即2的32次方-1.约等于42亿。
显然一个字符串不应该有这么多的字符个数。因此这个缺省值的意义在于,拷贝从pos位置开始复制,直到str的末尾。
std::string s4("hello world");//string为"hello world";
std::string s5(s4, 0, 5);
//拷贝s4当中从0位置开始的后5个字符,为"hello"
std::string s6(s4, 5);
//利用了_npos这个常量,那么s6会拷贝s4的从第6个字符开始,直到末尾的这部分。为" world"
cout << s5 << endl;//"hello"
cout << s6 << endl;//" world"
(5)以字符数组构造
将s指向的数组的前n个字符拷贝到string类的对象当中。
char buf[] = "123456789";
std::string s7(buf, 5);//拷贝buf的前五个字符即"12345"
cout << s7 << endl;//"12345"
(6)填充字符构造
向string类的对象填充n个字符c。
std::string s8(3, 'x');//向s8填充3个'x'
cout << s8 << endl;//"xxx"
(7)范围构造
范围构造会用到string类的迭代器,由于还没有对迭代器进行讲解,这部分内容将会放在下文。
string类的析构函数并不需要用户手动调用——当然你可以试试,而且string类只存在一个析构函数,这个析构函数的作用是将string开辟的堆区空间进行释放。
赋值重载运算符
赋值重载的意义在于,可以让对象之间,也能用赋值操作符(=),完成对象的赋值操作。string类将赋值运算符‘=’重载了3种形式
(1)string对象可以可以赋值给string对象
std::string str1("hello world");
std::string str2;
str2 = str1;//str2的字符串内容变成了"hello world"
cout << str2 << endl;//"hello world"
(2)可以用char*类型指针指向的字符串(C-string)赋值给string对象
const char* ptr = "hello world";//C-string
std::string str3;
str3 = ptr;//ptr指向的C-string赋值给str3
cout << str3 << endl;//"hello world"
(3)可以用单个字符赋值给string对象
std::string str4("hello world");
str4 = 'a';//字符'a'替换了str4中的内容,str的字符串长度也变为了1
cout << str4 << endl;//'a'
元素访问
此类接口使用最多就是下标重载函数了(operator []),在此之间,我们简单的了解一下其余三个函数。
char& at (size_t pos);
const char& at (size_t pos) const;
at函数是返回string当中pos位置的字符的引用,注意pos不能超过字符串长度。
std::string str1("hello world");
cout << str1.at(0) << endl;//打印第一个
str1.at(5) = ',';//str1的第6个字符被修改
cout << str1 << endl;//"hello,world"
back()和front()就是取得字符串串尾和字符串串头的引用,这里就不多介绍了,感兴趣的读者可以查阅c++使用手册。
operator[]
char& operator[] (size_t pos);
const char& operator[] (size_t pos)const;
string类重载了下标访问符号([]),这使得访问string对象的字符元素像访问数组元素一样,比如:
std::string str1("hello world");
for (size_t i = 0; i < 11; i++)
{
cout << str1[i] << endl;
}
可以用下标的形式,遍历整个字符串
容量
容量类的接口通常都是获取string类的成员对象,或者是提供修改成员函数的函数。
我们挑拣常用的说明
size()
size_t size() const;
size()没有参数,size的返回值是返回当前有效字符的字符串长度(不计入‘\0’),即string的成员对象(_Mysize)。
在遍历string的成员时,可以用到size()。
std::string s1("hello world");
for(size_t i=0;i<s1.size();i++)
{
cout << s1[i];
}
lenth()
lenth()的作用和size()是一样的。
empty()
empty()用来判断string对象是否是一个空字符,如果是空字符则返回true,反之返回false。
clear()
celar()会清除string对象的所有字符。
这一类的接口基本没有参数,其作用大多是让用户对string对象的成员_Mysize进行操作,或者是堆string对象的状态进行修改。
修改器
string的接口非常冗余,在修改器这一模块更加明显,比如operator+=,append,push_back的作用是重叠的,assign、insert、replace也有重叠,pop_back和erase是也是重叠的。所以学会使用c++手册学习STL比看博主的任何相关博客都要重要。
push_back()
其函数原型为:
void push_back (char c);
push_back()会在string对象的字符串末尾插入一个字符C。
std::string s1;
s1.push_back('a');
s1.push_back('b');
s1.push_back('c');
cout << s1 << endl;//"abc"
pop_back()
void pop_back();
pop_back()删除string对象的末尾字符。
std::string s2("abc");//"abc"
s2.pop_back();
cout << s2 << endl;//"ab"
swap()
void swap (string& str);
swap()会将string对象的字符串与str的字符串进行交换。注意,这个swap是string类的成员函数,在c++标准库中还存在一个swap,这是一个函数模板,不要用混咯。
std::string str1("abc");//"abc"
std::string str2("def");//"def"
str1.swap(str2);//str1是调用这个函数的对象
cout << str1 << endl;//"def"
cout << str2 << endl;//"abc"
operator +=
string将+=操作进行了重载,使得string对象可以使用+=符号进行字符或字符串的追加。
string (1)
string& operator+= (const string& str);
c-string (2)
string& operator+= (const char* s);
character (3)
string& operator+= (char c);
可追加的类型有三种,分别是string类型的对象字符串,C-string,和字符。
std::string str3("hello");
std::string str4("world");
str3 += str4;
cout << str3 << endl;//"helloworld"
str3 += "123";
cout << str3 << endl;//"helloworld123"
str3 += '?';
cout << str3 << endl;//"helloworld123?"
用+=来追加字符串还是非常好用的,其优点在于程序可读性高,操作简单,灵活。
append()
append的操作除去和opeartor+=重叠的部分,还多了几个,这里就讲讲多出的追加操作。
substring(2)
string& append (const string& str, size_t subpos, size_t sublen = npos);
string类型的对象在末尾追加str的子字符串,这个子字符串是str从subpos的位置开始,向后覆盖sublen个长度的字符串(如果sublen个长度超过了str的末尾,那么只会覆盖到str的末尾)。
std::string str5("xxx");
std::string str6("0123456789");
str5.append(str6, 3, 5);
cout << str5 << endl;//"xxx34567"
buffer(4)
string& append (const char* s, size_t n);
追加字符数组s的前n个字符。要注意数组越界这个问题。
char buffer[] = "hello world";
std::string str7;
str7.append(buffer, 5);
cout << str7 << endl;//"hello"
fill (5)
string& append (size_t n, char c);
向string对象追加n个字符c。
std::string str8("aaa");
str8.append(3, 'b');
cout << str8 << endl;//"aaabbb"
对string对象的操作
博主写到这已经很疲惫了,因为相比较于STL的其他容器,string冗余的操作是在是太多了(严格的说string不在STL当中,string库出现的比STL要早,是c++探索泛型编程的过程中出现的不成熟产物。因此在接口的设计方面存在不够简洁的问题),不仅博主写的很烦,估计读者看着也很烦了吧,所以剩余部分的内容博主只会找具有非常不相似性的接口进行说明。
为什么说相似性高的不再讲解呢?我们拿这个substr举例。这是substr的原型
string substr (size_t pos = 0, size_t len = npos) const;
我甚至不用打开c++手册都能才substr()这个接口的作用。因为pos和len这两个参数贯穿了整个类的成员函数。
其作用为,生成一个子字符串,这个子字符串是string对象的字符串从pos位置开始,贯穿len个长度的字符的拷贝。
这个模块当中的c_str()很值得讲讲。
c_str会返回一个char*类型的指针,这个指针指向的是string对象当中有效字符串的拷贝。
std::string str1("hello world");
char buf[12] = { 0 };
strcpy(buf, str1.c_str());
cout << buf << endl;//"hello world"
要注意,c_str并不是将string类型的对象进行了类型转换(虽然作用很类似)。string类型的对象也不支持类型转换成C-string类型(即char*,const char*)。随着string对象的使用,_str返回的指针的值可能会改变,因此不要用const char*类型的指针来接收。
这6个接口均是对字符串内容的索引,我们以find和find_first_of为例。
string (1) size_t find (const string& str, size_t pos = 0) const;
c-string (2) size_t find (const char* s, size_t pos = 0) const;
buffer (3) size_t find (const char* s, size_t pos, size_t n) const;
character (4) size_t find (char c, size_t pos = 0) const;
find重载了4中函数,分别是对string对象的索引,对字符指针s指向的字符串索引,对字符数组的索引,和对字符的索引。pos指的是调用此函数的对象开始查找的位置。
返回字符串中搜索由其参数指定的序列的第一此出现的位置。
当指定pos时,搜索只包括位置pos或位置pos之后的字符。
std::string str1("123456789");
std::string str2("345");
str1.find(str2);//返回4
str1.find("345");//返回4
str1.find('3');//返回4
//4是str1第一次出现这个字符串或字符的位置
find_first_of和find的区别在于,find是严格按照传入的参数进行查找的,比如“12345”,只有string对象存在“12345”才会返回这个位置。而find_first_of()只会查找传入字符串或者字符当中匹配的一个字符,只要string对象存在符合的任意字符就会返回这个位置。
int main ()
{
std::string str ("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found!=std::string::npos)
{
str[found]='*';
found=str.find_first_of("aeiou",found+1);
}
std::cout << str << '\n';
return 0;
}
这段代码会将字符串中的aeiou字符,全部替换成*。
迭代器
迭代器是c++泛型编程又一重大产物,如同它的名字,迭代器的作用是用来遍历泛型容器中的元素。
我们可以简单的认为迭代器是一个指针(至少string当中他是这样的),我们可以通过++,–让迭代器指向下一个元素。也可以让迭代器通过算术加减访问前n个或后n个元素(和指针性质一致)。甚至还能用解引用操作符(*),来读写这个元素。
在string中,我们有6个接口来获取string对象的迭代器。
begin()用来获取字符串的起始元素,end()用来获取字符串的末尾元素。
既然被称为迭代器了,那么它肯定是支持遍历整个字符串的。
std::string str1("hello world");
for (std::string::iterator it1 = str1.begin();
it1 != str1.end();
it1++)
{
cout << *it1;
}
string迭代器分为4中类型,分别为
(1)iterator
(2)const_iterator
(3)reverse_iterator
(4)const_recerse_iterator
iterator就是最基本的迭代器类型,string当中的迭代器我们可以认为是这种类型的
typedef char* iterator;//vs的实现不是这样!!博主只是为了方便理解
因此我们可以用const修饰迭代器,若是如此操作,那么迭代器就不能修改指向的位置。但是可以修改指向的数据。
std::string str1("hello world");
const std::string::iterator it1 = str1.begin();
it1++;//error,被const修饰的迭代器不能修改指向的位置
*it1 = 'j';//可以修改指向的位置
实际上很好理解,因为const iterator可以等价为char* const。当const修饰的是指针时,指向的位置是只读不写的,所以不能执行++操作。
如果是想让迭代器指向的数据只读不写,那就要用到const_iterator了。用const_iterator类型的迭代器,其指向地数据的权限是只读不写的。类似于const char*
std::string str1("hello world");
std::string::const_iterator it1 = str1.begin();
it1++;//const_iterator类型的迭代器修改指向的位置
*it1 = 'j';//error,指向的数据是只读不写的
如果想让迭代器不改变指向的位置,也不能修改数据,那么就用
const const_itearator修饰就行
reverse_iterator是为了支持反向迭代而建立的(string当中反向迭代的意义不大,但是其他容器是有反向迭代的场景的)
rbegin和rend就是提供给反向迭代器的接口,rbegin指向字符串尾,rend指向字符串口。
有趣的是,reverse_iterator也支持++和–以及算术加减运算。但是执行的效果却是与iterator相反的。如果iterator++是指向下一个元素,那么reverse_iterator++则是指向上一个元素,这也就是为什么说,迭代器像指针,但却不完全是指针,因为指针不会出现++是指向上一个元素这种操作。
std::string str2("hello world");
for (std::string::reverse_iterator rit1 = str2.rbegin();
rit1 != str2.rend();
rit1++)//倒着遍历str2
{
cout << *rit1;
}