C++之单例模式
目录
1. 请设计一个类,只能在堆上创建对象
2. 请设计一个类,只能在栈上创建对象
3.请设计一个类,不能被拷贝
C++98
C++11
4. 请设计一个类,不能被继承
C++98
C++11
5. 请设计一个类,只能创建一个对象(单例模式)
设计模式
单例模式
单例模式的两种实现方式
饿汉模式
懒汉模式
单例对象的释放
懒汉模式与饿汉模式的优缺点
现代懒汉模式的写法
1. 请设计一个类,只能在堆上创建对象
实现方式:
- 1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}HeapOnly(const HeapOnly& ho) = delete; //防止通过拷贝构造开在栈上
private:HeapOnly():_a(0){}private:int _a;
}; 
2. 请设计一个类,只能在栈上创建对象
目前看似杜绝在堆上创建出对象,但是我们却可以通过这样的方式在堆上创建出对象 。

我这里的new虽然没有调用构造函数,但是调用了拷贝构造,又因为获取对象的函数是传值返回,所以我们不能删除拷贝构造,但是我们却可以屏蔽new,因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。 一个类可以重载它专属的operator new,没有重载之前调用的是全局的new,但是重载专属的operator new以后,调用的就是专属的operator new。
class StackOnly
{
public:static StackOnly CreateObj(){return StackOnly(); //不能删除拷贝构造,因为这里是传值返回}void* operator new(size_t size)=delete;void operator delete(void* p) = delete;
private:StackOnly():_a(0){}private:int _a;
};
3.请设计一个类,不能被拷贝
C++98
- 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
}; 原因:
- 1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- 2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11
- C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};
4. 请设计一个类,不能被继承
C++98
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
}; C++11
class A NonInherit
{// ....
}; 5. 请设计一个类,只能创建一个对象(单例模式)
设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
迭代器就是一种设计模式,被广泛的用于数据结构中。这个数据结构可能是个数组,图,哈希表,二叉树等等,如果要去访问它的话要把数据结构的底层全部都暴露出来,但是C++是不希望暴露结构的,暴露的缺点在于:1.访问不方便(需要熟悉底层结构) 2.暴露底层结构,别人直接访问修改数据,不方便管理。所以就出现了迭代器模式,好处在于:1.统一方式封装访问结构。底层结构不暴露。2.可以使用统一的方式轻松访问容器,不关心底层是树,还是链表等。
单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
我们直接从实例入手,对于下面的这段代码(我们是采取分成三个部分写的,快排的定义放在一个文件里,实现放在一个文件里,最终的调用也放在一个文件里),我要进行统计快速排序递归调用次数。我们一般的思路就是定义一个全局变量。每次递归,这个全局变量就++,最终拿到这个全局变量就是一共递归调用的次数。
Singleton.h
#pragma once#include<iostream>
#include<time.h>
using namespace std;int callCount = 0; //统计次数的全局变量
// 统计快速排序递归调用次数
void QuickSort(int* a, int left, int right);
Singleton.cpp
#include"Singleton.h"
void QuickSort(int* a, int left, int right)
{++callCount;if (left >= right){return;}int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){swap(a[cur], a[prev]);}++cur;}swap(a[prev], a[keyi]);keyi = prev;// [left, keyi-1]keyi[keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}
test.cpp
#include"Singleton.h"void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();}int begin5 = clock();QuickSort(a1, 0, N - 1);int end5 = clock();printf("QuickSort:%d\n", end5 - begin5);printf("QuickSort Call Time:%d\n", callCount);free(a1);}
int main()
{TestOP();return 0;
}
但是对这段代码进行编译的时候,会有报错

为什么会报一个全局变量callCount重定义的错误呢?
Singleton.h中有一个callCount的定义,Singleton.h被Singleton.cpp和test.cpp都所包含。.h文件在.cpp文件中展开,.cpp会生成两个.obj(linux下叫做.o)然后这两个.obj中都有一个叫做callCount的变量。编译器链接的时候是都要包这些东西合在一起的,Singleton.h有callCount的定义,Singleton.cpp有一个callCount的变量,test.cpp中也有一个叫callCount的变量,这就会导致合并在一起的时候就冲突了,同一个域中不能有同名的变量,所以就会有重定义的错误。总之就是因为全局变量在多个文件中都可见就导致了这个问题。
static关键字
static这个关键字就可以解决这个问题,普通的全局变量在多个文件都可见(在Singleton.obj和test.obj的符号表里面都有,符号表里面除了有函数名还有变量。当别人要用这个变量的时候不知道是用你Singleton.obj的还是用test.obj的)。而静态全局变量只在当前文件可见,static除了会改变生命周期,还会影响变量和函数的链接属性,让他只在当前文件可见(意思就是Singleton.cpp和test.cpp中都有一个callCount的变量,但是Singleton.cpp中的callCount只在Singleton.cpp可见,test.cpp中的callCount只在test.cpp可见)这样链接的时候就不会看见他们冲突,因为他们只在内部可见。
但是也并没有真正的解决掉整个问题,我们发现最终的callCount是0。这是因为只在当前文件可见,导致 Singleton.cpp和test.cpp中的callCount并不是同一个。
我们通过打印地址发现并不是同一个
extern关键字
用extern关键字,我们在Singleton.h是定义,要做到把定义和声明分离就要有extern关键字。我们把声明放在一个.h中,定义也只放在一个.cpp中


这个时候只有Singleton.cpp生成的Singleton.obj中有这个变量,test.obj中是没有这个变量的,test.cpp包着Singleton.h只有callCount的声明,只有声明的话编译器是可以编过的,只是我不知道这个callCount的地址,我在链接的时候就可以访问到这个变量了。extern就告诉test.cpp说这个callCount变量是有的,是全局的,但是它在其他的.obj中定义的,你链接的时候自己去找吧,所以链接的时候除了要找这个QuickSort函数(因为他也是只有声明)的地址还要找这个callCount的地址,这个时候就可以把callCount这个变量访问到。
这个时候就可以统计出递归调用的次数了。
ps:我们可以通过小区间优化,优化一下递归调用的次数,当排序的数据量变小用插排
Singleton.cpp
#include"Singleton.h"int callCount = 0; //然后我们在这个cpp里面放上定义void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; ++i){// x[0, end]int end = i;int x = a[end + 1];while (end >= 0){if (a[end] > x){a[end + 1] = a[end];--end;}else{break;}}a[end + 1] = x;}
}void QuickSort(int* a, int left, int right)
{if (callCount == 0){cout << "Singleton.cpp中的callCount的地址" << &callCount << endl;}++callCount;if (left >= right){return;}if (right - left + 1 > 10){int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){swap(a[cur], a[prev]);}++cur;}swap(a[prev], a[keyi]);keyi = prev;// [left, keyi-1]keyi[keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort(a + left, right - left + 1);}}
递归次数明显减少 
单例模式的两种实现方式
假设我不仅要把全局变量保存下来,我还要有一个vector,要求把递归调用的区间也存储下来。你就又需要向之前一样一边声明一边定义。这个时候就可以用面向对象的思想来解决这个问题。
我们需要全局有一个变量或者对象,这个对象里面有一些信息,这个信息是全局的,并且全局只有唯一一份,且提供一个访问它的全局访问点 。我们就可以设计一个单例模式。
把需要统计的放在一个类里面,这个时候就不需要加extern,因为类里面的变量天生就是声明,类对象才是定义。我们同时不期望别人随便创建对象,我们就可以把构造函数私有化,并且我们还要提供一个让你获取对象的方式,并且这个对象是唯一的一个对象。这个时候我们就可以采用懒汉和饿汉两种方式获取。
饿汉模式
- 就是说,我已经把饭提前准备好了,我随时可以吃;在main函数之前就创建好了单例对象,程序随时可以访问这个单例对象
- 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
这个_inst对象目前只有声明,它的定义可以放在Singleton.h,但是这样的话就又有之前全局变量的问题,Singleton.cpp和test.cpp就各有一份了,所以我们最好在Singleton.cpp中定义。
这样的话每次调用GetInstance都是调用的_inst这个对象,并且都是同一个对象。
接下来统计递归调用的次数
因为这里涉及到类成员变量的++,一种方式就是把成员变量设置成public,一种就是通过提供成员函数间接++。博主这里选择增加成员函数进行++
test.cpp则用成员函数获取总次数
运行结果:
这个地方用类去封装,用一个单例的类是非常的好用的,避免我们统计的信息只有唯一一份。为了防止恶意拷贝,我们就可以把拷贝构造封死
//单例模式--任意一个类都可以设计成单例模式
//饿汉模式
class CallInfo
{
public:static CallInfo& GetInstance(){return _inst;}int GetCallCount(){return _callCount;}void AddCallCount(int n){_callCount += n;}void Push(const pair<int, int>& kv){_v.push_back(kv);}CallInfo(const CallInfo& cl) = delete;
private:CallInfo():_callCount(0){}
private:int _callCount; //统计次数vector<pair<int, int>> _v; //记录区间信息static CallInfo _inst; //类里面定义的变量都是声明//构造函数虽然是私有的,但是静态的变量是可以调用构造函数初始化的,因为它的作用域是类里面//它也是类的成员,只不过是在类外定义的而已。
};
PS:在类里面定义对象,如果是普通的对象是不可以定义的,因为这就成了递归;但如果是静态的就没问题,因为静态的就相当于是全局的,就是你这个类定义好了,然后我又定义了一个全局的,这个对象属于类整体,相当于你就是定义了一个全局的变量,只是它的作用域被限定在这个类域里面,且在类里面只是一个声明和你定义一个全局的变量没有任何区别。
官方来讲:因为声明的静态数据成员不占用对象的内存。但是普通的数据成员在初始化呢的时候,首先要为对象分配内存,然后里面又有一个类,一直持续下去就不行,会无限迭代的分配内存。当然,引用和指针是可以的。引用和指针的大小是固定的。

//例如:class A
{private:
int i;static A a;
}//A 的大小其实只是 i 的大小。
懒汉模式
- 事先没有准备好,只有第一次访问的时候,才创建单例对象
- 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
//懒汉模式
class CallInfo
{
public:static CallInfo& GetInstance(){if (_pInst == nullptr){_pInst = new CallInfo;}return *_pInst;}int GetCallCount(){return _callCount;}void AddCallCount(int n){_callCount += n;}void Push(const pair<int, int>& kv){_v.push_back(kv);}CallInfo(const CallInfo& cl) = delete;
private:CallInfo():_callCount(0){}
private:int _callCount; //统计次数vector<pair<int, int>> _v; //记录区间信息static CallInfo* _pInst; //类里面定义的变量都是声明};

这就是懒汉模式,懒汉就是创建一个指针,静态指针执行的这个对象保证全局唯一,保证每次进来获取的都是那个唯一的对象。
但是当前写的懒汉模式的GetInstance是存在大问题的,是存在线程安全问题的。
如果你有一个t1和一个t2,t1和t2都调用GetInstance,导致统计的次数不准确。假设t1和t2都走到62行,t1和t2走到这里都是空指针,t1走到这时间片到了,t2先new,new完后,返回这个对象,然后去调用++,把次数加到1。然后t2的时间片到了,t1开始运行,虽然此时的指针已经不为空了,但是t1不会判断(因为一个线程回来的时候是从它切出去的那一行开始运行的),然后t1在new一个,返回。t2在来的时候发现不为空,但是获取到的是t1刚刚new的,所以次数又从0次开始,t2之前统计的次数就丢失了。而且后面new出来的还会把前面new出来的覆盖掉会存在内存泄露的问题。
eg:
我们可以通过加锁解决:

PS:用静态的锁一方面是要保证两个线程使用同一把锁,另外一方面就是静态成员函数只能访问静态成员变量,不能访问普通成员变量
为什么不能在静态成员函数中使用非静态变量?
所谓静态就是程序编译好以后,就已经给它分配了内存区域,它一直在那里,所谓动态就是运行时候临时分配内存的变量。
程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。因为静态是针对类的,而成员变量为对象所有。
静态成员函数不属于任何一个类对象,没有this指针,而非静态成员必须随类对象的产生而产生,所以静态成员函数”看不见”非静态成员,自然也就不能访问了
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。
当前就可以保证不会发生线程安全的问题了。但是我们还可以进一步去优化,加锁是为了保护第一次获取对象。只要对象创建出来以后,就没有线程安全的问题,现在这种写法,后面每次获取对象都要进行加锁,就会影响效率。
所以我们可以采用双检查加锁
这个时候,单例对象创建好了以后,获取单例对象的时候,就不用每次进行加锁解锁。
单例对象的释放
一般情况下,单例对象是不需要释放的,不用担心它内存泄露的问题,因为它是进程堆上的资源,通常也不大,main函数结束以后,进程就销毁了,进程结束后的资源都会还给系统。但是你非要释放的话也是可以的、
1.直接主动的去提供一个释放的接口,但是这样做并不常见

2.提供一个内部类进行回收

这个gc对象出了作用域就会去调用它的析构函数,这个析构函数就会把new的对象带走。
一般懒汉的单例对象,不需要回收,因为进程正常结束,资源都会还给系统,这个对象只有一个,系统自动回收也没什么问题,但是如果你在单例对象释放析构时,有一些要完成的动作,比如要记录日志等等。那么可以考虑搞一个类似下面的回收类帮助去完成这个事情。
懒汉模式与饿汉模式的优缺点
饿汉没有线程安全的问题,因为它在main函数之前就准备好了,main函数之前是没有多线程竞争的问题的。
饿汉优点:简单。缺点:无法控制单例创建初始化顺序(假设两个单例类A,B,要求A单例先创建,B单例后创建,B的创建依赖A。饿汉是无法实现这样的需求的);如果单例对象初始化很费时间,会导致程序启动慢,就像卡死一样。
懒汉优点:对应饿汉的两个缺点。缺点:1.相对复杂,尤其是还要控制线程安全的问题,
现代懒汉模式的写法
class CallInfo
{
public:static CallInfo& GetInstance(){static CallInfo sInst;return sInst;}int GetCallCount(){return _callCount;}void AddCallCount(int n){_callCount += n;}void Push(const pair<int, int>& kv){_v.push_back(kv);}CallInfo(const CallInfo& cl) = delete;
private:CallInfo():_callCount(0){cout << "CallInfo()" << endl;}
private:int _callCount; //统计次数vector<pair<int, int>> _v; //记录区间信息};
对于这样的写法,我在GetInstance中,先创建了一个局部的静态对象,对于局部的静态对象,只有第一个调用它的人会进行初始化,后面的人都不会初始化,直接返回对象,并且每次都返回同一个,局部的静态对象生命周期属于全局,但是它的作用域只在GetInstance中。
但是这样的写法在C++98中,多线程调用的时候,静态的对象的构造初始化并不能保证线程安全,C++11优化了这个问题,C++11中,静态对象的构造初始化是线程安全的。
所以双加锁的懒汉模式是在任何环境下都安全的,其次这种双加锁的好处是如果new出来的对象很大,new出来的对象在堆上,堆就很大,如果把这个对象放在数据段上相对而言没那么好。
设计模式的扩展:工厂模式,观察者模式
相关文章:
C++之单例模式
目录 1. 请设计一个类,只能在堆上创建对象 2. 请设计一个类,只能在栈上创建对象 3.请设计一个类,不能被拷贝 C98 C11 4. 请设计一个类,不能被继承 C98 C11 5. 请设计一个类,只能创建一个对象(单例模式) 设计…...
Redis十大类型——Set与Zset常见操作
Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解,相信大家很容易就明白Set,在Redis中也一样,Set的value值…...
车载雷达实战之Firmware内存优化
内存(Memory)是计算机中最重要的部件之一,计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器(RAM),只读存储器(ROM)以及高速缓存(Cache)。仅仅雷达的原…...
【剑指Offer】JZ14--剪绳子
剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路:因为是求乘积的最大值,所以如果截取剩下的是1,那还是它本身就没有意义。从此出发,考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...
raspberry pi播放音视频
文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器, 播放某段音频, 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…...
【电子学会】2022年12月图形化二级 -- 老鹰捉小鸡
老鹰捉小鸡 小鸡正在农场上玩耍,突然从远处飞来一只老鹰,小鸡要快速回到鸡舍中,躲避老鹰的抓捕。 1. 准备工作 (1)删除默认白色背景,添加背景Farm; (2)删除默认角色小…...
C++的双端队列
双端队列介绍1.双端队列知识需知2.大试牛刀1.双端队列知识需知 由于队列是一种先进先出(FIFO)的数据结构,因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素,可以考虑使用双端队列(deque)。…...
【独家】华为OD机试 - 拼接 URL(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...
为什么使用Junit单元测试?Junit的详解
Hi I’m Shendi 为什么使用Junit单元测试?Junit的详解 Junit简介 Junit是一个Java语言的单元测试框架。 单元测试是一个对单一实体(类或方法)的测试 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression test…...
怎么学好嵌入式Linux系统和驱动
嵌入式专业是一门实践性非常强的学科,只有多动手,多实践,多编程,多调试,多看书,多思考才能真正掌握好嵌入式开发技术。 现在很多同学也意识到了学校培养模式和社会需求脱节问题,有一部分同学也先…...
Spring Aware总结
概述 Spring中Aware到底是什么意思? 我们在看Spring源码的时候,经常可以看到xxxAwarexxx的身影,通常我会很疑惑,Aware到底是什么意思呢? 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…...
【RocketMQ】源码详解:Broker端消息刷盘流程
消息刷盘 同步入口:org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口:org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种,在实例化Commitlog的时候,会根据配置创建不同的服务 p…...
编码器SIQ-02FVS3驱动
一.简介 此编码器可以是功能非常强大,可以检测左右转动,和按键按下,所以说这一个编码器可以抵三个按键,而且体积非常小,使用起来比三个按键要高大尚,而且驱动也简单。唯一不足的点就是价格有点小贵6-8元才…...
【2021.9.7】记一次exe手动添加shellcode
【2021.9.7】记一次exe手动添加shellcode 文章目录【2021.9.7】记一次exe手动添加shellcode0.大致思路1.获取MessageBox的真实地址VA2.通过OD在代码段添加shellcode3.dump出数据,设置程序OEP4.测试dump出来的exe5.方法总结测试的exe和添加了shellcode的exe:链接&…...
常用训练tricks,提升你模型的鲁棒性
目录一、对抗训练FGM(Fast Gradient Method): ICLR2017代码实现二、权值平均1.指数移动平均(Exponential Moving Average,EMA)为什么EMA会有效?代码实现2. 随机权值平均(Stochastic Weight Averaging,SWA&a…...
具有精密内部基准的 DACx0502 简介及驱动应用示例
DACx0502 说明 16 位 DAC80502、14 位 DAC70502 和 12 位DAC60502 (DACx0502) 数模转换器 (DAC) 均为具有电压输出的高精度、低功耗器件。 DACx0502 线性度小于 1LSB。凭借高精度和微型封装特性,DACx0502 非常适合以下 应用: 增益和失调电压校准、电流…...
C语言函数:字符串函数及模拟实现strncpy()、strncat()、strncmp()
C语言函数:字符串函数及模拟实现strncpy()、strncat()、strncmp() 在了解strncpy、strncat()、前,需要先了解strcpy()、strncat(): C语言函数:字符串函数及模拟实现strlen() 、strcpy()、 strcat()_srhqwe的博客-CSDN博客 strncp…...
学术论文插图要求简介
1. 类型 位图和矢量图是两种不同的图像类型,它们在存储和处理图像时使用不同的方法。以下是它们之间的详细区别: 图像构成方式:位图使用像素(或图像的最小单元)来构建图像,每个像素都有自己的颜色和亮度值。…...
【独家】华为OD机试 - 斗地主 2(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...
力扣-计算特殊奖金
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1873. 计算特殊奖金二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...






因为这里涉及到类成员变量的++,一种方式就是把成员变量设置成public,一种就是通过提供成员函数间接++。博主这里选择增加成员函数进行++ 
运行结果: 
如果你有一个t1和一个t2,t1和t2都调用GetInstance,导致统计的次数不准确。假设t1和t2都走到62行,t1和t2走到这里都是空指针,t1走到这时间片到了,t2先new,new完后,返回这个对象,然后去调用++,把次数加到1。然后t2的时间片到了,t1开始运行,虽然此时的指针已经不为空了,但是t1不会判断(因为一个线程回来的时候是从它切出去的那一行开始运行的),然后t1在new一个,返回。t2在来的时候发现不为空,但是获取到的是t1刚刚new的,所以次数又从0次开始,t2之前统计的次数就丢失了。而且后面new出来的还会把前面new出来的覆盖掉会存在内存泄露的问题。

