c++ 類的一些基礎

類的基本思想時數據抽象封裝。數據抽象依賴於接口實現 分離的編程技術

1. 定義抽象數據類型

1.1 定義改進類

  • 定義和聲明成員函數的方式和普通函數的方式差不多
class data {
public:
    string isbn() const {return bookno;} //成員函數返回值爲string對象
    data & combine(const data&);
    double avg() const;
    string bookno;
    unsigned sold = 0;
    double reve = 0.0;
};

data add(const data&,const data&);
ostream &print(ostream &, const data&);
istream &read(istream&, data&);
  • 引入this

    請求函數地址初始化this

    調用isbn() 》類似 total.isbn()

    編譯器負責把total的地址傳遞給isbn的隱式函數形參this

    //僞代碼實際過程
    data::isbn(&total);
    
    • 任何對類成員的直接訪問都被看作this的隱式引用
    string isbn() const {return this->bookno;}
    this 是個常量指針總是指着這個對象
    
  • 引入const成員函數

    • isbn函數的另一個關鍵之處是緊隨參數列表之後的const關鍵字,用於修改this指針類型

      • 默認情況下,this的類型是指向非常量版本的常量指針
      • 但把const放在後面,表示this是指向常量的指針 稱爲常量成員函數
      • 常量對象,常量的引用或指針只能調用常量成員函數
    • 編譯器的分兩部處理類

      • 首先編譯成員聲明,然後纔到成員函數體,
      • 所以再成員函數體內使用其他成員的順序無需在意
  • 定義一個返回this對象的函數

    • 設計雷士與複合運算的+=

    data & data::combine(const data &r) {
        sold += r.sold;
        reve += r.reve;
        return *this;
    }
    

1.2 定義類相關的非成員函數

用於輔助概念上是屬於類的接口的組成部分

輸入輸出流

istream &read(istream &is,data &item) {
    double price = 0;
    is >> item.bookno >> item.sold >> price;
    item.reve = price * item.sold;
    return is;
}

ostream &print(ostream &os, const data &item) {
    os << item.isbn() << " " << item.sold << " "
        << item.reve << " " << item.avg();
    return os;
}
  • 定義add函數

    接收兩個對象相加和

    data add(const data& a,const data& b) {
        data sum = a;
        sum.combine(b);
        return sum;
    }
    

1.3 構造函數

定義類對象的初始化方式

  • 構造函數不能聲明成const‘

    • 在const中寫值完後,對象才能取得const稱號
    • 會有默認初始化,默認構造函數
  • 自定義構造函數

    • 如果存在類內初始值,用它初始化該成員
    • 構造默認初始化該成員
  • 如果我們定義了構造函數,類將沒有默認構造函數

class data {
public:
    //新增的構造函數
    data() = default;
    data(const string &s) : bookno(s) {}
    data(const string &s, unsigned n, double p) : bookno(s), sold(n), reve(p*n) {}
    data(istream &);
    
    //之前的
    string isbn() const {
        avg();
        return this->bookno;
    }
    data & combine(const data&);
    double avg() const;
    string bookno;
    unsigned sold = 0;
    double reve = 0.0;
};
  • = default 的含義

    • 表示我們也需要默認構造函數

    c++11 要求編譯器生成構造函數

2. 訪問控制與封裝

  • public說明符定義類的接口

  • private說明符之後的只能被成員函數訪問

    class data {
    public:
        //新增的構造函數
        data() = default;
        data(const string &s) : bookno(s) {}
        data(const string &s, unsigned n, double p) : bookno(s), sold(n), reve(p*n) {}
        data(istream &);
        
        //之前的
        string isbn() const {
            avg();
            return this->bookno;
        }
        data & combine(const data&);
    private:
        double avg() const {
            return sold ? reve/sold:0;
        }
        string bookno;
        unsigned sold = 0;
        double reve = 0.0;
    };
    
    • class或struct關鍵字唯一的區別就是說明符之前的成員類型
      • struct : public 默認
      • class : private 默認

2.1 友元

既然數據成員已經變成private的,那我們的外部輔助函數就不能正常編譯了

  • 友元可以把函數作爲他的友元可允許其他函數或類訪問它的非公有成員

    class data {
    //友元
    friend data add(const data&, const data&);
    friend ostream &print(ostream &, const data&);
    friend istream &read(istream&, data&);
    public:
        //新增的構造函數
        data() = default;
        data(const string &s) : bookno(s) {}
        data(const string &s, unsigned n, double p) : bookno(s), sold(n), reve(p*n) {}
        data(istream &);
    
        //之前的
        string isbn() const {
            avg();
            return this->bookno;
        }
        data & combine(const data&);
    private:
        double avg() const {
            return sold ? reve/sold:0;
        }
        string bookno;
        unsigned sold = 0;
        double reve = 0.0;
    
    };
    
    • 友元並非聲明
  • 如果一個類指定了友元類

    • 友元類的成員函數就可以訪問此類包括非公有的所以成員

      class screen {
          friend class window_mgr;
          //剩餘部分在下面會講
      }
      class window_mgr {
          //窗口編號
      	using screenindex = vector<screen>::size_type;
          //重置爲空
          void clear(screenindex);
          private:
          vector<screen> screems{screen(24,80, ' ')};
      }
      void window_mgr::clear(screenindex i) {
          //s 是一個screen的引用, 指向我們想要清空的那個屏幕
          screen &s = screens[i];
          s.contents = string(s.height*s.width, ' ');
      }
      
      • 成員函數也可以做友元

        class screen {
            ///window_mgr::clear必須在screen類之前被聲明
            friend void window_mgr::clear(screenindex);
            //剩餘部分
        }
        
        • 定義window_mgr 類。聲明clear函數,不能定義它。在clear使用screen的成員之前必須先定義screen
        • 接下來定義screen,包括clear的友元聲明
        • 最後定義clear,此時才能用screen成員
      • 調用友元函數時必須時要聲明過的

        struct x {
            friend void f(){/* 可以定義在函數內部*/} 
            x() {f();} //錯f() 沒聲明
        }
        void x::x(){f();} 錯誤沒聲明
        void f();
        void x::x(){f();} //正確
        

3. 類的其他特性

3.1 類成員再探

class screen {
public: 
    typedef string::size_type pos; //部分定義pos
    screen() = default; //因爲有另一個構造函數,所以本函數需要
    screen(pos ht, pos wd, char c): height(ht), width(wd), contents(ht*wd,c) {}
    char get() const {///讀取光標處
    return contents[cursor];} //隱式內聯
    inline char get(pos ht, pos wd) const; //顯示內聯
    screen &move(pos r, pos c); //能在之後被設爲內聯
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    string contents;
};
  • 定義在類內部的函數是自動inline的,因此構造和get函數默認inline,用inline聲明的顯示聲明成員函數
inline
screen &screen::move(pos r, pos c) {
    pos row = r*width;
    cursor = row +c;
    return *this;
}
char screen::get(pos r, pos c) const { //內部聲明inline
    pos row = r*width;
    return contents[row+c];
}
  • 重載成員函數和非成員函數一樣

  • 可變數據成員

    • 關鍵字mutable

    • 永遠不是const,即使是也能更改它本身的值

      class screen{
          public:
          void spme_member() const;
          private:
          mutable size_t access_ctr;
      }
      void screen::spme_member() {
          access_ctr++; //保存調用次數
      }
      
      class window{
      private:
          //默認情況下爲空白的screen
          vector<screen> scr{screen(24,80,' ')};
      };
      

3.2 返回*this 的成員函數

class screen{
    public:
    screen &set(char);
    ///。。。。
        
    screen &display(ostream &os) {
    do_display(os); return *this;}
    const screen &display(ostream &os) const{
    do_display(os); return *this;}
private:
    void do_display(ostream &os) const {os << contents;}
}
inline screen &screen::set(char c) {
    contents[cursor] = c;
    return *this;
}
//----------------
myscreen.move(4,0).set('#');//等價於下面兩個

myscreen.move(4,0);
myscreen.set('#');

....
  • const能重載

  • };
    和我們之前所學的一樣,當一個成員調用另外一個成員時,this指針在其中隱式地傳遞。
    因此,當display調用do_display 時,它的this指針隱式地傳遞給do__ display。而當display的非常量版本調用do__ display 時,它的this指針將隱式地從指向非常量的指針轉換成指向常量的指針當do_ display 完成後,display函數各自返回解引用this所得的對象。在非常量版本中,this 指向一個非常量對象,因此display返回一個普通的(非常量)引用;而const成員則返回一個常量引用。當我們在某個對象.上調用display時,該對象是否是const決定了應該調用
    display的哪個版本:

  • 每個類定義都是唯一的,即使成員一樣,它們也不是同一個類型

  • 類的聲明

    • 可以只聲明類,不定義

      class screen; //聲明
      

      一旦聲明瞭,類就允許包括指向它自身類型的指針引用

      class link_screen {
       screen win;
       link_screen *next;
      };
      

4. 類的作用域

  • 作用域運算符

    screen::pos ht = 24, wd = 80;
    class window_mgr {
        public:
        //返回窗口編號
       	screenindex addscreen(const screen &);
        
    }
    window_mgr::screenindex
        window_mgr::addscreen(const screen &s) {
        screens.push_back(s);
        return screens.size()-1;
    }
    
    • 編譯器處理完全部聲明後才處理成員函數的定義
    • 先找函數體內,找不到在尋找外層聲明
    • 內外層同時定義一樣的類型名(using mx = int;)裏外都定義了,會報錯重定義mx;
    • 尋找過程都是從內到外一步一步來的
    • ::變量名,這是直接顯示訪問全局變量

5. 構造函數再探

  • 類成員中如果有const 型和引用,都必須初始話。而構造函數初始只能顯示初始化

    class A {
        public :
        A();
        private:
        int i;
        const int j;
        int &z;
    }
    A::A(int &x) : i(x), j(x), z(x) {}// 只用通過初始值列表提供初始值 
    
    • 儘量避免用成員初始化成員
  • 委託構造函數

    c++11 新標準擴展了構造函數初始值得功能

    class A {
        public:
        A(string s, int c, double b) : no(s), so(c), re(b) {}
        //下面是委託構造函數
        A():A(" ", 0, 0) {}
        A(string s):A(s, 0, 0) {}
        A(istream &is):A() { 
        	read(is, *this);
        }   
    }
    
    • 先執行受委託的,在執行委託者的
  • 使用默認構造函數

        screen get(); //這是定義一個函數不是對象
        cout << get.get() << endl; //錯誤
    
  • 類類型轉換

    data item;
        item.combine(string("sssss")); //隱式string轉換成data,執行了接受string的構造函數
        item.combine(data("sssss")); //隱式轉換string,顯示轉換data
    	item.combine(cin); //接受了一個istream類型的構造函數,隱式轉換
    
    • 抑制構造函數的隱式轉換,再構造函數前加上explicit即可
      • 此關鍵字值允許出現再類的構造函數聲明處即可
      • static_cast 可以用explicit的構造函數

5.1 聚合類

滿足以下條件

  • 所有成員都是public的
  • 沒有定義任何構造函數
  • 沒有類初始值
  • 沒有基類,也沒有virtual 函數
struct Data {
	int ival;
    string s;
}

可用花括號給成員初始值列表,初始化聚合類成員

順序一致

5.2 字面值常量類

  • 數據成員都是字面值類型
  • 類必須至少含一個constexpr 構造函數
  • 如果一個數據成員含有類內初始值,則內置類型成員初始值必須是一條常量表達式;如果是某種類類型,必須使用自己成員的constexpr構造函數
  • 類必須使用析構函數默認定義
class Debug {
    public:
    constexpr Debug(bool b = true) : hw(b), io(b),other(b) {}
    constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) {}
    constexpr bool any() {return hw || io || other;}
    private:
        bool hw;
        bool io;
        bool other;
}
  • constexpr 構造函數用於生成constexpr對象

    constexpr Debug io_sub(false, true, false);
    if (io_sub.any())
        cerr << "...." << endl;
    

6. 類的靜態成員

  • 聲明靜態成員

    • 加上static

    • 該對象被所以該類對象共享

    • 不與任何一個對象綁定

    • 不包含this指針

    • 作爲結果,不能聲明成const

    • 不能再static 函數體內使用this指針

    class Account {
    public:
        void calculate() {amount += amount * inter;}
        static double rate() {return inter;}
        static void rate(double);
    private:
        string own;
        double amount;
        static double inter;
        static double initRate();
    };
    double r;
    r = Account::rate(); //使用作用域運算符
    
    • 關鍵字只能放在類中,外部定義不要關鍵字

      • 也可以將它定義在外面
    • 靜態成員不是被創建類對象時初始化的,所以初始化不能寫在內部,要全局的初始只有一次

      double Account::inter = initRate();
      
    • 如果想在類內部初始化,通過要求字面值屬於常量表達式類型即可

      static constexpr int period = 30;//常量表達式
      double as[period];
      
    • 靜態成員屬於不完整類型,可以作爲默認實參

      • 普通成員不行
      class A {
        static A pr; //正確
        A *s; //正確不完整類型
        A sa; //錯誤
      public:
      	A & cl(A = pr);    
      };
      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章