【C++】队列模拟问题

news2024/11/13 18:54:49

文章目录

    • 队列模拟问题
      • 12.7.1 ATM问题
      • 12.7.2 队列类
      • 12.7.3 Queue类的接口
      • 12.7.4 **Queue类的实现**
      • 12.7.5 是否需要其他函数?
      • 12.7.6 Customer类
        • queue.h
        • queue.cpp
      • 12.7.7 ATM模拟
        • main.cpp

队列模拟问题

12.7.1 ATM问题

Heather银行打算在Food Heap超市开设一个自动柜员机(ATM)。Food Heap超市的管理者担心排队等待使用ATM的人流会干扰超市的交通,希望限制排队等待的人数。Heather银行希望对顾客排队等待的时间进行估测。要编写一个程序来模拟这种情况,让超市的管理者可以了解ATM可能造成的影响。

Heather银行的代表介绍:通常,三分之一的顾客只需要一分钟便可获得服务,三分之一的顾客需要两分钟,另外三分之一的顾客需要三分钟。另外,顾客到达的时间是随机的,但每个小时使用自动柜员机的顾客数量相当稳定。

设计一个类表示顾客,模拟顾客和队列之间的交互。
在这里插入图片描述

12.7.2 队列类

队列类的特征:

  • 队列存储有序的项目序列;
  • 队列所能容纳的项目数有一定的限制;
  • 应当能够创建空队列;
  • 应当能够检查队列是否为空;
  • 应当能够检查队列是否是满的;
  • 应当能够在队尾添加项目;
  • 应当能够从队首删除项目;
  • 应当能够确定队列中项目数。

12.7.3 Queue类的接口

从以上类的特征来判断如何定义Queue类的公共接口:

class Queue{
    enum{Q_SIZE = 10};
    public:
    Queue(int qs = Q_SIZE);
    ~Queue();
    bool isEmpty()const;
    bool isFull()const;
    int queueCount()const;
    bool enqueue(const Item &item);
    bool dequeue(Item &item);
}

12.7.4 Queue类的实现

确定接口后,便可以实现它。首先,需要确定如何表示队列数据。一种方法是使用new动态分配一个数组,它包含所需的元素数。然而,对于队列操作而言,数组并不太合适。例如,删除数组的第一个元素后,需要将余下的所有元素向前移动一位;否则需要作一些更费力的工作,如将数组视为是循环的。然而,链表能够很好地满足队列的要求。链表由节点序列构成。每一个节点中都包含要保存到链表中的信息以及一个指向下一个节点的指针。对于这里的队列来说,数据部分都是一个Item类型的值,因此可以使用下面的结构来表示节点:

struct Node{
    Item item;
    struct Node * next;
}

要跟踪链表,必须知道第一个节点的地址。可以让Queue类的一个数据成员指向链表的起始位置。具体地说,这是所需要的全部信息,有了这种信息后,就可以沿节点链找到任何节点。然而,由于队列总是将新项目
添加到队尾,因此包含一个指向最后一个节点的数据成员将非常方便(参见图12.9)。此外,还可以使用数据成员来跟踪队列可存储的最大项目数以及当前的项目数。所以,类声明的私有部分与下面类似:

在这里插入图片描述

class Queue{
    private:
    struct Node{ Item item; struct Node * next;};
    enum(Q_SIZE = 10);
    NODE * front;
    NODE * rear;
    int items;
    const int qsize;
}

只有构造函数可以使用这种初始化列表语法。对于非静态const类成员,则必须使用这种语法。对于被声明为引用的类成员,也必须使用这种语法。这是因为引用与const数据类似,只能在被创建时进行初始化。

数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的排列顺序无关。(这个观点存疑)

isempty()、isfull()、queuecount()的代码都非常简单。如果items为0,则队列为空;如果items等于qsize,则队列为满。要知道队列中的项目数,只需返回items的值。

将项目添加到队尾(入队)比较麻烦:

bool Queue::enqueue(const Item & item)
{
	if (isfull())
		return false;
	Node * add = new Node; //创建节点
	//如果失败,new将抛出std::bad_alloc的异常
	add->item = item; //设置节点指针
	add->next = NULL;
	items++;
	if (front == NULL) //如果队列为空
		front = add; //将元素放在队首
	else
		rear->next = add; //否则放在队尾
	rear = add; //设置尾指针指向新节点
	return true;
}

在这里插入图片描述

方法需要经过下面几个阶段:

1)如果队列已满,则结束。

2)创建一个新节点。如果new无法创建新节点,它将引发异常,除非提供了处理异常的代码,否则程序终止。

3)在节点中放入正确的值。代码将Item值复制到节点的数据部分,并将节点的next指针设置为NULL。这样为将节点作为队列中的最后一个项目做好了准备。

4)将项目计数(items)加1。

5)将节点附加到队尾。这包含两个部分。首先,将节点与列表中的另一个节点连接起来。这是通过将当前队尾节点的next指针指向新的队尾节点来完成的。第二部分是将Queue的成员指针rear设置为指向新节点,使队列可以直接访问最后一个节点。如果队列为空,则还必须将front指针设置成指向新节点(如果只有一个节点,则它既是队首节点,也是队尾节点)。

删除队首项目(出队)也需要多个步骤完成:

bool Queue::dequeue(Item & item)
{
	if (front == NULL)
		return false;
	item = front->item; //将队首元素赋值给item
	items--;
	Node * temp = front; //保存队首元素位置
	front = front->next; //将front设置成指向下一个节点
	delete temp; //删除以前的第一个节点
	if (items == 0)
		rear = NULL;
	return true;
}

在这里插入图片描述

需要经过下面几个阶段:

1)如果队列为空,则结束

2)将队列的第一个项目提供给调用函数,这是通过将当前front节点中的数据部分复制到传递给方法的引用变量中来实现。

3)将项目计数(items)减1。

4)保存front节点的位置,供以后删除。

5)让节点出队。这是通过将Queue成员指针front设置成指向下一个节点来完成的,该节点的位置由front->next提供。

6)为节省内存,删除以前的第一个节点。

7)如果链表为空,则将rear设置为NULL。

第4步是必不可少的,因为第5步将删除关于先前第一个节点位置的信息。

12.7.5 是否需要其他函数?

类构造函数没有使用new,所以乍一看,好像不用理会由于在构造函数中使用new给类带来的特殊要求。因为向队列中添加对象将调用new来创建新的节点。通过删除节点的方式,dequeue()方法确实可以清除节点,但这并不能保证队列在到期为空。因此,类需要一个显式析构函数——该函数删除剩余的所有节点。

下面一种实现,它从链表头部开始,依次删除其中的每个节点:

Queue::~Queue()
{
	Node * temp;
	while (front != NULL) //确定queue不为空
	{
		temp = front; //保存前一个元素
		front = front->next; //重置下一个元素
		delete temp; //删除前一个元素
	}
}

使用new的类通常需要包含显式复制构造函数和执行深度复制的赋值运算符,这个例子也是如此吗?

首先要回答的问题是,默认的成员复制是否合适?答案是否定的。

复制Queue对象的成员将生成一个新的对象,该对象指向链表原来的头和尾。因此,将项目添加到复制的Queue对象中,将修改共享的链表。这样做将造成非常严重的后果。更糟的是,只有副本的尾指针得到了更新,从原始对象的角度看,这将损坏链表。显然,要克隆或复制队列,必须提供复制构造函数和执行深度复制的赋值构造函数。

当然,这提出了这样一个问题:为什么要复制队列呢?也许是希望在模拟的不同阶段保存队列的瞬像,也可能是希望为两个不同的策略提供相同的输入。实际上,拥有拆分队列的操作是非常有用的,超市在开设额外的收款台时经常这样做。同样,也可能希望将两个队列结合成一个或者截短一个队列。

但假设这里的模拟不实现上述功能。难道不能忽略这些问题,而使用已有的方法吗?当然可以。然而,在将来的某个时候,可能需要再次使用队列且需要复制。另外,您可能会忘记没有为复制提供适当的代码。在这种情况下,程序将能编译和运行,但结果却是混乱的,甚至会崩溃。因此,最好还是提供复制构造函数和赋值运算符,尽管目前并不需要它们。

幸运的是,有一种小小的技巧可以避免这些额外的工作,并确保程序不会崩溃。这就是将所需的方法定义为伪私有方法:

class Queue
{
private:
	Queue(const Queue &q) : qsize(0) { } //先发制人的定义 
	Queue & operator=(const Queue &q) { return *this; }
	...
};

这样做有两个作用:

1)它避免了本来将自动生成的默认方法定义。

2)因为这些方法是私有的,所以不能被广泛使用。

如果nip和tuck是Queue对象,则编译器就不允许这样做:

Queue snick(nip); //错误,不被允许
tuck = nip; //错误,不被允许

C++11提供了另一种禁用方法的方式——使用关键字delete。

当对象被按值传递(或返回)时,复制构造函数将被调用。如果遵循优先采用按引用传递对象的惯例,将不会有任何问题。复制构造函数还被用于创建其他的临时对象,但Queue定义中并没有导致创建临时对象的操作,例如重载加法运算符。

12.7.6 Customer类

接下来需要设计客户类。通常,ATM客户有很多属性,例如姓名、账户和账户结余。然而,这里的模拟需要使用的唯一一个属性是客户何时进入队列以及客户交易所需的时间。当模拟生成新客户时,程序将创建一个新的客户对象,并在其中存储客户的到达时间以及一个随机生成的交易时间。当客户到达队首时,程序将记录此时的时间,并将其与进入队列的时间相减,得到客户的等候时间。下面的代码演示了如何定义和实现Customer类:

class Customer
{
private:
	long arrive; //顾客到达时间
	int processtime; //顾客进行时间
public:
	Customer() { arrive = processtime = 0; }
	void set(long when);
	long when() const { return arrive; }
	int ptime() const { return processtime; }
};
void Customer::set(long when)
{
	processtime = std::rand() % 3 + 1;
	arrive = when;
}

默认构造函数创建一个空客户。set()成员函数将到达时间设置为参数,并将处理时间设置为1~3中的随机值。

queue.h

#ifndef QUEUE_H_
#define QUEUE_H_
//这个队列包含Customer元素
class Customer
{
private:
	long arrive; //顾客到达时间
	int processtime; //顾客进行时间
public:
	Customer() { arrive = processtime = 0; }
	void set(long when);
	long when() const { return arrive; }
	int ptime() const { return processtime; }
};
typedef Customer Item;

class Queue
{
private:
	//类中嵌套结构声明
	struct Node { Item item; struct Node * next; };
	enum { Q_SIZE = 10 };
	//私有成员
	Node * front; //队首指针
	Node * rear; //队尾指针
	int items; //队列中当前元素个数
	const int qsize; //队列中最大元素个数
	//避免本来自动生成的默认方法定义
	Queue(const Queue &q) : qsize(0) { }
	Queue & operator=(const Queue &q) { return *this; }
public:
	Queue(int qs = Q_SIZE); //创建一个qs大小队列
	~Queue();
	bool isempty() const;
	bool isfull() const;
	int queuecount() const;
	bool enqueue(const Item &item); //在队尾添加元素
	bool dequeue(Item &item); //在队首删除元素
};
#endif // !QUEUE_H_

queue.cpp

#include "queue.h"
#include <cstdlib>

Queue::Queue(int qs) : qsize(qs)
{
	front = rear = NULL;
	items = 0;
}

Queue::~Queue()
{
	Node * temp;
	while (front != NULL) //确定queue不为空
	{
		temp = front; //保存前一个元素
		front = front->next; //重置下一个元素
		delete temp; //删除前一个元素
	}
}

bool Queue::isempty() const
{
	return items == 0;
}

bool Queue::isfull() const
{
	return items == qsize;
}

int Queue::queuecount() const
{
	return items;
}

//入队
bool Queue::enqueue(const Item & item)
{
	if (isfull())
		return false;
	Node * add = new Node; //创建节点
	//如果失败,new将抛出std::bad_alloc的异常
	add->item = item; //设置节点指针
	add->next = NULL;
	items++;
	if (front == NULL) //如果队列为空
		front = add; //将元素放在队首
	else
		rear->next = add; //否则放在队尾
	rear = add; //设置尾指针指向新节点
	return true;
}

//出队
bool Queue::dequeue(Item & item)
{
	if (front == NULL)
		return false;
	item = front->item; //将队首元素赋值给item
	items--;
	Node * temp = front; //保存队首元素位置
	front = front->next; //将front设置成指向下一个节点
	delete temp; //删除以前的第一个节点
	if (items == 0)
		rear = NULL;
	return true;
}

//设置处理时间为1~3的随机值
void Customer::set(long when)
{
	processtime = std::rand() % 3 + 1;
	arrive = when;
}

12.7.7 ATM模拟

现在已经拥有模拟ATM所需的工具。程序允许用户输入3个数:队列的最大长度、程序模拟的持续时间(单位为小时)以及平均每个小时的客户数。程序将使用循环——每次循环代表一分钟。在每分钟的循环中,程序将完成下面的工作:
1)判断是否来了新的客户。如果来了,并且此时队列未满,则将它添加到队列中,否则拒绝客户入队。
2)如果没有客户在进行交易,则选取队列的第一个客户。确定该客户的已等待时间,并将wait_time计数器设置为新客户所需的处理时间。
3)如果客户正在处理中,则将wait_time计数器减1。
4)记录各种数据,如获得服务的客户数目、被拒绝的客户数目、排队等候的累计时间以及累积的队列长度等。

当模拟循环结束时,程序将报告各种统计结果。

程序如何确定是否有新的客户到来:假设平均每小时有10名客户到达,则相当于每6分钟有一名客户。程序将计算这个值,并将它保存在min_per_cust变量中。然而,刚好每6分钟来一名客户不太现实,我们真正希望的是一个更随机的过程——但平均每6分钟来一个客户。

程序使用下面的函数来确定是否在循环期间有客户到来:

bool newcustomer(double x)
{
	return (std::rand() * x / RAND_MAX < 1); 
}

其工作原理如下:值RAND_MAX是在cstdlib文件中定义的,是rand()函数可能返回的最大值(0是最小值)。假设客户到达的平均间隔时间x为6,则rand()*x/RAND_MAX的值将位于0到6中间。具体地说,平均每隔6次,这个值会有1次小于1.然而,这个函数可能会导致客户到达的时间间隔有时为1分钟,有时为20分钟。这种方法虽然很笨拙,但是可使实际情况不同于有规则地每6分钟到来一个客户。如果客户到达的平均时间间隔少于1分钟,则上述方法将无效,但模拟并不是针对这种情况设计的。如果确实需要处理这种情况,最好提高时间分辨率,比如每次循环代表10秒钟。

main.cpp

长时间运行该模拟程序,可以知道长期的平均值;短时间运行该模拟程序,将只能知道短期的变化。

#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h"
const int MIN_PER_HR = 60;

bool newcustomer(double x);

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	using std::ios_base;
	
	std::srand(std::time(0)); //随机初始化

	cout << "Case Study: Bank of Heather Automatic Teller\n";
	cout << "Enter maximum size of queue: ";
	int qs;
	cin >> qs;
	Queue line(qs); //队列中能装下的人数

	cout << "Enter the number of simulation hours: ";
	int hours; //模拟小时
	cin >> hours;
	//模拟将每分钟运行 1 个周期 
	long cyclelimit = MIN_PER_HR * hours;

	cout << "Enter the average number of customer per hour: ";
	double perhour; //每小时顾客到达平均个数
	cin >> perhour;
	double min_per_cust; //平均间隔时间
	min_per_cust = MIN_PER_HR;

	Item temp; //创建一个customer对象
	long turnaways = 0; //队满,拒绝入队
	long customers = 0; //加入队列
	long served = 0; //
	long sum_line = 0; //排队等候累积的队列长度
	int wait_time = 0; //等待ATM空闲时间
	long line_wait = 0; //排队等候累积的时间
	//运行这个模拟
	for (int cycle = 0; cycle < cyclelimit; cycle++)
	{
		if (newcustomer(min_per_cust))
		{
			if (line.isfull())
				turnaways++;
			else
			{
				customers++;
				temp.set(cycle); //cycle = time of arrival
				line.enqueue(temp); //加入新顾客
			}
		}
		if (wait_time <= 0 && !line.isempty())
		{
			line.dequeue(temp); //下一个顾客
			wait_time = temp.ptime(); //等待时间
			line_wait += cycle - temp.when();
			served++;
		}
		if (wait_time > 0)
			wait_time--;
		sum_line += line.queuecount();
	}
	//打印结果
	if (customers > 0)
	{
		cout << "customers accepted: " << customers << endl;
		cout << "  customers served: " << served << endl;
		cout << "        turnaways: " << turnaways << endl;
		cout << "average queue size: ";
		cout.precision(2);
		cout.setf(ios_base::fixed, ios_base::floatfield);
		cout << (double)sum_line / cyclelimit << endl;
		cout << " average wait time: "
			<< (double)line_wait / served << " minutes\n";
	}
	else
		cout << "No customers!\n";
	cout << "Done!\n";
	return 0;
}

//x = 客户到达的平均间隔时间
bool newcustomer(double x)
{
	return (std::rand() * x / RAND_MAX < 1); //如果顾客到达的平均时间间隔少于1分钟,则返回真
}

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

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

相关文章

【C++STL精讲】vector的基本使用与常用接口

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;vector是什么&#xff1f;&#x1f337;vector的基本使用&#x1f337;vector常用函数接口&#x1f490;专栏导读 &#x1f338;作者简介&#xff1a;花想云&#xff0c;在读本科生一枚&#xff0c;致力于 C/C…

HAL库版FreeRTOS(上)

目录 FreeRTOS 简介初识FreeRTOS什么是FreeRTOS?为什么选择FreeRTOS&#xff1f;FreeRTOS 的特点商业许可 磨刀不误砍柴工查找资料FreeRTOS 官方文档Cortex-M 架构资料 FreeRTOS 源码初探FreeRTOS 源码下载FreeRTOS 文件预览 FreeRTOS 移植FreeRTOS 移植移植前准备添加FreeRTO…

浏览器断点调试说明

断点调试 断点调试面板 功能按钮介绍 描述&#xff1a;继续执行脚本 或者叫&#xff08;逐过程执行&#xff09; 快捷键 &#xff08;F8&#xff09;或者是&#xff08;Ctrl\&#xff09; 作用&#xff1a;打断点了的地方&#xff08;比如有是三个断点地方&#xff09;就会 第一…

大数据能力提升项目|学生成果展系列之四

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

13.vue-cli

单页面应用程序&#xff1a;所有的功能只在index.html中完成 vue-cli是vue版的webpack 目录 1 安装vue-cli 2 创建项目 3 使用预设 4 删除预设 5 开启项目 6 项目文件内容 6.1 node_moduls 中是项目依赖的库 6.2 public 6.2.1 favicon.ico 是浏览器页签内部…

尚融宝——整合OpenFeign与Sentinel实现兜底方法——验证手机号码是否注册功能

一、整合过程 在项目添加依赖&#xff1a;添加位置 <!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> 在需要的服务中添加启动注…

spring中常见的注解

DI(依赖注入中常见的注解) Autowired&#xff1a;按类型自动装配Resource&#xff1a;按名称或类型自动装配&#xff0c;Qualifier&#xff1a;按名称自动装配&#xff0c;Value &#xff1a;注入int、float、String等基本数据类型&#xff0c;只能标注在成员变量、setter方法上…

【Gradle-1】入门Gradle,前置必读

1、为什么要学习Gradle Gradle作为Android开发默认的构建工具&#xff0c;你的每一次编译都会用到它。招聘要求从以前的熟悉加分&#xff0c;到现在的必备技能&#xff0c;可见Gradle的重要性。 做开发这么久了&#xff0c;你是否对Gradle又爱又恨&#xff1f;是否对Gradle的…

第三章(1):自然语言处理概述:应用、历史和未来

第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来 目录第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来1. 自然语言处理概述&#xff1a;应用、历史和未来1.1 主要应用1.2 历史1.3 NLP的新…

【科普】PCB为什么常用50Ω阻抗?6大原因

在PCB设计中&#xff0c;阻抗通常是指传输线的特性阻抗&#xff0c;这是电磁波在导线中传输时的特性阻抗&#xff0c;与导线的几何形状、介质材料和导线周围环境等因素有关。 对于一般的高速数字信号传输和RF电路&#xff0c;50Ω是一个常用的阻抗值。 为什么是50Ω&#xff1f…

《程序员面试金典(第6版)》面试题 10.09. 排序矩阵查找(观察法,二分法,分治算法入门题目,C++)

题目描述 给定MN矩阵&#xff0c;每一行、每一列都按升序排列&#xff0c;请编写代码找出某元素。 示例: 现有矩阵 matrix 如下&#xff1a;[[1, 4, 7, 11, 15],[2, 5, 8, 12, 19],[3, 6, 9, 16, 22],[10, 13, 14, 17, 24],[18, 21, 23, 26, 30] ]给定 target 5&…

wma格式怎么转换mp3,4种方法超快学

其实我们在任何电子设备上所获取的音频文件都具有自己的格式&#xff0c;每种格式又对应着自己的属性特点。比如wma就是一种音质优于MP3的音频格式&#xff0c;虽然很多小伙伴比较青睐于wma所具有的音质效果&#xff0c;但也不得不去考虑因wma自身兼容性而引起很多播放器不能支…

【高危】Apache Solr 代码执行漏洞(MPS-wic0-9hjb)

漏洞描述 Apache Solr 是一款开源的搜索引擎。 在Apache Solr 受影响版本中&#xff0c;由于Solr默认配置下存在服务端请求伪造漏洞&#xff0c;且SolrResourceLoader中实现了java SPI机制。当Solr以SolrCloud模式启动时&#xff0c;攻击者可以通过构造恶意的solrconfig.xml文…

几个最基本软件的环境变量配置

在Windows中配置环境变量位置&#xff1a; 控制面板->系统和安全->系统。可以点击&#xff1a;“此电脑”->“属性”直接进入。 点击“高级系统设置”->【环境变量】。在这里可以看见用户变量和系统变量&#xff0c;如果你这台机器不是你一个人使用设置为用户变量…

接口文档设计避坑指南

我们做后端开发的,经常需要定义接口文档。 最近在做接口文档评审的时候&#xff0c;发现一个小伙伴定义的出参是个枚举值&#xff0c;但是接口文档没有给出对应具体的枚举值。其实&#xff0c;如何写好接口文档&#xff0c;真的很重要。今天田螺哥&#xff0c;给你带来接口文档…

Vue学习笔记(4. 生命周期)

1. 生命周期写法&#xff08;vue2与vue3比对&#xff09; 创建前&#xff1a;vue3 setup, vue2 beforeCreate //组件创建前执行的函数 创建后&#xff1a;vue3 setup, vue2 created //组件创建后执行的函数 挂载前&#xff1a;vue3 onBeforeMount, vue2 beforeMount //挂…

FastViT: A Fast Hybrid Vision Transformer using Structural Reparameterization

FastViT: A Fast Hybrid Vision Transformer using Structural Reparameterization 论文地址&#xff1a;https://arxiv.org/pdf/2303.14189.pdf 概述 本文提出了一种通用的 CNN 和 Transformer 混合的视觉基础模型 移动设备和 ImageNet 数据集上的精度相同的前提下&#xf…

SpringBoot自动配置原理分析

前言&#xff1a; 虽然工作中一直使用的是自研的一款基于spring的框架&#xff0c;但是随着springboot在各公司的广泛使用&#xff0c;公司的一些新项目也开始逐渐使用springBoot了&#xff0c;那么springBoot的一些特性就要仔细学习一下了。 什么是自动配置&#xff1f; 还记…

【牛客刷题专栏】0x21:JZ20 表示数值的字符串(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

Voice Control for ChatGPT 轻松实现使用语音与ChatGPT进行对话。

缘由 日常生活中&#xff0c;我们与亲人朋友沟通交流一般都是喜欢语音的形式来完成的&#xff0c;毕竟相对于文字来说语音就不会显的那么的苍白无力&#xff0c;同时最大的好处就是能解放我们的双手吧&#xff0c;能更快实现两者间的对话&#xff0c;沟通便更高效了。Voice Co…