C++ STL主要组件之String总结(第二部分 深、浅拷贝问题以及赋值运算符重载)

第一部分连接https://blog.51cto.com/14232799/2447326

二.String的模拟实现

在第一步之后紧接着的就该是模拟实现部分,这一部分主要是体现自己对第一部分的掌握情况。强烈推荐和我一样在学习String的朋友们自己动手实现一下。因为在面试中,面试官总喜欢让我们自己来模拟实现string类。

自己来实现String最主要是实现String类的构造、拷贝构造、赋值运算符重载(第一部分operator开头的方法)以及析构函数。

以下是我完成的基础模拟实现

#include<iostream>
#include<assert.h>
using namespace std;

namespace self{
    class string{
    public:
        string(const char* s = " "){
            if (s == nullptr){
                assert(false);
                return;
            }
            _s = new char[strlen(s) + 1];
            strcpy(_s, s);
        }
        ~string(){
            if (_s){
                delete[] _s;
                _s = nullptr;
            }
        }
    private:
        char* _s;
    };
}
int main(){
    self::string k = "hello";
    self::string i("world");
    self::string m;
    //self::string l(k);
    return 0;
}

以上就是没有重载赋值运算符且没有显式定义拷贝构造函数的string类模拟实现。基本完整的模拟实现会在本篇文章的最后给出(当然免不了有纰漏,若是发现请各位大佬提醒)
上面的代码中的main函数中有一句注释语句 //self::string l(k); 我将其注释是因为如果加入这一句代码程序就会运行崩溃!!!!
程序崩溃的原因是: 当我们不去显式定义拷贝构造方法的时候,系统就会生成默认的拷贝构造函数,这种拷贝构造函数是一种浅拷贝,最终结果就是导致 对象l和对象k在共用同一块内存空。看起来似乎没什么问题?
但是!当函数结束时,在调用析构函数的操作上就会出现大问题。
原本的话,每一个对象都会调用一次析构函数来清理自己占用的空间。但是当两个对象所占用的是同一块空间时,一个对象调用完析构函数后另一个对象调用析构函数的时候,就会生同一块空间被释放多次的程序错误!从而引起程序崩溃!
所以说在以上这个的代码中不可以使用拷贝构造方法。这个问题也就引出了下一个要总结的部分:浅拷贝和深拷贝

三.浅拷贝和深拷贝

1.浅拷贝
此处只是给个定义:
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规。所以要解决浅拷贝问C++中引入了深拷贝。
(第二部分的string模拟事先就是个例子)
放个图片占位:

2.深拷贝
“如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供”
上面这句话是真理!
首先给出深拷贝一般在string‘类中的实现:

String(const String& s)
 : _str(new char[strlen(s._str)+1])   // 看见这一步开辟空间就知道是深拷贝了
 {
 strcpy(_str, s._str);
 }

再来说深拷贝的定义:
每个string都需要空间来存放字符串,而当使用一个string类对象来构造另一个string类对象。就用到了深拷贝:给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成空间多次释放而造成的程序奔溃问题。

三.String中赋值运算符重载
1.先给出几种在string中常用的赋值运算符重载:
<1> <<

ostream& bit::operator<<(ostream& _cout, const self::String& s)
{
 cout << s._str;
 return _cout;
}

对于<<的重载算是比较特殊的了,因为会用到ostream类型,所以在这里展开说明一下:
ostream是output stream的简称,即输出流。一个典型的输出流对象就是在C++中标准输出流cout。
在C++中,很少自定义ostream的对象,更多的是直接使用cout。
ostream这个类型,往往出现在<<操作重载中,作为某个类的友元函数出现。
比如对于class A, 可以定义ostream & operator << (ostream &os, const A& a);
这样在调用A的对象var时,就可以这样使用
cout &lt;&lt; var ;

<2> =

String& operator=(String s)
 {
 swap(_str, s._str);
 return *this;
 }

<3> +=

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

<4> [ ]

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

四.最后是给出的较为完整的string类模拟实现:

namespace bit
{
 class String
 {
 public:
 typedef char* iterator;
 public:
 String(const char* str = "")
 {
 _size = strlen(str);
 _capacity = _size;
 _str = new char[_capacity+1];
 strcpy(_str, str);
 }
 String(const String& s)
 : _str(nullptr)
 , _size(0)
 , _capacity(0)
 {
 String tmp(s);
 this->Swap(tmp);
 }
 String& operator=(String s)
 {
 this->Swap(s)
 return *this;
 }
 ~String()
 {
 if (_str)
 {
 delete[] _str;
 _str = nullptr;
 }
 }
 /////////////////////////////////////////////////////////////////
 // iterator
 iterator begin() {return _str;}
 iterator end(){return _str + _size;}
 /////////////////////////////////////////////////////////////////
 // modify
 void PushBack(char c)
 {
 if (_size == _capacity)
 Reserve(_capacity*2);

 _str[_size++] = c;
 _str[_size] = ‘\0‘;
 }
 String& operator+=(char c)
 {
 PushBack(c);
 return *this;
 }
 void Clear()
 {
 _size = 0;
 _str[_size] = ‘\0‘;
 }
 void Swap(String& s)
 {
 swap(_str, s._str);
 swap(_size, s._size);
 swap(_capacity, s._capacity);
 }
 const char* C_Str()const
 {
 return _str;
 }
 size_t Size()const
 size_t Capacity()const
 bool Empty()const

 void Resize(size_t newSize, char c = ‘\0‘)
 {
 if (newSize > _size)
 {
 // 如果newSize大于底层空间大小,则需要重新开辟空间
 if (newSize > _capacity)
 {
 Reserve(newSize);
 }
 memset(_str + _size, c, newSize - _size);
 }
 _size = newSize;
 _str[newSize] = ‘\0‘;
 }
 void Reserve(size_t newCapacity)
 {
 // 如果新容量大于旧容量,则开辟空间
比特科技
 if (newCapacity > _capacity)
 {
 char* str = new char[newCapacity + 1];
 strcpy(str, _str);
 // 释放原来旧空间,然后使用新空间
 delete[] _str;
 _str = str;
 _capacity = newCapacity;
 }
 }
 char& operator[](size_t index)
 {
 assert(index < _size);
 return _str[index];
 }
 const char& operator[](size_t index)const
 {
 assert(index < _size);
 return _str[index];
 }
 private:
 friend ostream& operator<<(ostream& _cout, const bit::String& s);
 private:
 char* _str;
 size_t _capacity;
 size_t _size;
 };
}
ostream& bit::operator<<(ostream& _cout, const bit::String& s)
{
 cout << s._str;
 return _cout;
}

原文地址:https://blog.51cto.com/14232799/2447351

时间: 2024-08-29 14:45:38

C++ STL主要组件之String总结(第二部分 深、浅拷贝问题以及赋值运算符重载)的相关文章

C++ String类 ( 构造、拷贝构造、赋值运算符重载和析构函数)

class String { public: //普通构造函数 String(const char *str = NULL) { if(str == NULL) { m_data = new char[1]; *m_data = '\0'; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data, str); } } //拷贝构造函数 String(const String &s) { m_data = new char[strlen

C++ STL主要组件之String总结(第一部分,构造和操作)

最近在学习C++时,进入到了STL的学习阶段,在发现到这个部分的重要性时,我打算把对STL的学习分步骤记录下来,我首先打算学习的是组件String的部分,此文章主要只记录内部构造和对象基本操作. STL是由C++提供的标准模板库,内含多个主要组件,此次总结的是String部分的内容.String在STL中算是较为重要的部分,所以需要我重点攻克. 先放一张我学习String后对于此部分知识点的概括. 首先是第一部分: 一.标准库中的String类都有哪些内容 首先是String中包含的接口:其实这

C++ Primer 学习笔记_46_STL剖析(一):泛型程序设计、什么是STL、STL六大组件及其关系

一.泛型程序设计 1.泛型编程(generic programming):相同的逻辑和算法,对不同类型的数据进行处理 2.将程序写得尽可能通用 3.将算法从数据结构中抽象出来,成为通用的 4.C++的模板为泛型程序设计奠定了关键的基础 二.什么是STL 1.STL(Standard Template Library),即标准模板库,是一个高效的C++程序库. 2.包含了诸多在计算机科学领域里常用的基本数据结构和基本算法.为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性 3

STL学习笔记(string)

动机 C++标准程序库中的string class使我们可以将string当做一个一般型别.我们可以像对待基本型别那样地复制.赋值和比较string, 再也不必但系内存是否足够.占用的内存实际长度等问题. 操作函数 1.构造函数和析构函数 下表列出string的所有构造函数和析构函数 2.大小和容量 size()和length():返回string中现有的字符个数. max_size():返回一个string最多能够包含的字符数,这个跟机器本身的限制有关系. capacity():重新分配内存之

Blazor 组件库 Blazui 开发第二弹【按钮组件】

传送门 Blazor 组件库 Blazui 开发第一弹[安装入门]https://www.cnblogs.com/wzxinchen/p/12096092.html Blazor 组件库 Blazui 开发第二弹[按钮组件]https://www.cnblogs.com/wzxinchen/p/12096956.html 常规用法 @page "/" <h1>Hello, world!</h1> Welcome to your new app. <BBut

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载、!运算符重载、赋值运算符重载 、String类([]、 +、 += 运算符重载)、&gt;&gt;和&lt;&lt;运算符重载

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载.!运算符重载.赋值运算符重载 .String类([]. +. += 运算符重载).>>和<<运算符重载 一.++/--运算符重载 1.前置++运算符重载 成员函数的方式重载,原型为: 函数类型 & operator++(); 友元函数的方式重载,原型为: friend 函数类型 & operator++(类类型 &); 2.后置++运算符重载 成员函数的方式重载,原型为:

Effective STL:02vector和string

在STL容器中,vector和string的使用频率会更高一些.设计vector和string的目标就是为了替换大多数应用中要使用的数组. 13:vector和string优先于动态分配的数组 一旦要使用new动态分配数组,将要面临很多问题:必须确保delete.必须使用正确的delete形式:必须保证只delete一次.每当发现自己要动态分配一个数组时,都应该考虑用vector和string来代替.vector和string 消除了上述的负担,因为它们自己管理内存.如果你担心还得继续支持旧的代

STL六大组件之——分配器(内存分配,好深奥的东西)

SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器:当配置区小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂的memory pool 整理方式,而不再求助于第一级配置器.整个设计究竟只开放第一级配置器,取决于_USE_MALLOC是否被定义: 1 #ifdef __USE_MALLOC 2 ... 3 typedef __malloc_allo

STL六大组件之——算法小小小小的解析

参考自侯捷的<stl源码剖析> stl算法主要分为非可变序列算法(指不直接修改其所操作的容器内容的算法),可变序列算法(指可以修改它们所操作的容器内容的算法),排序算法(包括对序列进行排序和合并的算法.搜索算法以及有序序列上的集合操作),数值算法(对容器内容进行数值计算). 1.非可变序列算法 stl中的非可变序列算法有:for_each(), find(), find_if(), adjacent_find(), find_first_of(), count(), count_if(), m