13-y 使用语言参考

根据你在学习编程语言(特别是C++)过程中的所处阶段,LearnCpp.com可能是你学习C++或查阅资料的唯一资源。该网站旨在以初学者易懂的方式讲解概念,但它无法涵盖语言的方方面面。当你开始探索教程未涵盖的领域时,必然会遇到教程无法解答的问题。此时就需要借助外部资源。

Stack Overflow便是其中之一,你可以在这里提问(或更明智地查阅前人提出的相同问题的解答)。但有时更理想的起点是参考指南。不同于侧重核心主题、采用非正式/通俗语言的教程,参考指南使用正式术语精确描述C++特性。正因如此,参考资料往往全面准确,但也...难以理解。

本课将通过研究3个示例,演示如何使用cppreference——这是贯穿本课程的权威标准参考资料。


概述Overview

Cppreference 为您呈现核心语言与库的概述

image

从这里,您可以访问cppreference提供的所有内容,但使用搜索功能或搜索引擎会更便捷。当您完成LearnCpp.com上的教程后,这里是深入探索库文件、了解语言其他潜在功能的绝佳起点。

表格上半部分展示当前语言特性,下半部分则呈现技术规范——这些特性可能在未来版本中加入C++,也可能尚未被完全采纳。若想了解即将推出的新功能,此处尤为实用。

自C++11起,cppreference为所有特性标注了其所属的语言标准版本。标准版本即上图中部分链接旁显示的绿色数字。未标注版本号的特性自C++98/03起便已可用。这些版本号不仅出现在概述中,更遍布cppreference的每个角落,让你精确掌握特定C++版本中可用的功能。

警告:
若您使用搜索引擎查询,且某项技术规范刚被纳入标准,您可能被引导至该技术规范而非官方参考文件,二者内容可能存在差异。

提示:
Cppreference 是 C++ 和 C 的双重参考库。由于 C++ 与 C 共享部分函数名称,搜索时您可能会进入 C 参考部分。Cppreference 顶部的网址和导航栏始终会显示您当前浏览的是 C 还是 C++ 参考内容。


std::string::length

我们将从研究上一课中已知的函数开始——std::string::length,该函数返回字符串的长度。

在cppreference右上角搜索“string”。这样会显示一个长长的类型和函数列表,其中目前仅顶部内容相关。

image

我们本可以直接搜索“字符串长度”,但为了在本节课中尽可能多地展示内容,我们选择了绕远的路。点击“字符串库Strings library”会跳转到介绍C++支持的各类字符串的页面。

image
image

在“std::basic_string”部分下,我们可以看到一组typedef声明,其中包含std::string。

原网站图片:
image

点击“std::string”会跳转到std::basic_string的页面。因为std::string本身就是std::basic_string的别名定义,这一点在别名列表中也能再次确认:

注明:因为截止到12/25/15,cppreference网站已有所变更, learncpp教程中使用的之前的, 基于现在的网站,其中点击“std::string”会跳转到std::basic_string对应操作如下:
image
然后就能找到std::basic_string的页面了,其实点击string后面所对应的td::basic_string也可达到。

image

表示字符串中的每个字符都属于char类型。你会注意到C++还提供了使用不同字符类型的字符串。当使用Unicode而非ASCII时,这些类型会很有用。

在同一页面的下方,列出了成员函数(即类型所具备的行为)。若需了解类型支持的操作,此列表极为便捷。其中包含长度(及大小)的对应行。

image

点击链接可跳转至length与size函数的详细说明,两者功能完全相同。

每页顶部均以特性概述开篇,涵盖语法、重载或声明等内容:

image

页面标题显示了类名和函数名及其所有模板参数。我们可以忽略这一部分。标题下方列出了所有函数重载版本(名称相同但实现不同的函数变体)及其适用的语言标准。

再往下是函数的参数说明及返回值含义。

由于 std::string::length 是简单函数,此页面内容较少。多数文档页面会展示功能的示例用法,本页也不例外:

image

当您仍在学习 C++ 时,示例中将会有您以前从未见过的功能。如果有足够多的示例,您可能能够理解足够多的内容,以了解该函数的使用方式及其用途。如果例子太复杂,你可以在其他地方搜索例子,或者阅读你不明白的部分的参考资料(你可以点击例子中的函数和类型来看看它们的作用)。

现在我们知道了 std::string::length 的作用,但我们之前就知道了。让我们来看看新的东西吧!


std::cin.ignore

在第 9.5 课——std::cin 和处理无效输入中,我们讨论了 std::cin.ignore,它用于忽略换行符之前的所有内容。该函数的参数之一是一些长而详细的值。那又是什么?就不能用一个大数字吗?这个论证到底有什么作用呢?让我们来弄清楚吧!

在 cppreference 搜索中输入“std::cin.ignore”会产生以下结果:

image

  • std::cin、std::wcin - 我们想要 .ignore,而不是普通的 std::cin。
  • std::basic_istream<CharT,Traits>::ignore - 呃,这是什么?我们暂时跳过吧。
  • std::ignore - 不,不是这样。
  • std::basic_istream - 也不是这样。

不存在,现在怎么办?让我们转到 std::cin 并从那里开始工作。该页面上没有任何显而易见的内容。在顶部,我们可以看到 std::cin 和 std::wcin 的声明,它告诉我们需要包含哪个标头才能使用 std::cin:

image

image

我们可以看出 std::cin 是 std::istream 的对象。让我们沿着链接追溯到 std::istream:

image

image

等等!我们之前在搜索引擎里查找“std::cin.ignore”时就见过std::basic_istream。原来istream是basic_istream的别名,看来我们的搜索结果倒也没那么离谱。

继续往下翻,页面上出现了些熟悉的函数:

image

我们已经使用过其中许多函数:operator>>、get、getline、ignore。请浏览该页面了解std::cin还包含哪些其他函数。然后点击ignore,因为这正是我们关注的重点。

image

image

页面顶部给出了函数签名及其两个参数的功能说明。参数后面的等号表示默认参数default argument(详见第11.5节——默认参数)。若未为具有默认值的参数提供实参,则使用默认值。

首条要点解答了所有疑问:std::numeric_limitsstd::streamsize::max() 对 std::cin.ignore 具有特殊含义,即禁用字符计数检查。这意味着 std::cin.ignore 将持续忽略字符直至找到分隔符delimiter,或直至可处理字符耗尽。

许多情况下,若已知函数但忘记参数或返回值含义时,无需阅读完整函数描述。此时仅查阅参数或返回值说明即可。

image

这个参数说明是简短的。 它不包含的特殊处理的std::numeric_limitsstd::streamsize::max()或其他停止条件,但是作为一个很好的提醒。


语言语法示例

除了标准库之外,cppreference 还记录了语言语法。这是一个有效的程序:

#include <iostream>

int getUserInput()
{
  int i{};
  std::cin >> i;
  return i;
}

int main()
{
  std::cout << "How many bananas did you eat today? \n";

  if (int iBananasEaten{ getUserInput() }; iBananasEaten <= 2)
  {
    std::cout << "Yummy\n";
  }
  else
  {
    std::cout << iBananasEaten << " is a lot!\n";
  }

  return 0;
}

image

为什么 if 语句的条件中有变量定义?让我们通过在我们最喜欢的搜索引擎中搜索“cppreference if statements”来使用 cppreference 来弄清楚它的作用。这样做会将我们引向 if 语句if statements。顶部有语法参考。

image

image

查看 if 语句的语法。如果删除所有可选部分,您将得到一个您已经知道的 if 语句。在条件之前,有一个可选的 init 语句,看起来与上面代码中发生的情况类似。

if constexpr( init-statement  condition ) statement-true
if constexpr( init-statement  condition ) statement-true else statement-false	

在语法参考下面,有对语法每个部分的解释,包括 init 语句。它说初始化语句通常是带有初始值设定项的变量声明。

语法后面是 if 语句的解释和简单示例:

#include <iostream>
 
int main()
{
    // simple if-statement with an else clause
    int i = 2;
    if (i > 2)
        std::cout << i << " is greater than 2\n";
    else
        std::cout << i << " is not greater than 2\n";
 
    // nested if-statement
    int j = 1;
    if (i > 1)
        if (j > 2)
            std::cout << i << " > 1 and " << j << " > 2\n";
        else // this else is part of if (j > 2), not of if (i > 1)
            std::cout << i << " > 1 and " << j << " <= 2\n";
 
    // declarations can be used as conditions with dynamic_cast
    struct Base
    {
        virtual ~Base() {}
    };
 
    struct Derived : Base
    {
        void df() { std::cout << "df()\n"; }
    };
 
    Base* bp1 = new Base;
    Base* bp2 = new Derived;
 
    if (Derived* p = dynamic_cast<Derived*>(bp1)) // cast fails, returns nullptr
        p->df(); // not executed
 
    if (auto p = dynamic_cast<Derived*>(bp2)) // cast succeeds
        p->df(); // executed
}

Output:

2 is not greater than 2
2 > 1 and 1 <= 2
df()

我们已经了解if语句的工作原理,而示例中并未包含初始化语句,因此我们向下滚动一点,找到专门介绍带初始化器的if语句的章节:

image

首先,我们展示了如何在不实际使用初始化语句init-statement的情况下编写初始化语句。现在我们已知该代码的实际作用:它只是将普通变量声明合并到了if语句中。

接下来的句子颇有意思,它表明初始化语句中的变量名在两种语境下均可使用(无论真语句statement-true或为假语句statement-false)。这可能令人意外,因为人们通常会认为该变量仅在语句为真时可用。

初始化语句示例中涉及的特性和类型尚未讲解。理解初始化语句的工作原理并不需要完全理解所见内容。让我们跳过所有过于复杂的部分,直到找到可操作的内容:

// Iterators, we don't know them. Skip.
if (auto it = m.find(10); it != m.end()) { return it->second.size(); }

// [10], what's that? Skip.
if (char buf[10]; std::fgets(buf, 10, stdin)) { m[0] += buf; }

// std::lock_guard, we don't know that, but it's some type. We know what types are!
if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; }

// This is easy, that's an int!
if (int s; int count = ReadBytesWithSignal(&s)) { publish(count); raise(s); }

// Whew, no thanks!
if (auto keywords = {"if", "for", "while"};
    std::any_of(keywords.begin(), keywords.end(),
                [&s](const char* kw) { return s == kw; })) {
  std::cerr << "Token must not be a keyword\n";
}

最简单的例子似乎是使用整型的情况。接着我们看到分号之后还有另一个定义,有点奇怪……让我们回到std::lock_guard的例子。

if (std::lock_guard lock(mx); shared_flag)
{
  unsafe_ping();
  shared_flag = false;
}

由此不难看出初始化语句的工作原理:先定义变量(lock),接着是分号,然后是条件。这正是我们示例中发生的过程。


关于cppreference准确性的警告

Cppreference并非官方文档来源——它本质上是一个维基平台。维基允许任何人添加和修改内容,其内容来源于社区贡献。虽然这意味着错误信息可能被轻易添加,但这类错误通常会被迅速发现并删除,因此cppreference仍是一个可靠的参考来源。

C++的唯一官方来源是标准the standardGitHub上提供免费草案),该标准属于正式文档,不易作为参考资料使用。

image
image


测验时间

问题 #1

下列程序会输出什么?不要运行它,请通过参考资料弄清楚 erase 的作用。

#include <iostream>
#include <string>

int main()
{
  std::string str{ "The rice is cooking" };

  str.erase(4, 11);

  std::cout << str << '\n';

  return 0;
}

提示:
在cppreference中查找erase时,可忽略使用迭代器的重载形式。

C++中的索引从0开始。字符串“House”中索引为0的字符是'H',索引为1的是'o',依此类推。

显示答案

The king

以下是通过cppreference的搜索功能找到该内容的步骤(您可能已通过搜索引擎跳过了第一步):

搜索string并点击“std::string”后,我们进入std::basic_string页面。

滚动至“成员函数”列表,找到erase函数。如前文提示所示,使用的是第一个函数重载。该函数接受两个size_type(无符号整数类型)参数。在本例中为4和11。根据(1)的描述,它将删除“从索引index开始的min(count, size() - index)个字符”。代入参数后,即删除从索引4开始的min(11, 19 - 4) = 11个字符。

问题 #2

在以下代码中,修改字符串 str,使其值变为“昨天我看到一辆蓝色的车。”且不重复字符串。例如,不要这样做:

str = "I saw a blue car yesterday.";

你只需调用一个函数,就能将“红色”替换为“蓝色”。

#include <iostream>
#include <string>

int main()
{
  std::string str{ "I saw a red car yesterday." };

  // ...

  std::cout << str << '\n'; // I saw a blue car yesterday.

  return 0;
}

显示提示Show Hint

std::basic_string

显示提示

Member functions of std::basic_string

显示提示
Modifiers of std::basic_string

显示提示
std::basic_string::replace

显示答案Show Solution

#include <iostream>
#include <string>

int main()
{
  std::string str{ "I saw a red car yesterday." };

  str.replace(8, 3, "blue");

  std::cout << str << '\n'; // I saw a blue car yesterday.

  return 0;
}
posted @ 2025-12-25 11:23  游翔  阅读(10)  评论(0)    收藏  举报