zoukankan      html  css  js  c++  java
  • 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模拟事先就是个例子)
    放个图片占位:

    C++ STL主要组件之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 key
    {
     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] = '';
     }
     String& operator+=(char c)
     {
     PushBack(c);
     return *this;
     }
     void Clear()
     {
     _size = 0;
     _str[_size] = '';
     }
     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 = '')
     {
     if (newSize > _size)
     {
     // 如果newSize大于底层空间大小,则需要重新开辟空间
     if (newSize > _capacity)
     {
     Reserve(newSize);
     }
     memset(_str + _size, c, newSize - _size);
     }
     _size = newSize;
     _str[newSize] = '';
     }
     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& key::operator<<(ostream& _cout, const bit::String& s)
    {
     cout << s._str;
     return _cout;
    }
  • 相关阅读:
    tiny4412 串口驱动分析八 --- log打印的几个阶段之内核启动阶段(printk tiny4412串口驱动的注册)
    tiny4412 串口驱动分析七 --- log打印的几个阶段之内核启动阶段(earlyprintk)
    tiny4412 串口驱动分析六 --- TTY驱动架构
    Android简单的利用SoundPool进行播放铃声的实例代码
    Android简单的利用MediaRecorder进行录音的实例代码
    tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码
    tiny4412 串口驱动分析四 --- 修改默认的串口输出
    tiny4412 串口驱动分析三 --- log打印的几个阶段之内核自解压
    tiny4412 串口驱动分析二 --- printk的实现
    tiny4412 串口驱动分析一 --- u-boot中的串口驱动
  • 原文地址:https://www.cnblogs.com/Kaniso-Vok/p/13756235.html
Copyright © 2011-2022 走看看