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

怎样做1个网站/网络营销策略案例分析

怎样做1个网站,网络营销策略案例分析,建筑网络计划图,官方网站建设 磐石网络知名文章目录 一、多态二、对象的静态类型和动态类型三、虚函数和纯虚函数1、虚函数2、虚析构函数3、抽象基类和纯虚函数4、多态的原理 四、重载、覆盖(重写)、隐藏(重定义)的对比 一、多态 OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语,其含义是“多…

文章目录

  • 一、多态
  • 二、对象的静态类型和动态类型
  • 三、虚函数和纯虚函数
    • 1、虚函数
    • 2、虚析构函数
    • 3、抽象基类和纯虚函数
    • 4、多态的原理
  • 四、重载、覆盖(重写)、隐藏(重定义)的对比


一、多态

OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语,其含义是“多种形式”。我们把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无须在意它们的差异。引用或指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
当我们使用基类的引用或指针调用基类中定义的一个函数时,我们并不知道该函数真正作用的对象是什么类型,因为它可能是一个基类的对象也可能是一个派生类的对象。如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型。
另一方面,对非虚函数的调用在编译时进行绑定。类似的,通过对象进行的函数(虚函数或非虚函数)调用也在编译时绑定。对象的类型是确定不变的,我们无论如何都不可能令对象的动态类型与静态类型不一致。因此,通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本上。

❕ 当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。

二、对象的静态类型和动态类型

⚠️在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。

当我们使用存在继承关系的类型时,必须将一个变量或其他表达式的静态类型(static type)与该表达式表示对象的动态类型(dynamic type)区分开来。表达式的静态类型是在编译时确定的,它是变量声明时的类型或表达式生成的类型:动态类型则是变量或表达式表示的内存中的对象类型,在运行时才可知。

❕ 如果表达式既不是引用也不是指针,则它的动态类型永远与静态类型保持一致。

因此我们使用基类的引用或指针调用一个虚成员函数时会执行动态绑定,我们直到运行时才知道到底调用了哪个版本的虚函数,所以所有的虚函数都必须有定义。但是我们必须为每一个虚函数都提供定义,而不管它是否被用到了,这是因为连编译器也无法确定到底会使用哪个函数。

派生类可以继承其基类的成员,然而当遇到与类型相关的操作时,派生类必须对其重新定义。换句话说,派生类需要对这些操作提供自己的新定义以覆盖(override)从基类继承而来的旧定义。我们来看如下代码:

class A{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};
class B : public A{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(){B bb;B* p = &bb;p->test();		//B->1p->func();		//B->0bb.func();		//B->0A& a = bb;a.test();		//B->1a.func();		//B->1return 0;
}

当我们使用指向bb的指针p调用 test函数时,test函数中隐含的传入了 A* this因此在此处我们是多态调用。派生类用自己的新定义覆盖了从基类继承而来的旧定义,但是调用时仍使用的是基类的声明。下面 A& a 到底调用哪个版本的 func完全依赖于运行时绑定到它上面的动态类型。

虚函数与其他函数一样,虚函数也可以用有默认实参,如果某次虚函数调用使用了默认实参,则该实参指由本次调用的静态类型决定。

换句话说,如果我们通过基类的引用或指针调用函数。则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。此时,传入派生类函数的将是基类函数定义的默认实参。如果派生类函数依赖不同的实参,则程序结果将与我们的预期不符。

class A{ public: void test(float a) { cout << a; } }; class B :public A{ public: void test(int b){ cout << b; } }; 
int main() { A *a = new A; B *b = new B; a = b; a->test(1.1);   //输出1.1
}

📔 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

调用的虚函数在运行时才会被解析,当某个虚函数通过指针或引用被调用时,编译器产生的代码直到运行时才会确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。

那么如果我们使用普通类型(非指针非引用)的表达式调用虚函数,在编译时就会将调用的版本确定下来。

那么我们就产生疑问,inline函数可以是虚函数吗?答案当然是可以,当我们使用普通类型调用虚函数时,具有inline属性。如果是多态调用,这个函数酒不再是inline,因为虚函数要放进虚表中去。

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载 。
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态。

三、虚函数和纯虚函数

1、虚函数

在C++语言中,基类必须将它的两种成员函数区分开来,一种是基类希望其派生类进行覆盖的函数:另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通常将其定义未虚函数(virtual)。当我们使用指针或引用调用虚函数时,该调用将被动态绑定。根据引用或指针所绑定的对象类型不同,该调用可能执行基类的版本,也可能执行某个派生类的版本。

基类通过在其成员函数的声明语句之前加上关键字 virtual使得该函数执行动态绑定。任何构造函数之外的非静态函数都可以是虚函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。

同样的,派生类中虚函数的返回类型也必须与基类函数匹配。该规则存在两个例外。

  1. 派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变

    class A {};
    class B : public A {};
    class Person {
    public:virtual A* f() { return new A; }
    };
    class Student : public Person {
    public:virtual B* f() { return new B; }
    };
    
  2. 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统-处理成destructor。(析构函数的重写,我们将在后文再进行叙述)

❕ 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

⚠️关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。 而且 virtual不能与static同时使用。静态成员一定是不被包含在对象中的静态成员属于整个类,不属于任何对象,所以在整体体系中只有一份。

⚠️静态成员函数与具体对象无关,属于整个类,核心关键是没有隐藏的this指针,可以通过类名::成员函数名 直接调用,此时没有this无法拿到虚表,就无法实现多态,因此不能设置为虚函数。

当然我们也可以会批虚函数的机制,在某些情况下我们可能希望对虚函数的调用不进行动态绑定,而是强迫其执行虚函数的某个特定版本。我们可以使用作用域运算符实现此目的:

class Base {
public:virtual void func() {cout << "Base::func" << endl;}
};
class Derived :public Base {
public:virtual void func() {cout << "Derived::func" << endl;}
};
int main(){Derived d;Base* pb = &d;pb->func();			//"Base::func" pb->Base::func();	//"Derived::func" return 0;
}

运行时的多态性可通过和虚函数实现。不可通过模板实现,因为模板属于编译时多态。编译时的多态性可通过函数重载实现。

class A {
public:virtual void f() { cout << "A::f()" << endl; }
};class B : public A {
private:virtual void f() {cout << "B::f()" << endl;}
};
int main()
{A* pa = (A*)new B;	//或 A* pa = new B;均合法pa->f();			//B::f()
}

此段代码编译正确,虽然子类函数为私有,但是多态仅仅是用子类函数的地址覆盖虚表,最终调用的位置不变,只是执行函数发生变化。不强制也可以直接赋值,因为赋值兼容规则作出了保证。


2、虚析构函数

继承关系中对基类拷贝控制最直接的影响是基类通常应该定义一个虚析构函数,这样我们就能动态分配继承体系中的对象了。

当我们delete一个动态分配的对象的指针时将执行析构函数。如果该指针指向继承体系中的某个类型,则有可能出现指针的静态类型与被删除对象的动态类型不符的情况。

例如,QutoeBulk_quote 的父类。我们 delete一个 Quote*类型的指针,则该指针有可能实际指向了一个Bulk_quote 类型的对象。如果这样的话,编译器就必须清楚它应该执行的是Bu1k_quote的析构函数。和其他函数一样,我们通过在基类中将析构函数定义成虚函数以确保执行正确的析构函数版本:

class Quote {
public:如果我们删除的是一个指向派生类对象的基类指针,则需要虚析构函数virtual ~Quote() = default;		动态绑定析构函数
};

和其他虚函数一样,析构函数的虚属性也会被继承。因此,无论Quote的派生类使用合成的析构函数还是定义自己的析构函数,都将是虚析构函数。只要基类的析构函数是虚函数,就能确保当我们 delete基类指针时将运行正确的析构函数版本:

Quote* itemP = new Quote;	//静态类型与动态类型一致
delete itemP;				//调用 Quote的析构函数
itemP = new Bulk_quote;		//静态类型与动态类型不一致
delete itemP				//调用Bulk guote的析构函数

⚠️如果基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针将产生未定义的行为。

析构函数需要构成重写,那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

如前所述,在析构函数体执行完成后,对象的成员会被隐式销毁。类似的,对象的基类部分也是隐式销毁的。因此,和构造函数及赋值运算符不同的是,派生类析构函数只负责销毁由派生类自己分配的资源:

class D :public Base {
public://Base::~Base被自动调用执行~D() {/*该处由用户定义清除派生类成员的操作*/ }
};

对象销毁的顺序正好与其创建的顺序相反:派生类析构函数首先执行,然后是基类的析构函数,以此类推,沿着继承体系的反方向直至最后。

那么我们在构造函数和析构函数中调用虚函数会发生什么呢?

如我们所知,派生类对象的基类部分将首先被构建。当执行基类的构造函数时,该对象的派生类部分是未被初始化的状态。类似的,销毁派生类对象的次序正好相反,因此当执行基类的析构函数时,派生类部分已经被销毁掉了。由此可知,当我们执行上述基类成员的时候,该对象处于未完成的状态。
为了能够正确地处理这种未完成状态,编译器认为对象的类型在构造或析构的过程中仿佛发生了改变一样。也就是说,当我们构建一个对象时,需要把对象的类和构造函数的类看作是同一个:对虚函数的调用绑定正好符合这种把对象的类和构造函数的类看成同一个的要求;对于析构函数也是同样的道理。上述的绑定不但对直接调用虚函数有效,对间接调用也是有效的,这里的间接调用是指通过构造函数(或析构函数)调用另一个函数。为了理解上述行为,不妨考虑当基类构造函数调用虚函数的派生类版本时会发生什么情况。这个虚函数可能会访问派生类的成员,毕竟,如果它不需要访问派生类成员的话,则派生类直接使用基类的虚函数版本就可以了。然而,当执行基类构造函数时,它要用到的派生类成员尚未初始化,如果我们允许这样的访问,则程序很可能会崩溃。

在此我们看一道选择题:

假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则()

A.A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址

B.A类对象和B类对象前4个字节存储的都是虚表的地址

C.A类对象和B类对象前4个字节存储的虚表地址相同

D.A类和B类中的内容完全一样,但是A类和B类使用的不是同一张虚表

此题选 B。为什么呢?

A.父类对象和子类对象的前4字节都是虚表地址。
B.A类对象和B类对象前4个字节存储的都是虚表的地址,只是各自指向各自的虚表。
C.不相同,各自有各自的虚表。
D.A类和B类不是同一类内容不同。

如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对于的虚函数版本。

⚠️派生类对象销毁时,先调用基类析构函数,后调用子类析构函数!


3、抽象基类和纯虚函数

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。含有纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

抽象类负责定义接口,而后续的其他类可以覆盖该接口。

class LPL{
public:virtual void name() = 0;
};
class EDG :public LPL {
public:virtual void name() { cout << "EDG" << endl; }
};
class LNG :public LPL
{
public:virtual void name() { cout << "LNG" << endl; }
};
int main(){LPL* pEDG = new EDG;pEDG->name();		//EDGLPL* pLNG = new LNG;pLNG->name();		//LNGreturn 0;
}

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实 现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

我们可以为纯虚函数提供定义,不过函数体必须定义在类的外部。也就是说,我们不能在类的内部为一个 =0 的函数提供函数体。若定义在类的内部,会出现错误:pure-specifier on function-definition。

4、多态的原理

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个_vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f 代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。那么派生类中这个表放了些什么呢?我们接着往下分析

class Base{
public:virtual void func1()  { cout << "Base::func1()" << endl; }
private:int _b = 1;char _ch = 'a';
};
int main(){cout << sizeof(Base) << endl;//12//有了虚函数后对象中会多一个指针,虚函数表指针
}

在这里插入图片描述

添加了两个函数后,类的大小仍不改变。

class Base{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
private:int _b = 1;char _ch = 'a';
};
int main(){cout << sizeof(Base) << endl;//12//有了虚函数后对象中会多一个指针,虚函数表指针Base bb;
}

⚠️虚函数表指针简称虚表指针。

在这里插入图片描述

我们增加一个派生类Derive去继承Base,且Derive中重写Func1。Base再增加一个虚函数Func2和一个普通函数Func3。代码如下后:

class Base{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
private:int _b = 1;char _ch = 'a';
};
class Derive :public Base{
public:virtual void func1() { cout << "Derive::func1()" << endl; }
private:int _d = 2;
}; 
void t()
{Base bb;	Derive dd;cout << sizeof(Base) << endl;//12cout << sizeof(Derive) << endl;//16
}

虚函数的重写也叫覆盖。 在这里插入图片描述

派生类对象dd中也有一个虚表指针,dd对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。

基类对象和派生类对象虚表是不一样的,这里我们发现Func1完成了重写,所以dd的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数 的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

即派生类由父类和派生类构成,父类中有虚表,子类中包含的父类(含有虚表)+子类自己的成员(无虚表)。另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。

虚表中存储的是虚函数的地址。虚函数和普通函数都存在代码段。

在此我们总结一下派生类的虚表生成:

  1. 先将基类中的虚表内容拷贝一份到派生类虚表中。
  2. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数。
  3. 派生类自己,新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

满足多态以后的函数调用,不是在编译时确定的,是运行 起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

那么虚函数表存在哪呢? 栈区?堆区?还是常量区? (虚表地址存在对象的头四个字节上),我们通过如下代码观察:

class Base {
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }void func3() { cout << "Base::func3()" << endl; }
private:int _b = 1;
};
class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1()" << endl; }
private:int _d = 2;
};
void t() {int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxx";printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);Base b;  Derive d;Base* pb = &b;Derive* pd = &d;printf("Base虚表地址:%p\n", *(int*)pb);printf("Derive虚表地址:%p\n", *(int*)pd);
}

在这里插入图片描述

从打印结果可以看出 虚表位于常量区。(vs和linux下都是)

虚函数表是class specific的,也就是针对一个类来说的,这里就如同类里面的static成员遍历,即它是属于一个类所有对象的,不是属于某个对象特有的,是一个类所有对象公有的。

虚表是什么阶段生成的?

虚表是在编译时期生成的,而虚表指针是在构造函数的初始化列表生成的。一个类的不同对象用的同一张虚表。

虚表是在编译时生成的。
在构造函数中,走初始化列表之前,初始化虚表指针。

我们可以通过如下代码打印类的虚表,大家可以拿来实验:

class Base {
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() {cout << "Base::func2()" << endl;}void func3() { cout << "Base::func3()" << endl; }
private:int _b = 1;
};
class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }
private:int _d = 2;
};
typedef void(*VF_PTR)();
void PrintVFT(VF_PTR vtf[])
{cout << " 虚表地址>" << vtf << endl;for (int i = 0; vtf[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vtf[i]);VF_PTR f = vtf[i];f();}cout << endl;
}
void t()
{Base b;Derive d;// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数///指针的指针数组,这个数组最后面放了一个nullptr// 1.先取b的地址,强转成一个int*的指针// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。// 4.虚表指针传递给PrintVTable进行打印虚表// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。VF_PTR* vTableb = (VF_PTR*)(*(int*)&b);PrintVFT(vTableb);VF_PTR* vTabled = (VF_PTR*)(*(int*)&d);PrintVFT(vTabled);
}

⚠️多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。

下面我们看一道关于继承的选择题,来帮我们理解:

假设D类先继承B1,然后继承B2,B1和B2基类均包含虚函数,D类对B1和B2基类的虚函数重写了,并且D类增加了新的虚函数,则:( )

A.D类对象模型中包含了3个虚表指针

B.D类对象有两个虚表,D类新增加的虚函数放在第一张虚表最后

C.D类对象有两个虚表,D类新增加的虚函数放在第二张虚表最后

D.以上全部错误

此题选 B。为什么呢?

A.D类有几个父类,如果父类有虚函数,则就会有几张虚表,自身子类不会产生多余的虚表,所以只有2张虚表。
C.子类自己的虚函数只会放到第一个父类的虚表后面,其他父类的虚表不需要存储,因为存储了也不能调用。


四、重载、覆盖(重写)、隐藏(重定义)的对比

在C++中,重载、覆盖(重写)和隐藏(重定义)都是面向对象编程中的概念,用于处理函数的多态性。下面对它们进行比较:

  1. 重载(Overloading)
    • 定义:重载是指在同一个作用域内,使用相同的函数名但具有不同的参数列表的情况。函数重载可以根据参数的类型、顺序和个数进行区分。
    • 特点:
      • 函数名相同,参数列表不同。
      • 返回值类型可以相同也可以不同。
      • 发生在同一个类或命名空间中。
  2. 覆盖(重写,Override)
    • 定义:覆盖是指在派生类中重新实现基类中已经存在的虚函数。通过在派生类中使用相同的函数名、参数列表和返回类型来覆盖基类的函数。(协变除外)
    • 特点:
      • 函数名、参数列表和返回类型相同。
      • 发生在继承关系中,基类函数必须声明为虚函数。
  3. 隐藏(重定义,Hide)
    • 定义:隐藏是指在派生类中定义了与基类中相同名称的非虚函数,从而隐藏了基类中的同名函数。隐藏并不涉及到动态绑定。
    • 特点:
      • 函数名相同,参数列表可以相同也可以不同。
      • 发生在继承关系中,两个基类和派生类的同名函数不构成重写就是重定义。

总结:

  • 重载发生在同一个类或命名空间中的函数之间,根据参数的类型、顺序和个数进行区分。
  • 覆盖发生在继承关系中,派生类重新实现了基类中的虚函数,函数名、参数列表和返回类型相同。
  • 隐藏发生在继承关系中,派生类定义了与基类中同名的非虚函数,基类中的同名函数被隐藏。

需要注意的是,覆盖只能发生在虚函数上,而隐藏可以发生在虚函数和非虚函数上。使用 virtual 关键字声明函数为虚函数,从而允许覆盖。使用作用域解析运算符 :: 可以指定访问被隐藏的基类函数。

相关文章:

【C++私房菜】面向对象中的多态

文章目录 一、多态二、对象的静态类型和动态类型三、虚函数和纯虚函数1、虚函数2、虚析构函数3、抽象基类和纯虚函数4、多态的原理 四、重载、覆盖(重写)、隐藏(重定义)的对比 一、多态 OOP的核心思想是多态性(polymorphism)。多态性这个词源自希腊语&#xff0c;其含义是“多…...

(done) 什么是特征值和特征向量?如何求特征值的特征向量 ?如何判断一个矩阵能否相似对角化?

什么是齐次方程&#xff1f; https://blog.csdn.net/shimly123456/article/details/136198159 行列式和是否有解的关系&#xff1f; https://blog.csdn.net/shimly123456/article/details/136198215 特征值和特征向量 参考视频&#xff1a;https://www.bilibili.com/video/BV…...

[rust] 11 所有权

文章目录 一 背景二 Stack 和 Heap2.1 Stack2.2 Heap2.3 性能区别2.4 所有权和堆栈 三 所有权原则3.1 变量作用域3.2 String 类型示例 四 变量绑定背后的数据交互4.1 所有权转移4.1.1 基本类型: 拷贝, 不转移所有权4.1.2 分配在 Heap 的类型: 转移所有权 4.2 Clone(深拷贝)4.3 …...

MySQL安装

文章目录 MYSQL安装一、下载二、解压三、配置1. 添加环境变量2. 初始化MySQL3. 注册MySQL服务4. 启动MySQL服务5. 修改默认账户密码 四、登录MySQL五、卸载MySQL MYSQL安装 一、下载 点开下面的链接&#xff1a;https://dev.mysql.com/downloads/mysql/ 点击Download 就可以下…...

AI时代显卡如何选择,B100、H200、L40S、A100、H100、V100 含架构技术和性能对比

AI时代显卡如何选择&#xff0c;B100、H200、L40S、A100、H100、V100 含架构技术和性能对比。 英伟达系列显卡大解析B100、H200、L40S、A100、A800、H100、H800、V100如何选择&#xff0c;含架构技术和性能对比带你解决疑惑。 近期&#xff0c;AIGC领域呈现出一片繁荣景象&a…...

R语言入门笔记2.5

数据预处理 R语言处理的数据多以数据框的形式出现。 预备操作 数据查看 > dim(x) [1] 16 3 #数据框有16行3列 > names(x) #查看数据框的变量名 [1] "X" "Z" "Y" > head(x,3) #查看前3行&#xff0c;若为-3则是查看后三行之…...

命令记录学习

1. 查看当前用户 cat /etc/passwd 添加test用户 useradd test -G root 查看test用户状态 id test 查看用户adaminstrator passwd -S adaminstrator 禁用用户adaminstrator passwd -l adaminstrator 密码策略设置文件地址 vi /etc/pam.d/system-auth 设…...

Python3 TCP 客户端

下面是一个简单的 Python TCP 客户端示例代码&#xff0c;用于与之前提到的 EchoServer 进行通信&#xff1a; import socketserver_address (localhost, 8888)# 创建 TCP 客户端套接字 client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)try:# 连接到服务器…...

图论(算法竞赛、蓝桥杯)--拓扑排序

1、B站视频链接&#xff1a;D01 拓扑排序_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N100010; int n,m,a,b; vector<int> e[N],tp; int din[N]; bool topsort(){queue<int> q;for(int i1;i<n;i){if(din[i]0)q.push(i);}…...

内网穿透(docker部署frp)

文章目录 内网穿透实现内网穿透的常见方法 frpfrp的实现原理frps服务端应用部署配置文件部署参数配置 frpc客户端部署 内网穿透 内网穿透是指在内部网络&#xff08;内网&#xff09;中实现穿透外网&#xff08;公网&#xff09;的通信。内网通常是指公司、组织或家庭等内部网…...

Java设计模式 | 简介

设计模式的重要性&#xff1a; 软件工程中&#xff0c;设计模式&#xff08;design pattern&#xff09;是对软件设计中普遍存在&#xff08;反复出现&#xff09;的各种问题&#xff0c;所提出的解决方案。 这个术语由埃里希 伽玛&#xff08;Erich Gamma&#xff09;等人在1…...

微信小程序video 点击自动全屏播放

//因为这个地址可能是图片也可能是视频 点击 图片可以预览&#xff0c;点击视频可放大全屏自动播放。 代码如下 <view v-else :class{contentImg: x.picture.length0} style"margin-top: 10px;"v-for"(x1, y1) in x.picture" :key"y"><…...

MySQL基础面试问题(一)

MySQL面试问题的标准回答&#xff1a; 什么是数据库范式&#xff1f;MySQL中常见的范式有哪些&#xff1f; 标准回答&#xff1a;数据库范式是一组规则&#xff0c;用于设计关系型数据库的结构&#xff0c;以减少数据冗余和提高数据的一致性。常见的数据库范式包括第一范式&…...

抖音爬虫批量视频提取功能介绍|抖音评论提取工具

抖音爬虫是指通过编程技术从抖音平台上获取视频数据的程序。在进行抖音爬虫时&#xff0c;需要注意遵守相关法律法规和平台规定&#xff0c;以确保数据的合法获取和使用。 一般来说&#xff0c;抖音爬虫可以实现以下功能之一&#xff1a;批量视频提取。这个功能可以用于自动化地…...

mac真的安装不了vmware吗 mac如何安装crossover crossover序列号从哪里买 购买正版渠道

有些用户可能想在mac上运行一些只能在windows上运行的软件&#xff0c;比如游戏、专业软件等。这时候&#xff0c;就需要用到虚拟机技术&#xff0c;也就是在mac上安装一个可以模拟其他操作系统的软件&#xff0c;比如vmware或者crossover。那么&#xff0c;mac真的安装不了vmw…...

C语言-指针详解速成

1.指针是什么 C语言指针是一种特殊的变量&#xff0c;用于存储内存地址。它可以指向其他变量或者其他数据结构&#xff0c;通过指针可以直接访问或修改存储在指定地址的值。指针可以帮助我们在程序中动态地分配和释放内存&#xff0c;以及进行复杂的数据操作。在C语言中&#…...

C# RabbitMQ使用--动态更新

在使用 RabbitMQ 的 C# 客户端库时&#xff0c;只需要在应用程序中引用 RabbitMQ.Client 包&#xff0c;而不需要单独安装 RabbitMQ 服务器。 RabbitMQ.Client 是一个用于在 C# 中与 RabbitMQ 服务器进行通信的客户端库。通过使用该客户端库&#xff0c;你可以在应用程序中创建…...

Unity资源加密解决方案

据统计&#xff0c;全球范围内超过50%的游戏均使用Unity创作而成&#xff0c;作为游戏开发市场第一大游戏引擎占有者&#xff0c;Unity已经全面覆盖到各个游戏平台。 全球游戏引擎市场占有率 由于体量庞大&#xff0c;Unity游戏已成为受游戏黑灰产攻击的重灾区&#xff0c;因游…...

使用 yarn 的时候,遇到 Error [ERR_REQUIRE_ESM]: require() of ES Module 怎么解决?

晚上回到家&#xff0c;我打开自己的项目&#xff0c;执行&#xff1a; cd HexoPress git pull --rebase yarn install yarn dev拉取在公司 push 的代码&#xff0c;然后更新依赖&#xff0c;最后开始今晚的开发时候&#xff0c;意外发生了&#xff0c;竟然报错了&#xff0c;…...

多维时序 | Matlab实现基于VMD-DBO-BiLSTM、VMD-BiLSTM、BiLSTM的多变量时间序列预测

多维时序 | Matlab实现基于VMD-DBO-BiLSTM、VMD-BiLSTM、BiLSTM的多变量时间序列预测 目录 多维时序 | Matlab实现基于VMD-DBO-BiLSTM、VMD-BiLSTM、BiLSTM的多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于VMD-DBO-BiLSTM、VMD-BiLSTM、…...

实战营第四节笔记

这节课包含四大部分&#xff0c;为finetune简介、xtuner介绍、使用8GB玩转LLM和动手实践环节。 LoRA和QLoRA是两种很重要的方法&#xff0c;对微调模型、减少内存使用非常有效。 后面是XTuner的介绍。 之后是动手实践。可参考https://github.com/InternLM/tutorial/blob/ma…...

【网络】HTTPS协议原理

目录 一、HTTPS是什么&#xff1f; 二、常见加密方式 2.1、只使用对称加密 2.2、只使用非对称加密 2.3、双方都使用非对称加密 2.4、非对称加密对称加密(中间人攻击情况) 2.5、非对称加密对称加密证书认证 三、数据摘要&&数据指纹 四、CA证书 五、数字签名 六…...

C#常识篇(一)

面向对象的三大特性 继承&#xff1a;子类通过继承父类来获取基础特性&#xff0c;并且能够基于父类进行扩展以及提升代码的复用性。继承具有传递性&#xff0c;例如A继承自B&#xff0c;C继承自A&#xff0c;那么C就间接继承自B。在C#中&#xff0c;每个类仅允许继承一个父类。…...

Linux使用C语言获取进程信息

Linux使用C语言获取进程信息 Author: OnceDay Date: 2024年2月22日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可查看专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文档: Linux proc目录详解_/proc/mounts-CSDN博客Linux下/proc目录介绍 - 知乎 (zhihu.com)Linux内…...

tomcat通过JAVA_OPTS注入自定义变量 —— 筑梦之路

背景说明 tomcat部署的java应用在k8s集群或容器中&#xff0c;想要给tomcat传自定义变量&#xff0c;应该如何实现&#xff1f; 解决方法 1. 在k8s集群或容器环境中通过env或者configmap方式添加自定义的环境变量 比如&#xff1a; my_key: aaaa 2. tomcat下新增脚本&am…...

Linux——简单的Shell程序

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、Shell程序思路二、Shell代码展示 一、Shell程序思路 用下图的时间轴来表示事件的发生次序…...

计算机网络-网络互联与互联网(一)

1.常用网络互联设备&#xff1a; 1层物理层&#xff1a;中继器、集线器2层链路层&#xff1a;网桥、交换机3层网络层&#xff1a;路由器、三层交换机4层以上高层&#xff1a;网关 2.网络互联设备&#xff1a; 中继器Repeater、集线器Hub&#xff08;又叫多端口中继器&#xf…...

Android 解决后台服务麦克风无法录音问题

Android 解决后台无法录音问题 问题分析问题来源解决方案1. 修改清单文件:`AndroidManifest.xml`2. 修改启动服务方式3. 服务启动时创建前台通知并且指定前台服务类型参考文档最后我还有一句话要说我用心为你考虑黄浦江的事情,你心里想的却只有苏州河的勾当 问题分析 安卓9.…...

【计网】TCP的三次握手四次挥手

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 三次握手&#xff08;Connection Establishment&#xff09; 四次挥手&#xff08;Connection Termination&#xff09; 结语 我…...

android pdf框架-4,分析vudroid源码2

继续分析源码 阅读器的入口 MainBrowserActivity,这个是主ui,由于代码较旧,所以没有处理sdcard的权限. 一般阅读器申请整个卡的读写. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (!Environment.isExternalStorageManager()) {Intent intent = new Intent…...