Live555源码阅读笔记:哈希表的实现(C++)

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍哈希表的实现 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰: 2024-07-24 23:44:05

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、HashTable 类
  • 🎄三、BasicHashTable 类
    • ✨3.1 插入操作
    • ✨3.2 删除操作
    • ✨3.3 查询操作
    • ✨3.4 查询哈希表元素个数
  • 🎄四、哈希表的使用
  • 🎄五、总结


在这里插入图片描述

在这里插入图片描述

🎄一、概述

本文介绍Live555源码的哈希表实现,如果对哈希表不了解的,可以看这篇文章:https://blog.csdn.net/wkd_007/article/details/140655297

哈希表是存储 键值对 的一个数据结构。一个哈希表应该要提供插入元素、删除元素、查询元素的操作,而具体的哈希表实现还会出现哈希函数、处理哈希冲突、键值对相关结构体、比对键值等,我们看哈希表代码时需要格外注意这些实现。

下图是Live555哈希表相关类的关系图:
1、HashTable 是个基类,规范哈希表的接口;
2、BasicHashTable 是哈希表的具体实现;
3、TableEntry 是存储键值对的,同时它还有个指针指向自己,说明这里应该是使用 链地址法 来处理哈希冲突的。

在这里插入图片描述
Live555哈希表相关代码主要在这4个文件:HashTable.hh、HashTable.cpp、BasicHashTable.hh、BasicHashTable.cpp。主要涉及两个类,下面介绍具体实现。


在这里插入图片描述

🎄二、HashTable 类

HashTable 类声明:

class HashTable {
public:virtual ~HashTable();// The following must be implemented by a particular// implementation (subclass):static HashTable* create(int keyType);virtual void* Add(char const* key, void* value) = 0;  // Returns the old value if different, otherwise 0virtual Boolean Remove(char const* key) = 0;virtual void* Lookup(char const* key) const = 0;virtual unsigned numEntries() const = 0;	// Returns 0 if not foundBoolean IsEmpty() const { return numEntries() == 0; }// Used to iterate through the members of the table:class Iterator {public:// The following must be implemented by a particular// implementation (subclass):static Iterator* create(HashTable const& hashTable);virtual ~Iterator();virtual void* next(char const*& key) = 0; // returns 0 if noneprotected:Iterator(); // abstract base class};// A shortcut that can be used to successively remove each of// the entries in the table (e.g., so that their values can be// deleted, if they happen to be pointers to allocated memory).void* RemoveNext();// Returns the first entry in the table.// (This is useful for deleting each entry in the table, if the entry's destructor also removes itself from the table.)void* getFirst(); protected:HashTable(); // abstract base class
};

首先,我们看到 HashTable 类声明了4个纯虚函数:

virtual void* Add(char const* key, void* value) = 0;// Returns the old value if different, otherwise 0
virtual Boolean Remove(char const* key) = 0;
virtual void* Lookup(char const* key) const = 0;
virtual unsigned numEntries() const = 0;

这说明 HashTable 类是个抽象基类,所以它不会实例化对象,同时很可能会使用 HashTable 类的指针或引用去管理其派生类对象。这四个函数对应了哈希表的插入、删除、查询、哈希值总个数 4个操作,这里也规定了子类必须按照这样的声明去实现这几个操作。


其次,注意到static HashTable* create(int keyType);,说明很大可能使用这个函数来创建哈希表,并且这里的返回值是 HashTable*,但HashTable是抽象类,不会有对象,这里的指针应该指向派生类 BasicHashTable 的对象。函数参数 keyType 说明创建哈希表需要指定键值类型,HashTable.hh给出了两个键值类型:

int const STRING_HASH_KEYS = 0;
int const ONE_WORD_HASH_KEYS = 1;

再者,HashTable 类声明了一个迭代器类 Iterator ,并且要求传入哈希表引用,应该是用来遍历哈希表的。

最后就是 RemoveNext 操作删除一个哈希表元素,getFirst 操作获取第一个哈希表元素。


在这里插入图片描述

🎄三、BasicHashTable 类

BasicHashTable 类是哈希表的具体实现,它实现了哈希表的基本操作:插入、删除、查询。也实现了哈希函数、键值比对、哈希表扩容等函数,不过这些都是隐藏(private)的。

BasicHashTable 类声明如下:

class BasicHashTable: public HashTable {
private:class TableEntry; // forwardpublic:BasicHashTable(int keyType);virtual ~BasicHashTable();// Used to iterate through the members of the table:class Iterator; friend class Iterator; // to make Sun's C++ compiler happyclass Iterator: public HashTable::Iterator {// 迭代器,用于迭代传入的哈希表 2024-07-22 23:43:05public:Iterator(BasicHashTable const& table);private: // implementation of inherited pure virtual functionsvoid* next(char const*& key); // returns 0 if noneprivate:BasicHashTable const& fTable;unsigned fNextIndex; // index of next bucket to be enumerated after thisTableEntry* fNextEntry; // next entry in the current bucket};private: // implementation of inherited pure virtual functions// 实现继承的纯虚函数,这里是 private 的,也就是说不能通过 BasicHashTable 对象直接调用,但可以通过父类指针或引用来多态调用 2024-07-24virtual void* Add(char const* key, void* value);// Returns the old value if different, otherwise 0virtual Boolean Remove(char const* key);virtual void* Lookup(char const* key) const;// Returns 0 if not foundvirtual unsigned numEntries() const;private:class TableEntry {public:TableEntry* fNext;char const* key;void* value;};TableEntry* lookupKey(char const* key, unsigned& index) const;// returns entry matching "key", or NULL if noneBoolean keyMatches(char const* key1, char const* key2) const;// used to implement "lookupKey()"TableEntry* insertNewEntry(unsigned index, char const* key);// creates a new entry, and inserts it in the tablevoid assignKey(TableEntry* entry, char const* key);// used to implement "insertNewEntry()"void deleteEntry(unsigned index, TableEntry* entry);void deleteKey(TableEntry* entry);// used to implement "deleteEntry()"void rebuild(); // rebuilds the table as its size increasesunsigned hashIndexFromKey(char const* key) const;// used to implement many of the routines aboveunsigned randomIndex(uintptr_t i) const {return (unsigned)(((i*1103515245) >> fDownShift) & fMask);}private:TableEntry** fBuckets; // pointer to bucket arrayTableEntry* fStaticBuckets[SMALL_HASH_TABLE_SIZE];// used for small tablesunsigned fNumBuckets, fNumEntries, fRebuildSize, fDownShift, fMask;int fKeyType;
};

首先,BasicHashTable类继承自HashTable类,并且实现了继承的全部纯虚函数,所以BasicHashTable类不是抽象类,可以实例化对象。BasicHashTable类有点特别,只提供了创建对象接口(BasicHashTable)、销毁对象接口(~BasicHashTable),其他函数都是私有的,即使构造了对象,也没有公有接口可以使用,让用户只能通过基类(HashTable)指针或引用去使用基类声明为公有的几个纯虚函数,然后通过多态调用到 BasicHashTable 类的相应接口。

构造函数:

#define REBUILD_MULTIPLIER 3
BasicHashTable::BasicHashTable(int keyType): fBuckets(fStaticBuckets), fNumBuckets(SMALL_HASH_TABLE_SIZE),fNumEntries(0), fRebuildSize(SMALL_HASH_TABLE_SIZE*REBUILD_MULTIPLIER),fDownShift(28), fMask(0x3), fKeyType(keyType) {for (unsigned i = 0; i < SMALL_HASH_TABLE_SIZE; ++i) {fStaticBuckets[i] = NULL;}
}

哈希表最初创建时,只是用个包含3个元素的数组。

在阅读下面小节之前,先了解 BasicHashTable 存储键值对的结构体,除了key、value,还包含了一个 fNext 指针:

class TableEntry {
public:TableEntry* fNext;char const* key;void* value;
};

✨3.1 插入操作

BasicHashTable类插入操作通过 Add 函数实现,代码如下:

// 添加一个键值对到哈希表,2024-07-22 23:49:05
void* BasicHashTable::Add(char const* key, void* value) {void* oldValue;unsigned index;TableEntry* entry = lookupKey(key, index);if (entry != NULL) {// There's already an item with this keyoldValue = entry->value;} else {// There's no existing entry; create a new one:entry = insertNewEntry(index, key);oldValue = NULL;}entry->value = value;// If the table has become too large, rebuild it with more buckets:if (fNumEntries >= fRebuildSize) rebuild();return oldValue;
}

先查找 key 值是否已存在了,是的话就替换并返回旧的value值,不存在就插入新的键值对(insertNewEntry)。下面看看Add依赖的几个函数:lookupKey、hashIndexFromKey、keyMatches、insertNewEntry、assignKey、rebuild。

lookupKey 函数:根据key找到一个键值对(entry),下标index

// 根据key找到一个键值对(entry),下标index,2024-07-22 23:36:55
BasicHashTable::TableEntry* BasicHashTable
::lookupKey(char const* key, unsigned& index) const {TableEntry* entry;index = hashIndexFromKey(key);for (entry = fBuckets[index]; entry != NULL; entry = entry->fNext) {if (keyMatches(key, entry->key)) break;}return entry;
}

先计算出哈希地址(hashIndexFromKey),再比对键值,找到键值对 entry。

hashIndexFromKey函数:哈希函数,根据key生成哈希地址index

// 哈希函数,根据key生成一个下标 2024-07-22 23:07:25
unsigned BasicHashTable::hashIndexFromKey(char const* key) const {unsigned result = 0;if (fKeyType == STRING_HASH_KEYS) {while (1) {char c = *key++;if (c == 0) break;result += (result<<3) + (unsigned)c;}result &= fMask;printf("result = %d\n",result);} else if (fKeyType == ONE_WORD_HASH_KEYS) {result = randomIndex((uintptr_t)key);	} else {unsigned* k = (unsigned*)key;uintptr_t sum = 0;for (int i = 0; i < fKeyType; ++i) {sum += k[i];}result = randomIndex(sum);}return result;
}

按照不同键值类型去生成哈希地址。

keyMatches 函数:比对两个键值是否相等,相等返回true。

// 比较两个key是否一致 2024-07-22 23:35:24
Boolean BasicHashTable
::keyMatches(char const* key1, char const* key2) const {// The way we check the keys for a match depends upon their type:if (fKeyType == STRING_HASH_KEYS) {return (strcmp(key1, key2) == 0);} else if (fKeyType == ONE_WORD_HASH_KEYS) {return (key1 == key2);} else {unsigned* k1 = (unsigned*)key1;unsigned* k2 = (unsigned*)key2;for (int i = 0; i < fKeyType; ++i) {if (k1[i] != k2[i]) return False; // keys differ}return True;}
}

insertNewEntry函数:生成一个entry, 插入到链表,并给entry->key赋值

// 生成一个entry, 插入到链表,并给entry->key赋值,2024-07-22 23:34:42
BasicHashTable::TableEntry* BasicHashTable
::insertNewEntry(unsigned index, char const* key) {TableEntry* entry = new TableEntry();entry->fNext = fBuckets[index];fBuckets[index] = entry;++fNumEntries;assignKey(entry, key);return entry;
}

这里使用头插法,将新的entry插入到 fBuckets[index] 的位置。

assignKey函数: 根据键值类型、key值,创建一个 Key,并赋值到entry的key字段

// 根据键值类型、key值,创建一个 Key,并赋值到entry的key字段 2024-07-22 21:16:20
void BasicHashTable::assignKey(TableEntry* entry, char const* key) {// The way we assign the key depends upon its type:if (fKeyType == STRING_HASH_KEYS) {entry->key = strDup(key);} else if (fKeyType == ONE_WORD_HASH_KEYS) {entry->key = key;} else if (fKeyType > 0) {unsigned* keyFrom = (unsigned*)key;unsigned* keyTo = new unsigned[fKeyType];for (int i = 0; i < fKeyType; ++i) keyTo[i] = keyFrom[i];entry->key = (char const*)keyTo;}
}

rebuild 函数:重新建立一个哈希表

// 重新建立一个哈希表,2024-07-22 21:22:08
void BasicHashTable::rebuild() {// Remember the existing table size:unsigned oldSize = fNumBuckets;TableEntry** oldBuckets = fBuckets;// Create the new sized table:fNumBuckets *= 4;fBuckets = new TableEntry*[fNumBuckets];for (unsigned i = 0; i < fNumBuckets; ++i) {fBuckets[i] = NULL;}fRebuildSize *= 4;fDownShift -= 2;fMask = (fMask<<2)|0x3;// Rehash the existing entries into the new table:for (TableEntry** oldChainPtr = oldBuckets; oldSize > 0;--oldSize, ++oldChainPtr) {for (TableEntry* hPtr = *oldChainPtr; hPtr != NULL;hPtr = *oldChainPtr) {*oldChainPtr = hPtr->fNext;unsigned index = hashIndexFromKey(hPtr->key);hPtr->fNext = fBuckets[index];fBuckets[index] = hPtr;}}// Free the old bucket array, if it was dynamically allocated:if (oldBuckets != fStaticBuckets) delete[] oldBuckets;
}

当哈希表键值对个数太多时,就会重建哈希表,避免因个数太多降低查询效率。


✨3.2 删除操作

BasicHashTable 的删除操作通过 Remove 函数实现,代码如下:

// 删除一个键值对(entry),2024-07-22 23:49:08
Boolean BasicHashTable::Remove(char const* key) {unsigned index;TableEntry* entry = lookupKey(key, index);if (entry == NULL) return False; // no such entrydeleteEntry(index, entry);return True;
}

1、先根据key找到键值对entry、index;
2、再删除键值对 (deleteEntry)。

deleteEntry 函数:删除指定下标的一个键值对(Entry)

// 删除指定下标的一个键值对(Entry),2024-07-22 21:20:18
void BasicHashTable::deleteEntry(unsigned index, TableEntry* entry) {TableEntry** ep = &fBuckets[index];	// 找到存在数组的位置Boolean foundIt = False;while (*ep != NULL) {  // 遍历链表if (*ep == entry) {foundIt = True;*ep = entry->fNext;break;}ep = &((*ep)->fNext);}if (!foundIt) { // shouldn't happen
#ifdef DEBUGfprintf(stderr, "BasicHashTable[%p]::deleteEntry(%d,%p): internal error - not found (first entry %p", this, index, entry, fBuckets[index]);if (fBuckets[index] != NULL) fprintf(stderr, ", next entry %p", fBuckets[index]->fNext);fprintf(stderr, ")\n");
#endif}--fNumEntries;deleteKey(entry);delete entry;
}

1、先找到存在数组的位置;
2、遍历链表,找到键值对entry;
3、删除键值(deleteKey);

deleteKey函数:删除一个键值对(Entry)的Key

// 删除一个键值对(Entry)的Key,2024-07-22 21:21:12
void BasicHashTable::deleteKey(TableEntry* entry) {// The way we delete the key depends upon its type:if (fKeyType == ONE_WORD_HASH_KEYS) {entry->key = NULL;} else {delete[] (char*)entry->key;entry->key = NULL;}
}

✨3.3 查询操作

BasicHashTable 的查询操作通过 Lookup 函数实现,代码如下:

// 查找,根据key找到value,2024-07-22 23:48:20
void* BasicHashTable::Lookup(char const* key) const {unsigned index;TableEntry* entry = lookupKey(key, index);if (entry == NULL) return NULL; // no such entryreturn entry->value;
}

通过key找到键值对entry,然后将value返回。


✨3.4 查询哈希表元素个数

BasicHashTable 的查询元素个数通过 numEntries 函数实现,代码如下:

// 哈希表总的键值对(entry)个数,2024-07-22 23:47:35
unsigned BasicHashTable::numEntries() const {return fNumEntries;
}

在这里插入图片描述

🎄四、哈希表的使用

这个小节,写个示例来使用上面介绍的哈希表,完整源码也上传了。下载地址:https://download.csdn.net/download/wkd_007/89576315

// g++ *.cpp -o HashTest#include <iostream>
#include "BasicHashTable.hh"
using namespace std;void printHashTable(HashTable const& hashTable)
{HashTable::Iterator *itor = HashTable::Iterator::create(hashTable);void* value = NULL;const char* pKey = NULL;while((value = itor->next(pKey)) != NULL){const char* value_str = (const char*)value;cout << "Key=" << pKey << ", Value=" << value_str << endl;}delete itor;
}int main()
{HashTable *pHashTable = new BasicHashTable(STRING_HASH_KEYS);pHashTable->Add("testA",(void*)"testA");pHashTable->Add("testB",(void*)"testB");pHashTable->Add("testC",(void*)"testC");pHashTable->Add("testD",(void*)"testD");pHashTable->Add("testE",(void*)"testE");pHashTable->Add("",(void*)"testF");		// 空字符串也可以const char *pTestA = (const char*)pHashTable->Lookup("testA");cout << "testA=" << pTestA << endl;cout << endl;printHashTable(*pHashTable);cout << endl;pHashTable->Add("",(void*)"testG");		// 替换空字符串的值printHashTable(*pHashTable);cout << endl;delete pHashTable;return 0;
}

在这里插入图片描述

🎄五、总结

👉本文介绍了Live555的哈希表实现,最后给出了使用例子,对于想了解哈希表实现或Live555源码的同学有一定的帮助。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据倾斜优化思路实践

数据倾斜&#xff0c;顾名思义&#xff0c;就是在计算过程中数据分散度不够&#xff0c;导致某个节点数据过于集中&#xff0c;从而导致任务执行效率大大降低。参照对比下MR的整体流程和ODPS&#xff0c;整体结合理解数据倾斜发生的几个生命周期的节点&#xff0c;如下图&#…

Stable Diffusion绘画 | 新手必备知识点(一)

模型 模型分为 大模型 Checkpoint 、 Lora模型 以及 其他模型。 大模型是生成图片的基石&#xff0c;选择怎样的大模型&#xff0c;会直接影响图片最终生成的风格。 大模型 大模型又分为 普通模型 以及 SDXL模型&#xff0c;包括&#xff1a; 真实风二次元2.5D 普通模型&…

[C#]调用本地摄像头录制视频并保存

AForge.NET是一个基于C#框架设计的开源计算机视觉和人工智能库&#xff0c;专为开发者和研究者设计。它提供了丰富的图像处理和视频处理算法、机器学习和神经网络模型&#xff0c;具有高效、易用、稳定等特点。AForge库由多个组件模块组成&#xff0c;包括AForge.Imaging&#…

Datawhale AI 夏令营——AI+逻辑推理——Task1

# Datawhale AI 夏令营 夏令营手册&#xff1a;从零入门 AI 逻辑推理 比赛&#xff1a;第二届世界科学智能大赛逻辑推理赛道&#xff1a;复杂推理能力评估 代码运行平台&#xff1a;魔搭社区 比赛任务 本次比赛提供基于自然语言的逻辑推理问题&#xff0c;涉及多样的场景&…

大数据——Hive原理

摘要 Apache Hive 是一个基于 Hadoop 分布式文件系统 (HDFS) 的数据仓库软件项目&#xff0c;专为存储和处理大规模数据集而设计。它提供类似 SQL 的查询语言 HiveQL&#xff0c;使用户能够轻松编写复杂的查询和分析任务&#xff0c;而无需深入了解 Hadoop 的底层实现。 Hive…

Android TabLayout的简单用法

TabLayout 注意这里添加tab&#xff0c;使用binding.tabLayout.newTab()进行创建 private fun initTabs() {val tab binding.tabLayout.newTab()tab.text "模板库"binding.tabLayout.addTab(tab)binding.tabLayout.addOnTabSelectedListener(object : TabLayout.On…

day3 测试基础知识

1. 你认为性能测试的目的是什么&#xff1f;做好性能测试的工作的关键是什么&#xff1f; 性能测试工作的目的是检查系统是否满足在需求说明书中规定的性能&#xff0c;性能测试常常需要和强度测试结合起来&#xff0c;并常常要求同时进行软件和硬件的检测。 性能测试主要的关…

MySQL中实现动态表单中JSON元素精准匹配的方法

目录 前言 一、动态表单技术 1、包含的主要信息 2、元素属性设置 3、表单内容 二、表单数据存储和查询 1、数据存储 2、数据的查询 3、在5.7版本中进行JSON检索 4、8.0后的优化查询 三、总结 前言 在很多有工作流设置的地方、比如需要在不同的流程中&#xff0c;需要…

【初阶数据结构篇】顺序表的实现(赋源码)

文章目录 本篇代码位置顺序表和链表1.线性表2.顺序表2.1 概念与结构2.2分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现2.3.1动态顺序表的初始化和销毁及打印2.3.2动态顺序表的插入动态顺序表的尾插动态顺序表的头插动态顺序表的在指定位置插入数据 2.3.3动态顺序表…

html+css 实现单选按钮动画(input radio按钮)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

【第二天】计算机网络 HTTP请求报文和响应报文是什么样的 HTTP请求方式有哪些 GET请求和POST请求的区别

HTTP请求报文和响应报文是什么样的&#xff1f; 我去&#xff0c;以前都没怎么研究过这个。 客户端发送一个请求给服务器&#xff0c;服务器根据请求报文中的信息进行处理&#xff0c;并将处理结果放到响应报文中返回给客户端。 URL HTTP使用URL (Uniform Resource Locator&…

OriginPro 2024b (学习版) 绘制3D坐标下 边际直方图

OriginPro 2024b (学习版) 绘制3D坐标下 边际直方图 时间 2024年7月27日 1.导入数据 需要3列数据&#xff0c;分别作为x,y,z, 其中z值随便设置。快速设置z值的方法&#xff1a;在第4行“F(x)”输入1&#xff0c;这一列的值全设置为1了。 设置x,y,z的方法如下&#xff1a;点击…

WHAT - 一个 Github 仓库的 License 如何解读

目录 一、背景二、解读许可证说明的作用常见的开源许可证类型使用他人代码仓库时需要注意的事项结论 实践作为开发者1. 选择许可证类型2. 在 README 文件中编写许可证信息 作为使用者1. 确定权限2. 了解和遵守条款 总结 一、背景 我们经常在一些 Github 仓库里看到 License 部…

如何知道一个字段在selenium中是否可编辑?

这篇文章将检查我们如何使用Java检查selenium webdriver中的字段是否可编辑。 我们如何知道我们是否可以编辑字段&#xff1f;“readonly”属性控制字段的可编辑性。如果元素上存在“readonly”属性&#xff0c;则无法编辑或操作该元素或字段。 因此&#xff0c;如果我们找到一…

【每日一篇】使用图神经网络进行交通速度预测的上下文感知知识图谱框架【为了自己方便读论文】

Context-aware knowledge graph framework for traffic speed forecasting using graph neural network 论文链接&#xff1a; https://arxiv.org/abs/2407.17703 翻译&#xff1a; 摘要 人类流动在空间和时间上受到城市环境的密切影响&#xff0c;构成了理解交通系统的重…

electron TodoList网页应用打包成linux deb、AppImage应用

这里用的是windows的wsl的ubuntu环境 electron应用打包linux应用需要linux下打包&#xff0c;这里用windows的wsl的ubuntu环境进行操作 1&#xff09;linux ubuntu安装nodejs、electron 安装nodejs&#xff1a; sudo apt update sudo apt upgrade ##快捷安装 curl -fsSL http…

7-23学习笔记

一、异常 即程序中一些程序处理不了的特殊情况 Exception 能被程序本身处理( try-catch )&#xff0c; Error 是无法处理的(只能尽量避免)。 1、异常类 Exception 见过的异常 NullPointerException ArrayIndexoutOfBoundException等 String strnull;System.out.println(st…

《python程序语言设计》第6章14题 估算派值 类似莱布尼茨函数。但是我看不明白

这个题提供的公式我没看明白&#xff0c;后来在网上找到了莱布尼茨函数 c 0 for i in range(1, 902, 100):a (-1) ** (i 1)b 2 * i - 1c a / bprint(i, round(4 / c, 3))结果 #按题里的信息&#xff0c;但是结果不对&#xff0c;莱布尼茨函数到底怎么算呀。

Docker学习与实战

一、Docker安装 移除旧版本docker sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine配置docker yum源 sudo yum install -y yum-utils配置阿里云docker仓库 sudo y…

学习记录:ESP32控制舵机 FREERTOS BLE

控制舵机 PWM信号 PWM信号是一种周期性变化的方波信号&#xff0c;它有两个关键参数&#xff1a; 周期&#xff08;Period&#xff09;&#xff1a;一个完整的PWM信号的时间长度&#xff0c;通常用秒&#xff08;s&#xff09;或毫秒&#xff08;ms&#xff09;表示。占空比…