继承的基本使用
继承是C++的三大特性之一,体现的是代码复用的思想
所谓继承就是在一个已经存在的类的基础上,建立一个新的类,让新的类拥有之前的类的特性
已经存在的类叫做“基类”或“父类”
新建立的类被称为“派生类”或“子类”
通常派生类继承基类后,会对基类代码进行一定程序的扩充或改动
如果对继承来的基类成员函数不满意,可以使用函数隐藏来屏蔽基类同名函数。如果对于继承来的成员变量不满意,可以直接修改
#include <iostream>
using namespace std;
class Father
{
public:
string F_name = "zhang";
void work()
{
cout << "I'm a worker" << endl;
}
};
class Son:public Father
{
public:
void work()
{
cout << "鸡你太美" << endl;
}
void play()
{
cout << "巴拉巴拉" << endl;
}
};
int main()
{
Son s;
//更改继承来的成员变量
s.F_name = "wang";
s.work();
// 除了可以调用上述继承来的特性外,还可以调用自己的部分
s.play();
// 可以使用下面的方式调用被隐藏的基类函数
s.Father::work();
return 0;
}
注意:基类和派生类是相对的。
派生类是基类的具象化,基类是派生类的抽象化
关于基类的private成员的说明
派生类可以继承到基类private的成员,只是无法直接访问
#include <iostream>
using namespace std;
class Father
{
private:
string first_name = "张";
public:
void set_first_name(string fn)
{
first_name = fn;
}
string get_first_name() const
{
return first_name;
}
};
// Son类继承自Father类
class Son:public Father
{
public:
};
int main ()
{
Son s;
cout << s.get_first_name() << endl; // 张
s.set_first_name("王");
cout << s.get_first_name() << endl; // 王
return 0;
}
#构造函数和析构函数不能被继承
在上面代码中Father类没有手动写构造函数,因此编译器会自动添加一个没有参数的、函数体为空的构造函数。Son类中也没有手动写构造函数,编译器也会自动添加一个没有参数的、函数体为空的构造函数,且在Son类的构造函数中调用基类的构造函数。
如果在派生类中调用基类的构造函数,这种用法被称为透传构造
在继承中,每一个派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数
一个类的构造函数去调用其他构造函数的方式有三种:透传构造、委托构造、继承构造
透传构造
透传构造指的是在派生类的构造函数中调用基类的构造函数
#include <iostream>
using namespace std;
class Father{
private:
string name;
public:
Father(string fn):name(fn){}
string get_name() const
{
return name;
}
};
//Son类继承自Father类
class Son:public Father
{
public:
// Son():Father(){} 编译器默认添加
Son():Father("张"){}
Son(string name):Father(name){}
};
int main ()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
委托构造
委托构造指的是一个类的构造函数调用这个类的另一个构造函数,需要注意的是:不得形成委托闭环、最终委托的构造函数需要透传构造
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father(string fn):name(fn){}
string get_name() const
{
return name;
}
};
class Son:public Father
{
Son():Son("张"){}
Son(string name):Father(name){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2;
coout << s2.get_name() << endl;
return 0;
}
委托构造没有继承也能使用
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string name):name(name){}
Dog():Dog("旺财"){}
string get_name()
{
return name;
}
};
int main ()
{
Dog d;
cout << d.get_name() << endl;
return 0;
}
继承构造
这种用法并不是继承了构造函数,而是编译器自动生成了与基类构造函数参数一致的派生类构造函数,且每个派生类类的构造函数使用透传构造调用对应的基类构造函数
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father():name("佚名")
{
cout << "0" << endl;
}
Father(string fn):name(fn)
{
cout << "1" << endl;
}
string get_name() const
{
return name;
}
};
class Son:public Father
{
public:
using Father::Father;
// 编译器会自动添加下面的代码
// Son():Father(){}
// Son(string fn):Father(fn){}
};
int main ()
{
Son s1;
cout << s1.get_name() << endl;
Son s2("张三");
cout << s2.get_name() << endl;
return 0;
}
对象的创建与销毁流程
#include <iostream>
using namespace std;
/**
* @brief 作为其它类的成员变量或静态成员变量
*/
class Value
{
private:
string name;
public:
Value(string name):name(name)
{
cout << name << "成员变量创建了" << endl;
}
~Value()
{
cout << name << "成员变量销毁了" << endl;
}
};
class Father
{
public:
static Value sValue;
Value mValue = Value("Father类的");
Father()
{
cout << "Father类的构造函数" << endl;
}
~Father()
{
cout << "Father类的析构函数" << endl;
}
};
Value Father::sValue = Value("Father类的静态");
class Son:public Father
{
public:
static Value sValue;
Value mValue = Value("Son类的");
Son()
{
cout << "Son类的构造函数" << endl;
}
~Son()
{
cout << "Son类的析构函数" << endl;
}
};
Value Son::sValue = Value("Son类的静态");
int main ()
{
cout << "主函数开始执行" << endl;
{ // 局部代码块
Son s;
}
cout << "主函数结束执行" << endl;
return 0;
}
可以观察到有如下规律:
- 静态成员比非静态成员更早创建,更晚销毁。是因为静态成员不与实际的对象相关联,在程序运行的一开始就创建了,在程序运行结束时销毁。
- 对象的创建与销毁是相反的,创建阶段基类部分在先,销毁阶段派生类部分在先。
可以看到面向对象编程中,一个派生类对象的创建和销毁要调用基类的相关代码,实际开发的过程中,继承结构可能是多级的,所以程序的执行效率是比较低的,但是显著的提升了开发效率。因此在开发的过程中,不要滥用继承
多重继承
C++支持多重继承,即一个派生类有多个直接继承的基类,属于单一继承的一种拓展,其中每个基类与派生类的关系仍然可以看做是一个单继承
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "能坐着" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "能躺着" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
二义性问题
基类拥有同名成员
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "能坐着" << endl;
}
void position()
{
cout << "放在客厅" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "能躺着" << endl;
}
void position()
{
cout << "放在卧室" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
// sb.position(); 错误:二义性
sb.Sofa::position();
sb.Bed::position();
return 0;
}
菱形继承
如果一个派生类从多个基类中派生,而这些基类又有一个共同的基类,这种情况就是菱形继承
虽然可以通过之前的类名与作用域限定符的方式解决,但是并不是最佳解决方案
#include <iostream>
using namespace std;
/**
* @brief 家具类
*/
class Furniture
{
public:
void func()
{
cout << "家具是指人类维持正常生活、从事生产实践和开展社会活动必不可少的器具设施大类。" << endl;
}
};
class Sofa:public Furniture
{
public:
};
class Bed:public Furniture
{
public:
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
// sb.func(); 错误:二义性
sb.Bed::func();
sb.Sofa::func();
// sb.Furniture::func(); 错误
return 0;
}
更好的解决方案是使用虚继承,虚继承中的基类被称为虚基类,例如下图中Furniture类就是一个虚基类,虚基类中的派生类中会增加一个虚基类指针的成员变量,同时虚基类的派生类中也会新增一个虚基类表。当继续向下继承时,SofaBed类只会继承虚基类指针,不会继承虚基类表,在SofaBed对象调用Furniture的函数时,SofaBed对象的虚基类指针就会依次向上查询到函数的调用地址。从而避免二义性的产生。
实际上虚继承是牺牲一部分性能来换取的解决方案,因为查表有性能开销
#include <iostream>
using namespace std;
/**
* @brief 家具类
*/
class Furniture
{
public:
void func()
{
cout << "家具是指人类维持正常生活、从事生产实践和开展社会活动必不可少的器具设施大类。" << endl;
}
};
class Sofa:virtual public Furniture
{
public:
};
class Bed:virtual public Furniture
{
public:
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.func();
return 0;
}
权限
在类、对象、封装一篇中有三种权限的简单介绍,这里是详细一点:
#include <iostream>
using namespace std;
class Father
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
void test()
{
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
};
class Son:public Father
{
public:
void test()
{
// cout << s1 << endl; 错误
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
Father f;
f.test();
Son s;
s.test();
// cout << f.s1 << endl; 错误
// cout << f.s2 << endl; 错误
cout << f.s3 << endl;
return 0;
}
三种权限的继承
公有继承:
公有继承是最常用的一种继承,公有继承的特点是,派生类继承的基类的成员中,除了基类的private成员外,其它成员的权限在派生类中不变
#include <iostream>
using namespace std;
class Father
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:public Father
{
public:
void test()
{
cout << s2 << endl; // protecetd
cout << s3 << endl; // public
}
};
class Grandson:public Son
{
public:
void test()
{
cout << s2 << endl;
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
cout << s.s3 << endl;
Grandson gs;
gs.test();
return 0;
}
保护继承:
保护继承的特点是,派生类继承的基类的成员中,除了基类的private成员外,其它成员的权限都变为protected
#include <iostream>
using namespace std;
class Father
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:protected Father
{
public:
void test()
{
cout << s2 << endl; // protecetd
cout << s3 << endl; // protected
}
};
class Grandson:public Son
{
public:
void test()
{
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
// cout << s.s3 << endl; 错误
Grandson gs;
gs.test();
return 0;
}
私有继承:
私有继承的特点是,派生类继承的基类的成员中,除了基类的private成员外,其它成员的权限都变为private
#include <iostream>
using namespace std;
class Father
{
private:
string s1 = "private";
protected:
string s2 = "protected";
public:
string s3 = "public";
};
class Son:private Father
{
public:
void test()
{
cout << s2 << endl; // private
cout << s3 << endl; // private
}
};
class Grandson:public Son
{
public:
void test()
{
// cout << s2 << endl; 错误
// cout << s3 << endl; 错误
}
};
int main()
{
Son s;
// cout << s.s2 << endl; 错误
// cout << s.s3 << endl; 错误
Grandson gs;
gs.test();
return 0;
}