目录
一.继承
1.继承的三种方式
class 子类名 :继承方式 父类名
{
};
格式大概就是上面这样子,继承有如下三种方式:关于继承和派生,其实本质上差别不大,继承是子类中没有产生新的属性或行为,而派生可以理解为在基类的基础上产生了新的行为或属性
class parent //父类 基类
{
};
//子类
class son1:public parent
{
//公有继承
};
class son2 :private parent
{
//私有继承
};
class son3 :protected parent
{
//保护继承
};
关于这三种继承方式有何不同,主要体现在继承之后的权限上,可以总结为下表:
public | protected | private | |
public继承 | public | protected | 不可直接访问 |
protected继承 | protected | protected | 不可直接访问 |
private继承 | private | private | 不可直接访问 |
可以发现,继承只会增强父类属性在子类中的权限显示,而且,无论是什么继承方式,子类都不能直接访问父类的私有属性。下面给出案例代码:
class father
{
public:
protected:
string Fname;
double money;
private:
string wife;
};
class son : public father
{
public:
void print()
{
cout << Fname << endl; //可以访问
cout << wife << endl; //不可访问
}
protected:
};
继承的实质就是在子类中拷贝一份父类的数据,如下:
class A
{
public:
int a;
protected:
int b;
private:
int c;
};
class B :public A
{
public:
//int a;
protected:
//int b;
private:
//int c;
};
class C :protected A
{
public:
protected:
//int a;
//int b;
private:
//int c;
};
class D :private A //这种会断子绝孙,它的儿子不能继承到爷爷的任何参数
{
public:
protected:
private:
//int a;
//int b;
//int c;
};
2.继承中的构造函数
注意点:父类中的属性在子类中必须要调用父类构造函数,必须采用初始化参数列表的方式写子类的构造函数。如下:
class Parent
{
public:
Parent()
{
cout << "父类的构造函数" << endl;
}
protected:
string Fname;
string Sname;
};
class son :public Parent
{
public:
son()
{
cout << "子类的构造函数" << endl;
}
};
int main()
{
son boy;
}
来看看输出:
可以看到,构建boy的时候,先调用了父类的构造函数,再调用子类的,我们再来看看有参构造函数的写法:
class Parent
{
public:
Parent(string Fname,string Sname):Fname(Fname),Sname(Sname){}
protected:
string Fname;
string Sname;
};
class son :public Parent
{
public:
//son()
//{
// cout << "子类的构造函数" << endl;
//}
//上面报错,因为没有父类匹配的构造函数
son(string Fname, string Sname,string Myname) :Parent(Fname, Sname)
{
this->Myname = Fname + Myname;
}
void print()
{
cout << "父:" << Fname + Sname << endl;
cout << "子:" << Myname;
}
protected:
string Myname;
};
int main()
{
son boy("蓝","大星","小星");
boy.print();
}
可以看到,子类的构造函数初始化的时候必须用父类构造函数有的形式。
3.多继承
来看下面这段代码:
class MM
{
public:
MM(string mmFName, string mmSName) //父类没有继承产生子类前,怎么初始化都可以
{
this->mmFName = mmFName;
this->mmSName = mmSName;
}
protected:
string mmFName;
string mmSName;
};
class GG
{
public:
GG(string ggFName, string ggSName)
{
this->ggFName = ggFName;
this->ggSName = ggSName;
}
protected:
string ggFName;
string ggSName;
};
//继承GG&MM类,可以是不同权限,只是父类中的属性在子类中的权限不同
class Girl :public GG, public MM
{
public:
//子类想要构造无参对象,每个父类都要有一个无参构造函数
//Girl() {} //继承的属性必须采用初始化参数列表的方式调用父类构造函数初始化
Girl(string mmFName, string mmSName, string ggFName, string ggSName)
:MM(mmFName, mmSName), GG(ggFName, ggSName)
{
girlFName = ggFName + mmFName;
girlSName = ggSName + mmSName;
}
void print()
{
cout << "父:" << ggFName + ggSName << endl; //继承下来的属性
cout << "母:" << mmFName + mmSName << endl;
cout << "女:" << girlFName + girlSName << endl;
}
protected:
string girlFName;
string girlSName;
};
int main()
{
Girl girl("蓝", "天", "白", "云");
girl.print();
return 0;
}
可以看到,无论是单继承还是多继承都必须调用父类的构造函数。多继承有可能会产生二义性,造成菱形继承,什么是菱形继承?来看下图:
父中有一个a,它同时继承给了两个儿子,那孙子拿到的就有两份拷贝本,计算机就无法作出判断。例如下面这段程序:
class A
{
public:
A(int a):a(a){}
protected:
int a;
};
class B :public A
{
public:
B(int a,int b):A(a),b(b){}
protected:
int b;
};
class C :public A
{
public:
C(int a,int c):A(a),c(c){}
protected:
int c;
};
class D :public B, public C
{
public:
D():B(1,2),C(3,4){}
void print()
{
cout << a << endl;
//报错
}
};
D类是孙子类,构成菱形继承后,当要输出a时,报错会显示“a”不明确,因为这里的a有两份,那我们要怎么解决这一问题呢?我们可以在B和C前加一个virtual使其为虚继承。
class A
{
public:
A(int a):a(a){}
protected:
int a;
};
class B :virtual public A
{
public:
B(int a,int b):A(a),b(b){}
protected:
int b;
};
class C :virtual public A
{
public:
C(int a,int c):A(a),c(c){}
protected:
int c;
};
class D :public B, public C
{
public:
D():B(1,2),C(3,4),A(999){}
void print()
{
cout << a << endl;
cout << B::a << endl;
cout << C::a << endl;
}
};
int main()
{
D d;
d.print();
return 0;
}
从打印结果可以看到,在D的构造函数中,尽管用B和C为a各自赋值,但是D中沿用的是它爷爷A中的a的值,这就是virtual的作用所在。
4.继承中的一些同名问题
一句话,如果没有类名限定,则遵循就近原则,怎么理解?
class A
{
public:
A(string name, int age) :name(name), age(age) {}
void print()
{
cout << "A:";
cout << name << "\t" << age << endl;
}
protected:
string name;
int age;
};
class B :public A
{
public:
B(string name, int age) :A("父类", 28), name(name), age(age) {}
void print()
{
//不做特别处理,就近原则
cout << name << "\t" << age << endl;
//类名限定
cout << A::name << "\t" << A::age << endl;
//不做特别处理,就近原则
A::print();
}
protected:
string name;
int age;
};
int main()
{
//不做特别处理,就近原则
//正常对象调用
B b("b", 18);
b.print();
A a("a", 28);
a.print();
//正常的指针调用
//就近原则
B* pb = new B("newB", 19);
pb->print();
pb->A::print();
A* pa = new A("newA", 29);
pa->print();
//非正常的指针
//1.允许子类对象初始化父类指针
A* pA = new B("newB", 49);
pA->print(); //父类的
return 0;
}
总结:不做类名限定的时候,调用的同名属性或者函数就是类自己的,要调用父类的同名属性或函数应该用类名限定,且在类外使用的时候均同理,只是有一点比较特殊,那就是允许子类对象初始化父类指针,但不允许用父类对象初始化子类指针。
二.构造与析构顺序
老生长谈了,每学到一个新知识都要来分析一下哈哈。还是一样,先上代码后分析:
class A
{
public:
A() { cout << "A"; }
~A() { cout << "A"; }
protected:
};
class B
{
public:
B() { cout << "B"; }
~B() { cout << "B"; }
};
class C
{
public:
C() { cout << "C"; }
~C() { cout << "C"; }
};
class D :public C, public A, public B
{
public:
D() { cout << "D"; }
~D() { cout << "D"; }
};
int main()
{
D d;
return 0;
}
首先要明白一点,单继承中的构造顺序中,是先构造父类的再构造子类,而在多继承中,任何构造顺序问题都和初始化参数列表无关,其构造顺序和继承顺序一致。按这个逻辑我们来分析构造顺序,构造了d对象,首先应该调用父类的构造函数,而它有很多个爸爸,先调用哪个呢?应该按顺序来 “CAB” ,接着调用子类 “D” ,那么构造顺序就分析完了,接着说析构顺序,应该与构造顺序相反,则应该是先析构子类D,再从后往前析构父类 “BAC”。
可以看到的,分析结果正确!
好了,今天总结到这,下篇更新虚函数以及多态的内容。