轉載:https://blog.csdn.net/Sherlock_Homles/article/details/82927515
文章參考:https://blog.csdn.net/war1111886/article/details/8609957
一 .繼承中的訪問權限關系
1.基類,父類,超類是指被繼承的類,派生類,子類是指繼承於基類的類.
2.在C++中使用: 冒號表示繼承,如class A : public B;表示派生類A從基類B繼承而來
3.派生類包含基類的所有成員,而且還包括自已特有的成員,派生類和派生類對象訪問基類中的成員就像訪問自已的成員一樣,可以直接使用,不需加任何操作符,但派生類仍然無法訪問基類中的私有成員.
4.在C++中派生類可以同時從多個基類繼承,Java 不充許這種多重繼承,當繼承多個基類時,使用逗號將基類隔開.
5.基類訪問控制符
class A : public B 基類以公有方式被繼承,
class A:private B 基類以私有方式被繼承,
class A:protected B 基類以受保護方式被繼承,如果沒有訪問控制符則默認為私有繼承。
6. protected 受保護的訪問權限:使用protected 保護權限表明這個成員是私有的,但在派生類中可以訪問基類中的受保護成員。派生類的對象就不能訪問受保護的成員了。
7. 如果基類以public公有方式被繼承,則基類的所有公有成員都會成為派生類的公有成員.受保護的基類成員成為派生類的受保護成員
如果基類以private私有被繼承,則基類的所有公有成員都會成為派生類的私有成員.基類的受保護成員成為派生類的私有成員.
如果基類以protected 受保護方式被繼承,那么基類的所有公有和受保護成員都會變成派生類的受保護成員.
不管基類以何種方式被繼承,基類的私有成員,仍然保有其私有性,被派生的子類不能訪問基類的私有成員.
例:繼承中的訪問權限關系
-
class A {
-
int a;
-
protected:
-
int b;
-
public:
-
int c;
-
-
A() { a = b = c = 1; }
-
};
-
-
//類B以公有方式從基類A繼承
-
class B : public A {
-
public:
-
int d;
-
-
B() { //a=2; //錯誤,不能訪問基類中的私有成員
-
b = 2; //正確,可以在類中訪問基類中的受保護成員,但類的對象不能訪問,基類中的受保護成員b在類B中仍然是受保護成員
-
c = d = 2;
-
}
-
}; //基類中的公有成員c在類B中仍然是公有成員
-
-
//類C以受保護和私有方式從基類A繼承。
-
class C : protected A {
-
public:
-
int e;
-
-
C() { //a=3; //錯誤,不能訪問基類中的私有成員
-
b = c = e = 3;
-
}
-
}; //這里基類受保護成員b和公有成員c都成為類C中的受保護成員。
-
-
class D : private A {
-
public:
-
D()
-
{ b = c = 4; }
-
}; //基類中的公有和受保護成員都成為了類D中的私有成員。
-
-
//驗證受保護和私有方式繼承的訪問權限。
-
class C1 : public C {
-
public:
-
C1() { b = c = e = 4; }
-
}; //正確;類A中的成員b和c在類C中是以受保護方式被繼承的,b和c都成為了類C中的受保護成員。
-
-
class D1 : public D {
-
public:
-
D1() { //b=5; //錯誤,在A中受保護的成員b在類D中是以私有方式繼承的,這樣b就成為了類D中的私有成員,所以無法訪問。
-
//c=5; //錯誤,在A中公有的成員c在類D中是以私有方式繼承的,這樣c就成為了類D中的私有成員,所以無法訪問。
-
}
-
};
-
-
int main()
-
{
-
A m1;
-
B m2;
-
C m3;
-
D m4;
-
//cout<<m1.b<<m2.b<<m3.b<<m4.b<<endl; //錯誤;不能用類的對象訪問受保護的成員,只有在類中才能訪問。
-
cout << m1.c <<endl;
-
cout << m2.c <<endl;
-
//cout<<m3.c<<endl; //錯誤,類C是以受保護的方式從A繼承的,基類中的變量c在類C中就是受保護的,所以類的對象不能訪問
-
//cout<<m4.c<<endl; //錯誤,類C是以私有的方式從A繼承的,基類中的變量c在類C中就是私有的,所以類的對象不能訪
-
}
二.覆蓋和隱藏基類成員變量或成員函數
1. 基類的成員變量或函數被覆蓋:
如果派生類覆蓋了基類中的成員函數或成員變量,則當派生類的對象調用該函數或變量時是調用的派生類中的版本,當用基類對象調用該函數或變量時是調用的基類中的版本。
2. 隱藏基類成員函數的情況:
如果在派生類中定義了一個與基類同名的函數,不管這個函數的參數列表是不是與基類中的函數相同,則這個同名的函數就會把基類中的所有這個同名的函數的所有重載版本都隱藏了,這時並不是在派生類中重載基類的同名成員函數,而是隱藏,比如類A中有函數 f (int i , int j)和 f (int i)兩個版本,當在從A派生出的類B中定義了基類的 f() 函數版本時,這時基類中的 f (int i)和f (int i , int j)就被隱藏了,也就是說由類B創建的對象比如為m,不能直接訪問類A中的f(int i)版本,即使用語句m.f(2)時會發生錯誤。
3. 怎樣使用派生類的對象訪問基類中被派生類覆蓋或隱藏了的函數或變量:
3.1. 方法1 使用作用域運算符:: ,在使用對象調用基類中的函數或變量時使用作用域運算符即語句m.A::f(2),這時就能訪問基類中的函數或變量版本。注意,訪問基類中被派生類覆蓋了的成員變量只能用這種方法
3.2.方法2 使用using:: , 該方法只適用於被隱藏或覆蓋的基類函數,在派生類的類定義中使用語句using 把基類的字包含進來,比如using A::f;就是將基類中的函數f()的所有重載版本包含進來,重載版本被包含到子類之后,這些重載的函數版本就相當於是子類的一部分,這時就可以用派生類的對象直接調用被派生類隱藏了的基類版本,比如m.f(2),但是使用這種語句還是沒法調用基類在派生類中被覆蓋了的基類的函數,比如m.f()調用的是派生類中定義的函數f,要調用被覆蓋的基類中的版本要使用語句m.A::f()才行。
4. 在派生類的函數中調用基類中的成員變量和函數的方法:
就是在函數中使用的被派生類覆蓋的基類成員變量或函數前用作域解析符加上基類的類名,即A::f()就是在派生類的函數中調用基類中被派生類覆蓋了的函數f()的方法。
5. 派生類以私有方式被繼承時改變基類中的公有成員為公有的方法:
5.1.使用:: 作用域運算符,不提倡用這種方法,在派生類的public 后面用作用域運算符把基類的公有成員包含進來,這樣基類的成員就會成為派生類中的公有成員了,注意如果是函數的話后面不能加括號,如A::f;如果f是函數的話不能有括號。
5.2.使用using語句,現在一般用這種方法,也是在派生類的public使用using把基類成員包函進來,如using A::f。
例:隱藏或覆蓋基類中的成員,使用::作用域運算符訪問
-
class A {
-
int a;
-
protected:
-
int b;
-
public:
-
int c, d;
-
-
void f(int i) {
-
cout << "class A" << "\n";
-
}
-
-
A() { a = b = c = d = 1; }
-
};
-
-
class B : public A {
-
public:
-
int d;
-
//覆蓋基類中的成員變量d。
-
B() {
-
b = c = d = 2; //這里是給子類B中的成員變量d賦值,而不是基類中的d
-
A::d = 3;
-
} //給基類中被覆蓋的成員d賦值,注意在類中訪問的被覆蓋成員的方式。
-
void f() {
-
cout << "class B" << "\n"; //在子類中重定義基類中的同名函數,雖然參數列表不一樣,但同樣會隱藏基類中的同名函數
-
A::f( 1); //在函數中調用基類中被隱藏了的同名函數的方法,使用作用域解析運算符。
-
//f(1); //錯誤,因為基類中的函數被子類中的同名函數隱藏了,在這里子類不知道有一個帶參數的函數f。
-
}
-
};
-
int main() {
-
B m;
-
cout << m.d << "\n"; //輸出子類中的成員變量d的值,注意派生類中覆蓋了基類成員d.
-
cout << m.A::d << "\n"; //輸出基類中的成員變量d的值,注意這是使用對象訪問被覆蓋的基類成員的方式
-
m.f(); //調用子類中的不帶參數的函數f。
-
// m.f(2); //錯誤,因為基類中的帶一個參數的函數f被子類中的同名函數隱藏掉了,不能這樣訪問,須用作用域解析運算符來訪問。
-
m.A::f( 1);
-
} //使用子類對象訪問基類中被隱藏的函數的方法。
例:使用using 語句以便訪問基類中被隱藏的函數
-
class A {
-
int a;
-
protected:
-
int b;
-
public:
-
int c, d;
-
-
void f() { cout << "Amoren" << "\n"; }
-
-
void f(int i) { cout << "class A" << "\n"; }
-
-
A() { a = b = c = d = 1; }
-
};
-
-
class B : public A {
-
public:
-
int d;
-
//覆蓋基類中的成員變量d。
-
B() {
-
b = c = d = 2; //這里是給類B中的成員變量d賦值,而不是基類中的d
-
A::d = 3;
-
} //給基類中被覆蓋的成員d賦值,注意在類中訪問的被覆蓋成員的方式。
-
using A::f; //使用語句using把類A中的函數f包含進來,以便以后可以直接訪問基類被隱藏了的函數,注意函數f沒有括號
-
-
void f() {
-
cout << "class B" << "\n"; //在子類中覆蓋基類中的同名函數,注意這里是覆蓋,同時會隱藏基類中的其他同名重載函數
-
f( 1); //正確,因為使用了using語句,所以可以在類中直接使用基類中f函數的重載版本。
-
A::f( 2); //正確,雖然使用了using語句,但同樣可以按這種方法訪問基類中的函數。
-
A ma;
-
ma.f(); //正確,在子類中創建的基類對象,可以直接用對象名調用基類中被子類覆蓋或隱藏了的函數,因為這時不會出現二義性。
-
ma.f( 1); //正確,在子類中創建的基類對象,可以直接用對象名調用基類中被子類覆蓋或隱藏了的函數,因為這時不會出現二義性。
-
}
-
-
void g() {
-
cout << "this g" << "\n";
-
f(); //正確,但該語句訪問的是子類中的不帶參數函數f,雖然在類中使用了using語句,但直接調用被子類覆蓋了的基類函數時不能使用這種方法
-
A::f(); //正確,調用被子類覆蓋了的基類中的函數f,注意,雖然使用了using但要訪問被子類覆蓋了的函數,只能這樣訪問。
-
}
-
};
-
int main() {
-
B m;
-
m.f(); //調用子類中的不帶參數的函數,這里不會調用基類中的不帶參數的被覆蓋的函數f。
-
m.A::f(); //調用基類中被子類覆蓋了的函數f,雖然子類使用了using語句,但要訪問基類中被覆蓋的方法只能像這樣使用。
-
m.f( 1); //調用基類重載的f函數,注意這里可以不用::運算符,因為在子類中使用了using,只要子類沒有覆蓋基類中的方法,都可以這樣直接調用。
-
m.A::f( 2); //當然,使用了using后,也可以使用這種方法
-
}
例:派生類以私有方式被繼承時改變基類中的公有成員為公有的方法
-
class A {
-
public:
-
int a, b;
-
void f() { cout << "f" << "\n"; }
-
void g() { cout << "g" << "\n"; }
-
};
-
-
class B : private A {
-
public:
-
A::f; //使用::運算符使基類中的成員成為公有的。注意函數名后不能有括號。(C++11標准不允許,使用using A::f)
-
A::a; //(C++11標准不允許,使用using A::a)
-
using A::g; //使用using語句使基類中的成員函數g成為類B中的公有成員,注意函數名后不能有括號。
-
};
-
int main() {
-
B m;
-
//m.b=1; //錯誤,因為類B是以私有方式繼承的,類A中的成員在類B中是私有的,這里不能訪問私有成員。
-
m.f();
-
m.g();
-
m.a = 1;
-
}
三.繼承時的構造函數和析構函數問題
1. 在繼承中,基類的構造函數構建對象的基類部分,派生類的構造函數構建對象的派生類部分。
2. 當創建派生類對象時先用派生類的構造函數調用基類的構造函數構建基類,然后再執行派生類構造函數構造派生類。
即先構造基類再構造派生類的順序。執行析構函數的順序與此相反。
3. 調用基類帶參數的構造函數的方法:
在派生類的構造函數中使用初始化列表的形式就可以調用基類帶參數的構造函數初始化基類成員,如B():A(int i){},類B是類A的派生類。
4. 派生類的構造函數調用基類的構造函數的方法為:
4.1 如果派生類沒有顯示用初始化列表調用基類的構造函數時,這時就會用派生類的構造函數調用基類的默認構造
函數,構造完基類后,才會執行派生類的構造函數函數體,以保證先執行基類構造函數再執行派生類構造函數
的順序,如果基類沒有默認構造函數就會出錯。
4.2 如果派生類用 顯示的初始化列表調用基類的構造函數時,這時就會檢測派生類的初始化列表,當檢測到顯示調
用基類的構造函數時,就調用基類的構造函數構造基類,然后再構造派生類,以保證先執行基類構造函數再執
行派生類構造函數的順序,如果基類沒有定義派生類構造函數初始化列表調用的構造函數版本就會出錯。
5. 如果在基類中沒有定義默認構造函數,但定義了其他構造函數版本,這時派生類中定義了幾個構造函數的不同版本,
這時只要派生類有一個構造函數沒有顯示調用基類中定義的構造函數版本就會發生錯誤,因為編譯器會首先檢查派
生類構造函數調用基類構造函數的匹配情況,如果發現不匹配就會出錯,即使沒有創建任何類的對象都會出錯,而
不管這個派生類的對象有沒有調用派生類的這個構造函數。比如:基類有一個構造函數版本A(int i)而沒有定義默認
構造函數,派生類B,有這幾個版本的構造函數B():A(4){},B(int i):A(5){},再有語句B(int i, int j){}沒有顯示調用
基類定義的構造函數而是調用基類的默認構造函數,如果創建了B m和語句B m(1)時都會提示沒有可用的基類默認
構造函數可用的錯誤,雖然這時類B的對象m沒有調用派生類B的帶有兩個形參的構造函數,但同樣會出錯。
6. 同樣的道理,如果基類中定義了默認構造函數,卻沒有其他版本的構造函數,而這時派生類卻顯示調用了基類構造
函數的其他版本,這時就會出錯,不管你有沒有創建類的對象,因為編譯器會先在創建對象前就檢查構造函數的匹
配問題。
7. 派生類只能初始化他的直接基類。比如類C是類B的子類,而類B又是類A的子類,這時class C:public B{public:
B():A(){} };將會出錯,該語句試圖顯示調用類B的基類類A的構造函數,這時會出現類A不是類C的基類的錯誤。
8. 繼承中的復制構造函數和構造函數一樣,基類的復制構造函數復制基類部分,派生類的復制構造函數復制派生類部
分。
9.派生類復制構造函數調用基類復制構造函數的方法為:A(const A& m):B(m){}其中B是基類,A是派生類。
10.如果在派生類中定義了復制構造函數而沒有用初始化列表顯示調用基類的復制構造函數,這時不管基類是否定義了
復制構造函數,這時出現派生類對象的復制初始化情況時就將調用基類中的默認構造函數初始化基類的成員變量,
注意是默認構造函數不是默認復制構造函數,如果基類沒有默認構造函數就會出錯。也就是說派生類的復制構造函
數的默認隱藏形式是B(const B& j):A(){}這里B是A的派生類,也就是說如果不顯示用初始化列表形式調用基類的
復制構告函數時,默認情況下是用初始化列表的形式調用的是基類的默認構造函數。
11.當在派生類中定義了復制構造函數且顯示調用了基類的復制構造函數,而基類卻沒有定義基類的復制構造函數時,
這時出現派生類對象的復制初始化情況就將調用基類中的默認復制構造函數初始化基類部分,調用派生類的復制構
造函數初始化派生類部分,因為復制構造函數只有一種形式,即A(const A& m){},比如當出現調用時A(const A&
m):B(m){}如果這時基類B沒有定義復制構造函數,則該語句將會調用派生類A的默認復制構造函數。
12.如果基類定義了復制構造函數,而派生類沒有定義時,則會調用基類的復制構造函數初始化基類部分,調用派生類
的默認復制構造函數初始化派生類部分。