菜鸟学习历程【30】继承与派生
2018-01-15 16:55
274 查看
继承与派生
类之间的关系:使用、包含、继承使用:一个类使用了另一个类的对象,友元类、
包含:一个类中有另一个类的对象,例如,圆类 和 点类
继承:一个类是另一个类的特殊实例
继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有的基础上进行增补。
通常我们将新类称为派生类(子类),将被继承的类称为基类(父类);
语法:
class 派生类: 继承权限 基类 { };
继承权限有三种:public、protected、private
继承方式\基类类型 | public | protected | private |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
1.派生类中的 public 属性,在派生类的内部与外部都可以访问;
2.派生类中的 protected 属性,在派生类的内部可以访问,在外部无法访问;
3.派生类初次以 private 继承时,基类的public 和 protected 虽然都变成了 private,但依旧可以在类的内部进行访问,在类的外部不可访问。如果派生类又作为另一个类的基类,无论是以什么样的权限继承,都无法进行访问!
4.基类中的 private 成员无论以什么样的权限继承,都无法使用,但并不能认为没有从基类中继承过来。
举个动物类的例子,形象的说明:
#include <iostream> using namespace std; class Animal() { public: //基类有四个公有函数,init,sleep,eat,print; void init(int age, char *name) { this->age = age; this->name = name; } void eat() { cout << "动物: " << name << "在吃饭" << endl; } void sleep() { cout << "动物: " << name << "在睡觉" << endl; } void print() { printf("age = %d , name = %s\n", age, name); } private: int age; char *name; }; //公有继承时,基类中的四个函数都可被派生类对象直接调用 //派生类也可以实现自己的功能,例如,猫 捉老鼠 class Cat: public Animal { public: void CatchMouse() { printf("猫 在捉老鼠\n"); } }; class Dog: public Animal { public: void LookHouse() { printf("狗 在看家\n"); } }; int main() { Animal a; a.init(10, "动物"); a.sleep(); a.eat(); a.print(); Cat c; c.init(12, "蓝猫"); c.sleep(); c.eat(); c.print(); c.CatchMouse(); Dog d; d.init(13, "哈士奇"); d.sleep(); d.eat(); d.print(); d.LookHouse(); return 0; } //这个例子中,我们称 Aminal 为基类,Cat 和 Dog 为派生类
类型兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。首先我们定义两个类:Parent 和 Child 类,Child 公有继承 Parent
//基类有两个私有成员 a 和 b,两个公有函数 setAB、printAB class Parent { public: void setAB(int a, int b) { this->a = a; this->b = b; } void printAB() const { cout << "a = " << a << ", b = " << b << endl; } private: int a; int b; }; //派生类有自己的成员 c,自己的函数 setC、printC class Child :public Parent { public: void setC(int c) { this->c = c; } void printC() { cout << "c = " << c << endl; } private: int c; };
1.派生类对象可当做基类对象直接使用
理由:派生类对象只是在基类对象的基础上,添加自己特有的成员
int main() { Child p p.setAB(1, 2); p.printAB(); //a = 1, b = 2 return 0; }
2.基类指针可以指向派生类对象
int main() { Child *c = new Child; c->setAB(1, 2); c->SetC(3); Parent *p; p = c; p->printAB(); // a = 1, b = 2 4000 //基类的指针指向派生类的对象,但基类指针只可使用基类中有的方法和成员,派生类中特有的,无法访问 //p->printC(); return 0; }
3.基类引用可以直接引用派生类对象
原因:引用的本质 ==> 指针
void test(Parent *p) { p->printAB(); // printAB(p) } void test(const Parent &p) { p.printAB(); // printAB(p) } int main() { Parent p; p.setAB(1,2); // test(&p); test(p); Child c; c.setAB(5,6); c.setC(9); // test(&c); test(c); //调用函数时,&p = c ⇒ Parent *const p = &c Parent &p1 = c; p1.setAB(7, 8); p1.printAB(); // a = 7, b = 8 return 0; }
4.派生类对象可直接初始化基类对象
int main() { Child c; c.setAB(1, 2); c.setC(3); //派生类对象是一种特殊的基类对象,因此在初始化时,会发生拷贝构造 Parent p = c; //Parent(c) ⇒ Parent(const Parent &obj) ⇒ Parent(&c) //Parent(&c) ⇒ Parent(const Parent *const obj) return 0; }
5.派生类对象可直接对基类对象赋值
int main() { Child c; c.setAB(1, 2); c.setC(3); Parent p; //Parent &operator=(const Parent &obj) p = c; return 0; }
继承中的构造和析构
派生类的组成:基类继承过来的成员 + 派生类自己定义的原则:谁的成员,谁去初始化
对于基类的成员,在对象初始化列表中,显示调用基类的构造函数
构造函数执行的顺序:1.基类的构造函数、2.组合对象的构造函数、3.派生类自己的构造函数
析构函数执行的顺序与 构造函数 相反。
#include <iostream> using namespace std; class Obj { public: Obj(int a, int b) { this->a = a; this->b = b; cout << "调用了Obj的构造函数" << endl; } ~Obj() { cout << "调用了Obj的析构函数" << endl; } private: int a; int b; }; class Parent { public: Parent(int a, int b) { this->a = a; this->b = b; cout << "调用了Parent的构造函数" << endl; } ~Parent() { cout << "调用了Parent的析构函数" << endl; } private: int a; int b; }; class Child:public Parent { public: Child(int c) :Parent(1, 2),o1(2,3),o2(3,4) { this->c = c; cout << "调用了Child的构造函数" << endl; } ~Child() { cout << "调用了Child的析构函数" << endl; } private: int c; Obj o1; Obj o2; }; int main() { Child c(1); return 0; } 执行结果如下: 调用了Parent的构造函数 调用了Obj的构造函数 调用了Obj的构造函数 调用了Child的构造函数 调用了Child的析构函数 调用了Obj的析构函数 调用了Obj的析构函数 调用了Parent的析构函数
继承中的同名成员和同名函数
当基类成员变量(函数)和派生类成员变量(函数)重名时,默认使用派生类自己的成员(函数),如若想使用基类中的,使用域解析符 :: 即可。#include <iostream> using namespace std; class AA { public: void print() { printf("A 的成员函数\n"); } public: int a; int b; }; class BB: public AA { public: void print() { printf("B 的成员函数\n"); } public: int a; int c; }; int main() { BB b; b.b = 2; b.c = 3; b.a = 1; //默认使用派生类的成员 b.AA::a = 4; //使用域解析符,指明所属的类; b.print(); //B 的成员函数 b.AA::print(); //A 的成员函数 return 0; }
继承中的重载问题
重载只能发生在一个类之中, 派生类不能重载基类的同名函数当派生类重载基类的函数的时候,会将基类所有同名函数全屏蔽掉,不能再使用
重定义:派生类有和父类 成员函数原型一样的函数,叫函数重定义
#include <iostream> using namespace std; class Parent { public: void print() { printf("a = %d, b = %d\n", a, b); } void func() { printf("基类: 无参\n"); } void func(int a) { printf("基类: 有一个参数\n"); } public: int a; int b; }; class Child : public Parent { public: // 派生类的同名成员函数会屏蔽基类的同名成员函数 // 函数原型一样, 叫函数重定义 void print() { printf("a = %d, c = %d\n", a, c); } void func(int a, int b) { printf("派生类: 有两个参数\n"); } public: int a; int c; }; int main() { Child c; c.Parent::a = 1; c.b = 2; c.a = 3; c.c = 4; // 默认使用派生类的函数 c.print(); c.Parent::print(); c.Parent::func(); //c.func(10); c.func(1, 2); return 0; }
派生类中static关键字使用
#include <iostream> using namespace std; class A { public: int a; static int sa; }; int A::sa = 10; class B :public A { public: int b; }; class C:public A { public: int c; }; // 类的静态变量是所有派生类所共享的 // 静态变量存放在数据区,测算类的大小时,不包括静态变量 int main() { cout << "sizeof B : " << sizeof B << endl; // sizeof B : 8 B b; b.sa = 123; cout << "C::sa = " << C::sa << endl; // C::sa = 123 return 0; }
多继承
当一个派生类有多个基类时,构造函数的执行顺序与继承时的声明顺序有关。析构函数的执行顺序与 构造函数的执行顺序 相反
#include <iostream> using namespace std; class AA { public: AA(int a, int b) { this->a = a; this->b = b; printf("AA 的构造函数被调用\n"); } ~AA() { printf("AA 的析构函数被调用\n"); } void printa() { printf("a = %d, b = %d\n", a, b); } private: int a; int b; }; class BB { public: BB(int a, int b) { this->c = a; this->d = b; printf("BB 的构造函数被调用\n"); } ~BB() { printf("BB 的析构函数被调用\n"); } void printb() { printf("c = %d, d = %d\n", c, d); } private: int c; int d; }; class CC :public AA, public BB { public: CC(int a) :AA(1, 2), BB(3, 4) { this->e = a; printf("CC 的构造函数被调用\n"); } ~CC() { printf("CC 的析构函数被调用\n"); } void printc() { printf("e = %d\n", e); } private: int e; }; void func1(AA &obj) { obj.printa(); } void func2(BB &obj) { obj.printb(); } int main() { CC c(5); // 基类指针指向派生类对象的时候,指针会根据基类数据成员在派生类中的 // 存储位置不同,做不同的偏移 func1(c); func2(c); AA *p = &c; BB *p1 = &c; printf("AA %p\n", p); printf("BB %p\n", p1); printf("&c %p\n", &c); return 0; } 执行结果如下: AA 的构造函数被调用 BB 的构造函数被调用 CC 的构造函数被调用 a = 1, b = 2 c = 3, d = 4 AA 0115FDC0 BB 0115FDC8 &c 0115FDC0 CC 的析构函数被调用 BB 的析构函数被调用 AA 的析构函数被调用
内存模型:
多继承的二义性
1.普通继承时#include <iostream> using namespace std; class Parent1 { public: Parent1(int a, int b) { this->a = a; this->b = b; cout << "Parent1 构造函数被调用" << endl; } void print1() { printf("a = %d, b = %d\n", a, b); } public: int a; int b; }; class Parent2 { public: Parent2(int a, int d) { this->a = a; this->d = d; cout << "Parent2 构造函数被调用" << endl; } void print2() { printf("c = %d, d = %d\n", a, d); } public: int a; int d; }; class C :public Parent1, public Parent2 { public: C(int e) :Parent1(1, 2), Parent2(3, 4) { this->e = e; cout << "C 构造函数被调用" << endl; } void printc() { printf("e = %d\n", e); } private: int e; }; int main() { C c(10); printf("sizeof c = %d\n", sizeof(c)); c.Parent1::a = 10; c.Parent2::a = 12; //c.a = 13; 编译器并不知道该调用哪一个基类的 a c.print1(); c.print2(); c.printc(); return 0; } 运行结果如下: Parent1 构造函数被调用 Parent2 构造函数被调用 C 构造函数被调用 sizeof c = 20 a = 10, b = 2 c = 12, d = 4 e = 10
内存模型:
2.虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性,要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
这里举个钻石型结构的例子
#include <iostream> using namespace std; class Parent { public: Parent(int a = 0) { this->a = a; cout << "Parent 构造函数被调用" << endl; } public: int a; }; class Parent1 :virtual public Parent { public: Parent1(int b) :Parent(1) { this->b = b; cout << "Parent1 构造函数被调用" << endl; } void print1() { printf("b = %d\n", b); cout << "Parent1 构造函数被调用" << endl; } public: int b; }; class Parent2 :virtual public Parent { public: Parent2(int b) :Parent(3) { this->d = b; cout << "Parent2 构造函数被调用" << endl; } void print2() { printf("d = %d\n", d); } public: int d; }; class C :public Parent1, public Parent2 { public: C(int e) :Parent1(2), Parent2(4) { this->e = e; cout << "C 构造函数被调用" << endl; } void printc() { printf("e = %d\n", e); } private: int e; }; int main() { C c(10); printf("sizeof c = %d\n", sizeof(c)); c.Parent1::a = 11; printf("a = %d\n", c.a); c.Parent2::a = 12; printf("a = %d\n", c.a); c.a = 13; printf("a = %d\n", c.a); return 0; } 运行结果: Parent 构造函数被调用 Parent1 构造函数被调用 Parent2 构造函数被调用 C 构造函数被调用 sizeof c = 24 a = 11 a = 12 a = 13
内存模型:
相关文章推荐
- 【C++学习历程6】继承和派生
- asp.net2.0学习历程 菜鸟到中级程序员的飞跃
- asp.net2.0学习历程 菜鸟到中级程序员的飞跃【月儿原创】
- 面向对象与C++程序设计-类的继承与派生学习笔记
- 菜鸟学习历程【16】文件编程
- .Asp.Net 2.0 学习历程 菜鸟到中级程序员的飞跃
- Java菜鸟学习笔记--面向对象篇(十四):继承与组合
- asp.net2.0学习历程 菜鸟到中级程序员的飞越
- asp.net2.0学习历程 菜鸟到中级程序员的飞跃
- 菜鸟学习历程【4】数组(2)、函数
- asp.net2.0学习历程 菜鸟到中级程序员的飞跃
- C++ 学习(类的继承,派生)
- 第十一章 继承与派生 学习笔记
- asp.net2.0学习历程 菜鸟到中级程序员的飞跃 转自清清月儿
- C++学习摘要之三:继承和派生 .
- C++学习day20之继承与派生1
- 【C++的探索路13】继承与派生之练习篇(需重新学习)
- C++学习之路—继承与派生(二):派生类的构造函数与析构函数
- Android学习历程-从菜鸟开始
- java学习从菜鸟到笨鸟之二——关于类的继承