c++繼承,隱藏(重定義)
繼承是物件導向複用的重要手段,通過繼承定義一個類,繼承是型別之間的關係模型。共享公有的東西,實現各自本質的不同的東西。
一、三種繼承關係
- public:公有繼承
- protected:保護繼承
- private:私有繼承
- 實現一個簡單的繼承關係:
繼承是一種複用的手段,在繼承關係裡基類(父類)的成員都會成為派生類(子類)的成員,由此達到複用的目的
- 三種繼承關係下基類成員在派生類的訪問關係變化
總結:
- 父類中私有的成員,在子類中不可見
- 如果一些父類成員不想被子類物件直接訪問,但需要在子類中能訪問,就定義為保護成員,可以看出保護成員限定符是因為繼承才出現的
- public繼承是一個介面繼承,保持is-a原則(是一個),每個父類可用的成員對子類也可用,因為每一個子類物件也是一個父類物件
- protected/private 繼承是一個實現繼承,保持has-a的原則(有一個),父類的部分成員並未完全成為子類介面的一部分,所以非特殊情況不會使用這倆種繼承,在絕大多數場景下使用的都是公有繼承。
- 不管是哪種繼承方式,在子類內部都可以訪問父類的公有和保護成員,但是父類的私有成員子類中不可見
- 使用關鍵字class時,預設的繼承方式時private,使用struct時預設的繼承方式時public,不過最好寫出繼承方式
- 在實際應用時,一般都使用public繼承,極少情況下才使用protected/private繼承
二、繼承與轉換——賦值相容規則
- 子類物件可以賦值給父類物件(切片)
- 父類物件不能賦值給子類物件
- 父類的指標/引用可以指向子類物件
- 子類的引用/指標不能指向父類物件(可以通過強制型別轉換)
下證:子類物件能不能賦值給父類物件(可以)
父類物件能不能賦值給子類物件(不能)
class Person
{
public:
void Display()
{
cout << _name << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
int _num;
};
void Test()
{
Person p;
Student s;
p = s;
//s = p;
}
int main()
{
Test();
system("pause");
return 0;
}
子類物件能不能賦給子類的指標/引用? (可以)
class Person
{
public:
void Display()
{
cout << _name << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
int _num;
};
void Test()
{
Person p;
Student s;
Person* p1 = &s;//父類的指標
Person& r1 = s;//父類引用
}
int main()
{
Test();
system("pause");
return 0;
}
子類的指標/引用能不能指向父類的物件?
class Person
{
public:
void Display()
{
cout << _name << endl;
}
protected:
string _name;
};
class Student : public Person
{
public:
int _num;
};
void Test()
{
Person p;
Student s;
// Student* p1 = &p;
// Student& r1 = p;
Student* p2 = (Student*)&p;
Student& r2 = (Student&)p;
}
int main()
{
Test();
system("pause");
return 0;
}
子類繼承了父類,又定義了只屬於它自己的內容,所以子類的指標指向的空間肯定比父類的的大,如果將子類的指標指向父類,對齊進行操作時就會導致嚴重的後果(記憶體越界),所以不允許子類的指標/引用指向父類。但是我們可以強制型別轉化,但是這也存在著一定的風險,可能導致程式奔潰(因為強制型別轉換後,後面的內容本來不屬於父類,如果對其操作可能是越界處理,訪問記憶體越界,導致程式奔潰)
三、繼承體系中的作用域
- 在繼承體系中父類和子類都有獨立的作用域
- 子類和父類中有同名的成員,子類成員將遮蔽父類對成員直接訪問(在子類成員中,可以使用父類::父類成員訪問)——隱藏——重定義
- 注意在實際中在繼承體系裡最好不要定義同名成員
注意:這裡的隱藏,他是隻要名字一樣就會被隱藏。不管返回值,引數一樣或不一樣,並不影響。只要名字一樣,就會隱藏。
一個簡單的隱藏例子:
class person
{
public:
void f()
{
cout << "Person::f()" << endl;
}
void f1()
{
cout << "Person::f1()" << endl;
}
};
class student:public person
{
public:
void f1(int n) //構成隱藏
{
cout << "student::f1()" << endl;
}
};
void Test()
{
student s;
s.f1();
}
int main()
{
Test();
system("pause");
return 0;
}
子類中的f1()隱藏了父類中的f1(),所以呼叫時要按子類中的規則,加上引數
要呼叫父類中的:s.persin::f1();
隱藏(重定義)的一些坑:
class person
{
public:
void f()
{
cout << "Person::f()" << endl;
}
void f1()
{
cout << "Person::f1()" << endl;
}
};
class student:public person
{
public:
void f1()
{
cout << "student::f1()" << endl;
}
void f2()
{
cout << _stunum << endl;
}
void f3()
{
_stunum = 3;
}
void f4()
{
f1();
cout << "student::f4()" << endl;
}
private:
int _stunum;
};
場景一:
void Test()
{
student* p = NULL;
p->f1();
}
通過編譯,正常輸出;
場景二:
void Test()
{
student* p = NULL;
p->f2();
}
編譯通過,程式奔潰;
p->f2(),將p的地址作為第一個形參this指標,傳遞給函式,但是在訪問_stunum時要對其進行解引用。this指標為空,對空指標解引用,程式奔潰。
但是在訪問f1()時,我們雖然將p的地址作為實參傳遞給this指標,但是並沒有對其進行解引用操作, 所以程式沒有奔潰。
場景三:
void Test()
{
student* p = NULL;
p->f3();
}
編譯通過,程式奔潰;
void Test()
{
student* p = NULL;
p->f4();
}
編譯通過,程式正常執行
雖然呼叫了成員函式,但是並沒有進行解引用,所以程式不會奔潰
相關文章
- C++ protected繼承意義C++繼承
- C++ 成員函式的過載,繼承,覆蓋和隱藏C++函式繼承
- Spring Bean 定義繼承SpringBean繼承
- C++繼承一之公有繼承C++繼承
- C++繼承C++繼承
- c++中的隱藏及過載、重寫與隱藏的區別C++
- C++繼承詳解:共有(public)繼承,私有(private)繼承,保護(protected)繼承C++繼承
- C++ | 類繼承C++繼承
- C++菱形繼承C++繼承
- C++中公有繼承、保護繼承、私有繼承的區別C++繼承
- java -繼承 -重寫Java繼承
- python 基礎之繼承、重寫、多繼承Python繼承
- C++繼承體系C++繼承
- C++中的繼承C++繼承
- 重讀C++之一:封裝、繼承和多型C++封裝繼承多型
- [C++]繼承和派生類C++繼承
- C++虛繼承的概念C++繼承
- 說說C++多重繼承C++繼承
- c++ 虛繼承詳解C++繼承
- c++ 記憶體 繼承C++記憶體繼承
- C++ 整理15_繼承C++繼承
- 重學 JS 系列:聊聊繼承JS繼承
- 繼承 重寫和抽象類繼承抽象
- odoo 繼承(owl繼承、web繼承、view繼承)Odoo繼承WebView
- C++高階教程之繼承得本質:單繼承(一)C++繼承
- C++學習筆記——C++ 繼承C++筆記繼承
- 重寫,隱藏,抽象,多型抽象多型
- c++中的繼承關係C++繼承
- C++繼承:公有,私有,保護C++繼承
- C++多繼承的細節C++繼承
- 物件導向:類的定義和繼承的幾種方式物件繼承
- C++中過載、重寫、重定義的區別C++
- 虛擬繼承的意義 (轉)繼承
- 原型,繼承——原型繼承原型繼承
- 菱形繼承,虛繼承繼承
- C++的核心特性:繼承機制C++繼承
- 【c++基礎】菱形繼承問題C++繼承
- Effective C++:物件導向與繼承C++物件繼承