23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发)

本文主要是介绍23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

4.右值引用

4.1 左值引用和右值引用

什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址并且可以对它赋值,左值可以出现赋值符号的左边(也可以出现在赋值符号的右边),右值不能出现在赋值符号左边(只可以出现在符号的右边)。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用(给左值取别名)。

int main()
{// 以下的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;return 0;
}

什么是右值?什么是右值引用
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回,如函数返回值,这个返回值在函数表达式中存在,但是出了函数作用域这个值就会被销毁,所以不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

右值被分为
1.纯右值(内置类型表达式的值)
2.将亡值(自定义类型表达式的值)

  • 案例1
#include<iostream>// 一个函数模板
template<class T>
T fmin(T  x, T y)
{if (x < y){return x;}return y;	
}int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值// 字面常量:10;// 表达式的返回值 x + y;// 函数返回值 fmin(x, y);10;x + y;fmin(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1; x + y = 1;fmin(x, y) = 1;return 0;
}
  • 案例2
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;// ra1为a的别名,a是左值int& ra1 = a;  // 编译失败,因为10是右值,右值是不可以被左值引用的//int& ra2 = 10;   // const修饰的左值引用,既可引用左值,也可引用右值。const int& ra3 = 10;  const int& ra4 = a;	  // 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;// int&& r2 = a; // 报错// move()左值之后,编译器会将其识别为右值// 右值引用可以引用move()以后的左值int&& r3 = std::move(a);return 0;
}
  • 案例3

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{double x = 1.1, y = 2.2;// 右值引用之后,会导致右值被存储到特定位置(此时我们就可以对rr1进行修改,不过修改的是特定位置的变量,此时我们可以认为rr1就是一个左值),且可以取到该位置的地址int&& rr1 = 10;//  const修饰rr2之后,则rr2不可以被修改了const double&& rr2 = x + y;rr1 = 20;// rr2 = 5.5;  // 报错return 0;
}

4.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;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

4.3 右值引用使用场景和意义

左值引用的意义是什么?

1.函数传参或者是函数传返回值,使用左值引用,可以减少参数的拷贝
但是左值引用并没有完全解决问题,例如以下场景

  • 场景1
// 场景1:左值引用可以解决函数传参,减少参数的拷贝
// const引用,既可以接收左值,也可以接收右值
template<class T>
void func1(const T& x)
{}int main()
{// v1为左值vector<int> v1(10, 0);func1(v1);// vector<int>(10,0) 是一个匿名对象,也就是一个右值(属于将亡值)// 出了当前这一行,这个匿名对象就会被释放func1(vector<int>(10, 0));return 0;
}
  • 场景2:
// 场景2:函数传返回值,
// x的声明周期是直到main()返回,才会被销毁
// 因此我们才可以使用左值引用返回,如果x出func2()就被销毁,那么是不可以使用左值引用返回的
template<class T>
const T& func2(const T& x)
{// ...假设中间还做了许多操作return x;
}int main()
{// v1为左值vector<int> v1(10, 0);func2(v1);return 0;
}
  • 场景3
// 场景三:左值引用尚未解决的问题场景
// 当ret出func3()的函数作用于就会被销毁,那么我们是不可以使用左值引用返回的(这是因为引用的变量已经被销毁了)
// 因此,此时ret返回时,就会产生临时变量,就会增加参数ret的拷贝
// 右值引用的价值之一:就是补齐这个最后一块短板,传值返回的拷贝问题
template<class T>
T func3(const T& x)
{T ret;// ...return ret;
}int main()
{// v1为左值vector<int> v1(10, 0);func3(v1);return 0;
}
  • 场景4
// 但是其实也可以使用左值引用来解决场景三的问题
// 就是使用输出型参数,但是这样使用起来是很别扭的
// 假设ret的类型就是int
// 使用了输出型参数就不需要进行返回了
template<class T>
void func4(const T& x, int& ret)
{// ...//return ret;
}int main()
{// v1为左值vector<int> v1(10, 0);int ret = 10;// ret是一个输出型参数func4(v1,ret);return 0;
}

右值引用是怎样解决左值引用的短板的?

// 首先,我们先来看下面代码运行时,其底层的拷贝原理
#include<iostream>
#include<assert.h>
using namespace std;namespace qwy
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){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);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}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){push_back(ch);return *this;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做标识的\0};// 将整数转化为字符串string to_string(int value){bool flag = true;if (value < 0){flag = false;// 将value变为正数value = 0 - value;}qwy::string str;while (value > 0){// 获取value的个位int x = value % 10;  // 获取value的十位及以上value /= 10;str += ('0' + x); // 将x转化为对应的ascll值,并放入str}if (flag == false){str += '-';}// 逆转string对象中字符串的顺序std::reverse(str.begin(), str.end());return str;}
}
场景一
int main()
{// 场景1:// to_string()的返回值str,会先将其拷贝给一个临时变量,再将临时变量拷贝给ret// 但是编译器会自动对其进行优化,将两次拷贝简化为一次拷贝// 如下图所示qwy::string ret = qwy::to_string(-1234);return 0;
}
  • 打印结果为:string(const string& s) – 深拷贝
  • 通过打印结果我们可知,只调用了一次深拷贝,符合我们预期的结果

image-20230415141547347

场景二
// 场景2:
int main()
{// 对于场景二:编译器不敢将两次深拷贝优化为一次深拷贝// 优化的规定一般为:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化// 但是具体是取决于编译器的// 由于编译器对于如下这种情况是不进行优化的,// 因此to_string()的返回值str会先拷贝给临时变量,再由临时变量赋值给ret,但是赋值重载的过程中,会调用拷贝函数qwy::string ret;// 在上面创建string对象ret和下面对ret进行赋值之间,可能对ret进行了其他操作,因此编译器不敢进行优化ret = qwy::to_string(-1234);return 0;
}

注:编译器没有进行优化

第一步:拷贝给临时变量,调用了一次拷贝构造,由临时变量赋值给ret,调用了赋值重载,赋值重载的函数内部调用了一次拷贝构造

打印结果为:

string(const string& s) – 深拷贝

string& operator=(string s) – 深拷贝

string(const string& s) – 深拷贝

4.4移动构造和移动赋值

// 移动构造
// 所谓的移动构造就是将string对象的右值引用s与将要构造的对象进行资源交换
// 这样是不需要在移动构造内部创建新的对象
string(string&& s)
{cout << "string(const string& s) -- 移动拷贝" << endl;swap(s);
}// 移动赋值
string& operator=(string&& s)
{cout << "string& operator=(string s) -- 移动赋值" << endl;swap(s);return *this;
}		   
#include<iostream>
#include<assert.h>
using namespace std;namespace qwy
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){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);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造// 所谓的移动构造就是将s与将要构造的对象进行资源交换string(string&& s){cout << "string(const string& s) -- 移动拷贝" << endl;swap(s);}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string s) -- 移动赋值" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}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){push_back(ch);return *this;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; // 不包含最后做标识的\0};// 将整数转化为字符串string to_string(int value){bool flag = true;if (value < 0){flag = false;// 将value变为正数value = 0 - value;}qwy::string str;while (value > 0){int x = value % 10;  // 获取value的个位value /= 10; // 获取value的十位及以上str += ('0' + x); // 将x转化为对应的ascll值,并放入str}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}
场景一:右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
//  使用移动拷贝进一步提升构造的效率(移动构造的效率的大于拷贝构造的)
//  这就是右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
int main()
{qwy::string s1("hello world");// 用s1来构造s2// s1是左值,因此编译器会默认调用左值引用传参的拷贝构造qwy::string s2(s1);// 左值被move()之后,编译器会将其看作是右值// move(s1)是右值,编译器会默认调用右值引用传参的移动构造// 如果没有右值引用传参的移动构造,move(s1)也是可以被左值引用传参的拷贝构造调用qwy::string s3(move(s1));return 0;
}

打印结果为:
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝

image-20240506135630271

场景二:利用右值引用解决赋值问题
int main()
{// 根据优化的规则:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化// 因此对于如下的情况,编译器并不会进行优化// 1.编译器会将to_string()的返回值默认识别为右值,并将其移动拷贝给临时变量,// 2.再将临时变量移动赋值给retqwy::string ret;ret = qwy::to_string(-1234);return 0;
}

打印结果:

string(const string& s) – 移动拷贝

string& operator=(string s) – 移动赋值

场景三:
int main()
{list<qwy::string> lt;qwy::string s1("111111");// s1是左值,调用拷贝构造lt.push_back(s1);// "222222" 是右值(字面常量),调用移动拷贝// 如果我们没有实现移动拷贝,那么就会调用拷贝构造lt.push_back(qwy::string("222222"));// "333333" 会先构造一个string的对象,再调用移动拷贝// 如果我们没有实现移动拷贝,那么就会调用拷贝构造lt.push_back("333333");return 0;
}

打印结果
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

总结

右值引用和左值引用减少拷贝的原理不太一样

1.左值引用是取别名,直接起作用。

2.右值引用是间接起作用,实现移动构造和移动赋值,在拷贝的场景中,如果是右值(将亡值),转义资源

4,5右值引用引用左值及其一些更深入的使用场景分析

  • 场景1:
#include<iostream>
using namespace std;void func1(int& x) 
{ cout << "void func1(int& x)" << endl; 
}void func2(int&& x) 
{ cout << "void func2(int&& x)" << endl; 
}int main()
{int x = 1;// 左值调用对应的func1()func1(x);// 右值调用对应的func2()func2(2);return 0;
}
// 打印结果为:
// void func1(int& x)
// void func2(int&& x)
  • 场景二
void func2(int&& x) 
{ cout << "void func2(int&& x)" << endl; 
}int main()
{int x = 1;func2(2);func1(x); // 此处会报错,右值引用无法引用左值return 0;
}   
  • 模板中的&&万能引用
// 使用模板中的&&万能引用就可以解决场景二中:左值不可以被右值引用的问题了// 模板的右值引用我们可以认为是万能引用,
// 既可以引用右值,也可以引用左值
template<typename T>
void PerfectForward(T&& t)
{// t是否可以被加加,详情请看下图// t++;
}int main()
{perfectforward(10);           // 右值int a;perfectforward(a);            // 左值perfectforward(std::move(a)); // 右值// 且万能引用既可以引用const的左值,也可以引用const的右值const int b = 8;PerfectForward(b);		      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}    

image-20230415225029477

  • 万能引用存在的问题
// 万能引用还存在如下的问题:
#include<iostream>
using namespace std;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)
{// T&& t  不论是对左值引用,还是对右值引用,t本身都是一个左值// 因此调用Fun()函数,始终会调用参数为左值引用的Fun()函数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;
}
//  打印结果为:
// 左值引用
// 左值引用
// 左值引用
// const 左值引用
// const 左值引用
  • 解决万能引用存在的问题
// 使用完美转发来解决上述的问题
// 具体如下:
#include<iostream>
using namespace std;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)
{// 完美转发,保持t的本源属性,如果t是对左值的引用,那么就保留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;
}
// 打印结果为:
// 右值引用
// 左值引用
// 右值引用
// const 左值引用
// const 右值引用

对list类的改造(移动构造、移动赋值)(内部使用了完美转发)

// 关于list的封装
namespace qwy
{// 链表节点的类模板template<class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x):_next(nullptr), _prev(nullptr), _data(x){}// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处使用了完美转发)list_node(T&& x):_next(nullptr), _prev(nullptr), _data(std::forward<T>(x)){}};// 迭代器的类模板template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;__list_iterator(node* p):_pnode(p){}Ptr operator->(){return &_pnode->_data;}Ref operator*(){return _pnode->_data;}Self& operator++(){_pnode = _pnode->_next;return *this;}Self operator++(int){Self tmp(*this);_pnode = _pnode->_next;return tmp;}Self& operator--(){_pnode = _pnode->_prev;return *this;}Self operator--(int){Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const{return _pnode == it._pnode;}};template<class T>class list{typedef list_node<T> node;public:typedef __list_iterator<T, T&, T*> iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}void empty_initialize(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_initialize();}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// 拷贝构造list(const list<T>& lt){empty_initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);}size_t size() const{return _size;}bool empty() const{return _size == 0;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}// 左值引用的push_backvoid push_back(const T& x){insert(end(), x);}// 右值引用的push_backvoid push_back(T&& x){// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处,使用了完美转发)insert(end(), std::forward<T>(x));}// 左值引用的insertiterator insert(iterator pos, const T& x){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}// 右值引用的insertiterator insert(iterator pos, T&& x){// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处使用了完美转发)node* newnode = new node(std::forward<T>(x));node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}private:node* _head;size_t _size;};
}

image-20230416102036977

使用list::push_back
int main()
{qwy::list<qwy::string> lt;qwy::string s1("111111");lt.push_back(s1);lt.push_back(qwy::string("222222"));lt.push_back("333333");return 0;
}

打印结果为
string(const string& s) – 移动拷贝
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

这篇关于23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/970537

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象