【ONE·C++ || string类(一)】

在这里插入图片描述

总言

  string类的各种常见接口使用介绍。

文章目录


  

1、整体介绍

  相关参考网站
在这里插入图片描述

   可以看到这些函数接口在<string> 这个头文件中。
  
  基础介绍:
  1、string是表示字符串的字符串类
  2、该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3、 string在底层实际是:basic_string模板类的别名:

typedef basic_string<char, char_traits, allocator> string;

在这里插入图片描述

  4、 不能操作多字节或者变长字符的序列。
  5、在使用string类时,必须包含#include头文件以及using namespace std;
  

  

2、常用各种接口介绍

2.1、string类对象的常见构造:string::string

2.1.1、总览

  string类对象的常见构造

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

  相关链接:std::string::string
在这里插入图片描述

  
  
  

2.1.2、构造空的string类

在这里插入图片描述

string();

  演示代码如下:

#include<string>
int main(void)
{
	string s1;//无参的
	string s2("hello!");//带参的

	return 0;
}

  演示结果如下:
  此处说明string兼容C ,因此虽然是无参构造,但其结尾实际上是有"\0" 的。需要注意,这里的\0并不算在有效字符内(可看到其size值为0)。
在这里插入图片描述
  
  

2.1.3、用C-string来构造string类

在这里插入图片描述

string (const char* s);

  
  演示代码如下:

#include<string>
int main(void)
{
	string s1;//无参的
	string s2("hello!");//带参的

	return 0;
}

   演示结果如下:
在这里插入图片描述
  
  关于此处的另一种写法:涉及隐式类型转换
  演示代码如下:

void test_string3()
{
	string s1("hello string!");
	string s2 = "hello string!";//构造+拷贝构造->优化->直接构造
	cout << s1 << endl;
	cout << s2 << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

   演示结果如下:
在这里插入图片描述

  
  

2.1.4、用n个字符c构造string类

在这里插入图片描述

string (size_t n, char c);

  演示代码如下:

void test_string3()
{
	string s1(5, 'c');
	cout << s1 << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

   演示结果如下:

在这里插入图片描述

  
  

2.1.5、string类拷贝构造

在这里插入图片描述

   拷贝构造,此处实际上是涉及深拷贝,关于深拷贝的内容将在后续模拟实现strng类中讲解

void test_string2()
{
	string s1("hello string!");
	string s2(s1);
	cout << s1 << endl;
	cout << s2 << endl;
}

int main(void)
{
	test_string2();

	return 0;
}

   演示结果如下:
在这里插入图片描述

  
  

2.1.6、部分拷贝构造函数

在这里插入图片描述

   注意此处有个size_t len = npos,为默认的缺省参数。相关链接

在这里插入图片描述

  演示代码如下:

void test_string2()
{
	string s1("hello string!");
	string s2(s1);
	string s3(s1, 6);
	string s4(s1, 6, 3);
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
}

int main(void)
{
	test_string2();

	return 0;
}

   演示结果如下:
在这里插入图片描述

  
  
  
  

2.2、析构、赋值运算符重载

2.2.1、析构:string::~string

   相关链接
在这里插入图片描述

  
  

2.2.2、赋值运算符重载:string::operator=

   相关链接
在这里插入图片描述

  演示代码如下:

void test_string3()
{
	string s1("hello string!");
	//这是拷贝构造的写法:发生隐式类型转换
	string s2 = "hello string!";//构造+拷贝构造->优化->直接构造

	string s3;
	//这是赋值运算符重载:
	s3 = s1; 
	cout << s3 << endl;
	s3 = "example";
	cout << s3 << endl;
	s3 = "c";
	cout << s3 << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

  演示结果如下:
在这里插入图片描述
  
  
  
  

2.3、string中元素访问及遍历相关

  本小节涉及的string类成员函数:

函数名称功能说明
string::length (重点)返回字符串有效字符长度
string::size (重点)返回字符串有效字符长度
string::operator[] (重点)获取字符串的字符
string::at获取字符串中的字符
string::back访问最后一个字符
string::front访问第一个字符

  
  

2.3.1、string::operator[] ,返回pos位置的字符,const string类对象调用

  1)、概述
在这里插入图片描述

 char& operator[] (size_t pos);
 const char& operator[] (size_t pos) const;

   细节解释:
  1、此处有两个重载,一个是char&、一个是const char&,分别对应两种不同的类。
  2、该运算符重载作用:①访问某个位置的字串;②和其它函数结合,能达到遍历字符串的目的。
  3、使用引用返回的原因:①通过[ ]访问到对应字符,要能支持可读可写,因此需要引用返回而非传值返回(后者只是临时拷贝)。②引用返回可减少拷贝(次要原因)。
  4、访问时需要注意越界问题。
  
  
  2)、运用举例

在这里插入图片描述演示一:对单字符

void test_string3()
{
	string s1("hello world!");
	cout << s1[2] << endl;//访问单字符
	s1[2] = 'y';//修改字符
	cout << s1 << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

  演示结果如下:
在这里插入图片描述
  
  

在这里插入图片描述演示二:对字符串遍历

void test_string3()
{
	string s1("hello world!");
	cout << s1 << endl;
	//遍历s1,每个字符+1:
	for (size_t i = 0; i < s1.size(); ++i)
	{
		s1[i]++ ;
	}
	cout << s1 << endl;


	const string s2("hello world!");
	//const不能被修改,但仍然能遍历:
	for (size_t i = 0; i < s2.size(); ++ i)
	{
		cout << s2[i] << " ";
	}

	cout << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

   演示结果如下:
在这里插入图片描述

  
  

2.3.2、string::size、string::length

   相关链接:sizelength
在这里插入图片描述

size_t length() const;
size_t size() const;

   注意事项:
   1、此处使用size还是length结果都一样,只是一个发展先后问题。一般而言,为与其它SQL相同,我们推荐使用size。

  演示代码如下:

void test_string3()
{
	string s1("hello world!");
	cout << s1 << endl;
	//遍历s1,每个字符+1:
	for (size_t i = 0; i < s1.length(); ++i)
	{
		s1[i]++ ;
	}
	cout << s1 << endl;

	const string s2("hello world!");
	//const不能被修改,但仍然能遍历:
	for (size_t i = 0; i < s2.size(); ++ i)
	{
		cout << s2[i] << " ";
	}
	cout << endl;
}

int main(void)
{
	test_string3();

	return 0;
}

   演示结果如下:
在这里插入图片描述

  
  

2.3.3、string::at、string::back、string::front

在这里插入图片描述

   注意事项:string::at 类似于operator[],但其是成员函数,失败后抛异常。
   事实上以下两个函数可以用operator[]替代。
在这里插入图片描述
在这里插入图片描述

  
  

2.3.4、习题练习: 仅仅反转字母

   题源
在这里插入图片描述

#include<ctype.h>
class Solution {
public:
    string reverseOnlyLetters(string s) {
        size_t left=0,right=s.size()-1;
        while(left<right)
        {
            while((left<right) && !isalpha(s[left]))
             left++;
            while((left<right) && !isalpha(s[right]))
             right++;

             swap(s[left],s[right]);
             left++;
             right--;
        }
        return s;
    }
    
};

  
  
  
  

2.4、迭代器

   在C++中,迭代器(Iterator)是一种用于遍历容器(如数组、向量、列表、集合等)中元素的对象。迭代器提供了一种通用的方式来访问和操作容器中的元素,而不需要关心容器底层的具体实现细节。
在这里插入图片描述

2.4.1、正向迭代器介绍iterator:string::begin、string::end

  1)、是什么
  迭代器,单词iterator,其包含在对应的类中,用法和指针类似,其有可能是指针,也有可能不是指针。

string::iterator 变量名 
vector<int>::iterator 变量名
list<int>::iterator 变量名

  1、使用迭代器时要注意对应的类,这里举例了string、vector、list,后两者属于模板故使用时加上了类型,但它们的基本格式一致。
  
  
  2)、正向迭代器介绍与使用
  相关链接:beginend
在这里插入图片描述
在这里插入图片描述

iterator begin();
iterator end()//可以看到iterator类型:a random access iterator to char (convertible to const_iterator)

  相关示意图:注意string::end指向的是最后一个字符的下一个位置,即指向\0。整体而言达成一个左闭右开的区间:[begin,end)
在这里插入图片描述

  使用举例如下:

void test_string4()
{
	string s("hello string!");
	string::iterator it = s.begin();//定义一个迭代器命名为it变量,让其指向string类中begin位置
	while (it != s.end())//遍历,并打印string类中元素
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main(void)
{
	test_string4();

	return 0;
}

  
  演示结果如下:可看到使用迭代器,我们将string类中的数据都一一遍历并打印。
在这里插入图片描述

  
  
  3)、为什么要学习迭代器?
  1、对于SQL,迭代器iterator是所有容器通用的访问方式,使用法类似,由于这种高度统一性,学会并理解一个,我们就能快速上手其它sql的迭代器。
  2、从使用角度来讲,迭代器也是一种封装结构,我们使用它时并未了解其底层实现。但实际上不同SQL中,迭代器实现可能不同。
  3、一般情况下,string不喜欢使用iterator,因为operate[]更好用。同理,vector也不喜欢使用iterator,因为operate[]更好用。这两者具有的特性都是物理地址空间连续,而对于list、map、set等一些物理地址空间不连续的sql,要访问相应数据,则需要使用迭代器。

  以下为迭代器在list中的用法,此处list是带头双向链表:

#include<list>
void test_string4()
{
	list<int> lt(10, 1);
	list <int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;
}

  以下为演示结果:
在这里插入图片描述

  4、一个问题说明lit != lt.end()it != s.end(),可以看到这里我们在判断结束条件时,使用的是!=,而非<。那么可不可以使用小于号?
  回答:对于string、vector这样物理地址空间连续的情况,可以使用<,而对于其它物理地址空间不连续的情况,使用<不能达到要求。
  
  
  
  

2.4.2、反向迭代器reverse_iterator

  1)、基本介绍
  相关链接:rbeginrend
在这里插入图片描述
在这里插入图片描述

reverse_iterator rbegin();
reverse_iterator rend();

//可以看到二者类型为reverse_iterator:reverse_iterator<iterator>

  示意图:可以看到rbegin、rendbegin、end大同小异,其使用方法类同。
在这里插入图片描述
  
  演示代码如下:

void test_string4()
{
	string s1("hello string!");
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;//需要注意这里仍旧是++;
	}
	cout << endl;
}

int main(void)
{
	test_string4();

	return 0;
}

  演示结果如下:我们的字符串是倒置打印的。
在这里插入图片描述

  
  

2.4.3、const迭代器:const_iterator、const_reverse_iterator

  1)、基本介绍:
  在上述两者中,我们看到string除了实现了普通迭代器,还对被const修饰的对象给予了其相应的const迭代器。
  用法与之前,区别只在于不能修改元素(可读不可写)。

  const正向:

const_iterator begin() const;
const_iterator end() const;

  const反向:

const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;

  实际使用时,由于其类型名称太长,我们可以用auto自动推演类型。
  
  演示代码如下:

void test_string4(const string& s)
{
	string::const_iterator it = s.begin();//正向
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	string::const_reverse_iterator rit = s.rbegin();//反向
	while (rit != s.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}
int main(void)
{
	
	string s("hello string!");
	test_string4(s);
	return 0;
}

  演示结果如下:
在这里插入图片描述
  
  

2.4.4、范围for与迭代器的关系

  实际上,范围for的底层就是迭代器,我们写了范围for,编译器会对它进行替换处理。

void test()
{
	string s("hello");
	string::iterator it = s.begin();
	while (it != s.end())
	{
		(*it)++;
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto& ch : s)
	{
		ch++;
		cout << ch << " ";
	}
	cout << endl;
	cout << s << endl;
}
void test()
{
	list<int> lt(10, 1);
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;

	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

  
  
  

2.5、元素插入提取、修改搜索相关

2.5.1、string::push_back、string::append

  1)、string::push_back基本讲解

  说明: 我们可以使用该函数对string进行单字符尾插。相关链接:string::push_back
在这里插入图片描述

void push_back (char c);

  演示代码如下:

void test_string1()
{
	string s1("hello string!");
	s1.push_back(' ');
	s1.push_back('v');
	s1.push_back('i');
	s1.push_back('m');
	cout << s1 << endl;
}

int main(void)
{
	test_string1();
	return 0;
}

  演示结果如下:
在这里插入图片描述

  
  
  2)、string::append基本讲解

  说明: push_back只能解决单字符,若要尾插字符串,则可使用该函数。相关链接:string::append

在这里插入图片描述
  

在这里插入图片描述演示一:对C字符串

string& append (const char* s);

  代码如下:

void test_string_1()
{
	string str("hello ");
	str.append("world!");
	cout << str << endl;
}

  演示结果如下:
在这里插入图片描述

  

在这里插入图片描述演示二:对迭代器

template <class InputIterator>   
string& append (InputIterator first, InputIterator last);

  append函数还可以使用迭代器进行追加,演示效果如下:

void test_string_2()
{
	string s1("hello string!");
	string s2("welcome to the world!");
	cout << s1 << "\n" << s2 << "\n" << endl;

	s1.append(s2.begin(), s2.end());
	cout << s1 << endl;

	s1.append(++s2.begin(), --s2.end());
	cout << s1 << endl;
}

在这里插入图片描述

  其意义所在:我们只需要改变begin、end,就可以追加任意区间段。(注意,这里是英文字符,若是中文字符,由于中文一般占两个字节,直接++、–效果不同,但逻辑含义一致)
在这里插入图片描述

  关于这里的迭代器更多的意义需要学到后面才能认知到。

  
  
  

2.5.2、string::operator+=

  1)、string::operator+=基本讲解

  比起string::push_backstring::append,我们更常用string::operator+=(前两者是函数,后者是运算符重载),其上述对单字符、对字符串,我们都可以用该函数做到。相关链接:string::operator+=

在这里插入图片描述

  演示代码如下:

void test_string_4()
{
	string s1("hello string!");
	s1 += " welcome";//对字符串
	string s2(" the world");
	s1 += s2;//对string类
	s1 += '!';//对字符
	cout << s1 << endl;
}

  演示结果如下:
在这里插入图片描述

  
  当然,上述代码也能用下述的+n-n来实现:

	s1.append(s2.begin()+3, s2.end()-3);
	cout << s1 << endl;

在这里插入图片描述

  
  

2.5.3、习题: 字符串相加

  题源
在这里插入图片描述

  解析:此题的实际背景是实现大整数运算。因为int、unsigned int等都有其范围,对于一些庞大的数值,可以将其转化为字符串形式,但这样一来对其的运算比如加减乘除、按位域异或等需要重新思考。
  
  

2.5.3.1、头插写法

在这里插入图片描述在insert中不使用迭代器的情况

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1;
        int end2=num2.size()-1;
        
        string num;
        int next =0;
        while(end1>=0 || end2>=0)
        {
            //防止一个结束,另一个未结束:
            int val1=end1>=0?num1[end1]-'0':0;
            int val2=end2>=0?num2[end2]-'0':0;
            //当前位数相加结果:
            int val=val1+val2+next;
            next=val>9?1:0;
            //在新的string类中插入数值:
            //头插:
            num.insert(0,1,'0'+(val%10));
            --end1;
            --end2;
        }
        //处理next进位
        if(next)
            num.insert(0,1,'1');

        return num;
    }
};

  

在这里插入图片描述在insert中使用迭代器的情况

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1;
        int end2=num2.size()-1;
        
        string num;
        int next =0;
        while(end1>=0 || end2>=0)
        {
            //防止一个结束,另一个未结束:
            int val1=end1>=0?num1[end1]-'0':0;
            int val2=end2>=0?num2[end2]-'0':0;
            //当前位数相加结果:
            int val=val1+val2+next;
            next=val>9?1:0;
            //在新的string类中插入数值:
            //头插:
            num.insert(num.begin(),'0'+(val%10));
            --end1;
            --end2;
        }
        //处理next进位
        if(next)
            num.insert(num.begin(),'1');

        return num;
    }
};

  
  

2.5.3.2、尾插写法

  由于头插效率极低,因此此处我们一般建议尾插处理:

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1;
        int end2=num2.size()-1;
        
        string num;
        int next =0;
        while(end1>=0 || end2>=0)
        {
            //防止一个结束,另一个未结束:
            int val1=end1>=0?num1[end1]-'0':0;
            int val2=end2>=0?num2[end2]-'0':0;
            //当前位数相加结果:
            int val=val1+val2+next;
            next=val>9?1:0;
            //在新的string类中插入数值:
            //尾插:
            num+=('0'+(val%10));
            --end1;
            --end2;

        }
        //处理next进位
        if(next)
           // num.insert(num.begin(),'1');
            num+='1';

        //字符串逆置:使用了算法库
        reverse(num.begin(),num.end());
        return num;
    }
};

  

2.5.3.3、相关涉及函数:string::insert、std::reverse

  insert将在后续介绍:
在这里插入图片描述

  reverse该函数在头文件中:

template <class BidirectionalIterator>  
void reverse (BidirectionalIterator first, BidirectionalIterator last);

在这里插入图片描述
  
  
  
  
  

2.5.4、string::insert、string::assign、string::erase

2.5.4.1、插入字符串:string::insert

  1)、string::insert基本讲解

  相关链接:string::insert
在这里插入图片描述
  
  2)、使用演示

在这里插入图片描述习题演示一:要求在下述字符串中空白位置插入20%

void test_string1()
{
	string str("wo lai le");
	//要求:将上述字符串空白位置插入20%
	for (int i = 0; i < str.size();++i)
	{
		if (str[i] == ' ')
			str.insert(i, "20%");
	}
	cout << str << endl;
}

  思考:这样写法有没有什么问题?
  回答:insert头插,挪动了数据位置,而i仍旧指向pos位置,因此++i后再次符合条件时仍旧遇到原先的' '(空格),会导致在此处死循环无限插入。

  解决方案一:

void test_string1()
{
	string str("wo lai le");
	for (int i = 0; i < str.size();)
	{
		if (str[i] == ' ')
		{
			str.insert(i, "20%");
			i += 4;//要跳过已经访问到的空格
		}
		else
		{
			++i;//将++i单独放置
		}
	}
	cout << str << endl;
}

  解决方案二:

void test_string1()
{
	string str("wo lai le");
	for (int i = 0; i < str.size();i++)
	{
		if (str[i] == ' ')
		{
			str.insert(i, "20%");
			i += 3;
		}
	}
	cout << str << endl;
}

在这里插入图片描述
  
  
  
  

2.5.4.2、从字符串中删除字符:string::erase

  1)、string::erase基本讲解

在这里插入图片描述习题演示二:要求在下述字符串中空白位置插入20%,在此基础上删除空白字符或则替换空白字符呢?

  我们可以使用下列函数:erase
在这里插入图片描述

string& erase (size_t pos = 0, size_t len = npos);

  erase这里给了三个函数重载,注意第一个函数的缺省参数。当我们传入的实参len<sizelen>=size时,消除的字符串长度不同。
  
  上述问题演示如下:

void test_string1()
{
	string str("wo lai le");
	for (size_t i = 0; i < str.size(); ++i)
	{
		if (str[i] == ' ')
		{
			str.insert(i, "20%");
			i += 3;
		}
	}
	cout << str << endl;

	for (size_t i = 0; i < str.size(); ++i)
	{
		if (str[i] == ' ')
		{
			str.erase(i, 1);
		}
	}
	cout << str << endl;
}

  
在这里插入图片描述
  
  
  
  2)、方法改进:以空间换时间
  上述这种方法其时间复杂度不太行,此处提供一种以空间换时间的方法:

void test_string1()
{
	string str("wo lai le");
	string newstr;
	for (size_t i = 0; i < str.size(); i++)
	{
		if (str[i] != ' ')
			newstr += str[i];
		else
		{
			newstr += "20%";
		}
	}
	cout << newstr << endl;
}

  事实上,上述此题解法中,也可以用replace实现。
  
  
  
  
  

2.5.5、返回C字符串:string::c_str

  1)、基本讲解
在这里插入图片描述
  将string类转换为对应的C字符串,一般常用于C\C++多文件中。以下为使用说明。(实际应用举例:网络基础套接字那部分常涉及该函数。)
  
  
  2)、使用说明
在这里插入图片描述
  
  
  
  

2.5.6、string::find、string::refind、string::substr

  1)、基本讲解:

在这里插入图片描述string::find正序,在类中找首次出现的对应内容(字符、字符串、子类)

在这里插入图片描述
  注意点:
  1、注意这些参数的缺省值各自的含义。比如,size_t pos = 0,表示若不给值,则从首位址开始找
  2、注意其函数返回值。如下,若找到,则返回对应位置下标,找不到,则返回string::npos。因此,我们使用时,谨慎起见可加入条件判断。

The position of the first character of the first match.
If no matches were found, the function returns string::npos.

  
  

在这里插入图片描述string::refind查找字符串中最后一次出现的内容(find的逆序查找)

在这里插入图片描述
  说明:rfind用法和find大同小异,size_t pos = npos告诉我们默认从末尾元素找起,找不到仍旧返回string::npos
  
  

在这里插入图片描述string::substr获取子串

在这里插入图片描述

string substr (size_t pos = 0, size_t len = npos) const;

  说明:从pos位置(默认从0开始)处开始获取长度为len的子类,若不给确切n值,则len取到字符尾部。
  
  
  2)、使用演示:

  场景要求:给定一个文件名,打印其后缀

void test_string1()
{
	//要求:取得该文件名称的后缀
	string filename("test.cpp");
	//步骤:
	size_t pos = filename.find('.');
	if (pos != string::npos)
	{
		//若给定第二参数,需要注意减法含义
		string suff1 = filename.substr(pos, filename.size() - pos);
		cout << suff1 << endl;
		//若不给第二参数,需要注意默认npos的含义
		string suff2 = filename.substr(pos);
		cout << suff2 << endl;
	}
}

在这里插入图片描述

  
  
  当前情景下我们是通过正序遍历去找'.',实际上当文件有多个后缀名时,比如test.cpp.tar.zip。此时我们需要的是该文件的真实后置,即.zip。那么当前写法就会出问题,对此,提出了rfind。

void test_string1()
{
	//要求:取得该文件名称的后缀
	string filename("test.cpp.tar.zip");
	//步骤:
	size_t pos = filename.rfind('.');
	if (pos != string::npos)
	{
		//若给定第二参数,需要注意减法含义
		string suff1 = filename.substr(pos, filename.size() - pos);
		cout << suff1 << endl;
		//若不给第二参数,需要注意默认npos的含义
		string suff2 = filename.substr(pos);
		cout << suff2 << endl;
	}
}

在这里插入图片描述

  
  
  
  3)、实际场景演示:取网址分割域名(网络协议)
  问题说明:给你一串网址,要求分割其每部分(协议、域名、路径)

	string url1 = "https://cplusplus.com/reference/string/string/rfind/";
	string url2 = "https://www.ncbi.nlm.nih.gov/nuccore/NM_005368.3";
	string url3 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";

  
  思路如下:
  特点1:://,协议与域名的区分界限
  特点2:://后,首个/前的这一串,即为域名。
  
  
  写法如下:

void DearUrl(const string& url)
{
	//取协议
	size_t pos1 = url.find("://");//从头开始找.//分隔符
	if (pos1 == string::npos)//检查find所取值是否存在
	{
		cout << "非法url" << endl;
		return;
	}
	//取得https该段字符串,需要注意substr的参数输入含义
	string protocol = url.substr(0, pos1);
	cout << protocol << endl;

	//取域名
	size_t pos2 = url.find('/', pos1 + 3);//要将://跳过
	//需要注意此处参数的含义,第二参数中pos2-(pos1+3)
	string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);
	cout << domain << endl;

	//取资源位置
	string uri = url.substr(pos2 + 1);
	cout << uri << endl;

	cout << endl;
}

void test_string3()
{


	string url1 = "https://cplusplus.com/reference/string/string/rfind/";
	string url2 = "https://www.ncbi.nlm.nih.gov/nuccore/NM_005368.3";
	string url3 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";

	DearUrl(url1);
	DearUrl(url2);
	DearUrl(url3);

}

在这里插入图片描述

  
  

  
  

2.6、与扩容相关

  总述:
在这里插入图片描述

2.6.1、string::max_size、string::capacity

  1)、基本讲解
在这里插入图片描述
在这里插入图片描述

  演示代码如下:

void test_string2()
{
	string s1("hello string!");
	cout << s1.max_size() << endl;//相同机器下固定值,实际中一般不太用到它,没有太大的意义。
	cout << s1.capacity() << endl;
}

  
  演示结果如下:
在这里插入图片描述
  
  
  
  2)、扩容机制演示:
  演示代码如下:

void test_string2()
{
	string s = "abcde";

	size_t sz = s.capacity();
	cout << "capacity now: " << sz << '\n';

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)//插入100个数据
	{
		s.push_back('c');
		if (sz != s.capacity())
		{//当新旧容量不一致时,说明该对象进行了扩容
			sz = s.capacity();//那么我们更新一下sz记录的数据
			cout << "capacity changed: " << sz << '\n';//并将其显示出来
		}
	}

}

int main(void)
{
	test_string2();
	return 0;
}

  演示结果如下:
在这里插入图片描述
  
  
  
  

2.6.2、重新为stirng类申请容量:string::reserve

  1)、基本讲解
在这里插入图片描述

  说明:为了避免频繁扩容(注意与reverse区别),在知道预计的容量空间时可以使用该函数。

  演示代码如下:

void test_string2()
{
	string s = "abcde";

	s.reserve(1000);//
	size_t sz = s.capacity();
	cout << "capacity now: " << sz << '\n';

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)//插入100个数据
	{
		s.push_back('c');
		if (sz != s.capacity())
		{//当新旧容量不一致时,说明该对象进行了扩容
			sz = s.capacity();//那么我们更新一下sz记录的数据
			cout << "capacity changed: " << sz << '\n';//并将其显示出来
		}
	}

}

  演示结果如下:
在这里插入图片描述
  
  
  
  

2.6.3、string::resize

  1)、基本讲解
在这里插入图片描述

  演示代码如下:
  若给两个参数其会开空间并初始化

void test_string2()
{
	string s = "abcde";

	//s.reserve(1000);
	//s.resize(1000);
	s.resize(1000,'h');
	size_t sz = s.capacity();
	cout << "capacity now: " << sz << '\n';

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)//插入100个数据
	{
		s.push_back('c');
		if (sz != s.capacity())
		{//当新旧容量不一致时,说明该对象进行了扩容
			sz = s.capacity();//那么我们更新一下sz记录的数据
			cout << "capacity changed: " << sz << '\n';//并将其显示出来
		}
	}

}

  演示结果如下:
在这里插入图片描述
在这里插入图片描述

  
  
  

  

2.7、string类非成员函数

   总览:
在这里插入图片描述

  
  

2.7.1、流插入流提取(输入输出运算符重载)

  1)、概述与运用
  string类中也对流插入和流提取运算符进行了重载。
在这里插入图片描述
  相关链接:流提取流插入

ostream& operator<< (ostream& os, const string& str);
istream& operator>> (istream& is, string& str);

  使用举例:

#include<string>

void test_string1()
{
	string s1;
	string s2("hello!");
	cin >> s1;
	cout << s1 << endl;
	cout << s2 << endl;
}

int main(void)
{
	test_string1();

	return 0;
}

在这里插入图片描述
  
  
  
  

2.7.2、getline (以例题形式介绍:字符串最后一个单词的长度)

  1)、问题引入:字符串最后一个单词的长度
  题源
在这里插入图片描述

  
   总体思路:反向查询到第一次出现空格的位置,往后至\0即得最后一个字单词。

   错误演示:

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

int main(void)
{
   
    string str;
    cin >> str;//使用cin导致错误
    size_t pos=str.rfind(' ');
    if(pos!=string::npos)
    {	//size是最后一个字符的下一个位置:起点为1,pos是下标位置,起点为0
        cout<< str.size()-pos-1;//size-(pos+1)
    }
    else//需要注意只有单个单词的情况,该场景下rfind取不到空格。
    {
        cout<< str.size();
    }
}

   细节说明:需要注意cin、scanf等使用情况。二者在缓冲区取到的数据是以空格或换行符区分的。

cin>>str;

   此处出错的原因是:我们输入的英文句子里本身就有空格,cin会把它们当成多次值输入时分割,只取空格或换行前的数据记录在一个变量中,剩下的(输入缓冲区里)被当作下一次变量值。
   因此需要使用getline。

   正确演示:getline
在这里插入图片描述

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

int main(void)
{
   
    string str;
    getline(cin,str);
    size_t pos=str.rfind(' ');
    if(pos!=string::npos)
    {
        cout<< str.size()-pos-1;
    }
    else
    {
        cout<< str.size();
    }

}

  
  
  
  

2.8、与数值转换相关的函数

2.8.1、总览

在这里插入图片描述
在这里插入图片描述

  
  
  
  
  

2.9、 相关习题

2.9.1、字符串中的第一个唯一字符

  题源
在这里插入图片描述

class Solution {
public:
    int firstUniqChar(string s) {
        int CountArr[26]={0};//定义拥有26个元素的数组,用于统计string类中s中字母出现次数。
        for(auto ch:s)//通过范围for来遍历
        {
            CountArr[ch-'a']++;
        }
        for(int i=0;i<s.size();i++)//遍历string类,依次通过映射关系来判断其中字母是否唯一
        {
            if(CountArr[s[i]-'a']==1)
                return i;
        }
        return -1;
    }
};

  
  
  
  

2.9.2、验证回文串

  题源
在这里插入图片描述

class Solution {
public:
    bool isPalindrome(string s) {
        //范围for:大写转小写
        for(auto& ch :s)
        {
            if(ch>='A' && ch<='Z')
                ch+=32;
        }
        int begin=0;int end=s.size()-1;
        while(begin<end)
        {
            while(begin<end && !isalnum(s[begin]))
                ++begin;
            while(begin<end && !isalnum(s[end]))
                --end;
            if(s[begin]!=s[end])
                return false;
            else
            {
                begin++;
                end--;
            }
        }
        return true;
    }
};

  
  
  
  
  
  

  
  

Fin、共勉。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值