范围、持续时间和链接

参考:https://www.learncpp.com/

复合语句(块)

复合语句(也称为块或块语句)是一组零个或多个语句,编译器将其视为单个语句。

块以符号 {开头,以 } 符号结尾,要执行的语句放在两者之间。块可以在允许单个语句的任何地方使用。块的末尾不需要分号。

在编写函数时,您已经看到了一个块的示例,因为函数体是一个块

其他块内的块

虽然函数不能嵌套在其他函数中,但块可以嵌套在其他块中:

int add(int x, int y)
{ // block
    return x + y;
} // end block

int main()
{ // outer block

    // multiple statements
    int value {};

    { // inner/nested block
        add(3, 4);
    } // end inner/nested block

    return 0;

} // end outer block

当块嵌套时,封闭块通常称为外部块,封闭块称为内部块或嵌套块。

块最常见的用例之一是与 if 结合使用。最好将嵌套级别保持在 3 或更低。正如过长的函数是重构的良好候选者(分解为较小的函数)一样,过度嵌套的块难以阅读,并且是重构的良好候选者(嵌套最多的块成为单独的函数)

用户定义的命名空间和范围解析运算符

定义自己的命名空间

C++允许我们通过 namespace 关键字定义自己的命名空间。在自己的程序中创建的命名空间通常称为用户定义的命名空间(尽管将它们称为程序定义的命名空间会更准确)。

对于高级阅读者:

首选以大写字母开头的命名空间名称的一些原因:

  • 通常以大写字母开头命名程序定义的类型。对程序定义的命名空间使用相同的约定是一致的(尤其是在使用限定名称(如 Foo::x )时,其中 Foo 可以是命名空间或类类型)。
  • 它有助于防止与其他系统提供或库提供的小写名称发生命名冲突。

使用范围解析运算符 (: 访问命名空间

告诉编译器在特定命名空间中查找标识符的最佳方法是使用范围解析运算符 (:。范围解析运算符告诉编译器,应在左侧操作数的范围内查找右侧操作数指定的标识符。

下面是使用范围解析运算符告诉编译器我们明确希望使用 foo 位于命名空间中的版本 doSomething() 的示例:

#include 

namespace Foo // define a namespace named Foo
{
    // This doSomething() belongs to namespace Foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}

namespace Goo // define a namespace named Goo
{
    // This doSomething() belongs to namespace Goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}

int main()
{
    std::cout << Foo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace Foo
    return 0;
}

使用不带名称前缀的范围解析运算符

范围解析运算符也可以在标识符前面使用,而无需提供命名空间名称(例如 ::doSomething )。在这种情况下,标识符(例如 doSomething )在全局命名空间中查找。

#include 

void print() // this print() lives in the global namespace
{
	std::cout << " there\n";
}

namespace Foo
{
	void print() // this print() lives in the Foo namespace
	{
		std::cout << "Hello";
	}
}

int main()
{
	Foo::print(); // call print() in Foo namespace
	::print();    // call print() in global namespace (same as just calling print() in this case)

	return 0;
}

从命名空间中解析标识符

如果使用命名空间内的标识符,并且未提供范围解析,则编译器将首先尝试在同一命名空间中查找匹配的声明。如果未找到匹配的标识符,编译器将按顺序检查每个包含命名空间以查看是否找到匹配项,最后检查全局命名空间。

#include 

void print() // this print() lives in the global namespace
{
	std::cout << " there\n";
}

namespace Foo
{
	void print() // this print() lives in the Foo namespace
	{
		std::cout << "Hello";
	}

	void printHelloThere()
	{
		print();   // calls print() in Foo namespace
		::print(); // calls print() in global namespace
	}
}

int main()
{
	Foo::printHelloThere();

	return 0;
}

请注意,我们还使用不带命名空间 ( ::print() ) 的作用域解析运算符来显式调用 的 print() 全局版本。

允许多个命名空间块

在多个位置(跨多个文件或同一文件中的多个位置)声明命名空间块是合法的。命名空间中的所有声明都被视为命名空间的一部分。

警告:不要向 std 命名空间添加自定义功能。

嵌套命名空间

因为命名空间 Goo 在命名空间 Foo 内,所以我们访问 addFoo::Goo::add

命名空间别名

由于在嵌套命名空间中键入变量或函数的限定名称可能会很痛苦,因此C++允许您创建命名空间别名,这允许我们将一长串命名空间暂时缩短为更短的名称:

#include 

namespace Foo::Goo
{
    int add(int x, int y)
    {
        return x + y;
    }
}

int main()
{
    namespace Active = Foo::Goo; // active now refers to Foo::Goo

    std::cout << Active::add(1, 2) << '\n'; // This is really Foo::Goo::add()

    return 0;
} // The Active alias ends here

命名空间别名的一个很好的优势是:如果要将其中的功能 Foo::Goo 移动到其他位置,只需更新 Active 别名即可反映新的目标,而不必查找/替换 的每个 Foo::Goo 实例。

何时应使用命名空间

在应用程序中,命名空间可用于将特定于应用程序的代码与以后可能重用的代码(例如数学函数)分开。例如,物理函数和数学函数可以进入一个命名空间(例如 Math:: )。另一个语言和本地化功能(例如 Lang::

局部变量

局部变量具有块范围,这意味着它们从定义点到定义它们的块末尾都在范围内

作用域内的所有变量名称必须是唯一的

局部变量没有链接

标识符具有另一个名为 linkage 的属性。标识符的链接确定该名称的其他声明是否引用同一对象。

Scope , linkage 有些相似。但是,作用域Scope 定义了可以在何处查看和使用单个声明。链接linkage 定义多个声明是否引用同一对象。

全局变量简介

变量应在最有限的范围内定义

通过限制变量的作用域,可以降低程序的复杂性,因为活动变量的数量减少了。

全局变量简介

在C++中,变量也可以在函数外部声明。此类变量称为全局变量。

全局变量的作用域

在全局命名空间中声明的标识符具有全局命名空间作用域(通常称为全局作用域,有时非正式地称为文件作用域),这意味着它们从声明点到声明它们的文件末尾都是可见的。

全局变量具有静态持续/static duration

全局变量在程序启动时创建,在程序结束时销毁。这称为静态持续。具有静态持续时间的变量有时称为静态变量。

命名全局变量

按照惯例,一些开发人员在非常量全局变量标识符前面加上“g”或“g_”,以指示它们是全局的。此前缀有多种用途:

  • 它有助于避免与全局命名空间中的其他标识符发生命名冲突。

  • 它有助于防止无意中的名称阴影

  • 它有助于指示前缀变量在函数范围之外持续存在,因此我们对它们所做的任何更改也将持续存在。


全局变量初始化

与默认情况下未初始化的局部变量不同,具有静态持续的变量默认为零初始化。

int g_x;       // no explicit initializer (zero-initialized by default)
int g_y {};    // value initialized (resulting in zero-initialization)
int g_z { 1 }; // list initialized with specific value

常量全局变量

就像局部变量一样,全局变量可以是常量。与所有常量一样,必须初始化常量全局变量。

(const 变量用于声明运行时常量,而 constexpr 变量用于声明编译期常量。)

关于(非常量)全局变量的注意事项

新程序员经常倾向于使用大量全局变量,因为它们可以使用,而不必将它们显式传递给需要它们的每个函数。但是,通常应完全避免使用非常量全局变量!

变量阴影(名称隐藏)

每个块定义自己的范围区域。那么,当我们在嵌套块中有一个变量与外部块中的变量同名时会发生什么?发生这种情况时,嵌套变量会将外部变量“隐藏”在它们都在范围内的区域中。这称为名称隐藏或阴影。

局部变量的阴影

在嵌套块内部时,无法从外部块直接访问阴影变量。

全局变量的阴影

与全局变量同名的局部变量将在局部变量在作用域中的任何位置隐藏全局变量.但是,由于全局变量是全局命名空间的一部分,因此我们可以使用范围运算符 (::)没有前缀告诉编译器,我们的意思是全局变量而不是局部变量。

最佳实践:避免可变阴影。

声明全局变量

按照约定,全局变量在全局命名空间中文件顶部的包含下方声明。下面是正在定义的全局变量的示例:

内部链接

内部链接

全局变量和函数标识符可以具有 internal linkageexternal linkage 。具有内部链接的标识符可以在单个翻译单元中查看和使用,但无法从其他翻译单元访问。

具有内部链接的全局变量

具有内部链接的全局变量有时称为内部变量。为了使非常量全局变量成为内部变量,我们使用 static 关键字。

Const 和 constexpr 全局变量默认具有内部链接(因此不需要 static 关键字 - 如果使用,将被忽略)。

具有内部联动==链接的函数

由于链接是标识符(而不是变量)的属性,因此函数标识符具有与变量标识符相同的链接属性。函数默认为外部链接

单一定义规则和内部联系

前向声明和定义中,我们注意到一个定义规则说一个对象或函数不能有多个定义,无论是在文件还是程序中。但是,值得注意的是,在不同文件中定义的内部对象(和函数)被视为独立实体(即使它们的名称和类型相同),因此不会违反一个定义规则。每个内部对象只有一个定义。

static 与未命名的命名空间

在现代C++,使用 static 关键字为标识符提供内部链接正在失宠。未命名的命名空间可以为更广泛的标识符(例如类型标识符)提供内部链接,并且它们更适合为许多标识符提供内部链接。

为什么要费心给标识符内部链接?

  • 我们希望确保其他文件无法访问某个标识符。这可能是我们不想弄乱的全局变量,也可能是我们不想调用的帮助程序函数。
  • 迂腐地避免命名冲突。由于具有内部链接的标识符不会向链接器公开,因此它们只能与同一翻译单元中的名称发生冲突,而不能在整个程序中发生冲突。

当您有明确的理由禁止从其他文件访问时,请为标识符提供内部链接。

外部链接和可变前向声明

函数默认具有外部链接。为了调用在另一个文件中定义的函数,您必须在希望使用该函数的任何其他文件中放置该函数的 a forward declaration 。前向声明告知编译器函数的存在,链接器将函数调用连接到实际的函数定义。

具有外部链接的全局变量

具有外部链接的全局变量有时称为外部变量。为了使全局变量外部(因此可以被其他文件访问),我们可以使用关键字 extern 来执行此操作:

int g_x { 2 }; // non-constant globals are external by default

extern const int g_y { 3 }; // const globals can be defined as extern, making them external
extern constexpr int g_z { 3 }; // constexpr globals can be defined as extern, making them external (but this is useless, see the note in the next section)

int main()
{
    return 0;
}

通过 extern 关键字的变量前向声明

请注意,函数前向声明不需要 extern 关键字 - 编译器能够根据是否提供函数体来判断您是在定义新函数还是进行前向声明。变量前向声明确实需要 extern 关键字来帮助区分未初始化的变量定义和变量前向声明(它们在其他方面看起来相同)

变量的范围、持续时间和链接之间有什么区别?全局变量具有什么样的范围、持续时间和链接?

Scope determines where a variable is accessible. Duration determines when a variable is created and destroyed. Linkage determines whether the variable can be exported to another file or not.

Global variables have global scope (a.k.a. file scope), which means they can be accessed from the point of declaration to the end of the file in which they are declared.

Global variables have static duration, which means they are created when the program is started, and destroyed when it ends.

Global variables can have either internal or external linkage, via the static and extern keywords respectively.

为什么(非常量)全局变量是邪恶的

如果你向一位资深程序员询问一条关于良好编程实践的建议,经过一番思考,最有可能的答案是,“避免全局变量!而且有充分的理由:全局变量是该语言中历史上最被滥用的概念之一。

新程序员经常倾向于使用大量全局变量,因为它们易于使用,特别是当涉及对不同函数的许多调用时(通过函数参数传递数据是一种痛苦)。但是,这通常是一个坏主意。许多开发人员认为应该完全避免使用非常量全局变量!

为什么(非常量)全局变量是邪恶的
到目前为止,非常量全局变量危险的最大原因是因为它们的值可以被任何调用的函数更改,并且程序员没有简单的方法知道这种情况会发生。请考虑以下程序:

**全局变量还使您的程序模块化程度降低且灵活性降低。**一个只利用其参数并且没有副作用的功能是完全模块化的。模块化既有助于理解程序的功能,也有助于可重用性。全局变量显著降低了模块化。

全局变量的初始化顺序问题

静态变量(包括全局变量)的初始化作为程序启动的一部分,在 main 执行函数之前发生。这分两个阶段进行。

第一阶段称为 static initialization 。在静态初始化阶段,具有 constexpr 初始值设定项(包括文本)的全局变量将初始化为这些值。此外,没有初始值设定项的全局变量初始化为零。
第二阶段称为 dynamic initialization 。这个阶段更加复杂和微妙,但它的要点是初始化具有非 constexpr 初始值设定项的全局变量。

更大的问题是,没有定义不同文件之间的初始化顺序。给定两个文件,并且 b.cpp , a.cpp 任何一个都可以先初始化其全局变量。这意味着,如果 中的 a.cpp 变量依赖于 中的 b.cpp 值,则有 50% 的可能性这些变量尚未初始化。

那么使用非常量全局变量的充分理由是什么?

避免使用非常量全局变量。但在某些情况下,明智地使用非常量全局变量实际上可以降低程序的复杂性,在这些罕见的情况下,它们的使用可能比替代方案更好。

日志文件就是一个很好的示例,您可以在其中转储错误或调试信息。将其定义为全局可能是有意义的,因为您可能在一个程序中只有一个日志,并且它可能会在程序中的任何位置使用。

值得一提的是,std::cout 和 std::cin 对象被实现为全局变量(在 std 命名空间内)。

根据经验,全局变量的任何使用都应至少满足以下两个条件: 变量在程序中应该只代表一个东西,并且它的使用应该在整个程序中无处不在

保护自己免受全局变量破坏

如果您确实发现了非常量全局变量的良好用途,那么一些有用的建议将最大限度地减少您可能遇到的麻烦。此建议不仅适用于非常量全局变量,而且可以帮助处理所有全局变量。

首先,在所有非命名空间的全局变量前面加上“g”或“g_”,或者更好的是,将它们放在一个命名空间中(在第 7.2 课 - 用户定义的命名空间和作用域解析运算符中讨论),以减少命名冲突的机会。

**其次,与其允许直接访问全局变量,不如“封装”变量。**确保变量只能从声明它的文件中访问,例如,通过使变量成为静态或常量,然后提供外部全局“访问函数”来处理变量。这些功能可以确保保持正确的使用(例如,进行输入验证,范围检查等)。此外,如果您决定更改底层实现(例如,从一个数据库移动到另一个数据库),则只需更新访问函数,而不是直接使用全局变量的每段代码。

namespace constants
{
    constexpr double gravity { 9.8 }; // has internal linkage, is accessible only within this file
}

double getGravity() // has external linkage, can be accessed be other files
{
    // We could add logic here if needed later
    // or change the implementation transparently to the callers
    return constants::gravity;
}

第三,在编写使用全局变量的独立函数时,不要直接在函数体中使用该变量。改为将其作为参数传入。这样,如果您的函数在某些情况下需要使用不同的值,您可以简单地改变参数。这有助于保持模块化。

在多个文件之间共享全局常量(使用内联变量)

在某些应用程序中,可能需要在整个代码中使用某些符号常量(而不仅仅是在一个位置)。这些可以包括不变的物理或数学常数(例如 pi 或阿伏伽德罗数),或特定于应用的“调整”值(例如摩擦或重力系数)。

作为内部变量的全局常量

由于 const 全局具有内部链接,因此每个 .cpp 文件都会获得链接器看不到的全局变量的独立版本。
在 C++17 之前,以下是最简单和最常见的解决方案:

1.Create a header file to hold these constants
创建一个头文件来保存这些常量
2.Inside this header file, define a namespace (discussed in lesson 7.2 – User-defined namespaces and the scope resolution operator)
在此头文件中,定义一个命名空间(在第 7.2 课 – 用户定义的命名空间和作用域解析运算符中讨论)
3.Add all your constants inside the namespace (make sure they’re constexpr)
在命名空间中添加所有常量(确保它们是 constexpr)
4.#include the header file wherever you need it
将头文件 #include 到您需要的任何位置

全局常量作为外部变量

每次 constants.h #included 到不同的代码文件中时,这些变量中的每一个都会复制到包含代码文件中。因此,如果 constants.h 被包含在 20 个不同的代码文件中,则每个变量都会重复 20 次。

  • 更改单个常量值需要重新编译包含常量标头的每个文件,这可能会导致大型项目的重建时间过长。
  • 如果常量很大并且无法优化,则可能会占用大量内存。
    避免这些问题的一种方法是将这些常量转换为外部变量,因为这样我们就可以拥有一个在所有文件之间共享的变量(初始化一次)。在此方法中,我们将在 .cpp 文件中定义常量(以确保定义仅存在于一个位置),并在标头中提出声明(将由其他文件包含)。
#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants
{
    // since the actual variables are inside a namespace, the forward declarations need to be inside a namespace as well
    extern const double pi;
    extern const double avogadro;
    extern const double myGravity;
}

#endif

作为内联变量的全局常量 C++17

C++17引入了一个名为. inline variables 在C++,该术语 inline 已经演变为“允许多个定义”。因此,行联变量是允许在多个文件中定义而不违反一个定义规则的变量。默认情况下,内联全局变量具有外部链接。

链接器会将变量的所有内联定义合并到单个变量定义中(从而满足一个定义规则)。

#ifndef CONSTANTS_H
#define CONSTANTS_H

// define your own namespace to hold constants
namespace constants
{
    inline constexpr double pi { 3.14159 }; // note: now inline constexpr
    inline constexpr double avogadro { 6.0221413e23 };
    inline constexpr double myGravity { 9.2 }; // m/s^2 -- gravity is light on this planet
    // ... other related constants
}
#endif

c++ inline constexpr 与 extern使用区别

inline constexpr用于在编译时求值的常量表达式,并建议编译器将其内联展开,而extern用于声明具有外部链接性的全局变量或函数,使其在其他文件中可见和可用。它们在功能和使用上有明显的区别,并且用于不同的情况和目的。

如果常量较小且在多个编译单元中直接使用,保留inline constexpr的定义是合适的,并且可以避免额外的链接过程。
如果常量较大或希望在多个编译单元之间共享同一个实例,可以使用extern将常量定义移到源文件中,并在头文件中进行声明。这样可以减少重复的内存占用和链接时间。

Constexpr 和 consteval 函数

在第 4.13 课 – Const 变量和符号常量中,我们介绍了 constexpr 关键字,我们用它来创建编译时(符号)常量。我们还引入了常量表达式,这些表达式可以在编译时而不是运行时计算。

函数可以在编译时进行评估。

constexpr 函数是一个函数,其返回值可以在编译时计算。为了使函数成为 constexpr 函数,我们只需在返回类型前面使用 constexpr 关键字。

#include 

constexpr int greater(int x, int y) // now a constexpr function
{
    return (x > y ? x : y);
}

int main()
{
    constexpr int x{ 5 };
    constexpr int y{ 6 };

    // We'll explain why we use variable g here later in the lesson
    constexpr int g { greater(x, y) }; // will be evaluated at compile-time

    std::cout << g << " is greater!\n";

    return 0;
}

要符合编译时计算的条件,函数必须具有 constexpr 返回类型,并且不调用任何非 constexpr 函数。此外,对函数的调用必须具有 constexpr 参数。

Constexpr 函数也可以在运行时进行评估

#include 

constexpr int greater(int x, int y)
{
    return (x > y ? x : y);
}

int main()
{
    int x{ 5 }; // not constexpr
    int y{ 6 }; // not constexpr

    std::cout << greater(x, y) << " is greater!\n"; // will be evaluated at runtime

    return 0;
}

数 x 不是 y constexpr,因此无法在编译时解析该函数。但是,该函数仍将在运行时解析,将预期值作为非 constexpr 返回 int 。

不允许 constexpr 函数调用非 constexpr 函数。如果允许这样做,constexpr 函数将无法在编译时进行评估,这违背了 constexpr 的观点。尝试这样做将导致编译器产生编译错误。

那么在编译时何时计算 constexpr 函数呢?

只有在需要常量表达式的情况下使用返回值,则只有在编译时计算符合计算条件的 constexpr 函数才会在编译时计算。否则,不能保证编译时计算。

确定 constexpr 函数调用是在编译时还是运行时计算

std::is_constant_evaluated()

#include  // for std::is_constant_evaluated
constexpr int someFunction()
{
    if (std::is_constant_evaluated()) // if compile-time evaluation
        // do something
    else // runtime evaluation
        // do something else
}

强制在编译时计算 constexpr 函数

们可以强制在编译时计算的 constexpr 函数在编译时实际计算,方法是确保在需要常量表达式的地方使用返回值。这需要基于每个呼叫完成。

Consteval

C++20 引入了关键字 consteval,该关键字用于指示函数必须在编译时求值,否则将导致编译错误。此类函数称为即时函数。

#include 

consteval int greater(int x, int y) // function is now consteval
{
    return (x > y ? x : y);
}

int main()
{
    constexpr int g { greater(5, 6) };              // ok: will evaluate at compile-time
    std::cout << g << '\n';

    std::cout << greater(5, 6) << " is greater!\n"; // ok: will evaluate at compile-time

    int x{ 5 }; // not constexpr
    std::cout << greater(x, 6) << " is greater!\n"; // error: consteval functions must evaluate at compile-time

    return 0;
}

使用 consteval 使 constexpr 在编译时执行 C++20

#include 

// Uses abbreviated function template (C++20) and `auto` return type to make this function work with any type of value
// See 'related content' box below for more info (you don't need to know how these work to use this function)
consteval auto compileTime(auto value)
{
    return value;
}

constexpr int greater(int x, int y) // function is constexpr
{
    return (x > y ? x : y);
}

int main()
{
    std::cout << greater(5, 6) << '\n';              // may or may not execute at compile-time
    std::cout << compileTime(greater(5, 6)) << '\n'; // will execute at compile-time

    int x { 5 };
    std::cout << greater(x, 6) << '\n';              // we can still call the constexpr version at runtime if we wish

    return 0;
}

Constexpr 函数是隐式内联的

constexpr 函数通常在头文件中定义,因此它们可以 #included 到任何需要完整定义的.cpp文件中。

Constexpr/consteval 函数参数不是 constexpr,但可以用作其他 constexpr 函数的参数

未命名和内联命名空间

在内联命名空间中声明的任何内容都被视为父命名空间的一部分。但是,与未命名的命名空间不同,内联命名空间不会影响链接。
未命名(匿名)命名空间

#include 

namespace // unnamed namespace
{
    void doSomething() // can only be accessed in this file
    {
        std::cout << "v1\n";
    }
}

int main()
{
    doSomething(); // we can call doSomething() without a namespace prefix

    return 0;
}

在未命名命名空间中声明的所有内容都被视为父命名空间的一部分。因此,即使函数是在未命名的命名空间中定义的,函数 doSomething() 本身也可以从父命名空间(在本例中为全局命名空间)访问,这就是为什么我们可以在没有任何限定符的情况下调用 doSomething() from main() 的原因。
但未命名命名空间的另一个影响是,未命名命名空间中的所有标识符都被视为具有内部链接,这意味着在定义未命名命名空间的文件之外看不到未命名命名空间的内容。

内联命名空间

内联命名空间是通常用于对内容进行版本控制的命名空间。与未命名命名空间非常相似,在内联命名空间中声明的任何内容都被视为父命名空间的一部分。但是,与未命名的命名空间不同,内联命名空间不会影响链接。

#include 

inline namespace V1 // declare an inline namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

namespace V2 // declare a normal namespace named V2
{
    void doSomething()
    {
        std::cout << "V2\n";
    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    V2::doSomething(); // calls the V2 version of doSomething()

    doSomething(); // calls the inline version of doSomething() (which is V1)

    return 0;
}

如果要推送较新版本:

#include 

namespace V1 // declare a normal namespace named V1
{
    void doSomething()
    {
        std::cout << "V1\n";
    }
}

inline namespace V2 // declare an inline namespace named V2
{
    void doSomething()
    {
        std::cout << "V2\n";
    }
}

int main()
{
    V1::doSomething(); // calls the V1 version of doSomething()
    V2::doSomething(); // calls the V2 version of doSomething()

    doSomething(); // calls the inline version of doSomething() (which is V2)

    return 0;
}

总结

  • 复合语句或块是一组零个或多个语句,编译器将其视为单个语句。块以符号开头,以 { } 符号结尾,要执行的语句位于两者之间。块可以在允许单个语句的任何地方使用。块的末尾不需要分号。块通常与执行多个语句结合使用 if statements 。
  • 用户定义的命名空间是您为自己的声明定义的命名空间。由C++(如 global namespace )或库(如 namespace std )提供的命名空间不被视为用户定义的命名空间。
  • 可以通过范围解析运算符 (: 访问命名空间中的声明。范围解析运算符告诉编译器,应在左侧操作数的范围内查找右侧操作数指定的标识符。如果未提供左侧操作数,则假定全局命名空间。
  • 局部变量是在函数中定义的变量(包括函数参数)。局部变量具有块范围,这意味着它们从定义点到定义块的末尾都在范围内。局部变量具有自动存储持续时间,这意味着它们在定义点创建,并在定义它们的块结束时销毁。
  • 在嵌套块中声明的名称可以遮蔽或隐藏外部块中同名变量。应避免这种情况。
  • 全局变量是在函数外部定义的变量。全局变量具有文件范围,这意味着它们从声明点到声明它们的文件末尾都是可见的。全局变量具有静态持续时间,这意味着它们在程序启动时创建,并在程序结束时销毁。尽可能避免动态初始化静态变量。
  • 标识符的链接确定该名称的其他声明是否引用同一对象。局部变量没有链接。具有内部链接的标识符可以在单个文件中查看和使用,但不能从其他文件访问。具有外部链接的标识符既可以从定义它们的文件中查看和使用,也可以从其他代码文件(通过前向声明)查看和使用。
  • 尽可能避免使用非常量全局变量。Const 全局变量通常被认为是可以接受的。如果编译器支持 C++17,则对全局常量使用内联变量。
  • 局部变量可以通过 static 关键字给出静态持续时间。
  • 限定名称是包含关联作用域的名称(例如 std::string )。非限定名称是不包含范围限定符的名称(例如 string )。
  • 可以使用 using 语句(包括使用声明和 using 指令)来避免使用显式命名空间限定标识符。using 声明允许我们使用非限定名称(没有作用域)作为限定名称的别名。using 指令将所有标识符从命名空间导入到 using 指令的作用域中。通常应避免这两种情况。
  • 内联函数最初设计为请求编译器将函数调用替换为函数代码的内联扩展。您不需要为此目的使用 inline 关键字,因为编译器通常会为您确定这一点。在现代C++中, inline 关键字用于将函数从单定义规则中免除,从而允许将其定义导入到多个代码文件中。内联函数通常在头文件中定义,因此可以将它们 #included 到需要它们的任何代码文件中。
  • constexpr 函数是一个函数,其返回值可以在编译时计算。为了使函数成为 constexpr 函数,我们只需在返回类型前面使用 constexpr 关键字。如果在需要 constexpr 值的上下文中使用返回值,则必须在编译时计算符合编译时计算条件的 constexpr 函数。否则,编译器可以在编译时或运行时自由计算函数。
  • C++20 引入了关键字 ,该关键字 consteval 用于指示函数必须在编译时求值,否则将导致编译错误。此类函数称为即时函数。
  • 最后,C++ 支持未命名的命名空间,这些命名空间隐式地将命名空间的所有内容视为具有内部链接。C++还支持内联命名空间,这些命名空间为命名空间提供了一些基元版本控制功能。

你可能感兴趣的:(c++)