C++核心编程<类和对象>
- 4.类和对象
- 4.1封装
- 4.1.1封装的意义
- 封装的意义1
- 封装的意义2
- 4.1.2struct和class区别
- 4.1.3成员属性设置为私有
- 4.2对象的初始化和清理
- 4.2.1构造函数和析构函数
- 1.1构造函数语法:类名(){}
- 1.2析构函数语法: ~类名(){}
- 4.2.2构造函数的分类及调用
- 2.1构造函数的分类
- 2.2构造函数的调用
- 4.2.3拷贝构造函数调用时机
- 遗留问题(待解决)
- 4.2.4构造函数调用规则
- 4.2.5深拷贝与浅拷贝
- 4.2.6初始化列表
- 4.2.7类对象作为类成员
- 4.2.8静态成员
- 8.1静态成员变量(有访问权限)
- 8.2静态成员函数(都有访问权限)
- 4.3C++对象模型和this指针
- 4.3.1成员变量和成员函数分开存储
- 4.3.2this指针概念
- 4.3.3空指针访问成员函数
- 4.3.4const修饰成员函数
- 4.1常函数
- 4.2常对象
- 4.4友元
- 4.4.1友元的三种实现
- 1.1全局函数做友元
- 1.2类做友元
- 1.3成员函数做友元
- 4.5运算符重载
- 4.5.1加号运算符重载
- 4.5.2左移运算符重载
- 4.5.3递增运算符重载
- 4.5.4赋值运算符重载
- 4.5.5关系运算符重载
- 4.5.6函数调用运算符重载
- 4.6继承
- 4.6.1继承的基本语法
- 4.6.2继承方式
- 2.1继承方式共有三种
- 1.1公共继承
- 1.2保护继承
- 1.3私有继承
- 4.6.3继承中的对象模型
- 3.1打开开发人员命令提示工具查看对象模型
- 4.6.4继承中的构造和析构顺序
- 4.6.5继承同名成员处理方式
- 4.6.6继承同名静态成员处理方式
- 4.6.7多继承语法
- 4.6.8菱形继承
- 8.1菱形继承概念
- 4.7多态
- 4.7.1多态的基本概念
- 1.1多态分为两类
- 1.2静态多态和动态多态
- 1.3动态多态满足条件
- 1.4动态多态使用
- 4.7.2纯虚函数和抽象函数
- 2.1抽象类特点
- 4.7.3虚析构和纯虚析构
- 3.1虚析构和纯虚析构共性
- 3.2虚析构和纯虚析构区别
- 3.3语法
4.类和对象
- C++面向对象的三大特性: 封装、继承、多态
4.1封装
4.1.1封装的意义
封装的意义1
- 在设计类的时候,属性和行为写在一起,表现事物
- 语法
class 类名{访问权限: 属性 / 行为}
- 案例
#include<iostream>
using namespace std;
const double PI = 3.14;
class Circle {
// 访问权限
public:
// 属性
double m_r;
// 行为
double calculateZC() {
return 2 * PI * m_r;
}
};
int main() {
// 实例化
// 通过圆类 创建具体的圆(对象)
Circle cl;
// 给圆对象的属性进行赋值
cl.m_r = 5;
cout << "圆的周长为:" << cl.calculateZC() << endl;
system("pause");
return 0;
}
封装的意义2
- 访问权限有三种
- public 公共权限
- 成员 类内可以访问 类外可以访问
- protected 保护权限
- 成员 类内可以访问 类外不可以访问 继承中子类可以访问父类
- private 私有权限
- 成员 类内可以访问 类外不可以访问 继承中子类不可以访问父类
- public 公共权限
4.1.2struct和class区别
- 在C++中struct和class唯一的区别就在于默认的访问权限不同
- 区别:
- struct 默认权限为公共
- class 默认权限为私有
#include<iostream>
using namespace std;
// struct 和 class区别
// struct 默认权限为公共
// class 默认权限为私有
class Circle {
double PI = 3.14;
double c_r = 0;
void setR(int r = 5) {
c_r = r;
}
void circleArea() {
cout << "圆的面积为:" << PI * c_r * c_r << endl;
}
};
struct Person {
int pAge = 15;
string pName = "张三";
void setName(string name) {
pName = name;
}
void showInfo(){
cout << "学生年龄:" << pAge << endl;
cout << "学生姓名:" << pName << endl;
}
};
int main() {
Circle cir;
// 默认私有权限
// cir.setR(10);
Person per;
per.setName("李四");
per.showInfo();
per.pAge = 18;
per.showInfo();
system("pause");
return 0;
}
4.1.3成员属性设置为私有
- 优点
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,可以检测数据的有效性
- 案例
#include<iostream>
#include<string>
using namespace std;
// 成员属性设置为私有
// 1.可以自己控制读写权限
// 2.对于写可以检测数据的有效性
class Person {
public:
// 编辑姓名
void setName(string name) {
m_Name = name;
}
//获取姓名
string getName() {
return m_Name;
}
//设置年龄 (设置数据的有效性,默认为0)
void setAge(int age) {
if (age < 0 || age >150) {
m_Age = 0;
cout << "设置年龄有误" << endl;
return;
}
m_Age = age;
}
//获取年龄
int getAge() {
return m_Age;
}
//设置爱好
void setHobby(string hobbies) {
m_Hobbies = hobbies;
}
private:
//姓名 rw
string m_Name;
//年龄 rw
int m_Age;
//爱好 w
string m_Hobbies;
};
int main() {
//struct 和 class 区别
// struct 默认权限是 公用 public
// class 默认权限是 私有 private
Person person;
person.setName("yinbb是真的大佬");
cout << "原神大佬:" << person.getName() << endl;
person.setAge(120);
cout << "年龄:" << person.getAge() << endl; // 120
person.setHobby("篮球");
// 不能获取爱好
// cout << "爱好:" << person.getHobby() << endl;
system("pause");
return 0;
}
4.2对象的初始化和清理
4.2.1构造函数和析构函数
- 对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知的
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
- c++利用了构造函数和析构函数解决上述问题:
- 构造函数: 主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数: 主要是作用在于对象销毁前系统自动调用,执行一些清理工作
1.1构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称和类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
1.2析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称和类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
#include<iostream>
using namespace std;
//对象的初始化和清理
class Person {
public:
//构造函数
Person() {
cout << "构造函数调用" << endl;
}
//析构函数
~Person() {
cout << "析构函数调用" << endl;
}
};
// 在栈上的数据,test()执行完毕后,释放这个对象
void test() {
Person p;
}
int main() {
test();// 构造函数调用 析构函数调用
//在main函数执行完毕,才会调用析构函数
Person p;// 构造函数调用
system("pause");
return 0;
}
4.2.2构造函数的分类及调用
2.1构造函数的分类
- 两种分类方式
- 按参数分为: 有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
2.2构造函数的调用
- 三种调用方式
- 括号法
- 显示法
- 隐式转换法
#include<iostream>
using namespace std;
// 构造函数的分类及调用
class Person {
public:
// 无参构造
Person() {
cout << "构造函数的调用(默认)" << endl;
}
// 有参构造
Person(int num) {
age = num;
cout << "构造函数的调用(有参)" << endl;
}
// 拷贝构造函数
Person(const Person &person) {
age = person.age;
cout << "构造函数的调用(拷贝)" << endl;
}
// 析构函数
~Person() {
cout << "析构函数调用" << endl;
}
private:
int age;
};
// 调用
void test(){
// 1.括号法
// 调用无参构造
Person p1;
// 调用有参构造
Person p2(21);
// 调用拷贝构造函数
Person p3(p2);
// 注意事项
// 调用默认构造函数时候,不要添加括号;编译器会认为是一个函数声明,不认为在创建对象
// 2.显示法
Person p11;
Person p22 = Person(20);
Person p33 = Person(p22);
// Person(20);//匿名对象,特点: 当前行执行结束后,系统会立即回收匿名对象
// 注意事项2
// 不要利用拷贝构造函数 初始化匿名对象 编译器会认为Person (p3) === Person p3;对象声明
// 3.隐式法
Person p4 = 10;//相当于 写了 Person p4 = Person(10); 有参构造
Person p5 = p4;
}
int main() {
test();
system("pause");
return 0;
}
4.2.3拷贝构造函数调用时机
- C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include<iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person默认构造函数的调用" << endl;
}
Person(int age) {
m_Age = age;
cout << "Person有参构造函数的调用" << endl;
}
Person(const Person& person) {
m_Age = person.m_Age;
cout << "Person拷贝构造函数的调用" << endl;
}
~Person() {
cout << "Person析构函数的调用" << endl;
}
int m_Age;
};
// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test1() {
Person p1(20);
Person p2(p1);
cout << "P2的年龄: " << p2.m_Age << endl;
}
// 2.值传递的方式给函数参数传值
Person doWork(Person p) {
p.m_Age = 25;
return p;
}
void test2() {
Person p(12);
doWork(p);
cout << "p的年龄:" << p.m_Age << endl;//12
cout << "拷贝构造调用之后对值的修改:" << (doWork(p)).m_Age << endl; //25
}
// 3.值方式返回局部对象
Person doWork2() {
Person p1;
return p1;
}
void test3() {
Person p3 = doWork2();
}
int main() {
// test1();
// test2();
test3();
system("pause");
return 0;
}
遗留问题(待解决)
- 以值方式返回局部对象,拷贝函数未执行(不符合) 显示:由于G++优化导致,RVO有相关技术详情
4.2.4构造函数调用规则
-
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
-
构造函数的调用规则如下
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会提供其他普通构造函数
#include<iostream>
using namespace std;
// 构造函数的调用规则
class Person {
public:
Person() {
cout << "Person的默认构造函数调用" << endl;
}
Person(int age) {
cout << "Person的有参构造函数调用" << endl;
m_Age = age;
}
Person(const Person &p) {
m_Age = p.m_Age;
cout << "person的拷贝构造函数调用" << endl;
}
~Person() {
cout << "person的析构函数调用" << endl;
}
int m_Age;
};
void test1() {
Person p;
p.m_Age = 18;
/*
如果不自定义拷贝函数,编译器也会提供默认的拷贝函数,进行值拷贝 p2.m_Age 依旧为 18
*/
Person p2(p);
cout << "P2的年龄是:" << p2.m_Age << endl; //18
}
void test2() {
Person p(28);
Person p2(p);
cout << "p2的年龄:" << p2.m_Age << endl;// 28
}
void test3() {
Person p;
}
int main() {
// test1();
// test2();
// 如果只提供拷贝函数,其他普通函数不提供
test3();// error
system("pause");
return 0;
}
4.2.5深拷贝与浅拷贝
- 深浅拷贝的区别
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
class Person {
public:
Person() {
cout << "默认构造函数的调用" << endl;
}
Person(int age,int height) {
cout << "有参构造函数的调用" << endl;
m_Age = age;
m_Height = new int(height);
}
~Person() {
// 析构代码,将堆区开辟数据做释放操作
if (m_Height != NULL) {
delete m_Height;
// 防止野指针
m_Height = NULL;
}
cout << "析构函数的调用" << endl;
}
//解决拷贝函数的问题(浅拷贝)
Person(const Person &p) {
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
// m_Height = p.m_Height; 编译器默认实现就是这行代码
// 深拷贝
m_Height = new int(*p.m_Height);
}
int m_Age;
int* m_Height;
};
/*
* 栈区先进后出
* p2释放执行后,p1再去释放(非法操作)
* 浅拷贝带来的问题就是堆区内存重复释放
*/
void test1() {
Person p1(18,180);
cout << "p1的年龄:" <<p1.m_Age<<"身高为:" <<*p1.m_Height<< endl; // 18 180
Person p2(p1);
cout << "p1的年龄:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;// 18 180
}
int main() {
test1();
system("pause");
return 0;
}
4.2.6初始化列表
- 作用
- C++提供了初始化列表语法,用来初始化属性
- 语法
- 构造函数(): 属性1(值1),属性2(值2),…{}
#include<iostream>
using namespace std;
class Person {
public:
//传统方式
/*Person(int a, int b, int c) {
m_A = a;
m_B = b;
m_C = c;
}*/
// 初始化列表初始化属性
Person(int a,int b,int c) : m_A(a), m_B(b), m_C(c) {
}
int m_A;
int m_B;
int m_C;
};
void test1() {
//Person p(1, 2, 30);
Person p(10,50,100);
cout << p.m_A << endl;
cout << p.m_B << endl;
cout << p.m_C << endl;
}
int main() {
test1();
system("pause");
return 0;
}
4.2.7类对象作为类成员
- C++类中的成员可以是另一个类的对象,我们称为该成员为 对象成员
#include<iostream>
#include<string>
using namespace std;
class Phone {
public:
Phone(string pName,float pPrice): p_Name(pName),p_Price(pPrice) {
cout << "手机的构造函数调用" << endl;
}
~Phone() {
cout << "手机的析构函数调用" << endl;
}
// 手机品牌
string p_Name;
// 手机价格
float p_Price;
};
class Person {
public:
Person(string name,Phone phone): m_Name(name),m_Phone(phone) {
cout << "人的构造函数调用" << endl;
}
~Person() {
cout << "人的析构函数调用" << endl;
}
//姓名
string m_Name;
//物品
Phone m_Phone;
};
// 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身;析构的顺序相反
void test1() {
Phone ph("华为", 7999);
Person p("张三", ph);
}
int main() {
test1();
system("pause");
return 0;
}
4.2.8静态成员
- 静态成员就是成员变量和成员函数前加上关键字static,称为静态成员
8.1静态成员变量(有访问权限)
- 所有对象共享同一份数据
- 在编译阶段分配内存(全局区)
- 类内声明,类外初始化
#include<iostream>
using namespace std;
// 静态成员变量
class Person {
public:
static int m_A;
};
int Person::m_A = 100;
void test1() {
Person p;
cout << p.m_A << endl; // 100
Person p2;
p2.m_A = 200;
cout << p.m_A << endl;// 200
}
void test2() {
//静态成员变量 不属于某一个对象上,所有对象共享同一份数据
//1.通过对象进行访问
Person p;
cout << p.m_A << endl;
//2.通过类名进行访问
cout << Person::m_A << endl;
}
int main() {
//test1();
test2();
system("pause");
return 0;
}
8.2静态成员函数(都有访问权限)
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
class Person {
public:
static void func() {
m_A = 200;// 静态成员函数可以访问静态成员变量
// m_B = 300; // 静态成员函数不能访问非静态成员变量 无法区分那个对象的属性
cout << "static void func的调用" << endl;
}
static int m_A;
int m_B;
};
int Person::m_A = 100;
void test1() {
//1.通过对象访问
Person p;
p.func();
//2.通过类名访问
Person::func();
}
int main() {
test1();
system("pause");
return 0;
}
4.3C++对象模型和this指针
4.3.1成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
#include<iostream>
using namespace std;
class Person {
int m_A;// 非静态成员变量 属于类的对象上
static int m_B;// 静态成员变量 不属于类对象上
void func() {} // 非静态成员函数 不属于类对象上
static void func2(){}// 静态成员函数 不属于类对象上
};
int Person::m_B = 100;
void test1() {
Person p;
// c++编译器会给每个对象分配一个字节空间,是为了区分空对象占内存的位置
// 每个对象也应该有一个独一无二的内存地址
cout << "size of p = " << sizeof(p) << endl;// 4
}
int main() {
test1();
system("pause");
return 0;
}
4.3.2this指针概念
- this指针指向被调用的成员函数所属的对象
- this指针的用途
- 当形参和成员变量同名时,可以用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
#include<iostream>
using namespace std;
class Person {
public:
Person(int age) {
// this指针指向 被调用的成员函数 所属的对象
this->age = age;
}
// 返回Person值对象,会默认调用拷贝构造函数,复制一份当前所调对象
Person& PersonAddAge(Person &p) {
this->age += p.age;
return *this;
}
int age;
};
// 解决名称冲突
void test1() {
Person p(18);
cout << "年龄大小:" << p.age << endl;// 18
}
// 返回对象本身用*this
void test2() {
Person p1(10);
Person p2(20);
p2.PersonAddAge(p1).PersonAddAge(p1);
cout << "年龄:" << p2.age << endl;// 40
}
int main() {
//test1();
test2();
system("pause");
return 0;
}
4.3.3空指针访问成员函数
- C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream>
using namespace std;
// 空指针调用成员函数
class Person
{
public:
void showClassName() {
cout << "this is Person class" << endl;
}
// m_Age 默认 this->m_Age
// 报错原因 传入的指针为NULL
void showPersonAge() {
// 提高健壮性
if (this == NULL) {
return;
}
cout << "age=" << m_Age << endl;
}
int m_Age;
};
void test01() {
// 空指针
Person* p = NULL;
p->showClassName();
p->showPersonAge();
}
int main() {
test01();
system("pause");
return 0 ;
}
4.3.4const修饰成员函数
4.1常函数
- 成员函数后加const后,我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明是加关键词mutable后,在常函数总依然可以修改
4.2常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include<iostream>
using namespace std;
// 常函数
class Person
{
public:
// this指针的本质 是指针常量 指针的指向是不可以修改的
// const Person * const this;
// 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson() const
{
this->m_B = 120;
// this->m_A = 100;
// this = NULL; //this指针不可以修改指针指向的
}
void showNum() {
m_A = 130;
}
int m_A;
mutable int m_B;// 特殊变量,即使在常函数中可以修改,加mutable关键词
};
void test1() {
Person p;
p.showPerson();
}
void test2() {
const Person p2;// 在对象前面加上const,变为常对象
//p2.m_A = 100;
// m_B是特殊值,在常对象下也可以修改
p2.m_B = 120;
p2.showPerson();
// 常对象,不可以调用普通成员函数,因为普通成员函数可以修改属性
// p2.showNum();
}
int main() {
test2();
system("pause");
return 0;
}
4.4友元
- 在程序中,有些私有属性 也想让类外的特殊的一些函数或类进行访问,就需要用到友元的技术
- 友元的关键词为friend
4.4.1友元的三种实现
1.1全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building
{
//GoodF全局函数是Building的友元,可以访问Building中的私有成员
friend void goodF(Building* building);
public:
string m_SittingRom;
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;
};
// 全局函数
void goodF(Building* building)
{
cout << "全局函数被调用,当前访问:"<< building->m_SittingRoom << endl;
// 添加了friend关键词,可以访问私有成员
cout << "全局函数被调用,当前访问:" << building->m_BedRoom << endl;
}
void test1() {
Building building;
goodF(&building);
}
int main() {
test1();
system("pause");
return 0;
}
1.2类做友元
#include<iostream>
#include<string>
using namespace std;
// 类做友元
class Building;
class GoodFri
{
public:
GoodFri();
void visit();
Building* building;
};
class Building
{
friend class GoodFri;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodFri::GoodFri()
{
// 创建建筑物对象
building = new Building;
}
void GoodFri::visit()
{
cout << "正在访问:" << building->m_SittingRoom << endl;
cout << "正在访问:" << building->m_BedRoom << endl;
}
void test1() {
GoodFri gf;
gf.visit();
}
int main() {
test1();
system("pause");
return 0;
}
1.3成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodFri;
class GoodFri
{
public:
GoodFri();
void visit();// visit()函数能访问Building中的私有成员
void visit2();// visit2()函数不能访问Building中的私有成员
Building *building;
};
class Building
{
friend void GoodFri::visit();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodFri::GoodFri()
{
building = new Building;
}
void GoodFri::visit()
{
cout << "当前访问的位置:" << building->m_SittingRoom << endl; //客厅
cout << "当前访问的位置:" << building->m_BedRoom << endl;// 卧室
}
void GoodFri::visit2()
{
cout << "当前访问的位置:" << building->m_SittingRoom << endl;// 客厅
//cout << "当前访问的位置:" << building->m_BedRoom << endl;// error
}
void test1() {
GoodFri gf;
gf.visit();
gf.visit2();
}
int main() {
test1();
system("pause");
return 0;
}
4.5运算符重载
- 运算符重载概念:
- 对已有的运算符重新进行定义,赋予起另一种功能,以适应不同的数据类型
4.5.1加号运算符重载
#include<iostream>
using namespace std;
//加号运算符重载
class Person
{
public:
//成员函数重载+号
/*
* 成员函数重载的本质调用
* Person p3 = p1.operator+(p2);
*/
//Person operator+(Person &p);
int m_A;
int m_B;
};
//Person 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 = operator+(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;
}
void test1()
{
Person p1;
p1.m_A = 20;
p1.m_B = 30;
Person p2;
p2.m_A = 40;
p2.m_B = 20;
Person p3 = p1 + p2;
cout << "m_A=" << p3.m_A << endl;
cout << "m_B=" << p3.m_B << endl;
}
int main() {
test1();
system("pause");
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:
//成员函数重载 左移运算符
/*
* 不会利用成员函数重载<<运算符,因为无法实现 cout在左侧 *
*/
//void operator<<() {};
int m_A;
int m_B;
};
//全局函数重载左移运算符
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B << endl;
return cout;
}
void test1() {
Person p(10,12);
cout << p << endl;
}
int main() {
test1();
system("pause");
return 0;
}
4.5.3递增运算符重载
- 通过重载递增运算符,实现自己的整型数据
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myInt);
public:
MyInteger()
{
m_NUM = 0;
}
//重载前置++运算符
MyInteger& operator++();
//重置后置++运算符 int代表占位参数,可以用于区分前置和后置递增
MyInteger operator++(int);
private:
int m_NUM;
};
MyInteger& MyInteger::operator++()
{
m_NUM++;
return *this;
}
MyInteger MyInteger::operator++(int) {
// 先 记录当时结果
MyInteger temp = *this;
// 后 递增
m_NUM++;
// 最后将记录结果做返回
return temp;
}
//重载<<运算符
ostream& operator<<(ostream& cout, MyInteger myInt)
{
cout << myInt.m_NUM;
return cout;
}
void test1() {
MyInteger myInteger;
cout << ++myInteger << endl;
cout << myInteger << endl;
}
void test2() {
MyInteger myInteger;
cout << myInteger++ << endl;
cout << myInteger << endl;
}
int main() {
//test1();
test2();
system("pause");
return 0;
}
4.5.4赋值运算符重载
- c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝函数,属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
#include<iostream>
using namespace std;
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);
int* m_Age;
};
Person& Person::operator=(Person& p)
{
//先判断是否有属性在堆区,如果有先释放,在做深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);
return *this;
}
void test1()
{
Person p1(18);
cout << "年龄:" << *p1.m_Age << endl;
Person p2(20);
cout << "年龄: " << *p2.m_Age << endl;
p2 = p1;
cout << "年龄: " << *p2.m_Age << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
4.5.5关系运算符重载
- 重载关系运算符,可以自定义类型对象进行对比操作
#include<iostream>
using namespace std;
class Person;
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
bool operator==(Person &p);
string m_Name;
int m_Age;
};
bool Person::operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return true;
}
return false;
}
void test1() {
Person p1("Tom", 20);
Person p2("Tom", 23);
if (p1 == p2) {
cout << "p1和p2是相等的" << endl;
}
else {
cout << "p1和p2是不相等的" << endl;
}
}
int main() {
test1();
system("pause");
return 0;
}
4.5.6函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的使用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include<iostream> #include<string> using namespace std; class MyPoint { public: //重载函数调用运算符 void operator()(string str); }; void MyPoint::operator()(string str) { cout << "重载函数调用运算符===>"<<str << endl; } void test1() { MyPoint myPoint; // 使用起来非常类似于函数调用,因此称为仿函数 myPoint("hello world"); } int main() { test1(); system("pause"); return 0; }
4.6继承
4.6.1继承的基本语法
- 继承的好处
- 减少重复代码
- 语法
class 子类 : 继承方式 父类
#include<iostream>
using namespace std;
Java页面
//class Java
//{
//public:
// void header();
// void footer();
// void left();
// void content();
//};
//
//void Java::header()
//{
// cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void Java::footer()
//{
// cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void Java::left()
//{
// cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void Java::content()
//{
// cout << "JAVA学科的视频" << endl;
//}
//
Python页面
//class Python
//{
//public:
// void header();
// void footer();
// void left();
// void content();
//};
//
//void Python::header()
//{
// cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void Python::footer()
//{
// cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void Python::left()
//{
// cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void Python::content()
//{
// cout << "Python学科的视频" << endl;
//}
//
CPP页面
//
Python页面
//class CPP
//{
//public:
// void header();
// void footer();
// void left();
// void content();
//};
//
//void CPP::header()
//{
// cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//}
//void CPP::footer()
//{
// cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
//}
//void CPP::left()
//{
// cout << "JAVA、Python、C++...(公共分类列表)" << endl;
//}
//void CPP::content()
//{
// cout << "C++学科的视频" << endl;
//}
//公共部分
class BasePage
{
public:
void header();
void footer();
void left();
};
void BasePage::header()
{
cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
}
void BasePage::left()
{
cout << "JAVA、Python、C++...(公共分类列表)" << endl;
}
void BasePage::footer()
{
cout << "帮助中心、交流中心、站内地图...(公告底部)" << endl;
}
//JAVA页面
class Java : public BasePage
{
public:
void content();
};
void Java::content()
{
cout << "Java视频下载页面" << endl;
}
//Python页面
class Python : public BasePage
{
public:
void content();
};
void Python::content()
{
cout << "Python视频下载页面" << endl;
}
//C++页面
class CPP : public BasePage
{
public:
void content();
};
void CPP::content()
{
cout << "C++视频下载页面" << endl;
}
void test1() {
cout << "JAVA下载视频页面如下" << endl;
Java ja;
ja.header();
ja.left();
ja.content();
ja.footer();
cout << "---------------------------------" << endl;
cout << "Python下载视频页面如下" << endl;
Python py;
py.header();
py.left();
py.content();
py.footer();
cout << "---------------------------------" << endl;
cout << "C++下载视频页面如下" << endl;
CPP cpp;
cpp.header();
cpp.left();
cpp.content();
cpp.footer();
}
int main()
{
test1();
system("pause");
return 0;
}
4.6.2继承方式
2.1继承方式共有三种
1.1公共继承
#include<iostream>
using namespace std;
//继承方式----公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : public Base1
{
public:
void func();
};
void Son1::func()
{
//父类中的公共权限成员 到子类中依然是公共权限
m_A = 10;
//父类中的保护权限成员 到子类依然是保护权限
m_B = 20;
//父类中的私有权限成员 子类访问不了
//m_C = 30;
}
void test1()
{
Son1 s1;
s1.m_A = 12;
//保护权限成员访问不到 类外访问不到
// s1.m_B = 13;
}
int main()
{
test1();
system("pause");
return 0;
}
1.2保护继承
#include<iostream>
using namespace std;
//继承方式----公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : protected Base1
{
public:
void func();
};
void Son1::func()
{
//父类中的公共权限成员 到子类中是保护权限
m_A = 10;
//父类中的保护权限成员 到子类依然是保护权限
m_B = 20;
//父类中的私有权限成员 子类访问不了
//m_C = 30;
}
void test1()
{
Son1 s1;
//保护权限成员访问不到 类外访问不到
//s1.m_A = 12;
//保护权限成员访问不到 类外访问不到
// s1.m_B = 13;
}
int main()
{
test1();
system("pause");
return 0;
}
1.3私有继承
#include<iostream>
using namespace std;
//继承方式----公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : private Base1
{
public:
void func();
};
void Son1::func()
{
//父类中的公共权限成员 到子类中是私有权限
m_A = 10;
//父类中的保护权限成员 到子类中是私有权限
m_B = 20;
//父类中的私有权限成员 子类访问不了
//m_C = 30;
}
void test1()
{
Son1 s1;
//私有权限成员访问不到 类外访问不到
//s1.m_A = 12;
//私有权限成员访问不到 类外访问不到
// s1.m_B = 13;
}
int main()
{
test1();
system("pause");
return 0;
}
4.6.3继承中的对象模型
- 父类中所有非静态成员属性都会被子类继承下去
- 父类中私有成员属性 是被编译器给隐藏了 因此访问不到,但是确实被继承了
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Person
{
public:
int m_D;
};
void test1() {
/*
* 父类中所有非静态成员属性都会被子类继承下去
* 父类中私有成员属性 是编译器给隐藏了 因此访问不到,但是确实被继承了
*/
cout << "sizeof = " << sizeof(Son) << endl;// 16
}
int main()
{
test1();
system("pause");
return 0;
}
3.1打开开发人员命令提示工具查看对象模型
- 找到文件所在位置(可用dir查看一下)
- 查看命令
cl /d1 reportSingleClassLayout类名 "文件名"
4.6.4继承中的构造和析构顺序
- 子类继承父类后,当创建子类对象,也会调用父类的构造函数
- 顺序
- 先构造父类,再构造子类
- 先子类析构,再父类析构
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "调用父类的构造函数" << endl;
}
~Person()
{
cout << "调用父类的析构函数" << endl;
}
};
class Son : public Person
{
public:
Son()
{
cout << "调用子类的构造函数" << endl;
}
~Son()
{
cout << "调用子类的析构函数" << endl;
}
};
void test1()
{
/*
调用父类的构造函数
调用父类的析构函数
*/
// Person p;
/*
调用父类的构造函数
调用子类的构造函数
调用子类的析构函数
调用父类的析构函数
*/
Son son;
}
int main() {
test1();
system("pause");
return 0;
}
4.6.5继承同名成员处理方式
- 访问子类同名成员 直接访问
- 访问父类同名成员 需要加作用域
- 子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有的同名成员函数
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base of func的函数" << endl;
}
void func(int a)
{
cout << "Base of func(int a)的函数" << endl;
}
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 112;
}
void func()
{
cout << "Son of func的函数" << endl;
}
int m_A;
};
// 同名成员属性
void test1()
{
Son son;
cout << "Son of m_A = " << son.m_A << endl; // 112
// 如果通过子类对象访问父类同名成员,需要加作用域
cout << "Base of m_A = " << son.Base::m_A << endl; // 100
}
// 同名成员函数
void test2()
{
Son son;
son.func(); //直接调用 调用是子类的func函数
son.Base::func();//调用父类的func函数
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有的同名成员函数
// son.func(120);
son.Base::func(12);
}
int main()
{
// test1();
test2();
system("pause");
return 0;
}
4.6.6继承同名静态成员处理方式
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
- 子类中出现和父类同名的静态成员函数,子类的同名成员会隐藏父类中所有的同名成员函数
#include<iostream>
using namespace std;
class Base
{
public:
static void func()
{
cout << "Base of func()" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base
{
public:
static void func()
{
cout << "Son of func()" << endl;
}
static int m_A;
};
int Son::m_A = 120;
// 同名成员属性
void test1()
{
// 通过对象访问
Son son;
cout << "Son of m_A = " << son.m_A << endl;// 120
cout << "Base of m_A = " << son.Base::m_A << endl; // 100
// 通过类名访问
cout << "Son of m_A = " << Son::m_A << endl; //120
// 第一::代表通过类名方式访问 第二个::代表父类的作用域访问
cout << "Base of m_A = " << Son::Base::m_A << endl;
}
// 同名成员函数
void test2()
{
// 通过对象方式访问
Son son;
son.func();
son.Base::func();
// 通过类名方式访问
/*
Son of func()
Base of func()
*/
Son::func();
Son::Base::func();
}
int main()
{
// test1();
test2();
system("pause");
return 0;
}
4.6.7多继承语法
- C++允许一个类继承多个类
- 语法
class 子类: 继承方式 父类1,继承方式 父类2,...
- 实际开发环境不建议多继承
#include<iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 110;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 120;
}
int m_A;
};
class Base3
{
public:
Base3()
{
m_A = 130;
}
int m_A;
};
class Son :public Base1, public Base2, public Base3
{
public:
Son() {
m_A = 200;
}
int m_A;
};
void test1()
{
Son son;
cout << "Son of m_A = " << son.m_A << endl;// 200
cout << "Base1 of m_A = " << son.Base1::m_A << endl;// 110
cout << "Base3 of m_A = " << son.Base3::m_A << endl;// 130
}
int main()
{
test1();
system("pause");
return 0;
}
4.6.8菱形继承
8.1菱形继承概念
- 两个派生类继承同一个基类
- 某一个类同时继承两个派生类
- 这种继承方式称为菱形继承或钻石继承
#include<iostream>
using namespace std;
/*
* 菱形继承的问题
* 菱形继承会导致继承两份数据,资源浪费
* 解决方式----虚继承 加上virtual
*/
// 动物类
class Animal
{
public:
int m_Age;
};
// 羊类
class Sheep :virtual public Animal
{
public:
};
//驼类
class Camel:virtual public Animal
{
public:
};
class Alpaca:public Camel,public Sheep
{
public:
};
void test1()
{
Alpaca alpaca;
alpaca.Camel::m_Age = 5;
alpaca.Sheep::m_Age = 8;
//cout << "驼的年龄 age = " << alpaca.Camel::m_Age << endl;// 5
//cout << "羊的年龄 age = " << alpaca.Sheep::m_Age << endl;// 8
//添加了virtual关键词,实现虚继承
cout << "驼的年龄 age = " << alpaca.Camel::m_Age << endl;// 8
cout << "羊的年龄 age = " << alpaca.Sheep::m_Age << endl;// 8
cout << alpaca.m_Age << endl;// 8
}
int main()
{
test1();
system("pause");
return 0;
}
4.7多态
4.7.1多态的基本概念
1.1多态分为两类
- 静态多态
- 函数重载和运算符重载属于静态多态,复用函数名
- 动态多态
- 派生类和虚函数实现运行时多态
1.2静态多态和动态多态
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
1.3动态多态满足条件
- 有继承关系
- 子类重写父类的虚函数(virtual)
1.4动态多态使用
- 父类的指针或引用 指向子类的对象
#include<iostream>
using namespace std;
// 多态
//动物类
class Animal
{
public:
virtual void speak();
};
// 猫类
class Cat :public Animal
{
public:
void speak();
};
void Animal::speak()
{
cout << "动物在讲话" << endl;
}
void Cat::speak()
{
cout << "喵~喵~喵~~~" << endl;
}
// 地址早绑定 在编译阶段确定函数地址
// 如果想执行猫在说话,那么这个函数地址就不能提前绑定,需要在运行阶段绑定,地址晚绑定
// 可以加virtual,实现晚绑定
void doSpeak(Animal& animal)
{
animal.speak();
}
void test1()
{
Cat cat;
// 未加virtual
// doSpeak(cat);// 动物在说话
doSpeak(cat);//喵~喵~喵~~
}
int main()
{
test1();
system("pause");
return 0;
}
注意点: 上图默认在Cat类重写了speak()函数,如果不重写speak()函数,Cat类的虚拟函数指针指向的虚拟函数表依旧是&Animal::speak
4.7.2纯虚函数和抽象函数
2.1抽象类特点
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
- 当类有纯虚函数,这个类也称为抽象类
#include<iostream>
using namespace std;
// 纯虚函数 和 抽象类
class Base
{
public:
virtual void func() = 0;
};
class Son : public Base
{
public:
void func() {
cout << "Son类重写Base的纯虚函数" << endl;
}
};
void test1() {
//抽象类不能实例化
// Base base;
//new Base;
// Son son;// 子类必须重写父类中的纯虚函数,否则无法实例化对象
Base* base = new Son;
base->func();
}
int main()
{
test1();
system("pause");
return 0;
}
4.7.3虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟了堆区,那么父类指针在释放时,无法调用到子类的析构代码
- 解决方式
- 将父类中的析构函数改为虚析构或纯虚析构
3.1虚析构和纯虚析构共性
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
3.2虚析构和纯虚析构区别
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
3.3语法
- 虚析构
virtual ~类名(){}
- 纯虚析构
virtual ~类名() = 0;
类名::~类名(){}
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
// 利用虚析构可以解决,父类指针释放子类对象释放不干净的问题
/*virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}*/
// 纯虚析构
virtual ~Animal() = 0;
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "Animal析构函数调用" << endl;
}
class Cat:public Animal
{
public:
Cat(string name) {
cout << "Cat构造函数调用" << endl;
this->m_Name = new string(name);
}
void speak()
{
cout <<*m_Name<< "喵~~喵~~~喵~~~~" << endl;
}
~Cat()
{
if (m_Name != NULL) {
cout << "Cat的析构函数" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test1()
{
Animal* am = new Cat("Tommy");
am->speak();
// 父类指针在析构时候,不会调用子类的析构函数,导致子类如果在堆区属性,出现内存泄露
delete am;
}
int main()
{
test1();
system("pause");
return 0;
}