C++核心编程<类和对象>(4)

本文详细介绍了C++中的类和对象的概念,包括封装的意义、struct与class的区别、成员属性的访问权限、对象的初始化和清理(构造函数和析构函数)、拷贝构造函数的调用时机、深拷贝与浅拷贝的差异。此外,还讨论了this指针、友元、运算符重载、继承方式(公共、保护、私有)以及多态的基本概念,如虚函数和抽象类。
摘要由CSDN通过智能技术生成

C++核心编程<类和对象>

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 私有权限
      • 成员 类内可以访问 类外不可以访问 继承中子类不可以访问父类
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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值