目录
1.继承的概念及定义
2.继承中基类和派生类之间的赋值和类型转换
3.继承中的作用域
4.基类与派生类中构造函数析构函数的讲解
5.继承与友元
6.继承与静态成员
7.多继承、菱形继承和虚拟菱形继承
1.继承的概念及定义
继承是面向对象程序设计语言中实现代码复用非常重要的手段,我们设计一个类可以再设计另一个类用继承的语法使本类继承前一个类的成员变量和成员函数,这是在对前一个类进行功能和特性的扩充,前一个类在继承关系中就可以称为父类,后一个类就称为子类。最前面的祖先类称为基类,下面的都可以称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。下面演示继承语法
class Person
{
public:
void print() //基类中的一个成员函数
{
cout << "_name:" << _name << endl;
cout << "_age:" << _age << endl;
}
protected:
string _name="sam"; //两个成员变量
int _age=18;
};
class Student :public Person //这里是Person的一个派生类
{
protected:
int _stuid;
};
class Teacher :public Person //Person的一个派生类
{
protected:
string _dept;
};
int main()
{
Person p;
Student s;
Teacher t;
t.print();
s.print();
p.print();
}
在监视窗口就能看到两个派生类有自己的单独成员变量和从Person类中继承下来的成员变量
同样也把基类中的成员函数继承了。
继承规则是在写派生类时,写明继承方式和父类的名字
在类中有成员限定符 :public protected private
继承方式中有三种方式:public protected private
派生类中继承下来的基类成员访问方式的变化如下表:
总结下来基类中的所有成员都被派生类继承下来了,只是基类中的私有成员在派生类中就不可见了,即类外不能使用,类内也不能使用,基类的非私有成员在派生类中的访问方式等于基类访问方式和继承方式中较小者,大小关系是public >protected>private 。pretected这种访问方式就是专门用在继承这一块中不让外界直接访问但可以继承下去在类内访问。struct 定义的类中不写明访问方式那么默认为public,class定义的类中默认访问方式是private,继承上面同样是这样的默认方式。一般推荐写明访问方式,和继承方式,实际项目中大部分都是public继承很少使用protected和private。
2.继承中基类和派生类之间的赋值和类型转换
派生类对象可以赋值给基类对象、基类的指针或基类的引用,形象的称为切片或切割
赋值时只有将派生类对象中从基类中继承下来的成员变量的那部分数据赋值给基类中相应的成员变量。
Person p1,p2;
Student s;
Teacher t;
p1=s;
p2=t;
Person*p3=&s;
Person&p4=t; //这里都是语法允许的,不会报错
不允许基类对象赋值给派生类对象,可以通过强制类型转换将基类对象交给派生类的指针管理
这里报错不能将基类对象赋值给派生类,基类对象的地址强制类型转换给派生类的指针时,用这个指针访问继承的成员变量时不会有问题,但访问派生类中特有的成员变量时就会越界访问报错,因为对象是基类没有派生类的特有的成员变量。
3.继承中的作用域
基类和派生类都有独立的作用域,如果在子类中定义的成员名和父类的成员名相同在子类中就构成了隐藏,即在子类中访问成员时访问的是子类中独有的成员,分类中同名的成员都被隐藏了,隐藏的概念就有在子类中才有,还有注意成员函数只要函数名相同就构成隐藏。
class Person
{
public:
void print()
{
cout << "Person" << endl;
}
string _name = "zhangsan";
int _num = 111;
};
class Student:public Person
{
public:
void print()
{
cout << "Student" << endl;
}
int _num = 999; //这里子类中有成员函数print和成员变量_num与父类中
//相应的成员函数和成员变量构成隐藏,子类对象调用print和_num都是访问子类的成员,父类的被隐藏
};
int main()
{
Student s;
s.print();
cout << s._num << endl;
return 0;
}
加上作用域指明就能访问父类在子类中被隐藏的成员
int main()
{
Student s;
s.Person::print();
cout << s.Person::_num << endl;
return 0;
}
还有注意即使父类和子列中有相应函数名相同 函数参数相同的成员函数也不是函数重载,因为不在同一作用域。
4.基类与派生类中构造函数析构函数的讲解
- 如果基类中没有默认构造函数那么在派生类构造函数的初始化列列表中必须显示调用基类构造函数来初始化基类那部分的成员变量,然后再初始化子类的成员。
- 派生类对象的拷贝函数中必须调用基类的拷贝构造函数完成基类部分的成员拷贝,然后再拷贝子类成员。
- 派生类对象operator=函数中必须调用基类的operator=完成基类部分成员的赋值然后对子类成员完成赋值。‘
- 派生类对象调用完自身析构函数完成自己那部分清理后会自动调用基类的析构函数完成基类部分的资源清理。
- 我们要不要自己写拷贝和析构就看对象有没有资源要清理,如果没有动态开辟的资源就不用写。
- 派生类对象初始化是线初始化基类的再初始化本类的
- 派生类对象清理资源是先清理本类的再清理基类的
class Person
{
public:
Person(const char* name, int age)
:_name(name)
, _age(age)
{
cout << "Person(const char* name, int age)" << endl;
}
Person(const Person& p)
{
_name = p._name;
_age = p._age;
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
if (this != &p)
{
_name = p._name;
_age = p._age;
cout << "Person& operator=(const Person& p)" << endl;
}
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name;
int _age;
};
class Student :public Person
{
public:
Student(const char* name, int age, int no)
:Person(name, age) //先初始化基类的再初始化本类的
, _no(no)
{
cout << "Student(const char* name, int age, int no)" << endl;
}
Student(const Student& s)
:Person(s)
,_no(s._no)
{
cout << "Student(const Student& s)" << endl;
//先调用基类拷贝构造完成基类部分的拷贝,就传子类对象切片的关系,再完成本类部分的拷备
}
Student& operator=(const Student& s)
{
if (this != &s)
{
cout << "Student& operator=(const Student& s)" << endl;
Person::operator=(s); //先赋值基类的再赋值本类的
_no = s._no;
}
return *this;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int _no;
};
int main()
{
Student s1("zhangsan", 18, 23445);
Student s2(s1);
s2 = s1;
return 0;
}
5.继承与友元
对于基类的友元函数这层关系是不会继承到派生类的,就是说不能访问派生类的protected 和private成员。
class Student;
class Person
{
friend void goodguy(const Person& p, const Student& s);
protected:
void print()
{
cout << "_name:" << _name << endl;
cout << "_age:" << _age << endl;
}
string _name="sam";
int _age=18;
};
class Student :public Person
{
protected:
int _stuid;
};
class Teacher :public Person
{
protected:
string _dept;
};
void goodguy(const Person &p, const Student &s)
{
cout << p._name << endl;
cout << s._stuid << endl; //这里就会报错,不能访问
cout << s._name << endl;
}
6.继承与静态成员
基类中用static 定义的成员就只有一份不管派生了多少类,所有类共用这一份。用代码测试一下。
class Person
{
public:
Person()
{
++a;
}
static int a;
};
int Person::a = 0;
class Student :public Person
{
};
class greate :public Student
{
};
int main()
{
Person s1;
Person s2;
Student s3;
greate s4;
cout << s4.a << endl;
s3.a = 0;
cout << s2.a << endl;
return 0;
}
7.多继承、菱形继承和虚拟菱形继承
多继承就是一个子类可以有多个父类格式一样,中间用逗号隔开
class A
{
protected:
int _a;
};
class B
{
protected:
int _b;
};
class C :public A, public B
{
protected:
int _c;
};
int main()
{
A a;
B b;
C c;
return 0;
}
有多继承之后必然会带来重复继承同一个基类造成数据冗余和二义性的问题,这就是菱形继承带来的问题,用下图来说明
这里Assistant中有两份Person类的成员内容,一份从Student继承来的,一份从Teacher继承来的,但实际上我们不需要有两份相同的成员,这造成了冗余
在Assistant中访问Person类的成员时不能确定是访问Student继承的还是从Teacher继承的,造成了二义性。总结一下就是派生类继承了多份基类传下来的成员时就是菱形继承,所以下面这种继承方式也是菱形继承。
虚拟菱形继承的方式可以避免菱形继承带来的数据冗余和二义性。
虚拟菱形继承就是在共同的菱形顶端向下分叉继承下来的类这里加virtual。
class Person
{
public:
string _name = "zhangsan";
int _age = 18;
};
class Student :virtual public Person
{
public:
int _stuid;
};
class Teacher :virtual public Person
{
public:
int _dept;
};
class Assistant :public Student, public Teacher
{
public:
int _job;
};
int main()
{
Assistant at;
at._name = "wangwu"; //没加virtual 这里会报错
return 0;
}
加了virtual 继承公共父类的成员就只有一份。
这里就说明Assistant对象中继承下来的_name就只有一份。