继承的好处:减少重复的代码
语法: 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个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 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();
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)