1 继承
C++的继承是弱继承
继承的语法:
class 子类 : 继承方式1 基类1, 继承方式2 基类2, ... { ... };
继承方式:
共有继承 public
保护继承 protected
私有继承 private
2 继承的基本属性(3种继承方式均有)
继承所要达到的目的:
子类对象包含基类子对象
子类内部可以直接访问基类的所有非私有成员
// derived.cpp 继承最基本的特点:
// (1) 子类对象 内部包含 基类(子)对象
// (2) 子类内部可以访问 基类的 非私有(公有/保护)成员(变量/函数)
#include <iostream>
using namespace std;
class Base {
public:
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
//class Derived : public Base {
//class Derived : protected Base {
class Derived : private Base {
public:
void fun() {
m_a = 100; // ok
foo(); // ok
m_b = 200; // ok
bar(); // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
// m_c = 300; // error
// hum(); // error,以上两行代码证明子类内部不可以直接访问基类的私有成员
}
private:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Base b; // 基类对象-->|m_a m_b m_c|
cout << "基类对象b的大小:" << sizeof(b) << endl; // 12
Derived d; // 子类对象--> |基类子对象|m_d| --> |m_a m_b m_c|m_d|
cout << "子类对象d的大小:" << sizeof(d) << endl; // 16
return 0;
}
继承的本质:
基类的非私有成员在子类中仅为可见,而非子类拥有。 (可见表)(软继承)
注意:关于继承,切记不要理解为基类的成员变为子类的成员。继承不会改变类成员的作用域,基类的成员永远都是基类的成员,并不会因为继承而变成子类的成员。
尽管基类的公有和保护成员在子类中直接可见,但仍然可以在子类中重新定义这些名字,子类中的名字会隐藏所有基类中的同名定义(定义表隐藏可见表)。
如果需要在子类内部访问一个基类中定义却被子类标识符所隐藏的名字,可以借助作用域限定操作符::实现。
因为作用域的不同,分别在子类和基类中定义的同名成员函数(包括静态成员函数),并不构成重载关系,相反是一种隐藏关系。
// hide.cpp 基类和子类内部的同名定义 相互之间为隐藏关系
#include <iostream>
using namespace std;
class Base {
public:
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
//class Derived : protected Base {
//class Derived : private Base {
public:
void fun() {
Base::foo(); // 子类的foo将基类的foo隐藏,但可以利用作用域限定符强制调用基类的foo
bar(); // 子类的bar将基类的bar隐藏
}
private:
int m_d;
void foo() { cout << "Derived::foo" << endl; }
void bar() { cout << "Derived::bar" << endl; }
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Derived d;
d.fun();
return 0;
}
3 三种继承方式的差别(不是重点)
基类中的公有、保护、私有成员,在子类中将对这些基类成员的访问控制限定进行重新标记:
工作中主要使用public继承,其他极少使用。
在类的外部,通过 子类对象 访问 基类的成员时,需要考虑因继承方式对访问控制限定的影响:
// public.cpp 公有继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记
int m_b;
void bar() { cout << "Base::bar" << endl; }
private: // 原始标记
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
// 子类将对基类的成员重新标记访控限定 m_a/foo是public m_b/bar是protected
// m_c/hum是private
public:
void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记
m_a = 100; // ok
foo(); // ok
m_b = 200; // ok
bar(); // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
// m_c = 300; // error
// hum(); // error,以上两行代码证明子类内部不可以直接访问基类的私有成员
}
private:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记
d.m_a = 1000; // ok
d.foo(); // ok
// d.m_b = 2000; // error
// d.bar(); // error
// d.m_c = 3000; // error
// d.hum(); // error
return 0;
}
// protected.cpp 保护继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记
int m_b;
void bar() { cout << "Base::bar" << endl; }
private: // 原始标记
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : protected Base {
// 子类将对基类的成员重新标记访控限定 m_a/foo是protected m_b/bar是protected
//m_c/hum是private
public:
void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记
m_a = 100; // ok
foo(); // ok
m_b = 200; // ok
bar(); // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
// m_c = 300; // error
// hum(); // error,以上两行代码证明子类内部不可以直接访问基类的私有成员
}
private:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记
// d.m_a = 1000; // error
// d.foo(); // error
// d.m_b = 2000; // error
// d.bar(); // error
// d.m_c = 3000; // error
// d.hum(); // error
return 0;
}
// private.cpp 私有继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记
int m_b;
void bar() { cout << "Base::bar" << endl; }
private: // 原始标记
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : private Base {
// 子类将对基类的成员重新标记访控限定 m_a/foo是private m_b/bar是private
//m_c/hum是private
public:
void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记
m_a = 100; // ok
foo(); // ok
m_b = 200; // ok
bar(); // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
// m_c = 300; // error
// hum(); // error,以上两行代码证明子类内部不可以直接访问基类的私有成员
}
private:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记
// d.m_a = 1000; // error
// d.foo(); // error
// d.m_b = 2000; // error
// d.bar(); // error
// d.m_c = 3000; // error
// d.hum(); // error
return 0;
}
4 公有继承独有的特点
4.1 子类对象在类外可以访问基类公有成员
如果被子类同名标识符隐藏,可借助作用域限定符::指定访问基类的公有成员。
// phs.cpp 公有继承独有特点:
// (1)只有在公有继承下,子类对象在类外可以访问基类的公有成员(其他继承不可以)
#include <iostream>
using namespace std;
class Base {
public:
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
public:
void foo() { cout << "Derived::foo" << endl; }
private:
int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Derived d;
d.m_a = 1000; // ok
d.foo(); // ok,只有在公有继承下,子类对象在类外可以访问基类的公有成员
d.Base::foo();// ok
return 0;
}
4.2 子类类型的指针(引用) 和 基类类型的指针(引用)可以进行转换
子类类型的指针 能 隐式转换为基类类型的指针
子类类型的引用 能 隐式转换为基类类型的引用
(编译器认为访问范围缩小是安全的):
class Human { ... };
class Student : public Human { ... };
Student s;
Human* ph = &s; // 指针 访问范围缩小
Human& rh = s; // 引用 访问范围缩小
基类类型的指针 不能 隐式转换为子类类型的指针
基类类型的引用 不能 隐式转换为子类类型的引用
(编译器认为访问范围扩大是危险的):
class Human { ... };
class Student : public Human { ... };
Human h;
Student* ps = static_cast<Student*>(&h); // 指针 访问范围扩大
Student& rs = static_cast<Student&>(h); // 引用 访问范围扩大
编译器对类型安全的检测仅仅基于指针/引用本身
基类指针/引用的实际目标,究竟是不是子类对象,完全由程序员自己判断:
class Human { ... };
class Student : public Human { ... };
Student s;
Human* ph = &s;
Human& rh = s;
Student ps = static_cast<Student*> (ph); // 访问范围扩大,但安全合理
Student rs = static_cast<Student&> (rh); // 访问范围扩大,但安全合理
#include <iostream>
using namespace std;
#pragma pack(1)
class Human {
public:
int m_age;
string m_name;
};
class Student : public Human {
public:
int m_no;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Human h; // |m_age m_name|
cout << "基类对象h的大小:" << sizeof(h) << endl; // 36
Student s; // |基类子对象|m_no| --> |m_age m_name|m_no|
cout << "子类对象s的大小:" << sizeof(s) << endl; // 40
Human* ph = &s; // Student*-->Human* (子类型指针-->基类型指针)
Human& rh = s;
// 以上两种转换编译器认为访问范围缩小,是安全的
// Student* ps = static_cast<Student*>(&h); // Human*-->Student*
// (基类型指针-->子类型指针)
// Student& rs = static_cast<Student&>(h);
// 以上两种转换编译器认为访问范围扩大,是危险的,通过强转虽然可以成功,但风险依然存在
// (极其不建议大家这么做)
Student* ps = static_cast<Student*>(ph);//Human*-->Student*(基类型指针-->子类型指针)
Student& rs = static_cast<Student&>(rh);//Human&-->Student&(基类型引用-->子类型应用)
// 以人类高智商判断以上两个转换毫无风险(极其建议大家这么做)
// 编译器只是简单粗暴的根据类型来判断是否存在风险
return 0;
}
5 子类的构造和析构
5.1 子类的构造函数
1)子类没有定义构造函数时:
编译器会为子类提供默认的无参构造函数
定义完基类子对象后,调用基类的无参构造函数
2)子类定义构造函数,但未在初始化表中指明基类部分的构造方式时:
定义完基类子对象后,调用基类的无参构造函数
3)子类定义构造函数,并且在初始化表中指明基类部分的构造方式时:
定义完基类子对象后,调用指明的基类的构造函数
子类对象的构造过程:
1)构造基类子对象 2)构造子类的成员变量 3)执行自己在子类构造函数中书写的代码
阻断继承:
子类的构造函数 无论如何 都会调用基类的构造函数。
若把基类的构造函数定为私有,则该类的子类就永远不能被实例化为对象。
在C++中可以用这种方法阻断一个类被扩展。
5.2 子类的析构函数
1)子类没有定义析构函数时:
编译器将提供一个默认的析构函数,析构完子类所有的成员变量后,
会自动调用其基类的析构函数
2)子类定义了析构函数时:
子类的析构函数在执行完自身析构代码,并析构完所有的成员变量后,
会自动调用其基类的析构函数
子类对象的析构过程:
1)执行自己在子类析构函数中书写的代码 2)析构子类的成员变量 3)析构基类子对象
5.3 子类的拷贝构造函数
1)子类并没有定义拷贝构造函数时:
编译器会为子类提供默认的拷贝构造函数,
定义完基类子对象后,调用基类的拷贝数构造函 。
2)子类定义了拷贝构造函数,但没有在初始化表中指明其基类部分的构造方式时:
定义完基类子对象后,调用基类的无参构造函数。
3)子类定义了拷贝构造函数,且初始化表中指明了其基类部分以拷贝方式构造时:
定义完基类子对象后,调用基类的拷贝构造函数。
5.4 子类的拷贝赋值函数
1)子类没有定义拷贝赋值函数时:
编译器为子类提供的缺省拷贝赋值函数内部,会自动调用基类的拷贝赋值函数,
复制该子类对象中的基类子对象。
2)子类定义了拷贝赋值函数,但没有 显示调用 基类的拷贝赋值函数时:
编译器不会塞任何操作,子类对象中的基类子对象将得不到复制。
3)子类定义了拷贝赋值函数,同时 显示调用 了基类的拷贝赋值函数时:
子类对象中的基类子对象将得到复制。
// ccons.cpp 子类的构造函数 和 析构函数
#include <iostream>
using namespace std;
class Human {
public:
Human( int age=0, const char* name="无名" ) : m_age(age),m_name(name) {
//【int m_age=age;】定义m_age,初值为age
//【string m_name(name);】定义m_name,利用m_name.string(name)
cout << "Human类缺省构造函数被调用" << endl;
}
Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) {
//【int m_age=that.m_age;】定义m_age,初值为that.m_age
//【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)
cout << "Human类拷贝构造函数被调用" << endl;
}
Human& operator=( const Human& that ) {
// 编译器不会再拷贝赋值函数中塞任何操作
cout << "Human类的拷贝赋值函数被调用" << endl;
this->m_age = that.m_age;
this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类的拷贝赋值函数
return *this;
}
~Human() {
cout << "Human类的析构函数被调用" << endl;
// 对于基本类型成员变量m_age,什么都不做
// 对于类类型成员变量m_name,利用 m_name.~string()
// 释放 m_age/m_name 本身所占内存空间
}
void getinfo( ) {
cout << "姓名: " << m_name << ", 年龄: " << m_age;
}
private:
int m_age; // 基本类型的成员变量
string m_name; // 类类型的成员变量
};
class Student : public Human {
public:
void getinfo( ) {
Human::getinfo();
cout << ", 成绩:" << m_score << ", 评语:" << m_remark << endl;
}
// 如果子类没有提供任何构造函数,编译器将提供一个无参的构造函数
/* Student() {
【Human();】定义基类子对象,利用 基类子对象.Human()
【float m_score;】
【string m_remark;】
}*/
Student( int age=0, const char* name="无名", float score=0.0, const char* remark="没有")
: Human(age,name), m_score(score),m_remark(remark) {
//【Human(age,name);】定义基类子对象,利用 基类子对象.Human(age,name)
//【float m_score=score;】
//【string m_remark(remark);】
cout << "Student类的缺省构造函数被调用" << endl;
}
// 如果子类没有提供析构函数,编译器将提供一个默认的析构函数
/* ~Student() {
对于类类型m_remark,利用m_remark.~string()
对于基类子对象,利用基类子对象.~Human()
释放 m_score/m_remark/基类子对象 本身所占内存空间
}*/
~Student() {
cout << "Student类的析构函数被调用" << endl;
// 对于类类型m_remark,利用m_remark.~string()
// 对于基类子对象,利用基类子对象.~Human()
// 释放 m_score/m_remark/基类子对象 本身所占内存空间
}
// 如果子类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/* Student( const Student& that ) {
【Human(that);】定义基类子对象,利用 基类子对象.Human(that)-->Human类的拷贝构造函数
【float m_score=that.m_score;】
【string m_remark=that.m_remark;】
}*/
Student( const Student& that ) : Human(that), m_score(that.m_score), m_remark(that.m_remark) {
//【Human(that);】定义基类子对象,利用 基类子对象.Human(that)-->Human类的拷贝构造函数
//【float m_score=that.m_score;】
//【string m_remark=that.m_remark;】
cout << "Student类的拷贝构造函数被调用" << endl;
}
// 如果子类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
/* Student& operator=( const Student& that ) {
Human& rh = *this;
rh = that; // rh.operator=(that)-->Human类的拷贝赋值函数
this->m_score = that.m_score;
this->m_remark = that.m_remark;
return *this;
}*/
Student& operator=( const Student& that ) {
// 编译器不会再拷贝赋值函数中塞任何操作
cout << "Student类的拷贝赋值函数被调用" << endl;
Human& rh = *this;
rh = that; // rh.operator=(that)-->Human类的拷贝赋值函数
this->m_score = that.m_score;
this->m_remark = that.m_remark;
return *this;
}
private:
float m_score;
string m_remark;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
cout << "-----------------s1对象的创建信息-----------------------" << endl;
Student s1(22,"张飞",88.5,"良好"); // 定义s1,利用s1.Student(22,"张飞",88.5,"良好")
s1.getinfo();
cout << "-----------------s2对象的创建信息-----------------------" << endl;
Student s2 = s1; //(s1); 定义s2,利用s2.Student(s1)
s2.getinfo();
cout << "-----------------s3对象的创建信息-----------------------" << endl;
Student s3;
cout << "s3被赋值前--";
s3.getinfo();
s3 = s2; // s3.operator=(s2)
cout << "s3被赋值后--";
s3.getinfo();
cout << "-----------------main will be over----------------------" << endl;
return 0;
} // s1.~Student() 释放s1本身所占内存空间