GeekBand极客班C++面向对象高级编程(上)第二周笔记

字数 2343阅读 47

7.Big Three :拷贝构造、拷贝赋值、析构

. Class with pointer member


#ifndef __MYSTRING__

#define __MYSTRING__

class String

{

...

} ;

String :: function(...)...

Global-function(...)...

#endif

int main()

{

  String s1() ;

  String s2("hello") ;

  String s3(s1) ;             //拷贝构造

  cout << s3 << endl ;

  s3 = s2 ;                      //拷贝赋值

  cout << s3 << endl ;

}


. 拷贝构造与拷贝赋值如果没有定义,编译器会默认一套给你

. 当class with pointer 时,要自己写拷贝构造与拷贝赋值,不能用编译器默认函数


class String

{

public :

    String (const char* cstr = 0 ) ;                      //构造函数

    String (const String& str) ;                       //拷贝构造函数

    String& operator = (const String& str) ;  //拷贝赋值操作符重载

    ~String () ;                                                  //析构函数

    char* get_c_str() const { return m_data } ;  

private :

    char* m_data ;            //指向字符的指针,要动态分配,不能直接放数组

}


. ctor 和 dtor 构造函数和析构函数


inline

String :: String(const char* cstr = 0 )               //构造函数,参数是有默认值的指针

{

    if(cstr){                                                          //先检查传进来的指针是否为空

        m_data = new char[strlen(cstr)+1] ;         //分配空间+1给结束符号\0留位置

        strcpy(m_data , cstr) ;

    }else{

        m_data = new char[1] ;                           //分配1个字符空间

        *m_data = '\0' ;

    }

}

inline

String :: ~String()                                        //析构函数

{

    delete[] m_data ;                                   //关门清理之前new的空间

}


. 有指针的class一定要做动态分配,用完之后要释放

. class with pointer members 必须要有copy ctor 和copy op= 拷贝构造和拷贝赋值

拷贝构造函数

. 如果使用编译器默认浅拷贝,会造成memory leak以及alias,两个指针指向同一位置,叠名


inline

String :: String(const String& str)                       //拷贝构造函数

{

    m_data = new char[strlen(str.m_data)+1] ;    //创建足够空间存放蓝本

    strcpy(m_data , str.m_data) ;                        //深拷贝

}


copy assignment operator拷贝赋值函数

. 一定要在operator中检查是否self assignment,不然在对象删掉原来数据之后再拷贝蓝本时会产生不确定行为


inline String&

String :: operator = (const String& str)

{

    if(this == &str)                                                //self assignment检测自我赋值,防止出错

        return *this ;

    delete[] m_data ;                                           //1.删掉原来数据

    m_data = new char[strlen(str.m_data)+1] ;  //2.创建和蓝本一样大的空间

    strcpy(m_data , str.m_data) ;                       //3.拷贝蓝本

    return *this ;

}


8.堆、栈 与内存管理

output函数

. output operator操作符重载

. 只能写成全局函数,如果写为成员函数会改变操作符使用方向把cout放到右边去


#include <iostream>

ostream& operator << (ostream& os , const String& str)

{

    os << str.get_c_str() ;

    return os ;

}

{

    String s1(“hello ”) ;

    return os ;

}


stack栈,heap堆

. stack是存在于某作用域scope的一块内存空间memory space。调用函数时,会形成一个stack来存放所接收的参数和返回的地址

. 在函数本体function body内声明的任何变量所使用的内存块都取自stack

. Heap,又称system heap 是操作系统提供的global全局内存空间,程序动态分配dynamic allocated 从中获得若干区块blocks,用new来动态取得

. 离开scope后Stack中创建的数据生命结束,在Heap中new的数据离开作用域后依然存在需要手动delete掉

. stack objects的生命期,在scope结束之后动调用析构函数,又称auto object,被自动清理

. static local object 生命期 ,在scope结束后依然存在,直到整个程序结束,析构函数在程序结束调用

. global object 的生命期,在scope外定义,生命直至main结束

. heap object的生命期,new之后要delete,防止内存泄漏。

new和delete

. new:先分配memory,再调用ctor构造函数

. new编译时被分解为三个动作:分配内存、转换存储类型、通过指针调用构造函数

. delete:先调用dtor析构函数,再释放memory

. delete编译时分解为两个动作:调用析构函数、释放内存

. 如果类中没有定义析构函数,没有指针时编译器会在local结束自动清理内存,有指针时必须定义析构函数释放动态分配的内存,不然会产生memory leak

动态分配所得的内存块memory block,(in VC)

. 例如new complex,会获得2个double数据位置,调试时候会在数据前后多获得一些内存和cookies,分配内存要为16的倍数,不足16会用pad补齐。不在调试模式下不需要debugger header,于是刚好为16个byte。上下cookies用来记录整块大小,一个cookie占用4byte。malloc和free函数以cookies为前后标记,分配时cookies后一位是1,由于大小为16整数倍,所以cookies最后四位是0用来记录分配还是收回。

动态分配所得array

. 在良好的变成习惯中,new array[]一定要搭配delete[]


Complex* p = new Complex[3] ;

...

delete[] p ;                                            //如果new的是array,delete一定要加[]

                                                            //加[]会调用三次dtor析构函数释放所以指针

                                                           //如果不加[],只调用一次dtor,则会造成memory leak


9.String类的实现过程总结


class String

{

public :

    String(const char* cstr = 0) ;                      //接受一个指针为初值,默认值为0

    String(const String& str) ;                          //拷贝构造

    String& operator = (const String& str) ;     //返回如果不是local object,则返回reference

    ~String() ;                                                   //析构函数

    char* get_c_str() const { return m_data} ;  //返回字符串的辅助函数

private :

    char* m_data ;                                           //数据为动态分配数组

}

inline                                                                        //尽量让函数成为inline

String :: String(const char* cstr = 0)                        //构造函数

{

    if(cstr){

        m_data = new char[strlen(cstr)+1] ;                //调用其他函数记得include该头文件

        strcpy(m_data , cstr) ;

    }else{

        m_data = new char[1] ;

        *m_data = '\0' ;

    }

}

inline

String :: ~String()                                                //析构函数一定要写

{

    delete[] m_data ;                                            //前面有用array new,这里也要array delete

}

inline

String :: String(const String& str)

{

    m_data = new char[strlen(str.m_data)+1] ;      //分配足够大的空间

    strcpy(m_data , str.m_data) ;                           //把初值拷贝进来

}

inline                                                                 //复杂函数也写成inline是没有关系的

String& String :: operator = (const String& str)      //&符号放在typename后表示引用

    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 ;                                                  // 返回值以支持连续赋值


10.类模版、函数模板、及其他

补充一:static 

. 静态:static加在数据或者函数前面、

. 调用相同函数时使用了不同的地址,this指的调用函数的object的地址

. 一个函数要被很多对象调用时,由this pointer来告诉函数调用哪个地址.

. 数据加上static之后,数据与对象脱离,在内存某区域单独纯在处理,只有一份

. 函数加上static之后,成为静态函数,没有this pointer,不能处理对象,用来存取静态数据

. 静态数据一定要在函数外给出初值


class Account

{

public :

static double m_rate ;                                                         //静态数据

static void set_rate(const double& x){m_rate = x ;}            //静态函数

double Account :: m_rate = 8.0 ;                           //静态数据一定要在class外给出定义

                                                                                //定义可使变量获得内存,可同时给出初值

int main()

{                                                                                //调用静态函数方式有两种

Account :: set_rate(5.0) ;                                          //一种,通过object调用

Account a ;                                                              //二种,通过class name调用

a.set_rate(7.0) ;                                                     

}


补充二:把ctors放在private区

. 只希望产生一个对象的class,如singleton


class A

{

public :

static A& getInstance {return a ;} ;

setup() {...}

private :

A() ;

A(const A& rhs) ;

static A a ;                                                   //一个A本身a已经存在于static,外界创建不了A

...

}



class A{

public :

static A& getInstance() ;

setup() {...}

private :

A() ;

A(const A& rhs);

} ;

A& A :: getInstance()                                                 

{

static A a ;                                                        //也可将静态A创建写进函数中,防止空间浪费

return a;                                                            //有人使用函数时才会被创建,离开继续存在

}


补充三:cout

. ostream中将cout的<<操作符进行各种类型重载,cout即可接受各种不同类型数据

补充四:class template ,类模版


template <typename T>                                 //告诉编译器模板名称T

class complex

{

public :

complex(T r=0 ; T i=0)                                       //数据类型写为T

: re(r) , im(i)

{}

...

T real() const {return re ;} 

T imag() const {return im ;}

private :

T re , im ;

...

}

{

complex<double> c1(2.5 , 1.5) ;                            //模板使用方法

complex<int> c2(2 , 6) ;                                          //T会被替换为<>中的类型

}


补充五:function template , 函数模板

. 当


template <class T>

inline const T&

min(const T& a , const T& b)

{

return b<a?b:a ;                              //引数推导结果调用类中<操作符重载函数

}


. 编译器会对function template进行引数(实参)推导argument deduction

. c++中算法都为function template 形式

补充六: namespace


namespace std                               //std会被全部包在一起使用

{

...                                                 //其中内容会被包在一起

}


. using directive


#include <iostream>

using namespace std ;                      //将全部std包进去

int main()

{

cin << ...;

cout <<...;

return 0 ;

}


. using declarating


#include <iostream>

using std::cout ;                     //仅包入cout

int main()

{

std::cin <<...;

cout <<...;

return 0 ;

}


. 不使用namespace


include <iostream>

int main()

{

std::cin <<...;

std::cout <<...;

return 0;

}


补充七:其他

. operator type 转换函数

. explicit 

. Namespace

. template specialization

. Standard Library

. auto

... 



.

推荐阅读更多精彩内容