目录
1 内存分区模型
内存分为四个区:代码区(存放函数体的二进制代码,由操作系统进行管理),全局区(存放全局变量和静态变量以及常量),栈区(由编译器自动分配释放,存放函数的参数值,局部变量等),堆区(由程序员分配和释放,若程序员不释放,程序结束后由操作系统收回)。
1.1 程序运行前
程序编译后,生产可执行exe程序,未执行该程序前分为两个区域:
1.代码区
存放CPU执行的机器指令,代码区是共享的(可多次执行程序)、只读的(防止程序被修改)。
2.全局区
局部变量和全局变量不在一个区域中,全部变量和静态变量在一个区域,全局常量和字符串常量在一个区域 ,局部常量和局部变量在一个区域。
全局区:全局变量、静态变量(static)、常量(包含字符串常量和const修饰的全局变量)。
不在全局区:局部变量、局部常量。
该区域数据在程序结束后由操作系统释放。
1.2 程序运行后
1.栈区
不能返回局部变量地址,因为栈区内容在程序执行后会释放
2.堆区
利用new在堆区开辟内存,将堆区存放的内容都地址给new,并保存在栈区
#include<iostream>
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;
}
1.3 new操作符
delete释放堆区内存,利用new创建大数据,会返回该数据对应类型的指针。
#include<iostream>
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;
}
2 引用
2.1 引用的基本使用
引用就是给变量起别名,别名和原名可以一致
数据类型 &别名 = 原名
2.2 引用注意事项
1.引用必须初始化
2.初始化之后不可改变
2.3 引用做函数参数
值传递不能用形参修饰实参,地址传递可以用形参修饰实参。
可以利用引用技术让形参修饰实参
#include<iostream>
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;
}
2.4 引用做函数返回值
引用可以作为函数的返回值。
1.不能返回局部变量的引用
2.若函数的返回值是引用,这个函数的调用可以作为左值
2.5 引用本质
引用的本质在c++内部就是一个指针常量,推荐使用引用操作
2.6 常量引用
常量引用用来修饰形参,防止误操作。否则在引用中,形参的修改会影响实参
#include<iostream>
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;
}
3 函数提高
3.1 函数默认参数
函数的形参列表中可以有默认值,可以修改值
1.若某位置已有默认参数,之后位置都必须有默认值
2.函数声明和实现中只能一处有默认值
3.2 函数占位参数
函数的形参列表中可以有占位参数,在调用时必须填补该位置。占位参数可以有默认参数
返回类型 函数名(数据类型)
3.3 函数重载
函数名可以相同以提高复用性
1.函数重载满足条件
同一作用域下;函数名称相同;函数参数类型不同(或个数不同或顺序不同),其中函数的返回值不能做函数重载的条件。
2.注意事项
引用作为重载条件时,const添加与否在调用时有不同影响。
函数重载遇到默认参数,出现二义性。
4 类和对象
面向对象的三大特征:封装、继承、多态
具有相同性质的对象,可以抽象称为类
4.1 封装
1.意义
将属性和行为作为一个整体,表现生活中的事物
#include<iostream>
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;
}
2.案例1
自编
#include<iostream>
#include<string>
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;
}
类中的属性和行为统称为成员,属性称为成员属性或成员变量,行为称为成员函数或成员方法。
3.访问权限
公共权限:public。类内外皆可访问
保护权限:protected。类内可以访问,类外不可访问
私有权限:private。类内可以访问,类外不可访问
4.struct和class的区别
唯一的区别就是默认的访问权限不同,struct默认权限为公共,class默认权限为私有
5.成员属性私有化
可以控制读写权限;对于写权限可以检测数据有效性
6.案例2
#include<iostream>
#include<string>
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;
}
7.案例3
#include<iostream>
#include<string>
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<iostream>
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<iostream>
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<iostream>
#include<string>
#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;
}
4.2 对象的初始化和清理
1.构造函数和析构函数
构造函数:类名(){},作用在创建对象时为对象的成员属性赋值,由编译器自动调用。
没有返回值;函数名与类名相同;构造函数可以有参数,可以发生重载;自动调用构造
析构函数:~类名(){},作用在对象销毁前系统自动调用。没有返回值;函数名与类名相同,在名称前加上符号;不可以有参数,不可重载;自动调用析构
#include<iostream>
using namespace std;
#include<string>
// 对象的初始化和清理
class Person
{
public:
// 1.构造函数初始化
Person()
{
cout << "Person构造函数的调用" << endl;
}
// 2.析构函数清理,在对象执行完成后调用
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
system("pause");
return 0;
}
2.构造函数的分类及调用
构造函数分类:按参数类型分为有参构造和无参构造;按类型分为普通构造和拷贝构造
#include<iostream>
using namespace std;
#include<string>
// 构造函数的分类及调用
// 分类
// 按参数分类 无参(默认)和有参构造
// 按类型分类 普通和拷贝构造
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;
}
3.拷贝构造函数调用时机
使用时机:使用一个已经创建完毕的对象来初始化一个新对象;值传递的方式给函数参数传值;以值方式返回局部对象
#include<iostream>
using namespace std;
#include<string>
// 拷贝函数调用时机
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;
}
4.构造函数调用规则
默认情况下, c++编译器至少给一个类添加以下三个函数:
默认构造函数、默认解析函数、默认拷贝构造函数(值拷贝 )。
若用户定义有参构造函数,不在提供默认无参构造函数,提供默认拷贝构造函数;
若用户定义拷贝构造函数,不在提供其他构造函数。
5.深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作。可能会带来堆区内存重复释放的问题。
深拷贝:在堆区重新申请空间,进行拷贝操作
Person(const Person &p)
{
p_age = p.p_age;
// 深拷贝操作
p_height = new int(*p.p_height);
}
6.初始化列表
初始化类中的属性
// 构造函数法初始化
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;
7.类对象作为类成员
c++中类的成员可以上另一个类的对象,称为对象成员
当其他类对象作为本类成员,构造先构造类对象,再构造自身;析构与构造相反。
8.静态成员
在成员变量和成员函数前加上关键字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;
静态成员函数也有访问权限
4.3 C++对象模型和this指针
1.成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象。
空对象占用内存空间为1字节,每个空对象有单独的内存地址
2.this指针概念
this指向被调用的成员函数所属的对象。
this无需定义,可直接使用。
当形参和成员变量同名,可以this区分。
在类的非静态成员函数中返回对象本身,可用return *this
3.空指针访问成员函数
空指针可以访问成员函数,若用到this指针,需加以判断保证代码的健壮性。
4.const修饰成员函数
·成员函数加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(); // 常对象只能调用常函数
}
4.4 友元
让类外特殊函数访问私有属性
1.全局函数做友元
全局函数访问私有属性时,将函数在类中提前声明并添加关键字
class Building
{
friend void Accessible(Building &building);
public:
Building()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
public:
string m_sittingroom;
private:
string m_bedroom;
};
2.类做友元
将访问类的声明加入被访问类,并加上关键字
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();
}
3.成员函数做友元
在类中增加声明,加上关键字和作用域
friend void Good::visit();
4.5 运算符重
对运算符重新定义,以适应不同的数据类型
1.加号运算符重载
实现两个自定义数据类型相加,可通过成员函数与全局函数重载+号。
// 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;
}
运算符重载也可以发生函数重载。
2.左移运算符重载
可以输出自定义的数据类型。不利用成员函数重载<<运算符。
// 全局函数重载左移运算符
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(输出流)
3.递增运算符重载
实现自己的整型数据
// 重载递增运算符
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;
};
4.赋值运算符重载
对属性的值进行拷贝
// 重载赋值运算符
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;
};
5.关系运算符重载
让两个自定义类型对象进行对比
bool operator==(Person &p)
{
if (this->m_name == p.m_name)
{
return true;
}
return false;
}
6.函数调用运算符重载
仿函数即函数调用运算符重载。仿函数没有固定写法
void operator()(string test)
{
cout << test << endl;
}
匿名函数对象:匿名对象()()
4.6 继承
面向对象三大特性之一
1.继承基本语法
减少重复代码:class 子类 : 继承方式 父类
子类也称为派生类,父类也称为基类
2.继承方式
公共继承、保护继承、私有继承
3.继承中的对象模型
子类将继承父类所有非静态成员属性,父类中私有属性被隐藏。
查看对象模型布局:a.利用开发人员命令提示工具查看对象模型;b.跳转盘符;c.跳转文件路径 cd;d.查看命名:c1/d1 reportSingleClassLayout类名 文件名
4.继承构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
构造函数顺序:先构造父类,再构造子类;析构函数与之相反。
5.继承同名成员处理方式
·子类与父类出现同名成员,访问子类同名成员时直接访问即可,访问父类同名成员时加作用域(子类.父类::同名成员)
·若子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数。
6.继承静态成员处理方式
静态成员(类内声明,类外初始化)处理与非静态成员方式一致。
7.多继承语法
C++可以一个类继承多个类,在实际开发中不建议使用
8.菱形继承
两个子类继承同一个父类的同时,某个类继承两个子类,称为菱形或钻石继承。
// 菱形继承
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;
}
4.7 多态
多态是C++面向对象三大特性之一
1.基本概念
静态多态:函数重载和运算符重载;在编译阶段确定函数地址
动态多态:派生类和虚函数实现运行时多态;在运行阶段确定函数地址
·满足条件:有继承关系;子类要重写(函数返回值类型 函数名 参数列表完全相同)父类的虚函数。
·使用:父类的指针或引用指向子类对象
// 多态
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);
}
2.多态案例--计算器类
多态优点:可读性强、代码组织结构清晰、利用前后期扩展及维护。
// 利用多态实现计算器
// 实现计算器的抽象类
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;
}
3.纯虚函数和抽象类
父类中虚函数无作用,改为纯虚函数
virtual 返回值类型 函数名(参数列表) = 0;
当类中有纯虚函数,称为抽象类:无法实例化对象(无法在任何区用抽象类创建对象),子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
4.多态案例--制作饮品
// 利用多态实现饮品制作
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);
}
5.虚析构和纯虚析构
·多态使用时,若子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构代码,导致内存泄露。
·解决方法:将父类中的析构函数改为虚析构或纯虚析构
·共性:可以解决父类指针释放子类对象;都需要有具体函数实现
·差异:纯虚析构属于抽象类,无法实例化对象
·纯虚析构需要有具体实现
·若子类中没有堆区数据,可以不写虚析构和纯虚析构
6.多态案例--电脑组装
// 电脑组装
// 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;
}
5 文件操作
程序运行时产生的临时数据在结束后会被释放,文件可将数据持久化。
头文件:fstream
文件分为文本和二进制文件
操作:ofstream写操作;ifstream读操作;fstream读操作
5.1 文本文件
1.写文件
·包含头文件:fstream
·创建流对象:ofstream ofs;
·打开文件:ofs.open("文件路径",打开方式);
·写数据:ofs << "写入的数据";
·关闭文件:ofs.close();
同时使用两种打开方式,使用|操作符
// 1.包含头文件
#include<fstream>
int main()
{
// 2.创建流对象
ofstream ofs;
// 3.打开方式
ofs.open("test.txt", ios::out);
// 4.写内容
ofs << "name:zhangsan" << endl;
// 5.关闭文件
ofs.close();
system("pause");
return 0;
}
2.读文件
·包含头文件:fstream
·创建流对象:ifstream ofs;
·打开文件并判断是否成功:ifs.open("文件路径",打开方式);
·读数据
·关闭文件:ifs.close();
// 1.包含头文件
#include<fstream>
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();
}
5.2 二进制文件
1.写文件
调用成员函数write
ostream& write(const char * buffer, int len);
// 1.包含头文件
#include<fstream>
// 二进制写文件
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();
}
2.读文件
调用成员函数read
istream& read(char * buffer, int len);
// 1.包含头文件
#include<fstream>
// 二进制读文件
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();
}