说得简单一点继承就是用已有的类来定义新的类,所定义的新类不仅拥有已有的类的成员,还可以自己附加一些成员上去,实现了扩展原有功能的作用。
这种机制是面向对象编程使代码可以复用的重要手段,呈现了C++面向对象编程的思想。
举个栗子吧!
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
以看到变量的复用。调用Print可以看到成员函数的复用。
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
监视窗口结果:
可以看见Student对象s不止有着从Person继承的_name和_age,还有自己的_stuid,同理可以看见Teacher对象t不止有着从Person继承的_name和_age,还有自己的_jobid。
下面我们可以看到Person是父类,Student是子类,这就是继承的基本格式!
用一张图说明:
需要特别注意的是:
(1)基类private成员还是继承到派生类中,但是无论什么方式都访问不了 (2)class默认继承方式是private,struct默认继承方式是public
所以小蛇蛇们要注意了,在实际编程中,尽量不要在基类和派生类中定义同名的成员 。
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
string _name = "小李子"; // 姓名
int _num = 111; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
cout<<" 姓名:"<<_name<< endl;
cout<<" 身份证号:"<<Person::_num<< endl;
cout<<" 学号:"<<_num<<endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" <<i<<endl;
}
};
void Test()
{
B b;
b.fun(10);
};
4.2总结
其实在继承中的派生类的这些默认成员函数跟以前普通类一样,只是在构造/赋值/析构时,多了父类的那一部分。
公主王子请看代码!
class Person
{
public:
Person(const char* name)
:_name(name)
{
cout << "Person(const char* name)" << endl;
}
Person(const Person& p)
{
_name = p._name;
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
if(this!=&p)
{
_name = p._name;
}
cout << "Person operator=(const Person& p)" << endl;
return *this;
}
~Person()
{
cout << "~Person" << endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
Student(const char* name,int id)
:Person(name)
,_id(id)
{
cout << "Student(const char* name,int id)" << endl;
}
Student(const Student& s)
:Person(s)
{
cout << "Student(const Student& s)" << endl;
_id = s._id;
}
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s);
_id = s._id;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int _id;
};
int main()
{
Student s1("张三", 12);
Student s2(s1);
Student s3("李四", 11);
s1 = s3;
return 0;
}
在继承体系中,友元是不能被继承的,也就是说基类的友元不能访问派生类的私有和保护成员。
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
class Student :public Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
可以看见程序是报错了的!
当基类定义了一个静态成员时,则整个继承体系中只能有一个静态成员,无论派生多少个类,都只有一个静态实例。
class Person
{
public:
Person()
{
++_count;
}
private:
string _name;
public:
static int _count;
};
int Person::_count = 0;
class Student :public Person
{
protected:
int _stuNum;
};
int main()
{
Person p1;
Person p2;
Student s1;
cout << Person::_count << endl;
cout << Student::_count << endl;
cout << &Person::_count << endl;
cout << &Student::_count << endl;
return 0;
}
在讲菱形继承之前先讲讲最基础的单继承和多继承。
单继承:一个派生类只有一个基类的继承
多继承:一个派生类有两个及以上的直接基类的继承
而咱们得菱形继承就是多继承的一种特殊情况。
那么菱形继承有啥问题呢?
先看一段代码:
class Person
{
public:
string _name;
int _age;
int _tel;
string _address;
};
class Student:public Person
{
protected:
int _stuNum;
};
class Teacher :public Person
{
protected:
int _id;
};
class Assitant :public Student, public Teacher
{
protected:
string _majorCourse;
};
int main()
{
Assitant at;
at.Student::_name = "张三";
at.Teacher::_name = "李四";
at._name = "老张";
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
{
public:
int _d;
};
int main()
{
D dd;
dd.B::_a = 1;
dd.C::_a = 2;
dd._b = 3;
dd._c = 4;
dd._d = 5;
return 0;
}
调试一下看见:
直接上代码:
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D dd;
dd.B::_a = 1;
dd.C::_a = 2;
dd._b = 3;
dd._c = 4;
dd._d = 5;
B bb;
bb._a = 11;
bb._b = 12;
return 0;
}
那他为什么能解决上面菱形继承的问题呢?
我们还是先来看看他的内存结构:
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D dd;
dd.B::_a = 1;
dd.C::_a = 2;
dd._b = 3;
dd._c = 4;
dd._d = 5;
return 0;
}
可以看见图中多了个虚基表的概念,虚基表是啥呢?
我们先来看前面一张图,这就是D对象的组成,那么在B和C对象各自的前面都有一个指针,而这两个指针指向的一段区域就叫做虚基表,用官方的话来讲虚基表就是一段存放虚基类偏移量的空间。
这里的偏移量指的是到A的距离,可以通过左边的监视窗口的地址看出。
那我们可以看见:
此时D从B和C继承来的a就是唯一的,可以直接D::a访问。
总结
好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。
祝大家越来越好,不用关注我(疯狂暗示)