new实际上执行了三步操作:
delete实际执行了两步操作:
标准库定义了8个重载版本,4个可能抛出异常,4个不抛出,可以在全局作用域或类作用域中自定义函数版本中的任意一个。当定义为类成员时,它们是隐式静态的。
如果自定义operator new函数,可以提供额外的形参,但是时候时候必须使用new的定位形式
对于operator delete返回类型必须是void,第一个形参必须是void*
在头文件cstdlib头文件中。
malloc接收一个表示待分配字节数的size_t,返回指向分配空间的指针或者返回0以表示分配失败。 free函数接受一个void*,他是malloc返回的指针的副本,free将内存返回给系统。
分离开内存分配与初始化,可以调用operator new 和 operator delete,它们负责分配和释放内存空间,不会构造和销毁对象。
new (place_address) type
使用定位new形式构造对象,可以使用定位new传递一个地址。
调用析构函数会销毁对象,但是不会释放内存空间
RTTI 运行时类型识别由两个运算符实现:
//type是类类型
dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
如果转换目标是指针类型并且失败了,返回0
在条件部分执行dynamic_cast操作可以确保类型转换和结果检查在同一条表达式中完成。
如果转换目标是引用并且失败了,程序抛出bad_cast异常,该异常定义在typeinfo标准库头文件中。
typeid(e)
,e可以是任意表达式或类型的名字,操作结果是一个常量对象的引用,类型是type_info或者其公有派生类型,type_info在typeinfo头文件中。
如果e是一个引用,则返回该引用所引用对象的类型,如果作用域数组或函数,不会执行类型转换。
当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型,而当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得。
比较两表达式类型是否一致
当作用于指针时(不是指向的对象),返回的结果是该指针的静态编译类型
首先要明确:如果参与比较的两个对象类型不同,则比较结果是false。
定义一个虚函数比较两个对象是否相等,派生类要定义自己的比较函数,执行对象转换成运算符所属的类类型。
定义在头文件typeinfo中,创建type_info对象的唯一途径是使用typeid运算符,其name成员返回C风格字符串表示对象类型的名字,返回的字符串根据编译器不同而返回不同名字,不一定与在程序中使用的名字一致。
枚举类型将一组整形常量组织在一起,枚举属于字面值常量类型。
两种枚举:限定作用域的 和 不限定作用域的
//定义限定作用域的枚举类型,也可使用struct
enum class open {input, output, append};
//定义不限定作用域枚举类型,省略class或struct,名字可要可不要
enum color {red, blue, green};
enum {f = 3, c = 2};//如果不要名字,则只能在定义enum时定义其对象。
open n = open::input;
可以在enum名字后加上冒号以及想使用的类型enum v : unsigned long long {/* */}
。如果没有指定enum的潜在类型,限定作用域的enum成员类型是int,不限定作用域的成员没有默认类型。
如果指定了潜在类型(包括限定作用域的隐式指定),如果枚举成员超过该值,则引发错误
使用前置声明必须指定其成员的大小enum v : unsigned long long;
限定作用域的enum成员大小可以不指定,隐式被定义为int
不能定义同名的限定作用域enum和不限定作用域的enum
初始化enum对象,必须使用该enum类型的另一个对象或它的一个枚举成员。即使某个整型值恰好和枚举成员相等,也不能作为函数的enum实参
可以将一个不限定作用域的枚举类型的对象或枚举成员传给整形形参,此时enum的值提升为int或更大的整形。
成员指针是指可以指向类的非静态成员的指针。
const string ClassName::* p;
p = &ClassName::data;
//使用auto
auto p = &Classname::data;
我们初始化一个成员指针或为成员指针赋值时,该指针并没有指向任何数据,成员指针制定了成员而非该成员所属的对象,只有当解引用成员指针时我们才提供对象的信息。
使用.* 或是 ->*来解引用指针并获得该对象的成员
Screen myScreen, *pScreen = &myScreen;
auto s = myScreen.*p; //使用.*
s = pScreen->*p; //使用->*
一般成员函数是私有的,通常定义一个函数返回指向该成员的指针。
const string ClassName::* get() { return &ClassName::data; }
可以定义指向类的成员函数的指针,最简单的方法是使用auto来推断类型
auto pmf = &ClassName::get;
char (ClassName::*pmf2)(ClassName::pos, ClassName::pos) const; //pmf2的类型是指向一个这样的成员函数的指针
pmf2 = &ClassName::get; //指向get函数
使用.* 或是 ->*来解引用指针并获得该对象的成员函数
括号必不可少(myScreen.*pmf2)();
或(myScreen->*pmf2)();
使用类型别名方便理解成员指针,成员函数指针可以作函数参数和返回类型
using action = char (ClassName::*)(ClassName::pos, ClassName::pos) const;
如果一个类函数几个校内沟通类型的成员,则将其指向成员函数的指针存入一个函数表,使用的时候从函数表中选择一个。
要想通过一个指向成员函数的指针进行函数调用,必须首先利用.*和 ->*将该指针绑定到特定的对象上。 因此与普通的函数指针不同,成员指针不是一个可调用对象,不支持函数调用运算符。
需要用户显式提供成员的调用形式
vector<string*> p;
function<bool (const string&)> fc = &string::empty;
find_if(p.begin(), p.end(), fc);
// 我们告诉function:empty是一个接收string参数并返回bool值的函数。我们可以认为在find_if中使用调用运算符的部分转换成了使用.*运算符来处理指向成员函数的指针。
定义在functional头文件中。可以从成员指针生成一个可调用对象,编译器负责推断成员的类型,无需用户显示指定。
find_if(p.begin(), p.end(), mem_fn(&string::empty));
auto f = mem_fn(&string::empty);
f(*p.begin()); //通过对象调用,使用.*调用empty
f(&p[0]); //通过指针调用,使用->*调用empty
mem_fn生成的可调用对象可以通过对象调用,也可以通过指针调用
find_if(p.begin(), p.end(), bind(&string::empty, _1));
auto f = bind(&string::empty, _1);
f(*p.begin()); //通过对象调用,使用.*调用empty
f(&p[0]); //通过指针调用,使用->*调用empty
bind生成的可调用对象可以通过对象调用,也可以通过指针调用
一个定义在另一个类内部的类称为嵌套类或嵌套类型,嵌套类常用于定义作为实现部分的类。
在类中声明嵌套类class ClassName
class OutClass::InClass { /**/};
要指明外层类,该嵌套类属于外层类,可以直接使用外层类的成员,不用加限定符。
//定义嵌套类的析构函数
OutClass::InClass::InClass { /**/};
int OutClass::InClass::data = 3;
因为嵌套类是外层类的一个成员,所以外层类的成员可以直接使用嵌套类的名字,名字查找规则同样适用于嵌套类,嵌套类本身是一个嵌套作用域,因此还要查找嵌套类的外层作用域。
嵌套类的对象只包含嵌套类定义的成员;外层类的对象只包含外层类的定义的成员
联合 是一种特殊的类。一个union可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值,分配给一个unino对象的存储空间至少要能容纳它的最大的数据成员。
union T {
char a;
int c;
double d;
};
//使用
T fir = { 'f' };//初始化,默认初始化第一个成员
T sec; //未初始化的T对象
T* p = new T; //指向未初始化的T对象的指针
//使用成员访问运算符访问union对象成员
sec.a = 'z';
p->a = 32;
//为union的一个数据成员赋值会令其它数据成员变成未定义状态
匿名union是一个未命名的union,匿名union不能包含受保护的成员或私有成员,也不能定义成员函数。
匿名union的成员可以直接访问。
当我们将union的值改为类类型成员的对应的值时,必须运行该类型的构造函数;反之,当我们将类类型成员的值改为一个其他值时,必须运行该类型的析构函数。
当union包含的是内置类型成员,则我们使用普通的赋值语句改变union保存的值,同时编译器会按照成员次序依次合成默认构造函数或拷贝控制成员。
union构造或销毁类类型的成员必须执行非常复杂的操作,因此我们通常把含有类类型成员的union内嵌在另一个类中。
为了追踪union中到底存储了什么类型的值,我们通常会定义一个独立的对象,该对象称为union 的判别式。可以创建个枚举类型。
类可以定义在某个函数的内部,称为局部类。局部类定义的类型只在定义它的作用域内可见。局部类所有成员必须完整定义在类的内部。
不可移植的特性是指因机器而异的特性
算数类型的大小在不同机器上不一样,这是典型的不可移植特性的例子。
类可以将其非静态数据成员定义成位域,在一个位域中含有一定数量的二进制位,当程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
位域在内存中的布局时与机器相关的。
位域的类型必须是整形或者枚举类型。
位域的声明形式:在成员名字之后紧跟一个冒号以及一个常量表达式,通常使用无符号类型保存一个位域,常量表达式用于指定成员所占的二进制位数。unsigned int mode : 1
如果可能的话,在类内部连续定义的位域压缩在同一个unsigned int中,这些二进制位能否压缩以及如何压缩与机器相关。
取地址运算符不能作用于位域,因此任何指针都无法指向类的位域。
volatile的确切含义与机器有关,只能通过阅读编译器文档来理解。使用了volatile的程序在移植到新机器或新编译器后仍然有效,通常需要对程序进行某些改变。
直接处理硬件的程序常常包含这样的数据元素,它们的值由程序直接控制之外的过程控制,当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile,关键字volatile告诉编译器不应该对这样 的对象进行优化。
C++程序有时候调用其他语言编写的函数,其他语言中的函数名字也要在C++中进行声明,并且指定返回类型和形参列表。C++使用链接指示指出任意非C++函数所用的语言。
要想C++代码和其他语言放在一起使用,我们必须有权访问该语言的编译器,并且这两个编译器兼容。
链接指示可以有两种形式:单个的 或 复合的
链接指示不能出现在类定义或函数定义的内部
//声明方式:extern后加上字符串字面值常量以及一个函数声明,字面值常量表示编写函数所用的语言。
extern "C" size_t strlen(const char*); //单个的
//复合 的
extern "C" {
int strcmp();
char *strcat(char*);
}
extern "C" {
#include
}
如果花括号内是#include,则头文件中所有的普通函数声明都被认为是由链接指示的语言编写的,链接指示可以嵌套
指针必须与函数本身具有相同的链接指示
extern "C" void (*pf)(int);
//pf指向一个C函数,该函数接收int返回void
链接指示对函数有效,对作为函数的返回类型或形参类型的函数指针也有效
extern "C" void f1(void(*)(int));
//f1是C函数,它的参数是一个函数指针也是C函数
如果希望给C++函数传入一个指向C函数的指针,则必须使用类型别名
extern "C" typedef void FC(int);
void f2(FC *);
//f2是C++函数,参数是指向C函数的指针
可以使用链接指示对函数进行定义,可以令一个C++函数在其它语言编写的程序中可用:
extern "C" double cal(double p) { /*...*/ }
//这个包含了定义,不只是声明哦
//这个函数可以被C程序调用
//但是返回类型和参数类型受到很多限制
有时候需要在C和C++中编译同一个源文件,编译C++文件时候预处理器定义__cplusplus ,利用这个变量,可以在编译c++时候有条件地包含进来一些代码
//使用如下代码,编译C++时候就会加上extern "C"
#ifdef __cplusplus
extern "C"
#endif
int strcmp(const char*, const char*);
如果目标语言支持重载函数,则为该语言实现链接指示的编译器很可能也支持重载这些C++的函数
C语言不支持函数重载,一个C链接指示只能用于说明一组重载函数中的某一个。