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

news2025/7/19 20:51:27

文章目录

  • 特殊工具与技术-完结篇
    • 控制内存分配
    • 运行时类型识别
    • 成员指针
    • 嵌套类
    • 局部类
    • 固有的不可抑制特性
      • 位域
        • volatile限定符
        • 链接指示 extern "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程序根本无法理解构造函数、析构函数以及其他类特有的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2379668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCP实战:在扣子空间用扣子工作流MCP,一句话生成儿童故事rap视频

扣子最近迎来重要更新&#xff0c;支持将扣子工作流一键发布成MCP&#xff0c;在扣子空间里使用。 这个功能非常有用&#xff0c;因为我有很多业务工作流是在扣子平台上做的&#xff0c;两者打通之后&#xff0c;就可以在扣子空间里直接通过对话方式调用扣子工作流了&#xff0…

SpringBoot基础项目搭建

资料链接&#xff1a;https://download.csdn.net/download/ly1h1/90855288?spm1001.2014.3001.5501 1.准备工作 1.1 安装IntelliJ IDEA 2023.3.4 (Ultimate Edition) 1.2 采用apache-maven-3.6.3 1.2.1 maven配置文件设置 1.2.2 IDEA配置maven 1.3 JDK采用17版本 2.手动创建…

【拥抱AI】Deer-Flow字节跳动开源的多智能体深度研究框架

最近发现一款可以对标甚至可能超越GPT-Researcher的AI深度研究应用&#xff0c;Deer-Flow&#xff08;Deep Exploration and Efficient Research Flow&#xff09;作为字节跳动近期开源的重量级项目&#xff0c;正以其模块化、灵活性和人机协同能力引发广泛关注。该项目基于 La…

前端获取用户的公网 IP 地址

可以使用免费的免费的公共服务网站 一&#xff1a;https://www.ipify.org/ 获取 JSON 格式的 IP 地址 // 旧地址不好使 // https://api.ipify.org/?formatjson // 新地址 https://api64.ipify.org/?formatjson 二&#xff1a;https://ipinfo.io/ https://ipinfo.io/ 三&a…

云电竞服务器 工作原理

云电竞服务器工作原理详解 一、核心架构原理 虚拟化资源池‌ 通过 ‌KVM/VMware‌ 等虚拟化技术将物理服务器&#xff08;含NVIDIA GPU集群&#xff09;抽象为可动态分配的算力资源池&#xff0c;每个用户独享独立虚拟机实例&#xff0c;实现硬件资源的按需分配与隔离运行。 …

【数据结构】线性表--队列

【数据结构】线性表--队列 一.什么是队列二.队列的实现1.队列结构定义&#xff1a;2.队列初始化函数&#xff1a;3.队列销毁函数&#xff1a;4.入队列函数&#xff08;尾插&#xff09;&#xff1a;5.出队列函数&#xff08;头删&#xff09;&#xff1a;6.取队头元素&#xff…

[Vue3]语法变动

Vue3的语法相对比Vue2有不少改变&#xff0c;这篇讲一下基础语法在Vue3里的形式。 创建Vue对象 在脚手架项目中&#xff0c;index.html等资源不再编写代码&#xff0c;只作为一个容器。所有的页面代码都在.vue相关文件中进行编写&#xff0c;由main.js引入各个.vue文件渲染出页…

Ubuntu服务器开启SNMP服务 监控系统配置指南 -优雅草星云智控简易化操作

Ubuntu服务器开启SNMP服务 & 监控系统配置指南 -优雅草星云智控简易化操作 一、Ubuntu服务器开启SNMP服务 步骤1&#xff1a;安装SNMP服务 sudo apt update sudo apt install snmp snmpd snmp-mibs-downloader -y 步骤2&#xff1a;配置SNMP&#xff08;编辑配置文件&am…

linux本地部署ollama+deepseek过程

1.Tags ollama/ollama GitHub 选择一个版本下载&#xff0c;我下的是0.5.12 2.tar解压该文件 3.尝试启动ollama ollama serve 4.查看ollama的版本 ollama -v 5.创建一个系统用户 ollama&#xff0c;不允许登录 shell&#xff0c;拥有一个主目录&#xff0c;并且用…

从零开始实现大语言模型(十五):并行计算与分布式机器学习

1. 前言 并行计算与分布式机器学习是一种使用多机多卡加速大规模深度神经网络训练过程&#xff0c;以减少训练时间的方法。在工业界的训练大语言模型实践中&#xff0c;通常会使用并行计算与分布式机器学习方法来减少训练大语言模型所需的钟表时间。 本文介绍PyTorch中的一种…

OpenCV进阶操作:指纹验证、识别

文章目录 前言一、指纹验证1、什么是指纹验证2、流程步骤 二、使用步骤&#xff08;案例&#xff09;三、指纹识别&#xff08;案例&#xff09;1、这是我们要识别的指纹库2、这是待识别的指纹图3、代码4、结果 总结 前言 指纹识别作为生物识别领域的核心技术之一&#xff0c;…

网络安全-等级保护(等保) 2-5 GB/T 25070—2019《信息安全技术 网络安全等级保护安全设计技术要求》-2019-05-10发布【现行】

################################################################################ GB/T 22239-2019 《信息安全技术 网络安全等级保护基础要求》包含安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心、安全管理制度、安全管理机构、安全管理人员、安…

3D生成新突破:阶跃星辰Step1X-3D开源,可控性大幅提升

Step1X-3D 是由 StepFun 联合 LightIllusions 推出的新一代 高精度、高可控性 3D资产生成框架。基于严格的 数据清洗与标准化流程&#xff0c;我们从 500万 3D资产 中筛选出 200万高质量数据&#xff0c;构建了 标准化的几何与纹理属性数据集&#xff0c;为3D生成提供更可靠的训…

MySQL数据类型之VARCHAR和CHAR使用详解

在设计数据库字段时&#xff0c;字符串类型算是最常见的数据类型之一了&#xff0c;这篇文章带大家深入探讨一下MySQL数据库中VARCHAR和CHAR数据类型的基本特性&#xff0c;以及它们之间的区别。 VARCHAR类型 VARCHAR&#xff08;Variable Character&#xff0c;可变长度字符…

《Docker 入门与进阶:架构剖析、隔离原理及安装实操》

1 docker 简介 1.1 Docker 的优点 Docker 是一款开放平台&#xff0c;用于应用程序的开发、交付与运行&#xff0c;能将应用和基础架构分离&#xff0c;实现软件快速交付 &#xff0c;还能以统一方式管理应用和基础架构&#xff0c;缩短代码从编写到上线的时间。其核心优势如…

基于Akamai云计算平台的OTT媒体点播转码解决方案

点播视频&#xff08;VOD&#xff09;流媒体服务依赖于视频流的转码来高效分发内容。在转码工作流程中&#xff0c;视频被转换为适合观看设备、网络条件和性能限制的格式。视频转码是计算密集型过程&#xff0c;因此最大化可用硬件上可转码的视频流数量是首要考虑因素。不同基础…

【MySQL】02.数据库基础

1. 数据库的引入 之前存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件存储存在安全性问题&#xff0c;文件不利于数据查询和管理&#xff0c;文件不利于存储海量数据&#xff0c;文件在程序中控制不方便。而为了解决上述问题&#xff0c;专家们设计出更加利于…

选错方向太致命,华为HCIE数通和云计算到底怎么选?

现在搞HCIE的兄弟越来越多了&#xff0c;但“数通和云计算&#xff0c;到底考哪个&#xff1f;”这问题&#xff0c;依旧让不少人头疼。 一个是华为认证的老牌王牌专业——HCIE数通&#xff0c;稳、系统、岗位多&#xff1b; 一个是新趋势方向&#xff0c;贴合云原生、数字化…

经典启发算法【早期/启发式/HC爬山/SA模拟退火/TS禁忌搜/IA免疫 思想流程举例全】

文章目录 一、早期算法二、启发式算法三、爬山法HC3.1 基本思路3.2 伪代码 四、模拟退火SA4.1 算法思想4.2 基本流程4.3 再究原理4.3.1 Metropolis准则4.3.2 再理解 4.4 小Tips4.5 应用举例4.5.1 背包问题&#xff1a;分析&#xff1a;求解&#xff1a; 4.5.2 TSP问题&#xff…

IntraWeb 16.0.2 + Bootstrap 4 居中布局实战(附源码+效果图)

前言 最近在优化一个 IntraWeb 16.0.2 项目时&#xff0c;发现默认布局方式不够灵活&#xff0c;尤其是在不同屏幕尺寸下对齐效果不佳。于是&#xff0c;我决定引入 Bootstrap 4 来实现 完美居中布局&#xff0c;并成功落地&#xff01;今天就把完整的 源代码 实际效果图 分享…