C++-继承(重载 重写 重定义、菱形继承...)

1、继承方式
  1. 类的一个特征就是封装,public和private作用就是实现这一目的。所以:
    类外可以访问public成员而不能访问private成员;private成员只能由类内和友元访问。

  2. 类的另一个特征就是继承,protected的作用就是实现这一目的。所以:
    protected成员可以被派生类对象访问,不能被类外访问。

基类的 x 成员,通过 y 继承,得到派生类的 z 成员。
注意:z = min ( x , y )
例如:x = public , y = protected, z = protected .
特例:x = private , y = private , z = private .

2、切片

切片:派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。
切片的时候,把派生类中基类那一部分直接赋值过去。
基类对象不能赋值给派生类对象。
基类的指针可以通过强制类型转换赋值给派生类的指针,但必须是基类的指针是指向派生类对象时才是安全的;如果基类指针指向基类对象,虽然可以,但是会存在越界访问。
举例

class Person
{
protected :
    string _name; // 姓名
    string _sex; // 性别
    int _age; // 年龄
};
class Student : public Person
{
public :
    int _No ; // 学号
};
void Test ()
{
    Student sobj ;  //定义了一个派生类对象
    
    // 1.派生类对象可以赋值给基类的 对象/指针/引用
    Person pobj = sobj ; //派生类对象赋值给基类的对象
    Person* pp = &sobj;  //派生类对象赋值给基类的指针
    Person& rp = sobj;   //派生类对象赋值给基类的引用
    
    // 2.基类对象不能赋值给派生类对象
    sobj = pobj;
    
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj; // 基类指针指向派生类对象的条件下:
    Student* ps1 = (Student*)pp; 
    //这种情况转换时可以的:基类的指针可以通过强制类型转换赋值给派生类的指针
    
    ps1->_No = 10;
    pp = &pobj; // 基类指针指向基类对象
    Student* ps2 = (Student*)pp; //这种情况转换虽然可以,但是会存在越界访问
    ps2->_No = 10;
}

3、重载 重写(覆盖) 重定义(隐藏)

重载:

  • 同一作用域
  • 相同函数名
  • 不同参数列表(参数个数 / 类型 / 顺序)
  • 返回值可同可不同

只有返回值不同,不能构成重载:
因为调用函数的时候无法确认函数的返回类型,所以仅返回类型不同的函数都可以匹配,这样就造成二义性,所以仅仅是返回值类型不同是不能重载的。

重写(覆盖):

  • 一个在基类,一个在派生类;
  • 都是虚函数,派生类可以不写virtual,但是基类必须写virtual;
  • 其他都相同(函数名、参数列表、返回值)。

特例:协变
派生类重写基类虚函数时,基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

class A
{};
class B : public A 
{};
class Person {
public:
    virtual A* f() {return new A;}
};
class Student : public Person {
public:
    virtual B* f() {return new B;}
};

特例:析构函数的重写
基类与派生类析构函数的名字不同。
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。虽然基类与派生类析构函数名字不同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

class Person {
public:
    virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
    virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,
// 下面的delete对象调用析构函数,才能构成多态,
// 才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
    delete p1;
    delete p2;
    return 0;
}

运行结果:
在这里插入图片描述
重定义(隐藏)

  • 一个在基类,一个在派生类
  • 相同成员名 (成员函数 / 成员变量)
    派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫重定义(隐藏)。
    例:成员变量的重定义
    下面的代码中student和person各自的_num构成了隐藏关系。
class Person
{
protected:
	string _name = "李华"; // 姓名
	int _num = 610; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名: " << _name << endl;//李华
		cout << " 身份证号: " << Person::_num << endl; //610
		cout << " 学号: " << _num << endl;//170
	}
protected:
	int _num = 170; // 学号
};

例:成员函数的重定义

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A {
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A {
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b; 
	b.fun(10);  //func()
	            //func(int i)->10
};

4、实现一个不能被继承的类
  1. 构造函数私有化
  2. final关键字
// C++98中构造函数私有化,派生类中调不到基类的构造函数,则无法继承
class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};

// C++11给出了新的关键字final禁止继承
class NonInherit final
{};

5、友元关系 不能被继承

友元函数不是类的成员函数,而是类的接口。
如果访问类的非静态成员时,需要友元提供一个对象作为参数,才可以访问;如果访问类的静态成员时,不需要参数。

普通函数作为友元:

class Student
{
public:
	Student(string n, int a)
		:name(n)
		,age(a)
	{ }
	friend void NoStatic(const Student& s);    //打印所有的非静态成员
	friend void Static();                      //打印所有的静态成员

private:
	string name;
	int age;
	static int num;
};
int Student::num = 170;
void NoStatic(const Student& s)
{
	cout << "非静态成员:" << "name: " << s.name << "	"<< "age: " << s.age << endl;
}
void Static()
{
	cout << "静态成员:" << "num= " << Student::num << endl;
}

int main()
{
	Student s("lihua", 18);
	NoStatic(s);
	Static();
	system("pause");
	return 0;
}

在这里插入图片描述

类成员中的一个成员函数,作为另外一个类的友元函数:

class B; // 因为A中形参是B,需要先声明
class A
{
public:
	void change(B& b);  // 在A中定义一个成员函数
};
class B
{
public:
	B(int b1, int b2)
		:_b1(b1)
		,_b2(b2)
	{ }
	friend void A::change(B& b);  // 声明该成员函数为B的友元函数
	void print()
	{
		cout << "_b1:" << _b1 << "    " << "_b2:" << _b2 << endl;
	}
private:
	int _b1;
	int _b2;
};
void A::change(B& b)  // 实现友元函数
{
	b._b1 = 20;
}

int main()
{
	B b(100, 100);
	A a;
	cout << "修改前:" << endl;
	b.print();
	a.change(b);
	cout << "修改后:" << endl;
	b.print();
	system("pause");
	return 0;
}

在这里插入图片描述

友元类:
如果希望一个类可以访问另一个类的非公有成员在内的所有成员(主要是非公有的成员),可以将一个类指定为另一类的友元类。

将类A定义为类B的友元类:

class B;
class A
{
public:
	void print(B& b);  // 在A中定义一个成员函数
};
class B
{
public:
	B(int b1, int b2)
		:_b1(b1)
		,_b2(b2)
	{ }
	friend class A;  // 声明A为B的友元类
	
private:
	int _b1;
	int _b2;
};
void A::print(B& b)
{
	cout << "_b1:" << b._b1 << "    " << "_b2:" << b._b2 << endl;
}

int main()
{
	B b(100, 100);
	A a;
	a.print(b);
	system("pause");
	return 0;
}

在这里插入图片描述


6、菱形继承、菱形虚拟继承

菱形继承属于多继承。
在这里插入图片描述

// 这样会有二义性无法明确知道访问的是哪一个
D d ;
d._x = 1;
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
d.B::_x = 3;
d.C::_x = 5;

菱形继承的二义性和数据冗余的问题该怎么解决呢?

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
上面的继承关系,在B和C的继承A时使用虚拟继承,即可解决问题。

菱形虚拟继承的中间两个类都有各自的一个指针,这两个指针分别指向两个表,表中存放的是冗余数据的处理;指针是虚基表指针,表是虚基表,虚基表存的是偏移量:

举例:

class A 
{
public:
	int _a;
};

class B : virtual public A 
{
public:
	int _b;
};

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;
	system("pause");
	return 0;
}

多继承中指针偏移问题:
在这里插入图片描述
练习:

class Base1 
{
public:
	int _b1;
};
class Base2 
{
public:
	int _b2;
};
class Derive : public Base1, public Base2 
{
public:
	int _d;
};
int main() 
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	// A. p1 == p2 == p3
	// B. p1 < p2 < p3
	// C. p1 == p3 != p2   正确
	// D. p1 != p2 != p3
	system("pause");
	return 0;
}

在这里插入图片描述


7、组合
  1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  2. 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
  3. 优先使用对象组合,而不是类继承 。
    因为继承的耦合度特别高(依赖关系强),内聚度低;
    组合的耦合度低,内聚度高,代码维护性好。

继承和组合都是复用的方式:

  • 如果是is-a关系,使用继承
  • 如果是has-a关系,使用组合
  • 如果都能用,优先使用组合
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值