[Boolan] C++第二週(創建一個帶指針成員變量的類)[注意事項]

1. Big Three

class String
{
public:
    String (const char* cstr=0);
    String (const String &str);
    String &operator=(const String &str);
    char* get_c_str() const {return m_data;}
private:
    char *m_data;
};

構造函數 析構函數

inline
String::String(const char*cstr = 0)
{
    if (cstr) {
        m_data = new char[strlen(cstr)+1];  //+1是不要忘了結尾的'\0',strlen是不計算'\0'的
        strcpy(m_data, cstr);
    } else {
        m_data = new char[1];   //[1]爲了和上面統一,析構時方便
        *m_data = '\0';
    }
}
C/C++語言中,字符串以'\0'表示一個字符串的結束
inline String::~String()
{   
    delete [] m_data;   
}

如果類裏面有指針,動態分配內存,一定要再析構函數中釋放內存,避免內存泄漏

拷貝構造

String s1("hello");

String s2(s1);
    ||  完全相同,都是調用的拷貝構造
String s2 = s1;
inline
String::String(const String &str)
{
    m_data = new char[strlen(cstr)+1]; 
    strcpy(m_data, cstr);
}

重載等號運算符

1. 自我檢查,如果是自己,直接返回
2. 刪除當前的內容
3. 獲取右值內容
4. 返回自身引用,可以連等
inline
String& String::operator=(const String& str)
{
    if(this == &str)
    { return *this;}

    delete [] m_data;
    m_data = new char[strlen(str)+1];
    strcpy(m_data, str.m_data);
    return *this;
}

2. 在堆中創建對象

new : 先分配內存,在調用構造函數

當new一個對象的時候,實際上是 先分配內存,在調用類的構造函數
Complex *pc = new Complex(1,2);

void mem = operator new (sizeof(Complex));  -->  內部使用malloc分配內存
pc = static_cast<Complex*>(mem);
pc->Complex::Complex(1,2); ---> Complex::Complex(pc, 1, 2)

delete:先調用析構函數,再釋放內存

delete pc;

Complex::~Complex(pc);
operator delete(pc)     ---> free(pc)   使用free釋放內存

在實際應用中,建議遵循誰創建,誰釋放的原則,儘量避免內存泄漏

3. 擴展

使用static 的單例

class A
{
public:
    static A &getInstance()
    {
        static A a;
        return a;
    }
    static int num;
private:
    A()
    {}
};
int A::num = 10;    //類的靜態成員初始化方法

int main(int argc, char *argv[])
{
    A::getInstance();       //通過類名來調用
    return 0;
}

類模板

template <typename T>
class A
{
public:
    A(T a)
    :m_a(a)
    {}
    T m_a;
};

//調用
A<int>  a;  //類模板調用需要指明模板類型

函數模板

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

//調用
class A
{
...
};

A a,b;
min(a,b)    ===> 函數模板調用不需要指明參數對象,編譯器會對實參進行推導,然後確定類型

namespace 命名空間

#include <iostream>

using namespace std;    //打開標準庫 的命名空間

//自定義命名空間
namespace mySpace
{
    int a;
}

int main(int argc, char*argv[])
{
    mySpace::a = 10;
    return 0;
}

operator type() const;

參考鏈接: [C/C++]_[操作符重載operator type()和operator()的區別]

#include <iostream>  
#include <string>  
#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  

using namespace std;  

class Total  
{  
public:  
    Total(float sum,float discount)  
    {  
        sum_ = sum;  
        discount_ = discount;  
    }  
    ~Total(){}  

    operator std::string()  
    {  
        cout<<"operator std::string() "<<endl;
        char str[128];  
        sprintf(str,"%f",sum_* discount_);  
        return std::string(str);  
    }  

    operator int()  
    {  
        cout<<"operator int()"<<endl;
        return sum_* discount_;  
    } 

    operator float()  
    {  
        cout<<"operator float()"<<endl;
        return sum_* discount_;  
    }  

    float operator()()  
    {  
        cout<<"float operator()()   "<<endl;
        return sum_* discount_;  
    }  
    float sum_;  
    float discount_;  
};  

ostream& operator<< (ostream &out, const Total & ths)
{
    cout << "--------------------ostream& << (ostream &out, const Total & ths)"<<endl;
    return out;
}

int main(int argc, char const *argv[])  
{  
    Total to(89, 0.8);  
    cout << to << endl;  
    cout << to() << endl;  
    cout << (std::string)to << endl;  
    cout << (float)to << endl;  
    cout << (int)to << endl; 
    return 0;  
}  

輸出
--------------------ostream& << (ostream &out, const Total & ths)

float operator()()
71.2
operator std::string()
71.200001
operator float()
71.2
operator int()
71
需要注意的是如果沒有重載<<,並且Total裏面有operator float(),operator int()編譯器就會報錯,有二義性,因爲不確定cout << to << endl 是把Total轉成int,還是float,然後進行輸出,如果只有一個,就不會有問題 

explicit complex():initialization list {}

關鍵字explicit,可以阻止不應該允許的經過轉換構造函數進行的隱式轉換的發生。聲明爲explicit的構造函數不能在隱式轉換中使用。
class Test1
{
public:
    Test1(int n) { num = n; } //普通構造函數
private:
    int num;
};

class Test2
{
public:
    explicit Test2(int n) { num = n; } //explicit(顯式)構造函數
private:
    int num;
};
int main(int argc, char *argv[])
{
    Test1 t1 = 12;  //隱式調用其構造函數, 成功
    //Test2 t2 = 12;    //編譯錯誤,不能隱式調用其構造函數
    Test2 t3(12);   //顯示調用成功
    return 0;
}

function-like object

參考鏈接:std::function from cppreference.com
以下代碼需要編譯器C++11支持

#include <functional>
#include <iostream>

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_+i << '\n'; }
    int num_;
};

void print_num(int i)
{
    std::cout << i << '\n';
}

struct PrintNum {
    void operator()(int i) const
    {
        std::cout << i << '\n';
    }
};

int main()
{
    // store a free function
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // store a lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // store the result of a call to std::bind
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // store a call to a member function
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);

    // store a call to a data member accessor
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // store a call to a member function and object
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
    f_add_display2(2);

    // store a call to a member function and object ptr
    std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
    f_add_display3(3);

    // store a call to a function object
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
}

//輸出
-9
42
31337
314160
num_: 314159
314161
314162
18

4. 作業知識點

虛析構

爲了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。
class Shape
{
public:
    Shape(int no = 0)
        :m_no(no)
    {   }
    virtual ~Shape(){}
    virtual int getArea()=0;
    void setNo(int no)  { m_no = no; }
    int getNo() {return m_no;}
protected:
    int m_no;
};

class Rect:public Shape
{
public:
    Rect():m_p(0)
    {}
    ~Rect(){}
private
    char *m_p;
}

int main(int argc, char *argv[])
{
    Shape *p = new Rect;

    delete p;       
    return 0;
}
上例代碼,如果Shape的析構函數不是virtual的話,delete的時候就不會調用Rect的析構函數,可能會造成Rect::m_p的內存泄漏

還可以參考這個例子:析構函數什麼情況下要定義爲虛函數?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章