当前位置: 首页 > news >正文

c++ template-3

第 7 章 按值传递还是按引用传递

        从一开始,C++就提供了按值传递(call-by-value)和按引用传递(call-by-reference)两种参数传递方式,但是具体该怎么选择,有时并不容易确定:通常对复杂类型用按引用传递的成本更低,但是也更复杂。C++11 又引入了移动语义(move semantics),也就是说又多了一种按引用传递的方式:
1. X const &(const 左值引用)
参数引用了被传递的对象,并且参数不能被更改。
2. X &(非 const 左值引用)
参数引用了被传递的对象,但是参数可以被更改。
3. X &&(右值引用)
参数通过移动语义引用了被传递的对象,并且参数值可以被更改或者被“窃取”。仅仅对已知的具体类型,决定参数的方式就已经很复杂了。在参数类型未知的模板中,就更难选择合适的传递方式了。
不过在 1.6.1 节中,我们曾经建议在函数模板中应该优先使用按值传递,除非遇到以下情况: 对象不允许被 copy。

  • 参数被用于返回数据。
  • 参数以及其所有属性需要被模板转发到别的地方。
  • 可以获得明显的性能提升。

        本章将讨论模板中传递参数的几种方式,并将证明为何应该优先使用按值传递,也列举了不该使用按值传递的情况。同时讨论了在处理字符串常量和裸指针时遇到的问题。在阅读本章的过程中,最好先够熟悉下附录 B 中和数值分类有关的一些术语(lvalue,rvalue,prvalue,xvalue)。

7.1 按值传递

        当按值传递参数时,原则上所有的参数都会被拷贝。因此每一个参数都会是被传递实参的一份拷贝。对于 class 的对象,参数会通过 class 的拷贝构造函数来做初始化。调用拷贝构造函数的成本可能很高。

        但是有多种方法可以避免按值传递的高昂成本:事实上编译器可以通过移动语义(move semantics)来优化掉对象的拷贝,这样即使是对复杂类型的拷贝,其成本也不会很高。
        比如下面这个简单的按值传递参数的函数模板: 

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void printV(T arg) {}int main() {std::string returnString();std::string s = "hi";printV(s); //copy constructorprintV(std::string("hi")); //copying usually optimized away (if not,move constructor)printV(returnString()); // copying usually optimized away (if not, moveconstructor)printV(std::move(s)); // move constructorreturn 0;
}

        在第一次调用中,被传递的参数是左值(lvalue),因此拷贝构造函数会被调用。

        但是在第二和第三次调用中,被传递的参数是纯右值(prvalue,pure right value,临时对象或者某个函数的返回值,参见附录 B),此时编译器会优化参数传递,使得拷贝构造函数不会被调用。从 C++17 开始,C++标准要求这一优化方案必须被实现。在 C++17 之前,如果编译器没有优化掉这一类拷贝,它至少应该先尝试使用移动语义,这通常也会使拷贝成本变得比较低廉。

        在最后一次调用中,被传递参数是 xvalue(一个使用了 std::move()的已经存在的非const 对象),这会通过告知编译器我们不在需要 s 的值来强制调用移动构造函数(move constructor)。

        综上所述,在调用 printV()(参数是按值传递的)的时候,只有在被传递的参数是lvalue(对象在函数调用之前创建,并且通常在之后还会被用到,而且没有对其使用std::move())时, 调用成本才会比较高。不幸的是,这唯一的情况也是最常见的情况,因为我们几乎总是先创建一个对象,然后在将其传递给其它函数

按值传递会导致类型退化(decay)

        关于按值传递,还有一个必须被讲到的特性:当按值传递参数时,参数类型会退化(decay)。也就是说,裸数组会退化成指针,const 和 volatile 等限制符会被删除(就像用一个值去初始化一个用 auto

        

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void printV(T arg) {}int main() {std::string const c = "hi";printV(c); // c decays so that arg has type std::stringprintV("hi"); //decays to pointer so that arg has type char const*int arr[4];int arr[4];printV(arr); // decays to pointer so that arg has type int *return 0;
}

        当传递字符串常量“hi”的时候,其类型 char const[3]退化成 char const *,这也就是模板参数 T 被推断出来的类型。此时模板会被实例化成: 

void printV (char const* arg)
{ …
}

        这一行为继承自 C 语言,既有优点也有缺点。通常它会简化对被传递字符串常量的处理,但是缺点是在 printV()内部无法区分被传递的是一个对象的指针还是一个存储一组对象的数组。在 7.4 节将专门讨论如何应对字符串常量和裸数组的问题。 

 7.2 按引用传递

        现在来讨论按引用传递。按引用传递不会拷贝对象(因为形参将引用被传递的实参)。而且,按引用传递时参数类型也不会退化(decay)。不过,并不是在所有情况下都能使用按引用传递,即使在能使用的地方,有时候被推断出来的模板参数类型也会带来不少问题。

7.2.1 按 const 引用传递

        为了避免(不必要的)拷贝,在传递非临时对象作为参数时,可以使用const 引用传递。

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void printR(T const& arg) {
}int main() {std::string returnString();std::string s = "hi";printR(s); // no copyprintR(std::string("hi")); // no copyprintR(std::move(s)); // no copyint i = 42;printR(i); // passes reference instead of just copying ireturn 0;
}

         这个模板永远不会拷贝被传递对象(不管拷贝成本是高还是低)

        即使是按引用传递一个 int 类型的变量,虽然这样可能会事与愿违(不会提高性能,见下段中的解释),也依然不会拷贝。

        这样做之所以不能提高性能,是因为在底层实现上,按引用传递还是通过传递参数的地址实现的。地址会被简单编码,这样可以提高从调用者向被调用者传递地址的效率。不过按地址传递可能会使编译器在编译调用者的代码时有一些困惑:被调用者会怎么处理这个地址?理论上被调用者可以随意更改该地址指向的内容。这样编译器就要假设在这次调用之后,所有缓存在寄存器中的值可能都会变为无效。而重新载入这些变量的值可能会很耗时(可能比拷贝对象的成本高很多)。你或许会问在按 const 引用传递参数时:为什么编译器不能推断出被调用者不会改变参数的值?不幸的是,确实不能,因为调用者可能会通过它自己的非const 引用修改被引用对象的值(这个解释太好,另一种情况是被调用者可以通过const_cast 移除参数中的 const)。 

        不过对可以 inline 的函数,情况可能会好一些:如果编译器可以展开inline 函数,那么它就可以基于调用者和被调用者的信息,推断出被传递地址中的值是否会被更改。函数模板通常总是很短,因此很可能会被做 inline 展开。但是如果模板中有复杂的算法逻辑,那么它大概率就不会被做 inline 展开了。

按引用传递不会做类型退化(decay)  

        按引用传递参数时,其类型不会退化(decay)。也就是说不会把裸数组转换为指针,也不会移除 const 和 volatile 等限制符。而且由于调用参数被声明为 T const &,被推断出来的模板参数 T 的类型将不包含 const。比如:

std::string const c = "hi";
printR(c); // T deduced as std::string, arg is std::string const&printR("hi"); // T deduced as char[3], arg is char const(&)[3]
int arr[4];
printR(arr); // T deduced as int[4], arg is int const(&)[4]

因此对于在 printR()中用 T 声明的变量,它们的类型中也不会包含 const。

7.2.2 按非 const 引用传递

        如果想通过调用参数来返回变量值(比如修改被传递变量的值),就需要使用非const 引用(要么就使用指针)。同样这时候也不会拷贝被传递的参数。被调用的函数模板可以直接访问被传递的参数。 考虑如下情况:

template<typename T>
void outR(T& arg) {
}

        注意对于 outR(),通常不允许将临时变量(prvalue)或者通过 std::move()处理过的已存在的变量(xvalue)用作其参数:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void outR(T& arg) {
}int main() {std::string returnString();std::string s = "hi";outR(s); //OK: T deduced as std::string, arg is std::string&outR(std::string("hi")); //ERROR: not allowed to pass a temporary(prvalue)outR(returnString()); // ERROR: not allowed to pass a temporary(prvalue)outR(std::move(s)); // ERROR: not allowed to pass an xvaluereturn 0;
}

同样可以传递非 const 类型的裸数组,其类型也不会 decay:

int arr[4];
outR(arr); // OK: T deduced as int[4], arg is int(&)[4]

这样就可以修改数组中元素的值,也可以处理数组的长度。比如:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void outR(T& arg) {if (std::is_array<T>::value) {std::cout << "got array of " << std::extent<T>::value << " elems\n";}
}int main() {int arr[4];outR(arr); // OK: T deduced as int[4], arg is int(&)[4]return 0;
}

        但是在这里情况有一些复杂。此时如果传递的参数是 const 的,arg 的类型就有可能被推断为 const 引用,也就是说这时可以传递一个右值(rvalue)作为参数,但是模板所期望的参数类型却是左值(lvalue):

std::string const c = "hi";
outR(c); // OK: T deduced as std::string const
outR(returnConstString()); // OK: same if returnConstString() returnsconst string
outR(std::move(c)); // OK: T deduced as std::string const6
outR("hi"); // OK: T deduced as char const[3]

        在这种情况下,在函数模板内部,任何试图更改被传递参数的值的行为都是错误的。在调用表达式中也可以传递一个 const 对象,但是当函数被充分实例化之后(可能发生在接接下来的编译过程中),任何试图更改参数值的行为都会触发错误(但是这有可能发生在被调用模板的很深层次逻辑中,具体细节请参见 9.4 节)。

         如果想禁止想非 const 应用传递 const 对象,有如下选择:

        可以将任意类型的参数传递给转发引用,而且和往常的按引用传递一样,都不会创建被传递参数的备份:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void passR(T&& arg) {if (std::is_array<T>::value) {std::cout << "got array of " << std::extent<T>::value << " elems\n";}
}int main() {std::string s = "hi";passR(s); // OK: T deduced as std::string& (also the type of arg)passR(std::string("hi")); // OK: T deduced as std::string, arg is std::string&&passR(std::string()); // OK: T deduced as std::string, arg is std::string&&passR(std::move(s)); // OK: T deduced as std::string, arg is std::string&&int arr[4];passR(arr); // OK: T deduced as int(&)[4] (alsoreturn 0;
}

         但是,这种情况下类型推断的特殊规则可能会导致意想不到的结果:

        看上去将一个参数声明为转发引用总是完美的。但是,没有免费的午餐。比如,由于转发引用是唯一一种可以将模板参数 T 隐式推断为引用的情况,此时如果在模板内部直接用 T 声明一个未初始化的局部变量,就会触发一个错误(引用对象在创建的时候必须被初始化):

template<typename T>
void passR(T&& arg) {T x;
}
int main() {passR(42); // OK: T deduced as intint i;passR(i); // ERROR: T deduced as int&, which makes the declaration ofxin passR() invalidreturn 0;
}

??? 没看懂问题

7.3 使用 std::ref()和 std::cref() (限于模板)

        从 C++11 开始,可以让调用者自行决定向函数模板传递参数的方式。如果模板参数被声明成按值传递的,调用者可以使用定义在头文件中的 std::ref()和std::cref()将参数按引用传递给函数模板。比如

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>template<typename T>
void printT(T arg) {
}int main() {std::string s = "hello";printT(s); //pass s By valueprintT(std::cref(s)); // pass s “as if by reference”return 0;
}

7.4 处理字符串常量和裸数组

到目前为止,我们看到了将字符串常量和裸数组用作模板参数时的不同效果:

  •  按值传递时参数类型会 decay,参数类型会退化成指向其元素类型的指针。
  • 按引用传递是参数类型不会 decay,参数类型是指向数组的引用。

        两种情况各有其优缺点。将数组退化成指针,就不能区分它是指向对象的指针还是一个被传递进来的数组。另一方面,如果传递进来的是字符串常量,那么类型不退化的话就会带来问题,因为不同长度的字符串的类型是不同的。比如:

        这里 foo(“hi”, “guy”)不能通过编译,因为”hi”的类型是 char const [3],而”guy”的类型是char const [4],但是函数模板要求两个参数的类型必须相同。

        这种 code 只有在两个字符串常量的长度相同时才能通过编译。因此,强烈建议在测试代码中使用长度不同的字符串。

        如果将 foo()声明成按值传递的,这种调用可能可以正常运行: 

        但是这样并不能解决所有的问题。反而可能会更糟,编译期间的问题可能会变为运行期间的问题

7.4.1 关于字符串常量和裸数组的特殊实现

        有时候可能必须要对数组参数和指针参数做不同的实现。此时当然不能退化数组的类型。

        为了区分这两种情况,必须要检测到被传递进来的参数是不是数组。

        通常有两种方法:

 可以将模板定义成只能接受数组作为参数:

template<typename T, std::size_t L1, std::size_t L2>
void foo(T (&arg1)[L1], T (&arg2)[L2])
{
T* pa = arg1; // decay arg1
T* pb = arg2; // decay arg2
if (compareArrays(pa, L1, pb, L2)) { …
}
}

参数 arg1 和 arg2 必须是元素类型相同、长度可以不同的两个数组。但是为了支持多种不同类型的裸数组,可能需要更多实现方式(参见 5.4 节)。

可以使用类型萃取来检测参数是不是一个数组:

template<typename T, typename =
std::enable_if_t<std::is_array_v<T>>>
void foo (T&& arg1, T&& arg2)
{ …
}

        由于这些特殊的处理方式过于复杂,最好还是使用一个不同的函数名来专门处理数组参数。或者更近一步,让模板调用者使用 std::vector 或者 std::array 作为参数。但是只要字符串还是裸数组,就必须对它们进行单独考虑

7.5 处理返回值

将返回类型声明为 auto,从而让编译器去推断返回类型,这是因为auto 也会导致类型退化

template<typename T>
auto retV(T p) // by-value return type deduced by compiler
{
return T{…}; // always returns by value
}

7.6 关于模板参数声明的推荐方法

正如前几节介绍的那样,函数模板有多种传递参数的方式:

将参数声明成按值传递:
        这一方法很简单,它会对字符串常量和裸数组的类型进行退化,但是对比较大的对象可能会受影响性能。在这种情况下,调用者仍然可以通过 std::cref()和 std::ref()按引用传递参数,但是要确保这一用法是有效的。
将参数声明成按引用传递:
        对于比较大的对象这一方法能够提供比较好的性能。尤其是在下面几种情况下:

  • 将已经存在的对象(lvalue)按照左值引用传递, 
  • 将临时对象(prvalue)或者被 std::move()转换为可移动的对象(xvalue)按右值引用传递,
  • 或者是将以上几种类型的对象按照转发引用传递。

由于这几种情况下参数类型都不会退化,因此在传递字符串常量和裸数组时要格外小心。对于转发引用,需要意识到模板参数可能会被隐式推断为引用类型(引用折叠)。

一般性建议

        基于以上介绍,对于函数模板有如下建议: 1. 默认情况下,将参数声明为按值传递。这样做比较简单,即使对字符串常量也可以正常工作。对于比较小的对象、临时对象以及可移动对象,其性能也还不错。对于比较大的对象,为了避免成本高昂的拷贝,可以使用 std::ref()和 std::cref()

        2. 如果有充分的理由,也可以不这么做:

  • 如果需要一个参数用于输出,或者即用于输入也用于输出,那么就将这个参数按非const 引用传递。但是需要按照 7.2.2 节介绍的方法禁止其接受 const 对象。
  • 如果使用模板是为了转发它的参数,那么就使用完美转发(perfect forwarding)。也就是将参数声明为转发引用并在合适的地方使用 std::forward<>()。考虑使用std::decay<>或者 std::common_type<>来处理不同的字符串常量类型以及裸数组类型的情况。
  • 如果重点考虑程序性能,而参数拷贝的成本又很高,那么就使用const 引用。不过如果最终还是要对对象进行局部拷贝的话,这一条建议不适用。

3. 如果你更了解程序的情况,可以不遵循这些建议。但是请不要仅凭直觉对性能做评估。在这方面即使是程序专家也会犯错。真正可靠的是:测试结果

不要过分泛型化

        值得注意的是,在实际应用中,函数模板通常并不是为了所有可能的类型定义的。而是有一定的限制。比如你可能已经知道函数模板的参数只会是某些类型的 vector。这时候最好不要将该函数模板定义的过于泛型化,否则,可能会有一些令人意外的副作用。针对这种情况应该使用如下的方式定义模板:

template<typename T>
void printVector (std::vector<T> const& v)
{ …
}

        这里通过的参数 v,可以确保 T 不会是引用类型,因为 vector 不能用引用作为其元素类型。而且将 vector 类型的参数声明为按值传递不会有什么好处,因为按值传递一个vector 的成本明显会比较高昂(vector 的拷贝构造函数会拷贝 vector 中的所有元素)。此处如果直接将参数 v 的类型声明为 T,就不容易从函数模板的声明上看出该使用那种传递方式了。

以 std::make_pair<>为例

        std::make_pair<>()是一个很好的介绍参数传递机制相关陷阱的例子。使用它可以很方便的通过类型推断创建 std::pair<>对象。它的定义在各个版本的 C++中都不一样:

         在第一版 C++标准 C++98 中,std::make_pair<>被定义在 std 命名空间中,并且使用按引用传递来避免不必要的拷贝:

template<typename T1, typename T2>
pair<T1,T2> make_pair (T1 const& a, T2 const& b)
{
return pair<T1,T2>(a,b);
}

        但是当使用 std::pair<>存储不同长度的字符串常量或者裸数组时,这样做会导致严重的问题。

        因此在 C++03 中,该函数模板被定义成按值传递参数

template<typename T1, typename T2>
pair<T1,T2> make_pair (T1 a, T2 b)
{
return pair<T1,T2>(a,b);
}

        不过在 C++11 中,由于 make_pair<>()需要支持移动语义,就必须使用转发引用。因此,其定义大体上是这样:

template<typename T1, typename T2>
constexpr pair<typename decay<T1>::type, typename
decay<T2>::type>
make_pair (T1&& a, T2&& b)
{
return pair<typename decay<T1>::type, typename
decay<T2>::type>(forward<T1>(a), forward<T2>(b));
}

        完 整 的 实 现 还 要 复 杂 的 多 : 为 了 支 持 std::ref() 和 std::cref() ,该函数会将std::reference_wrapper 展开成真正的引用。 目前 C++标准库在很多地方都使用了类似的方法对参数进行完美转发,而且通常都会结合std::decay<>使用

7.7 总结

 最好使用不同长度的字符串常量对模板进行测试。
 模板参数的类型在按值传递时会退化,按引用传递则不会。
 可以使用 std::decay<>对按引用传递的模板参数的类型进行退化。 在某些情况下,对被声明成按值传递的函数模板,可以使用 std::cref()和std::ref()将参数按引用进行传递。
 按值传递模板参数的优点是简单,但是可能不会带来最好的性能。 除非有更好的理由,否则就将模板参数按值传递。
 对于返回值,请确保按值返回(这也意味着某些情况下不能直接将模板参数直接用于返回类型)。
 在比较关注性能时,做决定之前最好进行实际测试。不要相信直觉,它通常都不准确

第 8 章 编译期编程

8.1 模板元编程

        模板的实例化发生在编译期间(而动态语言的泛型是在程序运行期间决定的)。事实证明C++模板的某些特性可以和实例化过程相结合,这样就产生了一种 C++自己内部的原始递归的“编程语言”。因此模板可以用来“计算一个程序的结果”。第 23 章会对这些特性进行全面介绍,这里通过一个简单的例子来展示它们的用处。

判断一个数是不是质数

下面的代码在编译期间就能判断一个数是不是质数:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>// p: number to check, d: current divisor
template<unsigned p, unsigned d>
struct DoIsPrime {static constexpr bool value = (p % d != 0) && DoIsPrime < p, d - 1 >::value;
};// end recursion if divisor is 2
template<unsigned p>
struct DoIsPrime<p, 2> {static constexpr bool value = (p % 2 != 0);
};// primary template
template<unsigned  n>
struct IsPrime {// start recursion with divisor from p/2:static constexpr bool value = DoIsPrime < n, n / 2 >::value;
};// special cases (to avoid endless recursion with template instantiation):
template<>
struct IsPrime<0> { static constexpr bool value = false; };
template<>
struct IsPrime<1> { static constexpr bool value = false; };
template<>
struct IsPrime<2> { static constexpr bool value = true; };
template<>
struct IsPrime<3> { static constexpr bool value = true; };int main() {std::cout << IsPrime<9>::value;return 0;
}

        IsPrime<>模板将结果存储在其成员 value 中。为了计算出模板参数是不是质数,它实例化了DoIsPrime<>模板,这个模板会被递归展开,以计算 p 除以 p/2 和 2 之间的数之后是否会有余数。

        正如以上实例化过程展现的那样:

  • 我们通过递归地展开 DoIsPrime<>来遍历所有介于 p/2 和 2 之间的数,以检查是否有某个数可以被 p 整除。
  • 用 d 等于 2 偏特例化出来的 DoIsPrime<>被用于终止递归调用。但是以上过程都是在编译期间进行的。 

8.2 通过 constexpr 进行计算

        在 C++14 中,constexpr 函数可以使用常规 C++代码中大部分的控制结构。因此为了判断一个数是不是质数,可以不再使用笨拙的模板方式(C++11 之前)以及略显神秘的单行代码方式

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>constexpr bool IsPrime(unsigned int n) {for (unsigned int d = 2; d <= n / 2; ++d) {if (n % d == 0) {return false;}}return n > 1;
}int main() {constexpr bool b1 = IsPrime(9);return 0;
}

         但是上面所说的“可以”在编译期执行,并不是一定会在编译期执行。在需要编译期数值的上下文中(比如数组的长度和非类型模板参数),编译器会尝试在编译期对被调用的 constexpr 函数进行计算,此时如果无法在编译期进行计算,就会报错(因为此处必须要产生一个常量)。

        在其他上下文中,编译期可能会也可能不会尝试进行编译期计算,如果在编译期尝试了,但是现有条件不满足编译期计算的要求,那么也不会报错,相应的函数调用被推迟到运行期间执行。         比如:

constexpr bool b1 = isPrime(9); // evaluated at compile time

        会在编译期进行计算(因为 b1 被 constexpr 修饰)。而对

const bool b2 = isPrime(9); // evaluated at compile time if in namespacescope

        如果 b2 被定义于全局作用域或者 namespace 作用域,也会在编译期进行计算。如果b2 被定义于块作用域({}内),那么将由编译器决定是否在编译期间进行计算。下面这个例子就属于这种情况:

bool fiftySevenIsPrime() {
return isPrime(57); // evaluated at compile or running time
}

此时是否进行编译期计算将由编译期决定。

另一方面,在如下调用中:

int x;

std::cout << isPrime(x); // evaluated at run time

不管 x 是不是质数,调用都只会在运行期间执行

8.3 通过部分特例化进行路径选择

诸如 isPrime()这种在编译期进行相关测试的功能,有一个有意思的应用场景:可以在编译期间通过部分特例化在不同的实现方案之间做选择。

比如,可以以一个非类型模板参数是不是质数为条件,在不同的模板之间做选择:

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>constexpr bool IsPrime(unsigned int n) {for (unsigned int d = 2; d <= n / 2; ++d) {if (n % d == 0) {return false;}}return n > 1;
}// primary helper template:
template<int SZ, bool = IsPrime(SZ)>
struct Helper;
// implementation if SZ is not a prime number:
template<int SZ>
struct Helper<SZ, false> {};
// implementation if SZ is a prime number:
template<int SZ>
struct Helper<SZ, true> {};int main() {Helper<9> h;return 0;
}

相关文章:

c++ template-3

第 7 章 按值传递还是按引用传递 从一开始&#xff0c;C就提供了按值传递&#xff08;call-by-value&#xff09;和按引用传递&#xff08;call-by-reference&#xff09;两种参数传递方式&#xff0c;但是具体该怎么选择&#xff0c;有时并不容易确定&#xff1a;通常对复杂类…...

【实战篇】巧用 DeepSeek,让 Excel 数据处理更高效

一、为何选择用 DeepSeek 处理 Excel 在日常工作与生活里,Excel 是我们频繁使用的工具。不管是统计公司销售数据、分析学生成绩,还是梳理个人财务状况,Excel 凭借其强大的功能,如数据排序、筛选和简单公式计算,为我们提供了诸多便利。但当面对复杂的数据处理任务,比如从…...

【prompt实战】AI +OCR技术结合ChatGPT能力项目实践(BOL提单识别提取专家)

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 1. 需求背景 2. 目标 3. BOL通用处理逻辑…...

黑马 Linux零基础快速入门到精通 笔记

初识Linux Linux简介 提及操作系统&#xff0c;我们可能最先想到的是windows和mac&#xff0c;这两者都属于个人桌面操作系统领域&#xff0c;而Linux则属于服务器操作系统领域。无论是后端软件、大数据系统、网页服务等等都需要运行在Linux操作系统上。 Linux是一个开源的操作…...

蓝桥杯真题 - 像素放置 - 题解

题目链接&#xff1a;https://www.lanqiao.cn/problems/3508/learning/ 个人评价&#xff1a;难度 3 星&#xff08;满星&#xff1a;5&#xff09; 前置知识&#xff1a;深度优先搜索 整体思路 深搜&#xff0c;在搜索过程中进行剪枝&#xff0c;剪枝有以下限制条件&#xf…...

即梦(Dreamina)技术浅析(六):多模态生成模型

多模态生成模型是即梦(Dreamina)的核心技术之一,旨在结合文本和图像信息,生成更符合用户需求的视觉内容。多模态生成模型通过整合不同类型的数据(如文本和图像),能够实现更丰富、更精准的生成效果。 1. 基本原理 1.1 多模态生成模型概述 多模态生成模型的目标是结合不…...

C++小等于的所有奇数和=最大奇数除2加1的平方。

缘由 三种思路解题&#xff1a;依据算术推导得到一个规律&#xff1a;小等于的所有奇数和等于最大奇数除以2加1的平方。将在后续发布&#xff0c;总计有十种推导出来的实现代码。 int a 0,aa 1,aaa 0;cin >> a; while (aa<a) aaa aa, aa 2;cout << aaa;i…...

react的antd表单校验,禁止输入空格并触发校验提示

首先需要用到form组件&#xff0c;在form.item内添加rules属性&#xff0c;写正则表达式 <Form.Itemlabel"员工姓名"name"name"rules{[{ required: true, message: 员工姓名 },{ pattern: /^(?!\s*$).$/, message: 不能全是空格 },]}> <Input p…...

Kubernetes架构原则和对象设计(三)

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes常见问题解答 本文主要对kubernetes的核心技术概念和核心A…...

Qt+海康虚拟相机的调试

做机器视觉项目的时候&#xff0c;在没有相机或需要把现场采集的图片在本地跑一下做测试时&#xff0c;可以使用海康的虚拟相机调试。以下是设置步骤&#xff1a; 1.安装好海康MVS软件&#xff0c;在菜单栏->工具选择虚拟相机工具&#xff0c;如下图&#xff1a; 2.打开虚拟…...

485网关数据收发测试

目录 1.UDP SERVER数据收发测试 使用产品&#xff1a; || ZQWL-GW1600NM 产品||【智嵌物联】智能网关型串口服务器 1.UDP SERVER数据收发测试 A&#xff08;TX&#xff09;连接RX B&#xff08;RX&#xff09;连接TX 打开1个网络调试助手&#xff0c;模拟用户的UDP客户端设…...

【C#】一维、二维、三维数组的使用

在C#中&#xff0c;数组是用于存储固定数量相同类型元素的数据结构。根据维度的不同&#xff0c;可以分为一维数组、二维数组&#xff08;矩阵阵列&#xff09;、三维数组等。每增加一个维度&#xff0c;数据的组织方式就会变得更加复杂。 一维数组 一维数组是最简单的数组形…...

65【服务器攻击原理讲解】

我们经常可能会听说&#xff0c;某某的服务器被打了&#xff0c;被打死了&#xff0c;这里的打死并不一是指服务器直接死机 服务器有2个决定性参数 1&#xff1a;宽带&#xff0c;宽带越大&#xff0c;能传输的数据就越多 2&#xff1a;CPU&#xff0c;CPU越好能处理的运算…...

用AI写游戏3——模拟发牌

提示词 写一个python程序 &#xff0c;输入参数为玩家数&#xff0c;输出参数为每个玩家的3张扑克牌 # 写一个python程序 &#xff0c;输入参数为玩家数&#xff0c;输出参数为每个玩家的3张扑克牌 # 为了实现这个功能&#xff0c;我们可以使用Python的标准库random来生成随机…...

React 生命周期函数详解

React 组件在其生命周期中有多个阶段&#xff0c;每个阶段都有特定的生命周期函数&#xff08;Lifecycle Methods&#xff09;。这些函数允许你在组件的不同阶段执行特定的操作。以下是 React 组件生命周期的主要阶段及其对应的生命周期函数&#xff0c;并结合了 React 16.3 的…...

android 动态库加载机制

省流&#xff1a;android 不兼容 glibc&#xff0c;而是写了一套独立的 c 运行时库 (bionic libc)&#xff0c;为移动设备和 google 自己推的东西做了大量优化。在这套工具链里&#xff0c;aosp 实现了一个兼容 bionic libc 的链接器&#xff0c;放到系统中代替 ld。 这个链接…...

PyTorch torch.sign函数介绍

torch.sign 是 PyTorch 库中用于计算输入张量每个元素符号的函数。下面从功能概述、函数原型、参数解释、返回值、使用示例以及与相关函数对比等方面详细介绍 torch.sign。 功能概述 torch.sign 函数会返回一个与输入张量形状相同的新张量&#xff0c;其中每个元素的值表示输…...

Flink CDC YAML:面向数据集成的 API 设计

摘要&#xff1a;本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江&#xff08;雪尽&#xff09;老师在 Flink Forward Asia 2024 数据集成&#xff08;一&#xff09;专场中的分享。主要分为以下四个方面&#xff1a; Flink CDC YAML API Transform A…...

计算机网络知识速记:TCP 与 UDP

计算机网络知识速记&#xff1a;TCP 与 UDP 一、概念 TCP (Transmission Control Protocol): 一个面向连接的协议&#xff0c;确保数据在传输过程中完整无误。通过建立连接和数据确认机制&#xff0c;提高数据传输的可靠性。是面向字节传输的。 UDP (User Datagram Protocol)…...

差分算法解析

差分&#xff08;Difference Array&#xff09;是一种常见的算法技巧&#xff0c;广泛应用于区间更新与区间查询的问题。它通过将数组的更新操作转化为数组的差分操作&#xff0c;使得某些类型的算法能在更短的时间内完成计算&#xff0c;尤其在处理频繁的区间更新时表现得尤为…...

makefile 的strip,filter,ifeq,ifneq基础使用

目录 一、strip1.1 语法1.2 示例1.3 使用场景 二、filter2.1 语法2.2 示例2.3 使用 * 和 ? 通配符2.4 结合使用2.5 使用场景 三、ifeq 和 ifneq3.1 ifeq3.1.1 语法3.1.2 示例 3.2 ifneq3.2.1 语法3.2.2 示例 3.3 典型使用场景3.3.1 根据版本控制编译选项:3.3.2 选择不同的源文…...

SOA(面向服务架构)全面解析

1. 引言 什么是SOA&#xff08;面向服务架构&#xff09; SOA&#xff08;Service-Oriented Architecture&#xff0c;面向服务架构&#xff09;是一种将应用程序功能以“服务”的形式进行模块化设计的架构风格。这些服务是独立的功能模块&#xff0c;它们通过定义明确的接口…...

B树详解及其C语言实现

目录 一、B树的基本原理 二、B树操作过程图形化演示 三、B树的应用场景 四、C语言实现B树及示例 五、代码执行结果说明 六、应用实例&#xff1a;文件系统目录索引 七、总结 一、B树的基本原理 B树&#xff08;B-Tree&#xff09; 是一种自平衡的树数据结构&#xff0c;…...

3.1 学习UVM中的uvm_component类分为几步?

文章目录 前言一、定义1.1 角色和功能&#xff1a;1.2 与其他UVM类的区别&#xff1a;1.3 主要属性和方法&#xff1a; 二、使用方法2.1 定义和实例化&#xff1a;2.2 生命周期管理&#xff1a;2.3 组件间通信&#xff1a; 三、何时使用3.1 使用场景3.2 适用组件3.3 与uvm_obje…...

python:面向对象之魔法方法

概念&#xff1a;主要是提供一些特殊的功能。 1.__init__方法&#xff1a; 一.不带参数&#xff1a; python中类似__xx__() __init__():初始化对象class Car():def __init__(self):self.color blueself.type suvdef info(self):print(f车的颜色是&#xff1a;{self.color})p…...

postgresql 游标(cursor)的使用

概述 PostgreSQL游标可以封装查询并对其中每一行记录进行单独处理。当我们想对大量结果集进行分批处理时可以使用游标&#xff0c;因为一次性处理可能造成内存溢出。 另外我们可以定义函数返回游标类型变量&#xff0c;这是函数返回大数据集的有效方式&#xff0c;函数调用者…...

vivado 7 系列器件时钟

7 系列器件时钟 注释&#xff1a; 本章节以 Virtex -7 时钟源为例。 Virtex-6 的时钟资源与此类似。如果使用不同的架构&#xff0c;请参阅有关器件的 《时 钟资源指南》 [ 参照 40] 。 Virtex-6 和 Virtex-7 器件内含 32 个称为 BUFG 的全局时钟缓存。 BUFG 可满…...

Vue 3 部分新特性解析

1. 引言 Vue 3 引入了许多新特性和改进&#xff0c;使得开发更加高效和灵活。本文将深入探讨 Vue 3 的高阶部分&#xff0c;包括 Composition API、自定义指令、插件开发、状态管理和性能优化。 2. Composition API 2.1 引入 Composition API Composition API 是 Vue 3 中引…...

ubuntu24.04安装布置ros

最近换电脑布置机器人环境&#xff0c;下了24.04&#xff0c;但是网上的都不太合适&#xff0c;于是自己试着布置好了&#xff0c;留作有需要的人一起看看。 文章目录 目录 前言 一、确认 ROS 发行版名称 二、检查你的 Ubuntu 版本 三、安装正确的 ROS 发行版 四、对于Ubuntu24…...

数据结构与算法-链表

单向链表&#xff08;带哨兵&#xff09; public class SinglyLinkedList {private Node head new Node(Integer.MIN_VALUE, null); // 定义一个哨兵节点作为头部节点&#xff0c;避免对头节点进行特殊处理// 节点类&#xff0c;包含值和指向下一个节点的引用private static …...

【图片合并转换PDF】如何将每个文件夹下的图片转化成PDF并合并成一个文件?下面基于C++的方式教你实现

医院在为患者进行诊断和治疗过程中&#xff0c;会产生大量的医学影像图片&#xff0c;如 X 光片、CT 扫描图、MRI 图像等。这些图片通常会按照检查时间或者检查项目存放在不同的文件夹中。为了方便医生查阅和患者病历的长期保存&#xff0c;需要将每个患者文件夹下的图片合并成…...

协议-ACLLite-ffmpeg

是什么&#xff1f; FFmpeg是一个开源的多媒体处理工具包&#xff0c;它集成了多种功能&#xff0c;包括音视频的录制、转换和流式传输处理。FFmpeg由一系列的库和工具组成&#xff0c;其中最核心的是libavcodec和libavformat库。 libavcodec是一个领先的音频/视频编解码器库&…...

flask开发的网站,后端服务关闭后,可以找回之前的数据的吗

如果使用 Flask 开发的网页&#xff0c;后端服务关闭后&#xff0c;是否还能找回数据取决于数据的存储方式&#xff1a; 可能找回数据的情况&#xff1a; 数据库存储&#xff08;MySQL、PostgreSQL、SQLite 等&#xff09; 如果 Flask 连接的是持久化数据库&#xff0c;即使后…...

deepseek API开发简介

1、申请deepseek api key&#xff1a; https://platform.deepseek.com/api_keys创建API Key&#xff0c;并复制Key 2、安装python、pip&#xff0c;然后安装requests pip install requests3、.示例代码 import requests import json# DeepSeek API 地址 API_URL "ht…...

【AI】在Ubuntu中使用docker对DeepSeek的部署与使用

这篇文章前言是我基于部署好的deepseek-r1:8b模型跑出来的 关于部署DeepSeek的前言与介绍 在当今快速发展的技术环境中&#xff0c;有效地利用机器学习工具来解决问题变得越来越重要。今天&#xff0c;我将引入一个名为DeepSeek 的工具&#xff0c;它作为一种强大的搜索引擎&a…...

Baklib推进内容中台智能推荐系统的技术创新与执行方案

内容概要 在当前数字化快速发展的背景下&#xff0c;内容中台的智能化推荐系统显得尤为重要。通过技术创新&#xff0c;Baklib致力于提升平台的用户体验&#xff0c;实现精准的个性化推荐&#xff0c;满足多样化的用户需求。内容中台不仅能够高效管理和组织大量的信息与知识&a…...

MySQL8.0实现MHA高可用

一、简介 MHA&#xff08;Master HA&#xff09;是一款开源的 MySQL 的高可用程序&#xff0c;它为 MySQL 主从复制架构提供了 automating master failover 功能。MHA 在监控到 master 节点故障时&#xff0c;会提升其中拥有最新数据的 slave 节点成为新的master 节点&#xf…...

ip地址是手机号地址还是手机地址

在数字化生活的浪潮中&#xff0c;IP地址、手机号和手机地址这三个概念如影随形&#xff0c;它们各自承载着网络世界的独特功能&#xff0c;却又因名称和功能的相似性而时常被混淆。尤其是“IP地址”这一术语&#xff0c;经常被错误地与手机号地址或手机地址划上等号。本文旨在…...

多光谱成像技术在华为Mate70系列的应用

华为Mate70系列搭载了光谱技术的产物——红枫原色摄像头&#xff0c;这是一款150万像素的多光谱摄像头。 相较于普通摄像头&#xff0c;它具有以下优势&#xff1a; 色彩还原度高&#xff1a;色彩还原准确度提升约 120%&#xff0c;能捕捉更多光谱信息&#xff0c;使拍摄照片色…...

21.2.6 字体和边框

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 通过设置Rang.Font对象的几个成员就可以修改字体&#xff0c;设置Range.Borders就可以修改边框样式。 【例 21.6】【项目&#xff…...

详解命令模式

引言 当遇到发送者和接受者之间不是直接连接关系&#xff0c;而是间接连接关系&#xff0c;即发送者和接受者之间需要解耦&#xff0c;我们通常需要命令模式。比如电灯和开关&#xff0c;开关设计时并不知道自己是控制电灯的&#xff0c;也可能控制排气扇、电力设备等等&#x…...

Debian 安装 Nextcloud 使用 MariaDB 数据库 + Caddy + PHP-FPM

前言 之前通过 docker在ubuntu上安装Nextcloud&#xff0c;但是现在我使用PVE安装Debian虚拟机&#xff0c;不想通过docker安装了。下面开始折腾。 安装过程 步骤 1&#xff1a;更新系统并安装必要的软件 sudo apt update && sudo apt upgrade -y sudo apt install…...

string 与 wstring 的字符编码

测试代码: #include<stdio.h> #include<stdlib.h> #include<windows.h> #include <locale.h> #include <string> #include <iostream>// 函数用于计算UTF-8字符串中的字符数 int utf8_strlen(const char* str) {int len = 0;for (; *s…...

golang 开启HTTP代理认证

内部网路不能直接访问外网接口&#xff0c;可以通过代理发送HTTP请求。 HTTP代理服务需要进行认证。 package cmdimport ("fmt""io/ioutil""log""net/http""net/url""strings" )// 推送CBC07功能 func main() {l…...

第九届华为ICT大赛实践赛中国总决赛举行通知及考试地址

经大赛组委会决定&#xff0c;第九届华为ICT大赛实践赛中国总决赛将于2025年3月8日-9日举行具体赛事安排如下&#xff0c;期待与您顶峰相见! 理论考试:线上答题&#xff0c;团队3名成员共同完成1套试题&#xff0c;统一提交一份答案【60分钟&#xff0c;20道试题(含判断、单选…...

FFmpeg 与 FFplay 参数详解:-f、-pix_fmt、-pixel_format 和 -video_size 的区别与用法

FFmpeg 与 FFplay 参数详解:-f、-pix_fmt、-pixel_format 和 -video_size 的区别与用法 在使用 FFmpeg 和 FFplay 进行视频处理和播放时,-f、-pix_fmt、-pixel_format 和 -video_size 是常用的参数。这些参数的作用和使用场景略有不同,理解它们的区别和用法对于正确处理和播…...

深入理解 C++17 std::is_swappable

文章目录 深入理解 C17 std::is_swappable引言std::is_swappable 概述std::is_swappable 的工作原理std::is_swappable 的变体注意事项结论 深入理解 C17 std::is_swappable 引言 在 C 编程中&#xff0c;交换两个对象的值是一个常见的操作。为了确保代码的通用性和安全性&am…...

直接插入排序

一&#xff1a;直接插入排序是什么。 二&#xff1a;如何实现直接插入排序 三&#xff1a;直接插入排序时间复杂度 一&#xff1a;直接插入排序它是排序得一种&#xff0c;其目的无非是将乱序通过排序排成有序的。 我们可以通过动画直观看什么是直接插入排序 这是我找的直接…...

基于可信数据空间的企业数据要素与流通体系建设(附ppt 下载)

近期&#xff0c;可信数据空间会议召开。大数据系统软件国家工程研究中心总工程师王晨发表了题为《基于可信数据空间的企业数据要素与流通体系建设》主旨演讲。 WeChat Subscription Account【智慧城市指北】&#xff0c;可搜索相关关键字“20250107”&#xff0c;可获取具体获…...

处理 this

this指向改变this this指向 构造函数和原型对象都指向 实例 改变this指向的三个方法&#xff1a; call()apply()bind() call() apply() 与call的区别就是call中参数任意&#xff0c;但是apply中参数必须是数组 bind&#xff08;&#xff09;&#xff08;最重要&#xff09; 与…...