【C++】C++14的那些新特性

news2024/9/9 1:41:19

本文首发于 ❄️慕雪的寒舍

学习C++14的那些新特性

v2-642fda8762a7dcda4556e14c8ff5c1ef_1440w

为了方便指定使用C++14来编译代码,本文的测试都是在linux下进行的,g++版本如下

$ g++ --version
g++ (GCC) 8.5.0 20210514 (Red Hat 8.5.0-4)

如果你和我一样,也是使用VSC来链接linux进行代码编写,那一定要记得修改C++插件里面的CPP版本,否则默认以C++11来进行语法高亮的话,会把C++11不支持的语法标红,影响我们学习

image-20230828230838973

本文参考 https://zhuanlan.zhihu.com/p/588826142 进行学习;

官方文档 https://zh.cppreference.com/w/cpp/14

1.lambda新特性

C++14给lambda表达式添加了两个新功能

  • 参数推断(auto)
  • 参数初始化后捕获(可以在[]对某个新参数进行赋值)

先来复习一下C++11中学习的lambda捕获的基本方式

[val]:表示值传递方式捕捉变量val
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&val]:表示引用传递捕捉变量val
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

在C++14中,新增的是下面的这种情况

int a = 30;
// [] 中赋值了一个参数
// () 中可以使用auto关键字来推断参数类型
auto func = [x = 3](auto y) {return x + y; };
cout << func(a) << endl;

运行测试,可以看到成功输出了结果

$ make
g++ test.cpp -o test -std=c++14
$ ./test
33

修改一下类型,也能正常调用

double a = 30.2;
auto func = [x = 3](auto y)
{ return x + y; };
cout << func(a) << endl;
$ ./test
33.2

如果想将赋值参数和原本的捕获方式一起使用,则需要将赋值参数放在[]的最后面

void test_lambda2()
{
    int a = 10, b = 20;
    int c = 1, d = 3, e = 5;
    // 赋值的参数要放在[]的最后面,捕获方式放在前面
    auto func6 = [=, f = 30, g = 40]
    {
        return (a + b + c + d + e + f + g);
    };
    cout << func6() << endl;
}
$ ./test
109

初始化捕获的好处是可以支持移动捕获了;不然在C++11中,lambda就只能使用赋值捕获和引用捕获

std::unique_ptr<Item> item(new Item());
// std::move改为右值进行赋值后捕获
auto func = [m = std::move(item)] { /* do something */ };

这个新特性的提出,也让lambda成功有了和bind比拼的能力。在C++11中,bind的优势就是在于移动捕获的支持;如今lambda也有了这份能力了,我们可以更灵活地根据场景选用lambda或者bind,而不是只能使用bind了。

2.变量模板

2.1 示例

看清楚这个名字啊!是变量模板,可不是什么函数模板哈!

template<class T>
T pi = T(3.1415926535897932385L); // variable template
// 数字最后的L代表这是一个长浮点型
void test_value_template()
{
    cout << pi<double> << endl;
    cout << pi<float> << endl;
    cout << pi<int> << endl;
}

如上就是一个最最最简单的变量模板,我们在传入对应的类型后,他就会转成我们需要的类型

$ make
g++ test.cpp -o test -std=c++14
$ ./test
3.14159
3.14159
3

2.2 类中使用

当你需要在类中使用模板变量的时候,这个变量必须定义为static

因为它是模板,我们还可以接用模板本身就有的特性,将这个模板针对某一个类型进行特化

struct Limits
{
    template<typename T>
    static const T min; // 声明静态成员模板
};
 
template<typename T>
const T Limits::min = { }; // 定义静态成员模板,全部使用默认值

// 下面三个是模板变量的特化
template<>
const float Limits::min<float> = 4.5;
template<>
const double Limits::min<double> = 5.5;
template<>
const std::string Limits::min<std::string> = "hello";

int main()
{
    std::cout << Limits::min<int> << std::endl;
    std::cout << Limits::min<float> << std::endl;
    std::cout << Limits::min<double> << std::endl;
    std::cout << Limits::min<std::string> << std::endl;
    return 0;
}
$ ./test
0
4.5
5.5
hello

2.3 和类型转换的区别

这里我又直接定义了一个变量,使用static_cast直接转换变量,看看结果会不会有什么区别

// 数字最后的L代表这是一个长浮点型
template<class T>
T pi = T(3.1415926535897932385L); // 变量模板

long double lpi = 3.1415926535897932385L; // 直接定义长浮点型

void test_value_template()
{
    cout << pi<double> << endl;
    cout << pi<float> << endl;
    cout << pi<int> << endl;

    cout << " ----- \n";

    cout << static_cast<double>(lpi) << endl;
    cout << static_cast<float>(lpi) << endl;
    cout << static_cast<int>(lpi) << endl;
}

看上去二者的结果完全相同,那么既然可以直接使用变量类型转换,为什么还要新增一个模板变量呢?

$ ./test
3.14159
3.14159
3
 ----- 
3.14159
3.14159
3

以下内容来自GPT,我觉得它说的很对

定义一个变量并使用数据转换(类型转换)是一种常见的编程方式,但与变量模板有一些区别:

  1. 通用性: 变量模板允许你通过模板参数来生成多个不同类型的变量,从而在不同的上下文中使用。这使得代码更具通用性和可扩展性,因为你可以为多个类型生成相应的变量。相比之下,直接定义变量并使用数据转换通常只适用于特定的一种数据类型。
  2. 模板化: 变量模板是一种模板化的方式来生成变量,它遵循 C++ 的模板机制,这意味着你可以使用模板特化、部分特化等技术来定制化生成的变量,以满足不同的需求。而使用数据转换时,你必须显式地执行类型转换,这可能会在代码中引入不必要的重复。
  3. 编译时计算: 变量模板通常用于在编译时生成值,因此可以在编译阶段进行类型检查和计算。这有助于提高代码的性能和安全性。而数据转换可能在运行时进行,可能会引入一些运行时开销和类型错误的风险。
  4. 抽象性: 变量模板可以在更高的抽象层次上操作数据,使代码更具表达力和可读性。它允许你以更自然的方式描述某个值与特定类型之间的关系,而不必显式进行类型转换。

总之,变量模板提供了一种更灵活、通用和模板化的方式来生成变量,适用于需要在不同类型上工作的情况。当你需要为多个类型生成特定的变量或值时,变量模板是一种更优雅和强大的选择。

3.constexpr限制放宽

在C++11中被引入的constexpr,可以让编译器在编译程序的期间,就将一部分工作完成,不必等到运行期间再做;在C++11中,constexpr的限制很严格,这导致它并不好用:

  • constexpr修饰变量,要求变量必须可以在编译器推导出来
  • constexpr修饰函数(返回值),函数内除了可以包含using和typedef指令以及static_asssert断言外,只能包含一条return语句
  • constexpr同时可以修饰构造函数,但也会要求使用这个构造函数的时候,可以在编译器就把相关的内容全推导出来

以下是一个比较基础的C++11中的用例,给该函数设置了constexpr关键字后,该函数就可以在编译期间被计算出结果,再用static_assert在编译期间断言结果是否正确;

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

int test_constexpr1() {
    constexpr int result = factorial(5); // 编译时计算阶乘
    static_assert(result == 120, "Factorial of 5 should be 120"); // 编译时断言
    cout << result << endl;
    return 0;
}

如果在C++11中的constexpr函数内包含其他语句,编译的时候会报错,翻译过来是该函数内部不是一个return返回语句

$ g++ test.cpp -o test -std=c++11
test.cpp: In function ‘constexpr int FuncNew(int)’:
test.cpp:96:1: error: body of ‘constexpr’ function ‘constexpr int FuncNew(int)’ not a return-statement
 }
 ^

c++14中,对constexpr的限制放宽了,允许使用循环、if、switch等等语句,但是主旨还是一样的,需要在编译期间就可以计算出全部内容;限制放宽之后,这个关键字便可以更灵活的使用了。

// 计算前n项和,C++11
constexpr int Func(int n)
{
    return n > 0 ? Func(n - 1) + n : 0;
}
// 计算前N项和,C++14
constexpr int FuncNew(int n)
{
    if (n <= 0)
    {
        return 0;
    }

    int sum = 0;
    for (int i = 0; i < n; ++i)
    {
        sum += i;
    }
    return sum;
}

4.二进制变量

可以使用0b或者0B开头直接定义二进制变量。

int main()
{    
    int bit1 = 0b1001;
    int bit2 = 0B1011;
    std::cout << bit1 << " " << bit2 << std::endl;
}

运行结果如下

$ g++ test.cpp -o test -std=c++14
$ ./test
9 11

我在测试中发现,当我用C++11编译此代码的时候,似乎也没有引发编译错误,难道说0b是在C++11里面就支持了吗?

$ g++ test.cpp -o test -std=c++11
$ ./test
9 11

GPT给出了0B这种二进制变量是在C++14中引入的确认,并提到了为什么会出现上述情况;虽然C++11看上去编译和运行都没有问题,但我们还是得遵循版本,选用正确的版本进行编译,才能根本上避免错误

C++标准通常是向后兼容的,这意味着较新版本的编译器通常会继续支持较旧版本的标准。例如,如果你在使用支持C++11标准的编译器(如g++)时,使用了C++14或更高版本的特性,通常不会引发编译错误,因为这些编译器会尽量向后兼容,以保持现有代码的可编译性。

在你提到的情况下,即使你使用g++编译器以C++11标准编译,它仍然可以理解和接受C++14引入的二进制字面量特性。这是编译器开发者的一种设计选择,以便使代码的迁移更加平滑。但是,为了遵循最佳实践和保持代码的可读性,当你在使用特定C++标准的功能时,最好将编译器选项设置为该标准的版本,以确保代码的可移植性。

5.数字分隔符

在日常生活中使用数字的时候,为了更好的可读性,我们会以3个数组或者4个数组为分割,打一个点

1,0000,0000 一亿
100,000,000

C++14中,也支持了这样的打点,以方便我们更好的看出大数字的位数

void test_num_div()
{
    long long big_num1 = 100000000;
    long long big_num2 = 100'000'000;
    long long big_num3 = 1'0000'0000;
    cout << big_num1 << endl;
    cout << big_num2 << endl;
    cout << big_num3 << endl;
}

需要注意,这样的操作不会对数字本身有任何影响

$ ./test
100000000
100000000
100000000

在C++11中这种语法是不支持的

$ g++ test.cpp -o test -std=c++11
test.cpp:116:29: warning: multi-character character constant [-Wmultichar]
     long long big_num2 = 100'000'000;
                             ^~~~~
test.cpp:117:27: warning: multi-character character constant [-Wmultichar]
     long long big_num3 = 1'0000'0000;
                           ^~~~~~
test.cpp: In function ‘void test_num_div()’:
test.cpp:116:29: error: expected ‘,’ or ‘;’ before '\x303030'
     long long big_num2 = 100'000'000;
                             ^~~~~
test.cpp:117:27: error: expected ‘,’ or ‘;’ before '\x30303030'
     long long big_num3 = 1'0000'0000;
                           ^~~~~~

6.返回值auto推导

c++14新增了函数返回值的推导,当返回值声明为auto时,编译器会根据你的return语句推导出你的返回值类型。

template<typename T>
auto Func(T x, T y)
{
    return x + y;
}

int main()
{
    std::cout << Func(3, 4) << std::endl; // 返回值推导为int
    std::cout << Func(3.1, 4.2) << std::endl; // 返回值推导为double
    return 0;
}
$ make
g++ test.cpp -o test -std=c++14
$ ./test
7
7.3

这个推导是有限制条件的

1、如果有多个推导语句,那么多个推导的结果必须一致

// 编译报错,第一个return推导为int,第二个return推导为double,两次推导结果不一致
auto Func(int flag)
{
    if (flag < 0)
    {
        return 1;
    }
    else
    {
        return 3.14;
    }
}

2、如果没有return或者return为void类型,那么auto会被推导为void。

auto f() {}              // returns void
auto g() { return f(); } // returns void
auto* x() {}             // error: cannot deduce auto* from void

3、一旦在函数中看到return语句,从该语句推导出的返回类型就可以在函数的其余部分中使用,包括在其他return语句中。

auto Sum(int i)
{
    if (i <= 1)
    {
        return i; // 返回值被推导为int
    }
    else
    {
        return Sum(i - 1) + i; // sum的返回值已经被推导出来了,所以这里是没有问题的
    }
}

但是如果还没被推导出来,那就不能使用。

auto Sum(int i)
{
    if (i > 1)
    {
        return Sum(i - 1) + i;
    }
    else
    {
        return i;
    }
}

// 编译报错,因为Sum的返回值还没有被推导出来,所以还不能使用
error: use of ‘auto Sum(int)’ before deduction of ‘auto

4、不能推导初始化列表。

auto func () { return {1, 2, 3}; }

// 编译报错
error: returning initializer list

5、虚函数不能使用返回值推导

struct Item
{
    virtual auto Func();
};

// 编译报错
error: virtual function cannot have deduced return type

7.[[deprecated]]标记

这个标记的作用是告知其他人,某个函数被弃用了,不允许继续调用该函数;该字段的好处在于,如果一个方法已经在后续不需要使用了,你可以先给他加上这个关键字,然后再进行其他的代码检查,确认无误后,再将这个函数整体清除;

别人也不需要去检查函数的实现,因为在编译过程中编译器就会告诉你这个函数被弃用;但是编译依旧是成功的

[[deprecated]]
int test_return_auto()
{
    std::cout << Func(3, 4) << std::endl; // 返回值推导为int
    std::cout << Func(3.1, 4.2) << std::endl; // 返回值推导为double
    return 0;
}

int main()
{
    test_return_auto();

    return 0;
}

在编译的时候,编译器会警告你,这个函数已经被弃用了;但这里只是警告,编译依旧成功了,所以最终还是需要程序猿去瞅一眼各个警告到底是什么意思。

$ make
g++ test.cpp -o test -std=c++14
test.cpp: In function ‘int main()’:
test.cpp:145:22: warning: ‘int test_return_auto()’ is deprecated [-Wdeprecated-declarations]
     test_return_auto();
                      ^
test.cpp:132:5: note: declared here
 int test_return_auto()
     ^~~~~~~~~~~~~~~~
test.cpp:145:22: warning: ‘int test_return_auto()’ is deprecated [-Wdeprecated-declarations]
     test_return_auto();
                      ^
test.cpp:132:5: note: declared here
 int test_return_auto()
     ^~~~~~~~~~~~~~~~

std库的新特性

以下是STD库的新增内容!

8.std::make_unique

这个东西在cplusplus网站上找不到释义,所以就去cpp的官网上找了

https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique

该函数定义在<memory>头文件中

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );
//(1)	(C++14 起) (仅对非数组类型)
template< class T >
unique_ptr<T> make_unique( std::size_t size );
//(2)	(C++14 起) (仅对未知边界数组)
template< class T, class... Args >
/* unspecified */ make_unique( Args&&... args ) = delete;
//(3)	(C++14 起) (仅对已知边界数组)

作用是构造 T 类型对象并将其包装进 std::unique_ptr;

参数说明
args将要构造的 T 实例所用的参数列表。
size要构造的数组大小
  1. 构造非数组类型 T 对象。传递参数 argsT 的构造函数。此重载只有在 T 不是数组类型时才会参与重载决议。函数等价于:
unique_ptr<T>(new T(std::forward<Args>(args)...))
  1. 构造拥有动态大小的数组。值初始化数组元素。此重载只有在 T 是未知边界数组时才会参与重载决议。函数等价于:
unique_ptr<T>(new std::remove_extent_t<T>[size]())

使用示例

class test_class{
public:
    test_class(int a=-1):_a(a){}
    int _a;
};

int main()
{
    std::unique_ptr<test_class> pt = std::make_unique<test_class>(3);
    cout << pt->_a << endl;

    return 0;
}
$ make
g++ test.cpp -o test -std=c++14
$ ./test
3

9.std::shared_timed_mutex与std::shared_lock

c++11引入了多线程线程的一些库,但是是没有读写锁的,因此在c++14引入了读写锁的相关实现(头文件shared_mutex),其实c++14读写锁也还不够完善,直到c++17读写锁这块才算是完备起来。

std::shared_timed_mutex是带超时的读写锁对象,接口还算比较简洁易懂,和之前接触过的其他锁基本一致;内部成员中lock()是写锁,lock_shared()是读锁;

https://zh.cppreference.com/w/cpp/thread/shared_timed_mutex

image-20230829082045636

std::shared_lock是加锁的RAII实现,即构造时加锁,析构时解锁;我们使用shared_lock/unique_lock来从shared_timed_mutex中获取锁的时候,就会自动获取读锁和写锁;

std::shared_timed_mutex mutex;

void readOperation() {
    // 读锁是多人可以获取的,所以要用shared_lock (读锁)
    std::shared_lock<std::shared_timed_mutex> lock(mutex);
    std::cout << "Read operation: " << sharedResource << std::endl;
}

void writeOperation() {
    // 写锁互斥获取,用unique_lock (写锁)
    std::unique_lock<std::shared_timed_mutex> lock(mutex);
    sharedResource++;
    std::cout << "Write operation: " << sharedResource << std::endl;
}

10.std::exchange

c++14新增了一个接口std::exchange(头文件utility),其实这个也并不算是新增的,因为这个接口其实在c++11的时候就有了,只不过在c++11中作为一个内部函数,不暴露给用户使用,在c++14中才把它暴露出来给用户使用。使用方法也很简单。

int main()
{
    std::string s1 = "hello";
    std::string s2 = "world";
    std::exchange(s1, s2);
    std::cout << s1 << " " << s2 << std::endl;
    return 0;
}

// 输出结果
world world

我们可以看到,exchange会把第二个值赋值给第一个值,但是不会改变第二个值。我们来看下它的实现吧。

  /// Assign @p __new_val to @p __obj and return its previous value.
 
  template <typename _Tp, typename _Up = _Tp>
    _GLIBCXX20_CONSTEXPR
    inline _Tp
    
    exchange(_Tp& __obj, _Up&& __new_val)
    noexcept(__and_<is_nothrow_move_constructible<_Tp>,
		    is_nothrow_assignable<_Tp&, _Up>>::value)
    { return std::__exchange(__obj, std::forward<_Up>(__new_val)); }  


  // C++11 version of std::exchange for internal use.
  template <typename _Tp, typename _Up = _Tp>
    _GLIBCXX20_CONSTEXPR
    inline _Tp
    __exchange(_Tp& __obj, _Up&& __new_val)
    {
      _Tp __old_val = std::move(__obj);
      __obj = std::forward<_Up>(__new_val);
      return __old_val;
    }

通过注释我们可以明白含义,它的作用是把第二个值赋值给第一个值,同时返回第一个值的旧值。

除此之外,我们这里说明一个关键的点。exchange的第二个参数是万能引用,所以说他是既可以接收左值,也可以接收右值的,所以我们可以这样来使用。

int main()
{
    std::string s1 = "hello";
    // 第二个值是纯右值
    std::exchange(s1, "world");
    std::cout << s1 << std::endl;

    std::string s2 = "hello world";
    // 第二个值通过move语义转成右值
    std::exchange(s1, std::move(s2));
    std::cout << s1 << " | " << s2 << std::endl;
    return 0;
}

// 输出结果,注意这里s2为空字符串,因为s2的东西已经被移动拷贝给s1了
world
hello world | 

11.std::integer_sequence

类模板 std::integer_sequence 表示一个编译时的整数序列。在用作函数模板的实参时,能推导参数包 Ints 并将它用于包展开。

https://zh.cppreference.com/w/cpp/utility/integer_sequence

这个实在是太难懂了,搞不明白是干嘛的,放弃了😥

12.std::quoted

https://zh.cppreference.com/w/cpp/io/manip/quoted

该函数模板位于 <iomanip> 头文件中,用于在输入输出流中处理被引号包围的字符串。它通常用于处理 CSV(逗号分隔值)文件或其他格式,其中字段被引号括起来以处理包含特殊字符(如逗号、换行符等)的情况。

对于cout而言,quoted会将字符串包围在双引号中输出

int test_quorted() {
    std::string data = "Hello, \"world\"\n";
    
    // 输出流中使用 std::quoted,会将字符串在"内包围输出
    std::cout << std::quoted(data) << std::endl;
    return 0;
}
$ ./test
"Hello, \"world\"
"

以下是官方给的一个示例

#include <iostream>
#include <iomanip>
#include <sstream>
 
int main()
{
    std::stringstream ss;
    std::string in = "String with spaces, and embedded \"quotes\" too";
    std::string out;
 
    auto show = [&](const auto& what) {
        &what == &in
            ?   std::cout << "read in     [" << in << "]\n"
                          << "stored as   [" << ss.str() << "]\n"
            :   std::cout << "written out [" << out << "]\n\n";
    };
 
    ss << std::quoted(in);
    show(in);
    ss >> std::quoted(out);
    show(out);
 
    ss.str(""); // clear the stream buffer
 
    in = "String with spaces, and embedded $quotes$ too";
    const char delim {'$'};
    const char escape {'%'};
 
    ss << std::quoted(in, delim, escape);
    show(in);
    ss >> std::quoted(out, delim, escape);
    show(out);
}

输出

read in     [String with spaces, and embedded "quotes" too]
stored as   ["String with spaces, and embedded \"quotes\" too"]
written out [String with spaces, and embedded "quotes" too]
 
read in     [String with spaces, and embedded $quotes$ too]
stored as   [$String with spaces, and embedded %$quotes%$ too$]
written out [String with spaces, and embedded $quotes$ too]

在给定的代码中,delimescape 是用于指定自定义的分隔符和转义字符的参数。这些参数是用于 std::quoted 函数的重载形式,允许你指定不同于默认引号的字符来包围字符串,并指定一个不同于默认转义字符的字符来转义引号字符。以下是关于这两个参数的详细解释:

  1. delim: 分隔符 在第一个用法中,std::quoted 函数使用了三个参数的重载形式:std::quoted(in, delim, escape)delim 参数用于指定包围字符串的分隔符。通常情况下,std::quoted 使用双引号作为默认分隔符,但在某些情况下,你可能想要使用其他字符来包围字符串,以避免与字符串本身的字符冲突。在你的代码示例中,分隔符 delim 被设置为 $,这意味着字符串会被包围在 $ 字符内。
  2. escape: 转义字符 escape 参数允许你指定一个字符,用于转义分隔符字符本身。在默认情况下,std::quoted 使用双引号 " 作为转义字符,以确保在字符串中嵌入的引号不会被解释为结束引号。但如果你选择了自定义的分隔符,你可能还需要指定一个不同于默认转义字符的字符来进行转义。在你的代码示例中,转义字符 escape 被设置为 %,这意味着在字符串中,如果你想要表示分隔符 $ 本身,你需要使用 %$

这部分也不是很容易搞明白它是干嘛的,如果面试官问道了就说我不会吧😭

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

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

相关文章

直播麦克风哪个品牌音质最好,一文揭秘什么牌子的麦克风音质好

​随着领夹麦克风市场的变革和技术突破&#xff0c;其在网络直播、视频拍摄和内容创作等领域的应用也日益广泛。我们注意到许多消费者在选购领夹无线麦克风时面临困惑&#xff0c;因此&#xff0c;我们将提供详细的购买指南&#xff0c;并推荐几款实用的麦克风&#xff0c;以供…

米家护眼台灯怎么样?书客、米家、明基三款护眼台灯大PK

市面上出现的护眼台灯款式不得不说真的很多&#xff0c;大家若是想要在护眼台灯这个大市场里选购到一款性价比高、质量过关、口碑好且还真的实用的护眼台灯需要认真做好攻略。所以&#xff0c;我们要有技巧的对这些台灯进行筛选&#xff0c;避开那些三无的、网红品牌、无知名度…

最新版Bitwig studio 5.20 winmac,适合中国人的音乐工作站,支持Intel和apple silicon

一。Bitwig Studio 5.2 是在制作的每个阶段实现任何音乐创意的解决方案&#xff0c;自定义工作流程以匹配任何风格 Bitwig Studio旨在通过强大的编辑工具&#xff0c;帮助用户以尽可能少的步骤实现音乐创意&#xff0c;让用户专注于音乐本身&#xff0c;而不是工具。它提供了灵…

自定义表格_可拖拽排序

在做后台管理系统的时候&#xff0c;经常需要表格里面的每行排序&#xff0c;自定义可拖拽表格&#xff0c;更改样式方便。 一、实现效果 进行拖拽演示&#xff1a; 可拖拽排序表格 无滚动条样式&#xff1a; 有滚动条样式&#xff1a; 二、代码 使用reactscssts,实现页面。 …

Spring-原理篇-RequestMappingHandlerMapping 全流程是怎样的呢?

首先来看Controller 中的每个处理器是怎么样被扫描的 我们可以看到在这个AutoConfigurationSupport中&#xff0c;注册了这个RequestMappingHandlerMapping 这个Bean。 RequestMappingHandlerMapping实现了InitializingBean 这个接口&#xff0c;其逻辑在父类AbstractHandler…

CISAW信息安全保障人员认证是否值得学习?

CISAW信息安全保障人员认证的学习难度因人而异。 如果考生具备足够的学习能力以及丰富的信息安全工作经验&#xff0c;那么考试的难度可能会相对较低。 相反&#xff0c;如果考生缺少这些条件&#xff0c;学习难度可能会相对较高。 1. 从考试内容来看&#xff0c;CISAW以概念…

提升朋友圈曝光率:自动转发,让你告别复制粘贴

想要提高朋友圈的曝光率&#xff0c;单靠手动发布和复制粘贴已远远不够。 今天&#xff0c;我将告诉你如何利用工具&#xff0c;来实现自动转发&#xff0c;让你的朋友圈推广效率更高&#xff0c;轻松提升互动率。 首先&#xff0c;在个微管理系统上登录所有的微信号&#xf…

vue2使用wangeditor5及word导入解析的实现与问题

安装 wangeditor5 官网&#xff1a;https://www.wangeditor.com/v5/ yarn add wangeditor/editor # 或者 npm install wangeditor/editor --saveyarn add wangeditor/editor-for-vue # 或者 npm install wangeditor/editor-for-vue --save mammoth.js 官网&#xff1a;https…

vue2 vue3 props 的处理机制

在 Vue 2 中&#xff0c;props 是单向数据流&#xff0c;父组件向子组件传递的 props 默认情况下是不具有响应式特性的。这意味着当父组件的数据发生变化时&#xff0c;如果传递给子组件的 props 发生变化&#xff0c;子组件不会自动更新视图。 具体来说&#xff0c;在 Vue 2 …

Linux安装青龙面板并将本地服务映射至公网实现远程访问

文章目录 前言一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用。本教程使用Docker部署青龙&#xff0c;如何安装Docker详见&#xff1a; 二、安装青龙面板三、映射本地部署的青龙面板至公网四、使用固定公网地址访问本地部署的青龙面板 …

【Java】韩顺平Java学习笔记 第19章 IO流

文章目录 文件概述常用的文件操作创建文件获取文件信息目录的操作和文件删除流的分类各抽象类常用子类对象FileInputStreamFileOutputStreamFileReaderFileWriter 节点流和处理流概念BufferedReaderBufferedWriterBufferedInputStream & BufferedOutputStream 对象流&#…

代发考生战报:7月22号,广州,HCIP-Cloud Service SA云服务 H13-821考试通过

代发考生战报&#xff1a;7月22号&#xff0c;广州&#xff0c;HCIP-Cloud Service SA云服务 H13-821考试通过&#xff0c;遇到7-10个新题&#xff0c;剩下都是题库里的原题&#xff0c;记住了考试要带两个证件才行&#xff0c;我用的是身份证和驾照&#xff0c;想考的抓紧考吧…

php实现动态登录

简介&#xff1a; 效果&#xff1a;通过前端页面的注册&#xff0c;通过MD5将密码加密&#xff0c;发送到数据库&#xff0c;通过验证数据库的内容实现登录&#xff0c;以及各种保证安全的措施 实验环境&#xff1a;phphtmlcssmysql数据表&#xff0c;使用html css设计注册&a…

do语句——输入两个值求和

代码如下&#xff1a; #include<stdio.h> int main() {int sum0;int a,b;puts("请输入两个整数");printf("整数a:"); scanf("%d",&a);printf("整数b:"); scanf("%d",&b);int t(a>b)?b:a;do{sumsumt;tt1;…

PHP基于微信小程序的打车平台-计算机毕业设计源码78689

摘 要 本文介绍的是基于PHP开发的打车平台小程序。该系统旨在为用户提供一个便捷、高效的平台&#xff0c;以实现网约车的打车功能。随着社交媒体和互联网的普及&#xff0c;网约车已成为日常交通中常见的形式。然而&#xff0c;传统的打车方式存在不方便、不及时等问题。 微信…

【Kafka】对 kafka 消费程序客户端进行监控采集

前言 对于 Kafka 组件而言&#xff0c;我们通常会对 kafka 服务端添加一些监控&#xff0c;来确保服务的稳定性&#xff0c;虽然有 kafka-exporter 来对消费者进行监控&#xff0c;但是指标很少&#xff0c;对于生产者和消费者更细粒度的监控就无法做到了。只能将监控部署在客…

DDPM 核心代码解析(1)

所有代码 已上传至GitHub - duhanyue349/diffusion_model_learned_ddpm_main: 扩散模型基础框架源代码 目录结构如下 在train_cifar.py 中展示了扩散模型训练的所有代码 如果没有安装wandb 可以在create_argparser()设置 log_to_wandbFalse 一、加载模型参数 args 这里用了一…

语音转文字在线免费有什么工具?这4款工具让记录更高效

在当今职场、学术界和内容创作领域&#xff0c;人们越来越需要一种高效的方式来整理会议记录、讲座内容或采访对话。 幸运的是&#xff0c;除了传统的手动记笔记方式&#xff0c;我们还可以通过录音转文字软件来实现这一目标。这些软件能够直接将音频资料转写为文本&#xff0…

4nm点状激光模组的应用让未来科技走向潮流

在科技发展时代&#xff0c;激光技术以其高精度、高效率的特性&#xff0c;正逐步成为众多行业不可或缺的核心技术之一。其中&#xff0c;4nm点状激光模组作为激光技术领域的佼佼者&#xff0c;凭借其卓越的性能和广泛的应用前景&#xff0c;正引领着科技发展的新潮流。接下来我…

ubuntu20.04.6 安装Skywalking 10.0.1

1.前置准备 1.1. **jdk17&#xff08;Skywalking10 jdk22不兼容&#xff0c;用17版本即可&#xff09;**安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装&#xff1a; https://blog.csdn.net/CsethCRM/article/details…