继承是面向对象复用的重要手段。通过继承定义一个类,继承是类型之间的关系建模,共享共有的东西,实现各自本质不同的东西。
#include<iostream>
#include<Windows.h>
using namespace std;
class person
{
public:
char* name;
char* sex;
int age;
public:
void display()
{
cout << "name:" << name << " sex:" << sex << " age:" << age << endl;
}
};
class student :public person
{
public:
int stdnum;
public:
void show()
{
cout << "stdnum:" << stdnum << endl;
}
};
int main()
{
student s;
s.name = "xiaowang";
s.sex = "man";
s.age = 18;
s.stdnum = 2016;
s.display();
s.show();
system("pause");
return 0;
}
代码执行时的情况:
继承是一种复用手段,在继承关系基类的成员类的成员派生类的成员,从而达到复用多个目的。
三种继承关系下基类成员在派生类的访问关系变化:
总结:
class person
{
public:
char* name;
char* sex;
int age;
public:
void display()
{
cout << "name:" << name << " sex:" << sex << " age:" << age << endl;
}
};
class student :public person
{
public:
int stdnum;//学号
public:
void show()
{
cout << "stdnum:" << stdnum << endl;
}
};
int main()
{
person p;
p.name = "xiaozhang";
p.age = 18;
student s;
s.name = "xiaowang";
p.age = 20;
// 切割/切片
// 子类可以给父类赋值
p = s;
//父类不能给给子类赋值
//s=p;
//父类的指针可以指向子类对象
person* p1 = &s;
//父类的引用可以指向子类对象
person& r1 = s;
p1->name = "xx";
r1.name = "yy";
//子类的指针/引用不能指向父类(可以通过强制类型转换完成)
student* p2 = (student*)&p;
student& r2 = (student&)p;
p2->name = "xx";
r2.name = "yy";
system("pause");
return 0;
}
在继承关系里面,在派生类中如果没有显示定义的这六个成员函数,编译系统则会默认合成这六个默认的成员函数
1.构造函数
子类的构造函数会自动调用父类的构造函数。
派生类构造函数必须使用基类构造函数。
构造函数不同于其他类的方法,应为它创建新的对象,要调用基类中的私有成员,因为派生类不能直接访问基类的私有成员,必须要调用基类方法进行访问,而其他类仅仅是被现有的对象调用而已,所以构造函数不能被继承;
基类的构造函数先被调用,后面是派生类的构造函数,其实,在main函数中,先调用的是派生类的构造函数,基类的构造函数是在派生类的初始化列表中调用的
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{
cout << "Person()" << endl;
}
}
class Student : public Person
{
public:
Student(const char* name, int age, int stunum)
:Person(name, age)
{
_stunum = stunum;
cout << "Student()" << endl;
}
}
2.拷贝构造函数
子类的拷贝构造函数会自动调用父类的拷贝构造函数。
拷贝构造会有切片。
在下列情况下使用拷贝构造:
如果程序没有显式定义拷贝构造函数,编译器将自动生成一个。
当然,如果想在派生类中构造基类对象,那么不仅仅可以用构造函数,也可以用拷贝构造函数
class Person
{
public:
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{
cout << "Person(const Person& p)" << endl;
}
}
class Student : public Person
{
public:
Student(const Student& s)
:Person(s)
{
_stunum = s._stunum;
}
}
3.析构函数
子类析构函数与父类析构函数会构成隐藏(虽然名字不同,但编译器会使他们重名,从而构成隐藏)
为了保证析构顺序,系统会自己调用析构函数,依据栈的规则,先call子类析构,在call父类析构
初始化时,父类先初始化,子类后初始化
析构时,子类先析构,父类后析构。栈的后进先出
class Person
{
public:
~Person()
{
cout << "~Person()" << endl;
}
}
class Student : public Person
{
public:
~Student() //隐藏了父类的析构函数
{
Person::~Person();
cout << "~Student()" << endl;
}
}
4.赋值操作符重载
子类对象可以赋值给父类对象
子类的赋值操作符重载会自动调用父类的赋值操作符重载。
赋值运算符是不能被继承的,原因很简单。派生类继承的方法的特征与基类完全相同,但赋值操作符的特征随类而异,因为它包含一个类型为其所属类的形参。
如果编译器发现程序将一个对象赋给同一个类的另一个对象,它将自动为这个类提供一个赋值操作符。这个操作符的默认版本将采用成员赋值,即将原对象的相应成员赋给目标对象的每个成员。
如果对象属于派生类,编译器将使用基类赋值操作符来处理派生对象中基类部分的赋值,如果显示的为基类提供了赋值操作符,将使用该操作符。
class Person
{
public:
Person& operator=(const Person& p)
{
if (this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
}
class Student : public Person
{
public:
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_stunum = s._stunum;
}
return *this;
}
}