继承与派生
类之间的关系:使用、包含、继承
使用:一个类使用了另一个类的对象,友元类、
包含:一个类中有另一个类的对象,例如,圆类 和 点类
继承:一个类是另一个类的特殊实例
继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有的基础上进行增补。
通常我们将新类称为派生类(子类),将被继承的类称为基类(父类);
语法:
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
//基类的指针指向派生类的对象,但基类指针只可使用基类中有的方法和成员,派生类中特有的,无法访问
//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
内存模型: