C++ | (二)类与对象(上)

news2024/12/24 2:55:53

燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的假期为什么一去不复返呢?

目录

一、初识类

1.1 类的定义

1.2 C++中的struct 

1.3 类的访问限定符

1.4 类域

1.5 对类的对象的成员的访问方式

 1.6 this指针

1.7 实例化与对象大小

二、再探类

2.1 类的默认成员函数

2.2 构造函数

2.3 析构函数 

2.4 拷贝构造函数 

2.5  运算符重载

2.6 赋值运算符重载 

2.7 const成员函数


一、初识类

1.1 类的定义

C++中,有一个特殊的关键字,叫作"",它源自于C语言的结构体,但比结构体要更加高级,比如,在C语言中的结构体内不能定义函数,而C++中的类却可以。

类的定义的关键字为“class”,类的内部包括成员函数成员变量,它的基本结构如下:

class class_name
{
public:
    void Fun1()
    {
        //...
    }
    //...
private:
    int _a;
    int _b;
    //...    
};

与C语言的结构体类似,类的“}”后也需要加“ ;”,否则编译器会报错。

PS:在上面的举例中,你一定会对“public”与“private”产生好奇,它们是什么含义我们稍后再说,但现在请你记住一点:成员函数一般放在public内,而成员变量一般放在private中。

1.2 C++中的struct 

在C++中,结构体struct升级为“类”,也就是说,C++中的struct内可以定义函数,可以进行一系列在class中进行的操作。

1.3 类的访问限定符

在类中储存的内容多种多样,既有公共安全的,也有私密不想被外界访问的,如何将在同一个类的它们区分开,使外部对它们有不同的访问权限呢?访问限定符可以很好的解决这个问题。

访问限定符共有三种:private、protected、public。使用方式是:访问限定符 + ' : '

访问限定符的作用域是从自己起到下一访问限定符的出现,或者是遇到class的' } '结束。

一般来说,在类中,我们会将成员函数放在public(公共的)中,将成员变量放在private/protected(私有的/受保护的)中。

例1: 

class Example
{
public:
    void fun1()
    {
        //...
    }
    int fun2(int a,int b)
    {
        //...
    }
private:
    int _day;
    int _month;
    int _year;
};

这样做的结果就是,外界可以直接调用类里的成员函数,但无法直接访问类的成员变量,并对它进行修改。 

1.4 类域

类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义类的成员时,需要使用类名+ ::  (作用域操作符)以指明所定义的成员属于哪个类域。

例2:

#include <iostream>
using namespace std;
class Example 
{
public:
	void fun1();
	void Print() 
	{
		cout << _a <<"   " << _b << endl;
	}
private:
	int _a;
	int _b;
};
void Example::fun1() 
{
	_a = 1;
	_b = 1;
}
int main() 
{
	Example a;
	a.fun1();
	a.Print();
	return 0;
}

我们在类内对成员函数fun1进行了声明,但是在类外对成员函数fun1进行了定义,因而使用Example::对其加以限定。

1.5 对类的对象的成员的访问方式

与C语言中的结构体相同,有两种,一种是对象名 + ' . ' + 成员,一种是对象的地址 + ' -> ' + 成员

以例2中的对象a为例,想访问a中的fun1函数有两种形式:

	a.fun1();//1
	Example* pa = &a;
	pa->fun1();//2

 1.6 this指针

我们运行例2中的代码,运行结果为

这对于初学C++的人来说还是挺不可思议的,按照我们所学的C语言知识,fun1中的_a与_b应该与a中的_a与_b不是同一个变量,那么fun1对_a与_b修改就不会影响a中的成员变量_a与_b,但结果却是影响了,难道C++独树一帜,对C语言的语法进行大肆修改了?其实不然。

有上述疑问的人很正常,而有这样疑问的人恰恰证明你的C语言学的很扎实。

例2中的fun1与Print函数在编译器编译后的真正内容为:

void Example::fun1(Example* const this) 
{
	this->_a = 1;
	this->_b = 1;
}
void Print(Example* const this)
{
    cout << this->_a <<"   " << this->_b << endl;
}

也就是编译器之后给我们加上的内容。 

这样是不是就很熟悉了?这里的this指向的就是对象a。但是值得注意的是,在编译器中,不能在实参和形参的位置显示的写this指针,不要问为什么,这是规定。但是可以在函数体内显示使用this指针,所以上述内容应修改为:

void Example::fun1() 
{
	this->_a = 1;
	this->_b = 1;
}
void Print()
{
    cout << this->_a <<"   " << this->_b << endl;
}

这样编译就不会报错,且实现效果与例2一模一样。

我们从中也可以看出C++的优势,相对于C语言,C++省略了一些繁琐的地址传参,更加简洁。 

如果你充分了解了内部的机制,就不需要再在函数体内显示使用this指针,心里明白是为什么就可以了,该省劲的地方咱就省劲。

1.7 实例化与对象大小

用类类型在物理内存中创建对象的过程,称为 类实例化出对象。例如例2中类Example实例化出的对象a。类相当于工程图纸,而对象则是按照工程图纸建造出的建筑。

接着介绍如何对对象大小进行计算,首先,对象大小的计算仅包含其成员变量,因为成员函数在对象上的调用本质上是对该函数地址的调用,不需要每一个该类的对象都储存一个相同的函数,所以成员函数不占用对象所属的空间,而是存放在它处。至于成员变量,则是按照内存对齐规则进行计算,这里不再花费篇幅对内存对齐规则进行介绍,不懂的可以自行百度。

那么我们就可以计算得到例2中对象a的大小,为8字节。

到这里类的最基本的知识就介绍完毕了。

二、再探类

2.1 类的默认成员函数

什么是默认成员构造函数?默认成员构造函数就是用户没有显式实现,编译器自动生成的成员函数。一个类在未写成员函数的情况下编译器会自动生成六个默认成员函数,分别是构造函数,析构函数,拷贝构造函数,赋值重载函数,对普通对象取地址重载函数,对const对象取地址重载函数。最后两个函数很少会自己实现,所以我们先注重前四个默认成员函数,即构造函数、析构函数、拷贝构造函数、赋值重载函数。

默认成员函数的思维导图(取自比特就业课)

接下来让我们分别对其进行介绍。

2.2 构造函数

构造函数的作用是对象实例化时对对象进行初始化,它的出现是为了代替C语言中模拟实现像如Stack、Queue中写的Init函数的功能。构造函数自动调用的特点就可以作为Init函数的上位替代了。

接下来简单介绍构造函数的特点:

1. 函数名与类名相同

2. 无返回值,不写函数的返回值类型(void也不要写)

3. 对象实例化时系统会自动调用其对应的类中的构造函数。

4. 构造函数可以重载。

5. 若用户不显式定义构造函数,编译器会自动生成一个无参的默认构造函数;若用户显式定义构造函数,则编译器不会再生成默认的构造函数。

6. 编译器自动生成的构造函数对内置类型成员不做处理,对自定义类型成员会调用它所在类的构造函数

无参构造函数,全缺省构造函数,编译器默认生成的构造函数,均叫作默认构造函数(即不传实参就可调用的构造函数)

构造函数的使用举例:

#include <iostream>
using namespace std;
class Date 
{
public:
	Date() //无参构造函数,与全缺省构造函数只能存在一个
	{
		_day = 0;
		_month = 0;
		_year = 0;
	}
	Date(int year, int month, int day) //带参构造函数
	{
		_day = day;
		_month = month;
		_year = year;
	}
	void Print() 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main() 
{
	Date a;
	a.Print();
	Date b(2024, 9, 16);
	b.Print();
	return 0;
}

 代码运行结果:

注意:如果通过无参构造函数创建对象时,对象后不能跟括号,否则编译器无法区分是函数声明还是对象的实例化。

2.3 析构函数 

 析构函数的功能与构造函数相反,C++规定,对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能相当于C语言中Stack实现的Destroy功能,而部分对象的销毁没有资源需要释放,那么析构函数对于它们来说就是可有可无的。

析构函数的特点:

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

2. 无函数参数,无返回值,不需要写返回类型(void也不用)

3. 一个类仅能有一个析构函数。如果析构函数未被显式定义,编译器会自动生成默认的析构函数。

4. 对象的生命周期结束时,编译器会自动调用析构函数。

5. 编译器自动生成的析构函数对内置类型成员不做处理,对自定义类型成员会调用它所在类的析构函数。

6. 显式写析构函数,对于自定义类型成员也会调用它所在类的析构函数,即对于自定义类型成员,显式定义与编译器自动生成都会做出相同的处理:调用它所在类的析构函数。

7. 如果类中没有资源申请,析构函数可以不写,直接使用编译器生成的默认析构函数即可;如果有资源申请,一定要显式写析构函数(自己写),否则会造成资源泄露。

8. 一个局部域的多个对象,C++规定后定义的先析构。全局域的析构晚于局部域。

对于第八点的举例:

#include <iostream>
using namespace std;
class Exa 
{
public:
	Exa(char x='a') 
	{
		_a = x;
	}
	~Exa() 
	{
		cout <<"~Exa"<<_a<< endl;
	}
private:
	char _a;
};
Exa a('A');

int main() 
{	
	Exa b('B');
	Exa c('C');
	return 0;
}
Exa d('D');

代码运行结果:

2.4 拷贝构造函数 

如果一个构造函数的第一个参数是对自身类的类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数。拷贝构造是一个特殊的构造函数。

拷贝构造函数的特点:

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

2. C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以自定义类型的传值传参和传值返回都会调用拷贝构造完成。

3. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器会报错,因为在语法逻辑上会引发无穷递归调用。(参照第二点)拷贝构造函数可以有多个参数,除第一个参数,后面的参数必须要有缺省值。

4. 若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造函数对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用它所在类的拷贝构造函数。

5. 浅拷贝即可完成需求的类不需要写拷贝构造函数,而举例像如Stack(模拟实现栈的类)需要深拷贝,因为一个栈拷贝另一个栈不只有值拷贝,它的地址应不同于拷贝的栈,这时就需要深拷贝,即我们显式写类的拷贝构造函数,以完成深拷贝的需求。

6. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝,但是如果返回对象是一个当前函数局部域的局部对象,函数结束时该对象就被销毁了,那么此时使用引用返回是有问题的,这时的引用相当于野引用。因此,传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后未被销毁,才能返回引用,否则就返回值。

Ps 关于第三点中所提到的拷贝构造函数传值方式传参会引发无穷递归调用:

取自比特就业课

拷贝构造函数简单使用场景举例:

#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int x = 0,int y = 0,int z = 0) //默认构造函数
	{
		_capacity = 3;
		_top = 3;
		a = (int*)malloc(sizeof(int) * _capacity);
		a[0] = x;
		a[1] = y;
		a[2] = z;
	}
	Stack(const Stack& S) //拷贝构造函数
	{
		_capacity = S._capacity;
		_top = S._top;
		a = (int*)malloc(sizeof(int) * _capacity);
		a[0] = S.a[0];
		a[1] = S.a[1];
		a[2] = S.a[2];
	}
	void Print() 
	{
		cout << a << endl;
		cout << a[0] << "  " << a[1] << "  " << a[2] << endl;
	}
	~Stack()//析构函数
	{
		free(a);
	}
private:
	int _capacity;
	int _top;
	int* a;
};

int main() 
{	
	Stack s1(1, 2, 3);
	Stack s2;
	Stack s3(s1);
	s1.Print();
	s2.Print();
	s3.Print();
	return 0;
}

代码运行结果:

2.5  运算符重载

当运算符被用于类类型对象时,C++允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须调用对应的运算符重载,若没有对应的运算符重载,编译会报错。

运算符重载函数名由operator+运算符组成。运算符重载函数的参数个数和该运算符作用的运算对象数量一致。一元运算符有一个参数,二元运算符有两个参数,且二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。(这一点非常重要,牢记哦)

如果一个运算符重载函数是类的成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。

运算符重载以后,其优先级和结合性与对应的内置类型运算符一致。

不能通过连接语法中没有的符号来创建新的操作符,比如:operator@。

“ .* ”、“ :: ”、“ sizeof ”、“ ?: ”、“ . ”,这五个运算符不能进行重载

重载运算符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:

int operator+(int x,int y)

重载++运算符时,有前置++与后置++,运算符重载函数名都是operator++,无法区分,而C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

重载<<和>>时,需要重载为全局函数。若重载为成员函数,this指针默认为第一个形参位置,第一个形参位置是左侧运算对象,调用时就变为:对象<<cout,不符合使用习惯和可读性。重载为全局函数,把ostream/istream放到第一个形参位置,第二个形参位置为类类型对象引用

若运算符重载函数在全局,如何访问对象的私有成员变量?

方法1. 成员放公有

方法2. 对象所在的类提供getxxx函数

方法3. 把运算符重载函数声明为该对象所在类的友元函数

方法4. 把运算符重载函数放在该对象所在类的域里,即使其成为成员函数

运算符重载使用示例:

#include <iostream>
using namespace std;
int arr[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
class Date 
{
public:
	Date(int year=1,int month=1,int day=1) //默认构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int Getmday(int Y,int m) 
	{
		if (m == 2) 
		{
			if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0))
				return arr[m - 1] + 1;
		}
		return arr[m - 1];		
	}
    //+的重载
	Date operator+(int x) //(Date* const this,int x)
	{
		Date a(*this);//拷贝构造,因为加法不影响两个加数
		if (x >= 0) 
		{
			a._day += x;
			while (a._day > Getmday(a._year,a._month)) 
			{
				if (a._month == 12) 
				{
					a._day -= Getmday(a._year, a._month);
					++a._year;
					a._month = 1;
				}
				else 
				{
					a._day -= Getmday(a._year, a._month);
					++a._month;
				}				
			}
			return a;
		}
        //x小于0暂不讨论
	}
	void Print() 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() 
{	
	Date a(2024, 2, 9);
	Date b(a + 100);
	a.Print();
	b.Print();
	return 0;
}

运行结果:

 

2.6 赋值运算符重载 

赋值运算符重载用于两个已经存在的对象的直接拷贝赋值,注意跟拷贝构造区分开,拷贝构造用于一个要被创建的对象对一个已存在对象进行拷贝以完成自己的初始化。

赋值运算符重载的特点:

1. 必须为成员函数。其参数建议为const修饰的该类类型的引用,如果参数为类类型,那么函数接收实参时还会调用拷贝构造,繁琐且无用。

2. 有返回值,建议返回值类型为该类类型的引用,引用返回可以提高效率,有返回值的目的是为了支持连续赋值场景。

3. 无显式实现时,编译器会自动生成一个默认赋值运算符重载,其会对内置类型成员进行浅拷贝,对自定义类型成员调用它所在的类的赋值重载函数。

4. 如果一个类显式实现了析构函数并释放了资源,那么该类同样需要显式写赋值运算符重载,否则就不需要。

演示示例:

#include <iostream>
using namespace std;
class Date 
{
public:
	Date(int year = 0, int month = 0, int day = 0) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d) //拷贝构造
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date& operator=(const Date& d)//赋值运算符重载
	{
		if (this != &d) //检查是否自己给自己赋值的情况
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
	void Print() 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() 
{	
	Date a(2024, 9, 17);
	Date b(1,1,1);
	Date c(2,2,2);
	c = b = a;
	a.Print();
	b.Print();
	c.Print();
	return 0;
}

代码运行结果 :

2.7 const成员函数

将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。

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

例如:

class Date
{
public:
    Date(int year=1,int month=1,int day=1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()const
    {
        cout<<_year<<"/"<<_month<<"/"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

该代码中Date的成员函数Print是被const修饰的,它隐含的this指针由Date* const this变为const Date* const this 

本文完!

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

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

相关文章

面试真题-TCP的三次握手

TCP的基础知识 TCP头部 面试题&#xff1a;TCP的头部是多大&#xff1f; TCP&#xff08;传输控制协议&#xff09;的头部通常是固定的20个字节长&#xff0c;但是根据TCP选项&#xff08;Options&#xff09;的不同&#xff0c;这个长度可以扩展。TCP头部包含了许多关键的字…

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生 什么是幽灵依赖 (幻影依赖) 形成原因 幽灵依赖是指node_modules中存在 而package.json中没有声明过的依赖 但却能够在项目的依赖树中找到并使用的模块 Node.js 的模块解析规则&#xff1a; Node.js 采用了一种非传统的模…

C++速通LeetCode简单第20题-多数元素

方法一&#xff1a;暴力解法&#xff0c;放multiset中排序&#xff0c;然后依次count统计&#xff0c;不满足条件的值erase清除。 class Solution { public:int majorityElement(vector<int>& nums) {int ans 0;multiset<int> s;for(int i 0;i < nums.s…

「iOS」viewController的生命周期

iOS学习 ViewController生命周期有关方法案例注意 ViewController生命周期有关方法 init - 初始化程序&#xff1b;loadView - 在UIViewController对象的view被访问且为空的时候调用&#xff1b;viewDidLoad - 视图加载完成后调用&#xff1b;viewWillAppear - UIViewControll…

给大模型技术从业者的建议,入门转行必看!!

01—大模型技术学习建议‍‍‍ 这个关于学习大模型技术的建议&#xff0c;也可以说是一个学习技术的方法论。 首先大家要明白一点——(任何)技术都是一个更偏向于实践的东西&#xff0c;具体来说就是学习技术实践要大于理论&#xff0c;要以实践为主理论为辅&#xff0c;而不…

换个手机IP地址是不是不一样?

在当今这个信息爆炸的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。而IP地址&#xff0c;作为手机连接网络的桥梁&#xff0c;也时常引起我们的关注。你是否曾经好奇&#xff0c;换个手机&#xff0c;IP地址会不会也跟着变呢&#xff1f;本文将深入探讨这个问题&a…

Android15之编译Cuttlefish模拟器(二百三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

直流斩波电路

目录 1. 降压斩波电路&#xff08;Buck Converter&#xff09; 2. 升压斩波电路&#xff08;Boost Converter&#xff09; 3. 升降压斩波电路&#xff08;Buck-Boost Converter&#xff09; 4. Cuk斩波电路&#xff08;Cuk Converter&#xff09; 直流斩波电路是一种将直流…

Unity3D下如何播放RTSP流?

技术背景 在Unity3D中直接播放RTSP&#xff08;Real Time Streaming Protocol&#xff09;流并不直接支持&#xff0c;因为Unity的内置多媒体组件&#xff08;如AudioSource和VideoPlayer&#xff09;主要设计用于处理本地文件或HTTP流&#xff0c;而不直接支持RTSP。所以&…

上海人工智能实验室开源视频生成模型Vchitect 2.0 可生成20秒高清视频

上海人工智能实验室日前推出的Vchitect2.0视频生成模型正在悄然改变视频创作的游戏规则。这款尖端AI工具不仅简化了视频制作流程&#xff0c;还为创作者提供了前所未有的灵活性和高质量输出。 Vchitect2.0的核心优势在于其强大的生成能力和高度的可定制性。用户只需输入文字描…

用Matlab求解绘制2D散点(x y)数据的最小外接圆、沿轴外接矩形

用Matlab求解绘制2D散点&#xff08;x y&#xff09;数据的最小外接圆、沿轴外接矩形 0 引言1 原理概述即代码实现1.1 最小外接圆1.2 沿轴外接矩形 2 完整代码3 结语 0 引言 本篇简单介绍下散点数据最小外接圆、沿轴外接矩形的简单原理和matlab实现过程。 1 原理概述即代码实现…

C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)

拓扑排序算法的实现还是比较简单的&#xff0c;我们需要用到一个顺序栈辅助&#xff0c;采用邻接表进行存储&#xff0c;顶点结点存储入度、顶点信息、指向邻接结点的指针&#xff0c;算法过程是&#xff1a;我们先将入度为0的顶点入栈&#xff0c;然后弹出栈顶结点&#xff0c…

使用CUBE_MX使用I2C通信,实现对EEPROM的读写

一、使用CUBE_MX配置 1.配置I2C 2.配置USART1 3.重中之重(在KEIL5打开串口使用的库) 二、KEIL5配置 #include "main.h" #include "i2c.h" #include "gpio.h" #include "usart.h"#include <stdio.h>void SystemClock_Config(vo…

flash_attention简要笔记

优化效果 原来&#xff0c;attention部分的计算量和中间激活占用显存的复杂度都是 O ( N 2 ) O(N^2) O(N2) 计算量部分原来QK矩阵乘和attn_scoreV矩阵乘的计算量&#xff0c;复杂度都是 O ( N 2 ) O(N^2) O(N2)&#xff1b;中间激活因为中间有一个attn_score&#xff0c;所以复…

如何接口对接发送视频短信

随着移动通信技术的飞速发展&#xff0c;视频短信作为一种创新的多媒体信息传递方式&#xff0c;正逐渐成为众多行业不可或缺的沟通工具。它不仅丰富了信息传递的形式&#xff0c;还显著提高了信息接收者的参与度和满意度。 支持免费对接试用乐讯通PaaS平台 找好用的短信平台,选…

数据结构:(OJ141)环形列表

给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;…

探索iPhone一键删除重复照片的方法

在iPhone用户的生活中&#xff0c;存在一个不变的真理&#xff1a;不管你的照片库有多干净&#xff0c;重复的照片总会找到一种方法悄无声息地积累起来&#xff0c;就像袜子在洗衣机中神秘消失那样不可思议。而当你最终决定处理这些重复照片时&#xff0c;你可能已经面临着一个…

Electron 图标修改

目录 1. 图片基本要求 2. 在main.js中配置icon 位置 ​3. 在package.json 中配置icon 位置 4. 问题&#xff1a;左上角图片 开发环境下显示&#xff0c;生产环境下不显示 1. 图片基本要求 图片格式为ico&#xff0c;图片像素像素为256*256&#xff1b; 将ico文件放在pub…

基于Springboot的医疗健康助手开题报告

文未可获取一份本项目的java源码和数据库参考。 一&#xff0e;选题意义, 研究现状,可行性分析 选题意义&#xff1a;随着科技的高速发展&#xff0c;人们的生活水平也正在稳步提高&#xff0c;解决温饱问题以后&#xff0c;广大人民群众也越来越注重自己的身体健康&#xff0…

openGauss 基于PITR的恢复

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…