Язык программирования Си++ (1119468), страница 3
Текст из файла (страница 3)
. .X::destroyX (xx1); return 0; }47Статические члены класса• Объекты, имеющие статические члены, могут быть объявленыконстантамиclass X { public: static int c;};const X xc; // xc - константа• Поле ‘c’ константой при таком определении объекта ‘xc’ нестановится. С ним можно работать, как с обычной переменной• Чтобы объявить статическое поле данных константой, это надосделать явно:class Z { public: const static int c;};const int Z::c = 25;// задано значение поля ‘c’const Z zc;// ‘zc’ – константа, поле ‘c’ – тоже48Перекрытие имён• Разрешается использовать одинаковые имена дляразных объектов (переменных, констант, функций)• Области видимости в программах могут бытьвложенными друг в друга, определение объекта вовложенной области видимости отменяет (перекрывает)определение одноимённого объекта в объемлющейобласти• Перекрытие позволяет использовать в разных(вложенных друг в друга) областях видимостиодинаковые имена для разных, но одновременно неиспользуемых объектов49Перегрузка функций• Перегрузка функций – механизм, позволяющийодинаково именовать функции, видимые водной области• Перегрузка функций – проявление статического(полностью разрешаемого на стадиикомпиляции программ) полиморфизма• Перегрузка функций позволяет использовать водной области видимости одинаковые именадля разных, но одновременно не используемыхфункций50Перегрузка функций.
Конфликты• Неоднозначность выбора нужной функции:void f (char);void f (double);void g () {... f (1); ...}• Отсутствие подходящей функции:class X { ... };class Y { ... };void f (int);void f (Y);void g () { ... X a; f (a); ...}• Скрытая неоднозначность выбора функции:void f (int x = 1);void f ();void g () {... f (); ...}• Тип возвращаемого значения функции не участвует ввыборе обслуживающей функции:float sqrt (float);double sqrt (float);51Правила работы алгоритмавыбора перегруженной функцииа).
Точное отождествлениеб). Отождествление при помощи стандартныхцелочисленных и вещественных расширенийв). Отождествление с помощью стандартныхпреобразованийг). Отождествление с помощью пользовательскихпреобразованийд). Отождествление по эллиптической конструкции “…”(обычно этот шаг выполняется только при вызовахфункций с несколькими параметрами)52Выбор перегруженной функцииа). Точное отождествление• Точное совпадение типов• Совпадение с точностью до typedef (новый тип не создаётся!)• Тривиальные преобразования: Параметр-массив:T [] <--> T * Передача параметра по ссылке:T <--> T & (const T <--> const T &) Передача переменной на месте формальногопараметра-константы:T --> const T(обратное преобразование не рассматривается) Параметр-функция (через имя функции ичерез указатель на функцию):T (...) <--> (T*) (...)Выбор перегруженной функцииа). Точное отождествлениеvoid f (int);void f (float); void f (double);void f (int unsigned);void f (unsigned long);void g () { f (1.0); /* f (double) */ f (1.0F); // f (float)f (1); /* f (int) */f (1U); // f (unsigned int)f (1UL); }// f (unsigned long)void f (int * a); <=void g () { int m [5]; f (m); f (& m [0]); }void f (int & a); <=void g () { int m;f (m); }void f (int a);<=void g () { int m, & k = m;f (k); }void f (const int * a); void g () { int k, * m = & k;f (m); }void f (int * a) ;void g () { const int k = 7;const int * b = & k; f (b); }54Выбор перегруженной функцииб).
Стандартные расширения• Целочисленные расширения: char (signed/unsigned), short (signed/unsigned),enum => int (unsigned int, если тип int слишком узок) enum => long int (unsigned long int, если значения немогут быть представлены типом long int) bool => int (false --> 0, true --> 1) Если возможно, то битовые поля могут расширятьсядо int или unsigned int, иначе они не расширяются• Расширения чисел с плавающей точкой: float => double55Выбор перегруженной функцииб). Стандартные расширения• Расширения типов всегда имеют предпочтения переддругими стандартными преобразованиямиvoid f (int);void g () { short aa = 1;float ff = 1.0;• Нет неоднозначности, хотя:short => int & doublevoid f (double);f (aa);// f (int)f (ff); }// f (double)float => int & double• Если бы стандартные преобразования не делились на пунктыб и в, то неоднозначность возникала бы, поскольку всепреобразования одного шага работы алгоритма равноправныВыбор перегруженной функциив).
Стандартные преобразования• Все оставшиеся стандартные преобразования: все оставшиеся неявные стандартные целочисленныеи вещественные преобразования преобразования указателей и ссылок:•0=> любой указатель• любой указатель => свободный указатель (void *)• указатель (ссылка) на производный тип =>указатель (ссылка) на базовый тип(для однозначного доступного базового классанаследственной иерархии)57Выбор перегруженной функциив). Стандартные преобразования• Все стандартные преобразования равноправны:void f (char);void f (double);void g () { f (0); }• Неоднозначность возникает из-за отсутствия явногосовпадения (пункт а) и отсутствия стандартногорасширения для типа int (пункт б): при выбореподходящей функции по правилам пункта впреобразования int => char и int => double равноправныВыбор перегруженной функцииг).
Преобразования пользователя• Конструкторы преобразования (с одним параметром)• Функции преобразования – нестатические методыкласса с профилем “operator <тип> ()”, которыепреобразуют объект класса к типу <тип>struct S { S (long);// конструктор преобразования: long --> Soperator int (); // функция преобразования:S --> int};void f (char *);void g (char *);void h (char *);void f (long);void g (S);void h (const S &);void ex (S &a) { f (a); // f ((long) (a.operator int ())) = f (long)(г и в)g (1); // g (S ((long) (1)))= g (S)(г)g (0); // g ((char *) (0))= g (char *)(в)h (1); // h (S ((long) (1)))= h (const S &) (г)}Выбор перегруженной функцииг).
Преобразования пользователя• Разрешается один раз повторно искать подходящийтип (а), расширение (б) или преобразование (в)• Пользовательские преобразования применяютсянеявно только в том случае, если они однозначныstruct A { int a; public: A (int i) { ... }; struct B { int b; public: B (int i) { ... };int f (A x, int y) { return x.a + y; }int f (B x, int y) { return x.b - y; }Выражение f (1, 1) неоднозначно, поскольку можетинтерпретироваться двояко:f (A (1), 1) /* f (A, int) */f (B (1), 1)/* f (B, int) */60Выбор перегруженной функцииг). Преобразования пользователя• Допускается не более одного пользовательскогопреобразования для обработки одного вызова дляодного параметраclass X { ...
public: operator int (); ... }; // можно преобразовать в intclass Y { ... public: operator X (); ... }; // можно преобразовать в Xvoid f (int x, int y);void g () { Y a; int b; f (b, a); }// неправильноТребуется: a.operator X ().operator int () – два шага по пункту г•• Если написать это явно, ошибки не будет: цепочкиявных преобразований могут быть сколь угоднодлинными61Выбор перегруженной функцииг). Преобразования пользователя• Для использования в неявных преобразованиях и припоиске подходящей функции конструктор должениметь разрешение на неявный вызов• Неявный вызов конструктора может быть запрещёнспецификатором explicitclass X { ...
public: X (int); ... };void f () { X a (1);// явный вызов, всегда правильноX b = 2;// неявный вызовX c = X (2); // явный вызов с тем же эффектом}62Выбор перегруженной функциис несколькими параметрами1. Отбираются все функции, которые могут быть вызваны суказанным в вызове количеством параметров2. Для каждого параметра вызова строится множество функций,оптимально отождествляемых по этому параметру3.
Находится пересечение этих множеств: если это ровно одна функция – она и является искомой, если множество пусто или содержит более одной функции,генерируется сообщение об ошибке.• При работе алгоритма для функций с несколькими параметрамиобнаружение неоднозначности не приводит к немедленнойостановке алгоритма, неоднозначность может быть снятарассмотрением других параметров функции63Выбор перегруженной функциид). Выбор по ...• Функции с переменным числом параметров работают вСи++ с помощью макроопределений va_list и va_argclass Real { /* ... */ public: /* ... */ Real (double); /* ... */ };void f (int, Real); void f (int, ...);void g () { f (1, 1);// f (int, Real)// пункт д даже// не привлекается к рассмотрениюf (1, “Строка”); // f (int, ...)// int, const char *}• Многоточие может приводить к неоднозначностиvoid f (int);void f (int ...);void g () { /* ...
*/ f (1); /* … */ } // Ошибка, так как отождествление// по первому параметру даёт обе функции64Перегрузка операций• Можно перегружать обычные арифметические илогические операции, операции отношения, операцииприсваивания, сдвиги, операции индексирования,разыменования указателей на структуры, операциидоступа к членам класса через указатель на членкласса, операции группировки параметров, захвата иосвобождения свободной памяти (всего 42 операции):=~++===++new––=!=––*/*= /=!,–> –>*new []%%=<[]<< >> ^<<= >>= ^=<= >= >()delete| &|= &=|| &&delete []65Перегрузка операций• Нельзя перегружать:::..*?:sizeoftypeid###- разрешение области видимости- выбор члена класса- выбор через указатель на член класса- тернарная условная операция- операция вычисления размера объекта- операция вычисления имени типа- начало макродирективы- слияние или преобразование лексем66Перегрузка операций• Для перегрузки операций используетсяключевое слово operator, например, запись“operator==” обычно используется дляопределения операции проверки на равенстводля объектов невстроенных типов• При определении операций лучше сближатьсемантику новых операций с семантикойаналогично записываемых операцийвстроенных типовПерегрузка операций• Все перегрузки операций, необходимых в программе,нужно делать явно, для каждой операции (даже еслинекоторые операции эквивалентны):++ aa += 1a=a+1• Имея функции, определяющие сложение a.operator + ()и присваивание a.operator = () , нельзя автоматически,без явного определения, использовать операцию ‘+=’,то есть функцию a.operator += ()• Операции присваивания (‘=’), индексирования (‘[]’) идоступа (‘->’) могут перегружаться тольконестатическими методами классов68Перегрузка бинарных операций• Определение метода конкатенации в классе,созданном для работы со строками:class string { char * p; int size;/* … */string& concat (const string& s) { /* … */ }};• Реализация бинарной операции увеличения ‘+=’:string & operator+= (string & s1, const string & s2){ return s1.concat (s2); }• Определение этой же операции в составе класса:class string { /* … */string & operator += (const string & s) { return concat (s); }};string s1 (“ a”), s2 (“ b”);s1 += s2;69Методы классов ифункции-друзья классов• Свойства (ограничения) обычных методов класса:1.