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)數據項是數據結構中討論的最小單