Modern C++ for C 程序员 第4部分

2023-11-17

这是bert hubert的系列文章,旨在帮助c代码人快速了解c++实用的新特性。原文链接:https://berthub.eu/

面向 C 程序员的 Modern C++ 系列第4部分

欢迎回来!在第3部分中,我讨论了类、多态性、引用和模板,最后用基本容器构建了一个源索引器,实现了60MB/s的索引速度。

在这一部分,我们将继续探讨C++的其他特性,您可以使用这些特性逐行增强代码,而无需立即使用《C++编程语言》的所有1400页。这里会频繁引用第3部分中的索引器示例,所以您可能需要确保自己了解它是关于什么的。

这里讨论的各种代码示例可在GitHub上找到。

如果您有任何想讨论的喜欢的事物或提出问题,请随时通过@bert_hu_bertbert@hubertnet.nl与我联系。

Lambdas

我们之前已经遇到了这些看起来奇怪的代码片段,例如:

std::sort(vec.begin(), vec.end(), 
          [](const auto& a, const auto& b) { return a < b; }
         );

尽管lambda本质上是语法糖,但它们的可用性使modern C++成为一种更具表现力的语言。此外,正如第1部分所述,将代码片段作为函数指针传递会严重限制编译器优化代码的能力。所以lambda不仅可以减少代码行数,生成的二进制文件也可以更快。

C++ lambdas是一等公民,就像正常代码一样进行编译。脚本语言可以轻松实现lambda,因为它们自带运行时解释器,C++没有这种奢侈。那么它是如何工作的呢?

这里是解剖结构:[capture specification](parameters) { actual code }capture specification可以为空,这意味着lambda中的代码只“看到”全局变量,这是一个非常好的默认设置。捕获可以是按值或按引用。通常,如果lambda需要捕获大量详细信息,请考虑它是否仍然是一个lambda。

对于参数,您经常会在那里使用auto,但这绝不是强制性的。

然后实际代码在{}之间,唯一的特殊之处是返回类型是自动推导的,但如果您知道自己在做什么,也可以覆盖它。一个工作示例:

vector<string> v{"9", "12", "13", "14", "18", "42", "75"};
string prefix("hi ");  
for_each(v.begin(), v.end(), [&prefix](const auto& a) {
  cout << prefix + a << endl;
}); // 输出 hi 9, hi 12, hi 13 等

第一行使用了有趣的初始化器,允许modern C++快速填充容器。第二行创建一个前缀字符串。最后一行使用C++算法for_each遍历容器。

prefix变量是‘按引用捕获’。对于传递参数,const auto& a也可以是const std::string&。最后我们打印前缀和容器成员。

要按数字对这个字符串向量进行排序,我们可以这样做:

std::sort(v.begin(), v.end(), [](const auto& a, const auto& b)  
          {
            return atoi(a.c_str()) < atoi(b.c_str());
          });

lambda创建一个实际的对象,尽管是一种未指定的类型:

auto print = [](const vector<std::string>& c) {
  for(const auto& a : c)
    cout << a << endl;
};

cout<<"Starting order: "<<endl;
print(v);

我们现在已经将lambda存储在print中,我们可以传递它并在以后也可以使用它。但是print是什么?如果我们询问调试器,它可能会告诉我们:

(gdb) ptype print  
  struct <lambda(const std::vector<std::string>&)> {}

根据捕获的内容,类型会变得更加复杂。正因如此,lambda通常通过auto或泛型传递。

当需要存储lambda或任何可调用项时,有std::function:

std::function<void(const vector<std::string>&)> stored = print;
stored(v); // 与 print(v)相同

注意,我们也可以这样做:

void print2(const vector<string>& vec) 
{
  // ..
}

...

  std::function<void(const vector<std::string>&)> stored = print2;

std::function也可以存储其他可调用项,如定义了operator()的对象。std::function的缺点是它的速度不如直接调用函数或调用lambda快,所以如果可能的话,请尝试直接调用。

在类中使用的lambda可以捕获[this],这意味着它可以访问类成员。

为了进一步促进C互操作性,如果lambda没有捕获任何内容,它会衰减为普通C函数指针,这导致能够执行此操作:

std::vector<int> v2{3, -1, -4, 1, -5, -9, -2, 6, -5};
qsort(&v2[0], v2.size(), sizeof(int), [](const void *a, const void* b)
      {
        if(abs(*(int*)a) < abs(*(int*)b)) 
          return -1;
        if(abs(*(int*)a) > abs(*(int*)b))
          return 1;
        return 0;
      });

总的来说,lambda非常棒,但最好将它们用于小型、内联的构造。如果发现自己捕获了大量内容,使用functor(可以调用的类实例,因为它重载了operator())可能会更好。

扩展我们的索引器

在第3部分中的索引器中,我们最终得到:

struct Location  
{
  unsigned int fileno;
  size_t offset;
};

std::unordered_map<string, vector<Location>> allWords;

这包含在索引的文件中找到的所有单词的无序列表,每个单词都有一个Location向量,表示找到该单词的位置。我们使用无序映射,因为它比有序映射快40%。

然而,如果我们想执行诸如“main*”的查找以匹配所有以“main”开头的内容,我们也需要一个有序单词列表:

std::vector<string> owords;
owords.reserve(allWords.size()); // 节省malloc调用
for(const auto& w : allWords)
  owords.push_back(w.first); 
sort(owords.begin(), owords.end());

请注意,这使用范围for构造遍历allWords无序映射的键,并将其插入一个尚未排序的向量,我们在最后一行对其进行排序。

有趣的是,我们没有失去40%的速度提升,因为“排序完成后”比“一直保持排序”更快。

如果我们有兴趣,我们可以尝试更智能一点。如上所述,每个单词现在在内存中出现两次,一次在allWords中,一次在owords中。

采用C风格的语法,则如下:

std::vector<const string*> optrwords; 
optrwords.reserve(allWords.size());
for(const auto& w : allWords)
  optrwords.push_back(&w.first);
sort(optrwords.begin(), optrwords.end(), 
     [](auto a, auto b) { return *a < *b;}
    );

使用这段代码,我们存储allWords无序映射键的const指针。然后对optrwords进行排序,它包含指针,使用lambda解引用这些指针。

如果我们索引Linux源代码树,其中包含大约600,000个唯一单词,这确实为我们节省了大约14兆字节的内存,这很好。

然而,缺点是我们现在将原始指针直接存储在另一个容器(allWords)中。只要我们不修改allWords,这是安全的。并且对于某些容器,即使我们做出更改也是安全的。这碰巧是std::unordered_map的情况,只要我们不实际删除存储指针的条目就可以。

我认为这说明了使用modern C++的一个关键点。如果“你知道自己在做什么”,可以节省14兆字节的内存,但我强烈建议,只有在真正需要时才使用这种“C语言”技巧袋。但如果是这种情况,了解可以这样做是很好的。

容器和算法

到目前为止,我们已经看到了各种容器(例如std::vector,std::unordered_map)。此外,还有大量可以对这些容器进行操作的算法。关键是,通过使用模板,算法实际上与它们所操作的容器完全分离。

这种分离使标准能够规定比平常更多的泛用算法。我们已经遇到了std::for_eachstd::sort,但这里还有一个更奇特的std::nth_element

回到我们的索引器,我们有一个单词列表及其出现频率。假设我们想打印出现频率最高的20个单词,我们通常会取整个单词列表,根据频率对其排序,然后打印前20个。

有了std::nth_element,我们可以得到我们需要的。首先,让我们收集要排序的数据,并定义比较函数:

vector<pair<string, size_t>> popcount;
for(const auto& w : allWords)
  popcount.push_back({w.first, w.second.size()}); 

auto cmp = [](const auto& a, const auto& b)  
{
  return b.second < a.second; 
};

我们定义了一个包含pairvectorpair是一个方便的模板化结构,包含两个成员,称为firstsecond。我发现pair占据了一个非常有用的甜点地带,一个具有公知名称的“无名结构”。当对嵌套成对的对或使用std::tuple(std::pair的加强版)感到困惑时,会出现混淆。超过两个简单的成员,请创建具有命名成员的结构。

范围for循环展示了一个新特性,“brace initialization”,这意味着w.firstw.second.size()(这个单词的出现次数)用于构造我们的pair。这可以节省大量输入。

最后,我们定义一个比较函数,并将其称为cmp以便我们可以重用它。请注意,它以相反的顺序进行比较。

接下来是实际的排序和打印:

int top = std::min(popcount.size(), (size_t)20);
nth_element(popcount.begin(), popcount.begin() + top, popcount.end(), cmp);  
sort(popcount.begin(), popcount.begin() + top, cmp);

int count=0;
for(const auto& e : popcount) {
  if(++count > top)
    break;
  cout << count << "\t" << e.first << "\t" << e.second << endl;
}

调用std::nth_element需要一些解释。如前所述,迭代器是容器中的“位置”。begin()是第一个条目,首尾一致,end()在最后一个条目之后。在空容器上,begin() == end()

我们向nth_element传递三个迭代器: 开始排序的位置, 我们的“前20名”的截止点 , 容器的结尾。nth_element然后确保前20个完全位于容器的前20个位置。但是,它不保证前20个本身已排序。出于这个原因,我们对前20个条目进行快速排序。

最后6行打印实际的前20名,顺序正确。

C++带有许多有用的算法,允许您编写强大的程序。例如:std::set_differencestd::set_intersectionstd::set_symmetric_difference可以轻松编写“diff”类工具或找出从一种状态变化到另一种状态的内容。

同时,std::inplace_mergestd::next_permutation等算法可以防止您不得不拿出Knuth的书籍。

在进行任何数据操作或分析之前,我建议您浏览现有算法的列表,您可能会发现里面大部分已可满足您的需要。

查找 – STL中的查找算法

例如,回想一下我们创建了一个排序单词列表以进行前缀查找。所有单词都在std::vector<string> owords中结束。我们可以通过几种方式查询这个平面(因此非常高效)容器:

std::binary_search(begin, end, value) 将让您知道值是否在里面。

std::equal_range(begin, end, value) 返回一对迭代器,跨越所有完全匹配的条目。

std::lower_bound(begin, end, value) 返回一个指向可以插入value而不改变排序顺序的第一个地方的迭代器。upper_bound返回最后一个迭代器,这同样适用。

只要我们的容器中没有多个等效条目,lower_boundupper_bound是相同的。要从我们的排序向量owords列出所有以“main”开头的词,我们可以执行:

string val("main");
auto iter = lower_bound(owords.begin(), owords.end(), val);
for(; iter != owords.end() && !iter->compare(0, val.size(), val); ++iter)
  cout<<" "<<*iter<<endl;

std::lower_bound在这里完成繁重的工作,在我们排序的std::vector上执行二进制搜索。for循环需要一点解释。第一个检查iter != owords.end()将在lower_bound没有找到任何内容时停止我们。

第二个检查使用iter->compare执行候选单词的子字符串匹配,最多为前4个字符。一旦不再匹配,我们已经迭代超出以“main”开头的单词。

更多的容器

在前面的示例中,我们使用了非常基本的std::vector,它在内存中是连续的,与C兼容,以及std::unordered_map,这是一个相当快速的键/值存储,但没有顺序。

还有几个有用的容器:

std::map一个有序映射,您可以在希望时传递比较函数,例如获取不区分大小写的排序。您将看到的许多例子不必要地使用std::map。这是因为2011年之前,C++没有无序容器。当您需要排序时,有序容器是非常好的,但在其他情况下会带来不必要的开销。

std::set这就像一个std::map<string,void>,换句话说,它是一个没有值的键值存储。与std::map一样,它是有序的,这通常是不需要的。幸运的是,还有std::unordered_set

std::multimapstd::multiset。这些的工作原理与常规setmap完全一样,但允许多个等效键。这意味着不能使用[]查询这些容器,因为它只支持单个键。

std::deque。双端队列,是实现任何类型队列的好帮手。存储不是连续的,但从任一端弹出和推送元素都是快速的。

可以在此处找到标准容器的完整列表

Boost容器

尽管本系列文章侧重于“核心”C++,但在此处不谈及Boost的一些部分会令我很遗憾。Boost是一个大型的C++代码集合,其中一些代码非常出色(并倾向于进入C++标准,该标准由一些Boost作者编辑),一些代码不错,然后还有一些不幸运的部分。

但好消息是,Boost的大部分都非常模块化:它不是一个框架类库—如果您使用其中一部分,就要使用全部。事实上,许多最有趣的部分仅包含头文件,不需要链接库。Boost普遍可用且免费授权。

首先是Boost容器库,它不是一个库而是一个包含文件集合。它提供了与标准库容器几乎完全兼容但在匹配您的使用案例时提供具体优势的定制容器。

例如,boost::container::flat_map(和set)与std::mapstd::set类似,除了它们使用连续内存块以提高缓存效率。这使它们在插入时比较慢,但在查找时非常快。

另一个例子,boost::container::small_vector经过优化,用于存储少量(可模板化)元素,这可以节省大量malloc流量。

可以在此处找到更多Boost容器。

Boost.MultiIndex

其次,在本系列的第1部分中,我承诺会避免奇异用法和“模板元编程”。但我必须与您分享一个珍珠,我认为这是衡量编程语言强大程度的黄金标准 — 该语言是否足够强大以实现Boost.MultiIndex?

简而言之,我们经常需要以多种方式查找对象。例如,如果我们有一个开放TCP会话的容器,我们可能希望根据“完整源IP、源端口、目标IP、目标端口”元组查找会话,但也可能只根据源IP或目标IP。我们还可能希望按时间顺序获取/关闭旧会话。

“手动”执行此操作的方法是维护多个容器,对象存在其中,并使用各种键通过这些容器查找对象:

map<pair<IPEndpoint,IPEndpoint>, TCPSession*> d_sessions;
map<IPEndpoint, TCPSession*> d_sessionsSourceIP;
map<IPEndpoint, TCPSession*> d_sessionsDestinationIP;
multimap<time_t, TCPSession*> d_timeIP;

auto tcps = new TCPSession;
d_sessions[{srcEndpoint, dstEndpoint}] = tcps;
d_sessionsSourceIP[srcEndpoint] = tcps;  
d_sessionsDestinationIP[dstEndpoint] = tcps;
...

虽然这可行,但我们突然必须做很多管理工作。例如,如果要删除一个TCPSession,我们必须记住从所有容器中删除它,然后释放指针。

Boost.MultiIndex是一件艺术品,它不仅提供了可以通过多种方式同时搜索的容器,还提供了(无)有序、唯一和非唯一索引,以及局部键查找,以及使您可以使用char *查找std::string键的“备用键”等功能。

以下是我们查找TCP会话的方式。首先让我们做一些基础工作(完整代码):

struct AddressTupleTag{};
struct DestTag{}; 
struct TimeTag{};

struct Entry
{
  IPAddress srcIP;
  uint16_t srcPort;
  IPAddress dstIP;
  uint16_t dstPort;
  double time;
};

三个Tag提供了标识容器上我们将定义的三种不同索引的类型。然后我们定义Boost.MultiIndex容器将包含的结构。请注意,我们要搜索的键实际上在容器本身中——这里键和值没有区分。

接下来是容器的承认很难的模板定义。您可能会花一个小时才能把它做对,但一旦正确,一切都很简单:

typedef multi_index_container<
  Entry,
indexed_by<
  ordered_unique<
  tag<AddressTupleTag>,
composite_key<Entry, 
member<Entry, IPAddress, &Entry::srcIP>,
member<Entry, uint16_t, &Entry::srcPort>,
member<Entry, IPAddress, &Entry::dstIP>,
member<Entry, uint16_t, &Entry::dstPort>  
  >
  >,
ordered_non_unique<
  tag<DestTag>,
composite_key<Entry,
member<Entry, IPAddress, &Entry::dstIP>,
member<Entry, uint16_t, &Entry::dstPort> 
  >
  >,

ordered_non_unique<
  tag<TimeTag>,
member<Entry, double, &Entry::time>
  >
  tcpsessions_t;

这定义了三个索引,一个有序且唯一,两个有序且非唯一。第一个索引是TCP会话的“4元组”。第二个仅目标会话的目标。最后一个是时间戳。

重要的是要注意,此模板定义在编译时为容器生成全部代码。所有这一切导致的代码就像您自己编写的一样高效,正如模板化容器通常的情况一样。实际上,Boost.MultiIndex容器通常比std::map更快。

让我们用一些数据填充它:

tcpsessions_t sessions; 
double now = time(0);
Entry e{"1.2.3.4"_ipv4, 80, "4.3.2.1"_ipv4, 123, now};
sessions.insert(e);

sessions.insert({"1.2.3.4"_ipv4, 81, "4.3.2.5"_ipv4, 1323, now+1.0}); 
sessions.insert({"1.2.3.5"_ipv4, 80, "4.3.2.2"_ipv4, 4215, now+2.0});

第一行使用typedef使我们的容器的实际实例,第二行获取当前时间并将其放入double中。

然后发生一些名为用户定义字面量的魔术,这意味着"1.2.3.4"_ipv4被转换为0x01020304 - 在编译时。要观察这是如何工作的,请转到GitHub上的multi.cc。这些派对的诡计是C++的可选项,但constexpr编译时代码执行确实很酷。

运行此操作后,我们的sessions容器中有3个条目。让我们以时间顺序全部列出:

auto& idx = sessions.get<TimeTag>();
for(auto iter = idx.begin(); iter != idx.end(); ++iter)
  cout << iter->srcIP << ":" << iter->srcPort<< " -> "<< iter->dstIP <<":"<<iter->dstPort << "\n";

这会打印:

1.2.3.4:80 -> 4.3.2.1:123
1.2.3.4:81 -> 4.3.2.5:1323
1.2.3.5:80 -> 4.3.2.2:4215

在第一行中,我们请求TimeTag索引的引用,在第二行中像往常一样迭代它。

让我们在“main”(第一个)索引上进行部分查找,该索引基于完整的4元组:

cout<<"Search for source 1.2.3.4, every port"<<endl;
auto range = sessions.equal_range(std::make_tuple("1.2.3.4"_ipv4));
for(auto iter = range.first; iter != range.second ; ++iter)
  // 打印

通过仅使用一个成员创建元组std::make_tuple,我们指示我们仅希望根据4元组的第一部分进行查找。如果我们添加了“, 80”到std::make_tuple,我们只会找到一个匹配的TCP会话,而不是两个。请注意,此查找使用前面在本页描述的equal_range。

最后,根据TCP会话的目标搜索:

cout<<"Destination search for 4.3.2.1 port 123: "<<endl; 
auto range2 = sessions.get<DestTag>().equal_range(std::make_tuple("4.3.2.1"_ipv4, 123));
for(auto iter = range2.first; iter != range2.second ; ++iter)
  // 打印

这请求DestTag索引,然后使用它来找到目标为4.3.2.1:123的会话。

我希望您可以原谅我这次跨出标准C++的范畴,但由于Boost.MultiIndex几乎参与了我编写的所有代码,我觉得有必要分享它。

总结

在这长长的第4部分中,我们已经深入探讨了lambdas的一些细枝末节,以及它们如何用于自定义排序,如何存储以及何时是个好主意。

其次,我们通过增强代码索引器以查找部分词的能力,通过将无序词容器排序到一个平面向量中,探索了算法和容器之间的交互。我们还研究了如何使用某些“C式”技巧来使此过程既节省内存又更危险。

我们还查看了C++提供的丰富算法数组,这得益于代码在容器和算法之间的分离。在进行任何数据操作之前,请查看现有算法,如果已经有满足您需求的算法就直接使用它。

最后,我们介绍了Boost中的其他容器,包括最神奇和强大的Boost.MultiIndex

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Modern C++ for C 程序员 第4部分 的相关文章

  • 编译时运算符

    有人可以列出 C 中可用的所有编译时运算符吗 C 中有两个运算符 无论操作数如何 它们的结果始终可以在编译时确定 它们是sizeof 1 and 2 当然 其他运算符的许多特殊用途可以在编译时解决 例如标准中列出的那些整数常量表达式 1 与
  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • 在 Windows 窗体中保存带有 Alpha 通道的单色位图会保存不同(错误)的颜色

    在 C NET 2 0 Windows 窗体 Visual Studio Express 2010 中 我保存由相同颜色组成的图像 Bitmap bitmap new Bitmap width height PixelFormat Form
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

  • python世界你好的输出_Python语句print( ;世界,你好”)的输出是________。

    Python语句print 世界 你好 的输出是 答 世界 你好 供暖系统按系统管道敷设方式可以分为 式和 式 答 垂直 水平 绿茶一般不能用100 的沸水冲泡 以80 90 为宜 答 对 我国最早的指南车诞生于 答 涿鹿之战 Alexan
  • 使用vue-video-player实现直播的方式

    文章来源 学习通http www jaxp net 目录 一 安装vue video player 二 使用 vue video player 课前准备 直播流协议https www cnblogs com yangchin9 p 1493
  • 权威发布:新一代人工智能发展白皮书(2017)

    来源 机器人大讲堂 指导单位 专家顾问及编写人员 顾 问 潘云鹤 中国工程院院士 指导单位 工业和信息化部信息化和软件服务业司 指导委员会 谢少锋 工信部信软司司长 李冠宇
  • iOS相机相册调用 — UIImagePickerController

    在iOS开发中如果要调用相机拍取照片或者是直接获取相册中的照片 那么调用UIImagePickerController是个不错的选择 UIImagePickerController继承于UINavigationController 使用代理
  • uirecorder 模块化

    uirecorder 模块化 uirecorder原生代码问题 模块化 思考 有关资料 uirecorder原生代码问题 原生js文件十分臃肿 所有依赖都在一个js中 一个case一个js文件 后期维护十分困难 模块化 对原生js进行模块化
  • 修复 Python 错误TypeError: Missing 1 Required Positional Argument

    类是面向对象编程语言的基本特征之一 每个对象都属于 Python 中的某个类 我们可以创建我们的类作为蓝图来创建相同类型的对象 我们使用 class 关键字在 Python 中定义一个类 Python 中一个非常重要的特性是在定义类时使用
  • stm32学习笔记——如何理解stm32中标志位和中断位区别和联系

    1 当某个模块 比如串口 定时器 含有状态寄存器则涉及标志位和中断之间的区别 进而有库函数FlagStatus和ITStatus的使用区别 2 标志位置位 是指当某事件发生时 无论对应的中断是否使能都会使得相应的标志位置位 而当对应的中断也
  • akka设计模式系列-Chain模式

    链式调用在很多框架和系统中经常存在 算不得上是我自己总结的设计模式 此处只是简单介绍在Akka中的两种实现方式 我在这边博客中简化了链式调用的场景 简化后也更符合Akka的设计哲学 trait Chained def receive Rec
  • 数学建模--二次规划型的求解的Python实现

    目录 1 算法流程简介 2 算法核心代码 3 算法效果展示 1 算法流程简介 二次规划模型 二次规划我们需要用到函数 Cvxopt solvers qp P q G h A b 首先解决二次规划问题和解决线性规划问题的流程差不多 求解思路如
  • 中文医学知识语言模型:BenTsao

    介绍 BenTsao 原名 华驼 HuaTuo 基于中文医学知识的大语言模型指令微调 本项目开源了经过中文医学指令精调 指令微调 Instruction tuning 的大语言模型集 包括LLaMA Alpaca Chinese Bloom
  • 无法打开这个应用,查看Microsoft store, 了解有关Nahimic的详细信息

    win s 打开搜索框 输入 Nahimic 鼠标右键单击 打开应用设置 点击 修复 重启电脑
  • 计算机图形学:Bezier曲线的绘制

    1 实验目的 掌握Bezier曲线的定义原理及绘制过程 定义 贝塞尔曲线 Bezier curve 又称贝兹曲线或贝济埃曲线 是应用于二维图形应用程序的数学曲线 一般的矢量图形软件通过它来精确画出曲线 贝兹曲线由线段与节点组成 节点是可拖动
  • 什么是千年虫?计算机如何开始处理日期?都有哪些时间日期格式化?

    目录 千年虫 漏洞 Year 2000 Problem 简称 Y2K 计算机是怎么开始处理日期的么 举例1 时间格式化举例 过滤器 举例2 时间格式化 自定义私有过滤器 日期格式化 高性能计数器演示 OLE时间对象 时间的基本用法 千年虫
  • 【Hello Algorithm】二叉树的递归套路

    本篇博客介绍 介绍二叉树的递归套路算法 二叉树的递归套路 递归思路 判断二叉树是否是平衡二叉树 判断二叉树是否是搜索二叉树 返回二叉树节点的最大距离 验证一棵树是否是满二叉树 寻找最大的BST子树 判断二叉树是否是完全二叉树 判断二叉树的最
  • Shell脚本:expect脚本免交互

    Shell脚本 expect脚本免交互 expect脚本免交互 一 免交互基本概述 1 交互与免交互的区别 2 格式 3 通过read实现免交互 4 通过cat实现查看和重定向 5 变量替换 二 expect安装 1 概述 2 作用 3 e
  • RuntimeError: one of the variables needed for gradient computation has been modified by an inplace o

    RuntimeError one of the variables needed for gradient computation has been modified by an inplace operation torch cuda F
  • 牛客每日刷题

    作者简介 我是18shou 一名即将秋招的java实习生 个人主页 18shou 系列专栏 牛客刷题专栏 在线刷题面经模拟面试 目录 题目 思路 题解 题目 给定一个长度为 n 的字符串 请编写一个函数判断该字符串是否回文 如果是回文请返回
  • computed计算属性和data_computed与watched选项的比较

    computed 通过属性计算而得来的属性 1 支持缓存 只有依赖数据发生改变 才会重新进行计算 computed 属性值会默认走缓存 计算属性是基于它们的响应式依赖进行缓存的 也就是基于data中声明过的数据通过计算得到的 2 不支持异步
  • 3.python学习笔记——Python数据类型转换

    有时候 我们需要对数据内置的类型进行转换 数据类型的转换 你只需要将数据类型作为函数名即可 以下几个内置的函数可以执行数据类型之间的转换 这些函数返回一个新的对象 表示转换的值 int x base 将x转换为一个整数 float x 将x
  • Modern C++ for C 程序员 第4部分

    文章目录 面向 C 程序员的 Modern C 系列第4部分 Lambdas 扩展我们的索引器 容器和算法 查找 STL中的查找算法 更多的容器 Boost容器 Boost MultiIndex 总结 这是bert hubert的系列文章