【C++】深度解析--拷贝构造函数(从0开始,详解浅拷贝到深拷贝,小白一看就懂!!!)

目录

一、前言

 二、拷贝构造函数

🍎概念解析

🥝特性解析

 💦为什么拷贝构造函数使用传值方式会引发无穷递归调用?

 💦为什么拷贝构造函数的形参中要加入 const 修饰

 💦若未显式定义,编译器会生成默认的拷贝构造函数吗?

 💦【浅拷贝】与【深拷贝】

 💦总结

🍇 产生拷贝构造的三种形式

1.当用类的对象去初始化同类的另一个对象时 

2.当函数的形参是类的对象,调用函数进行形参和实参结合时 

3.当函数的返回值是对象,函数执行完成返回调用者时

三、拷贝构造函数的总结

四、共勉 


一、前言

        在我们前面学习的中,我们会定义成员变量成员函数,这些我们自己定义的函数都是普通的成员函数,但是如若我们定义的类里什么也没有呢?是真的里面啥也没吗?如下:

class Date {};

        如果一个中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的任何一个类在我们不写的情况下,都会自动生成6个默认成员函数。
【默认成员函数概念】:用户没有显式实现,编译器会生成的成员函数称为默认成员函数

 ⭐其中上次的博客已经详细的讲解了构造函数&&析构函数的使用方法,所以本次博客将继续深度的讲解拷贝构造函数

 二、拷贝构造函数

 🍎概念解析

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎👫

 那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

 答案是,当然可以的啦,这也就牵扯出了我们所要学习的 -----------拷贝构造函数


 【拷贝构造函数概念】:只有单个形参该形参是对本 类 类型对象 的引用(一般常用const修饰),在用已存在的类 类型对象创建新对象时由编译器自动调用


 【代码举例】:日期类

class Date
{
public:Date(int year = 2024 ,int month = 3 ,int day = 13)    // 构造函数{_year = year;_month = month;_day = day;}Date(const Date& d)      // 拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}void print(){cout << "今天的日期是 :" << endl;cout << _year << '-' << _month << '-' << _day << endl;}~Date()                        // 析构函数{_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.print();// 创建一个与已存在对象一某一样的新对象Date d2(d1);   // 拷贝构造d2.print();return 0;
}

  【运行结果】:

🥝特性解析

拷贝构造函数也是特殊的成员函数,其特征如下:

1️⃣: 拷贝构造函数是构造函数的一个重载形式

2️⃣: 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

3️⃣: 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的

 💦为什么拷贝构造函数使用传值方式会引发无穷递归调用?

首先我们来看如下代码:

//全缺省构造函数
Date(int y = 2000, int m = 1, int d = 1)
{_year = y;_month = m;_day = d;
}
//拷贝构造函数
Date(Date d)
{_year = d._year;_month = d._month;_day = d._day;
}
int main(void)
{Date d1;Date d2(d1);	//调用形式return 0;
}
  • 上面这个Date(Date d)指的就是拷贝构造函数Date d2(d1);便是它的调用形式,用已经定义出来的对象 d1 来初始化 d2
  • 但是我们编译一下却看到报出了错误,说【Date类的复制构造函数不能带有Date类】,这是为什么呢?

  • 但此时若是我将形参的部分加上一个引用&就可以编过了,这是为什么呢?

可能上面的这种形式过于复杂了,我先用下面这两个函数调用的形式来进行讲解

  • 如果你看过我的C++引用详解这篇文章的话就可以知道对于Func1(d1)来说叫做【传值调用】,对于Func2(d2)来说叫做【传引用调用】

【注意】:

“值” 调用的时候, 形参是实参的拷贝,改变形参的值并不会影响外部实参的值。
         
“引用” 调用的时候,形参是实参的别名,共同拥有一个地址,改变形参的值,就相当于对实参本身进行操作。

两者之间的区别:“值” 调用  会比  传 “引用” 调用 中间多一步  拷贝的操作

如果对以上两个概念还不清楚的老铁可以去看看这两篇文章:
C语言的传值调用
C++的传引用调用

class Date
{
public:// 构造函数// 通常都会先 运行 构造函数 在运行 InitDate(int year = 2024,int month = 4,int day = 12){_year = 2024;_month = 4;_day = 12;}//拷贝构造函数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;
};
// 传值调用
void Func1(Date d1)
{cout << "Func1函数的调用" << endl;
}
// 传引用带哦用
void Func2(Date& d2)
{cout << "Func2函数的调用" << endl;
}int main()
{Date d;Func1(d);Func2(d);return 0;
}

  • 通过运行上述代码观察可以发现,Func1传值调用,会去调用Date类的拷贝构造函数,然后再调用本函数;但是Func2传引用调用,却直接调用了本函数
  • 这就源于我们之前讲过的,对于【传值调用】会产生一个临时拷贝,所以此时d1d的拷贝;对于【传引用调用】不会产生拷贝,此时d2d的别名

        所以,从上述代码我们可以得出一个结论:在传值调用时,形参中有【自定义类型】会调用拷贝构造,在传引用调用时,形参中有【自定义类型】不会调用拷贝构造

  •  所以为什么说使用传值方式编译器直接报错,因为会引发无穷递归调用?
  •  对于 自定义类型(自己定义的类型) 的 传值调用 来说都会去调用拷贝构造,那此时我们转换回Date类的拷贝构造函数这里。通过下面的这张图其实你可以看出自定义类型的传值调用引发的递归问题是多么严重!

  •  通过Date d2(d1)需要实例化对象d2,所以要调用对应的构造函数,也就是拷贝构造函数,但是在调用拷贝构造函数之前要先传参,那刚才说了【自定义类型传参调用】就会引发拷贝构造,那调用拷贝构造就又需要传参数进来,传参数又会引发拷贝构造。。。于是就引发了这么一个无限递归的问题
  •  所以编译器就规定了对于拷贝构造这一块的参数不可以是【传值传参】,而要写成下面这种【传引用传参】的形式。此时d就是d1的别名,那因为是d2去调用的拷贝构造,此时this指针所接收的便是d2的地址,初始化的即为d2的成员变量

 所以 在写 拷贝构造的时候  单个的形参,必须是对本类 类型对象的引用(&)。

 正确写法:

Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}Date d2(d1);

 💦为什么拷贝构造函数的形参中要加入 const 修饰

 面这种拷贝构造的形式并不是很规范,一般的拷贝构造函数都写成下面这种形式

Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
  • 那此时就会同学很疑惑,为什么要在前面加上一个const呢?这一点其实我们在strcpy中其实也有说到过,有的时候你可能会不小心把代码写成下面这样👇
Date(Date& d)
{// 写反了d._year = _year;d._month = _month;d._day = _day;
}
  • 但若是将const加上后,编译器便报出了错误❌

  • 因此可以看到,加上这个const之后,程序的安全性就得到了提升,这就是它的第一个作用①

 它还有第二点作用,我们再来看看

  • 我在实例化这个d1对象的时候在前面加上了一个const,此时这个对象就具有常属性,不可以被修改,然后此时再去使用d1对象初始化d2对象会发生什么呢?
int main(void)
{const Date d1;Date d2(d1);return 0;
}
  • 可以看到,编译器报出了错误,说【没有匹配的构造函数】,其实这里真正的问题还是在于权限放大,这点我在C++引用中也重点讲解过,如果不懂的同学去看一看。
  •   本来这个d1对象被const所修饰具有常性,但是呢在将其当做参数传入给一个不具有常性的对象接收时,那么在拷贝构造函数内部便可以去修改这个对象的内容,也就造成了问题。不要以为这种问题不会发生,我们在写程序的时候一定要严谨,尽可能地考虑到多种情况

  • 但是给形参加上const做修饰之后,便可以做到【权限保持】,此时程序的安全性又增加了↑

小结一下,对于const Date& d这种不是做输出型参数,加上前面的const的好处在于

① 防止误操作将原对象内容修改
② 防止传入const对象造成【权限放大】

💦若未显式定义,编译器会生成默认的拷贝构造函数吗?

 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

  • 此时我将上面所写的拷贝构造去除之后,再去进行一个拷贝的操作,通过下面的运行结果可以看出,d1和d2均完成了初始化操作,而且和构造函数一样,对于内置类型也会去进行处理。其实在这里就是调用了编译器默认为我们生成的拷贝构造
//以下为有参构造
Date(int year = 2000, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}

内置类型会处理,那自定义类型呢?也会处理吗?

  • 此时我在Date类中声明了一个Time类的对象作为成员函数,并且去除了Date类中上面所写的【拷贝构造函数】,然后再用d1去初始化d2,你认为此刻会发生什么呢?
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
public://构造..//析构
private:int _year;int _month;int _day;Time _t;	//内置自定义类型的成员
};
  • 通过调试观察可以发现,即使是Date类中没有写拷贝构造函数d2依旧是完成了初始化工作.这个Time类我们在说析构函数的时候有讲到过,那此时要去析构Date类中的自定义类型成员_t,便要调用Time类的析构函数,但是要先调用编译器为Date类自动生成的析构函数,然后再去调用Time类的析构函数,此时自动生成的析构函数就派上了用场【忘记了再翻上去看看】
  • 既然构造、析构都可以自动生成,那么拷贝构造作为类的默认成员函数编译器也是会自动为我们生成。那么此时就会调用默认生成的拷贝构造去拷贝其内部自定义类型_t的时候就会去调用Time类的显式拷贝构造完成初始化工作 

 因此对于像Date这种日期类来说,我们可以不用去自己去实现拷贝构造,编译器自动生成的就够用了,那其他类呢,像Stack这样的,我们继续来看看

 💦【浅拷贝】与【深拷贝】

  • 继续延用我们上面所讲到过的Stack,而且没有写上拷贝构造函数,首先实例化出对象st1,接下去便通过st1去初始化st2,通过上面的学习可以知道会去调用编译器自动生成的【拷贝构造】来完成,不过真的可以完成吗?我们来运行一下试试💻
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;//}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 st1;st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);Stack st2(st1);return 0;
}

 运行结果出现报错:

  • 其实,根本的原因就是在于我们要使用到数组栈,便要去内存中开辟一块空间,那么s1开辟了一块空间后_array就指向堆中的这块内存地址,接着s2去拷贝了s1,里面的数据是都拷贝过来了,但是s2的_array也指向了堆中的这块空间

 s1 和 s2 指向了同一个空间,这是错误的。

  • 那此时我去往s1里面push数据的之后,s2再去push,就会造成【数据覆盖的情况】。假设现在s1push了【1】、【2】、【3】,那么它的size就是3,但是s1与s2二者的size是独立的,不会影响,所以此时s2的size还是0,再去push【4】、【5】、【6】的话还是会从0的位置开始插入,也这就造成了覆盖的情况

不仅如此,二者指向同一块数据空间还会造成其他的问题 

  • 现在定义出来两个Stack对象,那此时我想问谁会先去进行析构呢?

  • 揭晓一下,s2会先去析构,在C/C++内存分布一文中我们有讲到过【栈区】是里面的一个区域,原理都清楚是先进后出的,所以后实例化出的对象s2会先去进行一个析构的操作,接着再去析构对象s1。不过呢通过调试可以观察到s1和s2的_array都指向堆中的同一块空间,因此当s2去调用析构函数释放了这块空间后,那么s1对象的_array就已经是一个野指针了,指向了堆中的一块随机地址,那再去对这块空间进行析构的话就会出现问题⚠

👉所以来总结一下指向同一块空间的问题 

  1. 插入删除数据会互相影响
  2. 析构两次会造成程序的奔溃

 那要如何去解决这个问题呢?此时就要涉及到【深拷贝】了

 💬调用编译器自动为我们生成的拷贝构造函数去进行拷贝的时候会造成【浅拷贝】的问题,那什么又叫做深拷贝呢?

  •  因为浅拷贝是原封不动地拷贝,会使得两个指针指向同一块空间,那若是我们再去自己申请一块空间来使用,让两个对象具有不同的空间,此时便不会造成上面的问题了

 接下去我就来实现一下如何去进行【深拷贝】

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;
}

  • 而且两块空间是独立的,所以在对象进行析构的时候也不会造成二次析构的问题

 💬 但是这样自己去写拷贝构造感觉很麻烦诶,哪些类需要这样去深拷贝呢?

  • 你可以观察在当前这这个类中是否存在显式的析构函数,若是存在的话,表示当前这个类涉及资源管理了【资源管理指得就是去堆中申请空间了】,此时你一定要自己去是实现拷贝构造以达到一个深拷贝;若是不涉及资源管理的话,直接使用编译器自动生成的进行浅拷贝就可以了
  •  像Date日期类这种只存在【年】、【月】、【日】这种内置类型的浅拷贝就可以了;像是复杂一些的,例如:链表、二叉树、哈希表这些都会涉及资源的管理,就要考虑到深拷贝了

经过上面的【浅拷贝】与 【深拷贝】 的深度解析,我们再来写一个字符串类,练练手

class MyString {
public:// 默认构造函数MyString(const char* str = "winter"){_str = (char*)malloc(sizeof(char) * (strlen(str) + 1));if (_str == nullptr){perror("malloc fail!");exit(-1);}memcpy(_str, str, sizeof(char) * (strlen(str) + 1));}// 析构函数~MyString(){cout << "~String()" << endl;free(_str);}void MyPrintf(){cout << _str << endl;//printf("%s\n", _str);}private:char* _str;
};int main()
{MyString s1("hello C++");MyString s2(s1);s1.MyPrintf();cout << endl;s2.MyPrintf();cout << endl;
}

 如图:指向了同一块空间

那么会引发什么问题呢?会导致 _str 指向的空间被释放两次,引发程序崩溃。

加入深入拷贝构造函数: 

// 拷贝构造函数MyString(const MyString& s){//给新对象申请一段和原对象一样大小的空间_str = (char*)malloc(sizeof(char) * (strlen(s._str) + 1));if (_str == nullptr){perror("malloc fail!");exit(-1);}//把原对象的数据一一拷贝给新对象memcpy(_str, s._str, sizeof(char) * (strlen(s._str) + 1));}

 💦总结

⭐总结: 

1️⃣:你可以观察在当前这这个类中是否存在显式的析构函数,若是存在的话,表示当前这个类涉及资源管理了【资源管理指得就是去堆中申请空间了】,此时你一定要自己去是实现拷贝构造以达到一个深拷贝;若是不涉及资源管理的话,直接使用编译器自动生成的进行浅拷贝就可以了

2️⃣: 像Date日期类这种只存在【年】、【月】、【日】这种内置类型的浅拷贝就可以了;像是复杂一些的,例如:链表、二叉树、哈希表这些都会涉及资源的管理,就要考虑到深拷贝了

🍇 产生拷贝构造的三种形式

深刻理解了拷贝构造之后,我们再来看看产生拷贝构造的三种形式 

1.当用类的对象去初始化同类的另一个对象时 

Date d1;
Date d2(d1);
Date d3 = d2;	//也会调用拷贝构造

 在实例化对象d2和d3的时候都去调用了拷贝构造,最后它们初始化后的结果都是一样的

2.当函数的形参是类的对象,调用函数进行形参和实参结合时 

void func(Date d)	//形参是类的对象
{d.Print();
}int main(void)
{Date d1;func(d1);	//传参引发拷贝构造return 0;
}

函数func()的形参是类的对象,此时在外界调用这个函数并传入对应的参数时,就会引发拷贝构造, 

3.当函数的返回值是对象,函数执行完成返回调用者时

Date func2()
{Date d(2023, 3, 24);return d;
}int main(void)
{Date d1 = func2();d1.Print();return 0;
}

 可以看到,这一种方式也会引发拷贝构造,当函数内部返回一个Date类的对象时,此时外界再使用Date类型的对象去接收时,就会引发拷贝构造。 

三、拷贝构造函数的总结

 ✨总结:

1. 拷贝构造算是六大默认成员函数中较难理解的了。主要就是要理清【内置类型】和【自定义类型】是否会调用拷贝构造的机制。还有在实现这个拷贝构造时要主要的两点:一个就是在形参部分要进行引用接收,否则会造成无穷递归的现象;还有一点就是在前面加上const进行修饰,可以防止误操作和权限放大的问题
2. 一般的类,自己生成拷贝构造就够用了,只有像Stack这样自己直接管理资源的类,需要自己实现深拷贝。

四、共勉 

以下就是我对 拷贝构造函数 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对C++的理解请持续关注我哦!!!  

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

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

相关文章

支持向量机模型

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个支持向量机模型程序,最后打印5个条件分别的影响力。 示例一 为了计算每个条件对一件事情发生的影响力&#xff0c…

word从零基础到高手【办公】

第1课 - word基础操作快速入门第2课 - 让你效率10倍提升的快捷操作第3课 - word排版快速入门第4课 - 排版实战案例讲解第5课 - 搞定论文排版全过程第6课 - 让你的word更强大的神技第7课 - 提高工作效率必备的批量操作 资料截图如下: 发送: "word办公" 获取提取码

代码随想录算法训练营DAY25|C++回溯算法Part.2|216. 组合总和III、17.电话号码的字母组合

文章目录 216. 组合总和III题意理解树形结构伪代码实现剪枝操作CPP代码实现 17.电话号码的字母组合解题思路树形结构伪代码实现隐藏回溯CPP代码 216. 组合总和III 力扣题目链接 文章讲解&#xff1a;216. 组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算…

数据库(2)

目录 6.buffer pool,redo log buffer和undo logo&#xff0c;redo logo,bin log概念以及关系&#xff1f; 7.从准备更新一条数据到事务的提交的流程描述&#xff1f; 8.能说下myisam和innodb的区别吗&#xff1f; 9.说下MySQL的索引有哪些吧&#xff1f; 10.什么是B树&…

C语言-详解内存函数

文章目录 1.memcpy使用和模拟实现1.1 memcpy函数的使用规则1.2 memcpy函数的使用1.2 模拟实现memcpy函数 2.memmove 函数的使用和模拟实现2.1 memmove 函数使用规则2.2 memmove函数的使用2.3 模拟实现memmove函数2.3.1 从后往前移2.3.2 从前往后移 2.4 算法实现2.4.1 从前往后移…

使用docker制作Android镜像(实操可用)

一、安装包准备 1、准备jdk 下载地址&#xff1a;Java Downloads | Oracle 注意版本&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 参考下面的 对照表&#xff0c;不然后面构建镜像报错&#xff0c;就是版本不对。 我就是因为下载的jdk17&…

边缘计算平台原理、关键功能以及技术优势

1、什么是边缘计算及其工作原理&#xff1f; 边缘计算是一种分布式计算模型&#xff0c;它将数据处理和存储靠近数据源头和最终用户的边缘设备上&#xff0c;从而减少了数据传输和延迟。边缘计算旨在解决云计算模型所面临的问题&#xff0c;例如延迟高、带宽瓶颈和安全性等问题…

虚幻引擎架构自动化及蓝图编辑器高级开发进修班

课程名称&#xff1a;虚幻引擎架构自动化及蓝图编辑器高级开发进修班 课程介绍 大家好 我们即将推出一套课程 自动化系统开发。 自动化技术在项目开发的前中后期都大量运用。如何您是一家游戏公司&#xff0c;做的是网络游戏&#xff0c;是不是经常会遇到程序员打包加部署需…

Energia学习案例

案例一&#xff1a;编写程序&#xff0c;实现每次按下按键&#xff0c;红色LED灯改变状态&#xff08;初始点亮&#xff09;&#xff0c;在窗口监视窗中显示按击次数。[要求用计时器实现按键消抖] #include"Timer.h" //包含计时器头文件volatile int stateHIGH; //灯…

防止邮箱发信泄露服务器IP教程

使用QQ邮箱,网易邮箱,189邮箱,新浪邮箱,139邮箱可能会泄露自己的服务器IP。 泄露原理&#xff1a;服务器通过请求登录SMTP邮箱服务器接口&#xff0c;对指定的收件人发送信息。 建议大家使用商业版的邮箱&#xff0c;比如阿里云邮箱发信等 防止邮件发信漏源主要关注的是确保邮件…

蓝桥杯练习系统(算法训练)ALGO-954 逗志芃的暴走

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 逗志芃是有妹子的现充&#xff0c;但是有时候妹子就是烦恼。因为逗志芃太逗了&#xff0c;所以这段时间妹子对逗志芃发动了…

【Vue + keep-alive】路由缓存

一. 需求 列表页&#xff0c;n 条数据项可打开 n 个标签页&#xff0c;同时1条数据项的查看和编辑共用一个标签页。如下所示&#xff1a; 参考 // 主页面 // 解决因 路由缓存&#xff0c;导致 编辑后跳转到该页面 不能实时更新数据 onActivated(() > {getList() })二. 实现…

JAVA 4

这次我学习了第四次Java课程 Math #include<math.h> 数学运算 Math.main 随机数 double aMath.random(); System.out.println(a);对小数处理 double a 3.6415; System.out.println("Math.floor: " Math.floor(a));//向下最近的整数 System.out.println(&…

wpf下如何实现超低延迟的RTMP或RTSP播放

技术背景 我们在做Windows平台RTMP和RTSP播放模块对接的时候&#xff0c;有开发者需要在wpf下调用&#xff0c;如果要在wpf下使用&#xff0c;只需要参考C#的对接demo即可&#xff0c;唯一不同的是&#xff0c;视频流数据显示的话&#xff0c;要么通过控件模式&#xff0c;要么…

【黑马头条】-day09用户行为-精度丢失-点赞收藏关注

文章目录 1 long类型精度丢失问题1.1 解决1.2 导入jackson序列化工具1.3 自定义注解1.4 原理1.5 测试 2 用户行为要求3 创建微服务behavior3.1 微服务创建3.2 添加启动类3.3 创建bootstrap.yml3.4 在nacos中配置redis3.5 引入redis依赖3.6 更新minio 4 跳过 1 long类型精度丢失…

小红的白色字符串

题目描述 小红拿到了一个字符串&#xff0c;她准备将一些字母变成白色&#xff0c;变成白色的字母看上去就和空格一样&#xff0c;这样字符串就变成了一些单词。 现在小红希望&#xff0c;每个单词都满足以下两种情况中的一种&#xff1a; 1.开头第一个大写&#xff0c;其余为…

01、ArcGIS For JavaScript 4.29对3DTiles数据的支持

综述 Cesium从1.99版本开始支持I3S服务的加载&#xff0c;到目前位置&#xff0c;已经支持I3S的倾斜模型、3D Object模型以及属性查询的支持。Cesium1.115又对I3S标准的Building数据实现了加载支持。而ArcGIS之前一直没有跨越对3DTiles数据的支持&#xff0c;所以在一些开发过…

抖音滑块验证码加密的盐的位置

最近更新后之前很容易找到盐的位置的方法变了&#xff0c;抖音特意把盐隐藏起来了 {"reply": "RJC","models": "yAd8rl","in_modal": "DTn0nD2","in_slide": "ou7H0Ngda","move": …

Vue2(十五):replace属性、编程式路由导航、缓存路由组件、路由组件独有钩子、路由守卫、history与hash

一、router-link的replace属性 1、作用&#xff1a;控制路由跳转时操作浏览器历史记录的模式 2、浏览器的历史记录有两种写入方式&#xff1a;分别为push和replace&#xff0c;push是追加历史记录&#xff0c;replace是替换当前记录。路由跳转时候默认为push 3、如何开启repla…

环信 IM 客户端将适配鸿蒙 HarmonyOS

自华为推出了自主研发操作系统鸿蒙 HarmonyOS 后&#xff0c;国内许多应用软件开始陆续全面兼容和接入鸿蒙操作系统。环信 IM 客户端计划将全面适配统鸿蒙 HarmonyOS &#xff0c;助力开发者快速实现社交娱乐、语聊房、在线教育、智能硬件、社交电商、在线金融、线上医疗等广泛…