一.string类
1.在使用string类的时候必须包含对应的头文件
#include<string>
2.auto和范围for
C++11之后,标准委员会赋予了auto一个新的含义即:auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得到。
int func1()
{
return 10;
}
void func2(auto a)
{
}
auto func3()
{
return;
}
首先是不能使用auto做参数,但是可以用来做auto,具体类型是由编译器推导而成(谨慎使用)
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//输出
//int
//char
//int
编译器会自己推导变量的类型是什么,比如a是一个int类型的变量,将其赋值给b,那么auto会自动推导为int类型。
补充:typeid().name可以打印出对应变量的类型。
auto e;
但是不能这样写,因为此时编译器在编译阶段是无法推导出它的类型是什么的,所以auto类型的变量在声明的时候必须初始化,否则就会编译报错。跟引用类似
cout << endl;
int x = 10;
auto y = &x;
auto* z = &x;
auto& m = x;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;
//输出
//int
//int* __ptr64
//int* __ptr64
对于指针类型的变量来说,auto和auto*是没有什么差别的,但是auto声明引用类型的时候必须要加&,否则不知道是普通变量还是引用。
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;
在同一行声明多个变量的时候,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际上只会对第一个类型进行推导,然后用推导出来的类型定义其他变量。
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };
auto也是不能用来声明数组的。
范围for
对于一个有范围的集合来说,由程序员来说明范围的范围显得有点多余,有时候还容易出错。因此C++11中引入了基于范围for循环。for循环后的括号中由冒号 : 分为两个部分。
string str = "1234567";
for (auto e : str)
{
cout << e << ' ';
}
左边是范围内用于迭代的变量,右边是这个集合的名字也就是被迭代的范围。
范围for是自动迭代,自动取数据,自动判断结束的。
这里会输出:
范围for可以作用到数组和之后我们要学习的容器中。
当然这里auto e也可以写成auto&e。前面需要创建变量,而后者不需要更加高效。
3.string类的接口
通过文档我们可以看到有非常多种,接下来我们一一了解。
二.string接口
1.constructor构造函数
string的构造函数有非常多种,但是常用的不多。只需记住其中几个就好
1.默认构造
第一种就是默认构造,在我们不进行初始化的时候,这个字符串的长度就是0,也就是所谓的空字符串
string str;
//string str();不能这样写
2.拷贝构造
第一个参数是自身类类型的引用就是拷贝构造。
string s1 = "123";
string s2 = s1;
cout << s2;
3.substring
string s1 = "0123456789";
string s2(s1,3,5);
cout << s2;
//输出34567
这个也是拷贝构造,只不过是拷贝参数的部分,从s1下标为3的位置开始拷贝,拷贝五个字节。
string (const string& str, size_t pos, size_t len = npos);
有人可能好奇,这个缺省参数npos是什么东西。
如果我们不写第三个参数的时候,就会从pos位置一直赋值到末尾,也就是输出3456789.
这里的pos是一个静态类型的变量,size_t是一个无符号整形,也就是如果是-1的话,npos其实就是size_t类型的最大值。这个数是非常大的。
所以我们不写第三个参数,即代表从pos位置开始复制npos个字符,但是由于这个数非常大,几乎不可能用完,所以就用来表示一直复制到string的末尾。当第三个参数的值超过string的长度的时候,就只会复制到末尾。并不会越界。
4.from c-string
第四个就是从一个从s指向的以'\0'结尾的C语言字符串进行拷贝
5.from buffer
第五个就是拷贝字符串的前n个。
6.fill
用n个c字符填充这个字符串
string s2(5,'a');
cout << s2;
7.range迭代器构造
string s1 = "0123456789";
string s2(s1.begin(),s1.end());
cout << s2;
//输出"0123456789"
这个学完begin和end更好理解
8 .initializer list
string s1 = {'a','b','c','d'};
//或者这样写string s1({'a','b','c','d'});
cout << s1;
//输出abcd
这个构造函数允许我们使用{}包含的元素进行初始化。
2.destructor析构函数
这个非常简单,就是析构函数,类的析构函数会自动调用,销毁掉string对象,不需要我们过多了解。
3.operator= 赋值重载
1.string
string s1 ="123";
string s2;
s2 = s1;
将s1赋值给s2;
2.C-string
将一个C字符串赋值 对象
3.character
将一个字符赋值给对象。string的大小是任意的,可以为0也可以是1.
4.initializer list
将一个{}中的字符赋值给对象
4.begin()和end()
这两个都是一个迭代器,那什么是迭代器呢?
在C++中,迭代器是一个非常重要的概念,它提供了一种方式来访问容器中的元素。它跟指针类似,但是它的功能更加强大和通用,可以用于多种不同类型的容器。
简单来说可以把他看做另一种指针。
begin()是一个指向string开始的一个迭代器,也就是下标为0的位置。
而end()就是一个指向string末尾字符的下一个位置的迭代器,也就是下标为string长度的位置。
那么这个迭代器怎么使用呢?
首先就是访问元素
string s1 = "0123456789";
for (auto i = s1.begin(); i != s1.end(); i++)
{
cout << *i << ' ';
}
//输出0 1 2 3 4 5 6 7 8 9
由于begin()返回的迭代器,我们不知道是啥类型的,但是auto可以自己推导,是不是很方便。
就算你知道这个类型你写起来也很长,很麻烦。
然后就是*i,对这个迭代器进行解引用,就可以得到这个迭代器所指向位置的元素。这一点和指针是非常类似的。
其次就是++会是迭代器指向下一个位置。--就是前一个位置了。
而且对于迭代器来说是可以判断是否相等或者不等的。
有人可以会觉得这个跟我们之前写的for循环没有什么区别啊,而且看着好像更麻烦,其实迭代器并不是只为了访问元素所设计的,后面会了解更多内容,比如前面的构造函数就可以用迭代器进行构造。
其实范围for也是用迭代器的,上面的代码跟范围for是一样的。
5.rbegin()和rend()
跟begin和end类似,只不过均是返回反向迭代器。
rbegin返回一个反向迭代器指向反向字符串的开始。返回一个反向迭代器,指向字符串的最后一个字符(即其反向开头)。
rend返回一个反向迭代器指向反向字符串的结束。返回一个反向迭代器,指向字符串第一个字符之前的理论元素(被视为其反向端点)。可以理解为下标位置为-1的位置。
对反向迭代器进行++操作的时候,会使其靠近string的开始。
string s1 = "0123456789";
for (auto i = s1.rbegin(); i != s1.rend(); i++)
{
cout << *i << ' ';
}
//输出9 8 7 6 5 4 3 2 1 0
除了反过来,就跟begin和end没什么太大的区别。
6.cbegin(),cend(),crbegin(),crend()
cbegin这个c其实指的是const,也就是说这个迭代器是const迭代器,无法通过这个迭代器修改其指向的内容,但是任然可以访问。就算指向的内容本身并不是const修饰的也无法修改,相当于权限缩小,其他三个也是类似的。
7.size和length
这两个函数功能是一摸一样的,没有任何区别。
均是返回string对象的长度。
string s = "12314341235";
cout << s.size() << endl;
cout << s.length() << endl;
都是输出11.
之所以会出现这样的情况,是因为早期C++设计的时候,对于string来说,长度相比叫做大小更加合理,所以最开始是length,但是由于后面的容器大小的接口都是size,毕竟继续用length不合理了,所以其他容器大小也是用的size,为了保持统一性string就也采用size,之所以不删除length是因为为了兼容之前的代码以免作废。所以在string中就出现了这样具有相同功能的两个函数。看着设计很冗余,但是这个早期设计无法避免的事情。
8.max_size
这个会返回当前环境下,字符串所能达到的最大长度。
string s = "abc";
cout << s.max_size();
//我这里测试输出9223372036854775807
由于已知的系统或库实现限制,这是字符串可以达到的最大潜在长度,但不能保证对象能够达到该长度:在达到该长度之前,它仍然可能在任何时候无法分配存储。
这个函数作用并不大。
9.capacity
在讲这个接口之前,我们先了解一下string这个类到底是怎么设计的
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
};
大概是类似这样的设计,实际上到底是咋样的还要看编译器,这个只是简化版。
_str是一个指针,指向一块动态开辟的空间,这个空间里面存放字符串。
_size是这个字符串的长度
_capacity是_str所指向空间的大小,也就是容量。
这样的设计是为了避免频繁的扩容。
而这个capacity()返回的就是_capacity的值。单位是字节。
当我们对string进行操作的时候,如果_capacity小于string对象的大小了,这是会自动扩容的,也就是说,_capacity的值至少是大于或者等于_size的。它是不会限制string对象的大小(指_size)的。
比如我申请了20个字节的空间,里面存放的字符串是10个字节,那么_capacity就是20,而_size就是10,当我们向这个字符串中添加字符的时候,_size会不断增加,当_size和_capacity相等的时候,又需要继续增加时,就会进行扩容,具体怎么扩容和编译器有关。一般是二倍扩容。
10.resize
void resize (size_t n);
void resize (size_t n, char c);
这个函数的功能是调整string的长度到n。当这个n小于当前字符串的长度时,就会直接去除掉多余的字符,只保留前n个。
当n大于当前字符串的长度的时候,就会在字符串后添加字符c。如果没有指定添加的字符就会补'\0'。
11.reserve()
void reserve (size_t n = 0);
这个函数的功能是改变容量,也就是capacity.
当参数n的值大于当前capacity的大小的时候,就会改变这个capacity的值至少比n大。
因为不同的编译器在扩容的时候,扩容的方式是不同的。
string s = "0123456789";
s.resize(5);
s.resize(10);
cout << s.capacity() << endl;
s.reserve(20);
cout << s.capacity()<<endl;
vs2022对capacity的扩容就是类似于2倍扩容的。
但是当n的值小于当前capacity的值时,这个函数是不具有约束性的。也就是改不改变是不确定的, 任然可以使这个容器的大小大于n.
当n的大小小于这个字符串的长度的时候,也是不会影响其长度的,也就不会改变其内容。
12.clear()
清空字符串,使其变成一个空字符。这样的更改是不会改变容器空间大小的。
13.empty()
这个函数是用于判断这个字符串是不是空字符串的,如果是就会返回true,反之就是false。
14.[ ]和at
[ ]很常见就是跟数组类似,用于访问特定位置下的元素,而at的功能和[]一模一样。都是返回特定位置的值。
唯一的区别是at会自动检测下标的值到底是不是在字符串范围内。如果不在会抛异常。
15.operator+=和append
两者都是在一个字符串后面追加字符串。
operator+=
string (1)
string& operator+= (const string& str);
c-string (2)
string& operator+= (const char* s);
character (3)
string& operator+= (char c);
append
string (1)
string& append (const string& str);
substring (2)
string& append (const string& str, size_t subpos, size_t sublen);
c-string (3)
string& append (const char* s);
buffer (4)
string& append (const char* s, size_t n);
fill (5)
string& append (size_t n, char c);
range (6)
template <class InputIterator>
string& append (InputIterator first, InputIterator last);
第二是追加某个字符串时,从subpos位置开始,追加sublen个字符,如果不写默认-1;
第四个是追加某个字符串的前n个字符,
第五个是追加n个字符c.
第六个就是迭代器追加。
16.push_back
void push_back (char c);
尾差一个字符
17.assign
string (1)
string& assign (const string& str);
substring (2)
string& assign (const string& str, size_t subpos, size_t sublen);
c-string (3)
string& assign (const char* s);
buffer (4)
string& assign (const char* s, size_t n);
fill (5)
string& assign (size_t n, char c);
range (6)
template <class InputIterator>
string& assign (InputIterator first, InputIterator last);
这个函数的功能是对某个字符串的内容进行替换。上面的各种使用方法与前面所讲的函数类似,不再做过多的讲解。
// string::assign
#include <iostream>
#include <string>
int main()
{
std::string str;
std::string base = "The quick brown fox jumps over a lazy dog.";
// used in the same order as described above:
str.assign(base);
std::cout << str << '\n';
str.assign(base, 10, 9);
std::cout << str << '\n'; // "brown fox"
str.assign("pangrams are cool", 7);
std::cout << str << '\n'; // "pangram"
str.assign("c-string");
std::cout << str << '\n'; // "c-string"
str.assign(10, '*');
std::cout << str << '\n'; // "**********"
str.assign<int>(10, 0x2D);
std::cout << str << '\n'; // "----------"
str.assign(base.begin() + 16, base.end() - 12);
std::cout << str << '\n'; // "fox jumps over"
return 0;
}
18.insert()
string (1)
string& insert (size_t pos, const string& str);
substring (2)
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
c-string (3)
string& insert (size_t pos, const char* s);
buffer (4)
string& insert (size_t pos, const char* s, size_t n);
fill (5)
string& insert (size_t pos, size_t n, char c);
void insert (iterator p, size_t n, char c);
single character (6)
iterator insert (iterator p, char c);
range (7)
template <class InputIterator>
void insert (iterator p, InputIterator first, InputIterator last);
insert的功能是在pos位置之前插入字符串或者字符。
注意是pos位置之前。
所以这个进行插入的时候pos以及pos位置之后的数据会往后挪动。
// inserting into a string
#include <iostream>
#include <string>
int main ()
{
std::string str="to be question";
std::string str2="the ";
std::string str3="or not to be";
std::string::iterator it;
// used in the same order as described above:
str.insert(6,str2); // to be (the )question
str.insert(6,str3,3,4); // to be (not )the question
str.insert(10,"that is cool",8); // to be not (that is )the question
str.insert(10,"to be "); // to be not (to be )that is the question
str.insert(15,1,':'); // to be not to be(:) that is the question
it = str.insert(str.begin()+5,','); // to be(,) not to be: that is the question
str.insert (str.end(),3,'.'); // to be, not to be: that is the question(...)
str.insert (it+2,str3.begin(),str3.begin()+3); // (or )
std::cout << str << '\n';
return 0;
}
19.erase()
sequence (1)
string& erase (size_t pos = 0, size_t len = npos);
character (2)
iterator erase (iterator p);
range (3)
iterator erase (iterator first, iterator last);
erase是删除pos位置上的值,将后面的值往前挪动。
20.replace
string (1)
string& replace (size_t pos, size_t len, const string& str);
string& replace (iterator i1, iterator i2, const string& str);
substring (2)
string& replace (size_t pos, size_t len, const string& str,
size_t subpos, size_t sublen);
c-string (3)
string& replace (size_t pos, size_t len, const char* s);
string& replace (iterator i1, iterator i2, const char* s);
buffer (4)
string& replace (size_t pos, size_t len, const char* s, size_t n);
string& replace (iterator i1, iterator i2, const char* s, size_t n);
fill (5)
string& replace (size_t pos, size_t len, size_t n, char c);
string& replace (iterator i1, iterator i2, size_t n, char c);
range (6)
template <class InputIterator>
string& replace (iterator i1, iterator i2,
InputIterator first, InputIterator last);
replace的功能是用字符串替换某个字符串的从pos位置开始的一部分。
// replacing in a string
#include <iostream>
#include <string>
int main ()
{
std::string base="this is a test string.";
std::string str2="n example";
std::string str3="sample phrase";
std::string str4="useful.";
// replace signatures used in the same order as described above:
// Using positions: 0123456789*123456789*12345
std::string str=base; // "this is a test string."
str.replace(9,5,str2); // "this is an example string." (1)
str.replace(19,6,str3,7,6); // "this is an example phrase." (2)
str.replace(8,10,"just a"); // "this is just a phrase." (3)
str.replace(8,6,"a shorty",7); // "this is a short phrase." (4)
str.replace(22,1,3,'!'); // "this is a short phrase!!!" (5)
// Using iterators: 0123456789*123456789*
str.replace(str.begin(),str.end()-3,str3); // "sample phrase!!!" (1)
str.replace(str.begin(),str.begin()+6,"replace"); // "replace phrase!!!" (3)
str.replace(str.begin()+8,str.begin()+14,"is coolness",7); // "replace is cool!!!" (4)
str.replace(str.begin()+12,str.end()-4,4,'o'); // "replace is cooool!!!" (5)
str.replace(str.begin()+11,str.end(),str4.begin(),str4.end());// "replace is useful." (6)
std::cout << str << '\n';
return 0;
}
21.swap
这个函数的功能是交换两个字符串的值。
其实这个函数在库函数中,已经存在了,而且不仅仅可以用于交换字符串,其他任何类型都可以。
string s1 = "123";
string s2 = "321";
s1.swap(s2);
cout << s1 << " " << s2 << endl;//321 123
swap(s1,s2);
cout << s1 << " " << s2 << endl;//123 321
只不过使用的方式不一样,所以只需要记住第二个就可以了。
22.c_str 和data()
还记得上面讲的string对象的简单结构吗?
这两个函数就是返回这个string对象中_str的值。
string s = "123";
printf("%p\n",s.c_str());
printf("%p\n",&s[0]);
printf("%p\n",s.data());
结果是一模一样的。
23.copy
size_t copy (char* s, size_t len, size_t pos = 0) const;
复制字符串对象的一部分到s 所指向的字符串中。
但是这个函数并不会追加一个'\0'字符在s所指向的字符串末尾。所以极有可能会越界。
24.find()和rfind()
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;
在string对象中查找某个字符串,并返回它第一次出现的下标。如果没找到就返回npos。
第三个是从pos位置开始s所指向的数组中的前n个字符。
rfind就是查找最后一次出现的位置。可以理解为倒着查找。
25.find_first_of和find_last_of
string (1)
size_t find_first_of (const string& str, size_t pos = 0) const;
c-string (2)
size_t find_first_of (const char* s, size_t pos = 0) const;
buffer (3)
size_t find_first_of (const char* s, size_t pos, size_t n) const;
character (4)
size_t find_first_of (char c, size_t pos = 0) const;
find_first_of 就是在string对象中查找在s或者str所指向的序列中出现的字符的第一个位置的下标。注意:这个函数和find不同,find是查一个字符串,而这个仅仅查找一个字符。
string (1)
size_t find_last_of (const string& str, size_t pos = npos) const;
c-string (2)
size_t find_last_of (const char* s, size_t pos = npos) const;
buffer (3)
size_t find_last_of (const char* s, size_t pos, size_t n) const;
character (4)
size_t find_last_of (char c, size_t pos = npos) const;
find_last_of就是查找最后一个位置的下标
26.find_first_not_of和find_last_not_of
这两个函数就是查找不在str或者s所指向的序列中的出现的第一个或者最后一个位置的下标。
27.substr
string substr (size_t pos = 0, size_t len = npos) const;
得到某个字符串的子串。
std::string str="We think in generalities, but we live in details.";
// (quoting Alfred N. Whitehead)
std::string str2 = str.substr (3,5); // "think"
std::size_t pos = str.find("live"); // position of "live" in str
std::string str3 = str.substr (pos); // get from "live" to the end
std::cout << str2 << ' ' << str3 << '\n';
输出:think live in details.
28.compare函数
这个函数更加方便比较两个字符串的某一部分。如果是单纯的比较两个完整的字符串直接用大于小于等于符号即可。
string对这几个操作符都进行了重载。
string (1)
int compare (const string& str) const;
substrings (2)
int compare (size_t pos, size_t len, const string& str) const;
int compare (size_t pos, size_t len, const string& str,
size_t subpos, size_t sublen) const;
c-string (3)
int compare (const char* s) const;
int compare (size_t pos, size_t len, const char* s) const;
buffer (4)
int compare (size_t pos, size_t len, const char* s, size_t n) const;
比较两个字符串,比较方式和C语言的比较方式一模一样。不过多介绍。
int main ()
{
std::string str ("look for non-alphabetic characters...");
std::size_t found = str.find_first_not_of("abcdefghijklmnopqrstuvwxyz ");
if (found!=std::string::npos)
{
std::cout << "The first non-alphabetic character is " << str[found];
std::cout << " at position " << found << '\n';
}
输出:
green apple is not red apple still, green apple is an apple and red apple is also an apple therefore, both are apples
三.非成员函数
首先就是字符串对象是支持相加的。
string s1 = "abgcdefgh";
s1 += "123";
其次就是流插入和流提取操作符是可以直接输入输出string对象的。
cin >> s1;
cout << s1 << endl;
当我们采用cin >> s1;这样的操作来接受数据的时候,是无法在string对象中接受空格字符,因为遇到空格就停止了。
这时候就可以用到另一个函数getline。
它可以指定读取到什么字符才停止
string s1;
getline(cin,s1,'a');//输入12345 bcdfghsdfgafdsfsdf
cout << s1; //输出12345 bcdfghsdfg
当然遇到文件末尾也会自动停止
这样就更加方便输入输出了。