Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык Программирования C++. Вводный курс [rus]

.pdf
Скачиваний:
1144
Добавлен:
16.08.2013
Размер:
5.67 Mб
Скачать

С++ для начинающих

352

}

Реализуем обработку двух случаев пункта 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;

case 'd': debug_on = true;

значения true: break;

if ( debug_on )

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

С++ для начинающих

353

case 'v':

cout << program_name << "::"

<< program_version << endl;

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

Опция -h запрашивает информацию о синтаксисе запуска и завершает исполнение.

case 'h':

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

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

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

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

//если ofi1e_on==true,

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

//если ofi1e_on==true,

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

случаи, присвоим true переменным, отражающим внутреннее состояние: bool limit_on = false;

case 'l': limit_on = true; break;

case 'o': ofile_on = true;

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

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

С++ для начинающих

354

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

//

имя выходного файла для

-o

//

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

 

default: {

 

 

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

 

if ( ofile_on ) {

 

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

//выключим ofile_on

}

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

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

//выключим limit_on

}else {

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

}

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

if ( ofile_on ) { ofile_on = false; ofile = pchar;

ofile_on:

}

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

// 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 );

}

размера неотрицательно и выключить limit_on:

}

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

else

его в векторе строк:

file_names.push_back( string( pchar ));

С++ для начинающих

355

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

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

prog - d dataOl

будет обработана:

prog -oout_file dataOl

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

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

#include <string>

С++ для начинающих 356

#include <vector>

#include <ctype.h>

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 << "порядок вызова:\n"

<<program_name << " "

<<"[-d] [-h] [-v] \n\t"

<<"[-o output_file] [-l limit] \n\t"

<<"file_name\n\t[file_name [file_name [ ... ]]]\n\n"

<<"где [] указывает на необязательность опции:\n\n\t"

<<"-h: справка.\n\t\t"

<<"печать этого сообщения и выход\n\n\t"

<<"-v: версия.\n\t\t"

<<"печать информации о версии программы и выход\n\n\t"

<<"-d: отладка.\n\t\t включает отладочную печать\n\n\t"

<<"-l limit\n\t\t"

<<"limit должен быть неотрицательным целым числом\n\n\t"

<<"-o ofile\n\t\t"

<<"файл, в который выводится результат\n\t\t"

<<"по умолчанию результат записывается на стандартный вывод\n\n"

<<"file_name\n\t\t"

<<"имя подлежащего обработке файла\n\t\t"

<<"должно быть задано хотя бы одно имя --\n\t\t"

<<"но максимальное число не ограничено\n\n"

<<"примеры:\n\t\t"

<<"$command chapter7.doc\n\t\t"

<<"$command -d -l 1024 -o test_7_8 "

<<"chapter7.doc chapter8.doc\n\n";

exit( exit_value );

}

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

{

bool debug_on = false; bool ofile_on = false; bool limit_on = false; int limit = -1; string ofile;

vector<string> file_names;

cout << "демонстрация обработки параметров в командной строке:\n" << "argc: " << argc << endl;

for ( int ix = 1; ix < argc; ++ix )

{

cout << "argv[ " << ix << " ]: " << argv[ ix ] << endl;

char *pchar = argv[ ix ]; switch ( pchar[ 0 ] )

{

case '-':

{

cout << "встретился \'-\'\n"; switch( pchar[ 1 ] )

{

case 'd':

cout << "встретилась -d: "

<< "отладочная печать включена\n";

debug_on = true; break;

case 'v':

cout << "встретилась -v: "

С++ для начинающих

357

}

a.out -d -l 1024 -o test_7_8 chapter7.doc chapters.doc

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

демонстрация обработки параметров в командной строке: 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 в качестве аргументов и обрабатывает их:

С++ для начинающих

358

#include <vector> #include <string>

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<string> _file_names;

static const char *const program_name; static const char *const program_version;

};

#include "CommandOpt.h"

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

CommandOpt com_opt; int option_status;

opttion_status = com_opt. parse_options (argc, argv); // ...

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

}

Упражнение 7.15

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

prog -t -b 512 dataO

Упражнение 7.16

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

Упражнение 7.17

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

18 Полный текст реализации класса CommandOpt можно найти на Web-сайте издательства Addison-Wesley.

С++ для начинающих

359

prog - d dataO

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

Упражнение 7.18

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

Упражнение 7.19

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

Упражнение 7.20

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

prog +s +p -d -b 1024 dataO

7.9. Указатели на функции

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

sort( start, end, compare );

где start и end являются указателями на элементы массива строк. Функция sort() сортирует элементы между start и end, а аргумент compare задает операцию сравнения двух строк этого массива.

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

(Заметим, что в главе 12 описан алгоритм sort() и другие обобщенные алгоритмы из стандартной библиотеки С++. В этом разделе мы покажем свою собственную версию sort() как пример употребления указателей на функции. Наша функция будет упрощенным вариантом стандартного алгоритма.)

Один из способов удовлетворить наши потребности использовать в качестве третьего аргумента compare указатель на функцию, применяемую для сравнения.

Для того чтобы упростить использование функции sort(), не жертвуя гибкостью, можно задать операцию сравнению по умолчанию, подходящую для большинства случаев. Предположим, что чаще всего нам требуется лексикографическая сортировка, поэтому в качестве такой операции возьмем функцию compare() для строк (эта функция впервые встретилась в разделе 6.10).

С++ для начинающих

360

7.9.1. Тип указателя на функцию

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

#include <string>

int lexicoCompare( const string &sl, const string &s2 ) { return sl.compare(s2);

lexicoCompare(), которая сравнивает две строки лексикографически:

}

Если все символы строк s1 и s2 равны, lexicoCompare() вернет 0, в противном случае отрицательное число, если s1 меньше чем s2, и положительное, если s1 больше s2.

Имя функции не входит в ее сигнатуру она определяется только типом возвращаемого значения и списком параметров. Указатель на lexicoCompare() должен адресовать

int *pf( const string &, const string & ) ;

функцию с той же сигнатурой. Попробуем написать так:

// нет, не совсем так

Эта инструкция почти правильна. Проблема в том, что компилятор интерпретирует ее как объявление функции с именем pf, которая возвращает указатель типа int*. Список параметров правилен, но тип возвращаемого значения не тот. Оператор разыменования (*) ассоциируется с данным типом (int в нашем случае), а не с pf. Чтобы исправить

int (*pf)( const string &, const string & ) ;

положение, нужно использовать скобки:

// правильно

pf объявлен как указатель на функцию с двумя параметрами, возвращающую значение типа int, т.е. такую, как lexicoCompare().

pf способен адресовать и приведенную ниже функцию, поскольку ее сигнатура совпадает с типом lexicoCompare():

int sizeCompare( const string &sl, const string &s2 );

int calc( int , int );

Функции calc() и gcd()другого типа, поэтому pf не может указывать на них: int gcd( int , int );

Указатель, который адресует эти две функции, определяется так:

С++ для начинающих

361

int (*pfi)( int, int );

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

int printf( const char*, ... ); int strlen( const char* );

int (*pfce)( const char*, ... ); // может указывать на printf()

считается, что функции различны. Таковы же и типы указателей.

int (*pfc)( const char* );

// может указывать на strlen()

Типов функций столько, сколько комбинаций типов возвращаемых значений и списков параметров.

7.9.2. Инициализация и присваивание

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

lexicoCompare;

получается указатель типа

int (*)( const string &, const string & );

Применение оператора взятия адреса к имени функции также дает указатель того же типа, например lexicoCompare и &lexicoCompare. Указатель на функцию

int (*pfi)( const string &, const string & ) = lexicoCompare;

инициализируется следующим образом:

int (*pfi2)( const string &, const string & ) = &lexicoCompare;

pfi = lexicoCompare;

Ему можно присвоить значение: pfi2 = pfi;

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

Соседние файлы в предмете Программирование на C++