Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Передача параметра по ссылке
. В С для организации вызова по ссылке программисты используют указатели и операцию косвенной адресации. Если вызывается функция, аргументы которой должны изменяться, то в этом случае ей передаются адреса аргументов. Обычно для этой цели применяется операция взятия адреса & к переменной, значение которой будет изменяться. Если в качестве аргумента в функцию передается массив, то функция получает адрес начала массива и использовать операцию & в этом случае не нужно. Когда адрес переменной передан функции, то для изменения ее значения может быть использована операция косвенной адресации *. Если функция может получить в качестве аргумента одномерный массив, то в функциональном заголовке и в прототипе функции соответствующий параметр может быть определен как указатель. Компилятор не делает различия между функцией, имеющей параметром указатель, и функцией, которая получает в качестве аргумента одномерный массив. Однако понятно, что функция должна «знать», когда она получает массив и когда ссылку на одиночную переменную. Когда компилятор встречает одномерный массив в качестве параметра функции, заданный в форме int b[], компилятор преобразует этот параметр к виду int *b. Эти две формы являются взаимозаменяемыми. Массивы и указатели в С тесно связаны друг с другом и практически являются взаимозаменяемыми. Имя массива можно рассматривать как указатель-константу. А над указателями можно выполнять различные операции, в том числе использовать с указателем индексные выражения. В качестве примера объявим целочисленный массив b[5] и переменную-указатель bPtr на целое. Так как имя массива (если с ним не указан индекс) является указателем на первый элемент массива, мы можем присвоить указателю bPtr адрес первого элемента массива b при помощи оператора присваивания bPtr = b; Этот оператор эквивалентен следующему оператору, в котором используется операция взятия адреса первого элемента массива: bPtr = & b[0]; Альтернативный способ ссылки на элемент массива b[3], использующий выражение с указателем, представлен в следующем операторе: * (bPtr + 3) Константа 3 в приведенном выражении называется смещением. Когда указатель ссылается на начало массива, величина смещения указывает, на какой элемент массива производится ссылка; значение смещения равно значению индекса массива. Приведенный способ записи носит название нотации указатель/смещение. В этом выражении использованы круглые скобки, потому что операция * имеет больший приоритет, чем операция +. Без круглых скобок в вышеупомянутом выражении значение 3 было бы прибавлено к значению выражения *bPtr (т.е. число 3 будет прибавлено к элементу b[0], так как bPtr указывает на начало массива). Поскольку на значение элемента массива можно сослаться при помощи выражения с указателем, адрес элемента & b[3] представляется выражением bPtr + 3 Имя массива может рассматриваться как указатель, так что его можно использовать в выражениях арифметики указателей. Указатели, в свою очередь, могут быть использованы вместо имен массивов в индексных выражениях. Например, выражение bPtr[1] представляет собой ссылку на элемент массива b[1]. Такой способ записи можно назвать методом указатель/индекс. Не забудьте, что имя массива - это указатель-константа и он всегда указывает на начало массива. Поэтому выражение b += 3 является недопустимым, так как в нем делается попытка изменить значение начального адреса массива. Массивы могут состоять из указателей. Обычный случай такого массива - это массив строк, который так и называется массив строк. Элементом такого массива является строка, а строки в С являются, по существу, указателями на первый символ строки. Значит, элементами строкового массива являются указатели на начала строк. В качестве примера рассмотрим массив suit, который может пригодится для описания игральных карт. char *suit[4] = {" Hearts", " Diamonds", " Clubs", " Spades" }; Выражение suit[4] в объявлении означает массив из четырех элементов. При помощи char * этот массив объявляется состоящим из указателей на тип char. В массив помещаются четыре значения «Hearts», «Diamonds», «Clubs» и «Spades» ( «Червы», «Бубны», «Трефы» и «Пики» ). Каждое из этих значений хранится в памяти как строка символов с конечным нулем, длиной на один символ больше, чем количество символов, заключенных в кавычки. Строки эти занимают в памяти соответственно 7, 9, 6 и 7 байт. И хотя кажется, что в массив помещаются сами строки, элементами массива являются указатели. Каждый указатель ссылается на первый символ соответствующей строки. Таким образом, хотя массив suit имеет фиксированный размер, он может «хранить» символьные строки произвольной длины. Это еще один пример мощных возможностей структурирования данных в С. Рассматриваемый нами массив карточных мастей можно было бы сделать двумерным: имя каждой масти занимало бы одну строку, а в каждом столбце помещалось бы по одному символу имени масти. При таком подходе все строки массива должны быть одинаковой длины, равной размеру самой длинной строки символов. Это привело бы к неоправданному расходу памяти в случае, когда большинство сохраняемых строк короче, чем самая длинная строка. Указатель на функцию - это переменная, содержащая адрес в памяти, по которому расположена функция. Мы знаем, что имя массива является адресом первого элемента массива. Аналогичным образом имя функции - это адрес начала программного кода функции. Указатели на функции могут быть переданы функциям в качестве аргументов, могут возвращаться функциями, сохраняться в массивах и присваиваться другим указателям на функции. Структуры данных (Описание структур, Инициализация структур, Доступ к элементам структур, Использование структур с функциями, Typedef, Объединения, Структуры, ссылающиеся на себя, Динамическое распределение памяти, Связанные списки, Стеки, Очереди, Деревья) Структуры — это наборы (иногда их называют агрегатами) логически связанных переменных, объединенных под одним именем. В отличие от массивов, которые могут содержать элементы только одного типа, структуры могут состоять из переменных различных типов данных Структуры — это производные типы данных, они создаются из объектов других типов. Рассмотрим следующее описание структуры; struct card{char *face; char *suit; }; Ключевое слово struct определяет структуру. Идентификатор card является именем-этикеткой структуры. Имя-этикетка именует структуру, и используется совместно с ключевым словом struct для объявления переменных типа структуры. Переменные, объявленные внутри скобок структуры, являются элементами структуры. Каждое определение структуры должно заканчиваться точкой с запятой. Определение struct card содержит два элемента типа char * - face и suit. Элементы структуры могут быть переменными основных типов данных (то есть int, float и т.д.), Объявление struct card a, deck[52], *cPtr; объявляет а - переменную типа struct card, deck - массив из 52 элементов типа struct card и cPtr - указатель на struct card. Чтобы инициализировать структуру, после имени переменной в объявлении структуры ставится знак равенства, за которым следует помещенный в фигурные скобки, разделенный запятыми список инициализаторов. Например, объявление struct card a = {" Three", " Hearts" }; создает переменную а типа struct card (структура определена выше) и присваивает элементу face значение " Three", а элементу suit значение " Hearts", Если инициализаторов в списке меньше, чем элементов в структуре, остальным элементам автоматически присваивается значение 0 (или NULL, если элемент — указатель). Для обращения к элементам структур используются две операции: операция элемента структуры (.), также называемая операцией-точкой, и операция указателя структуры (-> ), также называемая операцией-стрелкой. Операция элемента структуры обращается к элементу через имя переменной структуры. Например, для того чтобы напечатать элемент suit структуры а из предыдущего объявления, можно написать оператор printf(" %s", a.suit); Операция указателя структуры, состоящая из знака минус (-) и знака больше (> ) без пробела между ними, обращается к элементу через указатель структуры. Предположим, что переменная aPtr была объявлена как указатель на struct card и ей был присвоен адрес структуры а. Чтобы напечатать элемент suit структуры а при помощи указателя aPtr, напишите оператор printf (" %s", aPtr-> suit); Выражение aPtr-> suit эквивалентно (*aPtr).suit, которое, обращаясь по адресу, содержащемуся в указателе, находит структуру а и обращается к элементу suit, используя операцию элемента структуры. Структуры могут передаваться функциям посредством передачи отдельных элементов структуры, передачи всей структуры или передачи указателя на структуру. Когда структуры или отдельные их элементы передаются функции, они передаются вызовом по значению. Поэтому вызванная функция не может изменять элементы структуры в вызывающей. Чтобы осуществить вызов структуры по ссылке, необходимо передать адрес структуры. Массивы структур, как и все прочие массивы, вызываются по ссылке автоматически. Ключевое слово typedef предоставляет программисту механизм для создания синонимов (или псевдонимов) для ранее определенных типов данных. Часто используют typedef для того, чтобы дать укороченное имя структурному типу. Например, оператор typedef struct card Card; определяет новый тип с именем Card, как синоним типа struct card. Пишущие на С часто используют typedef, чтобы определить тип структуры, при этом отпадает необходимость в имени-этикетке, Создание нового имени с помощью typedef не создает нового типа; typedef просто определяет новое имя для уже существующего типа, которое может использоваться как псевдоним последнего. Объединение - производный тип данных, подобный структуре, элементы которого разделяют одну и ту же область памяти. Элементы объединения могут принадлежать к любому типу. В большинстве случаев объединения содержат два или более типа данных.. Задача программиста - обеспечить, чтобы на данные, хранящиеся в объединении, ссылались как на данные соответствующего типа. Объединение объявляется с помощью ключевого слова union. Формат объявления тот же, что и в случае структуры. Объявление union: union number{ int x; float y; }; означает, что number является типом union сэлементами int x и float у. Определение объединения обычно располагается в программе перед функцией main, поэтому определение может использоваться для объявления переменных во всех функциях программы. Над объединениями можно выполнять следующие операции: присваивание объединения другому объединению того же типа, взятие адреса (& ), доступ к элементам объединения с использованием операций элемента структуры и указателя структуры. Объединения не могут сравниваться по тем же самым причинам, что и структуры. Структуры, ссылающиеся на себя, содержат в качестве элемента указатель, который ссылается на структуру того же типа. Например, определение struct node{int data; struct node *nextPtr; }; описывает тип struct node. Структура типа struct node состоит из двух элементов - целого data и указателя nextPtr. Элемент nextPtr указывает на структуру типа struct node - структуру того же самого типа, что и только что объявленная нами, отсюда и термин «структура, ссылающаяся на себя». Элемент nextPtr иногда называют связкой, т.е. nextPtr можно использовать для того, чтобы связать структуру типа struct node с другой структурой того же типа. Структуры, ссылающиеся на себя, могут связываться вместе для образования полезных структур данных, таких как списки, очереди, стеки и деревья. Создание и использование динамических структур данных требует динамического распределения памяти - возможности получать в процессе исполнения дополнительную память для хранения новых узлов и освобождать блоки памяти, ставшие ненужными. Максимальный размер выделяемой динамически памяти определяется доступной физической памятью компьютера или доступным виртуальным адресным пространством в системе с виртуальной памятью. Однако часто эти размеры значительно меньше, потому что память разделяется между многими пользователями (задачами). Для динамического распределения памяти необходимо применение функций malloc и free, атакже операции sizeof. Функция malloc принимает в качестве аргумента число байт, которое необходимо выделить, и возвращает указатель на выделенную память типа void*. Указатель void* можно присвоить любой переменной-указателю. Функция malloc обычно используется совместно с операцией sizeof. Например, оператор newPtr = malloc(sizeof(struct node)); оценивает sizeof(struct node) для определения размера в байтах структуры типа struct node, выделяет новый блок памяти размером в sizeof(struct node) байт, и сохраняет указатель на выделенную память в переменной newPtr. Если необходимого количества памяти нет в наличии, malloc возвращает указатель NULL. Функция free освобождает память, т.е. память возвращается системе, и в дальнейшем ее можно выделить снова. Для высвобождения памяти, динамически выделенной предыдущим вызовом оператора malloc, используется оператор free(newPtr); Связанный список - это линейный набор ссылающихся на себя структур, называемых узлами, и объединенных указателем-связкой, отсюда и название - «связанный» список. Доступ к связанному списку обеспечивается указателем на первый узел списка. Доступ к следующим узлам производится через связывающий указатель, хранящийся в каждом узле. По общему соглашению связывающий указатель в последнем узле списка устанавливается в NULL, отмечая конец списка. Данные хранятся в связанном списке динамически - каждый узел создается по мере необходимости. Узел может содержать данные любого типа, включая другие структуры. Две основных функции связанных списков - insert и delete. Функция isEmpty называется предикатной функцией -она никак не меняет список, а всего лишь определяет, является ли список пустым (т.е. указатель на первый узел списка равен NULL). Если список пуст, возвращается значение 1, в противном случае 0. Функция printList распечатывает список. Символы вставляются в список в алфавитном порядке. Функции insert передаются адрес списка и символ, который необходимо вставить. Адрес списка необходим, когда значение должно быть вставлено в начало списка. Передача адреса списка позволяет модифицировать список (т.е. указатель на первый узел списка) через вызов по ссылке. Так как список сам по себе является указателем (на свой первый элемент), при передаче адреса списка создается указатель на указатель (другими словами, это двойная косвенная адресация). Функция delete получает адрес указателя на начало списка и символ, который нужно удалить. Стек - это упрощенный вариант связанного списка. Новые узлы могут добавляться в стек и удаляться из стека только сверху. Стек часто называют структурой вида последним пришел - первым вышел ( LIFO ). На стек ссылаются через указатель на верхний элемент стека. Связывающий элемент в последнем узле стека установлен равным NULL, чтобы пометить границу стека. Основные функции, используемые при работе со стеками - push и pop. Функция push создает новый узел и помещает его на вершину стека. Функция pop удаляет верхний узел стека, освобождает память, которая была выделена изъятому узлу, и возвращает изъятое значение. Другой распространенной структурой данных является очередь. В очереди узлы удаляются только с головы, а добавляются только в хвост очереди. По этой причине очереди часто называют структурами данных типа первым пришел - первым ушел (FIFO). Операции постановки в очередь и удаления из очереди носят названия enqueue (поставить в очередь) и dequeue (исключить из очереди). Связанные списки, стеки и очереди - все это примеры линейных структур данных. Дерево же - нелинейная, двумерная структура данных с особыми свойствами. Узлы дерева содержат две или более связей. Первый узел дерева называется корневым. Каждая связь корневого узла ссылается на потомка. Левый потомок - первый узел левого поддерева, а правый потомок - первый узел правого поддерева. Потомки одного узла называются узлами-сиблингами. Узел, не имеющий потомков, называется листом. Программисты обычно рисуют деревья от корневого узла вниз - т.е. в направлении, противоположном росту настоящих деревьев. Двоичное дерево поиска (с неповторяющимися значениями в узлах) устроено так, что значения в любом левом поддереве меньше, чем значение в родительском узле, а значения в любом правом поддереве больше, чем значение в родительском узле.
Создание класса. Классы дают возможность программисту моделировать объекты, которые обладают атрибутами (представленными в виде элементов данных) и которым присуще определенное поведение или действие (представленное в виде элементов-функций). При определении типов, содержащих элементы данных и элементы-функции, в C++ обычно используется ключевое слово class. В других объектно-ориентированных языках программирования элементы-функции иногда называют методами и они активируются в ответ на посылаемые объекту сообщения. Сообщение соответствует вызову элемента-функции.Сразу после определения класса его имя может быть использовано для объявления объектов этого класса. На рис. 1 показано простое определение класса Time. class Time { public: Time(); void setTime(int, int, int); void printMilitary(); void printStandard(); private: int hour; // 0 - 23int minute; // 0 - 59int second; // 0 - 59 }; Рис. 1. Простое определение класса Time. Определение нашего класса Time начинается с ключевого слова class. Тело определения класса ограничено левой { и правой } фигурными скобками. Определение класса завершается точкой с запятой. Метки public: и private: называются спецификаторами доступа к элементам. Все элементы данных и элементы-функции, объявленные после спецификатора public: (и до следующего спецификатора доступа) доступны всюду, где программа имеет доступ к какому-либо объекту класса Time. Все элементы данных и элементы-функции, объявленные после спецификатора private: (и до следующего спецификатора доступа) доступны только для функций-элементов класса. Спецификаторы доступа к элементам класса всегда заканчиваются двоеточием (: ) и могут многократно появляться в определении класса. Определение класса после спецификатора public: содержит прототипы следующих четырех элементов-функций: Time, set Time, print Military и print Standard. Эти функции называют открытыми, или публичными элементами-функциями, а также интерфейсом класса. Эти функции будут использоваться клиентами (пользователями) класса для манипулирования его данными. Элемент-функция с тем же именем, что и сам класс, называется конструктором этого класса. Конструктор - это специальная функция-элемент класса, которая инициализирует элементы данных объекта класса. Конструктор класса вызывается автоматически при создании объекта. Три целочисленных элемента появляются после спецификатора private:. Это означает, что эти элементы данных доступны только для функций-элементов класса - и, как мы увидим далее, для друзей класса. Таким образом, к этим элементам данных могут иметь доступ только четыре функции, прототипы которых появляются в определении класса (а также друзья класса). Обычно элементы данных перечисляются в разделе private: класса, а элементы-функции - в разделе public:. Как мы увидим далее, могут существовать элементы-функции с доступом private и данные с доступом public:. Сразу после определения класса он может быть использован в качестве типа в объявлениях, подобных следующим: Time sunset, // объект типа Time arrayOfTimes[5], // массив объектов типа Time *poiterToTime, // указатель на объект типа Time & dinnerTine = sunset; // ссылка на объект типа Time Имя класса становится новым спецификатором типа. Может существовать много объектов класса, подобно тому, как может быть много переменных типа int. Программист может по мере необходимости создавать новые типы классов. Это одна из многих причин, благодаря которым C++ является расширяемым языком. Функция с тем же именем, что и класс, с предшествующим символом тильды ( ~ ), называется деструктором класса. Деструктор производит заключительную «приборку» каждого объекта класса перед тем, как выделенная для него память будет возвращена системе. Рассматриваемый пример не имеет деструктора. Обратите внимание, что функциям, которые класс предусматривает для внешнего мира, предшествует метка public:. В открытых (публичных) функциях реализованы возможности, которые класс предоставляет своим клиентам. Открытые функции класса называют интерфейсом либо открытым интерфейсом класса. Определение класса содержит объявления его элементов данных и элементов-функций. Объявления элементов-функций представляют собой прототипы функций. Элементы-функции могут быть определены внутри класса, однако хорошим стилем является определение функций вне определения класса. Любая функция-элемент класса может быть определена непосредственно в его теле (вместо включения туда ее прототипа) или же она может быть определена после тела класса. Когда элемент-функция определяется после соответствующего определения класса, имени функции предшествуют имя класса и двухместная операция разрешения области действия (:: ). Поскольку различные классы могут иметь одинаковые имена элементов, операция разрешения области действия «привязывает» имя элемента к имени класса, однозначно идентифицируя функции-элементы данного класса. Хотя функция-элемент, объявленная в определении некоторого класса, может быть определена вне его определения, эта функция, тем не менее, находится в области действия класса, т.е. ее имя известно только другим элементам данного класса, за исключением случаев, когда обращение к ней происходит через объект класса, ссылку на объект класса или указатель на объект класса. Вскоре мы поговорим подробнее об области действия класса. Можно определять элементы-функции и в теле определения класса. Функции размером более одной или двух строк обычно определяются вне тела определения класса - это помогает отделить интерфейс класса от его реализации. Если функция-элемент определена в определении класса, то она автоматически становится встроенной. Элементы-функции, определенные вне определения класса, можно сделать встроенными путем явного использования ключевого слова inline. He забывайте, что компилятор оставляет за собой право не делать встроенной любую функцию. Классы упрощают программирование, поскольку клиент (или пользователь объекта класса) должен иметь дело только с инкапсулированными, т. е. встроенными в объект, действиями. При разработке такого рода действий обычно ориентируются на клиентов класса, а не на реализацию. Клиенты не должны иметь дела с реализацией класса. Интерфейсы тоже изменяются, но гораздо реже, чем реализации. При изменении реализации соответствующим образом должен измениться зависящий от реализации код. Скрывая реализацию, мы устраняем возможность зависимости других частей программы от деталей реализации класса. Часто нет необходимости создавать классы «с нуля». Напротив, классы могут производиться от других классов, предоставляющих программисту действия, которые могут использовать новые классы. Классы также могут включать в качестве элементов объекты других классов. Такого рода повторное использование программного обеспечения может значительно повысить производительность труда программиста. Создание производных классов на основе существующих называется наследованием. Включение классов в качестве элементов других классов называется композицией 95. Язык программирования С++: область действия класса и доступ к элементам класса. Популярное:
|
Последнее изменение этой страницы: 2016-04-10; Просмотров: 667; Нарушение авторского права страницы