继承(1)
派生类 从 基类 中继承一些东西.
特性
- 继承是
被动接受的. - public, 外部可访问, 派生类可继承.
- protected, 外部无法访问, 内部可访问, 派生类可继承, 派生类可访问.
- private, 外部无法访问, 内部可访问, 派生类可继承(可通过sizeof验证), 派生类无法访问.
继承的语法
class Base
{
private:
int private_i_;
protected:
int protected_i_;
public:
int public_i_;
}
class A : Base
{
}
int main()
{
A a1;
a1.public_i_ = 1; // 可以
return 0;
}如上代码, 并没有指定继承方式, 类的继承默认是_私有private_的!
另外, 结构体如果没有指定继承方式, 它的继承默认是_public_的!
Has-A关系
其实就是 在 class A 中, 有class B的对象.
也就是 A has B.
这其实就是 组合.
Is-A关系
其实就是在 class A 和 class B中存在相同的功能或属性
而我们将这些相同的功能或属性提出出来, 然后子类化
在子类中, 可以对这些相同属性或功能进行强化等等.
这就是 继承.
狼狗, 宠物狗, 都继承自狗
不管怎么样, 都是狗, 这就是 Is A关系
class Base
{
}
class A : Base
{
}
int main()
{
Base base(100);
A a(200);
Base* pBase = &a; // 可以, 因为 A Is Base
A* pA = &base; // 错误的
return 0;
}继承访问控制符
- public // 公开的
- protected // 保护的
- private // 私有的
继承访问控制符出现在基类的前面.
class Base
{
};
class A : public Base
{
};Private继承
class Base
{
private:
int private_i_;
protected:
int protected_i_;
public:
int public_i_;
}
class A : private Base
{
}
int main()
{
A a1;
a1.public_i_ = 1; // 可以
return 0;
}会修改基类中的public和protected属性改变为private属性, 外部无法访问, 内部可访问
Protect继承
class Base
{
private:
int private_i_;
protected:
int protected_i_;
public:
int public_i_;
}
class A : protected Base
{
}
int main()
{
A a1;
a1.public_i_ = 1; // 不可以
return 0;
}会修改基类中的public属性改变为protected属性, 外部无法访问, 内部可访问
Public继承
class Base
{
private:
int private_i_;
protected:
int protected_i_;
public:
int public_i_;
}
class A : public Base
{
}
int main()
{
A a1;
a1.public_i_ = 1; // 可以
return 0;
}基类中的属性的访问控制权限没有任何修改
using关键字
使用_using_关键字可以改变基类成员变量或成员函数在派生类中的访问权限
例如将 public 改为 private, 或者将 protected 改为 private
但是, 无法将 private 改为 public 或者 protected, 因为 private 成员不可访问
#include<iostream>
using namespace std;
class People
{
public:
void Show()
{
cout << name_ << "的年龄是" << age_ << endl;
}
protected:
char* name_;
int age_;
};
class Student: public People
{
public:
void Learning()
{
cout << "我是" << name_ << ",今年" << age_ << "岁,这次考了" << score_ << "分!" << endl;
}
public:
using People::name_; //将protect改为public
float score_;
private:
using People::age_; //将protect改为private
using People::Show; //将public改为private
};
int main()
{
Student stu;
stu.name_ = "Hades";
stu.age_ = 18;
stu.score_ = 233.333f;
stu.Show(); //报错 因为改成private了
stu.Learning();
return 0;
}继承中的构造/析构函数
#include <iostream>
using namespace std;
class Base
{
private:
int num_;
public:
Base()
{
cout << "Base Ctor!" << endl;
}
~Base()
{
cout << "Base Dtor!" << endl;
}
int GetNum() const
{
return num_;
}
}
class A : public Base
{
public:
A()
{
cout << "A Ctor!" << endl;
}
~A()
{
cout << "A Dtor!" << endl;
}
}
int main()
{
A a1;
// A中并没有num_, num_在基类中.
a1.GetNum(); // 可以
return 0;
}派生类构造函数会先调用基类构造函数, 然后在调用自己的构造函数.
派生类构造函数会默认调用_基类默认的构造函数_
派生类析构函数调用完成后, 然后在调用基类析构函数.
派生类和基类现在是 IS A 关系.
#include <iostream>
using namespace std;
class Base
{
private:
int num_;
public:
Base(int num):num_(num)
{
cout << "Base Ctor!" << endl;
}
~Base()
{
cout << "Base Dtor!" << endl;
}
int GetNum() const
{
return num_;
}
}
class A : public Base
{
public:
A()
{
cout << "A Ctor!" << endl;
}
~A()
{
cout << "A Dtor!" << endl;
}
}
int main()
{
A a1; // 报错, 没有找到基类的默认构造函数
return 0;
}想要解决这个错误, 需要在派生类的构造函数中显示调用基类构造函数
修改如下:
A():Base(0)
{
cout << "A Ctor!" << endl;
}我们在修改一下:
#include <iostream>
using namespace std;
class Base
{
private:
int num_;
public:
Base(int num):num_(num)
{
cout << "Base Ctor!" << endl;
}
~Base()
{
cout << "Base Dtor!" << endl;
}
int GetNum() const
{
return num_;
}
}
class A : public Base
{
private:
int num_;
public:
A(int num):Base(0), num_(num)
{
cout << "A Ctor!" << endl;
}
~A()
{
cout << "A Dtor!" << endl;
}
}
int main()
{
A a1(100);
cout << a1.GetNum() << endl; // 结果是 0
return 0;
}如果我们在A中实现一个GetNum(), 改造如下:
class A : public Base
{
private:
int num_;
public:
A(int num):Base(0), num_(num)
{
cout << "A Ctor!" << endl;
}
~A()
{
cout << "A Dtor!" << endl;
}
int GetNum() const
{
return num_;
}
}
//....
cout << a1.GetNum() << endl; // 结果是 100
// ...
此时, 我们说派生类将基类中的方法_隐藏_了
而隐藏则指的是:
如果派生类的函数与基类的函数同名, 但是参数不同
- 此时, 不论有无virtual关键字, 基类的函数将被隐藏(注意, 不是重载, 因为作用域不同)
如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有virtual关键字.
- 此时,基类的函数被隐藏(注意, 不是覆盖)
注意:
重写(覆盖)形成的条件:
- 不在同一个作用域(派生类与基类)
- 函数名相同
- 参数相同
- const相同
- 返回值相同(或是协变)
- 基类函数必须有virtual关键字
重写(覆盖)后, 基类的方法被覆盖掉, 基类的方法就不存在了
根据以上例子中的类, 我们继续写如下代码:
int main()
{
A a(200);
Base* pBase = &a; // OK, 因为Is A关系
cout << pBase->GetNum() << endl; // 结果是 0
// 它其实调用的是a中的Base对象的GetNum方法,
// 调用的是基类的方法.
// 结果是a中Base对象的num_的值
return 0;
}为了解决这种不正确的调用(我们希望输出200), 所以我们使用虚函数.
虚函数
父类实现虚函数, 派生类重写了之后, 就会调用派生类的函数
如果派生类没写, 会调用基类的
虚函数语法: virtual int GetNum() const{}
根据上边的例子, 我们在来看:
int main()
{
A a(200);
Base* pBase = &a; // OK, 因为Is A关系
// 现在, 我们将Base中的GetNum变成虚函数
cout << pBase->GetNum() << endl; // 结果是 200
return 0;
}派生类和基类的成员内存管理
class String
{
public:
String(const char* str = "")
{
unsigned int len = strlen(str);
str_ = new char[len + sizeof(char)];
strcpy(str_, str);
}
~String()
{
delete str_;
}
String(const String& other)
{
if (this != &other)
{
unsigned int len = strlen(str);
str_ = new char[len + sizeof(char)];
strcpy(str_, str);
}
return *this;
}
String& operator+=(const String& other)
{
unsigned int len = strlen(str_) + strlen(other.str_);
char *temp = new char[len + sizeof(char)];
strcpy(temp, str_);
strcat(temp, other.str_);
delete[] str_;
str_ = temp;
trrurn *this;
}
String& operator+(const String& other)
{
String tmp(str_);
tmp += other;
return tmp;
}
friend std::ostream& operator<<(std::ostream& os, const String& other)
{
os << other.str_;
return os;
}
protected:
char* str_;
};
class MyString : public String
{
public:
MyString(const char* str = "PoEdu")
{
unsigned int len = strlen(str);
str_ = new char[len + sizeof(char)];
strcpy(str_, str);
}
~MyString()
{
delete[] str_;
str_ = nullptr;
}
MyString operator+(const MyString& other)
{
MyString temp(str_);
temp += other; // 继承了基类的+=
temp += "-----PoEdu";
return temp;
}
};
int main()
{
MyString str;
return 0;
}当前我们的程序代码下,
- 在进行新建对象时, 会先执行父类的构造函数, 然后在执行本身的构造函数.
- 在进行析构的时候, 会先执行本身的析构函数, 然后在调用父类的析构函数.
- str_ 被分配了2次空间
str_ 被delete了2次, 但是在String中delete时, String分配的空间已经丢失
- 内存泄露了
- delete野指针, 或者delete nullptr, 程序崩溃.
我们应该避免在派生类中管理基类的成员变量的内存 父类中继承下来的东西`不要在子类中进行显式`的操作.
父类的new让父类的析构来进行delete
由谁定义的变量由谁来管理
如何修改呢?
针对MyString改造如下:
class MyString : public String
{
public:
MyString(const char* str = "PoEdu") : Base(str)
{
}
~MyString()
{
}
};我们在来看:
int main()
{
MyString str("Hello Hades");
String *pStr = &str;
cout << str + ("OOO") << endl; // 这样会输出我们的 -----PoEdu
cout << *pStr << endl; // 这样却不会输出 -----PoEdu
return 0;
}这是因为, pStr调用的是父类的operator+, 而不是派生类的operator+
所以我们把父类的operator+做成虚函数, 改造如下:
virtual String& operator+(const String& other)
// .....经过改造后, 输出就正确了!
虚析构函数
class A
{
public:
virtual ~A() {}
}
class B : public A
{
public:
B()
{
demo_ = new int(0);
}
~B()
{
delete demo_;
}
private:
int *demo_;
}
int main()
{
B(); // 肯定会调用B的析构
A* pA = new B;
delete *pA; // 会调用A的析构 如果A的析构函数不是虚的, 那么B的析构不会被调用
// 需要把A的析构做成虚函数, 才会调用B的析构函数
return 0;
}
当基类的指针指向派生类时, 在析构的时候, 只会调用基类的析构函数
我们将基类的析构函数做成_虚析构函数_后, 在使基类的指针指向派生类
在析构的时候, 才会调用_派生类和基类_的析构函数
当派生类没有写析构函数时, 它_会调用基类的析构函数_
构造和析构的执行顺序
继承体系的类的调用方法
- 父类构造
- 父类成员的构造
- 子类构造
- 子类析构
- 父类成员的析构
- 父类析构
虚继承
class A
{
public:
int a_;
}
class B : public A
{
public:
int b_;
}
class C : public A
{
public:
int c_;
}
// 菱形继承
class D : public B, public C
{
// 现在有 a_ a_ b_ c_
}
class BV : virtual public A
{
public:
int b_;
}
class CV : virtual public A
{
public:
int c_;
}
class E : virtual public BV, virtual public CV
{
// 现在有 a_ b_ c_
}
int main()
{
D d;
d.a_ = 1;// 访问不明确
d.B::a_ = 1;
d.C::a_ = 2;
// 要解决这种问题 使用虚继承
E e;
e.a_ = 100; // OK
return 0;
}
尽量避免菱形继承的使用
纯虚函数
我们不对它来进行实现的函数, 叫纯虚函数.
class Animal
{
public:
// 纯虚函数 无需实现 需要派生类来实现(强制性的)
// 基类可以实现该函数, 但是无意义
virtual void Say() = 0;
}
class Dog : public Animal
{
public:
// 必须实现这个函数
// 否则无法实例化
// void Say()
// {}
// 它只要不新建对象 就可以不实现纯虚函数
// 可以交由Dog的派生类实现
}
class A : public Dog
{
public:
void Say()
{}
}
int main()
{
Dog dog(); // 错误的 不能实例化抽象类
A a(); //可以
Dog* dog = new A(); // 可以
dog->Say(); // 不管什么狗 都要叫出来
return 0;
}
但凡_有纯虚函数_的类, 我们称之为_抽象类_.
抽象类_不能够被创建对象_, 它是其他类的基类
它可以作为接口来用(接口是用来解耦的)
接口
接口是在设计的时候进行统一调配, 它是一种抽象.
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-02-19 at 04:02 pm