【C++】—— 类和对象(中)

【C++】—— 类和对象(中)

文章目录

  • 【C++】—— 类和对象(中)
    • 前言
    • 1. 类的默认成员函数
    • 2. 构造函数
    • 3. 析构函数
    • 4. 拷贝构造函数
    • 5. 赋值运算符重载
      • 5.1 运算符重载
      • 5.2 赋值运算符重载
    • 结语

前言

小伙伴们大家好呀,昨天的 【C++】——类和对象(上) 大家理解的怎么样了

今天的内容难度会上升一大截,大家需要做好准备哦

1. 类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。⼀个类,我们不写的情况下编译器会默认生成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不重要,我们稍微了解⼀下即可。其次就是C++11以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习:

  1. 我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求
  2. 编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现

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

在这里插入图片描述

我们的学习重点是在前4个,即构造函数,析构函数,拷贝构造函数和赋值运算符重载

2. 构造函数

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

我们就拿之前写过的日期类来举个构造函数的例子

class Date
{
public:Date()// 无参构造函数{}Date(int year = 1, int month = 1, int day = 1)// 带参构造函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;// 年int _month;// 月int _day;// 日
};

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

构造函数的特征

  1. 函数名与类名相同
  2. 无返回值(即不需要写void)
  3. 对象实例化时编译器会自动调用对应的构造函数
  4. 构造函数可以重载可以写多个,即可以定义多种初始化方式

下面是构造函数的调用的几种方式

void TestDate()
{
Date d1; // 调用无参构造函数 (注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明)// Date d1();// error
Date d2(2024, 4, 10); // 调用带参的构造函数
}
  1. 之前说过,如果我们没有写,则C++编译器会自动生成一个默认构造函数(这个构造函数是不需要传参的),反之,如果我们写了,编译器将不再生成

但是,一般情况下我们最好自己去写这个构造函数,因为编译器自动生成的这个默认构造函数的初始化做的并不是很好,比如下面这个代码,我们可以看一下它的初始化:

class Date
{
public:// 如果我们显式定义了构造函数,编译器将不再生成void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数Date d1;// 但是没有初始化,成员变量都是随机值!!!return 0;
}

在这里插入图片描述

  1. 关于编译器生成的默认构造函数,我们可能会有疑惑,既然编译器自动生成的这个默认构造函数的初始化做的并不是很好,还是要自己去写这个构造函数,为什么会有这个默认构造函数呢,对象调用了编译器生成的默认构造函数,但是对象 _year/_month/_day ,依旧是随机值,也就说在这里编译器生成的默认构造函数并没有什么用

我们要知道的是,C++把类型分成内置类型和自定义类型

举个例子:

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 内置类型int _year;int _month;int _day;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

image-20241004145131878

我们发现编译器生成默认的构造函数:

  1. 对于内置类型成员变量,它没有处理(其实在C++标准中没有规定要不要做处理,所以有些编译器会处理,有些不会处理)

  2. 对于自定义类型成员变量才会调用他的默认构造函数

再来看看一个两个栈实现队列的例子:

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}// ...~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};// 两个Stack实现队列
class MyQueue
{
public:// 编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化~MyQueue(){cout << "~MyQueue()" << endl;}
private:Stack pushst;Stack popst;
};int main()
{MyQueue mq;//Stack st1;//Stack st2;return 0;
}

编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化

大多数情况实现构造函数都需要我们自己实现(传参构造,内置类型的构造),少数情况类像MyQueue且Stack有默认构造可用和析构,可以不用写,但是构造函数大家应写尽写

这个构造函数不初始化的缺陷造成的种种复杂,所以在C++11 中,针对内置类型成员不初始化的缺陷,这里打了补丁

即:内置类型成员变量在类中声明时可以给默认值(缺省值)

还是举个例子:

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 这里的内置类型成员变量在类中声明时我们自己给了缺省值int _year = 2024;int _month = 10;int _day = 4;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

在这里插入图片描述

  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数,我们可以看出,这些函数都是不传参就可以调用的

像这样:

class Date
{
public:// 1.无参构造函数/*Date(){_year = 1;_month = 1;_day = 1;}*/// 2.带参构造函数/*Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/// 3.全缺省构造函数/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};

无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认⽣成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调用的构造就叫默认构造

小结

  1. 一般情况下,构造函数都需要我们自己显式的去实现
  2. 只有少数的情况下可以让编译器自动生成构造函数(比如两个栈实现队列 MyQueue,成员全是自定义类型)

3. 析构函数

析构函数的定义:析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能,而像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的

析构函数负责对象指向资源的清理,如果对象没有指向资源则不用写析构函数

栈帧销毁后类对象一同销毁,但其指针指向的资源没释放,会造成内存泄漏这时就需要析构函数在栈帧销毁前清理资源

析构函数的特征

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

  2. 无参数无返回值类型(这里的无参就印证的下面的析构函数不能重载的特性,而无返回值这点与上面的构造函数一致)

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数,析构函数不能重载

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

  5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?这里直接说结论,自动生成的析构函数做的事与构造函数类似:

    • 对于内置类型成员变量,它没有规定要不要处理
    • 对于自定义类型成员变量才会调用他的默认析构函数

举个例子:

class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 内置类型int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

代码运行结果如下:

在这里插入图片描述

那这个代码我们在Data里没有写析构函数,为什么会调用Time的析构呢

因为这里的 _year, _month和 _day 三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可,而 _t 是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数

  1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类,有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类,跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,其实也不用处理,因为栈帧销毁后会一起销毁。自定类型成员会调用他的析构函数

还是举一个两个栈实现队列的例子:

#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}// ...~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};// 两个Stack实现队列
class MyQueue
{
public:// 编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化// 编译器默认生成MyQueue的析构函数调用了Stack的析构,释放的Stack内部的资源// 显示写析构,也会自动调用Stack的析构~MyQueue(){cout << "~MyQueue()" << endl;}
private:Stack pushst;Stack popst;
};int main()
{MyQueue mq;//Stack st1;//Stack st2;return 0;
}
  • 自定义类型:没有写析构,生成默认析构,默认析构调用白定义类型的析构
  • 内置类型:写了析构调用显示的析构,没写生成默认析构,默认析构对自定义类型不做处理,函数栈帧结束后自动销毁

小结:

  1. 有资源需要显式清理,就需要写析构,如 Stack

  2. 有两种场景不需要显式写析构,默认生成即可

  • 没有资源需要清理,如:Date

  • 内置类型成员没有资源需要清理,剩下的都是自定义类型成员 ,如 : 两个栈实现队列MyQueue

4. 拷贝构造函数

拷贝构造函数的定义:如果⼀个构造函数的第⼀个参数是自⾝类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数

所以我们就可以采用拷贝构造函数的方式创建一个与已存在对象一某一样的新对象

拷贝构造函数的特征

  1. 拷贝构造函数是构造函数的一个重载形式(它是一种特殊的构造)
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,如果使用传值方式编译器直接报错,因为这会引发无穷递归调用
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date d) // 错误写法:编译报错,会引发无穷递归Date(const Date& d)  // 正确写法{_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 10, 4);d1.Print();//下面两种写法是等价的Date d2(d1);//拷贝构造d2.Print();Date d3 = d1;//拷贝构造也可以这样写d3.Print();
}

为什么必须时拷贝构造函数的参数必须用引用呢?不用引用可以吗?

在这里插入图片描述

传值传参:C++规定传值传参必须调用拷贝构造,所以又会调用拷贝构造构造形参,又因为拷贝构造是传值传参这样就会一直调用拷贝构造去构造形参也就形成无限递归,所以传值传参拷贝构造又会调用拷贝构造就相当于递归,并且递归没有结束条件

传引用传参:因为是传引用传参就是给d取别名,所以没有形成拷贝构造也就不会引发无穷递归

正是因为在语法逻辑上这里会形成无穷递归,所以拷贝的参数必须带引用,我讲清楚了吗

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

举个例子:

class Time
{
public:Time(){_hour = 0;_minute = 0;_second = 0;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 内置类型int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d1;Date d2(d1);// 因为Date类并没有显式定义拷贝构造函数,这里编译器会生成一个默认的拷贝构造函数return 0;
}

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

  1. 那就会有小伙伴问了,编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

让我们来看下面的代码:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

代码运行结果如下:

在这里插入图片描述

哎,系统怎么崩掉了

这里的问题就与上面我们说的浅拷贝联系上了,因为它只有值拷贝,数组会指向跟之前一样的空间,所以最后它会析构两次导致崩溃

想要解决也挺简单的,就是要用深拷贝,深拷贝就是要开一个跟之前一样大的空间,有了两个空间,析构两次也就不会崩了

5. 赋值运算符重载

5.1 运算符重载

想要讲清楚赋值运算符重载,我们首先要了解 一下什么是运算符重载

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

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

函数原型:返回值类型 operator操作符

需要注意的是

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. *(点星) , :: , sizeof ,?: ,. (星) 注意以上5个运算符不能重载

还是那我们熟知的日期类举例子:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& d2)// =={return _year == d2._year&& _month == d2._month&& _day == d2._day;}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;}return false;}bool operator >= (const Date& d)// >={return *this > d || *this == d;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 1);Date d2(2024, 4, 16);cout << (d1 == d2) << endl;cout << (d1 > d2) << endl;cout << (d1 >= d2) << endl;return 0;
}

代码运行结果如下:
在这里插入图片描述

在所有的比较大小的函数,我们已经实现的 == >>= , 像 < , <=!= 也差不多

现在我们就来思考一下,实现日期+天数呢,又该怎么实现呢

int GetMonthDay(int year, int month)//获取这个月的天数
{int monthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}else{return monthDayArray[month];}
}Date& operator+=(int day)
{_day += day;while (_day >= GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}

这里我们思考一下这个代码能不能实现我们要求的逻辑

我们会发现我们是不是实现的 日期+=日期的逻辑,为什么这样说呢,我们要实现 日期+日期 的逻辑的话,this指针是不是不应该改变,所以我们就应该这样写

Date operator+(int day)
{Date tmp(*this);tmp += day;// 复用+= return tmp;
}

实现 日期+日期 的逻辑就可以复用 += 的逻辑,使代码变得更简洁,更简单

5.2 赋值运算符重载

赋值运算符重载的定义:赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象

在形式上,我们得先与第4点讲的拷贝构造做一个对比

int main()
{Date d1;Date d2(2024, 10, 4);Date d3(d2);// 拷贝构造(初始化) d1 = d2;// 赋值重载(复制拷贝)    return 0;
}

通过上面的比较,我们不难看出,赋值重载和拷贝构造的区别

  • 赋值重载 : 已经存在两个对象之间拷贝
  • 拷贝构造 : 一个初始化另一个马上要创建的对象

举个例子:

Date& operator=(const Date& d)
{_year = d.year;_month = d.month;_day = d.day;return *this;
}

在这里插入图片描述

但是如果我们不写,用编译器默认的赋值运算符重载又会发生什么?

这里我们需要知道一个结论:如果类中未涉及到资源管理,赋值运算符是否实现都可以:(比如日期类),一旦涉及到资源管理则必须要实现(比如栈类),是不是与前面讲的默认成员函数很类似

小结:

  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数
  2. 引用返回可以提高效率,有返回值目的是为了支持连续赋值场景
  3. 没有显式实现时,编译器会自动生成⼀个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对定义类型成员变量会调用他的拷贝构造

结语

  • 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载
  • 像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)
  • 像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载

这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要

这就是今天类和对象的内容,感觉是不是有些难的,所以大家就好好理解

好了,感谢你能看到这里,溜了溜了,我们下期再见吧

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

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

相关文章

网约班车升级手机端退票

背景 作为老古董程序员&#xff0c;不&#xff0c;应该叫互联网人员&#xff0c;因为我现在做的所有的事情&#xff0c;都是处于爱好&#xff0c;更多的时间是在和各行各业的朋友聊市场&#xff0c;聊需求&#xff0c;聊怎么通过IT互联网 改变实体行业的现状&#xff0c;准确的…

卡码网KamaCoder 53. 寻宝

题目来源&#xff1a;53. 寻宝&#xff08;第七期模拟笔试&#xff09; C题解&#xff08;来源代码随想录&#xff09;&#xff1a;最小生成树 prim prim三部曲 第一步&#xff0c;选距离生成树最近节点第二步&#xff0c;最近节点加入生成树第三步&#xff0c;更新非生成树节…

随时随地,轻松翻译:英汉互译软件的便捷之旅

翻译英汉互译工具&#xff0c;就如同一位随时待命的语言助手&#xff0c;在这纷繁复杂的语言世界中为我们搭建起理解与沟通的桥梁。接下来&#xff0c;让我们一同深入了解这些神奇的英汉互译工具&#xff0c;探索它的诸多功能和独特魅力。 1.福晰在线翻译 链接直达>>h…

Python案例--三数排序

一、引言 在信息爆炸的时代&#xff0c;我们每天都会接触到大量的数据。无论是工作中的报表、学习中的数据集&#xff0c;还是日常生活中的购物清单&#xff0c;数据的有序性对于提高效率和决策质量都至关重要。排序算法作为数据处理的基础工具&#xff0c;其重要性不言而喻。…

RTSP协议讲解

1.RTSP协议 rtsp&#xff0c;英文全称 Real Time Streaming Protocol&#xff0c;RFC2326&#xff0c;实时流传输协议&#xff0c;是 TCP/IP 协议体系中的一个应用层协议。 RTSP 交互流程 1&#xff09;OPTIONS C--->S 客户端向服务器端发现 OPTIONS&#xff0c;请求可用…

netty之SpringBoot+Netty+Elasticsearch收集日志信息数据存储

前言 将大量的业务以及用户行为数据存储起来用于分析处理&#xff0c;但是由于数据量较大且需要具备可分析功能所以将数据存储到文件系统更为合理。尤其是一些互联网高并发级应用&#xff0c;往往数据库都采用分库分表设计&#xff0c;那么将这些分散的数据通过binlog汇总到一个…

Go基础学习11-测试工具gomock和monkey的使用

文章目录 基础回顾MockMock是什么安装gomockMock使用1. 创建user.go源文件2. 使用mockgen生成对应的Mock文件3. 使用mockgen命令生成后在对应包mock下可以查看生成的mock文件4. 编写测试代码5. 运行代码并查看输出 GomonkeyGomonkey优势安装使用对函数进行monkey对结构体中方法…

SQL专项练习第二天

在数据处理和分析中&#xff0c;Hive 是一个强大的工具。本文将通过五个 Hive 相关的问题展示其在不同场景下的应用技巧。 先在home文件夹下建一个hivedata文件夹&#xff0c;把我们所需的数据写成txt文件导入到/home/hivedata/文件夹下面。 一、找出连续活跃 3 天及以上的用户…

茄子病虫害数据集。四类:果肉腐烂、蛀虫、健康、黄斑病。4000张图片,已经按照8:2的比例划分好训练集、验证集 txt格式 含类别yaml文件 已经标注好

茄子病虫害数据集。可用于筛选茄子品质、质量&#xff0c;训练采摘机器人视觉算法模型……数据集大部分图片来源于真实果园拍摄的图片&#xff08;生长在果树之上的&#xff09;&#xff0c;图片分辨率高&#xff0c;数据集分为四类&#xff1a;果肉腐烂、蛀虫、健康、黄斑病。…

Pandas数据分析基础

目录标题 Pandas读取和写入数据数据读取读取csv读取excel数据输出 Pandas基础操作索引数据信息统计计算位置计算数据选择 Pandas高级操作复杂查询类型转换数据排序添加修改高级过滤数据迭代高阶函数 Pandas读取和写入数据 Pandas将数据加载到DataFrame后&#xff0c;就可以使用…

算法知识点————贪心

贪心&#xff1a;只考虑局部最优解&#xff0c;不考虑全部最优解。有时候得不到最优解。 DP&#xff1a;考虑全局最优解。DP的特点&#xff1a;无后效性&#xff08;正在求解的时候不关心前面的解是怎么求的&#xff09;&#xff1b; 二者都是在求最优解的&#xff0c;都有最优…

TB6612电机驱动模块(STM32)

目录 一、介绍 二、模块原理 1.原理图 2.电机驱动原理 三、程序设计 main.c文件 Motor.h文件 Motor.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 TB6612FNG 是东芝半导体公司生产的一款直流电机驱动器件&#xff0c;它具有大电流 MOSFET-H 桥结构&#xff…

【每天学个新注解】Day 15 Lombok注解简解(十四)—@UtilityClass、@Helper

UtilityClass 生成工具类的注解 将一个类通过注解变成一个工具类&#xff0c;并没有什么用&#xff0c;本来代码中的工具类数量就极为有限&#xff0c;并不能达到减少重复代码的目的 1、如何使用 加在需要委托将其变为工具类的普通类上。 2、代码示例 例&#xff1a; Uti…

(C语言贪吃蛇)9.贪吃蛇撞墙找死

目录 游戏说明​ 1.撞墙死翘翘的情况 2.如何解决初始化问题 封装函数initSnake(); 注意事项 解决方法 总结 效果演示 游戏说明 玩家通过上下左右按键来控制小蛇的移动&#xff0c;我们之前的内容完成了小蛇每按下一次右键小蛇便向右移动一格&#xff0c;但是玩贪吃蛇一…

vue-live2d看板娘集成方案设计使用教程

文章目录 前言v1.1.x版本&#xff1a;vue集成看板娘&#xff08;暂不使用&#xff0c;在v1.2.x已替换&#xff09;集成看板娘实现看板娘拖拽效果方案资源备份存储 当前最新调研&#xff1a;2024.10.2开源方案1&#xff1a;OhMyLive2D&#xff08;推荐&#xff09;开源方案2&…

Spring Boot中线程池使用

说明&#xff1a;在一些场景&#xff0c;如导入数据&#xff0c;批量插入数据库&#xff0c;使用常规方法&#xff0c;需要等待较长时间&#xff0c;而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先&#xff0c;创建一个Spr…

Agent 概念学习

Agent 概念学习 什么是 Agent OpenAI的研究员 Lilian 写过一篇博客:《 LLM Powered Autonomous Agents》&#xff0c;将 Agents 定义为&#xff1a;LLM memory planning skills tool use&#xff0c;即大语言模型、记忆、任务规划、工具使用的集合。 Overview of a LLM-…

多模态—图文匹配

可能最近大家已经发现了chatgpt可以根据自己的描述生成图片&#xff0c;其实这就是一个图文匹配的问题&#xff0c;可以理解为这是一个多模态的问题。 在模型训练时我们需要N个图片和N个文本对进行训练&#xff0c;文本通过text encoder形成文本语义向量&#xff0c;text enco…

930/105每日一题

算法 1 4,2,9,11, 4, 2,4 2,4,9 42 4 24 9 2&#xff08;0&#xff09; 4&#xff08;1&#xff09; 9&#xff08;2&#xff09; 11&#xff08;3&#xff09; 11&#xff08;0&#xff09;11&#xff08;1&#xff09; 9&#xff08;2&#xff09; 11&#xff08;3&#xf…

C++之多态篇(超详细版)

1.多态概念 多态就是多种形态&#xff0c;表示去完成某个行为时&#xff0c;当不同的人去完成时会有不同的形态&#xff0c;举个例子在车站买票&#xff0c;可以分为学生票&#xff0c;普通票&#xff0c;军人票&#xff0c;每种票的价格是不一样的&#xff0c;当你是不同的身…