类和对象初阶

news2024/11/27 18:51:38

目录

一、再谈构造函数

1.1 构造函数体赋值

1.2 初始化列表

1.3 注意

1.4 总结

二、拷贝对象时的一些编译器优化

三、static成员

3.1 静态成员变量

3.1.1 引入

3.1.2 特点

3.1.3 区别

3.2 静态成员函数

3.2.1 引入

3.2.2 特点

3.2.3 例题

四、友元

4.1 友元函数

4.2 友元类

五、内部类

5.1 定义

5.2 注意

5.2.1 特性

5.2.2 外部类对象的大小计算

5.3 例题

六、匿名对象

6.1 举例

6.2 特性

6.3 写法的探讨


C++类和对象(上)_李有鱼的博客-CSDN博客

类的六个默认成员函数_李有鱼的博客-CSDN博客

     在之前的两篇文章中,我们简单的分析了类和对象的一些知识,在这篇文章,主要是对上两篇中一些内容进行补充。

一、再谈构造函数

1.1 构造函数体赋值

     构造函数体赋值就是我们在之前的两篇文章中的方式,例如一个日期类的构造函数:

class Date
{
public:
    Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

   通过调用上述的构造函数之后,对象中会有一个初始值,但是不能将他称为对象中成员变量的初始化,构造函数体中的语句实际上相当于赋值,初始化只能初始化一次,构造函数体内可以有多次赋值。

1.2 初始化列表

     初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟着一个放在括号中的初始值或表达式。

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

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

     C++规定是在初始化列表进行初始化操作,初始化只能有一次,所以每个成员变量在初始化列表中最多只能出现一次。

     类中包含以下成员的话,必须放在初始化列表位置进行初始化:

  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

接下来我们根据以下的一段代码进行具体分析:

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};

class B
{
public:
	B(int a, int& ref)
		: _aobj(a)
		, _ref(ref)
		, _n(10)
	{}
private:
	A _aobj;     //没有默认构造
	int& _ref;        //引用
	const int _n;          //const
};

     为什么引用成员变量和const成员变量必须在初始化列表进行初始化?

      初始化列表是进行初始化的地方,即对象的成员定义的地方,所以引用成员变量和const成员变量必须在初始化列表进行初始化。

     为什么自定义类型成员(且该类没有默认构造函数时)必须在初始化列表进行初始化?
     自定义类型必须调用它的构造函数,在定义时必须初始化,如果有默认构造编译器会在初始化列表阶段自动去调用它的默认构造函数,没有默认构造时,在初始化列表阶段由程序员自己调用。

1.3 注意

     我们来看下面的一段代码:

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a2;
	int _a1;
};

int main()
{
	A aa(1);
	aa.Print();
	return 0;
}

     上述代码的打印结果是什么呢?

      为什么是上述的结果?
      这里引出了一个特性,成员变量在类中声明次序就是其在初始化列表中的初始化顺序,和它在初始化列表中的先后次序无关。

      根据这一特性,建议声明的顺序和定义的顺序保持一致。

1.4 总结

     初始化列表是进行成员定义的位置,在初始化列表进行初始化操作,所有的成员都会走初始化列表如果我们在初始化列表写了成员的定义编译器就使用我们写的,如果我们在初始化列表没有写某个成员的定义,编译器会自动生成或调用。

class Stack
{
//编译器会生成默认构造函数	
private:
	int _top;
	int* _a;
	int _capacity;
};

class MyQueue
{
public:
	MyQueue()   //在这里我们没有对自定义类型的_pushst和_popst进行初始化,但是所有的成员都会走初始化列表(他是成员定义的位置),编译器会自动去调用它的默认构造
	{}

private:
	Stack _pushst;
	Stack _popst;
};

补充知识:
默认的构造函数有三类:

  • 无参的构造函数
  • 我们没写编译器默认生成的构造函数
  • 全缺省的构造函数
class Stack
{
public:
	Stack(int capacity)   //不是默认构造函数
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_top = 0;
		_capacity = capacity;
	}
private:
	int _top;
	int* _a;
	int _capacity;
};

class MyQueue
{
public:
//如果自定义类型的成员没有默认构造函数,必须显式的去调用
	MyQueue(int capacity)    
		:_pushst(capacity)
		,_popst(capacity)
	{}
private:
	Stack _pushst;
	Stack _popst;
};

     构造函数体是成员赋值的位置,初始化列表不能代替函数体赋值,总有一些工作是初始化列表无法完成的,例如下例:

class Stack
{
public:
	Stack(int capacity)
		:_a((int*)malloc(sizeof(int) * capacity))
		,_top(0)
		,_capacity(capacity)
	{
		if (nullptr == _a)
		{
			perror("malloc fail");
			exit(-1);
		}
	}
private:
	int _top;
	int* _a;
	int _capacity;
};

     类似于上例,如果我们想要开辟一个数组,当申请空间后,想要判断空间是否申请成功的时候,我们就需要在构造函数体内进行判断。

class AA
{
public:
	AA(int row = 10,int col =10)
		:_row(row)
		,_col(col)
	{
		_aa = (int**)malloc(sizeof(int*) * row);
		for (int i = 0; i < row; i++)
		{
			_aa[i] = (int*)malloc(sizeof(int) * col);
		}
	}
private:
	int** _aa;
	int _row;
	int _col;
};

     其次在上例中,我们要创建一个二维数组,肯定要用到循环,这时就必须有函数体。

二、拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝。

有一个A类,

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

     下面我们对几种实例化对象的写法进行分析,观察编译器的优化以及在今后的学习生活中,更适合使用哪一种写法。
写法一:

A aa1(1);

     上述是实例化一个A类型的对象,并且进行初始化,给_a赋值。

A aa2 = 2;

     上述是一个隐式类型转换,整型转换为自定义类型。

const A& aa3 = 2;

     上述代码,aa3是2构造出来的A类型的临时对象的别名,临时变量具有常属性,所以这里必须加const。

对于函数调用返回值,也会进行优化:

三、static成员

     声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量,用static修饰的成员函数称为静态成员函数,静态成员变量一定要在类外进行初始化。

3.1 静态成员变量

3.1.1 引入

     有这样的一个场景,有一个A类,想要去统计A到底创建了多少个对象,即系统中,当前还有多少个对象正在使用?

     我们可以定义一个全局变量_scount,当调用构造(包含拷贝构造)函数的时候,_scount++,当调用析构函数时,_scount--;

int _scount = 0;
class A
{
public:
	A()
	{
		++_scount;
	}
	A(const A& t) 
	{
		++_scount;
	}
	~A(){
		--_scount;
	}
};

A aa0;

A Func(A aa1)
{
	cout << __LINE__ << ":" << _scount << endl;
	return aa1;
}

int main()
{
	cout << __LINE__ << ":" << _scount << endl;
	A aa1;
	static A aa2;
	cout << __LINE__ << ":" << _scount << endl;
	Func(aa1);
	cout << __LINE__ << ":" << _scount << endl;

	return 0;
}

     但是上面的代码有一个弊端,_scount是全局变量,全局变量在任何地方都可以随意改变,这就会造成一定的风险。

     基于全局变量的弊端,我们能想到如果把他像类的成员变量封装起来,那么就不能在类外面进行随意地改变了。这时就很容易想到了静态成员变量。

3.1.2 特点

class A
{
public:
	A()
	{
		++_scount;
	}
	A(const A& t)
	{
		++_scount;
	}
	~A() {
		--_scount;
	}
private:
	//成员变量
	int _a1 = 1;
	int _a2 = 2;
	//静态成员变量
	static int _scount;   //声明
};

//全局位置,类外面定义
int A::_scount = 0;

如果静态成员变量是公有的:

我们可以通过下面的方式进行访问:

cout<<A::_scount<<endl;

//d是A类型的对象
count<<d._scount<<endl;

如果静态成员变量是私有的,要通过公有的成员函数来访问,在静态成员函数处讲解。

3.1.3 区别

 类里面除了可以定义成员变量,还可以定义静态成员变量。

3.2 静态成员函数

3.2.1 引入

     在刚刚定义的A类中,如果静态成员变量_scount是私有的,那么只有通过公有的成员函数才能对他进行访问,在类外面访问成员函数,由于成员函数的第一个指针是隐含的this指针,所以调用成员函数需要有对象,如果我们没有创建对象,怎么办呢?把函数写为静态成员函数。

3.2.2 特点

     用static修饰的成员函数称为静态成员函数,静态成员函数没有this指针指定类域和访问限定符就可以访问。

     对于上面的A类,静态成员变量_scount是私有的,可以通过下面的静态成员函数进行访问,完整代码如下:

class A
{
public:
	A()
	{
		++_scount;
	}
	A(const A& t)
	{
		++_scount;
	}
	~A() {
		--_scount;
	}
	static int GetACount()
	{
		return _scount;
	}
private:
	//成员变量
	int _a1 = 1;
	int _a2 = 2;
	//静态成员变量
	static int _scount;   //声明
};

//全局位置,类外面定义
int A::_scount = 0;

int main()
{
	cout << __LINE__ << ":"<<A::GetACount() << endl;
}

注意:

  • 静态成员函数不可以调用非静态成员函数,静态成员函数没有this指针。
  • 非静态成员函数可以调用静态成员函数,静态成员函数指定类域和访问限定符就可以访问。

3.2.3 例题

例一:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

      这个题要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C),在这里我们提供一种思路,要求他们相加的和,我们要有n次循环,但是这里要求不能使用循环,所以我们创建一个有n个对象的数组,即需要调用n次构造函数,在这n次中不断对变量_ret进行累加。

class sum
{
public:
    sum()
    {
        _ret+=_n1;
        _n1++;
    }
    static int GetRet()
    {
        return _ret;
    }
private:
    static int _n1;
    static int _ret;
};

int sum::_n1 = 1;
int sum::_ret = 0;

class Solution {
public:
    int Sum_Solution(int n) {
       sum a[n];
       return sum::GetRet();
    }
};

     这里的_ret变量不能定义为局部变量,定义为全局变量有隐患,所以定义为类的静态成员变量,将变量封装起来。

例二:设计一个类,只能在栈或者堆上创建对象。

假设有一个类A,实例化了以下三个对象


 

      题目要求只能在栈或者堆上创建对象,实例化对象的共同点都是要调用构造函数,在这里可以将构造函数私有,然后写两个静态的函数,完成栈上和堆上对象的创建工作。

class A
{
public:
	static A GetStackObj()
	{
		A aa;
		return aa;
	}
	static A* GetHeapObj()
	{
		return new A;
	}
private:
	A()
		:_n1(1)
		,_n2(0)
	{}
	int _n1;
	int _n2;
	int _n3;
};

int main()
{
	A::GetStackObj();   //在栈上实例化一个对象
	A::GetHeapObj();   //在堆上实例化一个对象
	return 0;
}

四、友元

     友元提供了一种突破封装的方式,友元可以实现在某一个类外面可以访问类的私有和保护成员。但是友元会增加耦合(关联度更加紧密),破坏了封装,所以友元不宜多使用。

4.1 友元函数

     在之前的博客中,在对Date类实现的过程中,我们要实现运算符<<和>>的重载,当时我们就使用到了友元函数,对于<<的重载,不能写成成员函数,成员函数的第一个参数是隐含的this指针,但是<<要求第一个参数即左操作数是cout的输出流对象,所以<<运算符重载必须写到类外面,但是在重载函数中又需要访问Date类中的私有成员变量,这时可以使用友元函数。

     友元函数可以直接访问类的私有成员,他是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

实现Date类的博客:(8条消息) 类的六个默认成员函数_李有鱼的博客-CSDN博客

//友元函数在类中的声明可以在任意位置
class Date
{
	friend istream& operator>>(istream& in,Date& d);
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year=2023, int month=6, int day=8);
 
	void Print() const
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}
 
istream& operator>>(istream& in,Date& d)
{
	int year, month, day;
	in >> year >> month >> day;
	if (month > 0 && month < 13 && day>0 && day <= d.GetMonthDay(year, month))
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}
	return in;
}
 

4.2 友元类

     友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

下面有两个类,其中Date类是Time类的友元类:
 

class Time
{
	friend class Date;  //声明日期类是时间类的友元类,在日期类中就可以直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	void setTimeOfDate(int hour, int minute, int second)
	{
		//直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

五、内部类

5.1 定义

     如果一个类定义在另一个类的内部,这个定义在内部的类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。

5.2 注意

5.2.1 特性

5.2.2 外部类对象的大小计算

class A
{
private:
	static int k;
	int h;	
public:
	class B
	{
	public:
		B()
		{}
	private:
		int a;
		int b;
	};
};

     上述代码中,B是A的内部类,现在要求A类型的大小。

     注意:在计算A类型的时候,不需要计算静态变量k以及B类的大小,静态类不在对象中,他在公共区域,B类的大小也不需要计算,在A类中没有B对象,没有用B类创建对象,B定义在A的类域里,但是不占空间,都只是声明,定义出来的对象才占空间。

5.3 例题

在之前我们写了1+2+3+4+5+.....n的求解,在这里是对它的优化。

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

#include <regex>
class Solution {
public:
    class sum
    {
    public:
       sum()
       {
           _ret+=_n1;
           _n1++;
       }
    };
    int Sum_Solution(int n) {
       sum a[n];
       return _ret;
    }
private:
    static int _n1;
    static int _ret;
};

int Solution::_n1 = 1;
int Solution::_ret = 0;

      什么情况用内部类?当我们期望类是藏在另一个类里面的,别人都访问不到。

六、匿名对象

6.1 举例

下面有一个类A

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout<<"~A()" << endl;
	}
private:
	int _a;
};

下面就是A类型的有名对象:

A aa(1);   //有名对象

下面是A类型的匿名对象:

A(2);   //匿名对象

6.2 特性

//error   匿名对象具有常性
//A& ra = A(1);

//正确
//const引用延长匿名对象的生命周期,A(1)这个匿名对象的生命周期延长到和ra的生命周期相同,ra是匿名对象的别名,他的生命周期是在当前函数局部域
const A& ra = A(1);  

6.3 写法的探讨

     有一个类Solution

class Solution {
public:
	int Sum_Solution(int n) {
		cout << "Sum_Solution" << endl;
		return n;
	}
};

下面有两种写法,来调用Sum_Solution函数

//方式一
Solution s1;
s1.Sum_Solution(10);
//方式二
Solution().Sum_Solution(20);

     对比两种方式,可以发现方式二的代码更简洁易懂,当我们要调用Sum_Solution函数,就可以使用匿名对象去调用函数。

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

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

相关文章

数据结构与算法·第2章【线性表】

线性结构具有以下基本特征&#xff1a; 有唯一的一个被称为首元素&#xff08;或头元素&#xff09;的元素&#xff0c;没有直接前驱&#xff1b;有唯一的一个被称为尾元素&#xff08;或尾节点&#xff09;的元素&#xff0c;没有直接后继。 数据元素之间存在一对一的线性关…

python 实现单链表

链表 链表是一种在存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现。 链表是由一系列的结点组成&#xff0c;结点可以在运行时动态生成。每个结点包含两部分&#xff1a;数据域与指针域。数据域存储数据元素&#xff0c;指针域存储下一…

Yapi内网部署[CentOS7]

mongo安装 # 下载MongoDB https://www.mongodb.com/try/download/community4.2.24 RedHat/CentOS7.0 tgz# 安装MongoDB mkdir -p /home/jpge/devp-tools/tools cd /home/jpge/devp-tools/tools wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.24.tgz…

线性代数:线性方程求解、矩阵的逆、线性组合、线性独立

本文参考www.deeplearningbook.org一书第二章2.3 Identity and Inverse Matrices 2.4 Linear Dependence and Span 本文围绕线性方程求解依次介绍矩阵的逆、线性组合、线性独立等线性代数的基础知识点。 一、线性方程 本文主要围绕求解线性方程展开&#xff0c;我们先把线性…

揭秘Redis持久化原理,探索fork与Copy-on-Write的魔法!

大家好&#xff0c;我是小米&#xff0c;今天我将和大家一起探索Redis持久化原理中的两个关键概念&#xff1a;fork和Copy-on-Write。这两个概念对于理解Redis的数据持久化机制至关重要。让我们一起来揭开这些技术的神秘面纱吧&#xff01; Redis持久化简介 在开始之前&#…

组合数学第四讲

Generating Function&#xff08;生成函数&#xff09; 这里是一个普通生成函数例子&#xff0c;生成函数一般适用于根据递推关系式求出比较复杂的通项公式的 关键点&#xff1a; 1.项可转换成G(x)-,因为生成函数规定是从0到∞的 2.,当|x|<1时&#xff0c;最终可收敛为。这里…

基础算法(六):回溯算法

前言 Hello大家好&#xff0c;停了半个多月算法学习的荔枝又变菜了&#xff0c;最近决定认认真真地重新学习回溯&#xff0c;无意间看到Carl哥的代码随想录&#xff0c;感动之余也是跟着一步步走&#xff0c;后悔上车晚了呜呜呜~~~。之前自己摸索确实有点难受&#xff0c;在这篇…

YUM安装LNMP架构配置命令与搭建论坛

LNMP架构配置命令与搭建论坛 LNMP简介安装须知安装 Nginx配置yum源yum安装nginx并查看版本号开启服务并且设为开机自启 安装 MySQL 5.7 (mariadb)移除软件包下载安装mysql安装MySQL报错问题解决方案&#xff1a; 开启服务并设为开机自启在日志文件中找出root用户的初始密码登录…

数据结构 --- 树

1、二叉树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 每个结点最多有两棵子树&#xff0c;即二叉…

CAPL(vTESTStudio) - CAPL控制RS232继电器

目录 为什么要使用CAPL控制继电器? 一、RS232继电器选择 二、继电器通信协议

AList 一个支持多种存储的文件列表程序,使用 Gin 和 Solidjs。

一个支持多种存储&#xff0c;支持网页浏览和 WebDAV 的文件列表程序&#xff0c;由 gin 和 Solidjs 驱动。 特点 使用简单 AList 从一开始就设计为易于安装&#xff0c;并且可以在所有平台上使用。 多种存储 AList 支持多个存储提供商&#xff0c;包括本地存储、阿里云盘、O…

大数据治理入门系列:数据治理

在信息经济时代&#xff0c;数据是企业的一大关键资产。为了制定科学、有效、合理的决策&#xff0c;企业需要收集大量的数据并进行各种数据分析&#xff0c;为决策提供依据。在此过程中&#xff0c;收集数据的速度、数据的质量和可靠性、对数据的分析过程、合适的分析工具等&a…

三十四、数学知识——约数(试除法 + 约数个数 + 约数之和 + 欧几里得算法)

约数相关算法主要内容 一、基本思路1、定义2、试除法——求一个数的所有约数3、约数个数4、约数之和5、欧几里得算法——辗转相除法&#xff08;求解最大公约数&#xff09; 二、Java、C语言模板实现三、例题题解 一、基本思路 1、定义 约数&#xff1a; 约数是指一个数&…

利用百度API进行植物识别

植物识别_拍照识别植物-百度AI开放平台百度AI植物识别,支持识别超过2万种通用植物和近8千种花卉&#xff0c;接口返回植物的名称&#xff0c;并获取百科信息&#xff0c;适用于拍照识图类APP中https://ai.baidu.com/tech/imagerecognition/plant 偶然看到的&#xff0c;不过真…

STM32F103C8T6+2.4寸SPI TFT触摸屏代码+标准库 项目开发

目录 模块清单&#xff1a; 模块介绍&#xff1a; 1&#xff1a;STM32F103C8T6 2&#xff1a;2.4寸SPI TFT触摸屏 项目结果展示 2.4寸 TFT SPI显示触摸屏 2.4寸 SPI TFT 显示触摸屏代码下载链接&#xff1a; (1条消息) 2.4寸SPITFT显示触摸屏资源-CSDN文库 模块清单&#x…

Vue后台管理系统【附源码】

登录 – 完成 路由拦截 – 完成 商品管理&#xff08;增加、编辑、搜索、删除&#xff09; – 完成 角色管理&#xff08;增加、编辑、搜索、删除、权限管理&#xff09; – 完成 交易订单&#xff08;增加、编辑、搜索、删除&#xff09; – 完成 用户管理&#xff08;增加、编…

在Centos Stream 9上Docker的实操教程 - 实操准备篇

在Centos Stream 9上Docker的实操教程 - 实操准备篇 认识Docker准备Centos Stream 9安装Docker更新仓库绕不开的HelloWorld结语 认识Docker 什么都要实操了&#xff0c;你还不知道Docker是什么&#xff1f;网上关于Docker的介绍一搜一大把&#xff0c;博主就不必浪费时间去侃侃…

ESP32-OTA

文章目录 1. 什么是OTA&#xff1f;2. OTA的基本原理3. ESP32远程OTA步骤&#xff1a;3.1 将需要升级的程序放在该目录下3.2 启动HTTP服务器3.3 配置3.4 烧录程序3.5 上电测试ESP32端 4. 问题&#xff1a;5. 通过命令控制OTA6. 参考&#xff1a; 1. 什么是OTA&#xff1f; OTA…

如何用 GPT-4 帮你写游戏(以24点游戏举例)

目录 给我一个24点游戏 游戏规则 GPT给的代码 ​改进 再改进 最近呢掀起了一阵GPT-4的热潮&#xff0c;很多人都想用GPT-4&#xff0c;这里呢我就打一个广告&#xff08;嘿嘿&#xff09;&#xff0c;如果不知道国内如何使用GPT的&#xff0c;可以看看这个博客&#xff1a;G…