1. 程式人生 > >C++深度剖析(下)

C++深度剖析(下)

第 36 課 經典問題解析三

什麼時候需要過載賦值操作符?
編譯器是否提供預設的賦值操作?

編譯器為每個類預設過載了賦值操作符
預設的賦值操作符僅完成淺拷貝
當需要進行深拷貝時必須過載賦值操作符
賦值操作符與拷貝建構函式有相同的存在意義

例 1 預設賦值操作符過載

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int
i) { m_pointer = new int(i); } Test(const Test& obj) { m_pointer = new int(*obj.m_pointer); } Test& operator = (const Test& obj) { if( this != &obj ) { delete m_pointer; m_pointer = new int(*obj.m_pointer); } return
*this; } void print() { cout << "m_pointer = " << hex << m_pointer << endl; } ~Test() { delete m_pointer; } }; int main() { Test t1 = 1; Test t2; t2 = t1; t1.print(); t2.print(); return 0; }

一般性原則
過載賦值操作符,必然需要實現深拷貝

例 2 陣列類的優化

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;

    IntArray(int len);
    IntArray(const IntArray& obj);
    bool construct();
public:
    static IntArray* NewInstance(int length); 
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    int& operator [] (int index);
    IntArray& operator = (const IntArray& obj);
    IntArray& self();
    ~IntArray();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;

    m_pointer = new int[m_length];

    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }

    return ret;
}

IntArray* IntArray::NewInstance(int length) 
{
    IntArray* ret = new IntArray(length);

    if( !(ret && ret->construct()) ) 
    {
        delete ret;
        ret = 0;
    }

    return ret;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}

int& IntArray::operator [] (int index)
{
    return m_pointer[index];
}

IntArray& IntArray::operator = (const IntArray& obj)
{
    if( this != &obj )
    {
        int* pointer = new int[obj.m_length];

        if( pointer )
        {
            for(int i=0; i<obj.m_length; i++)
            {
                pointer[i] = obj.m_pointer[i];
            }

            m_length = obj.m_length;
            delete[] m_pointer;
            m_pointer = pointer;
        }
    }

    return *this;
}

IntArray& IntArray::self()
{
    return *this;
}

IntArray::~IntArray()
{
    delete[]m_pointer;
}
#include <iostream>
#include <string>
#include "IntArray.h"

using namespace std;

int main()
{
    IntArray* a = IntArray::NewInstance(5);   
    IntArray* b = IntArray::NewInstance(10);

    if( a && b )
    {
        IntArray& array = a->self();
        IntArray& brray = b->self();

        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;

        array = brray;

        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;
    }

    delete a;
    delete b;

    return 0;
}

編譯器預設提供的函式
class Test{};
class Test
{
public:
Test();
Test(const Test&);
Test& operator=(const Test&);
~Test();
};

下面的程式碼輸出什麼?為什麼?

string s=”12345”;
const char *p=s.c_str();
cout<<p<<endl;
s.append(“abcde”);
cout<<p<<endl;

例 3 字串問題 1

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s = "12345";
    const char* p = s.c_str();

    cout << p << endl;     

    s.append("abced");  // p 成為了野指標

    cout << p << endl;     


    return 0;
}

例 4 字串問題 1

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s = "12345";
    const char* p = s.c_str();

    cout << s << endl;     

    s.append("abced");  // p 成為了野指標

    cout << s << endl;     


    return 0;
}

下面的程式輸出什麼?為什麼?

const char *p=”12345”;
string s=””;
s.reserve(10);
for(int i=0;i<5;i++)
{
s[i]=p[i];
}
if(!s.empty())
{
cout<<s<<endl;
}

例 5 字串問題 2

#include <iostream>
#include <string>

using namespace std;

int main()
{
    const char* p = "12345";
    string s = "";

    s.reserve(10);

    // 不要使用 C 語言中的方式操作 C++ 中的字串
    for(int i=0; i<5; i++)
    {
        s[i] = p[i];
    }

    cout << s << endl;

    return 0;
}

例 6 字串問題 2

#include <iostream>
#include <string>

using namespace std;

int main()
{
    const char* p = "12345";
    string s = "";

    s=p;

    // 不要使用 C 語言中的方式操作 C++ 中的字串
    for(int i=0; i<5; i++)
    {
        s[i] = p[i];
    }

    cout << s << endl;

    return 0;
}

小結:
在需要進行深拷貝的時候必須過載賦值操作符
賦值操作符合拷貝建構函式有同等重要的意義
string 類通過一個數據空間儲存字元資料
string 類通過一個成員變數儲存當前字串的長度
c++開發時儘量避開 c 語言中慣用的程式設計思想

第 37 課 智慧指標分析

記憶體洩漏
動態申請堆空間,用完後不歸還
c++語言中沒有垃圾回收機制
指標無法控制所指堆空間的宣告週期

例 1 記憶體洩漏

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int i;
public:
    Test(int i)
    {
        this->i = i;
    }
    int value()
    {
        return i;
    }
    ~Test()
    {
    }
};

int main()
{
    for(int i=0; i<5; i++)
    {
        Test* p = new Test(i);

        cout << p->value() << endl;


    }

    return 0;
}

我們需要什麼
需要一個特殊的指標
指標生命週期結束時主動釋放堆空間
一片堆空間最多隻能由一個指標標識
杜絕指標運算和指標比較

解決方案
過載指標特徵操作符(->和*)
只能通過類的成員函式過載
過載函式不能使用引數
只能定義一個過載函式

例 2 智慧指標

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int i;
public:
    Test(int i)
    {
        cout << "Test(int i)" << endl;
        this->i = i;
    }
    int value()
    {
        return i;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

class Pointer
{
    Test* mp;
public:
    Pointer(Test* p = NULL)
    {
        mp = p;
    }
    Pointer(const Pointer& obj)
    {
        mp = obj.mp;
        const_cast<Pointer&>(obj).mp = NULL;
    }
    Pointer& operator = (const Pointer& obj)
    {
        if( this != &obj )
        {
            delete mp;
            mp = obj.mp;
            const_cast<Pointer&>(obj).mp = NULL;
        }

        return *this;
    }
    Test* operator -> ()
    {
        return mp;
    }
    Test& operator * ()
    {
        return *mp;
    }
    bool isNull()
    {
        return (mp == NULL);
    }
    ~Pointer()
    {
        delete mp;
    }
};

int main()
{
    Pointer p1 = new Test(0);

    cout << p1->value() << endl;

    Pointer p2 = p1;

    cout << p1.isNull() << endl;

    cout << p2->value() << endl;

    return 0;
}

智慧指標的使用軍規
只能用來指向堆空間中的物件或者變數

小結:
指標特徵操作符(->和*)可以被過載
過載指標特徵符能夠使用物件代替指標
智慧指標只能用於指向堆空間中的記憶體
智慧指標的意義在於最大程度的避免記憶體問題

第 38 課 邏輯操作符的陷阱

邏輯運算子的原生語義
運算元只有兩種值(true 和 false)
邏輯表示式不用完全計算就能確定最終值
最終結果只能是 true 或者 false

例 1 邏輯表示式

#include <iostream>
#include <string>

using namespace std;

int func(int i)
{
    cout << "int func(int i) : i = " << i << endl;

    return i;
}

int main()
{
    if( func(0) && func(1) )
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    cout << endl;

    if( func(0) || func(1) )
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    return 0;
}

邏輯操作符可以過載嗎?
過載邏輯操作符有什麼意義?

例 2 過載邏輯操作符

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int mValue;
public:
    Test(int v)
    {
        mValue = v;
    }
    int value() const
    {
        return mValue;
    }
};

bool operator && (const Test& l, const Test& r)
{
    return l.value() && r.value();
}

bool operator || (const Test& l, const Test& r)
{
    return l.value() || r.value();
}

Test func(Test i)
{
    cout << "Test func(Test i) : i.value() = " << i.value() << endl;

    return i;
}

int main()
{
    Test t0(0);
    Test t1(1);

    if( func(t0) && func(t1) )
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    cout << endl;

    if( func(1) || func(0) )
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    return 0;
}

問題的本質分析
1、c++通過函式呼叫擴充套件操作符的功能
2、進入函式體前必須完成所有引數的計算
3、函式引數的計算次序是不定的
4、短路法則完全失效

邏輯操作符過載後無法完全實現原生的語義

一些有用的建議:
實際工程開發中避免過載邏輯操作符
通過過載比較操作符代替邏輯操作符過載
直接使用成員函式代替邏輯操作符過載
使用全域性函式對邏輯操作符進行過載

小結:
c++從語法生支援邏輯操作符過載
過載後的邏輯操作符不滿足短路法則
工程中不要過載邏輯操作符
通過過載比較操作符替換邏輯操作符過載
通過專用成員函式替換邏輯操作符過載

第 39 課 逗號操作符的分析

逗號操作符(,)可以構成逗號表示式
逗號表示式用於將多個子表示式連線為一個表示式
逗號表示式的值為最後一個子表示式的值
逗號表示式中的前 N-1 個子表示式可以沒有返回值
逗號表示式按照從左向右的順序計算每個子表示式的值
exp1,exp2,exp3,….,expN

例 1 逗號表示式的示例

#include <iostream>
#include <string>

using namespace std;

void func(int i)
{
    cout << "func() : i = " << i << endl;
}

int main()
{   
    int a[3][3] = {
        (0, 1, 2),
        (3, 4, 5),
        (6, 7, 8)
    };

    int i = 0;
    int j = 0;

    while( i < 5 )    
        func(i),

    i++;

    for(i=0; i<3; i++)
    {
        for(j=0; j<3; j++)
        {
            cout << a[i][j] << endl;
        }
    }

    (i, j) = 6;

    cout << "i = " << i << endl;
    cout << "j = " << j << endl;

    return 0;
}

在 C++中過載逗號操作符是合法的
使用全域性函式對逗號操作符進行過載
過載函式的引數必須有一個類型別

過載函式的返回值型別必須是引用
Class& operator , (const Class& a,const Class& b)
{
return const_cast

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int mValue;
public:
    Test(int i)
    {
        mValue = i;
    }
    int value()
    {
        return mValue;
    }
};
/*
Test& operator , (const Test& a, const Test& b)
{
    return const_cast<Test&>(b);
}
*/
Test func(Test& i)
{
    cout << "func() : i = " << i.value() << endl;

    return i;
}

int main()
{   
    Test t0(0);
    Test t1(1);
    Test tt = (func(t0), func(t1));         // Test tt = func(t1);

    cout << tt.value() << endl; // 1

    return 0;
}

問題的本質分析
1、c++通過函式呼叫擴充套件操作符的功能
2、進入函式體前必須完成所有引數的計算
3、函式引數的計算次序是不定的
4、過載後無法嚴格從左向右計算表示式

工程中不要過載逗號操作符!

小結:
逗號表示式從左向右順序計算每個子表示式的值
逗號表示式的值為最後一個子表示式的值
操作符過載無法完成實現逗號操作符的原生意義
工程開發中不要過載逗號操作符

第 40 課 前置操作符和後置操作符

下面的程式碼有沒有區別?為什麼?
i++; //i 的值作為返回值,i 自增 1
++i; //i 自增 1,i 的值作為返回值

例 1 真的有區別嗎?

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int i = 0;

    i++;

    ++i;

    return 0;
}

意想不到的現實
現代編譯器產品會對程式碼進行優化
優化使得最終的二進位制程式更加高效
優化後的二進位制程式丟失了 c/c++的原生語義
不可能從編譯後的二進位制程式還原 c/c++程式

++操作符可以過載嗎?
如何區分前置++和後置++?

++操作符可以被過載
全域性函式和成員函式均可進行過載
過載前置++操作符不需要額外的引數
過載後置++操作符需要一個 int 型別的佔位引數

例 2 ++操作符的過載

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int mValue;
public:
    Test(int i)
    {
        mValue = i;
    }

    int value()
    {
        return mValue;
    }

    Test& operator ++ ()
    {
        ++mValue;

        return *this;
    }

    Test operator ++ (int)
    {
        Test ret(mValue);

        mValue++;

        return ret;
    }
};

int main()
{
    Test t(0);

    t++;

    ++t;

    return 0;
}

真正的區別
對於基礎型別的變數
前置++的效率與後置++的效率基本相同
根據專案編碼規範進行選擇
對於類型別的物件
前置++的效率高於後置++
儘量使用前置++操作符提高程式效率

例 3 複數類的進一步完善


#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
    double a;
    double b;
public:
    Complex(double a = 0, double b = 0);
    double getA();
    double getB();
    double getModulus();

    Complex operator + (const Complex& c);
    Complex operator - (const Complex& c);
    Complex operator * (const Complex& c);
    Complex operator / (const Complex& c);

    bool operator == (const Complex& c);
    bool operator != (const Complex& c);

    Complex& operator = (const Complex& c);

    Complex& operator ++ ();
    Complex operator ++ (int);
};

#endif
#include "Complex.h"
#include "math.h"

Complex::Complex(double a, double b)
{
    this->a = a;
    this->b = b;
}

double Complex::getA()
{
    return a;
}

double Complex::getB()
{
    return b;
}

double Complex::getModulus()
{
    return sqrt(a * a + b * b);
}

Complex Complex::operator + (const Complex& c)
{
    double na = a + c.a;
    double nb = b + c.b;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator - (const Complex& c)
{
    double na = a - c.a;
    double nb = b - c.b;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator * (const Complex& c)
{
    double na = a * c.a - b * c.b;
    double nb = a * c.b + b * c.a;
    Complex ret(na, nb);

    return ret;
}

Complex Complex::operator / (const Complex& c)
{
    double cm = c.a * c.a + c.b * c.b;
    double na = (a * c.a + b * c.b) / cm;
    double nb = (b * c.a - a * c.b) / cm;
    Complex ret(na, nb);

    return ret;
}

bool Complex::operator == (const Complex& c)
{
    return (a == c.a) && (b == c.b);
}

bool Complex::operator != (const Complex& c)
{
    return !(*this == c);
}

Complex& Complex::operator = (const Complex& c)
{
    if( this != &c )
    {
        a = c.a;
        b = c.b;
    }

    return *this;
}

Complex& Complex::operator ++ ()
{
    a = a + 1;
    b = b + 1;

    return *this;
}

Complex Complex::operator ++ (int)
{
    Complex ret(a, b);

    a = a + 1;
    b = b + 1;

    return ret;
}

小結:
編譯優化使得最終的可執行程式更加高效
前置++操作符和後置++操作符都可以被過載
++操作符的過載必須符合其原生語義
對於基礎型別,前置++與後置++的效率幾乎相同
對於類型別,前置++效率高於後置++

第 41 課 型別轉換函式(上)

標準資料型別之間會進行隱式的型別安全轉換

例 1 有趣的隱式型別轉換

#include <iostream>
#include <string>

using namespace std;

int main()
{   
    short s = 'a';
    unsigned int ui = 1000;
    int i = -2000;
    double d = i;

    cout << "d = " << d << endl;
    cout << "ui = " << ui << endl;
    cout << "ui + i = " << ui + i << endl;

    if( (ui + i) > 0 )
    {
        cout << "Positive" << endl;
    }
    else
    {
        cout << "Negative" << endl;
    }

    cout << "sizeof(s + 'b') = " << sizeof(s + 'b') << endl;

    return 0;
}

普通型別與類型別之間能否進行型別轉換?
類型別之間能否進行型別轉換?

例 2 普通型別->類型別

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int mValue;
public:
    Test()
    {
        mValue = 0;
    }

    explicit Test(int i)
    {
        mValue = i;
    }

    Test operator + (const Test& p)
    {
        Test ret(mValue + p.mValue);

        return ret;
    }

    int value()
    {
        return mValue;
    }
};

int main()
{   
    Test t;


    t = static_cast<Test>(5);    // t = Test(5);



    Test r;

    r = t + static_cast<Test>(10);   // r = t + Test(10);

    cout << r.value() << endl;

    return 0;
}

建構函式可以定義不同型別的引數
引數滿足下列條件時稱為轉換建構函式
有且僅有一個引數
引數是基本型別
引數是其他類型別

舊式的 c 方式強制型別轉換
int i;
Test t;
i=int(1.5);
t=Test(100);

編譯器會盡力嘗試讓原始碼通過編譯
Test t;
t=100;
100 這個立即數預設為 int 型別,怎麼可能賦值給 t 物件呢!現在就報錯嗎?不急,我
看看有沒有建構函式!ok,發現 Test 類中定義了 Test(int i),可以進行轉換,預設等價於:
t=Test(100);

編譯器盡力嘗試的結果是隱式型別轉換
隱式型別轉換
會讓程式以意想不到的方式進行工作
是工程中 bug 的重要來源

工程中通過 explicit 關鍵字杜絕編譯器的轉換嘗試
轉換建構函式被 explicit 修飾時只能進行顯示轉換
轉換方式
static_cast(value);
ClassName(value);
(ClassName)value; //不推介

小結:
轉換建構函式只有一個引數
轉換建構函式的引數型別是其他型別
轉換建構函式在型別轉換時被呼叫
隱式型別轉換是工程中的 bug 的重要來源
explicit 關鍵字用於杜絕隱式型別轉換

第 42 課 型別轉換函式(下)

類型別是否能夠轉換到普通型別?

C++類中可以定義型別轉換函式
型別轉換函式用於將類物件轉換為其他型別
語法規則:
operator Type()
{
Type ret;
return ret;
}

例 1 型別轉換函式初探

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int mValue;
public:
    Test(int i = 0)
    {
        mValue = i;
    }
    int value()
    {
        return mValue;
    }
    operator int ()
    {
        return mValue;
    }
};

int main()
{   
    Test t(100);
    int i = t;

    cout << "t.value() = " << t.value() << endl;
    cout << "i = " << i << endl;

    return 0;
}

型別轉換函式
與轉換建構函式具有同等的地位
使得編譯器有能力將物件轉換為其他型別
編譯器能夠隱式的使用型別轉換函式

編譯器會經理嘗試讓原始碼通過
Test t(1);
int i=t;
t 這個物件為 Test 型別,怎麼可能初始化 int 型別的變數呢!現在就報錯嗎?不急,我看看
有沒有型別轉換函式!ok,發現 Test 類中定義了 operator int(),可以進行轉換。

類型別之間的相互轉換
型別轉換函式 vs 轉換建構函式

例 2 類型別之間的轉換

#include <iostream>
#include <string>

using namespace std;

class Test;

class Value
{
public:
    Value()
    {
    }
    explicit Value(Test& t)
    {
    }
};

class Test
{
    int mValue;
public:
    Test(int i = 0)
    {
        mValue = i;
    }
    int value()
    {
        return mValue;
    }
    operator Value()
    {
        Value ret;
        cout << "operator Value()" << endl;
        return ret;
    }
};

int main()
{   
    Test t(100);
    Value v = t;

    return 0;
}

無法抑制隱式的型別轉換函式呼叫
型別轉換函式可能與轉換建構函式衝突
工程中以 Type toType()的公有成員代替型別轉換函式

例 3 qttest

#include <QDebug>
#include <QString>

int main()
{
    QString str = "";
    int i = 0;
    double d = 0;
    short s = 0;

    str = "-255";
    i = str.toInt();
    d = str.toDouble();
    s = str.toShort();

    qDebug() << "i = " << i << endl;
    qDebug() << "d = " << d << endl;
    qDebug() << "s = " << s << endl;

    return 0;
}

小結:
c++類中可以定義型別轉換函式
型別轉換函式用於將類物件轉換為其他型別
型別轉換函式與轉換建構函式具有同等的地位
工程中以 Type toType()的公有成員代替型別轉換函式

第 43 課 繼承的概念和意義

類之間是否存在直接的關聯關係?

例 1 組合關係的描述

#include <iostream>
#include <string>

using namespace std;

class Memory
{
public:
    Memory()
    {
        cout << "Memory()" << endl;
    }
    ~Memory()
    {
        cout << "~Memory()" << endl;
    }
};

class Disk
{
public:
    Disk()
    {
        cout << "Disk()" << endl;
    }
    ~Disk()
    {
        cout << "~Disk()" << endl;
    }   
};

class CPU
{
public:
    CPU()
    {
        cout << "CPU()" << endl;
    }
    ~CPU()
    {
        cout << "~CPU()" << endl;
    }    
};

class MainBoard
{
public:
    MainBoard()
    {
        cout << "MainBoard()" << endl;
    }
    ~MainBoard()
    {
        cout << "~MainBoard()" << endl;
    }    
};

class Computer
{
    Memory mMem;
    Disk mDisk;
    CPU mCPU;
    MainBoard mMainBoard;
public:
    Computer()
    {
        cout << "Computer()" << endl;
    }
    void power()
    {
        cout << "power()" << endl;
    }
    void reset()
    {
        cout << "reset()" << endl;
    }
    ~Computer()
    {
        cout << "~Computer()" << endl;
    }
};

int main()
{   
    Computer c;

    return 0;
}

組合關係的特點
將其他類的物件作為當前類的成員使用
當前類的物件與成員物件的生命期相同
成員物件在用法上與普通物件完全一致

面向物件中的繼承指類之間的父子關係
子類擁有父類的所有屬性和行為
子類是一種特殊的父類
子類物件可以當作父類物件使用
子類中可以新增父類沒有的方法和屬性

C++中通過下面的方式描述繼承關係
class Parent
{
int mv;
public:
void method(){};
};
class Child:public Parent
{
};

例 2 繼承初體驗

#include <iostream>
#include <string>

using namespace std;

class Parent
{
    int mv;
public:
    Parent()
    {
        cout << "Parent()" << endl;
        mv = 100;
    }
    void method()
    {
        cout << "mv = " << mv << endl;
    }
};

class Child : public Parent
{
public:
    void hello()
    {
        cout << "I'm Child calss!" << endl;
    }
};

int main()
{   
    Child c;

    c.hello();
    c.method();

    return 0;
}

重要規則:
子類就是一個特殊的父類
子類物件可以直接初始化父類物件
子類物件可以直接賦值給父類物件

繼承是 C++中程式碼複用的重要手段。通過繼承,可以獲得父類的所有功能,並且在子類中重
寫已有功能,或者新增新功能。

例 3 繼承的強化練習

#include <iostream>
#include <string>

using namespace std;

class Memory
{
public:
    Memory()
    {
        cout << "Memory()" << endl;
    }
    ~Memory()
    {
        cout << "~Memory()" << endl;
    }
};

class Disk
{
public:
    Disk()
    {
        cout << "Disk()" << endl;
    }
    ~Disk()
    {
        cout << "~Disk()" << endl;
    }   
};

class CPU
{
public:
    CPU()
    {
        cout << "CPU()" << endl;
    }
    ~CPU()
    {
        cout << "~CPU()" << endl;
    }    
};

class MainBoard
{
public:
    MainBoard()
    {
        cout << "MainBoard()" << endl;
    }
    ~MainBoard()
    {
        cout << "~MainBoard()" << endl;
    }    
};

class Computer
{
    Memory mMem;
    Disk mDisk;
    CPU mCPU;
    MainBoard mMainBoard;
public:
    Computer()
    {
        cout << "Computer()" << endl;
    }
    void power()
    {
        cout << "power()" << endl;
    }
    void reset()
    {
        cout << "reset()" << endl;
    }
    ~Computer()
    {
        cout << "~Computer()" << endl;
    }
};

class HPBook : public Computer
{
    string mOS;
public:
    HPBook()
    {
        mOS = "Windows 8";
    }
    void install(string os)
    {
        mOS = os;
    }
    void OS()
    {
        cout << mOS << endl;
    }
};

class MacBook : public Computer
{
public:
    void OS()
    {
        cout << "Mac OS" << endl;
    }
};

int main()
{   
    HPBook hp;

    hp.power();
    hp.install("Ubuntu 16.04 LTS");
    hp.OS();

    cout << endl;

    MacBook mac;

    mac.OS();

    return 0;
}

小結:
繼承是面向物件中類之間的一種關係
子類擁有父類的所有屬性和行為
子類物件可以當作父類物件使用
子類可以新增父類沒有的方法和屬性
繼承是面向物件中程式碼複用的重要手段

第 44 課 繼承中的訪問級別
子類是否可以直接訪問父類的私有成員?

根據面向物件理論:
子類擁有父類的一切屬性和行為->子類能夠直接訪問父類的私有成員! ?
根據 c++語法:
外界不能直接訪問類的 private 成員->子類不能直接訪問父類的私有成員! ?

例 1 繼承中的訪問級別

#include <iostream>
#include <string>

using namespace std;

class Parent
{
private:
    int mv;
public:
    Parent()
    {
        mv = 100;
    }

    int value()
    {
        return mv;
    }
};

class Child : public Parent
{
public:
    int addValue(int v)
    {
        mv = mv + v;    // ???? 如何訪問父類的非公有成員
    }
};

int main()
{   
    return 0;
}

面向物件中的訪問級別不只是 public 和 private
可以定義 protected 訪問級別
關鍵字 protected 的意義
修飾的成員不能被外界直接訪問
修飾的成員可以被子類直接訪問

例 2 protected 初體驗

#include <iostream>
#include <string>

using namespace std;

class Parent
{
protected:
    int mv;
public:
    Parent()
    {
        mv = 100;
    }

    int value()
    {
        return mv;
    }
};

class Child : public Parent
{
public:
    int addValue(int v)
    {
        mv = mv + v;    
    }
};

int main()
{   
    Parent p;

    cout << "p.mv = " << p.value() << endl;

    // p.mv = 1000;    // error

    Child c;

    cout << "c.mv = " << c.value() << endl;

    c.addValue(50);

    cout << "c.mv = " << c.value() << endl;

    // c.mv = 10000;  // error

    return 0;
}

為什麼面向物件中需要 protected?

例 3 綜合例項

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

class Object
{
protected:
    string mName;
    string mInfo;
public:
    Object()
    {
        mName = "Object";
        mInfo = "";
    }
    string name()
    {
        return mName;
    }
    string info()
    {
        return mInfo;
    }
};

class Point : public Object
{

            
           

相關推薦

C++深度剖析

第 36 課 經典問題解析三 什麼時候需要過載賦值操作符? 編譯器是否提供預設的賦值操作? 編譯器為每個類預設過載了賦值操作符 預設的賦值操作符僅完成淺拷貝 當需要進行深拷貝時必須過載賦值操作符 賦值操作符與拷貝建構函式有相同的存在意義 例 1

C++智能指針剖析boost::shared_ptr&其他

剖析 smart_ptr mage open log gin 內部使用 聲明 虛基類 1. boost::shared_ptr 前面我已經講解了兩個比較簡單的智能指針,它們都有各自的優缺點。由於 boost::scoped_ptr 獨享所有權,當我們真真需要復制智能指針時,

實戰深度學習OpenCV庫

基本 numpy port 學習 test lin 庫文件 矩陣 價格 在上一節中,我們講到了OpenCV庫的安裝,現在我們來進行實戰,看如何利用Python來調用OpenCV庫。 一: 如果您的電腦是win10的系統,那麽請您按下win鍵,再按下空格鍵,輸入Pyth

研究生,請你拒絕C++的愛

 扯了這麼多,再次回到開始,C++有那麼重要麼? 是或者不是,這個答案並不重要,而我也不能正確地給出答案。如果真要說我只能刷刷奸猾,告訴大家“因人而異”。 重要的是,我們用什麼樣的思路,用什麼樣的態度來衡量、評價、解決這個問題。 下面一段又是個老生常談,隨便到哪個搜尋引擎上都能搜到一堆的話題:

OpenCv VS C++ 影象處理

繼續OpenCv的影象處理對於上一節的inRange得到兩幅影象等情況,可以使用addWeighted處理。(1).然後講形態學濾波#include<opencv2\core\core.hpp> #include<opencv2\highgui\highgu

SpringBoot2 | Spring AOP 原理原始碼深度剖析

微信公眾號:吉姆餐廳ak 學習更多原始碼知識,歡迎關注。 概述 AOP(Aspect-Oriented Programming) 面向切面程式設計。Spring Aop 在 Spring框架中的地位舉足輕重,具體優勢和場景就不介紹了,本篇

Spark核心原始碼深度剖析1 - Spark整體流程 和寬依賴和窄依賴

1 Spark 整體流程 2 寬依賴和窄依賴 2.1 窄依賴 Narrow Dependency,一個RDD對它的父RDD,只有簡單的一對一的依賴關係。即RDD的每個 partition僅僅依賴於父RDD中的一個 partition。父RDD和子RDD的

Annotation 深度剖析

自定義註解。   1.基本註解程式碼如下: package com.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.

Annotation 深度剖析

 一.  Annotation  介面(interface) 所有 annotation 型別 都要擴充套件的公共介面。   二. 列舉型別   1. ElementType  程式元素型別  列舉類&

OpenCv3 VS C++ 影象識別

總結一下:         cv::KeyPoint——關鍵點         cv::Feature2D——找到關鍵點或計算描述符的抽象類,如上一節的FastFeatureDetector即派生於Feature

一個Python開源項目-哈勃沙箱源碼剖析

!= 設置 交互 核數 output type 打包 ESS 機器學習 前言 在上一篇中,我們講解了哈勃沙箱的技術點,詳細分析了靜態檢測和動態檢測的流程。本篇接著對動態檢測的關鍵技術點進行分析,包括strace,sysdig,volatility。volatility的介紹

一個Python開源專案-哈勃沙箱原始碼剖析

前言 在上一篇中,我們講解了哈勃沙箱的技術點,詳細分析了靜態檢測和動態檢測的流程。本篇接著對動態檢測的關鍵技術點進行分析,包括strace,sysdig,volatility。volatility的介紹不會太深入,記憶體取證這部分的研究還需要繼續。 strace機制 上一篇講到了st

Android應用開發以及設計思想深度剖析1

本文內容,主題是透過應用程式來分析Android系統的設計原理與構架。我們先會簡單介紹一下Android裡的應用程式程式設計,然後以這些應用程 序在執行環境上的需求來分析出,為什麼我們的Android系統需要今天這樣的設計方案,這樣的設計會有怎樣的意義, Android究竟

BP神經網路原理分析及c++程式碼實現

為了方便廣大使用者的使用,本人將BP神經網路寫成了一個BPNNS類,這樣使用者們可以很方便的將此類潛入到自己的工程當中,此類的具體的使用規則,下面會介紹。 /*********************************************************

Activity生命週期的回撥,你應該知道得更多!--Android原始碼剖析

private void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.co

解析“60k”大佬的19道C#面試題

# 解析“60k”大佬的19道C#面試題(下) 在上篇中,我解析了前 `10` 道題目,本篇我將嘗試解析後面剩下的所有題目。 > 姐妹篇:[解析“60k”大佬的19道C#面試題(上)](https://www.cnblogs.com/sdflysha/p/20200325-19-csharp-intervie

【nodejs原理&原始碼賞析6深度剖析cluster模組原始碼與node.js多程序

目錄 一. 引言 二.server.listen方法 三.cluster._getServer( )方法 四.跨程序通訊工具方法Utils 五.act:queryServer訊息

C# 類型基礎

合成 托管 相加 返回 長度 參數類型 一個 con 重載 前面介紹了基本的類型,接下來我們講講類型的轉換 值類型的兩種表現形式:未裝箱和已裝箱 ,而引用類型總是處於裝箱形式 int count = 10; object obj = count; 裝箱:值類型

修羅場第二天:C#之面向對象基礎

dog 主函數 div 接口 對象 blank 返回值 情況 抽象 ------------接(上)http://www.cnblogs.com/HoloSherry/p/7100795.html   抽象類     抽象類也可以實現多態,使用關鍵字abstract。那麽什

關於C#數據的儲存

並且 不同 不同類 long 引用 style 通過 函數 一個數 概念補充: (1)從某個類型模板創建實際的對象,稱為實例化該類型。通過實例化類型而創建的對象被稱為類型的對象或類型的實例。C#程序中,每個數據項都是某種類型的實例。 (2)數據項是數據結構中討論的最小單