PoEduo - C++阶段班【Po学校】-06自定义String类讲解- 课堂笔记

自定义String:  作业讲解
 1 -->  String类 简单构架
1.0  加namespace,避免重名。
1.1  改掉不能跨平台的语句:
#pragma once    //不能跨平台,就改为下面代码.
#ifndef _STRING_H_
#define _STRING_H_




#endif //! _STRING_H_

 

1.2  两种方式命名空间包含:

第1种:

using namespace PoEdu;

第2种:

namespace PoEdu
{
    String::String()
    {
        
    }

    String::~String()
    {
        
    }
}

提倡第2种写法,不会有命名空间污染的产生。

1.3  默认生成类有哪几种函数:   1 构造   2  析构  3  拷贝赋值  4  拷贝构造   5  重载&  6  重载 *  

1.4  成员数据类成员最好设为私有,还有注意命名的规范

private:
        char *data_  //现在统一为后下划线规范

 2 -->  String类 重写构造函数

 2.1  默认给一个空字符串。(注意:空字符串与空指针的区别:空字符串它是有地址的,占用了内存空间的。而空指针它就是一个NULL。)
2.2  注意点:浅拷贝  与   深拷贝
2.3   cstring  与  string.h 的区别:
 2.4  注意点:抛弃vc(微软)的标准,采用C++标准。VC是一套没有标准的标准。
 2.5   命名规范注意从始到终,统一风格很重要。
2.6  注意点:strlen()得出的长度,加上1才是我们要的,代码:
    String::String(const char *str)
    {
        int len = strlen(str) + sizeof(char);
        data_ = new char[len];
        strcpy(data_,str);

    }

2.7  这里析构函数还需要判断吗?

#include "String.h"
#include <cstring>

namespace PoEdu
{
    String::String(const char *str)
    {
        int len = strlen(str) + sizeof(char);
        data_ = new char[len];
        strcpy(data_,str);

    }

    String::~String()
    {
        if(data_)  //此时还需要判断吗?
          delete[]data_;
    }
}

不需要判断,因为构造函数默认给一个空字符串。

 

2.8  看下面代码,会delete2次

int main()
{
    using namespace PoEdu;
    String demo1;
    String demo2 = demo1;


return 0;
}

实践运行代码:

 String demo2 = demo1; 这句代码会调用默认拷贝构造,因为我们没有重载它,自动生成的默认拷贝构造函数里面,是浅拷贝,如果涉及到指针,浅拷贝是一个危险的行为   

    String::~String()
    {
        std::cout << "~String::String()析构函数" << std::endl;
        if(data_)
            delete[]data_;
    }

第2次析构进来,判断if(data_)为不为空?它不是空的,那么再次delete[]data_;就此触发一个断点。因为你在同一个地方进行第2次的delete。

delete之后,data_在内存里面也不是空的,这时判断语句就变得没用了。以前提到过一种掩耳盗铃的方法,就是在delete[]data_;之后,再给(data_=nullptr;)空指针,这种方法是有问题的。

然而说回来,以上代码就让它触发一个断点,对调试程序来说,还是一件好事。

所以,代码中的判断还是不写了。规避这种风险的正确方法:实现拷贝构造,让其深拷贝。

 

3 -->  String类 重写拷贝构造函数

 3.0  两段代码相似度很高,可以做个抽象,放在一个函数里面。

    String::String(const char *str)
    {
        int len = strlen(str) + sizeof(char);
        data_ = new char[len];
        strcpy(data_,str);
    }

    String::String(const String& other)
    {
        int len = strlen(other.data_) + sizeof(char);
        data_ = new char[len];
        strcpy(data_, other.data_);
    }

3.1 加入一个返回char*的分配函数  char* Alloc(const char *data); 

    char* String::Alloc(const char* data)
    {
        int len = strlen(data) + sizeof(char);
        data_ = new char[len];
        strcpy(data_, data);
        return data_;
    }

3.2  这里返回data_,相对来说,关联性太强,可以改成下面代码:

String::String(const char *str)
    {
        data_ = Alloc(str);
    }

    String::String(const String& other)
    {
        data_ = Alloc(other.data_);
    }

    char* String::Alloc(const char* data)
    {
        int len = strlen(data) + sizeof(char);
        char * newData = new char[len];
        strcpy(newData, data);
        return newData;
    }

3.3 用一个关联度不是很强的char* newData,方便后面函数中再来调用Alloc。

 

4-->  String类 重载赋值函数operator=()

 4.0  思考:赋值函数返回一个什么是恰当的?  答:引用,因为后面要用到连续赋值,用来当左值时,返回引用是恰当的。看示例

int num = 1, num1 = 2;
    num = num1 = 3;    //要的是改变之后的值 返回 自身对象

  num1在第2行要来当左值,那么 此时替代num1位置的函数,其返回就应当设置为一个引用

4.0.1  扩展:什么时候函数返回一个对象?

    int num = 1, num1 = 2;
    num = num1 + 3;    //要的是改变之前的值

  此时代替num1的函数,返回一个临时对象才是正确的。

4.0.2  小结:函数返回怎么设计,主要看代码需要,如果当时要的是改变之后的值,就返回自身;如果当时要的是改变之前的值,就返回临时对象。

    int num = 1, num1 = 2;
    num = ++num1;        //要的是改变之后的值   返回 自身对象
    num = num1++;        //要的是改变之前的值   返回 临时对象

 

 4.1  重写赋值 operator=()

    String & String::operator=(const String & other)
    {
        delete[]data_;
        data_ = Alloc(other.data_);
        return *this;
    }

4.2   注意点:加入检测,当自我拷贝时,直接返回本身。

    String & String::operator=(const String & other)
    {
        if (this == &other)   //比较地址
        {
            return *this;
        }
        delete[]data_;
        data_ = Alloc(other.data_);
        return *this;
    }

 4.3  写入其它重载operator=()函数

头文件String.h

//重载赋值
        String& operator=(const String &other);
        String& operator=(const char* str);
        String& operator=(const int num);
        String& operator=(const double num);

String.cpp

    String & String::operator=(const int num)
    {
        char *str = nullptr;
        itoa(num,str,10);  //以十进制显示
        delete[]data_;
        data_= Alloc(str);
        return *this;
    }

4.3.0   上面str没有分配空间~!要加一个常量:const int MAXSIZE = 255;

const int MAXSIZE = 255;

    String & String::operator=(const int num)
    {
        char str[MAXSIZE] = {0};    
        itoa(num,str,10);
        delete[]data_;
        data_= Alloc(str);
        return *this;
    }

4.3.1 const int MAXSIZE = 255;  这句代码写在头文件,又写在cpp里面,会发生重定义错误。(头文件里面的代码将会被展开来的。)所以,全局变量不要写在头文件里。

4.3.2  在需要外部使用的时候,用extern来导出。

4.4  注意点:1   itoa()这个函数,所在的头文件在<cstdlib>里面。  2   itoa()函数的用法 :里面有3个参数,第1个是value,第2个是Buffer,  第3个是进制。

 4.5  注意点:网上有言int类型只需10个就足够,那是32位系统中int的位数,如果平台一换,系统一换,10个就远远不够用。

4.6  注意点:寻找函数:itof()。

 

5-->  String类 重载操作符函数operator+()

5.0   写+之前,先写+= 。

    String String::operator+(const String& other)  //利用重载的operator+=()
    {
        String temp = *this;
        temp += other;
        return temp;
    }

    String & String::operator+=(const String & other)  //operator+=()
    {
        int len = strlen(data_) + sizeof(other.data_);
        char *newData = new char[len];
        strcpy(newData, data_);
        strcat(newData, other.data_);
        data_ = newData;
        return *this;
    }

 

6-->  String类 操作符operator[]()

 6.0  思考:返回应该是引用,参数应该是unsigned int。

    char& String::operator[](unsigned int index)
    {
        return data_[index];
    }

6.1   课堂问答,关于双目运算中,参数前后的问答。有些类型的参数在前时,要用友元重写操作符。

6.2  课堂问答小结:所有操作符,在C++中的本质是:用当前对象,调用operator重载函数。

6.3   小结续:String demo = "Hello";     demo = "Love " + "Mark" + demo;  编译不能通过。参数过多,无法实现。3个参数是不存在的。

6.4   友元要注意:1     运算的数量  +  -   2元  1元   2  连加的操作时,要注意对象本身要支持此操作符。

 

 

 

 

 
 

posted on 2017-01-11 16:22  zzdoit  阅读(208)  评论(0编辑  收藏  举报

导航