ChernoCPP 2

视频链接:【62】【Cherno C++】【中字】C++的线程_哔哩哔哩_bilibili

参考文章:TheChernoCppTutorial_the cherno-CSDN博客

Cherno的C++教学视频笔记(已完结) - 知乎 (zhihu.com)

C++ 的线程

#include<iostream>
#include<thread>
static bool is_Finished = false;
void DoWork()
{using namespace std::literals::chrono_literals; // 为 1s 提供作用域std::cout << "Started thread ID: "<<std::this_thread::get_id()<<std::endl;while (!is_Finished){std::cout<<"Working..."<<std::endl;std::this_thread::sleep_for(1s);//等待1s}
}
int main()
{std::thread worker(DoWork);std::cin.get(); // 其作用是阻塞主线程is_Finished = true;// 让worker线程终止的条件worker.join();// 让主线程等待worker线程std::cout << "Finished thread ID: " << std::this_thread::get_id() << std::endl;std::cin.get();
}

C++ 计时器

1、有两种选择,一种是用平台特定的API,另一种是用std::chrono,此处推荐后者

2、一个比较好的方法是建立一个Timer类,在其构造函数里面记下开始时刻,在其析构函数里面记下结束时刻,并打印从构造到析构所用的时间。如此就可以用这样一个类来对一个作用域进行计时:

#include<iostream>
#include<chrono>
struct Timer
{std::chrono::time_point<std::chrono::steady_clock> start,end;std::chrono::duration<float> duration;Timer(){start = std::chrono::high_resolution_clock::now();}~Timer(){end = std::chrono::high_resolution_clock::now();duration = end - start;float ms = duration.count() * 1000.0f;std::cout << "Timer took "<< ms << "ms" <<std::endl;}
};
void Function()
{Timer timer;for (int i = 0;i<100;i++)std::cout<<"Hello"<<std::endl;
}
int main()
{Function();std::cin.get();
}

多维数组

int** a2d = new int* [50];
for (int i = 0; i < 50; i++)
{a2d[i] = new int[50];
}for (int i = 0; i < 50; i++)
{delete[] a2d[i];
}
delete[] a2d;

存储二维数组一个很好的优化方法就是:存储在一维数组里面:

int** a2d = new int* [5];
for (int i = 0; i < 5; i++)
{a2d[i] = new int[5];for (int j = 0; j < 5; j++){a2d[i][j] = 2;}
}
int* a1d = new int[5 * 5];for (int i = 0; i < 5; i++)
{for (int j = 0; j < 5; j++){a1d[i + j * 5] = 2;}
}

Sorting

此处主要介绍std::sort,并结合lambda表达式可进行很灵活的排序:

#include<iostream>
#include<vector>
#include<algorithm>
int main()
{std::vector<int> values = {2,3,4,1,5};std::sort(values.begin(), values.end(), [](int a, int b){// 此处两个判断可以将等于2的值放到末尾if(a == 2)return false;if(b == 2)return true;return a < b;});// 此处输出为 1,3,4,5,2for(const int &v:values)std::cout<<v<<std::endl;std::cin.get();
}

类型双关 type punning

(取地址,换成对应类型的指针,再解引用) 

#include <iostream>int main()
{int a = 50;double value = *(double*)&a;std::cout << value << std::endl;std::cin.get();
}

1、可以将同一块内存的东西通过不同type的指针给取出来

2、指针的类型只是决定了其+1或者-1时地址的偏移量

3、以下这个示例说明了:弄清楚内存分布的重要性

struct Entity
{int x,y;
};
int main()
{Entity e = {2,3};int* pos = (int*)&e;std::cout<<pos[0]<<","<<pos[1]<<std::endl;int y = *(int*)((char*)&e+4);std::cout << y << std::endl;std::cin.get();
}

C++ Union

如果想要以不同形式去取出同一块内存的东西,可以用type punning,也可以使用union

共用内存。你可以像使用结构体或者类一样使用它们,你也可以给它添加静态函数或者普通函数、方法等待。然而你不能使用虚方法,还有其他一些限制。但通常人们用联合体来做的事情,是和类型双关紧密相关的。

通常union是匿名使用的,但是匿名union不能含有成员函数。

#include<iostream>
#include<vector>
#include<algorithm>
struct vec2
{float x,y;
};
struct vec4
{union{struct{float x,y,z,w;};struct{vec2 a,b;};};
};
void PrintVec2(const vec2& vec)
{std::cout<<vec.x<<","<<vec.y<<std::endl;
}
int main()
{vec4 vector = {1.0f,2.0f,3.0f,4.0f};PrintVec2(vector.a); // 输出 1,2PrintVec2(vector.b); // 输出 3,4vector.z = 10.0f;PrintVec2(vector.a); // 输出 1,2PrintVec2(vector.b); // 输出 10,4std::cin.get();
}

虚析构函数

只要你允许一个类拥有子类,就一定要把析构函数写成虚函数,否则没人能安全地扩展这个类。

C++ 类型转换

类型转换 casting, type casting

C++是强类型语言,意味着存在一个类型系统并且类型是强制的。
示例:

double value = 5.25;// C风格的转换
double a = (int)value + 5.3;// C++风格的转换
double s = static_cast<int>(value) + 5.3;

2、C++的cast:

static_cast:基础的类型转换,结合隐式转换和用户自定义的转换来进行类型转换

dynamic_cast:安全地在继承体系里面向上、向下或横向转换指针和引用的类型,多态转换

reinterpret_cast:通过解释底层位模式来进行类型转换

const_cast:添加或者移除const性质

条件断点和断点操作

1、条件断点,当达到什么条件触发断点;断点操作:当触发断点后执行什么操作(在窗口输出什么)

2、一个示例,在一个死循环里面,x每次加一,当x被5整除时触发断点,触发断点后打出x的值,并且可以在调试过程中,随时更改断点的条件和动作,并且可以设置是否让程序继续运行

现代C++中的安全以及如何教授

C++里说的安全是什么意思?

安全编程,或者说是在编程中,我们希望降低崩溃、内存泄漏、非法访问等问题。

这一节重点讲讲指针和内存。

用于生产环境使用智能指针,用于学习和了解工作积累,使用原始指针,当然,如果你需要定制的话,也可以使用自己写的智能指针

Precompiled Headers (预编译头文件)

1、由于每次编译时,都需要对头文件以及头文件里面包含的头文件进行编译,所以编译时间会很长。而预编译头文件则是将头文件预先编译为二进制文件,如果此后不修改的话,在编译工程的时候就直接用编译好的二进制文件,会大大缩短编译时间。

2、只把那些不太(经常)会被修改的头文件进行预编译,如std,如windows API或者一些其他的库,如GLFW。

3、如果进行预编译头文件,一个例子:

新建一个工程和解决方案,添加Main.cpp,pch.cpp,pch.h三个文件,内容分别如下:

// Main.cpp
#include"pch.h"int main()
{std::cout<<"Hello!"<<std::endl;std::cin.get();
}
// pch.cpp
#include"pch.h"
// pch.h
#pragma once
#include<iostream>
#include<vector>
#include<memory>
#include<string>
#include<thread>
#include<chrono>
#include<unordered_map>
#include<Windows.h>

在pch.cpp右键,属性-配置属性-C/C++-预编译头-预编译头,里面选择创建, 并在下一行预编译头文件里面添加 pch.h

在项目名称上右键,属性-配置属性-C/C++-预编译头-预编译头,里面选择使用,并在下一行预编译头文件里面添加 pch.h

打开计时工具:工具-选项-项目和解决方案-VC++项目设置-生成计时,就可以看到每次编译的时间

进行对比:

进行预编译头文件前后的首次编译耗时分别为:2634ms和1745ms

进行预编译头文件前后的二次编译(即修改Main.cpp内容后)的耗时分别为:1235ms和312ms

可以看到进行预编译头文件后,时间大大降低

Dynamic Casting

dynamic_cast可以在继承体系里面向上、向下或者平级进行类型转换,自动判断类型,如果转换失败会返回NULL,使用时需要保证是多态,即基类里面含有虚函数。由于dynamic_cast使用了RTTI(运行时类型识别),所以会对性能增加负担

#include<iostream>
class Base
{
public:virtual void print(){}
};
class Player : public Base
{
};
class Enemy : public Base
{
};
int main()
{Player* player = new Player();Base* base = new Base();Base* actualEnemy = new Enemy();Base* actualPlayer = new Player();// 旧式转换Base* pb1 = player; // 从下往上,是隐式转换,安全Player*  bp1 = (Player*)base; // 从上往下,可以用显式转换,危险Enemy* pe1 = (Enemy*)player; // 平级转换,可以用显式转换,危险// dynamic_castBase* pb2 = dynamic_cast<Base*>(player); // 从下往上,成功转换Player* bp2 = dynamic_cast<Player*>(base); // 从上往下,返回NULLif(bp2) { } // 可以判断是否转换成功Enemy* pe2 = dynamic_cast<Enemy*>(player); // 平级转换,返回NULLPlayer* aep = dynamic_cast<Player*>(actualEnemy); // 平级转换,返回NULLPlayer* app = dynamic_cast<Player*>(actualPlayer); // 虽然是从上往下,
//但是实际对象是player,所以成功转换
}

C++中的Structured Binding

C++17引入的新特性,可以在将函数返回为tuple、pair、struct等结构时且赋值给另外变量的时候,直接得到成员,而不是结构。(确保在项目属性-C/C++-语言-C++语言标准,里面打开C++17)

#include<iostream>
#include<tuple>
#include<string>
// 此处tuple换成pair或者struct结构也是一样的
std::tuple<std::string, int> CreatePerson()
{return {"ydc",24};
}
int main()
{auto[name,age] = CreatePerson();std::cout<<name<<","<<age<<std::endl;std::cin.get();
}

std::optional

比如在读取文件内容的时候,往往需要判断读取是否成功,常用的方法是传入一个引用变量或者判断返回的std::string是否为空,例如:

C++17引入了一个更好的方法,std::optional,就如名字一样,是检测变量是否是present的:

#include<iostream>
#include<fstream>
#include<optional>
std::optional<std::string> ReadFileAsString(const std::string& filepath)
{std::ifstream stream(filepath);if (stream){std::string result;//read filestream.close();return result;}return {};
}
int main()
{std::optional<std::string> data = ReadFileAsString("data.txt");// 可以用has_value()来判断是否读取成功if (data.has_value()){std::cout<<"File read successfully!\n";}else{std::cout<<"File not found!\n";}// 也可以用value_or()来判断是否读取成功std::string result = data.value_or("Not resprent");//如果数据不存在,就会返回我们传入的值 Not resprentstd::cout<<result<<std::endl;std::cin.get();
}

C++ 一个变量多种类型  std::variant

C++17引入一种可以容纳多种类型变量的结构,std::variant

#include<iostream>
#include<variant>
int main()
{std::variant<std::string,int> data; // <>里面的类型不能重复data = "ydc";// 索引的第一种方式:std::get,但是要与上一次赋值类型相同,不然会报错std::cout<<std::get<std::string>(data)<<std::endl;// 索引的第二种方式,std::get_if,传入地址,返回为指针if (auto value = std::get_if<std::string>(&data)){std::string& v = *value;}data = 2;std::cout<<std::get<int>(data)<<std::endl;std::cin.get();
}

std::variant的大小是<>里面的大小之和,与union不一样,union的大小是类型的大小最大值

std::any

也是C++17引入的可以存储多种类型变量的结构,其本质是一个union,但是不像std::variant那样需要列出类型

#include<iostream>
#include<any>
// 此处写一个new的函数,是为了断点,看主函数里面哪里调用了new,来看其堆栈
void* operator new(size_t size)
{return malloc(size);
}
int main()
{std::any data;data = 2;data = std::string("ydc");std::string& s = std::any_cast<std::string&>(data);std::cout<<s<<std::endl;std::cin.get();
}

如何让 string 运行更快

一种调试在heap上分配内存的方法,自己写一个new的方法,然后设置断点或者打出log,就可以知道每次分配了多少内存,以及分配了几次:

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(const std::string& name)
{std::cout<<name<<std::endl;
}
int main()
{std::string fullName = "yang dingchao";std::string firstName = fullName.substr(0,4);std::string lastName = fullName.substr(5,8);PrintName(firstName);PrintName(lastName);std::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

以下为运行结果:

Allocing: 8 bytes
Allocing: 8 bytes
Allocing: 8 bytes
yang
dingchao
3 allocations

这个程序仅仅是从一个string取子字符串,就多分配了两次内存,下面来改进它

2、用C++17引入的std::string_view来对同一块内存的string进行截取

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(std::string_view name)
{std::cout<<name<<std::endl;
}
int main()
{std::string fullName = "yang dingchao";std::string_view firstName(fullName.c_str(),4);std::string_view lastName(fullName.c_str()+5,8);PrintName(firstName);PrintName(lastName);std::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

输出如下:

Allocing: 8 bytes
yang
dingchao
1 allocations

3、上面的程序还是有一次分配,如果把std::string改成const char*,就变成了0次分配:

#include<iostream>
#include<string>
static uint32_t s_AllocCount = 0;
void* operator new(size_t size)
{s_AllocCount++;std::cout<<"Allocing: "<<size<<" bytes\n";return malloc(size);
}
void PrintName(std::string_view name)
{std::cout<<name<<std::endl;
}
int main()
{const char* fullName = "yang dingchao";std::string_view firstName(fullName,4);std::string_view lastName(fullName+5,8);PrintName(firstName);PrintName(lastName);std::cout<<s_AllocCount<<" allocations\n";std::cin.get();
}

输出如下:

yang
dingchao
0 allocations

Singleton单例

Singleton只允许被实例化一次,用于组织一系列全局的函数或者变量,与namespace很像。例子:随机数产生的类、渲染器类。

#include<iostream>
class Singleton
{
public:Singleton(const Singleton&) = delete; // 删除拷贝复制函数static Singleton& Get() // 通过Get函数来获取唯一的一个实例,//其定义为static也是为了能直接用类名调用{return s_Instance;}void Function(){} // 执行功能的函数
private:Singleton(){} // 不能让别人实例化,所以要把构造函数放进privatestatic Singleton s_Instance; // 定义为static,让其唯一
};
Singleton Singleton::s_Instance; // 唯一的实例化的地方
int main()
{Singleton::Get().Function();
}

具体的一个简单的随机数类的例子:

#include<iostream>
class Random
{
public:Random(const Random&) = delete; // 删除拷贝复制函数static Random& Get() // 通过Get函数来获取唯一的一个实例{static Random instance; // 在此处实例化一次return instance;}static float Float(){ return Get().IFloat();} // 调用内部函数,可用类名调用
private:float IFloat() { return m_RandomGenerator; } // 将函数的实现放进privateRandom(){} // 不能让别人实例化,所以要把构造函数放进privatefloat m_RandomGenerator = 0.5f;
};
// 与namespace很像
namespace RandomClass {static float s_RandomGenerator = 0.5f;static float Float(){return s_RandomGenerator;}
}
int main()
{float randomNum = Random::Float();std::cout<<randomNum<<std::endl;std::cin.get();
}

使用小的string

在release模式下面,使用size小于16的string,不会分配内存,而大于等于16的string,则会分配32bytes内存以及更多,所以16个字符是一个分界线

#include<iostream>
void* operator new(size_t size)
{std::cout<<"Allocated: "<<size<<" bytes\n";return malloc(size);
}
int main()
{std::string longName = "ydc ydc ydc ydc ydc";std::string shortName = "ydc";std::cin.get();
}

Release模式,只有longName在heap上面分配内存了,输出如下:

Allocated: 32 bytes 

跟踪内存分配的简易办法

重写new和delete操作符函数,并在里面打印分配和释放了多少内存,也可在重载的这两个函数里面设置断点,通过查看调用栈即可知道什么地方分配或者释放了内存

#include<iostream>
void* operator new(size_t size)
{std::cout<<"Allocing "<<size<<" bytes\n";return malloc(size);
}
void operator delete(void* memory, size_t size)
{std::cout<<"Free "<<size<<" bytes\n";free(memory);
}
struct Entity
{int x,y,z;
};
int main()
{{std::string name = "ydc";}Entity* e = new Entity();delete e;std::cin.get();
}

还可以写一个简单统计内存分配的类,在每次new的时候统计分配内存,在每次delete时统计释放内存,可计算出已经分配的总内存:

#include<iostream>
struct AllocationMertics
{uint32_t TotalAllocated = 0;uint32_t TotalFreed = 0;uint32_t CurrentUsage() {return TotalAllocated - TotalFreed;}
};
static AllocationMertics s_AllocationMetrics;
void* operator new(size_t size)
{s_AllocationMetrics.TotalAllocated+=size;return malloc(size);
}
void operator delete(void* memory, size_t size)
{s_AllocationMetrics.TotalFreed += size;free(memory);
}
static void PrintMemoryUsage()
{std::cout<<"Memory usage: "<<s_AllocationMetrics.CurrentUsage()<<" bytes\n";
}
int main()
{PrintMemoryUsage();{std::string name = "ydc";PrintMemoryUsage();}PrintMemoryUsage();std::cin.get();
}

 lvalue and rvalue(左值和右值)

1、 左值:有存储空间的值,往往长期存在;右值:没有存储空间的短暂存在的值

2、 一般而言,赋值符号=左边的是左值,右边的是右值

3、在给函数形参列表传参时,有四种情况:

#include<iostream>
void PrintName(std::string name) // 可接受左值和右值
{std::cout<<name<<std::endl;
}
void PrintName(std::string& name) // 只接受左值引用,不接受右值
{std::cout << name << std::endl;
}
void PrintName(const std::string& name) // 接受左值和右值,把右值当作const lvalue&
{std::cout << name << std::endl;
}
void PrintName(std::string&& name) // 接受右值引用
{std::cout << name << std::endl;
}
int main()
{std::string firstName = "yang";std::string lastName = "dingchao";std::string fullName = firstName + lastName;PrintName(fullName);PrintName(firstName+lastName);std::cin.get();
}

move semantics

比如一个类Entity含有一个成员Name为String类型,如果要用常量字符串来初始化这个类,就会先调用String的构造函数,再调用String的拷贝构造函数(经Entity构造函数里面调用),然后再调用String的析构函数,但是使用move操作就可以让中间的一次拷贝变成move,就可以少一次new,我理解为浅拷贝的意思:

#include<iostream>
class String
{
public:String() = default;String(const char* string) //构造函数{printf("Created\n");m_Size = strlen(string);m_Data = new char[m_Size];memcpy(m_Data,string,m_Size);}String(const String& other) // 拷贝构造函数{printf("Copied\n");m_Size = other.m_Size;m_Data = new char[m_Size];memcpy(m_Data,other.m_Data,m_Size);}String(String&& other) noexcept // 右值引用拷贝,相当于移动,就是把复制一次指针,原来的指针给nullptr{printf("Moved\n");m_Size = other.m_Size;m_Data = other.m_Data;other.m_Size = 0;other.m_Data = nullptr;}~String(){printf("Destroyed\n");delete m_Data;}
private:uint32_t m_Size;char* m_Data;
};
class Entity
{
public:Entity(const String& name) : m_Name(name){}Entity(String&& name) : m_Name(std::move(name)) // std::move(name)也可以换成(String&&)name{}
private:String m_Name;
};
int main()
{	Entity entity("ydc");std::cin.get();
}

如此的代码,在实例化entity的时候,如果传入的是字符串常量(右值),则会调用拷贝的右值版本,避免了一次new,如果传入的是String(左值),则仍然会进行一次左值拷贝

std::move

1、使用std::move,返回一个右值引用,可以将本来的copy操作变为move操作:

#include<iostream>class String
{
public:String() = default;String(const char* string){printf("Created\n");m_Size = strlen(string);m_Data = new char[m_Size];memcpy(m_Data,string,m_Size);}String(const String& other){printf("Copied\n");m_Size = other.m_Size;m_Data = new char[m_Size];memcpy(m_Data,other.m_Data,m_Size);}String& operator=(const String& other){printf("Cpoy Assigned\n");delete [] m_Data;m_Size = other.m_Size;m_Data = new char[m_Size];memcpy(m_Data, other.m_Data, m_Size);return *this;}String(String&& other) noexcept{printf("Moved\n");m_Size = other.m_Size;m_Data = other.m_Data;other.m_Size = 0;other.m_Data = nullptr;}String& operator=(String&& other) noexcept{printf("Move Assigned\n");if(this != &other){ delete [] m_Data;m_Size = other.m_Size;m_Data = other.m_Data;other.m_Size = 0;other.m_Data = nullptr;}return *this;}~String(){printf("Destroyed\n");delete m_Data;}
private:uint32_t m_Size;char* m_Data;
};
int main()
{	String name = "ydc"; // String name("ydc");调用构造函数String nameCopy = name; // String nameCopy(name);调用拷贝构造函数String nameAssign;nameAssign = name; // 调用拷贝赋值函数String nameMove = std::move(name); // String nameMove(std::move(name));调用右值引用构造函数String nameMoveAssign;nameMoveAssign = std::move(name); // 调用右值引用赋值函数std::cin.get();
}

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

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

相关文章

四、MySQL读写分离之MyCAT

一、读写分离概述 1、什么是读写分离&#xff1a; 读写分离&#xff1a;就是将读写操作分发到不同的服务器&#xff0c;读操作分发到对应的服务器 &#xff08;slave&#xff09;&#xff0c;写操作分发到对应的服务器&#xff08;master&#xff09; ① M-S (主从) 架构下&…

Java设计模式:外观模式之优雅门面(九)

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 在软件工程中&#xff0c;设计模式是解决常见设计问题的经验总结&#xff0c;它为开发者提供了一种通用的、可复用的解决方案。外…

书生浦语训练营2期-第二节课笔记作业

目录 一、前置准备 1.1 电脑操作系统&#xff1a;windows 11 1.2 前置服务安装&#xff08;避免访问127.0.0.1被拒绝&#xff09; 1.2.1 iis安装并重启 1.2.2 openssh安装 1.2.3 openssh服务更改为自动模式 1.2.4 书生浦语平台 ssh配置 1.3 补充&#xff08;前置服务ok…

Thread的基本用法

目录 正文&#xff1a; 1.线程创建 2.线程休眠 3.获取线程实例 4.线程中断 5.线程等待join() 总结&#xff1a; 正文&#xff1a; 1.线程创建 线程创建是多线程编程的第一步&#xff0c;它涉及到创建一个可以并行执行的新线程。在Java中&#xff0c;有几种不同的方法可…

【Laravel】08 RESTful风格

【Laravel】08 视图模板动态渲染数据 1. RESTful风格 1. RESTful风格 (base) ➜ example-app php artisan make:model Blog -mc Model created successfully. Created Migration: 2024_04_01_143040_create_blogs_table Controller created successfully.(base) ➜ example-…

简述JMeter实现分布式并发及操作

为什么要分布式并发&#xff1f; JMeter性能实践过程中&#xff0c;一旦进行高并发操作时就会出现以下尴尬场景&#xff0c;JMeter客户端卡死、请求错误或是超时等&#xff0c;导致很难得出准确的性能测试结论。 目前知道的有两个方法可以解决JMeter支撑高并发&#xff1a; …

阿里 对象存储OSS 云存储服务

1.简介 对象存储服务(Object Storage Service ,OSS) 是一种 海量、安全、低成本、高可靠的云存储服务&#xff0c;适合存放任意类型的文件。容量和处理能力弹性扩展&#xff0c;多种存储类型供选择&#xff0c;全面优化存储成本。 2.如何使用。参考文档 看文档&#xff0c;说的…

【python从入门到精通】-- 第四战:语句汇总

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

docker从入门到熟悉

一、什么是docker&#xff1f; Docker是一个用于开发&#xff0c;交付和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。借助Docker&#xff0c;您可以以与管理应用程序相同的方式来管理基础架构。通过利用Docker的快速交付…

GPT3, llama2, InternLM2技术报告对比

GPT3&#xff08;September 22, 2020&#xff09;是大语言应用的一个milestone级别的作品&#xff0c;Llama2&#xff08;February 2023&#xff09;则是目前开源大模型中最有影响力的作品&#xff0c;InternLM2&#xff08;2023.09.20&#xff09;则是中文比较有影响力的作品。…

Linux文件搜索工具(gnome-search-tool)

opensuse下安装: sudo zypper install gnome-search-tool 操作界面:

JNews Theme最新版本:新闻网站首选的WordPress主题,功能强大且灵活

JNews Theme最新版本&#xff1a;新闻网站的首选WordPress主题 在当今数字化时代&#xff0c;新闻网站扮演着至关重要的角色。为了打造一个吸引读者、功能齐全的新闻平台&#xff0c;选择一款合适的WordPress主题显得尤为关键。而JNews Theme最新版本正是新闻网站的首选WordPr…

MacOS - brew 和 brew cask 有什么区别?

brew 是 ruby 的包管理&#xff0c;后来看 yangzhiping 的博客介绍了 brew cask&#xff0c;感觉 cask 是更好的关联关系管理&#xff0c;但是&#xff0c;我后来使用过程中&#xff0c;发现很多软件 brew cask 里没有&#xff0c;但是 brew 里面倒是挺多&#xff01;今天来给说…

java面试题(Redis)

事情干的差不多了&#xff0c;开刷面试题和算法&#xff0c;争取在短时间内快速成长&#xff0c;理解java面试的常见题型 一、redis使用场景&#xff1a; 缓存&#xff1a;穿透、击穿、雪崩 双写一致、持久化 数据过期、淘汰策略 分布式锁&#xff1a;setnx、redisson 计数…

【JavaScript】函数 ⑦ ( 函数定义方法 | 命名函数 | 函数表达式 )

文章目录 一、函数定义方法1、命名函数2、函数表达式3、函数表达式示例 一、函数定义方法 1、命名函数 定义函数的标准方式 就是 命名函数 , 也就是之前讲过的 声明函数 ; 函数 声明后 , 才能被调用 ; 声明函数的语法如下 : function functionName(parameters) { // 函数体 …

计算机网络:数据链路层 - 可靠传输协议

计算机网络&#xff1a;数据链路层 - 可靠传输协议 可靠传输概念停止-等待协议 SW回退N帧协议 GBN选择重传协议 SR 可靠传输概念 如下所示&#xff0c;帧在传输过程中受到干扰&#xff0c;产生了误码。接收方的数据链路层&#xff0c;通过真伪中的真检验序列 FCS 字段的值&…

ROS 2边学边练(13)-- 创建一个功能包

前言 功能包是啥 简单理解&#xff0c;功能包就是一个文件夹&#xff0c;一个具备一定功能的文件夹&#xff0c;一个有组织有结构的文件夹&#xff0c;一个能方便分享给其他人使用的文件夹&#xff0c;比如我们的小海龟功能包&#xff0c;它就是一个文件夹&#xff0c;名字叫t…

WPS 不登录无法使用基本功能的解决办法

使用wps时&#xff0c;常常有个比较让人烦恼的事&#xff0c;在不登录的情况下&#xff0c;新建或者打开文档时&#xff0c;wps不让你使用其基本的功能&#xff0c;如设置字体等&#xff0c;相关界面变成灰色&#xff0c;这时Wps提示用户登录注册或登录&#xff0c;但我又不想登…

【QT入门】 无边框窗口设计之综合运用,实现WPS的tab页面

往期回顾&#xff1a; 【QT入门】 无边框窗口设计之实现窗口阴影-CSDN博客 【QT入门】 无边框窗口设计之实现圆角窗口-CSDN博客 【QT入门】 无边框窗口设计综合运用之自定义标题栏带圆角阴影的窗口-CSDN博客 【QT入门】 无边框窗口设计之综合运用&#xff0c;实现WPS的tab页面 …

Nexus的docker安装,maven私服

文章目录 前言安装创建文件夹设置文件夹权限docker创建指令制作docker-compose.yaml文件 查看网站访问网页查看密码 前言 nexus作为私服的maven仓库&#xff0c;在企业级应用中&#xff0c;提供了依赖来源的稳定性&#xff0c;为构建庞大的微服务体系&#xff0c;打下基础 安…