目录
1. 引言
C++20的标准库模块目前还没有正式得到编译器的支持,而MSVC中已经存在了对标准库模块的实验性支持。需要安装适用于v143生成工具的C++模块,并且将普通控制台应用项目属性C/C++下的所有选项中的启用实验性的C++标准库模块改为是。
1. 简单小程序"Hello World"
import std.core;
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
1.1 模块导入
C++20中最重要的新特性之一就是对模块的支持,用来替代之前的头文件机制。如果你想要使用某个模块中的功能,则需要导入这个模块,可以通过一条import声明做到。如import std.core;
如果你的编译器还不支持模块,只需要简单地将import声明替换为#include预处理指令。
1.2 预处理指令
1.2.1 简介
如果你的编译器还不支持C++20的模块,需要编写如下的预处理指令:#include <iostream>
。
简单来说,生成一个C++程序共有3个步骤。首先,代码经过预处理器,预处理器会识别代码中的元信息。其次,代码被编译或转换为计算机可识别的目标文件。最后,独立的目标文件被连接在一起生成一个应用程序。
预处理指令以#字符开始,前面实例中的#include <iostream>
就是一条预处理指令。在此例中,include指令告诉预处理器:提取<iostream>头文件中的所有内容并提供给当前文件。
头文件最常见的用途是声明在其他地方定义的函数,函数声明会通知编译器如何调用这个函数,并声明函数中参数的个数和类型,以及函数的返回类型;函数定义包含这个函数的实际代码。在C++20引入模块之前,声明通常放在扩展名为.h的文件中,称为头文件,其定义通常包含在扩展名为.cpp的文件中,称为源文件。有了模块,我们不再需要将声明与定义分离,但是之前的写法仍然可以使用。
注意:
1. 在C中,标准库头文件的名称通常以.h结尾,不使用名称空间。
2. 在C++中,标准库头文件的名称省略了.h后缀,所有文件都在std名称空间和std的子名称空间中定义。
3. C中的标准库头文件在C++中依然存在,但使用以下两个版本:
3.1 不使用.h后缀,改成前缀c,这是推荐使用的版本,这些头文件将内容放在std名称空间中。
3.2 使用.h后缀,这是旧版本,这些版本不适用名称空间。
1.2.2 常用的预处理指令
预处理指令 | 功能 | 常见用法 |
---|---|---|
#include [file] | 将指定的文件插入代码中指令所在的位置 | 几乎总是用来包含头文件,使代码可使用在其他位置定义的功能 |
#define [id] [value] | 每个指定的标识符都被替换成指定的值 | 在C中,常用来定义常数值或宏。C++提供了常数和大多数宏类型的更好机制。在C++使用宏有风险。 |
#ifdef [id] #endif #ifndef [id] #endif |
ifdef(if defined)块和ifndef(if not defined)块中的代码被有条件地包含或舍弃,这取决于是否使用#define定义了特定的标识符 | 经常用来防止循环包含。每个头文件都以#ifndef开头,以保证某个标识符还未被定义,然后用一条#define指令定义该标识符。头文件以#endif结束,这样这个头文件不会被多次包含 |
#pragma [xyz] | xyz因编译器而异。如果在预处理期间执行到这一指令,通常会显示一条警告或错误信息 | 参见该表之后的举例 |
下面是使用预处理指令避免重复包含的一个实例:
#ifndef MYHEADER_H
#define MYHEADER_H
// ... the contents of this header file
#endif
如果编译器支持#pragma once
指令,可采用以下方法重写上面的代码。
#pragma once
// ... the contents of this header file
1.3 main()函数
main()函数是程序的入口。main函数返回一个int值以指示程序的最终执行状态。在main()函数中可以忽略显式的return语句,这种情况下会自动返回0.main()函数要么没有参数,要么具有两个参数,如下所示。
int main(int argc, char* argv[])
argc给出了传递给程序的实参数目,argv包含了这些实参。注意argv[0]可能是程序的名称,也可能是空字符串,所以不应使用它。应当使用特定于平台的功能检索程序名。最重要的是,实际参数从索引1开始。
1.4 输入输出流
1.4.1 输出流
基本的输入输出非常简单,可将输出流想象为针对数据的洗衣滑槽,放入其中的任何内容都可以被正确地输出。std::cout就是对应于用户控制台或标准输出的滑槽,此外还有其他滑槽,包括用于输出错误信息的std::cerr。<<运算符将数据放入滑槽,在前面的示例中,引号中的文本字符串被送到标准输出。输出流可在一行代码中连续输出多个不同类型的数据,如下面的代码所示。
std::cout << "There are " << 219 << " ways I love you." << std::endl;
从C++20开始,推荐的写法是使用std::format(),定义在中,用来格式化字符串 。如std::cout << std::format("There are {} ways I love you.", 219) << std::endl;
。std::endl代表序列的结尾。当输出流遇到std::endl时,就会将已送入滑槽的所有内容输出并转移到下一行。
1.4.2 转义字符
表明一行结尾的另一种方法是使用\n字符,\n字符是一个转义字符,这是一个换行符。转义字符可以在任何被引用的文本字符串中使用。
转义字符 | 含义 |
---|---|
\n | 换行:将光标移到下一行的开头 |
\r | 回车:将光标移到本行的开头 |
\t | 制表符 |
\ | 反斜杠字符 |
" | 引号 |
警告:请记住endl会在流中插入新的一行,并且将当前缓冲区中的所有内容刷出滑槽。不建议过度地使用endl,例如在循环中使用,这会影响程序的性能。另一方面,在流中插入\n也会插入新的一行,但不会自动刷新缓冲区。
1.4.3 输入流
流还可用于接收用户的输入,最简单的方式是在输入流中使用>>运算符。std::cin输入流接受用户的键盘输入。示例如下:
int value;
std::cin >> value:
需要慎重对待用户输入,因为永远都不会知道用户会输入什么类型的数据。
如果你拥有C的背景知识但初次接触C++,你可能想了解过去使用的、可靠的printf()和scanf()现在究竟是什么情况。尽管C++中仍然可以使用这些函数,但强烈建议你改用format()和流库,主要原因是printf()和scanf()未提供类型安全。
2. 名称空间
2.1 定义名称空间
名称空间用来处理不同代码段之间的名称冲突问题。例如,你某天编写了一段代码,其中有一个名为foo()的函数。某天,你决定用第三库,其中也有一个foo()函数。编译器无法判断你的代码要使用哪个版本的foo()函数。库函数的名称无法改变,而改变自己的函数名称又会感觉到痛苦。
在此类情况下使用名称空间,从而指定定义名称的环境。为将某段代码加入名称空间,可用namespace块将其包含。示例如下:
namespace mycode {
void foo() {
std::cout << "foo() called in the mycode namespace" << std::endl;
}
}
将你编写的foo()版本放到名称空间mycode后,这个函数就与第三方库提供的foo()函数区分开来。为调用启用了名称空间的foo()版本,需要使用::在函数名称之前给出名称空间,::称为作用域解析运算符。
mycode::foo();
2.2 using指令
mycode名称空间中的任何代码都可调用该名称空间内的代码,而不需要显式说明该名称空间。隐式的名称空间可使代码清晰并易于阅读。可使用using指令避免预先指明名称空间。这个指令通知编译器,后面的代码将使用指定名称空间中的名称。示例如下:
using namespace mycode;
int main() {
foo(); // Implies mycode::foo();
}
一个源文件中可包含多条using指令,这种方法虽然便捷,但注意不要过度使用。极端情况下,如果你使用了已知的所有名称空间,实际上就是完全取消了名称空间。如果使用了两个同名的名称空间,将再次出现名称冲突问题。另外,应该知道代码在哪个名称空间内运行,这样就不会无意中调用错误版本的函数。
可以使用using指令引用名称空间内的特定项。例如,想要只使用std名称空间中的cout,可以使用如下的using声明:
using std::cout;
后面的代码可以使用cout而不需要预先指明这个名称空间,但仍然需要显式说明std名称空间中的其他项。
警告:切勿在全局作用域的头文件中使用using指令或using声明,否则添加你的头文件的每个人都必须使用它。将其放在较小的作用域,例如名称空间或类作用域中,是可以的,甚至是在文件头部。将using指令或using声明放在模块接口文件中也是不错的选择,只要你不将它导出。
2.3 嵌套名称空间
嵌套的名称空间,即将一个名称空间放在另一个名称空间中。各个名称空间之间由双冒号隔开,示例如下:
namespace MyLibraries::Networking::FTP {
/*...*/
}
这种紧凑写法是在C++17之后才得到支持的,在C++17之前,必须按如下方式使用嵌套的名称空间。
namespace MyLibraries {
namespace Networking {
namespace FTP {
/*...*/
}
}
}
2.4 名称空间别名
可使用名称空间别名,为另一个名称空间指定一个更简短的新名称。示例如下
namespace MyFTP = MyLibraries::Networking::FTP;
参考
[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)