前言
上一篇有关继承的文章只是对其有个广度的了解,这篇文章记录一点点继承中的常见问题。
文章链接: C++:继承 | 从基本语法到菱形继承
目录
1.struct 和 class 区别
在C++中,struct和class 都可以设计类。
区别是:
- struct在设计类时,成员的默认属性为公有的,而class设计的类成员默认属性为私有的。
- 由struct设计的类叫做数据集合,而class设计的类叫做对象。数据集合代表着只需要有数据,而对象既有数据也有方法。
- 在继承概念中,struct的继承,在缺省访问限定符情况下,默认为公有继承;class的继承,在缺省访问限定符情况下,默认为私有继承。
- 默认的继承方式和派生类有关。若派生类为class, 基类为struct,则默认继承方式为私有继承;若派生类为struct,基类为class,默认继承方式为公有继承。
示例:
// struct的公有继承
struct A{};
struct B : A {};
// class 私有继承
class A{};
class B : A{};
// class继承struct
struct A{};
class B : A {}; // 私有继承
//struct 继承class
class A {};
struct B : A {}; // 公有继承
2.若派生类公有继承了基类的公有成员问题
示例:
遇到这种情况,能否说派生类继承了基类的两个成员,即派生类现在又四个成员?
class Object
{
public:
int a;
int b;
};
class Base : public Object
{
public:
int c;
int d;
};
答:这种说法错误。
原因:类型是设计的产物,不能根据设计的产物来谈论内存的表达形式。
正确说法:
对于这个例子来说,派生类有三个成员,分别是隐藏的基类对象,c 和 d。这个隐藏的基类对象,里面包含了基类的成员。
内存:
3.派生类方法访问隐藏的基类成员问题
公有方式继承
示例:对于派生类的fun()函数,那条语句不能编译通过?
class A
{
private:
int ax;
protected:
int ay;
public:
int az;
public:
A() { ax = ay = az = 0; }
};
class B : public A
{
private:
int bx;
protected:
int by;
public:
int bz;
public:
B() { bx = by = bz = 0; }
void fun()
{
ax = 10; ay = 20; az = 30;
}
}
答: ax = 10; 无法编译通过,其他可以。
原因:因为fun()函数为派生类的类内公有方法,所以可以访问自己的成员,而继承下来的隐藏基类对象也属于派生类,所以可以访问这个隐藏对象的保护属性和公有属性。
图示:
私有方式继承
示例:如果以私有方式继承,访问权限如何?
class A
{
private:
int ax;
protected:
int ay;
public:
int az;
public:
A() { ax = ay = az = 0; }
};
class B : private A
{
private:
int bx;
protected:
int by;
public:
int bz;
public:
B() { bx = by = bz = 0; }
void fun()
{
ax = 10; ay = 20; az = 30;
}
}
答:依旧是除了ax = 10;编译不通过,其他都行。
原因:私有方式继承下来,那么隐藏的基类对象的权限和自身的 private成员权限相同,既然可以访问自己的私有成员,那么也可以访问隐藏基类对象的公有成员和保护成员。
图示:隐藏基类对象的权限和 bx权限相同。
4.派生类的隐藏基类对象和基类作为成员变量时的区别
示例: 派生类公有继承基类,在派生类的私有属性中有一个基类的对象,那么访问权限有什么不同?
class A
{
private:
int ax;
protected:
int ay;
public:
int az;
public:
A() { ax = ay = az = 0; }
};
class B : public A
{
private:
int bx;
A aa;
protected:
int by;
public:
int bz;
};
答: 对于隐藏基类对象,可访问保护属性和公有属性;对于私有属性的基类对象,可访问该对象的公有属性。
图示:
可以说:在派生类中,隐藏基类对象是无名的,可以访问除私有属性外的成员;而作为私有属性的基类对象,是具有名的,只可访问公有成员。即继承而来的对象和成员对象的差别主要在保护属性上。
5.切片现象(赋值兼容性规则)
在公有继承中,派生类对象可以对基类对象进行赋值。
示例:Object为基类,Base为派生类
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x)
{
cout << "Create Object: " << this << endl;
}
~Object() { cout << "Destroy Object: " << this << endl; }
};
class Base : public Object
{
private:
int num;
public:
Base(int x = 0) : Object(x + 10), num(x)
{
cout << "Create Base: " << this << endl;
}
~Base() { cout << "Destroy Base: " << this << endl; }
};
int main()
{
Object obj(10);
Base base(20);
obj = base;
return 0;
}
因为公有继承是is-a的关系,所以:
派生类对象可以对基类对象赋值,但赋值时候是拿派生类里面的隐藏基类对象成员对其赋值 ,这种情况叫做切片现象。
图示:
同样,指针和引用也可以发生切片现象。
当然,类型对指针有两个约数:
- 指针加一的能力
- 对内存解析的能力
所以将基类指针指向派生类对象的地址时,基类指针只会解析到它自身的那部分,派生类本身具有的属性不能解析。
6. 派生类使用拷贝构造
正确的改进方法
- 若基类与派生类的拷贝构造都是这种形式:
Object(const Object& obj) : value(obj.value)
{
cout << "Copy Create Object: " << this << endl;
}
Base(const Base& base) : num(base.num)
{
cout << "Copy Create Base: " << this << endl;
}
示例代码
int main()
{
Base base1(10);
Base base2(base1);
return 0;
}
运行结果:可以看出,在拷贝构造时,base2先调用的基类的构造函数,而不是基类的拷贝构造,所以这个拷贝,没有将base1的隐藏基类成员的内容拷贝到。
2.改进方法:如何拷贝到隐藏基类成员的内容?
只需要在派生类的拷贝构造函数的初始化列表加上基类的拷贝构造函数,参数为引用传进来的base。
Object(const Object& obj) : value(obj.value)
{
cout << "Copy Create Object: " << this << endl;
}
Base(const Base& base) : num(base.num), Object(base)
{
cout << "Copy Create Base: " << this << endl;
}
为什么可以这样写?
- 首先传入的是一个派生类对象,那么基类就会不会调用普通的构造函数,而是可以接收对象的拷贝构造函数。
- 这里有要说到上面提到的赋值兼容性规则,派生类对象可以赋值给基类对象,指针和引用同样适用。
错误示例
切勿在派生类的拷贝构造函数内部调用基类的拷贝构造。
错误示例是这样写的:
Base(const Base& base) : num(base.num) //, Object(base)
{
Object::Object(base);
cout << "Copy Create Base: " << this << endl;
}
这样的后果是:
调用这个基类的拷贝构造,只会构造出一个无名对象,它和我们的目标对象没有关系,所以拷贝构造完就会被析构。