从 C 向 C++ 进阶系列导航
1. 类的继承
类之间可以存在继承关系,又称父子关系,父类又称为基类,子类又称为派生类。父子类之间有以下特点:
- 子类拥有父类的所有属性和行为。
- 子类对象可以当父类对象使用,可增加父类没有的属性和方法。
- 子类对象可以直接初始化为父类对象。
- 子类对象可以直接赋值给父类对象。
继承的形式如下:
class Parent
{
...
};
class Child : public Parent
{
...
};
以上为公有属性继承,表示子类对父类的成员访问级别保持不变,这也是最常用的继承属性。
- 实验:
class Parent
{
private:
int mVar;
public:
int GetmVar()
{
return mVar;
}
void SetmVar(int num)
{
mVar = num;
}
};
class Child : public Parent
{
};
int main(int argc, char *argv[])
{
Child obj;
obj.SetmVar(5);
cout << "obj.GetmVar() = " << obj.GetmVar() << endl; // obj.GetmVar() = 5
}
2. 继承中的访问级别
在封装篇中提到,除了公有属性 public 与私有属性 private 之外,还有保护属性 protected。保护属性介于公有属性与私有属性之间,是专门为了继承而设置的。
父类中保护属性的成员,在子类中能直接访问,除此之外均不可直接访问。
- 实验:
class Parent
{
protected:
int mVar;
public:
int GetmVar()
{
return mVar;
}
};
class Child : public Parent
{
public:
void SetmVar(int num)
{
mVar = num;
}
};
int main(int argc, char *argv[])
{
Child obj;
obj.SetmVar(1);
cout << "obj.GetmVar() = " << obj.GetmVar() << endl; // obj.GetmVar() = 1
// cout << "obj.mVar = " << obj.mVar << endl; // error: ‘int Parent::mVar’ is protected
}
3. 继承的属性
类的继承属性有三种:公有继承、私有继承、保护继承。当没有显式指定继承属性时,默认的继承属性为私有继承。
不同的继承属性会改变子类对父类成员的访问属性,基本可以用这条公式求解:子类继承成员的访问属性 = MAX {继承方式,父类成员的访问属性}。其中,三种继承方式的安全级别为:私有继承 > 保护继承 > 公有继承。
在实际实践中,基本使用的是公有继承,在 C++ 的派生语言(如 Java、C# 等)中也仅保持公有继承这一种方式。
- 实验:
class Parent
{
public:
int mVar;
int GetmVar()
{
return mVar;
}
};
class Child_A : public Parent
{
public:
void SetmVar(int num)
{
mVar = num;
}
};
class Child_B : protected Parent
{
public:
void SetmVar(int num)
{
mVar = num;
}
};
class Child_C : private Parent
{
public:
void SetmVar(int num)
{
mVar = num;
}
};
int main(int argc, char *argv[])
{
Child_A obj_A;
obj_A.SetmVar(1);
cout << "obj_A.GetmVar() = " << obj_A.GetmVar() << endl;
cout << "obj_A.mVar = " << obj_A.mVar << endl;
Child_B obj_B;
obj_B.SetmVar(1);
// cout << "obj_B.GetmVar() = " << obj_B.GetmVar() << endl; // error: ‘int Parent::GetmVar()’ is inaccessible
// cout << "obj_B.mVar = " << obj_B.mVar << endl; // error: ‘int Parent::mVar’ is inaccessible
Child_C obj_C;
obj_C.SetmVar(1);
// cout << "obj_C.GetmVar() = " << obj_C.GetmVar() << endl; // error: ‘int Parent::GetmVar()’ is inaccessible
// cout << "obj_C.mVar = " << obj_C.mVar << endl; // error: ‘int Parent::mVar’ is inaccessible
}
4. 类继承的构造
子类的构造顺序为:父类的构造 => 成员类对象的构造 => 子类本身的构造。用一句话概括为:先父母,后客人,再自己。而子类析构顺序是与构造顺序相反。
假设仅使用父类的默认构造函数和成员类对象的默认构造函数能够满足场景需求,那么隐式地调用父类与成员类对象的默认构造方法即可。但如果需要进行自定义初始化时,该怎么指定调用父类与成员类的构造函数呢?
可以使用初始化列表的方法显式地对父类与成员类对象进行初始化,这是因为初始化列表的初始化是发生在子类本身的构造之前,满足子类的构造规则。
当成员函数的声明与实现分离时,初始化列表应放在类成员函数的实现处。
- 实验:
class Parent
{
public:
int mVar;
Parent(int num)
{
mVar = num;
}
};
class Child : public Parent
{
private:
Parent mObj;
public:
Child(int num_A, int num_B) : Parent(num_A), mObj(num_B)
{
}
int GetmVar()
{
return mVar;
}
int GetmObjmVar()
{
return mObj.mVar;
}
};
int main(int argc, char *argv[])
{
Child obj(1, 2);
cout << "obj.GetmVar() = " << obj.GetmVar() << endl; // obj.GetmVar() = 1
cout << "obj.GetmObjmVar() = " << obj.GetmObjmVar() << endl; // obj.GetmObjmVar() = 2
}
5. 类继承的成员冲突
当子类与父类的成员(包括变量与函数)同名时,子类对象会优先使用子类中的成员,即子类成员会覆盖父类中的同名成员。此时如果想调用父类中的成员,那在调用时需要在成员前指定父类的作用域。
当子类的成员函数与父类的成员函数同名时,当参数列表相同时父类的成员函数肯定会被子类的成员函数覆盖,但当参数列表不同时,父类成员函数是否能够构成重载而避免被覆盖呢?答案是否定的。函数重载必须发生在同一个作用域中,而子类与父类的作用域是不同的,即便子类继承了父类。
- 实验:
class Parent
{
public:
int mVar;
Parent(int num)
{
mVar = num;
}
void Print()
{
cout << "in Parent" << endl;
}
void Print(int i)
{
cout << "in Parent !!!" << endl;
}
};
class Child : public Parent
{
public:
int mVar;
Child() : Parent(1)
{
mVar = 0;
}
void Print()
{
cout << "in Child" << endl;
}
};
int main(int argc, char *argv[])
{
Child obj;
cout << "obj.mVar = " << obj.mVar << endl; // obj.mVar = 0
cout << "obj.Parent::mVar = " << obj.Parent::mVar << endl; // obj.Parent::mVar = 1
obj.Print(); // in Child
// obj.Print(0); // error: no matching function for call to ‘Child::Print(int)’
obj.Parent::Print(); // in Parent
obj.Parent::Print(0); // in Parent !!!
}