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

C++17完整导引-模板特性之编译器的if语句

编译期if语句 if constexpr

    • 编译期`if`语句
    • 使用编译期`if`语句
      • 编译期`if`的注意事项
        • 编译期`if`影响返回值类型
        • 即使在 *then* 部分返回也要考虑 *else* 部分
        • 编译期短路求值
      • 其他编译期`if`的示例
        • 完美返回泛型值
        • 使用编译期`if`进行类型分发
    • 带初始化的编译期`if`语句
    • 在模板之外使用编译期`if`
    • 参考

通过使用语法if constexpr(...)编译器可以计算编译期的条件表达式来在编译期决定使用一个if语句的 then 的部分还是 else 的部分。其余部分的代码将会被丢弃这意味着它们甚至不会被生成。然而这并不意味着被丢弃的部分完全被忽略,这些部分中的代码也会像没使用的模板一样进行语法检查。

例如:

#include <string>template <typename T>
std::string asString(T x)
{if constexpr(std::is_same_v<T, std::string>) {return x;                   // 如果T不能自动转换为string该语句将无效}else if constexpr(std::is_arithmetic_v<T>) {return std::to_string(x);   // 如果T不是数字类型该语句将无效}else {return std::string(x);      // 如果不能转换为string该语句将无效}
}

通过使用if constexpr我们在编译期就可以决定我们是简单返回传入的字符串、对传入的数字调用to_string()还是使用构造函数来把传入的参数转换为std::string。无效的调用将被 丢弃 ,因此下面的代码能够通过编译(如果使用运行时if语句则不能通过编译):

#include <iostream>int main()
{std::cout << asString(42) << '\n';std::cout << asString(std::string("hello")) << '\n';std::cout << asString("hello") << '\n';
}

预编译代码如下:

//代码std::cout << asString(42) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(true) {return std::to_string(x);} } }
#endif//代码std::cout << asString(std::string("hello")) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{if constexpr(true) {return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));} 
}
#endif//代码std::cout << asString("hello") << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(false) {} else /* constexpr */ {return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));} } 
}
#endifint main()
{std::operator<<(std::operator<<(std::cout, asString(42)), '\n');std::operator<<(std::operator<<(std::cout, asString(std::basic_string<char, std::char_traits<char>, 	   std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>())))), '\n');std::operator<<(std::operator<<(std::cout, asString("hello")), '\n');return 0;
}

编译期if语句

如果我们在上面的例子中使用运行时if,下面的代码将永远不能通过编译

#include <string>template <typename T>
std::string asString(T x)
{if (std::is_same_v<T, std::string>) {return x;                   // 如果不能自动转换为string会导致ERROR}else if (std::is_numeric_v<T>) {return std::to_string(x);   // 如果不是数字将导致ERROR}else {return std::string(x);      // 如果不能转换为string将导致ERROR}
}

这是因为模板在实例化时整个模板会作为一个整体进行编译。然而if语句的条件表达式的检查是运行时特性。即使在编译期就能确定条件表达式的值一定是falsethen 的部分也必须能通过编译。因此,当传递一个std::string或者字符串字面量时,会因为std::to_string()无效而导致编译失败。此外,当传递一个数字值时,将会因为第一个和第三个返回语句无效而导致编译失败。

使用编译期if语句时, then 部分和 else 部分中不可能被用到的部分将成为 丢弃的语句

  • 当传递一个std::string时,第一个if语句的 else 部分将被丢弃。
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{if constexpr(true) {return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));} }
  • 当传递一个数字时,第一个if语句的 then 部分和最后的 else 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(true) {return std::to_string(x);} } 
}
#endif
  • 当传递一个字符串字面量(类型为const char*)时,第一和第二个if语句的 then 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{if constexpr(false) {} else /* constexpr */ {if constexpr(false) {} else /* constexpr */ {return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));} } 
}
#endif

因此,在每一个实例化中,无效的分支都会在编译时被丢弃,所以代码能成功编译。注意被丢弃的语句并不是被忽略了。即使是被忽略的语句也必须符合正确的语法,并且所有和模板参数无关的调用也必须正确。事实上,模板编译的第一个阶段( 定义期间 )将会检查语法和所有与模板无关的名称是否有效。所有的static_asserts也必须有效,即使所在的分支没有被编译。

例如:

template<typename T>
void foo(T t)
{if constexpr(std::is_integral_v<T>) {if (t > 0) {foo(t-1);   // OK}}else {undeclared(t); // 如果未被声明且未被丢弃将导致错误undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)}
}

对于一个符合标准的编译器来说,上面的例子 永远 不能通过编译,报错如下:

<source>:17:9: error: there are no arguments to 'undeclared' that depend on a template parameter, so a declaration of 'undeclared' must be available [-fpermissive]17 |         undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)|         ^~~~~~~~~~
<source>:17:9: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
<source>:18:23: error: static assertion failed: no integral18 |         static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

原因有两个

  • 即使T是一个整数类型,如下调用:
undeclared(); // 如果未声明将导致错误(即使被丢弃也一样)

如果该函数未定义时即使处于被丢弃的 else 部分也会导致错误,因为这个调用并不依赖于模板参数

  • 如下断言
static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

即使处于被丢弃的 else 部分也总是会断言失败,因为它也不依赖于模板参数。一个使用编译期条件的静态断言没有这个问题:

static_assert(!std::is_integral_v<T>, "no integral");

注意有一些编译器(例如Visual C++ 20132015)并没有正确实现模板编译的两个阶段。它们把第一个阶段( 定义期间 )的大部分工作推迟到了第二个阶段( 实例化期间 ),因此有些无效的函数调用甚至一些错误的语法都可能通过编译

使用编译期if语句

理论上讲,只要条件表达式是编译期的表达式你就可以像使用运行期if一样使用编译期if。你也可以混合使用编译期和运行期的if

if constexpr (std::is_integral_v<std::remove_reference_t<T>>) {if (val > 10) {if constexpr (std::numeric_limits<char>::is_signed) {...}else {...}}else {...}
}
else {...
}

注意你不能在函数体之外使用if constexpr。因此,你不能使用它来替换预处理器的条件编译。

编译期if的注意事项

使用编译期if可能会导致一些并不明显的后果

编译期if影响返回值类型

编译期if可能会影响函数的返回值类型。例如,下面的代码总能通过编译,但返回值的类型可能会不同:

auto foo()
{if constexpr (sizeof(int) > 4) {return 42;}else {return 42u;}
}

这里,因为我们使用了auto,返回值的类型将依赖于返回语句,而执行哪条返回语句又依赖于int的字节数:

  • 如果大于4字节,返回42的返回语句将会生效,因此返回值类型是int
  • 否则,返回42u的返回语句将生效,因此返回值类型是unsigned int
    预处理代码如下:
unsigned int foo()
{if constexpr(false) {return 42;} else /* constexpr */ {return 42U;} }

这种情况下有if constexpr语句的函数可能返回完全不同的类型。例如,如果我们不写 else 部分,返回值将会是int或者void

auto foo()  // 返回值类型可能是int或者void
{if constexpr (sizeof(int) > 4) {return 42;}
}

预处理代码如下:

void foo()
{if constexpr(false) {return 42;} }
//或者
int foo()
{if constexpr(true) {return 42;} }

注意这里如果使用运行期if那么代码将永远不能通过编译,因为推导返回值类型时会考虑到所有可能的返回值类型,因此推导会有歧义
例如

#include <iostream>
#include <string>
#include <utility>using namespace std;auto foo() {if (sizeof(int) > 4) {return 42;} else {return 42u;}
}int main() { return 0; }

会有如下报错:

<source>:13:16: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'13 |         return 42u;|                ^~~

即使在 then 部分返回也要考虑 else 部分

运行期if有一个模式不能应用于编译期if:如果代码在 thenelse 部分都会返回,那么在运行期if中你可以跳过else部分。也就是说,

if (...) {return a;
}
else {return b;
}

可以写成:

if (...) {return a;
}
return b;

但这个模式不能应用于编译期if,因为在第二种写法里,返回值类型将同时依赖于两个返回语句而不是依赖其中一个,这会导致行为发生改变。例如,如果按照上面的示例修改代码,那么 也许能也许不能 通过编译:

auto foo()
{if constexpr (sizeof(int) > 4) {return 42;}return 42u;
}

如果条件表达式为true(int大于4字节),编译器将会推导出两个不同的返回值类型,这会导致错误。否则,将只会有一条有效的返回语句,因此代码能通过编译。
可能的报错如下:

<source>:11:12: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'11 |     return 42u;|            ^~~

编译期短路求值

考虑如下代码:

template<typename T>
constexpr auto foo(const T& val)
{if constexpr(std::is_integral<T>::value) {if constexpr (T{} < 10) {return val * 2;}}return val;
}

这里我们使用了两个编译期条件来决定是直接返回传入的值还是返回传入值的两倍

下面的代码都能编译:

constexpr auto x1 = foo(42);    // 返回84
constexpr auto x2 = foo("hi");  // OK,返回"hi"

预处理代码如下:

#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int foo<int>(const int & val)
{if constexpr(true) {if constexpr(true) {return val * 2;} } return val;
}
#endif#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr const char * foo<char[3]>(const char (&val)[3])
{if constexpr(false) {} return val;
}
#endifint main()
{constexpr const int x1 = foo(42);constexpr const char *const x2 = foo("hi");return 0;
}

运行时if的条件表达式会进行短路求值(当&&左侧为false时停止求值,当||左侧为true时停止求值)。这可能会导致你希望编译期if也会短路求值:

template<typename T>
constexpr auto bar(const T& val)
{if constexpr (std::is_integral<T>::value && T{} < 10) {return val * 2;}return val;
}

然而,编译期if的条件表达式总是作为整体实例化并且必须整体有效,这意味着如果传递一个不能进行<10运算的类型将不能通过编译:

constexpr auto x2 = bar("hi");  // 编译期ERROR

报错信息如下:

<source>: In instantiation of 'constexpr auto bar(const T&) [with T = char [3]]':
<source>:17:28:   required from here
<source>:10:53: error: taking address of temporary array10 |     if constexpr (std::is_integral<T>::value && T{} < 10) {|                                                 ~~~~^~~~
<source>:11:20: error: invalid operands of types 'const char [3]' and 'int' to binary 'operator*'11 |         return val * 2;|                ~~~~^~~
<source>: In function 'int main()':
<source>:17:28: error: 'constexpr auto bar(const T&) [with T = char [3]]' called in a constant expression17 |     constexpr auto x2 = bar("hi");|                         ~~~^~~~~~
<source>:8:16: note: 'constexpr auto bar(const T&) [with T = char [3]]' is not usable as a 'constexpr' function because:8 | constexpr auto bar(const T& val)|         

因此,编译期if实例化时并不短路求值如果后边的条件的有效性依赖于前边的条件,那你需要把条件进行嵌套。例如,你必须写成如下形式:

if constexpr (std::is_same_v<MyType, T>) {if constepxr (T::i == 42) {...}
}

而不是写成:

if constexpr (std::is_same_v<MyType, T> && T::i == 42) {...
}

其他编译期if的示例

完美返回泛型值

编译期if的一个应用就是先对返回值进行一些处理,再进行完美转发。因为decltype(auto)不能推导为void(因为void不完全类型),所以你必须像下面这么写:

#include <functional>  // for std::forward()
#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;double foo1(int x, int y) { return x * 2.5 + y; }
void foo2() { cout << "foo2" << endl; }template <typename Callable, typename... Args>
decltype(auto) call(Callable op, Args&&... args) {if constexpr (std::is_void_v<std::invoke_result_t<Callable, Args...>>) {// 返回值类型是void:op(std::forward<Args>(args)...);return;} else {// 返回值类型不是void:decltype(auto) ret{op(std::forward<Args>(args)...)};return ret;}
}
int main() {double tv1 = call(foo1, 5, 3);cout << tv1 << endl;call(foo2);return 0;
}

函数的返回值类型可以推导为void,但ret的声明不能推导为void,因此必须把op返回void的情况单独处理。
运行结果如下:

15.5
foo2

预编译代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;double foo1(int x, int y)
{return (static_cast<double>(x) * 2.5) + static_cast<double>(y);
}void foo2()
{std::operator<<(std::cout, "foo2").operator<<(std::endl);
}template<typename Callable, typename ... Args>
decltype(auto) call(Callable op, Args &&... args)
{if constexpr(std::is_void_v<std::invoke_result_t<Callable, Args...> >) {op(std::forward<Args>(args)... );return;} else /* constexpr */ {decltype(auto) ret = {op(std::forward<Args>(args)... )};return ret;} }#ifdef INSIGHTS_USE_TEMPLATE
template<>
double call<double (*)(int, int), int, int>(double (*op)(int, int), int && __args1, int && __args2)
{if constexpr(false) {} else /* constexpr */ {double ret = {op(std::forward<int>(__args1), std::forward<int>(__args2))};return ret;} }
#endif#ifdef INSIGHTS_USE_TEMPLATE
template<>
void call<void (*)()>(void (*op)())
{if constexpr(true) {op();return;} }
#endifint main()
{double tv1 = call(foo1, 5, 3);std::cout.operator<<(tv1).operator<<(std::endl);call(foo2);return 0;
}

使用编译期if进行类型分发

编译期if的一个典型应用是类型分发。在C++17之前,你必须为每一个想处理的类型重载一个单独的函数。现在,有了编译期if,你可以把所有的逻辑放在一个函数里。

例如,如下的重载版本的std::advance()算法:

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {using cat = std::iterator_traits<Iterator>::iterator_category;advanceImpl(pos, n, cat{}); // 根据迭代器类型进行分发
}template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::random_access_iterator_tag) {pos += n;
}template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::bidirectional_iterator_tag) {if (n >= 0) {while (n--) {++pos;}}else {while (n++) {--pos;}}
}template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::input_iterator_tag) {while (n--) {++pos;}
}

现在可以把所有实现都放在同一个函数中:

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {using cat = std::iterator_traits<Iterator>::iterator_category;if constexpr (std::is_convertible_v<cat, std::random_access_iterator_tag>) {pos += n;}else if constexpr (std::is_convertible_v<cat, std::bidirectional_access_iterator_tag>) {if (n >= 0) {while (n--) {++pos;}}else {while (n++) {--pos;}}}else {  // input_iterator_tagwhile (n--) {++pos;}}
}

这里我们就像是有了一个编译期switch,每一个if constexpr语句就像是一个case。然而,注意例子中的两种实现还是有一处不同的:

  • 重载函数的版本遵循 最佳匹配 语义。
  • 编译期if的版本遵循 最先匹配 语义。

另一个类型分发的例子是使用编译期if实现get<>()重载来实现结构化绑定接口
第三个例子是在用作std::variant<>访问器的泛型lambda中处理不同的类型。

带初始化的编译期if语句

注意编译期if语句也可以使用新的带初始化的形式。例如,如果有一个constexpr函数foo(),你可以这样写:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;double foo(int x) { return x * 2.5; }template <typename T>
void bar(const T x) {if constexpr (auto obj = foo(x); std::is_same_v<decltype(obj), T>) {std::cout << "foo(x) yields same type\n";} else {std::cout << "foo(x) yields different type\n";}
}
int main() {bar(2);bar(2.5);return 0;
}

运行结果如下:

foo(x) yields different type
foo(x) yields same type

预处理代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;double foo(int x)
{return static_cast<double>(x) * 2.5;
}template<typename T>
void bar(const T x)
{{auto obj = foo(x);if constexpr(std::is_same_v<decltype(obj), T>) {std::operator<<(std::cout, "foo(x) yields same type\n");} else /* constexpr */ {std::operator<<(std::cout, "foo(x) yields different type\n");} }}
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<int>(const int x)
{{double obj = foo(x);if constexpr(false) {} else /* constexpr */ {std::operator<<(std::cout, "foo(x) yields different type\n");} }
}
#endif/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<double>(const double x)
{{double obj = foo(static_cast<int>(x));if constexpr(true) {std::operator<<(std::cout, "foo(x) yields same type\n");} }}
#endif
int main()
{bar(2);bar(2.5);return 0;
}

如果有一个参数类型也为Tconstexpr函数foo(),你就可以根据foo(x)是否返回与x相同的类型来进行不同的处理。
如果要根据foo(x)返回的值来进行判定,那么可以这么写:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;constexpr int foo(int x) { return x * 2; }void bar() {constexpr auto c = 2;if constexpr (constexpr auto obj = foo(c); obj == 4) {std::cout << "foo("<<"c"<<") ==  " << obj << endl;} else {std::cout << "foo(x) yields different type\n";}
}
int main() {bar();return 0;
}

注意如果想在条件语句中使用obj的值,那么obj必须要声明为constexpr。运行结果如下:

foo(c) ==  4

预处理代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>using namespace std;inline constexpr int foo(int x)
{return x * 2;
}void bar()
{constexpr const int c = 2;{constexpr const int obj = foo(c);if constexpr(true) {std::operator<<(std::operator<<(std::operator<<(std::cout, "foo("), "c"), ") ==  ").operator<<(obj).operator<<(std::endl);} else /* constexpr */ {std::operator<<(std::cout, "foo(x) yields different type\n");} }
}
int main()
{bar();return 0;
}

在模板之外使用编译期if

if constexpr可以在任何函数中使用,而并非仅限于模板。只要条件表达式是编译期的,并且可以转换成bool类型。然而,在普通函数里使用时 thenelse 部分的所有语句都必须有效,即使有可能被丢弃

例如,下面的代码不能通过编译,因为undeclared()的调用必须是有效的,即使char是有符号数导致 else 部分被丢弃也一样:

#include <limits>template<typename T>
void foo(T t);int main()
{if constexpr(std::numeric_limits<char>::is_signed) {foo(42);        // OK}else {undeclared(42); // 未声明时总是ERROR(即使被丢弃)}
}

下面的代码也永远不能成功编译,因为总有一个静态断言会失败

if constexpr(std::numeric_limits<char>::is_signed) {static_assert(std::numeric_limits<char>::is_signed);
}
else {static_assert(!std::numeric_limits<char>::is_signed);
}

在泛型代码之外使用编译期if的唯一好处是被丢弃的部分不会成为最终程序的一部分,这将减小生成的可执行程序的大小。例如,在如下程序中:

#include <limits>
#include <string>
#include <array>int main()
{if (!std::numeric_limits<char>::is_signed) {static std::array<std::string, 1000> arr1;...}else {static std::array<std::string, 1000> arr2;...}
}

要么arr1要么arr2会成为最终可执行程序的一部分,但不可能两者都是。

参考

http://www.cppstd17.com/

相关文章:

C++17完整导引-模板特性之编译器的if语句

编译期if语句 if constexpr 编译期if语句使用编译期if语句编译期if的注意事项编译期if影响返回值类型即使在 *then* 部分返回也要考虑 *else* 部分编译期短路求值 其他编译期if的示例完美返回泛型值使用编译期if进行类型分发 带初始化的编译期if语句在模板之外使用编译期if参考…...

告别Excel,免费大数据分析与可视化工具,让你的论文图表“高大上”

数据分析工具很多&#xff0c;可以分为表格、数据库、BI工具、编程等四大工具。每个大类又有很多的工具&#xff0c;例如表格包括Excel、WPS、Google Sheets、Airtable等。编程工具包括Python和R。 搞科研几年了&#xff0c;笔者一直都是在使用Excel做数据分析和可视化&#xf…...

C++ 中的继承和多态

C 中的继承和多态 一、继承二、函数重载、隐藏、覆盖、重写1.函数重载&#xff08;Function Overload&#xff09;2.函数隐藏&#xff08;Function Hiding&#xff09;3.函数重写与函数覆盖&#xff08;Function Override&#xff09; 三、多态四、纯虚函数和抽象类五、多重继承…...

NestedFormer:用于脑肿瘤分割的嵌套模态感知Transformer

文章目录 NestedFormer: Nested Modality-AwareTransformer for Brain Tumor Segmentation摘要方法Global Poolformer EncoderNested Modality-Aware Feature AggregationModality-Sensitive Gating 实验结果 NestedFormer: Nested Modality-AwareTransformer for Brain Tumor …...

【SQLServer】sqlserver数据库导入oracle

将sqlserver数据库导入到oracle 实用工具&#xff1a; SQL Server Management Studio 15.0.18424.0 SQL Server 管理对象 (SMO) 16.100.47021.07eef34a564af48c5b0cf0d617a65fd77f06c3eb1 Microsoft Analysis Services 客户端工具 15.0.19750.0 Microsoft 数据访问组件 (MDAC) …...

【5.20】四、性能测试—性能测试工具

目录 4.5 性能测试工具 4.5.1 LoadRunner 4.5.2 JMeter 4.5 性能测试工具 性能测试是软件测试中一个很重要的分支&#xff0c;人们为了提高性能测试的效率&#xff0c;开发出了很多性能测试工具。一款好的测试工具可以极大地提高测试效率&#xff0c;为发现软件缺陷提供重要…...

朗诵素材-《少年正是读书时》(两角色主持朗诵)

少年正是读书时 1、少年正是读书时 男&#xff1a;我们生活在/古老的土地上 男&#xff1a;我们拥有/共同的梦想 女&#xff1a;那朗朗的书声/那浓浓的墨香 女&#xff1a;都在告诉我们 合&#xff1a;少年正是&#xff0f;读书时 2、为何要读书 男&#xff1a;养心&am…...

凭借这个笔记,拿下8家大厂offer....

如何拿到多家大厂的offer&#xff0c;没有过硬的实力&#xff0c;就需要不断的学习。 我是如何拿到&#xff0c;阿里&#xff0c;腾讯&#xff0c;百度等八家大厂的offer的&#xff0c;今天我就给大家来分享我的秘密武器&#xff0c;阿里大神整理的包括&#xff0c;测试基础&am…...

介绍一下全链路压测平台的相关内容

随着互联网技术的不断发展&#xff0c;越来越多的企业开始依赖互联网来实现业务的发展和增长。而对于这些企业而言&#xff0c;如何保证他们的业务在高并发、高负载的情况下依然能够正常运行&#xff0c;是非常重要的一个问题。为了解决这个问题&#xff0c;企业可以使用全链路…...

对于无效的数据,该如何处理

一、无效数据的来源&#xff1a; 在进行数据管理时&#xff0c;无效数据是非常常见的问题。 无效数据可能来自于数据采集、输入错误、数据处理或存储错误等方面。 这些无效数据会对结果造成严重的影响&#xff0c;因此需要及时发现和处理。 二、处理无效数据&#xff1a; …...

港联证券:机器人行业有望迎来整体性机会 六氟磷酸锂翻倍上涨

表示&#xff0c;当前AI调整的时间空间已接近13年水位&#xff0c;且调整的促发因素有望缓和&#xff0c;后续可积极一些。一方面&#xff0c;13年三次调整时间在40日以内、幅度在15%以内。当前AI调整已持续1个月、幅度在10%以上&#xff0c;时空已接近历史。另一方面&#xff…...

css 伪类选择器 结构伪类

css 伪类选择器 结构伪类 常用的&#xff1a; :first-child 所有兄弟元素中的第一个 :last-child 所有兄弟元素中的最后一个 :nth-child(n) 所有兄弟元素中的第n个 :first-of-type 所有同类型兄弟元素中的第一个 :last-of-type 所有同类型兄弟元素中的最后一个 :nth-of-type(…...

常用的表格检测识别方法-表格区域检测方法(上)

常用的表格检测识别方法 表格检测识别一般分为三个子任务&#xff1a;表格区域检测、表格结构识别和表格内容识别。本章将围绕这三个表格识别子任务&#xff0c;从传统方法、深度学习方法等方面&#xff0c;综述该领域国内国外的发展历史和最新进展&#xff0c;并提供几个先进…...

【运维知识进阶篇】集群架构-Rewrite重定向

Rewrite主要实现url地址重写&#xff0c;以及重定向&#xff0c;就是把传入web的请求重定向到其他url的过程。 分以下几种场景使用 1、地址跳转&#xff0c;用户访问一个URL&#xff0c;将其定向到另一个URL 2、协议跳转&#xff0c;用户通过http协议请求网站时&#xff0c;…...

JavaScript如何使用while循环

JavaScript 中的 while 循环是一种常用的循环结构&#xff0c;用于在满足一定条件时重复执行一段代码块。while 循环会先检查条件是否为真&#xff0c;如果为真&#xff0c;则执行循环体中的代码&#xff0c;然后再次检查条件。当条件变为假时&#xff0c;循环会结束。 while(…...

『MySQL 实战 45 讲』16 - “order by” 是怎么工作的

“order by” 是怎么工作的 首先创建一个表 CREATE TABLE t ( id int(11) NOT NULL, city varchar(16) NOT NULL, name varchar(16) NOT NULL, age int(11) NOT NULL, addr varchar(128) DEFAULT NULL, PRIMARY KEY (id), KEY city (city) ) ENGINEInnoDB;全字段排序 在 cit…...

怎么给移动硬盘查错?移动硬盘查错能恢复数据吗

移动硬盘在长期使用或使用不当的情况下&#xff0c;可能会出现硬盘文件损坏或者出现坏道等问题&#xff0c;影响数据安全和文件操作。这时候&#xff0c;移动硬盘查错工具就派上用场了。它可以帮助用户发现移动硬盘中的问题&#xff0c;并且还可以对移动硬盘进行修复。 但是&a…...

javaIO流之缓冲流

目录 简介1、字节缓冲流1.1构造方法1.2缓冲流的高效1.3为什么字节缓冲流会这么快&#xff1f;1.4byte & 0xFF 2、字符缓冲流2.1构造方法2.2字符缓冲流特有方法 3、练习 简介 Java 的缓冲流是对字节流和字符流的一种封装&#xff0c;通过在内存中开辟缓冲区来提高 I/O 操作…...

定义制造业操作(定义 MES/MOM 系统)

制造业操作包含众多工厂级活动&#xff0c;涉及设备&#xff08;定义、使用、时间表和维护&#xff09;、材料&#xff08;识别、属性、位置和状态&#xff09;、人员&#xff08;资格、可用性和时间表&#xff09;&#xff0c;以及这些资源与包含其信息碎片的众多系统之间的互…...

人工智能专栏第二讲——人工智能的基础技术

目录 一、机器学习 二、深度学习 三、自然语言处理 四、计算机视觉 五、总结 在第一讲中,我们介绍了人工智能的概念和发展趋势&#...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...