C++之String类(下)

片头

       嗨喽~ 我们又见面啦,在上一篇C++之String类(上)中,我们对string类的函数有了一个初步的认识,这一篇中,我们将继续学习string类的相关知识。准备好了吗?咱们开始咯~

二、标准库中的string类

2.5 string类对象的修改操作
函数名称功能说明
push back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+=在字符串后追加字符串str
assign给字符串原有的内容进行覆盖,类似于赋值
insert用于在字符串的pos位置插入一个字符串、字符串的前n个字符或n个字符c
erase用于在字符串的pos位置删除len个字符
replace替换源字符串中的字符
c str返回c格式字符串
rfind + npos从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
find从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回
swap用于交换2个string类对象
operator+返回一个新构造的string类对象,其值是lhs和rhs的合并
getline用于字符串的输入

(1)push back函数 

我们测试一下:

void test_string15() {
	string s1("hello world");
	cout << s1 << endl;

	//在s1字符串的后面尾插'x'
	s1.push_back('x');
	cout << s1 << endl;
}


(2)append函数

我们测试一下:

void test_string16() {
	string s1("hello world");
	cout << s1 << endl;

	//在s1字符串的后面追加一个字符串
	s1.append(" yyyyyy!!!!");
	cout << s1 << endl;
}

(3)此外,我们还可以使用运算符operator+=函数重载

代码如下:

void test_string17() {
	string s1("hello world");
	cout << s1 << endl;

	string s2("111111");

	//在s1字符串的后面追加一个字符串
	s1 += 'y';
	s1 += "zzzzzzzzzzz";
	s1 += s2;
	cout << s1 << endl;
}

注意:

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

②对string操作时,如果能够大概预估到放多少字符,可以先通过reverse把空间预留好


(4)assign函数

assign函数类似于给字符串原有的内容进行覆盖,类似于赋值

void test_string18() {
	string s1("hello world");
	cout << s1 << endl;

	s1.assign("11111");
	cout << s1 << endl;
}

  


 (5)insert函数

①在s1字符串前头插"xxxxx"  

void test_string19() {
	string s1("hello world");
	cout << s1 << endl;

	//在s1字符串前头插"xxxxx"
	s1.insert(0, "xxxxx");
	cout << s1 << endl;
}

 ②在s1字符串前头插一个字符'y'

void test_string20() {
	string s1("hello world");
	cout << s1 << endl;

	//在s1字符串前头插单个字符'y'
	s1.insert(0, 1,'y');
	cout << s1 << endl;
}

③使用迭代器,在s1字符串钱头插单个字符'y'

void test_string21() {
	string s1("hello world");
	cout << s1 << endl;

	//在s1字符串前头插单个字符'y'
	s1.insert(s1.begin(), 'y');
	cout << s1 << endl;
}

  ④在s1字符串前插入s2字符串,二者都采用迭代器的形式

void test_string22() {
	string s1("hello world");
	cout << s1 << endl;

	string s2("11111");

	//在s1字符串前头插s2字符串
	s1.insert(s1.begin(),s2.begin(),s2.end());
	cout << s1 << endl;
}


 (6)erase函数

①当pos=0,len=1,说明,从下标为0的元素开始,删除1个字符

void test_string23() {
	string s1("hello world");
	cout << s1 << endl;
	
	//从下标为0的元素开始,删除1个字符
	s1.erase(0, 1);
	cout << s1 << endl;
}

  

②当我不传第二个参数的时候,len默认为npos

void test_string24() {
	string s1("hello world");
	cout << s1 << endl;

	//省略第二个参数
	//默认删除从下标为4后的所有元素
	//包括下标为4的元素
	s1.erase(4);
	cout << s1 << endl;
}

③当我传入的第二个参数len大于字符串本身的长度时

void test_string25() {
	string s1("hello world");
	cout << s1 << endl;

	//从下标为3的位置开始,
	//删除100个元素
	s1.erase(3, 100);
	cout << s1 << endl;
}

 

另外,使用erase函数时,第二个参数len可以不合法,但是第一个参数pos必须合法!


(7)replace函数

哇哦,咋一看,这个函数怎么有这么多的用法?!

不慌,我们慢慢来看~

 ①string& replace(size_t pos,size_t len,const string& str);

void test_string27() {
	string s1("hello world");
	cout << s1 << endl;

	//将下标为5的位置,1个字符
	//替换为20%
	s1.replace(5, 1, "20%");
	cout << s1 << endl;
}

小练习:将下面字符串中出现的空白字符替换为"20%"

void test_string28() {
	string s1("hello world hello bit");
	cout << s1 << endl;

	for (int i = 0; i < s1.size();) {
		if (s1[i] == ' ') {
			s1.replace(i, 1, "20%");
			i += 3;
		}
		else {
			i++;
		}
	}
	cout << s1 << endl;
}

或者可以用另一种解法:

void test_string29() {
	string s1("hello world hello bit");
	cout << s1 << endl;

	string s2;
	//使用范围for
	for (auto e : s1) {
		if (e != ' ') {
			s2 += e;
		}
		else {
			s2 += "20%";
		}
	}
	cout << s2 << endl;
}

(8) c_str函数

const char* c_str() const;

 按照C语言的格式返回字符串

例如:

void test_string38() {
	string s1("hello");
	s1 += '\0';
	s1 += "world";

	cout << s1 << endl;
	cout << s1.c_str() << endl;
}

在hello后面加上'\0'和字符串world,重载后的流插入操作符函数会按照size的大小来打印字符串

而打印c_str的返回值,会遇到'\0'就停下

(9) rfind + npos函数

size_t rfind(char c,size_t pos = npos)const;

static const size_t npos = -1;

从字符串的pos位置向前找字符c,返回该字符在字符串中的位置

npos是string类中定义的一个静态成员变量,类型为无符号整型,值为-1,因为是无符号转换后变成整型的最大值,也就是4294967295(42亿多) ,当我们不给pos的值,按照缺省值执行,默认从字符串尾部开始寻找。

例如:

void test_string39() {
	string s1("hello");
	//从后往前寻找第一次出现的'l'
	cout << s1.rfind('l') << endl;
}

(10) find函数

size_t find (char c,size_t pos = 0) const;

 从字符串的pos位置向后找字符c,返回该字符在字符串中的位置

例如:

void test_string40() {
	string s1("hello");
	//从前往后寻找第一次出现的'e'
	cout << s1.find('e') << endl;
}

 (11)substr函数

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

 在str中从pos位置开始,截取len个字符,然后将其返回

例如:

void test_string41() {
	string s1("hello world!");
	//从下标为0的元素开始,截取5个字符
	string s2 = s1.substr(0, 5);
	cout << s2 << endl;
}

假设有个字符串string.cpp.zip,我想取后缀名.zip,怎么办?可以用rfind函数和substr函数

void test_string47() {
	string file("string.cpp.zip");
	//rfind函数:从后往前寻找'.'
	size_t pos = file.rfind('.');
	//从pos位置开始,截取file.size() - pos个字符
	string suffix1 = file.substr(pos, file.size() - pos);

	//或者省略第二个参数,默认为npos
	//若省略,则截取从pos位置开始的所有字符
	string suffix2 = file.substr(pos);
	cout << suffix1 << endl;
	cout << suffix2 << endl;
}

我们还可以利用find函数和substr函数来截取网址的部分内容,把网址切割成小段

void test_string48() {
	string url("https://www.bilibili.com/video/BV14QtzeUENE/?spm_id_from=333.1007.tianma.2-1-3.click&vd_source=689f4b5e68ebdf07d03671e221f3deb0");
    
    //截取https
	size_t pos1 = url.find(':');
	string url1 = url.substr(0, pos1 - 0);
	cout << url1 << endl;

	//截取www.bilibili.com
	size_t pos2 = url.find('/', pos1 + 3);
	string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << url2 << endl;

	//截取video
	size_t pos3 = url.find('/', pos2 + 1);
	string url3 = url.substr(pos2 + 1, pos3 - (pos2 + 1));
	cout << url3 << endl;
}

(12) swap函数

void swap (string& str);

用于交换2个string类对象

string类中的swap函数相比标准库中的swap函数,交换string类对象的效率更高

例如:

void test_string42() {
	string s1("hello");
	string s2("world!");
	//将s1字符串和s2字符串进行交换
	s1.swap(s2);
	cout << s1 << endl;
	cout << s2 << endl;
}

(13) operator+ 函数

string operator+ (const string& lhs,const string& rhs);

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

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

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

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

 返回一个新构造的string类对象,其值是lhs和rhs的合并

例如:

void test_string43() {
	string s1("hello");
	string s2("world!");
	string s3;

	s3 = s1 + s2;
	cout << s3 << endl;

	s3 = s1 + "123";
	cout << s3 << endl;

	s3 = "123" + s2;
	cout << s3 << endl;

	s3 = s1 + "x";
	cout << s3 << endl;

	s3 = 'x' + s1;
	cout << s3 << endl;
}

(14)getline函数

istream& getline(istream& is,string& str,char delim);

istream& getline(istream& is,string& str); 

 用于字符串的输入

在讲这个函数之前,我们先来练习一道题~

思路:我们可以找到最后一个单词的前一个空格,设为pos位置。用总长度-(pos+1)得到的就是最后一个单词的长度。

这是因为,无论是cin还是scanf函数,遇到空格或者换行就停止读取字符。默认规定空格或者换行是多个值之间分割。

那该怎么办呢?我们可以使用getline函数,getline函数遇到空格不会停止读取,遇到换行才会停止读取

#include <iostream>
using namespace std;

int main() {
    string s;
    getline(cin,s);

    int pos = s.rfind(' ');
    int len = s.size()-(pos+1);
    cout<<len<<endl;

    return 0;
}

相比cin遇到空格就停止读取,我们可以给出分隔符delim,遇到delim才停止读取。如果没有给出,则遇到换行停止读取。

例如:

void test_string44() {
	string s1;
	//没有给出分隔符delim
	//遇到换行,停止读取
	getline(cin, s1);
	cout << s1 << endl;

	//给出分隔符'%'
	//遇到分隔符,停止读取
	getline(cin, s1, '%');
	cout << s1 << endl;
}

 

(15) find_first_of函数

size_t find_first_of(const string& str,size_t pos=0)const;

size_t find_first_of(const char* s,size_t pos=0)const;

从前往后寻找在字符串中与其参数中指定的所有字符相匹配的任意字符

void test_string49() {
	std::string str("please,replace the vowels in this sentence by asterisks.");
	//从字符串中找到"aeiou"中任意一个字符,替换为'*'
	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';
}

(16) find_last_of函数

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

size_t find_last_of(const char* s,size_t pos = npos)const;

从后往前寻找在字符串中与其参数中指定的所有字符相匹配的任意字符 

void SplitFilename(const std::string& str) {
	std::cout << "Splitting:" << str << '\n';
	std::size_t found = str.find_last_of("/\\");
	std::cout << "path:" << str.substr(0, found) << '\n';
	std::cout << "file:" << str.substr(found + 1) << '\n';
}

void test_string50() {
	std::string str1("/user/bin/man");
	std::string str2("c:\\windows\\winhelp.exe");

	cout << str2 << endl;

	SplitFilename(str1);
	SplitFilename(str2);
}

2.6 各种运算符重载函数

(1)operator>>和operator<<

istream& operator>>(istream& is,string& str);

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

用于string对象的流提取和流插入

例如:

void test_string45() {
	string s1;
	cin >> s1;
	cout << s1 << endl;
}

 

(2)比较运算符

用于比较2个字符串的对应位置的ASCII码值大小

例如:

void test_string46() {
	string s1("abcde");
	string s2("bbcde");

	cout << (s1 > s2) << endl;
	cout << (s1 >= s2) << endl;
	cout << (s1 < s2) << endl;
	cout << (s1 <= s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 != s2) << endl;
}


刚刚我们学习了这么多的函数,一起来练练几道题吧~

小练习1:反转字符串

emmm,这道题是想表达什么意思呢?举2个例子吧~

OK,本题分析完成,具体代码如下:

class Solution {
public:
    void reverseString(vector<char>& s) {
        int begin = 0;          //begin从下标为0的位置开始
        int end = s.size()-1;   //end从最后一个元素的下标开始

        while(begin < end)      //当begin和end相遇时,退出循环
        {
            //交换
            int temp = s[begin];
            s[begin] = s[end];
            s[end] = temp;

            begin++;            //begin更新    
            end--;              //end更新
        }
    }
};

小练习2:仅仅反转字母

emmm,这道题和上面反转字符串很像,但是不太一样,因为这道题,字符串里面不仅有字母,还有其他符号。我们要做的,只是将字母反转,其他符号的位置不变。

第一步,我们首先要判断当前字符是否为字母还是其他符号:

//判断当前字符是否为字母
//注意:用短路或来连接
bool isLetter(char c)
{
        return c >= 'A' && c <= 'Z'
    ||         c >= 'a' && c <= 'z';
}

第二步,反转字母(定义begin和end,如果遇到字母,进行交换,否则直接跳过)

string reverseOnlyLetters(string s) {
        int begin = 0;          //begin指向第一个元素的位置
        int end = s.size()-1;   //end指向最后一个元素的位置

        while(begin < end)      //当begin和end相遇时,退出循环
        {   
            //当前字符不是字母,直接跳过,不需要反转
            while(begin < end && !isLetter(s[begin]))
            {
                begin++;
            }
            while(begin < end && !isLetter(s[end])){
                end--;
            }

            //当前字符为字母,需要反转,进行交换
            int temp = s[begin];
            s[begin] = s[end];
            s[end] = temp;

            //交换完毕后,begin更新,end也更新
            begin++;
            end--;
        }

        return s;   //最后返回s字符串
    }

小练习3:字符串中的第一个唯一字符

emmm,举个例子呗~

另外,这道题特别说明了:1<=s.length<=10^5,s只包括小写字母

第一步,我们需要给26个小写字母开辟数组,数组的类型为int(开辟空间足够大)

 //给26个小写字母开辟空间,初始化为0
 int count[26] = {0};

第二步,我们需要统计字符串中的每个字符出现的次数

//利用范围for,统计字符串中的每个字符出现的次数
//这里的count数组里面的元素是按照顺序的
//比如:下标为0是'a',下标为1是'b',下标为2是'c'

  for(auto e: s){
   count[e-'a']++;
}

第三步:遍历s字符串,找出第一次出现唯一字符

 //遍历s字符串,找出第一次出现的唯一字符
        //如果找到了,立即返回下标
        for(int i = 0;i<s.size();i++){
            if(count[s[i]-'a'] == 1){
                return i;
            }
        }
        //如果没有找到,返回-1
                return -1;

OK,本题完整代码如下:

class Solution {
public:
    int firstUniqChar(string s) {
        //给26个小写字母开辟空间,初始化为0
        int count[26] = {0};

        //利用范围for,统计字符串中的每个字符出现的次数
        //这里的count数组里面的元素是按照顺序的
        //比如:下标为0是'a',下标为1是'b',下标为2是'c'
        for(auto e: s){
            count[e-'a']++;
        }

        //遍历s字符串,找出第一次出现的唯一字符
        //如果找到了,立即返回下标
        for(int i = 0;i<s.size();i++){
            if(count[s[i]-'a'] == 1){
                return i;
            }
        }
        //如果没有找到,返回-1
                return -1;
    }
};

方法二:直接将字符串放入另一个数组中,统计字符串中的第一个唯一字符

class Solution {
public:
    int firstUniqChar(string s) {
        //开辟数组,初始化为0
        int count[256] = {0};

        //将s字符串里面的每一个元素都放入数组
        //相同的字符,每放入1次,该下标对应的值自增1次
        for(int i = 0; i<s.size();i++){
            count[s[i]]++;
        }

        //遍历s字符串,找出第一次出现的唯一字符
        for(int i = 0;i<s.size();i++){
            if(count[s[i]] == 1){
                return i;
            }
        }
        return -1;
    }
};

小练习4:验证回文串

emmm,举个例子吧~

第一步:判断是否为字母数字字符

    //1.判断是否为字母数字字符
    bool isLetterOrNumber(char c){
        return c >= '0' && c <= '9'
        ||     c >= 'a' && c <= 'z'
        ||     c >= 'A' && c <= 'Z';
    }

第二步:将所有的大写字母转换为小写字母(使用范围for时,必须传引用,这样才能保证修改e的同时,修改源字符串)

         //1.大写字符转换为小写字符
         for(auto& e:s){
            if(e >= 'A' && e<= 'Z'){
                e += 32;
            }
         }

第三步:验证是否为回文串

 //2.验证是否为回文串
         int begin = 0;         //begin指向第一个元素
         int end = s.size()-1;  //end指向最后一个元素

         while(begin < end)     //当begin和end相遇时,循环停止
         {
            //遇到非字母数字字符时,直接跳过
            while(begin < end && !isLetterOrNumber(s[begin])){
                begin++;
            }
            while(begin < end && !isLetterOrNumber(s[end])){
                end--;
            }

            //2个字母数字字符进行比较
            //如果不相同,返回false
            //如果相同,begin++,end--
            if(s[begin] != s[end]){
                return false;
            }else{
                begin++;
                end--;
            }
         }
            //比较完毕,begin和end指向的元素都相同,返回true
         return true;

OK,本题完整代码如下:

class Solution {
public:
    //1.判断是否为字母数字字符
    bool isLetterOrNumber(char c){
        return c >= '0' && c <= '9'
        ||     c >= 'a' && c <= 'z'
        ||     c >= 'A' && c <= 'Z';
    }
    bool isPalindrome(string s) {
         //1.大写字符转换为小写字符
         for(auto& e:s){
            if(e >= 'A' && e<= 'Z'){
                e += 32;
            }
         }

         //2.验证是否为回文串
         int begin = 0;         //begin指向第一个元素
         int end = s.size()-1;  //end指向最后一个元素

         while(begin < end)     //当begin和end相遇时,循环停止
         {
            //遇到非字母数字字符时,直接跳过
            while(begin < end && !isLetterOrNumber(s[begin])){
                begin++;
            }
            while(begin < end && !isLetterOrNumber(s[end])){
                end--;
            }

            //2个字母数字字符进行比较
            //如果不相同,返回false
            //如果相同,begin++,end--
            if(s[begin] != s[end]){
                return false;
            }else{
                begin++;
                end--;
            }
         }
            //比较完毕,begin和end指向的元素都相同,返回true
         return true;
    }
};

小练习5:字符串相加

emmm,举个例子吧~

面对这种题,该怎么做呢?

题目要求:不能使用任何内建的用于处理大整数的库,也不能直接将输入的字符串转换为整数形式

那我们可以把字符串拆成个位,十位,百位......然后将个位和个位相加,十位和十位相加,百位和百位相加

第一步:我们先将num1和num2字符串的最后一个元素取出来,保存到end1和end2

    int end1 = num1.size()-1;//end1保存的是num1字符串的最后一位(个位)
    int end2 = num2.size()-1;//end2保存的是num2字符串的最后一位(个位)

第二步:假设变量next表示进位,字符串s表示累加的最终结果。我们将num1和num2的个位数由字符转换为数字,进行相加,注意要处理进位的情况。

            int next = 0;   //进位
            string s;       //返回的最终结果

        //当end1和end2都不存在时,退出循环,用短路或来连接
    while(end1 >= 0 || end2 >= 0)
    {
        int x1 = end1 >= 0 ? num1[end1--]-'0' : 0;//x1表示把当前字符转换为数字
        int x2 = end2 >= 0 ? num2[end2--]-'0' : 0;//x2表示把当前字符转换为数字
        int x = x1+x2+next;//x表示2个位数进行累加的结果

                next = x/10;      //进位
                x = x % 10;       //对应位上的数

                s.insert(0,1,'0'+x);//头插,将数字转换为字符
              //s.insert(s.begin(),'0'+x);//使用迭代器
     }

            if(next == 1)       //当进位为1,直接在字符串s的前面头插"1"
            {
                s.insert(0,1,'1');
             // s.insert(s.begin(),'0'+x);//使用迭代器
            }

            return s;           //返回s字符串

OK,这道题我们解决了,完整代码如下:

class Solution {
public:
    string addStrings(string num1, string num2) {
    int end1 = num1.size()-1;//end1保存的是num1字符串的最后一位(个位)
    int end2 = num2.size()-1;//end2保存的是num2字符串的最后一位(个位)

            int next = 0;   //进位
            string s;       //返回的最终结果

        //当end1和end2都不存在时,退出循环,用短路或来连接
while(end1 >= 0 || end2 >= 0){
int x1 = end1 >= 0 ? num1[end1--]-'0' : 0;//x1表示把当前字符转换为数字
int x2 = end2 >= 0 ? num2[end2--]-'0' : 0;//x2表示把当前字符转换为数字
int x = x1+x2+next;//x表示2个位数进行累加的结果

                next = x/10;      //进位
                x = x % 10;       //对应位上的数

                s.insert(0,1,'0'+x);//头插,将数字转换为字符
              //s.insert(s.begin(),'0'+x);//使用迭代器
            }

            if(next == 1)       //当进位为1,直接在字符串s的前面头插"1"
            {
                s.insert(0,1,'1');
              // s.insert(s.begin(),'0'+x);//使用迭代器
            }

            return s;           //返回s字符串
    }
};

不过,每次插入要挪动数据,插入N次,挪动次数合计是等差数列。头插的时间复杂度为O(n^2),我们可以选择尾插,最后逆置。


片尾

今天我们把string类的函数掌握的差不多了,又练习了几道题,希望对大家有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值