一、命令行参数的必要性
正如课程《编译器、链接器与库简介》所述,当程序编译并链接完成后,输出结果为一个可执行文件。程序运行时,执行流程从 main()
函数顶部开始。迄今我们均将 main
声明为:
int main()
该形式表示 main()
不接收任何形参。然而,许多程序仍需外部输入方可工作。举例而言,假设你正在编写名为 Thumbnail 的程序,其功能为读取图像文件并生成缩略图(尺寸更小的版本)。Thumbnail 如何知晓应处理哪幅图像?用户必须以某种方式告知程序需打开的文件。或许你会采用如下方案:
// 程序: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;
// 打开图像文件
// 创建缩略图
// 输出缩略图
}
然而,此方案存在潜在缺陷:每次运行程序时,都会暂停等待用户输入。若你仅在命令行手动运行一次,这或许不成问题;但在其他场景下,例如需要批量处理大量文件,或由另一程序调用 Thumbnail 时,此方式便显得捉襟见肘。
让我们进一步探讨这些情况。
假设你需要为某一目录中的所有图像生成缩略图。若手动逐条输入文件名,目录下若有数百张图片,操作将耗时整日!一个更优的解决方案是编写程序,遍历目录中的每个文件名,并依次调用 Thumbnail。
再考虑另一种情况:你正在运营一个网站,每当用户上传图像时,网站需即时生成缩略图。该程序原本并不具备接收 Web 输入的能力,那么上传者如何输入文件名?一个合理的解决方案是让 Web 服务器在上传完成后自动调用 Thumbnail。
在这两种情况下,我们都需要一种机制,使得外部程序在启动 Thumbnail 时即可向其传递文件名作为输入,而非等待程序启动后用户再手动输入。
命令行参数即是由操作系统在程序启动时传递给程序的可选字符串参数。程序可据此作为输入(亦可忽略)。正如函数形参允许函数向另一函数提供输入,命令行参数亦允许用户或程序向程序提供输入。
二、传递命令行参数
可执行程序可通过命令行调用,只需键入其名称。例如,在 Windows 当前目录下运行可执行文件 “WordCount”,可键入:
WordCount
类 Unix 系统下则为:
./WordCount
若要向 WordCount 传递命令行参数,只需在可执行文件名后列出参数:
WordCount Myfile.txt
执行后,Myfile.txt
即成为命令行参数。程序可拥有多个参数,空格分隔:
WordCount Myfile.txt Myotherfile.txt
若你通过 IDE 运行程序,IDE 亦应提供输入命令行参数的途径。
- Microsoft Visual Studio:在“解决方案资源管理器”中右键项目 → 属性 → 配置属性 → 调试 → “命令参数” → 输入参数并运行。
- Code::Blocks:选择“项目 → 设置程序参数”。
三、使用命令行参数
既然已知如何提供参数,下一步便是在 C++ 程序内部访问它们。为此,我们使用不同于以往的 main()
形式:
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 风格字符串的指针数组,其长度为argc
。
让我们编写一个名为 “MyArgs” 的简短程序,打印所有命令行参数:
// 程序:MyArgs
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "There are " << argc << " arguments:\n";
// 遍历每个参数并输出序号及值
for (int count{ 0 }; count < argc; ++count)
{
std::cout << count << ' ' << argv[count] << '\n';
}
return 0;
}
若我们用命令行参数 “Myfile.txt” 和 “100” 调用 MyArgs,输出如下:
There are 3 arguments:
0 C:\MyArgs
1 Myfile.txt
2 100
参数 0 为当前运行程序的路径及名称;参数 1 与 2 即我们传入的两个命令行参数。
注意:由于 argv
为衰变后的 C 风格数组,不可使用范围 for 循环遍历。
四、处理数值参数
命令行参数始终以字符串形式传递,即便其内容看似数值。若需将其作为数字使用,必须手动从字符串转换。遗憾的是,C++ 在这方面略显繁琐。
C++ 的推荐做法如下:
#include <iostream>
#include <sstream> // for std::stringstream
#include <string>
int main(int argc, char* argv[])
{
if (argc <= 1)
{
// 某些操作系统下 argv[0] 可能为空字符串而非程序名。
// 我们根据 argv[0] 是否为空给出相应提示。
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] }; // 创建 stringstream 变量 convert,并用 argv[1] 初始化
int myint{};
if (!(convert >> myint)) // 执行转换
myint = 0; // 转换失败,置默认值
std::cout << "Got integer: " << myint << '\n';
return 0;
}
输入 “567” 时,程序输出:
Got integer: 567
std::stringstream
的用法类似 std::cin
。此处我们用 argv[1]
初始化,再用 operator>>
提取至整型变量。
我们将在后续章节深入探讨 std::stringstream
。
五、操作系统首先解析命令行参数
当你在命令行键入命令(或通过 IDE 运行程序)时,操作系统负责解释并路由该请求。这不仅包括执行可执行文件,亦包括解析任何参数,并决定如何将其传递给应用程序。
一般而言,操作系统对特殊字符(如双引号、反斜杠)有专门的处理规则。
例如:
MyArgs Hello world!
输出:
There are 3 arguments:
0 C:\MyArgs
1 Hello
2 world!
通常,双引号内的字符串被视为同一参数:
MyArgs "Hello world!"
输出:
There are 2 arguments:
0 C:\MyArgs
1 Hello world!
多数操作系统允许通过反斜杠转义以包含字面双引号:
MyArgs \"Hello world!\"
输出:
There are 3 arguments:
0 C:\MyArgs
1 \"Hello
2 world!\"
其他字符亦可能需反斜杠转义或转义,具体取决于操作系统如何解释。
六、结论
命令行参数为用户或其他程序在程序启动时传递输入数据提供了极佳方式。建议将程序启动时必需的所有输入数据设计为命令行参数。若未提供,可检测并提示用户输入。如此,程序即可双向兼容。