读《effective modern c++》笔记总结

news2024/11/13 9:57:07

文章目录

  • 一、类型推导与auto
      • 模板类型推导
        • ParamType是一个指针或引用,但不是通用引用
        • ParamType是一个通用引用
        • ParamType即不是指针也不是引用
        • 数组实参
        • 函数实参
      • auto类型推导
  • 二、decltype的理解
  • 三、优先考虑auto而非显示类型声明
  • 四、区别使用()和{}创建对象
  • 五、优先考虑nullptr而非0和NULL
  • 六、优先考虑别名声明而非typedefs
  • 七、优先考虑限域枚举而非未限域枚举
  • 八、优先考虑使用deleted函数而非使用未定义的私有声明
  • 九、使用override声明重载函数
  • 十、特殊成员函数的生成
  • 十一、 智能指针
      • 独占资源型指针std::unique_ptr
      • 共享资源型指针std::shared_ptr
      • std::weak_ptr解决std::shared_ptr悬空问题
      • 优先考虑使用std::make_unique和std::make_shared而非new
  • 十二、std::move和std::forward
      • 区分通用引用与右值引用
      • 右值引用使用std::move,通用引用使用std::forward
      • 避免在通用引用上重载
      • 引用折叠
  • 十三、Lambda表达式
      • 避免使用默认捕获模式
      • 使用初始化捕获来移动对象到闭包中
      • 对于std::forward的auto&&形参使用decltype


一、类型推导与auto

模板类型推导

针对 模板函数:

template<typename T>
void f(ParamType param)

调用:

f(expr);

接下来针对ParamType类型的三种情况:

  • ParamType是一个指针或引用,但不是通用引用
  • ParamType是一个通用引用
  • ParamType即不是指针也不是引用

这 三种情况,来理解看模板类型T的推导

ParamType是一个指针或引用,但不是通用引用

直接看例子:
模板声明:

template<typename T>
void f(T& param) //param是一个引用

变量声明:

int x=27;
const int cx=x;
const int&  rx=cx;

调用模板函数:

f(x); //T是int,param类型是int&
f(cx);//T是const int,param类型const int&
f(rx);//T是const int,param的类型是const int &

规律:
(1)当传递一个const对象给一个引用类型的参数时,传递的对象保留了常量性(向T&类型的参数传递const对象是安全的)
(2)如果expr类型是一个引用,将忽略引用部分

ParamType是一个通用引用

直接看例子:
模板声明

template<typename T>
void f(T&& param); //param现在是一个通用引用类型

变量声明:

int x=27;
const int cx=x;
const int& rx=cx;

调用模板函数:

f(x);//x是左值,所以T是int&,param是int&
f(cx);//cx是左值,所以T是const int&,param类型是const int&
f(rx);//rx是左值,所以T是const int&,param类型是const int&
f(27);//27是右值,所以T是int,param类型是int&&

规律:
(1)如果expr是左值,T和ParamType都会被推导为左值引用(唯一一种T和ParamType都被推导为引用的情况)
(2)如果expr是右值,推导规则为上面那个

ParamType即不是指针也不是引用

直接看例子:
模板声明:

template<typename T>
void f(T param); //以传值的方式处理param

变量声明:

int x=27;
const int cx=x;
const int& rx=cx;

调用模板函数 :

f(x);//T和param都是int
f(cx);//T和param都是int
f(rx);//T和param都是int

规律:
(1)只有在传值给形参时才会忽略常量性和易变性

数组实参

在普通写程序中,数组经常会退化为指向它的第一个元素的指针 ,比如:

const char name[]="J.P.Briggs"; //name的类型是const char[13]
const char* ptrToName=name;//数组退化为指针

直接看这组讲解的例子:
模板声明:

template<typename T>
void f(T param);

调用模板函数:

f(name);//name是一个数组,但是T被推导为const char*

将模板声明改成如下:

template<typename T>
void f(T& param);

调用模板函数:

f(name);//传数组,T被推导为const char[13],param被推导为const char(&)[13]

规律:
(1)对模板声明为一个指向数组的引用可以在模板 函数中推导出数组的大小

函数实参

直接看例子:
模板声明:

template<typename T>
void f1(T param); //传值

template<typename T>
void f2(T& param); //传引用

函数声明:

void someFunc(int,double); //someFunc是一个函数,类型是void(int,double)

使用模板函数:

f1(someFunc);//param被推导为指向函数的指针 ,类型是void(*)(int,double)
f2(someFunc);//param被推导为指向函数的引用,类型为void(&)(int,double)

auto类型推导

auto的类型推导除了一个例外,其他情况都和模板类型推导一样。接下来主要说这个特例:
变量声明:

auto x1=27;
auto x2(27);//类型int,值是27,同上
auto x3={27};//类型是std::initializer_list<int>,值是{27}
auto x4{27};//同上

当用auto声明的变量使用花括号进行初始
化,auto类型推导会推导出auto的类型为 std::initializer_list。
但大括号里面的变量类型不能不一样,如:

auto x5={1,2,3.0}; //错误!auto类型推导不能⼯作

对于花括号的处理是auto类型推导和模板类型推导唯一不同的地方。当使用auto的变量使用花括号的语法进行初始化的时候,会推导出std::initializer_list的实例化,但是对于模板类型推导这样就行不通:

auto x={11,23,9}; //x的类型是std::initializer_list<int>
template<typename T>
void f(T param);
f({11,23,9}); //错误!不能推导出T

二、decltype的理解

decltype可以通过一个 名字或者 表达式推断出类型。
举例:

const int i=0; //decltype(i)是const int
bool f(const Widget& w); //decltype(w)是const Widget&

三、优先考虑auto而非显示类型声明

auto变量从初始化表达式中推导出类型,所以我们必须初始化
std::function 是⼀个C++11标准模板库中的⼀个模板,它泛化了函数指针的概念。
实例化 std::function 并声明⼀个对象这个对象将会有固定的⼤小。当使用这个对象保存⼀个闭包时它可能大小不足不能存储,这个时候 std::function 的构造函数将会在堆上⾯分配内存来存储,这就造成了使用 std::function 比auto会消耗更多的内存

四、区别使用()和{}创建对象

c++11使用统一初始化来整合这些混乱且繁多的初始化语法(括号初始化叫统一初始化
使用花括号,指定一个容器元素很容易:

std::vector<int> v{1,3,5}; //v包括1,3,5

括号初始化也能被用于为非静态数据成员指定默认初始值

class Widget{
...
private:
  int x{0}; //没问题,x初始值为0
  int y=0; //同上
  int z(0);//错误
}

不可拷贝的对象可以使用花括号初始化或者小括号初始化,但是不能使用"="初始化:

std::vector<int> ai1{0}; //没问题,x初始值为0
std::atomic<int> ai2(0); //没问题
std::atomic<int> ai3 = 0; //错误!

由上看出,几种初始化方式只有括号任何地方都能被使用。
括号表达式有一个异常的特性,它不允许内置类型隐式的变窄转换(narrowing conversion)。如果一个使用了括号初始化的表达式的值无法用于初始化某个类型的对象,代码就不会通过编译:

double  x,y,z;
int sum1{x+y+z};//错误,三个double的和不能用来初始化int类型的变量
int sum2(x+y+z);
int sum3=x+y+z;//同上

C++规定任何能被决议为⼀个声明的东西必须被决议为声明。这个规则的副作用是让很多程序员备受折磨:当他们想创建⼀个使⽤默认构造函数构造的对象,却不小心变成了函数声明
如:
想使用一个实参调用一个构造函数,可以如下:

Widget w1(10); //使⽤实参10调⽤Widget的⼀个构造函数

但如果你尝试使⽤⼀个没有参数的构造函数构造对象,它就会变成函数声明:

Widget w2(); //最令⼈头疼的解析!声明⼀个函数w2,返回Widget

由于函数声明中形参列表不能使用花括号,所以使用花括号初始化表明你想调用默认构造函数构造对象就没有问题:

Widget w3{}; //调⽤没有参数的构造函数构造对象

还有两点注意的就是:
(1)auto对于花括号的推导,看上面
(2)vector对于()和{}区别

五、优先考虑nullptr而非0和NULL

六、优先考虑别名声明而非typedefs

c++11提供了一个别名声明:

using UPtrMapSS = std::unique_ptr<std::unordered_map<std::string, std::string>>;

当声明⼀个函数指针时别名声明更容易理解:

// FP是⼀个指向函数的指针的同义词,它指向的函数带有int和const std::string&形参,不返回任何东
西
typedef void (*FP)(int, const std::string&); // typedef
//同上
using FP = void (*)(int, const std::string&); // 别名声明

需要特别注意的一点:别名声明可以被模板化,但是typedef不能

template<typename T>
using MyAllocList = std::list<T,MyAlloc<T>>;
MyAllocList<Widget> lw;

七、优先考虑限域枚举而非未限域枚举

八、优先考虑使用deleted函数而非使用未定义的私有声明

deleted函数不能以任何方式被调用,即使你在成员函数或者友元函数里面调用deleted 函数也不能通过编译
注意,deleted 函数被声明为public而不是private;因为,C++会在检查 deleted 状态前检查它的访问性。当客户端代码调用一个私有的 deleted 函数,⼀些编译器只会给出该函数是private的错误,而没有诸如该函数被 deleted 修饰的错误。因此,如果要将老代码的"私有且未定义"函数替换为 deleted 函数时请⼀并修改它的访问性为public,这样可以让编译器产生更好的错误信息。

deleted 函数还有⼀个重要的优势是任何函数都可以标记为 deleted(包括非成员函数)
deleted 函数还可以(private成员函数做不到的地方)禁止一些模板的实例化。

九、使用override声明重载函数

重写与重载区分;
首先针对,重写,需要满足的要求:

  • 基类函数必须是virtual
  • 基类和派生类函数名必须完全一样(除非是析构函数)
  • 基类和 派生类函数参数必须完全一样
  • 基类和派生类函数常量性(constness)必须完全一样
  • 基类和派生类函数的返回值和异常说明(exception specifications)必须兼容
  • 函数的引用限定符必须完全一样;成员函数的引用限定符是C++11很少抛头露脸的特性,所以如果你从没听过它无需惊讶。它可以限定成员函数只能用于左值或者右值

如下,对于限定符 :

class Widget {
public:void doWork() &; //只有*this为左值的时候才能被调⽤
void doWork() &&; //只有*this为右值的时候才能被调⽤
};
…
Widget makeWidget(); // ⼯⼚函数(返回右值)
Widget w; // 普通对象(左值)
…
w.doWork(); // 调⽤被左值引⽤限定修饰的Widget::doWork版本
// (即Widget::doWork &)
makeWidget().doWork(); // 调⽤被右值引⽤限定修饰的Widget::doWork版本
// (即Widget::doWork &&)

需要注意的是,如果基类的虚函数有引用限定符,派生类的重写就必须具有相同的引用限定符。如果没有,那么新声明的函数还是属于派生类,但是不会重写父类的任何函数。

override会显式的将派生类函数指定为应该是基类重写版本,保证派生类虚函数结果是我们想要的,如果不是会报错(程序的健壮性)

final

十、特殊成员函数的生成

特殊成员函数是c++自己生成的,c++98有四个:默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符函数;并且这些函数仅在需要的时候才生成,且是隐式public且inline。
c++11又有了两个:移动构造函数和移动赋值运算符
如下:

class Widget {
public:
...
Widget(Widget&& rhs);
Widget& operator=(Widget&& rhs);
...
};

对于移动构造而言,一旦生成就会对非static数据执行逐成员的移动,核心是对对象使用 std::move,然后函数决议时会选择执行移动还是拷贝操作,如果支持移动就会逐成员移动类成员和基类成员,如果不支持移动就执行拷贝操作就好了。
如果你声明了某个移动函数,编译器就不再生成另⼀个移动函数。
如果你声明了某个移动函数,就表明这个类型的移动操作不再是“逐⼀移动成员变量”的语义,即你不需要编译器默认⽣成的移动函数的语义,因此编译器也不会为你生成另⼀个移动函数。
再进⼀步,如果⼀个类显式声明了拷贝操作,编译器就不会生成移动操作。这种限制的解释是如果声明拷⻉操作就暗⽰着默认逐成员拷⻉操作不适⽤于该类,编译器会明⽩如果默认拷⻉不适⽤于该类,移动操作也可能是不适⽤的。这是另⼀个⽅向。声明移动操作使得编译器不会⽣成拷⻉操作。编译器通过给这些函数加上delete来保证

十一、 智能指针

独占资源型指针std::unique_ptr

默认情况下,std::unique_ptr 等同于原始指针
因此, std::unique_ptr 只支持移动操作,且移动操作将所有权从源指针转移到目的指针。
std::unique_ptr 有两种形式,一种用于单个对象( std::unique_ptr ),一种用于数组( std::unique_ptr<T[]> )

共享资源型指针std::shared_ptr

std::shared_ptr 通过引用计数来确保它是否是最后⼀个指向某种资源的指针,引用计数关联资源并跟踪有多少 std::shared_ptr 指向该资源。 std::shared_ptr 构造函数递增引用计数值,析构函数递减值,拷贝赋值运算符可能递增也可能递减值。
引用计数影响的性能问题如下:

  • std::shared_ptr大小是原始指针的两倍,内部包含一个指向资源的原始指针,还包含一个资源的引用计数值;
  • 引用计数必须动态分配
  • 递增递减引用计数必须是原子性

移动 std::shared_ptr 会比拷贝它要快:拷贝要求递增引用计数值,移动不需要
指定自定义销毁器不会改变std::shared_ptr对象的大小。不管销毁器是什么,一个std::shared_ptr对象都是两个指针大小
std::shared_ptr对象的内存是这样的:
在这里插入图片描述
控制块创建遵循的规则:

  • std::make_shared总是创建一个控制块。它创建⼀个指向新对象的指针,所以可以肯定 std::make_shared 调⽤时对象不存在其他控制块。
  • 当从独占指针上构造出std::shared_ptr时会创建控制块(即std::unique_ptr或者std::auto_ptr)std::shared_ptr 侵占独占指针所指向的对象的独占权,所以std::unique_ptr 被设置为null
  • 当从原始指针上构造出std::shared_ptr时会创建控制块。但是用std::shared_ptr 或者std::weak_ptr 作为构造函数实参创建 std::shared_ptr 不会创建新控制块,因为它可以依赖传递来的智能指针指向控制块。

但是需要注意的是,从原始指针上构造超过⼀个 std::shared_ptr,容易造成同一个原始指针有多个引用计数值,每一个最后都会变成,然后最终导致原始指针销毁多次,第二次销毁开始产生未定义行为
如下:

auto pw = new Widget; // pw是原始指针
…
std::shared_ptr<Widget> spw1(pw); // 为*pw创建控制块
…
std::shared_ptr<Widget> spw2(pw); // 为*pw创建第⼆个控制块

对于这种情况,两点建议:

  • 避免传给 std::shared_ptr 构造函数原始指针。通常替代方案是使用std::make_shared
  • 如果你必须传给 std::shared_ptr 构造函数原始指针,直接传new出来的结果,不要传指针变量。
std::shared_ptr<Widget> spw1(new Widget); // 直接使⽤new的结果

控制块通常只占个word大小,自定义销毁器和分配器可能会让它变大一点。通常控制块的实现比你想的更复杂⼀些。它使用继承,甚至里面还有⼀个虚函数(用来确保指向的对象被正确销毁)。这意味着使⽤ std::shared_ptr 还会招致控制块使用虚函数带来的成本。

std::shared_ptr 不能处理的另一个东西是数组。

std::weak_ptr解决std::shared_ptr悬空问题

std::weak_ptr是一个类似std::shared_ptr但不影响对象引用计数的指针

std::weak_ptr 的潜在使用场景包括:caching、observer lists、打破 std::shared_ptr 指向循
环。

优先考虑使用std::make_unique和std::make_shared而非new

std::make_unique 和 std::make_shared 有三个make functions中的两个:接收抽象参数,完美转发到构造函数去动态分配⼀个对象,然后返回这个指向这个对象的指针。第三个make function 是std::allocate_shared. 它和 std::make_shared ⼀样,除了第⼀个参数是用来动态分配内存的对
象。

十二、std::move和std::forward

对于移动语义和完美转发:

  • 移动语义用移动操作来代替昂贵的复制操作。正如复制构造函数和复制赋值操作符给了你赋值对象的权利⼀样,移动构造函数和移动赋值操作符也给了控制移动语义的权利。移动语义也允许创建只可移动(move-only)的类型,例如 std::unique_ptr , std::future 和std::thread 。
  • 完美转发使接收任意数量参数的函数模板成为可能,它可以将参数转发到其他的函数,使目标函数接收到的参数与被传递给转发函数的参数保持⼀致。

记住,参数(parameter)永远是左值(lValue)

std::move 和 std::forward 仅仅是执行转换(cast)的函数(事实上是函数模板)。 std::move 无条件
的将它的参数转换为右值,而 std::forward 只在特定情况满足时下进行转换。
std::move 除了转换它的参数到右值以外什么也不做,避免了一次复制操作的代价
注意,移动构造函数只接受⼀个指向非常量(non-const) 的右值引用

  • 不要在移动对象的时候,声明他们为常量。对常量对象的移动请求会悄无声息的被转化为复制操作。
  • std::move 不仅不移动任何东西,而且它也不保证它执行转换的对象可以被移动。关于 std::move ,你能确保的唯一 一件事就是将它应用到一个对象上,你能够得到一个右值。

std::forward 是一个有条件的转换:它只把由右值初始化的参数,转换为右值。
std::move 的使用代表着无条件向右值的转换,而使用std::forward 只对绑定了右值的引用进行到右值转换。这是两种完全不同的动作。前者是典型地为了移动操作,而后者只是传递(亦作转发)⼀个对象到另外⼀个函数,保留它原有的左值属性或右值属性。

区分通用引用与右值引用

T&& 有两种不同的意思:

  • 右值引用,只绑定到右值上,并且它们主要的存在原因就是为了声明某个对象可以被移动
  • 既可以是一个右值引用,也可以是一个左值引用。这种引用在源码里看起来像右值引用(也即 T&& ),但是它们可以表现得它们像是左值引用(也即 T& )。(二重性)

出现通用引用的情况:(存在类型推导)

  • 函数模板参数
template <typename T>
void f(T&& param); //param是⼀个通⽤引⽤
  • auto声明符
auto&& val2 = var1; //var2是⼀个通⽤引⽤

如果T&& 不带有类型推导,那么它就是⼀个右值引用。

void f(Widget&& param); //没有类型推导
//param是⼀个右值引⽤
Widget&& var1 = Widget(); //没有类型推导
//var1是⼀个右值引⽤

通用引用是引用,所以它们必须被初始化。一个通用引用的初始值决定了它是代表了右值引用还是左值引用。

template <typename T>
void f(T&& param); //param是⼀个通⽤引⽤
Widget w;
f(w); //传递给函数f⼀个左值;参数param的类型
//将会是Widget&,也即左值引⽤
f(std::move(w)); //传递给f⼀个右值;参数param的类型会是
//Widget&&,即右值引⽤

对T&&排除是通用引用的情况:

  • 必须保证是T&&,否则就不是通用引用。如下:
template <typename T>
void f(std::vector<T>&& param); //param是⼀个右值引⽤

param 的类型声明并不是 T&& ,而是一个 std::vector&& ,排除了参数 param 是一个通用引用的可能性,param 因此是⼀个右值引用。验证如下:

std::vector<int> v;
f(v); //错误!不能将左值绑定到右值引⽤
  • 出现一个简单的 const 修饰符,也会使一个引用失去成为通用引用的资格。如下:
template <typename T>
void f(const T&& param); //param是⼀个右值引⽤

T&& 是决定只绑定到右值还是可以绑定任意对象

对于,通用引用来说,主要是通过类型推导将左值和右值区分,T类型的左值被推导为&类型,T类型的右值被推导为T(非引用)

右值引用使用std::move,通用引用使用std::forward

通用引用可能绑定到有资格移动的对象上,并且通用引用使用右值初始化时,才可将其强制转换为右值。

避免在通用引用上重载

引用折叠

通用引用的模板参数的编码问题:

template<typename T>
void func(T&& param);

左值被传入时,T被推导为左值,左值被编码为左值引用;当右值被传入时,T被推导为非引用

如果一个上下文中允许引用的引用存在(比如,模板函数的实例化),引用根据规则折叠为单个引用:
如果任一引用为左值引用,则结果为左值引用。否则(即,如果引用都是右值引用),结果为右值引用

引用折叠发生的四种情况:

  • 模板实例化
  • auto变量的类型生成
  • 使用typedef和别名声明,在创建或者定义typedef过程中出现了引用的引用,则引用折叠就会起作用
typedef T&& RvalueRefToT;
  • decltype使用的情况

十三、Lambda表达式

lambda表达式就是一个表达式,

std::find_if(container.begin(), container.end(),
[](int val){ return 0 < val && val < 10; }); // 本⾏⾼亮

闭包是lambda创建的运行时对象,在上面的std::find_if 调用中,闭包是运行时传递给 std::find_if 第三个参数。
Lambda通常被用来创建闭包,该闭包仅用作函数的参数,并且通常可以拷贝,所以可能有多个闭包对应于一个lambda。如下:

{
int x; // x is local variable
...
auto c1 = [x](int y) { return x * y > 55; }; // c1 is copy of the closure
//produced by the lambda
auto c2 = c1; // c2 is copy of c1
auto c3 = c2; // c3 is copy of c2
...
}

c1, c2,c3都是lambda产生的闭包的副本。

避免使用默认捕获模式

生命周期的问题-----------》变量悬空
使用显式的局部变量和参数引用捕获方式,显式捕获能让人更容易想起“确保没有悬空变量”。
在通常情况下,按值捕获并不能完全解决悬空引用的问题。这里的问题是如果你按值捕获的是⼀个指针,你将该指针拷贝到lambda对应的闭包里,但这样并不能避免lambda外删除指针的行为,从而导致你的指针变成悬空指针。
⼀个定义在全局空间或者指定命名空间的全局变量,或者是⼀个声明为static的类内或文件内的成
员。这些对象也能在lambda里使用,但它们不能被捕获。

使用初始化捕获来移动对象到闭包中

什么是初始化捕获?
初始化捕获就是支持使用初始化器来初始化捕获的变量

auto func = [pw = std::make_unique<Widget>()] 
{ return pw->isValidated() 
&& pw->isArchived(); };

如上面的pw = std::make_unique<Widget>()

使用初始化捕获可以让你指定:

  1. 从lambda生成的闭包类中的数据成员名称;
  2. 初始化该成员的表达式;

对于std::forward的auto&&形参使用decltype

对 auto&& 参数使用decltype 来( std::forward )转发参数;

auto f = [](auto&& x)
{ return func(normalize(std::forward<???>(x))); };

把 x 完美转发给函数 normalize 。首先,x需要改成通用引用,其次,需要使用std::forward 将 x 转发到函数 normalize 。
但是在理论和实际之间存在⼀个问题:你传递给 std::forward 的参数是什么类型,就决定了上面的 ??? 该怎么修改。
⼀般来说,当你在使用完美转发时,你是在⼀个接受类型参数为 T 的模版函数⾥,所以你可以写
std::forward 。但在泛型lambda中,没有可⽤的类型参数 T 。在lambda⽣成的闭包⾥,模版化的 operator() 函数中的确有⼀个 T ,但在lambda⾥却⽆法直接使⽤它。
把 decltype(x) 传递给 std::forward 都能得到我们想要的结果

auto f =
[](auto&& param)
{return func(normalize(std::forward<decltype(pram)>(param)));
};

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

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

相关文章

大华 海康 宇视 摄像头 onvif协议 调整时间 开发过程 整理

1、onvif官网 查看SetSystemDateAndTime 方法。 2、下载 ONVIF Device Test Tool 工具&#xff0c;使用教程可以 在这查看。 3、根据Test Tool 工具生成的request进行 Send request 测试。 有了这个本质就是http请求了&#xff0c;我认为可以自己写http请求尝试&#xff0c;我…

大地200C

8芯网线 【24&#xff0c;M03&#xff0c;冷却&#xff0c; m35&#xff0c;m34&#xff0c;m33&#xff0c;m32&#xff0c;24】 冷却【m08开&#xff0c;m09关】 M10夹紧M11松开 M18润滑【m127开&#xff0c;m227关】 X轴&#xff1a;5000 3.0A Y轴&#xff1…

Mybatis-Plus学习4 Page分页

ctrl P 查看可填的属性类型 alt 回车 自动填充数据类型 1、使用Page分页需要先配置config类&#xff0c;加上拦截器 Configuration MapperScan("com/learn/mybatisplus/mapper") public class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatisP…

TiDB 升级利器(参数对比)——TiDBA

作者&#xff1a; 啦啦啦啦啦 原文来源&#xff1a; https://tidb.net/blog/299f0bdc 一.背景 针对 LTS 版本&#xff0c;PingCAP 会提供最多至 3 年时间的常规版本更新&#xff0c;以解决版本运行过程中遇到的问题&#xff0c;以及安全相关的漏洞修复。而对于已经结束维护…

Css面试题

快速居中 flex margin:auto translate position padding和maring的区别 作用对象不同&#xff1a; padding是针对自身的 margin是作用于外部对象的 VW和百分比的区别 百分比有继承关系&#xff0c;继承父级 VW只和设备的宽度有关系 块元素和行内元素 行内元素&…

Vuex基础

Vuex介绍 为什么会有Vuex&#xff1f; https://v3.vuex.vuejs.org/zh/ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 Vue是采用集中式管理组件依赖的共享数…

技嘉 B660M Gigabyte Aorus Pro DDR4 i7-12700F 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板B660M Gigabyte Aorus Pro DDR4 处理器Intel Core i7-12700F 2.10 GHz, 25M Cache, up to 4.90 GHz已驱动 内存4 x Corsair Vengeance LPX 8GB 3…

多个SecurityFilterChain执行顺序问题,/oauth2/authorization报404

正常要请求/oauth2/authorization/{regId}跳转到authorization-uri进行认证的&#xff0c;但是搭建好之后&#xff0c;请求这个地址竟然直接报404了&#xff0c;说明oauth2的相关filter并没有生效&#xff0c;直接打到了dispatchServlet。 那到底是哪里的问题呢&#xff1f;de…

人机交互技术复习提纲

认知心理学与人机工程学是人机交互技术的理论基础&#xff0c;而多媒体技术、虚拟现实技术与人机交互是相互交叉和渗透的。 图形用户界面的英文简称为GUI GUI的特点是桌面隐喻技术 直接操纵 所见即所得 自然和谐的人机交互阶段 多通道交互 情感计算 虚拟现实 智能用户界面 自…

为了实现上网自由,我做了一个多功能串口服务器

项目作者&#xff1a;小华的物联网嵌入式之旅 介绍&#xff1a;从事电气自动化行业&#xff0c;多次获得物联网设计竞赛&#xff0c;爱好嵌入式设计开发&#xff0c;物联网开发。 设计方案思路的由来&#xff0c;是因为我们现在的开发板基本需要通过串口与WIFI模组或以太网模…

「2024」预备研究生mem-利润与利润率增长率问题

一、利润与利润率 二、增长率问题 易错题&#xff1a; 三、课后题 每日一练&#xff1a;

BC SAP ECC与 S4 pfcg不同

ECC PFCG 这里的账户咯&#xff0c;可以从Excel编辑好后&#xff0c;直接copy进去 但是在S4里里面&#xff0c;一次只能copy 这个页面范围内的行数&#xff0c;我的是14行&#xff0c;这个根电脑的分辨率有干系 &#xff0c;但是一定是不全的

ForkJoinPool的使用以及基本原理

文章目录 一、ForkJoinPool简介二、ForkJoinPool的基本原理1. 分治法2. 工作窃取 三、ForkJoinPool的使用场景1. 递归式的任务分解&#xff1a;2. 数据并行处理&#xff1a;3. 合并结果&#xff1a;4. 并行递归算法&#xff1a;5. 小结&#xff1a; 四、ForkJoinPool的基本使用…

第13节:特色数据——把握宏观经济脉搏

文章目录 中国主要宏观经济指标相关接口本节课任务 中国主要宏观经济指标 GDP&#xff08;国内生产总值&#xff09;&#xff1a;GDP是衡量一个国家或地区经济活动总量的指标&#xff0c;代表了一定时期内该国或地区所有最终产品和服务的市场价值总和。它反映了一个经济体的整体…

【花雕】青少年机器人教育等级评测模拟题_20200715(一级)

随着科技的不断进步&#xff0c;机器人技术已经成为了一个重要的领域。在这个领域中&#xff0c;机械结构是机器人设计中至关重要的一部分&#xff0c;它决定了机器人的形态、运动方式和工作效率。对于青少年机器人爱好者来说&#xff0c;了解机械结构的基础知识&#xff0c;掌…

分布式主键ID生成策略

小程序搜索“ 源码轻舟 ”后续将推出算法和面试模块 坚持学习&#xff0c;好文每日送达&#xff01; 业务系统对分布式ID的要求 唯一性&#xff1a;在分布式系统中&#xff0c;每个节点都需要生成唯一的标识符来确保数据的唯一性。传统的单点生成ID方式无法满足分布式环境下的…

音视频技术开发周刊 | 300

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 著名数据和AI企业Databricks&#xff0c;收购类ChatGPT开源平台MosaicML 6月27日&#xff0c;Databricks在官网宣布&#xff0c;以13亿美元&#xff08;约94亿元&#xff…

浅析便捷生活的新选择——抖音本地服务

抖音是一款风靡全球的短视频分享平台&#xff0c;其本地服务功能的发展也逐渐引起了广泛关注。本地服务是指抖音平台上的用户可以通过平台直接查找并使用周边的各种服务&#xff0c;比如美食外卖、快递配送、家政服务等。本地服务的发展对用户和商家都带来了很多便利和机遇。 首…

Spring Boot 中的 Spring Cloud Hystrix 是什么,原理,如何使用

Spring Boot 中的 Spring Cloud Hystrix 是什么&#xff0c;原理&#xff0c;如何使用 简介 在分布式系统中&#xff0c;服务之间的调用是不可避免的。但是&#xff0c;当一个服务调用另一个服务时&#xff0c;如果被调用的服务出现了故障或者延迟&#xff0c;那么调用者也会…

发布/上传Jar包到Maven中央仓库

1.注册Sonatype账号 2.项目申请&#xff0c;创建工单 2.1回复 groupId 域名 可以使用github&#xff0c; io.github.账号 创建工单根据评论回复&#xff0c;需要创建临时仓库&#xff0c;验证账户所有权。 3.gpg4win 地址&#xff1a;https://www.gpg4win.org/download.html &…