1、继承的概念
2、派生类的定义方法
3、继承重要说明
1、子类拥有父类的所有成员变量和成员函数
2、子类就是一种特殊的父类
3、子类对象可以当作父类对象使用
4、子类可以拥有父类没有的方法和属性
4、不同的继承方式会改变继承成员的访问属性
public继承:
父类成员在子类中保持原有访问级别
private继承:
父类成员在子类中变为private成员
protected继承:
父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
5、三看原则
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承(public、private、protected)
3)看父类中的访问级别(public、private、protected)
#include <cstdlib>
#include <iostream>
using namespace std;
class A
{
private:
int a;
protected:
int b;
public:
int c;
A()
{
a = 0;
b = 0;
c = 0;
}
void set(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
}
};
class B : public A
{
public:
void print()
{
//cout<<"a = "<<a;//private声明的变量不能被继承
cout<<" b = "<<b;
cout<<" c = "<<c<<endl;
}
};
class C : protected A
{
public:
void print()
{
//cout<<"a = "<<a;//private声明的变量不能被继承
cout<<" b = "<<b;
cout<<" c = "<<c<<endl;
}
};
class D : private A
{
public:
void print()
{
//cout<<"a = "<<a;//private声明的变量不能被继承
cout<<" b = "<<b<<endl;
cout<<" c = "<<c<<endl;
}
};
int main(void)
{
A aa;
B bb;
C cc;
D dd;
aa.c = 100; //ok
bb.c = 100; //ok
//cc.c = 100; //err 因为声明的是 protected
//dd.c = 100; //err 因为声明的是 private
aa.set(1, 2, 3);
bb.set(10, 20, 30);
//cc.set(40, 50, 60); //因为a是private,不能被继承
//dd.set(70, 80, 90); //因为a是private,不能被继承
bb.print();
cc.print();
dd.print();
system("pause");
return 0;
}
6、类型兼容性原则
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent03
{
protected:
const char* name;
public:
Parent03()
{
cout<<"I AM PA 构造函数"<<endl;
name = "Parent03";
}
void print()
{
cout<<"Name: "<<name<<endl;
}
~Parent03()
{
cout<<"I AM PA 虚构函数"<<endl;
}
};
class Child03 : public Parent03
{
//protected:
//int i;
public:
Child03(int i)
{
cout<<"I AM CHI"<<endl;
this->name = "Child2";
//this->i = i;
}
void Show(void){
;//cout<<"I=: "<<i<<name<<endl;
}
};
//分别定义父类对象 父类指针 父类引用 child
int main()
{
{
{
Parent03 a;
Parent03 b=a;//b不会进行构造,为因为表达式已经明确给出了初始化,也就是b=a,只有在
//没有给出初始化的规则下才会进行构造函数
}
printf("\n------1-----------\n");
Child03 child03(1000);
printf("\n------2-----------\n");
Parent03 parent = child03; //并没有进行构造函数
printf("\n------3-----------\n");
Parent03* pp = &child03; //并没有进行构造函数,定义一个类的指针,不会进行构造和析构
printf("\n------4-----------\n");
Parent03& rp = child03; //并没有进行构造函数
printf("\n------5-----------\n");
parent.print();
pp->print();
rp.print();
}
return 0;
}
小结:
a)子类对象可以当作父类对象使用
b)子类对象可以直接赋值给父类对象
c)子类对象可以直接初始化父类对象
d)父类指针可以直接指向子类对象
e)父类引用可以直接引用子类对象
之所以可以这样复制,是因为父类有的子类都会有。
7、继承中的构造析构调用原则
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类
8、继承中的同名成员变量处理方法
1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
4、同名成员存储在内存中的不同位置
9、派生类中的static关键字
基类定义的静态成员,将被所有派生类共享
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)
派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问 对象名 . 成员
10、多继承:
一个类有多个直接基类的继承关系称为多继承
多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
多继承的派生类构造和访问
多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
#include <cstdlib>
#include <iostream>
using namespace std;
class base
{
public:
int i;
base(int a=0)
{
printf("I AM base\n");
i=a;
}
};
class base2
{
public:
int b;
base2(int a=0)
{
printf("I AM base2\n");
b=a;
}
};
class base3
{
public:
int d;
base3(int a=0)
{
printf("I AM base3\n");
d=a;
}
};
class basec4:public base2,public base3
{
public:
int e;
/* 采用构造函数进行初始化,
basec4(int aa,int bb,int cc):base2(bb),base3(cc)
{
e=aa;
}
*/
//直接进行初始化
basec4(int aa,int bb,int cc)
{
e=aa;
d=bb;
b=cc;
printf("I AM base4\n");
}
//不管哪一种初始化方式,都会按继承的顺序进行构造
};
int main(void)
{
basec4 kk(1,2,3);
printf("%d %d %d",kk.e,kk.d,kk.b);
return 0;
}
11、虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
#include <cstdlib>
#include <iostream>
using namespace std;
//具有virtual定义
class base
{
public:
int i;
int j;
int k;
base(int a,int b)
{
printf("I AM base\n");
i=a;
j=b;
k=0;
}
};
class base2:virtual public base //谁是公共的类,前面就要添加一个virtual
{
public:
int b;
base2(int a,int c=0,int d=0):base(c,d)
{
printf("I AM base2\n");
b=a;
}
};
class base3:virtual public base
{
public:
int d;
base3(int a,int c=0,int dd=0):base(c,dd)
{
printf("I AM base3\n");
d=a;
}
};
class basec4:public base2,public base3
{
public:
int e;
basec4(int aa,int bb,int cc,int dd,int ee):base2(bb),base3(cc),base(dd,ee)
{
e=aa;
}
};
//没有virtual
class mybase
{
public:
int i;
int j;
int k;
mybase(int a,int b)
{
printf("I AM mybase\n");
i=a;
j=b;
k=0;
}
};
class mybase2:public mybase //谁是公共的类,前面就要添加一个virtual
{
public:
int b;
mybase2(int a,int c=0,int d=0):mybase(c,d)
{
printf("I AM mybase2\n");
b=a;
}
};
class mybase3:public mybase
{
public:
int d;
mybase3(int a,int c=0,int dd=0):mybase(c,dd)
{
printf("I AM mybase3\n");
d=a;
}
};
class mybasec4:public mybase2,public mybase3
{
public:
int e;
mybasec4(int aa,int bb,int cc,int dd,int ee):mybase2(bb,cc,cc),mybase3(dd,ee,ee)
{
e=aa;
}
};
int main(void)
{
printf("\n------------------------------------------\n");
basec4 kk(1,2,3,4,5);
printf("%d %d %d %d %d\n",kk.e,kk.b,kk.d,kk.i,kk.j); //添加了virtual之后变量i j就不会有冲突了
printf("basec4 sizeof=%d\n",sizeof(basec4));
printf("\n------------------------------------------\n");
mybasec4 mykk(1,2,3,4,5);
printf("%d %d %d %d %d\n",mykk.e,mykk.b,mykk.d,mykk.mybase2::i,mykk.mybase3::i); //变量i会有冲突,得在前面指定类
printf("mybasec4 sizeof=%d\n",sizeof(mybasec4));
return 0;
}
总结:
1、如果采用了virtual,那么就不会有两个base类,base类只会进行一次构造而已,如果没有
virtual就会进行两次base的构造函数
2、如果采用了virtual,mybasec4中的变量i,j就只有一个,不会像没有virtual事具有两个变量
3、如果定义了一个virtual,内部会自动添加一个指针,占据4个字节。