您的位置:首页 > 编程语言 > C语言/C++

c++三大特性之继承

2016-11-09 16:07 603 查看
1、概念:

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

一个新类从已有的类中获得其已有的特性称为继承,被继承的称为父类(Base class)或基类,新产生的类称为派生类或子类。

2、继承的方式:

继承权限规则表
继承方式
基类的public成员
基类的protected成员
基类的private成员
继承引起的访问控制变化
Public
Public
Protected
不可见
基类成员在派生类中访问权限不变
Protected
Protected
Protected
不可见
基类的非私有成员成为子类的保护成员
Private
Private
Private
不可见
基类的所有成员成为子类的私有成员

class Base
{
public:
Base()
{
_pri = 1;
_pro = 2;
_pub = 3;
}
private:
int _pri;
protected:
int _pro;
public:
int _pub;
};


class Derived1 :public Base
{
public:
Derived1()
{
_d1_pri = 4;
_d1_pro = 5;
_d1_pub = 6;
}
void showd()
{
_pub = 0;   // 仍为public成员
_pro = 2;   //  仍为protected成员
//_pri = 3; // 父类私有成员对子类 不可见

}
private:
int _d1_pri;
protected:
int _d1_pro;
public:
int _d1_pub;
};
void TestPublic()
{
Base b;
b._pub = 1;
//b._pro = 2;  // 父类对象不可访问父类保护成员
//b._pri = 3;  // 父类对象不可访问父类私有成员
Derived1 d1;
d1._pub = 1; // 子类对象可以访问父类公有成员
//d1._pro = 2; //子类对象不可访问父类保护成员
//d1._pri = 3; // 子类对象不可访问父类私有成员
d1._d1_pub = 4;
}


// protected 保护继承
class Derived2 :protected Base
{
public:
Derived2()
{
_d2_pri = 4;
_d2_pro = 5;
_d2_pub = 6;
}
void showd()
{
_pub = 1;   //  变为子类的protected成员
_pro = 2;   //  仍为protected成员
//_pri = 3; // 父类私有成员对子类不可见

}
private:
int _d2_pri;
protected:
int _d2_pro;
public:
int _d2_pub;
};
void TestProtected()
{
Base b;
b._pub = 1;
//b._pro = 2;  // 父类对象不可访问父类保护成员
//b._pri = 3;  // 父类对象不可访问父类私有成员
Derived2 d2;
//d2._pub = 1; // 父类的public成员权限被修改为protected,子类对象不可访问
//d2._pro = 2; //子类对象不可访问父类保护成员
//d2._pri = 3; // 子类对象不可访问父类私有成员
d2._d2_pub = 4;
}


// private私有继承
class Derived3 :private Base
{
public:
Derived3()
{
_d3_pri = 4;
_d3_pro = 5;
_d3_pub = 6;
}
void showd()
{
_pub = 1;   //  变为子类的privite成员
_pro = 2;   //  变为子类的privite成员
//_pri = 3; // 父类私有成员对子类不可见

}
private:
int _d3_pri;
protected:
int _d3_pro;
public:
int _d3_pub;
};
void TestPrivate()
{
Base b;
b._pub = 1;
//b._pro = 2;  // 父类对象不可访问父类保护成员
//b._pri = 3;  // 父类对象不可访问父类私有成员
Derived3 d3;
//d3._pub = 1; // 父类的public成员权限被修改为private,子类对象不可访问
//d3._pro = 2; // 父类的protected成员权限被修改为private,子类对象不可访问
//d3._pri = 3; // 子类对象不可访问父类私有成员
d3._d3_pub = 4;
}


3、继承又可以分为单继承,多继承,钻石继承(又叫菱形继承),现在来谈论下三种情况下派生类的布局:

(1)单继承:

#include<iostream>
using namespace std ;

class Base
{
public:
Base(int a= 0, int b= 1):_a(a),_b(b)
{
cout << "我是基类的构造函数" <<endl;
}
~Base()
{
cout << "我是基类的析构函数" << endl;
}
private:
int _a;
int _b;
};

class Derived:public Base
{
public:
Derived(int c= 2):_c(c)
{
cout << "我是派生类的构造函数" << endl;
}
~Derived()
{
cout << "我是派生类的析构函数" << endl;
}
private:
int _c;
};

int main()
{
Base b;
Derived d;

b = d; //可以
//d = b;报错

cout << "hello..." <<endl;
system("pause");
return 0;
}



为啥子类对象赋值给父类对象可以,父类对象赋值给子类对象不可以?

原因就是子类对象赋值给父类对象时,子类对象发生对象切片,将子类对象的父类部分赋值给父类对象,但是父类对象赋值给子类对象时,没有办法对子类自己的成员赋值,所以父类对象赋值给子类对象不可以。

(2)多继承

#include<iostream>
using namespace std ;

class Base1
{
public:
Base1(int a= 0, int b= 1):_a(a),_b(b)
{
cout << "我是基类的构造函数" <<endl;
}
~Base1()
{
cout << "我是基类的析构函数" << endl;
}
public: //将成员设置为public,好分析结果。
int _a;
int _b;
};

class Base2
{
public:
Base2(int c = 2, int d= 3):_c(c),_d(d)
{
cout << "我是第二个基类的构造函数"<< endl;
}
~Base2()
{
cout << "我是第二个基类的析构函数" << endl;
}
public: //将成员设置为public,好分析结果。
int _c;
int _d;
};

class Derived:public Base2,public Base1
{
public:
Derived(int e= 4):_e(e)
{
cout << "我是派生类的构造函数" << endl;
}
~Derived()
{
cout << "我是派生类的析构函数" << endl;
}
void Print()
{
cout << "a = "<< _a << ","<< "b = " << _b<< ","
<< "c = " << _c << ","<< "d = " << _d << ","<< "e = " << _e<< "," <<endl;
}
public: //将成员设置为public,好分析结果。
int _e;
};

void test()
{
Derived d;
d.Print();
}

int main()
{
test();

cout << "hello..." <<endl;
system("pause");
return 0;
}



从结果我们能分析,先调用base2的构造函数,在调用base1的构造函数,最后调用自己的构造函数,析构函数相反,这就说明先继承谁先调用谁的构造函数,子类的成员布局如下:



(3)  菱形继承:

#include<iostream>
using namespace std ;

class Base1
{
public:
Base1(int a= 0, int b= 1):_a(a),_b(b)
{
cout << "我是基类的构造函数" <<endl;
}
~Base1()
{
cout << "我是基类的析构函数" << endl;
}
public: //将成员设置为public,好分析结果。
int _a;
int _b;
};

class Derived1:public Base1
{
public:
Derived1(int c=2):_c(c)
{
cout << "我是Derived1的构造函数" << endl;
}
~Derived1()
{
cout << "我是Derived1的析构函数" << endl;
}
public:
int _c;

};

class Derived2:public Base1
{
public:
Derived2(int d=3):_d(d)
{
cout << "我是Derived2的构造函数" << endl;
}
~Derived2()
{
cout << "我是Derived2的析构函数" << endl;
}
public:
int _d;
};

class Derived :public Derived1, public Derived2
{
public:
Derived(int e=4):_e(e)
{
cout << "我是Derived的构造函数" << endl;
}
~Derived()
{
cout << "我是Derived的析构函数" << endl;
}
public:
int _e;
};

void test()
{
Derived d;
//d._a = 10; 访问不明确 ,造成这种原因是从base类继承两份base成员,导致访问不明确。
d.Derived1::_a = 12; //访问Derived1中的_a.
d.Derived2::_b = 2; //访问Derived2中的_a.
}

int main()
{
test();

cout << "hello..." <<endl;
system("pause");
return 0;
}



派生类Derived对象d成员布局如下:



从派生类对象成员布局可以看出,派生类对象继承了两份Base类成员,所以d._a访问不明确,因此虚继承的语法出现了。

#include<iostream>
using namespace std ;

class Base1
{
public:
Base1(int a= 0, int b= 1):_a(a),_b(b)
{
cout << "我是基类的构造函数" <<endl;
}
~Base1()
{
cout << "我是基类的析构函数" << endl;
}
public: //将成员设置为public,好分析结果。
int _a;
int _b;
};

class Derived1: virtual public Base1
{
public:
Derived1(int c=2):_c(c)
{
cout << "我是Derived1的构造函数" << endl;
}
~Derived1()
{
cout << "我是Derived1的析构函数" << endl;
}
public:
int _c;

};

class Derived2: virtual public Base1
{
public:
Derived2(int d=3):_d(d)
{
cout << "我是Derived2的构造函数" << endl;
}
~Derived2()
{
cout << "我是Derived2的析构函数" << endl;
}
public:
int _d;
};

class Derived :public Derived1, public Derived2
{
public:
Derived(int e=4):_e(e)
{
cout << "我是Derived的构造函数" << endl;
}
~Derived()
{
cout << "我是Derived的析构函数" << endl;
}
public:
int _e;
};

void test()
{
Derived d;
d._a = 10; //可以访问。

cout << "sizeof(d) = " << sizeof(d) << endl;

}

int main()
{
test();

cout << "hello..." <<endl;
system("pause");
return 0;
}



从结果分析我们能看出只是继承了一份基类,但是对象的大小变成了28,说明派生类中有8个字节是系统给我们自动加上的。



从图中我们可以看出确实只是继承了一份继承,但是基类的布局和原来的布局不一样,现在基类在最低下,Derived1中插入一个指针,指向一个表,表中存放了Deriverd1相对于基类的偏移量,Derived2也存放一个指针,同样指向一个表,这个表中同样存放了Derived2相对于基类的偏移量。从图中我们看出虚继承内存不一样的机制解决了变量的二义性问题。

4.继承综合案例:

#include<iostream>
using namespace std ;

namespace
{

class Base
{
public:
Base(int a):_a(a)
{
cout << "我是基类的构造函数" <<endl;
}
~Base()
{
cout << "我是基类的析构函数" << endl;
}
Base(const Base &B)
{
cout << "我是基类的拷贝构造函数" << endl;
_a = B._a;
}
Base& operator= (const Base & B)
{
_a = B._a;
return *this;
}

friend ostream& operator<< (ostream & out, const Base &B)
{
out << B._a ;
return out;
}

private:
int _a;
};

class Derived:public Base
{
public:
Derived(int a,int b):Base(a),_b(b) //当基类给出显示的构造函数时,子类必须要初始化基类对象。
{
cout << "我是派生类的构造函数"<< endl;
}

~Derived()
{
cout << "我是派生类的析构函数" << endl;
}

Derived(const Derived & d):Base(d) //先调用了基类的拷贝构造函数。
{
cout << "我是派生类的拷贝构造函数" << endl;
_b = d._b;
}
Derived& operator= (const Derived & d)
{
Base::operator=(d); //先调用基类的赋值运算符重载。
_b = d._b;
return *this;
}
friend ostream& operator<< (ostream & out ,const Derived &d)
{

out << "-------"<< (Base&)d; //先调用基类的重载函数。
out << "," << d._b ;
return out;
}

private:
int _b;
};

void test()
{
Derived d(1,2);
cout << d << endl;
Derived d2(d);
cout << d2 <<endl;
Derived d3(3,4);
d3 = d;
cout << d3 <<endl;

}

} //namespace结束。

int main()
{
test();

cout << "hello..." <<endl;
system("pause");
return 0;
}



5.继承注意点:

(1)父类的构造、析构、拷贝构造、重载赋值运算符,友元函数不能继承。

 (2)基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。

 (3)基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。

  (4)、基类定义了带有形参表构造函数,派生类就一定定义构造函数 (无参或者带参)

(5)继承时,先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数,析构时相反。

(6) public继承是is-a关系,private继承和组合对象是has-a关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 继承