一。继承
1.继承本质是复用相同的代码(属性)
2.格式:class 类名:继承方式 父类
3.继承方式的规律:
父类的:
对于私有成员,不管哪种继承方式都不可见--->不想被子类继承的成员
对于保护-->能子类内部直接用的(不管哪种继承都是)
对于公有-->公有继承让子类外部也能用
注意:不能直接访问,但可以通过父类提供访问方式(如getter,setter)间接访问
继承方式将比他访问权限大的变成他的访问权限,比他小的不变
子类的访问权限=min(父类访问权限,继承方式)
二。赋值兼容转换(或叫切割,切片)
注意:只限于子类父类是公有继承
子类的对象能赋值给父类对象,指针,引用。
特点:没有类型转换--->没有产生临时变量,将子类满足的一部分给父类对象,指针,引用。
不同于:截断(高到低),提升(低到高)---->都是类型转换---->产生临时变量--->不能直接给引用指针(有引用和指针的常性)
三。继承中的域
已知的域:
局部域,全局域,命名空间域,类域。----->影响语法编译时的查找(直接指定域也可)
---->全局域和局部域影响生命周期,后两个内部无实体无生命周期概念
注意:继承后子类和父类是两个独立的域,编译时子类调先找子类再找父类。
例:子类和父类有同名成员(能同时存在,不同域)构成隐藏(重定义)而不是重载,函数名相同就是隐藏,且子类调用时直接在子类找到,不会去 父类找(编译时按照函数名找,符号表的链接时才去函数名修饰)
例:
下面哪个正确()
A.fun构成重载
B.fun构成隐藏
C.编译报错
D.运行报错
答案:B,C
继承后两个fun函数位于两个域,不构成重载,同名隐藏,编译时根据函数名找域内是否有这个函数名,以及参数检查,找到fun(int i),且这个域内没重载函数,(fun()被隐藏了),发现参数不对,编译时语法检查就报错了,而不是等到链接时根据被修饰的函数名找函数。(函数名修饰是符号表的概念)
--->继承子类父类最好不用同名函数,同名如果要调父类的要指定作用域为父类
四。继承的默认成员函数
角度:自定义类型,内置类型,父类成员(当成一个整体)
对于子类:编译器自动生成的构造:调用父类的及自定义类型的默认构造
析构,拷贝构造同上。
构造,析构顺序:构造:强制保证先父类再子类
注意:强制要求使用父类的构造,而不是在初始化列表对父类成员一个一个初始化
实际初始化顺序不是初始化列表顺序,而是声明顺序(先父类再子类声明顺序)
目的:让子类再初始化列表调用父类的成员的值不会出错
析构:强制保证先子类再父类
1.在子类析构函数最后自动强制再调用父类析构,即如果在子类析构里调父类析构,最后还会再调。
2.直接调调不动父类析构:由于多态,析构函数名字编译器统一处理成destructor,构成隐藏。要指定作用域。
拷贝构造:
一般不需自己写,若涉及深拷贝,自己写(同)
深拷贝:将父类拷贝构造显式调用
自己写会自动调父类的拷贝构造/赋值运算符重载吗?不会,要自己显示调用。
自己写会自动调用父类的构造,析构(本身父类的部分就会自动初始化,析构)
赋值重载与拷贝构造类似。
总结:构造能显式调父类构造,析构不能显式调
(一定能保证先父后子,后者显式调用不能保证先子后父)
#include<iostream>
using namespace std;
#if 0
class Person
{
public:
Person(){}
void Print()//大驼峰
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
cout << _tel;
}
string _name;
protected://给子类里访问
string _age;
private://父类不想被继承的
int _tel=110;
};
class Student :public Person
{
public:
void Print()
{
//cout << _tel << endl;
cout << "age:"<<_age << endl;
cout << "name:" << _name << endl;
}
protected:
int _stuid;
};
class Teacher :public Person//使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过\
最好显示的写出继承方式
{
protected:
int _jobid;
};
int main()
{
Person p;
Student s;
Teacher t;//父类的对象引用指针可以接受子类的,把子类的父类部分接受是切片切割///基类和派生类对象赋值转换
p = s;
p._name = "刘";
p.Print();
Person* peo;
peo = &s;
peo->Print();
Person& per = t;
per._name = "张";
per.Print();
return 0;
}
#endif
#if 0
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student :public Person
{
public:
int _No;
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
//sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
//子类对象给基类对象本质是基类部分拷贝给基类对象
ps2->_No = 10;
}
int main()
{
Test();
return 0;
}
#endif
#if 0
class Person
{
protected:
string _name = "小李子";
int _num = 111;
};
class Student:public Person
{
public:
void Print()
{
cout << "学号:" << _num << endl;
}
protected:
int _num = 999;//同名隐藏
};
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B :public A
{
public:
void fun(int i)
{
cout << "func(int i) ->" << i << endl;
}
};
// 下面哪个是正确的()
// fun构成重载
// fun构成隐藏
// fun构成重写
// 编译报错
// 运行报错
int main()
{
B bb;
//bb.fun();//编译时根据函数名找,找到了在验证函数参数-->最好不要写同名函数,特别是不同参数,毕竟不构成重载
bb.fun(0);
bb.A::fun();//同名函数重载,参数在编译时不做检查,函数修饰规则在链接时起效
return 0;
}
#endif
class Person
{
public:
Person(const char* name = "") :_name(name) {
cout << "Person()" << endl;
}
Person(const Person& p) :_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(cosnt Person& p)" << endl;
if (this != &p)//不等于自己就拷贝
_name = p._name;
}
~Person()
{
cout << "~Person()" << endl;
delete[] _str;
}
protected:
string _name;
char* _str = new char[10] {'x', 'y', 'z' };
};
class Student :public Person
{
public:
//编译器规定父类构造在最前,不允许父类成员单独初始化
Student(const char*name="",int x=0,const char*address="")
:_x(x)
,_address(address)
,_name(Person::_name+'x')
,Person(name)
{}
Student(const Student& st)
:Person(st)
,_x(st._x)
,_address(st._address)
{}
protected:
int _x = 1;
string _address = "西安高新区";
string _name;
};