C++ Primer 拷贝、赋值与销毁

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 拷贝控制和资源管理…
    • 拷贝、赋值与销毁
      • 拷贝构造函数
      • 合成拷贝构造函数
      • 拷贝初始化
    • 拷贝初始化的限制
    • 拷贝赋值运算符
    • 重载赋值运算符
    • 合成拷贝赋值运算符
    • 析构函数
    • 三/五法则
    • 需要析构函数的类也需要拷贝和赋值操作
    • 需要拷贝操作的类也需要赋值操作,反之亦然
    • 使用=default
    • 阻止拷贝
    • 定义删除的函数
    • 合成的拷贝控制成员可能是删除的
    • private拷贝控制

拷贝控制和资源管理…

在本章中,我们将学到,类可以定义构造函数,用来控制在创建此类型对象时做什么。在本章中,我们还将学习类如何控制该类型对象拷贝、赋值、移动或销毁时做什么。类通过一些特殊的成员函数控制这些操作,包括:拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符以及析构函数。

当定义一个类时,我们显式地或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数(copy constructor)、拷贝赋值运算符(copy-assignment operator)、移动构造函数(move constructor)、移动赋值运算符(move-assignment operator)和析构函数(destructor)。拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。析构函数定义了当此类型对象销毁时做什么。我们称这些操作为拷贝控制操作(copy control)。

如果一个类没有定义所有这些拷贝控制成员,编译器会自动为它定义缺失的操作。因此,很多类会忽略这些拷贝控制操作。但是,对一些类来说,依赖这些操作的默认定义会导致灾难。通常,实现拷贝控制操作最困难的地方是首先认识到什么时候需要定义这些操作。

在定义任何C++类时,拷贝控制操作都是必要部分.对初学C++的程序员来说,必须定义对象拷贝、移动、赋值炎销毁时做什么,这常常令他们感到困扰。这种困扰很复杂,因为如果我们不显式定义这些操作,编译器也会为我们定义,但编译器定义的版本的行为可能并非我们所想。

拷贝、赋值与销毁

我们将以最基本的操作一一拷贝构造函数、拷贝赋值运算符和析构函数作为开始。

拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

class Foo{public:Foo();           //默认构造函数Foo(const Foo&); // 拷贝构造函数
}

拷贝构造函数的第一个参数必须是一个引用类型,原因我们稍后解释。虽然我们可以定义一个接受非const引用的拷贝构造函数,但此参数几乎总是一个const的引用。拷贝构造函数在几种情况下都会被隐式地使用。因此,拷贝构造函数通常不应该是explicit的。

合成拷贝构造函数

如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。与合成默认构造函数不同,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。

对某些类来说,合成拷贝构造函数(synthesized copy constructor)用来阻止我们拷贝该类类型的对象。而一般情况,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。

每个成员的类型决定了它如何拷贝:对类类型的成员,会使用其拷贝构造函数来拷贝;内置类型的成员则直接拷贝。虽然我们不能直接拷贝一个数组,但合成拷贝构造函数会逐元素地拷贝一个数组类型的成员。如果数组元素是类类型,则使用元素的拷贝构造函数来进行拷贝。

作为一个例子,我们的sales_data类的合成拷贝构造函数等价于:

class Sales_data
public:
//其他成员和构造函数的定义,如前
//与合成的拷贝构造函数等价的指贝构造函数的声明
Sales_data(const Sales_data&);
private:
std::string bookKNo;
int unit_sold = 0;
double revenue=0.0;
//与Sales_data的合成的指贝构造函数等价
Sales_data::Sales_data(const Sales_data&orig):
bookNo(orig.bookNo),//使用stritng的指贝构造函数
units_sold(orig.units_sold),//指贝orig.units_sold
revenue(orig.revenue)//拷贝orig.revenue
{}//空函数体

拷贝初始化

现在,我们可以完全理解直接初始化和拷贝初始化之间的差异了:

string dots(10,'.');//直接初始化
string s(dots)}//直接初始化
string s2=dots;//拷贝初始化
string null_book="9-~999-99999-9";//拷贝初始化
string nines=string(100,'9'); //拷贝初始化

当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。当我们使用拷贝初始化(copy initialization)时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。

拷贝初始化通常使用拷贝构造函数来完成。但是,如果一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数而非拷贝构造函数来完成。但现在,我们只需了解拷贝初始化何时发生,以及拷贝初始化是依靠拷贝构造函数或移动构造函数来完成的就可以了。

拷贝初始化不仅在我们用=定义变量时会发生,在下列情况下也会发生

  • 将一个对象作为实参传递给一个非引用类型的形参
  • 从一个返回类型为非引用类型的函数返回一个对象
  • 用花括号列表初始化一个数组中的元素或一个聚合类中的成员

某些类类型还会对他们所分配的对象使用拷贝初始化。例如,当我们初始化标准库容器或是调用其insert或push成员时,容器会对其元素进行拷贝初始化。与之相对,用emplace成员创建的元素都进行直接初始化。

拷贝初始化的限制

如前所述,如果我们使用初始化值要求通过一个explicit的构造函数来进行类型转换,那么使用拷贝初始化是直接初始化就不是无关紧要的了:

vector<int>v1(10);//正确,直接初始化
vector<int>v2=10;//错误:接受大小参数的构造函数是explicit的
void(vector<int>);//的参数进行指贝初始化
f(10);//错误:不能用一个explicit的构造函数拷贝一个实参
f(vector<int>(10));//正确:从一个int直接构造一个临时vector

直接初始化v1是合法的,但看起来与之等价的拷贝初始化v2则是错误的,因为vector的接受单一大小参数的构造函数是explicit的。出于同样的原因,当传递一个实参或从函数返回一个值时,我们不能隐式使用一个explicit构造函数。如果我们希望使用一个explicit构造函数,就必须显式地使用,像此代码中最后一行那样。编译器可以绕过拷贝构造函数

在拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。即,编译器被允许将下面的代码

string null_book="9-999-99999-9";//拷贝初始化

改写为

string null_book("9-999-99999-9");//编译器略过了拷贝构造函数

但是,即使编译器略过了拷贝/移动构造函数,但在这个程序点上,拷贝/移动构造函数必须是存在且可访问的(例如,不能是private的)。

拷贝赋值运算符

与类控制其对象如何初始化一样,类也可以控制其对象如何赋值:

Sales_data trans,accum;
trans=accum;//使用Sales_data的拷贝赋值运算符

与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个。

重载赋值运算符

在介绍合成赋值运算符之前,我们需要了解一点儿有关重载运算符(overloaded operator)的知识。

重载运算符本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。类似于任何其他函数,
运算符函数也有一个返回类型和一个参数列表。

重载运算符的参数表示运算符的运算对象。某些运算符,包括赋值运算符,必须定义为成员函数。如果一个运算符是一个成员函数,其左侧运算对象就绑定到隐式的this参数。对于一个二元运算符,例如赋值运算符,其右侧运算对象作为显式参数传递。

拷贝赋值运算符接受一个与其所在类相同类型的参数:

class Foo{
public:Foo& operator=(const Foo&);//赋值运算符
};

为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用。另外值得注意的是,标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧运算对象的引用。

赋值运算符通常应该返回一个指向其左侧运算对豫的引用。

合成拷贝赋值运算符

与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符(synthesized copy-assignment operator)。类似拷贝构造函数,对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值。如果拷贝赋值运算符并非出于此目的,它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,这一工作是通过成员类型的拷贝赋值运算符来完成的。对于数组类型的成员,逐个赋值数组元素。合成拷贝赋值运算符返名一个指向其左侧运算对象的引用。

作为一个例子,下面的代码等价于Sales_data的合成拷贝赋值运算符:

//等价于合成指贝赋值运算符
Sales_data&
Sales_data::operator=(const Sales_data&rhs)
{bookkNo=rhs.bookNo//调用string::operator=units_sold=rhs.units_sold;//使用内置的int赋值revenue=rhs.revenue;//使用内置的double赋值return*this;//返回一个此对象的引用
}

析构函数

析构函数执行与构造函数相反的操作:构造函数初始化对象的非static数据成员,还可能做一些其他工作;析构函数释放对象使用的资源,并销毁对象的非static数据成员。

析构函数是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数

class Foo{
public:
~Foo();//析构函数
}

由于析构函数不接受参数,因此它不能被重载。对一个给定类,只会有唯一一个析构函数。

析构函数完成什么工作

如同构造函数有一个初始化部分和一个函数体,析构函数也有一个函数体和一个析构部分。在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工作。通常,析构函数释放对象在生存期分配的所有资源。在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

隐式销毁一个内置指针类型的成员不会delete它所指向的对象。

与普通指针不同,智能指针是类类型,所以具有析构函数。因此,与普通指针不同,智能指针成员在析构阶段会被自动销毁。什么时候会调用析构函数

无论何时一个对象被销毁,就会自动调用其析构函数:

  • 变量在离开其作用域时被销毁。
  • 当一个对象被销毁时,其成员被销毁。
  • 容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。
  • 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁。
  • 对于临时对象,当创建它的完整表达式结束时被销毁。

由于析构函数自动运行,我们的程序可以按需要分配资源,而(通常)无须担心何时释放这些资源。

例如,下面代码片段定义了四个sales_data对象:

{//新作用域
//p和p2指向动态分配的对象
Sales_data *p=new Sales_data;//p是一个内置指针
auto p2=make_shared<Sales_data>();//p2是一个shared_ptr
Sales_data item(*p);//指贝构造函数将*p拷贝到item中
vector<Sales_data>vec;//局部对象
vec.push_back(*p2);//拷贝p2指向的对象
delete p;//对p指向的对象执行析构函数}//退出局部作用域;对item、p2和vec调用析构函数
//销毁p2会途减其引用计数;如果引用计数变为0,对象被释放
//销毁vec会销鳄它的元素

每个Sales_data对象都包含一个string成员,它分配动态内存娄保存bookNo成员中的字符。但是,我们的代码唯一需要直接管理的内存就是我们直接分配的Sales_data对象。我们的代码只需直接释放绑定到p的动态分配对象。

其他Sales_data对象会在离开作用域时被自动销毁。当程序块结束时,vec、p2和item都离开了作用域,意味着在这些对象上分别会执行vector、shared_ptr和Sales_data的析构函数。vector的析构函数会销毁我们添加到vec的元素。shared_ptr的析构函数会递减p2指向的对象的引用计数。在本例中,引用计数会变为0,因此shared_ptz的析构函数会delete p2分配的Sales_data对象。

在所有情况下,Sales_data的析构函数都会隐式地销毁bookNo成员.销毁bookNo会调用string的析构函数,它会释放用来保存ISBN的内存。

当指向一个对象的引用或指针离开作用域时,析构函数不会执行。

合成析构函数

当一个类未定义自己的析构函数时,编详器会为它定义一个合成析构函数(synthesized destructor)。类似拷贝构造函数和拷贝赋值运算符,对于树些类,合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,合成析构函数的函数体就为空。

例如,下面的代码片段等价于Sales_data的合成析构函数:

class Sales_data{
public://成员会被自动销毁,除此之外不需要做其他事情~Sales_data()//其他成员的定义,如前
}

在(空)析构函数体执行完毕后,成员会被自动销毁。特别的,string的析构函数会被调用,它将释放bookNo成员所用的内存。

认识到析构函数体自身并不直接销毁成员是非常重要的。成员是在析构函数体之后隐含的析构阶段中被销毁的。在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的。

三/五法则

如前所述,右三个基本操作可以控制类的拷贝操作:拷贝构造函数,拷贝赋值运算符和析构函数。而且,在新标准下,一个类还可以定义一个移动构造函数和一个移动赋值运算符。

C++语言并不要求我们定义所有这些操作:可以只定义其中一个或两个,而不必定义所有。但是,这些操作通常应该被看做一个整体。通常,只需要其中一个操作,而不需要定义所有操作的情况是很少见的。

需要析构函数的类也需要拷贝和赋值操作

当我们决定一个类是否要定义它自己版本的拷贝控制成员时,一个基本原则是首先确定这个恶类是否需要一个析构函数。通常,对析构函数的需求要比对拷贝构造函数或赋值运算符的需求更为明显。如果这个类需要一个析构函数,我们几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。

我们在练习中用过的HasPtr类是一个好例子。这个类在构造函数中分配动态内存。合成析构函数不会delete一个指针数据成员。因此,此类需要定义一个析构函数来释放构造函数分配的内存。

应该怎么做可能还有点儿不清晰,但基本原则告诉我们,HasPtr也需要一个拷贝构造函数和一个拷贝赋值运算符。

如果我们为HasPtr定义一个析构函数,但使用合成版本的拷贝构造函数和拷贝赋值运算符,考虑会发生什么:

class HasPtr{
public:
HasPtr(const std:;string&s=std::string()):
ps(new std::string(s)),i(0){}
~HasPtr(){deleteps;
//错误:HasPtr需要一个拷贝构造函数和一个拷贝赋值运算符
//其他成员的定义,如前
};在这个版本的类定义中,构造函数中分配的内存将在HasPtr对象销毁时被释放。但不幸的是,我们引入了一个严重的错误!这个版本的类使用了合成的拷贝构造函数和拷贝赋值运算符。这些函数简单拷贝指针成员,这意味着多个HasPtr对象可能指向相同的内存:HasPtr f(HasPtr hp)// HasPtr是传值参数,所以将被指贝
{HasPtr ret=hp;//拷贝给定的HasPt//处理retreturn ret;//ret和hp被销毁
}当f返回时,hp和ret都被销毁,在两个对象上都会调用HasPtr的析构函数。此析构函数会delete ret和hp中的指针成员。但这两个对象包含相同的指针值。此代码会导致此指针被delete两次,这显然是一个错误。将要发生什么是未定义的。此外,f的调用者还会使用传递给f的对象:```cpp
HasPtr("some values");
f(p);//当王结束时,p指向的肉存被释放
HasPtr(p);//现在p和q都指向无效内存!

p以及q指向的内存不再有效,在hp(或ret!)销毁时它就被归还给系统了。

需要拷贝操作的类也需要赋值操作,反之亦然

虽然很多类需要定义所有(或是不需要定义任何)拷贝控制成员,但某些类所要完成的工作,只需要拷贝或赋值操作,不需要析构函数。作为一个例子,考虑一个类为每个对象分配一个独有的、唯一的序号。这个类需要一个拷贝构造函数为每个新创建的对象生成一个新的、独一无二的序号。除此之外,这个拷贝构造函数从给定对象拷贝所有其他数据成员。这个类还需要自定义拷贝赋值运算符来邀

免将序号赋予目的对象。但是,这个类不需要自定义析构函数。

这个例子引出了第二个基本原则:如果一个类需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值运算符。反之亦然一一如果一个类需要一个拷贝赋值运算符,几乎可以肯定它也需要一个拷贝构造函数。然而,无论是需要拷贝构造函数还是需要拷贝赋值运算符都不必然意味着也需要析构函数。

使用=default

我们可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本

class Sales_data{
public:
//拷贝控制成员;使用default
Sales_data()=default;
Sales_data(const Sales_data&)=default;
Sales_data& operator=(const Sales_data&);
~Sales_data()=default;
//其他成员的定义,如前
Sales_data &Sales_data::operator=(const Sales_data&)=default;

当我们在类内用=default修饰成员的声明时,合成的函数将隐式地声明为内联的(就像任何其他类内声明的成员函数一样)。如果我们不希望合成的成员是内联函数,应该只对成员的类外定义使用=default,就像对拷贝赋值运算符所做的那样。

阻止拷贝

虽然大多数类应该定义(而且通常也的确定义了)拷贝构造函数和拷贝赋值运算符,但对某些类来说,这些操作没有合理的意义。在此情况下,定义类时必须采用某种机制阻止拷贝或赋值。例如,iostream类阻止了拷贝,以避免多个对象写入或读取相同的IO缓冲。为了阻止拷贝,看起来可能应该不定义拷贝控制成员。但是,这种策略是无效的:如果我们的类未定义这些操作,编译器为它生成合成的版本。

定义删除的函数

在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数(deleted function)来阻止拷贝。删除的函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上=delete来指出我们希望将它定义为删除的:

struct NoCopy{
NoCopy()=default;//使用合成的默认构造出数
NoCopy(const NoCopy&)=delete;//阻止拷贝
NoCopy&operator=(const NoCopy&)=delete;//阻止赋值
~NoCopy() = default; // 使用合成的析构函数
// 其他成员

一个删除了析构函数的类型,编译器将不允许定义该类型的变量或创建该类的临时对象。而且,如果一个类有某个成员的类型删除了析构函数,我们也不能定义该类的变量或临时对象。因为如果一个成员的析构函数是删除的,则该成员无法被销毁。而如果一个成员无法被销毁,则对象整体也就无法被销毁了。

对于删除了析构函数的类型,虽然我们不能定义这种类型的变量或成员,但可以动态分配这种类型的对象。但是,不能释放这些对象:

struct NoDtor{
NoDtor()=default;//使用合成默认构造函数
~NoDtor()=delete;//我们不能销毁NoDtor类型的对象
NoDtor nd;//错误:NoDtor的析构函数是刚除的
NoDtor*p=new NoDptor();//正确:但我们不能delete p
delete p;//错误:NoDtor的析构函数是剔除的
}

合成的拷贝控制成员可能是删除的

如前所述,如果我们未定义拷贝控制成员,编译器会为我们定义合成的版本。类似的,如果一个类未定义构造函数,编译器会为其合成一个默认构造函数。对某些类来说,编译器将这些合成的成员定义为删除的函数:

  • 如果类的某个成员的析构函数是删除的或不可访问的(例如,是private的),则类的合成析构函数被定义为删除的。
  • 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的。
  • 如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。
  • 如果类的树个成员的拷贝赋值运算符是删除的或不可访问的,或是类有一个const的或引用成员,则类的合成拷贝赋值运算符被定义为删除的。
  • 如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器,或是类有一个const成员,它没有类内初始化器且其类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。

本质上,这些规则的含义是:如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。

一个成员有删除的或不可访问的析构函数会导致合成的默认和拷贝构造函数被定义为删除的,这看起来可能有些奇怪。其原因是,如果没有这条规则,我们可能会创建出无法销毁的对象。

对于具有引用成员或无法默认构造的const成员的类,编详器不会为其合成默认构造函数,这应该不奇怪。同样不出人意料的规则是:如果一个类有const成员,则它不能使用合成的拷贝赋值运算符。毕竟,此运算符试图赋值所有成员,而将一个新值赋予一个const对象是不可能的。

虽然我们可以将一个新值赋予一个引用成员,但这样做改变的是引用指向的对象的值,而不是引用本身。如果为这样的类合成拷贝赋值运算符,则赋值后,左侧运算对象仍然指向与赋值前一样的对象,而不会与右侧运算对象指向相同的对象。由于这种行为看起来并不是我们所期望的,因此对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。

本质上,当不可能拷贝、赋值或销毁类的成员时,类的合成拷贝控制成员就被定义为删除的。

private拷贝控制

在新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝:

class PrivateCopy{
//无访问说明符;接下来的成员默认为private的;
//拷贝控制成员是private的,因此普通用户代码无法访问
PrivateCopy(const PrivateCopy&);
PrivateCopy&operator=(const PrivateCopy&);
//其他成员
public:
PrivateCopy()=default;//使用合成的默认构造出数
~PrivateCopy();//用户可以定义此类型的对象,但无法拷贝它们
}

由于析构函数是public的,用户可以定义PrivateCopy类型的对象。但是,由于拷贝构造函数和拷贝赋值运算符是private的,用户代码将不能拷贝这个类型的对象。但是,友元和成员函数仍旧可以拷贝对象。为了阻止友元和成员函数进行拷贝,我们将这些拷贝控制成员声明为private的,但并不定义它们。

声明但不定义一个成员函数是合法的,对此只有一个例外。试图访问一个未定义的成员将导致一个链接时错误。通过声明(但不定义)private的拷贝构造函数,我们可以预先阻止任何拷贝该类型对象的企图:试图拷贝对象的用户代码将在编译阶段被标记为错误;成员函数或友元函数中的拷贝操作将会导致链接时错误。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面经

Java 知识点总结 1. 封装&#xff0c;继承和多态 封装&#xff1a; “高内聚&#xff0c;低耦合”&#xff0c;隐藏内部实现细节&#xff0c;只通过接口开放部分使用权限给外部。继承&#xff1a; 主要是提高代码复用性&#xff0c;通过子类继承父类&#xff0c;来增加功能扩…

常见的限流算法有哪些?

好的&#xff0c;关于这个问题&#xff0c;我会从几个方面来回答。 首先&#xff0c;限流算法是一种系统保护策略&#xff0c;主要是避免在流量高峰导致系统被压垮&#xff0c;造成系统不可用的问题。 常见的限流算法有 5 种。 1. &#xff08;如图&#xff09;计数器限流&a…

GitHub获取token

获取token clone代码 git clone https://$tokengithub.com/*****/*****.git

公司网络安全组织结构

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 第17章 网络安全应急响应技术原理与应用 17.1 网络安全应急响应概述 居安思危&#xff0c;思则有备&#xff0c;有备无患。网络安全应急响应是针对潜在发生的网络…

《深度学习进阶》第7集:深度实战 通过训练一个智能体玩游戏 来洞察 强化学习(RL)与决策系统

深度学习进阶 | 第7集&#xff1a;深度实战 通过训练一个智能体玩游戏 来洞察 强化学习&#xff08;RL&#xff09;与决策系统 在深度学习的广阔领域中&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&#xff09;是一种独特的范式&#xff0c;它通过智能体与环境…

【web前端开发】CSS--CSS简介及其编写位置(上)

1、CSS简介 &#xff08;1&#xff09;CSS的全称为&#xff1a;层叠式样式表&#xff08;Cascading Style Sheets&#xff09; &#xff08;2&#xff09;CSS也是一种标记语言&#xff0c;用于给HTML结构设置样式&#xff0c;例如&#xff1a;文字大小、颜色、元素宽度等等…

计算机视觉|Swin Transformer:视觉 Transformer 的新方向

一、引言 在计算机视觉领域的发展历程中&#xff0c;卷积神经网络&#xff08;CNN&#xff09; 长期占据主导地位。从早期的 LeNet 到后来的 AlexNet、VGGNet、ResNet 等&#xff0c;CNN 在图像分类、目标检测、语义分割等任务中取得了显著成果。然而&#xff0c;CNN 在捕捉全…

UE4 组件 (对话组件)

制作一个可以生成对话气泡&#xff0c;显示对话台词的简单组件。这个组件要的变量&#xff1a;台词&#xff08;外部传入&#xff09;。功能&#xff1a;开始对话&#xff08;生成气泡UI&#xff09; &#xff0c;结束对话。 一、对话组件创建 二、开始对话事件 1、注意这里获…

WPF高级 | WPF 应用程序部署与发布:确保顺利交付到用户手中

WPF高级 | WPF 应用程序部署与发布&#xff1a;确保顺利交付到用户手中 一、前言二、部署与发布基础概念2.1 部署的定义与目的2.2 发布的方式与渠道2.3 部署与发布的关键要素 三、WPF 应用程序打包3.1 使用 Visual Studio 自带的打包工具3.2 使用第三方打包工具 四、发布到不同…

Vue3自定义hooks

Vue3自定义hooks 什么是自定义hooks 把一个功能的所有数据和方法写到同一个ts文件里。 命名规范&#xff1a;useXxx&#xff0c;小驼峰。 在每一个hooks中&#xff0c;都可以写onMounted、computed等属性&#xff0c;互不干扰。 体现出了组合式AI的优势&#xff0c;一个功能的…

Win7重装不翻车!ISO镜像安全下载渠道+BIOS设置避雷手册

一、写在前面&#xff1a;为什么你需要这份教程&#xff1f; 当电脑频繁蓝屏、系统崩溃甚至无法开机时&#xff0c;重装系统可能是最后的救命稻草。但市面上的教程往往存在三大痛点&#xff1a; ⚠️ 镜像来源不明导致系统被植入后门 ⚠️ 启动盘制作失败反复折腾 ⚠️ 操作失…

DeepSeek大模型 —— 全维度技术解析

DeepSeek大模型 —— 全维度技术解析 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff01;点我试试&#xff01;&#xff01; 文章目录 DeepSeek大模型 —— 全维度技术解析一、模型架构全景解析1.1…

AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型(LLM)应用开发平台

AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型&#xff08;LLM&#xff09;应用开发平台 目录 AGI 之 【Dify】 之 使用 Docker 在 Windows 端本地部署 Dify 大语言模型&#xff08;LLM&#xff09;应用开发平台 一、简单介绍 二、Docker 下载安…

外键 FK enable constraints enq:TM lock

ALTER TABLE X ENABLE NOVALIDATE CONSTRAINT XX&#xff1b; 如果referenced 表在DML 就会出现 enq:TM lock

深度学习语义分割数据集全景解析

一、语义分割任务概述 语义分割是计算机视觉领域的核心任务之一&#xff0c;目标是通过算法将图像中的每个像素精准划分到对应的语义类别&#xff08;如道路、车辆、行人等&#xff09;。高质量标注数据集是推动该领域发展的关键因素。本文将系统梳理主流数据集的技术特征与适…

【芯片设计】处理器芯片大厂前端设计工程师面试记录·20250307

面试问题和ds的参考答案 好的,我现在需要处理用户的请求,他们希望我扩展之前的回答,使其更详细、更丰富。首先,我需要回顾之前的回答,看看哪些部分可以进一步展开。用户提到“回答多说一些!”,这意味着他们希望每个问题的回答内容更深入,包含更多的技术细节、实际案例…

C++特殊类设计

目录 不能被拷贝的类 只能在堆上创建对象的类 构造函数私有化&#xff1a; 析构函数私有化&#xff1a; 只能在栈上创建对象的类 不能被继承的类 只能创建一个对象的类(单例模式) 设计模式&#xff1a; 单例模式&#xff1a; 饿汉模式&#xff1a; 懒汉模式&#xff1a; 线程…

Linux系统之配置HAProxy负载均衡服务器

Linux系统之配置HAProxy负载均衡服务器 前言一、HAProxy介绍1.1 HAProxy简介1.2 主要特点1.3 使用场景二、本次实践介绍2.1 本次实践简介2.2 本次实践环境规划三、部署两台web服务器3.1 运行两个Docker容器3.2 编辑测试文件3.3 访问测试四、安装HAProxy4.1 更新系统软件源4.2 安…

从零开始学机器学习——什么是机器学习

这个系列的文章旨在为初学者提供机器学习知识&#xff0c;避免使用专业术语和复杂的概念&#xff0c;以便更好地理解和应用。 首先给大家介绍一个很好用的学习地址&#xff1a;https://cloudstudio.net/columns 机器学习 在这里简要介绍机器学习&#xff1a;它利用真实世界或…

AI驱动的消费者体验优化——DeepBI如何用智能策略助力亚马逊卖家“俘获”消费者的心

在亚马逊这个竞争异常激烈的电商平台上&#xff0c;消费者体验已经成为决定卖家成败的关键因素之一。优质的消费者体验不仅能够提升客户满意度&#xff0c;还能加速口碑传播&#xff0c;带动销量持续增长。 今天&#xff0c;我们就来深入解析&#xff0c;DeepBI如何依托 AI 驱动…