c++ 类与对象
4 类和对象
C++ 面向对象的三大特征为: 封装,继承,多态
C++ 认为万物万事皆对象
4.1 封装
4.1.1 封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限:属性/行为};
示例: 设计一个圆类,求圆的周长
#include <iostream>
using namespace std;
// 设计一个圆类 求周长 2*PI*半径
const double PI = 3.1415;
// class 代表设计一个类,类后面紧跟着的就是类名称
class Circle {
// 访问权限
public:
// 属性
int m_r;
// 方法
// 获取圆周长
double get_zc() {
return 2 * PI * m_r;
}
};
void main() {
// 通过圆类 创建一个具体的圆对象 // 实例化
Circle c1;
// 给圆的属性赋值
c1.m_r = 15.5;
double zc = c1.get_zc();
cout << zc << endl;
}
示例2: 设计一个学生类,属性有姓名和学号,可以给姓名学号赋值,可以显示学生的姓名和学号
#include <iostream>
using namespace std;
class Student {
public:
string name;
int id;
void showStudentInfo() {
cout << "姓名:" << name << " 学号:" << id << endl;
}
void setNameID(string name,int id) {
this->name = name;
this->id = id;
}
};
void main() {
Student s1;
s1.setNameID("小明",1);
s1.showStudentInfo();
}
封装意义二:
类在设计时,可以把属性和行为放在不同权限下,加以控制
访问权限三种:
- public 公共权限
- private 私有权限
- protected 保护权限
// 三种权限
// 公共权限 public 类内可以访问 类外可以访问
// 私有权限 private 类内可以访问 类外不可以访问
// 保护权限 protected 类内可以访问 类外不可以访问 继承中 保护权限 儿子可以访问,而私有不行
4.1.2 struct 和 class区别
在c++ 中struct 和 class 唯一的区别在于 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
4.1.3 成员属性设置为私有
优点1: 将所有成员属性设置为私有,可以自己控制读写权限
优点2: 对于写权限,我们开业检测数据的有效性
#include <iostream>
using namespace std;
class Person {
public:
string getName() {
return name;
}
int getAge() {
return age;
}
void setName(string r_name) {
name = r_name;
}
private:
string name;
int age;
int score;
};
void main() {
}
在类中 可以让其他类作为一个成员
#include <iostream>
#include <math.h>
using namespace std;
// 点类
class Point {
private:
int m_x;
int m_y;
public:
void SetPont(int x,int y) {
m_y = y;
m_x = x;
}
int getPointX() {
return m_x;
}
int getPointY() {
return m_y;
}
};
class Circle {
private:
int m_r;
Point p;
public:
void setPoint(int x,int y) {
p.SetPont(x,y);
}
void setMr(int r) {
m_r = r;
}
void isPointInCircle(Point p2) {
int bj = m_r * m_r;
int jl = pow(p.getPointX() - p2.getPointX(), 2) + pow(p.getPointY() - p2.getPointY(),2);
if (jl==bj) {
cout << "在圆边" << endl;
}else if(jl > bj) {
cout << "不在圆内" << endl;
}
else {
cout << "在圆之内" << endl;
}
}
};
// 圆类
void main() {
Circle c1;
c1.setPoint(5,5);
c1.setMr(10);
Point p1;
p1.SetPont(10, 10);
c1.isPointInCircle(p1);
}
4.11重点 分文件写
circle.h
#pragma once
#include <iostream>
#include "Point.h"
using namespace std;
class Circle {
private:
int m_r;
Point p;
public:
void setPoint(int x, int y);
void setMr(int r);
void isPointInCircle(Point p2);
};
circle.cpp
#include "Circle.h"
void Circle::setPoint(int x, int y) { // 这个Circle::代表在circle的作用域下
p.SetPont(x, y);
}
void Circle::setMr(int r) {
m_r = r;
}
void Circle::isPointInCircle(Point p2) {
int bj = m_r * m_r;
int jl = pow(p.getPointX() - p2.getPointX(), 2) + pow(p.getPointY() - p2.getPointY(), 2);
if (jl == bj) {
cout << "在圆边" << endl;
}
else if (jl > bj) {
cout << "不在圆内" << endl;
}
else {
cout << "在圆之内" << endl;
}
}
Point.h
#pragma once // 加载一次
#include <iostream>
using namespace std;
// 点类
class Point {
private:
int m_x;
int m_y;
public:
void SetPont(int x, int y);
int getPointX();
int getPointY();
};
Point.cpp
#include "Point.h"
void Point::SetPont(int x, int y) {
m_y = y;
m_x = x;
}
int Point::getPointX() {
return m_x;
}
int Point::getPointY() {
return m_y;
}
4.2 对象的初始化和清理
- 类似 恢复出厂 设置
- C++ 中的面向对象来源生活,每个对象也会有初始设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
对象的初始化和清理 也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对齐使用后果是未知的
- 同样的使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题!
c++利用了 构造函数 和 析构函数解决以上问题,这两个函数将会被编译器自动调用,完成对对象初始化和清理工作,对象的初始化和清理是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供 编译器提供的是 空实现
- 构造函数:主要用于常见对象时对对象赋值操作,编译器自动调用,无需手动调用
- 析构函数:主要用于对象的销毁,系统自动调用 执行清理工作
构造函数语法: 类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值不写void
2.函数名称与类名相同 加 ~
3.析构函数不可以有参数,因此不能重载
4.程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;
//构造函数和析构函数
class Person {
public:
string m_name;
int m_age;
// 使用构造函数初始化
Person(string name,int age) {
m_name = name;
m_age = age;
}
// 使用析构函数清理
~Person() {
cout << "析构函数执行" << endl;
}
};
void main() {
Person s1("小明",18); // 栈上的数据,main执行完毕就会执行析构函数
cout << s1.m_age << endl;
cout << s1.m_name << endl;
}
4.2.2 构造函数的分类和调用 调用**
两种分离方式:
按参数分为: 有参构造,无参构造
按类型分类: 普通构造,拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
#include <iostream>
using namespace std;
// 1. 构造函数的分类及调用
class Person {
public:
// 构造函数
Person() {
cout << "Person的构造函数调用" << endl;
}
Person(string name) {
this->name = name;
cout << "Person的构造函数调用" << endl;
}
Person(string name,int age) {
this->age = age;
this->name = name;
cout << "Person的构造函数调用" << endl;
}
// 拷贝构造函数
Person(const Person & p) {
// 将传入的人身上的所有属性,拷贝到我身上
age = p.age;
name = p.name;
cout << age << endl;
cout << name << endl;
}
~Person() {
cout << "Person的析构函数调用" << endl;
}
int age = 0;
string name;
};
void main() {
// 调用
// 1.括号法
Person p("小明",18);
// 拷贝构造函数调用
Person p2(p);
// 注意事项
// 调用默认构造函数时 不要加小括号! Person p(); 编译器会认为是函数的声明
// 2.显示法
Person p3 = Person("小明"); // 由参构造
Person p4 = Person(p2); // 拷贝构造
Person("小明"); // 匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名函数
// 注意事项2 不要利用 拷贝构造函数 来初始化匿名对象
Person(p2); // 编译器会认为 Person(p2) == Person p2; 这里p2 == p2 所以会报错 重定义!
// 3.隐式转换法
// Person p5 = 10; // 相当于 Person p5 = Person(10);
}
4.2.3 拷贝构造函数调用时机 **
C++ 中 拷贝构造函数调用是通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream>
using namespace std;
// 拷贝构造函数调用时机
class Person {
public:
int m_age;
Person() {
cout << "无参构造函数执行" << endl;
}
Person(int age) {
m_age = age;
cout << "有参构造函数执行" << endl;
}
~Person() {
cout << "析构函数执行" << endl;
}
Person(const Person & p) {
m_age = p.m_age;
cout << "拷贝构造函数执行" << endl;
}
};
// 1. 使用一个已经创建完毕的对象初始化一个新对象
void test01() {
Person p1(20);
Person p2(p1);
}
// 2.值传递的方式给函数参数传值
void doWork(Person p) { // Person p(函数里的p); 复制了一份
}
void test02() {
Person p; // 这第一次执行调用了默认构造函数
doWork(p); // 实参传型参 调用了 拷贝构造函数
}
// 3.值方式返回局部对象
Person doWork2() {
Person p1; // 这里拷贝了一个新的对象返回
p1.m_age = 10;
return p1; // 这里也会调用拷贝构造函数
}
void test03() {
Person p2 = doWork2();
// 接收的对象和返回的对象不是一个对象 地址不一样
cout << p2.m_age << endl;
cout << p2.m_age << endl;
system("pause");
// 函数执行完毕后才会执p2的析构函数
}
void main() {
//test01();
//test02();
test03();
}
4.2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个构造函数
- 默认构造函数(无参数,函数体为空)
- 默认析构函数(无参数,函数体为空)
- 拷贝构造函数,对属性进行赋值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++ 不在提供默认构造函数,但是会提供拷贝构造函数
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
// 测试代码
#include <iostream>
using namespace std;
class Person {
public:
int age;
string name;
// 无参构造函数
Person() {
cout << "无参构造函数执行" << endl;
}
// 有参构造函数
Person(int a) {
cout << "有参构造函数调用,参数为:" << a << endl;
}
// 拷贝构造函数
Person(const Person & p) {
// 拷贝构造函数进行赋值操作
age = p.age;
name = p.name;
cout << "拷贝构造函数调用" << endl;
}
};
void main() {
Person p;
p.age = 18;
p.name = "hello word";
Person p2(p);
cout <<"p2的年龄为:" << p2.age << endl;
cout <<"p2的姓名为:" << p2.name << endl;
}
4.2.5 深拷贝和浅拷贝
深浅拷贝是面试的经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
// 自己实现拷贝构造函数 解决拷贝带来的问题
Person(const Person & p) {
cout << "拷贝构造函数调用" << endl;
/*
编译器写法:
m_age = p.m_age;
Height = p.Height;
*/
// 深拷贝操作 在申请一块内存给height赋值
Height = new int(*p.Height);
m_age = p.m_age;
}
// 骚操作
#include <iostream>
using namespace std;
// 深浅拷贝
class Person {
public:
int m_age;
int* Height;
Person() {
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height) {
m_age = age;
Height = new int(height); // 返回地址 手动开辟 手动释放
cout << "Person的有参构造函数" << endl;
}
// 将我们堆区开辟的数据 做释放操作
~Person() {
// 判断是否是空指针
if (Height!=NULL) {
delete Height; // 释放
Height = NULL; // 防止野指针的出现
}
cout << "析构函数调用" << endl;
}
// 自己实现拷贝构造函数 解决拷贝带来的问题
Person(const Person & p) {
cout << "拷贝构造函数调用" << endl;
/*
编译器写法:
m_age = p.m_age;
Height = p.Height;
*/
// 深拷贝操作 在申请一块内存给height赋值
Height = new int(*p.Height);
m_age = p.m_age;
}
};
void test01() {
Person p1(18, 160);
cout << "p1的年龄为:" << p1.m_age << " p1的身高为:" << *p1.Height << endl;
Person p2(p1); // 浅拷贝
cout << "p2的年龄为:" << p2.m_age << " p2的身高为:" << *p2.Height << endl;
system("pause");
// 浅拷贝带来的问题是 堆区的内存重复释放
// 浅拷贝的问题利用深拷贝来解决
// 释放的时候是堆栈 先进后出
}
void main() {
test01();
}
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
4.2.6 初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2){}
// 初始化列表属性
Person(string name,int age,int id):m_name(name),m_age(age),m_id(id) {
// 初始化完毕
}
#include <iostream>
using namespace std;
class Person {
public:
string m_name;
int m_age;
int m_id;
// 传统方式初始化
//Person(string name,int age,int id) {
// m_name = name;
// m_age = age;
// m_id = id;
//}
// 初始化列表属性
Person(string name,int age,int id):m_name(name),m_age(age),m_id(id) {
// 初始化完毕
}
};
void test01() {
Person p1("小明",18,1);
cout << p1.m_age << endl;
cout << p1.m_name << endl;
cout << p1.m_id << endl;
}
void main() {
test01();
}
4.2.7 类对象作为类成员
C++ 类中的成员可以是另一个类的对象,我们称该对象的 对象成员
例如:
class A{};
class B{
A a;
// B类中有对象A作为成员,A为对象成员
}
那么当创建B对象时,A与B的构造函数的顺序是谁先谁后?
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "A构造函数执行" << endl;
}
};
class B {
public:
A a;
B(){
cout << "B构造函数执行" << endl;
}
};
void main() {
B b;
}
先执行A 后执行B debug得知到了构造函数并未执行,跳转A构造函数中!
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构顺序与其相反
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上Static关键字,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 访问权限是 公共的
示例1: 静态成员变量
#include <iostream>
using namespace std;
// 静态成员函数
// 所有对象共享一个函数
// 静态函数只能访问静态成员变量
class Person {
public :
static void func() {
cout << "i am func" << endl;
m_ip = 192;
// port = 20; // 报错 不可改变 因为func是共享的 并不知道那个对象修改!!!
}
int port;
static int m_ip; // 静态成员
// 特点,类内声明,类外初始化
};
// 声明
int Person::m_ip = 0; // 初始化
void main() {
// 对象调用
Person p1;
p1.func();
// 类调用
Person::func();
}
4.3 c++对象模型和this指针
4.3.1 成员变量和成员函数分开储存
在c++ 中,类内的成员变量和成员函数分开储存 只有非静态成员变量才属于类的对象上
#include <iostream>
using namespace std;
// 初始化
class Person {
public:
int m_age; // 非静态成员
static int id; // 静态成员
};
int Person::id;
void test01() {
Person p1;
// 空对象占用内存空间为: 1个字节
// C++ 编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置 两个对象不能在同个内存
cout << "size of p1" << sizeof(p1) << endl; // 有了m_age 变成了4个字节
// 非静态成员变量属于类的
}
void main() {
test01();
}
4.3.2 this指针概念
静态函数如何区分那个对象调用自己?
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员同名时,可以用this指针
- 在类的非静态成员函数中返回对象本身, 可以使用 return*this
链式编程思想:
#include <iostream>
using namespace std;
class Person {
public:
int age;
Person& get(const Person & p) {
this->age += p.age;
return *this;
}
};
void main() {
Person p1;
p1.age = 10;
Person p2;
p2.age = 10;
p2.get(p1).get(p1).get(p1);
cout << p2.age << endl;
}
4.3.3 空指针访问成员函数
c++中空指针也是可以调用成员函数的,但是也需要注意有没有用到this指针
如果用到了this指针,需要加以判断保证代码的健壮
#include <iostream>
using namespace std;
class Person {
public:
int age;
void ShowClassName() {
cout << "i am person" << endl;
}
void ShowAge() {
if (this == NULL) {
return;
}
cout << age << endl;
}
};
void main() {
Person* p = NULL;
p->ShowClassName();
p->ShowAge(); // 报错! 内部age属于 this.age this是空指针 所以报错
}
4.3.4 coust修饰成员函数
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数不可以修改成员属性
- 成员属性声明时加, mutable 后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include <iostream>
using namespace std;
class Person {
public:
mutable int age ; // 加了mutable就可修改了
// this指针本质是 指针常量 指针指向是不可以修改的 加了const后 都不可改了
void ShowClassName() const {
//age = 15; // 不加const可以修改
age = 15 ;
}
};
void main() {
const Person p1; // 常对象 不可以修改指针指向的值
p1.ShowClassName();
}
4.4 友元
在程序中,有些私有属性 也想让类外的一些特殊函数或者类进行访问,就需要友元技术
友元的目的是让一个函数或者类访问一个类中的私有成员
友元的关键字为 friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
#include <iostream>
using namespace std;
// 房屋类
class Building
{
// goodGay 是 building的好朋友,可以访问building中的私有成员
friend void goodGay(Building& building); // 类似声明就行
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom; // 客厅
private:
string m_BedRoom; // 卧室
};
// 全局函数做友元
void goodGay(Building & building) {
cout << "好基友访问" << building.m_SittingRoom << endl;
cout << "好基友访问" << building.m_BedRoom << endl;
}
void main() {
Building building;
goodGay(building);
}
4.4.2 类做友元
#include <iostream>
using namespace std;
// 房屋类
class Building
{
friend class GoodGay; // goodGay是本类的好朋友 可以访问私有成员
public:
Building();
string m_SittingRoom; // 客厅
private:
string m_BedRoom; // 卧室
};
// 好基友
class GoodGay {
public:
Building* building;
GoodGay();
void visit();
};
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
building = new (Building);
}
void GoodGay::visit() {
cout << "好基友访问" << building->m_SittingRoom << endl;
cout << "好基友访问" << building->m_BedRoom << endl;
}
void main() {
GoodGay gg;
gg.visit();
}
4.4.3 成员函数做友元
#include <iostream>
using namespace std;
class Building; // 声明 待会写..
// 基友类
class GoodGay {
public:
GoodGay();
Building* building;
void visit();// 可以访问
void visit2(); // 不可访问
};
// 房屋类
class Building
{
friend void GoodGay::visit();
public:
Building();
string m_SittingRoom; // 客厅
private:
string m_BedRoom; // 卧室
};
Building::Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
building = new Building;
}
void GoodGay::visit() {
cout << "好基友访问" << building->m_SittingRoom << endl;
cout << "好基友访问" << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
cout << "好基友访问" << building->m_SittingRoom << endl;
// cout << "好基友访问" << building->m_BedRoom << endl; 不能访问
}
void main() {
GoodGay gg;
gg.visit();
gg.visit2();
}
4.5 运算符重载
运算符重载概念:对已有的运算符重新定义,赋予其另外一种功能,以使用不同数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
/*
对于内置数据类型,编译器知道如何进行运算 例如
int a = 10;int b = 10;
int c = a + b;
class Person{
public:
int m_a;
int m_b;
}
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 10;
p2.m_b = 10;
Person p3 = p1 + p2; // 我们想两个自定义数据的相加运算
*/
通过自己写的成员函数,实现两个对象相加属性后返回新的对象
//test
Person& test(Person & p){
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp
}
编译器起了一个通用名oprator+
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
}
// 调用
Person p3 = p1.operator+ (p2);
// 简化
Person p3 = p1 + p2;
全局函数重载+
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;
}
// 本质调用
Person p3 = operator+(p1,p2);
// 简化
Person p3 = p1 + p2;
示例:
// 加号运算符号重载
#include <iostream>
using namespace std;
class Person {
public:
Person() {
}
Person(int a,int b):m_a(a),m_b(b) {
}
int m_a;
int m_b;
// 1 成员函数重载+`
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;
};
~Person() {
cout << "我释放了" << endl;
}
Person(const Person & p) {
this->m_a = p.m_a;
this->m_b = p.m_b;
cout << "copy diao yong" << endl;
}
};
// 全局函数做重载+
//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;
//}
// 函数重载版本
Person operator+(Person& p1,int num) {
Person temp;
temp.m_a = p1.m_a + num;
temp.m_b = p1.m_b + num;
return temp;
// 返回完毕就会析构了 不要返回指针
}
int main() {
cout << "1" << endl;
Person p1(10,10);
cout << "2" << endl;
Person p2(10,10);
cout << "3+" << endl;
Person p3 = p1 + p2;
cout << p3.m_a << endl;
cout << p3.m_b << endl;
cout << "4+" << endl;
// 运算符号重载,也可以发生函数重载
Person p4 = p1 + 11;
cout << p4.m_a << endl;
cout << p4.m_b << endl;
return 0;
}
4.5.2 左移运算符重载
可以输出自定义的数据类型
// 左移运算符重载!
#include <iostream>
using namespace std;
class Person {
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a,int b):m_a(a),m_b(b) {
}
private:
int m_a;
int m_b;
// 成员函数重载
//void operator<<(Person &p) {
//}
// 不会利用成员函数重载<<运算符因为无法实现<< 在左侧
};
// 只能利用全局函数重载
ostream& operator<<(ostream &cout,Person &p) { // 本质 operator<<(cout,p) 简化 cout<<p; cout只能有一个
cout << "m_a= " << p.m_a <<endl;
cout << "m_a= " << p.m_b <<endl;
return cout; // 实现无限调用
}
void test01() {
Person p1(21,22);
cout << p1 << endl;
}
int main() {
test01();
return 0;
}
4.5.3 递增运算符重载
实现自己数据的递增
// 递增运算符号重载
#include <iostream>
using namespace std;
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger& i);
public:
MyInteger() {
// 初始化
m_num = 0;
}
// 前置++
MyInteger & operator++() {
this->m_num++;
return *this;
}
// 后置++ 加个int 这个int代表占位参数,用于区分前置和后置递增
MyInteger operator++(int) {
// 先表达式 记录当前结果
MyInteger temp = *this;
// 后递增
m_num++;
return temp; // 返回值,不然会被释放掉
}
MyInteger(const MyInteger&i) {
cout << "拷贝调用" << endl; // 加引用不加引用区别在于是否调用拷贝构造函数
}
private:
int m_num;
};
// 左运算符号重载
ostream& operator<<(ostream &cout,MyInteger &i) {
cout << i.m_num << endl;
return cout;
}
int main() {
MyInteger i;
++(++i);
cout << i << endl;
++i;
cout << i << endl;
return 0;
}
4.5.4 赋值运算符重载
c++ 编译器至少给一个类添加4个函数
1.默认构函数(无参数,函数体为空)
2.默认析构函数(无参,函数体为空)
3 默认拷贝函数 对属性进行copy
4 赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include <iostream>
using namespace std;
// 赋值运算符
class Person {
public:
Person(int age) {
m_age = new int(age);
}
int* m_age;
~Person() {
if (m_age != NULL) {
delete m_age;
m_age = NULL;
}
}
// 重载赋值运算符
Person & operator=(Person & p2) {
// 编译器提供拷贝
// m_age = p.m_age;
// 判断是否有属性在堆,如果有,先释放干净
if (this->m_age!=NULL) {
delete m_age;
m_age = NULL;
}
// 深拷贝
m_age = new int(*p2.m_age);
// 返回对象本身
return *this;
}
};
void test01() {
Person p1(18);
Person p2(20);
Person p3(50);
cout << *p1.m_age << endl;
cout << *p2.m_age << endl;
cout << *p3.m_age << endl;
p2 = p1 = p3;
cout << *p2.m_age << endl;
// 程序结束 报错 重复释放问题
}
int main() {
test01();
}
4.5.5 关系运算符重载
作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作
#include <iostream>
using namespace std;
class Person {
public:
Person(int age,string name,int mobile) :m_age(age),m_name(name),m_mobile(mobile) {
}
bool operator==(Person & p) {
if (this->m_age == p.m_age && this->m_mobile == p.m_mobile && this->m_name == p.m_name) {
return true;
}
return false;
}
private:
int m_age;
string m_name;
int m_mobile;
};
int main() {
Person p1(18, "tom", 1589);
Person p2(19, "tom", 1589);
if (p1 == p2) {
cout << "相等" << endl;
}
else {
cout << "不相等" << endl;
}
return 0;
}
4.5.6 函数调用运算符重载
- 函数调用运算符() 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为 仿函数
- 仿函数没有固定写法,非常灵活
// 仿函数 函数调用运算符号重载
#include <iostream>
using namespace std;
// 打印输出类
class MyPrint {
public:
// 重载函数调用运算符
void operator()(string str){
cout << str << endl;
}
int operator()(int num, int num2) {
return num + num2;
}
};
class MyAdd {
public:
int operator()(int num,int num2) {
return num + num2;
}
};
int main() {
MyPrint p;
p("hello"); // 使用起来特别像函数,所以叫仿函数
int res1 = p(22,5);
cout << res1 << endl;
MyAdd add;
int res = add(18,20);
cout << res << endl;
// 匿名函数对象
cout << MyAdd()(100,200) << endl; // 匿名对象 加了() 执行完毕就释放
return 0;
}
4.6继承
继承是面向对象的三大特性之一
有些类之间存在特殊关系如: 动物>狗,猫.....> 猫分为 品种.....
4.6.1 继承的基本语法
语法: class 子类: 继承方式 父类
子类称为:派生类,父类称为基类
#include <iostream>
using namespace std;
// 继承实现
class BasePage {
public:
void header() {
cout << "我是header" << endl;
}
void left() {
cout << "我是left" << endl;
}
};
// 普通页面实现
// 语法: class 子类: 继承方式 父类
class Java:public BasePage {
public:
void content(){
cout << "javaContent" << endl;
}
};
int main() {
Java j1;
j1.content();
j1.left();
j1.header();
return 0;
}
4.6.2 继承方式
继承的语法:class 子类:继承方式 父类
继承方式一共有三种
- 公共继承
- 保护继承
- 私有继承
父类中的私有权限 子类都访问不了
继承方式是 public 父类除了私有,其他都不动 该是什么访问权限是什么访问权限
继承方式是protected 父类除了私有,所有都变成 protected访问权限
继承方式是protected 父类除了私有,所有变为私有权限
//#include <iostream>
//using namespace std;
//
//
//
//// 继承实现
//class BasePage {
//public:
// void header() {
// cout << "我是header" << endl;
// }
//
// void left() {
// cout << "我是left" << endl;
// }
//};
//
//
//// 普通页面实现
//// 语法: class 子类: 继承方式 父类
//class Java:public BasePage {
//public:
// void content(){
// cout << "javaContent" << endl;
// }
//};
//
//int main() {
//
// Java j1;
// j1.content();
// j1.left();
// j1.header();
// return 0;
//}
#include <iostream>
using namespace std;
class B;
class Base {
friend class B;
public:
int a;
protected:
int b;
private:
int c;
};
class A :public Base {
public:
void func() {
this->a; // 能访问
this->b; // 能访问
}
};
class B : protected Base {
public:
void func() {
this->a; // 能访问
this->b; // 能访问
this->c; // 尝试用友元访问
c = 100;
cout << c << endl;
}
};
class C :private Base {
public:
void func() {
// 都不能访问
}
};
int main() {
B b;
b.func();
}
4.6.3 继承中的对象模型
问题: 从父类继承过来的成员,那些属于子类中的对象
开始文档>vs>工具查看 打开vs下的命令窗口 然后切换到项目目录 开发人员命令提示工具
cl /d1 reportSingleClassLayoutSon "15 继承.cpp"
cl /d1 reportSingleClassLayout类名 项目名
报告单个类的布局 reportSingleClassLayout
15 继承.cpp
class Son size(16):
+----------------
0 | +--- (base class Base)
0 | | m_a
4 | | m_b
8 | | m_c
| +---
12 | m_d
+-------------
// 继承中的对象模型
#include <iostream>
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;
};
int main() {
cout << sizeof(Son) << endl; // 16 4个int大小
// 父类中非静态的属性都会被子类继承下去
// 父类中私有的属性是被编译器隐藏了,但是继承了
}
父类中私有的属性是被编译器隐藏了,但是继承了 父类中非静态的属性都会被子类继承下去
4.6.4 继承中构造函数和析构函数
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题: 父类和子类的构造和析构顺序谁先谁后
// 继承中构造和析构顺序
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "基类构造函数执行" << endl;
}
~Base() {
cout << "基类析构函数执行" << endl;
}
};
class Son :public Base {
public:
Son() {
cout << "子类构造函数执行" << endl;
}
~Son() {
cout << "子类析构函数执行" << endl;
}
};
int main() {
Son s1;
return 0;
}
// 结果
/*
基类构造函数执行
子类构造函数执行
子类析构函数执行
基类析构函数执行
*/
4.6.5 继承同名成员处理方式
问题: 当子类与父类出现同名成员,如何子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员 执行访问即可
- 访问基类同名成员 需要添加作用域
示例:
// 继承同名成员处理方式
#include <iostream>
using namespace std;
class Base {
public:
int m_a;
Base() {
m_a = 100;
}
void func() {
cout << "base 调用" << endl;
}
};
class Son :public Base {
public:
Son() {
m_a = 200;
}
int m_a;
void func() {
cout << "son 调用" << endl;
}
};
// 同名 成员属性处理
void test01() {
Son s;
cout <<"Son:" << s.m_a << endl; // 200
cout <<"Base:" << s.Base::m_a << endl; // 100W
}
// 同名 成员函数处理
void test02() {
Son s;
s.func();
s.Base::func();
}
int main() {
test01();
test02();
return 0;
}
如果子类中出现了和父类同名的成员函数,编译器就会把父类的隐藏掉 只能访问到子类的 访问父类加作用域
4.6.6 继承同名静态成员处理
问题: 继承中同名的静态成员在子类对象上如何访问
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要添加作用域
示例:
// 继承中的同名静态成员处理方式
#include <iostream>
using namespace std;
class Base{
public:
int m_a;
static int m_b;
};
int Base::m_b = 100;
class Son :public Base {
public:
int m_a;
static int m_b;
};
int Son::m_b = 200;
int main() {
Son s1;
cout << s1.m_b << endl;
cout << s1.Base::m_b << endl;
// 2 通过类名访问
cout << Son::m_b << endl;
cout << Base::m_b << endl;
cout << Son::Base::m_b << endl; // 类名方式访问父类然后访问m_b
return 0;
}
4.6.7 多继承语法
c++ 运行一个类继承多个类
语法: class 子类:继承方式 父类, 继承方式 父类
多继承可会引发父类中同名成员出现,需要添加作用域
C++实际开发中不建议使用多继承
// 多继承
#include <iostream>
using namespace std;
class Base1 {
public:
int m_a = 100;
};
class Base2 {
public:
int m_a = 200;
};
class Son :public Base1, public Base2 {
public:
int m_a = 300;
};
int main() {
Son s1;
cout << s1.m_a << endl; // 300
cout << s1.Base1::m_a << endl; // 100
cout << s1.Base2::m_a << endl; // 200
}
4.6.8 菱形继承
菱形继承概念:
有两个派生类继承同一基类
又有某个类同时继承两个派生类
这种继承被称为菱形继承,或者钻石继承
// 菱形继承
#include <iostream>
using namespace std;
// 动物类
class Animal{
public:
int m_age;
};
// 羊类
class Sheep :virtual public Animal {
};
// 驼类
class Tuo :virtual public Animal {
};
// 羊驼
class SheepTuo : public Sheep, public Tuo {
};
// 利用虚继承 解决菱形继承的问题 在 public前加上 virtual
// 此时基类称为 虚基类
int main() {
SheepTuo s1;
s1.m_age = 100;
return 0;
}
4.7 多态
4.7.1 多态的基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定-编译阶段确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
#include <iostream>
using namespace std;
// 动物类
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 & animal = cat;
animal.speak(); // 函数地址早绑定 编译阶段确定了函数的地址是动物的
}
void test01() {
Cat cat;
doSpeak(cat);
// 如何让猫说话?
// 那么这个函数的地址就不能提前绑定,需要运行阶段绑定
// 基类函数添加 virtual 虚函数
Dog dog;
doSpeak(dog);
}
int main() {
test01(); // 走的动物在说话
return 0;
}
// 动态多态满足条件
// 1.有继承关系
// 2.子类需要重写父类虚函数
// 动态多态使用
// 父类的指针或者引用,指向子类的对象
总结:
动态多态满足条件
- 1.有继承关系
- 2.子类需要重写父类虚函数
动态多态使用条件
- 父类的指针或者引用,指向子类的对象
重写: 函数返回值类型 函数名 参数列表 完全一致为重写
4.7.2 多态案例一 计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态优点
- 代码组装结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
示例:
// 普通写法
#include <iostream>
using namespace std;
class Calculator
{
public:
int m_num1; // 操作数1
int m_num2; // 操作数2
int getResult(string oper) {
if (oper == "+") {
return this->m_num1 + this->m_num2;
}
else if (oper == "-") {
return this->m_num1 - this->m_num2;
}
else if (oper == "*-") {
return this->m_num1* this->m_num2;
}
else if (oper == "/") {
return this->m_num1 / this->m_num2;
}
else {
return 0;
}
}
};
int main() {
Calculator c;
c.m_num1 = 15;
c.m_num2 = 20;
int num = c.getResult("+");
cout << num << endl;
}
// 如果想扩展,需要修改源码
// 在真实开发中 提倡 开闭原则
// 对扩展开发,对修改封闭
// 利用多态实现计算器
#include <iostream>
using namespace std;
// 计算器基类
class CalculatorAbstact {
// 抽象类 什么功能都不写
public:
int m_num1;
int m_num2;
virtual int getResult() {
return 0;
}
};
// 加法计算器类
class AddCalculator :public CalculatorAbstact {
public:
int getResult() {
return m_num1 + m_num2;
}
};
// 减法计算器类
class SubCalculator :public CalculatorAbstact {
public:
int getResult() {
return m_num1 - m_num2;
}
};
void test02() {
// 多态的使用条件
// 父类的指针或者引用指向子类的对象
// 加法
CalculatorAbstact* abc = new AddCalculator;
abc->m_num1 = 10;
abc->m_num2 = 20;
cout << abc->getResult() << endl;
// 用完释放
delete abc;
// 减法
abc = new SubCalculator;
abc->m_num1 = 15;
abc->m_num2 = 20;
cout << abc->getResult() << endl;
delete abc;
}
int main() {
test02();
return 0;
}
4.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写内容
应次可以将虚函数改为纯虚函数
纯虚函数语法: virtual 返回值类型 函数名 (参数列表)=0
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类,否则也属于抽象类
// 纯虚函数 和 抽象类
#include <iostream>
using namespace std;
class Base {
public:
virtual void func() = 0; // 纯虚函数
// 这个类称为抽象类
// 抽象类特点
// 1 无法实例化
// 2 子类必须实现该方法
};
class Son :public Base {
void func() {
}
};
int main() {
// Base b; // 报错
// new Base; // 报错
Son s; // 报错 方法未实现
Base* res = new Son;
res->func(); // 实现多态的条件 父类指针或引用指向子对象
return 0;
}
4.7.4 多态案例二 制作饮品
案例描述:
制作饮品的大致流程:煮水-冲泡-倒入杯中-假如辅料
利用多态技术实现本案例,提供抽象制作饮品类,提供子类制作咖啡和茶叶
#include <iostream>
using namespace std;
class Base {
public:
virtual void zhushui() = 0;
virtual void chongpao() = 0;
virtual void daoru() = 0;
virtual void jiaru() = 0;
};
class Coffee :public Base {
public:
void zhushui() {
cout << "coffee 煮水" << endl;
};
void chongpao() {
cout << "coffee 冲泡" << endl;
};
void daoru() {
cout << "coffee 到水" << endl;
};
void jiaru() {
cout << "coffee 假如咖啡" << endl;
};
};
class Tee :public Base {
public:
void zhushui() {
cout << "tee 煮水" << endl;
};
void chongpao() {
cout << "tee 冲泡" << endl;
};
void daoru() {
cout << "tee 到水" << endl;
};
void jiaru() {
cout << "tee 假如咖啡" << endl;
};
};
int main() {
Base *res = new Coffee;
res->zhushui();
res->chongpao();
res->daoru();
res->jiaru();
delete res;
res = new Tee;
res->zhushui();
res->chongpao();
res->daoru();
res->jiaru();
}
4.7.5 虚析构和纯析构
多态使用时,如果子类有熟悉开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方法: 将父类中的析构函数改为 虚析构 或者 纯析构
虚析构和纯析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯析构区别:
- 如果是纯析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯析构语法:virtual ~ 类名()=0
声明: 类名::~类名(){}
案例认真读:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
Animal() {
cout << "animal构造函数调用" << endl;
}
// 解决方法 改为虚析构 那么应该就会有虚析构指针指向vftable 然后覆盖后指向cat的析构入口
// 父类指针释放子类对象时不干净的问题
/*virtual ~Animal() {
cout << "animal析构函数调用" << endl;
}*/
virtual ~Animal() = 0; // 两个只能有一个 纯虚析构 也能解决这个问题 但是会报错 必须要有函数体
// 有了纯虚析构 那么也属于抽象类,无法实例化
};
// 纯虚析构
Animal::~Animal() {
// 纯虚析构函数体
cout << "animal析构函数调用" << endl;
}
class Cat:public Animal {
public:
void speak() {
cout << *this->name <<"猫在说话" << endl;
}
string * name;
Cat(string name) {
cout << "Cat构造函数调用" << endl;
this->name = new string(name); // 堆区开辟内存
}
// 解决深浅拷贝问题
Cat(const Cat & c) {
cout <<* c.name << "猫 copy 函数执行了" << endl;
this->name = new string(*c.name); // 防止野指针的出现
}
// 解决深浅拷贝问题
// 当animal释放了 cat析构函数没有执行
~Cat() {
cout << "Cat析构函数执行" << endl;
// 堆区内存手动开辟手动释放
if (this->name != NULL) {
delete this->name;
this->name = NULL;
}
}
};
void test01() {
Animal* animal = new Cat("汤姆");
animal->speak();
delete animal; // 释放指针 释放的是animal 并没有走 cat的析构 如果cat内有堆区数据 那么会出现内存的泄露
}
int main() {
test01();
return 0;
}
总结:
- 虚析构或纯虚析构就是用来解决 通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写 虚析构或纯虚析构
- 拥有纯析构函数的类也属于抽象类
4.7.6 多态案例三 电脑组装
案例描述:
电脑主要组成部分为CPU(用于计算),显卡(用于显示),内存条(用于储存)
将每个零件封装出抽象基类,并提供不同产生生产不同零件,例如 intel 厂商 和 Lenovo厂商
创建电脑类提供让电脑工作的函数,并调用每个零件工作接口
测试时组装三台不同的电脑进行工作
示例:
#include <iostream>
using namespace std;
// 抽象出每个零件
class Cpu {
public:
virtual void calculate() = 0; // 纯虚函数 抽象计算函数
};
class VidoCard {
public:
virtual void display() = 0; // 纯虚函数 抽象显示函数
};
class Memory {
public:
virtual void storage() = 0; // 纯虚函数 抽象存储函数
};
// 具体厂商
// inter厂商
class IntelCpu :public Cpu{
public:
void calculate() {
cout << "英特尔电脑cpu开始工作" << endl;
}
};
class IntelVidoCard :public VidoCard {
public:
void display() {
cout << "英特尔电脑显卡开始工作" << endl;
}
};
class IntelMemory :public Memory {
public:
void storage() {
cout << "英特尔电脑内存开始工作" << endl;
}
};
// lenovo 厂商
class LenovoCpu :public Cpu {
public:
void calculate() {
cout << "Lenovo电脑cpu开始工作" << endl;
}
};
class LenovoVidoCard :public VidoCard {
public:
void display() {
cout << "Lenovo电脑显卡开始工作" << endl;
}
};
class LenovoMemory :public Memory {
public:
void storage() {
cout << "Lenovo电脑内存开始工作" << endl;
}
};
// 电脑类
class Computer {
// 构造函数中传入三个零件指针
// 提供工作函数 调用每个零件的功能
public:
Computer(Cpu* cpu, VidoCard* vidoCard, Memory* memory) {
this->m_cpu = cpu;
this->m_memory = memory;
this->m_vidoCard = vidoCard;
}
void run() {
this->m_cpu->calculate();
this->m_memory->storage();
this->m_vidoCard->display();
}
~Computer() {
if (m_cpu != NULL) {
delete m_cpu;
m_cpu = NULL;
}
else if (m_vidoCard != NULL) {
delete m_vidoCard;
m_vidoCard = NULL;
}
else if (m_memory != NULL) {
delete m_memory;
m_memory = NULL;
}
}
private:
Cpu* m_cpu;
VidoCard* m_vidoCard;
Memory* m_memory;
};
int main() {
Cpu* C1 = new LenovoCpu;
VidoCard* V1 = new IntelVidoCard;
Memory* M1 = new LenovoMemory;
// 创建台电脑
Computer * c1 = new Computer (C1,V1,M1);
c1->run();
// 释放电脑 同时释放零件
delete c1;
// 创建电脑2
Computer* c2 = new Computer(new LenovoCpu, new IntelVidoCard, new LenovoMemory);
c2->run();
// 释放电脑 同时释放零件
delete c2;
return 0;
}