【C++笔记】string类流食般投喂

声明:以下知识相关资料来自比特官网和小编手搓~
string类

++1、为什么学习string类?++

++2、标准库中的string类++

2.1、auto 和 范围for

2.2、string类的常用接口说明

++3、string类的模拟实现++

3.1、经典的string类问题

3.2、浅拷贝

3.3、深拷贝

3.3.1、String类的传统写法&现代写法

3.4、写时拷贝(了解)

1、为什么学习string类?

字符串常常出现在我们日常的学习和做算法题里面,而之前C语言里面,对于字符串的处理并没有形成体系,祖师爷在设计C++的时候,为了让字符串的处理更加方便、符合OOP的思想,就设计出了string类,用来统筹字符串的一切。其实string类的出世,是要早于后来设计的模板类的,所以,string类里面的接口会有些冗余,没办法,这是时代遗留的问题。

2、标准库中的string类

2.1、auto 和 范围for

auto关键字:

早期:使用auto修饰的变量,是具有自动存储器的局部变量。

C++11:变废为宝,赋予新的含义。auto作为一个新的类型指示符用来指示编译器,让编译器在编译时期完成对类型的推导。

1、用auto声明指针类型时,用 auto / auto*,是没有区别的;但是对于引用类型的变量,必须必须加 &,用 auto& 。

2、用 auto 连续声明多个变量时,变量的类型要保持一致,否则会报错,因为编译器只推导第一个变量的类型,用推导来的类型定义其他变量。

3、auto 不能作为参数,但是可以做返回值,建议谨慎使用。(这设计真逆天)

4、auto 不能用来直接声明数组。

cpp 复制代码
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
auto cc = 3, dd = 4.0;

// 不能做参数
void func2(auto a)
{}

// 可以做返回值,但是建议谨慎使用
auto func3()
{
    return 3;
}

// 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
auto array[] = { 4, 5, 6 };

auto的用武之地: --->auto用在对的地方是真的省事

cpp 复制代码
#include<iostream>
#include <string>
#include <map>

using namespace std;

int main()
{
    std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
"橙子" }, {"pear","梨"} };
    // auto的用武之地
    //std::map<std::string, std::string>::iterator it = dict.begin();
    auto it = dict.begin();
    while (it != dict.end())
    {
        cout << it->first << ":" << it->second << endl;
        ++it;
    }
    
    return 0;
}

范围for:

针对于一个有范围的集合,范围for就像是一个快捷键,方便、格式固定、不容易出错,for循环后括号内由冒号分为两部分:冒号前一部分:范围内用于迭代的变量;冒号的后一部分:被迭代的范围。

具体可以用于对数组和容器进行全范围的遍历。

范围for的底层其实就是迭代器,所以一般有迭代器的,就是支持范围for的。

要改变迭代到的一个位置的值,可以用引用呦(&)。

cpp 复制代码
// C++11的遍历
for (auto& e : array)
    e *= 2;

for (auto e : array)
    cout << e << " " << endl;

2.2、string类的常用接口说明

1.string类对象的常见构造

--- string() 构造空的string类对象,即空字符串

--- string(const char*) 用C语言里面的常量字符串构造string类对象

--- string(size_t n, char c) 构造有n个字符c的string类对象

--- string(const string& s) 用已有的string对象拷贝构造

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

2.string类对象的容量操作

--- size 返回字符串的有效字符个数

--- length 返回字符串的有效字符个数

--- capacity 返回空间总大小

--- empty 判断是不是空字符串,是空,返回true,不是空,返回false

--- clear 清空有效字符

--- reserve 为字符串预留空间

--- resize 重新定义有效字符个数,多出的空间用字符c填充

注意:

1、size和length这两个接口的功能是一样的,但是,为了和后面的容器接口保持一致,常用size。

2、clear只是清空有效字符个数,并不会改变底层空间容量大小。

3、reserve(size_t res_arg = 0) 为string预留空间,不会改变有效字符个数,当预留空间小于底层空间时,就不会改变容量大小;大于那当然会改变了。

4、resize(size_t n):当重新定义的有效字符个数大于原有的有效字符个数时,多出的空间用0填;resize(size_t n, char c):当重新定义的有效字符个数大于原有的有效字符个数时,多出的空间用字符c填。resize重新定义的有效字符个数比总空间大小还要大的话,是要扩容到n的。
3.string类对象的访问及遍历操作

--- operator\[\] 返回pos位置的字符,const string类对象的调用(只读)

--- begin + end begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

--- rbegin + rend rbegin获取最后一个字符的迭代器 + rend获取第一个字符前一个位置的迭代器

--- 范围for C++11支持的新的更加简洁的遍历方式
4.string类对象的修改操作

--- push_back 在字符串后尾插字符c

--- append 在字符串后追加一个字符串

--- operator+= 在字符串后追加一个字符串str

--- c_str 返回C语言格式的字符串

--- find + npos 从字符串的pos位置开始往后寻找想要的字符,找到就返回该字符的下标,找不到就返回 npos,即-1。

--- rfind 从字符串的pos位置往前寻找指定字符,返回该字符在字符串中的位置

--- substr 从字符串的pos位置开始,截取长度为n的子串,然后返回

注意:

1、在日常,不管给string尾部追加字符,还是字符串,常用的是 +=

2、在对string操作时,可以用reserve先预留一定的空间,减少麻烦
5.string类非成员函数

--- operator+ 尽量少用,因为传值拷贝,会频繁触发对象的拷贝(尤其是深拷贝),造成性能浪费

--- operator>> 输入运算符重载

--- operator<< 输出运算符重载

--- getline 获取一行字符串,可以读取空格,遇到'\n'停止,也可以传参指定遇到什么字符再停止

--- relational operators 支持了string的大小比较
6.vs和g++下string结构的说明

声明:均是32位,32位下指针是4字节。

VS:一个联合体:除了常见的指针、有效元素大小、容量大小(char* str,size,capacity),还有一个buff数组,大小是16,因为常用的字符串长度基本都是在16以内,超出就去堆上申请,所以VS下string大小为 16 + 4 + 4 + 4 = 28。

g++:string在该环境下是通过写时拷贝实现的,真实的内部只有一个指针,所以总共就 4 字节,该指针在将来会指向堆的一块空间,该空间包含(空间总大小、有效字符个数、引用计数)。

3、string类的模拟实现

3.1、经典的string类问题

这有一段代码,看看犯了啥经典错误:

cpp 复制代码
// 为了和标准库区分,此处使用String
class String
{
public:
    /*String()
        :_str(new char[1])
        {*_str = '\0';}
    */
    //String(const char* str = "\0") 错误示范
    //String(const char* str = nullptr) 错误示范
    String(const char* str = "")
    {
        // 构造String类对象时,如果传递nullptr指针,可以认为程序非
        if (nullptr == str)
        {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};

// 测试
void TestString()
{
    String s1("hello bit!!!");
    String s2(s1);
}

这段string类的实现中只写了构造和析构,并没有显示实现拷贝构造和赋值运算符重载,所以编译器自动生成了默认拷贝构造和赋值运算符重载,在执行 String s2(s1); 这个语句时,编译器调用的是默认拷贝构造,完成s2的初始化,但是,这是浅拷贝,所以完成初始化后,s1、s2都是指向同一块资源空间的,在编译器调用析构函数时,就会对一个空间调用两次析构函数了,程序会崩溃的。

3.2、浅拷贝

浅拷贝:又名位拷贝,编译器只是将对象中的值一个字节一个字节的拷贝过来,对于有资源申请的对象来说,调用默认拷贝构造或是默认的赋值运算符重载,会导致多个对象共享一块资源,对象各自析构互不知情,所以就会发生访问违规。

3.3、深拷贝

深拷贝对于资源的处理,不再是浅拷贝那般,简单的值拷贝,存在安全隐患,而是,给你搞一块一摸一样的资源来,但不是同一个奥!

对于涉及到资源管理:拷贝构造函数、赋值运算符重载、析构函数,都是要显示实现的!

3.3.1、String类的传统写法&现代写法

传统写法:

cpp 复制代码
String(const String& s)
    : _str(new char[strlen(s._str) + 1])
{
    strcpy(_str, s._str);
}

String& operator=(const String& s)
{
    if (this != &s)
    {
        char* pStr = new char[strlen(s._str) + 1];
        strcpy(pStr, s._str);
        delete[] _str;
        _str = pStr;
    }

    return *this;
}

现代写法:

cpp 复制代码
String(const String& s)
    : _str(nullptr)
{
    String strTmp(s._str);
    swap(_str, strTmp._str);
}

// 对比下和上面的赋值那个实现比较好?---上面
String& operator=(String s)
{
    swap(_str, s._str);
    return *this;
}

//不好的原因:
//参数传递方式:const String& s 是const 引用传递,不会直接拷贝对象。
//核心逻辑:在函数内部手动创建临时对象 strTmp,再交换资源。
//额外操作:需要手动写 if(this != &s) 来处理自赋值情况。
/*
String& operator=(const String& s)
{
    if(this != &s)
    {
        String strTmp(s);
        swap(_str, strTmp._str);
    }
    return *this;
}
*/

现代写法:纯纯就是把形参当作黑奴,不仅抢他吃的,还让他帮我把我吃完的餐盘洗了。

3.4、写时拷贝

写时拷贝本质上是一种偷懒的做法,当只用拷贝来的值,不进行修改,写时拷贝就可以用来偷懒,但是触及到资源内部调动,其实还是要写深拷贝的,这其实就是侥幸心理,赌你不对资源进行操作,只要值。

写时拷贝 = 浅拷贝 + 引用计数

引用计数:用来记录资源使用者的个数,在构造时,计数就+1,每增加一个使用者就+1,当某个对象销毁时,计数-1,再检查是否需要释放、清理资源,如果减完不为0,就是还有人使用就不需要清理、释放资源。

相关推荐
我是一颗柠檬15 小时前
【JDK8新特性】JDK8实战与面试高频考点汇总Day12
java·开发语言·后端·面试·职场和发展
wjs202415 小时前
C# 索引器(Indexer)
开发语言
Brilliantwxx15 小时前
【算法题】 面试级别的二叉树题目OJ复习(下)
数据结构·c++·算法·leetcode·面试·哈希算法·推荐算法
千寻girling15 小时前
机器学习 | 监督学习算法(了解) | 尚硅谷学习
开发语言·人工智能·后端·python·学习·算法·机器学习
阿方.91815 小时前
C++ string 超全精讲 | 从零使用、底层原理、手搓简易string、高频考点、易错点、面试手撕
开发语言·c++·字符串·string·知识分享
Chase_______15 小时前
【Java基础】5 / 2 为什么等于 2?整数除法、取余和 floorMod 一次讲清
java·开发语言
fish_xk15 小时前
c++11(二)
java·前端·c++
foundbug99915 小时前
实现MATLAB滚动轴承故障诊断
开发语言·matlab
gihigo199815 小时前
matlab实现三维四面体单元的有限元解法
开发语言·matlab