08 string类的使用

为什么要学习string类

c语言中的字符串

c语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,c标准库提供了一些str系列的函数,但是这些库函数与字符串是分离开的,不符合OOP的思想,而且底层空间需要自己管理,稍不留神就越界访问

标准库的string类

1,字符串是表示字符序列的类
2.标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于用于操作单字节字符串的设计特性
3.string类是使用char作为它的字符类型,使用它的默认char_traits和分配类型,更多信息,参考basic_string
4.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
5.注意,这个类独立于所使用的编码来处理字节,如果用来处理多字节或边长字符(UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将它按照字节(而不是实际编码的字节)来操作

总结:
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门操作string的常规操作
3.string再底层实际是: basic_string模板类的别名,typedef basic_string<char,char_traits,akkocator> string;
4.不能操作多字节或者变长字符的序列

string有四个版本,分别基于不同的编码,默认utf-8,根据字符第一个二进制是1还是0区分用ascii还是2字节一读,还有utf-16和utf-32,wstring
在这里插入图片描述

string类的常用接口

1.string类对象的构造

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

在这里插入图片描述

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

2. 容量操作

函数名称功能说明
size 重点返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty 重点检测字符串是否为空串
clear 重点清空有效字符
reserve 重点为字符串预留空间
resize 重点将有效字符的个数改成n个,多出的空间用字符c填充
void test2()
{
	string s1("hello");
	cout << s1.size() << "\n";
	cout << s1.length() << "\n";
	cout << s1.capacity() << "\n";
	/*s1.clear();
	cout << s1.size() << "\n";
	cout << s1.length() << "\n";
	cout << s1.capacity() << "\n";*/
	/*s1.resize(10);
	cout << s1 << endl;*/
	/*s1.resize(10, 'd');
	cout << s1 << endl;*/
	/*s1.resize(3);
	cout << s1.size() << "\n";
	cout << s1.length() << "\n";
	cout << s1.capacity() << "\n";*/
	s1.reserve(20);
	cout << s1.size() << "\n";
	cout << s1.length() << "\n";
	cout << s1.capacity() << "\n";
	cout << s1 << endl;
}

注意:
1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其它容器的接口保持一致,一般情况下基本都用size()
2.clear()只是将string中有效字符情况,不改变底层空间大小
3.resize(size_t n)和resize(size_t n, char c)都是将字符串有效字符改变为n个,不同的是当字符个数增多时,resize(n)用0填充空间,resize(size_t, char c)用字符c填充空间.注意: resize增多,可能会改变容量大小,减少时,空间大小不变
4.reserve(size_t res_art=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,不会改变容量大小

3. 访问及遍历操作

函数名称功能说明
operator[] 重点返回pos位置的字符,const string类对象调用
begin+endbegin去第一个字符的迭代器,end取最后一个字符下一个位置的迭代器
rbegin+rendrbegin取最后一个字符的迭代器,rend取第一个字符
范围forc++11支持更简洁的for遍历方式
void test3()
{
	string s1("hello");
	//遍历1 下标访问
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
	cout << endl;
	//遍历2 迭代器
	string::iterator it = s1.begin();
	//string::const_iterator cit = s1.begin();  //const类型
	for (; it != s1.end(); it++)
	{
		cout << *it;
	}
	cout << endl;

	//string::reverse_iterator rit = s1.rbegin();
	//string::const_reverse_iterator crit = s1.begin();
	auto it1 = s1.rbegin(); //自动推导迭代器类型
	while (it1 != s1.rend())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;
	//遍历3 范围for
	for (auto c:s1)
	{
		cout << c ;
	}
	cout << endl;

}

下标[]访问越界会报错,at访问则会抛异常

4. 修改操作

函数名称功能说明
push_back在字符串后面插入字符
append在字符串后追加一个字符串
operator+= 重点在字符串后追加字符串
c str 重点返回c格式字符串,遇\0结束
find+npos 重点从字符串pos位置开始往后找字符c,返回该字符的位置
rfind从字符串pos位置开始往前找字符c,返回该字符位置
substr在str中从pos位置开始,截取n个字符返回
void test4()
{
	
	string s1("hello");
	s1.push_back('c');
	s1.append("world");
	s1 += "ni";
	printf("%s\n",s1.c_str());
	int pos = s1.find('o');
	if (pos != string::npos)
	{
		cout << "找到"<<pos << endl;
	}

	int pos2 = s1.rfind('o');
	if (pos2 != string::npos)
	{
		cout << "找到" << pos << endl;
	}

	string s2 = s1.substr(pos, 2);
	cout << s2 << endl;
}

注意:
1.在string尾部追加字符时,pushback/append/+=三种实现方式差不多,一般+=用的比较多,不仅可以连接单个字符,还可以是字符串
2.string操作,如果能够预估到村多少字符,可以先通过reserve把空间预留好
3.insert往中间插入,但不推荐,效率不高
4.删除earse同中间插入,给一个位置,不给数量会将后面的全部删除
5.replace替换字符
6.npos的值是-1,size_t类型会变为int最大值

替换字符串指定字符

//	string s1("hello world i love you");
//	string newStr;
//	size_t num = 0;
//	for (auto ch : s1)
//	{
//		if (ch == ' ')
//			++num;
//	}
//	// 提前开空间,避免repalce时扩容
//	newStr.reserve(s1.size() + 2 * num);
//
//	for (auto ch : s1)
//	{
//		if (ch != ' ')
//			newStr += ch;
//		else
//			newStr += "%20";
//	}

两个swap功能一样,而针对类的交换函数更高效,只需要改变指针
在这里插入图片描述

转换c串用于调用c函数的接口,需要传入c字符串,例如打开文件fopen

substr取字符串指定内容,也可以查找任一字符

void test5()
{
	//string s1("hello");
	//size_t pos = s1.find('e');
	//if (pos != string::npos)
	//{
	//	string suffix = s1.substr(pos, s1.size() - pos);//第二个参数可以缺省
	//	cout << suffix;
	//}

	//取网址中间内容域名,xxxx.com
	string url = "https://legacy.cplusplus.com/reference/string/string/";
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		size_t finish = url.find('/',pos + 3);
		string suffix = url.substr(pos + 3, finish - (pos + 3));//第二个参数可以缺省
		cout << suffix;
	}

	url.find_first_of("abcd"); //找字符串中包含任一字符
	url.find_last_of("abcd"); //倒着找
}

非成员函数

函数名称功能说明
operator+尽量少用,传值返回导致深拷贝效率低
operator>> 重点输入运算符重载
operator<< 重点输出运算符重载
getline 重点获取一行字符串
relational operators大小比较

6. msvc和g++string结构

下述结构式32位平台验证,32位平台指针占4字节

vs的string
共占28字节,内部先有一个联合体,定义string字符串的存储空间

  • 当字符串长度小于16时,使用内部固定的字符数组存放
  • 当字符串长度大于等于16时,从堆上开辟空间
  • 扩容时除第一次外都是1.5被扩容
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对象创建时需要的空间是固定空间,不用从堆申请,效率高
还有一个size_t保存字符串长度,一个size_t字段保存从对数开辟空间的总量
还有一个指针做其他事情

所以总共28个字节
在这里插入图片描述

*g++结构
string通过写时拷贝实现,string对象共4个字节,内部包含一个指针,指向一块堆空间,包含如下字段:
空间不够2倍扩容

  • 空间总大小
  • 字符串有效长度
  • 引用计数
struct _Rep_base
{
 size_type _M_length;
 size_type _M_capacity;
 _Atomic_word _M_refcount;
};
  • 指向堆空间的指针,保存指针大小

练习

仅仅反转字母
https://leetcode.cn/problems/reverse-only-letters/submissions/
在这里插入图片描述

思路:
用两个下标,一个从头,一个从尾,找到是字母的就交换。交换完记得移动下标,不然还是换这两个

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int begin = 0;
        int end = s.size() - 1;

        while (begin < end)
        {
            while (begin < end && !isalpha(s[begin]))
            {
                begin++;
            }

             while (begin < end && !isalpha(s[end]))
            {
                end--;
            }

            swap(s[begin], s[end]);
            //交换后继续移动一步,防止再次交换
            begin++;
            end--;
        }
        return s;
    }
};

找出第一个只出现一次的字母
https://www.nowcoder.com/practice/e896d0f82f1246a3aa7b232ce38029d4?tpId=37&&tqId=21282&rp=1&ru=/activity/oj&qru=/ta/huawei/question-ranking

在这里插入图片描述

思路:
可以利用计数排序的思路,记录字符串内26个字母的数量,然后根据字符串每个字母查看计数,如果是1打印这个字符,如果不是就打印-1

#include <iostream>
using namespace std;

int main() {
    string s;
    cin >> s;
    int ary[26] = {0};
    //计数,对应26个字母
    for (auto ch : s ) {
        ary[ch - 'a']++;
    }

    for (int  i = 0; i < s.size(); i++) {
        if (ary[s[i] - 'a'] == 1) {
            cout << s[i];
            return 0;
        }
    }
    cout << -1;
}

字符串最后一个单词长度
https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking

在这里插入图片描述

思路:
记录长度,下标从最后一个字母开始,如果不是空格就循环,统计完长度

也可以利用字符串倒着查找函数,找到第一个空格,用字符串长度减去找到的位置再减1就是最后一个单词的长度

#include <iostream>
using namespace std;

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

    //记录下标和长度
    int length = 0;
    int sign = s.size() - 1;

    while (sign >=0 && s[sign] != ' ')
    {
        length++;
        sign--;
    }

    cout<<length;
}
#include <iostream>
using namespace std;

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

    //查找
    int pos = s.rfind(' ');

    if(pos != string::npos)
    {
        cout<<s.size() - pos -1<<endl;
    }else {
        cout<<s.size()<<endl;
    }

}

字符串相加
https://leetcode.cn/problems/add-strings/description/

在这里插入图片描述

思路:
两个字符串从最后一位开始,逐个相加,用一个新字符串保存结果。相加得到的数%10后得到存储的数,尾插到新字符串里。/10后得到进位,如果有进位,加到下一个位计算。哪个字符串空了,返回0来相加

注意相加时需要转换为整数,不是字符串ascii相加。最后有进位处理一次。将结果逆置

class Solution {
public:
    string addStrings(string num1, string num2) {

        int length1 = num1.size() - 1;
        int length2 = num2.size() - 1;
        string s;
         //预先开辟空间,节省消耗
        s.reserve( num1.size() >  num2.size()?  num1.size() + 1:
                   num2.size() + 1);
        int carry = 0;

        while (length1 >= 0 || length2 >= 0) {
            //取需要相加的数
            int val1 = length1 >=0 ? num1[length1] - '0' : 0;
             int val2 = length2 >=0 ? num2[length2] - '0' : 0;
             //相加,包括进位
            int num = val1 +val2 +carry;
           char ret = num % 10 + '0';
            s += ret;
            carry = num / 10;

            length1--;
            length2--;
        }

        if (carry)
        {
            s += '1';
        }
        reverse(s.begin(), s.end());
        cout << s;
        return s;
    }
};
  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值