类与对象(二)
继承
继承的基本语法
class 子类(派生类):继承方式 父类(基类)
示例代码:
#include<iostream>
#include<string>
using namespace std;
//继承语法:class 子类:继承方式 父类
//公共页面
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;
}
};
//C++页面
class CPP :public BasePage
{
public:
void content()
{
cout << "CPP学科视频" << endl;
}
};
void test01()
{
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main(){
test01();
system("pause");
return 0;
}
继承方式
继承方式和访问权限控制相同,分为三种:
1.公共继承(public)
2.保护继承(protected)
3.私有继承(private)
大致继承关系如下:
可以大致理解为继承方式为子类从父类手中拿过来的起步权限。
对于父类中的private变量,子类无法获取。
对于public和protected变量,如果以public方式继承,则维持原样,如果以protected方式继承,则在子类中都变为protected,如果以private方式继承,则都变为private。相当于继承方式是他们的上限,和他们自身在父类中的权限取min即可。
示例代码:
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base
{
public:
void func()
{
m_A = 10;// 父类中的公共和保护权限成员,依然可以访问。
m_B = 10;
//m_C = 10; //不可访问。
}
};
class Son2 :protected Base
{
public:
void func()
{
m_A = 100; //变为保护权限
m_B = 100;
//m_C = 100; 私有成员不可访问
}
};
class Son3 :private Base
{
public:
void func()
{
m_A = 100;
m_B = 100;
//m_C = 100; 私有成员不可访问
}
};
void test01()
{
Son1 s1;
s1.m_A = 10;
//s1.m_B = 10;//保护权限不可访问
Son2 s2;
//s2.m_A = 10; //保护权限不可访问
Son3 s3;
//s3.m_A = 100;//私有权限不可访问
}
int main(){
test01();
system("pause");
return 0;
}
继承中的对象模型
继承的子类继承父类的所有属性,包括私有的属性,私有属性只不过被编译器隐藏,即编译器不允许你访问,但人家确实继承下来了。
示例代码:
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base
{
public:
int m_D;
};
void test01()
{
//ans = 16
// 父类中所以非静态成员属性都会被子类继承下去
// 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下了。
cout << "size of Son = " << sizeof(Son) << endl;
}
int main(){
test01();
system("pause");
return 0;
}
之后采用开发人员命令提示符查看对象模型
在黑框中首先进入项目所在位置,之后输入以下代码:
c1 /d1 reportSingleClassLayouy类名 文件名
继承中的构造和析构顺序
当有继承时,顺序是先有父类构造,再子类构造,之后析构的话先有子类析构之后再有父类析构。
其实也挺好理解的,现有父亲再有儿子,然后按照栈式的方式取管理局部变量,所以先析构子类再析构父类。
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
cout << "这是Base的构造函数" << endl;
}
~Base()
{
cout << "这是Base的析构函数" << endl;
}
};
class Son :public Base
{
public:
Son()
{
cout << "这是Son的构造函数" << endl;
}
~Son()
{
cout << "这是Son的析构函数" << endl;
}
};
void test01()
{
Son son;
}
int main(){
test01();
system("pause");
return 0;
}
继承中同名成员的处理方式
补充一点:如果子类中出现和父类同名的成员函数,那么子类会隐藏父类中所有的同名函数(包括重载的)。如果想调用父类的函数,那么要么子类没有同名函数,直接调用,要么加作用域。
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "这是Base的func()函数调用" << endl;
}
void func(int a)
{
cout << "这是Base的func(int a)函数调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "这是Son的func()函数调用" << endl;
}
int m_A;
};
void test02()
{
Son son;
//直接调用为子类的函数
son.func();
//调用父类的同名函数需要加作用域
son.Base::func();
//如果子类中出现和父类同名的成员函数,那么子类会隐藏父类中所有的同名函数(包括重载的)
//如果想调用父类的函数,那么要么子类没有同名函数,直接调用,要么加作用域。
son.Base::func(100);
}
void test01()
{
Son son;
cout << "son.m_A = " << son.m_A << endl;
//如果通过子类对象访问父类同名变量的话,需要加作用域
cout << "Base.m_A = " << son.Base::m_A << endl;
test02();
}
int main(){
test01();
system("pause");
return 0;
}
继承中同名的静态成员处理方法
同名静态成员处理方式和非静态成员处理方式完全相同。
无同名,子类直接访问父类的成员。
有同名,子类直接访问的是子类,像访问父类的加作用域。
多继承
实际开发中不推荐使用,所以点到为止。
多态
多态的基本概念
示例代码如下:
#include<iostream>
#include<string>
using namespace std;
//动态多态满足条件:
//1.有继承关系
//2.子类重写父类的虚函数
//动态多态使用
//父类的指针或者引用 来指向子类的对象。
class Animal
{
public:
//函数加上virtual关键词,为虚函数
//虚函数在编译阶段不确定函数地址,在执行阶段才确定函数地址
virtual void speak()
{
cout << "动物再叫!!!" << endl;
}
};
class Sheep: public Animal
{
public:
//子类重写父类的虚函数
void speak()
{
cout << "Sheep在叫!!" << endl;
}
};
class Cat : public Animal
{
public:
//子类重写父类的虚函数
void speak()
{
cout << "Cat在叫!!" << endl;
}
};
//这里采用父类的指针或者引用接受子类的对象,可以多态。
void Speak(Animal &animal)
{
animal.speak();
}
void test01()
{
Sheep sheep;
Speak(sheep);
Cat cat;
Speak(cat);
}
int main(){
test01();
system("pause");
return 0;
}
关于运行时多态的内部原理如下:
具体的可以这样理解:
首先正常的成员函数是在代码区,每次类的对象调用时正常从代码区调用即可,所以是在编译时就确定了这个函数从哪调用,没办法更改。
而virtual关键字,使得函数称为虚函数,通过增加vfptr(virtual function pointer虚函数(表)指针)和vftable(virtual function table 虚函数表),使得多态称为可能。
当父类Animal中的speak增加了virtual函数指针后,Animal类中就多了一个vfptr的指针,指向了vftable,而vftable里存储的就是Animal的speak函数的地址。
之后子类Cat继承父类Animal,同样也继承了其中的vfptr和vftable,但当子类重写父类的speak方法时,他会在vftable中进行一个覆盖的操作,将原本继承过来的Animal::speak的地址替换成Cat::speak的地址,这样的话,每次运行从对象的虚函数表中取出函数地址进行执行,这样多态便运行了。
正常成员函数的对象模型:
虚函数的对象模型:
通过两者的对比,可以看出增加virtual关键词,对象模型中增加了vfptr的指针,和vftable。
子类不重写父类的虚函数时的对象模型:
子类重写父类的虚函数时的对象模型:
通过对比,发现子类重写父类虚函数时,将vftable中的地址替换成了自己函数的地址。
纯虚函数和抽象类
示例代码:
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,该类就为抽象类
//抽象类特点:
//1.无法实例化对象
//2.继承的子类要么重写纯虚函数,要么为抽象类。
virtual void func() = 0;
};
class Son :public Base
{
public:
//子类重写父类的纯虚函数
void func()
{
cout << "Son的func函数调用" << endl;
}
};
void test01()
{
//Base b; Error!抽象类无法实例化对象。
//new Base; 同理
//采用多态的形式调用,即父类的指针指向子类对象
Base* base = new Son;
base->func();
}
int main(){
test01();
system("pause");
return 0;
}
虚析构和纯虚析构
多态在使用时,若子类的属性中有用到堆区的内存,使用多态时,采用的是子类的匿名对象给父类的的引用或者指针,这样的话最终会导致无法调用到子类的析构函数,从而导致子类的堆区内存无法释放。
示例代码如下:
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
Base()
{
cout << "调用了Base的构造函数" << endl;
}
~Base()
{
cout << "调用了Base的析构函数" << endl;
}
//纯虚函数
virtual void func() = 0;
};
class Son :public Base
{
public:
//子类重写父类的纯虚函数
void func()
{
cout << "Son的func函数调用" << endl;
}
Son(int A)
{
m_A = new int(A);
cout << "调用了Son的构造函数,开辟了堆区空间" << endl;
}
~Son()
{
if (m_A != NULL)
{
delete m_A;
}
m_A = NULL;
cout << "调用了Son的析构函数,释放了m_A的堆区空间" << endl;
}
int* m_A;
};
void test01()
{
//用父类指针接受子类的匿名对象
Base* base = new Son(10);
base->func();
delete base;
//释放堆区空间
}
int main(){
test01();
system("pause");
return 0;
}
运行结果如下:
正常的继承的话,会先调用Base的构造,再调用Son的构造,之后调用Son的析构,最后调用Base的构造。但多态由于静态绑定的缘故,导致父类的指针无法调用子类的析构函数,所以就会导致子类的成员中开辟堆区内存泄露。
这时的解决办法就是将父类的析构函数也虚化即可,这就产生了虚析构和纯虚析构。
虚析构就是在析构函数的基础上加上了virtual,子类写上自己的析构函数,这样就会先调用子类的析构函数再调用父类的析构函数了。
结果如下:
代码改动如下:
class Base
{
public:
Base()
{
cout << "调用了Base的构造函数" << endl;
}
virtual ~Base()
{
cout << "调用了Base的析构函数" << endl;
}
//纯虚函数
virtual void func() = 0;
};
这也产生了纯虚析构,类似纯虚函数(实际上析构函数也只是特殊的函数罢了),但当写上去就会有很大的问题,因为纯虚析构意味着在当前这个类中没有具体实现,但是析构函数必须实现!!!
所以这只能在类外实现,具体示例如下:
代码如下:
class Base
{
public:
Base()
{
cout << "调用了Base的构造函数" << endl;
}
virtual ~Base() = 0;
//纯虚函数
virtual void func() = 0;
};
Base::~Base()
{
cout << "调用了Base的析构函数" << endl;
}
多态的实际应用
实际代码如下:
#include<iostream>
#include<string>
using namespace std;
class CPU
{
public:
virtual void calculate() = 0;
};
class InterCpu: public CPU
{
public:
void calculate()
{
cout << "因特尔的CPU正在工作" << endl;
}
};
class LenovoCpu : public CPU
{
public:
void calculate()
{
cout << "联想的CPU正在工作" << endl;
}
};
class Memory
{
public:
virtual void storage() = 0;
};
class InterMemoey : public Memory
{
public:
void storage()
{
cout << "因特尔的内存正在工作" << endl;
}
};
class LenovoMemoey : public Memory
{
public:
void storage()
{
cout << "联想的内存正在工作" << endl;
}
};
class VideoCard
{
public:
virtual void display() = 0;
};
class InterVideoCard : public VideoCard
{
public:
void display()
{
cout << "因特尔的显卡正在工作" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
public:
void display()
{
cout << "联想的显卡正在工作" << endl;
}
};
class Computer
{
public:
void Work()
{
cpu->calculate();
memory->storage();
vdcard->display();
}
Computer(CPU* cpu, Memory* memory, VideoCard* vdcard)
{
this->cpu = cpu;
this->memory = memory;
this->vdcard = vdcard;
}
~Computer()
{
if(cpu != NULL)
{
delete cpu;
}
if (memory != NULL)
{
delete memory;
}
if (vdcard != NULL)
{
delete vdcard;
}
cpu = NULL;
memory = NULL;
vdcard = NULL;
}
private:
CPU* cpu;
Memory* memory;
VideoCard* vdcard;
};
void test01()
{
Computer computer1(new InterCpu, new InterMemoey, new InterVideoCard);
computer1.Work();
cout << "-----------------------------------------" << endl;
Computer computer2(new LenovoCpu, new LenovoMemoey, new LenovoVideoCard);
computer2.Work();
}
int main(){
test01();
system("pause");
return 0;
}
文件操作
文件操作基本概念
写文件
注意: 文件打开方式可以配合使用,中间用|分隔。
代码实例:
//1.包含头文件
//2.创建流对象
ofstream ofs;
//3.打开文件,若文件不存在,则自动创建文件。
ofs.open("test.txt",ios::out);
//4.用输出流进行输出
ofs << "姓名:张三 " << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
//5.关闭文件
ofs.close();
读文件
示例代码如下:
//1.包含头文件
//2.创建流对象
ifstream ifs;
//3.打开文件
ifs.open("test.txt",ios::in);
//判断文件是否打开成功
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
//4.读数据-四种方法
//第一种
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, 1024))
{
cout << buf << endl;
}
//第三种
string buf;
while (getline(ifs,buf))
{
cout << buf << endl;
}
//第四种
char c;
while ( (c = ifs.get()) != EOF )
{
cout << c;
}
//5.关闭文件
ifs.close();
二进制写文件
示例代码如下:
//1.包含头文件
//2.常见对象
ofstream ofs;
//3.打开文件
ofs.open("Person.txt", ios::out | ios::binary);
//两步可以进行合并
//ofstream ofs("Person.txt", ios::out | ios::binary);
//4.写文件
Person p = { "张三", 18 };
ofs.write((const char * )&p, sizeof(Person));
//4.关闭文件
ofs.close();
二进制读文件
示例代码如下:
//1.包含头文件
//2.常见对象
ifstream ifs;
//3.打开文件
ifs.open("Person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败!" << endl;
return ;
}
//4.写文件
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名为:" << p.m_Name << ' ' << "年龄为" << p.m_Name << endl;
//4.关闭文件
ifs.close();