C++11重大新增特性:左值引用 右值引用 移动构造 移动赋值

C++11重大新增特性:左值引用 & 右值引用 & 移动构造 & 移动赋值

  • 一、右值引用和左值引用概念和区别
    • 1.1 左值 & 左值引用
    • 1.2 右值 & 右值引用
  • 二、左值引用和右值引用对比
    • 2.1 左值引用
    • 2.1 右值引用
  • 三、右值和右值引用诞生的意义
  • 四、移动构造 & 移动赋值
    • 4.1 移动构造函数
    • 4.2 移动赋值函数
  • 五、完美转发(引用在模板中的用途)
    • 5.1 模板中的万能引用(折叠引用)&&
    • 5.2 完美转发(std::forward)在传参的过程中保留对象原生类型属性
  • 六、新的类功能
    • 6.1 C++11新增默认成员函数
    • 6.2 类成员变量初始化
    • 6.3 强制生成默认函数的关键字default
    • 6.4 禁止生成默认函数的关键字delete

一、右值引用和左值引用概念和区别

 C++11为了支持移动操作(移动构造和移动赋值),新标准引入了新的引用类型 —— 右值引用。我们将C++11之前的引用都成为左值引用。但无论是左值引用还是右值引用,本质上都是给对象取别名!

1.1 左值 & 左值引用

 左值是一个表达式(如变量、解引用后的指针),表示的是一个对象的身份。我们可以对左值进行赋值操作、取地址。左值可以出现在等号的两边。

 评判一个表达式是否为左值的最根本标志就是:是否可以取地址。所以对于一个const修饰的变量,由于该变量可以取地址,所以也是一个典型的左值。左值引用即是左值的别名。

int main()
{//a、b、p、*p都是左值int* p = new int(0);int a = 10;const int b = 12;//rp、ra、rb、rval都是左值引用int*& rp = p;int& ra = a;const int& rb = b;int& rval = *p;return 0;
}

1.2 右值 & 右值引用

 右值也是一个表达式,和左值不同的是:右值只能出现在等号的右边(即不能被赋值),不能取地址(最根本原因),通常是字面常量、表达式返回值,函数返回值。右值引用就是对右值的引用,通过&&来获取右值引用。

右值不能取地址。但对右值取别名后,会导致右值被存储到特定的区域,并且可以取到该区域的指针。比如:字面常量10是一个右值,不能取地址。如果10被ra引用后,我们可以对ra取地址,并且可以通过修改ra进而修改右值。如果不想该右值被修改,我们可以通过const进行修饰!!

int Add(const int x, const int y)
{return x + y;
}int main()
{//10、10 + 20、Add函数返回值都是右值,无法取地址//ra、rb、rc都是右值引用int&& ra = 10;int&& rb = 10 + 20;int&& rc = Add(1, 2);ra = 20;return 0;
}
  • 需要注意的是:上述ra、rb、rc虽然是右值引用,但ra、rb、rc本身还是一个变量,并且可以取地址,是一个左值!!

二、左值引用和右值引用对比

2.1 左值引用

  1. 普通的左值引用只能引用左值,不能引用右值。
  2. const修饰的左值引用,不仅可以引用左值,还可以引用右值!!

【示例】:

int main()
{int a = 10;int& ra = a;//int& rb = 10;//error,普通左值引用不能引用右值const int& rc = a;const int& rd = 10;return 0;
}

2.1 右值引用

  1. 右值引用可以引用右值,但不能引用左值。
  2. 我们可以通过move函数,让右值引用引用左值!!move函数调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。需要注意的是,move函数的返回值是一个右值,但move函数本身不会修改左值属性!

【示例】:

int main()
{int a = 10;const int&& rb = 10;//int&& ra = 1;//error, 右值引用无法引用左值//error,move本身不会修改左值属性//move(a);//int&& ra = a;int&& ra = move(a);//move后的返回值是一个右值return 0;
}

三、右值和右值引用诞生的意义

 在C++11标志之前,如果在vector、list等容器保存的是一块空间的指针。此时如果调用拷贝构造函数和拷贝赋值函数,编译器会进行一个深拷贝构造出新对象,将就对象释放。
 在很多情况下拷贝对象时无法避免的。

但如果返回的是一个临时对象会发生什么呢?

【示例】:
在这里插入图片描述

  • 我们发现如果用一个临时对象拷贝构造一个变量s时,编译器会先拷贝构造出一个临时对象,在用该临时对象出拷贝构造变量s
  • 但该过程中存在一个问题:临时对象深拷贝创建后仅仅使用一次便立即销毁、原对象ret是一个临时对象马上就要出作用域销毁,此时依旧对ret进行拷贝构造。
  • 上述情况在实际过程中会在大量场景中频繁出现,并且意义不大。这也意味着大量的无意义的深拷贝产生,将导致性能的下降。我们是否可以不进行拷贝,直接将原始数据转移到新对象中呢?(该操作的前提是原始数据马上就要被销毁)
  • 为了解决上述情况,C++11引入了移动构造函数和移动拷贝函数。移动构造函数和移动拷贝函数可以将一个待销毁的变量数据(该变量通常被编译器识别为右值)直接转移到新对象。而右值和右值引则是为实现这些函数运营而生的!!

四、移动构造 & 移动赋值

 在C++中,右值分为两种:内置定义类型右值为纯右值;自定义类型右值为将亡值!!对于纯右值,移动构造函数、移动赋值函数没有太大价值,行为和拷贝构造函数、移动构造函数类型。(上述临时对象ret虽然可以取地址是一个左值,但编译器会特殊处理将其识别为右值,即将亡值)
 只有当自定义类型中存在资源的深拷贝时,此时才能移动构造和移动赋值的价值。(直接转移资源,而非深拷贝!!)

4.1 移动构造函数

 类似于拷贝构造函数,移动构造函数的第一个参数是该类类型的引用,不同的是引用参数是一个右值。移动构造函数的本质是直接将右值对象的资源窃取过来,占为己有,此时不在进行深拷贝。所以该构造称为移动构造,用别人的资源来构造自己。

【示例:移动构造和拷贝构造函数实现和对比】:

namespace mystring
{class string{public:string(const char* str = "")//默认构造函数:_size(strlen(str)), _capacity(_size){cout << "string(const char* str = "") ----- 构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造函数string(const string& s){cout << "string(const string& s) ---- 拷贝构造函数 深拷贝" << endl;string tmp(s._str);swap(tmp);}//移动构造函数string(string&& s){cout << "string(string&& s) ---- 移动构造函数  移动语义" << endl;swap(s);}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};
};int main()
{mystring::string s("123456");cout << endl;mystring::string s1 = s;cout << endl;mystring::string s2(move(s1));return 0;
}

【运行结果】:
在这里插入图片描述

  • 在上述过程中,我们发现移动构造过程中没有深拷贝。原因在于移动构造直接将原资源抢占,交换过来!!

4.2 移动赋值函数

 移动赋值函数和移动拷贝函数一样,直接将将亡值的资源交换抢占过来。

【示例: 移动赋值函数和拷贝赋值函数实现和对比】:

namespace mystring
{class string{public:string(const char* str = "")//默认构造函数:_size(strlen(str)), _capacity(_size){//cout << "string(const char* str = "") ----- 构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝赋值函数string& operator=(const string& s){string tmp(s);swap(tmp);cout << "string& operator=(const string& s) ---- 拷贝赋值函数 深拷贝" << endl;return *this;}//移动赋值函数string& operator=(string&& s){swap(s);cout << "string& operator=(string&& s) ---- 移动语义" << endl;return s;}//拷贝构造函数string(const string& s){string tmp(s._str);swap(tmp);cout << "string(const string& s) ---- 拷贝构造函数 深拷贝" << endl;}//移动构造函数string(string&& s){swap(s);cout << "string(string&& s) ---- 移动构造函数  移动语义" << endl;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};
};int main()
{mystring::string s("123456");cout << endl;mystring::string s1, s2;s1 = s;cout << endl;s2 = move(s);cout << endl;return 0;
}

【运行结果】:

在这里插入图片描述

五、完美转发(引用在模板中的用途)

5.1 模板中的万能引用(折叠引用)&&

 模板中的&&不是右值引用,表示的时万能引用,即可以接收左值也可接受右值。但万能引用只是提供了能够同时接受左值引用和右值引用的能力。
 引用类型的唯一作用就是限制接收的类型,后续使用中会退化成左值。
(这很好理解,以移动构造和移动赋值为例,其最重要的功能就是转移右值的资源。但显然右值是无法被修改的。所以当一个右值传递给移动构造和移动赋值后,后续使用会退化成左值)

【示例】:

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<class T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{int a = 11;PerfectForward(a);//左值PerfectForward(10);//右值const int b = 10;PerfectForward(b);//const左值PerfectForward(move(b));//const 右值return 0;

【运行结果】:
在这里插入图片描述

  • 函数模板中的&&称为万能引用或折叠引用,上述代码成功运行,从侧面说明万能引用既可以接收右值还可以接收左值。同时最终结果均为左值相关,进一步说明万能引用接收右值后会退化成左值!

5.2 完美转发(std::forward)在传参的过程中保留对象原生类型属性

 万能引用在接受相关引用后会退化成一个左值。这个机制也是移动构造和移动赋值的基础。
 同时在C++中提出了完美转发的概念,完美转发可以保留对象在传参过程中的原生类型属性。

【示例】:

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<class T>
void PerfectForward(T&& t)
{Fun(forward<T>(t));//完美转发,保留t的原生属性
}int main()
{int a = 11;PerfectForward(a);//左值PerfectForward(10);//右值const int b = 10;PerfectForward(b);//const左值PerfectForward(move(b));//const 右值return 0;
}

【运行结果】:
在这里插入图片描述

【完美转发实际场景中的作用】:

namespace mystring
{class string{public:string(const char* str = "")//默认构造函数:_size(strlen(str)), _capacity(_size){cout << "string(const char* str = "") ----- 构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造函数string(const string& s){string tmp(s._str);swap(tmp);cout << "string(const string& s) ---- 拷贝构造函数 深拷贝" << endl;}//移动构造函数string(string&& s){swap(s);cout << "string(string&& s) ---- 移动构造函数  移动语义" << endl;}//拷贝赋值函数string& operator=(const string& s){string tmp(s);swap(tmp);cout << "string& operator=(const string& s) ---- 拷贝赋值函数 深拷贝" << endl;return *this;}//移动赋值函数string& operator=(string&& s){swap(s);cout << "string& operator=(string&& s) ---- 移动语义" << endl;return s;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};
};template<class T>
struct ListNode
{ListNode* _next = nullptr;ListNode* _prev = nullptr;T _data;
};template<class T>
class List
{typedef ListNode<T> Node;
public:List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}void PushBack(T&& x){//Insert(_head, x);Insert(_head, std::forward<T>(x));}void PushFront(T&& x){//Insert(_head->_next, x);Insert(_head->_next, std::forward<T>(x));}void Insert(Node* pos, T&& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = std::forward<T>(x); // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}void Insert(Node* pos, const T& x){Node* prev = pos->_prev;Node* newnode = new Node;newnode->_data = x; // 关键位置// prev newnode posprev->_next = newnode;newnode->_prev = prev;newnode->_next = pos;pos->_prev = newnode;}
private:Node* _head = nullptr;
};int main()
{List<mystring::string> lt;cout << endl;lt.PushBack("11111111");return 0;
}

【运行结果】:

  • 调试时,lt.PushBack("11111111");走的是void Insert(Node* pos, T&& x)版本的插入,而非void Insert(Node* pos, const T& x)

在这里插入图片描述

六、新的类功能

6.1 C++11新增默认成员函数

 原来C++类中,有6个默认成员函数:(后两个作用、意义不大)

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

 在C++11中又新增两个默认成员函数:移动构造函数和移动赋值运算符重载。
 但比较特殊的是,对于移动构造函数和移动赋值运算符重载来说,只有当拷贝构造函数、拷贝赋值重载、析构函数都没显示的写时,编译器才会生成一个默认的移动构造函数和移动赋值运算符重载。
 对于默认生成的移动构造函数来说,内置类型成员按值拷贝;对于自定义类型成员,则需要看该自定义成员是否实现了移动构造。如果实现了,调用该自定义类型成员的移动构造;否则调用拷贝构造!!
 对于默认生成的移动赋值运算符重载,同上。

6.2 类成员变量初始化

 在C++11中,允许类成员变量在声明时给默认缺省值。当调用类的构造函数时,如果没有显示的传递初始值,编译器会用成员变量声明时的默认缺省值去初始化成员变量!

class string{public://调用该函数时,编译器会应声明是的缺省值去初始化成员变量//即:_str = nullptr、_size = 0、 _capacity = 0string()//默认构造函数{ }private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};

6.3 强制生成默认函数的关键字default

 C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

【示例】:

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name), _age(p._age){}Person(Person&& p) = default;
private:mystring::string _name;int _age;
};
int main()
{Person s1;cout << endl;Person s2 = s1;cout << endl;Person s3 = std::move(s1);cout << endl;return 0;
}

【成员函数_name的相关实现如下】:

namespace mystring
{class string{public:string(const char* str = "")//默认构造函数:_size(strlen(str)), _capacity(_size){cout << "string(const char* str = "") ----- 构造函数" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造函数string(const string& s){string tmp(s._str);swap(tmp);cout << "string(const string& s) ---- 拷贝构造函数 深拷贝" << endl;}//移动构造函数string(string&& s){swap(s);cout << "string(string&& s) ---- 移动构造函数  移动语义" << endl;}//拷贝赋值函数string& operator=(const string& s){string tmp(s);swap(tmp);cout << "string& operator=(const string& s) ---- 拷贝赋值函数 深拷贝" << endl;return *this;}//移动赋值函数string& operator=(string&& s){swap(s);cout << "string& operator=(string&& s) ---- 移动语义" << endl;return s;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};
};

【允许结果】:
在这里插入图片描述

  • 由于在Person类中,我们已经显示的实现了拷贝构造函数,因此无法编译器无法生成默认的移动构造函数。但我们通过default关键字让编译器强制生成默认的移动构造函数。对于默认生成的移动构造函数,内置类型int _age按值拷贝;对于自定义类型mystring::string _name;调用自身的移动构造函数。
  • 如果成员变量存在如const、引用的类型时,即使default强制让编译器实现,也是错误的,无法生成。

6.4 禁止生成默认函数的关键字delete

 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。(C++11之前,如果我们希望阻止拷贝的类,我们一般将相关函数声明为私有。但对于有元和成员函数来说,依旧可以访问它。所以为了阻止友元函数和成员函数进行拷贝,通常将拷贝控制成员声明为私有,但不定义)

在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:mystring::string _name;int _age;
};

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

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

相关文章

【射频通信电子线路基础第一讲】射频电子线路基础绪论——射频概念、通信系统、语义通信

1. 射频与高频广义上的概念厘清 高频&#xff1a;就是频率高&#xff08;大于10K&#xff09;&#xff0c;单位一般用MHz&#xff08;兆赫&#xff09;表示。 射频&#xff1a;Radio Frequency&#xff0c;简称RF&#xff0c;300K-300G。射频就是射频电流&#xff0c;它是一种…

Java详解String 字符串类以及String内存原理、StringBuilder类、StringJoiner类(附有代码+案例)

文章目录 九.String 字符串类型9.0 String概述9.1 字符串常用方法9.2 String内存图9.2.1直接赋值9.2.2new出来 9.3字符串比较9.4 字符串遍历9.4.1 统计字符串大小写及数字9.4.2 拼接字符串9.4.3字符串反转 9.5 StringBuilder类9.5.1StringBuilder 构造方法9.5.2StringBuilder常…

es集群详解

1、基本介绍 1.1、为什么需要集群 单台 Elasticsearch 服务器提供服务&#xff0c;往往都有最大的负载能力&#xff0c;超过这个阈值&#xff0c;服务器性能就会大大降低甚至不可用&#xff0c;所以生产环境中&#xff0c;ES 一般都是运行在指定服务器集群中。 除了负载能力&…

九银十拿到大模型(LLM)offer,面试八股

金九银十拿到大模型&#xff08;LLM&#xff09;offer&#xff0c;面试八股 从事大模型的朋友在 金J九银十拿到了一份不错的offer&#xff0c;面试十几家公司&#xff0c;通过了六家。好在分享了大佬总结的大模型方向面试的常见题目&#xff08;含答案&#xff09;&#xff0c;…

RS232转RS485

1.232转485转换器 232转485转换器是RS-232与RS-485之间的双向接口的转换器&#xff0c;应用于主控机之间&#xff0c;主控机与单片机或外设之间构成点到点&#xff0c;点到多点远程多机通信网络&#xff0c;实现多机应答通信&#xff0c;广泛地应用于工业自动化控制系统&#x…

LLM代码实现-Qwen(Function Calling)

简介 Function Calling 是一种让 Chat Completion 模型调用外部函数的能力&#xff0c;可以让模型不仅仅根据自身的数据库知识进行回答&#xff0c;而是可以额外挂载一个函数库&#xff0c;然后根据用户提问去函数库检索&#xff0c;按照实际需求调用外部函数并获取函数运行结…

Unknown command: “create-react-app“

在创建react项目时出现报错" Unknown command: "create-react-app" " 解决方法&#xff1a; 配置全局变量&#xff0c;" win r " 打开cmd窗口&#xff0c;输入下列命令&#xff0c;回车等待结束即可&#xff1a; npx create-react-app my-pro…

Docker部署项目时的服务端口设置——给容器添加新端口映射

Docker给容器添加新端口映射 1 Docker安装Ubuntu22.042 创建新容器3 给容器添加端口映射3.1 查看运行的容器3.2 查看容器挂载目录3.3 停止容器3.4 停止docker服务3.5 进入容器挂载目录3.6 修改config.v2.json文件3.7 修改hostconfig.json文件3.8 启动docker3.9 启动容器 4 端口…

七款最佳的渗透测试工具(非常详细)零基础入门到精通,收藏这一篇就够了

渗透测试工具是模拟对计算机系统、网络或 Web 应用程序的网络攻击的软件应用程序&#xff0c;它们的作用是在实际攻击者之前发现安全漏洞。它们可以作为系统的压力测试&#xff0c;揭示哪些区域可能会受到真正的威胁。 本文我将介绍七款最佳的渗透测试工具。 1 Kali Linux K…

【数据结构入门】排序算法之插入排序与选择排序

目录 前言 一、排序的概念及运用 1.排序的概念 2.排序的运用 3.常见排序算法 二、插入排序与选择排序 2.1插入排序 2.1.1直接插入排序 1&#xff09;基本思想 2&#xff09;具体步骤 3&#xff09;算法特性 4&#xff09;算法实现 2.1.2希尔排序 1) 基本思想 2&…

【前端面试】leetcode树javascript

写一个树 // 定义二叉树节点 function TreeNode(val, left, right) {this.val = (val === undefined ? 0 : val)this.left = (left === undefined ? null : left)this.right = (right === undefined ? null : right) }// 示例使用 const root = new TreeNode(1,new TreeNod…

Web APIs第一天

第一天&#xff1a;DOM获取元素&#xff0c;获取元素&#xff0c;修改属性 声明新变量&#xff0c;一般默认const&#xff0c;如果变量的值不变&#xff0c;则使用const。如果变量的值变化&#xff0c;则使用let。var已经被淘汰了。 <script>const arr [red, pink]arr.…

怎样在公司将手机屏幕(远程)投屏到家里的大电视上?

我不住家里&#xff0c;前几次回去都会替老爸老妈清理手机。这两个星期没空回去&#xff0c;老爸吐槽手机用几天就又卡了&#xff0c;其实就是清理一些手机缓存的问题。 我说我远程控制他的手机&#xff0c;给他清理一下。他一听“控制”就不喜欢&#xff0c;说我大了&#xf…

3600关成语填字APP游戏ACCESS\EXCEL数据库

成语类的APP游戏在最近一两年内非常的火爆&#xff0c;其主要原因是几乎所有中国人都能够冲个几十上百关&#xff0c;学习和趣味共享。看图猜成语类的数据之前已经弄到过很多&#xff0c;今天这份成语填字的倒是头一份。 该数据做成的APP效果如下&#xff1a; 数据以\符号分隔…

Java JVM 垃圾回收算法详解

Java 虚拟机&#xff08;JVM&#xff09;是运行 Java 应用程序的核心&#xff0c;它的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制是 JVM 中非常重要的一个部分。垃圾回收的主要任务是自动管理内存&#xff0c;回收那些不再被使用的对象&#xff0c;从而释放内…

【机器学习】机器学习引领未来:赋能精准高效的图像识别技术革新

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f50d;1. 引言&#x1f4d2;2. 机器学习基础与图像识别原理&#x1f341;机器学习概述&#xff1a;监督学习、无监督学习与强化学…

Ubuntu系统+宝塔面板部署Frp内网穿透服务

一、搭建目的 上次在局域网中搭建了自己的个人网盘之后&#xff0c;上传文件、照片都很方便&#xff0c;但是只能限制在内网中访问&#xff01;所以这次再搭建一个内网穿透服务器&#xff0c;这样不管在哪里都能访问到家里的云盘&#xff01; 二、内网穿透Frp是什么&#xff1…

【超详细】Linux开发环境搭建指南 | Ubuntu

文章目录 虚拟机安装对比Virtual Box 下载ubuntu 操作系统下载Virtual Box 安装安装ubuntu设置中文语言共享文件夹设置添加输入法安装步骤&#xff0c;参考官方教程 安装 vscode解决主机不能通过ssh连接宿主机网络连接几种网络连接区别主机和宿主机相互 ping通 网络代理 虚拟机…

智能未来:低代码与AI如何重塑企业应用开发

引言 在当今瞬息万变的商业环境中&#xff0c;企业面临着前所未有的挑战与机遇。数字化转型已经成为各行各业的必然趋势&#xff0c;而在这一过程中&#xff0c;应用开发的效率与智能化程度成为企业竞争力的重要衡量标准。传统的开发模式往往需要大量的时间和资源&#xff0c;而…

图像边缘检测技术详解:利用OpenCV实现Sobel算子

图像边缘检测技术详解&#xff1a;利用OpenCV实现Sobel算子 前言Sobel算子的原理代码演示结果展示结语 前言 在数字图像处理的广阔领域中&#xff0c;边缘检测技术扮演着至关重要的角色。无论是在科学研究、工业自动化&#xff0c;还是在日常生活中的智能设备中&#xff0c;我们…