对于面向对象的C++来说,不能仅仅依靠参数类型来使用函数来实现代码的复用,应该也有使用对象来实现代码的复用
目录
一、继承的初始
-
1、概念
一说到继承,在现实生活中便可以想到继承家产,继承不仅仅是家产,还继承了父亲的姓,和父亲的一些特征。在c++里继承是面向对象程序设计里到代码复用的一种手段。当然继承是指类的继承。被继承的类称为基类,继承的类叫派生类。
-
2、定义
1、定义格式:
2、继承方式:
3、继承间的访问限定
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成 员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
- public继承,父类成员是什么权限继承下来就是什么权限。
- protected继承,public成员变成保护成员。基类的保护成员在派生类可以访问。所以才说这是继承产生的权限符。
- private继承,都成为私有成员。
- 父类的私有成员在派生类中是不可见的,不可见是指看不见,但不是不存在。
- 由于后两种继承与继承的初衷有所违背,因为继承就是为了扩展,但是候两者成员只能在派生类使用,扩展性不强了。
-
3、实例
class Person
{
public:
Person()
:name_("张三")
,assess_("富有")
{
cout << "Person()" << endl;
}
protected:
string assess_;
private:
string name_;
};
class Student : public Person
{
public:
private:
int stunum_;//学号
};
上述代码student类继承了person类,会调用父类的构造方法来初始化来自父类的成员,当然由于name_是父类私有的,所以是不可见的,但是assess_是保护的,所以可见。
二、基类和派生类对象赋值转换
在内置类型中,我们可以这样,它们是可以进行隐式类型转换,但是会丢失精度。
int a = 1;
double b = 3.14;
a = b;
int c = 1;
double d = 3.14;
c + d;
在基类和派生类对象的赋值中也有转换。它们的转换是这样的。
这里有几个要注意的地方:
- 赋值的情况只能是基类的对象 / 基类的指针 / 基类的引用
- 只能派生类赋值给基类;因为派生类对于的成员会被切掉然后对基类进行赋值,派生类有基类的所有成员,但是基类没有派生类的,所以基类给派生类赋值可能造成,非法访问
- 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的
class Person
{
public:
Person()
:name_("张三")
,assess_("富有")
{
cout << "Person()" << endl;
}
void setName(const string& name)
{
name_ = name;
}
protected:
string assess_;
private:
string name_;
};
class Student : public Person
{
public:
Student()
:stunum_("123456")
{
assess_ = "富二代";
cout << "Student()" << endl;
}
void Myname(const string& name)
{
setName(name);
}
private:
string stunum_;//学号
};
对于上面的代码做以下实验:
三、继承中的作用域
之所以说作用域呢,是因为这里又会有一个新的词出现叫——隐藏。在继承中基类和派生类都有独立的作用域。 如果子类和父类中有同名(名字相同)成员,子类成员将屏蔽父类对同名成员的直接访问,子类只能访问自己的函数,如果是子类对基类进行赋值,父类也只能调用自己的函数。这种情况就叫隐藏,或者说是重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)。但这不是重载,因为不在同意作用域内。所以在实际中在继承中最好不要定义同名的成员
class Child;
class Father
{
public:
Father(const string& name = "张三", int age = 10)
:name_(name)
, age_(age)
{
cout << "Father()" << endl;
}
void Func(int tmp)//函数隐藏
{
cout << tmp << endl;
}
~Father()
{}
private:
string name_;
int age_;
};
class Child : public Father
{
public:
Child(const string& sex = "男", int grade = 90)
:sex_(sex)
, grade_(grade)
{
cout << "Child()" << endl;
}
void Func()
{
cout << "NULL" << endl;
}
~Child()
{}
private:
string sex_;
int grade_;
};
四、派生类的默认成员函数
在前面接触c++的类和对象时,已经学习来了,类的六大默认成员函数,来自继承之后的派生类,会继承基类的成员,所以它的成员函数又会是怎样的?
1、构造和析构
1)派生类对象初始化先调用基类构造始化基类的那一部分成员,如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用,再调派生类构造,,初始化自己的成员。
2) 派生类对象析构清理先调用派生类析构再调基类的析构。
class Father
{
public:
Father(const string& name = "张三", int age = 10)
:name_(name)
, age_(age)
{
cout << "Father()" << endl;
}
void Func(int tmp)//函数隐藏
{
cout << tmp << endl;
}
~Father()
{
cout << "~Father()" << endl;
}
private:
string name_;
int age_;
};
class Child : public Father
{
public:
Child(const string& sex = "男", int grade = 90)
:sex_(sex)
, grade_(grade)
{
cout << "Child()" << endl;
}
void Func()
{
cout << "NULL" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
private:
string sex_;
int grade_;
};
void test3()
{
Child c1;
}
int main()
{
test3();
system("pause");
return 0;
}
-
2、拷贝构造和运算符重载
1)派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
2)派生类的operator=必须要调用基类的operator=完成基类的复制。
class Father
{
public:
Father(const string& name = "张三", int age = 10)
:name_(name)
, age_(age)
{
cout << "Father()" << endl;
}
Father(const Father& f)
:name_(f.name_)
,age_(f.age_)
{
cout << "Father(&)" << endl;
}
Father& operator=(const Father& f)
{
if (this != &f)
{
this->name_ = f.name_;
this->age_ = f.age_;
}
cout << "Foperator=" << endl;
return *this;
}
~Father()
{
cout << "~Father()" << endl;
}
private:
string name_;
int age_;
};
class Child : public Father
{
public:
Child(const string& sex = "男", int grade = 90)
:sex_(sex)
, grade_(grade)
{
cout << "Child()" << endl;
}
Child(const Child& c)
:Father::Father(c)
,sex_(c.sex_)
, grade_(c.grade_)
{
cout << "Child(&)" << endl;
}
Child& operator=(const Child& c)
{
if (this != &c)
{
Father::operator=(c);
this->sex_ =c.sex_;
this->grade_ =c.grade_;
}
cout << "Coperator=" << endl;
return *this;
}
~Child()
{
cout << "~Child()" << endl;
}
private:
string sex_;
int grade_;
};
五、友元与静态
-
1、友元关系不能继承
就是说基类友元不能访问子类私有和保护成员,因为爸爸的朋友不是孩子的朋友。
class A
{
friend void Display(const A& a);
public:
A(const int& a = int())
:a_(a)
{}
private:
int a_;
};
void Display(const A& a)
{
cout << a.a_ << endl;
}
class B : public A
{
public:
B(const int& b = int())
:A::A()
,b_(b)
{}
private:
int b_;
};
-
2、继承体系共享静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一 个static成员实例
class A
{
public:
A(const int& a = int())
:a_(a)
{}
static void Print(const A& a);
private:
int a_;
};
void A::Print(const A& a)
{
cout << a.a_ << endl;
}
class B : public A
{
public:
B(const int& b = int())
:A::A()
,b_(b)
{}
private:
int b_;
};
class C : public A
{
public:
C(const int& c = int())
:A::A()
, c_(c)
{}
private:
int c_;
};
六、菱形继承
-
1、多继承
在c++的语法里,不仅仅有单继承还有多继承,多继承就像是多个身份一样,你可以是班长,也可以同时是学委;可以是老师,也会是学生,老师也有老师。
-
2、菱形继承
菱形继承是多继承和单继承组合形成的
-
3、问题
我们先从对象模型上来看
class Person
{
public:
Person()
:name_("张三")
,assess_("富有")
{
cout << "Person()" << endl;
}
protected:
string assess_;
private:
string name_;
};
class Student : public Person
{
public:
Student(const string str = "123456" )
:stunum_(str)
{
assess_ = "富二代";
cout << "Student()" << endl;
}
private:
string stunum_;//学号
};
class Teacher : public Person
{
public:
Teacher(const string str = "123456")
:edunum_(str)
{
assess_ = "富大代";
cout << "Teacher()" << endl;
}
private:
string edunum_;
};
class Self : public Student, public Teacher
{
public:
Self()
{}
};
从上面的监视中可以看见多继承的对象模型是这个样子的,
看到红色的圈子中,self类的对象是富二代还是富大代?这就带来菱形继承有数据冗余和二义性的问题。为了解决菱形继承的问题,大佬便发明了虚拟继承,什么是虚拟继承。就是在相同的基类前加virtual关键字。
class Student : virtual public Person
{
public:
Student(const string str = "123456" )
:stunum_(str)
{
assess_ = "富二代";
cout << "Student()" << endl;
}
private:
string stunum_;//学号
};
class Teacher : virtual public Person
{
public:
Teacher(const string str = "123456")
:edunum_(str)
{
assess_ = "富大代";
cout << "Teacher()" << endl;
}
private:
string edunum_;
};
具体是怎么回事,在后面的多态在进行介绍,毕竟,这篇博客有点长了【哈哈】。
总结
在继承这点,可以说在c++这里是个不足啊。首先是三个访问限定符,晕头转向,还妨碍与继承的初衷(扩展性)相悖。再者就是多继承了,有多继承就带来了菱形继承,有了菱形继承带来了虚拟继承,虚拟继承有带来了复杂的底层结构。补了一个坑,又有一个坑。有点拆东墙补西墙的感觉。