特性关系
- C语言面向过程
- C++面向过程 + 面向对象(封装 继承 多态)
- C++具备C语言的全部特性的基础上,并且支持更多新的特性
内存泄露
- 申请内存,没有释放
- 申请 malloc new
- 释放 free delete
- ProcessExplorer查看内存是否释放
代码移植
- 将生成的exe运行在别的平台,不因为缺乏环境而出错
- 使用软件 ProcessExplorer软件查找exe程序执行需要的dll等配置文件
- 或者在程序编译的时候整合对应的库文件,(属性->C/C++ -> 代码生成 ->运行库)
- 多线程 MT 静态编译 -> 链接库文件
- 多线程调试 MTH 静态编译 -> 链接库文件
- 多线程 DLL MD
- 多线程调试DLL MDT
pragma once
- 确保.h文件减少包含,减少编译错误
C++输入输出方式
- 输入 输入设备-> 输入流->scanf/cin -> 变量 cin>>a>>b;
- 输出 变量 -> printf/cout -> 输出流 -> 输出设备 cout<< a << endl;(endl输出换行)
- 不需要关注 数据类型 和 占位符号
命名空间
- std
- 关键字 namespace ,不同的命名空间使用相同的变量名字和函数
- 使用 命名空间:: 函数名 进行函数的调用
数据类型
- 布尔类型 bool
- 真 true
- 假 false
- bool flag = false;
- std::cout << boolalpha << flag << endl;//输出false
- 特性:C++随用随定义
引用
- 一般作为函数的参数,这样就不需要返回数据,直接取元素的数值
- 引用,避免了申请多个变量,大家对引用的使用都是针对同一个元素进行
函数默认值
- 缺省值 default value
- 函数使用默认值
- void fun(int i,int j = 7,int k = 8)
- 有默认值的参数必须在参数表的最右端
- 声明和定义同时给出默认值,有些编译器会报错。最好在函数的声明时给出默认值
void fun(int i,int j = 7,int k = 8){std::cout << "i = " << i << std::endl;std::cout << "j = " << j << std::endl;std::cout << "k = " << k << std::endl;
}int main(void)
{int i = 7;fun(i);return 0;
}
函数的重载
- 重载函数的参数的个数、参数的类型、参数的顺序三者中必须至少有一种不同,返回值不同不作为重载依据
- 重载函数的功能应该相近,不可以为了重载而重载
- main函数不可以重载
内存管理
- 栈区:由编译器自动分配释放 char型指针p
- 全局区(静态区)(static):存放全局变量、静态数据、常量 int x = 0;x属于全局变量
- 文字常量区:常量字符串就是存放在这里,程序结束之后,由系统释放 文字常量 Hello
- 程序代码区:存放函数体的二进制代码 整个代码的二进制数据
- 堆区:程序员申请/释放内存 由程序员申请释放
int x = 0;
int main(void)
{char *p = "Hello";x = 10;return 0;
}
- 申请内存 new 释放内存 delete
- int *p = new int; delete p;
- int *p = new int(10); delete p;
- int *p = new int[5]; delete []p;
- 内存申请有可能失败
- if (p == null) return False; p = null;最后让指针指向null
- 内存忘记释放会产生内存的泄露
- 如果需要大的内存 启用大地址
- 输入和输出都是相对于内存来说的
- 内存的特点是 有电则生,断电则往
文件的输出
常量const
- 关键字 修饰一个变量使其成为常量
- const int x = 0; 等价于 int const x = 0;
- const int * p = 0; 等价于 int const * p = 0;
- int * const p = 0;
- const int * const p = 0; 等价于 int const * const p = 0;
- const 修饰普通数据类型 const int x = 0; x=10;错误
- const修饰指针类型
- int const *p = &x; const修饰*,因此p可以变化,但是*p 不可以
- int * const p = &x;const 修饰p,因此*p可以变化 但是p不可以
//const 修饰指针类型int x=0,y=0;int const *p = &x;*p = 4;//错误p = &y;//正确
//const 修饰指针类型int x=0,y=0;int *const p = &x;*p = 4;//错误p = &y;//正确
- const修饰函数参数
- int max(const int x,const int y) 代码内部无法对其修改
- int max(int const *p,int *const q)
- #define 和const定义常量的区别?#define是预编译器,只进行字符串替换,const在编译的时候进行参数的检查
类和对象之间的关系
- 类 抽象
- 对象 具体
- class类关键字 类名 数据成员 成员函数
- 访问限定符号 public private protected
- 访问限定符号 可以多次使用
class Student{
public:char name[20];int gender;int age;void study(){std::cout << "learning!" << std::endl;}private:};
属性的封装
- 通过set和get的方式对成员变量进行封装
- 通过函数将private私有属性 进行返回
类的实例化
- 栈中定义 Car car; 操作系统会回收资源
- 堆中定义 Car *p = new Car(); 资源需要由程序员进行释放
类成员的访问
- 栈中定义 Car car; car.getWheelCount(); . 操作系统释放资源
- 堆中定义 Car *p = new Car(); p->getWheelCount(); -> delete p; p = nullptr;
- 相似于结构体(struct)
- 类的默认访问权限是private 结构体的默认访问权限是public
- struct结构体的使用和函数类似,也可以使用上述 堆和栈的方式进行定义
- Student *p = new Student[20];//定义20个学生变量,p[0].age = 10;控制第一个学生的age变量 使用delete[] p删除内存空间
#include <iostream>
#include <fstream>
#include <sstream>struct Student{
public:char name[20];int age;void study(){std::cout << "learning!" << std::endl;}private:int gender;int money;
};
int main(void)
{Student student{};student.study();Student *p = new Student[20];p[0].study();delete []p;p = nullptr;return 0;
}
类内定义和类外定义
- 类内定义 建议编译器使用内联方式进行编译
- 如图所示,普通函数需要进行函数的跳转;而内联函数将需要跳转的函数拷贝到指定的位置,避免了函数的拷贝,即避免了2和4的步骤,只剩下3的步骤,并且使用实参进行参数的替代
- 逻辑简单的函数才会使用内联的方式,如果函数很复杂,就按照普通函数处理
内联函数和宏函数
- 宏函数走预编译器 内联函数走编译器
- 宏函数没有参数类型的要求,不会进行参数检查
类内定义和类外定义
命名规范
- 类名单词首字母大写
- 文件名字 和 类名 相同
- 函数名第二个单词首字母大写,由单词或动宾短语构成
- 数据成员以(m_类型)作为前缀 int m_i m作为类内成员变量
对象的存储结构
- 类申明多个对象,每个对象占据不同的地址
- 类内的函数 只有一份,共同调用
构造函数
- 实例化对象的时候 自动调用
- 构造函数名字 和 类名相同,并且没有返回值
- 如果没有自定义构造函数,系统提供默认构造函数
- 构造函数可以使用 带参数的进行初始化
- 构造函数 可以重载
- 系统构造函数 Student(){} 也叫默认构造函数
- 有了自定义的构造函数,系统的构造函数还可以使用吗?不可以。如果自定义了带参数构造函数,使用的时候使用不带参数的默认构造函数会造成错误。因为,用户定义了构造函数,系统就不会再产生默认构造函数
初始化列表
- 使用Car():m_iWheelCount(4);进行初始化
- 初始化列表 伴随着构造函数,在构造函数的后面使用 : 进行对变量的赋值
- 即使是const修饰的变量 也可以使用初始化列表进行 赋值
#include <iostream>
#include <fstream>
#include <sstream>class Car{
public:Car():m_iWheelCount(4),m_iSpeed(0){};void getWheelCount(){std::cout << m_iWheelCount << std::endl;std::cout << m_iSpeed << std::endl;}
private:const int m_iWheelCount;int m_iSpeed;
};
int main(void)
{Car car;car.getWheelCount();return 0;
}
拷贝构造函数
- 对象初始化的两种方式
- 直接初始化 int x = 1024;
- 复制初始化 Student stu(stu1);
- 如上图所示 stu1 使用默认构造函数
- stu2 和 stu3 使用 拷贝构造函数
- 拷贝构造函数的特点
- 1,如果没有自定义的拷贝构造函数 则系统自动生成
- 2,当对象直接初始化 或者 复制初始化 时候 自动调用拷贝构造函数
- 3,当自定义了拷贝构造函数则系统 不再生成 默认的拷贝构造函数
#include <iostream>
#include <fstream>
#include <sstream>class Car{
public:Car(){};Car(const Car &car){ //拷贝构造初始化std::cout << "Car(const Car &car)" << std::endl;}
private:};
int main(void)
{Car car1; //直接初始化Car car2(car1); //拷贝构造初始化Car car3 = car1; //拷贝构造初始化return 0;
}
类的析构函数
- 析构函数名字 如 ~类名(){}
- 析构函数没有参数
- 析构函数没有返回数值
- 析构函数不可以重载
- 析构函数 销毁时候 自动执行
- 堆 和 栈 使用一致
#include <iostream>
#include <fstream>
#include <sstream>class Car{
public:Car(){std::cout << " 对象创建" << std::endl;};~Car(){std::cout << " 对象销毁 " << std::endl;}
private:};
int main(void)
{Car *car = new Car();delete car;car = nullptr;return 0;
}
- 什么时候需要自定义析构函数?比如在堆上申请了一大段内存空间,使用delete []p,进行资源的释放
#include <iostream>
#include <fstream>
#include <sstream>class Car{
public:Car(){m_pName = new char [20];std::cout << " 对象创建" << std::endl;};~Car(){delete[] m_pName;std::cout << " 对象销毁 " << std::endl;}
private:char *m_pName;
};
int main(void)
{Car *car = new Car();delete car;car = nullptr;return 0;
}
- 当函数参数是一个对象的时候,使用拷贝构造函数
对象成员
- 多个类之间互相使用调用,使用&得到对象
对象数组
#include <iostream>class Coordinate{
public:int m_iX;int m_iY;
};
int main(void)
{Coordinate coord[3]; //栈上申请内存coord[1].m_iX = 4;Coordinate *p = new Coordinate[3]; //堆上申请内存p[0].m_iX = 20;delete []p;//删除对象数组p = nullptr;return 0;
}
- 应该使用delete []p方式删除对象数组,不加上[] 只会清除一个对象
静态数据成员
- 静态数据成员 放在全局区
- 静态数据成员 没有对象仍然可以使用
- 静态数据成员 与 类 同生共死
- 静态成员函数 只可以使用 静态数据成员
- 非静态成员函数 可以使用 静态数据成员
#include <iostream>class Tank{
public:Tank(std::string code);~Tank();void attack();static int getCount();
private:std::string m_strCode;static int g_iCount;
};
int Tank::g_iCount = 0;Tank::Tank(std::string code) {m_strCode = code;g_iCount++;
}Tank::~Tank() {g_iCount--;
}
void Tank::attack() {if (g_iCount > 3){std::cout << "" << std::endl;} else{std::cout << "" << std::endl;}
}int Tank::getCount() {return g_iCount;
}int main(void)
{std::cout << Tank::getCount << std::endl;return 0;
}
对象指针成员
- 对象成员 如图左边所示,对象A的构造函数,需要先等对象B构造完成之后,才执行对象A的构造函数
- 对象成员指针 如图右边所示,对象B的指针不会提前执行对象B的构造函数,直接进行对象A的构造函数,对象B的构造函数 伴随其过程而产生
this指针
- this进行同名变量之间,使用this标记当前对象
- this指向这个类
- 每个对象都有自己的this指针,每个this指针都指向该对象的首地址
#include <iostream>class Student{
public:Student(std::string name);Student* getIt1(); //获取当前对象的指针Student& getIt2(); //获取当前对象void getIt3(Student **it); //获取当前对象指针void getIt4(Student &it); //通过传入引用 获取当前对象void printName();//打印名字void setName(std::string name);//设置名字
private:std::string m_strName;
};Student::Student(std::string name) {this->m_strName = name;
}Student * Student::getIt1() {return this;
}Student &Student::getIt2() {return *this;//返回this指针指向的对象
}void Student::getIt3(Student **it) {*it = this;
}void Student::getIt4(Student &it) {it = *this;
}void Student::printName() {std::cout << m_strName << std::endl;
}void Student::setName(std::string name) {m_strName = name;
}
int main() {//测试 getIt1
// Student stu("zhangsan");
// Student *p = stu.getIt1();
// p->printName();
// Student stu1("lisi");
// p = stu1.getIt1();
// p->printName();//测试 getIt2
// Student stu("zhangsan");
// stu.printName();
// Student &stu1 = stu.getIt2();
// stu1.setName("lisi");
// stu1.printName();//测试 getIt3 *无法拿到指针指向的数据 需要使用**
// Student stu("zhangsan");
// Student *p = nullptr;
// stu.printName();
// stu.getIt3(&p); //注意
// p->printName();//测试 getIt4Student stu("zhangsan");Student stu1("liis");stu.getIt4(stu1);stu1.printName();return 0;
}
常成员函数
- 在函数的名字后面添加 const,表示这个函数是常成员函数
- 在常成员函数中不能改变数据成员的数值
- const 也是函数重载的一个特性
- 只有常对象 才可以使用 常成员函数
- 常对象指针
- 常对象引用
#include <iostream>class Student{
public:Student(std::string name);void printInfo();//常成员函数//重载函数void printInfo() const;
private:std::string m_strName;
};void Student::printInfo() {std::cout << m_strName << std::endl;
}void Student::printInfo() const {//在常成员函数中不能改变数据成员的数值std::cout << m_strName << std::endl;
}int main() {Student s1("zhangsan");s1.printInfo();const Student s2("lisi");//1,常对象//修饰的时候 const 可以放在Student的前面 或者 后面s2.printInfo();Student *s3 = new Student("Merry");s3->printInfo();//2,常对象指针Student const *s4 = new Student("Jim");s4->printInfo();Student s5("Merry");Student &s6 = s5;Student const &s7 = s5;//常对象引用s6.printInfo();s7.printInfo();return 0;
}
深拷贝和浅拷贝
- 深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
- 假设B复制了A,修改A的时候,看B是否发生变化:
- 如果B跟着也变了,说明是浅拷贝(修改堆内存中的同一个值)
- 如果B没有改变,说明是深拷贝(修改堆内存中的不同的值)
- 深拷贝和浅拷贝的区别
友元函数
- friend 友元全局函数
- friend 类型 函数名字 (形参列表);
- 友元成员函数 friend 类型 类名::函数名字(形参表);
- 友元 不可以传递
- 友元 是单向的
- 私有private变量 通过申明为友元函数访问 私有变量
- friend友元函数 放在private public 都可以,不影响使用,但是一般放在类的最前边
友元全局函数 Time.h
#pragma onceclass Time{friend void printTime(Time &t);// 友元全局函数(当前类 + 引用)
// friend void Student::setTime(Time &t);//友元成员函数(类名 + 函数名字)
public:Time(int year,int month,int day){m_iYear = year;m_iMonth = month;m_iDay = day;};
private:int m_iYear;int m_iMonth;int m_iDay;
};
主函数
#include <iostream>
#include "lib/Time.h"void printTime(Time &t);int main() {Time t1(2015,8,7);printTime(t1);return 0;
}void printTime(Time &t){std::cout << t.m_iYear <<"-" << t.m_iMonth << "-" << t.m_iDay << std::endl;
}
友元成员函数
#include<iostream>
using namespace std;class Date;//对Date类的提前引用声明
class Time
{
public:Time(int, int, int);//声明构造函数void display(Date &);
private:int hour;int sec;int minute;
};class Date
{
public:Date(int, int, int);//声明构造函数friend void Time::display(Date &);
private:int mouth;int day;int year;
};Time::Time(int h, int m, int s)
{hour = h;minute = m;sec = s;
}void Time::display(Date &d)//display的作用是输出年月日,时分秒
{cout << d.mouth << "/" << d.day << "/" << d.year << endl;cout << hour << ":" << minute << ":" << sec << endl;
}Date::Date(int m, int d, int y)
{mouth = m;day = d;year = y;
}int main(void)
{Time t1(10, 13, 56);Date d1(4, 15, 2019);t1.display(d1);return 0;
}
- 友元 一般是在先前代码上进行使用,准确说是一种设计缺陷的弥补措施,尽量不要使用
继承
- 父类 叫做 基类
- 子类 叫做 派生类
- 父类是抽象,子类是在父类抽象的基础上进一步的细化;比如父类是人;子类是工人;子类的子类是 电工,逐步细化
三种派生方式
- 公有派生 公有继承 public
- 保护派生 保护继承 protected
- 私有派生 私有继承 private
多继承
- 一个类继承了多个类。比如童工类 继承 儿童类 的同时 也继承了 工人类
多重继承
- 类和类之间逐级继承。比如 技术工人类 继承 工人类;电焊工人类 继承 技术工人类;
菱形继承
- 类和类之间的继承关系构成了一个菱形
- 比如 工人类 和 农民类都继承自 人类;而农民工类 继承了工人类 和 农民类。四者之间构成了一个菱形
- 为了解决这个问题,需要使用虚继承 或者在 头文件引用 #pragma once
公有继承
- 如果未定义 默认使用 私有派生 私有继承 private
- 子类 派生自 父类。所以先执行 父类的构造函数 再 执行 子类的构造函数;销毁的时候,先执行 子类的析构函数 再执行 父类的析构函数
- 通过在父类 添加字段和方法,影响子类。而且派生自不同的父类,子类之间的影响是最低的
- 父类 的 private成员,子类无法访问
#include <iostream>class Worker{
public:Worker(std::string name,std::string code);~Worker();std::string getName() const;
protected:std::string m_strName;std::string m_strCode;
};Worker::Worker(std::string name,std::string code) {m_strName = name;m_strCode = code;
}Worker::~Worker() {}std::string Worker::getName() const {return m_strName;
}class Electrician : public Worker{
public:Electrician(std::string name,std::string code,size_t salary):Worker(name,code){m_iSalary = salary;};void printInfo() const{std::cout << m_strName <<" " << m_strCode <<" " << m_iSalary << std::endl;}protected:size_t m_iSalary;
};int main(void)
{Electrician *electrician = new Electrician("Jim","1234",1200000);std::cout << electrician->getName() << std::endl;electrician->printInfo();return 0;
}
三种继承方式
- public 公有继承方式 父类的public 派生到 子类的public;父类的protected 派生到 子类的protected;父类的private 对于子类不可见
- protected 保护继承方式 父类的public 派生到 子类的protected;父类的protected 派生到 子类的protected;父类的private 对于子类不可见
- private 私有继承方式 父类的public 派生到 子类的private;父类的protected 派生到 子类的privat;父类的private 对于子类不可见
同名隐藏
- 父类 和 子类 的成员函数的名字一致,参数无限制
- 成员函数的名字一样,但是所处的作用域不一样,比如处于protected public等
- 同名的时候 子类的函数名字 会 隐藏父类的名字,但是父类的名字也是可以使用的。比如 使用子类定义的对象.父类的名字::同名的函数的名字();
#include <iostream>class Worker{
public:Worker(std::string name,std::string code);~Worker();std::string getName() const;void printInfo(){std::cout << " 父类 " << std::endl;};
protected:std::string m_strName;std::string m_strCode;
};Worker::Worker(std::string name,std::string code) {m_strName = name;m_strCode = code;
}Worker::~Worker() {}std::string Worker::getName() const {return m_strName;
}class Electrician : public Worker{
public:Electrician(std::string name,std::string code,size_t salary):Worker(name,code){m_iSalary = salary;};void printInfo() const{std::cout << m_strName <<" " << m_strCode <<" " << m_iSalary << std::endl;std::cout << " 子类 " << std::endl;}protected:size_t m_iSalary;
};int main(void)
{Electrician *electrician = new Electrician("Jim","1234",1200000);std::cout << electrician->getName() << std::endl;electrician->printInfo();//子类electrician->Worker::printInfo();//父类return 0;
}
多重继承
- 电工类 继承自 技术工人类;技术工人类 继承自 工人类;
- 逐级继承,超过量层就是 多重继承
- 例子:m_strName来自Worker、m_strSkill 来自SkillWorker m_iSalary来自子类独有变量
#include <iostream>class Person{
public:Person(int age);~Person();int getAge();protected:int m_iAge;
};Person::Person(int age) {std::cout << "Person" << std::endl;m_iAge = age;
}Person::~Person() {std::cout << "~Person" << std::endl;
}int Person::getAge() {return m_iAge;
}
class Worker : public Person{
public:Worker(std::string name,int age);~Worker();std::string getName()const;
protected:std::string m_strName;
};
Worker::Worker(std::string name, int age):Person(age) {m_strName = name;std::cout << "Worker" << std::endl;
}Worker::~Worker() {std::cout << "~Worker" << std::endl;
}
std::string Worker::getName() const {return m_strName;
}class Electrician : public Worker{
public:Electrician(std::string name, int age, int salary): Worker(name,age){m_iSalary = salary;std::cout << "Electrician" << std::endl;};~Electrician(){std::cout << "~Electrician" << std::endl;};void printInfo() const;protected:int m_iSalary;
};
int main(void)
{Electrician *electrician = new Electrician("jim",30,233333);delete electrician;electrician = nullptr;return 0;
}// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
多继承
- 多继承是横向的,作为一个子类不仅仅拥有一个父类
- 例 在职研究生继承自研究生和职员两个类 两个类都是实体
- 例 水路两栖坦克继承自坦克和 能水中行驶 两个类 其中一个类表示能力、属性(多态)
- 继承的多个类 需要声明属性,并且使用 逗号相隔
#include <iostream>class Farmer{
public:Farmer(int age);~Farmer();int getAge();protected:int m_iAge;
};Farmer::Farmer(int age) {std::cout << "Farmer" << std::endl;m_iAge = age;
}Farmer::~Farmer() {std::cout << "~Farmer" << std::endl;
}int Farmer::getAge() {return m_iAge;
}
class Worker {
public:Worker(std::string name);~Worker();std::string getName()const;
protected:std::string m_strName;
};
Worker::Worker(std::string name) {m_strName = name;std::cout << "Worker" << std::endl;
}Worker::~Worker() {std::cout << "~Worker" << std::endl;
}
std::string Worker::getName() const {return m_strName;
}class MigrantWorker : public Worker,public Farmer{
public:MigrantWorker(std::string name, int age, int salary):Worker(name),Farmer(age){m_iSalary = salary;std::cout << "MigrantWorker" << std::endl;};~MigrantWorker(){std::cout << "~MigrantWorker" << std::endl;};void printInfo() const;protected:int m_iSalary;
};
void MigrantWorker::printInfo() const {std::cout << m_strName << " " << m_iAge << " " << m_iSalary << std::endl;
}
int main(void)
{MigrantWorker *p = new MigrantWorker("001",20,222222);p->printInfo();delete p;p = nullptr;return 0;
}
菱形继承
- 类和类之间的继承关系构成了一个菱形
- 比如 工人类 和 农民类都继承自 人类;而农民工类 继承了工人类 和 农民类。四者之间构成了一个菱形
- 为了解决这个问题,需要使用虚继承 或者在 头文件引用 #pragma once
- Person 人类
- Worker 工人
- Farmer 农民
- MigrantWorker 农民工
- 工人类 和 农民类 在自己的头文件中引用Person类,会多次引用 造成重定义 ,因此使用 #pragma once 或者#ifndef XXXX 换行 define XXXX 在代码的最末尾输入 #endif
- 农民工 继承的工人和农民都包含了 来自人类的冗余字段,即 人类类中的字段 拥有三份,分别来自 人类 工人 和农民。因此需要指定 继承的字段来自于哪个特定的类 使用虚继承
- class MigrantWorker : virtual public Worker 使用virtual,将相同的数据成员合并
- virtual 写在public前后 都可以
注意事项
- 结构体 也可以使用 继承机制
- 对于父类是基于属性的,不是抽象的实体,需要对其进行重写
- 面向对象的三大特征 封装 -> 继承 -> 多态
多态 虚函数理论
- 虚函数 virtual
- 如果不使用虚函数 virtual,子类都会使用父类的方法,不会使用自己重新定义的方法
#include <iostream>
#include <cmath>class Shape{
public:Shape(){std::cout << "Shape" << std::endl;};double calcArea(){std::cout << "Shape calcArea" << std::endl;};
};class Rectangle : public Shape{
public:Rectangle(double width,double height){m_dHeight = height;m_dWidth = width;std::cout << "Rectangle" << std::endl;}double caleArea(){std::cout << "Rectangle calcArea" << std::endl;return m_dWidth * m_dHeight;};protected:double m_dWidth;double m_dHeight;
};class Circle : public Shape{
public:Circle(double r){std::cout << "Circle" << std::endl;m_iR = r;};double caleArea(){std::cout << "Circle calcArea" << std::endl;return 3.14 * pow(m_iR,2);}protected:double m_iR;
};int main(void)
{Shape *p1 = new Rectangle(2.0,3.0);Shape *p2 = new Circle(2.0);p1->calcArea();p2->calcArea();return 0;
}
Shape.h
- 基类中 使用virtual,调用的时候,将执行子类的方法,不再使用父类的方法
#pragma onceclass Shape{
public:Shape();virtual double calcArea();
};Shape::Shape() {std::cout << "Shape" << std::endl;
}
double Shape::calcArea() {std::cout << "Shape calcArea" << std::endl;return -1;
}
Rectangle.h
#pragma once
#include "Shape.h"class Rectangle : public Shape{
public:Rectangle(double width,double height);virtual double calcArea();protected:double m_dWidth;double m_dHeight;
};Rectangle::Rectangle(double width, double height) {m_dHeight = height;m_dWidth = width;std::cout << "Rectangle" << std::endl;
}double Rectangle::calcArea() {std::cout << "Rectangle calcArea" << std::endl;return m_dWidth * m_dHeight;
}
Circle.h
#pragma once
#include "Shape.h"
#include <cmath>class Circle : public Shape{
public:Circle(double r);double calcArea();protected:double m_iR;
};Circle::Circle(double r) {std::cout << "Circle" << std::endl;m_iR = r;
}double Circle::calcArea(){std::cout << "Circle calcArea" << std::endl;return 3.14 * pow(m_iR,2);
}
main.c
#include <iostream>
#include "include/Shape.h"
#include "include/Rectangle.h"
#include "include/Circle.h"int main(void)
{Shape *p1 = new Rectangle(2.0,3.0);Shape *p2 = new Circle(2.0);p1->calcArea();p2->calcArea();return 0;
}
虚析构函数
- 父类指针 指向子类对象的副作用
- 虚析构函数,Shape *p1 = new Rectangle(2.0,3.0); Shape *p2 = new Circle(2.0); delete p1或者p2,删除他们指向的内存空间
- 但是 如果Rectangle或者CIrcle内部在堆上 申请的内存 无法通过父类指针 p1 p2 释放内部的内存空间,造成内存的泄露
- 需要定义 虚析构函数,子类定义自己的析构函数,否则自己申请的内存释放
- 定义析构函数之后 delete p1;就会执行子类的析构函数
#include <iostream>
#include <cmath>class Shape{
public:Shape();virtual ~Shape();virtual double calcArea();
};Shape::Shape() {std::cout << "Shape" << std::endl;
}Shape::~Shape() {std::cout << "~Shape" << std::endl;
}double Shape::calcArea() {std::cout << "Shape calcArea" << std::endl;return -1;
}class Rectangle : public Shape{
public:Rectangle(double width,double height);virtual double calcArea();~Rectangle();
protected:double m_dWidth;double m_dHeight;int *m_pArr;
};Rectangle::Rectangle(double width, double height) {m_dHeight = height;m_dWidth = width;std::cout << "Rectangle" << std::endl;m_pArr = new int[20];
}double Rectangle::calcArea() {std::cout << "Rectangle calcArea" << std::endl;return m_dWidth * m_dHeight;
}Rectangle::~Rectangle() {delete []m_pArr;std::cout << "~Rectangle" << std::endl;
}class Circle : public Shape{
public:Circle(double r);double calcArea();~Circle();
protected:double m_iR;
};Circle::~Circle() {std::cout << "~Circle" << std::endl;
}
Circle::Circle(double r) {std::cout << "Circle" << std::endl;m_iR = r;
}double Circle::calcArea(){std::cout << "Circle calcArea" << std::endl;return 3.14 * pow(m_iR,2);
}int main(void)
{Shape *p1 = new Rectangle(2.0,3.0);Shape *p2 = new Circle(2.0);p1->calcArea();p2->calcArea();delete p1;delete p2;p1 = nullptr;p2 = nullptr;return 0;
}
虚函数的实现原理
- 虚函数表只放 虚函数指针
- 虚析构函数 先调用子类的析构函数,如果父类的析构函数存在,再调用父类的析构函数。两者之间使用链表结构衔接
- 任何类型的指针 都占据 4个字节 用于验证虚函数 表指针的存在 (配合sizeof使用)
纯虚函数
- 基类 virtual double calcArea() = 0; 纯虚函数,没有函数体
- 当子类继承 基类的纯虚函数 需要对纯虚函数的内容进行 填写
- 含有纯虚函数的类叫做 抽象类
- Shape *p = new Shape(); //抽象类不可以实例化,抽象类不可以new出对象,因为对象无法表达内部的函数行为
- 纯虚函数本质,基于纯虚函数的子类也可以是子类,性质一致,直到可以形成对象
- 抽象类中只有纯虚函数呢?接口类
纯虚函数 抽象类
- 纯虚函数的改正 virtual double calcArea() = 0;
- 删除先前的基类对其 的定义
#include <iostream>
#include <cmath>class Shape{
public:Shape();virtual ~Shape();virtual double calcArea() = 0;
};Shape::Shape() {std::cout << "Shape" << std::endl;
}Shape::~Shape() {std::cout << "~Shape" << std::endl;
}class Rectangle : public Shape{
public:Rectangle(double width,double height);virtual double calcArea();~Rectangle();
protected:double m_dWidth;double m_dHeight;int *m_pArr;
};Rectangle::Rectangle(double width, double height) {m_dHeight = height;m_dWidth = width;std::cout << "Rectangle" << std::endl;m_pArr = new int[20];
}double Rectangle::calcArea() {std::cout << "Rectangle calcArea" << std::endl;return m_dWidth * m_dHeight;
}Rectangle::~Rectangle() {delete []m_pArr;std::cout << "~Rectangle" << std::endl;
}class Circle : public Shape{
public:Circle(double r);double calcArea();~Circle();
protected:double m_iR;
};Circle::~Circle() {std::cout << "~Circle" << std::endl;
}
Circle::Circle(double r) {std::cout << "Circle" << std::endl;m_iR = r;
}double Circle::calcArea(){std::cout << "Circle calcArea" << std::endl;return 3.14 * pow(m_iR,2);
}int main(void)
{Shape *p1 = new Rectangle(2.0,3.0);Shape *p2 = new Circle(2.0);p1->calcArea();p2->calcArea();delete p1;delete p2;p1 = nullptr;p2 = nullptr;return 0;
}
- 仅仅含有纯虚函数的类 叫做接口类
- 在接口类的基础上 实现部分纯虚函数,叫做抽象类 但是没有实现全部 不可以定义对象
- 只有全部实现父类 之路上所有的虚函数,才可以定义对象
- 抽象类 和 接口类的用途:
- 抽象类用于表达不完全的实体概念
- 接口类用于表达一种强制协议或者能力
C++ 数据类型装换
- reinterpret_cast<new_type>(expression) 将指针和引用的数据类型进行转换
- dynamic_cast<type>(expression) 父类和子类数据之间的转换 只有多态才可以采用
- static_cast<type>(expression) 普通数据类型的转换
- const_cast<type>(expression) const 和 非const之间的转换
#include <iostream>class Shape{
public:Shape();virtual ~Shape();virtual double calcArea();protected:double i_mNumber;
};class Circle : public Shape{
public:Circle(double r);~Circle();
protected:double m_iR;
};
int main(){double x = 2.5;//static_castint y = (int)x;//C语言int z = static_cast<int>(x);//C++//dynamic_castShape *p = new Circle(2.0);Circle *q = dynamic_cast<Circle *>(p);//reinterpret_castint r = 1000;int *w = reinterpret_cast<int *>(r);//将数值转化为地址 很危险//const int c = 0;
// int *o = &x;//Errorint *g = const_cast<int *>(&c);
}
RTTI
- C++中的RTTI机制
- RTTI的主要表现形式是 type_id
- RTTI的前提是 父类具有虚函数
异常处理
- 异常 程序运行期间出现的问题或者错误
- 异常处理:处理异常的方法在有可能发生异常的地方做出预见性的安排。异常处理提供了处理程序运行时出现的任何意外或者异常情况的方法
- C++ 处理异常基本思想:异常 的检测和处理进行分离
- 遇到异常
- 1,遇到错误,立即终止程序
- 2,返回一个表示错误的数值,同时保留错误的信息
- 3,针对错误情况再次分析处理
- throw
- try catch
- catch(...) 捕捉所有的异常
#include <iostream>void fun1(){std::cout << "fun1" << std::endl;throw "wer";
}void fun2(){try {fun1();} catch (int &e1){std::cout << "exception int:"<< e1 << std::endl;} catch (double &e2) {std::cout << "exception double:"<< e2 << std::endl;} catch (...) {std::cout << "全部捕捉!"<< std::endl;}std::cout << "fun2" << std::endl;
}
int main(){fun2();
}
- 异常的组织形式
- 平行机构:通过枚举来组织异常;
- 1,优点:语义相关的错误放在一起,条理清晰,适合一些简单的带有初始值的错误信息
- 2,缺点:无法详细的描述异常的类型
- 树形结构:通过异常的层次关系来组织异常
- 1,优点:可以在类的内部详细的描述错误的类型
- 2,缺点:容易造成类层次的无限扩充,反而不容易理解
#include <iostream>#define ERR_OK 0x0000
#define ERR_OK_MSG "OK"#define ERR_CARD 0x8101
#define ERR_CARD_MSG "No Card"#define ERR_UNKnown 0x8000
#define ERR_UNK_MSG "unknown error"enum MathException{ZeroException = 10,NegativeException = 20,NoSolutionException = 30
};class MyException{
public:MyException(int code,std::string msg);virtual int getErrorInfo(std::string &msg);protected:int m_iCode;std::string m_strMsg;
};
MyException::MyException(int code, std::string msg) {std::cout << "MyException" << std::endl;m_iCode = code;m_strMsg = msg;
}int MyException::getErrorInfo(std::string &msg) {std::cout << "getErrorInfo" << std::endl;msg = m_strMsg;return m_iCode;
}
class HardwareMyException : public MyException{
public:HardwareMyException(int code,std::string msg);virtual int getErrorInfo(std::string &msg);
};
HardwareMyException::HardwareMyException(int code, std::string msg) :MyException(code,msg) {std::cout << "HardwareMyException" << std::endl;
}
int HardwareMyException::getErrorInfo(std::string &msg) {std::cout << "HardwareMyException getErrorInfo" << std::endl;msg = m_strMsg;return m_iCode;
}void fun1(int flag){switch (flag) {case 0:throw MyException(ERR_CARD,ERR_CARD_MSG);break;case 1:throw MyException(ERR_UNKnown,ERR_UNK_MSG);break;case 2:throw HardwareMyException(ERR_OK,ERR_OK_MSG);break;}
}
int main(){try {fun1(1);} catch (MyException &e) {std::string msg;std::cout << e.getErrorInfo(msg) << std::endl;std::cout << msg << std::endl;}
}
运算符号重载
- 运算符重载的方式
- 1,成员函数重载
- 2,友元函数重载
- 一元运算符重载 负号(-) 递增运算符号(++) 递减运算符号(--)
- 二元运算符重载 加减号(+-) 索引符号[] 输出流 <<
- 不可以重载运算符号 . .* :: ?: {} sizeof
成员函数重载
#include<iostream>
using namespace std;class Binomial{
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator-();
private:int m_iA;int m_iB;
};
int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}
Binomial Binomial::operator-() {this->m_iA = -(this->m_iA);m_iB = -m_iB;return *this;//返回对象
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(0,0);bin2 = -bin1;std::cout << bin1.getA() << std::endl;std::cout << bin1.getB() << std::endl;std::cout << bin2.getA() << std::endl;std::cout << bin2.getB() << std::endl;return 0;
}
友元函数重载 this指针失效
#include<iostream>
using namespace std;class Binomial{friend Binomial operator-(Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();
private:int m_iA;int m_iB;
};int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}
//Binomial Binomial::operator-() {
// this->m_iA = -(this->m_iA);
// m_iB = -m_iB;
// return *this;//返回对象
//}
Binomial operator-(Binomial &bin){bin.m_iA = -bin.m_iA;bin.m_iB = -bin.m_iB;return bin;
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(0,0);bin2 = -bin1;std::cout << bin1.getA() << std::endl;std::cout << bin1.getB() << std::endl;std::cout << bin2.getA() << std::endl;std::cout << bin2.getB() << std::endl;return 0;
}
递增运算符重载
- ++J
- J++
#include<iostream>
using namespace std;class Binomial{friend Binomial operator-(Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++
private:int m_iA;int m_iB;
};int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}
//Binomial Binomial::operator-() {
// this->m_iA = -(this->m_iA);
// m_iB = -m_iB;
// return *this;//返回对象
//}
Binomial operator-(Binomial &bin){bin.m_iA = -bin.m_iA;bin.m_iB = -bin.m_iB;return bin;
}
Binomial Binomial::operator++() {m_iA++;m_iB++;return *this;
}
Binomial Binomial::operator++(int) {Binomial old(*this);m_iA++;m_iB++;return old;
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(0,0);
// bin2 = -bin1;++bin1;std::cout << bin1.getA() << std::endl;std::cout << bin1.getB() << std::endl;// std::cout << bin2.getA() << std::endl;
// std::cout << bin2.getB() << std::endl;return 0;
}
二元运算符重载
- +
- 成员函数重载
#include<iostream>
using namespace std;class Binomial{friend Binomial operator-(Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++Binomial operator+(Binomial &bin);
private:int m_iA;int m_iB;
};int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}
//Binomial Binomial::operator-() {
// this->m_iA = -(this->m_iA);
// m_iB = -m_iB;
// return *this;//返回对象
//}
Binomial operator-(Binomial &bin){bin.m_iA = -bin.m_iA;bin.m_iB = -bin.m_iB;return bin;
}
Binomial Binomial::operator++() {m_iA++;m_iB++;return *this;
}
Binomial Binomial::operator++(int) {Binomial old(*this);m_iA++;m_iB++;return old;
}Binomial Binomial::operator+(Binomial &bin) {Binomial temp(0,0);temp.m_iA += m_iA + bin.m_iA;temp.m_iB += m_iB + bin.m_iB;return temp;
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(8,-5);Binomial bin3(0,0);bin3 = bin1 + bin2;std::cout << bin1.getA() << std::endl;std::cout << bin1.getB() << std::endl;std::cout << bin2.getA() << std::endl;std::cout << bin2.getB() << std::endl;std::cout << bin3.getA() << std::endl;std::cout << bin3.getB() << std::endl;return 0;
}
//5
//3
//8
//-5
//13
//-2
友元类型
#include<iostream>
using namespace std;class Binomial{friend Binomial operator-(Binomial &bin);friend Binomial operator+(Binomial &bin1,Binomial &bin2);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++
// Binomial operator+(Binomial &bin);
private:int m_iA;int m_iB;
};int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}
//Binomial Binomial::operator-() {
// this->m_iA = -(this->m_iA);
// m_iB = -m_iB;
// return *this;//返回对象
//}
Binomial operator-(Binomial &bin){bin.m_iA = -bin.m_iA;bin.m_iB = -bin.m_iB;return bin;
}
Binomial Binomial::operator++() {m_iA++;m_iB++;return *this;
}
Binomial Binomial::operator++(int) {Binomial old(*this);m_iA++;m_iB++;return old;
}Binomial operator+(Binomial &bin1,Binomial &bin2){Binomial temp(0,0);temp.m_iA += bin1.m_iA+ bin2.m_iA;temp.m_iB += bin1.m_iB + bin2.m_iB;return temp;
}
//Binomial Binomial::operator+(Binomial &bin) {
// Binomial temp(0,0);
// temp.m_iA += m_iA + bin.m_iA;
// temp.m_iB += m_iB + bin.m_iB;
// return temp;
//}int main(void)
{Binomial bin1(5,3);Binomial bin2(8,-5);Binomial bin3(0,0);bin3 = bin1 + bin2;std::cout << bin1.getA() << std::endl;std::cout << bin1.getB() << std::endl;std::cout << bin2.getA() << std::endl;std::cout << bin2.getB() << std::endl;std::cout << bin3.getA() << std::endl;std::cout << bin3.getB() << std::endl;return 0;
}
//5
//3
//8
//-5
//13
//-2
重载 <<
- 只可以使用友元函数重载 ,不可以使用成员函数,因为第一个必须是cout类型
#include<ostream>
#include <iostream>class Binomial{friend Binomial operator-(Binomial &bin);friend Binomial operator+(Binomial &bin1,Binomial &bin2);friend std::ostream &operator << (std::ostream &out,Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++
// Binomial operator+(Binomial &bin);
private:int m_iA;int m_iB;
};int Binomial::getA() {return m_iA;
}
int Binomial::getB() {return m_iB;
}
Binomial::Binomial(int a, int b) {m_iA = a;m_iB = b;
}
Binomial::~Binomial() {}Binomial operator-(Binomial &bin){bin.m_iA = -bin.m_iA;bin.m_iB = -bin.m_iB;return bin;
}
Binomial Binomial::operator++() {m_iA++;m_iB++;return *this;
}
Binomial Binomial::operator++(int) {Binomial old(*this);m_iA++;m_iB++;return old;
}Binomial operator+(Binomial &bin1,Binomial &bin2){Binomial temp(0,0);temp.m_iA += bin1.m_iA+ bin2.m_iA;temp.m_iB += bin1.m_iB + bin2.m_iB;return temp;
}std::ostream &operator << (std::ostream &out,Binomial &bin){out << bin.m_iA << "x + (" << bin .m_iB << ")";return out;
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(8,-5);Binomial bin3(0,0);bin3 = bin1 + bin2;std::cout << bin1 << std::endl;//operator(cout , bin)std::cout << bin2 << std::endl;//operator(cout , bin2)std::cout << bin3 << std::endl;//operator(cout , bin3)return 0;
}
索引运算符重载
- 一元函数运算 5x + 3;5为a 3为b
- 预期通过 bin[0] 得到a,通过bin[1] 得到b
#include<ostream>
#include <iostream>class Binomial{friend Binomial operator-(Binomial &bin);friend Binomial operator+(Binomial &bin1,Binomial &bin2);friend std::ostream &operator << (std::ostream &out,Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++int operator[](unsigned int i);
// Binomial operator+(Binomial &bin);
private:int m_iA;int m_iB;
};int Binomial::operator[](unsigned int i) {if (i == 0){return m_iA;} else if (i == 1){return m_iB;} else{throw 1;}
}
int main(void)
{Binomial bin1(5,3);std::cout << bin1[0] << std::endl; //bin.operator[](0)std::cout << bin1[1] << std::endl; //bin.operator[](1)return 0;
}
==等于重载
#include<ostream>
#include <iostream>class Binomial{friend Binomial operator-(Binomial &bin);friend Binomial operator+(Binomial &bin1,Binomial &bin2);friend std::ostream &operator << (std::ostream &out,Binomial &bin);
public:Binomial(int a,int b);~Binomial();int getA();int getB();Binomial operator++();//前置 ++jBinomial operator++(int);//后置 j++int operator[](unsigned int i);bool operator==(Binomial &bin);
// Binomial operator+(Binomial &bin);
private:int m_iA;int m_iB;
};bool Binomial::operator==(Binomial &bin) {if (this->m_iA == bin.m_iA && this->m_iB == bin.m_iB){return true;}else{return false;}
}
int main(void)
{Binomial bin1(5,3);Binomial bin2(4,5);if (bin1 == bin2){std::cout << "A" << std::endl;}else{std::cout << "B" << std::endl;}return 0;
}
=运算符重载
注意事项
- 若一个运算的操作需要修改对象的状态,选择重载为成员函数,也可以使用友元函数修改对象的状态
- 当运算符号所需要的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能使用友元函数
- 当需要重载运算符号具有可交换性,选择重载为友元函数