1. 程式人生 > 其它 >C++ 類(一)建構函式,訪問控制,類的一部分特性(this,類成員,類型別,const函式等),行內函數

C++ 類(一)建構函式,訪問控制,類的一部分特性(this,類成員,類型別,const函式等),行內函數

防衛式宣告,防止標頭檔案重複引用帶來的錯誤

#ifndef __COMPLEX__
#define __COMPLEX__
class complex{};
#endif

1. 建構函式(一)

建構函式是特殊的類的成員函式,用於控制類的物件的初始化

建構函式沒有返回型別,不同建構函式的引數數量型別必須有區別

建構函式不能宣告為const的因此如果建立類的const物件時,構造過程中建構函式可以向其寫值,知道完成初始化過程,物件才真正擁有常量屬性

1.1 預設建構函式

無需實參,用於防止自定義型別的物件未初始化

只有類沒有宣告其它任何建構函式時,編譯器才會自動生成預設建構函式

預設建構函式的初始化規則:
(1)用類內的初始值來初始化成員:初始值放在{}中,或=右邊,但是不能用()

(2)預設初始化:由變數型別決定預設值(外加定義變數的位置的影響),內建型別定義於任何函式體之外的變數被初始化為0,定義在函式體內部的內建型別變數將不被初始化,未被初始化的內建型別變數的值是未定義的

如果類的物件沒有被顯式的初始化,則值由類決定

無法生成正確預設建構函式的情況:

如果類中包含一個其他類型別的成員,且這個成員的型別沒有預設建構函式,則編譯器會無法初始化該成員,所以我們必須自定義預設建構函式

Sales_data total;

定義了其他建構函式,則必須定義一個預設建構函式

C++11:

//定義預設建構函式
Sales_data() = default;

1.2 其他建構函式

1.2.1 初始值列表

如果沒有提供類內初始值,則應該在預設建構函式中使用建構函式初始值列表

    comlpex(double r,double i):re{r},im{i} {}
//如果re有類內初始值0.0,那麼下面的兩個建構函式等價
	complex(double i):re{0.0},im{i} {}
	complex(double i):im{i} {}

1.2.2 類外定義建構函式

一般如果建構函式執行一些實際的操作時,定義在類外部

//要指明建構函式是哪個類的成員
Sales_data::Sales_data(std::istream &is){ read(is,*this); }//*this為this物件,this是一個Sales_data物件的引用

2.1 三大函式(拷貝構造,拷貝賦值,解構函式)

2.1.1 拷貝建構函式

建構函式的第一個引數是自身類型別的引用,其他任何引數都有預設值

拷貝建構函式一般不應該是explicit的

物件發生拷貝的幾種情況:1. 使用 = 定義變數 2. 以值的方式返回或傳遞一個物件

注意注意:C++ primer中描述到:很多需要動態記憶體的類能夠使用vector物件或string物件管理必要的儲存控制元件,也就是如果類中包含這兩種成員,其拷貝,賦值,銷燬的合成版本可以正常工作

對於某些類我們不能直接使用編譯器為我們自動合成的拷貝賦值銷燬操作

比如類中帶指標時:

class String{
public:
    String(const char* cstr = 0);
    String(const String &str);//拷貝構造,特殊的點在於,它接收的引數是String類的
    String&operator = (const String &str);//操作符過載,特殊點:賦值符右邊也是String
    ~String();
    char* get_c_str()const {return m_data;}//返回一個指標
private:
	char* m_data;
};

一般來說只要類中帶有指標,就要考慮不適用預設的拷貝構造和拷貝賦值,自己來寫

而且一定要寫解構函式

上面的程式碼中的建構函式有傳入C風格字串的版本:char* str;指標指向頭,最後會有'\0'代表結束符號

拷貝賦值函式,解構函式主要會寫在第13章的筆記中 P441 P266

2. 訪問控制/封裝

預設的訪問許可權是使用class和struct定義類的唯一區別

public:成員在整個程式中可被訪問(用於定義介面)

private:可以被類的成員訪問,不能被使用該類的程式碼訪問


class Sales_data
{
public:
    Sales_data() = default;
    Sales_data(const string&s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream&);
    string isbn() const { return bookNo; }
    Sales_data& combine(const Sales_data&);
private:
    double avg_price()const { return units_sold ? revenue/units_sold:0; }
    string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

2.1 友元 (一)

讓其他類或函式成功friend,就可以訪問類的非公有成員

方法是做出友元宣告:

class Sales_data
{
        //做為非成員函數出友元宣告
    friend Sales_data add(const Sales_data&,const Sales_data&);
public:
    Sales_data() = default;
    Sales_data(const string&s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream&);
    string isbn() const { return bookNo; }
    Sales_data& combine(const Sales_data&);
private:
    double avg_price()const { return units_sold ? revenue/units_sold:0; }
    string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

Sales_data add(const Sales_data&,const Sales_data&){}

注意:友元宣告只能出現在類定義的內部,但是在內部出現的位置不限,因為不是類成員所以不受訪問控制的約束

封裝的好處

  1. 可以防止使用者對資料可能造成的破壞
  2. 被封裝的類的實現細節可以更容易的改變,而無需調整使用者程式碼

友元需要在類內的友元宣告之外專門對函式進行獨立的宣告

3. 類的一些特性

3.1 類成員

3.1.1 型別成員

類中可以自定義類型別的別名

型別成員通常出現在類開始的地方

3.1.2 成員函式也可以被過載

程式碼中的get有兩個版本

3.1.3 可變資料成員

希望修改類中的資料成員,在const成員函式內也可以在變數宣告中加入mutable關鍵字做到

可變資料成員永遠不會是const,即使它是const物件的資料成員

class Screen{
public:
	void some_member()const;
private:
	mutable size_t access_ctr = 0;//用於記錄成員函式被調次數
};
void Screen::some_member() const { ++access_ctr; }

3.1.4 類資料成員的初始值

類型別的資料成員:
提供類內初始值時,必須以 = ,{}表示:

// =  用於初始化Screen資料成員
// {}用於初始化Screen
class Window_mgr
{
private:
	vector<Screen>screens{Screen(24,80,'')};
};

3.2 返回*this的成員函式

3.2.1 this是一個常量指標

set返回的是呼叫set的物件的引用

返回引用的函式是左值的,所以set返回的是物件本身而非物件的副本

如果返回值不是引用型別,則返回值就是物件的副本,無法改變元物件的資料成員

class Screen
{
	Screen &set(char);
    Screen &set(pos,pos,char);
};
inline Screen &Screen::set(char c)
{
    contents[cursor] = c;//設定當前游標所在位置的新值
    return *this;
}

inline Screen &Screen::set(pos r,pos col,char ch)
{
    contents[r*width+col] = ch;
    return *this;//設定給定位置的新值
}


//呼叫:可以改變myscreen的成員
    myscreen.move(4,0).set('#');

3.2.2 const函式

一個const成員函式以引用的形式返回*this,它的返回型別將是常量引用****

this將是一個指向const的指標,*this是const物件,所以就不能對返回值呼叫set等函式

常量物件不能呼叫非常量函式,需要呼叫const成員函式,而非常量物件可以呼叫兩個版本(會自動匹配最合適的)

下面程式碼中的非常量版本display在呼叫do_display時,this指標會隱式的轉換為指向常量的指標

    Screen &display(ostream& os)
    {
        do_display(os);
        return *this;
    }
    const Screen &display(ostream& os)const
    {
        do_display(os);
        return *this;
    }
    
        void do_display(ostream& os) const
    {
        os<<contents;
    }

3.3 類型別

即使兩個類的成員完全相同,它們是不同的型別

類的前向宣告:

class Screen;

建立物件前,類必須被定義過

但是例外情況:一個類的成員不能是該類自己,但是一個類的名字出現後,就被認為是宣告過了,但是尚未定義,因此類允許包含指向它自己型別的引用或指標

我們可以定義指向不完全型別的指標,但是不能建立不完全型別的物件

//正確:
class X;
class Y{ 
	X* x;
};
class X{
	Y y;
};
//錯誤:
class Y;
class X{ 
	Y y;
};
class X{
	X* x;
};

行內函數

行內函數的實現是在類內部實現的,或是在函式前加inline關鍵字

行內函數理論上可以避免函式呼叫帶來的開銷(在函式不是特別複雜的情況下)

實際上內聯只是一個向編譯器的請求二一,編譯器也可以忽略這個請求

原則:

在我們編寫自己的類時,為了更加高效,簡單的操作應該指定為內聯的(建構函式等)

例如complex(複數類中的imag等函式):

class comlpex{
    double re,im;//預設為private
public:
    comlpex():re{0},im{0}{}//預設為{0,0}
    comlpex(double r,double i):re{r},im{i}{}
    comlpex(double r):re{r},im{0}{}

    double imag()const {return im;}
    

};

inline int Max(int x,int y){
    return (x>y)?x:y;
}
//行內函數將在呼叫點展開:
//例如:
    cout<<Max(2,5)<<endl;
//展開為:
	cout<<(x>y):x:y<<endl;