美文网首页
GeekBand C++ 第二周

GeekBand C++ 第二周

作者: hui1429 | 来源:发表于2016-05-21 10:16 被阅读51次

    7.三大函数:拷贝构造,拷贝赋值,析构

    String s3(s1);//拷贝构造函数(s3刚刚出现)
    String s4 = s1;//这种情况也是拷贝构造(虽然用的'=',但是S4刚刚出现)
    s3 = s2;//拷贝赋值(s3已经出现)
    

    无指针的类,不需要写拷贝构造和拷贝赋值。类内带指针,一定要写拷贝构造和拷贝赋值,不能用编译器自动生成的。

    • 构造函数,参数类型是自身类型,则为拷贝构造函数。
    • 拷贝赋值,重载=操作符,参数类型是自身类型。
    • 和构造函数名称相同,前面加~,是析构函数,当类的对象死亡的时候,析构函数会被调用。

    构造函数

    inline
    String::String(const char* cstr = 0)
    {
      if(cstr){
        m_data = new char[strlen(cstr) + 1];
        strcpt(m_data, cstr);
      }else{
        m_data = new char[1];
        *m_data = '\0';
      }
    }
    
    • c语言 字符串,以'\0'结尾,字符串长度,以'\0'标记来计算。另一种在字符串的前面有长度标示,后面没有结束符。
    • 字符串长度为0,也要用一个字符,来保存'\0',为了析构函数统一析构,一个字符用 new char[1] 来创建。
    • 当字符串长度不为0,则用strlen()计算出长度+1,用来保存最后的'\0'。

    析构函数

    inline
    String::~String(){
        delete[] m_data;//array delete配合 array new
    }
    
    • 析构函数的作用,清理,cleanup。
    • 离开作用域时要释放内存。

    big three
    class with pointer members 必须有 copy ctor 和 copy operator=。如果没有使用,则极易造成内存泄露,且两个类中的指针指向同一块内存,改变A,则B也被改变。

    copy ctor
    inline
    构造函数,接受参数类型为本身,则为拷贝构造函数。
    深拷贝:首先创造足够的空间,然后把内容拷贝到新的对象中。
    浅拷贝:则会造成两个‘人’在‘看’同一个东西。

    copy assignment operator 拷贝赋值函数
    右边的对象拷贝的左边,左右两边原本都有内容

    • 1.首先要清空左边。
    • 2.然后在左边分配和右边一样的空间。
    • 3.再把右边的内容拷贝到左边。
    • 4.特别要注意,要检测自我赋值,如果不检测,则自身在拷贝之前就被干掉了,造成内存错误。如果检测到自我赋值,则直接返回。不单单是为了效率,更是为了安全。

    8.堆,栈与内存管理

    8.1.Stack和Heap

    Stack

    是存在于某作用于(scope)的一块内存空间(memory space)。调用函数时,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址,以及local object。

    Heap

    是由操作系统提供的一块global内存空间,程序可以动态分配从其中获得若干区块,new出来的,必须手动delete掉。

    stack objects的生命周期

    stack object,即为local object,又称为 auto object,生命在作用域结束之后就结束了。对象的析构函数会被调用。

    static local objects的生命周期

    static object,其生命在作用域结束后仍然存在,直到整个程序结束。

    global objects的生命周期

    任何写在大括号之外的对象,其生命在main函数之前就存在,在程序结束之后才结束,作用域是“整个程序”。

    heap objects的生命周期

    new得到的对象,在使用完毕之后要delete掉。如果没有delete,则会造成内存泄露。

    8.2 new delete

    new:先分配memory,再调用ctor

    Complex *pc = new Complex(1, 2);
    

    编译器把new分解为三个动作:

    • 1.分配内存 void* mem = operator new(sizeof(Complex)); // 内部调用malloc(n);
    • 2.转型pc = static_cast<Complex*>(mem);
    • 3.构造函数pc->Complex::Complex(1,2); // 其实际参数列表为 Complex::Complex(pc, 1, 2);

    delete:先调用dtor,在释放memory

    String *ps = new String("Hello");
    ...
    delete ps;
    

    编译器把delete分解为两个动作:

    • 1.析构函数 String::~String(ps); // 析构函数会delete掉String类内部动态分配的空间
    • 2.释放内存 operator delete(ps); // 其内部调用free(ps);

    共计两次delete。

    8.3 动态分配所得的内存块(memory block)

    1.动态分配所得的对象

    1.Complex *pc = new Complex(1, 2);

    在debug模式下,class的前面有32字节,后面有4字节,前后cooky各4字节,cooky为0x41,共计:

    8+(32+4)+(4*2)=52 -> 64

    在release模式下,class本身8个字节,前后cooky各4个字节,cooky为0x11,共计:

    8+(4*2)=16 -> 16

    上下cooky的作用,记录整块给你的大小。采用16进制,如果是0,则代表系统回收,如果是1,则代表系统给出。在vs的编译器下,给的内存的大小为16的倍数,所以cooky在16进制时最后一位一直为0,所以可以用来标记内存的方向。

    2.String *ps = new String("Hello");

    在debug模式下,cooky为0x31,共计:

    4+(32+4)+(4*2)=48 -> 48

    在release模式下,cooky为0x11,共计:

    4+(4*2)=12 -> 16

    2.动态分配所得的 array

    array new 要搭配 array delete,不然会出错。

    1.Complex *p = new Complex[3];

    在debug模式下,cooky为0x51,共计:

    (8*3)+(32+4)+(4*2)+4=72 -> 80

    在release模式下,cooky为0x31,共计:

    (8*3)+(4*2)+4=36 -> 48

    2.String *p = new String[3];

    在debug模式下,cooky为0x41,共计:

    (4*3)+(32+4)+(4*2)+4=60 -> 64

    在release模式下,cooky为0x31,共计:

    (4*3)+(4*2)+4=24 -> 32

    3.array new 一定要搭配 array delete

    String *p = new String[3];
    ...
    delete[] p; // 调用3次dtor
    
    memory 解释
    21h cooky记录内存大小
    3 数组的大小
    String[0] 调用dtor
    String[1] 调用dtor
    String[2] 调用dtor
    000000000(pad) 填充内存
    21h cooky记录内存大小
    String *p = new String[3];
    ...
    delete p; // 调用1次dtor
    
    memory 解释
    21h cooky记录内存大小
    3 数组的大小
    String[0] 调用dtor
    String[1] 未调用dtor
    String[2] 未调用dtor
    000000000(pad) 填充内存
    21h cooky记录内存大小

    对比发现,整块的内存并没有发生内存泄露,因为整块内存的大小记录在cooky当中。如果没有写array delete而写的是delete,编译器不知道下面有几个对象,因此只有第一个也就是String[0]调用了dtor,其余的对象并没有调用dtor。当调用玩dtor之后,再释放掉整块的内存。由此可以发现,如果此时的例子是Complex类的话,那么由于类内部没有指针,所以即使用array new,但没用array delete,也不会产生内存泄露。

    但是在写代码时,我们应养成好的编码习惯,array new 一定要搭配 array delete。

    9.复习String类的实现

    • 1.防卫式声明
      #ifndef _MYSTRING_
      #define _MYSTRING_
      class String{
      ...
      };
      #endif
      
    • 2.如何去定义内部变量
      • 放数组,但是数组的大小无法确定。
      • 放指针,当需要时,动态分配(new)字符串的大小,在32位的系统中,一个指针是4byte,放在private中。
        char *m_data;
        
    • 3.ctor,放在public;
      String(const char* cstr = 0);
      
      • 只是接受字符串,不会改变,要加上const。
    • 4.class with point member:
      • copy ctor:
        String(cosnt String& str);
        
      • copy assignment operator:
        String& operator=(const String& str);
        
        • 对于copy ctor 和copy assignment函数,不会改变被拷贝的对象,所以要加上const。
        • 返回拷贝的对象,因为返回结果不是放在local object中,目标本来存在,因此使用return by reference。
      • dtor:
        ~String();
        
    • 5.辅助函数
      • 为了能够cout字符串,因此需要一个函数能够取出String类中的字符串。
        char* get_c_str() cosnt { return m_data; }
        
      • 因为函数简单,直接使用inline的方式。因为不会改变对象的成员变量,因此需要加上const。
    • 6.ctor,copy ctor,copy assignment 都不需要加const
    • 7.ctor和dtor
      • ctor
        inline //建议编译器
        String::String(cosnt char* cstr = 0){
            if(cstr){
                //以下两个函数为C函数,需要相应头文件
                m_data = new char[strlen(cstr) + 1];
                strcpy(m_data, cstr);
            }else{
                m_data = new char[1];
                *m_data = '\0';
            }
        }
        
      • dtor
        inline //建议编译器
        String::~String(){
            delete[] m_data;
            //由于ctor使用了array new,因此这里也要使用array delete   
        }
        
      • copy ctor
        inline
        String::String(cosnt String& str){
            m_data = new char[strlen(str.m_data) + 1];
            strcpy(m_data, str.m_data);
        }
        
        • inline只是建议,不能inline的话也没关系。
      • copy assignment operator
        inline
        String& String::operator= (cosnt String& str){//此时&为reference
            //首先判断是否自我赋值,不单单是效率问题,更是正确与否的问题。
            if(this == &str)//此时的&为取地址
                return *this;
                
            //在进行拷贝赋值
            delete[] m_data;
            m_data = new char[strlen(str.m_data) + 1];
            strcpy(m_data, str.m_data);
            return *this;
            //传出去值不在乎用何种方式接受
        }
        
        • 关于返回值,当不需要连续赋值时,则不需要返回值,当需要连续赋值时,则需要返回值。
        String s1, s2, s3("Hello");
        s1 = s2 = s3;
        

    10.扩展补充:类模板,函数模板及其他

    1.static

    Class complex{
    public:
        double real() const{
        return this->re;
        }
    private:
        double re;
        double im;
    };
    
    • C++的习惯写法
    complex c1, c2, c3;
    cout << c1.real();
    cout << c2.real();
    
    • 从C的角度考虑完成同上功能的写法
    complex c1, c2 ,c3;
    cout << complex::real(&c1);
    cout << complex::real(&c2);
    

    同一个函数real(),之所以能处理不同对象的数据,靠的就是this point。

    static data members 在内存的单独位置,有且只有一份。

    static member functions
    同样在内存的单独位置,函数本身也仅仅只有一份。但是跟一般的成员函数有个区别,它没有this point。它只能去处理静态的数据。

    静态的变量,在类的内部只是生命,需要在类外部定义。类型 类名称::变量名(初始化操作);

    调用静态函数的方法有两种:

    • 1)通过object调用。但是this指针不会被作为参数传入函数中。
    • 2)通过class name调用。

    单例模式,把ctors放在private区域。

    class A{
    public:
        static A& getInstance(){ return a; }
        setup()
    private:
        A();
        A(const A& rhs);
        static A a;
        ...
    };
    A::getInstance().setup();
    

    外界想要使用a,只能用过:getInstance()获得。

    meyers Singleton:

    class A{
    public:
        static A& getInstance();
        setup()
    private:
        A();
        A(const A& rhs);
        ...
    };
    
    A& A::getInstance(){
        static A a;
        return a;
    }
    

    当外界不需要使用这个类时,a不会被创建,只有当外界需要使用这个类,调用了getInstance()函数,a才会被创建。

    2.关于cout

    查看标准库ostream代码,重载了很多的operator<<

    ostream

    3.class template,类模板

    在类的前面加上:

    template<typename T>
    class complex{
    ...
    };
    

    用T吧具体的类代替,当实际使用时,根据实际的需要,生成具体的类代码。

    {
        complex<double> c1(2.5, 1.5); //用double代替T生成一份类的代码
        complex<double> c2(2, 6); //用int代替T生成一份类的代码
        
    }
    

    4.function template,函数模板

    template<class T>
    inline
    const T& min(const T& a, const T& b){
        retuen a < b ? a : b;
    }
    

    所有比较大小都是这么操作,因此可以使用函数模板。实际比较时如何去比较,则依赖于需要比较大小的类。类似于这种函数,称之为算法。

    {
        complex c1(1, 2), c2(3, 4), c3;
        c3 = min(c1, c2);//当调用min()函数时,编译器会进行实参推导(argument deduction),不必再使用的时候指定类型。
    }
    

    5.namespace

    避免全局变量,函数以及类的同名,则需要namespace,如果每个人自己顶一个namespace,则不会造成冲突。

    • using directive
      using namespace std;
      {
        cin >> ...;
        cout << ...;
      }
      
    • using declaration
      using std::cout;
      {
        std::cin >> ...;
        cout << ...;
      }
      
    • not use
      {
        std::cin >> ...;
        std::cout << ...;
      }
      

    6.更多的细节,仍需努力

    仍需努力的部分

    相关文章

      网友评论

          本文标题:GeekBand C++ 第二周

          本文链接:https://www.haomeiwen.com/subject/ozanrttx.html