zoukankan      html  css  js  c++  java
  • string类的实现

    string类底层是一个字符串指针

    1、类结构定义

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class CMyString
    {
    private:
    	char* m_pDate;
    public:
    	CMyString(const char* pDate = NULL); //普通构造函数,const:防止修改
    	CMyString(const CMyString& other); //拷贝构造函数,const:防止修改,&:省去调用复制构造函数提高效率,涉及深拷贝、浅拷贝
    	~CMyString(); //析构函数
    	CMyString& operator = (const CMyString& other); //重构赋值运算符,返回引用:为了连续赋值,const:防止修改,&:省去调用复制构造函数提高效率,涉及安全性
    
    	//CMyString& operator + (const CMyString& other);
    	//bool operator == (const CMyString& other);
    	int getLength();
    	void printString(){ cout<<m_pDate<<endl; } //用于测试
    };
    

    2、简单说明

    正如代码中注释部分说明,

    const 是为了防止函数内部修改;

    & 是为了省去隐式调用拷贝构造函数,从而提高效率;

    3、详细解说

    以“重构赋值运算符”例,详细解说注意事项

    (1)是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。

    只有返回一个引用,才可以允许连续赋值。否则如果函数的返回值是void,应用该赋值运算符将不能做连续赋值。假设有3个CMyString的对象:str1、str2和str3,在程序中语句str1=str2=str3将不能通过编译 。若只是两个对象之间的赋值,返回值为void也可以达到效果。

    (2)是否把传入的参数的类型声明为常量引用。

    如果传入的参数不是引用而是实例,那么从形参到实参会调用一次复制构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。即省去调用复制构造函数,提高效率

    (3)是否释放实例自身已有的内存。

    如果我们忘记在分配新内存之前释放自身已有的空间,程序将出现内存泄露

    (4)是否判断传入的参数和当前的实例(*this)是不是同一个实例。

    避免自赋值,如果是同一个,则不进行赋值操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。即存在非法访问或者多次释放同一内存单元的风险

    (5)是否有申请内存失败的安全处理。

    如果此时内存不足导致new char抛出异常,m_pData将是一个空指针,这样非常容易导致程序崩溃。先创建一个临时实例,再交换临时实例和原来的实例。把strTemp.m_pData和实例自身的m_pData做交换。由于strTemp是一个局部变量,但程序运行到 if 的外面时也就出了该变量的作用域,就会自动调用strTemp 的析构函数,把 strTemp.m_pData 所指向的内存释放掉。由于strTemp.m_pData指向的内存就是实例之前m_pData的内存,这就相当于自动调用析构函数释放实例的内存。即利用临时实例的生命周期自动释放原来实例内容

    4、类成员函数实现

    (1)普通构造函数

    参数为 const 防止修改

    strlen计算字符串长度没有把''算进去,所以要+1

    CMyString::CMyString(const char* pDate)
    {
    	if( pDate == NULL )
    	{
    		m_pDate = new char[1];
    		*m_pDate = '';
    	}
    	else
    	{
    		//strlen计算字符串长度没有吧''算进去
    		m_pDate = new char[strlen(pDate)+1];
    		strcpy(m_pDate, pDate);
    	}
    }
    

      

    (2)拷贝构造函数

    参数为 const 防止修改

    参数加 & 省去调用赋值构造函数提高效率

    (2.1)浅拷贝,也叫位拷贝

    CMyString::CMyString( const CMyString& other ) //浅拷贝
    {
    	//没有重新申请新空间,共用同一块内存空间
    	//隐患:非法访问,重复释放内存
    	m_pDate = other.m_pDate;
    }
    

    浅拷贝引发的错误

    (2.2)深拷贝

    CMyString::CMyString( const CMyString& other ) //深拷贝
    {
    	//delete m_pDate;//既然也是属于构造函数的一类,初始为空,不必delete
    	if( other.m_pDate == NULL )
    	{
    		m_pDate = NULL;
    	}
    	else
    	{
    		m_pDate = new char[strlen(other.m_pDate)+1];
    		strcpy(m_pDate, other.m_pDate);
    	}
    }
    

      

    (3)析构函数

    释放前判断,避免重复释放
    CMyString::~CMyString()
    {
    	if(m_pDate) //释放前判断,避免重复释放
    	{
    		delete m_pDate;
    		m_pDate = NULL;
    	}
    }
    

      

    (4)重载赋值运算符

    返回引用 实现连续赋值

    参数为 const 防止修改

    参数加 & 省去调用赋值构造函数提高效率

    (4.1)不安全实现

    CMyString& CMyString::operator = ( const CMyString& other )
    {
    	if( &other != this ) //避免自赋值
    	{
    		if( m_pDate ) //先判断再删除,避免重复操作
    			delete m_pDate;
    		m_pDate = new char[strlen(other.m_pDate)+1]; //如果申请失败,后面strcpy会不安全
    		strcpy(m_pDate, other.m_pDate);
    	}
    	return *this;
    }
    

      

    (4.2)安全实现

    利用临时实例巧妙实现安全转移

    CMyString& CMyString::operator = ( const CMyString& other )
    {
    	if( &other != this ) //避免自赋值
    	{
    		CMyString tmpOther(other);
    		//让tmpOther跟this交换date
    		char *tmpDate = tmpOther.m_pDate;
    		tmpOther.m_pDate = m_pDate;
    		m_pDate = tmpDate;
    
    		//临时实例tmpOther退出if会自动调用析构函数,清除了原本m_pDate的内容
    	}
    	return *this;
    }
    

     

    5、输出实例

    int main()
    {
    	CMyString str("hello"); //等同于 const char* p = "hello"; CMyString str(p);
    	str.printString();
    
    	cout<<"拷贝构造函数"<<endl;
    	CMyString str1(str);
    	str1.printString();
    
    	cout<<"重载赋值操作符"<<endl;
    	CMyString str2("world");
    	str2.printString();
    
    	CMyString str3("Birthday");
    	str3.printString();
    
    	str1 = str2 = str3;
    	str1.printString();
    	str2.printString();
    	str3.printString();
    
    	cout<<str1.getLength()<<endl;
    
    	return 0;
    }
    

    输出样子:

    6、参考

    《后台开发》核心技术与应用实践

    《剑指Offer》

  • 相关阅读:
    事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)
    file的getPath getAbsolutePath和getCanonicalPath的不同
    处理 JSON null 和空数组及对象
    Eclipse快捷键大全(转载)
    Annotation之三:自定义注解示例,利用反射进行解析
    Annotation之二:@Inherited注解继承情况
    innodb事务日志详解
    事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件)
    Java 数组的三种创建方法,数组拷贝方法
    Eclipse 远程调试
  • 原文地址:https://www.cnblogs.com/Christal-R/p/14629145.html
Copyright © 2011-2022 走看看