Г. Шилтд - Самоучитель C++ (PDF) (1114887), страница 25
Текст из файла (страница 25)
Однако что случается,если она вызывается с целым? Какую функцию вызовет компилятор f(float)или f(double)? (Оба преобразования правильны!) И в том, и в другом случаеправильно "привести" тип int либо к типу float, либо к типу double. Таким образом, возникает ситуация неоднозначности.Этот пример выявляет также то, как неоднозначность может прояштяться привызове перегруженных функций. Очевидно, что сама по себе неоднозначность не присуща перегруженным версиям функции f(), пока каждая вызывается с аргументом соответствующего типа.2. Другой пример перегрузки функции, которая сама по себе не должна приводить к неоднозначности. Тем не менее, при вызове с аргументом неправильного типа, правила преобразования типа C++ создают ситуацию неоднозначности.// Эта программа неоднозначна^include <iostream>using namespace std;void f (unsigned char c)cout « c;}void f (char c){cout « c;Глава5.Перегрузкафункций_767int main ()(f Сс1};f{86); // какая версия функции f(} вызывается?return 0;Когда функция f() вызывается с числовой константой 86, компилятор неможет понять, какую версию функции вызвать: f(unsigned char) или f(char).Оба преобразования одинаково правильны, что и ведет к неоднозначности.3.
Один из видов неоднозначности проявляется, если вы пытаетесь перегрузитьфункции, единственным отличием которых является то, что одна используетпараметр -ссылку, а другая параметр-значение по умолчанию. В рамках формального синтаксиса C++ у компилятора нет способа узнать, какую функцию вызвать. Запомните, что нет синтаксических отличий между вызовомфункции по значению и вызовом функции по ссылке.
Например:// Эта программа неоднозначна^include <iostream>using namespace std;int f { i n t a, int b){return a + b;// здесь внутренняя неоднозначностьint f f i n t a, int &b){return a — b;Jintmain()cout « f ( x , y) ; // какую версию f ( } вызвать?return 0;Здесь вызов функции f(x, у) неоднозначен, поскольку вызвана может бытьлюбая версия функции. При этом компилятор выставит флаг ошибки дажераньше того, как встретится такая инструкция, поскольку сама перегрузкаэтих двух функций внутренне неоднозначна, и компилятор не будет знать,какую из них предпочесть.4. Другим видом неоднозначности при перегрузке функций является случай,когда одна или более перегруженных функций используют аргумент поумолчанию.
Рассмотрим программу:168Самоучитель C++II Неоднозначность, основанная на аргументах по умолчанию// и перегрузке функций^include <iostream>using namespace std;intf f i n t a)return a * a;int f { i n t a, int b = 0)return a * b;intmainOcout « f(10, 2); // вызывается f(int, int)cout « f(10); // неоднозначность,// что вызвать f(int, int) или f(int)???return 0;Здесь вызов функции f(10, 2) совершенно правилен и не ведет к неоднозначности. Однако у компилятора нет способа выяснить, какую версиюфункции f() вызывает версия f(10) — первую или вторую, в которой параметр Ь передается по умолчанию.1. Попытайтесь провести компиляцию всех предыдущих программ, в которых имеет место неоднозначность.
Запомните сообщения об ошибках. Это поможет вамсразу распознать ошибки неоднозначности, если они появятся в ваших программах.5.6. Определение адресаперегруженной функцииВ заключение этой главы вы узнаете, как найти адрес перегруженной функции. Так же, как и в С, вы можете присвоить адрес функции указателю иполучить доступ к функции через этот указатель. Адрес функции можнонайти, если поместить имя функции в правой части инструкции присваивания без всяких скобок или аргументов. Например, если zap() — это функ-Глава 5.
Перегрузка функций169ция, причем правильно объявленная, то корректным способом присвоитьпеременной р адрес функции zap() является инструкция:р - zap;В языке С любой тип указателя может использоваться как указатель нафункцию, поскольку имеется только одна функция, на которую он можетссылаться. Однако в C++ ситуация несколько более сложная, посколькуфункция может быть перегружена. Таким образом, должен быть некий механизм, который позволял бы определять адреса перегруженных версийфункции.Решение оказывается не только элегантным, но и эффектным.
Способ объявления указателя и определяет то, адрес какой из перегруженных версийфункции будет получен. Уточним, объявления указателей соответствуютобъявлениям перегруженных функций. Функция, объявлению которой соответствует объявление указателя, и является искомой функцией.Здесь представлена программа, которая содержит две версии функцииspace(). Первая версия выводит на экран некоторое число пробелов, заданное в переменной count. Вторая версия выводит на экран некоторое числокаких-то иных символов, вид которых задан в переменной ch. В функцииmain() объявляются оба указателя на эти функции. Первый задается как указатель на функцию, имеющую только один целый параметр.
Второй объявляется как указатель на функцию, имеющую два параметра./* Иллюстрация присваивания и получения указателей на перегруженныефункции*/^include <iostream>using namespace std;// вывод заданного в переменной count числа пробеловvoid space(int count){for(; count; count —) cout « ' ';// вывод заданного в переменной count числа символов,// вид которых задан в переменной chvoid space(int count, char ch)(fort; count; count —) cout « ch;_170СамоучительC++int main С)f/* Создание указателя на функцию с одним целым параметром. */void (*fpl) (int) ;/* Создание указателя на функцию с одним целым и однимсимвольным параметром. */void (*fp2) (int, char);fpl = space; // получение адреса функции space (int)fp2 « space; // получение адреса функции space (int, char)fpl (22); // выводит 22 пробелаcout « "|\n";fp2(30, 'x'); // выводит 30 символов хcout « "|\n";return 0;Как показано в комментариях, на основе того, каким образом объявляютсяуказатели fpl и fp2, компилятор способен определить, какой из них на какуюиз перегруженных функций будет ссылаться.Повторим, если вы присваиваете адрес перегруженной функции указателю нафункцию, то объявление указателя определяет, адрес какой именно функцииему присваивается.
Более того, объявление указателя на функцию должно точно соответствовать одной и только одной перегруженной функции. Если это нетак, будет внесена неоднозначность, что приведет к ошибке при компиляциипрограммы.Упражненияения]1. Ниже приведены две перегруженные функции. Покажите, как получить адрес каждой из них.int d i f f i n t a,{int b)return a — b;float dif (float a, float b){return a — b;1Глава 5.
Перегрузка функций171Проверка уевматериала главыТеперь вам необходимо выполнить следующие упражнения и ответить навопросы:1. Перегрузите конструктор date() из раздела 5.1, пример 3 так, чтобы онимел параметр типа time_t. (Вспомните, что time_t — это тип данных,определенный стандартными библиотечными функциями времени и даты компилятора C++.)2. Что неправильно в следующем фрагменте?class samp {int а;public:samp (int i)int{ a = i;}rnain()(samp x, у (10) ;3. Приведите два довода в пользу того, почему вам могло бы потребоватьсяперегрузить конструктор класса.4. Какова основная форма конструктора копий?5.
Какой тип операций ведет к вызову конструктора копий?6. Кратко объясните, зачем нужно ключевое слово overload, и почему онобольше не употребляется.7. Объясните, что такое аргумент по умолчанию?8. Создайте функцию reverse() с двумя параметрами. Первый параметрstr — это указатель на строку, порядок следования символов в которой,после возвращения функцией своего значения, должен быть заменен наобратный. Второй параметр count задает количество переставляемых встроке str символов.
Значение count по умолчанию должно быть таким,чтобы в случае его задания функция reverse() меняла порядок следования символов в целой строке.9. Что неправильно в следующем прототипе функции?char *wordwrap(char *str, int size = 0, char ch) ;172Самоучитель С++10. Приведите несколько причин появления неоднозначности при перегрузке функций.11. Что неправильно в следующем фрагменте?void compute(double *num, int divisor = 1);void compute(double *num);// ...compute(&x);12. При присваивании указателю адреса перегруженной функции, что определяет конкретную версию используемой фуИкции?Проверка усвоенияматериала в целомВ этом разделе проверяется, хорошо ли вы усвоили материал этой и предыдущих глав.1.
Создайте функцию orderQ, которая получает два параметра-ссылки нацелые. Если первый аргумент больше второго, поменяйте их значения. Впротивном случае ничего делать не надо. Таким образом, порядок следования двух аргументов, используемых при вызове функции order(), должен быть таким, чтобы всегда после возвращения функцией своегозначения первый аргумент был меньше второго. Например, если даноint х = 1, у = 0;order(x, у ) ;то после вызова функции х будет равен 0, а у будет равен 1.2. Почему следующие две перегруженные функции внутренне неоднозначны?int f ( i n t a) ;int f ( i n t &a) ;3.
Объясните, почему использование аргумента по умолчанию связано сперегрузкой функций.4. Пусть дано следующее неполное описание класса, добавьте конструкторы так, чтобы оба объявления в функции main() были правильны.(Подсказка: вам необходимо дважды перегрузить конструктор samp().)class samp {int a;Глава 5. Перегрузкгпрункцийpublic:// добавьте конструкторыint get_a() { return a; }int ma insamp ob(88); // инициализация объекта а значением 88samp obarray[10]; // неинициализируемый 10-злементный массив5. Кратко объясните, зачем нужны конструкторы копий.173Глава 6Введениев перегрузкуоператоровВ этой главе рассматривается очередное важное свойство C++: перегрузкаоператоров. Это свойство позволяет определять значение операторов C++относительно задаваемых вами классов.
Путем перегрузки связанных с классами операторов можно легко добавлять в программу новые типы данных.Повторение пройденногоПеред тем как продолжить, необходимо правильно ответить на следующиевопросы и сделать упражнения.1. Покажите, как перегрузить конструктор для следующего класса так,чтобы можно было создавать не только инициализируемые, но и неинициализируемые объекты. (При создании таких объектов присвойте переменным х и у значение 0.)class myclass {int x,y;public:myclass (int i, int j) ( x = i; у = j; }2.
Используя класс из вопроса 1, покажите, как с помощью аргументов поумолчанию можно избежать перегрузки конструктора myclassQ3. Что неправильно в следующем объявлении?int f {int а = О, double balance) ;4. Что неправильно в следующих двух перегруженных функциях?void f (int a) ;void f (int &a) ;5. Когда удобнее использовать аргументы по умолчанию? Когда этого лучше не делать?776______СамоучительC++в.
Дано следующее определение класса. Возможно ли динамически выделить память для массива объектов такого типа?class test {char *p;int *q;int count ;public:test (char *x, int *y, int c) {p = x;q -y;count = c;7. Что такое конструктор копий и при каких условиях он вызывается?6.1. Основы перегрузки операторовПерегрузка операторов напоминает перегрузку функций. Более того, перегрузка операторов является фактически одним из видов перегрузки функций. Однако при этом вводятся некоторые дополнительные правила.Например, оператор всегда перегружается относительно определенногопользователем типа данных, такого, как класс. Другие отличия будут обсуждаться ниже по мере необходимости.Когда оператор перегружается, то ничего из его исходного значения не теряется.
Наоборот, он приобретает дополнительное значение, связанное склассом, для которого оператор был определен.Для перегрузки оператора создается оператор -функция (operator function).Чаще всего, оператор -функция является членом класса или дружественнойклассу, для которого она определена. Однако есть небольшая разница междуоператор -функцией — членом класса и дружественной оператор-функцией.В первой части этой главы обсуждается создание оператор-функций — членов класса.