c++初阶——类和对象(中)

news2024/12/23 15:06:30

大家好,我是小锋,我们今天继续来学习类和对象。

类的6个默认成员函数

我们想一想如果一个类什么都没有那它就是一个空类,但是空类真的什么都没有吗?

其实并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

我们下面来分别学习

构造函数

概念

我们先来写一个日期类

class date {
public:
	void  init(int yare = 1, int month = 1, int day = 1) {
		_yare = yare;
		_month = month;
		_day = day;
	}
	void Printf() {
		cout << _yare << "/" << _month << "/" << _day << "/" << endl;
	}
private:
	int _yare;
	int _month;
	int _day;
};
int main() {
	date add;
	add.init(2024, 4, 20);
	add.Printf();

	date arr;
	arr.init();
	arr.Printf();


	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

这里就可以用到我们的构造函数了。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

构造函数的特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。

其特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

# include<iostream>
using namespace std;


class date {
public:
	date(int yare = 0, int month = 0, int day = 0) {
		_yare = yare;
		_month = month;
		_day = day;
	}

	void Printf() {
		cout << _yare << "/" << _month << "/" << _day << "/" << endl;
	}
private:
	int _yare;
	int _month;
	int _day;
};
int main() {
	//调用无参构造函数
	date add;
	add.Printf();
	//调用带参数的构造函数
	date arr(2024, 4, 20);
	arr.Printf();
	return 0;
}

我们还要注意一个问题

相当于我们没有定义app的类,而是做了一个函数声明。

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?对象调用了编译器生成的默认构造函数,但是对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

编译器生成的默认构造函数对内置类型不处理,只处理自定义类型

class Time {
public:
	Time(int hour = 0, int minute = 0) {
		cout << "time(int hour = 0, int minute = 0)" << endl;
		_hour = hour;
		_minute = minute;
	}
private:
	int _hour;
	int _minute;
};
class date {
public:
	date(int yare = 0, int month = 0, int day = 0) {
		cout << "date(int yare = 0, int month = 0, int day = 0)" << endl;
		_yare = yare;
		_month = month;
		_day = day;
	}
	void Printf() {
		cout << _yare << "/" << _month << "/" << _day << "/" << endl;
	}
private:
	int _yare;
	int _month;
	int _day;
	Time _a;
};
int main() {
	date add;
	return 0;
}

从图中我们可以看到对于自定义类型Time编译器自动生成了构造函数。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在 类中声明时可以给默认值。

意思是我们可以这样初始化

这样编译器生成的默认构造函数就会对内置类型进行处理了

绝大部分的类要自己写构造函数,只有自定义类型的对象不用写构造函数,编译器会自己调用默认的构造函数。

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

析构函数

概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数的特性

析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(同样的编译器自动生成的析构函数只对自定义类型处理

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

我们简单来写个栈

# include<assert.h>
class stack {
public:
	stack(int n=4) {
		int* cur = (int*)malloc(sizeof(int) * n);
		if (cur == nullptr) {
			perror("空间开辟失败");
			return;
		}
		_a = cur;
		_size = 0;
		_capacity = n;
	}
	void push(int mon) {
		if (_size == _capacity) {
			int* cur = (int*)realloc(_a, sizeof(int) * _capacity * 2);
			if (cur == nullptr) {
				perror("空间开辟失败");
				return;
			}
			_a = cur;
			_capacity *= 2;
		}
		_a[_size] = mon;
		_size++;
	}
	void Printf() {
		for (int j = 0; j < _size; j++) {
			cout << _a[j] << endl;
		}
	}
	bool Empty() {
		return _size == 0;
	}
	void pop() {
		assert(!Empty());
		 _size--;
	}
	~stack() {
		free(_a);
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};


int main() {
	stack head;
	head.push(1);
	head.push(2);
	head.push(3);
	head.push(4);
	head.push(5);
	head.pop();
	head.Printf();
	return 0;
}

大家看是不是程序结束是编译器自动调用析构函数

关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

编译器自动生成的默认析构函数对自定义类型对象的处理

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏。

拷贝构造函数

概念

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

同学们我们想想拷贝构造函数的意义何在,我们在学习c语言时也没用拷贝构造啊?

我们来探讨一下大家跟紧我的思路

# include<iostream>
using namespace std;

class date {
public:

	date(int year=1, int month=1, int day=1) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Printf() {
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	date d1;
	d1.Printf();
	date d2(2024, 4, 23);
	d2.Printf();
	return 0;
}

我们还是简单的来实现一个日期类

大家看我们这里定义了一个函数fund1在传参时是不是发生了拷贝这里是简单的值拷贝。

我们发现这里都是内置类型的拷贝,那么自定义类型可以这样拷贝吗?

我们来简单实现一个栈来演示

class stack {
public:
	stack(int n=4) {
		datapety* cur = (datapety*)malloc(sizeof(datapety) * n);
		if (cur == nullptr) {
			perror("空间开辟失败");
			return;
		}
		_add = cur;
		_size = 0;
		_capacity = n;
	}
	void push(int n) {
		if (_size == _capacity) {
			datapety* cur = (datapety*)realloc(_add,sizeof(datapety) * _capacity * 2);
			if (cur == nullptr) {
				perror("扩容失败");
					return;
			}
			_add = cur;
			_capacity *= 2;
		}
		_add[_size] = n;
		_size++;
	}
	bool Empty() {
		return _size == 0;
	}


	void pop() {
		assert(!Empty());
		_size--;
	}
	void Printf() {
		for (int i = 0; i < _size; i++) {
			cout << _add[i];
		}
		cout << endl;
	}

	~stack() {
		free(_add);
		_add = nullptr;
		_size = 0;
		_capacity = 0;
	}

private:
	datapety* _add;
	int _size;
	int _capacity;

};

当我们这样拷贝时程序崩溃了

那这是为什么呢?

这里崩溃的原因是空间连续销毁了两次,我们来分析一下我们的代码

大家看我们在调用fund2时是采用值拷贝,将实参的值拷贝给形参,_add指针存放的是一个指向动态开辟空间的地址,在值拷贝时,将值拷贝,当fund2函数结束时要销毁栈帧,就会将_add指针指向的空间一起销毁,然后当我们的程序走完后又会自动调用析构函数,又会将这块空间再次free一次,所以这里程序崩溃了。

那我们这里应该怎么办呢?

这里就要用到我们学过的引用了

大家看这样程序是不是就可以这次运行并结束了?

但是我们引用又会造成一个问题

大家看我们这里对a进行push,a1也会改变,但是我们不行让它改变啊,我们应该这么做,

那就要用到我们的拷贝构造函数进行深拷贝了。

stack(stack& a) {
		_add = (datapety*)malloc(sizeof(datapety) * a._capacity);
		if (_add == nullptr) {
			perror("空间开辟失败");
			return;
		}
		for (int i = 0; i < a._size; i++) {
			_add[i] = a._add[i];
		}
		_size = a._size;
		_capacity = a._capacity;
	}

这样我们的a1和a就是两个互不关联但存储的数据相同的两个类了

特征

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

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用(这里可以加一个const防止我们在写拷贝构造的函数时不小心修改了原对象),使用传值方式编译器直接报错, 因为会引发无穷递归调用。(这里编译器是强制报错的)

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

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

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

赋值运算符重载

引出运算符重载

大家可以看到内置类型可以直接使用运算符,进行运算比较,那自定义类型可以直接比较吗?

很显然是不行的

我们应该写一个比较函数来进行比较

我们拿日期类来举例

我们写好函数后发现x1,x2无法访问到类的成员变量,这是因为我们在定义成员变量时是限定私有的,我们只要改成公有的或者直接把比较函数改成类的成员函数就解决了。

这里,我们还有一个问题,我们的函数名时拼音,可读性很差,改成其他的名称也不是很好,所以为了规范,我们c++祖师爷引入了运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

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

注意:

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

2,重载操作符必须有一个类类型参数

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

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

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

6,不能改变操作符的操作数个数(就是有几个操作数重载时就有几个参数)

我们对上面代码更改后就变成了下面的样子

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

这里我们的参数用了const,是因为比较大小是不用改变对象的值的,用引用是在传参时不用进行拷贝提高效率。

# include<iostream>
using namespace std;

class date {
public:
	date(int year = 1, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Printf() {
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	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;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	date d1(2018, 7, 9);
	date d2(2024, 4, 24);
	cout << (d1 < d2) << endl;

}

这里我们是不是就比较成功了?

接下来我们来看看赋值运算符重载是怎么样的

我们看这里我们定义了两个日期类,我们现在想把d2赋值给d1,应该怎么赋值,首先这里是自定义类型的赋值,看到不能直接用=进行赋值,所以我们这里就要用到我们刚刚学的运算符重载,对赋值运算符进行重载

date& operator=(const date & d) {
			if (this != &d) {
				_year = d._year;
				_month = d._month;
				_day = d._day;
			}
		}

大家对比是不是发现了这个赋值运算符重载函数与拷贝构造函数简直就是一模一样,实现的思路也是一样的,那它们两个函数的区别是什么呢?

首先赋值是两个已经存在的对象进行拷贝,而拷贝构造是一个已经存在的对象对另一个要创建的对象进行初始化。

赋值运算符主要有四点:

1. 参数类型

2. 返回值

3. 检测是否自己给自己赋值

4. 返回*this

5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个默认的赋值函数,完成对象按字节序的值拷贝。

如果编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?

答案当然是一定的,我们在拷贝构造函数时已经讲过了,如果只进行值拷贝,这是一种浅拷贝对于那些动态开辟的空间的内容是拷贝不了的,在退出函数时就会多次调用析构函数。

我们来完成一个完整的日期类,

日期类的实现

检验一下大家的学习成果

还是老规矩,我们还是用三个文件来分装不同的内容

date.h

#pragma once

# include<iostream>

using namespace std;

class date{
public:
	//构造函数
	date(int year = 1, int month = 1, int day = 1);
	//打印日期
	void Printf();
	//重载<
	bool operator<(const date& d);
	//重载==
	bool operator==(const date& d);
	//重载<=
	bool operator<=(const date& d);
	//重载>
	bool operator>(const date& d);
	//重载>=
	bool operator>=(const date& d);
	//重载!=
	bool operator!=(const date& d);

	//判断这个月有多少天
	int GetMonthDay(const int year, const int month);
	//重载+=
	date& operator+=(int day);
	//重载+
	date operator+(int day);
	//重载-=
	date& operator-=(int day);
	//重载-
	date operator-(int day);
	//重载前置++
	date& operator++();
	//重载后置++
	date& operator++(int);
	//重载前置--
	date& operator--();
	//重载后置--
	date& operator--(int);


private:
	int _year;
	int _month;
	int _day;
};

date.c

# include"date.h"


//构造函数
date::date(int year, int month, int day) {
	_year = year;
	_month = month;
	_day = day;
	//检查日期是否合法
	if ((month < 1 || month>12) || (day<1 || day>GetMonthDay(year, month))) {
		cout << "非法日期" << endl;
	}
}

//打印日期
void date::Printf() {

	cout << _year << "年" << _month << "月" << _day << "日" << endl;

}

//重载<
bool date::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 date::operator==(const date& d) {
	if (_year == d._year && _month == d._month && _day == d._day) {
		return true;
	}
	else {
		return false;
	}
}

//重载<=
bool date::operator<=(const date& d) {
	return (*this < d) || (*this == d);

}

//重载>
bool date::operator>(const date& d) {
	return !(*this <= d);
}

//重载>=
bool date::operator>=(const date& d) {
	return !(*this < d);

}

//重载!=
bool date::operator!=(const date& d) {
	return !(*this == d);
}


//判断这个月有多少天
int date::GetMonthDay(const int year, const int month) {
	int add[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31, };
	int n = add[month];
	if (month == 2 && ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
		n = 29;
	}
	return n;
}

//重载+=
date& date::operator+=(int day) {
	if (day < 0) {
		return *this -= (-day);
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month)) {
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13) {
			_year++;
			_month = 1;
		}
	}
	return *this;
}

//重载+
date date::operator+(int day) {
	date cmp (*this);
	cmp += day;
	return cmp;
}

//重载-=
date& date::operator-=(int day) {
	if (day < 0) {
		return *this += (-day);
	}
	_day -= day;
	while (_day <= 0) {
		_month--;
		if (_month == 0) {
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//重载-
date date::operator-(int day) {
	date tmp(*this);
	tmp -= day;
	return tmp;
}

//重载前置++
date& date::operator++() {
	*this += 1;
	return *this;
}
//重载后置++
date& date::operator++(int) {
	date cmp(*this);
	*this += 1;
	return cmp;
}
//重载前置--
date& date::operator--() {
	*this -= 1;
	return *this;
}
//重载后置--
date& date::operator--(int) {
	date tmp(*this);
	*this -= 1;
	return  tmp;
}

test.c

# include"date.h"


int main() {
	date d1(2023, 2, 23);
	date d2(2024, 4, 24);
	d1.Printf();
	d2.Printf();
	cout << (d1 < d2) << endl;
	cout << (d1 <= d2) << endl;
	cout << (d1 > d2) << endl;
	cout << (d1 >= d2) << endl;
	cout << (d1 == d2) << endl;
	cout << (d1 != d2) << endl;
	d1 += 10;
	d1.Printf();
	d1 -= 10;
	d1.Printf();
	d2 = d1 + 10;
	d2.Printf();
	d2 = d1 - 10;
	d2.Printf();
	++d2;
	d2.Printf();
	d1 = d2++;
	d1.Printf();
	d2.Printf();
	--d2;
	d2.Printf();
	d1 = d2--;
	d1.Printf();
	d2.Printf();


	return 0;
}

我们最后可以测试一下

这里主要讲一下前置++,后置++,以及前置--,后置--,在重载时默认是前置,后置则要在参数中加一个int参数,进行占位,跟前置构成函数重载进行区分,

日期-日期

我们要计算两个日期之间的天数就要用到这个函数

这里我们只要复用我们前面写的++函数就可以很简单搞定了

//日期相减
int date::operator - (const date& d) {
	date max = *this;
	date min = d;
	int fin = 1;
	if (min > max) {
		max = d;
		min = *this;
	}
	int n = 0;
	while (min != max) {
		min++;
		n++;
	}

	return n * fin;


}

我们测试一下

const成员

我们来看这段代码

这里我们用const修饰的d1对象无法调用类的成员函数,这是为什么呢?

我们知道类在调用成员函数时有一个this指针,它是指向调用函数的对象的,它的类型是date*的类型而我们调用函数的对象是从const date的类型,所以无法调用,那怎么才能调用呢?

肯定要将this指针的类型也用const 修饰,

接下来我们看看怎么修饰

const修饰类的成员函数

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

这样修改后我们就可以调用类的成员函数了

总结

1. const对象不可以调用非const成员函数

2. 非const对象可以调用const成员函数

3. const成员函数内不可以调用其它的非const成员函数

4. 非const成员函数内可以调用其它的const成员函数

取地址及const取地址操作符重载

我们知道理论上c++的操作符只能对内置类型进行处理对自定义类型是不进行处理的,但我们来看这样一段代码

我们发现这里的&操作符对自定义类型的对象取地址了,这就是编译器默认的取地址重载,取出的是对象本来的地址

它的实现类似于如下(有两版本一个是const 一个是非const两个函数形成重载)

这个函数绝大部分情况使用编译器自己生成就已经够用了,真要我们自己写,大部分是不想别人取到我们的地址,一般给空

我们还可以给一些随机的值

 以上就是全部内容了,如果有错误或者不足的地方欢迎大家给予建议。 

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

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

相关文章

JCE cannot authenticate the provider BC

前言&#xff1a; 公司项目有用AES加密的&#xff0c;报错原因是BC&#xff08;Bouncy Castle&#xff09;提供的加密服务时&#xff0c;JCE&#xff08;Java Cryptography Extension&#xff09;无法进行验证。这通常是由于 JCE 的默认策略文件不支持所需的加密算法&#xff…

北京车展“第一枪”:长安汽车发布全球首款量产可变新汽车

4月25日&#xff0c;万众瞩目的2024北京国际汽车展览会在中国国际展览中心如期而至。作为中国乃至全球汽车行业的盛宴&#xff0c;本次车展也吸引了无数业内人士的高度关注。 此次北京车展以“新时代 新汽车”为主题&#xff0c;汇聚了1500余家主流车企及零部件制造商&#xff…

最优化理论探析:函数优化与组合优化,约束优化与无约束优化的交织与应用

最优化理论作为运筹学、数学规划和工程科学中的核心研究领域&#xff0c;涵盖了函数优化、组合优化、约束优化和无约束优化等多个分支。这些理论在解决实际问题中发挥着重要作用&#xff0c;从资源分配、生产调度到网络路由、物流优化&#xff0c;再到机器学习、人工智能等领域…

DevOps(十四)怎么实现Gitlab更新后Jenkins自动发布

目录 1、在 Jenkins 中安装 GitLab 插件 2、在 GitLab 中创建一个访问令牌(Access Token) 3、在 Jenkins 中配置 GitLab 连接 4、在 Jenkins 中创建一个新的任务(Job) 5、在 GitLab 中配置 Webhook 6、以下是一些补充说明和建议 持续集成的一个特点就是开发可以随时提交&…

SRE运维和DevOps之间是什么关系?

一、SRE运维和DevOps之间是什么关系&#xff1f; SRE运维和DevOps之间存在紧密的联系和相互依赖。 首先&#xff0c;两者都是开发运维一体化时代的产物&#xff0c;旨在通过协同工作来构建高效可靠的软件运维团队。SRE&#xff08;Site Reliability Engineering&#x…

【UE5.1 C++】提升编译速度

步骤 1. 在“C:\Users\用户\AppData\Roaming\Unreal Engine\UnrealBuildTool”目录下找到“BuildConfiguration.xml”文件 打开“BuildConfiguration.xml”&#xff0c;添加如下部分内容 <?xml version"1.0" encoding"utf-8" ?> <Configuratio…

FastGPT编译前端界面,并将前端界面映射到Docker容器中

建议在linux系统下编译 1、克隆代码 git clone https://github.com/labring/FastGPT 2、进入FastGPT目录&#xff0c;执行 npm install 3、进入projects/app目录&#xff0c;执行 npm run dev 此时会自动下载依赖包&#xff0c;这里如果执行npm install的话&#xff0c;…

OS复习笔记ch4

引言 上一章&#xff0c;我们学习了进程的相关概念和知识&#xff0c;不知道小伙伴们的学习进度如何&#xff0c;没看的小伙伴记得去专栏看完哦。 线程从何而来 我们之前说过&#xff0c;进程是对程序运行过程的抽象&#xff0c;它的抽象程度是比较高的。 一个进程往往对应一…

LabVIEW专栏九、类的应用

一、类的应用 接上一章"类" 类在项目中&#xff0c;一般会在类的私有成员簇内&#xff0c;包含一个数据类型为参数类的队列。 例如网口类&#xff0c;里面实际会包含很多信息&#xff0c;有IP地址和端口等等参数。这些参数如果不放在队列引用中缓存下来&#xff0c;…

【热门前端【vue框架】】——vue框架和node.js的下载和安装保姆式教程

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;程序员-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

FPM 快速报表开发

背景&#xff1a; 使用FPM开发报表时&#xff0c;如果报表字段过多&#xff0c;页面拖拽等操作不方便 报表数量过多时&#xff0c;新建应用操作步骤较为繁琐 更习惯通过少量代码而非页面操作去实现功能 处理&#xff1a; 将FPM报表开发简化为类似GUI端ALV的开发过程:&#xff…

大模型微调之 使用 LLaMA-Factory 微调 Llama3

大模型微调之 使用 LLaMA-Factory 微调 Llama3 使用 LLaMA Factory 微调 Llama-3 中文对话模型 安装 LLaMA Factory 依赖 %cd /content/ %rm -rf LLaMA-Factory !git clone https://github.com/hiyouga/LLaMA-Factory.git %cd LLaMA-Factory %ls !pip install "unsloth…

力扣数据库题库学习(4.25日)

1484. 按日期分组销售产品 问题链接 思路与分析 编写解决方案找出每个日期、销售的不同产品的数量及其名称。 每个日期的销售产品名称应按词典序排列。 返回按 sell_date 排序的结果表。我来分析一下&#xff0c;这里的题目要求其实就是统计不同日期下的销售产品数&#xf…

excel文件导入dbeaver中文乱码

1.将excel文件进行另存为&#xff0c;保存类型选择【CSV】 2.选择【工具】–>【web选项】–> 【编码】–> 【简体中文&#xff08;GB18030&#xff09;】 3.在DBeaver进行数据导入 直接导入应该就可以&#xff0c;如果不行的话按下面处理。 选择【导入数据——选择cs…

【3GPP】【核心网】【4G】LTE中S1-MME流程字段分析(二)

1. 欢迎大家订阅和关注&#xff0c;精讲3GPP通信协议&#xff08;2G/3G/4G/5G/IMS&#xff09;知识点&#xff0c;专栏会持续更新中.....敬请期待&#xff01; 承接上文 目录 6. PDN Connectivity 7. PDN Disconnection 8. EPS Bearer resource allocation 9. EPS Bearer r…

Gin+WebSocket实战——在线聊天室WebSocketDemo详细使用教程

文章目录 仓库地址项目简介如何使用 仓库地址 Github&#xff1a;https://github.com/palp1tate/WebsocketDemo 欢迎star&#xff01;&#x1f60e; 项目简介 利用 GinWebSocket 实现的在线聊天室Demo项目&#xff0c;支持加入/离开聊天室广播、给其他用户发送消息等。 如何…

sqlplus / as sysdba登陆失败,(ORA-01017)

周一上班检查alert log&#xff0c;看到某个库报出大量的错误 提示无法连接到ASM实例&#xff0c;这是某知名MES厂商DBA创建的11G RAC刚刚​转交到我手上的&#xff0c;这又是给我挖了什么坑&#xff1f; 报错为ORA-01017​用户名密码不对&#xff1f;​what&#xff1f; 登陆o…

【线性代数 C++】求逆矩阵

对于 n n n阶矩阵 A A A&#xff0c;如果有 n n n阶矩阵 B B B&#xff0c;使 A B B A E ABBAE ABBAE&#xff0c;则说 A A A是可逆的&#xff0c;并把 B B B称为 A A A的逆矩阵. A A A的逆矩阵记作 A − 1 A^{-1} A−1&#xff0c;则 B A − 1 BA^{-1} BA−1.若 ∣ A ∣ ≠…

如何3分钟,快速开发一个新功能

背景 关于为什么做这个代码生成器&#xff0c;其实主要有两点: 参与的项目中有很多分析报表需要展示给业务部门&#xff0c;公司使用的商用产品&#xff0c;或多或少有些问题&#xff0c;这部分可能是历史选型导致的&#xff0c;这里撇开不不谈&#xff1b;项目里面也有很多C…

【白盒测试】单元测试的理论基础及用例设计技术(6种)详解

目录 &#x1f31e;前言 &#x1f3de;️1. 单元测试的理论基础 &#x1f30a;1.1 单元测试是什么 &#x1f30a;1.2 单元测试的好处 &#x1f30a;1.3 单元测试的要求 &#x1f30a;1.4 测试框架-Junit4的介绍 &#x1f30a;1.5 单元测试为什么要mock &#x1f3de;️…