一、基础概念
1.继承本质:实际上为了代码的复用;
2.类和类之间的基本关系:
2.1 代理:如STL中的容器适配器,被代理类的接口的功能的子集
2.2 组合:A类是B类的一部分
2.3 继承:A类是B类的一种
3.派生类和基类
基类我们也叫父类,派生类也叫子类,子类继承自父类,父类派生了子类。
二、不存在多态情况时对象内存布局
// 基类
class base
{
public:
int ma;
protected:
int mb;
private:
int mc;
};
//公有继承,当不写public,默认的是private继承
//默认和类的访问限定一样,都是私有的,一般都使用public继承
//派生类
class dev:public base
{
public:
int md;
protected:
int me;
private:
int mf;
};
1.分别定义一个子类对象和基类对象,分析内存布局:
sizeof(b) = = 12
sizeof(d) == 24
子类继承了父类哪些东西:
除了构造函数和析构函数以外的所有关系,包括基类的作用域和父类私有的成员,因为继承父类的构造和析构函数没什么用;
那也就是说,我在子类中可以定义与父类同名的成员变量和方法,因为我的作用域是不是同的。
base::void fun();
dev::void fun();
base::int ma;
dev::int mb;
不要在意这里写法对不对,为了说明问题
三、不同子类继承方式继承到父类的变量和方法访问权限:
1.三种访问限定的符的含义:
public:公开的,表示数据对用户的是公开的,都可以访问
protected:
保护的,虽然是保护的,但是对自己的子女和朋友(friend)是公开的,外部类不能访问
private:私有的,除自己和友元类都不能访问,因为只属于我本身。
2.不同继承方式带来不同访问权限:
public继承:
在子类能访问父类的public和protected成员,不可访问父类private,但是会继承下来;
在外部可以访问继承来父类public成员,但是不能访问protected和private;
//基类
class base
{
public:
int ma;
protected:
int mb;
private:
int mc;
};
//派生类,public继承
class dev:public base
{
public:
void show()
{ //public继承中,子类中可以访问继承来父类的public和protected
cout << ma << endl;
cout << mb << endl;
}
int md;
protected:
int me;
private:
int mf;
};
int main()
{
dev d;
//public继承中,类外可以访问继承来父类的public
cout << d.ma << endl;
return 0;
}
protected继承:
在子类中,原来父类的公开度比protected高的成员会被降级为protected,也就是父类成员到了子类中,开放度最大是protected,public成员降级为protected,其他不变。
//基类
class base
{
public:
int ma;
protected:
int mb;
private:
int mc;
};
//派生类,保护继承
class dev:protected base
{
public:
void show()
{ //protected继承中,子类中可以访问继承来父类的public和protected
//只不过会把基类中原本public降级为protected,protected依然可以自己访问
cout << ma << endl;
cout << mb << endl;
}
int md;
protected:
int me;
private:
int mf;
};
int main()
{
dev d;
//此时类外不能访问子类继承来父类的任何东西,因为是protected
return 0;
}
private继承:
这样把父类的public成员和protected降级为private,只能子类自己访问,类外的不能访问。
// 基类
class base
{
public:
int ma;
protected:
int mb;
private:
int mc;
};
//派生类,私有继承
class dev:private base
{
public:
void show()
{ //private继承中,子类中可以访问继承来父类的public和protected
//只不过会把基类中原本public降级为private,private依然可以对自己可见啊
cout <<ma << endl;
cout <<mb << endl;
}
int md;
protected:
int me;
private:
int mf;
};
int main()
{
dev d;
//此时类外不能访问子类继承来父类的任何东西,因为是private
return 0;
}
3.总结设计类的原则:
如果成员要对外部公开:public
如果成员要对只对子类公开:protected
如果成员只对自己公开,对外部和子类限定访问:private
四、派生类对象的构造方式
从一开始的内存布局来看,基类数据存放在派生类对象的内存前,所以我应该先构造基类部分。
如何调用在派生类中调用基类构造函数呢?
在派生类的构造函数初始化列表中调用。
实例:
class base
{
public:
base(int a) :ma(a){ cout << "base(int a)" << endl; }
~base(){ cout << "~base()" << endl; }
protected:
int ma;
};
class dev:public base
{
public:
dev(int data) :base(data), mb(data){ cout << "dev(int data)" << endl; }
~dev(){ cout << "~dev()" << endl; }
private:
int mb;
};
int main()
{
dev d(10);
return 0;
}
执行结果:
base(int a)
dev(int data)
~dev()
~base()
请按任意键继续. . .
从执行结果我们不难发现,构造一个派生类的对象的过程:
1.先调用基类的构造函数,初始化基类部分的内存;
2.调用派生类的构造函数,初始化派生类部分的内存;
3.析构时,先析构派生类部分,再析构基类部分。
(先构造的后析构,后构造的先析构)
注意事项:
如果不自己在派生类调用基类的构造函数,系统会自动帮我们调用默认的基类构造函数,前提是我们没有提高自定义构造函数,系统能帮我们产生构造函数。
五、基类和派生类同名成员方法的三种关系:
1.重载(overload):相同作用域下,同名不同参,与返回值无关;
2.隐藏(overhide):父类和子类,父类某个方法和子类的名字相同,就会被隐藏;
3.覆盖(override):下篇文章我将讲多态,会说到。
重载:base类中两个show方法就是重载关系
class base
{
public:
base(int a) :ma(a){}
~base(){}
void show(){ cout << "base::show()" << endl; }
void show(int i){ cout << "base::show(int)" << endl; }
protected:
int ma;
};
隐藏:子类的show会隐藏父类中的 两个 show方法,包括参数不同的show(int)
class base
{
public:
base(int a) :ma(a){}
~base(){}
void show(){ cout << "base::show()" << endl; }
void show(int i){ cout << "base::show(int)" << endl; }
protected:
int ma;
};
class dev:public base
{
public:
dev(int data) :base(data), mb(data){}
~dev(){}
void show(){ cout << "dev::show()" << endl; }
private:
int mb;
};
int main()
{
dev d(10);
d.show();
return 0;
}
执行结果:
dev::show()
请按任意键继续. . .
我们发现调用的是dev作用域下的show(),是因为把基类的show隐藏了
如果我们一定要调用基类的show方法该怎么做:
int main()
{
dev d(10);
//基类的方法是被继承下来了,只要指定基类的作用域就可以调用
d.base::show();
return 0;
}
执行结果:
base::show()
请按任意键继续. . .
同理,如果成员变量也有同名的,同样会构成隐藏
访问时默认是子类变量,访问父类可以讲父类的作用域
六、派生类从下到上转换为基类:
1.基类对象赋值给派生类对象:ERROR
2.派生类对象赋值给基类对象:OK
3.派生类指针(引用)指向基类对象:ERROR,存在越界问题
4.基类指针(引用)指向派生类对象:OK,虽然可以,但是访问不到派生类内存
(多态会解决这个问题)
如果你不懂为什么对还是为什么错我给你举个例子:
基类:人
派生类:男人 || 女人
我可说男人是人的一种,也可以说女人是人的一种
但是我不能说人就是男人。
这样会有什么问题:
int main()
{
dev d(10);
base *p = &d; //基类指针指向派生类对象
p->show();
//汇编上: call base::show
return 0;
}
执行结果:
base::show()
请按任意键继续. . .
调用基类的show,而不是派生类的
很奇怪,为什么我一个派生类对象要去调用基类的show的
原因分析:
指针的类型决定的了指针的“管辖范围”,base类的指针指向派生类对象,
只能管得了sizeof(base对象)那么大,所以就没法看到派生类的方法和变量了。
如图:
指针p指针只能看到红色部分,而看不到黑色部分。
虚函数的出现解决了这个问题,见下一篇文章。