【C++】深度解析---赋值运算符重载(小白一看就懂!!)

目录

一、前言

二、 运算符重载

 🍎运算符重载

① 概念引入 

② 语法明细

③ 练习巩固

④ 代码展示

 🍇赋值运算符重载

① 语法说明及注意事项 

② 默认的赋值运算符重载 

③ 值运算符不能重载成全局函数!

三、总结

 四、共勉


一、前言

      【C++】为了增强代码的可读性引入了赋值运算符重载赋值运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。但是 赋值运算符重载 的应用细节很多和之前讲过的拷贝构造函数数有着千丝万缕的关系,所以本文就来详细的讲解一下赋值运算符重载。

二、 运算符重载

 🍎运算符重载

① 概念引入 

  • 之前呢我们都是对一个日期进行初始化、销毁等操作,现在若是我要去比较一下两个日期,该怎么实现呢?
class Date
{
public:// 构造函数Date(int year = 2024,int month = 4,int day = 14){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date& d){cout << "调用拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){std::cout << "year:" << _year << std::endl;std::cout << "month:" << _year << std::endl;std::cout << "day:" << _year << std::endl;}// 析构函数~Date(){cout << "调用析构构造" << endl;cout << endl;_year = 0;_month = 0;_day = 0;}private:int _year;int _month;int _day;
};
int main()_
{Date d1(2024, 4, 14);Date d2(2024, 4, 14);cout<<(d1==d2)<<endl;return 0;
}
  • 可能你会想到直接这么去写,但是编译器允许吗?很明显它完全实现不了这样的比较

  • 于是就想到了把他们封装成为一个函数来进行实现
//等于==
bool Equal(const Date& d1, const Date& d2)
{//...
}//小于<
bool Less(const Date& d1, const Date& d2)
{//...
}//大于>
bool Greater(const Date& d1, const Date& d2)
{//...
}
Equal(d1, d2);
Less(d1, d2);
Greater(d1, d2);
  • 但是,你认为所有人都会像这样去仔细对函数进行命名吗,尤其是打一些算法竞赛的。它们可能就会把函数命名成下面这样

 若是每个函数都是上面这样的命名风格,那么调用的人该多心烦呀╮(╯▽╰)╭

  • 如果我们不用考虑函数名,可以直接用最直观的形式也就是一开始讲的那个样子去进行调用的话该多好呀

  • 但是呢编译器不认识我们上面所写的这种形式,之前我们去比较两个数的大小或者相等都是int、char、double这些【内置类型】的数据,对于这些类型是语法定义的,语言本身就已经存在了的,都将它们写进指令里了
  • 不过对于【自定义类型】而言,是我们自己定义的类型,编译器无法去进行识别,也无法去比较像两个日期这样的大小,所以对于自定义类型而言,在于以下两点
  1. 类型是你定义的,怎么比较,怎么用运算符应该由你来确定
  2. 自定义类型不一定可以加、减、乘、除,像两个日期相加是毫无意义的,相减的话还能算出他们之间所差天数【日期类中会实现日期加一个天数】

② 语法明细

基于上述的种种问题,C++为了增强代码的可读性引入了运算符重载 

 【概念】:运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

【函数名字】:关键字operator后面接需要重载的运算符符号 

【函数原型】:返回值类型 operator 操作符(参数列表)

  • 根据上面的语法概念,就可以写出==的运算符重载函数
bool operator==(const Date& d1, const Date& d2)

 注意事项:

 接下去我便通过几点注意实现来带你进一步了解运算符重载

1️⃣:不能通过连接其他符号来创建新的操作符:比如operator@

2️⃣:重载操作符必须有一个类类型(自定义类型)参数

3️⃣:用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

  • 可以看到,我重载了运算符+,内部实现了一个乘法运算,然后用当前对象的月数 * 10,最终改变了+运算符的含义,这种语法虽然是可以编译过的,但是写出来毫无意义,读者可以了解一下

 4️⃣:运算符重载可以放在全局,但是不能访问当前类的私有成员变量

  • 可以看到,虽然运算符重载我们写出来了,但是在内部调用当前对象的成员 时却报出了错误,说无法访问private的成员,那此时该怎么办呢?

👉解决办法1:去掉[private],把成员函数全部设置为公有[public] 

👉解决办法2:提供公有函数getYear()getMonth()getDay()

👉解决办法3:设置友元【不好,会破坏类的完整性】

👉解决办法4:直接把运算符重载放到类内

  • 此时我们来试试第四种解决方案,将这个函数放到类内。但是一编译却报出了下面这样的错误,这是为什么呢?【看看下一个点就知道了】

 5️⃣:作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

  • 还记得我们在类和对象的的封装思想中学习过的类成员函数中都存在一个隐藏形参this,用于接收调用对象所传入的地址,上面我们在写【日期计算器】的是有也有在类内使用过这个this指针
  • 不过对于==来说是个【双目操作符】,其运算符只能有两个,那此时再加上隐藏形参this的话就会出现问题
bool operator==(Date* this, const Date& d1, const Date& d2)
  • 所以当运算符重载函数放到类内时,就要改变其形参个数,否则就会造成参数过多的现象,在形参部分给一个参数即可,比较的对象就是当前调用这个函数的对象即【this指针所指对象】与【形参中传入的对象】
bool operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
  • 那既然这是一个类内的函数,就可以使用对象.的形式去调用,运行结果如下

 6️⃣*、 ::、 sizeof ?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

 7️⃣运算符重载和函数重载不要混淆了

【运算符重载】自定义类型对象可以使用运算符

【函数重载】支持函数名相同,参数不同的函数,同时可以用

 ③ 练习巩固

 上面教了要如何去写==的运算符重载,接下去我们就来对其他运算符写一个重载

  • 首先就是小于 ,读者可以试着自己在编译器中写写看
bool operator<(const Date& d) 
// 小于
bool operator<(const Date& d)
{if (_year < d._year) {return true;}else if (_year == d._year && _month < d._month) {return true;}else if (_year == d._year && _month == d._month && _day < d._day) {return true;}else {return false;}
}
  • 知道了==<如何去进行重载,那小于等于呢?该如何去实现?

有同学说:这简单,把 < 都改成 <= 不就好了 

bool operator<(const Date& d)
{if (_year <= d._year) {return true;}else if (_year == d._year && _month <= d._month) {return true;}else if (_year == d._year && _month == d._month && _day <= d._day) {return true;}else {return false;}
}
  • 确实上面这样是最直观的形式,但是刚才说了,我们在已经能够写对的情况下要去追求更优的情况。我采取的是下面这种写法,你能很快反应过来吗?
return (*this < d) || (*this == d);
  •  其实很简单,我就是做了一个【复用】,使用到了上面重载后的运算符 < ==this指向当前对象,那么*this指的就是当前对象,这样来看的话其实就一目了然了

小于、小于等于都会了,那大于>和大于等于>=呢?不等于!=呢? 

bool operator>(const Date& d)  
bool operator>=(const Date& d)  
bool operator!=(const Date& d)
  • 其实上面的这两个都可以用【复用】的思想去进行实现,相信此刻不用我说你应该都知道该如何去实现了把
//大于>
bool operator>(const Date& d)
{return !(*this <= d);
}//大于等于>=
bool operator>=(const Date& d)
{return !(*this < d);
}//不等于!=
bool operator!=(const Date& d)
{return !(*this == d);
}

这里就不给出测试结果了,读者可自己修改日期去查看一下 

④ 代码展示

class Date
{
public:// 构造函数Date(int year = 2024,int month = 4,int day = 14){_year = 2024;_month = 4;_day = 14;}//拷贝构造函数Date(Date& d){cout << "调用拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){std::cout << "year:" << _year << std::endl;std::cout << "month:" << _year << std::endl;std::cout << "day:" << _year << std::endl;}// 析构函数~Date(){cout << "调用析构构造" << endl;cout << endl;_year = 0;_month = 0;_day = 0;}// 等于== 运算符重载bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}// 小于< bool operator<(const Date& d){return _year < d._year|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day);}// 小于等于 <=bool operator<=(const Date& d){return (*this < d) || (*this == d);}// 大于>bool operator>(const Date& d){return !(*this <= d);}// >=bool operator>=(const Date d){return (*this < d);}private:int _year;int _month;int _day;
};

 🍇赋值运算符重载

 有了运算符重载的概念后,我们就来讲讲什么是赋值运算符重载

① 语法说明及注意事项 

首先给出代码,然后我再一一分解叙述 

Date& operator=(const Date& d)
{if (this != &d)		//判断一下是否有给自己赋值{_year = d._year;_month = d._month;_day = d._day;}return *this;
}

 👉 【参数类型】:const T&,传递引用可以提高传参效率

  • 这一点我在上面讲解拷贝构造的时候已经有重点提到过,加&是为了减少传值调用而引发的拷贝构造,加const则是为了防止当前对象被修改和权限访问的问题,如果忘记了可以再去看看这篇文章:拷贝构造函数

👉 【返回*this】 :要复合连续赋值的含义 

  • 这块重点讲一下,本来对于赋值运算符来说是不需要有返回值的,设想我们平常在定义一个变量的时候为其进行初始化使用的时候赋值运算,也不会去考虑到什么返回值,但是对于自定义类型来说,我们要去考虑这个返回值
Date d1(2023, 3, 27);
Date d2;
Date d3;d3 = d2 = d1;
  • 可以看到,就是上面这种情况,当d1为d2进行初始化后,还要为d3去进行初始化,那此时就要使用到d2,所以我们在写赋值重载的时候要考虑到返回值的问题,那返回什么呢?
  •  因为是为当前对象做赋值,所以应该返回当前对象也就是*this 

 👉 【返回值类型】:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

  • 返回值这一块我在引用一文也有相信介绍过,若是返回一个出了当前作用域不会销毁的变量,就可以使用引用返回来减少拷贝构造
  •  *this 是当前对象的引用,并不会在函数作用域结束时被销毁。*this 指的是调用成员函数的对象本身,即赋值运算符中 operator= 的左操作数。在赋值运算符函数结束后,*this 引用的对象依然存在,因为这个对象的生命周期与原来对象的生命周期一致。

👉 【多方位考虑】:检测是否自己给自己赋值 

  • 在外界调用这个赋值重载的时候,不免会有人写成下面这种形式
d1 = d1;
  • 那自己给自己赋值其实并没有什么意义,所以我们在内部应该去做一个判断,若是当前this指针所指向的地址和传入的地址一致的话,就不用做任何事,直接返回当前对象即可。若是不同的话才去执行一个赋值的逻辑
if (this != &d)

知晓了基本写法和注意事项后,我们就来测试运行一下看看是否真的可以完成自定义类型的赋值

  • 可以看到 ,确实可以使用=去进行日期之间的赋值

  • 不仅如此,也可以完成这种【链式】的连续赋值

  • 那现在我想问,下面的这两种都属于【赋值重载】吗?
  • 注意:d2 = d1就是我们刚才说的赋值重载,去类中调用了对应的成员函数;但是对于Date d3 = d2来说,却没有去调用赋值重载,而是去调用了【拷贝构造】,此时就会有同学很疑惑?

 这里一定要区分的一点是,赋值重载是两个已经初始化的对象才可以去做的工作;对于拷贝构造来说是拿一个已经实例化的对象去初始化另一个对象

② 默认的赋值运算符重载 

 重点地再来谈谈默认的赋值运算符重载,相信在看了构造、析构、拷贝构造后,本小节对你来说不是什么难事😎

  • 那还是咱熟悉的老朋友Time类和Date类,在Time类中我写了一个赋值重载
class Time
{
public:// 构造函数Time(){_hour = 1;_minute = 1;_second = 1;}// 赋值重载Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int y = 2000, int m = 1, int d = 1){_year = y;_month = m;_day = d;}
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
  • 我们首先通过如下结果进行观察

  • 可以发现,当使用d1初始化d2的时候,去调用了Time类的赋值运算符重载,这是为什么呢?我们其实可以先来看看Date类中的成员变量有:_year、_month、_day以及一个Time类的对象_t,通过上面的学习我们可以知道对于前三者来说都叫做【内置类型】,对于后者来说都叫做【自定义类型】
  •  那在构造、析构中我们有说到过对于【内置类型】编译器不会做处理;对于【自定义类型】会去调用默认的构造和析构。在拷贝构造中我们有说到过【内置类型】会按照值拷贝一个字节一个字节;对于【自定义类型】来说会去调用这个成员的拷贝构造
  • 那通过上面的调试可以看出赋值运算符重载似乎和拷贝构造是差不多,对于内置类型进行值拷贝,对于自定义类型Time去调用了其赋值重载函数 

 那我还是一样会提出疑问,既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

  • 没错,也是我们的老朋友Stack类。我想你可能已经猜到了结果😆 
typedef int DataType;
class Stack
{
public:// 构造函数 Stack(size_t capacity = 10){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}// 拷贝构造 Stack(const Stack& st){//根据st的容量大小在堆区开辟出一块相同大小的空间_array = (DataType*)malloc(sizeof(DataType) * st._capacity);if (nullptr == _array){perror("fail malloc");exit(-1);}memcpy(_array, st._array, sizeof(DataType) * st._size);		//将栈中的内容按字节一一拷贝过去_size = st._size;_capacity = st._capacity;}// // 赋值重载//Stack& operator=(const Stack& st)//{//	if (this != &st)//	{//		memcpy(_array, st._array, sizeof(DataType) * st._size);		//将栈中的内容按字节一一拷贝过去//		_size = st._size;//		_capacity = st._capacity;//	}//	return *this;//}void Push(const DataType& data){// 扩容..._array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}// 析构函数~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _capacity;size_t _size;
};int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}
  • 不出所料,程序出现了奔溃,如果你认真看了上面内容的话,就能回忆起上面Stack在进行浅拷贝时出现的问题,和这里的报错是一模一样的

  •  我们知道,对于浅拷贝来说是就是一个字节一个字节直接拷贝,和拷贝构造不同的是,两个对象是已经实例化出来了的,_array都指向了一块独立的空间,但是在赋值之后,s1和s2的_array还是指向了同一块空间。此时便会造成两个问题
  • 因为它们是同一指向,所以在析构的时候就会造成二次析构
  • 原本s2中的_array所申请出来的空间没有释放会导致内存泄漏

 还是一个画个图来分析一下 

📚所以还是一样,当果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现,所以遇到涉及到资源管理的类,就需要自行实现  赋值运算符重载

Stack& operator=(const Stack& st){if (this != &st){memcpy(_array, st._array, sizeof(DataType) * st._size);		//将栈中的内容按字节一一拷贝过去_size = st._size;_capacity = st._capacity;}return *this;}

 ③ 值运算符不能重载成全局函数!

  • 如下,可以看到我将赋值重载运算符放到了类外来定义,编译一下发现报出了错误,这是为什么呢?其实编译器已经给我们写得很明确了,对于operator=也就是赋值重载只能写在类内,不可以写在类外,但一定有同学还是会疑惑为什么要这样规定,且听我娓娓道来~

 拿一些权威性的东西来看看,以下是《C++ primer》中的原话

三、总结

 【总结一下】:

  • 本文我们介绍了两样东西,一个是运算符重载,一个是赋值运算符重载
  • 对于运算符重载来说,我们认识到了一个新的关键字operator,使用这个关键字再配合一些运算符封装成为一个函数,便可以实现对自定义类型的种种运算,也加深巩固了我们对前面所学知识的掌握
  • 对于赋值运算符重载而言,就是对赋值运算符=进行重载,分析了一些它的有关语法使用特性以及注意事项,也通过调试观察到了它原来和默认拷贝构造的调用机制是一样的,毕竟大家同为天选之子。但是也要区分二者的使用场景,不要混淆了

 四、共勉

  以下就是我对 赋值运算符重载 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对C++ 的理解请持续关注我哦!!!  

 

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

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

相关文章

centos 7.9 nginx本地化安装,把镜像改成阿里云

1.把centos7.9系统切换到阿里云的镜像源 1.1.先备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup1.2.下载新的CentOS-Base.repo配置文件 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo特别…

本地web项目启起来后,无法在浏览器(chrome)看到源码,从而无法打断点;Framework Ignore list

问题描述 本地web项目启起来后&#xff0c;无法在浏览器(chrome)看到源码&#xff0c;从而无法打断点 其他浏览器没看&#xff0c;开发环境一致专注于chrome&#xff08;其余浏览器有测试同事提缺陷了&#xff0c;才会去看&#xff09;&#xff0c;其余浏览器有没有这个问题&…

linux 内存寻址

&#xff08;持续更新&#xff09; 相关概念 查看的书籍为 深入linux内核 内存地址 当使用80x86&#xff08;32位&#xff09;微处理器时&#xff0c;一般分为三种不同的地址&#xff1a; 逻辑地址 包含在机器语言指令中用来指定一个操作数或一条指令的地址。每一个逻辑地址…

C#简单工厂模式的实现

using System.Diagnostics.Metrics; using System.Runtime.InteropServices; using static 手写工厂模式.Program;namespace 手写工厂模式 {internal class Program{public interface eats {void eat();}//定义了一个接口public class rice : eats{public void eat() {Console.…

neo4j-01

Neo4j是&#xff1a; 开源的&#xff08;社区版开源免费&#xff09;无模式&#xff08;不用预设数据的格式&#xff0c;数据更加灵活&#xff09;noSQL&#xff08;非关系型数据库&#xff0c;数据更易拓展&#xff09;图数据库&#xff08;使用图这种数据结构作为数据存储方…

Flutter第七弹 网格列表GridView

1) Flutter提供了网格列表&#xff0c;怎么设置列数&#xff1f; 2&#xff09;怎么初始化每个列表项Item&#xff1f; 一、GridView简介 Flutter也存在网格列表组建GridView&#xff0c;用于展示多行多列的列表。 1.1 GridView构建 采用GridView.count() 进行构建 1.2 Gr…

HarmonyOS实战开发-自定义分享

介绍 自定义分享主要是发送方将文本&#xff0c;链接&#xff0c;图片三种类型分享给三方应用,同时能够在三方应用中展示。本示例使用数据请求 实现网络资源的获取&#xff0c;使用屏幕截屏 实现屏幕的截取&#xff0c;使用文件管理 实现对文件&#xff0c;文件目录的管理&…

5.9 mybatis之callSettersOnNulls作用

文章目录 1. 当callSettersOnNullstrue时2. 当callSettersOnNullsfalse时 在mybatis的settings配置参数中有个callSettersOnNulls参数&#xff0c;官方解释为&#xff1a;指定当结果集中值为 null 的时候是否调用映射对象的 setter&#xff08;map 对象时为 put&#xff09;方法…

Collection与数据结构 二叉树(三):二叉树精选OJ例题(下)

1.二叉树的分层遍历 OJ链接 上面这道题是分层式的层序遍历,每一层有哪些结点都很明确,我们先想一想普通的层序遍历怎么做 /*** 层序遍历* param root*/public void levelOrder1(Node root){Queue<Node> queue new LinkedList<>();queue.offer(root);while (!qu…

Geeker-Admin:基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架

Geeker-Admin&#xff1a;基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架 一、引言 随着技术的不断发展&#xff0c;前端开发领域也在不断演变。为了满足现代应用程序的需求&#xff0c;开发人员需要使用最新、最强大的工具和技术。Geeker-Admin正是…

Ubuntu 22上安装Anaconda3。下载、安装、验证详细教程

在Ubuntu 22上安装Anaconda3&#xff0c;你可以遵循以下步骤&#xff1a; 更新系统存储库&#xff1a; 打开终端并运行以下命令来更新系统存储库&#xff1a; sudo apt update安装curl包&#xff1a; 下载Anaconda安装脚本通常需要使用curl工具。如果系统中没有安装curl&#x…

流媒体的安全谁来保障

流媒体的安全谁来保障 说起媒体&#xff0c;我们马上就会想到报纸新闻、广播、电视。 其实所谓的流媒体同我们通常所指的媒体是不一样的&#xff0c; 它只是一个技术名词。流媒体到底是什么&#xff1f;能给我们的生活带来什么&#xff1f;跟小德一起来看看。 流媒体是什么&a…

OSI七层网络模型 —— 筑梦之路

在信息技术领域&#xff0c;OSI七层模型是一个经典的网络通信框架&#xff0c;它将网络通信分为七个层次&#xff0c;每一层都有其独特的功能和作用。为了帮助记忆这七个层次&#xff0c;有一个巧妙的方法&#xff1a;将每个层次的英文单词首字母组合起来&#xff0c;形成了一句…

REINFORCE及进阶算法讲解笔记

REINFORCE 总结 估计VALUE-methods没有在理论上证明收敛&#xff0c;而policy-methods不需要估计value function。 本算法总结了过去的算法&#xff0c;将过去算法作为特例看待&#xff0c;证明了即使是结合函数估计和实际采样的value梯度都可以无偏估计&#xff0c;证明了某种…

PC-lint 学习之配置方法

1. 下载PC-lint 9.0后&#xff0c;点击pclint9setup.exe进行安装&#xff08;我只安装了C/C语言&#xff0c;其他语言可安装时选择&#xff09; 2.安装完成后&#xff0c;打开keil5&#xff0c;选择配置 3. 配置选项 &#xff08;1&#xff09;Lint Executable&#xff1a;在第…

知识图谱与人工智能:携手共进

知识图谱与人工智能&#xff1a;携手共进 一、引言&#xff1a;知识图谱与人工智能的融合 在这个数据驱动的时代&#xff0c;知识图谱与人工智能&#xff08;AI&#xff09;之间的融合不仅是技术发展的必然趋势&#xff0c;也是推动各行各业创新的关键。知识图谱&#xff0c;作…

docker 上达梦导入dump文件报错:本地编码:PG GBK,导入女件编码:PGGB18030

解决方案&#xff1a; 第一步进入达梦数据容器内部 docker exec -it fc316f88caff /bin/bash 第二步&#xff1a;在容器中 /opt/dmdbms/bin目录下 执行命令 cd /opt/dmdbms/bin./dimp USERIDSYSDBA/SYSDBA001 FILE/opt/dmdbms/ZFJG_LJ20240407.dmp SCHEMASZFJG_LJUSERIDSYSD…

Guava里一些比较常用的工具

随着java版本的更新提供了越来越多的语法和工具来简化日常开发&#xff0c;但是我们一般用的比较早的版本所以体验不到。这时就用到了guava这个包。guava提供了很多方便的工具方法&#xff0c;solar框架就依赖了guava的16.0.1版本&#xff0c;这里稍微介绍下。 一、集合工具类…

深度学习图像处理基础工具——opencv 实战2 文档扫描OCR

输入一个文档&#xff0c;怎么进行文档扫描&#xff0c;输出扫描后的图片呢&#xff1f; 今天学习了 opencv实战项目 文档扫描OCR 问题重构&#xff1a;输入图像 是一个含有文档的图像——> 目标是将其转化为 规则的扫描图片 那么怎么实现呢&#xff1f; 问题分解&#…

CSS快速入门

目录 一、CSS介绍 1、什么是CSS&#xff1f; ​编辑2、基本语法规范 3、引入方式 4、规范 二、CSS选择器 1、标签选择器 2、类&#xff08;class&#xff09;选择器 3、id选择器 4、通配符选择器 5、复合选择器 三、常用CSS 1、color 2、font-size 3、border 4…