C++11:现代C++的演变与提升

目录

前言

一、统一的列表初始化

1、{}初始化

2、std::initializer_list

二、新的声明 

1、auto

2、decltype

3、nullptr

三、范围for循环

四、右值引用与移动语义

1. 左值 vs 右值

2、移动构造与移动赋值

3、 move转换

 4、完美转发:forward

五、lambda表达式

六、包装器

1、function包装器

2、bind

总结


前言

在2003年,C++标准委员会发布了一份技术勘误表(简称TC1),使得C++03取代了C++98成为C++标准的最新版本。然而,C++03(TC1)主要集中于修复C++98中的缺陷,而语言的核心部分并没有重大变化。因此,人们通常将这两个标准统称为C++98/03。

C++11,这个被称为“C++0x”的标准,经过十年的发展终于正式发布。与C++98/03相比,C++11带来了许多显著的变化和改进。它包含了约140个新特性,并修正了C++03标准中的约600个缺陷,使得C++11不仅仅是C++98/03的延续,而是一个真正意义上的新标准。

C++11的核心优势在于:

  • 语言功能的增强:新特性使得C++11在系统开发和库开发中表现更加出色。
  • 语法的简化和泛化:C++11引入了自动类型推导、范围-based for 循环、nullptr等,使得代码更加简洁和易于维护。
  • 性能和安全性提升:移动语义和右值引用减少了不必要的拷贝,提高了性能,新的线程库增强了多线程编程的安全性和效率。

在实际开发中,C++11的引入不仅丰富了语言的功能,而且极大地提升了程序员的开发效率。虽然C++11的特性非常广泛,本篇文章将重点讲解一些实际中最为实用的语法和功能,以帮助更好地掌握这一现代C++标准。

一、统一的列表初始化

1、{}初始化

在C++98版本,我们可以使用{}来对数组和结构体元素来进行统一的列表初始值设定,例如:

//在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
struct Point
{int x;int y;
};int main()
{int arr[] = { 1,2,3,4,5 };int arr2[5] = { 1 };Point p = { 1,2 };return 0;
}

但是在C++11版本中,其扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

struct Point
{int x;int y;
};int main()
{int x1 = 1;int x2{ 2 };int arr1[]{ 1,2,3,4,5 };int arr2[5]{ 1 };Point p{ 1,2 };//甚至列表初始化也能运用到new表达式中int* pa = new int[4] {0};return 0;
}

就连自定义类型创建对象时,也可以通过列表初始化的方式调用构造函数初始化。

2、std::initializer_list

std::initializer_list 是 C++11 引入的一种用于支持列表初始化的类型。它可以让你方便地用一组元素来初始化容器、对象或自定义类型。std::initializer_list 的主要用途是提供一种简便的语法,用于将一系列值传递给函数、构造函数或其他结构。

void foo(std::initializer_list<int> values)
{for (auto value : values){std::cout << value << " ";}
}int main()
{foo({ 1,2,3,4,5 });return 0;
}

 std::initializer_list通常有以下三种运用方法:

  • 构造函数: 允许类通过 initializer_list 来接收多个初始化参数:

    class MyClass {
    public:MyClass(std::initializer_list<int> list) {for (auto elem : list) {std::cout << elem << " ";}std::cout << std::endl;}
    };MyClass obj = {1, 2, 3, 4};//用initializer_list 来接收一个初始化列表,并打印它的内容。

  • 容器的初始化: C++ STL 容器(如 std::vector, std::set)也可以使用 initializer_list 来进行初始化:

    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::set<int> s = {10, 20, 30};
  • 函数参数: 可以通过 initializer_list 传递不定数量的参数:

    void sum(std::initializer_list<int> list) 
    {int result = 0;for (auto elem : list) {result += elem;}std::cout << "Sum: " << result << std::endl;
    }sum({1, 2, 3, 4});  //输出结果为10

二、新的声明 

c++11 提供了多种简化声明的方式,尤其是在使用模板时。

1、auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。
C++11中废弃auto原来的用法,将 其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
#include<map>
#include<string>
int main()
{int i = 10;auto p = &i;auto pf = strcpy;cout << typeid(p).name() << endl;cout << typeid(pf).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}

2、decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。
通常用于配合auto使用,快速推到变量类型:
int main()
{map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };auto it = dict.begin();vector<decltype(it)>arr;return 0;
}

3、nullptr

C++把NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

三、范围for循环

C++11 引入了范围 for 循环,这是一个简洁、方便的语法,用于遍历容器或数组中的元素。相比传统的 for 循环,范围 for 循环无需显式管理索引,代码更加简洁明了。

for (declaration : container) 
{// 循环体
}
  • declaration 是一个变量,用来表示当前循环迭代的元素,可以使用自动类型推导 auto
  • container 是一个支持迭代的容器,比如数组、std::vectorstd::mapstd::set 等。

当我们不需要改变值时就可以使用按值传递,需要改变值时就按引用传递:

int main() 
{int arr[] = {1, 2, 3, 4, 5};for (int x : arr) {x *= 2;  // 修改的是副本,不会影响原数组}for (int x : arr) {cout << x << " ";  // 输出依然是 1 2 3 4 5}for (int& x : arr) {x *= 2;  // 修改的是原数组}for (int x : arr) {cout << x << " ";  // 输出 2 4 6 8 10}return 0;
}

四、右值引用与移动语义

C++11 引入了右值引用(Rvalue References)和移动语义(Move Semantics),它们大大提高了程序的效率,特别是在涉及大对象或资源管理时。右值引用允许我们利用临时对象的资源,而移动语义则通过转移资源所有权来避免不必要的拷贝。

1. 左值 vs 右值

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名
左值引用就是给左值的引用,给左值取别名。右值引用就是对右值的引用,给右值取别名。在理解右值引用,左值引用之前,首先要理解 左值右值的概念。
  • 左值(Lvalue):左值是一个表达式,它指向内存中的一个固定地址,并且可以出现在赋值操作的左侧。左值通常指的是变量的名字,它们在程序的整个运行期间都存在。左值的判断标准:可以取地址、有名字的就是左值。

    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;// 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;

  • 右值(Rvalue):右值是一个临时的、不可重复使用的表达式,它不能出现在赋值操作的左侧。右值通常包括字面量、临时生成的对象以及即将被销毁的对象。右值的判断标准:不可以取地址、没有名字的就是右值。

    // 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);// 以下几个都是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);

2、​​​​​​左值引用与右值引用的比较

左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是 const 左值引用既可引用左值,也可引用右值
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以 move 以后的左值
int main()
{int&& r1 = 10;int a = 10;int&& r2 = a;//会报错,无法将左值绑定到右值引用// 右值引用可以引用move以后的左值int&& r3 = move(a);return 0;
}

2、移动构造与移动赋值

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引
用呢?是不是化蛇添足呢?
在C++98中,当我们传递一个临时对象或表达式结果作为参数时,如果不使用引用,编译器通常会创建一个临时对象,并进行拷贝。尤其是在我们想要传递的参数是右值的情况下,只能使用传值返回。这个过程可能非常耗费性能,尤其是在处理大对象时。
而引入右值引用后,我们可以通过 移动语义直接转移临时对象的资源,而不是拷贝对象,从而减少内存分配和数据拷贝。
于是就由此衍生出来了移动构造与移动赋值。
在类与对象一章节我们学到,C++的一个类具有六大默认函数,而C++11之后,就新增了两个默认函数:移动构造函数与移动赋值函数。
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
这两个函数的原理都是通过右值引用临时数据,用swap来交换数据,避免了不需要的深拷贝,从而显著减少了内存分配和释放的开销。对于需要频繁操作大型对象的程序,移动操作能极大地提升效率。
我们以string为例,来看一下传统赋值构造,拷贝构造与移动构造移动复制的区别。
class string
{
public:string(const char* s = "")//默认构造:size(strlen(s)), capacity(size){str = new char[capacity + 1];strcpy(str, s);}void swap(string& s){std::swap(str, s.str);std::swap(size, s.size);std::swap(capacity, s.capacity);}string(const string& s)//拷贝构造:str(nullptr){cout << "拷贝构造" << endl;string tmp(s.str);swap(tmp);}string& operator=(const string& s){cout << "赋值重载" << endl;string tmp(s);swap(tmp);}string(string&& s):str(nullptr),size(0),capacity(0){cout << "移动构造" << endl;swap(s);}string& operator=(string&& s){cout << "移动赋值" << endl;swap(s);return *this;}~string(){delete[]str;str = nullptr;}char& operator[](size_t pos){assert(pos < size);return str[pos];}void reserve(size_t n){if (n > capacity){char* tmp = new char[n + 1];strcpy(tmp, str);delete[] str;str = tmp;capacity = n;}}void push_back(char ch){if (size >= capacity){size_t newcapacity = capacity == 0 ? 4 : capacity * 2;reserve(newcapacity);}str[size] = ch;++size;str[size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return str;}private:char* str;size_t size;size_t capacity;//不包含最后做标识的\0
};

我们这里实现了一个简单的string类

void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{bit::string s1("hello world");// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值func1(s1);func2(s1);// string operator+=(char ch) 传值返回存在深拷贝// string& operator+=(char ch) 传左值引用没有拷贝提高了效率s1 += '!';return 0;
}

先屏蔽掉两个移动构造函数我们可以发现,左值引用明显减少了我们资源不必要的拷贝,但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。例如:test::string to_string(int value)函数中可以看到,这里只能使用传值返回, 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)

所以此时右值引用的移动构造与移动赋值就出现帮我们解决这个问题了。

移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
所以我们将屏蔽解除后,再运行上面bit::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了

3、 move转换

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能
真的需要用右值去引用左值实现移动语义。 当需要用右值引用引用一个左值时,可以通过 move
函数将左值转化为右值 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,
并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

int main()
{test::string s1("hello world");// 这里s1是左值,调用的是拷贝构造test::string s2(s1);// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的// 资源被转移给了s3,s1被置空了。test::string s3(std::move(s1));return 0;
}

 4、完美转发:forward

void Fun(int &x)
{ cout << "左值引用" << endl; 
}
void Fun(const int &x) 
{ cout << "const 左值引用" << endl;
}void Fun(int &&x)
{ cout << "右值引用" << endl; 
}
void Fun(const int &&x)
{ cout << "const 右值引用" << endl; 
}// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}
int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
std::forward 完美转发在传参的过程中保留对象原生类型属性
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t));
}
int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

五、lambda表达式

在C++11之前,倘若我们要排序一个自定义类型的数组,就需要手动写一个算法。随着C++算法的发展,人们开始觉得这样做实在是太复杂了,每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

对于一个水果自定义类型来说:

struct Goods
{string _name;  // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};

我们可以这样写lambda:

int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 
3 }, { "菠萝", 1.5, 4 } };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; });
}
上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函
数。
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
}
(lambda表达式各部分说明):
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda
函数使用
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{}; // 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; }; // 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; }; fun1(10)cout<<a<<" "<<b<<endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int{return b += a+ c; }; cout<<fun2(10)<<endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; }; cout << add_x(10) << endl; return 0;
}
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。(比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复)
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同

六、包装器

1、function包装器

也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

C++ 提供了多种可调用对象,例如:

  • 普通的函数(函数指针)
  • 函数对象(重载了 operator() 的类或结构体)
  • Lambda 表达式
  • 成员函数指针

不同类型的可调用对象在不同场景下可能有不同的语法和接口,这使得在通用库设计中难以统一处理。std::function 出现的主要动机之一是提供一个统一的接口来包装这些可调用对象,使得用户无需关心它们具体的类型。

同时在设计某些库(如算法库、回调机制)时,通常需要接受函数作为参数。例如,STL 中的算法如 std::sort 可以接受一个比较函数。std::function 可以作为这样的通用接口,使得程序设计更加抽象和模块化,库的使用者可以自由地传入不同类型的可调用对象,而库的实现者只需针对 std::function 进行设计。

#include <iostream>
#include <functional>int add(int a, int b) 
{return a + b;
}int main() {std::function<int(int, int)> func = add;//std::function<int(int)> 可以存储任何接受一个 int 并返回 int 的可调用对象std::cout << func(2, 3) << std::endl; // 输出 5return 0;
}

2、bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器)接受一个可
调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而
言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M
可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺
序调整等操作。
// 使用举例
#include <functional>
int Plus(int a, int b)
{return a + b;
}
class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, 
placeholders::_2);//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);//func2的类型为 function<void(int, int, int)> 与func1类型一样//表示绑定函数 plus 的第一,二为: 1, 2auto  func2 = std::bind(Plus, 1, 2);   cout << func1(1, 2) << endl;cout << func2() << endl;Sub s;// 绑定成员函数std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);//参数调换顺序std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, 
placeholders::_2, placeholders::_1);cout << func3(1, 2) << endl; cout << func4(1, 2) << endl;return 0;
}

总结

C++11标准引入了众多新特性,显著增强了C++语言的功能和性能。主要更新包括:

  1. 统一的列表初始化:通过大括号{}可以统一对内置类型和用户自定义类型进行初始化,同时std::initializer_list简化了多个参数的初始化过程。

  2. 新的声明方式

    • auto:实现自动类型推导,简化代码。
    • decltype:用于获取表达式的类型,常与auto配合使用。
    • nullptr:引入了空指针nullptr,替代原先的NULL
  3. 范围for循环:简化了容器和数组的遍历过程,代码更加简洁。

  4. 右值引用与移动语义:通过右值引用和移动语义优化了内存管理,特别是处理临时对象时,避免了不必要的深拷贝操作,提升了程序的效率。

  5. Lambda表达式:引入了匿名函数,使得可以在函数内部快速定义和使用简单函数。

  6. 其他改进:包括多线程支持、std::tuplestd::function等,使得C++11不仅在语法上变得简洁,同时在性能、可维护性、并发编程等方面有了显著提升。(部分重要改动本文并未介绍,例如线程要配合linux的线程知识)

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

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

相关文章

HP UX服务器监控指标深度解读(Telnet)

随着企业IT架构的日益复杂&#xff0c;对关键服务器的监控与管理成为确保业务连续性和系统稳定性的重要环节。HP UX作为一款广泛应用于企业级环境的Unix服务器操作系统&#xff0c;其监控工作显得尤为重要。本文将针对监控易软件中HP UX服务器的监控指标进行深度解读&#xff0…

WinForm程序嵌入Web网页

文章目录 前言一、三方库或控件的选择测试二、Microsoft Edge WebView2安装、使用步骤1.安装2.使用 前言 由于此项目需要winform客户端嵌入web网页并于JAVA端交互数据&#xff0c;所以研究了一下嵌入web网页这部分&#xff0c;趟了一遍雷&#xff0c;这里做下记录。 一、三方库…

软考高级:系统设计 - MDA 模型 AI 解读

生活化例子 想象一下&#xff0c;你要建造一栋房子。建房子需要三个阶段&#xff1a; CIM (概念阶段)&#xff1a;这是你想象中的房子。你大概知道房子需要几间卧室、厨房、卫生间&#xff0c;但是还没有详细的设计图。就像在脑海中有个大概的想法&#xff1a;我要建个温馨的…

C++ 关于继承和多态常见的一些问题

大家好久不见&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#xff0c;欢迎…

【含文档】基于Springboot+Vue的高校洗浴管理系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

OJ在线评测系统 前端 完善题目提交服务 细讲异步前端请求与后端接口交互

题目提交服务完善 这则笔记是我们来梳理一下前后端逻辑 主要是我们的提交逻辑 先是看前端 按钮绑定的是这个异步请求 async 关键字表示这个函数内部会使用 await 来等待异步操作完成。 异步提交表单数据 const doSubmit async () > {// message.error("刷题机架构…

如何为创新成果进行专利保护?

专利作为保护创新成果的重要法律手段&#xff0c;如同为创新者披上了一层坚实的盔甲&#xff0c;确保他们的智慧结晶免受侵犯。然而&#xff0c;随着科技的飞速发展&#xff0c;专利侵权现象也日益增多&#xff0c;如何有效保护专利&#xff0c;维护创新者的合法权益&#xff0…

使用SNAP工具处理Sentinel-1数据应注意磁盘和内存问题

近期使用SNAP处理数据比较多&#xff0c;有一些心得给大家分享一下&#xff01;在预处理Sentinel-1数据 的过程中出错基本上是有3种情况&#xff1a; 磁盘被写满 由于JAVA优化一般是通过空间换效率的方式。所以SNAP为了提高效率&#xff0c;本版本升级增加了数据本地缓存的比重…

C++冷门知识点1

1.特殊情况汇总&#xff1a; 负数&#xff0c;空指针&#xff0c;叶节点&#xff0c;INT_MAX和INT_MIN 2.双指针法(快慢指针&#xff0c;头尾指针)&#xff0c;三数指针法(链表逆序那块) 3.一定要注意极端情况 2.e后边可以跟负数&#xff0c;但是不能跟小数 3.string的push_bac…

工具介绍---效率高+实用

Visual Studio Code (VS Code) 功能特点&#xff1a; 智能代码提示&#xff1a;内置的智能代码提示功能可以自动完成函数、变量等的输入&#xff0c;提高代码编写速度。插件丰富&#xff1a;支持成千上万的扩展插件&#xff0c;例如代码片段、主题、Linting等&#xff0c;能够…

Iceberg 基本操作和快速入门

安装 Iceberg 是一种适用于大型分析表的高性能工具&#xff0c;通过spark启动并运行iceberg&#xff0c;文章是通过docker来进行安装并测试的 新建一个docker-compose.yml文件 文件内容 version: "3" services: spark-iceberg: image: tabulario/spark-iceberg co…

FastAPI 第五课 -- 基本路由

目录 一. 前言 二. 根路径路由 三. 路径参数 四. 启动应用和测试路由 一. 前言 在 FastAPI 中&#xff0c;基本路由是定义 API 端点的关键。每个路由都映射到应用程序中的一个函数&#xff0c;用于处理特定的 HTTP 请求&#xff0c;并返回相应的响应。 二. 根路径路由 创…

Java中使用ZXing和QRCode生成二维码(附Demo)

目录 前言1. 基本知识2. ZXing3. QRCode 前言 对于Java的基本知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目】实战CRUD的功能整理&#xff08;持续更新&#xff09; 1. 基本知识 生成二维码在J…

SpringBoot实现自定义Redis的连接

SpringBoot实现自定义Redis的连接 1.docker安装Redis docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf2.maven 的pom文件导包 <dependency>&…

PDF 秒变 JPG,2024 这些工具来助力

有些扫描仪默认将扫描文档保存为PDF格式&#xff0c;若事先未加留意&#xff0c;便可能累积大量PDF文件。然而&#xff0c;在需要将这些文件插入到其他文档或进行图形设计时&#xff0c;PDF格式可能会显得不够灵活或便捷。这时&#xff0c;将PDF转换为JPG图片格式就成为了一个实…

选对工具很重要!师姐花了1天写代码,我3分钟就做出一样的倾向性得分匹配结果!...

风暴统计 √浙中大学统计老师郑卫军主持 √ 基于R语言软件开发 √ 免费使用&#xff0c;无需注册 √一键生成发表级图表 www.medsta.cn/software &#xff08;电脑端浏览器打开&#xff09; 倾向性得分方法&#xff08;PSM&#xff09;可有效降低混杂偏倚&#xff0c;得到类似随…

2024外研社综合能力大赛第一场真题

网上找滴~自用 审核不过&#xff0c;备考指南发知乎了&#xff1a;https://zhuanlan.zhihu.com/p/730698685

Spring Boot在甘肃非遗文化网站开发中的应用

2 相关技术 2.1 SSM框架介绍 本课题程序开发使用到的框架技术&#xff0c;英文名称缩写是SSM&#xff0c;在JavaWeb开发中使用的流行框架有SSH、SSM、SpringMVC等&#xff0c;作为一个课题程序采用SSH框架也可以&#xff0c;SSM框架也可以&#xff0c;SpringMVC也可以。SSH框架…

[网络]数据链路层-MAC帧与ARP协议

目录 一、数据链路层 二、以太网 2.1 认识以太网 2.2 数据碰撞 2.3 交换机 2.4 MAC帧格式 2.5 局域网通信过程 2.6 认识 MAC 地址 2.7 对比理解 MAC 地址和 IP 地址 2.8 认识 MTU 2.9 认识MSS 三、ARP 协议 3.1 了解ARP协议 3.2 ARP协议的工作流程 3.3 ARP 数据…

火车票有电子发票吗?没纸质火车票怎么报销?

火车票有电子发票吗&#xff1f; 火车票、高铁票目前没有电子发票&#xff0c;但是现在已经实行电子客票&#xff0c;车票即购票证件&#xff0c;乘车时&#xff0c;只需购票证件原件&#xff08;如身份证、护照、临时身份证等&#xff09;即可乘车。 没纸质火车票怎么报销&am…