Усі програми, які ми писали до цього, отримували вхідні дані одним способом: через std::cin — тобто програма запускалась, призупинялась і чекала, поки користувач введе щось з клавіатури. Цей підхід зручний для навчання, але він є непридатним для автоматизації.
Уявіть таку задачу: у вас є програма, що обробляє зображення — стискає їх і зберігає мініатюру. У каталозі лежить 500 файлів. Щоб обробити кожен через std::cin, потрібно 500 разів запустити програму вручну і ввести ім'я файлу. Але що, якщо цю програму викликає веб-сервер, скрипт або планувальник завдань? Вони не можуть «вводити» дані через клавіатуру — їм потрібно передати всю необхідну інформацію при запуску.
Саме для цього і існують аргументи командного рядка (command-line arguments) — рядки, які передаються програмі ОС у момент її запуску. Ви вже бачили цей механізм щодня:
// Компілятор g++ отримує ім'я файлу як аргумент
g++ main.cpp -o program
// git отримує команду і шлях
git add .
// Запуск власної програми з аргументами
./picture photo.jpg --width=200
Кожен з цих інструментів реалізований на C або C++, і їхній main отримує аргументи через стандартний механізм, який ми зараз вивчимо.
const char*), масивів вказівників і базової роботи з std::cin/std::cout. Аргументи командного рядка є частиною стандарту C++ і підтримуються усіма компіляторами і платформами.main: argc і argvДо цього ми оголошували main без параметрів:
int main()
{
// ...
}
Щоб отримати доступ до аргументів командного рядка, використовується інша форма main з двома параметрами:
int main(int argc, char* argv[])
{
// ...
}
Або еквівалентний запис через подвійний вказівник:
int main(int argc, char** argv)
{
// Ідентично попередньому — обидві форми правильні
}
char* argv[] — є інтуїтивнішою і частіше рекомендована в підручниках. Друга — char** argv — демонструє справжню природу: вказівник на вказівник на char.argc (від "argument count" — кількість аргументів) — ціле число, що зберігає кількість аргументів. Завжди щонайменше 1, бо першим аргументом є ім'я самої програми.
argv (від "argument values" — значення аргументів) — масив C-рядків (char*) довжиною argc. Кожен елемент — один аргумент:
| Індекс | Вміст argv[i] |
|---|---|
argv[0] | Шлях до виконуваного файлу ("C:\\program.exe" або "./program") |
argv[1] | Перший аргумент користувача |
argv[2] | Другий аргумент користувача |
argv[argc-1] | Останній аргумент |
argv[argc] | Гарантовано nullptr (нульовий вказівник) |
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "Кількість аргументів: " << argc << '\n';
std::cout << "Аргументи:\n";
for (int i = 0; i < argc; ++i)
{
std::cout << " argv[" << i << "] = " << argv[i] << '\n';
}
return 0;
}
Якщо скомпілювати цю програму і запустити її так:
./PrintArgs hello world 42
Вивід:
Розбір. Навіть якщо ми передали 3 аргументи (hello, world, 42), argc = 4, бо argv[0] — сама програма. Аргументи завжди є рядками — зверніть: 42 виведено як рядок "42", а не як числове значення.
Рядок 8. for (int i = 0; i < argc; ++i) — ітерація по всіх аргументах. Якщо хочете пропустити argv[0] (ім'я програми) і обробляти лише аргументи від користувача — починайте з i = 1.
Завжди перевіряйте argc перед доступом до argv[1] та далі. Якщо користувач не передав жодного аргументу — argv[1] просто не існує, і звертання до нього — невизначена поведінка:
#include <iostream>
#include <cstdlib> // для exit()
int main(int argc, char* argv[])
{
// Перевіряємо: чи передав користувач хоча б один аргумент?
// argc == 1 означає: є лише argv[0] (ім'я програми), більше нічого
if (argc < 2)
{
// argv[0] — ім'я програми: виводимо правильне «використання»
std::cout << "Використання: " << argv[0] << " <ім'я файлу>\n";
std::cout << "Приклад: " << argv[0] << " photo.jpg\n";
// exit(1) — завершуємо програму з кодом помилки 1
// За конвенцією: 0 = успіх, не 0 = помилка
exit(1);
}
// Тепер safe: argv[1] гарантовано існує
std::cout << "Обробляємо файл: " << argv[1] << '\n';
// ... подальша логіка роботи з файлом
return 0;
}
Рядок 8. if (argc < 2) — якщо менше двох аргументів (тобто немає argv[1]), виводимо підказку і завершуємо. Це стандартна конвенція: будь-яка CLI-програма повинна пояснювати користувачу своє «правильне» використання, якщо аргументів бракує.
Рядок 14. exit(1) — функція зі <cstdlib>, що завершує програму негайно, не повертаючись з main. Аргумент — код виходу: 0 означає успіх, будь-яке ненульове — помилку. Ця конвенція є стандартною в Unix/Linux/Windows і дозволяє скриптам та автоматизаційним інструментам перевіряти, чи завершилась програма успішно.
Усі аргументи командного рядка приходять як рядки (const char*), навіть якщо користувач ввів число. Щоб використати їх як числа — потрібно явно конвертувати.
Найпростіший спосіб у C++ — через std::stringstream:
#include <iostream>
#include <sstream> // для std::stringstream
#include <cstdlib> // для exit()
int main(int argc, char* argv[])
{
if (argc < 2)
{
std::cout << "Використання: " << argv[0] << " <число>\n";
exit(1);
}
// argv[1] — рядок, наприклад "42" або "hello"
// Конвертуємо його в int через std::stringstream
std::stringstream converter(argv[1]); // ініціалізуємо потік рядком
int number;
// Оператор >> намагається зчитати int з рядка
// Якщо рядок не є числом — повертає false (потік у стані помилки)
if (!(converter >> number))
{
std::cout << "Помилка: \"" << argv[1] << "\" — не є цілим числом.\n";
exit(1);
}
std::cout << "Отримано число: " << number << '\n';
std::cout << "Подвоєне: " << number * 2 << '\n';
std::cout << "Квадрат: " << number * number << '\n';
return 0;
}
Рядок 15. std::stringstream converter(argv[1]) — створюємо «рядковий потік», ініціалізований рядком-аргументом. std::stringstream поводиться як std::cin, але зчитує дані не з клавіатури, а з рядка в пам'яті. Включити потрібно <sstream>.
Рядок 21. if (!(converter >> number)) — оператор >> намагається зчитати int з потоку. Якщо це неможливо (рядок не є числом) — потік переходить у стан помилки і >> повертає false-подібне значення. Оператор ! інвертує результат: якщо конвертація не вдалась → заходимо в if.
#include <iostream>
#include <sstream>
#include <cstdlib>
int strToInt(const char* str)
{
std::stringstream ss(str);
int result;
if (!(ss >> result))
return 0; // значення за замовчуванням при помилці
return result;
}
int main(int argc, char* argv[])
{
if (argc < 3)
{
std::cout << "Використання: " << argv[0] << " <width> <height>\n";
exit(1);
}
int width = strToInt(argv[1]);
int height = strToInt(argv[2]);
if (width <= 0 || height <= 0)
{
std::cout << "Помилка: розміри мають бути додатніми числами.\n";
exit(1);
}
std::cout << "Розмір: " << width << " x " << height << '\n';
std::cout << "Площа: " << width * height << '\n';
std::cout << "Периметр:" << 2 * (width + height) << '\n';
return 0;
}
Рядки 5–11. Допоміжна функція strToInt — приймає C-рядок і повертає int. Можна розміщувати логіку конвертації прямо в main, але виносити її у функцію є гарною практикою: повторне використання і зрозумілість.
Коли ви вводите команду в терміналі, операційна система (точніше — shell: bash, cmd, PowerShell) відповідає за розбиття рядка на окремі аргументи. Важливо розуміти правила, щоб ваша програма отримувала саме те, що ви задумали.
./program Hello world
argc = 3: argv[1] = "Hello", argv[2] = "world". Кожне слово — окремий аргумент.
./program "Hello world"
argc = 2: argv[1] = "Hello world". Лапки говорять shell: все всередині — один аргумент, навіть якщо є пробіли.
./program ""
argc = 2: argv[1] = "" (порожній рядок). Порожній аргумент — теж аргумент.
./program file*.txt // shell розкриє * у список файлів!
./program 'file*.txt' // одинарні лапки у bash: передати буквально
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "argc = " << argc << '\n';
for (int i = 0; i < argc; ++i)
std::cout << "argv[" << i << "] = \"" << argv[i] << "\"\n";
return 0;
}
$ ./ParsingDemo one two three
argc = 4
argv[0] = "./ParsingDemo"
argv[1] = "one"
argv[2] = "two"
argv[3] = "three"
$ ./ParsingDemo "one two" three
argc = 3
argv[0] = "./ParsingDemo"
argv[1] = "one two"
argv[2] = "three"
$ ./ParsingDemo "" hello
argc = 3
argv[0] = "./ParsingDemo"
argv[1] = ""
argv[2] = "hello"
Побудуємо повноцінну маленьку програму-калькулятор, що приймає вираз <число> <оператор> <число> через аргументи командного рядка:
#include <iostream>
#include <sstream>
#include <cstdlib>
int strToInt(const char* str, bool& success)
{
std::stringstream ss(str);
int result;
success = static_cast<bool>(ss >> result);
return result;
}
int main(int argc, char* argv[])
{
// Перевірка: потрібно рівно 3 аргументи (число оператор число)
if (argc != 4)
{
std::cout << "Використання: " << argv[0] << " <число> <оператор> <число>\n";
std::cout << "Оператори: + - * /\n";
std::cout << "Приклад: " << argv[0] << " 10 + 5\n";
exit(1);
}
// Конвертуємо перше і третє аргументи в числа
bool ok1, ok2;
int left = strToInt(argv[1], ok1);
int right = strToInt(argv[3], ok2);
if (!ok1 || !ok2)
{
std::cout << "Помилка: очікуються цілі числа.\n";
exit(1);
}
// argv[2] — оператор (рядок, але нас цікавить перший символ)
char op = argv[2][0];
if (op == '+')
std::cout << left << " + " << right << " = " << (left + right) << '\n';
else if (op == '-')
std::cout << left << " - " << right << " = " << (left - right) << '\n';
else if (op == '*')
std::cout << left << " * " << right << " = " << (left * right) << '\n';
else if (op == '/')
{
if (right == 0)
{
std::cout << "Помилка: ділення на нуль!\n";
exit(1);
}
std::cout << left << " / " << right << " = " << (left / right) << '\n';
}
else
{
std::cout << "Помилка: невідомий оператор '" << op << "'.\n";
std::cout << "Допустимі: + - * /\n";
exit(1);
}
return 0;
}
Рядок 16. if (argc != 4) — строга перевірка: саме 4 аргументи (argv[0] + 3 від користувача). Якщо менше або більше — помилка.
Рядок 38. char op = argv[2][0] — argv[2] є рядком char*. Нас цікавить перший символ — оператор. argv[2][0] — читання нульового елементу рядку. Якщо користувач передав "+", то argv[2][0] == '+'.
Рядки 5–10. strToInt приймає додатковий параметр bool& success — прапорець успіху конвертації. Це дозволяє функції повідомляти, чи вдалась конвертація, без повернення спеціального «службового» значення на кшталт -1.
char** argv зсерединиОскільки ця стаття є частиною модуля про вказівники — розглянемо argv через призму того, що ми вивчили.
argv — це масив вказівників на рядки. Кожен елемент argv[i] є char* — вказівником на перший символ i-го рядка. Останній елемент argv[argc] завжди є nullptr.
#include <iostream>
int main(int argc, char* argv[])
{
// argv[i] — вказівник на char (перший символ рядка)
// argv[i][j] — j-й символ i-го аргументу
for (int i = 0; i < argc; ++i)
{
char* arg = argv[i]; // вказівник на початок рядка
std::cout << "argv[" << i << "]: ";
// Ітеруємо по символах рядка вручну — через арифметику вказівників
int j = 0;
while (arg[j] != '\0')
{
std::cout << arg[j];
++j;
}
std::cout << " (довжина: " << j << ")\n";
}
// argv[argc] — гарантовано nullptr (NULL-термінатор масиву вказівників)
std::cout << "argv[argc] == nullptr: "
<< (argv[argc] == nullptr ? "так" : "ні") << '\n';
return 0;
}
argv — це вказівник на вказівники. argv[i] — вказівник на C-рядок (масив символів). argv[i][j] — j-й символ i-го аргументу. argv[argc] — гарантований nullptr, що дозволяє також ітерувати через while (*argv) замість лічильника argc.
Рівень 1 — Базовий
Завдання 1. Напишіть програму EchoArgs, що виводить всі аргументи окрім argv[0] (ім'я програми), кожен на новому рядку. Якщо аргументів немає — вивести "Жодних аргументів не передано.".
Завдання 2. Що виведе команда? Поясніть значення argc і кожного argv[i]:
./MyProgram "one two" three "" four
Завдання 3. Напишіть програму CountChars, що приймає один рядковий аргумент і виводить кількість символів у ньому (не через strlen, а через власний цикл з вказівником або індексом).
Рівень 2 — Логіка
Завдання 4. Напишіть програму SumArgs, що приймає довільну кількість числових аргументів і виводить їхню суму. Приклад: ./SumArgs 10 20 30 40 → Sum: 100. Якщо якийсь аргумент не є числом — вивести помилку і завершити програму з кодом 1.
Завдання 5. Напишіть програму Repeat, що приймає два аргументи: рядок і ціле число. Виводить рядок стільки разів, скільки вказує число: ./Repeat hello 3 → виводить hello тричі кожен з нового рядка.
Завдання 6. Напишіть програму MaxOfArgs, що знаходить максимум серед переданих чисел: ./MaxOfArgs 7 2 15 3 11 → Max: 15. Якщо аргументів немає — вивести підказку.
Рівень 3 — Архітектура
Завдання 7. Реалізуйте просту утиліту FileInfo, що приймає один або кілька «імен файлів» як аргументи і для кожного виводить:
Наприклад: ./FileInfo photo.jpg document.pdf readme →
photo.jpg → розширення: jpg
document.pdf → розширення: pdf
readme → без розширення
(Не потрібно відкривати файли — лише аналізувати рядки-аргументи.)
Синтаксис main
int main(int argc, char* argv[]). argc ≥ 1 завжди. argv[0] — ім'я програми. argv[argc] — nullptr.Доступ до аргументів
argc перед доступом. argv[i] — C-рядок. argv[i][j] — символ. Ітерувати від i=1 для аргументів користувача.Числова конвертація
std::stringstream ss(argv[i]); int n; ss >> n; — найпростіший спосіб. Перевіряйте успішність через if (!(ss >> n)).Shell-парсинг
argc залежить від shell, not від вашої програми.Завжди exit(1) при помилці
0 = успіх, не 0 = помилка. Це дозволяє скриптам і автоматизації перевіряти результат.argv у пам'яті
argv — char**, масив вказівників на C-рядки. Кожен argv[i] — вказівник на перший символ. Гарантований nullptr в кінці.Еліпсис
Вивчіть еліпсис (три крапки ...) у C++ — механізм функцій зі змінною кількістю аргументів. Синтаксис va_list/va_start/va_arg/va_end, три способи відстеження кількості аргументів, небезпеки та сучасні альтернативи.
Перерахування (enum)
Дізнайтеся, що таке перелічуваний тип даних у C++, як оголошувати та використовувати enum, які є внутрішні механізми роботи перерахувань та як вони роблять код читабельнішим і безпечнішим.