C++ 学习笔记精要(二)
第一节 特殊类的设计
1. 一个类: 只能在堆上创建对象
关键点:自己控制析构
1.1 方法一: 使用delete禁掉默认析构函数
#include <iostream>
using namespace std;class HeapOnly
{
public:HeapOnly(){_str = new char[10];}~HeapOnly() = delete;void Destroy(){delete[] _str;operator delete(this);}private:char* _str;//...
};int main()
{HeapOnly* ptr = new HeapOnly;ptr->Destroy();return 0;
}
- 只要在堆上申请空间,并且使用delete把析构函数禁掉就行了
- 自己再实现一个释放空间的函数
1.2 方法二: 将析构函数私有化
#include <iostream>
#include <stdlib.h>
using namespace std;
class HeapOnly
{
public:/*static void Delete(HeapOnly* p){delete p;}*/void Delete(){delete this;}private:// 析构函数私有~HeapOnly(){cout << "~HeapOnly()" << endl;}
private:int _a;
};int main()
{//HeapOnly hp1;// error//static HeapOnly hp2;// errorHeapOnly* ptr = new HeapOnly;ptr->Delete();return 0;
}
1.3 方法三: 将构造函数私有化(禁掉拷贝)
#include <iostream>
#include <stdlib.h>
using namespace std;
class HeapOnly
{
public:// 提供一个公有的,获取对象的方式,对象控制是new出来的static HeapOnly* CreateObj(){return new HeapOnly;}// 防拷贝HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
private:// 构造函数私有HeapOnly():_a(0){}
private:int _a;
};int main()
{/*HeapOnly hp1;static HeapOnly hp2;HeapOnly* hp3 = new HeapOnly;delete hp3;*/HeapOnly* hp3 = HeapOnly::CreateObj();//HeapOnly copy(*hp3);delete hp3;return 0;
}
直接将构造函数私有化,然后再实现一个CreatObj创建对象,返回值是static;
创建的是堆的话,需要禁掉那2个函数
2. 一个类: 只能在栈上创建对象
关键点: 自己控制构造
2.1 方法一: 构造函数私有化(禁掉new)
#include <iostream>
#include <stdlib.h>
using namespace std;
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}// 不能防拷贝//StackOnly(const StackOnly& st) = delete;//StackOnly& operator=(const StackOnly& st) = delete;void* operator new(size_t n) = delete;
private:// 构造函数私有StackOnly():_a(0){}
private:int _a;
};int main()
{/*StackOnly st1;static StackOnly st2;StackOnly* st3 = new StackOnly;*/StackOnly st1 = StackOnly::CreateObj();// 拷贝构造static StackOnly copy2(st1); // 不好处理,算是一个小缺陷//StackOnly* copy3 = new StackOnly(st1);return 0;
}
3. 一个类:不能被继承
3.1 给父类加final关键字
#include <iostream>
#include <string>
using namespace std;//C98
//class A
//{
//private:
// A()
// {}
//
//protected:
// int _a;
//};// C++11中引用的final
class A final
{
public:A(){}protected:int _a;
};class B : public A
{};int main()
{B bb;// 这里对象实例化才会报错return 0;
}
-
C++98中:a. 父类构造函数私有-- 子类是不可见,b. 这种只有对象实例化才会报错
-
C++11中:给父类加上了final关键字,使子类不能继承父类,
4. 一个类: 只能创建一个对象(单例模式)
4.1 单例模式(饿汉模式 && 懒汉模式)
那两种模式都是将构造函数私有化,自己实现一个构造生成一个静态对象
- 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享
4.2 饿汉模式: 程序启动时就创建一个唯一的实例对象
class Singleton
{
public:static Singleton* GetInstance(){return &m_instance;}
private:// 构造函数私有Singleton() {};// C++11 : 防拷贝Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton m_instance;// 声明
};Singleton Singleton::m_instance;// 定义
-
优点:简单
-
缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
-
总结: 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
4.3 懒汉模式 : 第一次使用对象再创建实例对象
- 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取 文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,
- 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
#include <iostream>
#include <stdlib.h>
using namespace std;
class MemoryPool
{
public:static MemoryPool* GetInstance(){if (_pinst == nullptr) {_pinst = new MemoryPool;}return _pinst;}void* Alloc(size_t n){void* ptr = nullptr;// ....return ptr;}void Dealloc(void* ptr){// ...}// 实现一个内嵌垃圾回收类 class CGarbo {public:~CGarbo(){if (_pinst)delete _pinst;}};private:// 构造函数私有化MemoryPool(){// ....}char* _ptr = nullptr;// ...static MemoryPool* _pinst; // 声明
};// 定义
MemoryPool* MemoryPool::_pinst = nullptr;// 回收对象,main函数结束后,他会调用析构函数,就会释放单例对象
static MemoryPool::CGarbo gc;int main()
{void* ptr1 = MemoryPool::GetInstance()->Alloc(10);MemoryPool::GetInstance()->Dealloc(ptr1);
}
- 优点: 有控制顺序, 不影响启动速度
- 缺点: 相对复杂, 存在线程安全问题
4.4 单例对象释放问题:
- 一般情况下,单例对象不需要释放的。因为一般整个程序运行期间都可能会用它。单例对象在进程正常结束后,也会资源释放。
- 有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化(往文件、数据库写)操作。
第二节 C++的类型转换&&IO流
1. C语言的类型转换
#include <iostream>
#include <string>
using namespace std;void Insert(size_t pos, char ch)
{size_t _size = 5;//....int end = _size - 1;// size_t end = _size - 1;while (end >= pos) // end隐式类型转换{//_str[end + 1] = _str[end];--end;}
}void Test1()
{int i = 1;// 隐式类型转换(意义相近的类型)double d = i;printf("%d, %.2f\n", i, d);int* p = &i;// 显示的强制类型转换(意义不相近的类型,值转换后有意义)int address = (int)p;printf("%x, %d\n", p, address);Insert(3, 'a');Insert(0, 'a');// 触发死循环
}int main()
{Test1();return 0;
}
- 隐式类型转换(意义相近的类型)
- 显示的强制类型转换(意义不相近的类型,值转换后有意义)
2. C语言类型转换的缺陷
- 隐式类型转化有些情况下可能会出现问题: 比如数据精度丢失
- 显示类型转换将所有情况混合在一起,代码不够清晰
3. C++强制类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符
3.1 static_cast关键字 -> 隐式类型转换
#include <iostream>
#include <string>
using namespace std;int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;int* p = &a;//int address = static_cast<int>(p);// 不支持的return 0;
}
- static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast就像C语言的隐式类型转换,常用于意义相近的类型
- 但是它不能用于两个不相关的类型进行转换
3.2 reinterpret_cast关键字 -> 强制类型转换
#include <iostream>
#include <string>
using namespace std;int main()
{int a = 100;int* p = &a;int address = reinterpret_cast<int>(p);return 0;
}
-
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型,
-
reinterpret_cast就像C语言的强制类型转换
-
常用于两个类型不相关的
3.3 const_cast关键字->取消变量的const属性
- const_cast最常用的用途就是删除变量的const属性,方便赋值
- volatile关键字取消编译器的优化
3.4 dynamic_cast->父类指针 转换 子类指针
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)->切片
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
案例一
#include <iostream>
using namespace std;class A
{
public:virtual void f(){}
public:int _a = 0;
};class B : public A
{
public:int _b = 1;
};// A*指针pa有可能指向父类,有可能指向子类
void fun(A* pa)
{// 如果pa是指向子类,那么可以转换,转换表达式返回正确的地址// 如果pa是指向父类,那么不能转换,转换表达式返回nullptrB* pb = dynamic_cast<B*>(pa); // 安全的//B* pb = (B*)pa; // 不安全if (pb){cout << "转换成功" << endl;pb->_a++;pb->_b++;cout << pb->_a << ":" << pb->_b << endl;}else{cout << "转换失败" << endl;}
}int main()
{A aa;// 父类对象无论如何都是不允许转换成子类对象的//B bb = dynamic_cast<B>(aa);// error//B bb = (B)aa;// errorB bb;fun(&aa);fun(&bb);fun(nullptr);return 0;
}
-
dynamic_cast只能用于父类含有虚函数的类
-
dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
案例二
#include <iostream>
using namespace std;
class A1
{
public:virtual void f(){}
public:int _a1 = 0;
};class A2
{
public:virtual void f(){}
public:int _a2 = 0;
};class B : public A1, public A2
{
public:int _b = 1;
};int main()
{B bb;A1* ptr1 = &bb;A2* ptr2 = &bb;cout << ptr1 << endl;cout << ptr2 << endl << endl;B* pb1 = (B*)ptr1;B* pb2 = (B*)ptr2;cout << pb1 << endl;cout << pb2 << endl << endl;B* pb3 = dynamic_cast<B*>(ptr1);B* pb4 = dynamic_cast<B*>(ptr2);cout << pb3 << endl;cout << pb4 << endl << endl;return 0;
}
3.5 类型转换的实质
类型转换是通过临时对象来实现的,且临时对象具有常性,
- 但是尽量不要使用强制类型转换
3.6 常见面试题
- C++中的4种类型转换分别是:____ 、____ 、____ 、____。
- 分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。
- 说说4种类型转换的应用场景。
- static_cast用于相近类型的类型之间的转换,编译器隐式执行的任何类型转换都可用
- reinterpret_cast用于两个不相关类型之间的转换。
- const_cast用于删除变量的const属性,方便赋值。
- dynamic_cast用于安全的将父类的指针(或引用)转换成子类的指针(或引用)
4. RTTI->运行时类型识别
RTTI:Run-time Type identifification的简称,即:运行时类型识别
C++通过以下方式来支持RTTI:
- typeid运算符(获取对象类型字符串)
- dynamic_cast运算符(父类的指针指向父类对象或者子类对象)
- decltype(推导一个对象类型,这个类型可以用来定义另一个对象)
5. C语言的输入与输出
printf/scanf
fprintf/fscanf
sprintf/sscanf
#include <iostream>
using namespace std;
class A
{
public:// explicit A(int a)A(int a):_a(a){}operator int(){return _a;}private:int _a;
};int main()
{// 内置类型 转换成自定义类型A aa1 = 1; // 隐式类型转换 用1构造A临时对象,再拷贝构造aa1,优化后直接1构造aa1// 自定义类型 转换成内置类型int i = aa1;return 0;
}
说明一下:
- int i = aa1;能将自定义类型转换成内置类型,主要因为operator int()
-
explicit关键字: 不允许隐式类型的转换
5.1 多组输入
#include <iostream>
using namespace std;
class Date
{friend ostream& operator << (ostream& out, const Date& d);friend istream& operator >> (istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}operator bool(){// 这里是随意写的,假设输入_year为0,则结束if (_year == 0)return false;elsereturn true;}
private:int _year;int _month;int _day;
};istream& operator >> (istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}ostream& operator << (ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}// C++ IO流,使用面向对象+运算符重载的方式
// 能更好的兼容自定义类型,流插入和流提取
int main()
{// 自动识别类型的本质--函数重载// 内置类型可以直接使用--因为库里面ostream类型已经实现了int i = 1;double j = 2.2;cout << i << endl;cout << j << endl;// 自定义类型则需要我们自己重载<< 和 >>Date d(2022, 4, 10);cout << d;while (d){cin >> d;cout << d;}return 0;
}
- while(cin >> d){}遇到文件退出符才结束,因为库里面实现了operator bool()
5.2 fstream文件流
#include <iostream>
#include <fstream>
using namespace std;
int main()
{ifstream ifs("Test.cpp");char ch = ifs.get();while (ifs){cout << ch;ch = ifs.get();}return 0;
}
- C++中也有对文件进行操作的流fstream
- 它的使用就可以不用打开文件和关闭文件
- 库里面写的是一个类它会自己调构造,调析构
5.3 C++ 文件操作
#include <iostream>
#include <fstream>
using namespace std;
class Date
{friend ostream& operator << (ostream& out, const Date& d);friend istream& operator >> (istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}operator bool(){// 这里是随意写的,假设输入_year为0,则结束if (_year == 0)return false;elsereturn true;}
private:int _year;int _month;int _day;
};istream& operator >> (istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}ostream& operator << (ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}int main()
{ifstream ifs("test.txt");// 默认以读的方式打开//fscanf("%d%s%f", )int i;string s;double d;Date de;ifs >> i >> s >> d >> de;cout << i << s << d << de;return 0;
}
5.4 二进制文件的读写 && 文本文件的读写
#include <iostream>
#include <fstream>
using namespace std;
struct ServerInfo
{char _address[32];//string _address;int _port; // 100// Date _date;
};struct ConfigManager
{
public:ConfigManager(const char* filename = "server.config"):_filename(filename){}// 二进制文件的写void WriteBin(const ServerInfo& info){ofstream ofs(_filename, ios_base::out | ios_base::binary);ofs.write((char*)&info, sizeof(info));}// 二进制文件的写void ReadBin(ServerInfo& info){ifstream ifs(_filename, ios_base::in | ios_base::binary);ifs.read((char*)&info, sizeof(info));}// 文本文件的写void WriteText(const ServerInfo& info){ofstream ofs(_filename, ios_base::out);ofs << info._address << info._port;}// 文本文件的读void ReadText(ServerInfo& info){ifstream ifs(_filename, ios_base::in | ios_base::binary);ifs >> info._address >> info._port;}private:string _filename; // 配置文件
};
int main()
{// 二进制的写ServerInfo winfo = { "127.0.0.1", 888 };ConfigManager cm;cm.WriteBin(winfo);// cm.WriteText(winfo);// 文本的写// 二进制的读ServerInfo rinfo;ConfigManager rm;rm.ReadBin(rinfo);// rm.ReadText(rinfo);// 文本的读cout << rinfo._address << endl;cout << rinfo._port << endl;return 0;
}
-
二进制读写:在内存如何存储,就如何写到磁盘文件
-
优点:快
-
缺点:写出去内容看不见
-
- 文本读写:对象数据序列化字符串写出来,读回来也是字符串,反序列化转成对象数据
-
优点:可以看见写出去是什么
-
缺点:存在一个转换过程,要慢一些
-
5.5 字符串流-- stringstream
#include <iostream>
#include <fstream>
#include<sstream>using namespace std;
struct ChatInfo
{string _name; // 名字int _id; // idstring _msg; // 聊天信息
};int main()
{// 序列化ChatInfo winfo = { "张三", 135246, "晚上一起看电影吧" };//ostringstream oss;stringstream oss;oss << winfo._name << endl;oss << winfo._id << endl;oss << winfo._msg << endl;string str = oss.str();cout << str << endl;// 网络传输str,另一端接收到了字符串串信息数据// 反序列化ChatInfo rInfo;//istringstream iss(str);stringstream iss(str);iss >> rInfo._name;iss >> rInfo._id;iss >> rInfo._msg;cout << "----------------------------------" << endl;cout << rInfo._name << "[" << rInfo._id << "]:>" << rInfo._msg << endl;cout << "----------------------------------" << endl;return 0;
}
第三节 多线程
1.线程库
1.1 thread类的简单介绍
C++11中引入了对线程的支持了,使得C++在并行编程时不需要依赖第三方库
而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件
函数名 | 功能 |
thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 |
thread(fn, args1, args2, ...) | 构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的 参数 |
get_id() | 获取线程id |
jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程。 |
jion() | 该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行 |
detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离 的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
-
线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的
状态。
-
当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
1.2 线程对象关联线程函数
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([](){cout << "Thread2" << endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;return 0;
}
- 线程对象可以关联1.函数指针2.lambda表达式3.函数对象
- 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
注意
-
thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
- 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
- 采用无参构造函数构造的线程对象
- 线程对象的状态已经转移给其他线程对象
-
线程已经调用jion或者detach结束
1.3 线程函数参数
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;void Print(int n, int& x,mutex& mtx)
{for (int i = 0; i < n; ++i){mtx.lock();cout <<this_thread::get_id()<<":"<< i << endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));++x;mtx.unlock();}}int main()
{mutex m;int count = 0;thread t1(Print, 10, ref(count),ref(m));thread t2(Print, 10, ref(count),ref(m);t1.join();t2.join();cout << count << endl;return 0;
}
- 线程函数的参数是先传递给thread的,并以值拷贝的方式拷贝到线程栈空间中的
-
如果不给线程函数的参数不借助ref函数
-
即使线程参数为引用类型,在线程中修改后也不能修改外部实参,
-
因为其实际引用的是线程栈中的拷贝,而不是外部实参
-
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{mutex mtx;int x = 0;int n = 10;int m;cin >> m;vector<thread> v(m);//v.resize(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));++x;mtx.unlock();}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}
- 借助lambda表达式中的引用捕捉也可以实现上面那个函数,就可以不用借助ref函数
线程并行 && 并发的讨论
- 并行:任务的同时进行
- 并发: 任务的调动和切换
- 在这个函数中其实是并行的速度更快,因为线程切换十分耗时间
join与detach
join方式
主线程创建新线程后,可以调用join函数等待新线程终止,当新线程终止时join
函数就会自动清理线程相关的资源。
join
函数清理线程的相关资源后,thread对象与已销毁的线程就没有关系了,因此一个线程对象一般只会使用一次join
,否则程序会崩溃。比如:
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t(func, 20);t.join();t.join(); //程序崩溃return 0;
}
但如果一个线程对象join
后,又调用移动赋值函数,将一个右值线程对象的关联线程的状态转移过来了,那么这个线程对象又可以调用一次join
。比如:
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
int main()
{thread t(func, 20);t.join();t = thread(func, 30);t.join();return 0;
}
但采用join
的方式结束线程,在某些场景下也可能会出现问题。比如在该线程被join
之前,如果中途因为某些原因导致程序不再执行后续代码,这时这个线程将不会被join
void func(int n)
{for (int i = 0; i <= n; i++){cout << i << endl;}
}
bool DoSomething()
{return false;
}
int main()
{thread t(func, 20);//...if (!DoSomething())return -1;//...t.join(); //不会被执行return 0;
}
因此采用join
方式结束线程时,join
的调用位置非常关键,为了避免上述问题,可以采用RAII的方式对线程对象进行封装,也就是利用对象的生命周期来控制线程资源的释放。比如:
class myThread
{
public:myThread(thread& t):_t(t){}~myThread(){if (_t.joinable())_t.join();}//防拷贝myThread(myThread const&) = delete;myThread& operator=(const myThread&) = delete;
private:thread& _t;
};
- 每当创建一个线程对象后,就用myThread类对其进行封装产生一个myThread对象
- 当myThread对象生命周期结束时就会调用析构函数,在析构中会通过
joinable
判断这个线程是否需要被join
,如果需要那么就会调用join
对其该线程进行等待。
例如刚才的代码中,使用myThread类对线程对象进行封装后,就能保证线程一定会被join
int main()
{thread t(func, 20);myThread mt(t); //使用myThread对线程对象进行封装//...if (!DoSomething())return -1;//...t.join();return 0;
}
detach方式
主线程创建新线程后,也可以调用detach
函数将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给C++运行库,此时C++运行库会保证当线程退出时,其相关资源能够被正确回收。
- 使用
detach
的方式回收线程的资源,一般在线程对象创建好之后就立即调用detach
函数 - 否则线程对象可能会因为某些原因,在后续调用
detach
函数分离线程之前被销毁掉,这时就会导致程序崩溃 - 因为当线程对象被销毁时会调用thread的析构函数,而在thread的析构函数中会通过
joinable
判断这个线程是否需要被join
,如果需要那么就会调用terminate
终止当前程序(程序崩溃)
1.4 原子性操作库(atomic)
多线程最主要的问题是共享数据带来的问题(即线程安全)
当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{mutex mtx;atomic<int> x = 0;// int x = 0;int n = 1000000;int m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){// t1 t2 t3 t4++x;}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}
- C++98中传统的解决方式:可以对共享修改的数据加锁保护
- 加锁的问题: 这个线程执行的时候, 其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
-
C++11中使用atomic类模板,定义出需要的任意原子类型
-
程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。
-
注意
#include <atomic>
int main()
{atomic<int> a1(0);//atomic<int> a2(a1); // 编译失败atomic<int> a2(0);//a2 = a1; // 编译失败return 0;
}
- 原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,
- 因此在C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了
1.5 lock_guard与unique_lock
在多线程环境下,原子性只能保证某个变量的安全性
在多线程环境下,而需要保证一段代码的安全性,就只能通过加锁的方式实现
lock_guard
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;//RAII
template<class Lock>
class LockGuard
{
public:LockGuard(Lock& lk):_lock(lk){_lock.lock();cout << "thread:" << this_thread::get_id() << "加锁" << endl;}~LockGuard(){cout << "thread:" << this_thread::get_id() << "解锁" << endl << endl;_lock.unlock();}
private:Lock& _lock;// 成员变量是引用
};int main()
{mutex mtx;atomic<int> x = 0;//int x = 0;int n = 100;int m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){{lock_guard<mutex> lk(mtx);cout << this_thread::get_id() << ":" << i << endl;}std::this_thread::sleep_for(std::chrono::milliseconds(100));}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}
- lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装
-
调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。
-
lock_guard的缺陷:太单一,用户没有办法对该锁进行控制
unique_lock
与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:
- 上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
- 修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有 权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
- 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相 同)、mutex(返回当前unique_lock所管理的互斥量的指针)。
1.6 条件变量库(condition_variable)
condition_variable中提供的成员函数,可分为wait系列和notify系列两类。
wait系列成员函数
wait系列成员函数的作用就是让调用线程进行阻塞等待,包括wait
、wait_for
和wait_until
。
下面先以wait
为例进行介绍,wait函数提供了两个不同版本的接口:
//版本一
void wait(unique_lock<mutex>& lck);
//版本二
template<class Predicate>
void wait(unique_lock<mutex>& lck, Predicate pred);
- 调用第一个版本的wait函数时只需要传入一个互斥锁,线程调用wait后会立即被阻塞,直到被唤醒。
- 调用第二个版本的wait函数时除了需要传入一个互斥锁,还需要传入一个返回值类型为bool的可调用对象,与第一个版本的wait不同的是,当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么该线程还需要继续被阻塞。
注意: 调用wait系列函数时,传入互斥锁的类型必须是unique_lock。
notify系列成员函数
notify系列成员函数的作用就是唤醒等待的线程,包括notify_one
和notify_all
notify_one
:唤醒等待队列中的首个线程,如果等待队列为空则什么也不做。-
notify_all
:唤醒等待队列中的所有线程,如果等待队列为空则什么也不做
注意: 条件变量下可能会有多个线程在进行阻塞等待,这些线程会被放到一个等待队列中进行排队
1.7 实现两个线程交替打印1-100
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{int i = 0;int n = 100;mutex mtx;thread t1([&](){while (i < n){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;i += 1;mtx.unlock();}});this_thread::sleep_for(chrono::microseconds(100));thread t2([&](){while (i < n){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;i += 1;mtx.unlock();}});t1.join();t2.join();return 0;
}
- 在线程切换的中间时间也会发现线程竞争抢锁的问题
正确解决方案(条件变量)
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{int i = 0;int n = 100;mutex mtx;condition_variable cv;// 条件变量bool ready = true;// t1打印奇数thread t1([&](){while (i < n){{unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready](){return !ready; });// 等待线程cout << "t1--" << this_thread::get_id() << ":" << i << endl;i += 1;ready = true;cv.notify_one();// 解除线程等待}//this_thread::yield();this_thread::sleep_for(chrono::microseconds(100));}});// t2打印偶数thread t2([&]() {while (i < n){unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready](){return ready; });cout <<"t2--"<<this_thread::get_id() << ":" << i << endl;i += 1;ready = false;cv.notify_one();}});this_thread::sleep_for(chrono::seconds(3));cout << "t1:" << t1.get_id() << endl;cout << "t2:" << t2.get_id() << endl;t1.join();t2.join();return 0;
}
- cv.wait(lock, [&ready]() {return !ready; });
- 当ready返回的是false时,这个线程就会阻塞
- 阻塞当前线程,并自动调用lock.unlock(),允许其他锁定的线程继续执行
- cv.notify_one();
- 唤醒当前线程并自动调用lock.lock();就只允许自己一个线程执行
1.8 shared_ptr的多线程问题
#include<iostream>
#include<thread>
#include<mutex>
#include<vector>
#include<atomic>
#include<memory>
using namespace std;namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pMutex(sp._pMutex){AddRef();}void Release(){bool flag = false;_pMutex->lock();if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pMutex->unlock();if (flag)delete _pMutex;}void AddRef(){_pMutex->lock();++(*_pRefCount);_pMutex->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pMutex = sp._pMutex;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pRefCount;// 使用时需要加锁mutex* _pMutex;// 锁指针};
}int main()
{// shared_ptr是线程安全的吗?bit::shared_ptr<double> sp1(new double(1.11));bit::shared_ptr<double> sp2(sp1);mutex mtx;vector<thread> v(2);int n = 100000;for (auto& t : v){t = thread([&](){for (size_t i = 0; i < n; ++i){// 拷贝是线程安全的bit::shared_ptr<double> sp(sp1);// 访问资源不是mtx.lock();(*sp)++;mtx.unlock();}});}for (auto& t : v){t.join();}cout << sp1.use_count() << endl;cout << *sp1 << endl;return 0;
}
-
在多线程中,shared_ptr也应该对自己的引用计数进行加锁处理
- 在多线程中, shared_ptr的拷贝是线程安全的,但访问资源不是,所以访问资源也需要加锁
1.9 单例模式的多线程问题
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class Singleton
{
public:static Singleton* GetInstance(){// 保护第一次,后续不需要加锁// 双检查加锁if (_pInstance == nullptr){unique_lock<mutex> lock(_mtx);if (_pInstance == nullptr){_pInstance = new Singleton;}}return _pInstance;}private:// 构造函数私有Singleton(){};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton* _pInstance;static mutex _mtx;
};Singleton* Singleton::_pInstance = nullptr;
mutex Singleton::_mtx; int main()
{Singleton::GetInstance();Singleton::GetInstance();return 0;
}
- 在多线程的情况下, 第一次创建对象时也是需要加锁保护的
巧妙的解决方案
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;class Singleton
{
public:static Singleton* GetInstance(){static Singleton _s;// 局部的静态对象,第一次调用时初始化return &_s;}private:// 构造函数私有Singleton() {};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;
};int main()
{Singleton::GetInstance();Singleton::GetInstance();return 0;
}
- 局部的静态对象,第一次调用时初始化
- 在C++11之前是不能保证线程安全的
静态对象的构造函数调用初始化并不能保证线程安全的原子性 - C++11的时候修复了这个问题,所以这种写法,只能在支持C++11以后的编译器上玩
第四节 日期类
1. 日期类的实现
class Date
{
public:// 构造函数Date(int year = 0, int month = 1, int day = 1);// 打印函数void Print() const;// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day) const;// 日期-=天数Date& operator-=(int day);// 日期-天数Date operator-(int day) const;// 前置++Date& operator++();// 后置++Date operator++(int);// 前置--Date& operator--();// 后置--Date operator--(int);// 日期的大小关系比较bool operator>(const Date& d) const;bool operator>=(const Date& d) const;bool operator<(const Date& d) const;bool operator<=(const Date& d) const;bool operator==(const Date& d) const;bool operator!=(const Date& d) const;// 日期-日期int operator-(const Date& d) const;// 析构,拷贝构造,赋值重载可以不写,使用默认生成的即可private:int _year;int _month;int _day;
};
1.1 构造函数
// 获取某年某月的天数
inline int GetMonthDay(int year, int month)
{// 数组存储平年每个月的天数static int dayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int day = dayArray[month];if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){//闰年2月的天数day = 29;}return day;
}
// 构造函数
Date::Date(int year, int month, int day)
{// 检查日期的合法性if (year >= 0&& month >= 1 && month <= 12&& day >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{// 严格来说抛异常更好cout << "非法日期" << endl;cout << year << "年" << month << "月" << day << "日" << endl;}
}
- GetMonthDay函数会被多次调用,所以最好设置成内联函数
- 且该函数中的月天数用static修饰,避免每次调用该函数都需要重新开辟数组。
- 当函数声明和定义分开时,在声明时注明缺省参数,定义时不标出缺省参数
1.2 打印函数
// 打印函数
void Date::Print() const
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
1.3 日期 += 天数
// 日期+=天数
Date& Date::operator+=(int day)
{if (day<0){// 复用operator-=*this -= -day;}else{_day += day;// 日期不合法,通过不断调整,直到最后日期合法为止while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}}return *this;
}
-
首先判断日期是否合法
-
若日已满,则日减去当前月的天数,月加一
-
若月已满,则将年加一,月置为1
1.4 日期 + 天数
// 日期+天数
Date Date::operator+(int day) const
{Date tmp(*this);// 拷贝构造tmp,用于返回// 复用operator+=tmp += day;return tmp;
}
- 复用代码
1.5 日期 -= 天数
// 日期-=天数
Date& Date::operator-=(int day)
{if (day < 0){// 复用operator+=*this += -day;}else{_day -= day;// 日期不合法,通过不断调整,直到最后日期合法为止while (_day <= 0){_month--;if (_month == 0){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}}return *this;
}
-
首先判断日期是否合法
-
若日为负数,则月减一
- 若月为0,则年减一,月置为12
- 日加上当前月的天数
1.6 日期 - 天数
// 日期-天数
Date Date::operator-(int day) const
{Date tmp(*this);// 拷贝构造tmp,用于返回// 复用operator-=tmp -= day;return tmp;
}
- 复用代码
1.8 自增自减运算符的实现
前置++
// 前置++
Date& Date::operator++()
{// 复用operator+=*this += 1;return *this;
}
- 复用代码
后置 ++
// 后置++
Date Date::operator++(int)
{Date tmp(*this);// 拷贝构造tmp,用于返回// 复用operator+=*this += 1;return tmp;
}
- 注意: 后置++需要多给一个参数
- 复用代码
前置 - -
// 前置--
Date& Date::operator--()
{// 复用operator-=*this -= 1;return *this;
}
- 复用代码
后置 - -
// 后置--
Date Date::operator--(int)
{Date tmp(*this);// 拷贝构造tmp,用于返回// 复用operator-=*this -= 1;return tmp;
}
- 注意: 后置++需要多给一个参数
- 复用代码
1.9 比较运算符的实现
> 运算符的重载
bool Date::operator>(const Date& d) const
{if (_year > d._year){return true;}else if (_year == d._year){if (_month > d._month){return true;}else if (_month == d._month){if (_day > d._day){return true;}}}return false;
}
== 运算符的重载
bool Date::operator==(const Date& d) const
{return _year == d._year&&_month == d._month&&_day == d._day;
}
>= 运算符的重载
bool Date::operator>=(const Date& d) const
{return *this > d || *this == d;
}
< 运算符的重载
bool Date::operator<(const Date& d) const
{return !(*this >= d);
}
<= 运算符的重载
bool Date::operator<=(const Date& d) const
{return !(*this > d);
}
!= 运算符的重载
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}
1.10 日期 - 日期
// 日期-日期
int Date::operator-(const Date& d) const
{Date max = *this;// 假设第一个日期较大Date min = d;// 假设第二个日期较小int flag = 1;// 此时结果应该为正值if (*this < d){// 假设错误,更正max = d;min = *this;flag = -1;// 此时结果应该为负值}int n = 0;// 记录所加的总天数while (min != max){min++;// 较小的日期++n++;// 总天数++}return n*flag;
}
说明一下:
- 让较小的日期的天数一直加一,直到最后和较大的日期相等即可
- 代码中使用flag变量标记返回值的正负,
flag为1代表返回的是正值,flag为-1代表返回的是值,
最后返回总天数与flag相乘之后的值即可。
相关文章:
C++ 学习笔记精要(二)
第一节 特殊类的设计 1. 一个类: 只能在堆上创建对象 关键点:自己控制析构 1.1 方法一: 使用delete禁掉默认析构函数 #include <iostream> using namespace std;class HeapOnly { public:HeapOnly(){_str new char[10];}~HeapOnly() delete;void Destroy(){delete[…...
博士,超28岁,出局!
近日,长沙市望城区《2025年事业引才博士公开引进公告》引发轩然大波——博士岗位年龄要求28周岁及以下,特别优秀者也仅放宽至30周岁。 图源:网络 这份规定让众多"高龄"博士生直呼不合理,并在社交平台掀起激烈讨论。 图源…...
macOS - 根据序列号查看机型、保障信息
文章目录 最近在看 MacBook 二手机,有个咸鱼卖家放个截图 说不清参数,于是想根据 序列号 查看机型。苹果提供了这样的网页: https://checkcoverage.apple.com/ (无需登录) 结果 2025-06-20(五)…...
C/C++ 高频八股文面试题1000题(一)
原作者:Linux教程,原文地址:C/C 高频八股文面试题1000题(一) 在准备技术岗位的求职过程中,C/C始终是绕不开的核心考察点。无论是互联网大厂的笔试面试,还是嵌入式、后台开发、系统编程等方向的岗位,C/C 都…...
C++ map 和 unordered_map 的区别和联系
C map 和 unordered_map 的区别和联系 map 和 unordered_map 都是 C 标准库中关联容器,用于存储键值对。它们的主要区别在于底层实现和性能特性,联系在于它们都提供了键值对的存储和访问功能。 区别: 特性mapunordered_map底层实现红黑树 …...
Sentinel实现原理
Sentinel 是阿里巴巴开源的分布式系统流量控制组件,主要用于服务保护,涵盖流量控制、熔断降级、系统负载保护等功能。 以下是 Sentinel 的实现原理,使用中文简要说明: 1. 总体架构 Sentinel 采用 轻量级 设计,分为 核…...
python打卡day37
疏锦行 知识点回顾: 1. 过拟合的判断:测试集和训练集同步打印指标 2. 模型的保存和加载 a. 仅保存权重 b. 保存权重和模型 c. 保存全部信息checkpoint,还包含训练状态 3. 早停策略 作业:对信贷数据集训练后保存权重…...
MySQL复杂查询优化实战:从多表关联到子查询的性能突破
文章目录 一、复杂查询性能瓶颈分析与优化框架二、多表关联查询的优化策略与实战1. JOIN顺序优化:基于成本估算的表关联策略2. 复合索引与JOIN条件优化3. 大表JOIN的分片处理 三、子查询优化:从嵌套到JOIN的转换艺术1. 标量子查询转换为JOIN2. EXISTS子查…...
LeetCode 680.验证回文串 II
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: C代码: Java代码: 题目: 题目描述: 题目链接: 680. 验证…...
window显示驱动开发—输出合并器阶段
逻辑管道中的最后一步是通过模具或深度确定可见性,以及写入或混合输出以呈现目标,这可以是多种资源类型之一。 这些操作以及输出资源 (呈现目标) 绑定在输出合并阶段定义。 1. 核心功能与管线定位 输出合并是渲染管线的最终固定功能阶段,负…...
单片机开发日志cv MDK-ARM工具链迁移到MAKE
核心经验: STM32H7 多 RAM 区域,外设相关数据段必须放在 AXI SRAM(RAM)区,不能放在 DTCMRAM,否则外设无法访问,程序表面正常但外设全失效。迁移工程时,务必检查链接脚本的内存分布&a…...
大模型与搜索引擎的技术博弈及未来智能范式演进
基于认知革命与技术替代的全景综述 一、大模型对搜索引擎的替代性分析:技术范式与市场重构 (1)技术原理的代际分野 传统搜索引擎遵循 "爬虫抓取 - 索引构建 - 关键词排序" 的三段式架构,其核心是基于 PageRank 算法的…...
Ajax-入门
Ajax: 全称Asynchronous JavaScript And XML,异步的JavaScript和XML。其作用有如下2点: 与服务器进行数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。 异步交互:可以在不重新加载整个页面的情况下&a…...
FPGA基础 -- Verilog 共享任务(task)和函数(function)
Verilog 中共享任务(task)和函数(function) 的详细专业培训,适合具有一定 RTL 编程经验的工程师深入掌握。 一、任务(task)与函数(function)的基本区别 特性taskfunctio…...
c++set和pair的使用
set是C中的一种关联容器,具有以下特点: 存储唯一元素(不允许重复) 元素自动排序(默认升序) 基于红黑树实现(平衡二叉搜索树) 插入、删除和查找的时间复杂度为O(log n) 前言 在C…...
数据库中间件ShardingSphere5
一、高性能架构模式 数据库集群,第一种方式“读写分离”,第二种方式“数据库分片”。 1.1 读写分离架构 读写分离原理:将数据库读写操作分散到不同的节点上。 读写分离的基本实现: 主库负责处理事务性的增删改操作,…...
window显示驱动开发—使用状态刷新回调函数
用户模式显示驱动程序可以使用 Direct3D 运行时版本 10 State-Refresh回调函数 来实现无状态驱动程序或构建命令缓冲区前导数据。 Direct3D 运行时在调用 CreateDevice (D3D10 ) 函数时,向D3D10DDIARG_CREATEDEVICE结构的 pUMCallbacks 成员指向的D3D10DDI_CORELAY…...
windows11右击恢复为windows10
文章目录 前言一、问题描述二、解决方案 前言 为了解决win11的右击更多选项的问题 一、问题描述 win11的右键更多选项过于繁琐 二、解决方案 在windows11的终端管理员中输入如下代码: reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c…...
基于物联网的智能衣柜系统设计
标题:基于物联网的智能衣柜系统设计 内容:1.摘要 随着物联网技术的飞速发展,智能家居领域迎来了新的变革机遇。本研究的目的在于设计一种基于物联网的智能衣柜系统,以提升用户的衣物管理和使用体验。方法上,通过搭建物联网硬件平台ÿ…...
GM DC Monitor v2.0 卸载教程
以下俩种方法任选一种均可 第一种方法:一键自动卸载 进入到软件安装目录 卸载app 进入到app目录,运行一键卸载脚本:sh uninstall.sh 卸载es 进入到es目录,运行一键卸载脚本:sh uninstall.sh 卸载db 进入到db目录&a…...
C#上位机实现报警语音播报
我们在开发C#上位机时,有时候会需要将报警信息通过语音进行播报,今天跟大家分享一下具体的实现过程。 一、组件安装 首先我们创建好一个Windows窗体项目,然后添加System.Speech库引用。 点击引用,右击添加引用,在程…...
python自助棋牌室管理系统
目录 技术栈介绍具体实现截图系统设计研究方法:设计步骤设计流程核心代码部分展示研究方法详细视频演示试验方案论文大纲源码获取/详细视频演示 技术栈介绍 Django-SpringBoot-php-Node.js-flask 本课题的研究方法和研究步骤基本合理,难度适中…...
榕壹云婚恋相亲系统:ThinkPHP+UniApp打造高效婚配平台
引言 在数字化浪潮下,婚恋相亲行业正加速向线上迁移。榕壹云公司基于市场需求与技术积累,开发一款功能完备、技术开源的婚恋相亲小程序系统,为单身人士提供高效、安全的婚恋平台。本文将围绕系统背景、客户定位、核心技术、功能模块及优势场景展开详细解析,助力开发者与技…...
每日leetcode
2890. 重塑数据:融合 - 力扣(LeetCode) 题目 DataFrame report --------------------- | Column Name | Type | --------------------- | product | object | | quarter_1 | int | | quarter_2 | int | | quarter_3 | i…...
深入理解XGBoost(何龙 著)学习笔记(五)
深入理解XGBoost(何龙 著)学习笔记(五) 本文接上一篇,内容为线性回归,介绍三部分,首先介绍了"模型评估”,然后分别提供了线性回归的模型代码:scikit-learn的Linear…...
SelectDB 在 AWS Graviton ARM 架构下相比 x86 实现 36% 性价比提升
在海量数据分析中,追求高性价比已成为各大企业的主流趋势。ARM 架构凭借其高能效和低成本的特点,逐渐在数据中心崛起,成为理想的高性价比选择。基于 ARM 架构的 AWS Graviton 系列处理器,正是这一趋势的典型代表。Graviton 处理器…...
机器学习流量识别(pytorch+NSL-KDD+多分类建模)
本文主要实现以下功能,会提供完整的可运行的代码以及解释为什么这么设计。文章不会收费,若被限制查看,请私信我。 使用 NSL-KDD 数据集的CSV文件进行流量攻击检测,使用机器学习算法实现流量攻击检测,使用pytorch框架…...
三种经典算法无人机三维路径规划对比(SMA、HHO、GWO三种算法),Matlab代码实现
代码功能 该MATLAB代码用于对比三种元启发式优化算法(SMA、HHO、GWO三种算法, SMA黏菌算法、HHO哈里斯鹰优化算法、GWO灰狼优化算法) 在特定优化问题上的性能,运行环境MATLABR2020b或更高 : 初始化问题模型ÿ…...
FTTR+软路由网络拓扑方案
文章目录 网络拓扑软路由配置FTTR光猫路由器TPLink路由器配置WAN设置LAN设置 参考 网络拓扑 软路由配置 配置静态IP地址:192.168.1.100设置网关指向主路由的IP 设置自定义DNS服务器 开启DHCP 这一步很关键,可以让连上wifi的所有设备自动趴强。 FTTR光猫…...
服务器获取外网IP,并发送到钉钉
服务器获取外网IP,并发送到钉钉 import time import hmac import hashlib import base64 import urllib.parse import requests# 请填入你的钉钉机器人配置 access_token XXXX secret XXXX# 获取公网 IP def get_public_ip():try:response requests.get("…...
解决uni-app发布微信小程序主包大小限制为<2M的问题
一 问题说明 我想用uniapp开发多端应用,引入了uview组件库来美化样式,可发布为微信小程序却提示我代码质量不过关,主包代码量太大了: 二 问题分析 2.1 原生微信小程序开发代码质量限制: 1.主包代码大小不得大于2M&…...
魅族“换血”出牌:手机基本盘站不稳,想靠AI和汽车“改命”
撰稿|何威 来源|贝多财经 被吉利收购后,魅族逐渐转向在AI领域躬身耕作。 自2024年2月以“All in AI”正式宣告转型、喊出不再推出传统智能手机的豪言开始,这家曾以设计见长的手机厂商,将下半场押注在AI终端、AR眼镜与智能座舱系统上&#…...
原点安全入选 Gartner®“数据安全平台”中国市场指南代表厂商
2025年1月7日,全球权威咨询与分析机构 Gartner 发布《中国数据安全平台市场指南》(China Context: ‘Market Guide for Data Security Platforms’),北京原点数安科技有限公司(简称“原点安全”,英文名称&q…...
uni-app-配合iOS App项目开发apple watch app
假设你已经用uni-app开发好了一个iOS端的app,现在想要开发一个配套的apple watch app。改怎么去开发呢?是不是一头雾水,这篇文章就会介绍一些apple watch app开发的知识以及如何在uni-app开发的iOS app基础上去开发配套的watch app。 一、ap…...
如何理解Java反射机制
反射机制原理 反射是Java在运行时动态获取类信息、操作类属性和方法的能力。核心原理是JVM在类加载时创建Class对象,该对象包含类的完整结构信息。 关键类: Class:类的元数据入口 Field:类的成员变量 Method:类的方…...
SM3算法C语言实现(无第三方库,带测试)
一、SM3算法介绍 SM3算法是中国国家密码管理局(OSCCA)于2010年发布的商用密码散列函数标准,属于我国自主设计的密码算法体系之一 ,标准文档下载地址为:SM3密码杂凑算法 。SM3算法输出长度为256位(32字节&a…...
King’s LIMS 系统引领汽车检测实验室数字化转型
随着汽车保有量的持续攀升和车龄的增长,消费者对汽车的需求已悄然转变,从最初对外观和性能的追求,逐渐深化为对安全性、可靠性、耐久性、性能与舒适性以及智能化功能的全方位关注。这无疑让汽车检测行业在保障车辆质量、满足市场需求方面肩负…...
CppCon 2017 学习:Mocking Frameworks Considered
当然可以,下面是对 Fowler 的 Whiskey-Store 示例。 Fowler 的 Whiskey-Store 示例(坏设计) 贴出的类图是 Martin Fowler 在《重构》书中使用的一个教学用反面案例(故意设计得不合理),用来说明如何通过重…...
通过事件过滤器拦截QRadioButton点击事件
通过事件过滤器拦截QRadioButton点击事件 一、事件过滤器完整实现 1. 核心代码扩展(含注释) bool MainWindow::eventFilter(QObject* obj, QEvent* ev) {// 拦截所有QRadioButton的鼠标事件(包括点击、释放、双击)if (ev->ty…...
领码 SPARK 融合平台赋能工程建设行业物资管理革新——数智赋能,重塑中国模式新范式
摘要 工程建设行业正加速迈向数字化与精益化转型,物资管理成为项目成败的关键瓶颈。本文深入解析中国工程企业“项目部-物资部-企业项目管理部”三级协同的独特物资管理体系,聚焦集中采购与零星采购的统筹难题。基于领码 SPARK 融合平台,提出…...
“地标界爱马仕”再启:世酒中菜联袂陈汇堂共筑新会陈皮顶奢产业
“地标界爱马仕”再启战略新篇:世酒中菜联袂陈汇堂,共筑新会陈皮顶奢产业生态 ——中世国际与陈汇堂股权合作签约仪式在国际地理标志服务基地举行 江门市新会区,2025年6月20日——被誉为“地标界爱马仕”的全球顶奢品牌运营商世酒中菜 &…...
.Net Framework 4/C# 数据访问技术(ADO.NET)
一、数据库基础 (一) 数据库简介 数据库是按照数据结构来组织、存储和管理数据的仓库,是存储在一起的相关数据的集合。 (二) SQL 语言简介 SQL 是一种数据库查询和程序设计语言,用于存取数据以及查询,更新和管理关系型数据库系统。在编写 SQL 语句时,SQL 语句各关键字要以…...
北京京东,看看难度
最近由于三大外卖平台“打仗”,优惠券多到数不过来,一日三餐每个平台各点一单哈哈哈,正好最近组织内部还有朋友在北京的京东面试过,分享一下她的面经(Java岗): 1. Kafka消息不丢失问题…...
RPGMZ游戏引擎 如何手动控制文字显示速度
直接上代码 const _Window_Base_prototype_initialize Window_Base.prototype.initialize;Window_Base.prototype.initialize function(rect) {_Window_Base_prototype_initialize.call(this, rect);this.文字速度缓冲 0;}; this.文字速度缓冲 0; 进行缓冲 Window_Base…...
linux线程同步
互斥锁 同步与互斥概述** 现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能: 都需要访问/使用同一种资源 多个任务之间有依赖关系,某个任务的运行依赖于另一个任…...
大内存对电脑性能有哪些提升
在科技飞速发展的今天,电脑已经成为我们生活和工作中不可或缺的伙伴。无论是日常办公、追剧娱乐,还是进行复杂的游戏和专业设计,电脑的性能都至关重要。而在影响电脑性能的众多因素中,内存大小常常被人们忽视。 多任务处理更流畅…...
什么是“微博养铁粉”以及如何增加微博铁粉
发了个发微博养铁工具_微博养铁粉的定义 微博养铁粉是指粉丝通过与博主的互动,成为博主的铁粉。铁粉是微博推出的一种反映粉丝与博主之间亲密度的互动产品。成为铁粉后,粉丝的评论权重增加,更容易上前排,点赞和评论的效果也会更好…...
华为和H3C服务器配置远控管理地址
1、华为RH2288_V3服务器 1.1、启动服务器按DEL按键进入服务器bios 1.2、选择Advanced菜单中的 IPMI iBMC Configuration配置项回车进入。 1.3、IPMI iBMC Configuration配置界面中选择IBMC Configuration配置项回车进入。 1.4、IBMC Configuration 配置项中配置IPV4 Configura…...
Git 查询与切换分支的完整指南
Git 查询与切换分支的完整指南 1. 查询分支列表 查看本地分支 git branch当前分支会以绿色显示并带有 * 标记添加 -v 或 -vv 查看更详细的信息(最后一次提交和跟踪关系) git branch -v # 或者 git branch -vv查看所有分支(包括远程分支&a…...
Spring 中的依赖注入(DI)详解
📌 摘要 在现代 Java 开发中,依赖注入(Dependency Injection, DI) 是 Spring 框架最核心的功能之一。它通过解耦对象之间的依赖关系,提高了代码的可维护性、可测试性和可扩展性。 本文将全面讲解 Spring 中依赖注入的…...