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

  • Использованные в этих функциях параметры имеют тот же тип Если да, то почему Если нет, то тоже почему

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница15 из 54
    1   ...   11   12   13   14   15   16   17   18   ...   54
    Что если occurs сделать константной ссылкой?
    6.2.3. Константные параметры и аргументы
    При использовании параметров, являющихся константой, следует помнить об обсуждении спецификатора const верхнего уровня из раздела 2.4.3. Как упоминалось в этом разделе,
    спецификатор const верхнего уровня — это тот спецификатор, который относится непосредственно к объекту: const int ci = 42; // нельзя изменить ci; const верхнего уровня int i = ci; // ok: при копировании ci спецификатор const
    // верхнего уровня игнорируется int * const p = &i; // const верхнего уровня; нельзя присвоить p
    *p = 0; // ok: изменение при помощи p возможно; i теперь 0
    Как и при любой другой инициализации, при копировании аргумента для инициализации параметра спецификаторы const верхнего уровня игнорируются. В результате спецификатор const верхнего уровня для параметров игнорируется. Параметру, у которого есть спецификатор const верхнего уровня, можно передать и константный, и неконстантный объект: void fcn(const int i) { /* fcn может читать, но не писать в i */ }
    Функцию fcn() можно вызвать, передав ей аргумент типа const int или обычного типа int. Тот факт, что спецификаторы const верхнего уровня игнорируются у параметра, может иметь
    Page 274/1103
    удивительные последствия: void fcn(const int i) { /* fcn может читать, но не писать в i */ } void fcn(int i) { /* ... */ } // ошибка: переопределяет fcn(int)
    В языке С++ можно определить несколько разных функций с одинаковым именем. Однако это возможно только при достаточно большом различии их списков параметров. Поскольку спецификаторы const верхнего уровня игнорируются, мы можем передать те же типы любой версии функции fcn(). Вторая версия функции fcn() является ошибкой. Несмотря на внешний вид, ее список параметров не отличается от списка первой версии функции fcn(). Параметры в виде указателей или ссылок и константность
    Поскольку параметры инициализируются так же, как и переменные, имеет смысл напомнить общие правила инициализации. Можно инициализировать объект со спецификатором const нижнего уровня неконстантным объектом, но не наоборот, а простую ссылку следует инициализировать объектом того же типа. int i = 42; const int *cp = &i; // ok: но cp не может изменить i (раздел 2.4.2) const int &r = i; // ok: но r не может изменить i (раздел 2.4.1) const int &r2 = 42; // ok: (раздел 2.4.1) int *p = cp; // ошибка: типы p и cp не совпадают (раздел 2.4.2) int &r3 = r; // ошибка: типы r3 и r не совпадают (раздел 2.4.1) int &r4 = 42; // ошибка: нельзя инициализировать простую ссылку из
    // литерала (раздел 2.3.1)
    Те же правила инициализации относятся и к передаче параметров: int i = 0; const int ci = i; string::size_type ctr = 0;
    Page 275/1103
    reset(&i); // вызывает версию функции reset с параметром типа int* reset(&ci); // ошибка: нельзя инициализировать int* из указателя на
    // объект const int reset(i); // вызывает версию функции reset с параметром типа int& reset(ci); // ошибка: нельзя привязать простую ссылку к константному
    // объекту ci reset(42); // ошибка: нельзя привязать простую ссылку к литералу reset(ctr); // ошибка: типы не совпадают; ctr имеет беззнаковый тип
    // ok: первый параметр find_char является ссылкой на константу find_char("Hello World!", 'o', ctr);
    Ссылочную версию функции reset() (см. раздел 6.2.2) можно вызвать только для объектов типа int. Нельзя передать литерал, выражение, результат которого будет иметь тип int,
    объект, который требует преобразования, или объект типа const int. Точно так же версии функции reset() с указателем можно передать только объект типа int* (см. раздел 6.2.1). С
    другой стороны, можно передать строковый литерал как первый аргумент функции find_char()
    (см. раздел 6.2.2). Ссылочный параметр этой функции — ссылка на константу, и можно инициализировать ссылки на константу из литералов. По возможности используйте ссылки на константы
    Весьма распространена ошибка, когда не изменяемые функцией параметры определяют как простые ссылки. Это создает у вызывающей стороны функции ложное впечатление, что функция могла бы изменить значение своего аргумента. Кроме того, использование ссылки вместо ссылки на константу неоправданно ограничивает типы аргументов, применяемые функцией. Как уже упоминалось, нельзя передать константный объект, литерал или требующий преобразования объект как простой ссылочный параметр.
    В качестве примера рассмотрим функцию find_char() из раздела 6.2.2. Строковый параметр этой функции правильно сделан ссылкой на константу. Если бы этот параметр был определен как string&:
    Page 276/1103

    // ошибка: первый параметр должен быть const string& string::size_type find_char(string &s, char c, string::size_type &occurs); то вызвать ее можно было бы только для объекта класса string, так что find_char("Hello World", 'o', ctr); привело бы к неудаче во времени компиляции.
    Более того, эту версию функции find_char() нельзя использовать из других функций, которые правильно определяют свои параметры как ссылки на константу. Например, мы могли бы использовать функцию find_char() в функции, которая определяет, является ли строка предложением: bool is_sentence(const string &s) {
    // если в конце s есть точка, то строка s - предложение string::size_type ctr = 0; return find_char(s, ctr) == s.size() - 1 && ctr == 1;
    }
    Если бы функция find_char() получала простую ссылку string?, то этот ее вызов привел бы к ошибке при компиляции. Проблема в том, что s — ссылка на const string, но функция find_char() была неправильно определена как получающая простую ссылку.
    Было бы заманчиво попытаться исправить эту проблему, изменив тип параметра в функции is_sentence(). Но это только распространит ошибку, так как вызывающая сторона функции is_sentence() сможет передавать только неконстантные строки.
    Правильный способ решения этой проблемы — исправить параметр функции find_char().
    Если невозможно изменить функцию find_char(), определите локальную копию строки s в функции is_sentence() и передавайте эту строку функции find_char(). Упражнения раздела
    6.2.3
    Упражнение 6.16. Несмотря на то что следующая функция допустима, она менее полезна,
    чем могла бы быть. Выявите и исправьте ограничение этой функции: bool is_empty(string& s) { return s.empty(); }
    Упражнение 6.17. Напишите функцию, определяющую, содержит ли строка какие-нибудь заглавные буквы. Напишите функцию, переводящую всю строку в нижний регистр.

    Использованные в этих функциях параметры имеют тот же тип? Если да, то почему? Если нет, то тоже почему?
    Упражнение 6.18. Напишите объявления для каждой из следующих функций. Написав объявления, используйте имя функции для обозначения того, что она делает.
    (a) Функция compare() возвращает значение типа bool и получает два параметра, являющиеся
    Page 277/1103
    ссылками на класс matrix.
    (b) Функция change_val() возвращает итератор vector<int> и получает два параметра:
    один типа int, а второй итератор для вектора vector<int>.
    Упражнение 6.19. С учетом следующего объявления определите, какие вызовы допустимы, а какие нет. Объясните, почему они недопустимы. double calc(double); int count(const string &, char); int sum(vector<int>::iterator, vector<int>::iterator, int); vector<int> vec(10);
    (a) calc(23.4, 55.1); (b) count("abcda", 'a');
    (c) calc(66); (d) sum(vec.begin(), vec.end(), 3.8);
    Упражнение 6.20. Когда ссылочные параметры должны быть ссылками на константу? Что будет, если сделать параметр простой ссылкой, когда это могла быть ссылка на константу?
    6.2.4. Параметры в виде массива
    Массивы обладают двумя особенностями, влияющими на определение и использование функций, работающих с массивами: массив нельзя скопировать (см. раздел 3.5.1), имя массива при использовании автоматически преобразуется в указатель на его первый элемент
    (см. раздел 3.5.3). Поскольку копировать массив нельзя, его нельзя передать функции по значению. Так как имя массива автоматически преобразуется в указатель, при передаче массива функции фактически передается указатель на его первый элемент.
    Хотя передать массив по значению нельзя, вполне можно написать параметр, который выглядит как массив:
    // несмотря на внешний вид,
    // эти три объявления функции print эквивалентны
    // у каждой функции есть один параметр типа const int* void print(const int*); void print(const int[]); // демонстрация намерения получить массив void print(const int[10]); //
    Page 278/1103
    размерность только для документирования
    Независимо от внешнего вида, эти объявления эквивалентны: в каждом объявлена функция с одним параметром типа const int*. Когда компилятор проверяет вызов функции print(), он выясняет только то, что типом аргумента является const int*: int i = 0, j[2] = {0, 1}; print(&i); // ok: &i - int* print(j); // ok: j преобразуется в int*, указывающий на j[0]
    Если передать массив функции print(), то этот аргумент автоматически преобразуется в указатель на первый элемент в массиве; размер массива не имеет значения.
    Подобно любому коду, который использует массивы, функции, получающие в качестве параметров массив, должны гарантировать невыход за пределы его границ.
    Поскольку массивы передаются как указатели, их размер функции обычно неизвестен. Они должны полагаться на дополнительную информацию, предоставляемую вызывающей стороной. Для управления параметрами указателя обычно используются три подхода.
    Использование маркера для определения продолжения массива
    Первый подход к управлению аргументами в виде массива требует, чтобы массив сам содержал маркер конца. Примером этого подхода являются символьные строки в стиле С (см.
    раздел 3.5.4). Строки в стиле С хранятся в символьных массивах, последний символ которых является нулевым. Функции, работающие со строками в стиле С, прекращают обработку массива, когда встречают нулевой символ: void print(const char *cp) { if (cp) // если cp не нулевой указатель while (*cp) // пока указываемый символ не является нулевым cout << *cp++; // вывести символ и перевести указатель
    }
    Это соглашение хорошо работает с данными, где есть очевидное значение конечного маркера (такое, как нулевой символ), который не встречается в обычных данных. Это работает значительно хуже с такими данными, как целые числа, где каждое значение в диапазоне вполне допустимо. Использование соглашения стандартной библиотеки
    Второй подход обычно используется для управления аргументами в виде массива при передаче указателей на первый и следующий после последнего элемент массива. Подобный подход используется в стандартной библиотеке. Подробно этот стиль программирования обсуждается в части II. Используя этот подход, элементы массива можно отобразить
    Page 279/1103
    следующим образом: void print(const int *beg, const int *end) {
    // вывести все элементы, начиная с beg и до, но не включая, end while (beg != end) cout << *beg++ << endl; // вывести текущий элемент
    // и перевести указатель
    }
    Для вывода текущего элемента и перевода указателя beg на следующий элемент массива цикл while использует операторы обращения к значению и постфиксного инкремента (см.
    раздел 4.5). Цикл останавливается, когда beg становится равен end.
    При вызове этой функции передаются два указателя: один на первый подлежащий отображению элемент и один на элемент после последнего: int j[2] = {0, 1};
    // j преобразуется в указатель на первый элемент массива j
    // второй аргумент - указатель на следующий элемент после конца j print(begin(j), end(j)); // функции begin и end см. p. 3.5.3
    Эта функция безопасна, пока вызывающая сторона правильно вычисляет указатели. Здесь эти указатели предоставляют библиотечные функции begin() и end() (см. раздел 3.5.3). Явная передача параметра размера
    Третий подход распространен в программах С и устаревших программах С++. Он подразумевает определение второго параметра, указывающего размер массива. Используя этот подход, перепишем функцию print() следующим образом:
    // const int ia[] - эквивалент const int* ia
    // размер передается явно и используется для контроля доступа
    //
    Page 280/1103
    к элементам ia void print(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl;
    }
    }
    Эта версия использует параметр size для определения количества выводимых элементов.
    Когда происходит вызов функции print(), ей следует передать этот дополнительный параметр:
    int j[] = { 0, 1 }; // массив типа int размером 2 print(j, end(j) - begin(j));
    Функция безопасна, пока переданный размер не превосходит реальную величину массива.
    Параметры массива и константность
    Обратите внимание, что все три версии функции print() определяли свои параметры массива как указатели на константу. В разделе 6.2.3 было упомянуто о схожести указателей и ссылок.
    Когда функция не нуждается в записи элементов массива, параметр массива должен быть указателем на константу (см. раздел 2.4.2). Параметр должен быть простым указателем на неконстантный тип, только если функция должна изменять значения элементов. Ссылочный параметр массива
    Подобно тому, как можно определить переменную, являющуюся ссылкой на массив (см.
    раздел 3.5.1), можно определить параметр, являющийся ссылкой на массив. Как обычно,
    ссылочный параметр привязан к соответствующему аргументу, которым в данном случае является массив:
    // ok: параметр является ссылкой на массив; размерность - часть типа void print(int (&arr)[10]) { for (auto elem : arr) cout << elem << endl;
    }
    Круглые скобки вокруг части &arr необходимы (см. раздел 3.5.1): f(int &arr[10]) // ошибка: объявляет arr как массив ссылок f(int (&arr)[10]) // ok: arr - ссылка на массив из десяти целых чисел
    Page 281/1103

    Поскольку размер массива является частью его типа, на размерность в теле функции вполне можно положиться. Однако тот факт, что размер является частью типа, ограничивает полноценность этой версии функции print(). Эту функцию можно вызвать только для массива из десяти целых чисел: int i = 0, j[2] = {0, 1}; int k[10] = {0,1,2,3,4,5,6,7,8,9}; print(&i); // ошибка: аргумент не массив из десяти целых чисел print(j); // ошибка: аргумент не массив из десяти целых чисел print(k); // ok: аргумент массив из десяти целых чисел
    В разделе 16.1.1 будет показано, как можно написать эту функцию способом, позволяющим передавать ссылочный параметр массива любого размера. Передача многомерного массива
    Напомним, что в языке С++ нет многомерных массивов (см. раздел 3.6). Вместо многомерных массивов есть массив массивов.
    Подобно любому массиву, многомерный массив передается как указатель на его первый элемент (см. раздел 3.6). Поскольку речь идет о массиве массивов, элемент которого сам является массивом, указатель является указателем на массив. Размер второй размерности
    (и любой последующий) является частью типа элемента и должен быть определен:
    // matrix указывает на первый элемент массива, элементы которого
    // являются массивами из десяти целых чисел void print(int (*matrix)[10], int rowSize) { /* ... */ }
    Объявляет matrix указателем на массив из десяти целых чисел.
    Круглые скобки вокруг *matrix снова необходимы: int *matrix[10]; // массив из десяти указателей int (*matrix)[10]; // указатель на массив из десяти целых чисел
    Функцию можно также определить с использованием синтаксиса массива. Как обычно,
    компилятор игнорирует первую размерность, таким образом, лучше не включать ее:
    // эквивалентное определение
    Page 282/1103
    void print (int matrix[][10], int rowSize) { /* ... */ }
    Здесь объявление matrix выглядит как двумерный массив. Фактически параметр является указателем на массив из десяти целых чисел. Упражнения раздела 6.2.4
    Упражнение 6.21. Напишите функцию, получающую значение типа int и указатель на тип int, а возвращающую значение типа int, если оно больше, или значение, на которое указывает указатель, если больше оно. Какой тип следует использовать для указателя?
    Упражнение 6.22. Напишите функцию, меняющую местами два указателя на тип int.
    Упражнение 6.23. Напишите собственные версии каждой из функций print(), представленных в этом разделе. Вызовите каждую из этих функций для вывода i и j, определенных следующим образом: int i = 0, j[2] = {0, 1};
    Упражнение 6.24. Объясните поведение следующей функции. Если в коде есть проблемы,
    объясните, где они и как их исправить. void print(const int ia[10]) { for (size_t i = 0; i != 10; ++i) cout << ia[i] << endl;
    }
    6.2.5. Функция main(): обработка параметров командной строки
    Функция main() — хороший пример того, как программы на языке С++ передают массивы в функции. До сих пор функция main() в примерах определялась с пустым списком параметров. int main() { ... }
    Но зачастую функции main() необходимо передать аргументы. Обычно аргументы функции main() используют для того, чтобы позволить пользователю задать набор параметров,
    влияющих на работу программы. Предположим, например, что функция main() программы находится в исполняемом файле по имени prog. Параметры программе можно передавать следующим образом: prog -d -о ofile data0
    Так, параметры командной строки передаются функции main() в двух (необязательных)
    параметрах: int main(int argc, char *argv[]) { ... }
    Второй параметр, argv, является массивом указателей на символьные строки в стиле С, а первый параметр, argc, передает количество строк в этом массиве. Поскольку второй параметр является массивом, функцию main(), в качестве альтернативы, можно определить следующим образом: int main(int argc, char **argv) { ... }
    Page 283/1103

    Обратите внимание: указатель argv указывает на тип char*. При передаче аргументов функции main() первый элемент массива argv содержит либо имя программы, либо является пустой строкой. Последующие элементы передают аргументы, предоставленные в командной строке. Элемент сразу за последним указателем гарантированно будет нулем.
    С учетом предыдущей командной строки argc содержит значение 5, a argv — следующие символьные строки в стиле С: argv[0] = "prog"; // может также указывать на пустую строку argv[1] = "-d"; argv[2] = "-o"; argv[3] = "ofile"; argv[4] = "data0"; argv[5] = 0;
    При использовании аргументов в массиве argv помните, что дополнительные аргументы начинаются с элемента argv[1]; элемент argv[0] содержит имя программы, а не введенный пользователем параметр. Упражнения раздела 6.2.5
    Упражнение 6.25. Напишите функцию main(), получающую два аргумента. Конкатенируйте предоставленные аргументы и выведите полученную строку.
    Упражнение 6.26. Напишите программу, которая способна получать параметры командной строки, описанные в этом разделе. Отобразите значения аргументов, переданных функции main().
    6.2.6. Функции с переменным количеством параметров
    Иногда количество аргументов, подлежащих передаче функции, неизвестно заранее.
    Например, могла бы понадобиться функция, выводящая сообщения об ошибках, созданные нашей программой. Нам хотелось бы использовать одну функцию, чтобы выводить эти сообщения единообразным способом. Однако различные вызовы этой функции могли бы передавать разные аргументы, соответствующие разным видам сообщений об ошибках.
    Новый стандарт предоставляет два основных способа создания функций, получающих переменное количество аргументов: если у всех аргументов тот же тип, можно передать объект библиотечного класса initializer_list. Если типы аргументов разные, можно написать функцию специального вида, известную как шаблон с переменным количеством аргументов (variadic template), который мы рассмотрим в разделе 16.4.
    В языке С++ есть также специальный тип параметра, многоточие, применяющийся для передачи переменного количества аргументов. Мы кратко рассмотрим параметры в виде многоточия далее в этом разделе. Но следует заметить, что это средство обычно используют только в программах, которые должны взаимодействовать с функциями С. Параметры типа
    Page 284/1103
    initializer_list
    Функцию, получающую произвольное количество аргументов одинакового типа, можно написать, используя параметр типа initializer_list. Тип initializer_list — это библиотечный класс,
    который представляет собой массив (см. раздел 3.5) значений определенного типа. Этот тип определен в заголовке initializer_list. Операции, предоставляемые классом initializer_list,
    перечислены в табл. 6.1.
    Таблица 6.1. Операции, предоставляемые классом initializer_list initializer_list<T> lst;
    Инициализация по умолчанию; пустой список элементов типа T initializer_list<T>
    lst{a,b,с...}; lst имеет столько элементов, сколько инициализаторов; элементы являются копиями соответствующих инициализаторов. Элементы списка — константы lst2(lst) lst2 = lst
    Копирование или присвоение объекта класса. initializer_list не копирует элементы в списке.
    После копирования первоисточник и копия совместно используют элементы lst.size()
    Количество элементов в списке lst.begin() lst.end() Возвращает указатель на первый и следующий после последнего элементы lst
    Подобно типу vector, тип initializer_list является шаблоном (см. раздел 3.3). При определении объекта класса initializer_list следует указать тип элементов, которые будет содержать список:
    initializer_list<string> ls; // initializer_list строк initializer_list<int> li; // initializer_list целых чисел
    В отличие от вектора, элементы списка initializer_list всегда константы; нет никакого способа изменить значение его элементов.
    Функцию отображения сообщений об ошибках с переменным количеством аргументов можно написать следующим образом: void error_msg(initializer_list<string> il) { for (auto beg = il.begin(); beg != il.end(); ++beg) cout << *beg << " "; cout << endl;
    }
    Методы begin() и end() объектов класса initializer_list аналогичны таковым у класса vector (см.
    раздел 3.4.1). Метод begin() предоставляет указатель на первый элемент списка, а метод end() — на следующий элемент после последнего. Наша функция инициализирует переменную beg указателем на первый элемент и перебирает все элементы списка initializer_list. В теле цикла осуществляется обращение к значению beg, что позволяет получить доступ к текущему элементу и вывести его значение.
    При передаче последовательности значений в параметре типа initializer_list последовательность следует заключить в фигурные скобки:
    Page 285/1103

    // expected и actual - строки if (expected != actual) error_msg({"functionX", expected, actual}); else error_msg({"functionX", "okay"});
    Здесь той же функции error_msg() передаются при первом вызове три значения, а при втором
    — два.
    У функции с параметром initializer_list могут быть также и другие параметры. Например, у нашей системы отладки мог бы быть класс ErrCode, представляющий различные виды ошибок. Мы можем пересмотреть свою программу так, чтобы в дополнение к списку initializer_list передавать параметр типа ErrCode следующим образом: void error_msg(ErrCode е, initializer_list<string> il) { cout << e.msg() << ": "; for (const auto &elem : il) cout << elem << " "; cout << endl;
    }
    Поскольку класс initializer_list имеет члены begin() и end(), мы можем использовать для обработки элементов серийный оператор for (см. раздел 5.4.3). Эта программа, как и предыдущая версия, перебирает элементы заключенного в фигурные скобки списка значений, переданных параметру il.
    Для этой версии необходимо пересмотреть вызовы так, чтобы передать аргумент типа
    ErrCode: if (expected != actual) error_msg(ErrCode(42), {"functionX", expected, actual}); else error_msg(ErrCode(0), {"functionX", "okay"}); Параметр в виде многоточия
    Параметры в виде многоточия предоставляются языком С++ для взаимодействия программам с кодом на языке С, использующим такое средство библиотеки С, как varargs. В
    других целях параметр в виде многоточия не следует использовать. Использование varargs описано в документации компилятора С.
    Параметры в виде многоточия должны использоваться только для таких типов, которые есть и у языка С, и у С++. В частности, большинство объектов типа класса копируются неправильно, когда передаются параметру в виде многоточия.
    Параметр в виде многоточия может быть только последним элементом в списке параметров
    Page 286/1103
    и может принять любую из двух форм: void foo(parm_list, ...); void foo(...);
    Первая форма определяет тип (типы) для нескольких параметров функции foo(). Контроль типов аргументов, соответствующих определенным параметрам, осуществляется как обычно.
    Для аргументов, соответствующих параметру в виде многоточия, никакого контроля типов нет. В первой форме запятая после объявления параметра необязательна. Упражнения раздела 6.2.6
    Упражнение 6.27. Напишите функцию, получающую параметр типа initializer_list<int> и возвращающую сумму элементов списка.
    Упражнение 6.28. Во второй версии функции error_msg(), где у нее есть параметр типа

    1   ...   11   12   13   14   15   16   17   18   ...   54


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