公有成员:
对其对象是可见的
继承时,对派生类是可见的
保护成员:
对其对象不可见,等同于私有成员
继承时,对派生类是可见的
私有成员:
对其对象不可见。
继承时,对派生类是不可见的。(派生类新增加的成员函数不能访问基类的私有成员,但继承基类的成员函数可以访问)
保护成员,在不继承时等同于私有成员。当在继承时,等同于公有成员。
对于对象,只有公有成员可见。
当创建一个派生类对象时,系统会自动创建一个基类对象。在调用派生类构造函数创建派生类对象之前,系统会先调用基类的构造函数创建基类对象。当派生类对象生命周期结束,首先调用派生类的析构函数,然后调用基类的析构函数。
如果基类拥有构造函数但是没有默认的构造函数,那么派生类的构造函数必须显示的调用基类的某个构造函数。
在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的情况发生,基类的析构函数必须为虚函数。
基类的指针指向派生类的对象,指向的是派生类中基类的部分。所以只能操作派生类中从基类中继承过来的数据和基类自身的数据。
- :独占资源所有权的指针。
- :共享资源所有权的指针。
简单说,当我们独占资源的所有权的时候,可以使用 std::unique_ptr 对资源进行管理——离开 unique_ptr 对象的作用域时,会自动释放资源。
shared_ptr通过引用计数,来控制共享资源的释放。
shared_ptr 需要维护的信息有两部分:
- 指向共享资源的指针。
- 引用计数等共享资源的控制信息——实现上是维护一个指向控制信息的指针。
所以,shared_ptr 对象需要保存两个指针。shared_ptr 的 的 deleter 是保存在控制信息中,所以,是否有自定义 deleter 不影响 shared_ptr 对象的大小。
派生类的地址或者指针赋值给基类指针时,可以隐式转换,也可以使用static_cast显示转换,不过一般都用隐式转换。
倒过来,基类指针赋值给派生类指针的时候,必须要显示转换。这个转换,使用dynamic_cast或者static_cast都不会报错。
static_cast是在编译期进行转换。而dynamic_cast可以在运行的时候,对地址的实际类型进行检测,当不能转换的时候,将会返回一个nullptr值。
基类和派生类的智能指针转换要使用std::dynamic_pointer_cast和std::static_pointer_cast。由于std::dynamic_pointer_cast和dynamic_cast原理一样,std::static_pointer_cast和static_cast原理一样。
在类中,static成员数据,成员函数,不依赖于对象。可以认为他们是全局存在的。因此static数据不应在对象的构造函数初始化。且static成员函数不能访问类的非静态数据,因为static成员函数执行时,非静态数据可能还不存在。
在类定义体中由关键字friend加以修饰说明的非成员函数,在它的函数体中能够通过该类的对象来访问类中的private/protected成员。在友员函数体内可以利用对象访问类中的private成员,但非成员函数则不能。
class Window
{public: Window(int x, int y, int h, int w)
{ X=x , Y=y , H=h , W=w ;
}
friend long Area(Window & WinObj) ; //在类中定义出友员函数的原型
int getH()
{ return H ;
}
int getW()
{ return W ;
}
private: int X,Y,H,W ;
};
long Area(Window & WinObj) //在类外定义出友员函数的函数体
{ return (long)WinObj.H*WinObj.W ;
}
友员类:一个类为另一个类的友员类---此类的所有成员函数都能访问对方类的私有成员。应用目的:在类的封装和共享两方面合理选择---类的主要优点是实现数据隐藏与保护,从而将数据与方法相互组合产生独立的代码块,但这样将使各个类相互孤立,无法实现类之间的共享。但在应用需求中可能要实现多个类之间的共享,这可以采用友元类的方式来实现。
C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
//template 关键字告诉C++编译器 要开始泛型编程了 //T - 参数化数据类型
template <typename T>
T Max(T a, T b)
{ return a > b ? a : b; }
类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下面语句声明了一个类模板:
template <typename T>
class A
{
public:
A(T t){
this->t = t;
}
T& getT(){
return t;
}
public:
T t;
};
运算符重载是什么?使对象操作更美观的技术。运算符重载是一种形式的c++多态。要重载运算符,需使用被称为运算符函数的特殊函数形式。
运算符函数的格式如下:
operatop( argument-list)
例如,operator+() 重载+运算符
operator*()重载*运算符
op必须是有效的c++运算符,不能虚构一个新的符号
以下运算符不能重载:
sizeof
. 成员运算符
.* 成员指针运算符
:: 作用域解析运算符
?: 条件运算符
typeid 一个RTTI运算符
const_cast 强制类型转换运算符
dynamic_cast 强制类型转换运算符
reinterpret_cast 强制类型转换运算符
static_cast 强制类型转换运算符
下列运算符只能通过成员函数进行重载
=:赋值运算符
():函数调用运算符
[]:下标运算符
->:通过指针访问类成员的运算符
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
虚函数表
对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
构造函数不能为虚函数,因为构造函数执行的时候,虚函数表还没有建好。