继承是面向对象复用的重要手段.通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质不同的东西.
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Person
{
private:
string _name;
public:
void show()
{
cout << "Person" << endl;
}
};
class Student:public Person
{
public:
void display()
{
cout << "Student" << endl;
}
private:
int _stunum;;
};
int main()
{
Student s;
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/319a6e037f1f17925c354891ad8bf13b.png)
继承是一种复用手段,在继承关系里基类的成员类的成员派生类的成员,由此达到复用目的.
总结:
1.基类的私有成员在派生类中是不能被访问的,如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保护成员,可以看出保护成员限定符是因为继承才出现.
2.punlic继承是一个接口继承,保持is-a(每一个子类都是一个父类)原则,每个父类可用的成员对子类也是可用的,每个子类对象也都是一个父类对象.
3.protected/private继承是一个实现继承,基类的部分成员并未完全称为子类接口的一部分,是has-a(每一个子类中都有一个父类)的关系原则.所以非特殊情况下是不会用这两种继承关系,绝大多是情况下都是使用公有继承.
4.不管哪种继承方式,在派生内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能访问)
5.使用关键字class时默认的继承方式时private,使用struct时默认的继承方式时public,不过最好显示的写出继承方式.
6.在实际运用中一般都是用public继承,极少场景下才会使用protected/private继承.
继承与转换--赋值兼容规则--public继承
1.punlic继承时子类可以赋值给父类--切片
2.子类对象不能赋值给子类对象
3.父类的指针/引用可以指向子类对象
4.子类的指针/引用不可以指向父类对象(可以通过强制类型转换完成)
(这种情况会出现问题就是越界访问)
例:
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Person
{
protected:
string _name;
public:
void show()
{
cout << "Person" << endl;
}
};
class Student:public Person
{
public:
void display()
{
cout << "Student" << endl;
}
private:
int _num;;
};
int main()
{
Person p;
Student s;
p=s; //子类对象可以赋值给父类对象(切片/切割)
//s=p; // 父类对象不能赋值给子类对象
//父类的指针/引用可以指向子类对象
Person *p1 = &s;
Person &p2 = s;
//子类的指针/引用不可以指向父类对象(可以通过强制类型转换完成)
Student* s1 = (Student*)&p;
Student& s2 = (Student&)p;
system("pause");
return 0;
}
继承体系中的作用域:
1.在继承体系中基类和派生类都有独立的作用域
2.子类和父类中有同名成员(同名成员函数和同名成员变量),子类成员将屏蔽父类成员的直接访问(子类成员函数中,可以使用 基类::基类成员 访问)--(隐藏)重定义
3.注:在实际继承体系中最好不要定义同名的成员
例:(同名成员变量)
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Person
{
public:
string _name;
public:
void show()
{
cout << "Person" << endl;
}
};
class Student:public Person
{
public:
void display()
{
cout << "Student" << endl;
}
public:
int _num;;
};
int main()
{
Person p;
p._name = 10;
Student s;
cout << sizeof(s) << endl;
s._name = 20; //访问的是子类的_name(这就是重定义/隐藏)
s.Person::_name = 200;//指定作用域是可以访问到父类的_name
system("pause");
return 0;
}
例:(同名成员函数)
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Person
{
public:
void show()
{
cout << "Person show()" << endl;
}
void f1(int i)
{
cout << "Student shows()" << endl;
}
string _name;
};
class Student:public Person
{
public:
void show()
{
cout << "Student shows()" << endl;
}
void f1()
{
cout << "Student shows()" << endl;
}
int _num;;
};
int main()
{
Person p;
Student s;
s.show();//两个show构成重定义/隐藏;
s.f1(); //两个f1构成重定义/隐藏(虽然他们的参数不同但是名字是相同的)
system("pause");
return 0;
}
继承关系里面,在派生类中如果没有显示的定义这6个成员函数编译器会默认合成这6个成员函数
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Person
{
public:
Person()
:_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=(const Person& p)" << endl;
if (this != &p)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
//构造
Student()
:Person()//name是父类的成员所以要调用父类的构造函数
,_num(0)
{
cout << "Student()" << endl;
}
//拷贝构造
Student(const Student& s)
:Person(s)
,_num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
return *this;
}
~Student()
{
Person::~Person();
cout << "~Student()" << endl;
}
protected:
int _num;;
};
int main()
{
Student s1;
Student s2(s1);
Student s3;
s1 = s3;
Person p;
s1.~Student();
p.~Person();
system("pause");
return 0;
}
析构函数比较特殊,因为子类的析构函数跟父类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理
子类的构造是先构造父类再构造子类
子类的析构实现析构子类再析构父类
实现一个不能被继承的类: 方法一:
缺点:AA 也不能在类外用因为他们是私有的 方法二:
|
实现一个只能在栈上生成对象的类: |
|
实现一个只能在堆上生成对象的类:(为防止在堆上生成成员再拷贝构造一个栈上的所以要做到防拷贝) 方法一:
public: int _bb;};方法二:
int _bb;}; |
|
单继承&多继承
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class A
{
public:
int _name;
};
class B :public A
{
public:
int _num;
};
class C :public A
{
public:
int _id;
};
class D :public B, public C
{
public:
int _course;
};
int main()
{
D d;
d ._name=1;
d ._num= 2;
d._id = 3;
d._course = 4;
system("pause");
return 0;
}
C中有两份A成员所以菱形继承存在二义性和数据冗余;
菱形虚继承
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class A
{
public:
int _name;
};
class B :virtual public A
{
public:
int _num;
};
class C :virtual public A
{
public:
int _id;
};
class D :public B, public C
{
public:
int _course;
};
int main()
{
D d;
d._name = 1;
d._num = 2;
d._id = 3;
d._course = 4;
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/ccaaf5cc527e54ec4880bad661c0d474.png)
虚函数&多态
虚函数——类的成员函数前面加关键字virtual,则这个成员函数就是虚函数。
虚函数重写——当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写了(也称覆盖)了父类的这个虚函数。
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class A
{
public:
virtual void f1()
{
cout << "A" << endl;
}
int _name;
};
class B : public A
{
public:
virtual void f1()
{
cout << "B" << endl;
}
int _num;
};
void fun(A& m)
{
m.f1();
}
void fun1(A* m)
{
m->f1();
}
int main()
{
//构成多态的条件--跟对象有关
//1.函数(fun/fun1)的参数必须是父类的指针/引用
//2.函数(fun/fun1)里调用的函数(f1)必须是虚函数的重写
A a;
B b;
fun(a);
fun(b);
A c;
B d;
fun1(&c);
fun1(&d);
system("pause");
return 0;
}
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class A
{
public:
void f1()
{
cout << "A" << endl;
}
int _name;
};
class B : public A
{
public:
void f1()
{
cout << "B" << endl;
}
int _num;
};
void fun(A& m)
{
m.f1();
}
int main()
{
//不构成多态的函数--跟类型有关
A a;
B b;
fun(a);
fun(b);
system("pause");
return 0;
}
总结:
1.子类重写父类的虚函数实现多态,要求函数名,参数列表,返回值完全相同。
2.父类定义了虚函数,在子类中该函数始终保持虚函数特性。
3.只有类的成员函数才能定义为虚函数。
4.静态成员函数不能定义为虚函数。
5.如果在类外面定义了虚函数,只能是声明时加virtual,类外定义虚函数时不能加virtual。
6.构造函数不能是虚函数,虽然可以将operator=定义为虚函数,但是最好不要把operator=定义为虚函数,因为使用时容易引起混淆。
7.不要在构造函数和析构函数中调用虚函数,在构造和析构函数中对象是不完整的容易引发未定义行为。
8.最好把父类的析构函数声明为虚函数(原因:析构函数比较特殊,因为子类的析构函数跟父类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理(参考上面合成默认函数))
纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化处对象。
class A
{
virtual void show() = 0;//纯虚函数
protected:
string _name;//名字
};
class B :public A
{};
友元与继承
有缘关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
友元与静态成员
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论父类中出多少个类,都只有一个static成员
.h
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class AA
{
public:
AA()
{
++_count;
}
protected:
string _name;
public:
static int _count;
};
int AA::_count = 0;
class BB : public AA
{
protected:
int _stunum;
};
class CC:public BB
{
protected:
string _course;
};
.cpp
#include"test.h"
int main()
{
AA a1;
AA a2;
AA a3;
CC c;
cout << "人数" << AA::_count << endl;
AA::_count = 0;
cout << "人数" << AA::_count << endl;
system("pause");
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/a9c5c0b398a6e655fc54d0bf5dfb35d3.png)