特殊工具和技术
控制内存分配
new
的工作机制
string *sp = new string("a Value")
;- 1.
new
表达式调用operator new
或者operator new[]
标准库函数,这个函数分配一块足够大的,原始的,未命名的内存空间用来存储这些特定类型的对象; - 2.编译器运行相应的构造函数用来构造这些对象,并传入初始值;
- 3.对象的到了分配的空间并且构造完成,返回一个指向这个对象的指针;
delete
的工作机制- 1.首先对指针指向的对象执行对应的析构函数;
- 2.编译器调用
operator delete
或者operator delete[]
的标准库函数释放内存空间; - 如果存在自定义的
new
和delete
版本,那么编译器将使用自定义版本来替代标准库定义的版本;这个函数可以定义在全局作用域或者定义为成员函数; - 这种形式的函数重载只能提供给标准库使用
void *operator new(size_t,void*)
,不允许被重新定义; malloc
和free
函数
- 1.
malloc
函数接受一个表示待分配字节数的size_t
,返回指向分配空间的指针或者返回0
表示内存分配失败; - 2.
free
函数接受一个void*
参数必须是malloc
返回的指针的副本,free
将相关内存返回给系统. - 3.调用
free(0)
没有任何任何意义;
- 1.
new
表达式- 1.
operator new
函数和operator delete
函数是一般的库函数,因此编写的代码是可以直接调用的,这两个函数用于负责分配和释放内存空间,但是不会析构或者销毁对象; - 2.使用上述函数的到的空间是不能够直接构造对象的.应该使用
定位new
形式来构造对象;
new (place_address) type; new (place_address) type (initializers); new (place_address) type [size]; new (place_address) type [size] { braced initializer list };
- 其中
place_address
必须是一个指针,同时在initializers
中提供一个使用逗号分隔的初始值列表,该初始值将用于构造分配的对象; - 当传入一个指针类型的实参时,定位
new
表达式构造对象但是不分配内存;
- 1.
- 显示的析构函数调用
- 1.析构函数的显示调用可以通过对象,对象的指针,引用调用,这和成员函数是没有区别的;
- 2.调用析构函数会销毁对象,但是不会释放内存.
- 运行时类型识别是由
typedef
运算符,用于返回表达式的类型,以及dynamic_cast
运算符,用于将基类的指针或引用安全的转换为派生类的指针或引用; - 当我们将这两个运算符用于某种类型的指针或者引用,并且该类型含有虚函数时,运算符将使用指针或者引用所绑定的动态类型;
- 这两个运算符适用于一下场景:
- 1.使用基类对象的指针或者引用某个派生类操作并且该操作不是虚函数时,当我们使用虚函数时,编译器蒋根据对象的动态类型自动的选择正确的函数版本;
dynamic_cast
运算符:dynamic_cast<type*>(e); dynamic_cast<type&>(e); dynamic_cast<type&&>(e);
- 上面三种形式:第一种形式
e
,必须是一个有效的指针;第二种形式e
必须是一种左值,第三种形式e
不能够是左值; - 同时还需要符合以下条件之一
- 1.
e
的类型是目标type
的公有派生类; - 2.
e
的类型是目标type
的公有基类; - 3.
e
的类型是目标type
的类型;
- 1.
- 对一个控制真执行
dynamic_cast
,结果是所需类型的空指针; - 在条件部分执行
dynamic_cast
操作可以确保类型转换和结果检查在通一条表达式中完成; typeid
运算符
- 这个运算符用于向
typeid
运算符,允许程序向表达式提问类型?使用的是typeid(e)
,其中e
可以是任意表达式或者类型的名字;typeid
操作的结果是常量对象的引用,引用的类型是type_info
或者type_info
的公有派生类类型; - 需要注意的几点:
- 1.
typeid
运算符可以用于任意类型的表达式,并且顶层const
会被忽略; - 2.如果表达式是一个引用,那么
typeid
返回该引用所引对象的类型. - 3.当
typeid
作用于数组或者函数时,并不会执行指向指针的标准类型转换,也就是说当我们对数组a
指向typeid(a)
,所得到的结果是数组类型而不是指针类型;
- 1.
- 当运算对象不属于类类型或者是一个不包含任何虚函数的类时,
typeid
的运算对象是静态类型; - 当运算对象是定义了至少一个虚函数的类的左值时,
typeid
的结果直到运行时才会求得; - 当
typeid
作用于指针时,返回的结果是改指针的静态编译类型;
- 这个运算符用于向
- 使用
PTTI
- 对于两个对象来说,它们的类型相同,并且对应的数据成员取值相同,那么这两个对象相等,在类的继承体系中,每个派生类负责添加自己的数据成员,因此派生类的相等运算符必须考虑派生类的新成员;
type_info
类必须定义在typeinfo
文件中,并且是没有默认构造函数的,而且它的拷贝和移动构造函数以及赋值运算符都被定义为删除,所以是不能够定义typeinfo
对象的,也不能进行拷贝等操作,只能够通过typeid
来使用;
- 枚举类型:
- 枚举类型可以用于将一组整型常量组织在一起;
C++11
支持限定作用域的枚举和不限定作用域的枚举,限定作用域的枚举使用enum struct{};
,不限定作用域的枚举enum color{};
; - 限定作用域的枚举类型中,枚举成员的名字遵循常规的作用域准则,并且在枚举类型的作用域外是不能够访问的.不在限定作用域的枚举类型中,枚举成员的作用域和枚举类型本身的作用域相同;
- 默认情况下,枚举值从
0
开始,依次加1.但是也可以为枚举成员制定特殊的值; - 枚举成员默认是
const
,因此在初始化枚举成员时,提供的初始值必须是常量表达式,对于每一个枚举成员来说,本身就是常量表达式; - 在枚举具有名字的时候,可以使用枚举对象来初始化或者为枚举对象赋值;
- 一个不限定作用域的枚举类型的对象或枚举成员自动的转换成整型;
- 对于枚举类型来说,可以按照这种格式来制定成员类型
enum intValues: unsigned long long{};
,默认情况下enum
的成员类型是int
; - 枚举可以提前进行声明,也就是使用前置类型,但是必须指定大小;
enum intValues : unsigned long long;
; - 初始化枚举类型的对象时,必须使用相同类型的枚举对象进行初始化;
- 枚举类型可以用于将一组整型常量组织在一起;
- 类成员指针
- 1.成员指针表示的含义是可以指向类的非静态成员的指针.类的静态成员不属于任何对象;
- 2.指向静态成员的指针和普通指针是没有多大的区别的;
- 3.成员指针的类型囊括了包括类的类型以及成员的类型当初始化这样一个指针时,令其指向类的某个成员,但是不指定该成员所属的对象,直到使用成员指针时,才提供成员所属的对象;
- 4.成员指针的工作原理:
- 5.数据成员指针的声明
const string Screen::*pdata;
;表示的含义是一个指向Screen对象的
string成员
;
数据成员指针
- 1.在初始化一个成员指针时,需要指定它所指的成员;
pdata = &Screen::contents
;更常用的方法是:pdata = &Screen::contents;
; - 2.在
C++11
标准中声明成员指针最简单的方法是使用auto
或者decltype
;例如:auto pdata = &Screen::contents
;
Screen myScreen, *pScreen = &myScreen; auto s = myScreen.*pdata; s = pScreen->*pdata;
- 1.在初始化一个成员指针时,需要指定它所指的成员;
成员函数指针
1.指针可以是指向类的成员函数的指针,数据成员的指针,同样还可以是一个成员函数的指针;
auto pmf = &Screen::get_cursor;
如果上述函数的存在重载的问题,就需要显示的声明函数类型;
- 2.成员函数指针不存在成员函数和成员指针之间的自动转换规则;
Screen myScreen,*pScreen = &myScreen; char c1 = (pScreen->*pmf)(); char c2 = (myScreen.*pmf)();
- 3.函数调用的运算符的优先级比较高,所以在声明指向成员函数的指针并使用这样的指针进行函数调用时,必须使用
()
;
可以将成员函数用作可调用对象
- 1.成员指针不是可调用的对象,所以不能够将一个指向成员函数的指针传递给算法;
auto fp = &string::empty; function<bool (const string&)> fcn = &string::empty; find_if(svec.begin(),svec.end(),fcn);
- 解释:执行成员函数的对象首先被隐式的传递给
this
指针形参,当需要使用function
为成员函数的一个可调用对象时,必须首先翻译该代码,使隐式的参
数变为显式;当定义一个function
对象时,必须指定该对象所能表示的函数类型; - 解决上面问题的另一种方法,使用
mem_fn
来生成一个可调用对象;
find_if(svec.begin(),svec.end(),mem_fn(&string::empty));
- 另一种方法使用
bind
函数;
auto it = find_if(svec.begin(),svec.end(),bind(&string::empty,_1));
嵌套类
- 1.一个类可以定义在另一个类的内部,前者称为嵌套类或者嵌套类型,嵌套类用于定义作为实现部分的类;
- 2.外层类的对象和嵌套类的对象是相互独立的;
- 3.嵌套类的名字在外层类作用域中是可见的,但是在外层作用域之外是不可见的;
- 4.嵌套类里面的成员的种类和非嵌套类是一样的;
- 5.嵌套类在其外层类中定义了一个类型成员;
class TextQuery{ public: class QueryResult; };
- 在外层类之外定义一个嵌套类
class TextQuery::QueryResult{ friend std::ostream& print(std::ostream&,const QueryResult&); public: QueryResult(std::string, std::shared_ptr<std::set<line_no>>, std::shared_ptr<std::vector<std::string>>); };
- 在嵌套类在其外层类之外完成定义之前,都是一个不完整类型;
- 在嵌套类里面名字的查找规则同样适用,但是需要在外层作用进行查找;
- 嵌套类和外层类是相互独立的;
union
表示的是一种节省空间的类- 1.联合表示一种特殊的类,一个
union
的类可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值.在进行空间分配时,得到的空间最小能够容纳
最大的数据成员; - 2.联合不能含有引用类型的成员,除此之外,它的成员可以是绝大多数类型.
- 3.
C++11
标准中,含有构造函数或者析构函数的类类型也可以作为union
的成员类型. - 4.
union
类可以指定访问权限控制,例如public
,protected
,private
来进行保护;默认情况下,union
成员都是公有的,这点和struct
的含义是相同的; - 5.
union
可以定义包括构造函数和析构函数在内的成员函数.但是union
不能够继承自其他类,也不能够作为积累使用,所以在union
里面不能够含有虚函数.
union Token { char cval; int ival; double dval; };
- 为
union
的一个成员赋值,会导致其他的数据成员编程未定义的状态,所以在进行赋值时,就必须知道当前存储的到底是什么类型; - 匿名
union
是一个未命名的union
,一旦存在未命名union
的定义,就会自动为该union
创建一个未命名的对象;
union { char cval; int ival; double dval; };
- 匿名
union
不能包含受保护的成员或私有成员,也不能够定义成员函数; - 在
C++11
新标准可以含有定义了构造函数或者拷贝控制成员的类类型成员;
- 1.如果
union
包含的是内置类型成员时,可以使用普通的赋值语句改变union
保存的值;如果是含有特殊类型的union
时,在进行union
的赋值时,就分别需要提供这种类类型的构造或者是析构函数;如果需要修改类类型的成员的值时,也必须运行该类型的析构函数; - 2.如果编译器包含的是内置类型的成员,编译器将按照成员的次序依次合成默认的构造函数或这拷贝控制成员;如果含有类类型成员,并且该类类型自定义了默认构造函数或拷贝控制成员,编译器会将为
union
合成的对应的版本声明为删除的; - 对于
union
的管理来说,通常是需要将其内嵌在另一个类中,同时还需要定义一个独立的union
判别式,用来区分里面存储的值;
- 1.如果
- 局部类
- 1.类可以定义在某个函数的内部,称这样的类为局部类.局部类定义的类型只能在其作用域里面可见,局部类的成员受到严格的限制;
- 2.局部类的所有成员(包括函数在内)都必须完整的定义在类的内部;
- 3.在局部类中不允许声明静态数据成员,因为没有办法进行这样的定义;
- 4.局部类不能够使用函数作用域中的变量,只能够访问外层作用域定义的类型名,静态变量以及枚举成员,如果局部类定义在某个函数内部,那么该函数的普通局部变量不能被该局部类使用;
- 5.常规的访问保护规则对于局部类同样是适用的;
- 6.在局部类里面进行名字查找的顺序和其他类是类似的;
- 7.可以在局部类的内部在嵌套一个类.嵌套类的定义可以出现在局部类之外.嵌套类必须定义在与局部类相同的作用域中;
- 1.联合表示一种特殊的类,一个
固有的不可移植的特性
- 1.位域:类可以将其非静态数据成员定义成位域,当一个程序需要向程序或者是二进制设备传递二进制数据时,通常需要使用位域;
- 2.位域的类型必须是整型或者是枚举类型;
- 3.位域的声明形式是在成员名字之后紧跟一个冒号以及一个常量表达式,这个表达式用于指定成员的二进制数据;
typedef unsigned int Bit; class File{ Bit mode:2; Bit modified:1; Bit prot_owner:3; Bit Prot_group:3; Bit prot_world:3; public: enum mode {READ = 01;WRITE = 02;EXECUTE=03}; File &open(mode); void close(); void write(); void isRead() const; void setWrite(); };
&
取地址运算符是不能够用于位域的,因此任何指针都无法指向类的位域;- 通常情况下最好将位域设置为无符号类型,存储带符号类型中的位域的行为将是具体情况而定;
- 如果一个类定义了位域成员,那么它通常还需要定义一组内联的成员函数以检验或设置位域的值;
extern C
- 如果需要把
C++
的代码和其他语言的代码在一起进行使用,首先需要有全乡访问该语言的编译器,并且这个编译器应当于当前的C++
编译器是兼容的; - 链接指示:
extern "C" size_t strlen(const char *); extern "C"{ int strcmp(const char*,const char*); char *strcat(char*,const char*); }
- 链接指示有两种形式:包括单个的或者是复合的;链接指示不能够出现在类定义或函数定义的内部.链接指示必须在每个函数的声明中都出现;
- 链接指示的第一种形式包含一个
extern
关键字,后面是一个普通的函数声明;
extern " "
:引号里面表示的是编写程序所支持的语言;
extern "C"{ #include<string.h> }
C++
语言从C
语言继承的标准库函数可以定义为C
函数,但是决定使用C
还是使用C++
实现C标准库;- 编写函数所用的语言是函数类型的一部分.对于使用链接指示定义的函数来说,它的每个声明都必须使用相同的链接指示.
extern "C" void (*pf)(int);
- 指向
C
的函数指针与指向C++
的函数指针不是一样的类型; - 链接指示对于整个声明都是有效的;
- 通过使用链接指示对函数进行定义,可以令一个
C++
函数在其他语言编写的程序中可用;
extern "C" double calc(double dparm){ }
- 通过上述的方式是不能够将具有析构函数等其他特有的操作传递给无法识别的
C
语言的;
“`
- 如果需要把