前言
之前在黑马程序员的零基础课程里系统学习了c++,但是黑马程序员的课程还是太慢了,近四百节课让人很难受 ,基本上前一百节后就已经对C语言了解的差不多了(和c++差不多),两百节之后对c++也基本熟悉了。
总归还是太慢了,其中课程的很多内容都是重复出现的,为了方便我将课程中可以省略的重复步骤省略,将其余内容直接以代码加注释的方式展示出来,在其中还加入了《c++程序设计》机械工业出版社的黑皮书的内容进去作为补充。
c++是非常基础的语言,学习也比较容易,如果减去自己的代码编写时间单纯的看懂就可以的话其实不用一个星期就能基本了解。
但是还是需要多练习,很多程序bug等(有版本不适问题,操作系统不适问题)都在是实操中发现并处理。
简单的学习之后就可以应对csp考试,数据结构等课程了就。
我就是本来用java,考csp前学习回顾一下c++想用c++考。java岗的就业情况也比较饱和,会c++一定是很合算的事。
附上第四部分链接
c++速成(vs)短期学会在vs上使用c++编程(4)-CSDN博客
本期主要内容
面向对象编程的三大特点
封装,继承,多态
多继承,菱形继承
多态的优势,开闭原则
抽象类(JAVA同样适用)
案例*2
这一节主要就介绍了c++类的继承多态相关的一些内容,帮助更好理解程序开发过程中的开闭原则的意义和实现原理。用实例具象化了,抽象的面向对象编程思想。
还是主要为了帮助理解面向对象编程的思想及其意义。
下面代码可以直接运行,在vs或者dev环境运行,类对象声明都在前面,main中只有测试函数和注释。
其中多态和抽象类部分与JAVA的十分相似,对学习JAVA语言有很大的帮助。
代码
#include <iostream>
#include <string>
using namespace std;
class BasePage {
public:
void header() {
cout << "头部导航栏" << endl;
}
void fotter() {
cout << "底部导航栏" << endl;
}
void left() {
cout << "左侧的导航栏" << endl;
}
};
class Java :public BasePage {
public:
void content() {
cout << "Java内容" << endl;
}
};
class Cpp :public BasePage {
public:
void content() {
cout << "C++内容" << endl;
}
};
class Python :public BasePage {
public:
void content() {
cout << "Python内容" << endl;
}
};
void testBasePage() {
cout << "----------------------" << endl;
cout << "下载JAVA的页面" << endl;
Java ja;
ja.header();
ja.fotter();
ja.left();
ja.content();
cout << "----------------------" << endl;
cout << "下载C++的页面" << endl;
Cpp cp;
cp.header();
cp.fotter();
cp.left();
cp.content();
cout << "----------------------" << endl;
cout << "下载Python的页面" << endl;
Python py;
py.header();
py.fotter();
py.left();
py.content();
}
class Animal {
public:
Animal(){
cout << "animal的构造函数调用" << endl;
}
virtual void speak() {
cout << "动物在叫" << endl;
}
virtual ~Animal() = 0;
};
Animal::~Animal() {
cout << "Animal纯虚析构函数调用" << endl;
}//需要声明和实现 必须实现,应为就会直接调用了,会自动调用 有了纯虚析构函数也是抽象类
class Cat:public Animal {
public:
void speak() {
cout<<*M_name<< "maomao在叫" << endl;
}
Cat(string name) {
M_name = new string(name);
}
string* M_name;
~Cat() {
if (M_name != 0) {
cout << "释放name内存" << endl;
delete M_name;
M_name = NULL;
}//没有释放cat的name,怎么处理?
//父类指针在析构时候不会调用子类的析构函数 导致子类的堆区属性内存泄漏
// 将析构改成虚析构 加入virtual;
}
};
class Dog :public Animal {
public:
void speak() {
cout << "xiaoma在叫" << endl;
}
};
void doSpeak(Animal &an) {//animal &animal=cat; 允许父子之间的类型转换
an.speak();
}//不管怎么搞都会直接走animal 编译时就确定了地址 编译时多态
void testSpeak() {
Cat cat("tom");
doSpeak(cat);
//想要让他走cat的speak就得给speak加一个virtual 给他定义成虚函数
Dog dog;
doSpeak(dog);
}//这样就可以实现地址晚绑定
class Computer {//Calculator
public:
int getResult(string oper){
if (oper == "+") {
return num1 + num2;
}
else if(oper=="-") {
return num1 - num2;
}
else if (oper == "*") {
return num1 * num2;
}
else if (oper == "/") {
return num1 / num2;
}
}//需要扩展的情况下,需要修改源代码 在真实的开发中对扩展是开放的,对修改是关闭的(开闭原则)
//扩展不修改原来的代码,确保原来的代码稳定
//利用多态实现计算器
//先搞一个抽象类
int num1=2;
int num2=3;
private:
};
class computerPlus {
public:
virtual int getResult() {
return 0;
}
int num1=3;
int num2=2;
};
class Add:public computerPlus{
public:
int getResult() {
return num1 + num2;
}
};
class Sub :public computerPlus {
public:
int getResult() {
return num1 - num2;
}
};
class Mul :public computerPlus {
public:
int getResult() {
return num1 * num2;
}
};
class Div :public computerPlus {
public:
int getResult() {
return num1/num2;
}
};
void testCom() {
Computer com;
cout << "一般处理计算机" << com.getResult("*") << endl;
//多态使用条件 父类指针或者引用指向子类对象
//加法
computerPlus* count = new Add;
cout << "多态加法" << count->getResult() << endl;
//用完后要销毁
delete count;
//减法
computerPlus* count1 = new Sub;
cout << "多态减法" << count1->getResult() << endl;
//用完后要销毁
delete count1;
//乘法
computerPlus* count2 = new Mul;
cout << "多态乘法" << count2->getResult() << endl;
//用完后要销毁
delete count2;
//除法
computerPlus* count3 = new Div;
cout << "多态除法" << count3->getResult() << endl;
//用完后要销毁
delete count3;
}
class Base {
public:
virtual void func() = 0;//纯虚函数,抽象类
//不能Base b;new Base;不能实例化对象
};
class Son :public Base {
public:
void func() {
//不写这个函数就不行,必须写,为了保护原来的程序,确保是扩展
cout << "重写了 func纯虚函数,才可以变成普通类" << endl;
}
};
void testAbstract() {
Son s1;
s1.func();
Base* bas = new Son;
bas->func();
}
//案例
class Pao {
public:
//煮水
virtual void boil() = 0;
//冲泡
virtual void brew() = 0;
//倒入杯中
virtual void putInCup() = 0;
//加入作料
virtual void putSomeSeasoning() = 0;
void makeDrink() {
boil();
brew();
putInCup();
putSomeSeasoning();
}
};
class tea :public Pao
{
public:
//煮水
virtual void boil() {
cout << "煮开水" << endl;
}
//冲泡
virtual void brew() {
cout << "冲茶叶" << endl;
}
//倒入杯中
virtual void putInCup() {
cout << "倒进瓷杯" << endl;
}
//加入作料
virtual void putSomeSeasoning() {
cout << "方块冰糖" << endl;
}
};
class coofee:public Pao {
public:
//煮水
virtual void boil() {
cout << "煮开水" << endl;
}
//冲泡
virtual void brew() {
cout << "冲咖啡" << endl;
}
//倒入杯中
virtual void putInCup() {
cout << "倒进瓷杯" << endl;
}
//加入作料
virtual void putSomeSeasoning() {
cout << "牛奶" << endl;
}
};
void doDrink(Pao* pao) {
pao->makeDrink();
delete pao;
}
void testDrink() {
doDrink(new coofee);
cout << "上面咖啡下面茶,一辈子都不刷牙" << endl;
doDrink(new tea);
}
void testXiGou() {
Animal * animal = new Cat("xiaoma");
animal->speak();
//父类指针在析构时候不会调用子类的析构函数 导致子类的堆区属性内存泄漏
// 将析构改成虚析构 加入virtual;
delete animal;
}
//案例3 电脑
class Cpu {
public:
//抽象计算函数
virtual void caculate() = 0;
};
class Screen {
public:
//抽象显示函数
virtual void dispaly() = 0;
};
class Memory {
public:
//抽象存储函数
virtual void meo() = 0;
};
class myComputer {
public:
myComputer(Cpu*cpu,Memory*mem,Screen*scre) {
m_cpu = cpu;
m_mem = mem;
m_scre = scre;
}
void work() {
m_cpu->caculate();
m_mem->meo();
m_scre->dispaly();
}
~myComputer() {
if (m_cpu != NULL || m_mem != NULL || m_scre != NULL) {
delete m_cpu;
delete m_mem;
delete m_scre;
}
m_cpu = NULL;
m_mem = NULL;
m_scre = NULL;
}
private:
Cpu* m_cpu;
Memory* m_mem;
Screen* m_scre;
};
class IntelCpu :public Cpu{
public:
void caculate() {
cout << "cpu开始工作" << endl;
}
};
class IntelScreen :public Screen {
public:
void dispaly() {
cout << "Screen开始工作" << endl;
}
};
class IntelMemory :public Memory {
public:
void meo() {
cout << "memory开始工作" << endl;
}
};
//class Company {
//public:
//
//
//};
void testRunCom() {
Cpu* dispaly = new IntelCpu;
Memory* intelm = new IntelMemory;
Screen* intels = new IntelScreen;
myComputer* computer1 = new myComputer(dispaly, intelm, intels);
computer1->work();
delete computer1;
}
int main()
{
std::cout << "Hello World!\n";
//继承 面向对象三大特性之一 封装继承多态
//类之间存在 继承关系 就是属于。。。比如 动物类里包含猫狗人
//网页也会有公共的导航栏等 就是每个页面的公共类,导航栏就相当于是每个网页的父类 都会有导航栏
//普通实现页面需要不停地重复写
//
//
//直接使用继承的模式就可以不用重复写这些 公共部分
//类后面加:继承方式 父类名
//前面的是子类 子类也叫派生类 父类也叫基类
testBasePage();
//父类中私有的内容 不管用哪种继承方式都拿不到
//公共继承 父类的其他属性在子类中访问权限不变
//保护继承 除了私有访问不到,别的都变成保护内容
//私有继承 私有内容不能访问,别的变成子类私有内容(类内访问)
//
//这里非常好理解就不再使用程序来说明了
//继承中的对象模型 从父类继承过来的成员 哪些属于子类对象中呢?
//继承中的对象模型
//父类中所有非静态的成员属性都会被子类继承下去
//虽然隐藏了,但是还是在里面,子类中也拥有这些数据,只是隐藏了
//开发人员命令提示符工具 vs cmd 跳转盘 跳转路径 赋值路径 cd跳转路径
// 使用下面的代码
//cl /d1 reportSingleClassLayoutBasePage类名 文件名
//可以看到对象分布表格
//开发人员提示符工具 开始窗口可以找到
//继承中构造和析构的顺序 创建子类对象的时候
//继承中的构造和析构的顺序
/*继承中的构造和析构的顺序
* 由于比较简单好理解就不专门搞代码了
* 构造函数的顺序是 先父后子 有父亲才会有儿子
* 析构函数和构造函数顺序相反
*/
/*继承中同名的成员的处理方式
* 子类中的直接 使用.去运行就好 ,直接访问就是访问自身的
* 父类中的数据也在里面 只要在.后面加一个作用域 父类::
*/
/*同名 的成员函数的处理
* 子类依旧是直接调用
* 父类的同名成员函数 还是加作用域
* 如果有重载怎么区分 同名之后必须说明作用域,再满足重载
* 子类的成员函数会隐藏所有的父类的同名成员函数
* 就算参数不同也要 加作用域才会不隐藏
*
*/
/* 同名的静态成员处理方式
* 和非静态成员的情况是一样的 处理方式也是一样
* 静态成员可以直接通过类名访问 直接用::也可以 可以用子::父::静态成员变量
*
*/
//多继承语法 一个儿子可以认多个爹 class 子类: 继承方式 父类,继承方式 父类2
//一般开发中不建议使用 会出先重名的混乱问题
//不同的父类同名成员 需要加作用域来访问
/*多继承会给队友造成困难(这里直接空格就会出现*在行首)
*
*/
//菱形继承 钻石继承 两个类继承同一个父类 又被同一个子类继承
//会继承两份分别来自两个中间子类的最高父类的内容
/*然而其实只需要一份就好
* 案例用的是 动物 羊类 骆驼类 羊驼
* 要加入作用域来说明 继承的到底是 来自骆驼 还是来自羊,但是依旧会有空间的浪费 两份相同的内容
* 菱形继承会导致资源浪费
* 利用虚继承可以解决 菱形继承的问题
* 最上面的类就被称为 虚基类 在最上面的类名前面
加入vitural vitural public 父类
vbptr 虚基类指针 指向vbtable 虚基类表格 记录了偏移表格 指针指向的地址 是唯一的,不管从哪继承都只有一份,但是不同的子类继承出来的是不同的数据,继承的是两个指针
代码进行中 不要写菱形继承 这么麻烦,,队友不弄死你
*/
/*多态 函数重载和运算符重载都是 静态多态
* 动态多态 一般c++里面讲的都是动态多态
* 通过派生类和虚函数实现运行时多态
* 静态函数地址早就绑定号了 编译时就确定函数地址 编译时多态
* 动态函数地址确定的晚,运行阶段才能确定函数地址 运行时多态
*/
testSpeak();
//动态多态的满足条件
/*有继承关系
* 子类要重写 父类中的虚函数
* 重写和重载不一样 重载 参数不一样 函数名一样
* 重写 函数返回值 函数名,参数列表完全相同 子类的重写可以写vitural也可以不写 没啥区别 有个标识的作用
*
* 动态使用的说明
* 父类的引用 指针 指向子类的对象 这种就会有一种多态
*/
//加入vitural内存占用空间变成4字节 (是一个指针)
/*vfptr 虚函数表指针 或者虚函数指针 指向虚函数表
* vFtable 虚函数表 表内部记录的是虚函数的地址
* 成员函数的函数地址 (要加入作用域) 指针记录了虚函数的入口地址
* 子类的虚函数地址表 会把父类的东西复制下来,但是重写就会把父类的虚函数地址给替换成子子类重写的虚函数地址
* 指针 指向子类重写的内容而不是父类的内容了
* 动态多态靠指针的指向变化
* 动态就是指针和物理的位置顺序有区别
*/
testCom();
/*多态的优势
组织结构清晰
可读性强
一眼能看懂别人的代码说明写的好
对于前期的修改少,可扩展性强 对修改关闭
符合开闭原则*/
//父类中的一般不用,最后用的其实都是子类的重写,父类里面的虚函数其实可以不写
//写了也没啥屌用
// 抽象类
//
//virtual 返回值类型 函数名 (参数列表)=0;
/*这个玩意就是纯虚函数
* 当类中有了纯虚函数,这个类就是抽象类
* 抽象类不能实例化对象
* 子类必须重写抽象类里的纯虚函数,不然也是抽象类
*
*/
testAbstract();//多态就是为了,让函数的接口更加通用化
//案例 制作饮品
//多态实现 茶叶和咖啡
testDrink();
//虚析构和纯虚析构 子类中有属性开辟到堆区 父类指针无法在释放的时候调用到子类的析构代码
/*父类指针无法释放子类对象问题
都需要具体的函数实现
继续在cat dog类里面实现 代码不搞新的了
*/
//纯虚析构
//virtual~animal()=0
testXiGou();
//虚析构
//和纯虚析构都需要代码实现 必须有声明和实现 因为会自动调用
/* 纯虚析构 和虚析构的区别
有属性开辟到堆区一般才需要加入
解决父类指针释放子类对象 子类析构不运行的问题
纯析构的话类属于抽象类 不能实例化对象
*/
//子类中没有堆区数据,可以不写许析构和纯虚析构
//案例3 电脑组装
testRunCom();
//多态主要就是为了不修改 只能扩展 对接口进行扩展
//不同的情况执行不同的代码,不同的类 就是多态
return 0;
}