Главная страница
Навигация по странице:

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

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

  • 7.9.4. Массивы указателей на функции

  • 7.9.5. Параметры и тип возврата

  • 7.9.6. Указатели на функции, объявленные как extern "C"

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница33 из 93
    1   ...   29   30   31   32   33   34   35   36   ...   93
    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() деталями, касающимися обработки параметров командной строки, лучше отделить этот фрагмент. Можно написать для этого функцию.
    Например:
    }
    Как вернуть несколько значений? Обычно для этого используются глобальные объекты, которые не передаются ни в функцию для их обработки, ни обратно. Альтернативной стратегией является инкапсуляция обработки параметров командной строки в класс.
    Данные-члены класса представляют собой параметры, заданные пользователем в командной строке. Набор открытых встроенных функций-членов позволяет получать их значения. Конструктор инициализирует параметры значениями по умолчанию. Функция- член получает argc и argv в качестве аргументов и обрабатывает их: 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 );
    // ...

    С++ для начинающих
    358
    };
    Так выглядит модифицированная функция main():
    18
    }
    Упражнение 7.15
    Добавьте обработку опций -t (включение таймера) и -b (задание размера буфера bufsize
    ). Не забудьте обновить usage(). Например: prog -t -b 512 dataO
    Упражнение 7.16
    Наша реализация не обрабатывает случая, когда между опцией и ассоциированным с ней значением нет пробела. Модифицируйте программу для поддержки такой обработки.
    Упражнение 7.17
    Наша реализация не может различить лишний пробел между дефисом и опцией:
    18 Полный текст реализации класса CommandOpt можно найти на Web-сайте издательства Addison-Wesley.
    #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;
    #include "CommandOpt.h" int main( int argc, char "argv[] ) {
    // ...
    CommandOpt com_opt; int option_status; opttion_status = com_opt. parse_options (argc, argv);
    // ...

    С++ для начинающих
    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.
    Тип указателя на функцию
    Как объявить указатель на функцию? Как выглядит формальный параметр, когда фактическим аргументом является такой указатель? Вот определение функции lexicoCompare()
    , которая сравнивает две строки лексикографически:
    }
    Если все символы строк s1 и s2 равны, lexicoCompare() вернет 0, в противном случае – отрицательное число, если s1 меньше чем s2, и положительное, если s1 больше s2.
    Имя функции не входит в ее сигнатуру – она определяется только типом возвращаемого значения и списком параметров. Указатель на lexicoCompare() должен адресовать функцию с той же сигнатурой. Попробуем написать так:
    // нет, не совсем так
    Эта инструкция почти правильна. Проблема в том, что компилятор интерпретирует ее как объявление функции с именем pf, которая возвращает указатель типа int*. Список параметров правилен, но тип возвращаемого значения не тот. Оператор разыменования
    (*) ассоциируется с данным типом (int в нашем случае), а не с pf. Чтобы исправить положение, нужно использовать скобки:
    // правильно pf объявлен как указатель на функцию с двумя параметрами, возвращающую значение типа int, т.е. такую, как lexicoCompare(). pf способен адресовать и приведенную ниже функцию, поскольку ее сигнатура совпадает с типом lexicoCompare(): int sizeCompare( const string &sl, const string &s2 );
    Функции calc() и gcd()другого типа, поэтому pf не может указывать на них: int gcd( int , int );
    Указатель, который адресует эти две функции, определяется так:
    #include int lexicoCompare( const string &sl, const string &s2 ) { return sl.compare(s2); int *pf( const string &, const string & ) ; int (*pf)( const string &, const string & ) ; int calc( int , int );

    С++ для начинающих
    361
    int (*pfi)( int, int );
    Многоточие является частью сигнатуры функции. Если у двух функций списки параметров отличаются только тем, что в конце одного из них стоит многоточие, то считается, что функции различны. Таковы же и типы указателей. int (*pfc)( const char* ); // может указывать на strlen()
    Типов функций столько, сколько комбинаций типов возвращаемых значений и списков параметров.
    7.9.2.
    Инициализация и присваивание
    Вспомним, что имя массива без указания индекса элемента интерпретируется как адрес первого элемента. Аналогично имя функции без следующих за ним скобок интерпретируется как указатель на функцию. Например, при вычислении выражения lexicoCompare; получается указатель типа int (*)( const string &, const string & );
    Применение оператора взятия адреса к имени функции также дает указатель того же типа, например lexicoCompare и &lexicoCompare. Указатель на функцию инициализируется следующим образом: int (*pfi2)( const string &, const string & ) = &lexicoCompare;
    Ему можно присвоить значение: pfi2 = pfi;
    Инициализация и присваивание корректны только тогда, когда список параметров и тип значения, которое возвращает функция, адресованная указателем в левой части операции присваивания, в точности соответствуют списку параметров и типу значения, возвращаемого функцией или указателем в правой части. В противном случае выдается сообщение об ошибке компиляции. Никаких неявных преобразований типов для указателей на функции не производится. Например: int printf( const char*, ... ); int strlen( const char* ); int (*pfce)( const char*, ... ); // может указывать на printf() int (*pfi)( const string &, const string & ) = lexicoCompare; pfi = lexicoCompare;

    С++ для начинающих
    362
    }
    Такой указатель можно инициализировать нулем или присвоить ему нулевое значение, в этом случае он не адресует функцию.
    7.9.3.
    Вызов
    Указатель на функцию применяется для вызова функции, которую он адресует. Включать оператор разыменования при этом необязательно. И прямой вызов функции по имени, и косвенный вызов по указателю записываются одинаково:
    }
    Вызов pf( ia, iaSize ); может быть записан также и с использованием явного синтаксиса указателя:
    (*pf)( ia, iaSize ); int calc( int, int ); int (*pfi2s)( const string &, const string & ) = 0; int (*pfi2i)( int, int ) = 0; int main() { pfi2i = calc; // правильно pri2s = calc; // ошибка: несовпадение типов pfi2s = pfi2i; // ошибка: несовпадение типов return 0;
    #include int min( int*, int ); int (*pf)( int*, int ) = min; const int iaSize = 5; int ia[ iaSize ] = { 7, 4, 9, 2, 5 }; int main() { cout << "
    Прямой вызов: min: "
    << min( ia, iaSize ) << endl; cout << "
    Косвенный вызов: min: "
    << pf( ia, iaSize ) << endl; return 0;
    } int min( int* ia, int sz ) { int minVal = ia[ 0 ]; for ( int ix = 1; ix < sz; ++ix ) if ( minVal > ia[ ix ] ) minVal = ia[ ix ]; return minVal;

    С++ для начинающих
    363
    Результат в обоих случаях одинаковый, но вторая форма говорит читателю, что вызов осуществляется через указатель на функцию.
    Конечно, если такой указатель имеет нулевое значение, то любая форма вызова приведет к ошибке во время выполнения. Использовать можно только те указатели, которые адресуют какую-либо функцию или были проинициализированы таким значением.
    7.9.4.
    Массивы указателей на функции
    Можно объявить массив указателей на функции. Например: int (*testCases[10])(); testCases
    – это массив из десяти элементов, каждый из которых является указателем на функцию, возвращающую значение типа int и не имеющую параметров.
    Подобные объявления трудно читать, поскольку не сразу видно, с какой частью ассоциируется тип функции.
    В этом случае помогает использование имен, определенных с помощью директивы typedef
    :
    PFV testCases[10];
    Данное объявление эквивалентно предыдущему.
    Вызов функций, адресуемых элементами массива testCases, выглядит следующим образом:
    }
    Массив указателей на функции может быть инициализирован списком, каждый элемент которого является функцией. Например:
    // typedef делает объявление более понятным typedef int (*PFV)(); // typedef для указателя на функцию const int size = 10;
    PFV testCases[size]; int testResults[size]; void runtests() { for ( int i = 0; i < size; ++i )
    // вызов через элемент массива testResults[ i ] = testCases[ i ]();

    С++ для начинающих
    364
    };
    Можно объявить и указатель на compareFuncs, его типом будет “указатель на массив указателей на функции”:
    PFI2S (*pfCompare)[2] = compareFuncs;
    Это объявление раскладывается на составные части следующим образом:
    (*pfCompare)
    Оператор разыменования говорит, что pfCompare является указателем. [2] сообщает о количестве элементов массива:
    (*pfCompare) [2]
    PFI2S
    – имя, определенное с помощью директивы typedef, называет тип элементов. Это
    “указатель на функцию, возвращающую int и имеющую два параметра типа const string &
    ”. Тип элемента массива тот же, что и выражения &lexicoCompare.
    Такой тип имеет и первый элемент массива compareFuncs, который может быть получен с помощью любого из выражений:
    (*pfCompare)[ 0 ];
    Чтобы вызвать функцию lexicoCompare через pfCompare, нужно написать одну из следующих инструкций:
    ((*pfCompare)[ 0 ])( string1, string2 ); // явная форма
    7.9.5.
    Параметры и тип возврата
    Вернемся к задаче, сформулированной в начале данного раздела. Как использовать указатели на функции для сортировки элементов? Мы можем передать в алгоритм сортировки указатель на функцию, которая выполняет сравнение: int lexicoCompare( const string &, const string & ); int sizeCompare( const string &, const string & ); typedef int ( *PFI2S )( const string &, const string & );
    PFI2S compareFuncs[2] =
    { lexicoCompare, sizeCompare compareFunc[ 0 ];
    // эквивалентные вызовы pfCompare [ 0 ]( string1, string2 ); // сокращенная форма

    С++ для начинающих
    365
    int (*)( const string &, const string & ) );
    И в этом случае директива typedef помогает сделать объявление sort() более понятным: int sort( string*, string*, PFI2S );
    Поскольку в большинстве случаев употребляется функция lexicoCompare, можно использовать значение параметра по умолчанию: int sort( string*, string*, PFI2S = lexicoCompare );
    Определение sort() выглядит следующим образом:
    23 } sort()
    реализует алгоритм быстрой сортировки Хоара (C.A.R.Hoare). Рассмотрим ее определение детально. Она сортирует элементы массива от s1 до s2. Это рекурсивная функция, которая вызывает сама себя для последовательно уменьшающихся подмассивов. Рекурсия окончится тогда, когда s1 и s2 укажут на один и тот же элемент или s1 будет располагаться после s2 (строка 5). int sort( string*, string*,
    //
    Использование директивы typedef делает
    // объявление sort() более понятным typedef int ( *PFI2S )( const string &, const string & );
    // значение по умолчанию для третьего параметра int lexicoCompare( const string &, const string & );
    1 void sort( string *sl, string *s2,
    2 PFI2S compare = lexicoCompare )
    3 {
    4 // условие окончания рекурсии
    5 if ( si < s2 ) {
    6 string elem = *s1;
    7 string *1ow = s1;
    8 string *high = s2 + 1;
    9 10 for (;;) {
    11 while ( compare ( *++1ow, elem ) < 0 && low < s2) ;
    12 while ( compare( elem, *--high ) < 0 && high > s1)
    14 if ( low < high )
    15 1ow->swap(*high);
    16 else break;
    17 } // end, for(;;)
    18 19 s1->swap(*high);
    20 sort( s1, high - 1 );
    21 sort( high +1, s2 );
    22 } // end, if ( si < s2 )

    С++ для начинающих
    366
    elem
    (строка 6) является разделяющим элементом. Все элементы, меньшие чем elem, перемещаются влево от него, а большие – вправо. Теперь массив разбит на две части. sort()
    рекурсивно вызывается для каждой из них (строки 20-21).
    Цикл for(;;) проводит разделение (строки 10-17). На каждой итерации цикла индекс low увеличивается до первого элемента, большего или равного elem (строка 11).
    Аналогично high уменьшается до последнего элемента, меньшего или равного elem
    (строка 12). Когда low становится равным или большим high, мы выходим из цикла, в противном случае нужно поменять местами значения элементов и начать новую итерацию (строки 14-16). Хотя элементы разделены, elem все еще остается первым в массиве. swap() в строке 19 ставит его на место до рекурсивного вызова sort() для двух частей массива.
    Сравнение производится вызовом функции, на которую указывает compare (строки 11-
    12). Чтобы поменять элементы массива местами, используется операция swap() с аргументами типа string, представленная в разделе 6.11.
    Вот как выглядит main(), в которой применяется наша функция сортировки:
    }
    Результат работы программы:
    "a"
    "drizzle"
    "falling"
    "left"
    "light"
    "museum"
    "the"
    "they"
    "was"
    "when"
    Параметр функции автоматически приводится к типу указателя на функцию:
    #include
    #include
    // это должно бы находиться в заголовочном файле int lexicoCompare( const string &, const string & ); int sizeCompare( const string &, const string & ); typedef int (*PFI)( const string &, const string & ); void sort( string *, string *, PFI=lexicoCompare ); string as[10] = { "a", "light", "drizzle", "was", "falling",
    "when", "they", "left", "the", "museum" }; int main() {
    // вызов sort() с значением по умолчанию параметра compare sort( as, as + sizeof(as)/sizeof(as[0]) - 1 );
    // выводим результат сортировки for ( int i = 0; i < sizeof(as)/sizeof(as[0]); ++i ) cout << as[ i ].c_str() << "\n\t";

    С++ для начинающих
    367
    void sort( string *, string *, functype ); sort()
    рассматривается компилятором как объявленная в виде int (*)( const string &, const string & ) );
    Два этих объявления sort() эквивалентны.
    Заметим, что, помимо использования в качестве параметра, указатель на функцию может быть еще и типом возвращаемого значения. Например: int (*ff( int ))( int*, int ); ff()
    объявляется как функция, имеющая один параметр типа int и возвращающая указатель на функцию типа int (*)( int*, int );
    И здесь использование директивы typedef делает объявление понятнее. Объявив PF с помощью typedef, мы видим, что ff() возвращает указатель на функцию:
    PF ff( int );
    Типом возвращаемого значения функции не может быть тип функции. В этом случае выдается ошибка компиляции. Например, нельзя объявить ff() таким образом: func ff( int ); // ошибка: тип возврата ff() - функция
    7.9.6.
    Указатели на функции, объявленные как extern "C"
    Можно объявлять указатели на функции, написанные на других языках программирования. Это делается с помощью директивы связывания. Например, указатель pf ссылается на С-функцию: extern "C" void (*pf)(int);
    // typedef представляет собой тип функции typedef int functype( const string &, const string & ); void sort( string *, string *,
    //
    Использование директивы typedef делает
    // объявления более понятными typedef int (*PF)( int*, int );
    // typedef представляет собой тип функции typedef int func( int*, int );

    С++ для начинающих
    1   ...   29   30   31   32   33   34   35   36   ...   93


    написать администратору сайта