继承的概念
继承是类层次的复用。类似函数复用。。
class Person
{
private:
string _name = "abcd";
int _age = 1;
};
class Student : public Person
{
private:
int _id;
};
// Person有的属性,Student也有。
int main()
{
Student s1;
return 0;
}

继承的格式
子类又可叫派生类。父类又可叫基类。

继承关系和访问限定符

基类成员访问方式
基类的成员在子类中的访问方式 == Min(成员在基类的访问限定符, 继承方式),三者之间的关系:
public>protected>private基类
private成员在派生类中无论以什么方式继承都是不可见的。不可见!=私有,私有在类里面可以访问,在类外面不可以访问。而不可见是类里面类外面的都不可访问。因此,如果父类不想给子类继承下来成员变量,就可以设置成私有。但是子类对象可以调用父类的函数去访问父类的成员。
private和protected限定符在当前类没区别,在子类中有很大区别,前者不可见,后者可见。由此可见protected限定符是因为继承才出现的。关键字
class和struct,最好还是显示写。
父类和子类的赋值转换
子类对象可以赋值给父类的对象、父类的指针、父类的引用。这种方式叫切片。
父类对象不能赋值给子类对象。
继承中的作用域
在继承中,父类和子类有自己独立的作用域。
父类和子类中有同名成员,子类对象访问该同名成员时按照就近原则,这种情况叫隐藏,也叫重定义。
(如果想访问父类的该成员,就要使用父类::父类成员显示)
如果是成员函数,只需要函数名相同就构成隐藏。
函数重载:同一作用域,函数名相同、参数列表不同构成函数重载。
隐藏:在父子类中,只要函数名相同就构成隐藏。
子类的默认成员函数
构造函数
由以下代码我们可知:
当子类不显示写构造函数时:
- 当父类有默认构造函数时,子类实例化对象时,会调用父类的构造函数。因为子类中有父类的成员,父类的成员需要调用父类的构造函数。
- 当父类没有默认构造函数时,子类实例化对象会报错。
class A
{
public:
A(int a = 10)
:_a(a)
{
cout << "A()" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
cout << "A& operator=(const A& a)" << endl;
return *this;
}
private:
int _a;
};
class B :public A
{
public:
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
B b3;
b3 = b1;
return 0;
}


当子类显示写构造函数时:
- 当父类有默认构造函数时,子类实例化对象时,会调用父类的构造函数。因为会走B类的初始化列表。
- 当父类没有默认构造函数时,要显示调用父类的构造函数。
- 子类实例化的时候,先调用父类的构造,后调用子类的构造。


拷贝构造函数
当在父类中显示写了拷贝构造函数时:
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
A(const A& a)
:_arr(a._arr)
,_a(a._a)
{
cout << "A(const A& a)" << endl;
}
private:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{}
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
return 0;
}


在父类中不写拷贝构造函数:
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
private:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{}
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
return 0;
}


由于此时对堆上申请了内存,所以要管理内存,添加析构函数后:
- 因为是浅拷贝,所以析构函数会析构两次,程序会崩溃。
- 改变一方会引起另一方的改变。
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
~A()
{
delete[] _arr;
_arr = nullptr;
}
private:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{}
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
return 0;
}

所以不管是编译器生成的,还是自己写的值拷贝的构造函数,都有问题,应当要深拷贝。
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
// b2(b1)
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
_arr = new char[1];
_arr[0] = 'a';
}
~A()
{
delete[] _arr;
_arr = nullptr;
}
private:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{}
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
return 0;
}

赋值重载
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
// b2(b1)
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
_arr = new char[1];
_arr[0] = 'a';
}
A& operator=(const A& a)
{
if (this != &a)
{
_arr = new char[1];
_arr[0] = 'z';
}
_a = a._a;
return *this;
}
~A()
{
delete[] _arr;
_arr = nullptr;
}
protected:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{}
// b3 = b1
B& operator=(const B& b)
{
if (this != &b)
{
operator=(b);
_b = b._b;
}
return *this;
}
protected:
int _b = 20;
};
int main()
{
B b1;
B b3(20);
b3 = b1;
return 0;
}
子类对象中属于父类的成员要想被赋值,在子类中写operator=(子类对象)是不可以的,因为operator=()构成了==隐藏==。必须显示调用,改进如下:
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
return *this;
}
代码完整执行后的结果:

析构函数
将子类父类的打印补充完整:
class A
{
public:
A(int a = 10)
:_arr(new char[1])
,_a(a)
{
cout << "A(int a = 10)" << endl;
}
// b2(b1)
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
_arr = new char[1];
_arr[0] = 'a';
}
A& operator=(const A& a)
{
if (this != &a)
{
_arr = new char[1];
_arr[0] = 'z';
}
_a = a._a;
cout << "A& operator=(const A& a)" << endl;
return *this;
}
~A()
{
delete[] _arr;
_arr = nullptr;
cout << "~A()" << endl;
}
private:
char* _arr;
int _a;
};
class B :public A
{
public:
B(int b = 10)
:_b(b)
,A(1000)
{
cout << "B(int b = 10)" << endl;
}
B(const B& b)
:_b(b._b)
,A(b)
{
cout << "B(const B& b)" << endl;
}
// b3 = b1
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
cout << "B& operator=(const B& b)" << endl;
return *this;
}
private:
int _b = 20;
};
int main()
{
B b1;
B b2(b1);
B b3(5);
b3 = b1;
return 0;
}

发现都是先父后子,补充完析构函数后结果报错,为什么?
析构函数会被处理成
destructor,子类的析构和父类的析构又构成隐藏。所以要显示调用。但是,子类中是不允许你自己写对父类的析构函数的。因为构造时顺序是先父后子,析构时要保证先子后父。子类析构函数完成时, 会自动调用父类析构函数,保证先析构子,在析构父。
~B()
{
//~A();
A::~A();
cout << "~B()" << endl;
}
继承与友元
友元关系不能继承:就是父类友元不能访问子类私有和保护成员。
继承和静态成员
父类定义了
static静态成员,整个继体系中只有这样的一个成员。可以用来计算继承后一共创建了多少个对象。
单继承和多继承
单继承:一个子类只有一个直接父类。
多继承:一个子类有两个或以上直接父类。

菱形继承:多继承会导致菱形继承,菱形继承是一种特殊情况。
问题:数据冗余和二义性问题。数据冗余导致浪费空间,二义性导致访问不明确。
**如何解决菱形继承的数据冗余和二义性问题? **
虚继承解决--
virtual关键字。
菱形继承的问题:B和C里都有一份A,数据冗余。访问时必须指定域作用限定符,否则二义性。
class A
{
//protected:
public:
int _a;
};
class B :public A
{
//protected:
public:
int _b;
};
class C :public A
{
//protected:
public:
int _c;
};
class D :public B, public C
{
//protected:
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}

用virtual关键字修饰,成虚继承后:
class A
{
//protected:
public:
int _a;
};
class B : virtual public A
{
//protected:
public:
int _b;
};
class C : virtual public A
{
//protected:
public:
int _c;
};
class D :public B, public C
{
//protected:
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}


虚继承就是通过增加指针,然后经过指针指向的偏移量来得到要访问的变量。


