C++两种类,一种类是带指针的类,一种是不带指针的类。当写一个不带指针的类的时候,拷贝构造函数、拷贝复制函数和析构函数是可以使用默认的。当一个类带有指针之后,拷贝构造函数、拷贝复制函数和析构函数需要自己去写。
const char* cstr=0;也相当于 char const * cstr=0;
第一点,const在 * 左边,所以是常量指针,即cstr指向的内容是常量,即cstr指向内容无法修改,cstr本身可修改,赋值0是给指针cstr赋值,
在c++中NULL被定义为0或者void*,所以是初始化cstr为空指针。
如果是 char * const cstr=0 ; const在 * 右边,表面这是指针常量,即cstr本身无法修改,其指向内容可以修改。
如果要均无法修改,即指向常量的常指针:const char * const cstr; 或者 char const * const cstr;
1、强制类型转换
//type是目标类型,expression是要转换的值。
static_cast<type>(expression)
int i,j;
double slope=static<double>(j)/i;
static适用于任何具有明确定义的类型转换。只要不包含底层const。
2、allocator类
/*** allocator 类是为了解决new的一些局限,比如new会将内存分配和构造对象结合在一起。当分配单个对象***时,可以使用new。
**** 当分配一大块内存时,通常需要计划在这块内存上按需构造对象。这时候希望内存分配和对象构造分离。意***味着可以分配大块内存,只在真正需要时执行对象创建工作。
**** allocator 是一个模板,定义一个allocator对象必须指明alloctor可以分配分对象类型。当他分配内存***时会根据对象类型来确定恰当的内存和对齐位置。
****/
allocator<T> a //定义一个名为a的allocator对象,为类型T对象分配内存
a.allocator(n) //分配一段原始的、未构造的内存。保存为n个类型为T的对象。
a.deallocate(p,n) //释放从T*指针p中地址开始的内存。
a.construct(p,args) //p必须是一个类型为T*的指针,指向一块原始内存,args被传递给类型为T的构造函
//数,用来在p指向的内存中构造一个对象
a.destroy(p) //p为T*类型的指针,对p指向的对象执行析构函数。
3、面对对象程序设计的核心思想:数据抽象(可以将类的接口与实现分离)、继承(可以定义相似的类型并对其相似关系建 模)、动态绑定(可以在一定程度上忽略相似类型的区别)。
4、当我们使用基类的引用或指针调用一个虚函数时将发生动态绑定。
5、基类通常都应该定义一个虚析构函数,即使函数不执行任何操作。
6、如果基类定义一个静态成员,则整个继承体系中只存在该成员的唯一定义。
class Base{
public:
static void statment();
}
class Derived : public Base{
void f(const Derived&);
}
void Derived::f(const Derived &derived_obj)
{
Base::statment(); //Base定义了stayment
Derived::statment(); //Deived继承了statment
derived_obj.statment(); //通过derived对象访问
stament(); //通过this访问
}
7、虚函数
对虚函数的调用可能在运行时才被解析。
动态绑定只有在我们通过指针或者引用调用虚函数时才发生。
一旦某个函数被声明为虚函数,则所有派生类中他都是虚函数。
一个派生类的函数如果覆盖了某个继承而来的虚函数,则他的形参类型必须与他覆盖的基类函数完全一致。返回类型也必须一致。例外的是:当类的虚函数返回类型是类本身的指针或引用时,上述规则无效。
可以使用 override来标记函数, 表示该函数为虚函数需要重写。可以使用final表示函数不能再被覆盖。
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1:B{
void f1(int) const override; //正确,与基类匹配
void f2(int) override; //错误。基类中没有匹配的函数,但是如果不加override编译器不会报错
void f3() override; //错误,f3不是虚函数
void f4() override; //错误,基类中没有f4这个函数。
};
struct D2:B{
void f1(int) const final; //不允许后续的其他类覆盖f1
};
struct D3:D2{
void f2(); //正确,覆盖间接从基类继承的f2
void f1(int) const; //错误,D2已经声明为final
};
某些情况下,我们希望对虚函数的调用不要进行动态绑定,也就是回避虚函数机制。可以使用作用域运算符。
含有纯虚函数的类称为抽象基类。抽象基类不能创建对象。
可以定义抽象基类的派生类的对象。
8、访问控制与继承
prtected 关键字声明那些他希望和派生类分享但是不想被其他公共访问使用的成员。
可以看作是public 和 private 中和的产物:
***和私有成员类似,受保护的成员对于类的用户是不可访问的。
***和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的。
***派生类的成员或友元只能通过派生类对象来访问基类的受保护成员,派生类对于一个基类对象中受保护的成员没有任何访问权限。派生类不能访问基类对象中的受保护成员。
派生访问说明符对派生类中的成员(友元)能否访问其直接基类的成员没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。(不论派生类访问说明符是什么,派生类成员和友元都是根据基类中访问说明符有关。)
派生类访问说明符目的是控制派生类用户(包括派生类的派生类在内)对基类成员的访问权限。(如果是共有继承,那么访问权限是按照基类的访问说明符,如果是私有继承,那么基类中的公有、保护都会变成私有。如果是保护继承,那么基类中共有变成保护类型、保护会变成私有类型。)
9、友元关系不能继承,每个类负责控制各自成员的访问权限。
通过在类的内部使用 using 声明改变派生类继承的某个名字的访问权限,using 语句中名字的访问权限由该 using 出现之前的访问说明符决定。
class Base{
public:
std::size_t szie(){return n};
protected:
std::size_t n;
};
class Derived:private Base{
public:
using Base::size();
private:
using Base::n;
};
使用class关键字定义的派生类是私有继承,使用struct关键字定义的派生类是公有继承。使用struct和class定义的类的区别只有默认成员访问说明和默认派生类访问说明符,除此之外没有任何不同。
一个私有派生类最好显式的将private声明出来。
10、派生类的成员将隐藏同名的基类成员。
可以使用域运算符来使用一个被隐藏的基类成员。域运算符将覆盖原有的查找规则。
11、 名字查找优于类型检查。
struct Base{
int memfcn();
};
struct Derived::Base{
int menfcn(int );
};
Derived d;
Base b;
b.memfcn(); //调用Base::memfcn
d.memfcn(10); //调用Derived::menfcn
d.memfcn(); //错误。参数列表为空的memfcn被隐藏
d.Base::memfcn(); //正确。
12、虚析构函数
如果需要delete一个动态分配的对象指针时,如果改指针是指向一个继承体系中的某个类型,则可能出现静态类型与被删除的动态类型不符的情况。因此必须将基类中的析构函数定义为虚函数以确保执行正确的析构函数版本。
如果基类的析构函数不是虚函数,那么delete一个指向派生类对象的基类指针将产生未定义的行为。
虚析构函数会组织合成移动操作。
13、合成拷贝与继承
如果基类中的默认构造函数、拷贝构造函数或是析构函数被删除或是不可访问,则其派生类中的相应成员是被删除的
大多数基类都会有一个虚析构函数,因此基类通常不会含有合成的移动操作,派生类中也如此,如果我们确实需要移动的操作,我们需要自行首先在基类中进行定义。
14、派生类的拷贝控制成员
默认情况下,基类的默认构造函数初始化派生类对象的基类部分。
派生类的构造函数不仅要初始化自己的成员,还负责初始化派生类对象的基类部分,而派生类对象的基类部分是自动销毁的,派生类的析构函数只负责销毁自身派生类的成员,但是拷贝和移动操作,都会包含基类的部分。
定义派生类的拷贝和移动构造函数,需要在其初始值列表中显式的调用其基类的拷贝或移动构造函数,否则的话,派生类对象的基类部分会被默认初始化(派生类中定义拷贝和移动赋值运算符也是一样,需要在函数体中调用基类的相应成员)。
对象的销毁顺序正好和其创建的顺序相反,派生类的部分先被销毁,基类部分后被销毁。
如果构造函数或者析构函数调用了某个虚函数,那么我们应该执行与构造函数和析构函数所属类类型相对应的虚函数版本。
15、 C++11 新标准,派生类可以直接重用基类的构造函数,一个类只能” 继承 “它的直接基类的构造函数,并且不能继承默认、拷贝、移动构造函数,若派生类中没有这些构造函数,编译器会自动产生合成版本
在派生类中使用 using 声明语句。
//使用using使派生类继承基类的构造函数
class B{
public:
const B(int i);
};
class C:public B{
using B:B;
};
16、explicit 关键字
explicit只对一个实参的构造函数有效。阻止隐式创建构造函数。
17、容器与继承
当派生类对象被赋值给基类对象时,其中的派生类部分将被切掉。因此容器与存在继承关系的类型无法兼容。
当希望在容器中存放具有继承关系的对象时,我们实际上存放的是基类的指针,更好选择智能指针。
派生类的智能指针可以转换成基类的智能指针。
18、纯虚函数
含有纯虚函数的类是抽象基类。抽象基类负责定义接口。后续的类可以覆盖该接口。