C++11 新特性全解:语法糖、容器进化与可调用对象包装

5. 新的类功能

5.1 默认的移动构造和移动赋值

一、背景回顾

C++98/C++03 中,类有 6 个默认成员函数

构造函数、析构函数、拷贝构造函数、拷贝赋值重载、 取地址重载、const 取地址重载

其中最重要的是前 4 个(构造、析构、拷贝构造、拷贝赋值)。后两个用处不大。

默认成员函数:程序员不写,编译器会自动生成。

二、C++11 新增的两个默认成员函数

三、默认移动构造的生成条件

自动生成的前提条件(缺一不可):

  1. 用户没有自己实现移动构造函数

  2. 以下所有函数用户全部没有实现

    • 析构函数

    • 拷贝构造函数

    • 拷贝赋值重载

满足以上条件时,编译器会自动生成一个默认移动构造函数。

默认移动构造的行为:

四、默认移动赋值的生成条件

自动生成的前提条件(缺一不可):

  1. 用户没有自己实现移动赋值重载函数

  2. 用户没有实现以下任意一个函数:

    • 析构函数

    • 拷贝构造函数

    • 拷贝赋值重载

**默认移动赋值的行为:**与默认移动构造完全类似

五、重要规则:移动与拷贝的互斥关系

如果用户提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

这意味着:

  • 自定义了移动语义 → 需要自己考虑拷贝语义的实现

  • 反之亦然(提供了拷贝构造/赋值,也可能抑制移动的自动生成,如第三条所述)

规则总结表


5.2成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表⽤的,如果没有显⽰在初始化列表初始化,就会在初始化列表⽤这个缺省值初始化,这个我们在类和对象部分讲过了,这里是详细介绍它的链接: C++类与对象详解-3-CSDN博客


5.3defult和delete

控制默认函数:=default=delete

C++11 提供了更精细的方式来控制默认成员函数的生成。

一、=default:显式要求生成默认函数

使用场景:

你想使用某个默认函数,但因为某些原因(如提供了拷贝构造),编译器不会自动生成它。

典型例子:

  • 提供了拷贝构造函数 → 编译器不会自动生成移动构造函数

  • 可以用 =default 显式指定移动构造的生成

效果:

  • 告诉编译器:请显式生成该函数的默认版本

  • 适用于:移动构造、移动赋值、拷贝构造、拷贝赋值、析构函数、默认构造函数等

二、=delete:禁止生成默认函数

使用场景:

想要限制某些默认函数的生成,禁止对象进行某种操作。

C++98 的旧做法:

  • 将函数设为 private

  • 只声明不定义

  • 缺点:代码不够直观,错误信息不友好

C++11 的新做法:

在函数声明后加上 =delete,指示编译器不生成对应函数的默认版本。

语法示例:

术语:

=delete 修饰的函数称为删除函数(deleted function)

效果:

  • 任何尝试调用删除函数的代码都会导致编译错误

  • 错误信息比 C++98 的 private 链接错误更清晰

三、对比总结

四、常见应用示例

cpp 复制代码
class Widget
{
public:
    // 默认构造
    Widget() = default;
    
    // 禁止拷贝(使类型不可拷贝)
    Widget(const Widget&) = delete;
    Widget& operator=(const Widget&) = delete;
    
    // 允许移动
    Widget(Widget&&) = default;
    Widget& operator=(Widget&&) = default;
    
    // 禁止在堆上分配(删除特定 operator new)
    void* operator new(size_t) = delete;
};

int main()
{
    Widget w1;
    // Widget w2(w1);  // 编译错误:拷贝构造被删除
    Widget w3(std::move(w1));  // OK:移动构造可用
}

5.4 final与override

这个在继承和多态中讲过,这里附上链接:C++ 继承的奥秘与技巧-CSDN博客C++ 多态详解_c++多态是什么-CSDN博客


6.STL 中的一些变化

C++11 对 STL 进行了多项扩充和改进,主要包括:新容器新接口遍历方式

一、新增容器

下图中圈出的即为 STL 新增的容器,其中最有实际价值 的是:

这两个容器前面已经进行了非常详细的讲解。其他新增容器了解即可。有兴趣可以看看我之前的博客,有详细讲解

二、容器的新接口(重要)

C++11 为现有容器增加了许多新接口,最重要的与右值引用和移动语义相关

三、其他小改动(了解即可)

四、遍历方式

范围 for 循环(Range-based for loop):

cpp 复制代码
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto& x : vec)
{
    std::cout << x << " ";
}

在容器部分已经讲过了,这里不再赘述。

总结表:C++11 STL 主要变化


7.lambda

7.1 lambda表达式语法

1. 本质

Lambda 表达式本质上是一个匿名函数对象

与普通函数不同的是:它可以定义在函数内部

2. 类型与接收

Lambda 在语法层面没有显式的类型,一般使用:

  • auto

  • 模板参数

    来接收 Lambda 对象。

3. 语法格式

cpp 复制代码
[capture-list] (parameters) -> return_type { function_body }

4. 各部分的说明

  • [capture-list] ------ 捕捉列表

    • 必须写在最前面,编译器通过 [] 识别 Lambda。

    • 用于捕获上下文中的变量,供函数体使用。

    • 支持传值传引用捕获。

    • 即使为空也不能省略 []

  • (parameters) ------ 参数列表

    • 和普通函数的参数列表相同。

    • 不需要参数时可以连同 () 一起省略

  • -> return_type ------ 返回值类型

    • 使用追踪返回类型的形式声明。

    • 无返回值时可省略

    • 返回值类型明确时,也常省略,由编译器自行推导。

  • { function_body } ------ 函数体

    • 实现具体逻辑。

    • 可以使用参数和捕获到的变量。

    • 函数体为空也不能省略 {}

cpp 复制代码
int main()
{
	// ⼀个简单的lambda表达式
	auto add1 = [](int x, int y)->int {return x + y; };
	cout << add1(1, 2) << endl;
	// 1、捕捉为空也不能省略
	// 2、参数为空可以省略
	// 3、返回值可以省略,可以通过返回对象⾃动推导
	// 4、函数体不能省略
	auto func1 = []//()->int
	{
		cout << "hello bit" << endl;
		return 0;
	};
	func1();

	int a = 0, b = 1;
	auto swap1 = [](int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap1(a, b);
	cout << a << ":" << b << endl;
	return 0;
}

7.2捕捉列表

基本原则:Lambda 表达式默认只能使用自己的函数体内部 以及参数列表 中的变量。

若要使用外层作用域 的变量,必须通过捕获列表 [] 进行捕获。

1、捕获方式分类

  1. 显式捕获

在捕获列表中逐个写出变量,用逗号分隔,并可指定捕获方式。

  • 值捕获 :直接写变量名(如 x

  • 引用捕获 :变量名前加 &(如 &z

示例[x, y, &z]xy 值捕获,z 引用捕获

  1. 隐式捕获

在捕获列表中写一个 =&,让编译器自动推导需要捕获哪些变量。

  • [=]:隐式捕获

  • [&]:隐式引用捕获

  1. 混合捕获(隐式 + 显式)

先写隐式捕获符号,再显式写出个别例外变量。

  • [=, &x]:其他变量值捕获,x 引用捕获

  • [&, x, y]:其他变量引用捕获,xy 值捕获

混合捕获规则

  • 第一个元素必须是 =&

  • [&, ...] 后面只能跟值捕获变量

  • [=, ...] 后面只能跟引用捕获变量

2、作用域与捕获限制

函数局部域中的 Lambda

  • 只能捕获 Lambda 定义位置之前 的变量

  • 不能捕获静态局部变量和全局变量(也不需要捕获,可直接使用)

全局位置的 Lambda

  • 捕获列表必须为空 (即 []

3、mutable 修饰符

默认情况下,值捕获的变量在 Lambda 内部被 const 修饰,不可修改。

  • 在参数列表后加 mutable,可以取消常量性

  • 效果:允许修改值捕获的变量,但修改的是拷贝的形参,不影响外部原变量

语法要求

使用 mutable 时,参数列表 () 不可省略(即使参数为空)。

示例

cpp 复制代码
int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量
auto func1 = []()
{
	x++;
};//但是一般不会这样用
int main()
{
	// 只能⽤当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	//auto func1 = [a, &b,&a]"a"已经是 lambda 捕获列表的一部分也就是同一个变量不能捕捉多次
	auto func1 = [a, &b]
	{
		// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
		//a++;//值捕获的变量默认被 const 修饰
		b++;
		int ret = a + b;
		return ret;
	};
	cout << func1() << endl;

	// 隐式值捕捉
	// ⽤了哪些变量就捕捉哪些变量
	auto func2 = [=]
	{
		int ret = a + b + c;
		return ret;
	};
	cout << func2() << endl;

	// 隐式引⽤捕捉
	// ⽤了哪些变量就捕捉哪些变量
	auto func3 = [&]
	{	a++;c++;d++;	};
	func3();
	cout << a << " " << b << " " << c << " " << d << endl;

	// 混合捕捉1
	auto func4 = [&, a, b]//除了ab值捕捉其他变量都是引用捕捉
	{
		//a++;b++;
		c++;d++;
		return a + b + c + d;
	};
	func4();
	cout << a << " " << b << " " << c << " " << d << endl;

	// 混合捕捉2
	auto func5 = [=, &a, &b]//除了ab引用捕捉其他变量都是值捕捉
	{
		a++;b++;
		//c++;d++;
		return a + b + c + d;
	};
	func5();
	cout << a << " " << b << " " << c << " " << d << endl;

	// 局部的静态和全局变量不能捕捉,也不需要捕捉
	static int m = 0;
	//auto func6 = [m,x]无法在 lambda 中捕获带有静态存储持续时间的变量
	auto func6 = []
	{
		int ret = x + m;
		return ret;
	};

	// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
	// mutable相当于去掉const属性,可以修改了
	// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉加了 mutable 后,
	// const 属性确实没了,而且相当于用外面变量的值去拷贝一份副本,存在 Lambda 对象内部。
	auto func7 = [=]()mutable
	{
		a++;b++;c++;d++;
		return a + b + c + d;
	};
	cout << func7() << endl;
	cout << a << " " << b << " " << c << " " << d << endl;
	return 0;
}

7.3lambda的应⽤

  • 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
  • lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到
  • Lambda 适合短小、局部的函数逻辑;函数很长或需要复用,应写成普通函数。
cpp 复制代码
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()
{//lamda价值
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, 
						{ "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };
	// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
	// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了

	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
	//匿名函数对象
	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;
		});
	return 0;
}

7.4lambda的原理

核心结论

Lambda 和范围 for 在底层原理上很像:

  • 范围 for → 底层是迭代器

  • Lambda → 底层是仿函数对象(函数对象)

从汇编指令层面看,不存在 Lambda 这种语法结构,它完全是编译器语法糖。也就是说在我们写了一个lamda之后,编译期会生成一个对应的仿函数的类,类的名称由编译器自己生成

一、编译器如何实现 Lambda

当我们写了一个 Lambda 表达式后,编译器会自动生成一个对应的仿函数类

类名生成规则

  • 类名由编译器按一定规则生成

  • 保证不同的 Lambda 生成不同的类名

类成员与函数的映射关系

构造函数与捕获:

  • 捕捉列表中的变量 → 仿函数类的成员变量

  • 这些变量作为构造函数的实参传递给仿函数类的对象

  • 对于隐式捕获 ,编译器会分析 Lambda 体中使用了哪些变量,只传递那些实际用到的变量,通过下面汇编可以很清晰看到

二、汇编层面的印证编译器自动生成一个对应的仿函数类

cpp 复制代码
class Rate
{
public:
	Rate(double rate)
		: _rate(rate)
	{
	}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};
int main()
{
	double rate = 0.49;
	// lambda rate相当于成员变量
	auto r2 = [rate](double money, int year) {
			return money * rate * year;
		};
	// 函数对象
	Rate r1(rate);
	r1(10000, 2);
	r2(10000, 2);
	auto func1 = [] {
			cout << "hello world" << endl;
		};
	func1();
	return 0;
}

这意味着:

  • 汇编中会出现仿函数类的构造operator() 的调用

  • 捕捉的变量会被存入类的成员变量位置

  • 不存在原始的 Lambda 语法痕迹

总结表:Lambda 与仿函数类的对应关系

Lambda 写法 编译器生成的内容
[x, &y](int a) -> int { return x + y + a; } 类成员:x(值)、y(引用) operator()(int a) 返回 int
[=] 隐式值捕获 自动识别使用的变量,生成对应成员变量
[&] 隐式引用捕获 自动识别使用的变量,生成对应引用成员变量

8.包装器

8.1function

一、基本概念

std::function 是 C++ 标准库中的一个类模板 ,也是一个通用包装器

  • 定义在 <functional> 头文件中

  • 可以包装并存储各种可调用对象

二、原型说明

cpp 复制代码
template <class T>
class function;      // 主模板(未定义)

template <class Ret, class... Args>
class function<Ret(Args...)>;  // 偏特化版本
  • Ret:返回值类型

  • Args...:参数包(可变参数)

三、可包装的目标类型

std::function 可以存储以下可调用对象:

这些被存储的对象称为 std::function目标

四、空状态与异常

  • std::function:不包含任何目标

  • 调用空对象的 operator() → 抛出 std::bad_function_call 异常

五、核心优势:类型统一

函数指针、仿函数、Lambda 等可调用对象虽然行为相似,但类型各不相同

std::function 的优势在于:

统一类型,对所有可调用对象进行包装,方便声明、存储和传递。

六、典型应用场景

150. 逆波兰表达式求值 - 力扣(LeetCode)

我们之前的写法vs现代C++11写法

cpp 复制代码
class Solution {
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int> st;
		for (auto& e : tokens)
		{//处理负数与减法的混乱
			if (e.size() == 1 &&(e[0] == '+' || e[0] == '-'
				|| e[0] == '*' || e[0] == '/'))
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				switch(e[0])
				{
					case '+':
						st.push(right + left); break;
					case '-':
						st.push(left - right); break;
					case '*':
						st.push(right* left); break;
					case '/':
						st.push(left / right); break;
				}
			}
			else
				st.push(stoi(e));
		}
		return st.top();
	}
};

// 使⽤map映射string和function的⽅式实现
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int> st;
		// function作为map的映射可调⽤对象的类型
		map<string, function<int(int, int)>> opFuncmap = {
			{"+",[](int x,int y) {return x + y; }},
			{"-",[](int x,int y) {return x - y; }},
			{"*",[](int x,int y) {return x * y; }},
			{"/",[](int x,int y) {return x / y; }} };
		
		for (auto& e : tokens)
		{
			if (opFuncmap.count(e))// 操作符
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				st.push(opFuncmap[e](left,right));
			}
			else
			{
				st.push(stoi(e));
			}
		}
		return st.top();
	}
};
cpp 复制代码
int fx(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}
	//静态成员函数没有this指针
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
	int _n;
};

int main()
{
	// 包装各种可调⽤对象
	function<int(int, int)> f1 = fx;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };
	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;

	// 包装静态成员函数,静态成员函数没有this指针
	// 成员函数要指定类域并且前⾯加&才能获取地址
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	// 包装普通成员函数
	// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus pd;
	cout << f5(&pd, 1.1, 1.1) << endl;
	//还可以这样写
	function<double(Plus, double, double)> f6 = &Plus::plusd;
	cout << f6(pd, 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(move(pd), 1.1, 1.1) << endl;
	cout << f7(Plus(), 1.1, 1.1) << endl;


	function<double(Plus&, double, double)> f8 = &Plus::plusd;
	cout << f8(pd, 1.1, 1.1) << endl;
	//cout << f8(Plus(), 1.1, 1.1) << endl;//右值不能绑定到左值
	return 0;
}
  1. 普通函数:函数名在表达式中会隐式转换为函数指针,所以 &func 和 func 等价。
  2. 成员函数:成员函数名不能隐式转换为成员函数指针。标准规定,成员函数名必须通过 &类名::函数名 才能获得其指针,否则编译错误。

通过 std::function<void()> 统一了所有无参、无返回值的可调用对象类型,实现了运行时多态的可调用对象表

七、参考资料


8.2bind

一、函数原型

cpp 复制代码
// 简单版本 (1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

// 指定返回类型版本 (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

二、基本概念

  • std::bind 是一个函数模板 ,也是一个可调用对象的包装器

  • 可以把它看作一个函数适配器(Function Adapter)

  • 对接收的可调用对象 fn 进行处理后,返回一个新的可调用对象

  • 主要用途:调整参数个数调整参数顺序

  • 定义在 <functional> 头文件中

三、基本用法

一般形式:

cpp 复制代码
auto newCallable = bind(callable, arg_list);
  • newCallable:返回的新的可调用对象

  • callable:被包装的原始可调用对象

  • arg_list:逗号分隔的参数列表,对应 callable 的参数

调用过程:

当调用 newCallable 时 → newCallable 会调用 callable → 并传入 arg_list 中的参数

四、占位符(Placeholders)

arg_list 中的参数可以包含 占位符 表示newCallable的参数,形如 _nn 为整数),他表示可调用对象中参数的位置

占位符 含义
_1 表示 newCallable第一个参数
_2 表示 newCallable第二个参数
_3 表示 newCallable第三个参数
... 以此类推

作用:

占位符占据了传递给 newCallable 的参数位置,用于重新排列参数顺序延迟参数绑定

命名空间:

cpp 复制代码
// 占位符定义在 placeholders 命名空间中
using namespace std::placeholders;

五、使用示例

cpp 复制代码
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
	return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;
	// bind 本质返回的⼀个仿函数对象
	// 调整参数顺序(不常⽤)
	// _1代表第⼀个实参
	// _2代表第⼆个实参
	// ...
	auto sub2 = bind(Sub, _2, _1);
	cout << sub2(10, 5) << endl;

	// 调整参数个数 (常⽤)
	auto sub3 = bind(Sub, 100, _1);
	cout << sub3(5) << endl;
	auto sub4 = bind(Sub, _1, 100);
	cout << sub4(5) << endl;

	// 分别绑死第123个参数
	auto sub5 = bind(SubX, 100, _1, _2);
	cout << sub5(5, 1) << endl;
	auto sub6 = bind(SubX, _1, 100, _2);
	cout << sub6(5, 1) << endl;
	auto sub7 = bind(SubX, _1, _2, 100);
	cout << sub7(5, 1) << endl;

	// 成员函数对象进⾏绑死,就不需要每次都传递了
	function<double(Plus&&, double, double)> f6 = &Plus::plusd;
	Plus pd;
	cout << f6(move(pd), 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	// bind⼀般⽤于,绑死⼀些固定参数
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;
	// 计算复利的lambda     利率          本金        年份
	auto func1 = [](double rate, double money, int year)->double {
	double ret = money;
	for (int i = 0; i < year; i++)
	{
		ret += ret * rate;
	}
		return ret - money;
	};
	// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
	function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
	function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
	function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
	function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
	cout << func3_1_5(1000000) << endl;
	cout << func5_1_5(1000000) << endl;
	cout << func10_2_5(1000000) << endl;
	cout << func20_3_5(1000000) << endl;
	return 0;
}

六、核心功能总结

七、参考资料

9.智能指针

智能指针内容较多,后面我会继续更新它的使用以及原理的深度剖析

相关推荐
qq_白羊座2 小时前
Langchain、Cursor、python的关系
开发语言·python·langchain
kiku18182 小时前
Python网络编程
开发语言·网络·python
小李子呢02112 小时前
前端八股3---ref和reactive
开发语言·前端·javascript
SWAGGY..2 小时前
【C++初阶】:(7)STL简介
开发语言·c++
dog2502 小时前
卡瓦列里积分赏析
开发语言·php
流星蝴蝶没有剑2 小时前
CoPaw Agent 对接 Python 客户端开发指南:实现流式响应与实时打印
开发语言·python
咬_咬2 小时前
go语言学习(数组与切片)
开发语言·学习·golang·数组·切片
小陈工2 小时前
Python Web开发入门(十八):跨域问题解决方案——从“为什么我的请求被拦了“到“我让浏览器乖乖听话“
开发语言·python·机器学习·架构·数据挖掘·回归·状态模式
m0_497214152 小时前
Qt事件系统
开发语言·qt