继承关系(public继承、protected继承、private继承)
基类的私有成员在派生类中不能被访问,如果一些基类成员不想被基类对象直接访问,但需要在派生类中访问,就定义为保护成员。保护成员限定符因继承而生
public继承保持is-a原则,每个父类的成员子类也可以使用
protected/private继承保持has-a原则,基类部分成员并未完全成为子类接口的一部分。(几乎很少使用)
不管是哪种继承关系,在子类中都可以访问父类的共有成员和保护成员,但父类的私有成员存在但在子类中不可见(不能访问)
使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public(最好显式写出)
实际应用中一般使用public继承
继承与转换——赋值兼容规则——public继承
子类对象可以赋值给父类对象(切片/切割)
父类对象不能赋值给子类对象
父类的指针/引用可以指向子类对象
子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
class Person
{
protected:
string _name;
};
class Student : public Person
{
public:
int _stunum;
};
int main()
{
Person p;
Student s;
//1. 子类对象可以赋值给父类对象(切片 / 切割)
p = s;
//2. 父类对象不能赋值给子类对象
//s = p;
//3. 父类的指针 / 引用可以指向子类对象
Person* p1 = &s;
Person& r1 = s;
//4. 子类的指针 / 引用不能指向父类对象(可以通过强制类型转换完成)
Student* p2 = (Student*)&p;
Student& r2 = (Student&)p;
p2->_stunum = 10; //程序崩溃
r2._stunum = 20;
return 0;
}
继承体系的作用域
- 继承体系中基类和派生类都有独立作用域
- 子类和父类中有同名成员时,构成隐藏,子类成员会隐藏父类成员的直接访问。(在子类成员中,可以通过 基类::基类成员 来访问)
- 在实际继承体系中最好不要定义同名成员
class Person
{
public:
string _name;
int _stunum;
};
class Student : public Person
{
public:
int _stunum;
};
int main()
{
Person p;
p._stunum = 10;
Student s;
//隐藏 重定义
s._stunum = 20;
s.Person::_stunum = 200;
return 0;
}
知识拓展题:
第一题:
class Person
{
public:
void f1()
{
cout << "Person::f1()" << endl;
}
string _name;
int _stunum;
};
class Student : public Person
{
public:
void f1(int i)
{
cout << "Student::f1()" << endl;
}
int _stunum;
};
int main()
{
Student s;
s.f1();
return 0;
}
- A.两个f1构成重载
- B.两个f1构成隐藏
- C.代码编不过
- D.以上选项都不对
此题正确答案应该是BC两个选项,Student类继承Person类,在Student类中出现两个同名的成员f1,两者构成隐藏,子类隐藏父类的同名成员,而重载是在同一作用域下的。所以此处的s.f1()
调用的应该是子类的f1(),但此处未给f1传参,所以代码编译不通过。
第二题:
class Student : public Person
{
public:
void f()
{
cout << "Student::f()" << endl;
}
void f1()
{
cout << "Student::f1()" << endl;
}
void f2()
{
cout << _stunum << endl;
cout << "Student::f2()" << endl;
}
void f3()
{
_stunum = 3;
cout << "Student::f3()" << endl;
}
void f4()
{
f();
cout << "Student::f4()" << endl;
}
int _stunum;
};
int main()
{
Student *p = NULL;
p->f1();
p->f2();
p->f4();
return 0;
}
- A.代码编译不过
- B.可以编译通过,程序会崩溃
- C.可以编译通过,正常输出
Student::f()
- D.以上选项都不对
1.p->f1()
选C,代码正常输出
隐含的this指针虽然是空,但并未解引用,所以代码正常输出。
2.p->f2()
选B,编译通过,程序会崩溃
this指针指向空,this->_stunum
失败
3.p->f3()
选B,编译通过,程序会崩溃
this指针指向空,this->_stunum
失败
4.p->f4()
选C,代码正常输出
不会通过this指针去寻找f()这个函数,因为这个函数并不在对象上而是存在代码段上,同时隐含的this指针虽然是空,但并未解引用,所以代码正常输出。
派生类默认成员函数
在继承关系中,若子类没有显式定义如下6个成员函数,编译系统会默认合成这六个默认成员函数:
- 构造函数
- 拷贝构造函数
- 析构函数
- 赋值运算符重载
- 取地址操作符重载
- const修饰的取地址运算符重载。
class Person
{
public:
Person(const char* name)
:_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()
:Person("aaaaa") //在派生类中如果要初始化基类成员,应该调父类的构造函数(若父类构造函数是缺省的,此处也可以省略)
, _stunum(0)
{
cout << "Student()" << endl;
}
Student(const Student& s)
:Person(s)
, _stunum(s._stunum)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
cout << "Student& opertator = (const Student& s)" << endl;
if (this != &s)
{
Person::operator=(s); //此处的赋值运算符重载与父类的构成隐藏,若不指定作用域,则表示递归导致栈溢出
_stunum = s._stunum;
}
return *this;
}
~Student()
{
//Person::~Person(); //~Person()和~Student()构成隐藏,出了作用域会自动调父类析构,所以此处不用显式写出,为了满足先构造的后析构
cout << "~Student()" << endl;
}
protected:
int _stunum;
};
int main()
{
Student s;
Student s1(s);
Student s3;
s1 = s3;
return 0;
}
面试题:
实现一个不能被继承的类
class AA
{
private:
AA()
{}
public:
static AA* GetAAObj()
{
return new AA;
}
static AA GetAAOBj()
{
return AA();
}
protected:
int _aa;
};
class BB : public AA
{
};
所用知识:私有的成员变量在子类中是不可见的,子类的构造函数是合成的。
实现一个只能在栈上生成对象的类
//方法一
class AA
{
private:
AA(int aa = 0)
:_aa(aa)
{}
public:
static AA GetAAOBj()
{
return AA();
}
protected:
int _aa;
};
int main()
{
AA aa = AA::GetAAOBj();
return 0;
}
//方法二
class AA
{
private:
void* operator new(size_t size);
void operator delete(void* p);
protected:
int _aa;
};
AA aa; //此方法的缺陷在于防止不了在静态区定义对象
int main()
{
//AA* p = new AA; //失败(operator new+构造)
AA aa;
return 0;
}
实现一个只能在堆上生成对象的类
class AA
{
private:
AA(int aa = 0)
:_aa(aa)
{}
//类的防拷贝
//1.只声明不定义
//2.声明成私有
AA(const AA& aa);
public:
static AA* GetAAObj()
{
return new AA;
}
protected:
int _aa;
};
int main()
{
AA* p = AA::GetAAObj();
//AA aa(*p); //栈上定义的对象,若想要其定义失败,应使用类的防拷贝
return 0;
}
构造若不用缺省的:
static AA GetAAObj(int aa)
{
return AA(aa);
}
单继承&多继承
- 单继承——一个子类只有一个直接父类时称为单继承
- 多继承——一个子类有两个或以上直接父类时称为多继承
虚继承
//1.什么是菱形继承
//2.菱形继承有什么问题?(性能损耗)
//3.如何解决数据冗余和二义性(指定作用域、虚继承)
//4.虚继承是如何解决数据冗余和二义性?——内存对象模型
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B,public C
{
public:
int _d;
};
int main()
{
cout << sizeof(D) << endl; //非虚继承20,虚继承24
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
虚基表指针表现在B b = d;
时能够找到d中的_a。当公共部分比较大时(1000个字节),可以节省空间(多用8个字节而节省1000个字节)。