1.继承中的作用域
1.1.隐藏规则
-
在继承体系中基类和派⽣类都有独⽴的作⽤域。
-
派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
(在派⽣类成员函数中,可以使⽤基类::基类成员显⽰访问)
-
需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
-
注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
cpp
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是⾮常容易混淆
class Person
{
protected :
string _name = "⼩李⼦"; // 姓名
int _num = 111; // ⾝份证号
};
class Student : public Person
{
public:
void Print()
{
cout<<" 姓名:"<<_name<< endl;
cout<<" ⾝份证号:"<<Person::_num<< endl;
cout<<" 学号:"<<_num<<endl;
}
protected:
int _num = 999; // 学号
};
int main()
{
Student s1;
s1.Print();
return 0;
};
1.2.隐藏习题
构成隐藏关系肯定不用多说,我们看第二个因为在派生类中的fun与基类构成隐藏关系所以我们去调用fun()的时候我们就要使用派生类中的方法来调用函数,我们需要传一个int类型的参数才可以调用。所以第二个问题答案是编译报错。
2.派生类的默认成员函数
2.1.4个常见默认成员函数
6个默认成员函数,默认 的意思就是指我们不写,编译器会变我们⾃动⽣成⼀个,那么在派⽣类中,这⼏个成员函数是如何⽣成的呢?
-
派生类的构造函数必须调用基类的构造函数初始化基类的那⼀部分成员 。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
-
派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
-
派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
-
派⽣类的析构函数会在被调⽤完成后**⾃动调⽤基类的析构函数清理基类成员** 。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
-
派生类对象初始化先调用基类构造再调派生类构造。
6.派生类对象析构清理先调用派生类析构再调基类的析构。
- 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。
cpp
class Person
{
public :
Person(const char* name = "peter")
: _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(const char* name, int num)
: Person(name)
, _num(num)
{
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()
{
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
int main()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
return 0;
}
2.2.定义一个不能继承的类
⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不⻅就不能调⽤了,那么派⽣类就⽆法实例化出对象。
⽅法2:C++11新增了⼀个final关键字,final修改基类,派⽣类就不能继承了。
cpp
class Base final
{
public :
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
private:
// C++98的⽅法
/*Base()
{}*/
};
class Derive : public Base
{
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
3.继承与友元
友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员。
cpp
class Student;
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;
}
int main()
{
Person p;
Student s;
// 编译报错:error C2248: "Student::_stuNum": ⽆法访问 protected 成员
// 解决⽅案:Display也变成Student 的友元即可
Display(p, s);
return 0;
}
4.继承与静态成员
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
cpp
class Person
{
public :
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected :
int _stuNum;
};
int main()
{
Person p;
Student s;
// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的
// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份
cout << &p._name << endl;
cout << &s._name << endl;
// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的
// 说明派⽣类和基类共⽤同⼀份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
return 0;
}