C++ для начинающих.

7.8. Функция main(): разбор параметров командной строки.

При запуске программы мы, как правило, передаем ей информацию в командной строке. Например, можно написать.

Prog -d -o of lie dataO.

Фактические параметры являются аргументами функции main() и могут быть получены из массива C-строк с именем argv; мы покажем, как их использовать.

Во всех предыдущих примерах определение main() содержало пустой список:

Int main() { ... }.

Развернутая сигнатура main() позволяет получить доступ к параметрам, которые были заданы пользователем в командной строке:

Int main( int argc, char *argv[] ){...}.

Argc содержит их количество, а argv – C-строки, представляющие собой отдельные значения (в командной строке они разделяются пробелами). Скажем, при запуске команды.

Prog -d -o ofile data0.

Argc получает значение 5, а argv включает следующие строки:

Argv[ 0 ] = "prog";

Argv[ 1 ] = "-d";

Argv[ 2 ] = "-o";

Argv[ 3 ] = "ofile";

Argv[ 4 ] = "dataO";

В argv[0] всегда входит имя команды (программы). Элементы с индексами от 1 до argc-1 служат параметрами.

Посмотрим, как можно извлечь и использовать значения, помещенные в argv. Пусть программа из нашего примера вызывается таким образом:

Prog [-d] [-h] [-v].

[-o output_file] [-l limit_value].

File_name.

[ file_name [file_name [ ... ]]].

Параметры в квадратных скобках являются необязательными. Вот, например, запуск программы с их минимальным количеством – одним лишь именем файла:

Prog chap1.doc.

Но можно запускать и так:

Prog -l 1024 -o chap1-2.out chapl.doc chap2.doc.

Prog d chap3.doc.

Prog -l 512 -d chap4.doc.

При разборе параметров командной строки выполняются следующие основные шаги:

По очереди извлечь каждый параметр из argv. Мы используем для этого цикл for с начальным индексом 1 (пропуская, таким образом, имя программы):

For ( int ix = 1; ix argc; ++ix ) {

Char *pchar = argv[ ix ];

// ...

}

*

Определить тип параметра. Если строка начинается с дефиса (-), это одна из опций { h, d, v, l, o}. В противном случае это может быть либо значение, ассоциированное с опцией (максимальный размер для -l, имя выходного файла для -o), либо имя входного файла. Чтобы определить, начинается ли строка с дефиса, используем инструкцию switch:

Switch ( pchar[ 0 ] ) {

Case '-': {

// -h, -d, -v, -l, -o.

}

Default: {

// обработаем максимальный размер для опции -1.

// имя выходного файла для -o.

// имена входных файлов ...

}

}

*

Реализуем обработку двух случаев пункта 2.

Если строка начинается с дефиса, мы используем switch по следующему символу для определения конкретной опции. Вот общая схема этой части программы:

Case '-': {

Switch( pchar[ 1 ] ).

{

Case 'd':

// обработка опции debug.

Break;

Case 'v':

// обработка опции version.

Break;

Case 'h':

// обработка опции help.

Break;

Case 'o':

// приготовимся обработать выходной файл.

Break;

Case 'l':

// приготовимся обработать макс.размер.

Break;

Default:

// неопознанная опция:

// сообщить об ошибке и завершить выполнение.

}

}

Опция -d задает необходимость отладки. Ее обработка заключается в присваивании переменной с объявлением.

Bool debug_on = false;

Значения true:

Case 'd':

Debug_on = true;

Break;

В нашу программу может входить код следующего вида:

If ( debug_on ).

Display_state_elements( obj );

Опция -v выводит номер версии программы и завершает исполнение:

Case 'v':

Cout program_name "::".

Program_version endl;

Return 0;

Опция -h запрашивает информацию о синтаксисе запуска и завершает исполнение. Вывод сообщения и выход из программы выполняется функцией usage():

Case 'h':

// break не нужен: usage() вызывает exit().

Usage();

Опция -o сигнализирует о том, что следующая строка содержит имя выходного файла. Аналогично опция -l говорит, что за ней указан максимальный размер. Как нам обработать эти ситуации?

Если в строке параметра нет дефиса, возможны три варианта: параметр содержит имя выходного файла, максимальный размер или имя входного файла. Чтобы различать эти случаи, присвоим true переменным, отражающим внутреннее состояние:

// если ofi1e_on==true,

// следующий параметр - имя выходного файла.

Bool ofi1e_on = false;

// если ofi1e_on==true,

// следующий параметр - максимальный размер.

Bool limit_on = false;

Вот обработка опций -l и -o в нашей инструкции switch:

Case 'l':

Limit_on = true;

Break;

Case 'o':

Ofile_on = true;

Break;

Встретив строку, не начинающуюся с дефиса, мы с помощью переменных состояния можем узнать ее содержание:

// обработаем максимальный размер для опции -1.

// имя выходного файла для -o.

// имена входных файлов ...

Default: {

// ofile_on включена, если -o встречалась.

If ( ofile_on ) {

// обработаем имя выходного файла.

// выключим ofile_on.

}

Else if ( limit_on ) { // если -l встречалась.

// обработаем максимальный размер.

// выключим limit_on.

} else {

// обработаем имя входного файла.

}

}

Если аргумент является именем выходного файла, сохраним это имя и выключим ofile_on:

If ( ofile_on ) {

Ofile_on = false;

Ofile = pchar;

}

Если аргумент задает максимальный размер, мы должны преобразовать строку встроенного типа в представляемое ею число. Сделаем это с помощью стандартной функции atoi(), которая принимает строку в качестве аргумента и возвращает int (также существует функция atof(), возвращающая double). Для использования atoi() включим заголовочный файл ctype.h. Нужно проверить, что значение максимального размера неотрицательно и выключить limit_on:

// int limit;

Else.

If ( limit_on ) {

Limit_on = false;

Limit = atoi( pchar );

If ( limit 0 ) {

Cerr program_name "::".

Program_version " : error: ".

"negative value for limit.\n\n";

Usage( -2 );

}

}

Если обе переменных состояния равны false, у нас есть имя входного файла. Сохраним его в векторе строк:

Else.

File_names.push_back( string( pchar ));

При обработке параметров командной строки важен способ реакции на неверные опции. Мы решили, что задание отрицательной величины в качестве максимального размера будет фатальной ошибкой. Это приемлемо или нет в зависимости от ситуации. Также можно распознать эту ситуацию как ошибочную, выдать предупреждение и использовать ноль или какое-либо другое значение по умолчанию.

Слабость нашей реализации становится понятной, если пользователь небрежно относится к пробелам, разделяющим параметры. Скажем, ни одна из следующих двух строк не будет обработана:

Prog - d dataOl.

Prog -oout_file dataOl.

(Оба случая мы оставим для упражнений в конце раздела.).

Вот полный текст нашей программы. (Мы добавили инструкции печати для трассировки выполнения.).

#include.

#include.

#include.

#include.

Const char *const program_name = "comline";

Const char *const program_version = "version 0.01 (08/07/97)";

Inline void usage( int exit_value = 0 ).

{

// печатает отформатированное сообщение о порядке вызова.

// и завершает программу с кодом exit_value ...

Cerr file_names;

Cout.

Вот трассировка обработки параметров командной строки:

Демонстрация обработки параметров в командной строке:

Argc: 8.

Argv[ 1 ]: -d.

Встретился '-'.

Встретилась -d: отладочная печать включена.

Argv[ 2 ]: -l.

Встретился '-'.

Встретилась -l: ограничение ресурса.

Argv[ 3 ]: 1024.

Default: параметр без дефиса: 1024.

Argv[ 4 ]: -o.

Встретился '-'.

Встретилась -o: выходной файл.

Argv[ 5 ]: test_7_8.

Default: параметр без дефиса: test_7_8.

Argv[ 6 ]: chapter7.doc.

Default: параметр без дефиса: chapter7.doc.

Argv[ 7 ]: chapter8.doc.

Default: параметр без дефиса: chapter8.doc.

Заданное пользователем значение limit: 1024.

Заданный пользователем выходной файл: test_7_8.

Файлы, подлежащий(е) обработке:

Chapter7.doc.

Chapter8.doc.

7.8.1. Класс для обработки параметров командной строки.

Чтобы не перегружать функцию main() деталями, касающимися обработки параметров командной строки, лучше отделить этот фрагмент. Можно написать для этого функцию. Например:

Extern int parse_options( int arg_count, char *arg_vector );

Int main( int argc, char *argv[] ) {

// ...

Int option_status;

Option_status = parse_options( argc, argv );

// ...

}

Как вернуть несколько значений? Обычно для этого используются глобальные объекты, которые не передаются ни в функцию для их обработки, ни обратно. Альтернативной стратегией является инкапсуляция обработки параметров командной строки в класс.

Данные-члены класса представляют собой параметры, заданные пользователем в командной строке. Набор открытых встроенных функций-членов позволяет получать их значения. Конструктор инициализирует параметры значениями по умолчанию. Функция-член получает argc и argv в качестве аргументов и обрабатывает их:

#include.

#include.

Class CommandOpt {

Public:

CommandOpt() : _limit( -1 ), _debug_on( false ) {}.

Int parse_options( int argc, char *argv[] );

String out_file() { return _out_file; }.

Bool debug_on() { return _debug_on; }.

Int files() { return _file_names.size(); }.

String& operator[]( int ix );

Private:

Inline void usage( int exit_value = 0 );

Bool _debug_on;

Int _limit;

String _out_file;

Vector _file_names;

Static const char *const program_name;

Static const char *const program_version;

};

Так выглядит модифицированная функция main():

#include "CommandOpt.h".

Int main( int argc, char "argv[] ) {

// ...

CommandOpt com_opt;

Int option_status;

Opttion_status = com_opt. parse_options (argc, argv);

// ...

}

Упражнение 7.15.

Добавьте обработку опций -t (включение таймера) и -b (задание размера буфера bufsize). Не забудьте обновить usage(). Например:

Prog -t -b 512 dataO.

Упражнение 7.16.

Наша реализация не обрабатывает случая, когда между опцией и ассоциированным с ней значением нет пробела. Модифицируйте программу для поддержки такой обработки.

Упражнение 7.17.

Наша реализация не может различить лишний пробел между дефисом и опцией:

Prog - d dataO.

Модифицируйте программу так, чтобы она распознавала подобную ошибку и сообщала о ней.

Упражнение 7.18.

В нашей программе не предусмотрен случай, когда опции -l или -o задаются несколько раз. Реализуйте такую возможность. Какова должна быть стратегия при разрешении конфликта?

Упражнение 7.19.

В нашей реализации задание неизвестной опции приводит к фатальной ошибке. Как вы думаете, это оправдано? Предложите другое поведение.

Упражнение 7.20.

Добавьте поддержку опций, начинающихся со знака плюс (+), обеспечив обработку +s и +pt, а также +sp и +ps. Предположим, что +s включает строгую проверку синтаксиса, а +p допускает использование устаревших конструкций. Например:

Prog +s +p -d -b 1024 dataO.