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

  • Вызов функций с указателями

  • Вызов функций с массивами

  • Аргументы функции main(): argc и argv

  • Передача числовых аргументов командной строки

  • Преобразование числовых строк в числа

  • Функции, которые не возвращают значений (void-функции)

  • Функции, которые возвращают указатели

  • Сравнение старого и нового стилей объявления параметров функций

  • Рекурсия

  • Шилдт c++_базовый_курс издание 3. Герберт Шилдт С базовый курс


    Скачать 9.37 Mb.
    НазваниеГерберт Шилдт С базовый курс
    АнкорШилдт c++_базовый_курс издание 3.pdf
    Дата13.02.2017
    Размер9.37 Mb.
    Формат файлаpdf
    Имя файлаШилдт c++_базовый_курс издание 3.pdf
    ТипКнига
    #2637
    страница8 из 33
    1   ...   4   5   6   7   8   9   10   11   ...   33
    Передача указателей и массивов в качестве аргументов
    До сих пор в приводимых здесь примерах функциям передавались значения простых переменных. Но возможны ситуации, когда в качестве аргументов необходимо использовать указатели и массивы. Рассмотрению особенностей передачи аргументов этого типа и посвящены следующие подразделы.
    Вызов функций с указателями
    В C++ разрешается передавать функции указатели. Для этого достаточно объявить параметр типа указатель. Рассмотрим пример.

    // Передача функции указателя.
    #include
    using namespace std;
    void f (int *j);
    int main()
    {
    int i;
    int *p;
    p = &i; // Указатель p теперь содержит адрес переменной i.
    f(p);
    cout << i; // Переменная i теперь содержит число 100.
    return 0;
    }
    void f (int *j)
    {
    *j
    =
    100;
    //
    Переменной, адресуемой указателем j,
    присваивается число 100.
    }
    Как видите, в этой программе функция f() принимает один параметр: указатель на целочисленное значение. В функции main() указателю р присваивается адрес переменной i.
    Затем из функции main() вызывается функция f(), а указатель р передается ей в качестве аргумента. После того как параметр-указатель j получит значение аргумента р, он (так же,
    как и р) будет указывать на переменную i, определенную в функции main(). Таким образом,
    при выполнении операции присваивания
    *j = 100;
    переменная i получает значение 100. Поэтому программа отобразит на экране число 100.

    В общем случае приведенная здесь функция f() присваивает число 100 переменной, адрес которой был передан этой функции в качестве аргумента.
    В предыдущем примере необязательно было использовать переменную р. Вместо нее при вызове функции f() достаточно использовать переменную i, предварив ее оператором
    "&" (при этом, как вы знаете, генерируется адрес переменной i). После внесения оговоренного изменения предыдущая программа приобретает такой вид.
    // Передача указателя функции -- исправленная версия.
    #include
    using namespace std;
    void f (int *j);
    int main()
    {
    int i;
    f(&i);
    cout << i;
    return 0;
    }
    void f (int * j)
    {
    *j
    =
    100;
    //
    Переменной, адресуемой указателем j,
    присваивается число 100.
    }
    Передавая указатель функции, необходимо понимать следующее. При выполнении некоторой операции в функции, которая использует указатель, эта операция фактически выполняется над переменной, адресуемой этим указателем. Таким образом, такая функция может изменить значение объекта, адресуемого ее параметром.
    Вызов функций с массивами
    Если массив является аргументом функции, то необходимо понимать, что при вызове
    такой функции ей передается только адрес первого элемента массива, а не полная его копия. (Помните, что в C++ имя массива без индекса представляет собой указатель на первый элемент этого массива.) Это означает, что объявление параметра должно иметь тип,
    совместимый с типом аргумента. Вообще существует три способа объявить параметр,
    который принимает указатель на массив. Во-первых, параметр можно объявить как массив,
    тип и размер которого совпадает с типом и размером массива, используемого при вызове функции. Этот вариант объявления параметра-массива продемонстрирован в следующем примере.
    #include
    using namespace std;
    void display(int num[10]);
    int main()
    {
    int t[10], i;
    for(i=0; i<10; ++i) t[i]=i;
    display(t); // Передаем функции массив t.
    return 0;
    }
    // Функция выводит все элементы массива.
    void display(int num[10])
    {
    int i;
    for(i=0; i<10; i++) cout << num[i] <<' ';
    }
    Несмотря на то что параметр num объявлен здесь как целочисленный массив, состоящий из 10 элементов, С++-компилятор автоматически преобразует его в указатель на
    целочисленное значение. Необходимость этого преобразования объясняется тем, что никакой параметр в действительности не может принять массив целиком. А так как будет передан один лишь указатель на массив, то функция должна иметь параметр, способный принять этот указатель.
    Второй способ объявления параметра-массива состоит в его представлении в виде безразмерного массива, как показано ниже.
    void display(int num[])
    {
    int i;
    for(i=0; i<10; i++) cout << num[i] << ' ';
    }
    Здесь параметр num объявляется как целочисленный массив неизвестного размера.
    Поскольку C++ не обеспечивает проверку нарушения границ массива, то реальный размер массива — нерелевантный фактор для подобного параметра (но, безусловно, не для программы в целом). Целочисленный массив при таком способе объявления также автоматически преобразуется С++-компилятором в указатель на целочисленное значение.
    Наконец, рассмотрим третий способ объявления параметра-массива. При передаче массива функции ее параметр можно объявить как указатель. Как раз этот вариант чаще всего используется профессиональными программистами. Вот пример:
    void display(int *num)
    {
    int i;
    for(i=0; i<10; i++) cout << num[i] << ' ';
    }
    Возможность такого объявления параметра (в данном случае num) объясняется тем, что любой указатель (подобно массиву) можно индексировать с помощью символов квадратных скобок ([]). Таким образом, все три способа объявления параметра-массива приводятся к одинаковому результату, который можно выразить одним словом: указатель.
    Однако отдельный элемент массива, используемый в качестве аргумента,
    обрабатывается подобно обычной переменной. Например, рассмотренную выше программу можно было бы переписать, не используя передачу целого массива:
    #include
    using namespace std;
    void display(int num);
    int main()
    {
    int t[10],i;
    for(i=0; i<10; ++i) t[i]=i;
    for(i=0; i<10; i++) display(t[i]);
    return 0;
    }
    // Функция выводит одно число.
    void display(int num)
    {
    cout << num << ' ';
    }
    Как видите, параметр, используемый функцией display(), имеет тип int. Здесь не важно,
    что эта функция вызывается с использованием элемента массива, поскольку ей передается только один его элемент.
    Помните, что, если массив используется в качестве аргумента функции, то функции передается адрес этого массива. Это означает, что код функции может потенциально изменить реальное содержимое массива, используемого при вызове функции. Например, в следующей программе функция cube() преобразует значение каждого элемента массива в куб этого значения. При вызове функции cube() в качестве первого аргумента необходимо передать адрес массива значений, подлежащих преобразованию, а в качестве второго — его размер.
    #include
    using namespace std;
    void cube(int *n, int num);
    int main()
    {
    int i, nums[10];
    for(i=0; i<10; i++) nums[i] = i+1;
    cout << "Исходное содержимое массива: ";
    for(i=0; i<10; i++) cout << nums[i] << ' ';
    cout << '\n';
    cube(nums, 10); // Вычисляем кубы значений.
    cout << "Измененное содержимое: ";
    for(i=0; i<10; i++) cout << nums[i] << ' ';
    return 0;
    }
    void cube(int *n, int num)
    {
    while(num) {
    *n = *n * *n * *n;
    num--;
    n++;
    }
    }
    Результаты выполнения этой программы таковы.

    Исходное содержимое массива: 12345678910
    Измененное содержимое: 1 8 27 64 125 216 343 512 729 1000
    Как видите, после обращения к функции cube() содержимое массива nums изменилось:
    каждый элемент стал равным кубу исходного значения. Другими словами, элементы массива nums были модифицированы инструкциями, составляющими тело функции cube(),
    поскольку ее параметр n указывает на массив nums.
    Передача функциям строк
    Как вы уже знаете, строки в C++ — это обычные символьные массивы, которые завершаются нулевым символом. Таким образом, при передаче функции строки реально передается только указатель (типа char*) на начало этой строки. Рассмотрим, например,
    следующую программу. В ней определяется функция stringupper(), которая преобразует строку символов в ее прописной эквивалент.
    // Передача функции строки.
    #include
    #include
    #include
    using namespace std;
    void stringupper(char *str);
    int main()
    {
    char str[80];
    strcpy(str, "Мне нравится C++");
    stringupper(str);
    cout << str; // Отображаем строку с использованием прописного написания символов.
    return 0;
    }
    void stringupper(char *str)
    {
    while(*str) {
    *str = toupper(*str); // Получаем прописной эквивалент одного символа.
    str++; // Переходим к следующему символу.
    }
    }
    Результаты выполнения этой программы таковы.
    МНЕ НРАВИТСЯ C++
    Обратите внимание на то, что параметр str функции stringupper() объявляется с использованием типа char*. Это позволяет получить указатель на символьный массив,
    который содержит строку. Рассмотрим еще один пример передачи строки функции. Как вы узнали в главе 5, стандартная библиотечная функция strlen() возвращает длину строки. В
    следующей программе показан один из возможных вариантов реализации этой функции.
    // Одна из версий функции strlen().
    #include
    using namespace std;
    int mystrlen(char *str);
    int main()
    {
    cout << "Длина строки ПРИВЕТ ВСЕМ равна: ";
    cout << mystrlen("ПРИВЕТ ВСЕМ");
    return 0;
    }
    // Нестандартная реализация функции strlen().
    int mystrlen(char *str)
    {
    int i;
    for(i=0; str[i]; i++); // Находим конец строки.
    return i;
    }
    Вот как выглядят результаты выполнения этой программы.
    Длина строки ПРИВЕТ ВСЕМ равна: 11
    В качестве упражнения вам стоило бы попытаться самостоятельно реализовать другие строковые функции, например strcpy() или strcat(). Этот тест позволит узнать, насколько хорошо вы освоили такие элементы языка C++, как массивы, строки и указатели.
    Аргументы функции main(): argc и argv
    Аргумент командной строки представляет собой информацию, задаваемую в командной
    строке после имени программы.
    Иногда возникает необходимость передать информацию программе при ее запуске. Как правило, это реализуется путем передачи аргументов командной строки функции main().
    Аргумент командной строки представляет собой информацию, указываемую в команде
    (командной строке), предназначенной для выполнения операционной системой, после имени программы. (В Windows команда "Run" (Выполнить) также использует командную строку.) Например, С++-программы можно компилировать путем выполнения следующей команды,
    cl prog_name
    Здесь элемент prog_name — имя программы, которую мы хотим скомпилировать. Имя программы передается С++-компилятору в качестве аргумента командной строки.
    В C++ для функции main() определено два встроенных, но необязательных параметра,
    argc и argv, которые получают свои значения от аргументов командной строки. В
    конкретной операционной среде могут поддерживаться и другие аргументы (такую информацию необходимо уточнить по документации, прилагаемой к вашему компилятору).
    Рассмотрим параметры argc и argv более подробно.
    На заметку. Формально для имен параметров командной строки можно выбирать
    любые идентификаторы, однако имена argc и argv используются по соглашению уже в
    течение нескольких лет, и поэтому имеет смысл не прибегать к другим идентификаторам,
    чтобы любой программист, которому придется разбираться в вашей программе, смог
    быстро идентифицировать их как параметры командной строки.
    Параметр argc имеет целочисленный тип и предназначен для хранения количества аргументов командной строки. Его значение всегда не меньше единицы, поскольку имя программы также является одним из учитываемых аргументов. Параметр argv представляет собой указатель на массив символьных указателей. Каждый указатель в массиве argv
    ссылается на строку, содержащую аргумент командной строки. Элемент argv[0] указывает на имя программы; элемент argv[1] — на первый аргумент, элемент argv[2] — на второй и т.д. Все аргументы командной строки передаются программе как строки, поэтому числовые аргументы необходимо преобразовать в программе в соответствующий внутренний формат.
    Важно правильно объявить параметр argv. Обычно это делается так.
    char *argv[];
    Доступ к отдельным аргументам командной строки можно получить путем индексации массива argv. Как это сделать, показано в следующей программе. При ее выполнении на экран выводится приветствие ("Привет" ), а за ним — ваше имя, которое должно быть первым аргументом командной строки.
    #include
    using namespace std;
    int main(int argc, char *argv[])
    {
    if(argc!=2) {
    cout << "Вы забыли ввести свое имя.\n";
    return 1;
    }
    cout << "Привет, " << argv[1] << '\n';
    return 0;
    }
    Предположим, что вас зовут Том и что вы назвали эту программу именем name. Тогда,
    если запустить эту программу, введя команду name Том, результат ее работы должен выглядеть так: Привет, Том. Например, вы работаете с диском А, и в ответ на приглашение на ввод команды должны ввести упомянутую выше команду и получить следующий результат.
    A>name Том
    Привет, Том
    А>
    В C++ точно не оговорено, как должны быть представлены аргументы командной строки, поскольку среды выполнения (операционные системы) имеют здесь большие различия. Однако чаще всего используется следующее соглашение: каждый аргумент
    командной строки должен быть отделен пробелом или символом табуляции. Как правило,
    запятые, точки с запятой и тому подобные знаки не являются допустимыми разделителями аргументов. Например, строка один, два и три состоит из четырех строковых аргументов, в то время как строка один, два, три включает только два, поскольку запятая не является допустимым разделителем.
    Если необходимо передать в качестве одного аргумента командной строки набор символов, который содержит пробелы, то его нужно заключить в кавычки. Например, этот набор символов будет воспринят как один аргумент командной строки:
    "это лишь один аргумент"
    Следует иметь в виду, что представленные здесь примеры применимы к широкому диапазону сред, но это не означает, что ваша среда входит в их число.
    Чтобы получить доступ к отдельному символу в одном из аргументов командной строки,
    при обращении к массиву argv добавьте второй индекс. Например, при выполнении приведенной ниже программы посимвольно отображаются все аргументы, с которыми она была вызвана.
    /* Эта программа посимвольно выводит все аргументы командной строки, с которыми она была вызвана.
    */
    #include
    using namespace std;
    int main(int argc, char *argv[])
    {
    int t, i;
    for(t=0; t i = 0;
    while(argv[t][i]) {
    cout << argv[t][i];
    ++i;

    }
    cout << ' ';
    }
    return 0;
    }
    Нетрудно догадаться, что первый индекс массива argv позволяет получить доступ к соответствующему аргументу командной строки, а второй — к конкретному символу этого строкового аргумента.
    Обычно аргументы argc и argv используются для ввода в программу начальных параметров, исходных значений, имен файлов или вариантов (режимов) работы программы.
    В C++ можно ввести столько аргументов командной строки, сколько допускает операционная система. Использование аргументов командной строки придает программе профессиональный вид и позволяет использовать ее в командном файле (исполняемом текстовом файле, содержащем одну или несколько команд).
    Передача числовых аргументов командной строки
    Как упоминалось выше, при передаче программе числовых данных в качестве аргументов командной строки эти данные принимаются в строковой форме. В программе должно быть предусмотрено их преобразование в соответствующий внутренний формат с помощью одной из стандартных библиотечных функций, поддерживаемых C++. Например, при выполнении следующей программы выводится сумма двух чисел, которые указываются в командной строке после имени программы. Для преобразования аргументов командной строки во внутреннее представление здесь используется стандартная библиотечная функция atof().
    Она преобразует число из строкового формата в значение типа double.
    /* Эта программа отображает сумму двух числовых аргументов командной строки.
    */
    #include
    #include
    using namespace std;
    int main(int argc, char *argv[])
    {
    double a, b;
    if(argc!=3) {
    cout << "Использование: add число число\n";
    return 1;
    }
    a = atof(argv[1]);
    b = atof(argv[2]);
    cout << a + b;
    return 0;
    }
    Чтобы сложить два числа, используйте командную строку такого вида (предполагая, что эта программа имеет имя add).
    C>add 100.2 231
    Преобразование числовых строк в числа
    Стандартная библиотека C++ включает несколько функций, которые позволяют преобразовать строковое представление числа в его внутренний формат. Для этого используются такие функции, как atoi(), atol() и atof(). Они преобразуют строку в целочисленное значение (типа int), длинное целое (типа long) и значение с плавающей точкой (типа double) соответственно. Использование этих функций (для их вызова необходимо включить в программу заголовочный файл ) демонстрируется в следующей программе.
    // Демонстрация использования функций atoi(), atol() и atof().
    #include
    #include
    using namespace std;
    int main()
    {
    int i;
    long j;
    double k;
    i = atoi ("100");
    j = atol("100000");
    k = atof("-0.123");
    cout << i << ' ' << j << ' ' << k;
    cout << ' \n';
    return 0;
    }
    Результаты выполнения этой программы таковы.
    100 100000 -0.123
    Функции преобразования строк полезны не только при передаче числовых данных программе через аргументы командной строки, но и в ряде других ситуаций.
    Инструкция return
    До сих пор (начиная с главы 2) мы использовали инструкцию return без подробных разъяснений. Напомним, что инструкция return выполняет две важные операции. Во- первых, она обеспечивает немедленное возвращение управления к инициатору вызова функции. Во-вторых, ее можно использовать для передачи значения, возвращаемого функцией. Именно этим двум операциям и посвящен данный раздел.
    Завершение функции
    Как вы уже знаете, управление от функции передается инициатору ее вызова в двух ситуациях: либо при обнаружении закрывающейся фигурной скобки, либо при выполнении инструкции return. Инструкцию return можно использовать с некоторым заданным значением либо без него. Но если в объявлении функции указан тип возвращаемого значения (т.е. не тип void), то функция должна возвращать значение этого типа. Только
    void-функции могут использовать инструкцию return без какого бы то ни было значения.
    Для void-функций инструкция return главным образом используется как элемент программного управления. Например, в приведенной ниже функции выводится результат возведения числа в положительную целочисленную степень. Если же показатель степени окажется отрицательным, инструкция return обеспечит выход из функции, прежде чем будет сделана попытка вычислить такое выражение. В этом случае инструкция return действует как управляющий элемент, предотвращающий нежелательное выполнение определенной части функции.
    void power(int base, int exp)

    {
    int i;
    if(exp<0) return; /* Чтобы не допустить возведения числа в отрицательную степень, здесь выполняется возврат в вызывающую функцию и игнорируется остальная часть функции. */
    i = 1;
    for( ; exp; exp--) i = base * i;
    cout << "Результат равен: " << i;
    }
    Функция может содержать несколько инструкций return. Функция будет завершена при выполнении хотя бы одного из них. Например, следующий фрагмент кода совершенно правомерен.
    void f()
    {
    // ...
    switch(с) {
    case 'a': return;
    case 'b': // ...
    case 'c': return;
    }
    if(count<100) return;
    // ...
    }
    Однако следует иметь в виду, что слишком большое количество инструкций return
    может ухудшить ясность алгоритма и ввести в заблуждение тех, кто будет в нем разбираться.
    Несколько инструкций return стоит использовать только в том случае, если они способствуют ясности функции.
    Возврат значений
    Каждая функция, кроме типа void, возвращает какое-нибудь значение. Это значение явно
    задается с помощью инструкции return. Другими словами, любую не void-функцию можно использовать в качестве операнда в выражении. Следовательно, каждое из следующих выражений допустимо в C++.
    х = power(у);
    if(max(х, у)) > 100) cout << "больше";
    switch(abs (х)) {
    Несмотря на то что все не void-функции возвращают значения, они необязательно должны быть использованы в программе. Самый распространенный вопрос относительно значений, возвращаемых функциями, звучит так: "Поскольку функция возвращает некоторое значение, то разве я не должен (должна) присвоить это значение какой-нибудь переменной?". Ответ: нет, это необязательно. Если значение, возвращаемое функцией, не участвует в присваивании, оно попросту отбрасывается (теряется).
    Рассмотрим следующую программу, в которой используется стандартная библиотечная функция abs().
    #include
    #include
    using namespace std;
    int main()
    {
    int i;
    i = abs(-10); // строка 1
    cout << abs(-23); // строка 2
    abs(100); // строка 3
    return 0;
    }
    Функция abs() возвращает абсолютное значение своего целочисленного аргумента. Она использует заголовок . В строке 1 значение, возвращаемое функцией abs(),
    присваивается переменной i. В строке 2 значение, возвращаемое функцией abs(), ничему не присваивается, но используется инструкцией cout. Наконец, в строке 3 значение,
    возвращаемое функцией abs(), теряется, поскольку не присваивается никакой другой
    переменной и не используется как часть выражения.
    Если функция, тип которой отличен от типа void, завершается в результате обнаружения закрывающейся фигурной скобки, то значение, которое она возвращает, не определено (т.е.
    неизвестно). Из-за особенностей формального синтаксиса C++ не void-функция не обязана выполнять инструкцию return. Это может произойти в том случае, если конец функции будет достигнут до обнаружения инструкции return. Но, поскольку функция объявлена как возвращающая значение, значение будет таки возвращено, даже если это просто "мусор". В
    общем случае любая создаваемая вами не void-функция должна возвращать значение посредством явно выполняемой инструкции return.
    Выше упоминалось, что void-функция может иметь несколько инструкций return. То же самое относится и к функциям, которые возвращают значения. Например, представленная в следующей программе функция find_substr() использует две инструкции return, которые позволяют упростить алгоритм ее работы. Эта функция выполняет поиск заданной подстроки в заданной строке. Она возвращает индекс первого обнаруженного вхождения заданной подстроки или значение -1, если заданная подстрока не была найдена. Например,
    если в строке "Я люблю C++ " необходимо отыскать подстроку "люблю", то функция
    find_substr() возвратит число 2 (которое представляет собой индекс символа "л" в строке
    люблю C++ ").
    #include
    using namespace std;
    int find_substr(char *sub, char *str);
    int main()
    {
    int index;
    index = find_substr("три", "один два три четыре");
    cout << "Индекс равен " << index; // Индекс равен 9.
    return 0;
    }
    // Функция возвращает индекс искомой подстроки или -1, если она не была найдена.
    int find_substr(char *sub, char *str)
    {
    int t;
    char *p, *p2;
    for(t=0; str[t]; t++) {
    p = &str[t]; // установка указателей p2 = sub;
    while(*p2 && *p2==*p) { // проверка совпадения p++; p2++;
    }
    /* Если достигнут конец р2-строки (т.е. подстроки), то подстрока была найдена. */
    if(!*p2) return t; // Возвращаем индекс подстроки.
    }
    return -1; // Подстрока не была обнаружена.
    }
    Результаты выполнения этой программы таковы.
    Индекс равен 9
    Поскольку искомая подстрока существует в заданной строке, выполняется первая инструкция return. В качестве упражнения измените программу так, чтобы ею выполнялся поиск подстроки, которая не является частью заданной строки. В этом случае функция
    find_substr() должна возвратить значение -1 (благодаря второй инструкции return).
    Функцию можно объявить так, чтобы она возвращала значение любого типа данных,
    действительного для C++ (за исключением массива: функция не может возвратить массив).
    Способ объявления типа значения, возвращаемого функцией, аналогичен тому, который используется для объявления переменных: имени функции должен предшествовать спецификатор типа. Спецификатор типа сообщает компилятору, значение какого типа данных должна возвратить функция. Указываемый в объявлении функции тип должен быть совместимым с типом данных, используемым в инструкции return. В противном случае компилятор отреагирует сообщением об ошибке.
    Функции, которые не возвращают значений (void-функции)

    Как вы заметили, функции, которые не возвращают значений, объявляются с указанием типа void. Это ключевое слово не допускает их использования в выражениях и защищает от неверного применения. В следующем примере функция print_vertical() выводит аргумент командной строки в вертикальном направлении (вниз) по левому краю экрана. Поскольку эта функция не возвращает никакого значения, в ее объявлении использовано ключевое слово void.
    #include
    using namespace std;
    void print_vertical(char *str);
    int main(int argc, char *argv[])
    {
    if(argc==2) print_vertical(argv[1]);
    return 0;
    }
    void print_vertical(char *str)
    {
    while(*str)
    cout << *str++ << '\n';
    }
    Поскольку print_vertical() объявлена как void-функция, ее нельзя использовать в выражении. Например, следующая инструкция неверна и поэтому не скомпилируется.
    х = print_vertical("Привет!"); // ошибка
    Важно! В первых версиях языка С не был предусмотрен тип void. Таким образом, в
    старых С-программах функции, не возвращающие значений, по умолчанию имели тип int.
    Если вам придется встретиться с такими функциями при переводе старых С-программ "на
    рельсы" C++, просто объявите их с использованием ключевого слова void, сделав их void-
    функциями.
    Функции, которые возвращают указатели
    Функции могут возвращать указатели. Указатели возвращаются подобно значениям
    любых других типов данных и не создают при этом особых проблем. Но, поскольку указатель представляет собой одно из самых сложных (или небезопасных) средств языка
    C++, имеет смысл посвятить ему отдельный раздел.
    Чтобы вернуть указатель, функция должна объявить его тип в качестве типа возвращаемого значения. Вот как, например, объявляется тип возвращаемого значения для функции f(), которая должна возвращать указатель на целое число.
    int *f();
    Если функция возвращает указатель, то значение, используемое в ее инструкции return,
    также должно быть указателем. (Как и для всех функций, return-значение должно быть совместимым с типом возвращаемого значения.)
    В следующей программе демонстрируется использование указателя в качестве типа возвращаемого значения. Это — новая версия приведенной выше функции find_substr(),
    только теперь она возвращает не индекс найденной подстроки, а указатель на нее. Если заданная подстрока не найдена, возвращается нулевой указатель.
    // Новая версия функции find_substr().
    // которая возвращает указатель на подстроку.
    #include
    using namespace std;
    char *find_substr(char *sub, char *str);
    int main()
    {
    char *substr;
    substr = find_substr("три", "один два три четыре");
    cout << "Найденная подстрока: " << substr;
    return 0;
    }
    // Функция возвращает указатель на искомую подстроку или нуль,
    если таковая не будет найдена.
    char *find_substr(char *sub, char *str)
    {
    int t;
    char *p, *p2, *start;
    for(t=0; str[t]; t++) {
    p = &str[t]; // установка указателей start = p;
    р2 = sub;
    while(*р2 && *p2==*p) { // проверка совпадения р++; р2++;
    }
    /* Если достигнут конец р2-подстроки, то эта подстрока была найдена. */
    if(!*р2) return start; // Возвращаем указатель на начало найденной подстроки.
    }
    return 0; // подстрока не найдена
    }
    При выполнении этой версии программы получен следующий результат.
    Найденная подстрока: три четыре
    В данном случае, когда подстрока "три" была найдена в строке "один два три четыре",
    функция find_substr() возвратила указатель на начало искомой подстроки "три", который в функции main() был присвоен переменной substr. Таким образом, при выводе значения
    substr на экране отобразился остаток строки, т.е. "три четыре".
    Многие поддерживаемые C++ библиотечные функции, предназначенные для обработки строк, возвращают указатели на символы. Например, функция strcpy() возвращает указатель на первый аргумент.
    Прототипы функций
    Прототип объявляет функцию до ее первого использования.
    До сих пор в приводимых здесь примерах программ прототипы функций использовались без каких-либо разъяснений. Теперь настало время поговорить о них подробно. В C++ все функции должны быть объявлены до их использования. Обычно это реализуется с помощью прототипа функции. Прототипы содержат три вида информации о функции:
    ■ тип возвращаемого ею значения;
    ■ тип ее параметров;
    ■ количество параметров.
    Прототипы позволяют компилятору выполнить следующие три важные операции.
    ■ Они сообщают компилятору, код какого типа необходимо генерировать при вызове функции. Различия в типах параметров и значении, возвращаемом функцией, обеспечивают различную обработку компилятором.
    ■ Они позволяют C++ обнаружить недопустимые преобразования типов аргументов,
    используемых при вызове функции, в тип, указанный в объявлении ее параметров, и сообщить о них.
    ■ Они позволяют компилятору выявить различия между количеством аргументов,
    используемых при вызове функции, и количеством параметров, заданных в определении функции.
    Общая форма прототипа функции аналогична ее определению за исключением того, что в прототипе не представлено тело функции.
    type func_name(type parm_name1, type parm_name2,
    ...,
    type parm_nameN);

    Использование имен параметров в прототипе необязательно, но позволяет компилятору идентифицировать любое несовпадение типов при возникновении ошибки, поэтому лучше имена параметров все же включать в прототип функции.
    Чтобы лучше понять полезность прототипов функций, рассмотрим следующую программу. Если вы попытаетесь ее скомпилировать, то получите от компилятора сообщение об ошибке, поскольку в этой программе делается попытка вызвать функцию
    sqr_it() с целочисленным аргументом, а не с указателем на целочисленное значение
    (согласно прототипу функции). Ошибка состоит в недопустимости преобразования целочисленного значения в указатель.
    /* В этой программе используется прототип функции, который позволяет осуществить строгий контроль типов.
    */
    void sqr_it(int *i); // прототип функции int main()
    {
    int х;
    х = 10;
    sqr_it(x); // *** Ошибка *** — несоответствие типов!
    return 0;
    }
    void sqr_it(int *i)
    {
    *i=*i * *i;
    }
    Важно! Несмотря на то что язык С допускает прототипы, их использование не
    является обязательным. Дело в том, что в первых версиях С они не применялись. Поэтому
    при переводе старого С-кода в С++-код перед компиляцией программы необходимо
    обеспечить наличие прототипов абсолютно для всех функций.
    Подробнее о заголовках
    В начале этой книги вы узнали о существовании стандартных заголовков C++, которые
    содержат информацию, необходимую для ваших программ. Несмотря на то что все вышесказанное — истинная правда, это еще не вся правда. Заголовки C++ содержат прототипы стандартных библиотечных функций, а также различные значения и определения, используемые этими функциями. Подобно функциям, создаваемым программистами, стандартные библиотечные функции также должны "заявить о себе" в форме прототипов до их использования. Поэтому любая программа, в которой используется библиотечная функция, должна включать заголовок, содержащий прототип этой функции.
    Чтобы узнать, какой заголовок необходим для той или иной библиотечной функции,
    следует обратиться к справочному руководству, прилагаемому к вашему компилятору.
    Помимо описания каждой функции, там должно быть указано имя заголовка, который необходимо включить в программу для использования выбранной функции.
    Сравнение старого и нового стилей объявления параметров функций
    Если вам приходилось когда-либо разбираться в старом С-коде, то вы, возможно,
    обратили внимание на необычное (с точки зрения современного программиста) объявление параметров функции. Этот старый стиль объявления параметров, который иногда называют
    классическим форматом, устарел, но его до сих пор можно встретить в программах "эпохи раннего С". В C++ (и обновленном С-коде) используется новая форма объявлений параметров функций. Но если вам придется работать со старыми С-программами и,
    особенно, если понадобится переводить их в С++-код, то вам будет полезно понимать и форму объявления параметров, "выдержанную" в старом стиле.
    Объявление параметра функции согласно старому стилю состоит из двух частей: списка параметров, заключенного в круглые скобки, который приводится после имени функции, и собственно объявления параметров, которое должно находиться между закрывающей круглой скобкой и открывающей фигурной скобкой функции. Например, это "новое"
    объявление (т.е. по новому стилю)
    float f(int a, int b, char ch)
    { ...
    будет выглядеть с использованием старого стиля несколько по-другому.
    float f(a, b, ch)
    int а, b;
    char ch;
    { ...
    Обратите внимание на то, что в классической форме после указания имени типа в списке может находиться несколько параметров. В новой форме объявления это не допускается.
    В общем случае, чтобы преобразовать объявление параметров из старого стиля в новый
    (С++-стиль), достаточно внести объявление типов параметров в круглые скобки, следующие за именем функции. При этом помните, что каждый параметр должен быть объявлен отдельно, с собственным спецификатором типа.

    Рекурсия
    Рекурсивная функцияэто функция, которая вызывает сама себя.
    Рекурсия — это последняя тема, которую мы рассмотрим в этой главе. Рекурсия,
    которую иногда называют циклическим определением, представляет собой процесс определения чего-либо на собственной основе. В области программирования под рекурсией понимается процесс вызова функцией самой себя. Функцию, которая вызывает саму себя,
    называют рекурсивной.
    Классическим примером рекурсии является вычисление факториала числа с помощью функции factr(). Факториал числа N представляет собой произведение всех целых чисел от 1
    до N. Например, факториал числа 3 равен 1x2x3, или 6. Рекурсивный способ вычисления факториала числа демонстрируется в следующей программе. Для сравнения сюда же включен и его нерекурсивный (итеративный) эквивалент.
    #include
    using namespace std;
    int factr(int n);
    int fact(int n);
    int main()
    {
    // Использование рекурсивной версии.
    cout << "Факториал числа 4 равен " << factr(4);
    cout << '\n';
    // Использование итеративной версии.
    cout << "Факториал числа 4 равен " << fact(4);
    cout << '\n';
    return 0;
    }

    // Рекурсивная версия.
    int factr(int n)
    {
    int answer;
    if(n==1) return(1);
    answer = factr(n-1)*n;
    return(answer);
    }
    // Итеративная версия.
    int fact(int n)
    {
    int t, answer;
    answer =1;
    for(t=1; t<=n; t++) answer = answer* (t);
    return (answer);
    }
    Нерекурсивная версия функции fact() довольно проста и не требует расширенных пояснений. В ней используется цикл, в котором организовано перемножение последовательных чисел, начиная с 1 и заканчивая числом, заданным в качестве параметра:
    на каждой итерации цикла текущее значение управляющей переменной цикла умножается на текущее значение произведения, полученное в результате выполнения предыдущей итерации цикла.
    Рекурсивная функция factr() несколько сложнее. Если она вызывается с аргументом,
    равным 1, то сразу возвращает значение 1. В противном случае она возвращает произведение
    factr(n-1) * n. Для вычисления этого выражения вызывается метод factr() с аргументом n-1.
    Этот процесс повторяется до тех пор, пока аргумент не станет равным 1, после чего вызванные ранее методы начнут возвращать значения. Например, при вычислении факториала числа 2 первое обращение к методу factr() приведет ко второму обращению к тому же методу, но с аргументом, равным 1. Второй вызов метода factr() возвратит значение

    1, которое будет умножено на 2 (исходное значение параметра n). Возможно, вам будет интересно вставить в функцию factr() инструкции cout, чтобы показать уровень каждого вызова и промежуточные результаты.
    Когда функция вызывает сама себя, в системном стеке выделяется память для новых локальных переменных и параметров, и код функции с самого начала выполняется с этими новыми переменными. Рекурсивный вызов не создает новой копии функции. Новыми являются только аргументы. При возвращении каждого рекурсивного вызова из стека извлекаются старые локальные переменные и параметры, и выполнение функции возобновляется с "внутренней" точки ее вызова. О рекурсивных функциях можно сказать,
    что они "выдвигаются" и "задвигаются".
    Следует иметь в виду, что в большинстве случаев использование рекурсивных функций не дает значительного сокращения объема кода. Кроме того, рекурсивные версии многих процедур выполняются медленнее, чем их итеративные эквиваленты, из-за дополнительных затрат системных ресурсов, связанных с многократными вызовами функций. Слишком большое количество рекурсивных обращений к функции может вызвать переполнение стека.
    Поскольку локальные переменные и параметры сохраняются в системном стеке и каждый новый вызов создает новую копию этих переменных, может настать момент, когда память стека будет исчерпана. В этом случае могут быть разрушены другие ("ни в чем не повинные") данные. Но если рекурсия построена корректно, об этом вряд ли стоит волноваться.
    Основное достоинство рекурсии состоит в том, что некоторые типы алгоритмов рекурсивно реализуются проще, чем их итеративные эквиваленты. Например, алгоритм сортировки Quicksort довольно трудно реализовать итеративным способом. Кроме того,
    некоторые задачи (особенно те, которые связаны с искусственным интеллектом) просто созданы для рекурсивных решений. Наконец, у некоторых программистов процесс мышления организован так, что им проще думать рекурсивно, чем итеративно.
    При написании рекурсивной функции необходимо включить в нее инструкцию проверки условия (например, if-инструкцию), которая бы обеспечивала выход из функции без выполнения рекурсивного вызова. Если этого не сделать, то, вызвав однажды такую функцию, из нее уже нельзя будет вернуться. При работе с рекурсией это самый распространенный тип ошибки. Поэтому при разработке программ с рекурсивными функциями не стоит скупиться на инструкции cout, чтобы быть в курсе того, что происходит в конкретной функции, и иметь возможность прервать ее работу в случае обнаружения ошибки.
    Рассмотрим еще один пример рекурсивной функции. Функция reverse() использует рекурсию для отображения своего строкового аргумента в обратном порядке.
    // Отображение строки в обратном порядке с помощью рекурсии.
    #include
    using namespace std;
    void reverse(char *s);
    int main()
    {
    char str[] = "Это тест";
    reverse(str);
    return 0;
    }
    // Вывод строки в обратном порядке.
    void reverse(char *s)
    {
    if(*s)reverse(s+1);
    else return;
    cout << *s;
    }
    Функция reverse() проверяет, не передан ли ей в качестве параметра указатель на нуль,
    которым завершается строка. Если нет, то функция reverse() вызывает саму себя с указателем на следующий символ в строке. Этот "закручивающийся" процесс повторяется до тех пор, пока той же функции не будет передан указатель на нуль. Когда, наконец,
    обнаружится символ конца строки, пойдет процесс "раскручивания", т.е. вызванные ранее функции начнут возвращать значения, и каждый возврат будет сопровождаться "довыполнением" метода, т.е. отображением символа s. В результате исходная строка посимвольно отобразится в обратном порядке.
    Создание рекурсивных функций часто вызывает трудности у начинающих программистов. Но с приходом опыта использование рекурсии становится для многих обычной практикой.

    1   ...   4   5   6   7   8   9   10   11   ...   33


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