初识String类(上篇)

目录

一.序言

二.String类的常用接口说明

1.String类对象的常见构造

1.构造空的String类对象

2.用C-string来构造string类对象

3.String类对象包含n个字符c

4.拷贝构造

5.一些补充(不常用)

2.string类对象的容量操作 

​1.返回String对象的大小(有效字符串)

2.返回String对象的空间容量

3.字符串清空

4.改变字符串的大小

5.为字符串预留空间

6.检测字符串是否为空

7.一些补充

​3.string类对象的访问及遍历操作

1.迭代器访问与遍历

2.反向迭代器的访问与遍历

3.运算符[ ]访问字符串 

4.string类对象的修改与操作

1.字符串的拼接(push_back,append,+=)

2.字符串插入

3.字符串消除和末尾删除元素

​4.返回C格式字符串 

5.查找相应字符串(find + npos)/查找子串(substr)

5.string类非成员函数

三.有关String类的题目


一.序言

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

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

我们可以去cplusplus.com网站查看有关string类更为详细的官方介绍.

PS:在使用string类时,必须包含#include头文件以及using namespace std;

二.String类的常用接口说明

1.String类对象的常见构造

1.构造空的String类对象

2.用C-string来构造string类对象

由于常量字符串默认已经以‘\0’结尾,所以可以直接构造. 

3.String类对象包含n个字符c

如果第二个是数字,默认会将其按照Ascal码对应的字符进行看待. 

 4.拷贝构造

同样的,通过赋值来构造一个string类对象也是支持的.

 5.一些补充(不常用)

可以通过字符串的子串进行构造String类对象,如果给定的字符串长度过大,或者给了npos长度,则直接取到末尾. 

npos是一个size_t类型的数,给定的值是-1,但是size_t,也就是相当于unsigned int类型,所以npos相当于一个非常大的正数.

迭代器构造

注意是左闭右开区间,String类的迭代器可以理解为指针,其底层实现也是指针进行实现的. 

2.string类对象的容量操作 

1.返回String对象的大小(有效字符串)

两者实现的功能都是一样的,即返回String对象的大小.

为什么要构造出两个呢?String类先于STL库诞生,一开始用length来实现,但后面树,图等等的

数据结构,用length来表示就不太合理,所以为了统一,与其他容器的接口保持一致,给String类

也补充了size操作,实际使用size更多.

2.返回String对象的空间容量

注意:不是字符串的有效长度,两者一般并不相等,对象的空间可以是100个字节,而实际上只用了5个字节.

下面的例子,空间中最后存储了'\0',所以空间容量是15,而不是14.

3.字符串清空

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

4.改变字符串的大小

假如调整的大小比原字符串小,则保留n个字符,其余全部删除,但底层空间总大小不变

假如比原字符串大,则resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间,底层空间大小可能改变也可能不改变.

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s0("Initial string");
	cout << s0.size() << endl;
	cout << s0.capacity() << endl;
	cout << ".................." << endl;
	s0.resize(7);
	cout << s0.size() << endl;
	cout << s0.capacity() << endl;
	cout << ".................." << endl;
	s0.resize(14);
	cout << s0.size() << endl;
	cout << s0.capacity() << endl;
	cout << ".................." << endl;
    //有效字符size调整为20,多的用字符a来填充
	s0.resize(20,'a');
	cout << s0 << endl;
	cout << s0.size() << endl;
	cout << s0.capacity() << endl;
	return 0;
}

 

5.为字符串预留空间

reserve(size_t res_arg=0):为string预留空间不改变有效元素个数,当reserve的参数小于

string的底层空间总大小时,reserver不会改变容量大小

字符串扩容虽然操作上对比C十分方便,但效率上依旧不理想,如果提前已知字符串的大小,提前

开空间,就可以减少空间开辟的消耗.

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:\n";
    //输出初始容量值
    cout << sz << endl;
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c');
		//检测到字符串扩容时,及时将当前扩容的容量打印出来
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
	return 0;
}

 

从上面这段程序,就可以看出字符串扩容,也是有时间消耗的,第一次2倍扩后,后面都是按接近

1.5倍进行扩容. 

如果提前开好空间,就没有这样的消耗.

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
	return 0;
}

  

 6.检测字符串是否为空

如果为空,返回true;否则,返回false

不会改变字符串的值

 7.一些补充

1.与resize不同,shrink_to_fit可以改变容量大小,但不常用,并且可以想象它的消耗是巨大的.

2.max_size返回字符串可以储存的最大长度,但实际上并没有那么大.

3.string类对象的访问及遍历操作

1.迭代器访问与遍历

迭代器在string类里面可以看作是指针,也就是说字符串s.begin()获取的是指向字符串首元素的指

针,而s.end()获取的是字符串最后一个元素的下一个指针.

因此可以通过这两个迭代器实现遍历字符串.

当然,我们接触过auto,和范围for操作,因此上面代码,可以进一步化简.

虽然实际上两段代码本质上是一样的.

2.反向迭代器的访问与遍历

反向迭代器rbegin,和rend两者结合起来使用,可以达到反向输出字符串的结果.

PS:rbegin++的话,是往前移动.

3.运算符[ ]访问字符串 

String类支持运算符[ ]重载,平时最常使用访问字符串的方式也是这种.

 和[ ]运算符功能相似,at也支持访问字符串中的元素,但是假如范围超过字符串长度,程序不会崩

溃,而是抛异常解决.

4.string类对象的修改与操作

1.字符串的拼接(push_back,append,+=)

push_back可以给字符串末尾追加一个字符

append 可以给字符串追加一个字符串

+=则既可以加一个字符,又可以加一个字符串

2.字符串插入

string类里面的insert操作支持函数重载,有着多种形式.

使用的时候,自行去查表即可.

下面给出官网的标准代码,函数与上图顺序一一对应.

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string str = "to be question";
	string str2 = "the ";
	string str3 = "or not to be";
	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 )

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

3.字符串消除和末尾删除元素

erase操作支持删除指定位置的n个字符,如果没有指定,则默认npos个,也就是指定位置之后的字符串全部删除.

指定位置和数组一样,第一个字符对应下标为0,即可以实现头删

pop_back弹出字符串的最后一个字符,并且会改变字符的大小,也就是size的大小,但容量不发生

改变.

4.返回C格式字符串 

 C++兼容C,因此肯定会设计一些接口,使其兼容c的格式.

c_str就是这样一个接口,结合C++cout函数能够自动识别类型,它能帮助我们实现C格式打印字符

串,类似我们使用printf的时候,传常量字符串指针,默认会自动打印字符串.

5.查找相应字符串(find + npos)/查找子串(substr)

find:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

如果没有找到,则返回npos

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string str("There are two needles in this haystack with needles.");
	string str2("needle");
	size_t found = str.find(str2);
	if (found != string::npos)
		cout << "first 'needle' found at: " << found << '\n';
    
    //查找字符串中第二个needle的位置,匹配字符个数为6
	found = str.find("needles", found + 1, 6);
	if (found != string::npos)
		cout << "second 'needle' found at: " << found << '\n';
	return 0;
}

substr:找子串,从pos位置开始,截取n个字符,然后将其返回

substr和find两者可以结合起来,起到意想不到的效果

比如,我想取出一个网址的域名,就可以这样操作,找到相应位置,取出对应子串


#include <string>
#include <iostream>
using namespace std;
int main()
{
	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return 0;
	}
	//跳过://
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;
	return 0;
}

  

5.string类非成员函数

relational operators其实就是我们之前日期类实现的大小比较,和C一样也是逐个字符进行比较. 

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string foo = "alpha";
	string bar = "beta";

	if (foo == bar) cout << "foo and bar are equal\n";
	if (foo != bar) cout << "foo and bar are not equal\n";
	if (foo < bar)  cout << "foo is less than bar\n";
	if (foo > bar)  cout << "foo is greater than bar\n";
	if (foo <= bar) cout << "foo is less than or equal to bar\n";
	if (foo >= bar) cout << "foo is greater than or equal to bar\n";
	return 0;
}

 

这里再简单补充一下getline即可,它可以帮我们获取一行数据,用于用户输入,并转成字符串类

型,具有重要的意义.

三.有关String类的题目

1.翻转字符串

917. 仅仅反转字母 - 力扣(LeetCode)

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) {
         //如果字符为空,直接返回s
         if (s.empty())
           return s;
         //定义begin指向第一个字符,end指向最后一个字符
         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;
    }
};

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

387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

class Solution {
public:
    int firstUniqChar(string s) {
        //统计字符串中出现的次数
        int Count[256] = {0};
        int sz = s.size();
        for (int i = 0;i < sz;i++)
        {
            Count[s[i]]++;
        }
        //遍历字符串,如果为1,则返回对应位置
        for (int j = 0;j < sz;j++)
        {
            if (Count[s[j]] == 1)
              return j;
        }
        return -1;
    }
};

3.字符串最后一个单词的长度

字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)

PS:不能用cin来录入数据,因为遇到空格就会停止录入,导致结果错误. 

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

int main() {
    string line;
    getline(cin,line);
    size_t pos = line.rfind(' ');
    cout << line.size() - pos - 1 << endl;
}

4.检验是否是回文

125. 验证回文串 - 力扣(LeetCode)

class Solution {
public:
    //判断是字母数字字符
    bool ischar(char ch)
    {
        if (ch >= '0' && ch <= '9')
          return true;
        if (ch >= 'a' && ch <= 'z')
          return true;
        if (ch >= 'A' && ch <= 'Z')
          return true;
        return false;
    }
    bool isPalindrome(string s) {
        //引用范围for遍历
        for (auto&ch:s)
        {
            //将所有大写字母转成小写字母
            if (ch >= 'A' && ch <= 'Z')
              ch+=32;
        }
        //判断是否是回文
        int begin = 0,end = s.size() - 1;
        while (begin < end)
        {   
            //从左往右找字母字符
            while (begin < end && !ischar(s[begin]))
              begin++;
            //从右往左找字母字符
            while (begin < end && !ischar(s[end]))
              end--;
            if (s[begin] != s[end])
            {
                return false;
            }
            else
            {
                begin++;
                end--;
            }
        }
        //假如只有空格字符串
        return true;
    }
};

5.字符串相加

415. 字符串相加 - 力扣(LeetCode)

class Solution {
public:
    string addStrings(string num1, string num2) {
        //从后往前计算字符大小
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        int value1 = 0,value2 = 0,carry = 0;
        string addret;
        //只要有一个字符串没有到达头部,就继续循环
        while (end1 >= 0 || end2 >= 0)
        {   
            //计算字符时,自动移动end1
            if (end1 >= 0)
               value1 = num1[end1--] - '0';
            else
               value1 = 0;
            //计算字符时,自动移动end2
            if (end2 >= 0)
               value2 = num2[end2--] - '0';
            else
               value2 = 0;
            
            //该位数字等于两数相加,和上一位进位相加
            int valueret = value1 + value2 + carry;
            //保留进位,获取对应该位数字
            carry = valueret/10;
            valueret%=10;
            addret += (valueret+'0');
        }
        //最后一位可能是进位
        if(carry == 1)
          addret += '1';
        //采用reverse比insert直接头插效率更高
        reverse(addret.begin(),addret.end());
        return addret;
    }
};

6.翻转字符串(二)

541. 反转字符串 II - 力扣(LeetCode)

class Solution {
public:
    string reverseStr(string s, int k) {
        //步长一次移动2k步
        for (size_t i = 0;i < s.size();i+= (2*k))
        {
            //如果该字符串长度大于k,小于2k
            if (i + k <= s.size())
                reverse(s.begin() + i,s.begin() + i + k);
            else//如果剩余字符小于k个
                reverse(s.begin() + i,s.end());
        }
        return s;
    }
};

 7.翻转字符串(三)

557. 反转字符串中的单词 III - 力扣(LeetCode)

class Solution {
public:
    string reverseWords(string s) {
        int i = 0,pos = s.find(' ');
        while (pos!= string::npos)
        {
            reverse(s.begin() + i,s.begin() + pos);
            i = pos+1;
            pos = s.find(' ',pos+1);
        }
        //翻转最后一个单词
        reverse(s.begin() + i,s.end());
        return s;
    }
};

8.字符串相乘

43. 字符串相乘 - 力扣(LeetCode) 

整体思路借鉴字符串相加,如果字符串相加可以理解,那乘法是一样的道理.

num2从低位开始,每一位都和num1中的数字相乘,得到的数字,再赋用我们已经实现过的字符串

相加,一起全部相加.

class Solution {
public:
      string addStrings(string num1, string num2) {
        //从后往前计算字符大小
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        int value1 = 0,value2 = 0,carry = 0;
        string addret;
        //只要有一个字符串没有到达头部,就继续循环
        while (end1 >= 0 || end2 >= 0)
        {   
            //计算字符时,自动移动end1
            if (end1 >= 0)
               value1 = num1[end1--] - '0';
            else
               value1 = 0;
            //计算字符时,自动移动end2
            if (end2 >= 0)
               value2 = num2[end2--] - '0';
            else
               value2 = 0;
            
            //该位数字等于两数相加,和上一位进位相加
            int valueret = value1 + value2 + carry;
            //保留进位,获取对应该位数字
            carry = valueret/10;
            valueret%=10;
            addret += (valueret+'0');
        }
        //最后一位可能是进位
        if(carry == 1)
          addret += '1';
        //采用reverse比insert直接头插效率更高
        reverse(addret.begin(),addret.end());
        return addret;
    }
    string multiply(string num1, string num2) {
         //如果num1或者num2有一个等于0,则直接返回结果为0
         if(num1 == "0" || num2 == "0")
            return "0";
         string ret("0");
         //从num2的最后一位开始遍历
         for (int n2 = num2.size() - 1;n2 >=0;n2--)
         {
             int value2 = num2[n2] - '0';
             int carry = 0;
             string temp;
             //补0
             for (size_t i = 0;i < num2.size() - 1 - n2;i++)
             {
                 temp.push_back('0');
             }
             //计算,value2和num1的每一位相乘
             for (int n1 = num1.size() - 1;n1 >=0 ;n1--)
             {
                 int value1 = num1[n1] - '0';
                 //该位数字等于两数相乘,和上一位进位相加
                 int valueret = value1 * value2 + carry;
                 carry = valueret/10;
                 valueret %= 10;
                 temp += (valueret+'0');
             }
             //最后一位可能是进位
            if (carry != 0)
                temp += (carry + '0');
             //翻转
             reverse(temp.begin(),temp.end());
             //每一次得到的结果相加
             ret = addStrings(ret,temp);
         }
        return ret;
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值