目录
一.继承(重点)
1.1 概念
1.2 构造函数
1.2.1 派生类与基类的构造函数关系
1.2.2 解决方案
1.2.2.1 补充基类的无参构造函数
1.2.2.2 手动在派生类中调用基类构造函数
1.2.2.2.1 透传构造
1.2.2.2.2 委托构造
1.2.2.2.3 继承构造
1.3 对象的创建与销毁流程(掌握)
1.4 多重继承(掌握)
1.4.1 概念
1.4.2 可能出现的问题
1.4.2.1 问题1-重名问题(掌握)
1.4.2.2 问题2-菱形继承(熟悉)
二.权限
2.1 公有继承
2.2 保护继承
2.3 私有继承
一.继承(重点)
1.1 概念
继承是面向对象的三大特性之一,主要体现了代码复用的思想。
继承就是在一个已存在的类的基础上,建立一个新的类。并拥有其特性。
● 已存在的类被称为“基类”或者“父类”
● 新建立的类被称为“派生类”或者“子类”
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
void set_name(string name)
{
this->name = name;
}
string get_name()
{
return name;
}
void work()
{
cout << "我的工作是厨师,我负责炒菜" << endl;
}
};
// 派生类
class Son:public Father // public:继承权限,基类的所有权限,在派生类中保持不变
{
};
int main()
{
Son son;
cout << son.get_name() << endl;
son.work();
return 0;
}
● 修改继承来的基类内容
○ 属性:1、公有属性可以直接改。更改后基类的属性也会改变,因为改的是同一份变量。私有属性,需要使用基类的公有函数进行更改。
○ 函数:函数隐藏,通过派生类实现一个同名同参数的函数,来隐藏基类的函数。
● 新增派生类的内容
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
void set_name(string name)
{
this->name = name;
}
string get_name()
{
return name;
}
void work()
{
cout << "我的工作是厨师,我负责炒菜" << endl;
}
};
// 派生类
class Son:public Father // public:继承权限,基类的所有权限,在派生类中保持不变
{
public:
void init()
{
// name = "王"; // 错误 基类的私有属性,派生类无法直接访问
set_name("王");
}
void work()
{
cout << "我是程序猿,我在打代码" << endl;
}
void game()
{
cout << "我不光干活,我还打游戏,原神启动。杂交版启动" << endl;
}
};
int main()
{
Son son;
son.init();
cout << son.get_name() << endl; // 王
son.work(); // 我是程序猿,我在打代码
son.game(); // 我不光干活,我还打游戏,原神启动。杂交版启动
son.Father::work(); // 调用基类被隐藏的成员函数
return 0;
}
基类与派生类是相对的,一个类可能存在又是基类又是派生类的情况,取决于那两个类进行比较。
1.2 构造函数
1.2.1 派生类与基类的构造函数关系
构造函数与析构函数不能被继承。
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
};
int main()
{
// Son s; // 找不到基类的无参构造函数
Son s("张"); // 没有匹配的构造函数
return 0;
}
1.2.2 解决方案
1.2.2.1 补充基类的无参构造函数
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
// 补充基类无参构造函数
Father()
{
cout << "构造函数被调用了" << endl;
}
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
// 编译器自动添加的构造函数
Son():Father()
{
cout << "派生类构造函数被调用了" << endl;
}
};
int main()
{
Son s; // 找不到基类的无参构造函数
// Son s("张"); // 没有匹配的构造函数
return 0;
}
1.2.2.2 手动在派生类中调用基类构造函数
1.2.2.2.1 透传构造
在派生类的构造函数中,调用基类的构造函数,实际上编译器自动添加的构造函数,调用基类无参构造函数时,采用的就是这种方式。
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
// 透传构造
Son():Father("张"){}
// 派生类有参构造函数,调用基类有参构造函数
Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s1("王");
cout << s1.get_name() << endl;
return 0;
}
1.2.2.2.2 委托构造
一个类的构造函数可以调用这个类的另一个构造函数,但是要避免循环委托。
委托构造的性能要低于透传构造,但是代码的维护性“更好”。因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数,在代码重构时,只需要更改这个能力最强的构造函数即可。
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
// 委托构造
Son():Son("张"){}
// 派生类有参构造函数,调用基类有参构造函数
Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
// Son s1("王");
// cout << s1.get_name() << endl;
return 0;
}
1.2.2.2.3 继承构造
C++11 新增的写法,只需要一句话,就可以自动给派生类添加n(n为基类构造函数的个数(不包含默认无参构造函数))个构造函数。并且每个派生类的构造函数的格式都与基类相同,每个派生类的构造函数都通过透传构造调用对应格式的基类构造函数。
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name = "孙";
public:
Father():Father("张"){}
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
using Father::Father;
// // 委托构造
// Son():Son(){}
// // 派生类有参构造函数,调用基类有参构造函数
// Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
// Son s1("王");
// cout << s1.get_name() << endl;
return 0;
}
1.3 对象的创建与销毁流程(掌握)
在继承中,构造函数与析构函数的调用顺序
#include <iostream>
using namespace std;
class Value
{
private:
string str;
public:
Value(string str):str(str)
{
cout << str << "构造函数" << endl;
}
~Value()
{
cout << str <<"析构函数" << endl;
}
};
class Father
{
public:
static Value s_value;
Value val = Value("Father成员变量");
Father()
{
cout << "Father 构造函数被调用了" << endl;
}
~Father()
{
cout << "Father 析构函数被调用了" << endl;
}
};
Value Father::s_value = Value("静态FatherValue被创建了");
class Son:public Father
{
public:
static Value s_value;
Value val = Value("Son成员变量"); // int i = int(10);
Son()
{
cout << "Son 构造函数被调用了" << endl;
}
~Son()
{
cout << "Son 析构函数被调用了" << endl;
}
};
Value Son::s_value = Value("静态SonValue被创建了");
int main()
{
cout << "主函数开始执行" << endl;
{ // 局部代码块
Son s;
cout << "对象执行中" << endl;
}
cout << "主函数结束了" << endl;
return 0;
}
上面的执行结果中,得到那些规律?
● 以对象执行中 为轴,上下对称。
● 在创建的过程中,同类型的内存区域,基类先开辟。对象创建:(先基类后派生类)。对象销毁(先派生类,后基类)。
● 静态的创建早于非静态。
1.4 多重继承(掌握)
1.4.1 概念
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
{
public:
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
1.4.2 可能出现的问题
1.4.2.1 问题1-重名问题(掌握)
当多个基类具有重名成员时,编译器在编译的过程中会出现二义性的问题。
解决方式:使用基类的类名::方式调用。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "沙发可以坐着" << endl;
}
void clean()
{
cout << "打扫沙发" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "床可以躺着" << endl;
}
void clean()
{
cout << "打扫床" << endl;
}
};
// 多重继承
class SofaBed :public Sofa,public Bed
{
public:
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
sb.Sofa::clean();
sb.Bed::clean();
return 0;
}
1.4.2.2 问题2-菱形继承(熟悉)
当一个基类有多个派生类,且这些派生类又有一个共同基类时。就会出现二义性问题,这种继承方式称为菱形(钻石)继承。
有两种解决方式:
1、使用基类的类名::方式调用
#include <iostream>
using namespace std;
// 家具厂
class Furniture
{
public:
void func()
{
cout << "家具厂里有家具" << endl;
}
};
class Sofa:public Furniture
{
public:
};
class Bed:public Furniture
{
public:
};
// 多重继承
class SofaBed :public Sofa,public Bed
{
public:
};
int main()
{
SofaBed sb;
sb.Bed::func();
sb.Sofa::func();
return 0;
}
当出现虚继承时,Furniture类会生成一张虚基类表。这个表并不会占用任何对象的存储空间,属于Furniture类持有,在程序启动时加载进内存,表中记录了Furniture函数的调用地址偏移量。
Bed和Sofa对象会出现一个隐藏的成员变量指针,指向Furniture类中的虚基类表,占用对象四个字节。
SofaBed继承Sofa和Bed时,SofaBed类对象会同时拥有两个虚基类表指针成员,在调用时通过查表解决二义性。
#include <iostream>
using namespace std;
// 家具厂
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
{
public:
};
int main()
{
SofaBed sb;
sb.func();
return 0;
}
二.权限
类内 | 派生类中 | 全局 | |
private | √ | × | × |
protected | √ | √ | × |
public | √ | √ | √ |
2.1 公有继承
上面的代码中一直使用的就是公有继承,公有继承也是使用最多的一种继承方式。
在公有继承中,派生类可以继承基类的成员,但是不可以访问基类的私有成员,基类的公有成员与保护成员在派生类中权限不变。
2.2 保护继承
在保护继承中,派生类可以继承基类的成员,不可以访问基类的私有成员,基类的公有成员与保护成员在派生类中都是保护权限。(只能在基类和派生类中访问,外部无法访问)。
2.3 私有继承
在私有继承中,派生类可以继承基类的成员,但是不可以访问基类的私有成员,基类的公有成员与保护成员在派生类中权限都是私有权限。
#include <iostream>
using namespace std;
class Base
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
class Son:private Base
{
public:
Son()
{
// cout << str1 << endl; // 错误str1为私有成员,派生类中无法访问
cout << str2 << endl;
cout << str3 << endl;
}
};
class Sun:public Son
{
public:
Sun()
{
// cout << str1 << endl; // 错误str1为私有成员,派生类中无法访问
// cout << str2 << endl; // 错误str2 在Son类中是私有成员,派生类无法访问
// cout << str3 << endl; // 错误str3 在Son类中是私有成员,派生类无法访问
}
};
int main()
{
Sun s1;
// cout << s1.str1 << endl; // 错误,私有权限类外无法访问
// cout << s1.str2 << endl; // 错误,私有权限类外无法访问
// cout << s1.str3 << endl; // 错误,私有权限类外无法访问
return 0;
}