20-4 命令行参数
命令行参数的必要性
正如你在第0.5课——编译器、链接器和库简介中所学到的,当你编译并链接程序时,输出的结果是一个可执行文件。程序运行时,执行流程将从名为main()的函数顶部开始。到目前为止,我们都是这样声明main函数的:
int main()
请注意,此版本的 main() 函数不接受任何参数。然而,许多程序需要某种输入才能运行。例如,假设你正在编写一个名为 Thumbnail 的程序,该程序读取图像文件并生成缩略图(即图像的缩小版本)。那么Thumbnail如何知道要读取和处理哪张图片?用户必须能告知程序打开哪个文件。为此,你可以采用以下方法:
// Program: Thumbnail
#include <iostream>
#include <string>
int main()
{
std::cout << "Please enter an image filename to create a thumbnail for: ";
std::string filename{};
std::cin >> filename;
// open image file
// create thumbnail
// output thumbnail
}
然而,这种方法存在潜在问题。每次运行程序时,程序都会等待用户输入。若仅在命令行手动运行一次,这或许不成问题。但在其他场景下则会引发困扰,例如需要对大量文件处理,或由其他程序调用本程序时。
让我们深入探讨这些场景。
假设需要为某个目录下的所有图像文件生成缩略图。该如何操作?你可以手动输入每个文件名,重复运行程序直到目录清空。但若文件多达数百个,这将耗费整天时间!更好的解决方案是编写程序循环遍历目录中的每个文件名,对每个文件调用一次缩略图生成程序。
现在设想你运营一个网站,希望用户每次上传图片时网站自动生成缩略图。由于该程序未配置网络输入接口,上传者如何输入文件名?理想方案是让网页服务器在上传完成后自动调用缩略图程序。
这两种场景都要求外部程序在启动缩略图程序时传递文件名作为输入,而非让程序启动后等待用户输入。
命令行参数Command line arguments是操作系统在程序启动时传递的可选字符串参数。程序可将其作为输入使用(或忽略)。正如函数参数为函数向其他函数提供输入提供了途径,命令行参数也为用户或程序向程序提供输入提供了途径。
传递命令行参数
可执行程序可在命令行中通过名称调用运行。例如,要在Windows机器的当前目录下运行名为“WordCount”的可执行文件,可输入:
WordCount
在基于Unix的操作系统上,等效的命令行应为:
./WordCount
要向 WordCount 传递命令行参数,我们只需在可执行文件名后列出命令行参数:
WordCount Myfile.txt
现在当执行WordCount时,Myfile.txt将作为命令行参数提供。程序可以拥有多个命令行参数,这些参数通过空格分隔:
WordCount Myfile.txt Myotherfile.txt
若您通过集成开发环境(IDE)运行程序,该环境应提供输入命令行参数的途径。
在 Microsoft Visual Studio 中,请在解决方案资源管理器中右键单击项目,选择“属性”。展开“配置属性”树节点,选择“调试”。右侧窗格中有一行名为“命令行参数”。您可在该处输入测试用的命令行参数,运行程序时这些参数将自动传递至程序。
在Code::Blocks中,请选择“项目 -> 设置程序参数”。
使用命令行参数
既然您已了解如何向程序提供命令行参数,下一步就是在 C++ 程序内部访问这些参数。为此,我们将使用一种与之前不同的 main() 函数形式。这种新的 main() 函数形式会接收两个参数(按惯例命名为 argc 和 argv),具体如下:
int main(int argc, char* argv[])
有时你也会看到它被写成:
int main(int argc, char** argv)
尽管这两种表示方式被视为等价,我们更倾向于第一种,因为它更符合直觉。
argc 是包含程序接收参数数量的整型参数(可理解为:argc = 参数计数argument count)。argc 值永远不小于 1,因为第一个参数始终是程序本身的名字。用户提供的每个命令行参数都会使 argc 增加 1。
argv 存储实际参数值(可理解为:argv = 参数值argument values,但其正式名称为“参数向量argument vectors”)。尽管 argv 的声明看似复杂,其实它只是一个 C 风格的 char 指针数组(每个指针指向一个 C 风格字符串)。该数组的长度即为 argc。
现在编写一个名为“MyArgs”的简短程序,用于打印所有命令行参数的值:
// Program: MyArgs
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "There are " << argc << " arguments:\n";
// Loop through each argument and print its number and value
for (int count{ 0 }; count < argc; ++count)
{
std::cout << count << ' ' << argv[count] << '\n';
}
return 0;
}
现在,当我们使用命令行参数“Myfile.txt”和“100”调用此程序(MyArgs)时,输出结果如下:

参数 0 是当前正在运行的程序的路径和名称。在此情况下,参数 1 和 2 是我们传递的两个命令行参数。
请注意,我们不能使用基于范围的 for 循环遍历 argv,因为基于范围的 for 循环无法作用于衰减的 C 风格数组。
处理数字参数
命令行参数始终以字符串形式传递,即使提供的值本质上是数字。若要将命令行参数作为数字使用,必须将其从字符串转换为数字。遗憾的是,C++对此的处理比预期更复杂。
C++的实现方式如下:
#include <iostream>
#include <sstream> // for std::stringstream
#include <string>
int main(int argc, char* argv[])
{
if (argc <= 1)
{
// On some operating systems, argv[0] can end up as an empty string instead of the program's name.
// We'll conditionalize our response on whether argv[0] is empty or not.
if (argv[0])
std::cout << "Usage: " << argv[0] << " <number>" << '\n';
else
std::cout << "Usage: <program name> <number>" << '\n';
return 1;
}
std::stringstream convert{ argv[1] }; // set up a stringstream variable named convert, initialized with the input from argv[1]
int myint{};
if (!(convert >> myint)) // do the conversion
myint = 0; // if conversion fails, set myint to a default value
std::cout << "Got integer: " << myint << '\n';
return 0;
}
当输入“567”时,该程序输出:

std::stringstream 的工作原理与 std::cin 类似。在此示例中,我们使用 argv[1] 的值初始化它,以便能够使用 >> 运算符将值提取到整型变量中(与使用 std::cin 的方式相同)。
我们将在后续章节中进一步探讨 std::stringstream。
操作系统首先解析命令行参数
当你在命令行输入内容(或从IDE运行程序)时,操作系统负责将该请求进行转换并正确路由。这不仅涉及运行可执行文件,还包括解析所有参数以确定其处理方式并传递给应用程序。
通常,操作系统对双引号和反斜杠等特殊字符的处理有特定规则。
例如:
MyArgs Hello world!
输出:

通常情况下,用双引号包裹的字符串会被视为同一个字符串的一部分:
MyArgs "Hello world!"
输出:

大多数操作系统允许通过在双引号前添加反斜杠来包含字面上的双引号:
MyArgs \"Hello world!\"
输出:
There are 3 arguments:
0 C:\MyArgs
1 "Hello
2 world!"

其他字符也可能需要反斜杠转义或特殊处理,具体取决于操作系统对其的解释方式。
结论
命令行参数为用户或其他程序在程序启动时传递输入数据提供了绝佳途径。建议将程序启动时所需的任何输入数据都设计为命令行参数。若未传递命令行参数,程序可检测到该情况并提示用户输入。如此设计,程序即可实现两种运行模式。

浙公网安备 33010602011771号