目录
1 内存分区模型
1.1 程序运行前
1.代码区
2.全局区
1.2 程序运行后
1.栈区
2.堆区
1.3 new操作符
2 引用
2.1 引用的基本使用
2.2 引用注意事项
2.3 引用做函数参数
2.4 引用做函数返回值
2.5 引用本质
2.6 常量引用
3 函数提高
3.1 函数默认参数
3.2 函数占位参数
3.3 函数重载
1.函数重载满足条件
2.注意事项
4 类和对象
4.1 封装
1.意义
2.案例1
3.访问权限
4.struct和class的区别
5.成员属性私有化
6.案例2
7.案例3
4.2 对象的初始化和清理
1.构造函数和析构函数
2.构造函数的分类及调用
3.拷贝构造函数调用时机
4.构造函数调用规则
5.深拷贝与浅拷贝
6.初始化列表
7.类对象作为类成员
8.静态成员
4.3 C++对象模型和this指针
1.成员变量和成员函数分开存储
2.this指针概念
3.空指针访问成员函数
4.const修饰成员函数
4.4 友元
1.全局函数做友元
2.类做友元
3.成员函数做友元
4.5 运算符重
1.加号运算符重载
2.左移运算符重载
3.递增运算符重载
4.赋值运算符重载
5.关系运算符重载
6.函数调用运算符重载
4.6 继承
1.继承基本语法
2.继承方式
3.继承中的对象模型
4.继承构造和析构顺序
5.继承同名成员处理方式
6.继承静态成员处理方式
7.多继承语法
8.菱形继承
4.7 多态
1.基本概念
2.多态案例--计算器类
3.纯虚函数和抽象类
4.多态案例--制作饮品
5.虚析构和纯虚析构
6.多态案例--电脑组装
5 文件操作
5.1 文本文件
1.写文件
2.读文件
5.2 二进制文件
1.写文件
2.读文件
内存分为四个区:代码区(存放函数体的二进制代码,由操作系统进行管理),全局区(存放全局变量和静态变量以及常量),栈区(由编译器自动分配释放,存放函数的参数值,局部变量等),堆区(由程序员分配和释放,若程序员不释放,程序结束后由操作系统收回)。
程序编译后,生产可执行exe程序,未执行该程序前分为两个区域:
存放CPU执行的机器指令,代码区是共享的(可多次执行程序)、只读的(防止程序被修改)。
局部变量和全局变量不在一个区域中,全部变量和静态变量在一个区域,全局常量和字符串常量在一个区域 ,局部常量和局部变量在一个区域。
全局区:全局变量、静态变量(static)、常量(包含字符串常量和const修饰的全局变量)。
不在全局区:局部变量、局部常量。
该区域数据在程序结束后由操作系统释放。
不能返回局部变量地址,因为栈区内容在程序执行后会释放
利用new在堆区开辟内存,将堆区存放的内容都地址给new,并保存在栈区
#include
using namespace std;
int *func()
{
// 利用new关键字开辟堆区
int * p = new int(10); // new返回地址,指针是局部变量,放在栈上,指针保存的数据放在堆区
return p;
}
int main()
{
// 在堆区开辟数据
int *p = func();
cout << *p << endl;
system("pause");
return 0;
}
delete释放堆区内存,利用new创建大数据,会返回该数据对应类型的指针。
#include
using namespace std;
int *func()
{
// 利用new关键字开辟堆区
int * p = new int(10); // new返回地址,指针是局部变量,放在栈上,指针保存的数据放在堆区
return p;
}
void test01() // 由程序员释放堆区数据
{
int *p = func();
cout << *p << endl;
delete p;
cout << *p << endl; // 此时堆区内容已经得到释放
}
// 在堆区开辟数组
void test02()
{
int * arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
// 释放数据
delete[] arr;
}
int main()
{
test02();
system("pause");
return 0;
}
引用就是给变量起别名,别名和原名可以一致
数据类型 &别名 = 原名
1.引用必须初始化
2.初始化之后不可改变
值传递不能用形参修饰实参,地址传递可以用形参修饰实参。
可以利用引用技术让形参修饰实参
#include
using namespace std;
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10,b = 20;
swap(a, b);
cout << a << endl;
cout << b << endl;
system("pause");
return 0;
}
引用可以作为函数的返回值。
1.不能返回局部变量的引用
2.若函数的返回值是引用,这个函数的调用可以作为左值
引用的本质在c++内部就是一个指针常量,推荐使用引用操作
常量引用用来修饰形参,防止误操作。否则在引用中,形参的修改会影响实参
#include
using namespace std;
void showValue(const int &val)
{
cout << "val=" << val << endl;
}
int main()
{
// 常量引用
int a = 10;
const int & ref = 10; // 引用必须引用一块合法空间,const使ref不可修改
int b = 100;
showValue(b);
system("pause");
return 0;
}
函数的形参列表中可以有默认值,可以修改值
1.若某位置已有默认参数,之后位置都必须有默认值
2.函数声明和实现中只能一处有默认值
函数的形参列表中可以有占位参数,在调用时必须填补该位置。占位参数可以有默认参数
返回类型 函数名(数据类型)
函数名可以相同以提高复用性
同一作用域下;函数名称相同;函数参数类型不同(或个数不同或顺序不同),其中函数的返回值不能做函数重载的条件。
引用作为重载条件时,const添加与否在调用时有不同影响。
函数重载遇到默认参数,出现二义性。
面向对象的三大特征:封装、继承、多态
具有相同性质的对象,可以抽象称为类
将属性和行为作为一个整体,表现生活中的事物
#include
using namespace std;
const double PI = 3.14;
// 设计一个圆类,求圆的周长
class Circle
{
// 访问权限
// 公共权限
public:
// 属性
int m_r; // 半径
// 行为
// 获取圆的周长
double calculateZC()
{
return 2 * PI*m_r;
}
};
int main()
{
Circle c1; // 通过类创建具体的对象
c1.m_r = 10; // 给属性赋值
cout << "圆的周长为:" << c1.calculateZC() << endl;
system("pause");
return 0;
}
自编
#include
#include
using namespace std;
// 案例:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student
{
public:
// 属性
string name;
string number;
// 行为
void writeStudent()
{
cout << "输入姓名:" << endl;
cin >> name;
cout << "输入学号:" << endl;
cin >> number;
}
void showStudent()
{
cout << "学生的姓名:" << name << "\t";
cout << "学生的学号:" << number << endl;
}
};
int main()
{
Student s1;
s1.writeStudent();
s1.showStudent();
system("pause");
return 0;
}
类中的属性和行为统称为成员,属性称为成员属性或成员变量,行为称为成员函数或成员方法。
公共权限:public。类内外皆可访问
保护权限:protected。类内可以访问,类外不可访问
私有权限:private。类内可以访问,类外不可访问
唯一的区别就是默认的访问权限不同,struct默认权限为公共,class默认权限为私有
可以控制读写权限;对于写权限可以检测数据有效性
#include
#include
using namespace std;
// 案例2:设计立方体,求出立方体的面积和体积
// 分别利用全局函数和成员函数判断两个立方体是否相等
class Cube
{
private:
// 属性
int length;
int width;
int height;
public:
// 行为
// 设置及获取长度
void setLength(int c_l)
{
length = c_l;
}
int showLength()
{
return length;
}
// 设置及获取宽度
void setWidth(int c_w)
{
width = c_w;
}
int showWidth()
{
return width;
}
// 设置及获取高度
void setHeight(int c_h)
{
height = c_h;
}
int showHeight()
{
return height;
}
// 获取立方体面积
int calculateS()
{
return 2 * length * width + 2 * length * height + 2 * width * height;
}
// 获取立方体体积
int calculateV()
{
return length * width * height;
}
// 利用成员函数判断两个立方体是否相等
bool isSame2(Cube &c)
{
if (length == c.showLength() && width == c.showWidth() && height == c.showHeight())
{
return true;
}
else
{
return false;
}
}
};
// 利用全局函数判断,两个立方体是否相等
bool isSame(Cube &c1, Cube &c2)
{
if (c1.showLength() == c2.showLength() && c1.showWidth() == c2.showWidth() && c1.showHeight() == c2.showHeight()){
return true;
}
}
int main()
{
Cube c1;
c1.setLength(10);
c1.setWidth(10);
c1.setHeight(10);
cout << "长方体的面积:" << c1.calculateS() << endl;
cout << "长方体的体积:" << c1.calculateV() << endl;
Cube c2;
c2.setLength(10);
c2.setWidth(10);
c2.setHeight(10);
bool ret = isSame(c1, c2);
if (ret)
{
cout << "c1和c2相等" << endl;
}
else
{
cout << "c1和c2不相等" << endl;
}
ret = c1.isSame2(c2);
if (ret)
{
cout << "成员函数中c1和c2相等" << endl;
}
else
{
cout << "成员函数中c1和c2不相等" << endl;
}
system("pause");
return 0;
}
#include
#include
using namespace std;
// 案例3:设计一个圆形类和一个点类,计算点和圆的关系
// 点类
class Point
{
public:
// 设置及获取x坐标
void setX(int x)
{
x_point = x;
}
int getX()
{
return x_point;
}
// 设置及获取y坐标
void setY(int y)
{
y_point = y;
}
int getY()
{
return y_point;
}
private:
int x_point;
int y_point;
};
// 圆类
class Circle
{
public:
// 设置及获取半径
void setRadius(int r)
{
radius = r;
}
int getRadius()
{
return radius;
}
// 设置及获取圆心
void setCentre(Point c)
{
centre = c;
}
Point getCentre()
{
return centre;
}
private:
int radius; // 半径
Point centre; // 圆心
};
// 判断点和圆的关系
void isInCircle(Circle &c, Point &p)
{
// 计算两点间距平方
int distance =
(c.getCentre().getX() - p.getX())*(c.getCentre().getX() - p.getX()) +
(c.getCentre().getY() - p.getY())*(c.getCentre().getY() - p.getY());
// 计算半径的平方
int r_2 = c.getRadius()*c.getRadius();
if (distance == r_2)
{
cout << "点在圆上" << endl;
}
else if (distance < r_2)
{
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆外" << endl;
}
}
int main()
{
// 创建圆
Circle c1;
c1.setRadius(10); // 半径
Point c_c;
c_c.setX(10);
c_c.setY(0);
c1.setCentre(c_c); // 圆心
// 创建点
Point p;
p.setX(10);
p.setY(10);
isInCircle(c1, p);
system("pause");
return 0;
}
通常将类放到其他文件中
(1)point.h
#pragma once
#include
using namespace std;
// 点类 仅需声明
class Point
{
public:
void setX(int x);
int getX();
void setY(int y);
int getY();
private:
int x_point;
int y_point;
};
(2)point.cpp
#include"point.h"
// 点类
// 设置及获取x坐标
void Point::setX(int x)
{
x_point = x;
}
int Point::getX()
{
return x_point;
}
// 设置及获取y坐标
void Point::setY(int y)
{
y_point = y;
}
int Point::getY()
{
return y_point;
}
(3)circle.h
#pragma once
#include
using namespace std;
// 点类 仅需声明
class Point
{
public:
void setX(int x);
int getX();
void setY(int y);
int getY();
private:
int x_point;
int y_point;
};
(4)circle.cpp
#include"circle.h"
// 圆类
// 设置及获取半径
void Circle::setRadius(int r)
{
radius = r;
}
int Circle::getRadius()
{
return radius;
}
// 设置及获取圆心
void Circle::setCentre(Point c)
{
centre = c;
}
Point Circle::getCentre()
{
return centre;
}
(5)main.cpp
#include
#include
#include"circle.h"
#include"point.h"
using namespace std;
// 案例3:设计一个圆形类和一个点类,计算点和圆的关系
// 判断点和圆的关系
void isInCircle(Circle &c, Point &p)
{
// 计算两点间距平方
int distance =
(c.getCentre().getX() - p.getX())*(c.getCentre().getX() - p.getX()) +
(c.getCentre().getY() - p.getY())*(c.getCentre().getY() - p.getY());
// 计算半径的平方
int r_2 = c.getRadius()*c.getRadius();
if (distance == r_2)
{
cout << "点在圆上" << endl;
}
else if (distance < r_2)
{
cout << "点在圆内" << endl;
}
else
{
cout << "点在圆外" << endl;
}
}
int main()
{
// 创建圆
Circle c1;
c1.setRadius(10); // 半径
Point c_c;
c_c.setX(10);
c_c.setY(0);
c1.setCentre(c_c); // 圆心
// 创建点
Point p;
p.setX(10);
p.setY(10);
isInCircle(c1, p);
system("pause");
return 0;
}
构造函数:类名(){},作用在创建对象时为对象的成员属性赋值,由编译器自动调用。
没有返回值;函数名与类名相同;构造函数可以有参数,可以发生重载;自动调用构造
析构函数:~类名(){},作用在对象销毁前系统自动调用。没有返回值;函数名与类名相同,在名称前加上符号;不可以有参数,不可重载;自动调用析构
#include
using namespace std;
#include
// 对象的初始化和清理
class Person
{
public:
// 1.构造函数初始化
Person()
{
cout << "Person构造函数的调用" << endl;
}
// 2.析构函数清理,在对象执行完成后调用
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
system("pause");
return 0;
}
构造函数分类:按参数类型分为有参构造和无参构造;按类型分为普通构造和拷贝构造
#include
using namespace std;
#include
// 构造函数的分类及调用
// 分类
// 按参数分类 无参(默认)和有参构造
// 按类型分类 普通和拷贝构造
class Person
{
public:
// 无参构造函数
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
// 有参构造函数
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
// 拷贝构造函数
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
// 析构函数
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int age;
};
// 调用
void test()
{
// 1.括号法
Person p1; // 默认调用法
Person p2(10); // 调用有参构造函数
Person p3(p2); // 调用拷贝构造函数
cout << "\n" << endl;
// 调用默认构造函数时不加()
// 2.显示法
Person p4;
Person p5 = Person(10); // 匿名对象,当前行执行结束后,系统会立即回收匿名对象
Person p6 = Person(p5);
cout << "\n" << endl;
// 不要利用拷贝构造函数 初始化匿名对象
// 3.隐式转换法
Person p7 = 10; // 等同于Person p4 = Person(10)
Person p8 = p7;
cout << "\n" << endl;
}
int main()
{
test(); // p1被释放前调用析构函数
system("pause");
return 0;
}
使用时机:使用一个已经创建完毕的对象来初始化一个新对象;值传递的方式给函数参数传值;以值方式返回局部对象
#include
using namespace std;
#include
// 拷贝函数调用时机
class Person
{
public:
Person()
{
cout << "Person默认构造函数的调用" << endl;
}
Person(int age)
{
p_age = age;
cout << "Person有参构造函数的调用" << endl;
}
Person(const Person & p)
{
p_age = p.p_age;
cout << "Person拷贝函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int p_age;
};
// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
}
// 2.值传递方式给函数参数传值
void work(Person p)
{
}
void test02()
{
Person p;
work(p);
}
// 3.值方式返回局部对象
Person work2()
{
Person p1;
return p1;
}
void test03()
{
Person p = work2();
}
int main()
{
test03();
system("pause");
return 0;
}
默认情况下, c++编译器至少给一个类添加以下三个函数:
默认构造函数、默认解析函数、默认拷贝构造函数(值拷贝 )。
若用户定义有参构造函数,不在提供默认无参构造函数,提供默认拷贝构造函数;
若用户定义拷贝构造函数,不在提供其他构造函数。
浅拷贝:简单的赋值拷贝操作。可能会带来堆区内存重复释放的问题。
深拷贝:在堆区重新申请空间,进行拷贝操作
Person(const Person &p)
{
p_age = p.p_age;
// 深拷贝操作
p_height = new int(*p.p_height);
}
初始化类中的属性
// 构造函数法初始化
Person(int a, int b, int c)
{
p_a = a;
p_b = b;
p_c = c;
}
int p_a;
int p_b;
int p_c;
// 初始化列表法初始化
Person(int a,int b, int c) :p_a(a), p_b(b), p_c(c)
{
}
int p_a;
int p_b;
int p_c;
c++中类的成员可以上另一个类的对象,称为对象成员
当其他类对象作为本类成员,构造先构造类对象,再构造自身;析构与构造相反。
在成员变量和成员函数前加上关键字static,称为静态成员。
静态成员变量:
·所有对象共享同一数据
·编译阶段分配内存
·类内声明,类外初始化
class Person
{
public:
static int m_a; // 类内声明
};
int Person::m_a = 100; // 类外初始化
有两种访问方式:通过对象访问或通过类名
void test()
{
// 通过对象访问
Person p;
cout << p.m_a << endl;
// 通过类名访问
cout << Person::m_a << endl;
}
静态成员变量有访问权限
静态成员函数:
·两种访问方式,所有对象共享同一函数
class Person
{
public:
static void func()
{
cout << "static void func调用" << endl;
}
};
void test()
{
// 通过对象访问
Person p;
p.func();
// 通过类名访问
Person::func();
}
·静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_a = 100;
cout << "static void func调用" << endl;
}
static int m_a;
};
int Person::m_a = 10;
静态成员函数也有访问权限
只有非静态成员变量才属于类的对象。
空对象占用内存空间为1字节,每个空对象有单独的内存地址
this指向被调用的成员函数所属的对象。
this无需定义,可直接使用。
当形参和成员变量同名,可以this区分。
在类的非静态成员函数中返回对象本身,可用return *this
空指针可以访问成员函数,若用到this指针,需加以判断保证代码的健壮性。
·成员函数加const称为常函数
·常函数内不可修改成员属性
·成员数学声明时加关键字mutable后,在常函数中可以修改
·this指针本质上是指针常量,其指向不可修改
class Person
{
public:
void showPerson() const // 本质上是修饰this指向,让其指向的值不可修改
{
m_b = 100;
}
int m_a;
mutable int m_b; // 添加关键字后,可以修改指针指向的值
};
·声明对象前加const称为常对象,只能调用常函数
void test()
{
const Person p; // 常对象
p.showPerson(); // 常对象只能调用常函数
}
让类外特殊函数访问私有属性
全局函数访问私有属性时,将函数在类中提前声明并添加关键字
class Building
{
friend void Accessible(Building &building);
public:
Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
public:
string m_sittingroom;
private:
string m_bedroom;
};
将访问类的声明加入被访问类,并加上关键字
friend class Building;
// 类做友元
class Building;
class Good
{
public:
Good();
void visit(); // 让参观函数访问building中属性
Building * building;
};
class Building
{
friend class Good;
public:
Building(); // 类外实现初始化
public:
string m_sittingroom;
private:
string m_bedroom;
};
Good::Good()
{
// 创建一个building对象
building = new Building; // 在堆区创建
}
// 类外成员函数
Building::Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
// 类外实现
void Good::visit()
{
cout << "Good正在访问:" << building->m_sittingroom << endl;
cout << "Good正在访问:" << building->m_bedroom << endl;
}
void test()
{
Good gg;
gg.visit();
}
在类中增加声明,加上关键字和作用域
friend void Good::visit();
对运算符重新定义,以适应不同的数据类型
实现两个自定义数据类型相加,可通过成员函数与全局函数重载+号。
// 1.成员函数
class Person
{
public:
Person operator+(Person &p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
int m_a;
int m_b;
};
// 2.全局函数
Person operator+(Person &p1,Person &p2)
{
Person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
void test()
{
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 10;
p2.m_b = 10;
Person p3 = p1 + p2;
cout << p3.m_a << p3.m_b << endl;
}
运算符重载也可以发生函数重载。
可以输出自定义的数据类型。不利用成员函数重载<<运算符。
// 全局函数重载左移运算符
class Person
{
public:
int m_a;
int m_b;
};
ostream & operator<<(ostream &cout, Person &p)
{
cout << p.m_a << p.m_b;
return cout;
}
void test()
{
Person p;
p.m_a = 10;
p.m_b = 10;
cout << p << endl;
}
out属于ostream(输出流)
实现自己的整型数据
// 重载递增运算符
class MyInt
{
friend ostream& operator << (ostream &cout, MyInt my_int);
public:
MyInt()
{
m_num = 0;
}
// 重载前置++运算符
MyInt& operator ++() // 返回引用是为了对一个数据进行递增
{
m_num++;
return *this;
}
// 重载后置++运算符
MyInt operator ++(int) // int占位参数 返回值
{
// 先记录结果
MyInt temp = *this;
// 再递增
m_num++;
// 最后输出
return temp;
}
private:
int m_num;
};
对属性的值进行拷贝
// 重载赋值运算符
class Person
{
public:
Person(int age)
{
m_age = new int(age); // 在堆区开创
}
~Person() // 浅拷贝使堆区内存重复释放
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
}
// 重载赋值运算符
Person & operator=(Person &p)
{
// 编译器提供浅拷贝
// 先判断是否有属性在堆区
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
// 深拷贝,在堆区开创一块新空间
m_age = new int(*p.m_age);
// 返回对象自身
return *this;
}
int *m_age;
};
让两个自定义类型对象进行对比
bool operator==(Person &p)
{
if (this->m_name == p.m_name)
{
return true;
}
return false;
}
仿函数即函数调用运算符重载。仿函数没有固定写法
void operator()(string test)
{
cout << test << endl;
}
匿名函数对象:匿名对象()()
面向对象三大特性之一
减少重复代码:class 子类 : 继承方式 父类
子类也称为派生类,父类也称为基类
公共继承、保护继承、私有继承
子类将继承父类所有非静态成员属性,父类中私有属性被隐藏。
查看对象模型布局:a.利用开发人员命令提示工具查看对象模型;b.跳转盘符;c.跳转文件路径 cd;d.查看命名:c1/d1 reportSingleClassLayout类名 文件名
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
构造函数顺序:先构造父类,再构造子类;析构函数与之相反。
·子类与父类出现同名成员,访问子类同名成员时直接访问即可,访问父类同名成员时加作用域(子类.父类::同名成员)
·若子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数。
静态成员(类内声明,类外初始化)处理与非静态成员方式一致。
C++可以一个类继承多个类,在实际开发中不建议使用
两个子类继承同一个父类的同时,某个类继承两个子类,称为菱形或钻石继承。
// 菱形继承
class Animal
{
public:
int m_age;
};
// 利用虚继承解决菱形继承的问题
// 父类称为虚基类
// 羊类
class Sheep:virtual public Animal{};
// 驼类
class Tuo:virtual public Animal{};
// 羊驼类
class Sheep_Tuo:public Sheep,public Tuo{};
void test()
{
Sheep_Tuo st;
// 菱形继承时,两个父类具有相同数据,需加以作用域区分
st.Sheep::m_age = 18;
st.Tuo::m_age = 28;
cout << st.Sheep::m_age << endl;
cout << st.Tuo::m_age << endl;
}
多态是C++面向对象三大特性之一
静态多态:函数重载和运算符重载;在编译阶段确定函数地址
动态多态:派生类和虚函数实现运行时多态;在运行阶段确定函数地址
·满足条件:有继承关系;子类要重写(函数返回值类型 函数名 参数列表完全相同)父类的虚函数。
·使用:父类的指针或引用指向子类对象
// 多态
class Animal
{
public:
// 虚函数
virtual void speak()
{
cout << "动物说话" << endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout << "猫说话" << endl;
}
};
class Dog : public Animal
{
public:
void speak()
{
cout << "狗说话" << endl;
}
};
// 地址早绑定,在编译阶段确定地址
// 修改为晚绑定
void doSpeak(Animal &animal)
{
animal.speak();
}
void test()
{
Cat c;
Dog d;
doSpeak(c);
doSpeak(d);
}
多态优点:可读性强、代码组织结构清晰、利用前后期扩展及维护。
// 利用多态实现计算器
// 实现计算器的抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
// 加法计算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1+ m_Num2;
}
};
// 减法计算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
// 乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test()
{
// 多态使用
AbstractCalculator * abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
// 堆区数据使用后销毁
delete abc;
}
父类中虚函数无作用,改为纯虚函数
virtual 返回值类型 函数名(参数列表) = 0;
当类中有纯虚函数,称为抽象类:无法实例化对象(无法在任何区用抽象类创建对象),子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
// 利用多态实现饮品制作
class AbstractDrinking
{
public:
// 煮水
virtual void Boil() = 0;
// 冲泡
virtual void Brew() = 0;
// 倒入杯中
virtual void PourInCup() = 0;
// 加辅料
virtual void PutSometing() = 0;
// 制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSometing();
}
};
// 制作咖啡
class Coffee :public AbstractDrinking
{
public:
virtual void Boil() {
cout << "煮水中" << endl;
}
virtual void Brew() {
cout << "冲泡咖啡中" << endl;
}
virtual void PourInCup() {
cout << "倒入咖啡杯中" << endl;
}
virtual void PutSometing() {
cout << "加牛奶中" << endl;
}
};
// 制作茶叶
class Tea :public AbstractDrinking
{
public:
virtual void Boil() {
cout << "煮水中" << endl;
}
virtual void Brew() {
cout << "冲泡茶叶中" << endl;
}
virtual void PourInCup() {
cout << "倒入茶杯中" << endl;
}
virtual void PutSometing() {
cout << "加柠檬中" << endl;
}
};
// 制作函数
void doWork(AbstractDrinking *abs)
{
abs->makeDrink();
delete abs; // 释放堆区内容
}
void test()
{
// 制作咖啡
doWork(new Coffee);
}
·多态使用时,若子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构代码,导致内存泄露。
·解决方法:将父类中的析构函数改为虚析构或纯虚析构
·共性:可以解决父类指针释放子类对象;都需要有具体函数实现
·差异:纯虚析构属于抽象类,无法实例化对象
·纯虚析构需要有具体实现
·若子类中没有堆区数据,可以不写虚析构和纯虚析构
// 电脑组装
// CPU类
class CPU
{
public:
virtual void caculate() = 0;
};
// 内存类
class Memory
{
public:
virtual void storage() = 0;
};
// GPU类
class GPU
{
public:
virtual void display() = 0;
};
// 电脑类
class Computer
{
public:
Computer(CPU * cpu, Memory * memory, GPU * gpu) // 接受三个零件接口
{
c_cpu = cpu;
c_memory = memory;
c_gpu = gpu;
}
void work() // 工作函数
{
c_cpu->caculate();
c_gpu->display();
c_memory->storage();
}
// 提供析构函数,释放堆区零件内容
~Computer()
{
if (c_cpu != NULL)
{
delete c_cpu;
c_cpu = NULL;
}
if (c_memory!= NULL)
{
delete c_memory;
c_memory = NULL;
}
if (c_gpu != NULL)
{
delete c_gpu;
c_gpu = NULL;
}
}
private:
CPU * c_cpu;
Memory * c_memory;
GPU * c_gpu;
};
// 电脑厂商
class IntelCPU :public CPU
{
public:
void caculate()
{
cout << "Intel CPU" << endl;
}
};
class IntelMemory :public Memory
{
public:
void storage()
{
cout << "Intel Memory" << endl;
}
};
class IntelGPU :public GPU
{
public:
void display()
{
cout << "Intel GPU" << endl;
}
};
// 另一个厂商
class LenoveCPU :public CPU
{
public:
void caculate()
{
cout << "Lenove CPU" << endl;
}
};
class LenoveMemory :public Memory
{
public:
void storage()
{
cout << "Lenove Memory" << endl;
}
};
class LenoveGPU :public GPU
{
public:
void display()
{
cout << "Lenove GPU" << endl;
}
};
void test()
{
// 第一台电脑零件
CPU * intelCPU = new IntelCPU;
Memory * intelMemory = new IntelMemory;
GPU * intelGPU = new IntelGPU;
// 第一台电脑
Computer * c = new Computer(intelCPU, intelMemory, intelGPU);
c->work();
delete c;
}
程序运行时产生的临时数据在结束后会被释放,文件可将数据持久化。
头文件:fstream
文件分为文本和二进制文件
操作:ofstream写操作;ifstream读操作;fstream读操作
·包含头文件:fstream
·创建流对象:ofstream ofs;
·打开文件:ofs.open("文件路径",打开方式);
·写数据:ofs << "写入的数据";
·关闭文件:ofs.close();
同时使用两种打开方式,使用|操作符
// 1.包含头文件
#include
int main()
{
// 2.创建流对象
ofstream ofs;
// 3.打开方式
ofs.open("test.txt", ios::out);
// 4.写内容
ofs << "name:zhangsan" << endl;
// 5.关闭文件
ofs.close();
system("pause");
return 0;
}
·包含头文件:fstream
·创建流对象:ifstream ofs;
·打开文件并判断是否成功:ifs.open("文件路径",打开方式);
·读数据
·关闭文件:ifs.close();
// 1.包含头文件
#include
void test()
{
// 2.创建流对象
ifstream ifs;
// 3.打开文件
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "打开失败" << endl;
return;
}
// 4.读数据
// 方法一
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
// 方法二
char buf2[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf))
{
cout << buf2 << endl;
}
// 方法三
string buf3;
while (getline(ifs, buf3))
{
cout << buf3 << endl;
}
// 方法四,不推荐
char c;
while((c=ifs.get())!= EOF) // EOF = End Of File
{
cout << buf3 << endl;
}
// 5.关闭文件
ifs.close();
}
调用成员函数write
ostream& write(const char * buffer, int len);
// 1.包含头文件
#include
// 二进制写文件
class Person
{
public:
char m_name[64];
int m_age;
};
void test()
{
// 2.创建流对象
ofstream ofs;
// 3.打开文件
ofs.open("person.txt", ios::out | ios::binary);
// 4.写文件
Person p = { "张三", 18 };
ofs.write((const char *)&p, sizeof(Person));
// 5.关闭文件
ofs.close();
}
调用成员函数read
istream& read(char * buffer, int len);
// 1.包含头文件
#include
// 二进制读文件
class Person
{
public:
char m_name[64];
int m_age;
};
void test()
{
// 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_age << endl;
// 5.关闭文件
ifs.close();
}