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

Effective C++ 读书笔记(十二)

条款三十四:区分接口继承和实现继承

public继承由两部分组成:函数接口继承和函数实现继承。这两者的差异很像函数声明和函数定义之间的差异。

作为类的设计者,我们有时希望派生类只继承成员函数的接口(也就是函数声明),有时候希望能够继承接口和实现,但又希望能够覆写所继承的实现。有时候有希望继承函数的接口和实现并且不允许覆盖任何东西。

为了感受上述差异,我们看下面的例子

class Shape{
public:virtual void draw()const=0;virtual void error(const string &msg);int objectID()const;
};
class Rectangle:public Shape{...};
class Ellipse:public Shape{...};

Shape是一个抽象类,用户不能创建其实体,只能创建其派生类实体。尽管如此,Shape还是强烈的影响到了public继承它的派生类。因为:

成员函数的接口总会被继承。因此,某个函数如果可施行与某类身上,那么一定可以施行于其派生类身上。

Shape类有三个成员函数。draw于某个隐喻的视屏中画出当前对象。error让那些需要报导某个错误的成员函数调用。第三个是objectId用于返回每个对象独一无二的id。其中draw是纯虚函数,error是虚函数,objectId是非虚函数。这都有什么样的暗示呢?

先考虑纯虚函数draw

class Shape{
public:virtual void draw()const=0;
};

纯虚函数有两个最突出的特性:它们必须被任何继承了它们的具体类重新声明,而它们在抽象类中通常没有定义。这两个性质放在一起会发现:

声明一个纯虚函数的目的是为了让派生类只继承函数接口。

这对Shape::draw是在正常不过的事了,因为所有的Shape对象都应该是可绘制的。但shape类无法为此函数提供合理的实现,毕竟每个图形的绘制都是不一样的。Shape::draw声明式是对派生类设计者说你必须提供一个draw函数,但我不干涉你如何实现。

令人意外的是,我们可以为纯虚函数提供定义。也就是说我们可以为Shape::draw提供一份实现代码,但调用它的唯一途径是调用时明确指出类名称。

Shape *ps=new Shape; //错误,Shape是抽象的
Shape *ps1=new Rectangle; 
Shape *ps2=new Ellipse;
ps1->draw();//调用 Rectangle::draw
ps2->draw();//调用 Ellipse::draw
ps1->Shape::draw();//调用Shape::draw
ps2->Shape::draw();//调用Shape::draw

一般而言这项性质用途有限,但是它可以实现一种机制,它可以提供一种机制,为非纯虚函数提供更平常更安全的缺省实现。

虚函数和纯虚函数有些不同,派生类继承其函数接口,但虚函数会提供一份实现代码,派生类可能覆写它。

声明一个虚函数的目的是让派生类继承该函数的接口和缺省实现。

看Shape::error的例子

class Shape{
public:virtual void error(const string& msg);
};

其接口表示,每个类都必须支持一个error函数,但每个类可以自由处理。即你必须支持一个error函数,但若不想自己写,可以使用Shape的缺省版本。

但是,允许虚函数同时指定函数声明和函数缺省行为,却有可能造成危险。我们考虑XYZ公司设计的飞行继承体系。该公司只有A和B两种飞机,两者都以相同模式飞行。因此设计出这样的继承体系:

class Airport{...}//机场
class Airplane{
public:virtual void fly(const Airport& destination);};
void Airplane::fly(const Airport& destination)
{//飞往指定目的地
}
class ModelA:public Airplane{...};
class ModelB:public Airplane{...};

为了表示所有飞机都能飞,并阐明不同飞机原则上需要不同的实现代码,fly被声明为虚函数。为了避免modelA和modelB中撰写相同代码,缺省行为由Airplane::fly提供,它同时被modelA和modelB继承。这是一个很好的继承体系。

现在,XYZ决定购买一种新式C飞机。C型和AB两种飞行方式不同。

XYZ公司程序员在继承体系中针对C添加了一个class,但其忘记重新定义fly函数

class ModelC:public Airplane{...};//未声明fly

现在代码中有诸如此类的动作

Airport PDX(...) //机场
Airplane *pa=new ModelC;
pa->fly(PDX);

这将酿成大灾难:这个程序试图以ModelA和ModelB的飞行方式来飞ModelC。

问题不在Airplane::fly有缺省行为,而在于ModelC在未明确说出我要的情况下就继承了该缺省行为。幸运的是我们可以轻易做到提供缺省实现给派生类,但除非他们明白要求否则免谈。这种方法在于切断虚函数接口和缺省实现之间的连接。下面是一种做法


class Airplane{
public:virtual void fly(const Airport& destination)=0;
protected:void defaultFly(const Airport& destination);};
void Airplane::defaultFly(const Airport& destination)
{//缺省行为,飞往指定目的地
}

这里Airplane::fly已经被改为一个纯虚函数,只提供接口。其缺省行为也出现在Airplane类中,但此次以独立函数defaultFly的姿态出现。若想使用缺省实现,可以在其fly函数中对defaultFly做一个inline调用

class ModelA:public Airplane
public:
virtual void fly(const Airport& destination)
{deFaultFly(destination);
}
};
class ModelB:public Airplane{
public:
virtual void fly(const Airport& destination)
{deFaultFly(destination);
}
};

现在ModelC class不可能意外继承不正确的fly实现代码了,因为纯虚函数将迫使Model C必须提供自己的fly版本:

class ModelC:public Airplane{
public:
virtual void fly(const Airport& destination)};
void ModelC::fly(const Airport& destination)
{将C飞机开往目的地
}

这个方案并非绝对安全,程序员可能还是因为copy代码而带来麻烦,但其绝对比原来的设计值得信赖。至于defaultFly,其是一个protected,因为它是Airplane及其派生类的实现细目。

另外Airplane::defaultFly是一个非虚函数,这很重要,因为没有任何一个派生类应该重新定义此函数。

有些人反对以不同的函数分别提供接口和缺省实现,像上述的fly和defaultfly那样,他们关心因过度雷同的函数名称而引起的class命名空间污染问题。但他们也同意,接口和缺省实现应该分开,这个矛盾该如何解决?我们可以利用纯虚函数必须在派生类中重新生命,但它们也拥有自己的实现这一事实,如下:

class Airplane{
public:virtual void fly(const Airport& destination)=0;};
void Airplane::fly(const Airport& destination)
{//缺省行为,飞往指定目的地
}
class ModelA:public Airplane
public:
virtual void fly(const Airport& destination)
{Airplane::fly(destination);
}
};
class ModelB:public Airplane{
public:
virtual void fly(const Airport& destination)
{Airplane::fly(destination);
}
};
class ModelC:public Airplane{
public:
virtual void fly(const Airport& destination)
};
void ModelC::fly(const Airport& destination)
{将C飞机开往目的地
}

这几乎和全面的设计一样,只不过纯虚函数Airplane::fly替换了独立函数Airplane::defaultfly。本质上,这里的fly分为两个部分,声明部分表现接口。定义部分表现出缺省行为。将其合并丧失了两个函数有不同保护级别的机会,protected函数变为了public。

最后,我们看看Shape的非虚函数的objectID:

class Shape{
public:int objectID()const;
};

如果成员函数是个非虚函数,意味着其不打算在派生类中有不同行为。实际上非虚函数所表现的不变性凌驾于其特异性,因为它表示派生类无论多么特异化,它的行为都不可以改变。

  • 声明非虚函数的目的是为了令派生类继承函数的接口及一份强制实现。

我们可以把objectID的声明想作是每个shape对象都有一个用来生产对象识别码的函数:此识别码总是采用相同计算方法,由objectID决定,任何派生类都不应该尝试改变其行为。所以非虚函数绝不该在派生类中重新定义。

纯虚函数、虚函数、非虚函数之间的差异使你可以精确指定派生类想要继承的东西:只继承接口,或是继承接口和一份缺省实现、或是继承接口和一份强制实现。所以我们声明成员函数时,必须慎重选择。如果你确实履行,应该能避免经验不足的类设计者最常犯的两个错误。

第一个错误是将所有函数声明为非虚函数。这使得派生类没有充裕的空间进行特化工作。实际上任何一个类作为基类,都会拥有若干虚函数。

如果你关心virtual函数的成本,听一听80-20准则:一个典型的程序80%的时间花费在20%代码身上。这个法则很重要,它意味着,平均而言你的函数调用中可以有80%是虚函数而不冲击函数的大体效率,所以我们应该将重心放在那20%代码的身上。

另一个常见错误是是将所有成员声明为虚函数。有时候这是正确的,例如条款31中的接口。然而某些函数不应该在派生类中重新定义,那些函数应该被声明为非虚函数。

总结:

  • 接口继承和实现继承不同。在public继承下,派生类总是继承基类的接口。
  • 纯虚函数只具体指定接口继承。
  • 虚函数具体指定接口继承以及缺省实现的继承。
  • 非虚函数具体指定接口继承以及强制实现继承。

条款三十五:考虑虚函数以外的的其他选择

假设有一个游戏,游戏角色有其健康状态。提供一个成员函数healthValue,其返回一个整数,表示健康程度。由于不同人物以不同方式计算其健康程度,所以应该将该函数设为虚函数。

class GameCharacter{
public:virtual int healthValue()const;
};

healthValue未被声明为纯虚函数,说明其将来会有一个计算血量的缺省算法。

这个设计很明确,但从某个角度来说反而成为了它的弱点。让我们考虑其他解法。

藉由Non-Virtual Interface接口手法实现Template Method模式

我们将从一个有趣的思量流派开始,该流派主张虚函数应该几乎总是private。其拥护者建议,较好的设计是保留healthValue为public成员函数,调用一个private virtual函数为其工作:

class GameCharacter{
public:int healthValue()const
{int val=doHealthValue();...return val;
}
private:virtual int doHealthValue()const
{
}
};

这段代码中直接在类定义式内呈现成员函数本体。如条款30所言,它们也就暗自成为了inline,本处只为方便阅读,无特殊用意。

这一设计就是令客户通过public non-virtual函数间接调用private virtual函数,成为non-virtual interface(NVI)手法。它是Template Method设计模式的一个独特表现形式。这个非虚函数称为虚函数的外敷器。

NVI的一个优点是保证虚函数在进行真正工作被调用。这意味着外敷器确保在调用虚函数之前设定好适当的场景,在调用结束后清理场景。场景包括:锁定互斥器、验证函数先决条件等。

NVI手法涉及在派生类内重新定义private virtual函数。即重新定义若干个派生类并不调用的函数!这并不矛盾,重新定义virtual函数表示某些事如何被完成,调用虚函数则表示它何时被完成。

在NVI手法下没有必要让虚函数一定得是private。某些类继承体系要求派生类在虚函数时限内必须调用其基类的对应兄弟,而为了让这样的调用合法,虚函数必须是protected,有些虚函数甚至一定得是public(基类虚析构),这么一来就不能实施NVI手法了。

藉由Function Pointers实现Strategy模式

NVI手法对public virtual函数而言是一个有趣的代替方法,但毕竟我们还是使用虚函数来计算每个人的健康指数。另一个更戏剧性的设计主张:人物健康指数的计算与人物类型无关,这样的计算完全不需要人物这个成分。例如我们可能要求每个人物的构造函数接受一个指针,指向一个健康计算函数,我们可以调用这个函数进行实际计算:

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:typedef int (*HealthCalcFunc)(const GameCharacter&);explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):healthFunc(hcf){}int healthValue()const{return healthFunc(*this);}
private:HealthCalcFunc  healthFunc;
};

这是常见的Strategy设计模式的简单应用。拿它和基于GameCharacter继承体系内之virtual函数的做法比较,它提供了某些有趣的弹性:

  • 同一人物类型的不同实体可以有不同的健康计算函数。
class EvilBadGuy:public GameCharacter{
public:explicit EvilBadGuy(HealthCalcFunc hcf=defaultHealthCalc):GameCharacter(hcf){...}
};
int loseHealthQuickly();//计算方式1
int loseHealthSlowly();//计算方式2
EvilBadGuy ebg(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);//相同类型的人物对应不同的健康计算方式
  • 某已知人物的健康计算函数可以在运行期来变更。

上述意味着健康计算函数不属于GameCharacter继承体系的成员函数。这代表健康计算函数没有使用GameCharacter的私有成员。但如果健康计算函数内使用了GameCharacter的私有成员,那就会有问题。

要解决上述问题,一般需要弱化class的封装,例如利用友元函数。是否利用函数指针,其利弊还需自己权衡。

藉由tr1::function完成Strategy模式

基于函数指针的作法有一些限制,比如为什么健康指数计算必须是函数,而不能是某样像函数的东西(如函数对象)呢?如果一定是个函数,为什么不能是个成员函数?为什么一定需要返回int而不是其他可转换为int的东西呢?

如果我们使用类型为tr1::function的对象,这些约束就全都挥发不见了。像条款54中所说,这样的对象持有任何可调用物(函数指针、函数对象、成员函数指针),只要其签名式兼容于需求端。以下刚才的设计可以改为tr1::function:

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):healthFunc(hcf){}int healthValue()const{return healthFunc(*this);}
private:HealthCalcFunc  healthFunc;
};

在这里,HealthyFunc是一个typedef,用来表现tr1::function的某个具现体,意味着该具现体的行为像是一般的函数指针。

让我们仔细看HealthyFunc的typedef:

std::tr1::function<int (const GameCharacter&)>

这里tr1::function具现体的目标签名式以不同颜色标识。这个签名代表的函数是接受一个const GameCharacter的引用并返回一个int。这个tr1::function类型产生的对象可以持有任何与此签名式兼容的可调物。兼容的含义是这个可调物的参数可被隐式转换为const GameCharacter&,而其返回值类型可以被隐式转换为int。

和函数指针的方法类似。唯一不同点是如今的GameCharacter持有的是tr1::function对象,相当于一个指向函数的泛化型指针。这个改变看起来这么小,但可以给客户在制定健康计算函数这件事带来更多的弹性。

short calcHealth(const GameCharacter&);//其返回short而非intstruct HealthCalculator{int operator()(const GameCharactor&)const{...}};class GameLevel{
public:float health(const GameCharactor&)const;
...
};class EvilBadGuy:public GameCharacter{
...
};class EyeCandyCharacter:public GameCharacter{
...
};EvilBadGuy ebg1(calcHealth); //人物1,使用某个函数计算健康指数EyeCandyCharacter ecc1(HealthCalculator());//人物2,使用某个函数对象计算健康指数GameLevel currentLevel;
EvilBadGuy ebg2(
std::tr1::bind(&GameLevel::health,currentLevel,_1));//人物3,使用成员函数计算健康指数

为了计算ebg2的健康指数,应该使用GameLevel类的成员函数health。GameLevel::health宣称自己需要接受一个参数,实际它接受两个参数,因为它也获得一个隐式参数GameLevel,也就是this。然而GameCharacters的健康计算函数只接受一个参数,若我们使用GameLevel::health作为ebg2的健康计算指数,我们必须转换,使其只接受一个参数。在本例中我们使用GameLevel currentLevel作为ebg2健康计算函数所需的参数(this),于是我们将currentLevel绑定为GameLevel对象,让它在每次GameLevel::health被调用来计算ebg2的健康时被使用,这就是tr1::bind的作用:它指出ebg2的健康计算函数应该总是以currentLevel作为GameLevel的对象。

若以tr1::function替换函数指针,我们将因此允许客户在计算人物健康指数时使用任何兼容的可调用物。

古典的strategy模式

古典的strategy模式将healthCalcFunc继承体系独立出来

class GameCharacter;
class HealthFunc{
public:virtual int calc(const GameCharacter& gc)const{...}
...
};
HealthFunc defaultHealthCalc;
class GameCharacter{
public:explicit GameCharacter(HealthCalcFunc* hcf=&defaultHealthCalc):healthFunc(hcf){}int healthValue()const{return phealthFunc->calc(*this);}
private:HealthCalcFunc*  phealthFunc;
};

快速复习一下本条款中所提到的替换策略

  • 使用Non-Virtual Interface手法。它以public non-virtual成员函数包裹较低访问性的虚函数。
  • 将虚函数替换为函数指针成员变量。
  • 以tr1::function成员变量替代虚函数。
  • 将继承体系的虚函数替换为另一个继承体系的虚函数。

总结

  • 虚函数的代替方案包括NVI手法及strategy设计模式的多种形式。
  • 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问类的非公有成员
  • tr1::function对象的行为就像一般函数指针。这样的对象可接纳与给定目标签名式兼容的所有可调物。

条款三十六:绝不重新定义继承而来的非虚函数

classD由classB以public继承派生而来,classB定义一个public成员函数mf,其参数和返回值不重要均设为void。

class B{
public:void mf();...
};
class D:public B{
};
D x;以下行为
B* pb=&x; //获得一个指针指向x
pb->mf();   //由该指针调用mf
异于以下行为
D* pD=&x;//获得一个指针指向x
pD->mf();//由该指针调用mf

你可能会相当惊讶,两者都通过对象x调用成员函数mf,其对象相同,函数也相同,所以行为也应该相同?

但事实可能并不是这样。若mf是一个非虚函数而D定义有自己的mf版本,那就不是如此。原因是非虚函数 B::mf和D::mf都是静态绑定。意思是由于pb被声明为指向B的指针,那么通过pb调用的非虚函数永远是B所定义的版本,既使pb指向一个派生类的对象。

但另一方面,虚函数是动态绑定,所以不受这个问题的困扰,若mf是个虚函数,无论通过pb还是pd都会访问D::mf。

若在classD中重新定义继承自classB的非虚函数mf,D对象很可能展现精神分裂的不一致现象。当mf被调用,任何一个D对象都有可能表现B或D的行为;决定因素在于指向对象的指针,引用和指针类似。

  • 适用于B对象的每一件事也适用于D对象(is-a)。
  • B的派生类一定会继承mf的借口和实现,因为mf是一个非虚函数。

若D重新定义mf,则会出现矛盾。若D必须要实现mf,那么就不能public继承,若非要public继承,那么应该将mf声明为虚函数。

总结

  • 绝对不要重新定义继承而来的非虚函数。

相关文章:

Effective C++ 读书笔记(十二)

条款三十四&#xff1a;区分接口继承和实现继承 public继承由两部分组成&#xff1a;函数接口继承和函数实现继承。这两者的差异很像函数声明和函数定义之间的差异。 作为类的设计者&#xff0c;我们有时希望派生类只继承成员函数的接口&#xff08;也就是函数声明&#xff0…...

【卡梅德生物】构建噬菌体文库与噬菌体展示文库构建服务新探索

在生命科学与生物技术快速发展的当下&#xff0c;抗体文库构建、构建噬菌体文库以及噬菌体展示文库构建服务在生物医药研发领域中占据着举足轻重的地位。它们不仅是基础研究的重要工具&#xff0c;更是推动抗体药物开发、疾病诊断技术进步的关键力量。 构建噬菌体文库是整个技…...

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter19-表单脚本

十九、表单脚本 表单脚本 JavaScript 较早的一个用途是承担一部分服务器端表单处理的责任。虽然 Web 和 JavaScript 都已经发展了很多年&#xff0c;但 Web 表单的变化不是很大。由于不能直接使用表单解决问题&#xff0c;因此开发者不得不使用JavaScript 既做表单验证&#xf…...

C++STL容器之map

1.介绍 map是 C 标准模板库&#xff08;STL&#xff09;中的一个关联容器&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。map中的元素是按照键&#xff08;key&#xff09;进行排序的&#xff0c;并且每个键在容器中是唯一的。map通常基于红黑树&#xf…...

基于Nanopi duo2的WiFi智能摄像头

1.固件包烧录 https://wiki.friendlyelec.com/wiki/index.php/NanoPi_Duo2/zh#.E8.BF.9E.E6.8E.A5WiFi 固件包链接以及烧录工具都在上面链接中 烧录过程 使用读卡器将SD卡插入到电脑,然后打开烧录工具 2.通过串口工具连接板子使其连接WiFi 对应的串口工具,就是这个HyperT…...

Java 内存区域详解

1 常见面试题 1.1 基本问题 介绍下Java内存区域&#xff08;运行时数据区&#xff09;Java对象的创建过程&#xff08;五步&#xff0c;建议能够默写出来并且要知道每一步虚拟机做了什么&#xff09;对象的访问定位的两种方式&#xff08;句柄和直接指针两种方式&#xff09;…...

MyBatis框架详解与核心配置解读

目录 前言 一、MyBatis框架概述 1.1 什么是MyBatis 1.2 MyBatis的优点 二、MyBatis的使用入门与案例 2.1 MyBatis核心配置文件&#xff08;mybatis-config.xml&#xff09; 2.2 XML映射文件&#xff08;UserMapper.xml&#xff09; 三、MyBatis的常用注解及其用法 3.1…...

Windows 快速搭建C++开发环境,安装C++、CMake、QT、Visual Studio、Setup Factory

安装C 简介 Windows 版的 GCC 有三个选择&#xff1a; CygwinMinGWmingw-w64 Cygwin、MinGW 和 mingw-w64 都是在 Windows 操作系统上运行的工具集&#xff0c;用于在 Windows 环境下进行开发和编译。 Cygwin 是一个在 Windows 上运行的开源项目&#xff0c;旨在提供类Uni…...

GO系列-IO 文件操作

os io 判断文件是否存在 func fileExist(filePath string) (bool, error) {_, err : os.Stat(filePath)if err nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, &CheckFileExistError{filePath} } 读取文件内容 func readFileContext(…...

Unity Excel导表工具转Lua文件

思路介绍 借助EPPlus读取Excel文件中的配置数据&#xff0c;根据指定的不同类型的数据配置规则来解析成对应的代码文本&#xff0c;将解析出的字符串内容写入到XXX.lua.txt文件中即可 EPPlus常用API //命名空间 using OfficeOpenXml;//Excel文件路径 var fileExcel new File…...

Helix——Figure 02发布通用人形机器人控制的VLA:一组神经网络权重下的快与慢双系统,让两个机器人协作干活

前言 过去一周&#xff0c;我花了很大的心思、力气&#xff0c;把deepseek的GRPO、MLA算法的代码解析通透&#xff0c;比如GRPO与PPO的详细对比&#xff0c;再比如MLA中&#xff0c;图片 公式 代码的一一对应 2.20日晚&#xff0c;无意中刷到figure 02发布Helix的一个演示视频…...

汽车自动驾驶辅助L2++是什么?

自动驾驶辅助级别有哪些&#xff1f; 依照SAE&#xff08;SAE International&#xff0c;Society of Automotive Engineers国际自动机工程师学会&#xff09;的标准&#xff0c;大致划分为6级&#xff08;L0-L5&#xff09;&#xff1a; L0人工驾驶&#xff1a;即没有驾驶辅助…...

进程的介绍--进程状态/切换

1.冯 • 诺依曼体系结构 1.1 体系结构 冯•诺依曼结构也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。数学家冯•诺依曼提出了计算机制造的三个基本原则&#xff0c;即采用二进制逻辑、程序存储执行以及计算机由五个部分组成&#x…...

goby(蓝队红队版)扫描工具扫描使用时候报错解决方法

1.Goby 是一款开源的网络安全扫描工具&#xff0c;主要用于漏洞扫描、资产发现和信息收集。它旨在帮助安全研究人员、渗透测试人员和红队成员自动化和简化网络漏洞扫描过程。Goby 提供了多种功能&#xff0c;能够在大量的目标中高效地识别出潜在的安全漏洞。 2.今天在官网下载…...

Word文档中插入的图片不能完整显示

在在Word文档中插入图片&#xff0c;只显示图片最下面的一小部分。 将“固定值”更改为“单倍行距”...

模电知识点总结(6)

1.选取频率高于1000Hz的信号时&#xff0c;可选用高通滤波器&#xff1b;抑制50Hz的交流干扰时&#xff0c;可选用带阻滤波器如果希望抑制500Hz以下的信号&#xff0c;可选用高通滤波器。 2.有用信号频率高于1000Hz&#xff0c;可选用高通滤波器&#xff1b;希望抑制50Hz的交流…...

Linux操作系统4-进程间通信4(共享内存原理,创建,查看,命令)

上篇文章&#xff1a;Linux操作系统4-进程间通信3&#xff08;基于管道的进程池设计&#xff09;-CSDN博客 本篇Gitee代码&#xff1a;myLerningCode/l24 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com) 本篇重点&#xff1a;使用共享内存来实现两个进程…...

Grok 使用指南

文章来源&#xff1a;Grok 漫游指南 | xAI Docs 欢迎&#xff01;在本指南中&#xff0c;我们将引导您了解使用 xAI API 的基础知识。 #第 1 步&#xff1a;创建 xAI 帐户 您需要一个 xAI 帐户才能访问 xAI API。在此处注册帐户。 创建账户后&#xff0c;您需要为其加载积分…...

使用Ubuntu搭建Java部署环境

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f439;今日诗词:小舟从此逝&#xff0c;江海寄余生&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小博主&#x1f64f; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小…...

MATLAB学习之旅:从入门到基础实践

在当今科技飞速发展的时代,MATLAB作为一款强大的数学软件,犹如一把神奇的钥匙,能够打开众多领域的大门。无论是工程计算、数据分析,还是算法开发、可视化呈现,MATLAB都展现出了无与伦比的魅力。今天,就让我们踏上这段奇妙的MATLAB学习之旅,从最基础的部分开始,逐步探索…...

蓝桥杯核心内容

核心内容 数学 质数与筛质数&#xff0c;分解质因数 分解质因数 所有的数都可以写成有限个数相乘质数&#xff1a;可以写成1✖本身&#xff08;如131✖13&#xff09;合数&#xff1a;ab1✖...✖bn-》把乘数里面是合数的再分&#xff08;如b3是合数-》b3c1✖c2&#xff09;进…...

C/C++ | 每日一练 (2)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 C/C | 每日一练 (2)题目参考答案封装继承多态虚函数底…...

金融时间序列【量化理论】

业界常用的技术分析指标都与价格本身有关&#xff0c;而时间序列分析由于对数据平稳性的要求常常是基于收益率这样更加偏稳定的数据&#xff08;收益率由于会涨停和跌停每天最多10%&#xff09; 平稳性&#xff1a; 强平稳性&#xff1a;随时间变化&#xff0c;各个统计特征都…...

未来AI方向落地场景:小语言模型,super_private_agent

未来AI方向落地场景:小语言模型,super_private_agent 目录 未来AI方向落地场景:小语言模型,super_private_agent小语言模型super - private - agent(注重隐私的智能代理)碳基生命和硅基生命交互界面面向agent的专用交互协议和数据接口从web平台经济到网络平台举例说明社交…...

快速入门——第三方组件element-ui

学习自哔哩哔哩上的“刘老师教编程”&#xff0c;具体学习的网站为&#xff1a;10.第三方组件element-ui_哔哩哔哩_bilibili&#xff0c;以下是看课后做的笔记&#xff0c;仅供参考。 第一节 组件间的传值 组件可以有内部Data提供数据&#xff0c;也可由父组件通过prop方式传…...

数据库索引:优点、缺点及常见类型

在现代数据库管理系统中&#xff0c;索引是优化查询性能的关键工具。通过创建索引&#xff0c;可以显著减少数据检索的时间。然而&#xff0c;索引并不是万能的&#xff0c;它们也有其自身的缺点和适用场景。本文将详细介绍索引的优势、劣势以及不同类型的索引。 索引的优点 …...

Qt5 C++ TcpSocket 如何判断是服务主动断开tcp socket连接?

文章目录 实现思路示例代码代码解释主要功能和用法注意事项 在 Qt 5.9.9 的 C 开发中&#xff0c;使用 QTcpSocket 时&#xff0c;要判断是服务端主动断开 TCP Socket 连接&#xff0c;可以通过处理 QTcpSocket 的 disconnected 信号&#xff0c;结合 QTcpSocket 的状态以及…...

DeepSeek动画视频全攻略:从架构到本地部署

DeepSeek 本身并不直接生成动画视频,而是通过与一系列先进的 AI 工具和传统软件协作,完成动画视频的制作任务。这一独特的架构模式,使得 DeepSeek 在动画视频创作领域发挥着不可或缺的辅助作用。其核心流程主要包括脚本生成、画面设计、视频合成与后期处理这几个关键环节。 …...

MySQL 中的回表是什么?MySQL 中使用索引一定有效吗?如何排查索引效果?在 MySQL 中建索引时需要注意哪些事项?

MySQL 中的回表是什么&#xff1f; 1. 背景知识 MySQL 中有两种常见的索引&#xff1a; 主键索引&#xff08;Primary Key Index&#xff09; &#xff1a;唯一标识表中的每一行数据&#xff0c;使用 B 树结构存储。主键索引的叶子节点存储的是完整的行数据。非主键索引&…...

AI大模型有哪些常见的应用场景

图像生成 应用场景: 图像生成功能描述: 根据文本描述或参考图生成高质量图片&#xff0c;支持艺术、写实等多种风格。用法示例: 输入提示词&#xff08;如“赛博朋克风格的城市夜景”&#xff09;&#xff0c;调整参数&#xff08;分辨率、风格强度&#xff09;。代表应用: 即…...