我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何理解Objective-C高性能的循环

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何理解Objective-C高性能的循环

这篇文章将为大家详细讲解有关如何理解Objective-C高性能的循环,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

Cocoa编程的一个通常的任务是要去循环遍历一个对象的集合 (例如,一个 NSArray, NSSet 或者是 NSDictionary). 这个看似简单的问题有广泛数量的解决方案,它们中的许多不乏有对性能方面问题的细微考虑.

对于速度的追求

首先,是一个免责声明: 相比其它问题而言,一个 Objective-C 方法原始的速度是你在编程时***才需要考虑的问题之一 – 区别就在于这个问题够不上去同其它更加需要重点考虑的问题进行比较,比如说代码的清晰度和可读性.

但速度的次要性并不妨碍我们去理解它. 你应该经常去了解一下性能方面的考虑将如何对你正在编写的代码产生影响,一边在极少数发生问题的情况下,你会知道如何下手.

还有,在循环的场景中,大多数时候不管是从可读性或者是清晰度考虑,你选择哪种技术都没什么关系的, 所以你还不如选择速度最快的那一种. 没有必要选择编码速度比要求更慢的。

考虑到这一点,就有了如下的选择:

经典的循环方式

for (NSUInteger i = 0; i < [array count]; i++){    id object = array[i];    &hellip;}

这是循环遍历一个数组的一个简单熟悉的方式; 从性能方面考虑它也相当的差劲. 这段代码***的问题就是循环每进行一次我们都会调用数组的计数方法. 数组的总数是不会改变的,因此每次都去调用一下这种做法是多余的. 像这种代码一般C编译器一般都会优化掉, 但是 Objective-C 的动态语言特性意味着对这个方法的调用不会被自动优化掉. 因此,为了提升性能,值得我们在循环开始之前,将这个总数存到一个变量中,像这样:

NSUInteger count = [array count];for (NSUInteger i = 0; i < count; i++){    id object = array[i];    &hellip;}

NSEnumerator

NSEnumerator 是循环遍历集合的一种可选方式. 所有的集合都已一个或者更多个枚举方法,每次它们被调用的时候都会返回一个NSEnumerator实体. 一个给定的 NSEnumerator 会包含一个指向集合中***个对象的指针, 并且会有一个 nextObject 方法返回当前的对象并对指针进行增长. 你可以重复调用它直到它返回nil,这表明已经到了集合的末尾了:

id obj = nil;NSEnumerator *enumerator = [array objectEnumerator];while ((obj = [enumerator nextObject]));{    &hellip;            }

NSEnumerator 的性能可以媲美原生的for循环, 但它更加实用,因为它对索引的概念进行了抽象,这意味着它应用在结构化数据上,比如链表,或者甚至是无穷序列和数据流,这些结构中的数据条数未知或者并没有被定义.

快速枚举

快速枚举是在 Objective-C 2.0 中作为传统的NSEnumerator的更便利(并且明显更快速) 的替代方法而引入的. 它并没有使得枚举类过时因为其仍然被应用于注入反向枚举, 或者是当你需要对集合进行变更操作 (之后会更多地提到) 这些场景中.
快速枚举添加了一个看起来像下面这样子的新的枚举方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state      objects:(id *)stackbuf count:(NSUInteger)len;

如果你正在想着“那看起来并不怎么舒服啊!”, 我不会怪你的. 但是新的方法顺便带来了一种新的循环语法, for&hellip;in 循环. 这是在幕后使用了新的枚举方法, 并且重要的是在语法和性能上都比使用传统的for循环或者 NSEnumerator 方法都更省心了:

for (id object in array){    &hellip;}

枚举块

随着块的诞生,Apple加入第四个基于块语法的枚举机制. 这无疑比快速枚举更加的少见, 但是有一个优势就是对象和索引都会返回, 而其他的枚举方法只会返回对象.

枚举块的另外一个关键特性就是可选择型的并发枚举 (在几个并发的线程中枚举对象). 这不是经常有用,取决于你在自己的循环中具体要做些什么, 但是在你正有许多工作要做,并且你并不怎么关心枚举顺序的场景下,它在多核处理器上可能会产生显著的性能提高 (现在所有的 Mac和iOS设备都已经有了多核处理器).

基准测试

那么这些方法叠加起来会如何呢, 性能会更加的好么? 这里有一个简单的基准测试命令行应用,比较了使用多种不同方法枚举一个数据的性能. 我们已经在 ARC 关闭的情况下运行了它,以排除任何干扰最终结果的隐藏在幕后的保留或者排除处理. 由于是运行在一个很快的 Mac 机上面, 所有这些方法运行极快以至于我们实际上不得不使用一个存有10,000,000 (一千万) 对象的数组来测量结果. 如果你决定在一个 iPhone 进行测试, 最明智的做法是使用一个小得多的数量!

为了编译这段代码:

  • 把代码保存在一个文件中,命名为 benchmark.m

  • 在终端中编译应用程序:
    clang -framework Foundation benchmark.m -o benchmark

  • 运行程序: ./benchmark

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){    @autoreleasepool  {      static const NSUInteger arrayItems = 10000000;       NSMutableArray *array = [NSMutableArray arrayWithCapacity:arrayItems];    for (int i = 0; i < arrayItems; i++) [array addObject:@(i)];      array = [array copy];          CFTimeInterval start = CFAbsoluteTimeGetCurrent();       // Naive for loop      for (NSUInteger i = 0; i < [array count]; i++)      {        id object = array[i];    }       CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"For loop: %g", forLoop - start);       // Optimized for loop      NSUInteger count = [array count];    for (NSUInteger i = 0; i <  count; i++)      {        id object = array[i];    }       CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();      NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);       // NSEnumerator      id obj = nil;    NSEnumerator *enumerator = [array objectEnumerator];    while ((obj = [enumerator nextObject]))      {     }       CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);       // Fast enumeration      for (id object in array)      {     }       CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"For&hellip;in loop: %g", forInLoop - enumeratorLoop);       // Block enumeration      [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];          CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();      NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);       // Concurrent enumeration      [array enumerateObjectsWithOptions:NSEnumerationConcurrent         usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];          CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();      NSLog(@"Concurrent enumeration block: %g",         concurrentEnumerationBlock - enumerationBlock);  }    return 0;}

下面展示出了结果:

$ For loop: 0.119066  $ Optimized for loop: 0.092441  $ Enumerator: 0.123687  $ For&hellip;in loop: 0.049296  $ Enumeration block: 0.295039  $ Concurrent enumeration block: 0.199684

忽略掉时间的具体长短. 我们感兴趣的是它们同其它方法比较的相对大小. 如果我们按顺序排列它们,快的放前面,我会得到了下面的结果:

  1. For&hellip;in循环 &ndash; 最快.

  2. 对for循环的优化 &ndash; 比 for&hellip;in 慢两倍.

  3. 没有优化的for循环 &ndash; 比 for&hellip;in 慢2.5倍.

  4. Enumerator &ndash; 大约同没有优化的循环相同.

  5. 并发的枚举块 &ndash; 比 for&hellip;in 大约慢6倍.

  6. 枚举块 &ndash; 比 for&hellip;in 几乎慢6倍.

For&hellip;in 是胜出者. 显然他们将其称为快速枚举是有原因的! 并发枚举看起来是比单线程的快一点点, 但是你没必要对其做更多的解读: 我们这里是在枚举一个非常非常大型的对象数组,而对于小一些的数据并发执行的开销远多于其带来的好处.

并发执行的主要是在当你的循环需要大量的执行时间时有优势. 如果你在自己的循环中有许多东西要运行,那就考虑试下并行枚举,在你不关心枚举顺序的前提下 (但是请用行动的去权衡一下它是否变得更快乐,不要空手去揣度).

其它集合类型Other Collection Types

那么其它的结合类型怎么样呢, 比如 NSSet 和 NSDictionary? NSSet 是无序的, 因此没有按索引去取对象的概念.我们也可以进行一下基准测试:

$ Enumerator: 0.421863  $ For&hellip;in loop: 0.095401  $ Enumeration block: 0.302784  $ Concurrent enumeration block: 0.390825

结果同 NSArray 一致; for&hellip;in 再一次胜出了. NSDictionary怎么样了? NSDictionary 有一点不同因为我们同时又一个键和值对象需要迭代. 在一个字典中单独迭代键或者值是可以的, 但典型的情况下我们两者都需要. 这里我们有一段适配于操作NSDictionary的基准测试代码:

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){    @autoreleasepool  {      static const NSUInteger dictItems = 10000;       NSMutableDictionary *dictionary =         [NSMutableDictionary dictionaryWithCapacity:dictItems];    for (int i = 0; i < dictItems; i++) dictionary[@(i)] = @(i);      dictionary = [dictionary copy];          CFTimeInterval start = CFAbsoluteTimeGetCurrent();       // Naive for loop      for (NSUInteger i = 0; i < [dictionary count]; i++)      {        id key = [dictionary allKeys][i];      id object = dictionary[key];    }       CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"For loop: %g", forLoop - start);       // Optimized for loop      NSUInteger count = [dictionary count];    NSArray *keys = [dictionary allKeys];    for (NSUInteger i = 0; i <  count; i++)      {        id key = keys[i];      id object = dictionary[key];    }       CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();      NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);       // NSEnumerator      id key = nil;    NSEnumerator *enumerator = [dictionary keyEnumerator];    while ((key = [enumerator nextObject]))      {        id object = dictionary[key];    }       CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);       // Fast enumeration      for (id key in dictionary)      {        id object = dictionary[key];    }       CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();      NSLog(@"For&hellip;in loop: %g", forInLoop - enumeratorLoop);       // Block enumeration      [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {     }];          CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();      NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);       // Concurrent enumeration      [dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent         usingBlock:^(id key, id obj, BOOL *stop) {     }];          CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();      NSLog(@"Concurrent enumeration block: %g",         concurrentEnumerationBlock - enumerationBlock);  }    return 0;}

NSDictionary 填充起来比 NSArray 或者 NSSet 慢得多, 因此我们把数据条数减少到了10,000 (一万) 以避免机器锁住. 因而你应该忽略结果怎么会比那些 NSArray 低那么多,因为我们使用的是更少对象的 1000 次循环:

$ For loop: 2.25899  $ Optimized for loop: 0.00273103  $ Enumerator: 0.00496799  $ For&hellip;in loop: 0.001041  $ Enumeration block: 0.000607967  $ Concurrent enumeration block: 0.000748038

没有优化过的循环再这里慢得很壮观,因为每一次我们都复制了键数组. 通过把键数组和总数存到变量中,我们获得了更快的速度. 查找对象的消耗现在主宰了其它的因素,因此使用一个for循环, NSEnumerator 或者for&hellip;in 差别很小. 但是对于枚举块方法而言,它在一个方法中把键和值都返回了,所以现在变成了最快的选择。

反转齿轮

基于我们所见,如果所有其它的因素都一样的话,在循环遍历数组时你应该尝试去使用for...in循环, 而遍历字典时,则应该选择枚举块. 也有一些场景下这样的做法并不可能行得通,比如我们需要回头来进行枚举,或者当我们在遍历时想要变更集合的情况.
为了回过头来枚举一个数据,我们可以调用reverseObjectEnumerator方法来获得一个NSEnumerator 以从尾至头遍历数组. NSEnumerator, 就像是 NSArray 它自己, 支持快速的枚举协议. 那就意味着我们仍然可以在这种方式下使用 for&hellip;in, 而无速度和简洁方面的损失:

for (id object in [array reverseObjectEnumerator])   {  &hellip;  }

(除非你异想天开, NSSet 或者 NSDictionary 是没有等效的方法的, 而反向枚举一个 NSSet 或者NSDictionary无论如何都没啥意义, 因为键是无序的.)

如果你想使用枚举块的话, NSEnumerationReverse你可以试试, 像这样:

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {    &hellip;  }];

变更Mutation

应用同样的循环技术到变更中的集合上是可能的; 其性能也大致相同. 然而当你尝试在循环数组或者字典的时候修改它们,你可能经常会面临这样的异常:

'*** Collection XYZ was mutated while being enumerated.'

就像我们优化了的for循环, 所有这些循环技术的性能取决于事先把数据总数存下来,这意味着如果你开始在循环中间加入或者去掉一个数据时,这个数据就不正确了. 但是在循环进行中加入,替换或者移除一条数据时经常想要做的事情. 那么什么才是这个问题的解决之道呢?

我们经典的for循环可以工作得很好,因为它不依赖于驻留的总数常量; 我们只需要记得,如果我们添加或者移除了一条数据,就要增加或者减小索引. 但我们已经了解到for循环并不是一种速度快的解决方案. 我们优化过的for循环则是一个合理的选择, 只要我们记得按需递增或者递减技术变量,还有索引.

我们仍然可以使用for&hellip;in, 但前提是我们首先创建了一个数组的拷贝. 这会起作用的,例如:

for (id object in [array copy])    {     // Do something that modifies the array, e.g. [array removeObject:object];   }

如果我们对不同的技术进行基准测试(必要时把复制数组的开销算在内,以便我们可以对原来数组内的数据进行变更), 我们发现复制抵消了 for&hellip;in 循环之前所拥有的好处:

$ For loop: 0.111422  $ Optimized for loop: 0.08967  $ Enumerator: 0.313182  $ For&hellip;in loop: 0.203722  $ Enumeration block: 0.436741  $ Concurrent enumeration block: 0.388509

在我们遍历一个数组时修改这个数组最快的计数,似乎是需要使用一个优化了的for循环的.

对于一个 NSDictionary, 我们不需要为了使用NSEnumerator 或者快速枚举而复制整个字典; 我们可以只去使用allKeys方法获取到所有键的一个副本. 这都将能很好的运作起来:

// NSEnumerator    id key = nil;  NSEnumerator *enumerator = [[items allKeys] objectEnumerator];  while ((key = [enumerator nextObject]))    {      id object = items[key];    // Do something that modifies the value, e.g. dictionary[key] = newObject;    }   // Fast enumeration    for (id key in [dictionary allkeys])     {      id object = items[key];    // Do something that modifies the value, e.g. dictionary[key] = newObject;    }

然而同样的技术在使用enumerateKeysAndObjectsUsingBlock方法时并不能起作用. 如果我们循环遍历一个字典进行基准测试, 按照需要对键或者对字典整体创建备份,我们得到了下面的结果:

$ For loop: 2.24597  $ Optimized for loop: 0.00282001  $ Enumerator: 0.00508499  $ For&hellip;in loop: 0.000990987  $ Enumeration block: 0.00144804  $ Concurrent enumeration block: 0.00166804

这里我们可以看到 for&hellip;in 循环是最快的一个. 那是因为在for...in循环中根据键取对象的开销现在已经被在调用枚举块方法之前复制字典的开销盖过去了.

当枚举一个NSArray的时候:

  • 使用 for (id object in array) 如果是顺序枚举

  • 使用 for (id object in [array reverseObjectEnumerator]) 如果是倒序枚举

  • 使用 for (NSInteger i = 0; i < count; i++) 如果你需要知道它的索引值,或者需要改变数组

  • 尝试 [array enumerateObjectsWithOptions:usingBlock:] 如果你的代码受益于并行执行

当枚举一个NSSet的时候:

  • 使用  for (id object in set) 大多数时候

  • 使用 for (id object in [set copy]) 如果你需要修改集合(但是会很慢)

  • 尝试 [array enumerateObjectsWithOptions:usingBlock:] 如果你的代码受益于并行执行

当枚举一个NSDictionary的时候:

  • 使用  for (id object in set) 大多数时候

  • 使用 for (id object in [set copy]) 如果你需要修改词典

  • 尝试 [array enumerateObjectsWithOptions:usingBlock:] 如果你的代码受益于并行执行

这些方法可能不是最快的,但他们都是非常清晰易读的。所以请记住,有时是在不写干净的代码,和快速的代码之间做出选择,你会发现,你可以在两个世界得到***的。

关于如何理解Objective-C高性能的循环就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何理解Objective-C高性能的循环

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何理解Objective-C高性能的循环

这篇文章将为大家详细讲解有关如何理解Objective-C高性能的循环,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Cocoa编程的一个通常的任务是要去循环遍历一个对象的集合 (例如,一个
2023-06-17

如何提高MySQL循环查询的性能

可以通过以下方式提高MySQL循环查询的性能:使用索引:确保循环查询的字段都有适当的索引,这样可以加快查询速度。可以通过使用EXPLAIN语句来查看查询计划,确保索引被正确使用。优化查询语句:尽量避免在循环中执行复杂的查询语句,可以将多个
如何提高MySQL循环查询的性能
2024-04-30

触发器中如何优化循环逻辑的性能

在数据库触发器中优化循环逻辑的性能是一个复杂的问题,因为触发器通常在事务提交时执行,这可能会导致性能瓶颈。以下是一些建议,可以帮助你优化触发器中的循环逻辑:减少循环次数:尽可能地减少循环的迭代次数。例如,如果你可以通过调整数据模型或查询来避
触发器中如何优化循环逻辑的性能
2024-09-26

vue.js的事件循环机制如何理解

这篇文章主要介绍了vue.js的事件循环机制如何理解的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇vue.js的事件循环机制如何理解文章都会有所收获,下面我们一起来看看吧。一、事件循环机制介绍 JS
2023-06-29

c++循环引用导致的内存泄露如何解决

在 C++ 中,循环引用(circular reference)是指两个或多个对象相互引用,导致内存泄漏的情况。解决循环引用导致的内存泄漏问题可以采取以下几种方法:1. 使用智能指针(smart pointers):智能指针是 C++ 提供
2023-10-10

如何理解Python基础中的for循环语句

如何理解Python基础中的for循环语句,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Python for循环可以遍历任何序列的项目,如一个列表或者一个字符串。for循环的语
2023-06-02

如何理解高性能PHP开发框架Yii

这篇文章将为大家详细讲解有关如何理解高性能PHP开发框架Yii ,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Yii开发团队今天发布了Yii 1.1.11版本,这是Yii项目迁移至Githu
2023-06-17

如何理解C++Test的覆盖性

这篇文章将为大家详细讲解有关如何理解C++Test的覆盖性,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。对接触到C++Test测试的用户和学者来说,充分了解C++Test的工作原理是很重要的
2023-06-17

Tomcat高并发之如何理解道与性能调优

这篇文章主要介绍“Tomcat高并发之如何理解道与性能调优”,在日常操作中,相信很多人在Tomcat高并发之如何理解道与性能调优问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Tomcat高并发之如何理解道与性
2023-06-16

C++开发建议:如何提高C++代码的质量和性能

C++是一种强大的编程语言,广泛应用于系统级开发、游戏开发和高性能计算等领域。然而,C++也因其复杂性和灵活性而需要更高的代码质量和性能。本文将探讨一些关于如何提高C++代码质量和性能的建议。了解内存管理:C++是一种底层语言,能够直接操作
C++开发建议:如何提高C++代码的质量和性能
2023-11-22

代码重构如何帮助提高C++程序的性能?

代码重构对 c++++ 程序性能的提升作用,体现在:提高可读性和可维护性;消除重复代码;优化数据结构和算法;减少内存使用和计算开销。通过采用智能指针、避免深拷贝、内联函数和使用现代 c++ 语言特性等重构技术,可以显著提高 c++ 程序的性
代码重构如何帮助提高C++程序的性能?
2024-05-08

C++ 函数异常处理的性能影响如何?

c++++ 异常处理会带来额外的开销,包括内存分配、函数调用展开和查找匹配的 catch 子句。这些开销可能导致缓存未命中,从而影响性能。要减轻这些影响,建议限制异常使用、使用 noexcept 规范和考虑使用错误代码。C++ 函数异常处理
C++ 函数异常处理的性能影响如何?
2024-04-15

内存管理如何影响C++程序的性能?

在 c++++ 中,内存管理通过堆和栈影响程序性能。在堆上分配内存比栈上慢,应尽可能在栈上分配变量。内存泄漏会降低性能,应使用 raii、智能指针和内存分析工具来管理内存。内存管理如何影响 C++ 程序的性能在 C++ 中,内存管理对程序
内存管理如何影响C++程序的性能?
2024-05-08

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录