【编码艺术:掌握String类函数接口的妙用指南】

news2024/11/16 17:35:25

【本节目标】

  • 1. 为什么要学习string类

  • 2. 标准库中的string类

1. 为什么要学习string类

1.1 C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

2. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

总结:

1. string是表示字符串的字符串类

2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;

4. 不能操作多字节或者变长字符的序列。

在使用string类时,必须包含#include<string>头文件以及using namespace std。

2.2 string类的常用接口说明

1. string类对象的常见构造

void Teststring()
{
	string s1; // 构造空的string类对象s1
	string s2("hello bit"); // 用C格式字符串构造string类对象s2
	string s3(s2); // 拷贝构造s3
	string s4 = s3;// 拷贝构造s4
}

我们再来介绍一下string其他相关的构造

(3)Copies the portion(一部分) of str that begins at the character position pos and spans(跨越) len characters (or until the end of str, if either str is too short or if len is string::npos).

我们来使用一下该函数:

string (const string& str, size_t pos, size_t len = npos);

我们再来看一下npos是什么?

所以我们就可以完全理解上面的函数:复制字符串 str 从字符位置 pos 开始,跨越 len 个字符(或者直到 str 的末尾,如果 str 太短或 lenstring::npos)的部分。

(5)Copies the first n characters from the array of characters pointed by s.

我们来使用一下该函数:

string (const char* s, size_t n);

这个构造函数比较简单,就是复制字符数组 s 指向的前 n 个字符,不过这里要注意该函数的第一个参数是字符串,而不是string类滴对象。

(5)Fills the string with n consecutive(连续的) copies of character c.

我们来使用一下该函数:

string (size_t n, char c);

这个构造函数就是用字符 c 的连续副本填充字符串,填充的数量为 n。

(7)Copies the sequence of characters in the range [first,last), in the same order.

这个构造函数涉及迭代器,我们后面再讲解。

2.string类对象的析构函数

析构函数会自动调用的,我们可以不用重点关注在这一块。

3. string类对象的赋值

这里赋值可以支持string类的对象常量字符串字符

4. string类对象的遍历和访问

  • 4.1.通过下标 + [ ]运算符重载实现
//遍历和访问
int main()
{
	string str("Test string");

	//下面两个函数结果相同,结果是不包含'\0'的
	cout << "size:" << str.size() << endl;//推荐使用
	cout << "length:" << str.length() << endl;

	for (size_t i = 0; i < str.size(); i++)
	{
		cout << str[i];
	}
	cout << endl;
	return 0;
}

运行结果:

我们再来写一个字符串的逆序

//字符串的逆序
int main()
{
	string str("Test string");

	int begin = 0, end = str.size() - 1;
	while (begin < end)
	{
		char tmp = str[begin];
		str[begin] = str[end];
		str[end] = tmp;
		++begin;
		--end;
	}
	for (size_t i = 0; i < str.size(); i++)
	{
		cout << str[i];
	}
	cout << endl;
	return 0;
}

上面的逆序字符串交换的这个代码很繁琐,C++为我们提供了Swap函数接口

于是我们根据上面的swap函数就可以这样写

//字符串的逆序
int main()
{
	string str("Test string");

	int begin = 0, end = str.size() - 1;
	while (begin < end)
	{
		swap(str[begin], str[end]);
		++begin;
		--end;
	}
	for (size_t i = 0; i < str.size(); i++)
	{
		cout << str[i];
	}
	cout << endl;
	return 0;
}

运行结果:

我们现在再来看一个细节,也就是我们之前提到的const的成员函数,这里的[ ]运算符重载实现了两个,这里是两个不同的函数,它们的参数是不同的,第二个隐形的this指针用了const修饰,这里我们要提一下参数匹配的问题。

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1("hello world");
	const string s2("hello world");
	// 如果只实现了const char& operator[] (size_t pos) const;
	s1[0];//权限缩小
	s2[0];//权限平移
	// 但是此时返回值是const char& - 返回值不可修改
	//s2[0] = 'x';//error:error C3892: “s2”: 不能给常量赋值
	//所以就提供了char& operator[] (size_t pos);
	s1[0] = 'x';
	return 0;
}
  • 4.2.迭代器(Iterator)
//迭代器iterator:遍历和访问
int main()
{
	string str("Test string\0");
	//定义在类域里面,需要域作用限定符才可以访问
	//迭代器iterator是一个类型,用法像指针
	//[begin,end)左边右开区间
	string::iterator it = str.begin();
	while (it != str.end())
	{
		*it += 1;//访问修改
		cout << *it;//遍历
		++it;
	}
	cout << endl;
	return 0;
}

运行结果:

解析:

总结:虽然下标 + [ ]很方便,但是它仅仅适用于这些底层物理空间连续,比如string、vector等等。但是链式结构,树形和哈希结构,只能用迭代器,迭代器才是容器访问主流形态。

 所以我们上面的逆序就不用写上面的这么多代码,C++为我们提供了逆置的函数:reverse

所以就可以直接这样写:

//字符串的逆序
int main()
{
	string str("Test string");
	string::iterator it = str.begin();
	reverse(str.begin(), str.end());
	while (it != str.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	return 0;
}

运行结果:

上面通过函数模板实现,注意泛型编程不是针对某个容器的迭代器实现的,函数模板是针对各个容器迭代器实现。关于我们的迭代器,begin获取一个字符的迭代器,end获取最后一个字符下一个位置,普通的迭代器是可读可写的,但是这里也有一个细节,我们这里迭代器也实现了重载,const重载的只能进行可读

当我们对只读对象进行迭代器遍历的时候,就出现错误了。同时这里还有一个细节,我们发现上面是const_iterator,而不是const iterator,中间多了一个_,这里的const_iterator本质上是保护迭代器指向的数据"*it"不能被修改,而不是const iterator是迭代器本身不能被修改,也就"it"不能被修改,否则这样无法++,无法遍历,不符合我们的需求。

4.3.反向迭代器

我们也可以通过反向迭代器进行遍历和访问,但是此时的遍历是逆序的。

样例:

我们可以观察到反向迭代器也实现了两个版本,但是普通情况下我们基本上很少定义const对象,只有在传参的时候我们才最容易出现const对象。

//void fun(const string s1){}//调用拷贝构造,深拷贝,代价大
void fun(const string& s1)//引用,作为str的别名,开销小
{
	//error
	//string::reverse_iterator it = s1.rbegin();
	//const的对象调用rbegin应该返回const_reverse_iterator
	string::const_reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
		*it = 1;//const迭代器不可修改,error:“it”: 不能给常量赋值	
		cout << *it;
		++it;
	}
	cout << endl;
}

int main()
{
	string str("Test string");
	string::reverse_iterator it = str.rbegin();
	while (it != str.rend())
	{
		*it = 1;//普通迭代器不可修改
		cout << *it;
		++it;
	}
	cout << endl;
	fun(str);
	return 0;
}

不过通常我们都使用下标 + [ ]进行逆序遍历,但是反向迭代器也是非常有用的,对于链表这种没有下标的逆序遍历就需要使用反向迭代器。对于上面的迭代器,类型名都是非常长的,而且比较容易写错,这里我们可以使用之前我们讲到的auto关键字自动推至类型,但是这个写法对代码的可读性不好。

关于迭代器这里,之前的迭代器实现了两个版本,容易混淆,于是C++11就对const对象的迭代器进行了单独的处理,在普通迭代器前面加上了字符'c'表示此时的对象是cosnt的。

4.4.范围for
#include <iostream>
using namespace std;

int main()
{
	string str("Test string");

	for (auto e : str)
	{
		cout << e;
	}
	cout << endl;
	return 0;
}

范围for在我们这里不仅可以支持string,还能支持vertor,list等其他容器。我们现在再来介绍一下下面的元素存取相关函数。

我们先来看一个代码

当我们如果不小心访问了越界元素,此时程序就会报错,但是此时看右边我们知道是assert断言出错,如果我们不想出现这个报错界面,并且提示一下错误信息,就可以使用at,at也可以访问元素,它返回字符串中位置 pos 处的字符的引用,当访问越界的时候,此时提示信息"invalid string position"。

我们来验证一下。

再来看一下front和back,它们是访问字符串的头位置和尾位置的字符

int main()
{
	string s("hello world");
	cout << s.front() << endl;
	cout << s.back() << endl;

	cout << s[0] << endl;
	cout << s[s.size() - 1] << endl;
	return 0;
}

运行结果:

一般情况上我们很少用frong和back,因为我们可以通过pos为0和size-1位置访问头和尾元素。

5. string类对象的容量操作

5.1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
5.2.max_size()返回字符串可以达到的最大长度,不同的机器这个最大长度是不一样的,下面的测试是32位机器下,

虽然上面给了我们这个字符串能到达的最大长度,但是这个值没有参考意义,实际上我们开辟不了这个最大长度的空间,使得字符串到达的最大长度。我们来验证一下

int main()
{
	try
	{
		string str1;
		string str2("Test string");

		cout << "str1:" << str1.max_size() << endl;
		cout << "str2:" << str2.max_size() << endl;
		//reverse:为字符串预留空间 - 相当于扩容
        //实践中没有参考的价值和意义		
        str1.reserve(str1.max_size());
	}
	catch(const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

运行结果:

5.3.capacity()返回空间总大小

注意:这里的容量是有效字符的数量,而'\0'不属于有效字符,这里实际上开了16个字节的空间,其中一个用来存 '\0'。

我们这里还能用来检测string的扩容机制

int main()
{
	try
	{
		string str1;

		size_t old = str1.capacity();
		cout << "str1:" << str1.capacity() << endl;
		//检测string的扩容机制
		for (size_t i = 0; i < 100; i++)
		{
			str1.push_back('x');
			if (old != str1.capacity())
			{
				cout << "str1:" << str1.capacity() << endl;
				old = str1.capacity();
			}
		}
	}
	catch(const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

vs下结果:P. J. 版本 - 1.5倍扩容

Linux(g++)下结果:SGI版本 - 2倍扩容

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

		str1.reserve(100);
		size_t old = str1.capacity();
		cout << "str1:" << str1.capacity() << endl;
		//检测string的扩容机制
		for (size_t i = 0; i < 100; i++)
		{
			str1.push_back('x');
			if (old != str1.capacity())
			{
				cout << "str1:" << str1.capacity() << endl;
				old = str1.capacity();
			}
		}
	}
	catch(const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

reserve可以和capaciy搭配使用,当使用reverse(100)的时候,此时就已经开辟了100空间,后面就不需要扩容了。如果我们确定需要多少空间时,提前开空间即可,此后就不需要扩容了。但是这里我们只需开辟100个空间,而程序却开辟了111个空间,因为vs下可能存在了内存对齐机制,会以它内部的规则去对齐,从而会开辟更大一点,Linux下直接开辟你确定所要的空间。

vs下运行结果:

Linux下的结果:

我们再来看一下当reserve的参数小于 string的底层空间capacity总大小时,reserver不会改变容量大小。

int main()
{
	string str2("hello world!xxxxxxxxxxx");
	str2.reserve(100);
	cout << "str2:" << str2.capacity() << endl;
	str2.reserve(5);
	cout << "str2:" << str2.capacity() << endl;
	return 0;
}

vs下运行结果:

Linux下的结果:                                                                                                                               

当reserve的参数小于 string的底层空间capacity总大小时,vs平台下reserver不会改变容量大小,Linux下reserver会改变容量大小,但是它缩小空间只会缩小到该字符串的有效字符数量size处,它是不会影响到已经存储的字符串的。

5.5. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用’\0'来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

reserve只影响我们的容量,不会影响数据,而resize既影响容量,也会影响数据。我们会发现s1的大小和容量都增加了,那它是用什么填充这些空间的呢?

我们先来看一下大于当前容量的情况,我们可以通过监视窗口观察到,但是'\0'不一定是标识字符,有效字符属于0到size-1范围内。

我们再来看一下大于size但是小于capacity的情况

此时容量大小没有改变,只变化了数据,仅仅变化了size。如果小于size呢?

看一下监视窗口

此时删除了数据,只保留前resize个。resize应用的场景是开辟空间并指定字符初始化。注意:C++中new对应C语言中的malloc函数,而C++中没有C语言中对应的realloc扩容函数。所以C++中,我们如果使用字符数组,就会使用string,而string中提供了相应的接口,从而进行扩容。

5.6.clear()只是将string中有效字符清空,不改变底层空间大小。

6. string类对象的增删查改操作

6.1.增加

        在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

这里有一个问题:string s1 = "hello world";//这个可行吗?可行,这里是一个单参数的构造函数,它会隐式类型转换,它先去构造,然后再拷贝构造,不过这里编译器可能会优化成直接构造。

我们来看一下append的使用。

int main()
{
	std::string str;
	std::string str2 = "Writing ";
	std::string str3 = "print 10 and then 5 more";

	// used in the same order as described above:
	str.append(str2);                       // "Writing "
	str.append(str3, 6, 3);                   // "10 "
    str.append("here: ");                   // "here: "
	str.append("dots are cool", 5);          // "dots "
	str.append(10u, '.');                    // ".........."
	str.append(str3.begin() + 8, str3.end());  // " and then 5 more"

	std::cout << str << '\n';
	return 0;
}

我们上面有了尾插,那我们有没有头插呢?或者在中间插入呢?

// 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;
}

实际中头插使用较少,因为要挪动数据,效率不高。那删除字符呢?

6.2.删除

// string::erase
#include <iostream>
#include <string>

int main ()
{
  std::string str ("This is an example sentence.");
  std::cout << str << '\n';
                                           // "This is an example sentence."
  str.erase (10,8);                        //            ^^^^^^^^
  std::cout << str << '\n';
                                           // "This is an sentence."
  str.erase (str.begin()+9);               //           ^
  std::cout << str << '\n';
                                           // "This is a sentence."
  str.erase (str.begin()+5, str.end()-9);  //       ^^^^^
  std::cout << str << '\n';
                                           // "This sentence."
  return 0;
}
6.3.查找

注意:

  • pos含义:搜索字符串中要查找的第一个字符的位置。
  • 函数返回值:第一个匹配项的第一个字符的位置。 如果未找到任何匹配项,则该函数返回 string::npos
// string::find
#include <iostream>       // std::cout
#include <string>         // std::string

int main ()
{
  std::string str ("There are two needles in this haystack with needles.");
  std::string str2 ("needle");

  // different member versions of find in the same order as above:
  std::size_t found = str.find(str2);
  if (found!=std::string::npos)
    std::cout << "first 'needle' found at: " << found << '\n';

  found=str.find("needles are small",found+1,6);
  if (found!=std::string::npos)
    std::cout << "second 'needle' found at: " << found << '\n';

  found=str.find("haystack");
  if (found!=std::string::npos)
    std::cout << "'haystack' also found at: " << found << '\n';

  found=str.find('.');
  if (found!=std::string::npos)
    std::cout << "Period found at: " << found << '\n';

  // let's replace the first needle:
  str.replace(str.find(str2),str2.length(),"preposition");
  std::cout << str << '\n';

  return 0;
}

现在我们来使用一下find,查找字符串的后缀是什么?这里我们会使用到substr。

int main()
{
	string s1("Test.cpp");
	string s2("Test.tar.zip");

	size_t pos1 = s1.find('.');
	if(pos1 != string::npos)
	{
		string suff1 = s1.substr(pos1, s1.size() - pos1);
		cout << suff1 << endl;//.cpp
		string suff2 = s1.substr(pos1);//默认取到结尾
		cout << suff2 << endl;//.cpp
	}

	size_t pos2 = s2.find('.');
	if (pos2 != string::npos)
	{
		string suff1 = s2.substr(pos2, s2.size() - pos2);
		cout << suff1 << endl;//.tar.zip
		string suff2 = s2.substr(pos2);//默认取到结尾
		cout << suff2 << endl;//.tar.zip
	}

	//搜索字符串中由参数指定的序列的最后一次出现
	size_t pos3 = s2.rfind('.');
	if (pos2 != string::npos)
	{
		string suff1 = s2.substr(pos3, s2.size() - pos3);
		cout << suff1 << endl;//.zip
		string suff2 = s2.substr(pos3);//默认取到结尾
		cout << suff2 << endl;//.zip
	}
	return 0;
}

运行结果:

如何将一个网址分开呢?

#include <iostream>
#include <string>
using namespace std;

int main()
{
	string str("https://cplusplus.com/reference/string/string/substr/");
	string sub1, sub2, sub3;
	size_t pos1 = str.find(':');
	//左闭右开区间
	sub1 = str.substr(0, pos1 - 0);
	cout << sub1 << endl;
	size_t pos2 = str.find('/', pos1 + 3);
	sub2 = str.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << sub2 << endl;
	sub3 = str.substr(pos2 + 1);
	cout << sub3 << endl;
	return 0;
}

运行结果:

6.4.修改
// 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;
}

根据上面的查找和修改可以轻松解决一个我们曾经遇到的问题,将日期"2023-11-23"中的'-'修改为'/'。

int main()
{
	string str("2023-11-23");
	cout << str << endl;
	size_t pos = str.find('-');
	// '-'修改为'/'
	while (pos != string::npos)
	{
		str.replace(pos, 1, 1, '/');
		pos = str.find('-');
	}
	cout << str << endl;
	return 0;
}

我们看一下我们的代码有什么问题没?我们发现我们每次找'-'都是从字符串的其实位置开始找,那么这也效率比较低,其实我们第一找到'-'后,得到第一次出现'-'的位置,后面再找'-'就可以从上次找的位置+1即可,这样效率就提高很多,但是replace的效率比较低,我们下面替换一个字符还好,但是如果替换成'///'时,此时就要往后挪动数据才能插入,这样效率比较低,所以replace我们能少用尽量就少用。

int main()
{
	string str("2023-11-23");
	cout << str << endl;
	size_t pos = str.find('-', 0);
	// '-'修改为'/'
	if (pos != string::npos)
	{
		str.replace(pos, 1, 1, '/');
		pos = str.find('-', pos + 1);
	}
	cout << str << endl;
	return 0;
}

所以这里我们有更好的方法,使用范围for+赋值。

int main()
{
	string str("2023-11-23");
	cout << str << endl;
	string str1;
	for (auto ch : str)
	{
		if (ch == '-')
			str1 += '/';
		else
			str1 += ch;
	}
	cout << str1 << endl;
    str.swap(str1);
	return 0;
}

运行结果:

这里有一个细节问题:我们上面使用的是C++ 标准库中 std::string 类的成员函数。

string::swap 是 C++ 标准库中 std::string 类的成员函数,用于交换两个字符串的内容。它是在字符串对象上调用的函数,例如:

std::string str1 = "Hello";
std::string str2 = "World";

str1.swap(str2); // 交换 str1 和 str2 的内容

swap 是一个通用的 C++ 函数,用于交换两个对象的值。对于字符串来说,可以使用 std::swap 或直接使用 swap 来交换两个字符串的内容,例如:

std::string str1 = "Hello";
std::string str2 = "World";

std::swap(str1, str2); // 交换 str1 和 str2 的内容
// 或者直接使用 swap
swap(str1, str2);

主要区别在于调用方式和命名空间。string::swapstd::string 类的成员函数,而 swap 是一个通用的函数,可以在合适的作用域下直接使用或通过 std::swap 来调用。两者都可以用于交换字符串的内容。同时std::swap 参数是函数模板,使用的时候实例化,需要拷贝,消耗较大,而string::swap 是直接交换两个字符串的地址,后面实现string类的时候可以看到。

6.5.string::c_str:返回一个指向数组的指针,该数组包含表示字符串对象当前值的以 null 结尾的字符序列(即,一个 C 字符串)。
int main()
{
	string s1 = "Sample string";
	char* s2 = new char[s1.length() + 1];
	//char * strcpy ( char * destination, const char * source );
	//strcpy(s2, s1);//参数类型不匹配
	//const char* c_str() const;//返回值为const char*
	strcpy(s2, s1.c_str());
    delete[] s2;
	return 0;
}
7. string类非成员函数

这里为什么实现了两种参数不同的方式?

        这两个重载的 operator+ 函数是为了提供更大的灵活性,使得在字符串的拼接操作中更方便使用不同类型的参数。这是一种 C++ 中的重载技术,允许相同的操作符在不同的情境下具有不同的行为。

让我们分析这两个重载函数:

  1. string operator+ (const string& lhs, const char* rhs);

    • 允许将一个 std::string 对象和一个 C 字符串(以 const char* 表示)进行拼接。
    • 这使得你可以直接将一个 C 字符串连接到一个 std::string 对象的末尾。
  2. string operator+ (const char* lhs, const string& rhs);

    • 允许将一个 C 字符串和一个 std::string 对象进行拼接。
    • 这使得你可以直接将一个 std::string 对象连接到一个 C 字符串的末尾。

这两种方式的存在是为了方便用户在不同的场景下进行字符串的拼接,无论是从 std::string 到 C 字符串,还是从 C 字符串到 std::string

7.1.getline (string):从输入流 is 中提取字符并将它们存储到字符串 str 中,直到找到分隔字符 delim(或者对于 (2) 来说是换行符 '\n')。

流插入字符输入字符时默认以空格或者换行结束。

如果不想以空格作为字符输出结束的标志,就要使用getline().

7.1. vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • vs下string的结构 string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义
  • string中字 符串的存储空间: 当字符串长度小于16时,使用内部固定的字符数组来存放 当字符串长度大于等于16时,从堆上开辟空间
  • 当字符串长度小于16时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ 
    // storage for small buffer or pointer to larger one
    value_type _Buf[_BUF_SIZE];
    pointer _Ptr;
    char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内 部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节。

​​​​​​​

上面的几个接口了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的 操作,这里不一一列举,在需要用到时不明白了查文档即可。

8.小试牛刀

仅仅反转字母

class Solution {
public:
	bool isLetter(char ch)
	{
		if (ch >= 'a' && ch <= 'z')
			return true;
		if (ch >= 'A' && ch <= 'Z')
			return true;
		return false;
	}
	string reverseOnlyLetters(string S)
	{
		if (S.empty())
			return S;

		size_t begin = 0, end = S.size() - 1;
		while (begin < end)
		{
			while (begin < end && !isLetter(S[begin]))
				++begin;

			while (begin < end && !isLetter(S[end]))
				--end;
			swap(S[begin], S[end]);
			++begin;
			--end;
		}
		return S;
	}
};

找字符串中第一个只出现一次的字符   

class Solution{
public:
 int firstUniqChar(string s) {

	 // 统计每个字符出现的次数
	 int count[256] = {0};
	 int size = s.size();
	 for (int i = 0; i < size; ++i)
	 count[s[i]] += 1;

	 // 按照字符次序从前往后找只出现一次的字符
	 for (int i = 0; i < size; ++i)
	 if (1 == count[s[i]])
	 return i;

	 return -1;
	 }
};

字符串里面最后一个单词的长度  

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string line;
	// 不要使用cin>>line,因为会它遇到空格就结束了
	// while(cin>>line)
	while (getline(cin, line))
	{
		size_t pos = line.rfind(' ');
		cout << line.size() - pos - 1 << endl;
	}
	return 0;
}

验证一个字符串是否回文 

class Solution {
public:
bool isPalindrome(string s) {
    int begin = 0;
    int end = s.size() - 1;
    while (begin < end)
    {
        while (!isalpha(s[begin]) && !isdigit(s[begin]) && begin < end)
            begin++;
        while (!isalpha(s[end]) && !isdigit(s[end]) && begin < end)
            end--;

        if (s[begin] >= 'A' && s[begin] <= 'Z')
            s[begin] = tolower(s[begin]);
        if (s[end] >= 'A' && s[end] <= 'Z')
            s[end] = tolower(s[end]);

        if (s[begin] != s[end])
            return false;

        if (begin < end)
        {
            ++begin;
            --end;
        }
    }
    return true;
    }
};

​​​​​​​字符串相加​​​​​​​

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        string str;
        //进位
        int next = 0;
        while(end1 >= 0 || end2 >= 0)
        {
            int value1 = 0,value2 = 0;
            if(end1 >= 0)
                value1 = num1[end1--] - '0';
            if(end2 >= 0)
                value2 = num2[end2--] - '0';

            int addvalue = value1 + value2 + next;
            next = addvalue/10;
            addvalue %= 10;
            str.insert(0,1,addvalue + '0');
        }
        if(next == 1)
        {
            str.insert(0,1,'1');
        }
        return str;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1272008.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+SpringBoot+Vue3+Uniapp+TypeScript(有视频教程)前后端分离的求职招聘小程序

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

LeetCode(46)汇总区间【区间】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 汇总区间 1.题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某…

Linux下如何运行.sh文件

运行环境为ubuntu20.04 如在/home/zoe/map运行.sh文件&#xff1a; 进入到/home/zoe文件夹下&#xff1a; cd /home/zoe/map 第一种运行方式&#xff1a; sh play.sh 结果&#xff1a; 第二种方式&#xff1a; 使用chmod修改文件的执行权限&#xff0c;然后运行 chmod x …

六、初识FreeRTOS之FreeRTOS的任务挂起和恢复函数介绍

本节需要掌握以下内容&#xff1a; 1&#xff0c;任务的挂起与恢复的API函数&#xff08;熟悉&#xff09; 2&#xff0c;任务挂起与恢复实验&#xff08;掌握&#xff09; 3&#xff0c;课堂总结&#xff08;掌握&#xff09; 一、任务的挂起与恢复的API函数&#xff08;熟…

使用MechanicalSoup库的爬虫程序

1. 首先&#xff0c;我们需要导入MechanicalSoup库和requests库&#xff0c;这两个库都是Python中爬虫常用的库。 2. 接着&#xff0c;我们要设置一个代理服务器&#xff0c;使用proxy_host和proxy_port参数来指定。 3. 使用requests.get方法来获取网页的HTML代码。 4. 使用Bea…

CentOS 7安装Java 8

前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 要在CentOS 7上安装Java 8&#xff0c;请按照以下步骤操作&#xff1a; 打开终端并以root身份登录。 更新系统软件包&#xff1a; …

GPT-3解读:惊艳世界的模型原来是大力出奇迹

欢迎来到魔法宝库&#xff0c;传递AIGC的前沿知识&#xff0c;做有格调的分享❗ 喜欢的话记得点个关注吧&#xff01; 今天&#xff0c;我们将共同探索OpenAI的GPT-3&#xff0c;与GPT-2相比&#xff0c;GPT-3更像是一个大力出奇迹的结果。接下来&#xff0c;就让我们跟随论文…

性能测试:系统架构性能优化

今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#x…

故障排查:rpm安装报错事务锁定(can‘t create transaction lock on /var/lib/rpm/.rpm.lock)

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; 目录 故障详情故障原因解决办法 故障详情 自己编写spec文件&#xff0c;制…

解决电脑蓝屏问题:SYSTEM_THREAD_EXCEPTION_NOT_HANDLED,回到系统还原点

解决电脑蓝屏问题&#xff1a;SYSTEM_THREAD_EXCEPTION_NOT_HANDLED&#xff0c;回到系统还原点 1&#xff0c;蓝屏显示问题1.1&#xff0c;蓝屏1&#xff0c;清楚显示1.2&#xff0c;蓝屏2&#xff0c;模糊显示 2&#xff0c;排除故障问题3&#xff0c;解决蓝屏的有效方法 1&a…

DES加密技术概述与应用

一、引言 随着信息技术的飞速发展&#xff0c;数据安全已成为越来越受到重视的问题。数据加密技术作为保障信息安全的核心技术之一&#xff0c;得到了广泛的研究和应用。DES&#xff08;Data Encryption Standard&#xff09;作为一种对称加密算法&#xff0c;凭借其高效、可靠…

Pycharm新手开发指南

文章目录 前言一、常用功能介绍二、常用高效pycharm使用方法关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 前言…

带大家做一个,易上手的家常土豆片

还是先从冰箱里那一块猪瘦肉 搞一点蒜和生姜 切成小块 装进一个碗里 这里一点就够了 一条绿皮辣椒 切片 三个左右干辣椒 随便切两刀 让它小一点就好了 一起装一个碗 一大一小两个土豆切片 猪肉切片 起锅烧油 然后 下肉翻炒 等肉变颜色捞出来 然后放入土豆 和小半碗水 让…

【C++】: unordered_map的使用

1、概念 key 键值的类型。unordered_map中的每个元素都是由其键值唯一标识的。 T 映射值的类型。unordered_map中的每个元素都用来存储一些数据作为其映射值。 Hash 一种一元函数对象类型&#xff0c;它接受一个key类型的对象作为参数&#xff0c;并根据该对象返回size_t类型…

IIS post .html页面报 405错误

IIS是不允许本地文件默认post请求的&#xff0c;windows10系统下的IIS&#xff08;10.0版&#xff09;默认也是不能 post请求\*.html或\*.json文件的 1 需要配置一下&#xff0c;配置如下&#xff1a; 2 双击处理程序映射&#xff0c;添加托管处理程序&#xff1a; 3 请求路径 …

低价商品采购API接口

采购商品地址http://sly.yizhaosulianyun.com/More/Push/888889?type3 低价商品采购API接口 1) 请求地址 http://sly.yizhaosulianyun.com/jd/keyWords 2) 调用方式&#xff1a;HTTP post 3) 接口描述&#xff1a; 低价商品采购接口 4) 请求参数: POST参数: 字段名称字段…

MySQL组合索引,最左匹配原则失效

说明&#xff1a;在SQL优化时&#xff0c;建立组合索引&#xff0c;我们需要知道最左匹配失效的情况&#xff0c;本文通过实例介绍最左匹配原则失效&#xff1b; 建立组合索引 如下&#xff0c;是一张大表&#xff0c;有1000万条数据&#xff1b; 对表中password、sex和email…

JMeter从入门到精通

1、 jmeter的介绍 jmeter也是一款接口测试工具&#xff0c;由java语言开发的&#xff0c;主要进行性能测试。 2、jmeter安装 jmeter官网下载链接&#xff1a; https://jmeter.apache.org/download_jmeter.cgi &#xff0c;查看是否安装成功【jmeter -v】 下载 java jdk1.8&…

StartRocks 连接 Paimon外部表

版本 StartRocksPaimon3.2.00.5 sr 环境准备 CREATE external CATALOG paimon_hdfs PROPERTIES ("type" "paimon",paimon.catalog.type filesystem,"paimon.catalog.warehouse" "hdfs://hadoop03:9000/paimon/test" );mysql> …

虚幻学习笔记4—文本内容处理

一、前言 本文使用的虚幻引擎5.3.2&#xff0c;在虚幻中已经集成了很多可以直接处理多样化文本的蓝图&#xff0c;比如格式化动态显示、浮点数多样化等。 二、实现 2.1、格式化文本显示动态内容&#xff1a;在设置某个文本时可以使用“Format Text”蓝图设置自定义可以的显示…