Г. Шилтд - Самоучитель C++ (DJVU) (1114955), страница 18
Текст из файла (страница 18)
Используя следующее объявление класса, создайте массив из 10 элементов, инициализируйте переменную ппа значениями от 1 до 10, а переменную я(г — квадратом пцщ. ((1пс1ас1е <1оя1геап> ияьпд аатвеярасе ясс1; Глава 4. Массивы, указатели и ссылки с(авв зс(алагез (пг пшп, ас(г; рпа11с: вяааГЕЗ (1ПГ а, тпс Ы ( пот = а; зг(г = Ь; ) чоуа зйох() (ооп «п(лл « ' ' «зс(х. « "М"; 3. Измените инициализацию переменной с)) из упражнения 1 так, чтобы использовать ее длинную форму (т. е. чтобы конструктор 1е11егя явно вызывался в списке инициализации). 4.2. Использование указателей на объекты Как отмечалось в главе 2, доступ к объекту можно получить через указатель на этот объект.
Как вы знаете, при использовании указателя на объект к членам объекта обращаются не с помощью оператора точка 1.), а с помощью оператора стрелка (->). Арифметика указателей на объект аналогична арифметике указателей на данные любого другого типа: она выполняется относительно объекта. Например, если указатель на объект инкрементируется, то он начинает указывать на следующий объект. Если указатель на объект декрементируется, то он начинает указывать на предыдущий объект. ":.Примеры11, !. Пример арифметики указателей на объекты; // Указатели иа объекты Ипс1пс(а <ъсзггеап~> пз1па патаеарасе згг(; с)ава заптр ( Ьзс а, Ь; рггЬ11с: зал)р (ъпе и, )пг тп) ( а = и; Ь = аи 1пс асг а() ( гсгпгп а; (пг даг ь() ( гегпгп Ь; ) ъпе л)атп() затр оЬ(4] -- ( авар(1, 2). 1?5 Глава 4.
Массивы, указатели и ссылки Функции ПО автоматически передается указатель на объект о(?. Этот указа- тель и называется й(а. Важно понимать, что указатель й(а передается только функциям-членам. Дружественным функциям указатель 1й(а не передается. 1. Как вы уже видели, если функция-член работает с другим членом того же класса, она делает это без уточнения имени класса или объекта. Например, исследуйте эту короткую программу, в которой создается простой класс !пчеп1огу: /У Демонстрация указателя т??1а (1 ьпс1п<?е <).оаггеал> ()Ьпс1ие?е <сагггп9> ца)пя патпеарасе аМ; с1аяа ?пчеп?оту ( с(тат веет(20); с?оиЫе соат; (п? оп ЬапФ роЬ?хс: ?пчепгогу (с?таг *1, доиЫе с, )пт о) ( асгсру (ьеип~ 3.) ' соя? = с„. оп Ьапб = о; чои? аЬои(); чо1с? з.тяепгогу::а??ои() соос « сепп соие « ": З" « соег.; соне « " Оп Ьапс?: " « оп Ьапо « "~п"; ?пе ?паап() ?тюетйогу оЬ("игепсЬ", 4.95, 4)) оЬ.аЬои(); гесогп О; Самоучитель Сн- 11б Обратите внимание, что внутри конструктора 1пчел1огу() и функции-члена вЬоиО переменные-члены !1еп), соя1 и оп Ьапй упоминаются явно.
Так происходит потому, что функция-член может вызываться только в связи с объектом. Следовательно, в данном случае компилятор "знает", данные какого объекта имеются в виду. Однако имеется еще более тонкое объяснение. Если вызывается функция- член, ей автоматически передается указатель 1Ь(я на объект, который является источником вызова. Таким образом, предыдущую программу можно переписать так: // Демонстрация указателя ?Ь)я ()тпс) цс)е <1сясгеап> () тпс1цс)е <сягг) пя> ця)пя пагпеярасе яМ; с1аяя )пчепгогу ( сЬаг 1Ьет[20); доцЫе соя(; )пг сп Ьагс(; рцЬ'тс: .' пчел?сгу(сцат *1, г)оцЫе с, 1пг с) ясгсру(ьь(я->тгет, 1); // доступ к члену ЕЬтя->созе = с; // через ЬЬья->сг.
Ьапг) = о; // указатель ()з)я ) чо1г) яцон() чоЫ 1пчептсгу:: яппи () ( ссцс « ЬЬтя->(секо /( использование жцтя для доступа к членам сон « ": $" « Е)11я->соя?; сон? « " Оп Ьапс(: " « сЬ'я->оп Ьапс « "~п"; )и( гпя1п () ( )пчепгогу оЬ("игепсЬ", 4. 95, )); оЬ. яЬон(); ге(цгп О; Здесь к переменным-членам объекта оЬ осуществляется прямой доступ через указатель 1Ыя. Таким образом, внутри функции яЪотчО следующие две инструкции равнозначны: Глава 4. Массивы, указатели и ссылки сове = .гз.гз; ЬЬЬя->соя1 = 123.23; На самом деле первая форма — это сокращенная запись второй. Пока, наверное, еще не родился программист С++, который бы использовал указатель 1)пв для доступа к членам класса так, как было показано, поскольку сокращенная форма намного проще, но здесь важно понимать, что под этим сокращением подразумевается.
Использовать указатель Ф)з можно по-разному. Он особенно полезен при перегрузке операторов. Такое его применение более подробно будет изучаться в главе 6. На данный момент важно то, что по умолчанию всем функциям-членам автоматически передается указатель на вызывающий объект. 1. Дана следующая программа, переделайте все соответствующие обращения к членам класса так, чтобы в них явно присутствовал указатель 1111з. Й(пс!иое <воя.леал> ия(па патпеврасе яЫ; с1авв тпус1аяя (пг а, Ь; р.~Ь1хс: тпус1аяв (ьпе п, 1иг ит) ( а =- и; Ь = ти; ) )пг асЫ() ( тегитп а + Ь; ) уоЫ яиои() т уоЖ ~пус1аяя:: япои() (пт Ь; л = ае(е((); // вызов функции-члена сопя « в «"~п"; 1пг гааги () пус1аяя оЬ(10, 14); оЬ. яЬои () г геяппп О; УУВ Самоучитель С++ 4.4.
Операторы пеи~ и бе)е1е До сих пор при выделении динамической памяти вы использовали функцию пи!1ос0, а при освобождении памяти — функцию 1гее0. Вместо этих стандартных функций в С++ стал применяться более безопасный и удобный способ выделения и освобождения памяти. Выделить память можно с помощью оператора пеж, а освободить ее с помошью оператора йе1е1е. Ниже представлена основная форма этих операторов: р-айаг = пеи гуре; бетеле р-айаг; Здесь гуре — это спецификатор типа объекта, для которого вы хотите выделить память, ар-гаг — указатель на этот тип. Меч — это оператор, который возвращает указатель на динамически выделяемую память, достаточную для хранения объекта типа фре. Оператор де!е1е освобождает эту память, когда в ней отпадает необходимость.
Вызов оператора ве1е1е с неправильным указателем может привести к разрушению системы динамического выделения памяти и возможному краху программы. Если свободной памяти недостаточно для выполнения запроса, произойдет одно из двух: либо оператор пев возвратит нулевой указатель, либо будет сгенерирована исключительная ситуация. (Исключительные ситуации и обработка исключительных ситуаций описываются далее в этой книге.
Коротко об исключительной ситуации можно сказать следующее — это динамическая ошибка, которую можно обработать определенным образом.) В соответствии с требованиями языка Бгапйагт С++ по умолчанию оператор пев должен генерировать исключительную ситуацию при невозможности удовлетворить запрос на выделение памяти.
Если ваша программа не обрабатывает эту исключительную ситуацию, выполнение программы прекращается. К сожалению, точные требования к тому, какие действия должны выполняться, если оператор пеи не в состоянии удовлетворить запрос на выделение памяти, за последние годы менялись несколько раз. Поэтому вполне возможно, что в вашем компиляторе реализация оператора пеи выполнена не так, как это предписано стандартом Яапг!аг! С++.
Когда С++ только появился, при невозможности удовлетворить запрос на выделение памяти оператор пеи возврашал нулевой указатель. В дальнейшем эта ситуация изменилась, и при неудачной попытке выделения памяти оператор пеи стал генерировать исключительную ситуацию. В конце концов было принято решение, что при неудачной попытке выделения памяти оператор пеа будет генерировать исключительную ситуацию по умолчанию, а возвращение нулевого указателя останется в качестве возможной опции.
Таким образом, реализация оператора пеи оказалась разной у разных производителей компиляторов. К примеру, во время написания этой книги в компиляторе М1сгою11 Уьиа! С++ при невозможности удовлетворить запрос на выделение памяти оператор пев возврашал нулевой указатель, а в компиляторе Вог1апг! С++ генерировал исключительную ситуацию. Хотя в буду- Глава 4.
Массивы, указатели и ссылки щем во всех компиляторах оператор пем будет реализован в соответствии со стандартом Ыапдап. С++, в настоящее время единственным способом узнать, какие именно действия он выполняет при неудачной попытке выделить память, является чтение документации на компилятор. Поскольку имеется два возможных способа, которыми оператор пев может сигнализировать об ошибке выделения памяти, и поскольку в разных компиляторах он может быть реализован по-разному, в примерах программ этой книги сделана попытка удовлетворить оба требования.
Во всех примерах значение возвращаемого оператором пеи указателя проверяется на равенство нулю. Такое значение указателя обрабатывается теми компиляторами, в которых при неудачной попытке выделить память оператор пев возвращает нуль, хотя никак не влияет на те, в которых оператор пев генерирует исключительную ситуацию. Если в вашем компиляторе во время выполнения программы при неудачной попытке выделить память оператор пеи сгенерирует исключительную ситуацию, то такая программа просто завершится.
В дальнейшем, когда вы ближе познакомитесь с обработкой исключительных ситуаций, мы вернемся к оператору пеж, и вы узнаете, как лучше обрабатывать неудачные попытки выделения памяти. Вы также узнаете об альтернативной форме оператора пем, который при наличии ошибки всегда возвращает нулевой указатель. И последнее замечание: ни один из примеров программ этой книги не должен вести к ошибке выделения памяти при выполнении оператора пев, поскольку в каждой конкретной программе выделяется лишь считанное число байтов.
Хотя операторы пеи и йе1е1е выполняют сходные с функциями шаПосО и 1гееО задачи, они имеют несколько преимуществ перед ними. Во-первых, оператор пев автоматически выделяет требуемое для хранения объекта заданного типа количество памяти. Вам теперь не нужно использовать киео1; например, для подсчета требуемого числа байтов. Это уменьшает вероятность ошибки. Во-вторых, оператор пеи автоматически возвращает указатель на заданный тип данных.
Вам не нужно выполнять приведение типов, операцию, которую вы делали, когда выделяли память, с помощью функции пга11осО (см. следующее замечание). В-третьих, как оператор пев, так и оператор Йе1е1е можно перегружать, что дает возможность простой реализации вашей собственной, привычной модели распределения памяти. В-четвертых, допускается инициализация объекта, для которого динамически выделена память.