1.继承的概念及定义
1.1继承的概念
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "xxx";
int _age = 18;
};
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
s.Print();
Teacher t;
t.Print();
return 0;
}
1.2 继承定义
1.2.1定义格式
1.2.2继承关系和访问限定符
1.2.3继承基类成员访问方式的变化
1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的 不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它 。2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected 。 可以看出保护成员限定符是因继承才出现的 。3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min( 成员在基类的访问限定符,继承方式 ) , public > protected> private 。4. 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public , 不过最好显示的写出继承方式 。5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承 ,也不提倡使用 protetced/private 继承,因为 protetced/private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
当我把Student变成保护继承之后,就会发生报错,因为Print就变成了保护函数,访问不了变量。
2.基类和派生类对象赋值转换
3.继承中的作用域
1. 在继承体系中 基类 和 派生类 都有 独立的作用域 。2. 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。 (在子类成员函数中,可以 使用 基类 :: 基类成员 显示访问 )3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。4. 注意在实际中在 继承体系里 面最好 不要定义同名的成员 。
我在Person类和Student类都写了一个fun函数,这两个fun函数只有参数不同,那么是构成重载吗?不是,这两个函数构成隐藏 ,为什么呢?因为两个类的作用域是独立的,而构成重载的前提是同一作用域。
如果出现基类和派生类定义了同名成员,可以使用使用 基类::基类成员 显示访问。
4.派生类的默认成员函数
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:
protected:
int _id;//学号
};
int main()
{
Student s;
return 0;
}
当我实例化一个Student的对象s时,调用了父类的构造和析构函数,这就说明了当派生类进行实例化时,会调用基类的构造函数来构造派生类中基类的成员。
当我自己写了一个Student的构造函数,那么怎么初始化基类的成员呢?可以在初始化列表调用基类的构造函数,然后我们可以看到,是先调用了基类的构造函数,再构造派生类的成员。
那么拷贝构造和赋值重载怎么构造的呢?也是差不多的,使用基类的构造函数完成对基类成员的构造,然后使用赋值的切片,把基类的成员切过去构造。
那析构函数呢?析构函数需要显示调用,而且还有一个特殊处理,就是析构要先析构派生类再析构基类。因为先析构基类的话,万一派生类使用了基类的成员,而且基类已经被析构,有可能出现基类资源已经清理释放掉了,然后派生类还去访问了基类的成员,就会存在野指针的问题。所以在派生类的析构函数被调用之后,基类的析构函数会自动调用。
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 id)
:_id(id)
,Person(name)
{
cout << "Student()" << endl;
}
Student(const Student& s)
:Person(s)
,_id(s._id)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s)
{
_id = s._id;
Person::operator=(s);
}
return *this;
}
~Student()
{
//Person::~Person();
cout << "~Student()" << endl;
}
protected:
int _id;//学号
};
int main()
{
Student s1("张三",18);
Student s2(s1);
Student s3 = s1;
return 0;
}
今天的分享到这里就结束了,感谢大家的阅读!