C++ 物件導向概念 ( 中 )

初始化列表

C++ 物件導向概念 ( 上 )") 中提到,對於⾃定義類型成員變量,要求調⽤這個成員變量的默認構造函數初始化。

如果這個成員變量沒有默認構造函數,那麼就會報錯,這時候就是要利用初始化列表初始化。

不僅只有⾃定義類型的成員變量,引⽤成員變量、const成員變量也都需要放在初始化列表中初始化。

因此建議都使用初始化列表來對成員變量初始化。
初始化列表.png

C++11⽀持在成員變量宣告的位置給缺省值 (默認參數),這個缺省值主要是給沒有顯⽰在初始化列表初始化的成員使⽤的。

此外,初始化列表中是按照成員變量在類別中宣告順序進⾏初始化跟成員在初始化列表出現的的先後順序無關。建議宣告順序和初始化列表保持一致。

成員變量走初始化列表的邏輯

初始化列表邏輯.png

class Time {
public:
Time(int hour) : _hour(hour) {}
int Get_Hour() { return _hour; }
private:
int _hour;
};

class Date {
public:
Date(int &x, int year = 2025, int month = 7, int day = 31)
  : _year(year), _month(month), _day(day), _n(10), _t(7), ret(x) {}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
 cout << _n << " " << _t.Get_Hour() << " " << ret << endl;
}
private:
int _year;
int _month;
int _day;
const int _n; // const 成員變量 (可以_n後直接給值 (相當於默認值) -> 注意這裡給值不是初始化 => )

// 為什麼一定要在初始化列表初始化?如果在構造函數內部初始化就相當於賦值了,對於const而言是不被允許的
// 因為const 不允許不給值的情況
// 初始化列表是變量誕生的第一瞬間就給值,不是在它誕生之後才賦值 不會違背const的規定
Time _t;      // ⾃定義類型
int &ret;     // 引⽤ (這和引⽤的規定有關 => 必須初始化)
};
int main() {
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
// 輸出:2025-7-31 \n 10 7 0\n

類型轉換

  • C++⽀持內建類型隱式類型轉換為類別類型物件,需要有相關內建類型為參數的構造函數。
  • 構造函數前⾯加explicit就不再⽀持隱式類型轉換。
  • 類別類型的物件之間也可以隱式轉換,需要相應的構造函數⽀持。

    class A {
    public:
    // 構造函數explicit就不再⽀持隱式類型轉換
    // explicit A(int a1)
    A(int a1) : _a1(a1) {}
    // explicit A(int a1, int a2)
    A(int a1, int a2) : _a1(a1), _a2(a2) {}
    void Print() { cout << _a1 << " " << _a2 << endl; }
    int Get() const { return _a1 + _a2; }
    
    private:
    int _a1 = 1;
    int _a2 = 2;
    };
    class B {
    public:
    B(const A &a) : _b(a.Get()) {}
    
    private:
    int _b = 0;
    };
    int main() {
    // 1構造⼀個A的臨時物件,再⽤這個臨時物件拷貝構造aa3
    // 編譯器遇到連續構造+拷貝構造->優化為直接構造
    A aa1 = 1;
    aa1.Print();
    const A &aa2 = 1;
    // C++11之後才⽀持多參數轉化
    A aa3 = {2, 2};
    // aa3隱式類型轉換為b物件
    // 原理跟上⾯類似
    B b = aa3;
    const B &rb = aa3;
    return 0;
    }

靜態成員

static 修飾的成員變量稱為靜態成員變量 => 靜態成員變量要在類別內實現,類別外初始化。

靜態成員變量的特點在於是所有類別的物件共享的,不屬於單獨某個物件。

static 修飾的成員函數稱為靜態成員函數 => 靜態成員函數不要求在類別外實現。

靜態成員函數的特點是其不像一般的成員函數第一個參數默認是this指標,靜態成員函數沒有this指標。

=> 沒有指標也變向說明靜態成員函數不能存取非靜態成員,靜態成員函數只可以存取靜態成員。

突破類別域就可以存取靜態成員,可以透過類別名::靜態成員或者 物件.靜態成員來存取靜態成員變量和靜態成員函數

靜態成員也是類別的成員,受public、protected、private 存取限定符的限制

此外,靜態成員變量不能在宣告位置給缺省值初始化,因為缺省值是給構造函數初始化列表的,靜態成員變量不屬於某個物件,不⾛構造函數初始化列表。

class A {
public:
A() { ++_scount; }
A(const A &t) { ++_scount; }
~A() { --_scount; }
static int GetACount() { return _scount; }
private:
// 類別⾥⾯宣告
static int _scount;
};
// 類別外⾯初始化
int A::_scount = 0;
int main() {
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
// 編譯報錯:error C2248: "A::_scount": 無法存取 private 成員(在"A"類別中宣告)
// cout << A::_scount << endl;
return 0;
}

內部類別

內部類別就是在類別的內部在巢狀類別,內部類別是⼀個獨⽴的類別,跟定義在全域相⽐,他只是受外部類別類別域限制和存取限定符限制。
所以外部類別定義的物件中不包含內部類別,此外,內部類別默認是外部類別的友元類別。

=> A 是 B 的友元 -- A可以存取B的私有和保護成員

class A {
private:
static int _k;
int _h = 1;

public:
class B // B默認就是A的友元
{
 public:
 void foo(const A &a) {
   cout << _k << endl;   // OK 
   cout << a._h << endl; // OK 
   /*
   思考環節
   為什麼 _k 不用物件存取 但_h卻要
     因為 B 屬於 A 的友元 所以 B 可以直接存取 A 的靜態成員(直接看到 A 類別的靜態成員)
     然後_h 要用 物件存取是因為 如果沒有用a去存取 就會變成是this指標去B的類別域找 
     但B的成員變量中並沒有 _h 會導致編譯錯誤
     -
     為什麼不能使用 A::_h? -> 因為 _h 是非靜態成員變量,它屬於某個 A 的實例物件,而不是類別本身
   */
 }
 int _b1;
};
};
int A::_k = 1;
int main() {
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}

匿名物件

類型(實參)定義出來的物件叫做匿名物件,相⽐之前我們定義的類型 物件名(實參)定義出來的叫有名物件。
匿名物件⽣命週期只在當前⼀⾏,⼀般臨時定義⼀個物件當前⽤⼀下即可,就可以定義匿名物件

class A {
public:
A(int a = 0) : _a(a) { cout << "A(int a)" << endl; }
~A() { cout << "~A()" << endl; }
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
 //...
 return n;
}
};
int main() {
A aa1;
// 不能這麼定義物件,因為編譯器無法識別下⾯是⼀個函數宣告,還是物件定義
// A aa1();
// 但是我們可以這麼定義匿名物件,匿名物件的特點不⽤取名字,
// 但是他的⽣命週期只有這⼀⾏,我們可以看到下⼀⾏他就會⾃動調⽤析構函數
A();
A(1);
A aa2(2);
// 匿名物件在這樣場景下就很好⽤,當然還有⼀些其他使⽤場景,這個我們以後遇到了再說
Solution().Sum_Solution(10);
return 0;
}

匿名物件.png