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

news2024/10/5 18:24:05

目录

一、前言

 二、拷贝构造函数

🍎概念解析

🥝特性解析

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

 💦为什么拷贝构造函数的形参中要加入 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:
	
	// 构造函数
	// 通常都会先 运行 构造函数 在运行 Init
	Date(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.coloradmin.cn/o/1602459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c语言,单链表的实现----------有全代码!!!!

1.单链表的定义和结构 单链表是一种链式的数据结构&#xff0c;它用一组不连续的储存单元存反线性表中的数据元素。链表中的数据是以节点的形式来表示的&#xff0c;节点和节点之间相互连接 一般来说节点有两部分组成 1.数据域 &#xff1a;数据域用来存储各种类型的数据&…

深入理解同步与异步编程及协程管理在Python中的应用

文章目录 1. 同步与异步函数的对比1.1 同步函数1.2 异步函数1.3 对比 2. 管理多个协程与异常处理2.1 并发执行多个协程2.2 错误处理2.3 任务取消 本文将探索Python中同步与异步编程的基本概念及其区别。还会详细介绍如何使用asyncio库来有效管理协程&#xff0c;包括任务的创建…

一文读懂uniapp中的tabBar底部导航

目录 1. 基本知识2. Demo 1. 基本知识 UniApp 中的 tabBar 是用来在应用程序底部显示可切换的选项卡的组件&#xff0c;通常用于实现底部导航栏 允许用户通过点击不同的选项卡来切换应用程序的不同页面或功能模块 其代码如下&#xff1a; "tabBar":{"color&q…

UOS系统-mips架构---Java环境安装

平时都是在windows系统上安装的java环境&#xff0c;今天需要在uos系统安装java1.8的环境&#xff0c;记录一下安装过程。 &#xff08;以下均在root权限下运行&#xff09; 一、查找java1.8 jdk版本 apt search openjdkopenjdk-8-jdk/未知,未知 1.8.0.212-2deepin mips64el O…

车载摄像头画质增强解决方案,赋能智能驾驶新时代

在智能化浪潮席卷汽车产业的今天&#xff0c;车载摄像头作为智能驾驶的“眼睛”&#xff0c;其画质清晰度直接关系到车辆感知环境的准确性和驾驶的安全性。然而&#xff0c;面对复杂多变的行车环境&#xff0c;如何确保车载摄像头在不同场景下都能呈现出高质量的图像&#xff0…

分布式调度平台xxl-job

1.xxl-job介绍 xxl-job 是一个轻量级分布式任务调度框架&#xff0c;支持动态添加、修改、删除定时任务&#xff0c;支持海量任务分片执行&#xff0c;支持任务执行日志在线查看和分页查询&#xff0c;同时支持任务失败告警和重试机制&#xff0c;支持分布式部署和高可用。xxl…

一文弄懂Seaborn绘制热力图

1. 引言 在本文中&#xff0c;我们将使用Seaborn库来以heatmap热力图的形式来表示数据。我们将重点介绍如何创建它&#xff0c;以及如何更改其颜色&#xff0c;调整对应字体大小等等。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. 什么是热力图&#xff1f; Heatma…

Python 数学应用(四)

原文&#xff1a;zh.annas-archive.org/md5/123a7612a4e578f6816d36f968cfec22 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十一章&#xff1a;其他主题 在本章中&#xff0c;我们将讨论一些在本书前几章中没有涉及的主题。这些主题大多涉及不同的计算方式以及优…

向量数据库与图数据库:理解它们的区别

作者&#xff1a;Elastic Platform Team 大数据管理不仅仅是尽可能存储更多的数据。它关乎能够识别有意义的见解、发现隐藏的模式&#xff0c;并做出明智的决策。这种对高级分析的追求一直是数据建模和存储解决方案创新的驱动力&#xff0c;远远超出了传统关系数据库。 这些创…

C代码编译过程与进程内存分布

C代码编译过程 在这篇文章中&#xff0c;我们将探讨C语言代码的编译流程以及进程在运行时的内存布局。编译过程通常包括几个关键步骤&#xff1a;预处理、编译、汇编和链接。 预处理阶段主要是处理源代码文件中的宏定义、头文件包含和条件编译指令。在此阶段&#xff0c;编译…

ping命令的使用

一、实验环境 同实验案例分析ARP解析过程环境。 二、需求描述 熟悉 ping 命令的用法并熱悉 ping 命令的各种参数 三、推荐步骤 分别 ping 一个存在的和不存在的IP地址&#xff0c;观察返回的信息分别测试 ping 命令的相关参数。 四、实验步骤 1.ping 一个存在的和不存在…

工业电脑在ESOP工作站行业应用

ESOP工作站行业应用 项目背景 E-SOP是实现作业指导书电子化&#xff0c;并统一管理和集中控制的一套管理信息平台。信迈科技的ESOP终端是一款体积小巧功能齐全的高性价比工业电脑&#xff0c;上层通过网络与MES系统连接&#xff0c;下层连接显示器展示作业指导书。ESOP控制终…

FPGA - ZYNQ 基于EMIO的PS和PL交互

前言&#xff1a; Xilinx ZYNQ系列的芯片&#xff0c;GPIO分为 MIO 、EMIO、AXI_GPIO三种方式。 MIO &#xff1a;固定管脚&#xff0c;属于PS端&#xff0c;也就是ARM端。 EMIO &#xff1a;通过PL扩展&#xff0c;使用时需要分配PL(FPGA)管脚&#xff0c;消耗PL端资源。…

redis-plus-plus的安装与使用

文章目录 一、安装第一步&#xff1a;安装hiredis第二步&#xff1a;安装redis-plus-plus第三步&#xff1a;将编译后的可执行文件移动到/usr/local对应目录第四步&#xff1a;更新动态库 二、使用第一步&#xff1a;编写示例代码第二步&#xff1a;编译运行 本文参考自 redis-…

Pytest测试用例中的mark用法(包含代码示例与使用场景详解)

在软件开发中&#xff0c;测试是确保代码质量和功能稳定性的重要环节。Python作为一门流行的编程语言&#xff0c;拥有丰富的测试工具和框架&#xff0c;其中pytest是其中之一。pytest提供了丰富的功能来简化测试用例的编写&#xff0c;其中的mark功能允许我们对测试用例进行标…

Pytest精通指南(16)利用skip、skipif跳过用例执行

文章目录 前言skip源码分析skip装饰方法skip装饰类skip装饰模块skipif源码分析skipif装饰方法skipif装饰类skipif装饰模块拓展-用例内部跳过执行 前言 skip: skip用于无条件地跳过测试用例&#xff0c;无论测试环境的状态或条件如何。通常用于那些在任何情况下都不应该执行的测…

idea使用plantuml插件报错(类图):Dot Executable: /opt/local/bin/dot

报错提示&#xff1a; 解决方式&#xff1a; 方式一: 直接设置Remote Rendering即可 &#xff08;使用服务器地址&#xff09; 无特殊要求可直接使用默认提供的服务地址&#xff0c;也可自行搭建服务替换地址。 自行搭建服务可参考&#xff1a; 在本地Windows 11 系统的桌面…

分布式调度器

xxl-job介绍 xxl-job 是一个轻量级分布式任务调度框架&#xff0c;支持动态添加、修改、删除定时任务&#xff0c;支持海量任务分片执行&#xff0c;支持任务执行日志在线查看和分页查询&#xff0c;同时支持任务失败告警和重试机制&#xff0c;支持分布式部署和高可用。xxl-j…

中文编程入门(Lua5.4.6中文版)第十三章 Lua 文件操作

在《Lua世界》的冒险旅途中&#xff0c;勇士们时常需要与神秘的文本卷轴打交道。为了更好地掌握这些知识宝藏&#xff0c;Lua I/O库提供了两种强大的探索模式&#xff1a;简单模式和完全模式&#xff0c;助你轻松应对各类文献挑战。 简单模式&#xff1a;初识卷轴 简单模式如…

结构体及应用;结构体指针及应用;union、enum、typedef三个关键字

结构体及应用 参考文章链接&#xff1a;https://blog.csdn.net/zw1996/article/details/53844585结构体的声明 结构体的初始化 注意如果在定义结构体变量的时候没有初始化&#xff0c;那么后面就不能全部一起初始化了。 /这样是可以的&#xff0c;在定义变量的时候就初始化了…