C++学习笔记(12)
学习是一件任重而道远的事情,与其焦虑不如动手起来,借助平台记录自己学习笔记,希望和大家多多交流,今天又是努力成为程序媛的一天!
17.类和对象
17.6 继承
继承是面向对象三大特性之一
对于类,下级别的成员除了拥有上一级的共性,还有自己的特点
17.6.1 继承的基本语法
语法:class 子类:继承方式 父类
子类又称为派生类
父类又称为基类
#include<iostream>
using namespace std;
//避免重复代码 用继承实现
//写一个公共的页面代码
class Basepage {
public:
void header() {
cout << "首页、公开课、登录、注册..." << endl;
}
void footer() {
cout << "帮助中心、交流合作" << endl;
}
void left() {
cout << "Java、Python、C++" << endl;
}
};
//Java页面
class Java :public Basepage{//继承共同的页面
public:
void content() {
cout << "Java 学科视频" << endl;
}
};
//Python页面
class Python :public Basepage {//继承共同的页面
public:
void content() {
cout << "Python 学科视频" << endl;//写自己特点内容
}
};
//C++页面
class CPP :public Basepage {//继承共同的页面
public:
void content() {
cout << "C++ 学科视频" << endl;
}
};
void test() {
Java j;
Python p;
CPP c;
j.header();
j.left();
j.footer();
j.content();
cout << "-----------------------------------" << endl;
p.header();
p.left();
p.footer();
p.content();
cout << "-----------------------------------" << endl;
c.header();
c.left();
c.footer();
c.content();
}
int main() {
test();
system("pause");
return 0;
}
17.6.2 继承方式
-
继承语法:class 子类:继承方式 父类
-
继承方式三种:
- 1.公共继承
- 2.保护继承
- 3.私有继承
#include<iostream>
using namespace std;
//继承方式
class Base {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
//派生类
//公有继承
//在公有继承中 m_a是Son1的公共成员 m_b是Son1的保护成员 m_c在Son1不可访问 即父类的私有属性不管怎么都不可访问
class Son1:public Base {
public:
void fun1() {
m_a = 1000;//公共 类内类外可访问
m_b = 20;//保护 类内可访问
//m_c = 20;//报错 不可访问
}
};
void test01() {
Son1 s1;
s1.fun1();
s1.m_a = 22;//公共成员 类外可访问
//s1.m_b = 22;//保护成员 ,类外不可访问
//s1.m_c = 22;//不可访问
}
//保护继承
//在保护继承中 m_a是Son1的保护成员 m_b是Son1的保护成员 m_c在Son1不可访问 即父类的私有属性不管怎么都不可访问
class Son2 :protected Base {
public:
void fun2() {
m_a = 22;//保护 类内可访问,类外不可
m_b = 23;//保护 类内可访问
//m_c = 20;//报错 不可访问
}
};
void test02() {
Son2 s2;
s2.fun2();
//s2.m_a = 22;//保护成员 类外不可访问
//s1.m_b = 22;//保护成员 ,类外不可访问
//s1.m_c = 22;//不可访问
}
//私有继承
//在私有继承中 m_a是Son1的私有成员 m_b是Son1的私有成员 m_c在Son1不可访问 即父类的私有属性不管怎么都不可访问
class Son3 :private Base {
public:
void fun3() {
m_a = 22;//私有 类内可访问,类外不可
m_b = 23;//私有 类内可访问
//m_c = 20;//报错 不可访问
}
};
void test03() {
Son3 s3;
s3.fun3();
//s2.m_a = 22;//私有成员 类外不可访问
//s1.m_b = 22;//私有成员 ,类外不可访问
//s1.m_c = 22;//不可访问
}
//继承子类的
//公有继承子类的Son3 即继承父类的所有私有属性 那么都无法访问 继承原理一致
class Sun :public Son3 {
public:
void fun4() {
//m_a = 22;//父亲的私有属性无法访问
//m_b = 23;//父亲的私有属性无法访问
//m_c = 20;//报错 不可访问
}
};
//保护继承 子类的保护继承
class Sun2 :protected Son2 {
public:
void fun5() {
m_a = 222;
m_b = 333;
//m_c = 444;//报错 不可访问
}
};
void test05() {
Sun2 ss2;
//ss2.m_a = 10;//保护成员类外无法访问
//ss2.m_a = 10;//保护成员类外无法访问
//ss2.m_a = 10;//报错
}
int main() {
test01();
test02();
test03();
test05();
system("pause");
return 0;
}
注意:三种成员特点如下:公有权限,私有权限,保护权限
17.6.3 继承中的对象模型
#include<iostream>
using namespace std;
class Base {
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son :public Base {
public:
void fun1() {
m_a = 1000;
m_b = 20;
//m_c = 20;
}
int m_d;
};
class Son3 :private Base {
public:
void fun3() {
m_a = 22;//私有 类内可访问,类外不可
m_b = 23;//私有 类内可访问
//m_c = 20;//报错 不可访问
}
int m_d3;
};
void test() {
cout << "size of son = " << sizeof(Son) << endl;//16
cout << "size of son = " << sizeof(Son3) << endl;//16
}
//父类中所有的非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器隐藏了 因此访问不到 但确实被继承下去了
int main() {
test();
system("pause");
return 0;
}
注意:
1.父类中所有的非静态成员属性都会被子类继承下去
2.父类中私有成员属性 是被编译器隐藏了 因此访问不到 但确实被继承下去了
(Windows)用工具查看继承中的对象属性:
1.打开编写的VS版本中的开发人员命令提示符 我这里显示是英文Developer Command Prompt for VS 2022 Preview
2.转到文件路径下:右击目前项目,打开所在文件夹,查看路径,如果是在当前盘符就不用跳转,如果命令符打开盘符不在当前路径盘符,就在命令行后面输 入文件所在盘符加上冒号跳转,再cd 到当前项目文件夹路径
3.输入dir查看目录内容
4.cl / d1 reportSingleClassLayout类名 文件名
报告单个类布局 第一个是字母l,第二个是数字1
5.可以查看该类的所有属性,哪些是父类和自己的都能显示出来
17.6.4 继承中的构造和析构顺序
继承中父类和子类的构造和析构顺序:创建子类对象时,会先创建一个父类出来,即先是父类的构造 然后子类构造,之后析构子类先 父类后
#include<iostream>
using namespace std;
class Base {
public:
Base() {
cout << "这是父类的构造函数调用" << endl;
}
~Base() {
cout << "这是父类的析构函数调用" << endl;
}
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son :public Base {
public:
Son() {
cout << "这是子类的构造函数调用" << endl;
}
~Son() {
cout << "这是子类的析构函数调用" << endl;
}
int m_d;
};
void test() {
//继承中构造和析构顺序如下:
//先构造父类 再构造子类 析构顺序和构造相反
Son s;
}
int main() {
test();
system("pause");
return 0;
}
继承中构造和析构顺序如下:
先构造父类 再构造子类 析构顺序和构造相反
17.6.5 继承同名成员处理方式
前情提要:如果子类和父类出现同名成员,如何通过子类对象访问子类或父类中同名数据?
1.访问子类同名成员 直接访问即可
2.访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
class Base {
public:
Base() {
m_a = 18;
}
void fun() {
cout << "父类成员函数" << endl;
}
void fun(int a) {
cout << "父类成员函数2" << endl;
}
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son :public Base {
public:
Son() {
m_a = 100;
}
void fun() {
cout << "子类成员函数" << endl;
}
int m_a;
int m_d;
};
void test() {
//直接访问是子类自己的成员
//如果想要通过子类对象访问父类的同名成员需要加一个作用域
Son s;
cout << "m_a = " << s.m_a << endl;//100
cout << "Base m_a = " << s.Base::m_a << endl;//18
s.fun();//只有父类有该成员函数时,公共权限也是子类成员 可以直接调用 但是如果与子类重名就调用子类的
s.Base::fun();//重名时只有加父类作用域才会调用父类
//s.fun(100);//会报错 虽然是父类含参的同名函数但是当子类出现与父类同名的成员函数
//子类的同名成员会隐藏父类中所有的同名成员函数 即所有重载的函数
//如果想访问就加作用域
s.Base::fun(100);//父类成员函数2
}
int main() {
test();
system("pause");
return 0;
}
注意:
- 子类和父类有同名成员时,子类对象直接访问的是子类中的同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类和父类拥有同名的成员函数,子类会隐藏父类中所有的同名函数,包括本身和重载函数,加作用域可以访问到父类中同名函数
17.6.6 继承同名静态成员处理方式
前情提要:如果子类和父类出现同名成员,静态成员如何通过子类对象访问子类或父类中同名数据?
处理方式和非静态成员处理方式一致:
1.访问子类同名成员 直接访问即可
2.访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
class Base {
public:
static void fun() {
cout << "父类成员函数" << endl;
}
static void fun(int a) {
cout << "父类成员函数2" << endl;
}
static int m_a;//静态成员所有对象共享一份数据 类内声明 类外初始化
protected:
int m_b;
private:
int m_c;
};
int Base::m_a = 100;
class Son :public Base {
public:
static void fun() {
cout << "子类成员函数" << endl;
}
static int m_a;
int m_d;
};
int Son::m_a = 120;
void test01() {
//1.通过对象来访问
Son s1;
cout << "----------通过对象访问 -----------" << endl;
cout << "Son 下 m_a = " << s1.m_a << endl;
//2.通过类名来访问
cout << "------------通过类名访问--------------- " << endl;
cout << "Son 下 m_a = " << Son::m_a << endl;
}
void test02() {
//1.通过对象来访问
Son s2;
cout << "---------通过对象访问--------- " << endl;
s2.fun();
//2.通过类名来访问
cout << "------------通过类名访问---------- " << endl;
Son::fun();
}
void test03() {
//访问父类同名静态成员和函数
//1.通过对象的方式
Son s3;
cout << "---------通过对象访问-------- " << endl;
cout << "Son 下 m_a = " << s3.Base::m_a << endl;
s3.Base::fun();
//2.通过子类的类名的方式
cout << "---------通过子类类名访问--------- " << endl;
cout << "Base 下 m_a = " << Son::Base::m_a << endl;
//第一个::代表通过类名的方式访问 第二个::表示访问父类作用域下
Son::Base::fun();
//Son::fun(100)//报错 //子类出现和父类同名静态成员函数 也会隐藏父类中所有的同名成员函数
//如果想访问父类中被隐藏的同名成员 需要加作用域
Son::Base::fun(100);
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
总结:同名静态成员处理和同名成员处理原理方式基本一致,不过静态成员还可以通过类名的方式访问,这点普通成员没有,以及静态成员属性要类内声明,类外初始化。
17.6.7 多继承语法
语法:class 子类:继承方式 父类1,继承方式 父类2...
多继承可能引起父类同名成员出现,所以通常会加作用域区分
实际开发并不推荐多继承使用
#include<iostream>
using namespace std;
class Base1 {
public:
Base1() {
m_a = 18;
}
void fun() {
cout << "父类成员函数" << endl;
}
void fun(int a) {
cout << "父类成员函数2" << endl;
}
int m_a;
};
class Base2 {
public:
Base2() {
m_b = 28;
m_a = 30;
}
void fun() {
cout << "父类成员函数" << endl;
}
void fun(int a) {
cout << "父类成员函数2" << endl;
}
int m_b;
int m_a;
};
//多继承语法 逗号隔开
class Son :public Base1,public Base2 {
public:
Son() {
m_c = 100;
m_d = 500;
}
void fun() {
cout << "子类成员函数" << endl;
}
int m_c;
int m_d;
};
void test01() {
Son s1;
//每个Son类创建的对象所占的字节大小
cout << "size of Son = " << sizeof(Son) << endl;//这里Son换成s1也可 size of Son = 20
//没有同名时可以直接调用s1.m_a不会错
//但父类中出现同名成员 需要加作用域区分
cout << "Base1::m_a = " << s1.Base1::m_a << endl;//18
cout << "Base2::m_a = " << s1.Base2::m_a << endl;//30
}
int main() {
test01();
system("pause");
return 0;
}
父类没有同名成员时可以直接对象调用,但是出现同名成员时要加作用域,实际开发时,代码通常多人合作,不清楚是否存在同名情况,所以多继承编写代码较繁琐,不建议使用
17.6.8 菱形继承
概念:
两个派生类继承同一个基类
又有某个类同时继承两个派生类
这种继承方式被称为菱形继承或者钻石继承
#include<iostream>
using namespace std;
class Base {
public:
int m_age;
};
//利用虚继承 解决菱形继承问题
//在继承之前加上关键字virtual变为虚继承
//Base类称为 虚基类
class Son1 :virtual public Base {};//虚继承
class Son2 :virtual public Base {};//虚继承
class Sun :public Son1,public Son2 {};
void test() {
Sun s1;
//s1.m_age = 18;//会报错 Son1和Son2都有这个属性 出现二义性
s1.Son1::m_age = 25;
s1.Son2::m_age = 28;
//当出现菱形继承 两个父类有相同属性 用作用域区分
cout << "s1.Son1::m_age = " << s1.Son1::m_age << endl;
cout << "s1.Son2::m_age = " << s1.Son2::m_age << endl;
//然而这个数据只需要一份 菱形继承导致数据有两份 资源浪费
//虚继承后数据只有一个 以最后为准 这时按照对象直接调用也不会出错
cout << "s1.m_Age = " << s1.m_age << endl;
//底层逻辑:这时Sun继承的不是两个数据 而是两个指针,这两个指针通过偏移量找到唯一的数据
}
int main() {
test();
system("pause");
return 0;
}
底层逻辑:这时Sun继承的不是两个数据 而是两个指针,这两个指针通过偏移量找到唯一的数据
第十二篇笔记到此结束,C++基础学习会持续更新在C++学习笔记合集中,当作学习笔记复习,如果能帮助其他小伙伴就更好了。
笔记是看黑马程序C++时做的记录,笔记中如果有错误和改进的地方,欢迎大家评论交流,up up up!!!
学习原视频来自:黑马程序员C++从0到1