C++11右值与列表初始化

1.列表初始化

C++98传统的{}

C++98中一般数组和结构体可以用{}进行初始化。

struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}

C++11中的{}

C++11以后统一初始化方式,想要实现一切对象皆可用{}初始化,{}初始化也叫做列表初始化

内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后就只有构造

{}初始化过程可以省略=

代码示例

#include<iostream>
#include<vector>
using namespace std;struct Point
{int _x;int _y;
};class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year),_month(month),_day(day){cout << "Date(int year,int month,int day)" << endl;}Date(const Date& d):_year(d._year),_month(d._month),_day(d._day){cout << "Date(const Date& d)" << endl;}private:int _year;int _month;int _day;
};int main()
{//C++11支持//内置类型支持int x1 = { 2 };//自定义类型支持//这里本质是用{2025,1,1}构造一个Date临时对象//临时对象再去拷贝构造d1,编译器优化后会合二为一变成{2025,1,1}直接构造Date d1 = { 2025,1,1 };//这里d2引用的是{2024,7,25}构造的临时对象const Date& d2 = { 2024,7,25 };//C++98支持单参数类型转换,也可以不用{}Date d3 = { 2025 };Date d4 = 2025;//可以省略掉=Point p1{ 1,2 };int x2{ 2 };Date d6{ 2024,7,25 };const Date& d7{ 2024,7,25 };//只有{}初始化才能省略=//不支持 Date d8 2025vector<Date> v;v.push_back(Date(2025, 1, 1));v.push_back(d1);//对于匿名对象和匿名对象传参,这里{}相对好v.push_back({ 2025,1,1 });return 0;}

C++11中std::initializer_list

上面初始化很方便,当对于容器的初始化还是不方便,像vector对象,想用N个值去构造初始化,就得实现很多个构造函数才能支持

vector<int> v1 = {1,2,3};vector<int> v2 = {1,2,3,4,5};
C++11库中提出了一个std::initializer_list的类, auto il = { 10, 20, 30 }; // the type of il is an initializer_list,这个类本质是底层开一个数组,将数据拷贝过来,std::initializer_list内部有俩个指针分别指向数组的开始和结束。
容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的{x1,x2,x3....}进行初始化。STL中的容器支持任意多个值构成的{x1,x2,x3...}进行初始化,就是通过std::initializer_list的构造函数支持的。
// STL 中的容器都增加了⼀个 initializer_list 的构造
vector (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type ());
list (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type ());
map (initializer_list<value_type> il, const key_compare& comp =
key_compare (), const allocator_type& alloc = allocator_type ());
// ...
template < class T >
class vector {
public :
        typedef T* iterator;
        vector (initializer_list<T> l)
        {
        for ( auto e : l)
                push_back (e)
        }
private :
        iterator _start = nullptr ;
        iterator _finish = nullptr ;
        iterator _endofstorage = nullptr ;
};
// 另外,容器的赋值也⽀持 initializer_list 的版本
vector& operator = (initializer_list<value_type> il);
map& operator = (initializer_list<value_type> il);
# include <vector>
# include <string>
# include <map>
using namespace std;
int main ()
{
        std::initializer_list< int > mylist;
        mylist = { 10 , 20 , 30 };
        cout << sizeof (mylist) << endl;
        // 这⾥ begin end 返回的值 initializer_list 对象中存的两个指针
        // 这两个指针的值跟 i 的地址跟接近,说明数组存在栈上
        int i = 0 ;
        cout << mylist. begin () << endl;
        cout << mylist. end () << endl;
        cout << &i << endl;
        // {}列表中可以有任意多个值
        // 这两个写法语义上还是有差别的,第⼀个 v1 是直接构造,
        // 第⼆个 v2 是构造临时对象 + 临时对象拷⻉ v2+ 优化为直接构造
        vector< int > v1 ({ 1 , 2 , 3 , 4 , 5 });
        vector< int > v2 = { 1 , 2 , 3 , 4 , 5 };
        const vector< int >& v3 = { 1 , 2 , 3 , 4 , 5 };
        // 这⾥是 pair 对象的 {} 初始化和 map initializer_list 构造结合到⼀起⽤了
        map<string, string> dict = { { "sort" , " 排序 " }, { "string" , " 字符串 " }};
        / / initializer_list版本的赋值⽀持
        v1 = { 10 , 20 , 30 , 40 , 50 };
        return 0 ;
}

 右值引用和移动语义

C++98就有引用,在C++11新增了右值引用语法特性,C++11之后之前的学的就叫左值引用。无论左值还是右值引用,都是给对象取别名。

左值和右值

左值是一个表示数据的表达式,一般是有持久状态,存储在内存中,可以获取1它的地址,左值可以出现在赋值符号的左边,也可以出现在右边。定义时const修饰符后的左值,不能给它赋值,可以取地址。

右值也是一个表示数据的表达式,要么是字面值常量,要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现在左边,右值不能取地址。

左右值核心区别在于能不能取地址。

代码示例

#include<iostream>
using namespace std;int main()
{//以下p,b,c,*p,s,s[0]就是常见的左值int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';cout << &s << endl;cout << (void*)&s[0] << endl;//右值不能取地址double x = 1.1, y = 2.2;10;x + y;fmin(x, y);//参数为浮点数,返回较小的浮点数string("111111");return 0;
}

补充:用(void*)强制转换是为了打印出地址,没有就会打印出值 

左值引用和右值引用

Type% r1=x;Type&& rr1=y; 第一个语句就是左值引用,左值引用就是给左值取别名,右值引用就是给右值取别名。

左值引用不能直接引用右值,但是const左值可以引用右值。

右值引用不能直接引用左值,但是右值可以引用move(左值)

template <class T> typename remove_reference<T>::type&& move (T&& arg);

move是库里面的一个函数模板,本质内部是进行强制类型转换。

需要注意变量表达式都是左值属性,也就说一个右值被右值引用绑定后,右值引用变量表达式的属性是左值。

代码示例

#include<iostream>using namespace std;int main()
{int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("11111");s[0] = 'x';double x = 1.1, y = 2.2;//左值引用给左值取别名int& r1 = b;int*& r2 = p;int& r3 = *p;string& r4 = s;char& r5 = s[0];//右值引用给右值取别名int&& rr1 = 10;double&& rr2 = x + y;//表达式返回值是临时变量double&& rr3 = fmin(x, y);//返回值临时变量string&& rr4 = string("11111");//匿名对象,活不过下一行//左值引用不能直接引用右值,但是const左值可以引用右值const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");//右值引用不能直接用于左值,但是可以引用move(左值)int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);string&& rrx5 = (string&&)s;//跟move(s)一样//int&& r1=10,此时rr1的属性是左值,所以不能再被右值引用绑定,除非moveint& r6 = rr1;//因为rr1的属性是左值所以左值引用不用加const就可以引用rr1//int&& rrx6=rr1 这里是不行的,右值不能直接引用左值int&& rrx6 = move(rr1);return 0;
}

引用延长生命周期

右值引用可以为临时变量延长生命周期,const的左值引用也可以延长临时对象生命周期,但是这些对象无法修改。

代码示例

int main()
{std::string s1 = "Test";const std::string& r2 = s1 + s1;//const的左值引用延长了生命周期,但是不能被修改std::string&& r3 = s1 + s1;//右值引用延长生命周期r3 += "Test";//能通过非constreturn 0;
}

左值和右值的参数匹配

在C++98中,实现一个const左值引用作为参数的函数,则实参传递左值和右值都可以匹配。

C++11后,分别重载左值引用,const左值引用,右值引用作为形参的f函数,可以试验前面的结论

代码示例

#include<iostream>using namespace std;void f(int& x)
{cout << "左值引用重载\n" << endl;
}void f(const int& x)
{cout << "const左值引用重载\n" << endl;
}void f(int&& x)
{cout << "右值引用重载\n" << endl;
}int main()
{int  i = 1;const int ci = 2;f(i);//f(int&)f(ci);//f(const int&)f(3);//f(int&&),没有f(int&&),就会走f(const int&)f(move(i));//f(int&&)int&& x = 1;f(x);//右值引用在用于表达式时是左值f(move(x));//f(int&&)return 0;
}

在 C++ 中,右值引用可以延长生命周期的原因在于它们专门设计用来处理临时对象(右值),而左值引用则是用来引用可以持续存在于内存中的对象(左值)。以下是为什么右值引用可以延长生命周期,而左值引用不可以的详细解释:

### 右值引用延长生命周期的原因:

1. **临时对象的生命周期**:
   - 临时对象(右值)通常在表达式求值结束后立即销毁。右值引用通过绑定到这些临时对象,可以延长它们的生命周期,使得它们可以被多次使用,直到右值引用本身被销毁或再次赋值。

2. **移动语义**:
   - 右值引用是实现移动语义的关键。移动语义允许资源(如动态内存、文件句柄等)从一个地方“移动”到另一个地方,而不是复制。这种转移是通过右值引用实现的,它允许临时对象的资源被转移给另一个对象,从而延长了资源的生命周期。

3. **优化和性能**:
   - 使用右值引用可以避免不必要的复制,提高程序性能。例如,在函数返回临时对象时,如果使用右值引用,可以直接将资源转移给调用者,而不是创建一个临时对象的副本。

### 左值引用不延长生命周期的原因:

1. **持久性**:
   - 左值引用是设计用来引用具有持久存储的对象。它们指向对象的身份,而不是值。左值引用的目的是提供对对象的直接访问,而不是延长其生命周期。

2. **别名问题**:
   - 如果左值引用可以延长临时对象的生命周期,那么它可能会引入别名问题(aliasing)。例如,如果两个左值引用指向同一个对象,对其中一个引用的修改可能会影响另一个引用,这在很多情况下是不希望发生的。

3. **语义清晰**:
   - 左值引用和右值引用有不同的语义。左值引用意味着对对象的持久引用,而右值引用意味着对临时对象的引用。这种区分有助于编译器优化和程序员理解代码。

4. **避免悬挂引用**:
   - 如果左值引用可以延长临时对象的生命周期,那么在对象被销毁后,引用仍然存在,这将导致悬挂引用(dangling reference),即引用指向一个已经不再有效的内存区域。

总结来说,右值引用可以延长生命周期是因为它们专门设计用来处理临时对象,并且是实现移动语义的关键。而左值引用不延长生命周期是因为它们设计用来引用持久存储的对象,并且延长临时对象的生命周期可能会导致别名问题和悬挂引用等问题。这种设计使得左值引用和右值引用在语义上保持清晰,有助于编译器优化和代码理解。
 

 右值引用和移动语义的使用场景

左值引用主要使用场景

左值引用主要使用场景是在函数中左值引用传参和左值引用返回值时减少拷贝,同时还可以修改实参和修改返回对象的值。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如addStrings和generate函数。而C++11以后的右值引用也不可以解决,这里本质是返回对象是一个局部对象,函数结束这个对象就析构销毁,也就是给了一个野指针。

代码示例


class Solution {
public:// 传值返回需要拷⻉string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;// 进位int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());return str;}
};
class Solution {
public:// 这⾥的传值返回拷⻉代价就太⼤了vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);}for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}return vv;}
};

移动构造和移动赋值

移动构造函数是一种构造函数,类似于拷贝构造,移动构造函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其它参数,额外参数必须有缺省值。

移动赋值是一个赋值运算符的重载,它跟拷贝赋值构成函数重载,类似拷贝函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。

对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用类型,本质是要抢夺引用的右值对象资源,而不是像拷贝构造和拷贝赋值去拷贝资源。

代码示例

在参数为string&& s的移动构造里,是通过swap函数交换俩个指向的内容,效率是比拷贝高的,同样移动赋值也是交换指向的内容。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
using namespace std;
namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)-构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷⻉赋值" <<endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}~string(){cout << "~string() -- 析构" << endl;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];if (_str){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){push_back(ch);return *this;}const char* c_str() const{return _str;}size_t size() const{return _size;}
private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;
};
}
int main()
{bit::string s1("xxxxx");// 拷⻉构造bit::string s2 = s1;// 构造+移动构造,优化后直接构造bit::string s3 = bit::string("yyyyy");// 移动构造bit::string s4 = move(s1);cout << "******************************" << endl;return 0;
}

右值引用和移动语义解决传值返回问题

场景一是匿名对象,参数为匿名对象就会走移动拷贝,直接交换指向内容,场景二是先创建了一个对象,然后在用移动赋值。

namespace bit
{string addStrings(string num1, string num2){string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());cout << "******************************" << endl;return str;}
}
// 场景1
int main()
{bit::string ret = bit::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}
// 场景2
int main()
{bit::string ret;ret = bit::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

补充

临时变量和匿名对象区别在一个是自己创建的,一个是编译器生成的

对于一个函数内部变量的生命周期,如果给这个变量改变生命周期是不行的,因为函数结束后会销毁栈区,作为这个栈区的内部变量也会销毁,被跟着带走了

延长生命周期前提是存储空间都在,引用对象的存储空间不在就无法延长生命周期

把左值move后,就给这个左值赋予被抢夺资源的标签

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

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

相关文章

单片机复位电路基本理解教程文章·含上拉电阻理解电容开路理解!!!

目录 常见复位电路种类 复位电路电阻上拉理解 电容储能断路理解 ​​​​​​​ ​​​​​​​ 编写不易&#xff0c;仅供学习&#xff0c;请勿搬运&#xff0c;感谢理解 常见元器件驱动电路文章专栏连接 LM7805系列降压芯片驱动电路降压芯片驱动电路详解-…

【大模型系列】Mobile-Agent(2024.04)

Paper: https://arxiv.org/pdf/2401.16158Github: https://github.com/X-PLUG/MobileAgentAuthor: Junyang Wang et al. 北交、阿里巴巴 Mobile-agent核心工作&#xff1a; 首先使用视觉感知工具(检测和OCR模型)识别前端界面中文本和图像元素的精确位置 检测图标&#xff1a;…

Android Studio学习笔记

01-课程前面的话 02-Android 发展历程 03-Android 开发机器配置要求 04-Android Studio与SDK下载安装 05-创建工程与创建模拟器

【数据库系列】Spring Boot 中整合 MyBatis-Plus详细步骤

在 Spring Boot 中整合 MyBatis-Plus 可以按照以下步骤进行&#xff1a; 一、整合步骤 1. 创建 Spring Boot 项目 首先&#xff0c;使用 Spring Initializr&#xff08;https://start.spring.io/&#xff09;创建一个新的 Spring Boot 项目。在创建过程中&#xff0c;选择以…

CVSS漏洞评分系统曝出严重缺陷

在网络安全领域&#xff0c;漏洞的管理和评估是企业防御的重要一环。然而&#xff0c;随着技术的快速发展和攻击手段的不断演变&#xff0c;传统的漏洞评分系统显露出了不可忽视的弊端。在近期的Black Hat欧洲大会上&#xff0c;摩根大通的网络安全专家警告称&#xff0c;通用漏…

深入剖析MySQL数据库架构:核心组件、存储引擎与优化策略(一)

sql语句分为两大类&#xff1a;查询&#xff08;select&#xff09;、增删改----修改&#xff08;update&#xff09; select语句的执行流程 执行sql语句的流程&#xff1a;连接数据库、缓存查询、解析器、优化器、执行器、存储引擎操作数据 客户端&#xff1a;图形界面工具…

电子病历四级视角下SQL语句的优化策略与实践用例研究

一、引言 1.1 研究背景与意义 在当今数智化医疗时代,电子病历(Electronic Medical Record,EMR)系统已成为医疗机构信息化建设的核心组成部分。电子病历不仅承载着患者的诊疗信息,更是医疗决策、质量控制、科研分析以及医疗管理的重要依据。根据国家卫生健康委发布的《电…

C++简明教程(14)动态库和静态库的内存共享机制

总结&#xff0c;动态库共享内存&#xff0c;静态库独占一份内存。

Elasticsearch向量检索需要的数据集以及768维向量生成

Elasticsearch8.17.0在mac上的安装 Kibana8.17.0在mac上的安装 Elasticsearch检索方案之一&#xff1a;使用fromsize实现分页 快速掌握Elasticsearch检索之二&#xff1a;滚动查询(scrool)获取全量数据(golang) Elasticsearch检索之三&#xff1a;官方推荐方案search_after…

CSS2笔记

一、CSS基础 1.CSS简介 2.CSS的编写位置 2.1 行内样式 2.2 内部样式 2.3 外部样式 3.样式表的优先级 4.CSS语法规范 5.CSS代码风格 二、CSS选择器 1.CSS基本选择器 通配选择器元素选择器类选择器id选择器 1.1 通配选择器 1.2 元素选择器 1.3 类选择器 1.4 ID选择器 1.5 基…

基于AT89C51单片机的可暂停八路抢答器设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/90196607?spm1001.2014.3001.5503 C15 部分参考设计如下&#xff1a; 摘要 随着社会进步和科技发展&#xff0c;电子设备在各类活动中的应用日益普遍&#xff0c…

【python】unittest单元测试

文章目录 基本使用不同启动方式的区别 基本使用 下面是根据文档写的一个demo&#xff0c;主要的内容基本都包含了&#xff0c;使用时导入自己的业务类测试类中的方法就行。 import unittest# 测试类不强制test开头&#xff0c;仅作为规范。但必须继承unittest.TestCase class…

从0入门自主空中机器人-4-【PX4与Gazebo入门】

前言: 从上一篇的文章 从0入门自主空中机器人-3-【环境与常用软件安装】 | MGodmonkeyの世界 中我们的机载电脑已经安装了系统和常用的软件&#xff0c;这一篇文章中我们入门一下无人机常用的开源飞控PX4&#xff0c;以及ROS中无人机的仿真 1. PX4的安装 1.1 PX4固件代码的下载…

SqlSession的线程安全问题源码分析

&#x1f3ae; 作者主页&#xff1a;点击 &#x1f381; 完整专栏和代码&#xff1a;点击 &#x1f3e1; 博客主页&#xff1a;点击 文章目录 SqlSession 是线程安全的吗&#xff1f;为什么说是线程不安全的&#xff1f;事务管理问题 数据库连接的共享问题 一级缓存线程安全问题…

Unity Mesh生成Cube

1. 配置一个Cube的每个面的数据 一共是6个面&#xff0c;每个面包含的数据包括4个顶点的相对顶点坐标&#xff08;Cube的中心为原点&#xff09;&#xff0c;法线方向&#xff0c;UV坐标&#xff0c;顶点渲染顺序&#xff0c;以及这个面用到的材质&#xff0c;因为这里是Top&am…

小程序组件 —— 22 组件案例 - 轮播区域绘制

这一节我们实现轮播图最外层的盒子&#xff0c;也就是把轮播图的最外层搭好&#xff0c;先不给轮播图添加图片&#xff0c;因为图片属于新的组件&#xff0c;组件里面有一些知识点&#xff0c;需要单独分开讲&#xff1b; 回顾一下&#xff0c;在进行传统网页开发时&#xff0…

【文献精读笔记】Explainability for Large Language Models: A Survey (大语言模型的可解释性综述)(二)

****非斜体正文为原文献内容&#xff08;也包含笔者的补充&#xff09;&#xff0c;灰色块中是对文章细节的进一步详细解释&#xff01; 3.1.2 基于注意力的解释&#xff08;Attention-Based Explanation&#xff09; 注意力机制可以揭示输入数据中各个部分之间的关系&#…

git reset --hard(重置到当前提交,所有未提交的更改都会被永久丢弃)

git reset --hard 是一个强大的命令&#xff0c;它会将你的工作目录、暂存区和当前分支的 HEAD 指针重置到指定的提交状态&#xff0c;所有未提交的更改都会被永久丢弃。因此&#xff0c;使用这个命令时需要非常小心。 基本用法 重置到当前提交&#xff08;丢弃所有未提交的更…

单元测试入门和mockup

Java 新手入门&#xff1a;Java单元测试利器&#xff0c;Mock详解_java mock-CSDN博客 这个是典型的before when assert三段式&#xff0c;学一下单测思路 这个没有动态代理&#xff0c;所以是直接class(对比下面) Jmockit使用笔记_增加代码覆盖率_覆盖try catch_使用new Mock…

智能化人才招聘系统是怎样的?

随着企业规模的扩大和业务范围的拓展&#xff0c;人才招聘成为了企业发展的关键环节。然而&#xff0c;市面上的人才招聘系统琳琅满目&#xff0c;质量参差不齐&#xff0c;许多企业发现&#xff0c;并非所有系统都能满足他们的需求&#xff0c;特别是智能化的需求。今天&#…