C++教你如何模拟实现string,如何实现string写时拷贝

文章目录

  • 前言
  • 成员变量
  • 默认成员函数
    • 默认构造函数
    • 拷贝构造函数
    • 析构函数
    • 赋值运算符重载
  • 容量相关函数(Capacity)
    • reserve函数
    • resize函数
    • size函数
    • capacity 函数
    • clear函数
  • 修改函数(Modifiers)
  • swap函数
    • insert函数
      • 字符插入
      • 字符串插入
    • append函数
    • push_back函数
    • erase函数
    • +=重载
  • 元素获取函数(Element access)
  • operator[]
  • 操作函数(String operations)
    • c_str函数
    • substr函数
    • find函数
  • 运算符重载
    • 流插入运算符<<重载
    • 流提取运算符>>重载
  • 完整代码
  • 写实拷贝
    • 写时拷贝完整代码

前言

  本文将要对STL容器string进行模拟实现,将要实现string常用构造函数,析构函数,拷贝构造函数以及常用增删查改接口,介绍如何通过函数复用以达到简化代码,如何通过写实拷贝提高程序效率,通过模拟实现达到加深对string的理解,提高自身编程技巧的效果。

注:本文在读者拥有string相关知识储备的基础下更易于理解,可跳转链接阅读博主的另一篇文章掌握如何使用string后再来阅读


成员变量

	private:		char* _str=nullptr;//字符串size_t _capacity=0;//容量size_t _size=0;//有效字符个数static const size_t npos;//static const size_t npos=-1  vs下int类型支持给static和const同时修饰的变量用缺省值

默认成员函数

默认构造函数

  默认构造函数(无参构造函数)是构造一个空的字符串。

        string():_str(new char[1]), _capacity(0), _size(0){_str[0] = '\0';}

  像上面这样写如何?可以,但是可以更简洁一些。

  像下面这样写更加简洁并且好处在于C-string构造函数可以同时担任默认构造函数与C-string构造函数的角色,这一点利用了语言的语法规则,如果我们显示定义构造函数后编译器则不会再生成默认构造函数

		string(const char* str="")//对象实例化不传递参数则默认用空字符串拷贝构造{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}

  注:实现string(const char* str=“”)后不可以再显示实现默认构造函数,否则会造成调用歧义。
  在调用方面,如果我们想要实例化一个空字符串的string对象要以以下方式调用

string s1;

  绝对不可以用以下的方式调用,错误示例如下:
  在这种情况下编译器会把它识别为函数声明

string s1();

拷贝构造函数

  拷贝构造函数是用一个类对象实例化另一个类对象

		string(const string& str){_size = str._size;_capacity = str._capacity;_str = str._str;}

  如果以以上方式编写后进行调用会发生什么
  在调用之前我们先实现一个方便我们观察运行时现象的析构函数

析构函数

  清理对象占用内存资源

		~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;//cout<<~string()<<endl;此语句仅为观察使用}

  运行程序在这里插入图片描述
  监视窗口在这里插入图片描述

  RUN:运行错误
在这里插入图片描述   以上为典型浅拷贝引起的delete内存释放错误,浅拷贝使得两个对象共享同一块内存资源,在内存释放时对同一内存空间进行多次释放引起错误。
  默认拷贝构造函数同样是浅拷贝,仅对对象的成员变量的值进行拷贝。

   什么是浅拷贝?
  浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。其实我们可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。

  什么是深拷贝

深拷贝是指在进行对象拷贝时,不仅复制对象本身的成员变量,还复制对象所指向的动态分配的资源(例如堆内存)到新的对象中。这意味着拷贝后的对象和原对象拥有独立的资源副本,彼此之间不会相互影响。
当对象中含有动态分配的资源,如指针指向的内存块,或者其他动态分配的资源(文件句柄、网络连接等),进行深拷贝是非常重要的,以避免多个对象共享同一块资源导致释放重复、悬挂指针(悬挂指针:指的是一个指针变量指向了曾经被分配的内存地址,但该内存已经被释放或者回收了。在这种情况下,指针仍然指向原来的内存地址,但那个地址现在可能已经被操作系统重新分配给了其他程序或变量,或者已经被标记为不可用。)等问题。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供

  拷贝构造正确编写方法

		string(const string& str){_str = new char[str._capacity + 1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}

  通过函数复用进行优化

		void swap(string& str)//交换对象内容{std::swap(_size, str._size);std::swap(_capacity, str._capacity);std::swap(_str, str._str);}string(const string& str):_str(nullptr)//_str用nullptr初始化是为了确保与之交换的指针不会成为野指针,导致不必要错误,_size(0),_capacity(0){string temp(str._str);swap(temp);//this->swap(temp);编译器为每个非静态的成员函数配备一个this指针//这个this指针可以显示使用,也可以不显示使用}

  这样设计的原理是调用C-string构造函数后再交换两个对象的内容,temp对象出作用域之后自动销毁。

  这种方法虽然简化了代码但存在着一定问题,像_size 与_capacity的大小可能与被拷贝对象的值不相同,但问题可忽略。
  关于为什么不使用标准库的swap函数进行数据交换
   在C++之前swap是以下形式实现
在这里插入图片描述   它首先会拷贝构造一个对象,然后再进行赋值拷贝,这种实现方法十分低效
  C++11之后新增的运行移动语义使得其实现更高效,如果使用C++之后的swap,它的效率与我们模拟实现的方法的效率相比更高效,它省去了一切资源的开辟。
  此内容需要大篇幅讲解才能理清逻辑,因本文重点不在此,故不进行详细讲解。在这里插入图片描述

赋值运算符重载

		string& operator =(const string& s){if (this != &s)//如果“自己”给“自己”赋值则直接跳过{char* temp = new char[s._capacity + 1];strcpy(temp, s._str);delete[] _str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}

  优化

		string& operator=(const string& s){if (this != &s){string temp(s);swap(temp);}return *this;}

  再优化

		string& operator=(string s){swap(s);return *this;}

  通过值拷贝的方式传递参数,直接生成一个临时对象,再交换对象内容,达到简化代码的效果。


容量相关函数(Capacity)

reserve函数

  根据Windows平台下的reserve规则

n > _capacity时对对象的容量进行扩容
n<=_capacity时不对对象的容量进行修改

		void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}

  开辟新空间,转移数据,释放原空间,修改成员变量

resize函数

  根据Windows平台下的reserve规则

n>_size
  首先判断n > _capacity是否成立,成立则首先进行扩容操作
  再将[_size,n)区间内容填入指定字符ch
n<=_size时对对象的有效字符进行缩减,将有效字符缩减至指定个数

		void resize(size_t n, char ch = '\0'){if (n > _size){reserve(n);for (int i = _size; i < n; ++i){_str[i] = ch;}}_str[n] = '\0';//必须给_str[n]赋值'\0',以做字符串结束标志_size = n;}

size函数

  返回字符串有效字符长度

size_t size() const
{return _size;
}

capacity 函数

  返回空间总大小

size_t capacity() const
{return _capacity;
}

clear函数

		void clear(){_str[0] = '\0';_size = 0;}

  清空字符串内容只需要修改字符串结束标志,下次再对字符串进行操作会覆盖式写入内容。
在这里插入图片描述


修改函数(Modifiers)

swap函数

  交换两个对象内容

		void swap(string& str){std::swap(_size, str._size);std::swap(_capacity, str._capacity);std::swap(_str, str._str);}

insert函数

字符插入

  在指定位置插入一个字符

string& insert(size_t pos, char ch)
{assert(pos <= _size);// 判断是否需要扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos)//将插入位置 pos 之后的字符依次向后移动一个位置。{_str[end] = _str[end - 1];--end;}_str[pos] = ch;//将字符 ch 插入到指定的插入位置 pos++_size;//插入字符后,将字符串的实际大小 _size 增加 1return *this;
}

字符串插入

  在指定位置插入一个字符串

string& insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 挪动数据size_t end = _size + len;while (end >= pos + len)//将插入位置 pos 之后的字符依次向后移动 len 个位置,为新字符串的插入留出空间{_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);//将字符串拷贝到指定位置_size += len;//插入字符后,将字符串的实际大小 _size 增加 lenreturn *this;
}

append函数

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

void append(const char* str)
{size_t len = strlen(str);// 判读是否需要扩容if (_size + len > _capacity){reserve(_size+len);}strcpy(_str + _size, str);_size += len;
}

  通过复用insert实现

void append(const char* str)
{insert(_size, str);
}

push_back函数

  字符串尾插一个字符

void push_back(char ch)
{// 判读是否需要扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';//作为字符串必须要字符串结束标志
}

  通过复用insert实现

void push_back(char ch)
{insert(_size, ch);
}

erase函数

		void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}

  如果len 等于缺省值 npos则将pos及以后字符全部删除
  如果删除字符个数超过pos位置后字符总和则同样将pos及以后字符全部删除
  如果以上两种情况不成立则直接将从位置 pos + len 开始的字符复制到位置 pos,覆盖掉要删除的字符。

+=重载

  += 运算符在字符串的操作中通常被用作连接(拼接)操作。
  连接一个字符

		string& operator+=(char ch){push_back(ch);return *this;}

  连接一个字符串

		string& operator+=(const char* str){append(str);return *this;}

元素获取函数(Element access)

operator[]

  返回pos位置的字符,可修改pos位置字符

		char& operator[](size_t pos){assert(pos < _size);return _str[pos];}

  返回pos位置的字符,不可修改pos位置字符

		const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}

注意断言,下标位置不能等于_size,_size指向最后一个字符的下一个也就是字符0,但是实际库中的string的字符0我们是可以访问到的,这里主要表示有效数据的访问。


操作函数(String operations)

c_str函数

  返回C格式字符串

		const char* c_str() const{return _str;}

substr函数

  获取一个子字符串

		string substr(size_t pos, size_t len = npos) const{assert(pos < _size);string str;if (len == npos || pos + len >= _size){str.reserve(_size-pos);str._size = _size - pos;strcpy(str._str, _str + pos);}else{str.reserve(len);str._size = len;strncpy(str._str,_str + pos,len);str._str[_size] = '\0';}return str;}

  如果len长度超过pos位置后字符总和或者为缺省值则将pos及以后字符全部复制,反之则复制其区间到目标字符串str,然后将字符串末尾添加字符串结束标志’\0’.
  优化

		string substr(size_t pos, size_t len = npos) const{assert(pos < _size);size_t end = pos+len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; ++i){str += _str[i];}return str;}

  确定被拷贝字符串的末尾位置,然后将被拷贝字符串的字符依次连接到目标字符串中。

find函数

  查找一个字符查找成功返回其下标,查找一个字符串查找成功返回其字符串的起始下标,查找失败均返回npos值

size_t find(const char ch, size_t pos = 0)
{assert(pos < _size);while (pos < _size){if (_str[pos] == ch){return pos;}++pos;}return npos;
}
size_t find(const char* str, size_t pos = 0)
{const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}
}

  strstr 是一个库函数,用于在一个字符串中查找另一个字符串的首次出现位置

char *strstr(const char *haystack, const char *needle);

  haystack 是要在其中进行搜索的字符串,也被称为主字符串。
  needle 是要在 haystack 中查找的子字符串。
  如果 needle 在 haystack 中找到,则 strstr 返回指向 haystack 中 needle 首次出现位置的指针。如果未找到 needle,则返回 NULL。

运算符重载

流插入运算符<<重载

  流插入运算符<<重载要定义为全局函数,因为定义为全局函数,因此在其实现中不能直接访问类的私有成员,而需要通过类的公有接口进行访问,或者将其定义为类的友元函数进而访问其私有成员。

	std::ostream& operator<<(std::ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i];}return out;}

流提取运算符>>重载

	std::istream& operator>>(std::istream& in, string& s){s.clear();char ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

  使用istream的成员函数get,每次读入一个字符
在这里插入图片描述  以上设计方法存在频繁扩容的问题,如果我们频繁输入就会频繁进行扩容操作,频繁进行函数调用会降低效率,因此我们可以创建一个”输入缓冲区buff“当缓冲区填满则将缓冲区内容刷新出去,当输入结束再将未刷新的缓冲区进行刷新,该策略在语言层面和操作系统层面有着广泛应用。

	std::istream& operator>>(std::istream& in, string& s){s.clear();char buff[128] = { '\0' };size_t i = 0;char ch = in.get();while (ch != ' ' && ch != '\n'){if (i == 127){s += buff;i = 0;}buff[i++] = ch;ch = in.get();}if (i >= 0){buff[i] = '\0';s += buff;}return in;}

完整代码

#pragma once
#include<Cassert>
namespace zyc
{class string{//friend std::ostream& operator<<(std::ostream& out, const zyc::string& s);//设置为友元函数直接访问类的私有成员变量typedef char* iterator;typedef const char* const_iterator;public:string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& str){std::swap(_size, str._size);std::swap(_capacity, str._capacity);std::swap(_str, str._str);}string(const string& str):_str(nullptr), _size(0), _capacity(0){string temp(str._str);swap(temp);}string& operator=(string s){swap(s);return *this;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){if (n > _size){reserve(n);for (int i = _size; _size < n; i++){_str[i] = ch;}}_str[n] = '\0';_size = n;}string& insert(size_t pos, char ch){assert(pos <= _size);// 满了就扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 挪动数据size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}void append(const char* str){size_t len = strlen(str);// 满了就扩容if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);_size += len;}/*void append(const char* str){insert(_size, str);}*/void push_back(char ch){// 满了就扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}/*string substr(size_t pos, size_t len = npos) const{assert(pos < _size);string str;if (len == npos || pos + len >= _size){str.reserve(_size-pos);str._size = _size - pos;strcpy(str._str, _str + pos);}else{str.reserve(len);str._size = len;strncpy(str._str,_str + pos,len);str._str[_size] = '\0';}return str;}*/string substr(size_t pos, size_t len = npos) const{assert(pos < _size);size_t end = pos+len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; ++i){str += _str[i];}return str;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}const char* c_str() const{return _str;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}size_t find(const char ch, size_t pos = 0){assert(pos < _size);while (pos < _size){if (_str[pos] == ch){return pos;}++pos;}return npos;}size_t find(const char* str, size_t pos = 0){const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;//字符串size_t _capacity;//容量size_t _size;//有效字符个数static const size_t npos = -1; //vs下int类型支持给static和const同时修饰的变量用缺省值};std::ostream& operator<<(std::ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i];}return out;}
/*std::istream& operator>>(std::istream& in, string& s){s.clear();char ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}*/std::istream& operator>>(std::istream& in, string& s){s.clear();char buff[128] = { '\0' };size_t i = 0;char ch = in.get();while (ch != ' ' && ch != '\n'){if (i == 127){s += buff;i = 0;}buff[i++] = ch;ch = in.get();}if (i >= 0){buff[i] = '\0';s += buff;}return in;}}

写实拷贝

写时拷贝(Copy-on-Write,简称COW)是一种计算机程序设计领域的优化策略,用于延迟复制资源的实际发生,直到真正需要修改资源时。在写时拷贝的场景中,多个引用(或“视图”)最初指向同一份资源。当某个引用尝试修改资源时,系统才会创建该资源的一个副本,并让修改的引用指向这个新的副本,而其他的引用仍然指向原始的资源。

  写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的,既然是引用计数,必然有一个变量类是某些类所共有的。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数变为1或0,这时对对象的空间进行释放。
  关于这个引用计数怎么设计,我在这里介绍两种设计思路:
  1:在类内部设置一个指针指向一片开辟的内存空间。
  2:在string成员变量_str指向的堆内存空间中多开辟一个整形的空间进行计数(这里博主采用第二种设计思路)。
在这里插入图片描述
  当我们知道引用计数的设计思路后,就要考虑什么时候应该进行写时拷贝

  写时拷贝发生在对string对象进行修改操做时,比如insert,push_back,append,erease等成员函数+=,[],=赋值操作,以及析构操作时发生写时拷贝。
  对于写时拷贝为主要实现了以下三个私有成员函数,方便发生写时拷贝时进行调用。

//获得引用计数int& GetRefCount(){return *((int*)(_str - 4));}
//写时拷贝void Sub(size_t n){char* tmp = new char[n + 5];tmp += 4;strcpy(tmp, _str);Release();_str = tmp;GetRefCount() = 1;_capacity = n;}

  申请新空间,释放旧空间,在交换前前对引用计数–,交换后将新空间的引用计数置为1.

//检查是否需要对空间进行释放void Release(){if (--GetRefCount() == 0){delete[](_str - 4);}}

写时拷贝完整代码

#pragma once
#include<Cassert>
#include<iostream>
namespace zyc
{class string{typedef char* iterator;typedef const char* const_iterator;public:string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 5];_str += 4;GetRefCount()=1;strcpy(_str, str);}void swap(string& str){Sub(_capacity);str.Sub(str._capacity);std::swap(_size, str._size);std::swap(_capacity, str._capacity);std::swap(_str, str._str);}string(const string& str):_str(str._str),_size(str._size),_capacity(str._capacity){	++GetRefCount();}string& operator=(const string& s){_str = s._str;_size = s._size;_capacity = s._capacity;++GetRefCount();return *this;}~string(){Release();}void reserve(size_t n){if (n > _capacity){Sub(n);}}void resize(size_t n, char ch = '\0'){if (n > _size){reserve(n);for (int i = _size; _size < n; i++){_str[i] = ch;}}_str[n] = '\0';_size = n;}string& insert(size_t pos, char ch){assert(pos <= _size);Sub(_capacity);// 满了就扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);Sub(_capacity);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}// 挪动数据size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}void append(const char* str){insert(_size, str);}void push_back(char ch){insert(_size, ch);}void erase(size_t pos, size_t len = npos){assert(pos < _size);Sub(_capacity);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string substr(size_t pos, size_t len = npos) const{assert(pos < _size);size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; ++i){str += _str[i];}return str;}const char* c_str() const{return _str;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}size_t find(const char ch, size_t pos = 0){assert(pos < _size);while (pos < _size){if (_str[pos] == ch){return pos;}++pos;}return npos;}size_t find(const char* str, size_t pos = 0){const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}char& operator[](size_t pos){assert(pos < _size);Sub(_size);return _str[pos];}void clear(){Sub(_capacity);_str[0] = '\0';_size = 0;}int count(){return GetRefCount();}private:void Release(){if (--GetRefCount() == 0){delete[](_str - 4);}}int& GetRefCount(){return *((int*)(_str - 4));}void Sub(size_t n){char* tmp = new char[n + 5];tmp += 4;size_t len = _size;strcpy(tmp, _str);Release();_str = tmp;GetRefCount() = 1;_capacity = n;_size = len;}char* _str;//字符串size_t _capacity;//容量size_t _size;//有效字符个数static const size_t npos = -1; //vs下int类型支持给static和const同时修饰的变量用缺省值};std::ostream& operator<<(std::ostream& out, const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i];}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear();char buff[128] = { '\0' };size_t i = 0;char ch = in.get();while (ch != ' ' && ch != '\n'){if (i == 127){s += buff;i = 0;}buff[i++] = ch;ch = in.get();}if (i >= 0){buff[i] = '\0';s += buff;}return in;}}

  注意:我们实现的写时拷贝存并不成熟,像以下修改策略,发生修改操作时,这种操作根本就不会被我们所发现。

string str1 = "hello";char& ref = str1[0];string str2 = str1;ref = 'y';

  并且库的string也存在着缺陷,详情我在这里推荐一篇文章,推荐大家阅读
C++的STD::STRING的“读时也拷贝”技术!


本章到此结束,感谢您的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/313115.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础小白,如何入门计算机视觉?

目录 前言 计算机视觉技术学习路线 基础知识 1. 数学基础 2. 编程基础 3. 图像处理基础 基础算法与技术 1. 特征提取与描述符 2. 图像分割与对象检测 3. 三维重建与立体视觉 机器学习与深度学习 1. 机器学习基础 2. 深度学习 高级主题与应用 1. 高级机器学习与深度学习 2. 计算…

基于docker的Jenkin的服务平台搭建

项目拓扑图 项目环境: jenkins-2.440 sonarqube-9.9.4 apache-maven-3.9.6 gitlab-ce-12.4.2 java17 docker20 harbor.v2.6.0 centos7.9 项目目的: 模拟企业构建一个流行的持续集成和持续部署环境,可以更轻松地创建和管理构建环境&#xff0c;实现自动化构建和部署应用程序的…

读天才与算法:人脑与AI的数学思维笔记03_AlphaGo

1. 国际象棋 1.1. 1997年计算机“深蓝”&#xff08;Deep Blue&#xff09;击败了顶尖国际象棋手&#xff0c;但机器取代数学研究机构还言之尚早 1.2. 下国际象棋与数学的形式化证明颇有相似之处&#xff0c;但学者认为中国围棋的思维方式更能够体现数学家思考的创造性和直觉…

使用lambda表达式Collectors.toMap 遇到的报错,带有源码分析

概述 正常hashMap中的key和value都允许为null&#xff0c;但是在list转map中&#xff0c;使用lambda表达式要求key和value都不能为null。这很反常识 起因 本身上游返回contentId和traceId 内容id和跟踪id&#xff0c;但是项目人员变动修改了接口没有给traceId导致 代码 pu…

kafka---topic详解

一、分区与高可用 在Kafka中,事件(events 事件即消息)是以topic的形式进行组织的;同时topic是分区(partitioned)的,这意味着一个topic分布在Kafka broker上的多个“存储桶”(buckets)上。这种数据的分布式放置对于可伸缩性非常重要,因为它允许客户端应用程序同时从多个…

MySQL Explan执行计划详解

Explan执行计划 首先我们采用explan执行计划 执行一条sql&#xff0c;发现返回了12个列&#xff0c;下面会详细解释每一列 1、ID列 id列的值是代表了select语句执行顺序&#xff0c;是和select相关联的&#xff1b;id列的值大的会优先执行&#xff0c;如果id列为空最后执行&a…

【数据挖掘】实验8:分类与预测建模

实验8&#xff1a;分类与预测建模 一&#xff1a;实验目的与要求 1&#xff1a;学习和掌握回归分析、决策树、人工神经网络、KNN算法、朴素贝叶斯分类等机器学习算法在R语言中的应用。 2&#xff1a;了解其他分类与预测算法函数。 3&#xff1a;学习和掌握分类与预测算法的评…

大数据------JavaWeb------JDBC(完整知识点汇总)

JDBC 定义 全称为Java数据库连接&#xff08;Java DataBase Connectivity&#xff09;&#xff1a;是使用java语句来操作所有关系型数据库的一套API JDBC本质 它是官方定义的一套操作所有关系型数据库的规则&#xff08;即接口&#xff09;&#xff0c;各个数据库厂商会去实现…

Day 16 Linux服务管理和日志管理

服务管理 启动服务&#xff1a;systemctl start 服务名 停止服务&#xff1a;systemctl stop 服务名 重启服务&#xff1a;systemctl restart 服务名 重新加载配置文件&#xff1a;systemctl reload 服务名&#xff08;期间并不停止服务进程&#xff09; 查看服务运行状态…

pycharm/idea专业版过期永久解决

1、在file-settings-plungins中找到设置 2、点击增加如图网址3、下载安装此插件 4、按照如下步骤操作即可 5、如果又过期了重复4步骤即可&#xff0c;idea编辑器也是如此操作

如何用ChatGPT进行论文撰写?

原文链接&#xff1a;如何用ChatGPT进行论文撰写&#xff1f;https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247601619&idx1&snb686fbe87dedfac2df3a6afe780b2ffe&chksmfa820c34cdf5852251dca64597024ea62ddbde280086535ec251f4b62b848d9f9234688384e6…

深度学习 Lecture 9 信息增益、One-hot、回归树、集成树、随机森林、XGBoost模型

一、信息增益&#xff08;Information Gain) 决定使用什么特征来划分一个节点取决于什么样的特征选择最能减少熵&#xff08;也就是使纯度最大化&#xff09; 在决策树中&#xff0c;熵的减少被称为信息增益。 所以如何选择呢&#xff1f; 假设现在有三个特征可以选择&#…

政安晨:【深度学习神经网络基础】(十一)—— 激活函数的导数以及在反向传播中的应用

目录 线性激活函数的导数 Softmax激活函数的导数 S型激活函数的导数 双曲正切激活函数的导数 ReLU激活函数的导数 如何在反向传播中应用 批量训练和在线训练 随机梯度下降 反向传播权重更新 选择学习率和动量 Nesterov动量 政安晨的个人主页&#xff1a;政安晨 欢迎…

Go 语言中的 GIF 图像处理完全指南:`image/gif`的技术与实践

Go 语言中的 GIF 图像处理完全指南&#xff1a;image/gif的技术与实践 概述安装与基础设置导入 image/gif 包初步配置示例&#xff1a;设置一个简单的 GIF 编码环境 读取与解码 GIF 图像读取 GIF 文件解析 GIF 数据 创建与编码 GIF 图像创建 GIF 图像编码 GIF 图像 处理 GIF 动…

中文编程入门(Lua5.4.6中文版)第十二章 Lua 协程 参考《愿神》游戏

在《愿神》的提瓦特大陆上&#xff0c;每一位冒险者都拥有自己的独特力量——“神之眼”&#xff0c;他们借助元素之力探索广袤的世界&#xff0c;解决谜题&#xff0c;战胜敌人。而在提瓦特的科技树中&#xff0c;存在着一项名为“协同程序”的高级秘术&#xff0c;它使冒险者…

使用Canal同步MySQL 8到ES中小白配置教程

&#x1f680; 使用Canal同步MySQL 8到ES中小白配置教程 &#x1f680; 文章目录 &#x1f680; 使用Canal同步MySQL 8到ES中小白配置教程 &#x1f680;**摘要****引言****正文**&#x1f4d8; 第1章&#xff1a;初识Canal1.1 Canal概述1.2 工作原理解析 &#x1f4d8; 第2章&…

企业网站制作如何被百度收录

1、网站在百度中的整体评分 说俗点就是网站的权重&#xff0c;在优化过程中我们会见到很多网站出现秒收的情况&#xff0c;发布的文章几分钟就可以收录&#xff0c;这个通过SITE语法都可以去查询&#xff0c;那么这跟自己的网站权重以及内容更新习惯是有非常重要的关联。 我们…

Real3DPortrait照片对口型,数字人,音频/视频驱动数字人

先看效果 上传一张图片和一段音频&#xff0c;照片如下&#xff1a; 合成后效果如下&#xff1a; 照片对口型-音频驱动 支持音频驱动和视频驱动&#xff0c;视频可以使照片有参照视频中的口型和和动作。 项目地址 https://github.com/yerfor/Real3DPortrait 我的环境 win…

PVE grub resue错误修复 lvmid BUG

服务器断电后启动不起来&#xff0c;显示grub resue 找了半天没有找到修复方法。看官方文档有一处Recovering from grub “disk not found” error when booting from LVM 极为类似。https://pve.proxmox.com/wiki/Recover_From_Grub_Failure 下面是处理过程。 使用PVE 6.4启…

单例模式详解

什么是单例模式 首先&#xff0c;单例模式是一种设计模式&#xff0c;按字面意思&#xff0c;指一个类只能创建一个对象&#xff0c;当创建出多个对象的时候&#xff0c;就会出现报错异常 单例模式为何出现&#xff1f; 1.资源共享:某些情况下&#xff0c;多个对象都需要共享一…