C++ STL学习笔记(2) 容器结构与分类

接着学习侯捷老师的C++ STL!

在使用容器的时候,需要明白容器中元素之间在内存里的关系是什么样的,是连续的,还是非连续的。

容器可以分为两类:
1. sequence container , 即序列容器

a. Array 连续空间 , 大小固定,不能扩充

b. Vector  后部可以扩充(当然扩充工作由Allocator完成),我们只需将元素放进去即可

c. Deque 双向的队列,前后都可以扩充

d. List 链表, 双向链表

e. Forward-List ; c++ 11 引进, 单向链表

2. associative container , 即关联式容器 (key,value)

a. set,内部用红黑树实现。(红黑树:一种高度平衡的二叉树,会自动调整左右的大小,方便查找),对于set, 它的key就是value

multiset,放入的元素的内容可以有重复。set则不能重复。

b.map: 具有key和value

multimap,放入的元素的key可以有重复。map则不能重复。

3. unordered container,  可翻译为不定序, 元素放入容器中,其次序是不定的,也可以看作一种关联式容器

a. unordered set/multiset

 

b. unordered map/multimap

上面的不定序用哈希表来表示的话会更加的直观:
侯捷老师PPT中的哈希表的结构:

通过哈希表,也能很好的解释为什么上述的容器可以翻译为不定序。

可以将上述的每个一深色的方块看作一个bucket,每个篮子中的元素用链表表示(seperate chaining), hash可以理解为:假如有二十个空间,现在放入一个元素进去,元素放入的位置需要通过计算得到,假设元素a经过计算需要放到第三个位置去,元素b经过计算也要放到第三个位置去,这时就会发生碰撞。一种解决方法是:通过一定的计算方式将他们分开放置。另一种方法是:既然碰撞,索性都放到同一个bucket中,以链表表示。这是目前最好的做法。

这样做面临一个问题: 如果一个bucket的链表太长,而查找的元素恰好又在最后一个,那么在查找过程中,这个bucket的链表查找效率会非常低,所以会对bucket的链表长度做某一些限制,如果链表长度超过限制,则会将所有的元素都打散重新进行计算排列位置,这就是为什么会称为不定序的原因。因为每次放入一个元素,原来的排列顺序可能会被打乱。

(void 类型的指针)

1. aray测试:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <array>
const int arraySize = 100000;
using std::cout;
using std::endl;
int main()
{
Timer timer; // 计时器
std::array<int, arraySize> c;
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(0, 100);
e.seed(10); // 设置生成随机数的种子
for (int i = 0; i < arraySize; i++)
{
c[i] = u(e);
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.data() << endl; // return pointer to mutable data array 返回指针 数组中第一个元素的地址
cout << c.size() << endl; // 大小
cout << c.front() << endl; // 第一个元素
cout << c.back() << endl; // 最后一个元素
return 0;
}

2. 容器vector放入元素,且排序

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <vector>
#include <cstdlib> // qsort snprintf
#include <string>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::vector<std::string> c;
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.data() << endl; // return pointer to mutable data array 返回指针
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
cout << c.capacity() << endl; // 实际的空间的大小 vector中内存分配以两倍增长的
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}

3. list双向链表

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <list>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::list<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort();
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.max_size() << endl; // 最大的元素个数
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
timer.reset();
c.sort(); // 排序
std::cout << "Sort Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}

4. forward_list单向链表

这个的用法于双向链表list基本相同

6. deque 双向队列

 一个容器占用一定的内存后,就不能再向外扩充了。而是以别的方式进行扩充,例如,对于vector,它是按照每次两倍的内存进行扩充的,但是它的扩充策略是先找到另一块两倍大小的内存,然后再将其原来的内容复制过去。

那么deque的扩充策略是什么样的,两端都可以扩充?
deque的内部结构:

 

可以看出deque是分段连续的,map中存储指针,每个指针指向一段连续的buffer,当一个buffer放满元素的时候,会自动分配下一个buffer。其中buffer是连续的。

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <deque>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::deque<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.max_size() << endl; // 最多能存储的元素个数
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}

7. stack, 我们非常熟悉的栈,出战,入栈操作

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <stack>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::stack<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
return 0;
}

8. queue,用法于stack非常类似,不做详细的介绍了

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <queue>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::queue<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
return 0;
}

对于stack和queue,它们内部实际上是通过deque实现的,所以也可以将他们称为容器适配器。

关联式容器

1. 使用multiset容器

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::multiset<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //set inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
std::string target("3456");
timer.reset();
auto iter = ::find(c.begin(), c.end(), target); // 通用的find函数
if (iter == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
cout << "found target " << *iter << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}

这里有必看介绍一下运行的结果:

可以看到,set内部的find函数比通用的::find()函数速度快很多,set内部利用红黑树实现,所以它的查找速度是非常快的。但是也可以发现,关联式容器在插入数据的时候,速度还是相当慢的。

2. 使用容器multimap

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::multimap<int, std::string> c; // key, value
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::pair<int, std::string>(i, std::string(buf))); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
// std::string target("3456")
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}

3. unordered_multiset

不定序, 这种multiset的实现是通过hash table进行的,而不是经典的红黑树

 

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <unordered_set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::unordered_multiset<std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
cout << c.max_bucket_count() << endl;
cout << c.bucket_count() << endl; // bucket的数量
// 关联式容器的查找
std::string target("3456");
// int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
int bucket_cnt = c.bucket_count(); // 遍历一下bucket
for (int i = 0; i < 10; i++)
{
cout << "Bucket #" << i << " has " << c.bucket_size(i) << " elements" << endl;
}
return 0;
}

可以查看hash table中数据存储的具体情况,例如可以看到bucket的数量,以及bucket中存储的元素个数

运行结果:

从结果可以看到,bucket的数量居然是大于元素的个数,实际上,bucket的个数确实是大于元素个数,因为从上面的hash table图可以发现,有的bucket中存放了多个元素,有的hash table中却是空的。bucket的个数大于元素个数是为了避免一个bucket过长(会降低查找元素的效率),所以每当元素个数大于bucket的个数的时候,bucket的个数就会按照两倍扩充,然后将元素打散,重新按照一定的规则计算元素放置的位置。

4. unordered multi_map

与上面的multi_set类似

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <unordered_map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::unordered_multimap<int, std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(pair<int, std::string>(i, std::string(buf))); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
cout << c.max_bucket_count() << endl;
cout << c.bucket_count() << endl; // bucket的数量
// 关联式容器的查找
// std::string target("3456");
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
int bucket_cnt = c.bucket_count(); // 遍历一下bucket
for (int i = 0; i < 10; i++)
{
cout << "Bucket #" << i << " has " << c.bucket_size(i) << " elements" << endl;
}
return 0;
}

5 set集合,于multi_set的用法类似

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::set<std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
std::string target("3456");
// int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}

5 map,与multi_map的用法类似

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::map<int, std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c[i] = std::string(buf); // map可以用这种写法
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
// std::string target("3456");
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}

后续的unordered set, unordered map的用法与前面的都很相似。

----------------------------------------------------------分割线--------------------------------------------------------------

 

posted @   Alpha205  阅读(126)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示