C++STL~~string

一、string类的发展历史

在C++ 的早期版本中,处理字符串主要依赖于 C 风格的字符数组。但这种方式存在诸多不便,如手动管理内存、容易出现缓冲区溢出等问题。随着 C++ 标准的不断演进,string 类逐渐成为处理字符串的更安全和便捷的方式。
在 C++98 标准中,引入了 std::string 类,它提供了自动内存管理、丰富的操作方法,如字符串连接、查找、替换等。这使得字符串的操作更加直观和安全。例如,在 C 风格中连接两个字符串可能需要手动分配足够的内存并复制字符,而使用 std::string 可以简单地通过 + 运算符实现。
C++11 标准进一步增强了 string 的功能,例如提供了更高效的移动语义,使得字符串的传递和赋值更加高效。
C++17 及之后的标准也对 string 的性能和功能进行了一些优化和改进。
总的来说,C++ 中 string 的发展历史是一个不断改进和完善的过程,旨在为开发者提供更强大、更安全和更高效的字符串处理工具。

二、string的使用

(1).string类对象的常见构造

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

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

(2).string类对象的容量操作
在这里插入图片描述

void Teststring1()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello, TU!!!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << 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 n, char c)用字符c来填充多出的
    元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大
    小,如果是将元素个数减少,底层空间总大小不变。

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

(3)string类对象的访问及遍历操作
在这里插入图片描述

void Teststring2()
{
	string s("hello TU");
	// 3种遍历方式:
	// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
	// 另外以下三种方式对于string而言,第一种使用最多
	// 1. for+operator[]
	for (size_t i = 0; i < s.size(); ++i)
		cout << s[i] << endl;

	// 2.迭代器
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << endl;
		++it;
	}

	// string::reverse_iterator rit = s.rbegin();
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	auto rit = s.rbegin();
	while (rit != s.rend())
		cout << *rit << endl;

	// 3.范围for
	for (auto ch : s)
		cout << ch << endl;
}

(4)string类对象的修改操作
在这里插入图片描述

// 测试string:
// 1. 插入(拼接)方式:push_back  append  operator+= 
// 2. 正向和反向查找:find() + rfind()
// 3. 截取子串:substr()
// 4. 删除:erase
void Teststring3()
{
	string str;
	str.push_back(' ');   // 在str后插入空格
	str.append("hello");  // 在str后追加一个字符"hello"
	str += 'b';           // 在str后追加一个字符'b'   
	str += "it";          // 在str后追加一个字符串"it"
	cout << str << endl;
	cout << str.c_str() << endl;   // 以C语言的方式打印字符串

	// 获取file的后缀
	string file("string.cpp");
	size_t pos = file.rfind('.');
	string suffix(file.substr(pos, file.size() - pos));
	cout << suffix << endl;

	// npos是string里面的一个静态成员变量
	// static const size_t npos = -1;

	// 取出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;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;
}
  1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般
    情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

(5) string类非成员函数
在这里插入图片描述
getline 函数的一般使用形式

#include <iostream>
#include <string>

int main() {
    std::string str;
    std::cout << "请输入一行字符串: ";
    std::getline(std::cin, str);
    std::cout << "您输入的字符串是: " << str << std::endl;
    return 0;
}

在上述代码中,std::getline(std::cin, str) 表示从标准输入中读取一行内容,并将其存储到 str 字符串中。
getline 函数的一个重要特点是它可以读取包含空格的字符串,而不像 cin >> str 那样在遇到空格时就停止读取。
例如,如果用户输入 “Hello World” ,使用 cin >> str 时,str 只会被赋值为 “Hello” ,而使用 getline 则可以完整地将 “Hello World” 存储到 str 中。
relational operators函数的一般使用形式
在这里插入图片描述

#include <iostream>
#include <vector>
#include <string>

int main ()
{
  std::string foo = "alpha";
  std::string bar = "beta";

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

  return 0;
}

这段 C++ 代码主要是对两个字符串 foo(值为 “alpha”)和 bar(值为 “beta”)进行关系比较,并根据比较结果输出相应的信息。
由于 “alpha” 和 “beta” 在字典序上不同,且 “alpha” 小于 “beta”,所以输出结果为:

foo and bar are not equal
foo is less than bar
foo is less than or equal to bar

(6)vs和g++下string结构的说明
1.vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

  • 当字符串长度小于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个字节。
在这里插入图片描述

2.g++下string的结构
G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指
针将来指向一块堆空间,内部包含了如下字段:

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

三、string的练习

1.反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

我们使用两个指针,一个指向字符串的开头 left,一个指向字符串的末尾 right。然后通过一个循环,不断交换这两个指针所指向的字符,直到两个指针相遇或者交错。这样就实现了字符串的反转,而且没有使用额外的存储空间,只在原字符串上进行操作,空间复杂度为 O(1)。

class Solution {
public:
    void reverseString(vector<char>& s) {
        
        int left=0,ringt=s.size()-1;
        while(left<ringt)
        {
            swap(s[left++],s[ringt--]);
        }
    }
};

2.字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

思路如下:
我们从两个字符串的末尾开始逐位相加。使用两个指针 i 和 j 分别指向 num1 和 num2 的最后一位。同时设置一个进位 carry 初始化为 0 。
在每次循环中,如果指针还没有超出字符串范围,就取出当前位的数字进行相加,再加上进位。计算出当前位的和后,得到新的进位,并将当前位的数字(取模 10 )添加到结果字符串中。
如果指针超出了字符串范围,就认为当前位为 0 。
最后,因为我们是从低位向高位计算并添加到结果字符串的,所以需要将结果字符串反转,得到最终正确的结果。

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1,end2=num2.size()-1;
        int next =0;
        string str;
        while(end1>=0||end2>=0)
        {
            int val1= end1 >= 0 ? num1[end1--]-'0':0;
            int val2= end2 >= 0 ? num2[end2--]-'0':0;
            int sum =val1+val2+next;
            next = sum / 10;
            sum = sum % 10;
            str.insert(str.begin(),'0'+sum);
        }
        if(next==1)
        {
            str.insert(str.begin(),'1');
        }
        return str;
    }
};

3.字符串中的第一个唯一字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

1.int cont[26]={0};:创建了一个大小为 26 的整数数组 cont,用于统计每个小写字母出现的次数,并初始化为 0。
2.for(auto ch :s):这是一个范围 for 循环,遍历字符串 s 中的每个字符 ch。
cont[ch-‘a’]++;:通过 ch - ‘a’ 将字符转换为对应的索引(例如,‘a’ - ‘a’ = 0,‘b’ - ‘a’ = 1 等),然后将对应索引位置的计数加 1,从而统计每个字符出现的次数。
3.第二个 for 循环:再次遍历字符串 s。
4.if(cont[s[i]-‘a’]==1):对于当前位置的字符,通过同样的方式将其转换为索引,然后检查其在 cont 数组中的计数是否为 1。如果是,则返回当前位置的索引 i。
如果整个字符串遍历完都没有找到计数为 1 的字符,就返回 -1。
这种方法的时间复杂度为 O(n),空间复杂度为 O(26),主要是通过一次遍历统计字符出现次数,再一次遍历查找第一个不重复的字符,避免了使用复杂的数据结构,提高了效率。

class Solution {
public:
    int firstUniqChar(string s) {
        int cont[26]={0};
        for(auto ch :s)
        {
            cont[ch-'a']++;             
        }
        for(size_t i=0;i<s.size();i++)
        {
            if(cont[s[i]-'a']==1)
            {
                return i;
            }
            
        }
        return -1;
    }
};

四、总结

std::string 是 C++ 标准库中用于处理字符串的重要类。
内存管理:
自动管理内存,无需手动分配和释放,避免了内存泄漏和缓冲区溢出的风险。动态调整字符串的存储空间,以适应字符串内容的变化。
丰富的操作:
支持字符串的连接,通过 + 运算符可以方便地将两个字符串拼接在一起。提供查找、替换、比较等操作,如 find() 、 replace() 、 compare() 。可以获取字符串的长度、子串等。
高效性:
在 C++11 及以后的标准中,利用移动语义提高了字符串对象的传递和赋值效率。
迭代访问:可以使用迭代器遍历字符串中的每个字符。

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值