C++11详解
文章目录
- 前言
- 一、列表初始化
- 1.1 {} 初始化
- 1.2 initializer_list 类型
- 三、声明
- 3.1 auto
- 3.2 decltype
- 四、右值引用和移动语义
- 4.1 左值引用和右值引用
- 4.2 移动语义
- 五、可变参数模板
- 六、lambda表达式
- 各部分详细解释
- 示例代码
- 代码解释
- 七、包装器
- 八、bind
- 注意事项
前言
C++11在系统开发和库开发中表现出色,它简化了语法,让代码编写更加灵活,同时增强了稳定性和安全性。凭借丰富的新特性,C++11不仅功能更强大,还能显著提升开发效率。接下来,我们就一起学习C++11中那些好用的新增语法。
一、列表初始化
1.1 {} 初始化
在C++98的标准中,允许我们使用{}
对数组或结构体元素进行进行统一的列表初始值设定。
struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };//聚合初始化,感兴趣可以搜一下return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
int x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };//将申请的空间全部初始化为0//创建对象时也可以使用列表初始化方式调用构造函数初始化
class Date
{
public:Date(int year, int month, int day):_year(year),_month(month),_day(day){cout << "Date(int year, int month, int day)" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 1, 1); // C++98风格// C++11支持的列表初始化,这里会调用构造函数初始化,与聚合初始化区分开Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0;
}
1.2 initializer_list 类型
std::initializer_list
是 C++11
引入的一个标准库类型,它提供了一种方便的方式来处理初始化列表。std::initializer_list
允许函数或构造函数接受任意数量的同类型参数,这在需要初始化一组值时非常有用。
int main()
{vector<int> v = { 1,2,3,4 };list<int> lt = { 1,2 };// 这里{"sort", "排序"}会先初始化构造一个pair对象map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };return 0;}
initializer_list
类型的引入后C++11的容器都提供了一个指持,使用该类型初始化的构造函数,这使得我们可以像上述方式传参。
三、声明
3.1 auto
C++11中auto,将其用于实现自动类型推断。这样就要求定义对象必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型,在定义一些类型较复杂的变量时,我们可以使用这种方法快速定义,如:
vector<int>::iterator it1 = v.begin();auto it2 = v.begin();
3.2 decltype
在C++中,auto
关键字的使用存在特定限制:必须对其声明的变量进行初始化,否则编译器将无法推导出auto
所代表的实际类型。这是由于auto
类型推导依赖于初始化表达式,而在编译期间,代码尚未执行,无法获取运行时的结果,因此对于那些需要基于运行时表达式结果进行类型推导的场景,auto
就显得力不从心。
此时,decltype
关键字应运而生。decltype
的核心功能是根据表达式的实际类型,推导出定义变量时所需的类型。例如,在定义变量时,decltype
能够精准地获取表达式的类型,并将其作为变量的定义类型,有效弥补了auto
在编译期类型推导方面的局限性,如:
四、右值引用和移动语义
4.1 左值引用和右值引用
在C++语言的发展历程中,引用机制始终是其重要特性之一。在传统C++语法中,引用概念早已存在,而随着C++11标准的发布,右值引用作为一项全新的语法特性被引入。为了区分这两种不同类型的引用,我们将此前学习的引用正式命名为左值引用。无论是左值引用还是右值引用,其本质都是为对象赋予别名。
左值、左值引用
在C++中,左值是一类具有明确内存存储位置的表达式,像变量名、解引用后的指针等都属于左值范畴。它们不仅允许获取地址,还能进行赋值操作,正因如此,左值可以出现在赋值运算符的左侧。与之相对,右值则不具备这种特性,无法置于赋值符号左边。当左值被const
修饰,虽然丧失了赋值权限,但仍保留取地址的能力。从本质上讲,能够执行取地址操作,正是左值的核心特征。而左值引用,便是专门针对左值设计的引用类型,其作用在于为左值创建别名。
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
右值、右值引用
在C++中,右值同样是用于表示数据的表达式,涵盖字面常量(如10
、"hello"
)、表达式运算结果以及函数返回值(排除以左值引用形式返回的情况)等。与左值不同,右值仅能出现在赋值运算符右侧,无法作为赋值目标置于等号左边,并且由于右值通常不具备固定存储位置,因此不允许对其执行取地址操作。右值引用就是对右值的引用,给右值取别名,一般右值引用会增加右值的生命周期。
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
在 C++ 里,右值引用的一些特性使得右值原本不能取地址的规则在特定情况下有所变化。
右值不能直接取地址
右值通常是临时对象,没有固定的存储位置,所以按常规不能直接对其取地址。比如,字面常量 10 就是一个右值,若尝试对它取地址,像 &10
这样的操作,编译器会报错。这是因为 10 作为一个临时存在的值,并没有被分配具体的内存空间供我们获取其地址。
右值引用使右值可被存储和取地址
当使用右值引用给右值取别名时,右值会被存储到一个特定的位置,此时就可以获取该位置的地址。示例代码如下:
int main() {int&& rr1 = 10; std::cout << "Address of rr1: " << &rr1 << std::endl; rr1 = 20; std::cout << "Value of rr1 after modification: " << rr1 << std::endl;return 0;
}
在这个例子中,rr1 是对右值 10 的右值引用。通过右值引用,10 被存储到了一个特定位置,我们可以用 &rr1
获取其地址。同时,由于 rr1 没有被const
修饰,我们还能对它进行修改,将其值从 10 改成 20。如果不希望右值引用所引用的值被修改,可以使用 const
修饰右值引用。
move
在一些特殊情况下我们需要使用右值引用来引用一个左值,这是后就可以使用move
,将表达式由左值属性强制转化为右值属性:
void func(int&& x)
{return;}
int main()
{int a = 0;func(move(a));int&& r = std::move(a);return 0;
}
万能引用
这块大家可看一下,这个使用涉及了两个问题
template<class T>
void func(T&& x)//使用模板的这种形式,被称为万能引用(转发引用)
{//既可以引用左值也可以引用右值,编译器会根据你传递的参数进行推导int&& r1 = x;//这里是的编译是无法通过的,需要再对x movereturn;
}
int main()
{int a = 0;func(move(a));return 0;
}
在函数func
内部,x
虽然在函数参数列表中是右值引用形式,但它们本身是左值。这是因为它们有名字,在函数作用域内有具体的存储位置,所以int&& r1 = x;
编译无法通过,需要对x
再次进行move
,这种方法太过于局限,一般我们使用forward<T>(x)
完美转发的形式来解决。
完美转发:允许我们在函数模板中精确地将参数传递给其他函数,同时保留参数的左值 / 右值属性和 const 属性。
#include <iostream>
#include <utility>// 辅助函数,用于接收左值
void printValue(int& value) {std::cout << "Lvalue: " << value << std::endl;
}// 辅助函数,用于接收右值
void printValue(int&& value) {std::cout << "Rvalue: " << value << std::endl;
}// 函数模板,使用完美转发
template <typename T>
void forwardValue(T&& value) {// 使用 std::forward 保持参数的原始值类别printValue(std::forward<T>(value));
}int main() {int a = 42;// 传递左值forwardValue(a);// 传递右值forwardValue(123);return 0;
}
无论是右值引用还是完美转发,都是为了给后面的移动语义做铺垫。
4.2 移动语义
左值引用的短板:
当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回,对于一些需要进行深拷贝的容器,这个代价是比较大的:
对于上述这种情况to_string
函数返回后str
所拥有的资源就会被释放,中间还要生成一份同样的临时资源用来初始化ret2
对象,而移动语义的出现就很完美的避免了资源频繁拷贝的问题。当str
被返回并准备调用拷贝构造,去拷贝ret2
对象时,编译器会识别出str
是将亡值,转而去调用,通过右值引用实现的移动构造,将str
的资源直接转移给ret2
对象。
在C++中,右值表达式按类型可分为两类:内置类型的右值称为纯右值,如字面常量、算术表达式结果;自定义类型的右值则称为将亡值,常见于函数返回的临时对象或移动操作前的对象。
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "string(string&& s) -- 移动语义" << endl;swap(s);//转移资源
}
移动语义这种机制很好的避免深拷贝时间的开销,移动构造中没有开辟新开空间,拷贝数据,效率得到了显著提高。
不仅仅有移动构造,还有移动赋值:
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{bit::string ret1;ret1 = bit::to_string(1234);//编译器识别到to_string的返回值为将亡值,调用移动赋值转移资源return 0;
}
该处和移动构造实现原理是一样的,可以结合上面的一起理解,这部分知识还是很好理解的,这里就不画图展示了。
可以看出无论是右引用还是完美转发本质都是为了服务于移动语义。
五、可变参数模板
C++11 引入的可变参数模板是一项极为重要的新特性,它允许开发者创建能够接受可变数量参数的函数模板和类模板。在 C++98/03 标准里,类模板和函数模板所能容纳的模板参数数量是固定的,可变参数模板的出现无疑是一个重大突破,极大地增强了模板的灵活性和通用性。
不过,可变参数模板相对抽象,其使用需要掌握特定的技巧,具有一定的理解难度。在本篇内容中,我们将聚焦于可变参数模板的简单使用方法。
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template<class ...Args>
void func(Args ...args)
{}int main()
{func(1,2.1, 'a', "abc");//可以传递任意数量,任意类型的参数return 0;
}
在 C++ 可变参数模板的运用中,当参数 args
前面带有省略号时,它便成为了可变模板参数。我们将这种带省略号的参数称作“参数包”,其特点是能够容纳从 0 到 N(N 大于等于 0)个模板参数,极大地提升了模板的灵活性。
然而,我们无法直接访问参数包 args
里的每个参数,必须通过展开参数包的方式才能逐一获取其中的参数,这也正是可变模板参数使用的核心特点与最大挑战所在。具体而言,关键就在于如何巧妙地展开可变模板参数。由于语法层面并不支持采用 args[i]
这样直观的方式来获取可变参数,所以我们不得不借助一些特别的技巧,才能逐个获取参数包中的值。
template<class T>
void func(T&x)
{cout << x << " ";return;
}
template<class T,class ...Args>
void func(T x,Args ...args)
{cout << x << " ";func(args...);//此处传参比较特殊
}
int main()
{func(1,2.1, 'a', "abc");//编译器推导生成模板return 0;
}
在这段代码里,函数调用的执行流程如下:在主函数中,有 func(1, 2.1, 'a', "abc")
这样的调用语句。编译器会依据 void func(T x, Args ...args)
这个函数模板进行类型推导。具体来说,它会把实参列表中的第一个实参存储到形参 x
里,接着继续递归调用 func
函数。在递归调用时,会将剩余的参数组成的参数包 args
(这里是 2.1
, 'a'
, "abc"
)传递给下一次调用。这个过程会持续进行,直到参数包中仅剩下一个形参。此时,编译器会匹配到 void func(T& x)
这个函数,从而完成整个参数包的解析过程。
逗号表达式展开参数包
template <class T>
void PrintArg(T t)
{cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));r
int arr[] = { (PrintArg(args), 0)... };
这行代码是核心部分。(PrintArg(args), 0)
是一个逗号表达式,逗号表达式会先计算左边的表达式 PrintArg(args)
,也就是调用 PrintArg
函数打印参数,然后返回右边的值 0。...
是参数包展开的语法,它会将参数包 args 中的每个参数依次展开,形成多个逗号表达式。具体来说,假设 args 包含参数 a, b, c,
那么 (PrintArg(args), 0)...
会展开为 (PrintArg(a), 0), (PrintArg(b), 0), (PrintArg(c), 0)
。
六、lambda表达式
在之前的讨论中我们了解到,对于自定义类型而言,通常无法直接进行比较操作。为了实现自定义类型之间的比较,我们可以借助仿函数这一工具。具体做法是新定义一个类,在这个类中通过运算符重载的方式来实现所需的比较逻辑
struct Goods
{string _name; double _price; int _evaluate; Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
}
这种方式比较笨重,在C++11中给出了lambda
的方法,简化了这一逻辑。
lambda表达式语法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type {statement }
Lambda 表达式是 C++11 引入的一种匿名函数对象,它允许你在代码中快速定义一个轻量级的内联函数。下面详细解释其书写格式的各个部分:
各部分详细解释
[capture-list]
(捕获列表)- 捕获列表用于指定 lambda 表达式能够访问的外部变量。捕获列表可以为空,也可以包含一个或多个捕获项,不同捕获项之间用逗号分隔。
- 捕获方式主要有以下几种:
- 值捕获:使用变量名进行捕获,例如
[x]
表示按值捕获变量x
,lambda 表达式内部会创建一个x
的副本,在 lambda 表达式内部修改该副本不会影响外部的x
。 - 引用捕获:在变量名前加
&
进行捕获,例如[&x]
表示按引用捕获变量x
,lambda 表达式内部对x
的修改会影响外部的x
。 - 隐式捕获:可以使用
[=]
表示按值捕获所有外部变量,[&]
表示按引用捕获所有外部变量。也可以混合使用隐式捕获和显式捕获,例如[=, &x]
表示按值捕获所有外部变量,但按引用捕获x
。
- 值捕获:使用变量名进行捕获,例如
(parameters)
(参数列表)- 参数列表与普通函数的参数列表类似,用于指定 lambda 表达式接受的参数。参数列表可以为空,也可以包含一个或多个参数,不同参数之间用逗号分隔。
- 例如
(int a, double b)
表示 lambda 表达式接受一个int
类型的参数a
和一个double
类型的参数b
。
mutable
(可变修饰符,可选)- 默认情况下,按值捕获的变量在 lambda 表达式内部是只读的,不能修改。如果需要修改按值捕获的变量,可以使用
mutable
关键字。 - 例如
[x] mutable { x++; }
表示可以在 lambda 表达式内部修改按值捕获的变量x
,再次强调,此时依然不会修改外部的x
。
- 默认情况下,按值捕获的变量在 lambda 表达式内部是只读的,不能修改。如果需要修改按值捕获的变量,可以使用
-> return-type
(返回类型,可选)- 返回类型用于指定 lambda 表达式的返回值类型。如果 lambda 表达式的返回类型可以由编译器自动推导,则可以省略返回类型。
- 例如
-> int
表示 lambda 表达式的返回值类型为int
。
{ statement }
(函数体)- 函数体包含了 lambda 表达式的具体实现代码,可以包含任意的语句。
- 例如
{ return a + b; }
表示 lambda 表达式的功能是返回两个参数的和。
示例代码
#include <iostream>int main() {int x = 10;int y = 20;// 按值捕获 x 和 y,返回它们的和auto add = [x, y]() -> int { return x + y; };std::cout << "add result: " << add() << std::endl;// 按引用捕获 x 和 y,修改 x 的值auto modify = [&x, &y]() { x = 30; y = 40; };modify();std::cout << "x: " << x << ", y: " << y << std::endl;// 使用 mutable 关键字修改按值捕获的变量auto mutableTest = [x]() mutable { x = 50; std::cout << "mutable x: " << x << std::endl; };mutableTest();std::cout << "original x: " << x << std::endl;return 0;
}
代码解释
add
lambda 表达式:按值捕获x
和y
,返回它们的和。modify
lambda 表达式:按引用捕获x
和y
,在 lambda 表达式内部修改x
和y
的值,会影响外部的x
和y
。mutableTest
lambda 表达式:使用mutable
关键字修改按值捕获的变量x
,在 lambda 表达式内部修改x
的值不会影响外部的x
。
当掌握了lambda
表达式我们就可以使用下面的方式,编写比较逻辑了:
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; });
}
其实lambda
表达式的底层实现依然采用的是仿函数的形式,由于C11
的内容太多这里就不验证了,感兴趣的可以自行看看汇编逻辑。
七、包装器
在 C++ 中,function
是 <functional>
头文件中定义的一个类模板,它是一种通用、多态的函数包装器。function
可以存储、复制和调用任何可调用对象(如函数、Lambda 表达式、函数指针、成员函数指针、仿函数等)。
function
的模板参数是函数类型,指定了可调用对象的参数列表和返回类型。例如,function<int(int, int)>
表示可以存储接受两个 int
类型参数并返回bool
类型值的可调用对象:
struct Less {bool operator()(int x, int y)//仿函数{return x < y;}
};bool compare(int x, int y)//函数
{return x < y;
}
int main()
{auto f=[](int x, int y) {return x < y; };//lambda表达式function<bool(int, int)>func1 = f;function<bool(int, int)>func2 = Less();function<bool(int, int)>func3 = compare;cout << func1(1, 2) << endl;cout << func2(3, 2) << endl;cout << func3(2, 2) << endl;return 0;
}
借助包装器,我们成功地把这三种不同形式的比较操作,整合为统一的调用形式。如此一来,无论是哪种比较方式,在实际调用时都遵循相同的规则和流程,极大地提升了代码的一致性与可维护性。
在使用包装器进行统一时,必须保障他们的参数类型及返回值类型相同。
八、bind
std::bind
函数定义于 <functional>
头文件,它是一个功能强大的函数模板,灵活的函数包装器(也可称作适配器)。它能够接收任意可调用对象,如普通函数、成员函数、函数指针、Lambda 表达式等,然后生成一个全新的可调用对象。
通常情况下,我们可以利用 std::bind
对一个原本需要接收 N 个参数的函数进行处理。通过绑定部分参数,它会返回一个新函数,这个新函数接收 M 个参数(理论上 M 可以大于 N,但在实际应用中,这种做法的意义不大)。除此之外,std::bind
还具备调整参数顺序的能力。
// 原型如下:
template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);
我们可以通过bind
来个获得一个新的可调用对象,调整可调用对象个数,满足统一包装的条件。
int main()
{auto f=[](int x, int y) {return x < y; };function<bool(int, int)>func1 = f;function<bool(int, int)>func2 = Less();function<bool(int, int)>func3 = compare;function<bool(int, int)>func4 = bind(sum,2, placeholders::_1,placeholders::_2);func4(1, 2);return 0;
}
绑定成员函数:
#include <iostream>
#include <functional>class Calculator {
public:int multiply(int a, int b) {return a * b;}
};int main() {Calculator calc;// 绑定成员函数auto multiplyFunc = std::bind(&Calculator::multiply, &calc, std::placeholders::_1, std::placeholders::_2);int result = multiplyFunc(4, 6);std::cout << "Result: " << result << std::endl;return 0;
}
对于成员函数的绑定,需要传入对象的指针(或者引用),并且要使用取地址符 &
获取成员函数的地址。这里bind
把Calculator
类的 multiply
成员函数和对象 calc
的指针以及两个占位符绑定,生成了新的可调用对象 multiplyFunc
。
调整参数顺序
#include <iostream>
#include <functional>// 普通函数
int subtract(int a, int b) {return a - b;
}int main() {// 调整参数顺序auto reversedSubtract = std::bind(subtract, std::placeholders::_2, std::placeholders::_1);int result = reversedSubtract(8, 3);std::cout << "Result: " << result << std::endl;return 0;
}
注意事项
- 占位符的使用:占位符
std::placeholders::_n
中的n
表示在调用新的可调用对象时,第n
个参数的位置。 - 对象生命周期:如果绑定的是成员函数,要确保对象在新的可调用对象被调用时仍然有效。
- 性能开销:
std::bind
会带来一定的性能开销。
通过使用 std::bind
,可以让代码更加灵活,特别是在需要对函数的参数进行预绑定或者调整参数顺序的场景中非常有用。
``
相关文章:
C++11详解
文章目录 前言一、列表初始化1.1 {} 初始化1.2 initializer_list 类型 三、声明3.1 auto3.2 decltype 四、右值引用和移动语义4.1 左值引用和右值引用4.2 移动语义 五、可变参数模板六、lambda表达式各部分详细解释示例代码代码解释 七、包装器八、bind注意事项 前言 C11在系统…...
VLM-RL:用于安全自动驾驶的统一视觉语言模型和强化学习框架——论文阅读
《VLM-RL: A Unified Vision Language Models and Reinforcement Learning Framework for Safe Autonomous Driving》2024年12月发表,来自Wisconsin Madison分校和Purdue大学的论文。 近年来,基于强化学习(RL)的学习驾驶策略的方法…...
新手安装java所有工具(jdk、idea,Maven,数据库)
新手安装JAVA工具 介绍JDK11IDEA 2025.1Maven数据库(Navicat Premium Lite) 介绍 涉及安装JAVA所需的各种工具 JDK(以JDK11为例)IDEA(以2025.1为例)Maven(以3.8.8为例)数据库&…...
hive在配置文件中添加了hive.metastore.uris之后进入hive输入命令报错
在hive-site.xml文件中加入配置hive.metastore.uris启动hive后报错 <property><name>hive.metastore.uris</name><value>thrift://node154:9083</value></property> 加完属性就需要手动启动metastore服务,因为不使用 Zookeepe…...
Hive原理
Hive 是构建在 Hadoop 上的数据仓库工具,其核心原理是通过类 SQL 语言(HiveQL)将结构化数据查询转换为分布式计算任务(如 MapReduce、Tez、Spark),并利用 HDFS 存储数据。以下是 Hive 的核心原理和架构: 1. 核心设计思想 数据仓库抽象:将 HDFS 上的文件抽象为…...
cursor 出现 unauthorized request
文档出自:https://www.kdocs.cn/l/csE3iuSauHoS...
uniapp|商品列表加入购物车实现抛物线动画效果、上下左右抛入、多端兼容(H5、APP、微信小程序)
以uniapp框架为基础,详细解析商品列表加入购物车抛物线动画的实现方案。通过动态获取商品点击位置与购物车坐标,结合CSS过渡动画模拟抛物线轨迹,实现从商品图到购物车图标的动态效果。 目录 核心实现原理坐标动态计算抛物线轨迹模拟动画元素控制代码实现详解模板层设计脚本…...
点下4个Winform UI开源控件库
从零学习构建一个完整的系统 今天一起来盘点下4个Winform UI开源控件库,有.Net Framework,也有.Net Core。 1、支持.Net 7的开源UI组件框架 项目简介 这是一个基于.Net Framework、.Net 6开发的,WinForm开源UI框架,框架包含常…...
【AI】mcp server本质就是一个接口服务么
以下为元宝的回答: 你的理解非常准确!MCP Server本质上是一个接口服务,但其设计目标、交互逻辑和使用场景与传统后端接口存在显著差异。以下是两者的对比分析: 1. 核心定位差异 维度MCP Server…...
chalrs正常使用一段时间后开启代理访问网页 显示“不是私密链接”解决办法
chalrs正常使用一段时间后开启代理访问网页 显示“不是私密链接”解决办法 背景: charles用了好长时间了,最近发现打开charles有些软件无法上网,浏览器访问网页提示“您的连接不是私密链接”,按照网上的教程重装了几次证书&#x…...
如何通过DNS解析实现负载均衡?
在当今的互联网时代,随着网络应用的飞速发展,网站和各类在线服务面临着海量的用户请求。为了保障服务的高可用性和高性能,负载均衡技术应运而生。DNS(域名系统)负载均衡作为其中一种重要的实现方式,凭借其简…...
uni-app微信小程序登录流程详解
文章目录 uni-app微信小程序登录流程实战详解微信小程序登录流程概述1. 获取登录凭证(code)2. 发送登录请求3. 保存登录态4. 登录状态管理5. 应用登录状态请求拦截器中添加 token自动登录页面路由守卫 使用 Vuex 集中管理登录状态登录组件示例登录流程最…...
基于LVS和Keepalived实现高可用负载均衡架构
目录 一、资源清单 二、修改主机名 三、配置调度器 四、配置Web节点服务器(web1、web2) 五、测试负载均衡 六、测试LVSKeepalived高可用群集 一、资源清单 主机 操作系统 IP地址 lb01 OpenEuler24.03 192.168.16.142 lb02 OpenEuler24.03 …...
微信小程序仿淘宝拍照/照片点位识图、点位裁剪生图、图片裁剪组件、图片点位框选、裁剪生成图片,canvasToImg
实现效果 效果: 1.微信小程序仿淘宝拍照/照片点位识图、根据点位裁剪生图、图片可裁剪、图片高度可控 2.识别点位自动生成标准构图方案,支持手动微调实现像素级精准裁剪 3.可以根据接口识别的点位信息实现拍照/相册图片特征点自动识别并裁剪 实现步骤 …...
EnumUtils:你的枚举“变形金刚“——让枚举操作不再手工作业
各位枚举操控师们好!今天要介绍的是Apache Commons Lang3中的EnumUtils工具类。这个工具就像枚举界的"瑞士军刀",能让你的枚举操作从石器时代直接跃迁到星际文明! 一、为什么需要EnumUtils? 手动操作枚举就像…...
在Taro中开发一个跨端Svg组件,同时支持小程序、H5、React Native
Taro系列中一直没有跨端的绘图工具,小程序端支持canvas但是不支持svg,RN端有 react-native-svg 支持svg,但是没有很好原生的canvas插件,社区的canvas都是基于WebView实现的,或者skia,这个插件的书写方式和c…...
大型视频学习平台项目问题解决笔记
一 数据库大量读操作导致数据库压力过大的解决方案 1. 优化SQL语句 2. 缓存 二 数据库大量写操作导致数据库压力过大的解决方案 1. 优化SQL语句 2. 改同步写为异步写——解决复杂事务的高并发写 3. 合并写请求——解决简单事务的高并发写(额外实现一个异步操作来…...
day18-数据结构引言
一、 概述 数据结构:相互之间存在一种或多种特定关系的数据元素的集合。 1.1 特定关系: 1. 逻辑结构 2.物理结构(在内存当中的存储关系) 逻辑结构物理结构集合,所有数据在同一个集合中,关系平等顺…...
Android音频解码中的时钟同步问题:原理、挑战与解决方案
一、为什么音频同步如此重要? 在多媒体播放系统中,音频同步问题直接影响用户体验。根据行业研究数据: • 15ms以上的同步偏差:53%的用户能感知到音画不同步 • 超过100ms的偏差:会导致明显的"口型对不上"现…...
深入浅出 iOS 对象模型:isa 指针 与 Swift Metadata
在 iOS 开发中,我们经常听到两个看似神秘的词:isa 指针 和 Metadata。这两个概念分别源自 Objective-C 和 Swift 的对象系统,是我们理解底层运行机制、优化性能乃至调试疑难问题的关键。今天我们就来聊一聊,它们到底是什么&#x…...
ARMV8 RK3399 u-boot TPL启动流程分析 --crt0.S
上一篇介绍到start.S 最后一个指令是跳转到_main, 接下来分析 __main 都做了什么 arch/arm/lib/crt0.S __main 注释写的很详细,主要分为5步 1. 准备board_init_f的运行环境 2. 跳转到board_init_f 3. 设置broad_init_f 申请的stack 和 GD 4. 完整u-boot 执行re…...
Lynx-字节跳动跨平台框架多端兼容Android, iOS, Web 原生渲染
介绍 字节跳动近期开源的跨平台框架Lynx被视为一项重要的技术创新。相较于市场上已有的解决方案如React Native (RN) 和Flutter,Lynx具有独特的特性。 首先,Lynx采用轻量级JavaScript逻辑设计,DOM节点构建完全置于Native层,确保U…...
手机换地方ip地址会变化吗?深入解析
在移动互联网时代,我们经常带着手机穿梭于不同地点,无论是出差旅行还是日常通勤。许多用户都好奇:当手机更换使用地点时,IP地址会随之改变吗?本文将深入解析手机IP地址的变化机制,帮助您全面了解这一常见但…...
Linux——数据库备份与恢复
一,Mysql数据库备份概述 1,数据库备份的重要性 数据灾难恢复:数据库可能会因为各种原因出现故障,如硬件故障、软件错误、误操作、病毒攻击、自然灾害等。这些情况都可能导致数据丢失或损坏。如果有定期的备份,就可以…...
矩阵键盘模块
目录 1.矩阵键盘介绍 2.扫描的概念 数码管扫描(输出扫描) 矩阵键盘扫描(输入扫描) 矩阵按键采用逐行扫描: 3.矩阵键盘代码 第一步: 第二步: 第三步: 第四步࿱…...
连接词化归律详解
1. 连接词化归律的基本概念 连接词化归律(也称为归结原理)是数理逻辑中用于简化逻辑表达式的重要方法,它允许我们将复杂的逻辑表达式转化为更简单的等价形式,特别是转化为合取范式(CNF)或析取范式(DNF)。 核心思想 连接词化归律基于一系列逻辑等价关系…...
Ubuntu 18.04 iso文件下载
参考:https://blog.csdn.net/Li060703/article/details/106075597 Rufus 官网: https://rufus.ie/zh/ 镜像下载地址 阿里云镜像站:https://mirrors.aliyun.com/ubuntu-releases/18.04/ 网易镜像:http://mirrors.163.com/ub…...
【C#】ToArray的使用
在 C# 中,ToArray 方法通常用于将实现了 IEnumerable<T> 接口的集合(如 List<T>)转换为数组。这个方法是 LINQ 提供的一个扩展方法,位于 System.Linq 命名空间中。因此,在使用 ToArray 方法之前࿰…...
学习日志03 java
最近有点懈怠了,多多实践,多敲代码,多多专注! 1 ArithmeticException ArithmeticException 是 Java 中的一个异常类,它继承自 RuntimeException,用于表示在算术运算中出现的错误。这个异常通常在以下情况…...
数据库故障排查指南
对于项目研发来讲,数据库是必不可少的一个重要环节,本文详细总结了项目研发中数据库故障问题排查指南,希望会对大家有所帮助。 数据库连接问题 检查数据库服务是否正常运行,确认网络连接是否畅通,验证数据库配置文件…...
洛谷 P1955 [NOI2015] 程序自动分析
【题目链接】 洛谷 P1955 [NOI2015] 程序自动分析 【题目考点】 1. 并查集 2. 离散化 【解题思路】 多组数据问题,对于每组数据,有多个 x i x j x_ix_j xixj或 x i ≠ x j x_i \neq x_j xixj的约束条件。 所有相等的变量构成一个集合&…...
音视频学习:使用NDK编译FFmpeg动态库
1. 环境 1.1 基础配置 NDK 22b (r22b)FFmpeg 4.4Ubuntu 22.04 1.2 下载ffmpeg 官网提供了 .tar.xz 包,可以直接下载解压: wget https://ffmpeg.org/releases/ffmpeg-4.4.tar.xz tar -xvf ffmpeg-4.4.tar.xz cd ffmpeg-4.41.3 安装基础工具链 sudo …...
OpenHarmony Linux内核本地管理
概述 写这篇文章的初衷,其实也是作者从事多年Android系统开发中,根深蒂固的目录情节导致的,再开发Harmony系统中,总是想模拟Android系统的开发思路。 对于OpenHarmony这个patch机制,其实我很讨厌它,虽然这样…...
2025最新出版 Microsoft Project由入门到精通(六)
目录 三种资源类型的分配方式 成本类资源的分配方式 第一步:切换视图为”任务分配状况“视图 第二步:选中任务→资源→分配资源,打开分配资源窗口选择资源单击”分配“ 资源成本的修改方式 编辑工时类资源的分配方式 工时类资源的…...
Tomcat服务部署
目录 一. Tomcat概述 1.1 什么是Tomcat 1.2 安装Tomcat 1.2.1 CentOS7 安装 1.2.2 ubuntu 安装 1.2.3 使用脚本快速安装 二. 配置文件及核心组件 2.1 配置文件 2.1.1 安装目录下文件介绍 2.1.2 conf子目录 2.2 组件 三. tomcat 处理请求过程 四. 常见配置详解 4.…...
Chrome更新到136以后selenium等自动化浏览器失效
Chrome更新到136以后,已经不再支持对默认浏览器数据文件夹进行自动化调试,从而导致selenium在指定user-data-dir为默认路径“C:\Users\{计算机名}\AppData\Local\Google\Chrome\User Data”会报错,相应地的selenium-wire,undetect…...
数据库原理期末考试速成--最后附带两套题
引言 为什么从3开始呢,毕竟是速成吗,总要放弃一些东西 前两章1.概论 2.关系数据库:这里面都是一些运算符什么的,我感觉都学这个:笛卡尔积之列的都会算 这两章比较重要的我就放在这里了 选择、投影、连接、除、并、交、差,其中选择、投影、并、差、笛卡尔积是5种基本关…...
网络基础1(应用层、传输层)
目录 一、应用层 1.1 序列化和反序列化 1.2 HTTP协议 1.2.1 URL 1.2.2 HTTP协议格式 1.2.3 HTTP服务器示例 二、传输层 2.1 端口号 2.1.1 netstat 2.1.2 pidof 2.2 UDP协议 2.2.1 UDP的特点 2.2.2 基于UDP的应用层…...
使用Spring Boot集成Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它集成了服务发现、服务配置和服务管理等功能,是微服务架构中一个非常重要的组件。以下是使用Spring Boot集成Nacos的详细步骤。 1. 环境准备 确保你已经安装和配置了以下环境࿱…...
破局智算瓶颈:400G光模块如何重构AI时代的网络神经脉络
一、技术演进与市场需求双重驱动 在数字化转型浪潮下,全球互联网流量正以每年30%的复合增长率持续攀升。根据Dell’Oro Group最新报告,2023年400G光模块市场规模已突破15亿美元,预计2026年将占据数据中心光模块市场60%以上份额。这种爆发式增…...
Vue:插值表达
Vue 的插值表达式是数据绑定的基础形式,它通过 {{ }} 将 JavaScript 数据动态渲染到模板中。下面通过代码示例,直观感受它的用法和限制。 基础用法 <template><div><!-- 直接显示数据 --><p>{{ messag…...
26考研|数学分析:函数列与函数项级数
前言 函数列与函数项级数这一章虽然课本安排章节较少,只要两小节,但是在具体学习过程中,确实会有一定的难度,首先难点便是在对于函数列与函数项级数的理解,其次关于一致收敛性质的理解与判断,也是难点所在…...
设置环境变量启动jar报
1. 环境变量设置 set PATHC:\Program Files\java17\jdk-17.0.9\bin;%PATH%2. 启动jar java -jar jar包名3. 记录原因 PATH路径前添加java执行文件路径才会管用。添加后可以试试以下命令 直接输入PATH 回车 PATH进行java版本测试 java -version...
项目售后服务承诺书,软件售后服务方案,软件安装文档,操作文档,维护文档(Word原件)
一、系统安全性保障 (一)设计原则 (二)应用安全 (三)数据安全 (四)用户安全 (五)管理安全 二、售后服务 (一)服务总体要…...
Arduino快速入门
Arduino快速入门指南 一、硬件准备 选择开发板: 推荐使用 Arduino UNO(兼容性强,适合初学者),其他常见型号包括NANO(体积小)、Mega(接口更多)。准备基础元件:…...
每日一题——樱桃分级优化问题:最小化标准差的动态规划与DFS解决方案
文章目录 一、问题描述输入格式输出格式 二、问题本质分析三、解题思路1. 前缀和预处理2. DFS 枚举与剪枝3. 剪枝策略4. 标准差计算 四、代码实现五、样例解析样例 1样例 2 六、一行行代码带你敲dfs 七、总结 一、问题描述 某大型樱桃加工厂使用自动化机械扫描了一批樱桃的尺寸…...
C++类与对象(二):六个默认构造函数(一)
在学C语言时,实现栈和队列时容易忘记初始化和销毁,就会造成内存泄漏。而在C的类中我们忘记写初始化和销毁函数时,编译器会自动生成构造函数和析构函数,对应的初始化和在对象生命周期结束时清理资源。那是什么是默认构造函数呢&…...
荣耀手机,系统MagicOS 9.0 USB配置没有音频来源后无法被adb检测到,无法真机调试的解决办法
荣耀手机,系统MagicOS 9.0 USB配置没有音频来源后无法被adb检测到,无法真机调试的解决办法 前言环境说明操作方法 前言 一直在使用的uni-app真机运行荣耀手机方法,都是通过设置USB配置的音频来源才能成功。突然,因为我的手机的系…...
每日分享-Python哈希加盐加密实战分享
没事找事干,找到本地有个hashdemo.py,那就来分享一下代码吧,主要内容就是使用python实现哈希加盐加密方式。 1、导入所需库 不多BB,先打开我们的 pychram 然后导入所需要用到的库 import hashlib import random import strin…...
Webpack中Compiler详解以及自定义loader和plugin详解
Webpack Compiler 源码全面解析 Compiler 类图解析: 1. Tapable 基类 Webpack 插件系统的核心,提供钩子注册(plugin)和触发(applyPlugins)能力。Compiler 和 Compilation 均继承此类,支持插件…...