С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 8
Текст из файла (страница 8)
Все управление циклом for осуществляетсяинструкциями в круглых скобках за ключевым словом for. Первая присваиваетначальное значение переменной index. Это производится один раз перед началом цикла:index = 0;Вторая инструкция:index < 10;представляет собой условие окончания цикла. Оно проверяется в самом начале каждойитерации цикла. Если результатом этой инструкции является true, то выполнение циклапродолжается; если же результатом является false, цикл заканчивается. В нашемпримере цикл продолжается до тех пор, пока значение переменной index меньше 10. Накаждой итерации цикла выполняется некоторая инструкция или группа инструкций,составляющих тело цикла.
В нашем случае это инструкцияia[index] = index;Третья управляющая инструкция цикла++indexвыполняется в конце каждой итерации, по завершении тела цикла. В нашем примере этоувеличение переменной index на единицу. Мы могли бы записать то же действие какindex = index + 1но С++ дает возможность использовать более короткую (и более наглядную) формузаписи. Этой инструкцией завершается итерация цикла. Описанные действияповторяются до тех пор, пока условие цикла не станет ложным.Вторая инструкция for в нашем примере печатает элементы массива. Она отличается отпервой только тем, что в ней переменная index уменьшается от 9 до 0. (Подробнееинструкция for рассматривается в главе 5.)Несмотря на то, что в С++ встроена поддержка для типа данных “массив”, она весьмаограничена.
Фактически мы имеем лишь возможность доступа к отдельным элементаммассива. С++ не поддерживает абстракцию массива, не существует операций надмассивами в целом, таких, например, как присвоение одного массива другому илисравнение двух массивов на равенство, и даже такой простой, на первый взгляд,операции, как получение размера массива. Мы не можем скопировать один массив вдругой, используя простой оператор присваивания:32С++ для начинающихint array0[10]; array1[10];...array0 = array1; // ошибкаВместо этого мы должны программировать такую операцию с помощью цикла:for (int index=0; index<10; ++index)array0[index] = array1[index];Массив “не знает” собственный размер.
Поэтому мы должны сами следить за тем, чтобыслучайно не обратиться к несуществующему элементу массива. Это становится особенноутомительным в таких ситуациях, как передача массива функции в качестве параметра.Можно сказать, что этот встроенный тип достался языку С++ в наследство от С ипроцедурно-ориентированной парадигмы программирования. В оставшейся части главымы исследуем разные возможности “улучшить” массив.Упражнение 2.1Как вы думаете, почему для встроенных массивов не поддерживается операцияприсваивания? Какая информация нужна для того, чтобы поддержать эту операцию?Упражнение 2.2Какие операции должен поддерживать “полноценный” массив?2.2.
Динамическое выделение памяти и указателиПрежде чем углубиться в объектно-ориентированную разработку, нам придется сделатьнебольшое отступление о работе с памятью в программе на С++. Мы не сможем написатьсколько-нибудь сложную программу, не умея выделять память во время выполнения иобращаться к ней.В С++ объекты могут быть размещены либо статически – во время компиляции, либодинамически – во время выполнения программы, путем вызова функций из стандартнойбиблиотеки. Основная разница в использовании этих методов – в их эффективности игибкости.
Статическое размещение более эффективно, так как выделение памятипроисходит до выполнения программы, однако оно гораздо менее гибко, потому что мыдолжны заранее знать тип и размер размещаемого объекта. К примеру, совсем не просторазместить содержимое некоторого текстового файла в статическом массиве строк: намнужно заранее знать его размер. Задачи, в которых нужно хранить и обрабатыватьзаранее неизвестное число элементов, обычно требуют динамического выделения памяти.До сих пор во всех наших примерах использовалось статическое выделение памяти.Скажем, определение переменной ivalint ival = 1024;заставляет компилятор выделить в памяти область, достаточную для храненияпеременной типа int, связать с этой областью имя ival и поместить туда значение 1024.Все это делается на этапе компиляции, до выполнения программы.С объектом ival ассоциируются две величины: собственно значение переменной, 1024 вданном случае, и адрес той области памяти, где хранится это значение.
Мы можемобращаться к любой из этих двух величин. Когда мы пишем:int ival2 = ival + 1;33С++ для начинающихто обращаемся к значению, содержащемуся в переменной ival: прибавляем к нему 1 иинициализируем переменную ival2 этим новым значением, 1025. Каким же образомобратиться к адресу, по которому размещена переменная?С++ имеет встроенный тип “указатель”, который используется для хранения адресовобъектов.
Чтобы объявить указатель, содержащий адрес переменной ival, мы должнынаписать:int *pint; // указатель на объект типа intСуществует также специальная операция взятия адреса, обозначаемая символом &. Еерезультатом является адрес объекта. Следующий оператор присваивает указателю pintint *pint;адрес переменной ival:pint = &ival; // pint получает значение адреса ivalМы можем обратиться к тому объекту, адрес которого содержит pint (ival в нашемслучае), используя операцию разыменования, называемую также косвенной адресацией.Эта операция обозначается символом *.
Вот как можно косвенно прибавить единицу кival, используя ее адрес:*pint = *pint + 1; // неявно увеличивает ivalЭто выражение производит в точности те же действия, что иival = ival + 1; // явно увеличивает ivalВ этом примере нет никакого реального смысла: использование указателя для косвеннойманипуляции переменной ival менее эффективно и менее наглядно. Мы привели этотпример только для того, чтобы дать самое начальное представление об указателях. Вреальности указатели используют чаще всего для манипуляций с динамическиразмещенными объектами.Основные отличия между статическим и динамическим выделением памяти таковы:•статические объекты обозначаются именованными переменными, и действиянад этими объектами производятся напрямую, с использованием их имен.Динамические объекты не имеют собственных имен, и действия над нимипроизводятся косвенно, с помощью указателей;•выделение и освобождение памяти под статические объекты производитсякомпилятором автоматически.
Программисту не нужно самому заботиться об этом.Выделение и освобождение памяти под динамические объекты целиком иполностью возлагается на программиста. Это достаточно сложная задача, прирешении которой легко наделать ошибок. Для манипуляции динамическивыделяемой памятью служат операторы new и delete.Оператор new имеет две формы. Первая форма выделяет память под единичный объектопределенного типа:int *pint = new int(1024);34С++ для начинающихЗдесь оператор new выделяет память под безымянный объект типа int, инициализируетего значением 1024 и возвращает адрес созданного объекта. Этот адрес используется дляинициализации указателя pint.
Все действия над таким безымянным объектомпроизводятся путем разыменовывания данного указателя, т.к. явно манипулироватьдинамическим объектом невозможно.Вторая форма оператора new выделяет память под массив заданного размера, состоящийиз элементов определенного типа:int *pia = new int[4];В этом примере память выделяется под массив из четырех элементов типа int.
Ксожалению, данная форма оператора new не позволяет инициализировать элементымассива.Некоторую путаницу вносит то, что обе формы оператора new возвращают одинаковыйуказатель, в нашем примере это указатель на целое. И pint, и pia объявлены совершенноодинаково, однако pint указывает на единственный объект типа int, а pia – на первыйэлемент массива из четырех объектов типа int.Когда динамический объект больше не нужен, мы должны явным образом освободитьотведенную под него память. Это делается с помощью оператора delete, имеющего, как// освобождение единичного объектаdelete pint;// освобождение массиваи new, две формы – для единичного объекта и для массива:delete[] pia;Что случится, если мы забудем освободить выделенную память? Память будетрасходоваться впустую, она окажется неиспользуемой, однако возвратить ее системенельзя, поскольку у нас нет указателя на нее.
Такое явление получило специальноеназвание утечка памяти. В конце концов программа аварийно завершится из-занехватки памяти (если, конечно, она будет работать достаточно долго). Небольшая утечкатрудно поддается обнаружению, но существуют утилиты, помогающие это сделать.Наш сжатый обзор динамического выделения памяти и использования указателей,наверное, больше породил вопросов, чем дал ответов.
В разделе 8.4 затронутые проблемыбудут освещены во всех подробностях. Однако мы не могли обойтись без этогоотступления, так как класс Array, который мы собираемся спроектировать впоследующих разделах, основан на использовании динамически выделяемой памяти.Упражнение 2.3(a) int ival = 1024;(b) int *pi = &ival;(c) int *pi2 = new int(1024);Объясните разницу между четырьмя объектами:(d) int *pi3 = new int[1024];Упражнение 2.435С++ для начинающихЧто делает следующий фрагмент кода? В чем состоит логическая ошибка? (Отметим, чтооперация взятия индекса ([]) правильно применена к указателю pia. Объяснение этомуint *pi = new int(10);int *pia = new int[10];while ( *pi < 10 ) {pia[*pi] = *pi;*pi = *pi + 1;}delete pi;факту можно найти в разделе 3.9.2.)delete[] pia;2.3.
Объектный подходВ этом разделе мы спроектируем и реализуем абстракцию массива, используя механизмклассов С++. Первоначальный вариант будет поддерживать только массив элементовтипа int. Впоследствии при помощи шаблонов мы расширим наш массив для поддержкилюбых типов данных.Первый шаг состоит в том, чтобы определить, какие операции будет поддерживать нашмассив. Конечно, было бы заманчиво реализовать все мыслимые и немыслимыеоперации, но невозможно сделать сразу все на свете. Поэтому для начала определим то,что должен уметь наш массив:1. обладать некоторыми знаниями о самом себе.
Пусть для начала это будет знаниесобственного размера;2. поддерживать операцию присваивания и операцию сравнения на равенство;3. отвечать на некоторые вопросы, например: какова величина минимального имаксимального элемента; содержит ли массив элемент с определенным значением;если да, то каков индекс первого встречающегося элемента, имеющего это значение;4.