当前位置: 首页 > news >正文

c++从入门到精通(六)--特殊工具与技术-完结篇

特殊工具与技术-完结篇

控制内存分配

重载new和delete:

在这里插入图片描述

​ ==如果应用程序希望控制内存分配的过程,则它们需要定义自己的operator new函数和operator delete函数。==当自定义了全局的operator new函数和operator delete函数后,我们就担负起了控制动态内存分配的职责。这两个函数必须是正确的:因为它们是程序整个处理过程中至关重要的一部分。

​ 下面是标准库中定义的4个operator new和4个operator delete。我们可以自定义下面8个中的任意一个,当我们将运算符函数定义成类的成员时,他们是隐式静态的。当我们重载这些运算符时(3-8),必须使用noexcept异常说明符指定其不抛出异常。
在这里插入图片描述

​ 对于operator new函数或者operator new[]函数来说,它的返回类型必须是void*,第一个形参的类型必须是size_t且该形参不能含有默认实参。当编译器调用operator new时,把存储指定类型对象所需的字节数传给size_t形参;当调用operator new[]时,传入函数的则是存储数组中所有元素所需的空间。

​ 尽管在一般情况下,我们可以自定义具有任何形参的operator new,注意下面这种形式不能被用用户重载:

void *operator new (size_t,void*);

​ 对于operator delete函数或者operator delete[]函数来说,它们的返回类型必须是void,第一个形参的类型必须是void*。执行一条delete表达式将调用相应的operator函数,并用指向待释放内存的指针来初始化void*形参。

​ 当我们将operator delete或operator delete[]定义成类的成员时,该函数可以包含另外一个类型为size_t的形参。此时,该形参的初始值是第一个形参所指对象的字节数。size_t形参可用于删除继承体系中的对象。如果基类有一个虚析构函数(参见15.7.1节,第552页),则传递给operator delete的字节数将因待删除指针所指对象的动态类型不同而有所区别。而且,实际运行的operator delete函数版本也由对象的动态类型决定

​ 我们提供新的operator new函数和operator delete函数的目的在于改变内存分配的方式,但是不管怎样,我们都不能改变new运算符和delete运算符的基本含义。

​ **malloc函数与free函数:**当你定义了自己的全局operator new和operator delete后,这两个函数必须以某种方式执行分配内存与释放内存的操作。也许你的初衷仅仅是使用一个特殊定制的内存分配器,但是这两个函数还应该同时满足某些测试的目的,即检验其分配内存的方式是否与常规方式类似。

​ malloc函数接受一个表示待分配字节数的size_t,返回指向分配空间的指针或者返回0以表示分配失败。free函数接受一个void*,它是malloc返回的指针的副本,free将相关内存返回给系统。调用free(0)没有任何意义。

​ 下面是用malloc与free重载operator new和operator delete的一种简单方式

void *operator new (size_t size)
{if (void* mem = malloc(size))return mem;else throw bad_alloc();
void operator delete(void *mem) noexcept {free(mem);}

定位new表达式:我们可以使用alloctor类把内存分配和初始化分离(allocate和dealocate)。我们也可以直接调用operator new函数来分配内存,但此时不能用construct函数构造对象。相反,我们应该使用new的定位new形式来构造对象。形式如下:new(&sval) string(s)

在这里插入图片描述

place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。当仅通过一个地址值调用时,==定位new使用operator new(size_t,void*)“分配”它的内存。这是一个我们无法自定义的operator new版本。==该函数不分配任何内存,它只是简单地返回指针实参;然后由new表达式负责在指定的地址初始化对象以完成整个工作。事实上,定位new允许我们在一个特定的、预先分配的内存地址上构造对象。

定位new和construct的一个重要区别:传递给construct的指针必须指向同一个allocator对象分配的空间。传递给定位new的指针无需指向operator new分配的内存,甚至不需要指向动态内存。

我们可以显式调用new表达式初始化对象的析构函数,注意,调用析构函数可以销毁给定的对象,但是不会释放该对象所在的空间

运行时类型识别

​ 运行时类型识别(run-time type identification,RTTI)由两个运算符实现:typeid(用于返回表达式的类型)。dynamic_cast(用于将基类的指针或引用安全地转换成派生类的指针或引用)。当我们将这两个运算符用于某种类型的指针或引用,并且该类型含有虚函数,运算符将使用指针或引用所绑定对象的动态类型。

当我们想使用基类对象的指针或引用执行某个派生类的非虚函数时,我们可以使用上述两个运算符

dynamic_cast

如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个bad_cast异常。

在条件部分执行dynamic_cast操作可以确保类型转换和结果检查在同一条表达式中完成。

dynamic_cast<type*>(e);//e是一个有效的指针
dynamic_cast<type&>(e);//e是左值
dynamic_cast<type&&>(e);//e不能是左值
//上述所有形式:e必须是共有派生类|e是目标type的共有基类|e的类型就是type//【【【我们可以对一个空指针执行dynamic_cast,结果是所需类型的空指针。】】】

typeid运算符:

​ typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字。typeid操作的结果是一个常量对象的引用,该对象的类型是标准库类型type_info或者type_info的公有派生类型
在这里插入图片描述

==当typeid作用于指针时(而非指针所指的对象),返回的结果是该指针的静态编译时类型。==例如上述例子中typeid(bp)返回的类型是指针的静态类型Base*。

typeid是否需要运行时检查决定了表达式是否会被求值。只有当类型含有虚函数时,编译器才会对表达式求值。反之,如果类型不含有虚函数,则typeid返回表达式的静态类型;编译器无须对表达式求值也能知道表达式的静态类型。

如果表达式的动态类型可能与静态类型不同,则必须在运行时对表达式求值以确定返回的类型。这条规则适用于typeid(*p)的情况。如果p所知类型不含有虚函数,p不必是一个有效指针,否则p必须是一个有效指针。如果p是一个空指针,会抛出bad_typeid的异常。

使用RTTI解决派生类比较问题:

如果我们把相等运算符定义为虚函数,用不同的派生类各自实现的虚函数来实现相等判断。这回有一个问题,虚函数的基类版本和派生类版本必须就有相同的形参。如果我们想定义一个虚函数equal,则形参必须是基类的引用,此时equal函数只能比较基类成员。

我们可以使用RTTI解决上述问题。我们定义的相等运算符的形参是基类的引用,然后使用typeid检查两个运算对象的类型是否一致。如果运算对象的类型不一致,则==返回false;类型一致才调用equal函数。每个类定义的equal函数负责比较类型自己的成员。这些运算符接受Base&形参,但是在进行比较操作前先把运算对象转换成运算符所属的类类型。

在这里插入图片描述

type_info类
在这里插入图片描述

除此之外,因为type_info类一般是作为一个基类出现,所以它还应该提供一个公有的虚析构函数。type_info类没有默认构造函数,而且它的拷贝和移动构造函数以及赋值运算符都被定义成删除的。创建type_info对象的唯一途径是使用typeid运算符。

type_info类的name成员函数返回一个C风格字符串,表示对象的类型名字。对于某种给定的类型来说,name的返回值因编译器而异,并且不一定与在程序中使用的名字一致。对于name返回值的唯一要求是,类型不同则返回的字符串必须有所区别。

成员指针

数据成员指针

是指可以指向类的非静态成员的指针。但是成员指针指示的是类的成员,而非类的对象。当初始化一个这样的指针时,我们令其指向类的某个成员,但是不指定该成员所属的对象;直到使用成员指针时,才提供成员所属的对象。const string classname::*name;

auto name=&classname::contentsname是指向classname类contents成员的成员指针。

读者必须清楚的一点是,当我们初始化一个成员指针或为成员指针赋值时,该指针并没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时我们才提供对象的信息。

在这里插入图片描述

返回数据成员指针的函数:

类的数据成员一般都是私有的,因此我们无法直接获得数据成员指针。类可以定义一个函数,返回数据成员指针。

class Screen{
public:static const std::string Screen::*data(){ return &Screen::contents;}//data是函数名字,返回类型是const std::string Screen::* 返回指向Screen string类型的成员的指针。
}

成员函数指针

和普通的函数指针类似,如果成员存在重载的问题,则我们必须显式地声明函数类型以明确指出我们想要使用的是哪个函数。

和普通的指针不同,成员函数和指向该成员的指针之间不存在自动转换规则,我们必须在成员函数名前加上取值符。
在这里插入图片描述

用数组保存成员函数指针:

class Screen{
public:using Action =Screen& (Screen::*)();Screen& home();Screen& forward();Screen& back();Screen& up();Screen& down();enum Directions{HOME,FORWARD,BACK,UP,DOWN};Screen& move(Directions);
private:static Action Menu[];//假定数组Menu中依次保存了每个光标移动函数的指针。(成员函数指针)
}Screen& Screen::move(Directions cm)
{return(this->*Menu[cm])();//cm使用的时候可以当作int来用,因此可以作为数组的偏移量。
}int main{Screen myScreen;myScreen.move(Screen::HOME);//使用Screen类中定义的枚举类型成员
}

为成员函数指针生成可调用对象:

成员函数指针不是一个可调用对象,我们必须使用具体的类型对象才能调用成员函数指针所指向的成员函数。因此如下的写法会报错(我们定义一个指向string中empty成员函数的指针,然后将该指针作为find_if的可调用对象实参,显然编译器会报错。

在这里插入图片描述

我们可以使用function生成一个可调用对象。我们告诉function一个事实:即empty是一个接受string参数并返回bool值的函数。通常情况下,执行成员函数的对象将被传给隐式的this形参。当我们想要使用function为成员函数生成一个可调用对象时,必须首先“翻译”该代码,使得隐式的形参变成显式的。

在这里插入图片描述

如果vector中存储的是对象的指针,则我们必须指定function接受指针。const string*指明成员函数在以指针方式传递的对象上执行的。
在这里插入图片描述

**使用mem_fn生成可调用对象:**mem_fn可以根据成员指针的类型推断可调用对象的类型,而无须用户显式地指定。mem_fn生成的可调用对象可以通过对象调用,也可以通过指针调用。实际上,我们可以认为mem_fn生成的可调用对象含有一对重载的函数调用运算符:一个接受string*,另一个接受string&。

find_if(svec.begin(),svec.end(),mem_fn(&string::empty));

使用bind生成可调用对象

在这里插入图片描述

和mem_fn类似的地方是,bind生成的可调用对象的第一个实参既可以是string的指针,也可以是string的引用

嵌套类

嵌套类可以声明在类的内部,定义在类的外部。嵌套类的静态成员定义位于外层类的作用域之外。

尽管嵌套类定义在其外层类的作用域中,但是读者必须谨记外层类的对象和嵌套类的对象没有任何关系。嵌套类的对象只包含嵌套类定义的成员;同样,外层类的对象只包含外层类定义的成员,在外层类对象中不会有任何嵌套类的成员。

class TextQuery{
public:class QueryResult;//QueryResult是一个嵌套类
}class TestQuery::QueryResult{//外部定义嵌套类
}

局部类

类可以定义在某个函数的内部,我们称这样的类为局部类(localclass)。局部类定义的类型只在定义它的作用域内可见。和嵌套类不同,局部类的成员受到严格限制。局部类的所有成员(包括函数在内)都必须完整定义在类的内部。因此,局部类的作用与嵌套类相比相差很远。

局部类中不能定义静态数据成员。局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员

在这里插入图片描述

外层函数对局部类的私有成员没有任何访问特权。当然,局部类可以将外层函数声明为友元;或者更常见的情况是局部类将其成员声明成公有的。

可以在局部类的内部再嵌套一个类。此时,嵌套类的定义可以出现在局部类之外。嵌套类必须定义在与局部类相同的作用域中。局部类内的嵌套类也是一个局部类,必须遵循局部类的各种规定。【a内有一个局部类b,b类又嵌套了一个嵌套类c,则c的定义可以出现在b类作用域内。而b的定义只能出现在b的作用域内,不能出现在b外a内的地方。此时c也是一个局部类】

固有的不可抑制特性

位域

介绍

类可以将其(非静态)数据成员定义成位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。位域在内存中的布局是与机器相关的

位域的类型必须是整型或枚举类型(参见19.3节,第736页)。因为带符号位域的行为是由具体实现确定的,所以在通常情况下我们使用无符号类型保存一个位域。

typedef unsigned int Bit;
class File{Bit mode:2;//位域,占2位enum modes {READ=01,WARITE=02};}

如果可能的话,在类的内部连续定义的位域压缩在同一整数的相邻位,从而提供存储压缩。这些二进制位是否能压缩到一个整数中以及如何压缩是与机器相关的。

取地址运算符(&)不能作用于位域,因此任何指针都无法指向类的位域。

使用位域
在这里插入图片描述

volatile限定符

volatile的确切含义与机器有关,只能通过阅读编译器文档来理解。要想让使用了volatile的程序在移植到新机器或新编译器后仍然有效,通常需要对该程序进行某些改变。

直接处理硬件的程序常常包含这样的数据元素,它们的值由程序直接控制之外的过程控制。例如,程序可能包含一个由系统时钟定时更新的变量。当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile。关键字volatile告诉编译器不应对这样的对象进行优化。

在这里插入图片描述

只有volatile的成员函数才能被volatile的对象调用。只有当某个引用是volatile的时,我们才能使用一个volatile对象初始化该引用。
在这里插入图片描述

合成的拷贝对volatile对象无效:不能把一个非volatile引用绑定到一个volatile对象上。

在这里插入图片描述

尽管我们可以为volatile对象定义拷贝和赋值操作,但是一个更深层次的问题是拷贝volatile对象是否有意义呢?不同程序使用volatile的目的各不相同,对上述问题的回答与具体的使用目的密切相关。

链接指示 extern “C”

C++程序有时需要调用其他语言编写的函数,最常见的是调用C语言编写的函数。像所有其他名字一样,其他语言中的函数名字也必须在C++中进行声明,并且该声明必须指定返回类型和形参列表。对于其他语言编写的函数来说,编译器检查其调用的方式与处理普通C++函数的方式相同,但是生成的代码有所区别。C++使用链接指示(linkagedirective)指出任意非C++函数所用的语言。

要想把C++代码和其他语言(包括C语言)编写的代码放在一起使用,要求我们必须有权访问该语言的编译器,并且这个编译器与当前的C++编译器是兼容的。

链接指示不能出现在类定义或函数定义的内部。同样的链接指示必须在函数的每个声明中都出现。

在这里插入图片描述

指向C函数的指针与指向C++函数的指针是不一样的类型。一个指向C函数的指针不能用在执行初始化或赋值操作后指向C++函数,反之亦然。

在这里插入图片描述

导出C++语言到其他语言

通过使用链接指示对函数进行定义,我们可以令一个C++函数在其他语言编写的程序中可用:

在这里插入图片描述

编译器将为该函数生成适合于指定语言的代码。值得注意的是,可被多种语言共享的函数的返回类型或形参类型受到很多限制。例如,我们不太可能把一个C++类的对象传给C程序,因为C程序根本无法理解构造函数、析构函数以及其他类特有的操作。

相关文章:

  • leetcode hot100刷题日记——1.两数之和
  • 中文分词与数据可视化03
  • restTemplate
  • 深入解析Spring Boot与Spring Cloud在微服务架构中的实践
  • 什么是dom?作用是什么
  • FreeRTOS的学习记录(临界区保护,调度器挂起与恢复)
  • 利用Shp裁剪nc数据
  • 十一、STM32入门学习之FREERTOS移植
  • 最新缺陷检测模型:EPSC-YOLO(YOLOV9改进)
  • RabbitMQ 工作模式(上)
  • LabVIEW汽车CAN总线检测系统开发
  • SpringBoot(一)--- Maven基础
  • [人月神话_6] 另外一面 | 一页流程图 | 没有银弹
  • 游戏引擎学习第292天:实现蛇
  • Java文件读写程序
  • 提示工程 - 系统提示(System Prompts)
  • 健康生活:养生实用指南
  • AM32电调学习解读六:main.c文件的函数介绍
  • 在 Vue 中插入 B 站视频
  • 关于 Web 漏洞原理与利用:1. SQL 注入(SQLi)
  • 重庆城市轨道交通拟听证调价:公布两套票价方案,正征求意见
  • 陕西省市监局通报5批次不合格食品,涉添加剂超标、微生物污染等问题
  • 秦洪看盘|风格有所转变,热钱回流高弹性品种
  • 艺术稀缺性和价值坚守如何构筑品牌差异化壁垒?从“心邸”看CINDY CHAO的破局之道
  • 在本轮印巴冲突的舆论场上也胜印度一筹,巴基斯坦靠什么?
  • “三个集中”之后:图说浦东新区28次撤乡并镇