繼承是面向?qū)ο蟮某绦蛟O(shè)計(jì)能夠?qū)崿F(xiàn)類的代碼復(fù)用的重要手段,它允許程序員在保留原有類的特性的基礎(chǔ)上進(jìn)行擴(kuò)展。
我們將共有的數(shù)據(jù)和方法提取到一個(gè)類中,這個(gè)類叫做父類/基類;
然后在此基礎(chǔ)上用需要擴(kuò)展的數(shù)據(jù)和方法產(chǎn)生新的類,并且繼承父類,這些類叫做子類/派生類。
代碼如下:
class Person
{public:
void print()
{cout<< "name: "<< _name<< endl;
cout<< "age: "<< _age<< endl;
}
protected:
string _name;
int _age;
};
class Student : public Person
{protected:
int _stuid;
};
class Teacher : public Person
{protected:
int _Jobid;
};
int main()
{Student s1;
Teacher t1;
s1.print();
t1.print();
return 0;
}
從以上代碼我們可以看出,父類是Person,Student和Teacher兩個(gè)類都繼承了Person,在創(chuàng)建對象后,都能夠通過對象訪問Person中的公有成員函數(shù)。
2.定義在上面的代碼Student類中,Student是子類,:后面的public是繼承方式,Person是父類。
繼承方式與訪問限定符一樣,都有三種:public、protected和private,繼承方式與訪問限定符之間的關(guān)系如下:
1.基類的private成員無論什么繼承方式都是不可見的;
不可見的意思是:隱身,在類作用域里面和類外面都不能訪問,例如:
class Person
{public:
void print()
{cout<< "name: "<< _name<< endl;
cout<< "age: "<< _age<< endl;
}
//protected:
private:
string _name;
int _age;
};
class Student : public Person
{public:
void print_stu()
{cout<< _name<< _age<< _stuid<< endl;
}
protected:
int _stuid;
};
Student以public繼承了Person類,但是Person類中的成員變量都是private的,即使繼承到Student類中,也不能夠訪問,而且在類的外面也不能訪問。
相對而言,protect和private的意思是,在類作用域里面可以訪問,在類外面不能訪問:
class Person
{public:
void print()
{cout<< "name: "<< _name<< endl;
cout<< "age: "<< _age<< endl;
}
protected:
//private:
string _name;
int _age;
};
class Student : public Person
{public:
void print_stu()
{cout<< _name<< _age<< _stuid<< endl;
}
protected:
int _stuid;
};
將Person中的成員變量限定改為protected,公有繼承到Student類中,就能在子類里訪問_name和_age了,但是在類外面依舊不能訪問。
2.實(shí)際上最常用的是以下的兩種繼承關(guān)系:
3.除了父類的私有成員在子類中都是不可見的,其他基類成員在子類中的訪問方式 == Min(成員在父類的訪問限定符, 繼承方式),public >protected >private;
4.使用關(guān)鍵字struct的默認(rèn)繼承方式是public,使用class的默認(rèn)繼承方式是private,最好顯式寫出繼承方式;
class Student1 : Person//默認(rèn)繼承方式是private
{protected:
int _stuid;
};
struct Student2 : Person//默認(rèn)繼承方式是public
{int _stuid;
};
注:struct的默認(rèn)訪問限定符是public,class的默認(rèn)訪問限定符是private;
5.private成員的意義:不想被子類繼承的成員,可以設(shè)計(jì)成私有;
如果父類中想給子類復(fù)用,但又不想直接暴露在外面的成員,就設(shè)計(jì)成protected;
1.在繼承中,父類和子類都有獨(dú)立的作用域;
2.若父類和子類中有同名的成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫做隱藏,也叫重定義。
若想訪問父類中的同名成員,可以在前面加上父類的作用域,如 Person::print();
class Person
{public:
void print()
{cout<< "name: "<< _name<< endl;
cout<< "age: "<< _age<< endl;
}
protected:
string _name;
int _age;
int _num = 111;
};
class Student : public Person
{public:
void print_stu()
{cout<< _num<< endl;
cout }
protected:
int _stuid;
int _num = 999;
};
int main()
{Student s1;
s1.print_stu();
return 0;
}
上述代碼的運(yùn)行結(jié)果是:
第一行的結(jié)果999就是由于子類和父類有同名成員,父類中的就被隱藏了,默認(rèn)訪問的是子類的_num;
第二行的結(jié)果111就是在_num前加了Person的作用域限定,訪問的就是父類中的成員了。
3.如果是同名的成員函數(shù),只需要函數(shù)名相同就構(gòu)成隱藏,參數(shù)不相同也是隱藏,這里需要和函數(shù)的重載進(jìn)行區(qū)分;
函數(shù)的隱藏是有繼承關(guān)系的父類和子類中有同名的成員函數(shù),這兩個(gè)函數(shù)有不同的作用域,且只需函數(shù)名相同;
函數(shù)的重載是在同一作用域下,兩個(gè)同名且參數(shù)不一樣的函數(shù),才構(gòu)成重載;
1.派生類對象可以賦值給基類的對象、基類的指針、基類的引用,是將派生類中基類的那部分切片后,再賦值過去;
2.基類對象不可賦值給派生類對象;
3.基類的指針或引用可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針或引用;
int main()
{Student s1;
Teacher t1;
Person p1 = s1;
Person* pp = &s1;
Person& rp = s1;
Student* ps = (Student*)pp;//父類指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給子類指針
return 0;
}
注:父類的指針指向的是子類中切片的父類的那部分;
例題:以下代碼中p1,p2,p3的關(guān)系是:
創(chuàng)建了一個(gè)子類對象d,父類Base1的指針p1指向d,父類Base2的指針p2指向d,p3是子類Derive的指針;
d對象中包含繼承的父類對象Base1和Base2還有_d,p1指向的是d中切片的Base1的那部分,p2指向的是d中切片的Base2的那部分,所以p1 != p2;
p3指向的是子類對象d,指向的是最開始的部分,因此p3 == p1;
正確答案:C
注:
先繼承的類放在子類對象的前面部分;
由于棧是從高地址向低地址增長的,而對象是按照一個(gè)整體去創(chuàng)建的,因此子類對象的位置如上圖所示,類對象在棧里面是倒著放的,因此p2 >p1;
子類默認(rèn)生成的構(gòu)造函數(shù)會(huì)完成如下操作:
1.對于自己特有的成員,內(nèi)置類型不做處理,自定義類型調(diào)用其構(gòu)造函數(shù);
2.對于繼承父類的成員,必須調(diào)用父類的構(gòu)造函數(shù)初始化。
如果要顯式寫構(gòu)造函數(shù),代碼如下:
class Person
{public:
Person(const char* name)
: _name(name)
{cout<< "Person(const char* name)"<< endl;
}
protected:
string _name;
};
class Student : public Person
{public:
Student(const char* name = "Peter", int id = 1000000)
: _name(name)
, _stuid(id)
{cout<< "Student(const char* name, int id)"<< endl;
}
protected:
int _stuid;
};
int main()
{Student s1;
return 0;
}
當(dāng)子類用初始化列表初始化父類的成員時(shí),上面的寫法會(huì)報(bào)錯(cuò):
這是因?yàn)樽宇愂菍⒏割惪醋饕粋€(gè)整體去初始化的,父類成員必須調(diào)用父類的構(gòu)造函數(shù),不能再子類中單獨(dú)初始化;
正確方式入下:
class Student : public Person
{public:
Student(const char* name, int id)
: Person(name) //注意這種特殊寫法!
, _stuid(id)
{cout<< "Student(const char* name, int id)"<< endl;
}
這里類似于一個(gè)匿名對象,但不是,注意這種特殊寫法!
2.默認(rèn)析構(gòu)函數(shù)子類默認(rèn)生成的析構(gòu)函數(shù)與默認(rèn)構(gòu)造函數(shù)類似:
1.對于自己特有的成員,內(nèi)置類型不做處理,自定義類型調(diào)用其析構(gòu)函數(shù);
2.對于繼承父類的成員,必須調(diào)用父類的析構(gòu)函數(shù)。
如果要顯式寫析構(gòu)函數(shù),代碼如下:
class Person
{public:
~Person()
{cout<< "~Person()"<< endl;
}
protected:
string _name;
};
class Student : public Person
{public:
~Student()
{~Person(); //在子類的析構(gòu)中調(diào)用父類的析構(gòu)
}
protected:
int _stuid;
};
上面這種寫法時(shí)會(huì)報(bào)錯(cuò)的,因?yàn)橛捎诙鄳B(tài)的需要,析構(gòu)函數(shù)的名字會(huì)被統(tǒng)一處理成destructor(),子類的析構(gòu)和父類的析構(gòu)構(gòu)成了隱藏,只要加上作用域就行了:
~Student()
{Person::~Person();
}
如果子類的析構(gòu)中顯式調(diào)用了父類的析構(gòu)(如上面代碼所示),那么會(huì)出現(xiàn)以下情況:
父類的析構(gòu)被調(diào)用了兩次,重復(fù)析構(gòu)了,造成這種現(xiàn)象的原因是:
每個(gè)子類的析構(gòu)函數(shù)后面,都會(huì)默認(rèn)調(diào)用父類的析構(gòu)函數(shù),這樣才能保證先析構(gòu)子類,再析構(gòu)父類,所以子類的析構(gòu)函數(shù)中不去顯式調(diào)用父類的析構(gòu)函數(shù)才是正確的。
~Student()
{cout<< "~Student()"<< endl;
}
注:子類構(gòu)造時(shí),先構(gòu)造父類,再構(gòu)造子類的成員;
子類析構(gòu)時(shí),先析構(gòu)子類成員,再析構(gòu)父類。
子類默認(rèn)拷貝構(gòu)造會(huì)進(jìn)行如下操作:
1.對于自己特有的成員,內(nèi)置類型會(huì)進(jìn)行淺拷貝,自定義類型會(huì)調(diào)用其拷貝構(gòu)造;
2.對于繼承的父類成員,必須調(diào)用父類的拷貝構(gòu)造。
如果子類中顯式寫拷貝構(gòu)造,代碼如下:
class Person
{public:
Person(const Person& p)
: _name(p._name)
{cout<< "Person(const Person& p)"<< endl;
}
protected:
string _name;
};
class Student : public Person
{public:
Student(const Student& s)
: Person(s) //運(yùn)用了切片的原理
, _stuid(s._stuid)
{cout<< "Student(const Student& s)"<< endl;
}
protected:
int _stuid;
};
在子類的拷貝構(gòu)造中,沒法將子類中父類的成員直接拿出來進(jìn)行賦值,這時(shí)就將子類直接賦值給父類,構(gòu)成賦值切片。
4.默認(rèn)賦值重載函數(shù)編譯器默認(rèn)生成的子類賦值重載函數(shù)進(jìn)行的操作如下:
1.對于自己特有的成員,內(nèi)置類型會(huì)進(jìn)行淺拷貝,自定義類型會(huì)調(diào)用其賦值重載;
2.對于繼承的父類成員,必須調(diào)用父類的賦值重載。
如果子類顯式寫賦值重載,代碼如下:
class Person
{public:
Person& operator=(const Person& p)
{if (this != &p)
{ _name = p._name;
}
return *this;
}
protected:
string _name;
};
class Student : public Person
{public:
Student& operator=(const Student& s)
{if (this != &s)
{ operator=(s); //父類的成員調(diào)用父類的賦值重載
_stuid = s._stuid;
}
return *this;
}
protected:
int _stuid;
};
上面的寫法是會(huì)報(bào)錯(cuò)的,因?yàn)樽宇惖膐perator=()和繼承父類的operator=()構(gòu)成了隱藏,這時(shí),只要指定了父類的作用域就可以了:
Student& operator=(const Student& s)
{if (this != &s)
{ Person::operator=(s); //父類的成員調(diào)用父類的賦值重載
_stuid = s._stuid;
}
return *this;
}
五、繼承與友元友元關(guān)系不能繼承?。?!
也就是說,父類的友元函數(shù)不能訪問子類的私有和保護(hù)成員。
父類定義了一個(gè)static靜態(tài)成員,那么整個(gè)繼承體系中就只有這一個(gè)成員,無論派生出多少子類,都只有一個(gè)static成員的實(shí)例。
七、多繼承、菱形繼承與虛擬繼承 1.單繼承:一個(gè)子類只有一個(gè)父類2.多繼承:一個(gè)子類有兩個(gè)或以上直接父類3.菱形繼承:
代碼如下:
class Person
{protected:
string _name;
};
class Student : public Person
{protected:
int _stuid;
};
class Teacher : public Person
{protected:
int _Jobid;
};
class Assistant :public Student, public Teacher
{protected:
string _majorCourse;
};
菱形繼承是由多繼承引發(fā)的一個(gè)問題:
從上圖可以看出,菱形繼承有數(shù)據(jù)冗余和二義性的問題,在Assistant對象中Person成員會(huì)有兩份,而且在Assistant對象訪問Person的成員時(shí)們也會(huì)出現(xiàn)二義性問題,無法明確知道訪問的是哪一個(gè)成員。
int main()
{Assistant a1;
a1._name = "Jack"; //編譯器無法知道訪問的是那個(gè)父類的_name
a1.Student::_name = "Jack"; //可以通過指定作用域來解決二義性問題,但是數(shù)據(jù)冗余問題無法解決
a1.Teacher::_name = "Bob";
return 0;
}
4.虛擬繼承虛擬繼承可以解決菱形繼承的數(shù)據(jù)冗余問題和二義性問題,在Student和Teacher繼承Person時(shí),使用虛擬繼承,代碼如下:
class Person
{protected:
string _name;
};
class Student : virtual public Person //虛擬繼承
{protected:
int _stuid;
};
class Teacher : virtual public Person
{protected:
int _Jobid;
};
class Assistant :public Student, public Teacher
{protected:
string _majorCourse;
};
這樣在實(shí)例化Assistant對象時(shí),對象中只會(huì)有一組Person的成員,不會(huì)出現(xiàn)數(shù)據(jù)冗余和二義性問題。
5.虛擬繼承解決數(shù)據(jù)冗余和二義性問題的原理給出一個(gè)簡化的菱形繼承,代碼如下:
class A
{public:
int _a;
};
class B : public A
//class B : virtual public A
{public:
int _b;
};
class C : public A
//class C : virtual public A
{public:
int _c;
};
class D : public B, public C
{public:
int _d;
};
int main()
{D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
下圖是菱形繼承的對象成員模型,可以看到有數(shù)據(jù)冗余,A的成員有兩份:
如果將B和C對A的繼承改成虛擬繼承,其對象成員模型如下:
不存在數(shù)據(jù)冗余了,A的成員只有一份,B和C中原本存放A成員的地方,現(xiàn)在存放了一個(gè)地址,叫做虛基表指針,指向的就是虛基表,虛基表中存放的是當(dāng)前對象與虛繼承的父類對象之間的偏移量,通過偏移量來找到A。
在普通繼承中不要使用虛繼承,因?yàn)闀?huì)開辟虛基表,造成空間浪費(fèi)。
八、如何定義一個(gè)不能被繼承的類在類名后加一個(gè)關(guān)鍵詞:final,表示當(dāng)前類為最終類,無法被繼承。代碼如下:
class A final
{public:
int _a;
};
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧