C++ string

时间:2024-10-22 16:04:52

一.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

当然遇到文件末尾也会自动停止 

这样就更加方便输入输出了。