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

  • 13.9.1. Разрешение имен в области видимости класса

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


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница61 из 93
    1   ...   57   58   59   60   61   62   63   64   ...   93
    636
    };
    Хотя такие аргументы в объявлениях функций-членов разрешаются во всей области видимости класса, программа будет считаться ошибочной, если он ссылается на нестатический член. Нестатический член должен быть привязан к объекту своего класса или к указателю на такой объект, иначе использовать его нельзя. Употребление подобных членов в качестве аргументов по умолчанию нарушает это ограничение. Если переписать предыдущий пример так:
    }; то имя аргумента по умолчанию разрешается нестатическим членом bkground, а это считается ошибкой.
    Определения членов класса, появляющиеся вне его тела, – это еще один пример части программы, которая находится в области видимости класса. В ней имена членов распознаются несмотря на то, что оператор доступа или оператор разрешения области видимости при обращении к ним не применяется. Как же разрешаются имена в определениях членов?
    Как правило, если такое определение появляется вне тела, то часть программы, следующая за именем определяемого члена, считается находящейся в области видимости класса вплоть до конца определения члена. Вынесем определение оператора operator[]()
    из класса String:
    } class Screen { public:
    // bkground относится к статическому члену,
    // объявленному позже в определении класса
    Screen& clear( char = bkground ); private: static const char bkground = '#'; class Screen { public:
    // ...
    // ошибка: bkground - нестатический член
    Screen& clear( char = bkground ); private: const char bkground = '#'; class String { public: typedef int index_type; char& operator[]( index_type ); private: char *_string;
    };
    // в operator[]() есть обращения к index_type и _string inline char& operator[]( index_type elem )
    { return _string[ elem ];

    С++ для начинающих
    637
    Обратите внимание, что в списке параметров встречается typedef index_type без квалифицирующего имени класса String::.Текст, следующий за именем члена
    String::operator[]
    и до конца определения функции, находится в области видимости класса. Объявленные в этой области типы рассматриваются при разрешении имен типов, использованных в списке параметров функции-члена.
    Определения статических данных-членов также появляются вне определения класса. В них часть программы, следующая за именем статического члена вплоть до конца определения, считается находящейся в области видимости класса. Например, инициализатор статического члена может непосредственно, без соответствующих операторов, ссылаться на члены класса: double Account::_interestRate = initInterest();
    Инициализатор
    _interestRate вызывает статическую функцию-член
    Account::initInterest()
    несмотря на то, что ее имя не квалифицировано именем класса.
    Не только инициализатор, но и все, что следует за именем статического члена
    _interestRate до завершающей точки с запятой, находится в области видимости класса
    Account
    . Поэтому в определении статического члена name может быть обращение к члену класса nameSize: const char Account::name[nameSize] = "Savins Account";
    Хотя член nameSize не квалифицирован именем класса Account, определение name не является ошибкой, так как оно находится в области видимости своего класса и может ссылаться на его члены после того, как компилятор прочитал Account::name.
    В определении члена, которое появляется вне тела, часть программы перед определяемым именем не находится в области видимости класса. При обращении к члену в этой части следует пользоваться оператором разрешения области видимости. Например, если типом статического члена является typedef Money, определенный в классе Account, то имя
    Money должно быть квалифицировано, когда статический член данных определяется вне тела класса: class Account:
    // ... private: static double _interestRate; static double initInterestRate();
    };
    // ссылается на Account::initInterest() class Account:
    // ... private: static const int nameSize = 16; static const char name[nameSize];
    // nameSize не квалифицировано именем класса Account

    С++ для начинающих
    638
    Account::Money Account::_interestRate = initInterest();
    С каждым классом ассоциируется отдельная область видимости, причем у разных классов эти области различны. К членам одного класса нельзя напрямую обращаться в определениях членов другого класса, если только один из них не является для второго базовым. (Наследование и базовые классы рассматриваются в главах 17 и 18.)
    13.9.1.
    Разрешение имен в области видимости класса
    Конечно, имена, используемые в области видимости класса, не обязаны быть именами членов класса. В процессе разрешения в этой области ведется поиск имен, объявленных и в других областях. Если имя, употребленное в области видимости класса, не разрешается именем члена класса, то компилятор ищет его в областях, включающих определение класса или члена. В этом подразделе мы покажем, как разрешаются имена, встречающиеся в области видимости класса.
    Имя, использованное внутри определения класса (за исключением определений встроенных функций-членов и аргументов по умолчанию), разрешается следующим образом:
    1. Просматриваются объявления членов класса, появляющиеся перед употреблением имени.
    2. Если на шаге 1 разрешение не привело к успеху, то просматриваются объявления в пространстве имен перед определением класса. Напомним, что глобальная область видимости – это тоже область видимости пространства имен. (О пространствах имен речь шла в разделе 8.5.)
    Например:
    };
    Сначала компилятор ищет объявление Money в области видимости класса Account. При этом учитываются только те объявления, которые встречаются перед использованием
    Money
    . Поскольку таких объявлений нет, далее поиск ведется в глобальной области видимости. Объявление глобального typedef Money найдено, именно этот тип и используется в объявлениях _interestRate и initInterest(). class Account { typedef double Money;
    //... private: static Money _interestRate; static Money initInterest();
    };
    // Money должно быть квалифицировано именем класса Account:: typedef double Money; class Account {
    // ... private: static Money _interestRate; static Money initInterest();
    // ...

    С++ для начинающих
    639
    Имя, встретившееся в определении функции-члена класса, разрешается следующим образом:
    1. Сначала просматриваются объявления в локальных областях видимости функции- члена. (О локальных областях видимости и локальных объявлениях говорилось в разделе 8.1.)
    2. Если шаг 1 не привел к успеху, то просматриваются объявления для всех членов класса.
    3. Если и этого оказалось недостаточно, просматриваются объявления в пространстве имен перед определением функции-члена.
    Имена, встречающиеся в теле встроенной функции-члена, разрешаются так:
    };
    В поисках объявления имени _height, которое встретилось в определении конструктора
    Screen
    , компилятор просматривает локальную область видимости функции и находит его там. Следовательно, это имя относится к объявлению параметра.
    Если бы такое объявление не было найдено, компилятор начал бы поиск в области видимости класса Screen, просматривая все объявления его членов, пока не встретится объявление члена _height. Говорят, что имя члена _height скрыто объявлением параметра конструктора, но его можно использовать в теле конструктора, если квалифицировать имя члена именем его класса или явно использовать указатель this:
    };
    Если бы не были найдены ни объявление параметра, ни объявление члена, компилятор стал бы искать их в объемлющих областях видимости пространств имен. В нашем примере в глобальной области видимости просматриваются объявления, которые расположены перед определением класса Screen. В результате было бы найдено объявление глобального объекта _height. Говорят, что такой объект скрыт за int _height; class Screen { public:
    Screen( int _height ) {
    _height = 0; // к чему относится _height? К параметру
    } private: short _height; int _height; class Screen { public:
    Screen( long _height ) { this->_height = 0; // относится к Screen::_height
    // тоже правильно:
    // Screen::_height = 0;
    } private: short _height;

    С++ для начинающих
    640
    объявлением члена класса, однако его можно использовать в теле конструктора, если квалифицировать оператором разрешения глобальной области видимости:
    };
    Если конструктор объявлен вне определения класса, то на третьем шаге разрешения имени просматриваются объявления в глобальной области видимости, которые встретились перед определением класса Screen, а также перед определением функции- члена:
    }
    Обратите внимание, что объявление глобальной функции verify() невидимо до определения класса Screen. Однако на третьем шаге разрешения имени просматриваются объявления в областях видимости пространств имен, видимые перед определением члена, поэтому нужное объявление обнаруживается.
    Имя, встретившееся в определении статического члена класса, разрешается следующим образом:
    1. Просматриваются объявления всех членов класса.
    2. Если шаг 1 не привел к успеху, то просматриваются объявления, расположенные в областях видимости пространств имен перед определением статического члена, а не только предшествующие определению класса.
    Упражнение 13.18
    Назовите те части программы, которые находятся в области видимости класса. int _height; class Screen { public:
    Screen( long _height ) {
    ::_height = 0; // относится к глобальному объекту
    } private: short _height; class Screen { public:
    // ... void setHeight( int ); private: short _height;
    }; int verify(int); void Screen::setHeight( int var ) {
    // var: относится к параметру
    // _height: относится к члену класса
    // verify: относится к глобальной функции
    _height = verify( var );

    С++ для начинающих
    641
    Упражнение 13.19
    Назовите те части программы, которые находятся в области видимости класса и для которых при разрешении имен просматривается полная область (т.е. принимаются во внимание все члены, объявленные в теле класса).
    Упражнение 13.20
    К каким объявлениям относится имя Type при использовании в теле класса Exersise и в определении его функции-члена setVal()? (Напоминаем, что разные вхождения могут относиться к разным объявлениям.) К каким объявлениям относится имя initVal при употреблении в определении функции-члена setVal()?
    }
    Определение функции-члена setVal() ошибочно. Можете ли вы сказать, почему?
    Внесите необходимые изменения, чтобы в классе Exercise использовался глобальный typedef Type и глобальная функция initVal().
    13.10.
    Вложенные классы A
    Класс, объявленный внутри другого класса, называется вложенным. Он является членом объемлющего класса, а его определение может находиться в любой из секций public, private или protected объемлющего класса.
    Имя вложенного класса известно в области видимости объемлющего класса, но ни в каких других областях. Это означает, что оно не конфликтует с таким же именем, объявленным в объемлющей области видимости. Например: typedef int Type;
    Type initVal(); class Exercise { public:
    // ... typedef double Type;
    Type setVal( Type );
    Type initVal(); private: int val;
    };
    Type Exercise::setVal( Type parm ) { val = parm + initVal();

    С++ для начинающих
    642
    };
    Для вложенного класса допустимы такие же виды членов, как и для невложенного:
    };
    Закрытым называется член, который доступен только в определениях членов и друзей класса. У объемлющего класса нет права доступа к закрытым членам вложенного. Чтобы в определениях членов List можно было обращаться к закрытым членам ListItem, класс ListItem объявляет List как друга. Равно и вложенный класс не имеет никаких специальных прав доступа к закрытым членам объемлющего класса. Если бы нужно было разрешить ListItem доступ к закрытым членам класса List, то в объемлющем классе List следовало бы объявить вложенный класс как друга. В приведенном выше примере этого не сделано, поэтому ListItem не может обращаться к закрытым членам
    List
    Объявление ListItem открытым членом класса List означает, что вложенный класс можно использовать как тип во всей программе, в том числе и за пределами определений членов и друзей класса. Например: class Node { /* ... */ } class Tree { public:
    // Node инкапсулирован внутри области видимости класса Tree
    //
    В этой области Tree::Node скрывает ::Node class Node {...};
    // правильно: разрешается в пользу вложенного класса: Tree::Node
    Node *tree;
    };
    // Tree::Node невидима в глобальной области видимости
    // Node разрешается в пользу глобального объявления Node
    Node *pnode; class List { public:
    // Node инкапсулирован внутри области видимости класса List
    //
    В этой области List::Node скрывает ::Node class Node {...};
    // правильно: разрешается в пользу вложенного класса: List::Node
    Node *list;
    //
    Не идеально, будем улучшать class List { public: class ListItem { friend class List; // объявление друга
    ListItem( int val=0 ); // конструктор
    ListItem *next; // указатель на собственный класс int value;
    };
    // ... private:
    ListItem *list;
    ListItem *at_end;

    С++ для начинающих
    643
    List::ListItem *headptr;
    Это дает более широкую область видимости, чем мы планировали. Вложенный ListItem поддерживает абстракцию класса List и не должен быть доступен во всей программе.
    Поэтому лучше объявить вложенный класс ListItem закрытым членом List:
    };
    Теперь тип ListItem доступен только из определений членов и друзей класса List, поэтому все члены класса ListItem можно сделать открытыми. При таком подходе объявление List как друга ListItem становится ненужным. Вот новое определение класса List:
    };
    Конструктор ListItem не задан как встроенный внутри определения класса и, следовательно, должен быть определен вне него. Но где именно? Конструктор класса
    ListItem не является членом List и, значит, не может быть определен в теле последнего; его нужно определить в глобальной области видимости – той, которая содержит определение объемлющего класса. Когда функция-член вложенного класса не определяется как встроенная в теле, она должна быть определена вне самого внешнего из объемлющих классов.
    Вот как могло бы выглядеть определение конструктора ListItem. Однако показанный ниже синтаксис в глобальной области видимости некорректен:
    // правильно: объявление в глобальной области видимости
    //
    Не идеально, будем улучшать class List { public:
    // ... private: class ListItem {
    // ...
    };
    ListItem *list;
    ListItem *at_end;
    // так лучше class List { public:
    // ... private:
    //
    Теперь ListItem закрытый вложенный тип class ListItem {
    // а его члены открыты public:
    ListItem( int val=0 );
    ListItem *next; int value;
    };
    ListItem *list;
    ListItem *at_end;

    С++ для начинающих
    644
    ListItem:: ListItem( int val ) { ... }
    Проблема в том, что имя ListItem отсутствует в глобальной области видимости. При использовании его таким образом следует указывать, что ListItem – вложенный класс в области видимости List. Это делается путем квалификации имени ListItem именем объемлющего класса. Следующая конструкция синтаксически правильна:
    }
    Заметим, что квалифицировано только имя вложенного класса. Первый квалификатор
    List::
    именует объемлющий класс и квалифицирует следующее за ним имя вложенного
    ListItem
    . Второе вхождение ListItem – это имя конструктора, а не вложенного класса.
    В данном определении имя члена некорректно:
    }
    Если бы внутри ListItem был объявлен статический член, то его определение также следовало бы поместить в глобальную область видимости. Имя этого члена могло бы выглядеть так: int List::ListItem::static_mem = 1024;
    Обратите внимание, что функции-члены и статические данные-члены не обязаны быть открытыми членами вложенного класса для того, чтобы их можно было определить вне его тела. Закрытые члены ListItem также определяются в глобальной области видимости.
    Вложенный класс разрешается определять вне тела объемлющего. Например, определение
    ListItem могло бы находиться и в глобальной области видимости: class List { public:
    // ... private: class ListItem { public:
    ListItem( int val=0 );
    // ...
    };
    };
    // ошибка: ListItem вне области видимости
    // имя вложенного класса квалифировано именем объемлющего
    List::ListItem::ListItem( int val ) { value = val; next = 0;
    // ошибка: конструктор называется ListItem, а не List::ListItem
    List::ListItem::List::ListItem( int val ) { value = val; next = 0;

    С++ для начинающих
    645
    };
    В глобальном определении имя вложенного ListItem должно быть квалифицировано именем объемлющего класса List. Заметьте, что объявление ListItem в теле List опустить нельзя. Определение вложенного класса не может быть задано в глобальной области видимости, если предварительно оно не было объявлено членом объемлющего класса. Но при этом вложенный класс не обязательно должен быть открытым членом объемлющего.
    Пока компилятор не увидел определения вложенного класса, разрешается объявлять лишь указатели и ссылки на него. Объявления членов list и at_end класса List правильны несмотря на то, что ListItem определен в глобальной области видимости, поскольку оба члена – указатели. Если бы один из них был объектом, то его объявление в классе List привело бы к ошибке компиляции:
    };
    Зачем определять вложенный класс вне тела объемлющего? Возможно, он поддерживает некоторые детали реализации ListItem, а нам нужно скрыть их от пользователей класса
    List
    . Поэтому мы помещаем определение вложенного класса в заголовочный файл, содержащий интерфейс List. Таким образом, определение ListItem может находиться лишь внутри исходного файла, включающего реализацию класса List и его членов.
    Вложенный класс можно сначала объявить, а затем определить в теле объемлющего. Это позволяет иметь во вложенных классах члены, ссылающиеся друг на друга: class List { public:
    // ... private:
    // объявление необходимо class ListItem;
    ListItem *list;
    ListItem *at_end;
    };
    // имя вложенного класса квалифицировано именем объемлющего класса class List::ListItem { public:
    ListItem( int val=0 );
    ListItem *next; int value; class List { public:
    // ... private:
    // объявление необходимо class ListItem;
    ListItem *list;
    ListItem at_end; // ошибка: неопределенный вложенный класс ListItem

    С++ для начинающих
    1   ...   57   58   59   60   61   62   63   64   ...   93


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