目录
1.继承是什么?
继承是C++的三大特性之一。用于建立类与类之间的关系。
2.继承如何用?
class A
{
public:
int a;
};
class B : public A
{
public:
int b;
};
类A : (继承方式) 类B。
类B被称为父类(基类),类A称为子类(派生类)
3.继承的特点.
先让我们看这样一段代码.
3.1 继承方式
可以看到 Student 是 继承 Person,继承方式选择是public。
Student 所实例化的对象,可以使用基类Person中的函数,也可以使用Student中的函数。
这是因为我们选择的继承方式是 public
继承方式一共有三种:
public,protected,private
其具体含义就是 在子类中,对父类成员变量或函数的使用权限.
如果我们将三种访问权限进行比较的话。
public>protected>private
- 如果选择public继承的方式,则子类中对于父类中的所有访问权限不改变。
- 如果选择protected继承的方式,则子类中对于父类中所有public的访问权限全部更新为protected。
- 如果选择private继承的方式,则子类中对于父类中所有的访问方式都将改为private,也就是什么都访问不了。
class Person
{
public:
void Buyticket()
{
cout << "买票---全价" << endl;
}
public:
int _num;
string _name;
};
class Student : private Person
{
public:
void BuyticketS()
{
cout << "买票---半价" << endl;
}
public:
int _num;
string _name;
};
int main()
{
Person p;
Student s;
s.Buyticket();
return 0;
}
可以看出,如果我们使用private的方式继承,再去调用基类Person中的函数便会报错.
通常情况下,选择继承方式不会选择private。
权限的改变仅仅是在 这一个子类中,并不会直接在父类中修改访问权限!
3.2 重定义
class Person
{
public:
void Buyticket()
{
cout << "买票---全价" << endl;
}
void print()
{
cout << "姓名:" << _name << " 号码:" << _num << endl;
}
public:
int _num;
string _name;
};
class Student : public Person
{
public:
void Buyticket()
{
cout << "买票---半价" << endl;
}
void print()
{
cout << "姓名:" << _name << " 号码:" << _num << endl;
}
public:
int _num;
string _name;
};
int main()
{
Person p;
p._name = "张三";
p._num = 15;
p.Buyticket();
p.print();
Student s;
s._name = "李四";
s._num = 18;
s.Buyticket();
s.print();
return 0;
}
如果子类中的成员函数和成员变量的名字与父类中的成员函数和成员变量相同,又该如何?
先让我们来看看输出结果:
可以看出来,如果出现同名的函数或者成员变量,类外使用的时候,首先使用类中的,类中找不到再到父类中去找。
如果我们想使用父类中的同名函数或者同名变量如何解决呢?
答案是:在使用的时候加上类名。(类内和类外都可以使用这种方法)
int main()
{
Person p;
p._name = "张三";
p._num = 15;
p.Buyticket();
p.print();
Student s;
s._name = "李四";
s._num = 18;
s.Person::Buyticket();
s.print();
return 0;
}
3.3 四个常用默认成员函数
1.构造函数。
①子类可以没有构造函数吗?
可以,但是在实例化的时候,要与父类的构造函数统一。
class Person
{
public:
Person(string name, int num)
{
cout << "Person()"<<endl;
}
void Buyticket()
{
cout << "买票---全价" << endl;
}
void print()
{
cout << "姓名:" << _name << " 号码:" << _num << endl;
}
public:
int _num;
string _name;
};
class Student : public Person
{
public:
void Buyticket()
{
cout << "买票---半价" << endl;
}
void print()
{
cout << "姓名:" << _nameS << " 号码:" << _numS << endl;
}
public:
int _numS;
string _nameS;
};
int main()
{
Person p("张三",15);
p.Buyticket();
p.print();
Student s;
s.Person::Buyticket();
s.print();
return 0;
}
如果不按照父类的构造函数去实例化,则无法通过编译。
②如果子类有构造函数
继承父类的部分,要用父类的构造函数进行定义!
子类在定义构造函数的时候,同时需要满足父类的构造函数.
③拷贝构造
继承父类的部分,要用父类的拷贝构造进行定义!
Person(const Person& p)
{
_name = p._name;
_num = p._num;
}
Student(const Student& s):Person(s)
{
_nameS = s._nameS;
_numS = s._numS;
}
Person的拷贝构造函数的形参是Person类的引用,为什么这里能够直接传s过去呢?
那么这里就会介入 三种子类传给父类的方式
Person p1;
Student s1;
p1 =s1;
p1& = &s1;
Person* p2 = &s1;
父类可以通过 普通类, 引用,指针来接收子类。
而子类不可以接收父类。
原因在于 父类中的成员变量和成员函数都被子类所继承,相当于子类中具有父类所有的东西。
而父类中可能不存在子类中其他的成员变量或成员函数。
因此 子类不可以接收父类!
④赋值拷贝
Person& operator=(const Person& p)
{
if (this != &p)
{
_num = p._num;
_name = p._name;
}
return *this;
}
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_numS = s._numS;
_nameS = s._nameS;
}
return *this;
}
赋值的时候 ,也是需要继承父类的部分,要用父类的赋值构造进行定义!
⑤析构函数
析构的时候,内置类型不需要处理,自定义类型,调用自己的析构函数。
~Person()
{}
~Student()
{
~Person();//千万不要这么写!!
}
如果在子类的析构函数中,再写上父类的析构函数,则会实现两次父类析构的情况,这样会报错!
通常情况下,父类的析构都是自己运行的!
子类构造和析构的时候会发生什么?
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B :public A
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
答案是:在构造子类的时候,会先构造父类,再进行构造子类。
析构的时候:先析构子类,再析构父类。
4、继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
class Student : public Person
{
protected:
int _stuNum;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
这样写会报错的,因为友元函数是访问不到子类中的成员和函数的。
五、继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。
class Person
{
public :
Person () {++ _count ;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :
string _seminarCourse ; // 研究科目
};
int main()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout<<" 人数 :"<<Person::_count<<endl;
Student::_count =0;
cout<<" 人数 :"<<Person::_count<<endl;
}
六、菱形继承
谈菱形继承之前,先谈一下单继承和多继承。
单继承:
class A
{};
class B : public A
{};
class C : public B
{};
多继承:
class A
{};
class B
{};
class C : public A,public B
{};
菱形继承:
class A
{
public:
int _a;
};
class B :public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
菱形继承的影响:数据冗余和二义性!
int main()
{
D d;
return 0;
}
可以看出,实例化d, d中具有两份A,这就是数据冗余。
这是二义性,如果我们要修改d中_a的值,那么二义性无法明确知道访问的是哪一个。
如何解决菱形继承! 在中间进行虚拟继承
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 2;
d.C::_a = 3;
d._b = 4;
d._c = 5;
d._a = 1;
return 0;
}
可以看出,B、C、D中的_a都是指向同一个 _a。
从内存的角度来看:
0x007EFDC8是 d的地址,同时也是d中B的地址
B、C 由数值和虚表指针构成。
开头是虚表指针,下面是数值。
而虚表指针里面存的是偏移量
以B的虚表指针举例.
16进制的14等于十进制的20.而A的地址 0x007EFDDC -B的地址 0x007EFDC8 恰好为20.