C++学习:六个月从基础到就业——C++11/14:lambda表达式

C++学习:六个月从基础到就业——C++11/14:lambda表达式

本文是我C++学习之旅系列的第四十篇技术文章,也是第三阶段"现代C++特性"的第二篇,主要介绍C++11/14中引入的lambda表达式。查看完整系列目录了解更多内容。

引言

Lambda表达式是C++11引入的一项重要特性,它允许我们在需要函数对象的地方直接定义内联匿名函数,无需显式定义一个函数或函数对象类。这大大简化了代码,特别是在使用STL算法和回调函数的场景中。C++14进一步扩展了lambda的功能,使其更加灵活和强大。本文将深入探讨lambda表达式的语法、使用方法及实际应用,帮助你充分利用这一强大特性。

Lambda表达式基础

基本语法

Lambda表达式的基本语法如下:

[capture_list](parameters) mutable noexcept -> return_type { body }

其中:

  • [capture_list]:捕获外部变量的列表(可为空[]
  • (parameters):参数列表(可为空(),与普通函数一样)
  • mutable:可选,允许修改按值捕获的变量
  • noexcept:可选,指明函数不抛出异常
  • -> return_type:可选,指定返回类型(C++11中有时需要,C++14中往往可以省略)
  • { body }:函数体

最简单的lambda表达式例子:

#include <iostream>int main() {// 没有参数的lambdaauto sayHello = []() { std::cout << "Hello, Lambda!" << std::endl; };sayHello();  // 输出:Hello, Lambda!// 带参数的lambdaauto add = [](int a, int b) { return a + b; };std::cout << "5 + 3 = " << add(5, 3) << std::endl;  // 输出:5 + 3 = 8// 使用auto参数(C++14)auto multiply = [](auto a, auto b) { return a * b; };std::cout << "5 * 3 = " << multiply(5, 3) << std::endl;       // 整数相乘std::cout << "5.5 * 3.5 = " << multiply(5.5, 3.5) << std::endl;  // 浮点数相乘return 0;
}

捕获列表详解

捕获列表指定了lambda可以访问的外部作用域中的变量:

1. 值捕获

使用值捕获时,lambda在创建时获取变量的副本:

#include <iostream>int main() {int x = 10;// 按值捕获xauto lambda = [x]() { std::cout << "Captured x: " << x << std::endl; };x = 20;  // 修改原始变量lambda();  // 输出:Captured x: 10,因为lambda捕获的是创建时的副本return 0;
}
2. 引用捕获

使用引用捕获时,lambda可以访问并修改原始变量:

#include <iostream>int main() {int x = 10;// 按引用捕获xauto lambda = [&x]() {std::cout << "Before modification x: " << x << std::endl;x = 30;  // 修改原始变量std::cout << "After modification x: " << x << std::endl;};lambda();  // 修改xstd::cout << "x in main(): " << x << std::endl;  // 显示修改后的值:30return 0;
}
3. 隐式捕获

使用[=][&]可以隐式捕获所有使用的变量:

#include <iostream>int main() {int x = 10;int y = 20;// 隐式按值捕获所有变量auto lambda1 = [=]() { std::cout << "Captured x: " << x << ", y: " << y << std::endl; };// 隐式按引用捕获所有变量auto lambda2 = [&]() {x = 30;y = 40;std::cout << "Modified x: " << x << ", y: " << y << std::endl;};lambda1();  // 输出:Captured x: 10, y: 20lambda2();  // 修改原始变量并输出:Modified x: 30, y: 40std::cout << "After lambda2: x = " << x << ", y = " << y << std::endl;  // 显示修改后的值return 0;
}
4. 混合捕获

可以混合使用显式和隐式捕获:

// 隐式按值捕获所有变量,但x按引用捕获
auto lambda = [=, &x]() { x = y + z;  // 可以修改x,但不能修改y和z
};// 隐式按引用捕获所有变量,但x按值捕获
auto lambda = [&, x]() { y = x + z;  // 可以修改y和z,但不能修改x
};
5. 初始化捕获(C++14)

C++14允许在捕获列表中初始化变量:

#include <iostream>
#include <memory>int main() {// 初始化捕获auto ptr = std::make_unique<int>(10);// 在捕获列表中移动unique_ptr的所有权auto lambda = [value = std::move(ptr)]() {if (value) {std::cout << "Captured value: " << *value << std::endl;}};// ptr现在为nullptrstd::cout << "ptr is " << (ptr ? "not null" : "null") << std::endl;lambda();  // 输出:Captured value: 10return 0;
}

mutable关键字

默认情况下,lambda表达式无法修改按值捕获的变量。使用mutable关键字可以解除这个限制:

#include <iostream>int main() {int x = 10;// 没有mutable,尝试修改x会导致编译错误// auto lambda1 = [x]() { x = 20; };  // 编译错误// 使用mutable允许修改值捕获的变量auto lambda2 = [x]() mutable {x = 20;  // 可以修改捕获的副本,但不影响外部原始变量std::cout << "Inside lambda: x = " << x << std::endl;};lambda2();  // 输出:Inside lambda: x = 20std::cout << "Outside lambda: x = " << x << std::endl;  // 输出:Outside lambda: x = 10return 0;
}

返回类型推导

在C++11中,当lambda的函数体包含单一return语句时,返回类型可以自动推导。在其他情况下,需要显式指定返回类型:

#include <iostream>int main() {// 返回类型自动推导为intauto add = [](int a, int b) { return a + b; };// 复杂情况需要显式指定返回类型(C++11)auto getValueC11 = [](bool condition) -> int {if (condition) {return 42;} else {return 0;}};std::cout << "Add result: " << add(5, 3) << std::endl;std::cout << "True condition: " << getValueC11(true) << std::endl;std::cout << "False condition: " << getValueC11(false) << std::endl;return 0;
}

C++14增强了返回类型推导,即使在复杂情况下也能自动推导返回类型:

#include <iostream>
#include <string>int main() {// C++14中返回类型自动推导,即使有多个return语句auto getValueC14 = [](bool condition) {if (condition) {return 42;} else {return 0;}};// 不同类型的返回值也可以推导(根据上下文转换)auto convertC14 = [](bool condition) {if (condition) {return 42;      // int} else {return 42.0;    // double}};  // 返回类型被推导为doublestd::cout << "Type of convertC14 result: " << typeid(convertC14(true)).name() << std::endl;return 0;
}

Lambda表达式的进阶特性

泛型Lambda(C++14)

C++14引入了泛型lambda,允许在参数中使用auto关键字:

#include <iostream>
#include <vector>
#include <string>int main() {// 泛型lambda,可以接受任何类型的参数auto print = [](const auto& value) {std::cout << "Value: " << value << std::endl;};print(42);             // 整数print(3.14159);        // 浮点数print("Hello");        // 字符串字面量print(std::string("World"));  // std::string对象// 处理不同容器auto sumElements = [](const auto& container) {typename std::decay<decltype(container)>::type::value_type sum{};for (const auto& elem : container) {sum += elem;}return sum;};std::vector<int> intVec = {1, 2, 3, 4, 5};std::vector<double> doubleVec = {1.1, 2.2, 3.3, 4.4, 5.5};std::cout << "Sum of integers: " << sumElements(intVec) << std::endl;std::cout << "Sum of doubles: " << sumElements(doubleVec) << std::endl;return 0;
}

Lambda表达式的类型

每个lambda表达式都有唯一的闭包类型,该类型只有编译器知道。我们通常使用auto来存储lambda:

auto lambda = []() { std::cout << "Hello" << std::endl; };

如果需要存储具有相同签名的不同lambda,可以使用std::function

#include <iostream>
#include <functional>
#include <vector>int main() {// 使用std::function存储lambdastd::function<int(int, int)> operation;bool use_addition = true;if (use_addition) {operation = [](int a, int b) { return a + b; };} else {operation = [](int a, int b) { return a * b; };}std::cout << "Result: " << operation(5, 3) << std::endl;  // 输出:Result: 8// 存储多个相同签名的lambdastd::vector<std::function<int(int)>> transformations;transformations.push_back([](int x) { return x * x; });         // 平方transformations.push_back([](int x) { return x + x; });         // 加倍transformations.push_back([](int x) { return x * x * x; });     // 立方int value = 5;for (const auto& transform : transformations) {std::cout << "Transformed: " << transform(value) << std::endl;}return 0;
}

递归Lambda

Lambda表达式也可以递归调用自身,但需要一些技巧:

#include <iostream>
#include <functional>int main() {// 使用std::function和引用捕获实现递归std::function<int(int)> factorial;factorial = [&factorial](int n) {return (n <= 1) ? 1 : n * factorial(n - 1);};std::cout << "Factorial of 5: " << factorial(5) << std::endl;  // 输出:120// C++14中的另一种方法:使用Y-combinator技巧auto Y = [](auto lambda) {return [=](auto... args) {return lambda(lambda, args...);};};auto factorial_y = Y([](auto self, int n) -> int {return (n <= 1) ? 1 : n * self(self, n - 1);});std::cout << "Y-combinator factorial of 5: " << factorial_y(5) << std::endl;  // 输出:120return 0;
}

实际应用示例

与STL算法结合使用

Lambda表达式与STL算法结合使用是最常见、最有用的场景之一:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 使用lambda过滤元素auto evenCount = std::count_if(numbers.begin(), numbers.end(), [](int n) { return n % 2 == 0; });std::cout << "Number of even elements: " << evenCount << std::endl;// 使用lambda转换元素std::transform(numbers.begin(), numbers.end(), numbers.begin(),[](int n) { return n * n; });std::cout << "After squaring: ";for (int n : numbers) {std::cout << n << " ";}std::cout << std::endl;// 自定义排序std::vector<std::pair<std::string, int>> people = {{"Alice", 25},{"Bob", 30},{"Charlie", 20},{"David", 35}};// 按年龄排序std::sort(people.begin(), people.end(), [](const auto& a, const auto& b) { return a.second < b.second; });std::cout << "People sorted by age:" << std::endl;for (const auto& person : people) {std::cout << person.first << ": " << person.second << std::endl;}// 使用lambda进行累加auto sum = std::accumulate(numbers.begin(), numbers.end(), 0,[](int total, int value) { return total + value; });std::cout << "Sum of squares: " << sum << std::endl;return 0;
}

事件处理与回调函数

Lambda表达式非常适合用作回调函数:

#include <iostream>
#include <functional>
#include <vector>class Button {
private:std::string name;std::function<void()> clickHandler;public:Button(const std::string& n) : name(n) {}void setClickHandler(std::function<void()> handler) {clickHandler = handler;}void click() {std::cout << "Button '" << name << "' clicked" << std::endl;if (clickHandler) {clickHandler();}}
};class EventSystem {
private:std::vector<std::function<void(const std::string&)>> eventListeners;public:void addEventListener(std::function<void(const std::string&)> listener) {eventListeners.push_back(listener);}void triggerEvent(const std::string& eventName) {std::cout << "Event '" << eventName << "' triggered" << std::endl;for (const auto& listener : eventListeners) {listener(eventName);}}
};int main() {// 按钮回调示例Button saveButton("Save");Button cancelButton("Cancel");int saveCount = 0;saveButton.setClickHandler([&saveCount]() {std::cout << "Saving data..." << std::endl;++saveCount;std::cout << "Data has been saved " << saveCount << " times" << std::endl;});cancelButton.setClickHandler([]() {std::cout << "Operation cancelled" << std::endl;});saveButton.click();cancelButton.click();saveButton.click();// 事件系统示例EventSystem events;// 添加监听器events.addEventListener([](const std::string& event) {std::cout << "Listener 1 received: " << event << std::endl;});events.addEventListener([](const std::string& event) {std::cout << "Listener 2 received: " << event << std::endl;});// 触发事件events.triggerEvent("application_start");events.triggerEvent("user_login");return 0;
}

自定义迭代器和生成器

Lambda表达式可以用于创建自定义迭代器和生成器:

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>// 简单的整数序列生成器
std::function<int()> makeIntGenerator(int start, int step) {return [start, step]() mutable {int current = start;start += step;return current;};
}// 斐波那契序列生成器
std::function<int()> makeFibonacciGenerator() {return [a = 0, b = 1]() mutable {int current = a;int next_val = a + b;a = b;b = next_val;return current;};
}int main() {// 使用整数生成器auto intGen = makeIntGenerator(1, 2);  // 生成1, 3, 5, 7, ...std::cout << "Generated integers: ";for (int i = 0; i < 5; ++i) {std::cout << intGen() << " ";}std::cout << std::endl;// 使用斐波那契生成器auto fibGen = makeFibonacciGenerator();  // 生成0, 1, 1, 2, 3, 5, ...std::cout << "Fibonacci sequence: ";for (int i = 0; i < 10; ++i) {std::cout << fibGen() << " ";}std::cout << std::endl;// 生成器与STL算法结合std::vector<int> numbers(10);auto gen = makeIntGenerator(0, 5);  // 生成0, 5, 10, 15, ...std::generate(numbers.begin(), numbers.end(), gen);std::cout << "Generated vector: ";for (int n : numbers) {std::cout << n << " ";}std::cout << std::endl;return 0;
}

IIFE (立即调用的函数表达式)

Lambda表达式可以实现JavaScript中流行的IIFE模式:

#include <iostream>
#include <vector>int main() {// 普通变量初始化int result = 0;for (int i = 1; i <= 10; ++i) {result += i;}std::cout << "Sum: " << result << std::endl;// 使用IIFE初始化int sum = [](int n) {int total = 0;for (int i = 1; i <= n; ++i) {total += i;}return total;}(10);std::cout << "Sum using IIFE: " << sum << std::endl;// 复杂对象初始化std::vector<int> primes = []{std::vector<int> p;p.push_back(2);p.push_back(3);p.push_back(5);p.push_back(7);p.push_back(11);return p;}();std::cout << "Prime numbers: ";for (int prime : primes) {std::cout << prime << " ";}std::cout << std::endl;return 0;
}

C++14中的Lambda增强

C++14对lambda表达式进行了几项重要增强:

1. 泛型Lambda

如前所述,C++14引入了泛型lambda,允许在参数中使用auto关键字。

2. 初始化捕获

C++14允许在捕获列表中初始化新变量:

#include <iostream>
#include <memory>
#include <utility>int main() {std::string message = "Hello";// C++11必须这样写auto lambda1 = [message = message + " World!"]() {std::cout << message << std::endl;};// 移动构造情况auto resource = std::make_unique<int>(42);// 在C++11中无法捕获unique_ptrauto lambda2 = [resource = std::move(resource)]() {std::cout << "Resource value: " << *resource << std::endl;};lambda1();  // 输出:Hello World!lambda2();  // 输出:Resource value: 42// 原始resource现在是nullptrstd::cout << "Original resource is " << (resource ? "valid" : "nullptr") << std::endl;return 0;
}

3. 返回类型推导改进

C++14中,编译器可以从lambda体中推导出返回类型,即使函数体包含多个返回语句。

Lambda表达式的性能考量

Lambda表达式通常被编译为内联函数对象,性能与手写函数对象相当。一些注意事项:

  1. 捕获的开销

    • 值捕获会创建变量的副本,可能有额外开销
    • 引用捕获几乎没有额外开销
  2. 内联优化

    • 简单的lambda通常会被内联,没有函数调用开销
    • 但过大的lambda可能不会被内联
  3. std::function的开销

    • std::function比直接使用lambda有更多开销
    • 当需要多态行为时才使用std::function
#include <iostream>
#include <chrono>
#include <functional>
#include <vector>// 时间测量辅助函数
template<typename Func>
long long measureTime(Func func, int iterations) {auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {func(i);}auto end = std::chrono::high_resolution_clock::now();return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}int main() {const int iterations = 10000000;// 测试直接使用lambdaauto directLambda = [](int x) { return x * x; };// 测试通过std::function存储的lambdastd::function<int(int)> funcLambda = [](int x) { return x * x; };// 测试值捕获int multiplier = 2;auto valueLambda = [multiplier](int x) { return x * multiplier; };// 测试引用捕获auto refLambda = [&multiplier](int x) { return x * multiplier; };// 测试性能auto directTime = measureTime([&](int i) { directLambda(i); }, iterations);auto funcTime = measureTime([&](int i) { funcLambda(i); }, iterations);auto valueTime = measureTime([&](int i) { valueLambda(i); }, iterations);auto refTime = measureTime([&](int i) { refLambda(i); }, iterations);std::cout << "Direct lambda: " << directTime << " ns" << std::endl;std::cout << "std::function lambda: " << funcTime << " ns" << std::endl;std::cout << "Value capture lambda: " << valueTime << " ns" << std::endl;std::cout << "Reference capture lambda: " << refTime << " ns" << std::endl;return 0;
}

最佳实践与注意事项

何时使用Lambda

  1. 简短的一次性函数:特别是作为算法参数
  2. 需要捕获局部变量的函数:当需要访问作用域中的变量
  3. 回调函数:事件处理或异步操作的回调
  4. 在本地定义辅助函数:不需要在全局或类范围内可见的函数

避免常见错误

  1. 捕获的生命周期问题

    std::function<int()> createLambda() {int local = 42;// 危险!返回的lambda捕获了即将销毁的局部变量的引用return [&local]() { return local; };  // 引用已销毁的变量
    }// 安全版本:按值捕获
    std::function<int()> createSafeLambda() {int local = 42;return [local]() { return local; };  // 复制了local的值
    }
    
  2. 捕获this指针

    class Widget {
    private:int value = 42;public:// 危险,隐式捕获this可能导致悬挂指针auto badClosure() {return [=]() { return value; };  // 隐式捕获this}// C++14安全版本:显式捕获成员变量auto goodClosureC14() {return [value = value]() { return value; };}// C++11安全版本:显式复制需要的数据auto goodClosureC11() {int v = value;return [v]() { return v; };}
    };
    
  3. 按值捕获且修改

    int counter = 0;// 错误:无法修改按值捕获的变量
    // auto increment = [counter]() { ++counter; };  // 编译错误// 正确:使用mutable关键字
    auto increment = [counter]() mutable { ++counter; return counter; };
    

提高Lambda可读性

  1. 保持简短:长函数应提取为命名函数
  2. 使用适当的捕获方式:明确指定需要捕获的变量
  3. 考虑命名lambda:对于复杂lambda,使用auto给它一个有意义的名字
  4. 适当添加注释:特别是对于复杂的逻辑
// 不要这样做:过于复杂的lambda
std::sort(employees.begin(), employees.end(), [](const Employee& a, const Employee& b) {if (a.department != b.department)return a.department < b.department;if (a.salary != b.salary)return a.salary > b.salary;  // 注意:薪水是降序排列return a.name < b.name;});// 更好的做法:命名lambda提高可读性
auto compareEmployees = [](const Employee& a, const Employee& b) {// 首先按部门升序排序if (a.department != b.department)return a.department < b.department;// 然后在同一部门内按薪水降序排序if (a.salary != b.salary)return a.salary > b.salary;// 最后按姓名字母顺序排序return a.name < b.name;
};std::sort(employees.begin(), employees.end(), compareEmployees);

总结

Lambda表达式是C++11/14引入的最强大、最有用的特性之一,它极大地简化了代码,使C++编程更加灵活和表达力更强。主要优势包括:

  1. 简化代码:无需定义单独的函数或函数对象类
  2. 局部范围:能够访问当前作用域的变量
  3. 即时定义:在需要使用的地方直接定义
  4. 提高可读性:使代码意图更加清晰
  5. 增强表达力:特别是与STL算法结合使用时

C++14通过泛型lambda、初始化捕获和改进的返回类型推导进一步增强了lambda表达式的功能。掌握lambda表达式是现代C++编程的必备技能,它能够帮助你编写更简洁、更易维护的代码。

在下一篇文章中,我们将探讨C++11/14的另一个重要特性:auto类型推导,它如何简化变量声明并改善代码可读性。


这是我C++学习之旅系列的第四十篇技术文章。查看完整系列目录了解更多内容。

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

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

相关文章

AIDC智算中心建设:计算力核心技术解析

目录 一、智算中心发展概览 二、计算力核心技术解析 一、智算中心发展概览 智算中心是人工智能发展的关键基础设施&#xff0c;基于人工智能计算架构&#xff0c;提供人工智能应用所需算力服务、数据服务和算法服务的算力基础设施&#xff0c;融合高性能计算设备、高速网络以…

IoTDB时序数据库V2.0.2大版本更新的一些梳理

一些小知识&#xff1a; 关于事务&#xff1a;时序数据库是没有事务的&#xff0c;它和关系数据库的应用场景不同&#xff0c;通常情况下不需要多点同时操作同一条数据&#xff0c;而且要保证极高的吐出量&#xff0c;事务太消耗资源&#xff0c;并且时序数据库提供了覆写的功能…

CSS定位详解

在前端开发中&#xff0c;CSS 定位&#xff08;positioning&#xff09;是一个核心概念&#xff0c;它决定了元素在页面上的位置和布局方式。无论是构建复杂的交互界面&#xff0c;还是实现简单的页面排版&#xff0c;CSS 定位都是不可或缺的工具。本文将全面介绍 CSS 中的五种…

React 语法扩展

useReducer钩子函数 不同action类型返回不同处理行为 useState()函数返回解构为两个值 state当前状态 dispatch修改状态函数 dispatch()函数参数为一个actuon对象 如 &#xff1a; 样例&#xff1a; import { useReducer } from react; import ./App.css;// 定义一个Reduce…

MCP协议与Dify集成教程

一、MCP协议概述 MCP&#xff08;Model Control Protocol&#xff09;是一种新兴的开放协议&#xff0c;为大型语言模型&#xff08;LLM&#xff09;与外部应用之间构建了双向通信通道。它就像是AI的"USB-C"接口&#xff0c;帮助模型发现、理解并安全调用各种外部工…

学习springboot-条件化配置@Conditional(条件注解)

前言 在Spring Boot中&#xff0c;Conditional 注解及其相关注解是用于条件化配置的重要工具。它们允许开发者根据特定条件决定是否加载某个Bean或配置类。 注意&#xff1a;Conditional 相关注解&#xff0c;通常和Bean搭配使用 学习springboot-Bean管理&#xff08;Bean 注…

2025年- H18-Lc126-54.螺旋矩阵(矩阵)---java版

1.题目描述 2.思路* 思路1&#xff1a; 补充2&#xff1a; directions[1][0] // 表示“下”这个方向的行增量&#xff08;1&#xff09; directions[1][1] // 表示“下”这个方向的列增量&#xff08;0&#xff09; int[][] directions {{0, 1}, {1, 0}, {0, -1}, {-…

微信小程序连续多个特殊字符自动换行解决方法

效果图 .wxml <view class"container"><text>没转换{{text}}</text><view style"height: 60rpx;" /><text>转换后{{convert}}</text> </view>.js Page({data: {text:&#xff01;&#xff01;&#xff01;&am…

编程速递-RAD Studio 12.3 Athens四月补丁:关注软件性能的开发者,安装此补丁十分必要

2025年4月22日&#xff0c;Embarcadero发布了针对RAD Studio 12.3、Delphi 12.3以及CBuilder 12.3的四月补丁。此更新旨在提升这些产品的质量&#xff0c;特别关注于Delphi编译器、C 64位现代工具链、RAD Studio 64位IDE及其调试器、VCL库和其他RAD Studio特性。强烈建议所有使…

Linux 进程基础(二):操作系统

目录 一、什么是操作系统&#xff1a;用户和电脑之间的「翻译官」&#x1f310; OS 的层状结构&#x1f9e9; 案例解析&#xff1a;双击鼠标的「跨层之旅」 二、操作系统的必要性探究&#xff1a;缺乏操作系统的环境面临的挑战剖析&#x1f511; OS 的「管理者」属性&#xff1…

第 11 届蓝桥杯 C++ 青少组中 / 高级组省赛 2020 年真题,选择题详细解释

一、选择题 第 2 题 在二维数组按行优先存储的情况下&#xff0c;元素 a[i][j] 前的元素个数计算如下&#xff1a; 1. **前面的完整行**&#xff1a;共有 i 行&#xff0c;每行 n 个元素&#xff0c;总计 i * n 个元素。 2. **当前行的前面元素**&#xff1a;在行内&#x…

1️⃣7️⃣three.js_OrbitControls相机控制器

17、相机轨道控制器 3D虚拟工厂在线体验相机轨道控制器OrbitControls 它是 Three.js 中最常用的交互控制器之一,专门用于通过鼠标/触摸控制相机围绕一个目标点(target)旋转、缩放和平移。import {OrbitControls } from three/addons/controls/OrbitControls.js; const cont…

以下是在 Ubuntu 上的几款PDF 阅读器,涵盖轻量级、功能丰富和特色工具:

默认工具&#xff1a;Evince&#xff08;GNOME 文档查看器&#xff09; 特点&#xff1a;Ubuntu 预装&#xff0c;轻量快速&#xff0c;支持基本标注和书签。 安装&#xff1a;已预装&#xff0c;或手动安装&#xff1a; sudo apt install evince功能全面&#xff1a;Okular&…

基于用户场景的汽车行驶工况构建:数据驱动下的能耗优化革命

行业现状&#xff1a;标准工况与用户场景的割裂 全球汽车行业普遍采用WLTC工况进行能耗测试&#xff0c;但其与真实道路场景差异显著。据研究&#xff0c;WLTC工况下车辆能耗数据比实际道路低10%-30%&#xff0c;导致用户对续航虚标投诉激增&#xff08;数据来源&#xff1a;东…

chili3d调试10 网页元素css node deepwiki 生成圆柱体 生成零件图片

.input是input的外框&#xff0c;.input input是input的内框 沙雕 全部input都换成textarea了 自己的方法用接口定义&#xff0c;把自己的方法pub出去&#xff0c;定义在内部拉出去只是取个值 这其实是mainwindow端pub回来的 窗口pub端把数据pub回 mainwindow端让mainwindow端…

Redis 启用 TLS 加密传输配置

Redis 启用 TLS 加密传输配置 一、Redis TLS 加密概述 Redis 从 6.0 版本开始原生支持 TLS 加密传输&#xff0c;可以保护客户端与服务器之间的通信安全&#xff0c;防止数据被窃听或篡改。 二、准备工作 确认 Redis 版本‌&#xff1a; redis-server --version确保版本 ≥…

【Linux】深入理解程序地址空间

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、什么是程序地址空间 二、深入理解程序地址空间 1. 引例 2. 理解地址转化 3. 再谈程序地址空间 4. 补充知识 总结 前言 在现代操作系…

【深度学习-Day 5】Python 快速入门:深度学习的“瑞士军刀”实战指南

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

解决在 Linux 中 WPS 字体缺失问题

解决在 Linux 中 WPS 字体缺失问题 安装方式 安装方式 首先下载你所需要的字体文件 在字体文件所在的目录下右键点击在命令行中打开 或 Open in Terminal sudo mkdir /usr/share/fonts/myfontssudo cp ./* /usr/share/fonts/myfonts执行命令&#xff0c;更新字体缓存 sudo fc…

668SJBH报刊发行系统

1 前言 随着我国信息产业的迅猛发展&#xff0c;手工管理方式已不适应社务管理的要求&#xff0c;报社的日常管理正面临着信息化的挑战&#xff0c;采用计算机管理以提高服务质量和管理水平势在必行。发行管理是社务管理的一个重要组成部分&#xff0c;是报社和客户联系的纽带…