C++string类

文章目录

C++string类

为什么要学习string类

C语言字符串的问题

C 语言中字符串是以'\0' 结尾的字符数组,为了操作方便C语言提供了一些 str 系列函数,例如 strlen、strcpy、strcat。但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

标准库中的string类

string类

string类型文档介绍。

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

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

比较不舒服的地方就是string类并不支持char类型的构造,原因大致有一下几种:

  1. C语言的char类型既是字符也是字符串,这就导致string准确没办法处理,所以就干脆不处理,就算处理也是将char类型直接当成字符串来处理;
  2. 因为C++标准库强烈倾向于 显式优于隐式 。这是因为迭代器的原因,后续我们会了解我为什么说是因为迭代器的原因,这里就简单说一下迭代器可以让这些类(容器)拥有一套统一的访问接口,从而完美地与各种泛型算法;
  3. 因为标准库有 string(size_t n, char c)为了避免产生逻辑冲突,那么可以认为2和3才是为什么C++不提供char类型构造的核心原因。

其实还有一些原因就不展开的说了,因为在后续我们对C++的不断的学习就会不断的了解这些原因。

string类的常用接口说

  1. 构造函数
函数 功能
string() 构造空 string
string(const char* s) 用 C 字符串构造
string(size_t n, char c) 包含 n 个字符 c
string(const string& s) 拷贝构造
cpp 复制代码
#include <iostream>
#include <string>

using std::string;

void test1()
{
    string s1; // 构造空的string类对象s1
    string s2("hello world"); // 用C-string来构造string类对象s2
    string s3(s2); // 拷贝构造s3
    string s4(5,'x'); // 构造包含 n 个字符 c类对象s4
}
  1. string类对象的容量操作
函数 功能
size() 返回有效字符长度
length() 同上,历史原因保留
capacity() 返回空间总大小
empty() 是否为空
clear() 清空有效字符
reserve() 预留空间
resize() 改变有效字符个数
cpp 复制代码
void test2()
{
    string s1 = "hello world";

    int size = s1.size();
    cout << "size: " << size << endl;

    size = s1.capacity();
    cout << "capacity: " << size << endl;

    if(s1.empty())
    {
        cout << "s1为空!" << endl;
    }
    else {
        cout << "s1不为空!" << endl;
    }

    s1.clear();
    cout << s1 << endl;

    string s2("hello c++");
    s2.reserve(13); 
    cout << s2.size() << endl;

    s2.resize(12,'a'); 
    cout << s2 << endl;

}

注意:

  • size() 和 length() 底层一致,推荐 size(),为了和其他容器统一。
  • clear() 只清空有效字符,不改变底层空间大小。
  • resize(n) 增大时用 '\0' 填充;resize(n, c) 用 c 填充。
  • resize 增大可能扩容,减小时底层容量通常不变。
  • reserve(n) 只预留空间,不改变有效元素个数;如果 n 小于当前容量,通常不改变容量。
  1. string类对象的访问及遍历操作
函数 功能
operator[] 返回 pos 位置字符
begin()/end() 正向迭代器
rbegin()/rend() 反向迭代器
范围 for C++11 简洁遍历
cpp 复制代码
void test3()
{
    string s1 = "abcdefg";

    // 常规
    for(size_t i = 0; i < s1.size();++i)
    {
        cout << s1[i] << " ";
    }
    cout << endl;

    // 范围for
    for(auto ch : s1)
    {
        cout << ch << " ";
    }
    cout << endl;
}

注:范围for的底层是迭代器来实现的,范围for使用起来更方便并且不易越界。

  1. tring类对象的修改操作
函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c_str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回
cpp 复制代码
void test4()
{
    string s1;
    s1 += "hello";
    s1 += " world";

    cout << s1 << endl;

    // char ch = s1; 
    const char* ch = s1.c_str();

    size_t size = s1.find("hello");
    if(size != string::npos)
    {
        cout << "找到了" << endl;
    }
    else {
        cout << "没有找到了" << endl;
    }

    cout << s1.substr(0,5) << endl;
}

注意:

  • push_back( c)、append(1, c)、+= 'c' 效果类似,但 += 最常用。
  • += 不仅能连接字符,还能连接字符串。
  • 如果能预估字符数量,先 reserve。
  1. string类非成员函数
函数 功能说明
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> 输入运算符重载
operator<< 输出运算符重载
getline 获取一行字符串
relational operators 大小比较
cpp 复制代码
void test5()
{
    string s1;
    string s2 = "hello ";
    string s3 = "world";
    s1 = s2 + s3;

    cout << s1 << endl;

    s1.clear();
    std::getline(cin,s1);
    cout << s1 << endl;

    if (s1 == s2) std::cout << "s1 == s2(内容相同)" << std::endl;
    if (s1 != s2) std::cout << "s1 != s2" << std::endl;
    if (s1 < s2)  std::cout << "s1 < s2 ('a'的ASCII小于'b')" << std::endl;
    if (s1 > s2)  std::cout << "s1 > s2" << std::endl;

}

注 :如果连续输入string类型,不要使用cin >> s1,因为它遇到空格就会退出建议使用std::getline(cin,s1);

VS 和 G++ 下 string 的结构

VS 下:小字符串优化 SSO

32 位平台下,VS 的 string 大约占 28 字节:

  • vs下string的结构
    string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字
    • 当字符串长度小于16时,使用内部固定的字符数组来存放
    • 当字符串长度大于等于16时,从堆上开辟空间
cpp 复制代码
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、capacity、指针等字段。

故总共占16+4+4+4=28个字节。

g++下string的结构

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

  • 空间总大小
  • 字符串有效长度
  • 引用计数
cpp 复制代码
struct _Rep_base
{
    size_type M_length;
    size_type M_capacity;
    _Atomic_word M_refcount;
};

这里的写时拷贝在后续章节我们会了解。写时拷贝就是一种按需修改,当你需要拷贝时它才会真的干活,如果你不需要它就会在哪里等着你需要,就像吃完饭有的是吃完就去洗碗,有的是等下次吃的时候再去洗。写时拷贝就是第二种吃的时候在洗碗的表现。

小试牛刀

我们经过上述对C++的string类进行了充分的了解现在我们通过下面的一些编程题巩固一下对string各种接口的使用。

  1. 仅仅反转字母
cpp 复制代码
bool isLetter(char ch)
{
    if (ch >= 'A' && ch <= 'Z')
    {
        return true;
    }
    if (ch >= 'a' && ch <= 'z')
    {
        return true;
    }

    return false;
}

string reverseOnlyLetters(string s)
{
    if (s.empty())
    {
        return s;
    }

    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;

        std::swap(s[begin],s[end]);
        ++begin;
        --end;
    }

    return s;
}

核心思路:定义两个双指针头和尾,跳过非字母,交换字母。

  1. 第一个只出现一次的字符
cpp 复制代码
int firstUniqChar(string s)
{
    int buffer[256] = {0};

    for(auto ch : s)
    {
        buffer[ch]++;
    }

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

思路:统计每个字符出现次数,再按原顺序找第一个次数为 1 的字符。为什么用 256 数组?

字符范围通常是 0~255,直接映射计数,O(n) 时间,实际上不一定就要用256要根据题目给出的范围来定,比如保证了题目只有小写字母就是只需要给数组26的大小即可。

  1. 最后一个单词的长度
cpp 复制代码
int main() {
    string s1;
    while(getline(cin,s1))
    {
        size_t pos = s1.rfind(' ');
        cout << s1.size() - pos - 1 << endl;
    }

    return 0;
}

我们在上面了解过为什么这里使用getline,使用rfind是因为可以直接从后往前找最后一个空格,空格后面的部分就是最后一个单词。同样这里的判断条件需要根据题目描述决定。

本章节我们了解了C++的string类,下一章节我们就开始自己去实现属于直接的string类了,为什么要实现string类,因为我要通过实现string类来了解底层到底是怎么回事从而进一步加强我们对该类的了解。

完

相关推荐
Yunovian2 小时前
AI时代,针对模型与应用,浅谈一下各编程语言
开发语言·c++·人工智能·python·ai·rust·ai编程
UIU1147 小时前
递归算法与汉诺塔
c++·学习·算法·c#·递归
Fruit_Caller7 小时前
static
开发语言·c++
小小龙学IT13 小时前
Boost.Interprocess 开源 C++ 进程间通信库深度解析
c++·开源
倒头就睡的小比特6 天前
算法竞赛C++常用的STL
c++·算法
weilx12346 天前
C++笔记-文件IO-<fcntl.h>
c++
Smileyqp沛沛6 天前
前端?C++ ?较大差异基础罗列
c++·基础·前端转c++
C语言小火车6 天前
C/C++ 为什么需要编译器?
开发语言·c++
旖旎夜光6 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控