一、类与类之间的三大关系
(1)组合:一个类是另一个类的一部分
class A
{
class B
{};
};
(2)代理:一个类的方法是另一个类方法的子集,也就是说这个类它本身是不进行任何的操作,只是调用另一个类来帮它实现它想做的事情。
(3)继承:一个类是另一个类的一种
class Parent
{
};
class child : public Parent
{
};
二、对于父类中三种权限的成员变量以及成员方法被子类继承后的特点
父类 | 继承方式 | 子类 | 外界 |
public | public | public | 可以访问 |
protected | protect | 不能被访问 | |
private | private | ||
protected | public | protected | 不能被访问 |
protected | protected | ||
private | private | ||
private | public | 能继承,不能访问 | 不能被访问 |
protected | |||
private |
注:(1)在继承的时不写继承权限时默认private权限继承
(2)继承以后,以继承时的权限和本身的权限保密等级高的为主
三、构造:先构造父类,再构造子类,也就是说在子类构造之前就要先构造父类
class A
{
public:
A(int tmp = 10)
{
a = tmp;
}
int a;
};
class Aa : public A
{
public:
Aa(int tmp1, int tmp2)
:A(tmp1)//先构造父类 //如果在这里写:a(tmp1)那就是错误的,这个是赋值不是初始化
{
a = tmp1;
a1 = tmp2;
}
int a;
int a1;
};
四、成员方法之间的三大关系
(1)重载
条件:作用域相同、函数名相同、参数列表不同
显然父类和子类是两个不同的作用域,所以继承中不存在重载
(2)隐藏:子类会隐藏父类中成员方法名相同的成员方法
代码解释:
class B
{
public:
void showa()
{
cout<<"B::void showa()"<<endl;
}
void showb()
{
cout<<"B::void showb()"<<endl;
}
void show()
{
cout<<"B::void show()"<<endl;
}
};
class D : public B
{
public:
void showa()
{
cout<<"D::void showa()"<<endl;
}
void showb(int b)
{
cout<<b<<"D::void showb()"<<endl;
}
int show()
{
cout<<"D::int show()"<<endl;
return 0;
}
};
int main()
{
B b;
b.show();
b.showa();
b.showb();
D d;
d.show();
d.showa();
d.showb(10);
return 0;
}
结果展示:
根据结果我们发现,不论是子类定义的对象还是父类定义的对象,在调用成员方法的时候都调用的是子类的成员方法,这时我们称子类隐藏了父类同名的成员方法,根据上面代码的验证,不管返回值、参数列表是否相同,只要函数名相同就会被隐藏。
如果要调用父类的成员方法怎么做呢?(即调用被隐藏的成员方法)加上作用域即可
代码修改
//上面类的代码与上一个代码完全一样,不再重写
int main()
{
B b;
b.B::show();
b.B::showa();
b.B::showb();
D d;
d.B::show();
d.B::showa();
d.B::showb();
return 0;
}
结果展示:
(3)覆盖(重写):只会发生在虚函数列表中,如果子类成员方法与父类中虚函数同名、同返回值、同参数列表,则子类中的函数会覆盖虚函数并将本类中的这个函数地址cunzau虚函数列表中
在继承关系中提供的由子类构造父类的构造方法:先利用子类构造一个父类对象,用父类对象给 an (代码中定义的一个父类对象)赋值
子类对象 = 父类对象 // 这种构造是错误的,因为子类继承了父类,如果这样写就是增加了无效的访问空间
父类对象 = 子类对象 //这种构造方法正确
子类指针 = 父类指针 //错误,与上面的相同,当子类访问子类本身的成员或成员方法时,它已经不存在了
父类指针 = 子类指针 //正确
虚函数表
父类中的虚函数继承到子类依旧是虚函数(子类中与与父类同返回值、同函数名、同参数列表时,子类中的这个方法也是虚函数)
先析构子类,后析构父类,又因为是继承关系,所以在调用子类析构之后会自动调用父类的析构
class Animal
{
public:
Animal(char* name)
{
_name = new char[strlen(name) + 1];
strcpy_s(_name,strlen(name) + 1,name);
}
void eat()
{
cout<<_name<<":eat eat eat"<<endl;
}
void call()
{
cout<<_name<<" wo ye bu zhi dao jiao sha"<<endl;;
}
~Animal()
{
cout << "Animal::~Animal()" << endl;
if(NULL != _name)
{
delete[]_name;
}
}
protected:
char* _name;
};
class Dog : public Animal
{
public:
Dog(char* name)
:Animal(name)
{}
void call()
{
cout<<_name<<"wang wang wang"<<endl;
}
~Dog()
{
cout<<"~Dog()"<<endl;
}
char* _name;
};
int main
{
Animal *pa = new Dog("dog");
delete pa;
}
结果展示:
我们发现它只调用了父类的析构,而没调用子类的,所以这一定会造成一定的内存泄漏,但是上面说了,调用子类的析构之后会自动调用父类的析构,所以我们现在只需要调用子类的析构即可,但是怎么调用呢,指针是父类的指针,人家调用的肯定是自己的析构呀,所以这里我们用到虚函数
虚函数表:里面存的是虚函数的入口地址
覆盖只可能发生在虚函数的情况下,为解决上述问题,我们将父类的析构函数设成虚函数,那么它就会存在虚函数表中,而又让父类指向了子类,所以当子类发现它是虚函数的时候,它就会去虚函数表中找到这个函数析并将它覆盖,此时再调用的时候就会调用这个虚函数,从而实现了两个都被析构
代码展示:
class Animal
{
public:
Animal(char* name)
{
_name = new char[strlen(name) + 1];
strcpy_s(_name,strlen(name) + 1,name);
}
void eat()
{
cout<<_name<<":eat eat eat"<<endl;
}
void call()
{
cout<<_name<<" wo ye bu zhi dao jiao sha"<<endl;;
}
virtual~Animal()
{
cout << "Animal::~Animal()" << endl;
if(NULL != _name)
{
delete[]_name;
}
}
protected:
char* _name;
};
class Dog : public Animal
{
public:
Dog(char* name)
:Animal(name)
{}
void call()
{
cout<<_name<<"wang wang wang"<<endl;
}
~Dog()
{
cout<<"~Dog()"<<endl;
}
char* _name;
};
int main()
{
Dog dog("dog");
Animal an("animal");
Dog *Pd = &dog;
Animal *Pa = &an;
Pa = Pd;
}
结果展示: