文章目录
1.基础知识
1.1概念
继承(inheritance)机制:面向对象程序设计使代码可以复用。在保持原有类特性的基础上进行扩展,增加功能,产生新的类称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "Mike";
int _age = 20;
};
class Student : public Person
{
protected:
int _grade;
};
class Teacher : public Person
{
protected:
int _id;
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
1.2定义
1.3继承方式
- 基类private成员在派生类中无论以什么方式继承都是不可见。基类的私有成员被继承到了派生类对象中,语法上限制派生类对象不管在类里面还是类外面都不能去访问。
- 基类成员在基类外不能访问,派生类内能访问,定义为protected。
- 基类的public、protected成员在子类的访问方式与继承方式比取小权限
- class默认继承方式是private,struct默认继承方式是public
- 一般使用public继承,很少使用protetced/private继承,不提倡使用protetced/private继承,protetced/private继承下来的成员只能在派生类的类里使用。
2.类与对象知识重演
2.1赋值问题
- 派生类对象 可以赋值给基类的 对象 / 指针 / 引用。
- 派生类中父类那部分赋值–派生类独有的无法赋值给基类
- 基类对象不能赋值给派生类对象
- 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 进行识别后安全转换。
class Dad
{
protected:
string _name;
string _sex;
int _age;
};
class Son : public Dad
{
public:
int _id;
};
void Test()
{
Son son;
// 1.子类对象可以赋值给父类对象/指针/引用
Dad dad1 = son;
Dad* dad2 = &son;
Dad& dad3 = son;
//2.基类对象不能赋值给派生类对象
//son = dad1;
// 3.基类的地址可以通过强制类型转换赋值给派生类的指针
Son * pson = (Son*)dad2;
pson->_id = 10;
}
2.2作用域问题
- 基类和派生类有独立的作用域
- 子类和父类中有同名成员,直接访问时将屏蔽父类同名成员==》隐藏/重定义。
class Dad
{
protected:
string _name = "Mike";
int _age = 20;
};
class Son : public Dad
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 年龄【直接访问】:" << _age << endl;
cout << " 年龄【指明类域】:" << Dad::_age << endl;
}
protected:
int _age = 18;
};
void Test()
{
Son s1;
s1.Print();
};
int main()
{
Test();
return 0;
}
- 成员函数:函数名相同构成隐藏
class Dad
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class Son : public Dad
{
public:
void fun(int i)
{
Dad::fun();
cout << "func(int i)->" << i << endl;
}
};
void Test()
{
Son b;
b.fun(10);
};
int main()
{
Test();
return 0;
}
2.3默认成员函数
- 派生类的构造函数必须调用基类的构造函数初始化基类的成员。若基类没有默认构造函数,则必须在派生类构造函数的初始化列表显示调用
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类成员的拷贝初始化
- 派生类的**operator=**必须要调用基类的operator=完成基类成员的赋值
- 派生类的析构函数在被调用后自动调用基类的析构函数清理基类成员。这样能保证派生类对象先清理派生类成员再清理基类成员。
- 构造顺序:基类–派生类
- 析构顺序:派生类–基类
- C++语法中析构函数需要有构成重写的功能,重写的条件之一是函数名相同。
编译器对析构函数名特殊处理成destrutor(),父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系
class Dad
{
public:
//缺省构造
Dad(const char* name = "Mike")
: _name(name)
{
cout << "Dad()" << endl;
}
//拷贝构造
Dad(const Dad& p)
: _name(p._name)
{
cout << "Dad(const Dad& p)" << endl;
}
//赋值重载
Dad& operator=(const Dad& p)
{
cout << "Dad operator=(const Dad& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
//析构函数
~Dad()
{
cout << "~Dad()" << endl;
}
protected:
string _name;
};
class Son : public Dad
{
public:
//构造函数
Son(const char* name, int num)
: Dad(name)
, _num(num)
{
cout << "Son()" << endl;
}
//拷贝构造
Son(const Son& s)
: Dad(s)
, _num(s._num)
{
cout << "Son(const Son& s)" << endl;
}
//赋值重载
Son& operator = (const Son& s)
{
if (this != &s)
{
Dad::operator =(s);//加作用域限定:与父类赋值构成隐藏
cout << "Son& operator= (const Son& s)" << endl;
_num = s._num;
}
else
{
cout << "Son& operator= (const Son& s)" << endl;
return *this;
}
}
//析构函数
~Son()
{
cout << "~Son()" << endl;
}
protected:
int _num;
};
void Test()
{
Son s1("John", 20);
Son s2(s1);
Son s3("Lisa", 30);
s1 = s3;
}
int main()
{
Test();
return 0;
}
2.4友元关系
子类无法继承父类的友元关系
class Son;
class Dad
{
friend void Print(const Dad& p, const Son& s);
protected:
string _name;
};
class Son : public Dad
{
protected:
int _id;
};
//全局函数Print是基类的友元 他可以访问基类的protected、provate成员 但是它不能访问子类的protected、provate成员
void Print(const Dad& p, const Son& s)
{
cout << p._name << endl;
cout << s._name << endl;
cout << s._id << endl;
}
int main()
{
Dad p;
Son s;
Print(p, s);
return 0;
}
2.5静态成员
基类定义的static静态成员,在整个继承体系只有一个static成员实例 。
class Dad
{
public:
Dad()
{
++_num;
}
protected:
string _name;
public:
static int _num;
};
int Dad::_num = 0;
class Son : public Dad
{
protected:
int _id;
};
class GrandSon : public Son
{
protected:
string _age;
};
void test()
{
Son s1;
Son s2;
Son s3;
GrandSon s4;
cout << " 次数 :" << Dad::_num << endl;
Son::_num = 0;
cout << " 次数 :" << Dad::_num << endl;
}
int main()
{
test();
return 0;
}
3.单继承与多继承
3.1分类
单继承: 一个子类只有一个直接父类
多继承:一个子类有两个或以上直接父类【菱形继承是多继承的一种特殊情况】
3.2菱形继承/钻石继承
菱形继承有数据冗余和二义性的问题。
1.二义性问题
class Dad
{
public:
string _name;
};
class Son : public Dad
{
protected:
int _id;
};
class Daughter : public Dad
{
protected:
int _id;
};
class Toy : public Son, public Daughter
{
protected:
string _size;
};
void Test()
{
Toy toy;
toy._name = "Ultraman";
//指定类域:解决二义性问题 【数据冗余问题无法解决】
toy.Son::_name = "擎天柱";
toy.Daughter::_name = "芭比娃娃";
}
2.数据冗余问题
虚拟继承可以解决菱形继承二义性和数据冗余的问题。
class Dad
{
public:
string _name;
};
class Son : virtual public Dad
{
protected:
int _id;
};
class Daughter : virtual public Dad
{
protected:
int _id;
};
class Toy : public Son, public Daughter
{
protected:
string _size;
};
void Test()
{
Toy toy;
toy._name = "芭比娃娃";
}
int main()
{
Test();
return 0;
}
3.虚拟继承的底层
class A
{
public:
int _a;
};
// class B : public A
class B : virtual public A
{
public:
int _b;
};
// class C : public A
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;
return 0;
}
4.继承相关问题
1.不可被继承的类
- 加final关键字
- 基类构造函数私有化
2.指针问题
class A
{
public:
int _a;
};
class B
{
public:
int _b;
};
class C : public A, public B
{
public:
int _c;
};
int main()
{
C c;
A* p1 = &c;
B* p2 = &c;
C* p3 = &c;
cout << p1 << endl;
cout << p2 << endl;
cout << p3 << endl;
return 0;
}
class A
{
public:
int _a;
};
class B
{
public:
int _b;
};
class C : public B, public A
{
public:
int _c;
};
int main()
{
C c;
A* p1 = &c;
B* p2 = &c;
C* p3 = &c;
cout << p1 << endl;
cout << p2 << endl;
cout << p3 << endl;
return 0;
}
4.内部类、类的组合、类的继承
1.内部类
- B类定义在A类的内部,B类就叫做内部类。
- 内部类是一个独立的类,不属于外部类,不能通过外部类的对象去访问内部类的成员。
- 内部类是外部类的友元类,内部类可以访问外部类的成员。外部类不是内部类的友元。
- 内部类可以定义在外部类的public、protected、private
- 内部类可以直接访问外部类中的static成员
- sizeof(外部类)=外部类
class A
{
public:
class B
{
public:
void func(const A& a)
{
cout << _aa << endl;
cout << a._a << endl;
}
};
private:
static int _aa;
int _a;
};
int A::_aa = 1;
int main()
{
A::B b;
b.func(A());
return 0;
}
2.类的组合
- B类中的成员是A类的对象
- B类的构造函数要完成:自身数据+对象成员数据初始化
class A
{
public:
A(int x)
{
_a = x;
}
private:
int _a;
};
class B
{
public:
B(A x, int y)
:a(x)
{
_b = y;
}
private:
A a;
int _b;
};
int main()
{
B b(1, 2);
return 0;
}
3.区别
组合:B组合了A B类有A类的成员【is-a关系】
继承:B继承了A B类是A类的子类【has-a关系】
B组合A:在类B中访问A的public成员【protected、private不可访问】
B公有继承A:在类B中可以访问A的非private成员
5.经典题目
- 什么是菱形继承?菱形继承的问题是什么?
- 什么是菱形虚拟继承?如何解决数据冗余和二义性的
- 继承和组合的区别?什么时候用继承?什么时候用组合?
答案: - 菱形继承是指在一个类的继承关系中,子类同时继承了两个不同的父类,而这两个父类又共同继承自同一个基类的情况。例如,类A是类B和类C的父类,而类D同时继承了类B和类C,这就形成了一个菱形继承的结构。
菱形继承的问题是会导致数据冗余和二义性。当子类继承了两个父类时,如果这两个父类中有一些成员函数或数据成员重名,那么子类在访问这些成员时就会出现二义性,无法确定使用哪个父类的成员。此外,如果父类中有一些公共的数据成员,那么子类在继承时会出现这些数据成员的冗余。
- 菱形虚拟继承是为了解决菱形继承的问题而提出的一种解决方案。虚拟继承可以通过在继承关系中使用关键字"virtual"来实现,例如,类B和类C在继承类A时使用虚拟继承,即声明为"virtual public A"。
通过虚拟继承,类D将只继承一份共同的父类A,而不会出现数据冗余的问题。同时,通过使用虚拟继承,子类在访问重名成员时将根据就近原则来确定使用哪个父类的成员,避免了二义性的问题。
- 继承和组合是面向对象编程中两种不同的代码复用方式。
继承是指一个类继承另一个类的成员函数和数据成员,通过继承可以实现代码的复用和层次化结构。继承可以帮助我们构建更加通用的代码框架,减少重复代码的编写。通常情况下,当新的类具有与已存在类相似的属性和行为时,可以考虑使用继承。
组合是指一个类包含了其他类的对象作为自己的成员变量,通过组合可以实现对象之间的关联和复杂的数据结构。组合可以更加灵活地构建对象之间的关系,通过将各个对象组合在一起,实现更加复杂的功能。通常情况下,当新的类需要与其他类进行协作,并且对象之间的关系较为复杂时,可以考虑使用组合。
继承和组合各有其优缺点,需要根据具体的需求和设计目标来选择使用哪种方式。
6.内部类、类的组合、继承
内部类、类的组合和继承是面向对象编程中三种不同的概念,它们之间有以下区别和联系:
-
内部类(Inner Class):内部类是一个定义在其他类内部的类。它可以访问外部类的成员,包括私有成员,并且可以被外部类的其他成员函数使用。内部类可以分为静态内部类和非静态内部类。
-
类的组合(Class Composition):类的组合是指一个类将其他类作为其成员变量来使用。这种关系是“具有”(has-a)的关系,即一个类包含(组合)其他类的对象作为它的一部分。类的组合实现了代码的重用和聚合的概念。
-
继承(Inheritance):继承是通过派生类(子类)从基类(父类)继承其属性和方法。派生类可以获得基类的所有公有和受保护成员,还可以扩展和修改基类的功能。继承实现了代码的重用、封装和多态性的概念。
区别:
- 内部类是在一个类内部定义的类,通过内部类可以实现更好的封装和组织代码。而类的组合和继承则是不同的关系模式,组合是一个类包含其他类的对象,而继承是一个类从另一个类继承其属性和方法。
- 内部类可以直接访问外部类的成员,而组合和继承关系中,成员的访问权限取决于具体实现。
联系:
- 内部类可以作为类的组合和继承的一种方式,可以将一个类作为另一个类的组合对象或派生类的一部分,以继承的方式共享它们的功能和属性。
- 在使用组合和继承的时候,内部类提供了更灵活的方式来实现代码的组织和封装,可以通过内部类的方式定义和实现复杂的数据结构或功能模块。