【c++】继承+多态+虚函数+运算符重载

【c++】继承+多态+虚函数+运算符重载,第1张

继承

继承的好处:减少重复的代码

语法: class 子类名:继承方式 父类名
子类(派生类)父类(基类)
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。


继承方式一共有三种:
1.公共继承 public 继承下来的成员属性不改变
2.保护继承 protected 继承下来的成员属性都变为protected
3.私有继承 private 继承下来的成员属性都变为private
三种继承方式下,父类的私有成员都不可以继承

创建一个子类时:先调用父类构造函数,再调用子类构造函数
释放时:先调用子类析构函数,再调用父类析构函数

子类对象反问子类和父类中继承的同名成员的处理方式:

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
#include
#include
using namespace std;

class Father {
public:
	Father() {
		cout << "父类构造函数" << endl;
	}
	Father(int a, int b, int c) {
		this->a = a;
		this->b = b;
		this->c = c;
		cout << "父类构造函数" << endl;
	}
	~Father() {
		cout << "父类析构函数" << endl;
	}
public:
	int a;
protected:
	int b;
private:
	int c;//私有成员只是被隐藏了,但是还是会继承下去
};

class Son:public Father{
public:
	int a;
	int b;
	Son() {
		cout << "子类构造函数" << endl;
	}
	~Son() {
		cout << "子类析构函数" << endl;
	}
};

void test(){
	Son son;
	//父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
	cout << "sizeof Son = " << sizeof(Son) << endl;
	son.a = 10; //访问子类同名成员   直接访问即可
	son.Father::a = 20;//访问父类同名成员   需要加作用域
}

void main() {
	test();
}

多继承可能会引发父类中有同名成员出现,需要加作用域区分

不建议使用各种交叉继承等,造成混乱和浪费空间(继承了同一个成员),同一个数据出现两次,造成二歧性
利用虚继承可以解决二歧性问题 class A : virtual public B {};

#include
#include
using namespace std;

class Per {
public:
	int age;
};

class Pera :virtual public Per {};

class Perb :virtual public Per {};

class Perc :public Pera, public Perb {};

void test() {
	Perc p;

	cout << sizeof(Perc) << endl;

	//Perc下面的age成员其实是共用一块数据,一个改了就全改了
	p.age = 10;
	cout << p.age << endl;
	cout << p.Pera::age << endl;
	cout << p.Perb::age << endl;

	p.Pera::age = 15;
	cout << p.age << endl;
	cout << p.Pera::age << endl;
	cout << p.Perb::age << endl;

}

void main() {
	test();
	system("pause");
}
多态

父类指针或引用指向子类对象,可调用子类成员和函数

多态分为两类
1.静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
2.动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址

多态满足条件
1.有继承关系
2.子类重写父类中的虚函数

多态使用条件
1.父类指针或引用指向子类对象
2.重写:函数返回值类型 函数名 参数列表 完全一致称为重写

注意(重点):
1.多态就算提供一个接口,根据传入的对象不同有多种实现的形态
2.父类指针或引用指向子类对象 Base *p=new Son;(指针) Base &p=son;(引用)

#include
using namespace std;

class Animal {
public:
	//Speak函数就是虚函数
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。


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 test() { Cat cat; dospeak(cat); Dog dog; dospeak(dog); } void main() { test(); }

纯虚函数和抽象类

纯虚函数:
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类

#include
#include
using namespace std;

class Base {
public:
	virtual void func() = 0;//当类中只要有一个纯虚函数,这个类也称为==抽象类==
};

class Son :public Base {//子类必须重写抽象类中的纯虚函数,否则也属于抽象类
public:
	virtual void func() {
		cout << "子类必须重写抽象类中的纯虚函数" << endl;
	}
};

void test() {
	//Base a;//抽象类无法实例化对象,报错
	//new Base;//抽象类无法使用new开辟空间,报错
	Son a;
	Base* p = new Son;
	p->func();
}

void main() {
	test();
}

虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区(用 new),那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构(要声明也要实现)

虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
纯虚析构函数和纯虚函数不一样,纯虚析构函数也需要实现(写在类外面)(因为父类也有空间要释放)

虚析构和纯虚析构区别:
1.如果是纯虚析构,该类属于抽象类,无法实例化对象

总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类

#include
#include

using namespace std;

class Animal {
public:
	Animal() {
		cout << "父类构造函数" << endl;
	}
	//virtual ~Animal() {//利用虚析构函数可以解决  父类指针无法释放子类对象时不干净的问题
	//	cout << "父类虚析构函数" << endl;
	//}

	virtual ~Animal() = 0;//需要声明,需要实现
	virtual void speak() = 0;	
};

Animal::~Animal() {
	cout << "父类纯虚析构函数" << endl;
}

class Cat:public Animal {
public:
	Cat(string name) {
		this->name = new string(name);//对象属性姓名存储在堆区域
		cout << "子类构造函数" << endl;
	}
	~Cat() {
		cout << "子类析构函数" << endl;
		if (name != NULL) {
			delete name;
			name = NULL;
		}
	}
	virtual void speak() {
		cout << *this->name <<"在说话" << endl;
	}
	string *name;

};

void test() {
	Animal *animal = new Cat("Tom");//实例化的对象存储在堆区域
	animal->speak();
	delete animal;
	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	//怎么解决?给基类增加一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象
}

void main() {
	test();
	system("pause");
}
运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

“+”号运算符重载
作用:实现两个自定义数据类型相加的运算
实现:1.成员函数实现“+”号运算符重载
2.全局函数实现“+”号运算符重载
总结:1.对于内置的数据类型的表达式的的运算符是不可能改变的
2.不要滥用运算符重载

“<<”左移运算符重载
实现:全局函数

“++”递增运算符重载,包括前置递增运算符重载和后置递增运算符重载
实现:成员函数

#include
#include
using namespace std;

class Person {
public:
	int A;
	int B;

	void show() {
		cout << "A:" << A << "\tB:" << B << endl;
	}
	//Person operator+(Person &p) {//1.成员函数实现“ + ”号运算符重载
	//	Person temp;
	//	temp.A = this->A + p.A;
	//	temp.B = this->B + p.B;
	//	return temp;
	//}

	//我们不用成员函数实现<<运算符重载
	//成员函数 实现不了  p << cout 不是我们想要的效果
	//void operator<<(Person& p){
	//}

	Person& operator++() {//前置递增
		this->A++;//先+
		this->B++;
		return *this;//再返回
		//前置递增可以链式 *** 作,所以返回引用(本体)
	}

	Person operator++(int) {//用占位符实现函数重载,区分前置和后置
		Person temp = *this;//先记录
		A++;//先+
		B++;
		return temp;//再返回
		//后置递增不能链式 *** 作,所以返回类(形参)
	}

	//通过析构函数可以来看变量的释放情况
	//~Person() {
	//	cout << "释放类的变量了" << endl;
	//}

};

Person operator+(Person p1, Person &p2) {//2.全局函数实现“+”号运算符重载
	Person temp;
	temp.A = p1.A + p2.A;
	temp.B = p1.B + p2.B;
	return temp;
}

//全局函数实现“<<”左移运算符重载
ostream& operator<<(ostream &out, Person p) {//cout是标准的数据输出流,只有一个,所以只能用引用,引用是起别名,所以命名可以随便,例:out
	out << "A:" << p.A << "\tB:" << p.B;//为什么这个地方用引用传递Person &p,在91行(cout << "p2++:\t\t" << p2++ << endl;//后置递增)报错
	return out;
}

void test() {
	//“+”号运算符重载
	Person p1;
	p1.A = 10;
	p1.B = 10;
	Person p2;
	p2.A = 15;
	p2.B = 15;
	Person p3 = p1 + p2;

	cout << "p1.A=10\tp1.B = 10" << endl;
	cout << "p2.A=15\tp1.B = 15" << endl;
	cout << "p3=p1+p2\t";
	p3.show();

	//“ << ”左移运算符重载
	cout << "p3:\t\t"<< p3 << "\t链式思想" << endl;

	//“++”递增运算符重载
	cout << "++++++p1:\t"<< ++++++p1 << endl;//前置递增
	cout << "p1:\t\t" << p1 << endl;//前置递增
	cout << "p2++:\t\t" << p2++ << endl;//后置递增
	cout << "p2:\t\t" << p2 << endl;//后置递增
}

void main() {
	test();
	system("pause");
}

"="符号重载

c++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=, 对属性进行值拷贝

如果类中有属性指向堆区,做赋值 *** 作时也会出现深浅拷贝问题

#include
#include
using namespace std;

class Person {
public:
	int *age;
	Person(int age) {
		this->age = new int(age);
	}

	~Person() {
		if (this->age != NULL) {
			delete this->age;//delete实际上是释放指针指向的内存空间
			this->age = NULL;//指针置空
		}
		cout << "释放" << endl;
	}

	Person& operator=(Person &p) {
		if (this->age != NULL) {//先检查被赋值的类是否为空
			delete this->age;//delete实际上是释放指针指向的内存空间
			this->age = NULL;//指针置空
		}
		this->age = new int(*p.age);
		return *this;
	}
};

void test() {
	Person p1(18);
	Person p2(20);
	Person p3(30);

	//Person p3 = p2;//不要写这种,这是创建对象,对应的是构造函数,并没有定义这样的构造函数,编译器还是只会复制指针,释放时出错
	p3= p2 = p1;//赋值 *** 作,没有问题
	cout << "p1.age:" << *p1.age << "\t\tp2.age" << *p2.age << "\t\tp3.age" << *p3.age << endl;//值
	cout << "&p1.age:" << p1.age << "\t&p2.age" << p2.age << "\t\t&p3.age" << p3.age << endl;//地址
}

void main() {
	test();
	system("pause");
}

关系运算符重载:== , !=

重载关系运算符,可以让两个自定义类型对象进行对比 *** 作

#include
#include
using namespace std;

class Person {
public:
	int age;
	Person(int age) {
		this->age = age;
	}

	bool operator==(Person &p) {
		if (this->age == p.age) {
			return true;
		}
		else {
			return false;
		}
	}

	bool operator!=(Person& p) {
		if (this->age == p.age) {
			return false;
		}
		else {
			return true;
		}
	}
};

void test() {
	Person p1(23);
	Person p2(24);

	if (p1 == p2) {
		cout << "p1与p2年龄相同" << endl;
	}
	else {
		cout << "p1与p2年龄不相同" << endl;
	}

	if (p1 != p2) {
		cout << "p1与p2年龄不相同" << endl;
	}
	else {
		cout << "p1与p2年龄相同" << endl;
	}
}

void main() {
	test();
}

函数调用运算符()重载

函数调用运算符() 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活

类当函数用,伪函数

#include
#include
using namespace std;

class class_a {
public:
	void operator()() {
		cout << "函数调用运算符()重载" << endl;
	}
	void showprint() {
		cout << "匿名对象调用普通函数" << endl;
	}
};

class class_b {
public:
	int operator()(int a, int b) {
		return a + b;
	}
};

void test() {
	class_a cla;//先创建对象,然后对象当函数用
	cla();//伪函数的调用
	class_b clb;
	cout << "a+b两数之和:" << clb(10,15) << endl;

	//匿名对象,不实例化对象,直接调用类里面的函数
	class_a().showprint();//匿名对象调用普通函数
	cout << "匿名对象调用伪函数:a+b两数之和:" << class_b()(10, 15) << endl;//匿名对象调用伪函数

}

void main() {
	test();
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/569299.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存