您的位置:首页 > 其它

菜鸟学习历程【30】继承与派生

2018-01-15 16:55 274 查看

继承与派生

类之间的关系:使用、包含、继承

使用:一个类使用了另一个类的对象,友元类、

包含:一个类中有另一个类的对象,例如,圆类 和 点类

继承:一个类是另一个类的特殊实例

继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有的基础上进行增补。

通常我们将新类称为派生类(子类),将被继承的类称为基类(父类);

语法:

class 派生类: 继承权限 基类
{
};


继承权限有三种:public、protected、private

继承方式\基类类型publicprotectedprivate
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateprivateprivateprivate
说明:

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


内存模型:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  继承与派生