深入理解C++11右值引用与移动语义:高效编程的基石

文章目录
- 前言
- 📚一、C++11的历史发展
- 📖1.1 C++11 之前的背景
- 📖1.2 C++11 的发展历程
- 📖1.3 C++11 的主要设计目标
- 📖1.4 C++11 的主要特性
- 📖1.5 C++11 的影响
- 📚二、统一的列表初始化
- 📖2.1 基本列表初始化
- 📖2.2 使用初始化列表初始化 STL 容器
- 📖2.3 类构造函数中的列表初始化
- 📖2.4 列表初始化防止隐式窄化转换
- 📖2.5 默认初始化
- 📖2.6 聚合类型的列表初始化
- 📚三、`decltype`——编译时获取表达式的类型
- 📖3.1 如何声明一个未知类型变量
- 📖3.2 获取自定义类的成员函数类型
- 📚四、左、右值引用和移动语义
- 📖4.1 什么是左值,右值?
- 📒1. 左值(Lvalue)
- 📒2. 右值(Rvalue)
- 📒3. 左值和右值的主要区别
- 📖4.2 左值引用和右值引用
- 📒1. 左值引用(Lvalue Reference)
- 📒2. 右值引用(Rvalue Reference)
- 📒3. 左值引用与右值引用的区别
- 📒4. 左值引用能否给右值取别名?
- 📒5. 右值引用能否给左值取别名?
- 📒总结
- 📖4.3 右值引用的使用场景和意义
- 📒1. 移动构造函数
- 📒2. 移动赋值运算符
- 📒3. 为什么要使用 `noexcept`
- 📚五、完美转发
- 📖5.1 模板中的万能引用`&&`
- 📖5.2 `forward<T>`完美转发
- 结语
前言
在现代C++编程中,性能优化和资源管理一直是开发者追求的目标。C++11引入的右值引用(rvalue reference)和移动语义(move semantics)为解决这些问题提供了强有力的工具。通过右值引用,我们能够更高效地处理临时对象;而移动语义的引入,则进一步优化了对象的资源转移和管理。在这篇文章中,我们将深入探索右值引用和移动语义的核心概念、实现原理,以及它们在实际开发中的应用场景。
📚一、C++11的历史发展
C++11 是 C++ 标准的一次重大更新,于 2011 年发布。它引入了许多新的特性和改进,使得 C++ 更加现代化、高效且易于使用。C++11 的发布可以说是 C++ 语言的一次**“复兴”**,在过去几十年中,标准化组织(ISO)对 C++ 语言的不断完善起到了重要作用。
📖1.1 C++11 之前的背景
在 C++11 之前,C++ 的最新标准是 C++98 和它的技术修正(C++03)。C++98 于 1998 年发布,建立了 C++ 的基础特性,如模板、标准模板库(STL)、异常处理等。C++03 是对 C++98 的一些小修订,主要是修复了 C++98 中的缺陷,并未引入新的语言特性。随着计算机硬件的快速发展和软件开发需求的变化,C++98 和 C++03 逐渐显得陈旧,无法满足更高效、更现代化的软件开发需求。
📖1.2 C++11 的发展历程
C++11 的标准化过程可以追溯到 2002 年。C++ 标准委员会(ISO/IEC JTC1/SC22/WG21)开始对 C++ 语言进行改进的讨论,目标是让 C++ 语言更加高效和现代化,同时保持其核心的性能和灵活性。在这个过程中,多个 C++ 提案被提出,委员会从这些提案中选取了对语言发展最为重要的部分进行标准化。经过近 10 年的讨论和修改,最终在 2011 年发布了 C++11 标准。
📖1.3 C++11 的主要设计目标
C++11 的设计目标主要包括以下几个方面:
- 提高程序性能和效率:为了解决大型项目中的性能瓶颈,C++11 引入了移动语义、智能指针、多线程支持等特性。
- 改进编程体验:C++11 提供了许多语法改进,使得代码更简洁清晰,如自动类型推导(
auto)、范围for循环、初始化列表等。 - 增强可移植性和可维护性:通过标准库的扩展和新功能,C++11 提供了更好的跨平台支持和更高的代码可维护性。
📖1.4 C++11 的主要特性
C++11 引入了大量新特性,使得 C++ 语言得到了显著的改进。以下是一些主要特性:
- 右值引用和移动语义:通过右值引用(
T&&)和std::move实现移动语义,优化了资源管理和对象拷贝。 - 自动类型推导:
auto关键字可以自动推导变量类型,使代码更加简洁。 - 智能指针:
std::shared_ptr、std::unique_ptr和std::weak_ptr解决了原始指针的内存管理问题。 - Lambda 表达式:引入了 Lambda 表达式,使得 C++ 具备了更现代化的函数式编程能力。
- 多线程支持:标准库引入了
std::thread、std::mutex等多线程工具,为并发编程提供了标准化的支持。 - 新容器:如
std::unordered_map和std::array等,丰富了 C++ 的数据结构。 - 范围
for循环:更简洁的循环语法,便于遍历容器。 - constexpr:引入
constexpr关键字支持编译时常量计算,提升了程序的执行效率。 - 初始化列表:统一的初始化语法,提供了更灵活的初始化方式。
- 空指针常量:引入
nullptr代替原来的NULL,避免类型不安全的问题。 - 静态断言:
static_assert允许在编译期进行断言检查,提高了代码的健壮性。
📖1.5 C++11 的影响
C++11 的发布使得 C++ 语言变得更加强大和现代化,成为工业级开发的主流选择之一。以下是 C++11 的主要影响:
- 编程范式的改变:C++11 引入了许多现代化的语法和特性,使得编程范式从传统的面向对象逐渐向更简洁和高效的函数式编程发展。
- 提高了开发效率:通过更简洁的语法和自动化的内存管理工具(如智能指针),C++11 提高了开发效率,降低了代码复杂度。
- 促进了后续标准的制定:C++11 的成功促使标准委员会在此基础上继续改进,推出了 C++14、C++17、C++20 和 C++23,使 C++ 语言保持了活力和竞争力。
📚二、统一的列表初始化
在 C++11 中,列表初始化(List Initialization)是一种新的初始化方式,它允许使用花括号 {} 来初始化变量和对象。这种方式提供了更一致和灵活的初始化方法,避免了一些潜在的错误。列表初始化主要有以下几种形式:
📖2.1 基本列表初始化
最常见的列表初始化形式是直接用 {} 初始化变量或对象。这种方式可以应用于内置类型、类类型和数组。
int a{5}; // a 初始化为 5
double b{3.14}; // b 初始化为 3.14
int arr[3]{1, 2, 3}; // 初始化数组
📖2.2 使用初始化列表初始化 STL 容器
C++11 允许通过列表初始化来直接构造 STL 容器。
#include <vector>
#include <iostream>
using namespace std;int main() {vector<int> vec{1, 2, 3, 4}; // 使用列表初始化向量for (int val : vec) {cout << val << " "; // 输出: 1 2 3 4}return 0;
}
📖2.3 类构造函数中的列表初始化
C++11 引入了 std::initializer_list,使得可以通过列表初始化构造类对象。为此,类需要实现接受 std::initializer_list 的构造函数。
#include <initializer_list>
#include <iostream>
using namespace std;class MyClass {
public:MyClass(initializer_list<int> list) {for (auto val : list) {cout << val << " ";}cout << endl;}
};int main() {MyClass obj{1, 2, 3, 4}; // 使用列表初始化return 0;
}
在这个例子中,MyClass 接受一个 std::initializer_list<int> 类型的参数,可以在初始化时传入多个值。
📖2.4 列表初始化防止隐式窄化转换
列表初始化可以防止某些类型转换错误(例如浮点数到整数的窄化转换),从而提高代码的安全性。C++11 标准规定,列表初始化不允许隐式的窄化转换。
int x{3.14}; // 错误:3.14 是 double,不能隐式转换为 int
上面代码会报错,因为 3.14 是 double 类型,而列表初始化不允许将 double 隐式转换为 int。
📖2.5 默认初始化
通过列表初始化,可以直接实现默认初始化:使用 {} 直接初始化,没有提供具体值。
int x{}; // x 初始化为 0
double y{}; // y 初始化为 0.0
std::string s{}; // s 初始化为空字符串
📖2.6 聚合类型的列表初始化
对于聚合类型(如数组、struct),可以使用列表初始化为其成员赋值。
- 多参数构造函数的隐式类型转换
struct Point {Point(int x, int y):_x(x),_y(y){}int _x;int _y;
};Point p{10, 20}; // 使用列表初始化 struct 成员
explicit关键字避免隐式类型转换带来的不确定性
struct Point {explicit Point(int x, int y):_x(x),_y(y){}int _x;int _y;
};Point p{10, 20}; // 会报错

📚三、decltype——编译时获取表达式的类型
📖3.1 如何声明一个未知类型变量
总所周知,在C++98之中有这样一个运算符名叫typeid,它可以查看任何变量或者函数的类型,例如:
int main() {int i = 10;auto p = &i;auto pf = malloc;cout << typeid(p).name() << endl;cout << typeid(pf).name() << endl;return 0;
}

很显然,typeid推出的类型只能看不能用,当然你也可以将其打印出来,再确定它的类型之后手动声明或定义新变量,不过这样未免显得有点太繁琐。于是我们想到经常使用的auto关键字:
int i = 10;
auto p = &i; // 只能定义变量
可以发现,使用auto必须要给左值添加一个右值,用来推导类型,可有些时候我们只想声明,先不想赋值该怎么办?C++11推出了一个新的关键字叫做decltype,用于在编译时获取表达式的类型。它允许开发者在不显式指定类型的情况下获取变量或表达式的类型信息,从而提高代码的灵活性和可维护性。
- 基本用法
decltype 的基本语法是 decltype(expression),其中 expression 是一个有效的 C++ 表达式或者变量。编译器会分析表达式或者变量的类型,并将其作为 decltype 的结果类型。
示例
int main() {auto pf = malloc;decltype(pf) pf2;cout << typeid(pf2).name() << endl;return 0;
}

📖3.2 获取自定义类的成员函数类型
decltype 可以用于获取类成员变量的类型,这在使用模板和泛型编程时非常有用。
template<class Func>
class B{
private:Func _f;
};int main() {auto pf = malloc;B<decltype(pf)> b1;cout << "b1->type:" << typeid(b1).name() << endl;const int x = 1;double y = 2.2;B<decltype(x * y)> b2;cout << "b2->type:" << typeid(b2).name() << endl;return 0;
}

📚四、左、右值引用和移动语义
📖4.1 什么是左值,右值?
在C++中,左值(Lvalue)和右值(Rvalue)是表达式类型的重要概念。它们决定了表达式的“值类别”,即表达式的结果可以用于什么类型的操作,比如赋值、地址取用等。
📒1. 左值(Lvalue)
左值(Lvalue,Locator value)是一个可以取地址的表达式,表示一个持久的、可命名的存储位置。它可以出现在赋值运算符的左边,也就是说,它是可以被赋值的对象。
- 特点:具有持久性,可多次访问。
- 示例:变量、数组元素、解引用的指针等。
- 用法:左值通常用于表示可以被修改的对象,但需要注意的是,有些左值可能是
const的,即使是左值也不能修改。
示例
int x = 10; // x 是一个左值,可以赋值
int* p = &x; // 可以取 x 的地址
在这里,x就是左值,因为我们可以取它的地址并在后续操作中多次使用它。
📒2. 右值(Rvalue)
右值(Rvalue,Read value)是一个不持久的、临时的值,通常是表达式的结果。它不能取地址,通常出现在赋值的右侧。右值通常是字面量、临时对象或是表达式的计算结果,不能重复使用。
- 特点:通常为临时值,只在表达式中短暂存在。
- 示例:字面量(如
10)、表达式如(x + y)、临时对象。 - 用法:右值不能直接取地址,不能在后续操作中重复使用,除非绑定到右值引用。
示例
int y = 5 + 3; // 5 + 3 是一个右值
int z = y * 2; // y * 2 是一个右值
这里,5 + 3 和 y * 2是右值,它们是表达式的计算结果,不能取地址。
📒3. 左值和右值的主要区别
| 特性 | 左值(Lvalue) | 右值(Rvalue) |
|---|---|---|
| 持久性 | 是持久性的 | 是临时性的 |
| 可赋值性 | 可以出现在赋值运算符的左边 | 通常不能出现在赋值的左边 |
| 取地址 | 可以取地址 | 不能取地址 |
| 用途 | 可多次访问的对象 | 通常为表达式结果或临时值(将亡值) |
📖4.2 左值引用和右值引用
在C++中,左值引用和右值引用是两种不同的引用类型,主要用于资源管理、性能优化和控制对象的生命周期。它们分别是为左值(持久对象)和右值(临时对象)设计的。
📒1. 左值引用(Lvalue Reference)
左值引用(T&)是C++中最常见的引用类型,用于引用变量、对象等持久化的左值,通常用于需要在多个地方访问和修改同一对象的情况。
- 定义:
T&,例如int& ref = x; - 绑定对象:左值引用只能绑定到左值上,如变量、数组元素等。
- 典型用途:
- 传递和修改函数参数。
- 提高效率,避免函数参数的拷贝。
- 提供统一的接口来操作对象。
示例
void updateValue(int& ref) {ref = 20; // 修改原始对象
}int main() {int x = 10;updateValue(x); // 传递左值引用,直接修改 xcout << x; // 输出 20
}
在这个例子中,updateValue函数使用左值引用来修改传入的参数x,避免了不必要的拷贝。
📒2. 右值引用(Rvalue Reference)
右值引用(T&&)是C++11引入的一种新型引用类型,用于绑定到右值(如临时对象或表达式的计算结果)。右值引用允许在编程中直接使用和操作临时对象,是实现移动语义的关键。
- 定义:
T&&,例如int&& rref = 5; - 绑定对象:右值引用只能绑定到右值(临时值)上,比如常量、表达式结果, 函数的传值返回值(不能是左值引用返回值)等。
- 典型用途:
- 实现移动语义:右值引用可以通过转移资源而非复制资源,来优化程序性能。
- 避免不必要的拷贝:右值引用允许在需要生成临时对象的地方避免对象拷贝,从而提高效率。
示例:实现移动语义
class MyClass {
public:int* data;MyClass() : data(new int[1000]) {}// 移动构造函数MyClass(MyClass&& other) : data(other.data) {other.data = nullptr; // 转移资源}~MyClass() { delete[] data; }
};MyClass createMyClass() {MyClass temp;return temp; // 返回右值,触发移动构造
}
在这里,createMyClass函数返回一个临时对象(右值),可以通过移动构造函数实现资源转移,避免拷贝,从而提高性能。
📒3. 左值引用与右值引用的区别
| 特性 | 左值引用(T&) | 右值引用(T&&) |
|---|---|---|
| 绑定对象 | 只能绑定到左值 | 只能绑定到右值 |
| 常见用途 | 函数参数传递和修改、避免拷贝 | 移动语义、转移资源所有权、优化性能 |
| 示例 | int& ref = x; | int&& rref = 5 + 3; |
| 用法限制 | 不能绑定右值 | 不能直接绑定左值,需std::move转换 |
📒4. 左值引用能否给右值取别名?
在C++中,左值引用不能直接绑定到右值。通常情况下,左值引用(T&)只能绑定到左值,而不是右值。右值是临时的、短暂存在的值,而左值引用需要绑定到一个持久的、可以命名的对象,因此不能直接给右值取别名。
- 间接方式:通过
const左值引用绑定右值
不过,const左值引用(const T&)可以绑定到右值。这是因为 const 左值引用不会修改绑定对象的值,允许在函数中引用临时对象或字面量等右值。使用 const T& 可以间接为右值取别名。
示例
void print(const int& ref) {cout << ref << endl;
}int main() {print(10); // 10 是右值,但可以绑定到 const int& 上
}
在这个例子中,字面量 10 是右值,但可以通过 const int& 引用传递给 print 函数。通过这种方式,可以间接地为右值取一个别名。
📒5. 右值引用能否给左值取别名?
右值引用不能直接给左值取别名。右值引用(T&&)的设计初衷是用于绑定右值(即临时对象)来实现移动语义。因此,右值引用只能绑定到右值,不能直接绑定到左值。
- 但是:通过
std::move可以实现
如果希望将左值转化为右值引用,可以使用 std::move 将左值转换成右值来绑定到右值引用。std::move 不会真正移动数据,只是将左值“视为”右值,以便能够绑定到右值引用。
示例
void process(int&& rref) {cout << "Processing value: " << rref << endl;
}int main() {int x = 10; // x 是一个左值process(move(x)); // 将 x 转换为右值引用,可以绑定到 int&&
}
在这个例子中,std::move(x)将左值x转换为右值引用,从而能够绑定到右值引用参数rref上。
- 为什么右值引用不直接绑定左值
右值引用的目的是为了避免拷贝,通过资源转移提升效率,而左值通常是需要继续使用的持久对象,不适合绑定到右值引用(右值引用的绑定会引导资源转移,导致左值状态不可预测)。因此设计上不允许右值引用直接绑定左值,除非明确使用 std::move 来告知编译器。
📒总结
-
左值引用只能引用左值,不能引用右值
-
但是
const左值引用既可以引用左值,也可以引用右值 -
右值引用只能引用右值,不能引用左值
-
但是右值引用可以引用
move以后的左值
📖4.3 右值引用的使用场景和意义
在 C++11 中,为了提高程序的性能,增加了移动构造函数和移动赋值运算符,它们使对象的资源可以从一个对象“移动”到另一个对象,而不是进行深拷贝。这样可以显著减少不必要的内存分配和复制,尤其是对于动态分配资源的类(如包含指针的类)而言。
📒1. 移动构造函数
移动构造函数的作用是通过“移动”资源来构造一个新对象,而不是“复制”资源。这意味着,资源的所有权将从源对象转移到目标对象,而源对象在移动后通常会处于“空”或“无效”的状态,但仍然可析构。
移动构造函数的定义使用右值引用 &&,通常在构造函数声明中使用以下形式:
class MyClass {
public:MyClass(MyClass&& other) noexcept; // 移动构造函数
};
假设我们有一个简单的类 MyClass,包含一个动态分配的数组指针:
#include <iostream>
#include <string>
using namespace std;class MyClass {
public:string data;// 普通构造函数MyClass(const string& str) : data(str) {}// 移动构造函数MyClass(string&& str) noexcept : data(move(str)) {cout << "Move constructor called\n";}
};int main() {string temp = "Hello";MyClass obj(move(temp)); // 调用移动构造函数cout << "temp after move: " << temp << endl; // temp 可能为空return 0;
}
在上面的例子中,std::move(temp) 将 temp 转换为右值,触发移动构造函数,将 temp 的资源移动到 obj 中。这避免了深拷贝,提高了效率。
输出结果:

📒2. 移动赋值运算符
移动赋值运算符用于在赋值操作中转移资源的所有权。它通常用于将一个临时对象或不再需要的对象的资源“移动”到另一个已存在的对象上。
移动赋值运算符同样使用右值引用 &&,并返回当前对象的引用 *this:
class MyClass {
public:MyClass& operator=(MyClass&& other) noexcept; // 移动赋值运算符
};
在前面的 MyClass 基础上,我们可以实现移动赋值运算符:
class MyClass {
public:string data;// 普通构造函数MyClass(const string& str) : data(str) {}// 移动构造函数MyClass(string&& str) noexcept : data(move(str)) {cout << "Move constructor called\n";}// 移动赋值运算符重载MyClass& operator=(MyClass&& other) noexcept {if (this != &other) {data = move(other.data); // 使用 move 将资源移动other.data.clear(); // 清空 other 的 datacout << "Move Assigned called\n";}return *this;
}
};int main() {string temp = "Hello";MyClass obj(move(temp)); // 调用移动构造函数MyClass obj2("World");obj = move(obj2); // 调用移动赋值运算符cout << "obj2.data after move: " << obj2.data << endl; // obj2.data 可能为空return 0;
}
在这个示例中:
- 移动赋值运算符首先检查对象是否为自赋值。
- 如果不是自赋值,则释放当前对象的资源,将
other.data转移到this->data,并将other.data置空,防止重复释放。
输出结果:

📒3. 为什么要使用 noexcept
通常在移动构造函数和移动赋值运算符中添加 noexcept,表示该操作不会抛出异常。这是因为许多标准库容器会检查移动操作是否为 noexcept,以决定是否使用移动操作。
📚五、完美转发
📖5.1 模板中的万能引用&&
在C++11的模板编程中,**&&**代表万能引用,既能接收左值,又能接收右值。我们以下面的代码为例,分析一下&& 在模板中的意义:
代码示例:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 万能引用:既可以接收左值,又可以接收右值
template<typename T>
void PerfectForward(T&& t){Fun(t);
}
int main(){PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(move(b)); // const 右值return 0;
}
注意看,我定义了4个Fun函数,用来判断PerfectForward函数接收的左值或者右值能否在Fun函数中持续左值或者右值的状态。
输出结果:

让人意想不到的是打印出来的竟然全部都是左值引用!这究竟是怎么一回事呢?我们先拿第一行代码PerfectForward(10); 解释一下,PerfectForward 的形参 t 接收了一个右值 10 ,这里是右值引用。不过在函数体中,调用了 Fun(t); 这一语句,**而此时的 t 却是完完全全的一个左值,因为右值引用变量的属性会被编译器识别成左值,否则在移动构造的场景下,无法完成资源转移,必须要修改。**所以 Fun() 函数只会调用 void Fun(int& x) 。main 函数中的其他关于右值的语句也都是犯了这样一个错误,当然左值不受影响。**总的来说,引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。**为了防止这种错误的大面积发生,C++11做出了相应调整,增加了一个函数模板叫做std::forward<T>,主要用于实现 完美转发(perfect forwarding)。它可以根据参数的类型是左值还是右值,保留参数的值类别(即左值或右值)并转发给另一个函数。
📖5.2 forward<T>完美转发
std::forward<T> 的主要作用是保持传入参数的值类别(左值或右值),并正确地转发给接收方函数。它通常用于模板函数中,使得可以处理并转发任意的值类别。它的使用场景是右值引用和模板参数的结合。
对于以上代码我们就可以进行更改啦:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t) {// t是左值引用,保持左值属性// t是右值引用,保持右值属性Fun(forward<T>(t));
}
int main() {PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(move(b)); // const 右值return 0;
}
输出结果:

这下就能一一对上了。
结语
右值引用与移动语义是C++11标准中的重要组成部分,它们不仅提升了程序的执行效率,也为开发者提供了更灵活的资源管理手段。在理解和掌握这些特性后,您将能够编写出更加高效和优雅的代码。未来,在C++的学习和使用中,希望您能将这些新特性融入实践,享受现代C++的强大魅力!

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

相关文章:
深入理解C++11右值引用与移动语义:高效编程的基石
文章目录 前言📚一、C11的历史发展📖1.1 C11 之前的背景📖1.2 C11 的发展历程📖1.3 C11 的主要设计目标📖1.4 C11 的主要特性📖1.5 C11 的影响 📚二、统一的列表初始化📖2.1 基本列表…...
【WRF-Urban】URBPARM_LCZ.TBL 查找表解释及内容
【WRF-Urban】URBPARM_LCZ.TBL 查找表解释及内容 URBPARM_LCZ.TBL 文件的作用URBPARM_LCZ.TBL 文件中的参数URBPARM_LCZ.TBL 的使用URBPARM_LCZ.TBL 文件内容如何调整或扩展 URBPARM_LCZ.TBL参考URBPARM_LCZ.TBL 文件是 WRF(天气研究与预报模型) 中用于处理 局地气候区(Loca…...
网络是怎么连接的
目录 URL HTTP(超文本传输协议) FTP(文件传输协议) File mailto HTTP的方法 Socket URL URL(Uniform Resource Locator)中文名为统一资源定位符,它是互联网上用来标识某一资源的地址。就…...
Java 实现PDF添加水印
maven依赖: <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.4.3</version> </dependency>网络地址添加水印代码: public static boolean waterMarkNet(Stri…...
网络安全问题概述
1.1.计算机网络面临的安全性威胁 计算机网络上的通信面临以下的四种威胁: (1) 截获——从网络上窃听他人的通信内容。 (2) 中断——有意中断他人在网络上的通信。 (3) 篡改——故意篡改网络上传送的报文。可应用于域名重定向,即钓鱼网站。 (4) 伪造——伪…...
(udp)网络编程套接字Linux(整理)
源IP地址和目的IP地址 唐僧例子1 在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进…...
Web应用安全入门:架构搭建、漏洞分析与HTTP数据包处理
Web应用安全入门:架构搭建、漏洞分析与HTTP数据包处理 引言 在当今数字化时代,Web应用已成为企业和个人在线交互的核心。然而,随着技术的发展,Web应用面临的安全挑战也日益增加。本文旨在为初学者提供一个关于Web应用架构搭建、…...
[JAVA]MyBatis框架—获取SqlSession对象
SqlSessionFactory作为MyBatis框架的核心接口有三大特性 SqlSessionFactory是MyBatis的核心对象 用于初始化MyBatis,创建SqlSession对象 保证SqlSessionFactory在应用中全局唯一 1.SqlSessionFactory是MyBatis的核心对象 假设我们要查询数据库的用户信息&#x…...
Perl 简介
Perl 简介 Perl 是一种高级、通用、解释型、动态编程语言。由 Larry Wall 于 1987 年首次发布,它结合了 C、sed、awk 和 shell 脚本语言的特性。Perl 最初被设计用于文本处理,如报告生成和文件转换,但随着时间的推移,它已经发展成…...
spring-bean的销毁流程
1 引入 在 Spring 框架中,Bean 的生命周期管理是其核心功能之一,而 Bean 的注销(Destruction)是生命周期的最后一步。无论是关闭数据库连接、释放线程资源,还是执行缓存持久化操作,合适的销毁策略都至关重…...
问:Spring MVC DispatcherServlet流程步骤梳理
DispatcherServlet是Spring MVC框架中的核心组件,负责接收客户端请求并将其分发到相应的控制器进行处理。作为前端控制器(Front Controller)的实现,DispatcherServlet在整个请求处理流程中扮演着至关重要的角色。本文将探讨Dispat…...
用源码编译虚幻引擎,并打包到安卓平台
用源码编译虚幻引擎,并打包到安卓平台 前往我的博客,获取更优的阅读体验 作业内容: 源码编译UE5.4构建C项目,简单设置打包到安卓平台 编译虚幻 5 前置内容 这里需要将 Epic 账号和 Github 账号绑定,然后加入 Epic 邀请的组织,…...
快速搭建Android开发环境:Docker部署docker-android并实现远程连接
目录 前言 1. 虚拟化环境检查 2. Android 模拟器部署 3. Ubuntu安装Cpolar 4. 配置公网地址 5. 远程访问 小结 6. 固定Cpolar公网地址 7. 固定地址访问 作者简介: 懒大王敲代码,计算机专业应届生 今天给大家聊聊快速搭建Android开发环境&#x…...
「Mac玩转仓颉内测版21」基础篇1 - 仓颉程序的基本组成
本篇将系统介绍Cangjie编程语言中程序的基本组成部分,涵盖 main 函数的定义、包与模块的使用、变量类型、作用域和代码结构原则,帮助开发者理解Cangjie程序的整体结构。 关键词 程序入口点main函数包与模块变量类型与作用域值类型与引用类型代码结构与规…...
【Linux网络编程】简单的UDP套接字
目录 一,socket编程的相关说明 1-1,sockaddr结构体 1-2,Socket API 二,基于Udp协议的简单通信 三,UDP套接字的应用 3-1,实现英译汉字典 一,socket编程的相关说明 Socket编程是一种网络通信…...
在Vue中使用Excalidraw实现在线画板
概述 Excalidraw是一个非常好用的画图板工具,但是是用React写的,本文分享一种在Vue项目中使用的方法。 效果 实现 Excalidraw简介 这篇文章(Excalidraw 完美的绘图工具:https://zhuanlan.zhihu.com/p/684940131)介绍的很全面,…...
游戏+AI的发展历程,AI技术在游戏行业的应用有哪些?
人工智能(AI)与游戏的结合,不仅是技术进步的体现,更是人类智慧的延伸。从最初的简单规则到如今的复杂决策系统,AI在游戏领域的发展历史可谓波澜壮阔。 早在2001年,就有研究指出游戏人工智能领域࿰…...
Methode Electronics EDI 需求分析
Methode Electronics 是一家总部位于美国的全球性技术公司,专注于设计和制造用于多个行业的电子和电气组件,产品涵盖汽车、工业、电信、医疗设备以及消费电子等多个领域,提供创新的解决方案。 填写Methode_EDI_Parameters_Template Methode_…...
2023AE软件、Adobe After Effects安装步骤分享教程
2023AE软件是一款由Adobe公司开发的视频编辑软件,也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用,用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式,具有丰富的插件和预设&#…...
【前端】JavaScript 变量引用、内存与数组赋值:深入解析三种情景
博客主页: [小ᶻZ࿆] 本文专栏: 前端 文章目录 💯前言💯场景一:直接赋值与重新引用为什么结果不是 [3, 4, 5]?1. 引用与赋值的基本概念2. 图示分析 关键总结 💯场景二:引用指向的变化为什么…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
