C++11介绍
目录
一、C++11的两个小点
1.1、decltype
1.2、nullptr
二、列表初始化
2.1、C++98传统的{}
2.2、C++11中的{}
2.3、C++11中的std::initializer_list
三、右值引用和移动语义
3.1、左值和右值
3.2、左值引用和右值引用
3.3、引用延长生命周期
3.4、左值和右值的参数匹配
3.5、右值引用和移动语义的使用场景
3.5.1、左值引用主要使用场景回顾
3.5.2、移动构造和移动赋值
3.5.3、右值引⽤和移动语义解决传值返回问题
3.5.4、右值引⽤和移动语义在传参中的提效
3.6类型分类
3.7、折叠引用
3.8、完美转发
四、可变参数模版
4.1、基本语法及原理
4.2、包扩展
4.3、emplace系列接口
五、新的类功能
5.1、默认的移动构造和移动赋值
5.2、defult和delete
六、lambda
6.1、lambda表达式语法
6.2、捕捉列表
6.3、lambda的应⽤
6.4、lambda的原理
七、包装器
7.1、function
7.2、bind
一、C++11的两个小点
1.1、decltype
关键字decltype将变量的类型声明为表达式指定的类型。如图:
1.2、nullptr
由于C++中NULL被定义为字面量0,这样可能会带来一些问题,因为0既能表示指针常量,又能表示整型常量,所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
二、列表初始化
2.1、C++98传统的{}
C++98中⼀般数组和结构体可以⽤{}进⾏初始化。如图:
2.2、C++11中的{}
- C++11以后想统⼀初始化⽅式,试图实现⼀切对象皆可⽤{}初始化,{}初始化也叫做列表初始化。
- 内置类型⽀持,⾃定义类型也⽀持,⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化了以后变成直接构造。
- {}初始化的过程中,可以省略掉=。
- C++11列表初始化的本意是想实现⼀个⼤统⼀的初始化⽅式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很⽅便。
示例代码:
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++98⽀持的 int a1[] = { 1, 2, 3, 4, 5 };int a2[5] = { 0 };Point p = { 1, 2 };// 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 2025;vector<Date> v;v.push_back(d1);v.push_back(Date(2025, 1, 1));// ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐ v.push_back({ 2025, 1, 1 });return 0;
}
2.3、C++11中的std::initializer_list
- 上⾯的初始化已经很⽅便,但是对象容器初始化还是不太⽅便,⽐如⼀个vector对象,我想⽤N个值去构造初始化,那么我们得实现很多个构造函数才能⽀持。而initializer_list可以让一个构造函数接受任意个数的参数,如: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内部不直接存储数组的数据,而是有两个指针分别指向数组的开始和结束。
- 这是它的⽂档:initializer_list - C++ Reference,std::initializer_list⽀持迭代器遍历。
- 容器⽀持⼀个std::initializer_list的构造函数,也就⽀持任意多个值构成的{x1,x2,x3...} 进⾏初始化。STL中的容器⽀持任意多个值构成的{x1,x2,x3...}进⾏初始化,就是通过 std::initializer_list的构造函数⽀持的。
如图:(这里只截取了vector和list)
vector:
list:
示例代码:
三、右值引用和移动语义
C++98的C++语法中就有引⽤的语法,⽽C++11中新增了的右值引⽤语法特性,C++11之后我们之前学习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。
3.1、左值和右值
- 左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。
- 右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象 等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。
- 值得⼀提的是,左值的英⽂简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left value、right value 的缩写。现代C++中,lvalue 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,⽽ rvalue 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字⾯量常量,存储于寄存器中的变量等,也就是说左值和右值的核⼼区别就是能否取地址。
注意:引用了右值的变量的属性是左值。例如:int&& r1 = 10;10是右值,但是r1是左值。
示例代码:
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 << &c << endl;cout << (void*)&s[0] << endl;// 右值:不能取地址 double x = 1.1, y = 2.2;// 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值 10; x + y;fmin(x, y);string("11111");//右值取地址会报错//cout << &10 << endl;//cout << &(x+y) << endl;//cout << &(fmin(x, y)) << endl;//cout << &string("11111") << endl;return 0;
}
3.2、左值引用和右值引用
- Type& r1 = x; Type&& rr1 = y;第⼀个语句就是左值引⽤,左值引⽤就是给左值取别名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。
- 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值。
- 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)。
- template <class T> typename remove_reference<T>::type&& move (T&& arg);
- move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换,当然他还涉及⼀些引⽤折叠的知识,这个下面会细讲。
- 需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量的变量表达式的属性是左值
- 语法层⾯看,左值引⽤和右值引⽤都是取别名,不开空间。从汇编底层的⻆度看下⾯代码中r1和rr1 汇编层实现,底层都是⽤指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要⼀起去理解,互相佐证,这样反⽽是陷⼊迷途。
move声明:
示例代码:
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';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);//底层(汇编层面)并不分左值引用和右值引用,在底层它们都是一样的//语法上通不过是因为上层封装时将他们封装为不同类型//编译器检测语法时,因为类型不同报错//所以只要强转成相同类型就可以了,move的本质也是在强转string&& rrx5 = (string&&)s;// b、r1、rr1都是变量表达式,都是左值 cout << &b << endl;cout << &r1 << endl;cout << &rr1 << endl;// 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除非move⼀下 int& r6 = r1;// int&& rrx6 = rr1;int&& rrx6 = move(rr1);return 0;
}
注意:右值底层其实是有地址的,但是在语法层取不到。
3.3、引用延长生命周期
右值引⽤可⽤于为临时对象延⻓⽣命周期,const 的左值引⽤也能延⻓临时对象⽣存期,但这些对象⽆法被修改。
上图中,r3引用的是右值,但是r3本身的属性是左值,所以可以延长临时对象的生命周期。
3.4、左值和右值的参数匹配
- C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。
- C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的 f 函数,那么实参是左值会匹配 f (左值引⽤),实参是const左值会匹配 f (const 左值引⽤),实参是右值会匹配f(右值引⽤)。
- 右值引⽤变量在⽤于表达式时属性是左值,这个设计这⾥会感觉跟怪,后面我们讲右值引⽤的使⽤场景时,就能体会这样设计的价值了。
示例代码:
void f(int& x)
{std::cout << "左值引用重载 f(" << x << ")\n";
}void f(const int& x)
{std::cout << "const 的左值引用重载 f(" << x << ")\n";
}void f(int&& x)
{std::cout << "右值引用重载 f(" << x << ")\n";
}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(std::move(i)); // 调用 f(int&&) // 右值引用变量在用于表达式时是左值 int&& x = 1;f(x); // 调用 f(int& x) f(std::move(x)); // 调用 f(int&& x) return 0;
}
效果:
3.5、右值引用和移动语义的使用场景
3.5.1、左值引用主要使用场景回顾
左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题,但是有些场景不能使⽤传左值引⽤返回,如下面addStrings和generate函数,C++98中的解决⽅案只能是被迫使⽤输出型参数解决。那么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;}
};
3.5.2、移动构造和移动赋值
- 移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。
- 移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。
- 对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要“窃取”引⽤的右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率。下⾯的bit::string 样例实现了移动构造和移动赋值,我们需要结合场景理解。
示例代码:
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;};
}
3.5.3、右值引⽤和移动语义解决传值返回问题
注意:下面所有测试都基于上面实现的string类。
右值对象构造,只有拷⻉构造,没有移动构造的场景:
图1展⽰了vs2019 debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次拷⻉构造,右 边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次拷⻉构造。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常恐怖,会直接 将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。 变为直接构造。要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。
linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次拷⻉。
图一:
右值对象构造,有拷⻉构造,也有移动构造的场景:
图2展⽰了vs2019 debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次移动构造,右 边为编译器优化的场景下连续步骤中的拷⻉合⼆为⼀变为⼀次移动构造。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常恐怖,会直接 将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。 要理解这个优化要结合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。
linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次移动。
图二:
图三:
右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景:
图4左边展⽰了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,⼀次拷⻉构造,⼀次拷⻉赋值。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造 要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以 看到str的析构是在赋值以后,说明str就是临时对象的别名。
图四:
右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景:
图5左边展⽰了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,⼀次移动构造,⼀次移动赋值。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造 要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以 看到str的析构是在赋值以后,说明str就是临时对象的别名。
图五:
总结:如果传入的是右值,对于自定义类型而言,右值又叫将亡值,使用之后就会消亡,所以没有必要进行拷贝浪费效率,直接转移资源即可,即直接交换数据(例如通过swap),如果是左值可能就需要进行深拷贝了。
3.5.4、右值引⽤和移动语义在传参中的提效
查看STL⽂档我们发现C++11以后容器的push和insert系列的接⼝否增加的右值引⽤版本。
例如:
- 当实参是⼀个左值时,容器内部继续调⽤拷⻉构造进⾏拷⻉,将对象拷⻉到容器空间中的对象。
- 当实参是⼀个右值,容器内部则调⽤移动构造,右值对象的资源到容器空间的对象上。
- 把我们之前模拟实现的 list 拷⻉过来,实现右值引⽤参数版本的push_back和insert。
实现代码:
//右值版本的insertiterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newnode = new Node(move(x));Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}//右值版本的push_backvoid push_back(T&& x){insert(end(), move(x));}
这里有一个注意点:引用了右值的变量的属性其实是左值,也就是说,对于一个变量,虽然它引用的是右值,但是他本身是左值,所以这里只是将这两个方法的形参改为接收右值的形式是不够的,例如:push_back的右值版本的方法中,如果只将形参改为能够接收右值,其他不变,那么形参x引用的是右值,但x本身其实是左值,那么在方法内部调用insert方法时就会因为x是左值而调用左值对应的insert方法,而不是右值对应的insert方法,对于这种情况,我们有两种方法解决,第一种是完美转发,下面会介绍,这里使用第二种,因为这个方法内部我们是知道我们接收的是右值的,所以向下传递时将该变量move一下,将其转换为右值,当然,这样做所有对应调到的方法都要有右值版本才行,而且每次向下传递时都要move一下。
代码完善:(仅右值相关部分)
//节点的构造方法-右值版本ListNode(T&& data):_next(nullptr), _prev(nullptr), _data(move(data)){}
解释:需要这个方法是因为在insert方法中通过右值直接构造节点。
其实这⾥还有⼀个emplace系列的接⼝,但是这个涉及可变参数模板,我们需要把可变参数模板讲解以后再讲解emplace系列的接⼝。
3.6类型分类
C++11以后,进⼀步对类型进⾏了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值 (expiring value,简称xvalue)。
纯右值是指那些字⾯值常量或求值结果相当于字⾯值或是⼀个不具名的临时对象。如:42、true、nullptr 或者类似 str.substr(1, 2)、str1 + str2 传值返回函数调⽤,或者整形a、b,a++,a+b等。纯右值和将亡值C++11中提出的,C++11中的纯右值概念划分等价于 C++98中的右值。(即内置类型的右值叫做纯右值)
将亡值是指返回右值引⽤的函数的调⽤表达式和转换为右值引⽤的转换函数的调⽤表达,如 move(x)、static_cast(x)。(即自定义类型的右值叫做将亡值)
泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值。
两个关于值类型的中⽂和英⽂的官⽅⽂档,有兴 趣可以了解细节:
https://zh.cppreference.com/w/cpp/language/value_categoryhttps://zh.cppreference.com/w/cpp/language/value_category
3.7、折叠引用
C++中不能直接定义引⽤的引⽤如 int& && r = i;,这样写会直接报错,通过模板或 typedef 中的类型操作可以构成引⽤的引⽤。 通过模板或 typedef 中的类型操作构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
下⾯的程序中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则。
- 像PerfectForward这样的函数模板中,T&& x参数看起来是右值引⽤参数,但是由于引⽤折叠的规则,他传递左值时就是左值引⽤,传递右值时就是右值引⽤,有些地⽅也把这种函数模板的参数叫做万能引⽤。
- Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引⽤折叠规则,就实现了当实参是左值时,就实例化出左值引⽤版本形参的Function,实参是右值时,就实例化出右值引⽤版本形参的Function。
从上面代码可以看到,无论是右值还是左值都可以调用该模版函数,且实际使用中,传入左值就会生成左值版本的函数,传入右值就会生成右值版本的函数,这就是引用折叠。对于函数而言,普通函数和使用了类的模版的成员函数都无法实现引用折叠,想要实现引用折叠,函数必须有自己的模版参数。例如:
上图是稍加改动后的以前模拟实现的list,这个成员函数就只是右值版本的函数,不是引用折叠,因为它的模版是 list 这个类的,不是自己的,当类实例化时,这个模版参数就定下来了,他就没法根据传入的参数在去实例化不同的左值和右值的版本。
3.8、完美转发
PerfectForward(T&& t)函数模板程序中,传左值实例化以后是左值引⽤的PerfectForward函数,传右值实例化以后是右值引⽤的PerfectForward函数。
但是结合我们在前面的讲解,变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定 后,右值引⽤变量表达式的属性是左值,也就是说PerfectForward函数中t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性, 就需要使⽤完美转发实现。
完美转发声明:
template <class T> T&& forward (typename remove_reference<T>::type& arg);
template <class T> T&& forward (typename remove_reference<T>::type&& arg);
完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 PerfectForward的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 PerfectForward的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤返回。
四、可变参数模版
4.1、基本语法及原理
C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}
我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
这⾥我们可以使⽤sizeof...(参数包)方式去计算参数包中参数的个数。
示例代码一:
void Print()
{cout << endl;
}//参数包传入时第一个参数解析到x中
//然后再递归依次进行解析
template <class T, class ...Args>
void Print(T&& x, Args&&... args)
{//不可以这样结束递归//因为这样是运行时结束递归的方式//但是解析参数包时编译时解析的,所以需要提供一个空参的函数来结束编译时递归//if (sizeof...(args) == 0)// return;cout << x << " ";Print(args...);
}// 可变模版参数
// 参数类型可变
// 参数个数可变
template <class ...Args>
void ShowList(Args... args)
{//计算参数包的参数个数cout << sizeof...(args) << endl;// 可变参数模版在编译时解析
// 下面是运行获取和解析,所以不支持这样用
// /*cout << sizeof...(args) << endl;
// for (size_t i = 0; i < sizeof...(args); i++)
// {
// cout << args[i] << " ";
// }//正确解析方式Print(args...);
}int main()
{//下面这些调用方式都可以ShowList();ShowList(1);ShowList(1, "xxxxx");ShowList(1, "xxxxx", 2.2);return 0;
}
示例代码二:
template <class T>
int PrintArg(T t)
{cout << t << " ";//借助数组解析,但是返回值会被放入数组中//所以返回int类型的0,符合数组存储数据的类型//当然也可以不返回该类型的数据,那就需要用到逗号表达式return 0;
}template <class ...Args>
void ShowList(Args... args)
{//借助数组来帮助我们解析参数包//返回值会放到数组中int arr[] = { PrintArg(args)... };//如果函数返回值和数组类型不相符可以这样// 通过逗号表达式,最终放入数组中的是0//int arr[] = { (PrintArg(args),0)...};cout << endl;
}//编译推演生成下面的函数
//void ShowList(int x, char y, std::string z)
//{
// int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
// cout << endl;
//}//这样也可以
//template <class ...Args>
//void ShowList(Args... args)
//{
// //这里必须用逗号表达式,因为cout返回的类型是ostream的
// int arr[] = { (cout<<(args)<<" ", 0)...};
//
// cout << endl;
//}int main()
{ShowList(1, 'A', std::string("sort"));return 0;
}
4.2、包扩展
对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。底层 的实现细节如图1所⽰。
C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。
4.3、emplace系列接口
template <class... Args> void emplace_back (Args&&... args);
template <class... Args> iterator emplace (const_iterator position, Args&&... args);
- C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container,empalce还⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
- emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列。
- 第⼆个程序中我们模拟实现了list的emplace和emplace_back接⼝,这⾥把参数包不段往下传递, 最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
- 传递参数包过程中,如果是Args&&... args的参数包,要⽤完美转发参数包,⽅式如下:std::forward<Args>(args)...,否则编译时包扩展后右值引⽤变量表达式就变成了左值。
示例代码一:(使用)
// emplace_back总体而言是更高效,推荐使用
int main()
{//基本使用和push_xxx/insert方法类似//这里使用的是前面我们自己实现的string,方便我们看效果list<bit::string> lt;// 左值bit::string s1("111111111111");lt.emplace_back(s1);// 右值lt.emplace_back(move(s1));list<pair<bit::string, int>> lt1;// 构造pair + 拷贝/移动构造pair到list的节点中data上pair<bit::string, int> kv("苹果", 1);lt1.emplace_back(kv);lt1.emplace_back(move(kv));cout << endl << endl;// 不同点// 直接把构造pair参数包往下传,直接用pair参数包构造pairlt1.emplace_back("苹果", 1);cout << endl;// 直接把构造string参数包往下传,直接用string参数包构造stringlt.emplace_back("111111111111");//使用的构造函数:string(int n, const char ch)lt.emplace_back(10, 'x');cout << endl << endl;return 0;
}
效果:
示例代码二:(emplace_back实现)
//节点的构造函数 ListNodetemplate<class... Args>ListNode(Args... args):_next(nullptr), _prev(nullptr), _data(forward<Args>(args)...){}//listtemplate<class... Args>iterator insert(iterator pos, Args&&... args){Node* cur = pos._node;Node* newnode = new Node(forward<Args>(args)...);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}template<class... Args>void emplace_back(Args&&... args){insert(end(), forward<Args>(args)...);}
五、新的类功能
5.1、默认的移动构造和移动赋值
- 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷⻉构造函数/拷⻉赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,后两个⽤处不⼤,默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
- 如果你没有⾃⼰实现移动构造函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀ 个。那么编译器会⾃动⽣成⼀个默认移动构造。默认⽣成的移动构造函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调⽤移动构造,没有实现就调⽤拷⻉构造。
- 如果你没有⾃⼰实现移动赋值重载函数,且没有实现析构函数、拷⻉构造、拷⻉赋值重载中的任意⼀个,那么编译器会⾃动⽣成⼀个默认移动赋值。默认⽣成的移动赋值函数,对于内置类型成员会执⾏逐成员按字节拷⻉,⾃定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调⽤移动赋值,没有实现就调⽤拷⻉赋值。(默认移动赋值跟上⾯移动构造完全类似)。
- 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷⻉构造和拷⻉赋值。所以如果强制生成默认的移动构造和移动赋值,就必须主动实现拷贝构造和拷贝赋值。
5.2、defult和delete
- C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因这个函数没有默认⽣成。⽐如:我们提供了拷⻉构造,就不会⽣成移动构造了,那么我们可以使⽤default关键字强制移动构造的⽣成。
- 如果能想要限制某些默认函数的⽣成,在C++98中,是该函数设置成private,并且只声明不定义, 这样只要其他⼈想要调⽤就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指⽰编译器不⽣成对应函数的默认版本,称=delete修饰的函数为删除函数。
六、lambda
6.1、lambda表达式语法
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }:
- [capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [ ] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使⽤,捕捉列表可以传值和传引⽤捕捉,捕捉列表为空也不能省略。
- (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略。
- ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
- {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
6.2、捕捉列表
lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就需要进⾏捕捉。捕获的三种方式如下:
- 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x, y,&z] 表⽰x和y值捕捉,z引⽤捕捉。
- 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些变量。
- 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=, &x]表⽰其他变量隐式值捕捉, x引⽤捕捉;[&, x, y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。
捕获列表详细说明:
捕获列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。
[var]:表示值传递方式捕获变量var。默认会被const修饰
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
[&var]:表示引用传递捕获变量var。
[&]:表示引用传递捕获所有父作用域中的变量(包括this)。
lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改, mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以 修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
6.3、lambda的应⽤
在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较麻烦,仿函数要定义⼀个类,相对会⽐较麻烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
示例代码一:
// lambda 可以理解为定义了一个匿名函数的对象
int main()
{//lambda表达式没有类型,需要使用auto推导auto add1 = [](int x, int y)->int {return x + y; };cout << add1(1, 2) << endl;//多行时可以这样写auto func1 = []()->int{cout << "hello bit" << endl;cout << "hello world" << endl;return 0;};func1();// 返回值类型可自动推导类型,所以可以省略auto func2 = []{cout << "hello bit" << endl;cout << "hello world" << endl;return 0;};cout << func2() << endl;//无形参是也可以省略auto func3 = []{cout << "hello bit" << endl;cout << "hello world" << endl;};func3();return 0;
}
示例代码二:
int main()
{int a = 0, b = 1;auto swap1 = [](int& x, int& y){// 只能用当前lambda局部域(即形参)和捕捉的对象(捕获列表)int tmp = x;x = y;y = tmp;};swap1(a, b);// 传值捕捉本质是一种拷贝,并且const修饰了// mutable相当于去掉const属性,可以修改了// 但是修改了不会影响外面被捕捉的值,因为是一种拷贝auto swap2 = [a, b]()mutable{int tmp = a;a = b;b = tmp;};swap2();//如果想在lambda表达式中影响外部的值,可以传引用捕获//传引用捕获的值不会被const修饰,在表达式内可以直接修改auto swap3 = [&a, &b](){int tmp = a;a = b;b = tmp;};swap3();return 0;
}
示例代码三:
int x = 0;int main()
{// 只能用当前lambda局部域和捕捉的对象// 全局对象和静态变量不需要捕获,直接就可以使用int a = 0, b = 1, c = 2, d = 3;// 所有值传值捕捉auto func1 = [=]{int ret = a + b + c + d + x;return ret;};// 所有值传引用捕捉auto func2 = [&]{a++;b++;c++;d++;int ret = a + b + c + d;return ret;};// 混合捕捉auto func3 = [&a, b]{a++;// b++;int ret = a + b;return ret;};// 混合捕捉// 所有值以引用方式捕捉,d用传值捕捉auto func4 = [&, d]{a++;b++;c++;//d++;int ret = a + b + c + d;};//所有值以传值方式捕获,d传引用捕获auto func5 = [=, &d]() mutable{a++;b++;c++;d++;int ret = a + b + c + d;};return 0;
}
lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到。
6.4、lambda的原理
lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for 这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
七、包装器
7.1、function
- std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装,存储其他的可调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对象被称为std::function 的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空 std::function 的⽬标导致抛出 std::bad_function_call 异常。
- function的官⽅⽂件链接:function - C++ Reference
下面是 function 的原型,他被定义<functional>头⽂件中:
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。
语法: function<返回值类型(形参类型)> 变量名 = 封装的可调用对象
示例代码:
#include<functional>int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{// 包装可调用对象//函数function<int(int, int)> f1 = f;//仿函数function<int(int, int)> f2 = Functor();//lambda表达式function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数//静态成员函数不用取地址符也可以取到地址,所以这里不写&也可以function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装非静态成员函数//类的非静态成员函数必须有取地址符才能取到地址//因为非静态成员函数有隐含的this指针,所以需要多传一个参数//这个参数可以是类类型的指针,也可以是类对象//指针版function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;// 类对象版function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;return 0;
}
下⾯的代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。
// 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution
{
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调⽤对象的类型 map<string, function<int(int, int)>> opFuncMap = {{"+", [](int x, int y) {return x + y; }},{"-", [](int x, int y) {return x - y; }},{"*", [](int x, int y) {return x * y; }},{"/", [](int x, int y) {return x / y; }}};for (auto& str : tokens){if (opFuncMap.count(str)) // 操作符 {int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);}else{st.push(stoi(str));}}return st.top();}
};
7.2、bind
- bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。
- bind 也在<functional>这个头⽂件中。
- 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
- arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰ newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占位符放到placeholders的⼀个命名空间中。
bind声明:
placeholders声明:
示例代码:
//这里它们从命名空间中放开
//否则指定命名空间的方式写起来太长了
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return (a - b) * 10;
}int SubX(int a, int b, int c)
{return (a - b - c) * 10;
}int main()
{auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// bind 本质返回的一个仿函数对象// 调整参数顺序(不常用)// _1代表第一个实参// _2代表第二个实参// ...以此类推auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常用)//直接将第一个参数固定了auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind一般用于,绑死一些固定参数function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;//auto func1 = [](double rate, double monty, int year)->double {return monty * rate * year;};auto func1 = [](double rate, double monty, int year)->double {double ret = monty;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - monty;};function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_1_5(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;return 0;
}
图解:
相关文章:
C++11介绍
目录 一、C11的两个小点 1.1、decltype 1.2、nullptr 二、列表初始化 2.1、C98传统的{} 2.2、C11中的{} 2.3、C11中的std::initializer_list 三、右值引用和移动语义 3.1、左值和右值 3.2、左值引用和右值引用 3.3、引用延长生命周期 3.4、左值和右值的参数匹配 3…...
AI数字人:繁荣背后的伦理困境与法律迷局(8/10)
摘要:本文深入剖析 AI 数字人从虚拟走向现实的历程,阐述其融合多技术实现从静态到动态交互的跨越,爆发式应用于各领域带来的商业价值与社会影响,同时直面由此引发的伦理法律挑战,包括身份认同、数据隐私、责任归属及权…...
SOLID 原则在单片机环境下的 C 语言实现示例,结合嵌入式开发常见场景进行详细说明
1. 单一职责原则 (SRP) 定义:一个模块(函数/文件)只负责一个功能。 示例:传感器数据采集与处理分离 // SensorAdc.h - 仅负责ADC原始数据采集 typedef struct { uint16_t (*ReadRaw)(void); // 原始数据读取接口 } SensorAdc; // SensorProcessor.h - 仅负责数据处理…...
RT Thread 发生异常时打印输出cpu寄存器信息和栈数据
打印输出发生hardfault时,当前栈十六进制数据和cpu寄存器信息 在发生 HardFault 时,打印当前栈的十六进制数据和 CPU 寄存器信息是非常重要的调试手段。以下是如何实现这一功能的具体步骤和示例代码。 1. 实现 HardFault 处理函数 我们需要在 HardFault 中捕获异常上下文,…...
SQL 函数进行左边自动补位fnPadLeft和FORMAT
目录 1.问题 2.解决 方式1 方式2 3.结果 1.问题 例如在SQL存储过程中,将1 或10 或 100 长度不足的时候,自动补足长度。 例如 1 → 001 10→ 010 100→100 2.解决 方式1 SELECT FORMAT (1, 000) AS FormattedNum; SELECT FORMAT(12, 000) AS Form…...
Unity中数据和资源加密(异或加密,AES加密,MD5加密)
在项目开发中,始终会涉及到的一个问题,就是信息安全,在调用接口,或者加载的资源,都会涉及安全问题,因此就出现了各种各样的加密方式。 常见的也是目前用的最广的加密方式,分别是:DES、3DES、AES、MD5、XOR(异或) 其中DES、3DES、AES、MD5用在数据加密中偏多,特别是…...
C++初窥门径
const关键字 一、const关键字 修饰成员变量 常成员变量:必须通过构造函数的初始化列表进行初始化,且初始化后不可修改。 示例: class Student { private: const int age; // 常成员变量 public: Student(string name, int age) : age(ag…...
Spring知识点总结
目录 1.什么是spring?你对spring的理解? 2.spring的优缺点? 3.解释一下IOC和AOP? 4.IOC和DI的区别? 5.spring中管理对象注入的方式? 6.自动注入的注解有哪些? 声明bean的注解 Bean的生命…...
Oracle_开启归档日志和重做日志
在Oracle中,类似于MySQL的binlog的机制是归档日志(Archive Log)和重做日志(Redo Log) 查询归档日志状态 SELECT log_mode FROM v$database; – 输出示例: – LOG_MODE – ARCHIVELOG (表示已开启) – NO…...
【金仓数据库征文】-数据库界新兴前列者,本篇带你速懂金仓数据库!
最近写课程设计、搞毕设是不是被数据库折腾到崩溃?动不动就报错、数据迁移还超麻烦!今天挖到个宝藏 —— 国产金仓数据库 KingbaseES,操作超简单,还自带 “翻译器” 帮你迁移数据!性能强还稳定,关键完全免费…...
人工智能与机器学习,谁是谁的子集 —— 再谈智能的边界与演进路径
人工智能(Artificial Intelligence, AI)作为当代最具影响力的前沿技术之一,常被大众简化为 “深度学习” 或 “大模型” 等标签。然而,这种简化认知往往掩盖了AI技术内部结构的复杂性与多样性。事实上,AI并非单一方法的…...
Linux进程学习【进程状态】
🌼🌼前言:在操作系统中,进程是最基本的资源管理单位,而操作系统通过精确管理这些进程的状态来确保系统能够高效运行。进程的状态不仅仅是操作系统设计的一部分,它对系统的性能、稳定性以及资源的分配起着至…...
用 ESP32 模拟 Wiegand 刷卡器:开发门禁系统必备的小工具
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
什么是 MCP?与 AI Agent 的关系是什么?
首先先回答一下什么是MCP? 如果你经常使用像Claude这样的大语言模型,你可能已经注意到它们虽然强大,但有时候也有局限性,比如无法获取实时信息或访问特定工具。 模型上下文协议(Model Context Protocol,简…...
Python ZIP文件操作全解析:从基础压缩到高级技巧
目录 一、ZIP文件操作基础三板斧 1.1 创建压缩包 1.2 解压操作 1.3 文件遍历与信息获取 二、进阶技巧:让压缩更智能 2.1 加密压缩实战 2.2 增量更新策略 2.3 性能优化技巧 三、高级场景解决方案 3.1 分卷压缩实现 3.2 跨平台路径处理 3.3 异常处理最佳实…...
Linux:进程的等待
当以一个进程结束时,它会变成僵尸进程,这个僵尸进程如果不处理,就会一直占用CPU资源,如果父进程要回收这个进程会通过进程等待的方式处理,回收子进程只会,会得到进程的退出信息 进程等待 父进程通过进程等…...
玉米产量遥感估产系统的开发实践(持续迭代与更新)
项目地址:项目首页 - maize_yield_estimation:玉米估产的flaskvue项目 - GitCode 开发中,敬请期待。。。 以下是预先写的提纲,准备慢慢补充 一、项目背景与工程目标 业务需求分析 农业遥感估产的行业痛点(数据分散、模型精度不足…...
Python解析地址中省市区街道
Python解析地址中省市区街道 1、效果 输入:海珠区沙园街道西基村 输出: 2、导入库 pip install jionlp3、示例代码 import jionlp as jiotext 海珠区沙园街道西基村 res jio.parse_location(text, town_villageTrue) print(res)...
论文学习:《聚类矩阵正则化指导的层次图池化》
原文标题:Clustering matrix regularization guided hierarchical graph pooling 原文链接:https://www.sciencedirect.com/science/article/abs/pii/S0950705125001558 图池化技术大致可以分为两类:平面图池化和层次图池化。后者通过迭代粗化…...
【金仓数据库征文】- 国产化迁移实战:从Oracle到KingbaseES的平滑过渡
文章目录 引言:国产数据库的崛起与迁移需求一、兼容性架构设计与配置优化1.1 Oracle兼容模式的核心实现1.2 潜在语法差异的深度处理1.3 环境预配置关键技术1.3.1 用户与模式映射1.3.2 字符集与日期格式 1.4 深度兼容模式配置1.4.1 语法兼容开关1.4.2 数据类型映射策…...
「零配置陷阱」:现代全栈工具链的复杂度管控实践
一、工具链膨胀的「死亡螺旋」 2024年典型全栈项目的初始化噩梦: $ npm create vitelatest ✔ Project name: … demo ✔ Select a framework: › React ✔ Select a variant: › TypeScript SWC ✔ Install shadcn/ui? … Yes ✔ Add Storybook? … Yes ✔ Co…...
浅析锁的应用与场景
锁的应用与场景:从单机到分布式 摘要:在多线程和分布式系统中,“锁”是避免资源竞争、保障数据一致性的核心机制。但你真的了解锁吗?什么时候该用锁?用哪种锁?本文通过通俗的比喻和代码示例,带…...
图论---Kruskal(稀疏图)
O( m * log n )。 1,将所有边按权重从小到大排序,调用系统的sort() 2,枚举每条边的 a , b ,权重 if(a、b 不联通) 就将这条边加入集合中 // 最小生成树 —Kruskal算法(稀疏图) #include<iostream> #include<algorithm> using …...
MySQL 从入门到精通:第二篇 - 数据类型、约束与索引
1. MySQL数据类型详解 数值类型 整数类型 -- 常用整数类型及范围 CREATE TABLE integer_types (tiny_col TINYINT, -- 1字节,有符号(-128~127),无符号(0~255)small_col SMALLINT, -- 2字节,有符号(-32768~32767),无符号(0~65535)medium_col MEDIUMINT,...
基于AI技术的高速公路交通引流系统设计与应用研究
基于AI技术的高速公路交通引流系统设计与应用研究 1. 研究背景与意义 1.1 交通系统演化脉络 1.1.1 发展阶段划分 机械化时代(1950-1990):固定式信号控制信息化时代(1991-2010):SCATS/SCOOT系统智能化时代…...
n8n 中文系列教程_09. 从原始需求到精准实现:n8n节点选择指南
在自动化工作流工具n8n中,正确选择和使用节点是高效实现需求的关键。本文将从需求分析入手,逐步解析触发节点与执行节点的区别,梳理n8n的节点分类逻辑,并揭示外部服务节点的本质,帮助您精准匹配需求与实现方案。无论您…...
P19:Inception v1算法实战与解析
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 一、模型结构 Inception V1 的主要特点是在一个网络中同时使用不同大小的卷积核(1x1、3x3、5x5)和池化操作来提取多尺度特征。以下是…...
day32 学习笔记
文章目录 前言一、霍夫变换二、标准霍夫变换三、统计概率霍夫变换四、霍夫圆变换 前言 通过今天的学习,我掌握了霍夫变换的基本原本原理及其在OpenCV中的应用方法 一、霍夫变换 霍夫变换是图像处理中的常用技术,主要用于检测图像中的直线,圆…...
2025时间序列都有哪些创新点可做——总结篇
作为AI和数据科学的核心方向之一,时间序列在2025年依然保持着强劲的发展势头,稳站各大顶会顶刊投稿主题前列。 关于它的研究,目前在结合传统统计方法和深度学习的基础上,已延伸至频域等数理工具与神经网络的交叉创新。同时针对垂…...
头歌实训之索引
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习C语言的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
通讯的基础概念:涵盖串行通信、并行通信、TCP、UDP、Socket 等关键概念和技术
一、通信基础概念 1. 串行通信与并行通信 串行通信 定义:通过一条线路逐位传输数据,每个字节包含起始位、数据位、校验位和停止位。特点: 传输稳定,但速度较慢(因逐位传输)。常用接口:RS-232、…...
Uni-App 多端电子合同开源项目介绍
项目概述 本项目是一款基于 uni-app框架开发的多端电子合同管理平台,旨在为企业及个人用户提供高效、安全、便捷的电子合同签署与管理服务。项目创新性地引入了 “证据链”与“非证据链”两种签署模式,满足不同场景下的签署需求,支持多种签署…...
一个非常快速的 Latex 入门教程【Part 1】
目录 1.LaTex简介 2.LaTex 中最基础的格式化命令 2.1加粗,斜体,下划线,添加新段落 2.2文档分节 2.3 图片 2.4 LaTeX 中列表的创建 无序列表 有序列表 2.5对数学公式的排版 2.6表格 1.LaTex简介 LaTex的主要优势是它会将文…...
用Obsidian四个插件打造小说故事关联管理系统:从模板到图谱的全流程实践
用Obsidian四个插件打造小说故事关联管理系统:从模板到图谱的全流程实践 一、前言:为什么需要故事关联管理系统 在小说创作中,复杂的人物关系、交错的情节线和多维的世界观常导致创作混乱。本文将通过 Dataview(数据查询…...
C++ 日志系统实战第三步:熟悉掌握各种设计模式
全是通俗易懂的讲解,如果你本节之前的知识都掌握清楚,那就速速来看我的项目笔记吧~ 相关技术知识补充,也是最后的补充知识了~ 下文将加入项目代码编写! 目录 设计模式 单例模式 饿汉模式 懒汉模式 工厂模式 简单…...
[ESP-IDF]:esp32-camera 使用指南 ESP32S3-OV2640 用例测试
【核知坊】:释放青春想象,码动全新视野。 我们希望使用精简的信息传达知识的骨架,启发创造者开启创造之路!!! 内容摘要:esp32-camera 组件为 ESP32 系列 SoC 提供了兼容的图…...
在统信UOS/麒麟Kylin OS中创建网页桌面快捷方式
在统信UOS/麒麟Kylin OS中创建网页桌面快捷方式 本文将详细介绍如何在统信UOS或麒麟KYLINOS中使用命令行创建一个网页桌面快捷方式,以方便构建云桌面模板及镜像模板。欢迎大家浏览、分享和转发!请关注我以获取更多技术分享。 1. 查看系统信息 首先&am…...
SQLite 是什么?
📌 一、SQLite 是什么? SQLite 是一个轻量级、嵌入式数据库,意思是它直接集成在你的 App 内部,不需要单独安装数据库服务端。 ✅ 特点: 特点说明本地使用所有数据保存在手机内部存储文件形式数据以 .db 文件形式存储…...
恒创科技「香港大带宽云」新老用户专享实例及热门配置
全球化数字浪潮下,高带宽应用正深度重构各行业运营模式——从跨境电商、流媒体与视频点播,到在线游戏与云游戏加速,涵盖所有高并发、强交互的业务场景。在此背景下,企业对高性能 IT 基础架构的需求持续升级,以此来支持…...
fpga系列 HDL:verilog latch在fpga中的作用 避免latch的常见做法
目录 Latch在FPGA中的作用Quartus中有关latch的警告⚠避免Latch的常见做法1. if-else 语句未覆盖所有条件生成Latch的代码:修复后的代码: 2. case语句未覆盖所有分支生成Latch的代码:修复后的代码: 3. 组合逻辑中缺少默认赋值生成…...
java配置
环境变量...
解决虚拟主机ping不通本地主机问题
win11 1 问题 虚拟主机和本地主机在同一网段。 2 解决方案 以win11为例: 设置 -> 网络和 Internet -> 高级网路设置 -> Windows 防火墙 -> 高级设置 -> 入站规则 -> 新建规则 需要设置:规则类型、 协议和端口、名称,其…...
Move Registry 发布,实现 Sui 的超级互操作性
Move Registry(MVR)的到来对 Sui 来说是一件大事。MVR 是一个功能齐全的链上包管理系统,提升了整个生态的可发现性、可信度和互操作性。Sui 本身就是最具互操作性的链之一,凭借 Move 语言和可编程交易区块(PTBs&#x…...
【Linux】gdb工具,Linux 下程序调试的 “透视眼”
目录 调试代码调试注意事项gdb和Cgdb调试命令汇总行号显示断点设置查看断点信息删除断点开启 / 禁用断点运行 / 调试逐过程和逐语句打印 / 追踪变量指定行号跳转强制执行函数 补充命令watchset var 替换变量值条件断点 end 调试代码 这是本次调试要用的代码 1 #include <st…...
脚本分享:快速作图对比wannier拟合能带python脚本
本脚本通过Python实现电子能带结构数据的快速作图,能够从两个不同的数据文件(BAND.dat 和 wannier90_band.dat)中提取有效数据,并在同一坐标系下绘制对比图。 准备工作:使用VASPKIT处理获得能带数据BAND.datÿ…...
解决ssh拉取服务器数据,要多次输入密码的问题
问题在于,每次循环调用 rsync 都是新开一个连接,所以每次都需要输入一次密码。为了只输入一次密码,有以下几种方式可以解决: ✅ 推荐方案:设置 SSH 免密登录 最稳最安全的方式是:配置 SSH 免密登录&#x…...
金仓数据库 KingbaseES 产品深度优化提案:迈向卓越的全面升级
文章目录 一、引言二、性能优化(一)查询性能提升1. 优化查询优化器引入基于代价的查询优化算法支持更多的查询优化提示 2. 索引优化支持更多类型的索引优化索引的创建和维护策略 (二)并发处理能力增强1. 锁机制优化采用更细粒度的…...
企业级智能合同管理解决方案升级报告:道本科技携手DeepSeek打造智能合同管理新标杆
当传统合同管理系统还在与堆积如山的纸质文档较劲时,道本科技与DeepSeek联合开发的智能合同平台已为国央企打开新视界。我们以某大型能源集团的实际应用为例,带您直观感受技术升级带来的管理变革。 一、技术升级的具象化呈现 在未接入DeepSeek技术前&a…...
C#并行编程极大提升集合处理速度,再也没人敢说你程序性能差了!
马工撰写的年入30万C#上位机项目实战必备教程(点击下方链接即可访问文章目录) 1、《C#串口通信从入门到精通》 2、《C#与PLC通信从入门到精通 》 3、《C# Modbus通信从入门到精通》 4、《C#Socket通信从入门到精通 》 5、《C# MES通信从入门到精通》 6、…...
[贪心_7] 最优除法 | 跳跃游戏 II | 加油站
目录 1.最优除法 题解 2.跳跃游戏 II 题解 3.加油站 题解 利用 单调性,可以实现 区间跳跃 1.最优除法 链接: 553. 最优除法 给定一正整数数组 nums,nums 中的相邻整数将进行浮点除法。 例如,nums [2,3,4],我…...