C++多态、虚函数、纯虚函数、抽象类
多态的概念
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个简单的例子:抢红包,我们每个人都只需要点击一下红包,就会抢到金额。有些人能抢到几十元,而有些人只能抢到几元甚至几毛。也正说明了不同的人做相同的事,结果却不同,这就是多态。
在C++中有两种多态性,一种是静态的多态、一种是动态的多态;
静态的多态:函数重载,看起来调用同一个函数却有不同的行为。静态:原理是编译时实现。
动态的多态:一个父类的引用或指针去调用同一个函数,传递不同的对象,会调用不同的函数。动态:原理是运行时实现。
一、前言
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。下面的实例中,基类 Shape 被派生为两个类,如下所示:
#include <iostream>
using namespace std;class Shape {
public:void area(){cout << "Parent class area :" << endl;}
};
class Rectangle : public Shape {
public:void area(){cout << "Rectangle class area :" << endl;}
};
class Triangle : public Shape {
public:void area(){cout << "Triangle class area :" << endl;}
};void func(Shape& p) {p.area();
}
// 程序的主函数
int main()
{Rectangle Rec;// 调用矩形的求面积函数 areafunc(Rec);Triangle Tri;// 调用三角形的求面积函数 areafunc(Tri);return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Parent class area :
Parent class area :
导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,其余不变,如下所示:
#include <iostream>
using namespace std;class Shape {
public:virtual void area(){cout << "Parent class area :" << endl;}
};
class Rectangle : public Shape {
public:void area(){cout << "Rectangle class area :" << endl;}
};
class Triangle : public Shape {
public:void area(){cout << "Triangle class area :" << endl;}
};void func(Shape& p) {p.area();
}
// 程序的主函数
int main()
{Rectangle Rec;// 调用矩形的求面积函数 areafunc(Rec);Triangle Tri;// 调用三角形的求面积函数 areafunc(Tri);return 0;
}
修改后,当编译和执行前面的实例代码时,它会产生以下结果:
Rectangle class area :
Triangle class area :
此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。
正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
二、多态的定义及实现
1.多态的构成条件
在继承中要构成多态还有两个条件:
(1)必须通过基类的指针或者引用调用虚函数。
(2)被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
2.虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
一旦定义了虚函数,该基类的派生类中同名函数也自动成为了虚函数。也就是说在派生类中有一个和基类同名的函数,只要基类加了virtual修饰,派生类不加virtual修饰也是虚函数。虚函数只能是类中的一个成员函数,不能是静态成员或普通函数。
注意:我们在继承中为了解决数据冗余和二义性的问题,需要用到虚拟继承,关键字也是virtual,和多态中的virtual是没有关系的。
3.虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
通过对虚函数的重写,就能够实现多态:
#include<iostream>
using namespace std;//买票
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};//学生买票
class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};//军人买票
class Soldier : public Person
{
public:void BuyTicket() { cout << "优先-买票-半价" << endl; }};//构成多态,传的哪个类型的对象,调用的就是这个类型的虚函数 --- 跟对象有关
//不构成多态,调用就是P的类型 --- 跟类型有关
void Func(Person& p) //或void Func(Person* p)
{p.BuyTicket(); //p->BuyTicket();
}int main()
{Person ps;Func(ps); //没有任何身份去买票,一定是全价Student st;Func(st); //以学生的身份去买票,是半价Soldier so;Func(so); //以军人的身份去买票,是优先并且半价return 0;
}
4.虚函数重写的两个例外
(1).协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
另一种解释:
C++中的协变(Covariance)指的是派生类的返回类型可以是基类函数的返回类型的子类型。当一个派生类继承了一个基类,并且覆盖(override)了基类中的虚函数时,可以使用协变来改变返回类型。
具体而言,如果基类函数的返回类型是指针或引用,那么派生类中覆盖该函数时,返回类型可以是基类返回类型所指向或引用的类型的派生类型。
实现协变需满足以下条件:
- 基类中的函数必须是虚函数(使用 virtual 关键字声明)。
- 派生类中重写的函数必须具有相同的函数签名(函数名、参数列表和常量性)。
- 派生类中重写的函数的返回类型必须是基类函数返回类型的子类型。
示例:
引用自:C++协变(covariant)-CSDN博客
假设有一个基类 Animal 和两个派生类 Dog 和 Cat。Animal 类中有一个虚函数 makeSound(),它返回一个指向 Animal 对象的指针。在派生类 Dog 中,可以重写 makeSound() 函数并返回一个指向 Dog 对象的指针。同样,在派生类 Cat 中也可以重写 makeSound() 函数并返回一个指向 Cat 对象的指针。
#include <iostream>
class Animal {
public:virtual Animal* makeSound() {std::cout << "Animal makes a sound." << std::endl;return this;}
};
class Dog : public Animal {
public:virtual Dog* makeSound() {std::cout << "Dog barks." << std::endl;return this;}
};
class Cat : public Animal {
public:virtual Cat* makeSound() {std::cout << "Cat meows." << std::endl;return this;}
};
int main() {Animal* animal;Dog dog;Cat cat;animal = &dog;animal->makeSound(); // Output: "Dog barks."animal = &cat;animal->makeSound(); // Output: "Cat meows."return 0;
}
协变与多态的区别:
C++中协变和多态是密切相关的。多态(Polymorphism)指的是同一个函数在不同的对象上被调用时,可以表现出不同的行为方式。
在C++中,通过使用虚函数(virtual function),实现了运行时多态的语法机制。基类中的虚函数可以在派生类中被重写(覆盖),当派生类对象调用该虚函数时,会根据对象的实际类型来确定调用哪个虚函数。
而协变则指的是派生类可以改变继承自基类函数的返回类型,使得返回类型成为基类返回类型所指向或引用的类型的派生类型。
通过将协变和多态结合起来,我们可以在派生类中覆盖基类的虚函数,并且返回派生类特有的类型。这就允许我们在多态的情况下,在派生类中使用更具体的返回类型。
(2).析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
在C++中,析构函数是一种特殊的成员函数,用于在对象销毁时执行清理工作。通常情况下,析构函数会自动由编译器生成,默认执行对象的成员变量和基类的析构函数。
当需要对派生类进行额外的清理工作或资源释放时,可以通过重写(override)基类的析构函数来实现。
在派生类中重写析构函数需要遵循以下规则:
- 函数名与基类的析构函数完全相同。
- 参数列表为空。
- 返回类型为空(void)。
- 可以添加
override
关键字(可选),以显式地说明正在重写基类的析构函数。
以下是一个示例代码:
class Base {
public:virtual ~Base() {// 基类的析构函数}
};class Derived : public Base {
public:~Derived() override {// 派生类的析构函数,重写了基类的析构函数}
};
在上述代码中,基类Base
定义了一个虚析构函数,派生类Derived
通过重写基类的析构函数,实现了自己的清理逻辑。
需要注意的是,在继承关系中,如果基类的析构函数是一个虚函数,则派生类中的析构函数也应该声明为虚函数。这样,在使用基类指针或引用指向派生类对象,并通过该指针或引用调用析构函数时,能够正确地调用到派生类的析构函数。
总之,通过在派生类中重写基类的析构函数,可以实现额外的清理工作或资源释放。重写析构函数需要遵循特定的规则,并且建议将基类的析构函数声明为虚函数。
另一种解释:
(引用自:C++ 多态(一) : 多态的构成条件、final、override、协变、析构函数的重写、抽象类_c++ 多态 override-CSDN博客)
析构函数虽然函数名不同,但是也能构成重写,因为站在编译器的视角,他所调用的析构函数名称都叫做destructor。
为什么编译器要通过这种方式让析构函数也能构成重写呢?
假设我用一个基类指针或者引用指向派生类对象,如果不构成多态会怎样?
class Human
{
public:~Human(){cout << "~Human()" << endl;}
};class Student : public Human
{
public:~Student(){cout << "~Student()" << endl;}
};int main()
{Human* h = new Student;delete h;return 0;
}
输出结果:
~Human()
分析:
上述代码只会调用类Human的析构函数,即如果不构成多态,那么指针是什么类型的,就会调用什么类型的析构函数,这也就导致了一种情况,如果派生类的析构函数中有资源释放,而这里却没有释放掉那些资源,就会导致内存泄漏的问题。
所以为了防止这种情况,必须要将析构函数定义为虚函数。这也就是编译器将析构函数重命名为destructor的原因。
class Human
{
public:virtual ~Human(){cout << "~Human()" << endl;}
};class Student : public Human
{
public:virtual ~Student() // 该virtual关键字可省略{cout << "~Student()" << endl;}
};int main()
{Human* h = new Student;delete h;return 0;
}
输出结果:
~Student()
~Human()
5.C++11 override和final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了 override 和 final 两个关键字,可以帮助用户检测是否重写。
(1) final
在C++11标准中,final
是一个关键字,用于禁止继承和覆盖类的虚函数。当一个类或者一个类的成员函数被声明为final
时,意味着它不能再被其他类继承或者它的虚函数不能被派生类覆盖。
使用final
关键字的好处是:
- 可以增强代码的安全性:使用
final
关键字可以防止不恰当的继承和覆盖。 - 可读性:使用
final
关键字可以增强代码的可读性和可维护性,明确了类或函数的意图。
final:修饰虚函数,表示该虚函数不能再被重写。
#include<iostream>
class Car
{
public:virtual void Drive() final{}
};class Benz :public Car
{
public:virtual void Drive() override //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive();
}
上述程序因为final关键字的存在会报错,报错原因是:
final:修饰类,表示该类不能再被继承。
示例:
#include<iostream>
class Car final
{
public:virtual void Drive() {}
};class Benz :public Car
{
public:virtual void Drive() //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive();
}
上述程序报错:
不能将final用于基类,否则程序报错!
(2) override
在C++中,override
是一个特殊的关键字,用于显式地标识派生类中的函数是覆盖(override)基类中的虚函数。
当派生类中的函数与基类中的虚函数具有相同的名称、参数列表和返回类型时,可以使用override
关键字来明确指示该函数是对基类函数的覆盖。
使用override
关键字的好处是:
- 错误检查:编译器会在编译时检查是否存在函数覆盖错误。如果派生类中使用了
override
关键字,但没有正确地覆盖基类中的虚函数,编译器将报错。 - 可读性:使用
override
关键字可以增强代码的可读性和可维护性,明确了派生类函数的意图。
下面是一个示例代码:
#include<iostream>
class Car
{
public:virtual void Drive(){}
};class Benz :public Car
{
public:virtual void Drive() override //检查是否完成重写{std::cout << "Benz-舒适" << std::endl;}
};
int main() {Benz benz;benz.Drive(); // Benz-舒适
}
三、抽象类
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
在虚函数的后面写上 = 0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
class Shape {public:// pure virtual functionvirtual int area() = 0;
};
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
包括纯虚函数的类叫做抽象类,也叫接口类,抽象类不能实例化出对象。
示例:
#include<iostream>
//抽象类
class Car
{
public:virtual void Drive() = 0;//纯虚函数
};int main()
{Car c;//抽象类不能实例化出对象return 0;
}
上述程序运行报错:
派生类继承后也不能实例化出对象。
示例:
#include<iostream>
class Car
{
public:virtual void Drive() = 0; // 纯虚函数
};
class Benz :public Car{};
int main()
{Benz b1;
}
上述程序运行出错:
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
示例:
#include<iostream>
class Car
{
public://纯虚函数一般只声明,不实现(可以实现,但没有价值,因为不能实例化出对象,可以定义指针或引用)virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){std::cout << "Benz-舒适" << std::endl;}
};class BMW :public Car
{
public:virtual void Drive(){std::cout << "BMW-操控" << std::endl;}
};int main()
{//派生类只有重写了纯虚函数才能实例化出对象Benz b1;BMW b2;//通过基类的指针去调用不同对象的函数Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}
输出结果:
Benz-舒适
BMW-操控
接口继承和实现继承
- 普通函数的继承是一种实现继承,派生类继承了基类的普通成员函数,可以使用函数,继承的是函数的实现。
- 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
- 所以如果不实现多态,不要把函数定义成虚函数。
参考自(很值得学习):
【精选】【C++】—— 多态_c++多态_霄沫凡的博客-CSDN博客
注:参考内容只是为了自身学习,并无其他想法!!!
相关文章:

C++多态、虚函数、纯虚函数、抽象类
多态的概念 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 举个简单的例子:抢红包,我们每个人都只需要点击一下红包,就会抢到金额。有些人能…...
20231019_vue学习
引入vue.js: <script src"https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> vue.js <script src"https://cdn.staticfile.org/vue-router/2.7.0/vue-router.min.js"></script> 路由vue模板语法 v-html:添加html模板…...

熟练使用 Redis 的五大数据结构:Java 实战教程
入门 入门阶段主要记住 Redis 的命令,熟练使用 Redis 的 5 大数据结构就可以了。 如果没有 Redis 环境,可以直接通过这个网址https://try.redis.io/,很赞,它会给你模拟一个在线的环境可供你尽情使用! 熟练使用Redis的…...

【Linux】kill 命令使用
经常用kill -9 XXX 。一直在kill,除了kill -9 -15 ,还能做什么?今天咱们一起学习一下。 kill 命令用于删除执行中的程序或工作。 kill命令 -Linux手册页 命令选项及作用 执行令 man kill 执行命令结果 参数 -l 信号,若果…...
面试-Redis-缓存雪崩
问:什么是缓存雪崩 ? 答:缓存过期是指设置缓存时都采用了同一过期时间,导致缓存在莫一时刻同时失效,从而请求全部全部打到数据库中,导致数据库压力过大而挂机。 它与缓存击穿的区别是:缓存击穿是一个key…...

AI全栈大模型工程师(九)Function Calling 的机制
文章目录 Function Calling 的机制Function Calling 示例 1:加法计算器Function Calling 实例 2:四则混合运算计算器后记Function Calling 的机制 Function Calling 示例 1:加法计算器 需求:用户输入任意可以用加法解决的问题,都能得到计算结果。 # 加载环境变量import o…...

音乐制作软件 Ableton Live 11 Suite mac中文版功能介绍
Ableton Live 11 Suite mac是一款专业级别的音乐制作软件,它提供了多种音乐制作和编辑功能,可以帮助用户创建各种音乐作品。界面简单直观,可以方便地进行各种音乐制作操作。它提供了丰富的音乐制作工具和功能,如录音、采样、编曲、…...
v-model和.sync区别
在vue2中提供了.sync修饰符,但是在vue3中不再支持.sync,取而代之的是v-model。 1.在vue2中v-model和.sync区别: 1.相同点:都是语法糖,都可以实现父子组件中的数据的双向通信。 区别在于往回传值的时候. sync 的 $…...
django cloudflare csrf 403
网站套了cloudflare flare发现登录接口403了,csrf验证失败, debug设置为False 详细报错如下: Reason given for failure: Referer checking failed - https://xxx/login does not match any trusted origins.In general, this can occur w…...
Hive 中级练习题(40题 待更新)
前言 最近快一周没更了,主要原因是最近在忙另一件事情(关于JavaFX桌面软件开发),眼看大三上一半时间就要过去了,抓紧先学Hive,完了把 Spark 剩下的补了,还有 Kafka、Flume,任务还是…...
核酸检测人员安排
题目描述: 在系统、网络均正常的情况下组织核酸采样员和志愿者对人群进行核酸检测筛查。每名采样员的效率不同,采样效率为N人/小时。由于外界变化,采样员的效率会以M人/小时为粒度发生变化,M为采样效率浮动粒度,M=N10%,输入保证N10%的结果为整数。采样员效率浮动规则:采…...
Vue组件间传值
一、父传子 子组件中定义一个props,用来取出父组件传来的值 <script>export default {props:[msg] //子组件定义props} </script> 在父组件中对子组件的自定义属性绑定父组件的变量 <template><div class"parent">//子组件&a…...

《低代码指南》——维格云和Airtable的比较
Airtable 什么是Airtable Airtable 是一个任务管理应用程序,它合并了电子表格、数据存储和模板,以帮助组织构建他们的工作流程。 适用于哪些企业/组织/人群 根据 Airtable 网站,该工具被超过 200,000 个组织的团队使用。 维格表与Airtable相比如何 Airtable作为…...

牛客:NC59 矩阵的最小路径和
牛客:NC59 矩阵的最小路径和 文章目录 牛客:NC59 矩阵的最小路径和题目描述题解思路题解代码 题目描述 题解思路 动态规划,递推公式:matrix[i][j] min(matrix[i-1][j], matrix[i][j-1]) 题解代码 func minPathSum( matrix [][…...

20231017定时任务
1. 构建定时任务 表达式生成 在线Cron表达式生成器 1.1 启动类 1.2 测试范例 描述: 1,将该类用Component描述,交给spring管理. 2,定时任务方法用Scheduled+cron表达式描述 2. 定时任务的弊端和优化方案 1.假如有一个定时任务,每小时检查关闭超时未支付订单,当10…...

通讯录和内存动态管理
目录 (通讯录)动态增长版 实现效果 找单身狗 题目 源码 思路 三个内存函数的模拟实现 模拟实现strncpy 模拟实现strncat 模拟实现atoi (通讯录)动态增长版 该版本通讯录在原版的基础上增加了检查容量函数,实现了通讯录的动态…...
安全渗透测试之网络基础知识(IP地址)
#1.IP地址介绍 注意:不同局域网需要有不同的网络部分,通过网络部分区别出网段/网络; 局域网内部,主机部分不能一样,否则会出现地址冲突 范围:0.0.0.0-255.255.255.255 表示:点分十进制 组成:由网络部分和主机部分组成 192.168.1.1 1.1.1.1 255.254.188.2 二进制:00000…...

dubbogo-1 基础rpc服务
文章目录 基本环境处理编译pb接口开启rpc调用业务观察qa1 能取出protoc里面的字段值吗? 基本环境处理 https://cn.dubbo.apache.org/zh-cn/overview/quickstart/go/install/ 这里没有 protoc-gen-go --version 执行 go get -u github.com/golang/protobuf/protoc…...

分布式缓存Spring Cache
一、缓存里的数据如何和数据库的数据保持一致? 缓存数据一致性1)、双写模式2)、失效模式1、缓存数据一致性-双写模式 2、 缓存数据一致性-失效模式 我们系统的一致性解决方案: 1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新 2、读写数据…...

CI2454 2.4g无线MCU芯片应用
Ci2454集成MCU芯片 | Ci2454是一款集成无线收发器和 8 位 RISC(精简指令集)MCU 的SOC芯片。 #Ci2454芯片 集成MCU芯片# 中国芯片# 无线收发器特性: 工作在 2.4GHz ISM 频段 调制方式:GFSK/FSK 数据速率:2Mbps/1Mbps…...
centos中的ulimit命令
centos中的ulimit命令 ulimit的作用CENTOS系统文件配置配置文件地址配置格式 配置方法 ulimit的作用 ulimit用于限制shell启动进程所占用的资源,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell进程创建文件的大小、内存锁住的…...
Mac 双系统
准备——Windows10 ISO文件下载 下载地址:https://msdn.itellyou.cn 操作系统 Win10-1903镜像 复制链接迅雷下载 第一步——查看系统磁盘剩余空间 打开“启动台”找到“其他”文件夹,打开“磁盘工具”(剩余空间要大于40GB) 第二…...

每日算法-250605
每日算法 - 20240605 525. 连续数组 题目描述 给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。 思路 前缀和 哈希表 解题过程 核心思想是将问题巧妙地转换为寻找和为特定值的子数组问题。 转换问题:我…...

明基编程显示器终于有优惠了,程序员快来,错过等一年!
最近618的活动已经陆续开始了,好多人说这是买数码产品的好时候,作为一名资深程序员,我做了不少功课,决定给自己升级办公设备,入手明基 RD 系列的显示器,这是市面上首家专注于我们程序员痛点和需求的产品&am…...
前端项目eslint配置选项详细解析
文章目录 1. 前言2、错误级别3、常用规则4、目前项目使用的.eslintrc.js 1. 前言 ESLint 是一个可配置的 JavaScript 代码检查工具,旨在帮助开发者发现并修复代码中的潜在问题,包括语法错误、逻辑错误以及风格不一致等问题。以下是其核心功能和特点…...
Linux中 SONAME 的作用
🧠 一、从 -lexample 到 SONAME ✅ 假设你有以下文件结构: /libexample.so → libexample.so.1 /libexample.so.1 → libexample.so.1.0.0 /libexample.so.1.0.0 # SONAME: libexample.so.1/libexample.so.2 → libexample.so.2.0.0 /libexample.so.2.0…...
在MyBatis中设计SQL返回布尔值(Boolean)有几种常见方法
方案一:使用COUNT查询存在性(推荐) <select id"checkUserExists" resultType"_boolean">SELECT COUNT(*) > 0 FROM users WHERE username #{username} </select> 说明: MySQL中COU…...

《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P38 变量复制(Variable Replication)》 的学习笔记,该系列教学视频为计算机工程师、程序员、游戏开发者、作家(Engineer, Programmer, Game Developer, Author)…...
枫之谷Artale端午节大当机----后端技术的巨大风险
枫之谷Artale在端午节活动造成大量玩家上线塞爆,进不去,甚至在最后时段大当机,造成数万玩家怒火。 这体现了后端技术的影响,它不像是前端技术只对少数人造成影响,只要一出事,就是大批的玩家一起面对崩溃的伺…...

wordpress免费主题网站
这是一款WordPress主题,由jianzhanpress开发,可以免费下载。专为中小微企业设计,提供专业的网站建设、网站运营维护、网站托管和网站优化等服务。主题设计简约、现代,适合多种行业需求。 主要特点: 多样化展示&#…...