Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Бинарные арифметические операции




· + - сложение чисел или строк;

· - - вычитание чисел;

· * - умножения чисел;

· / - деления чисел;

· % - вычисление остатка от деления чисел.

Операции отношения

· > - больше;

· >= - больше либо равно;

· < - меньше;

· <= - меньше либо равно;

· == - равно;

· != - не равно.

Логические операции

· && - логическая И;

· || - логическая ИЛИ;

· ! - логическое НЕ.

Побитовые операции

Данные операции выполняются над целочисленными типами.

· & - побитовая И;

· | - побитовая ИЛИ;

· ^ - побитовая исключающая ИЛИ;

· ~ - побитовое инвертирование;

· >> - битовый сдвиг вправо;

· << - битовый сдвиг влево.

Операции с памятью

· * - разъименование;

· [] - индексация;

· & - взятие адреса;

· new - выделение памяти;

· delete - освобождение памяти.

Доступа к членам класса

· . - доступ к члену класса;

· -> - доступ к члену класса по указателю;

· .* - доступ к указателю на член класса;

· ->* - доступк указателю на член класса по указателю.

Преобразования типов

· () - преобразование типов (старый стиль);

· const_cast - изменяет атрибут const у объекта;

· dynamic_cast - динамическое преобразование;

· reinterpret_cast - преобразование типа указателя;

· static_cast - обычное преобразование.

Операции присвоения

· = - присваивает левому операнду значение правого операнда;

· op= - выполняет операцию op над операндами и сохраняет результат в левом операнде.

Прочие операции

· () - вызов функции;

· , - запятая, позволяет вычислить последовательно несколько выражений (например, удобно использовать в цикле for);

· :: - операция расширения видимости;

· ? : - условная операция;

· sizeof - определяет размер операнда;

· typeof - определяет тип операнда;

· typeid - возвращает информацию о типе.

 

13. Операторы выбора if и switch языка C++

Формат оператора:

if (выражение) оператор-1; [else оператор-2;]

Выполнение оператора if начинается с вычисления выражения.

Далее выполнение осуществляется по следующей схеме:

- если выражение истинно (т.е. отлично от 0), то выполняется оператор-1.

- если выражение ложно (т.е. равно 0),то выполняется оператор-2.

- если выражение ложно и отсутствует оператор-2 (в квадратные скобки заключена необязательная конструкция), то выполняется следующий за if оператор.

После выполнения оператора if значение передается на следующий оператор программы, если последовательность выполнения операторов программы не будет принудительно нарушена использованием операторов перехода.

Оператор switch предназначен для организации выбора из множества различных вариантов. Формат оператора следующий:

switch ( выражение ) { [объявление]         :      [ case константное-выражение1]: [ список-операторов1]      [ case константное-выражение2]: [ список-операторов2]         :         :      [ default: [ список операторов ]] }

Выражение, следующее за ключевым словом switch в круглых скобках, может быть любым выражением, допустимыми в языке СИ, значение которого должно быть целым. Отметим, что можно использовать явное приведение к целому типу, однако необходимо помнить о тех ограничениях и рекомендациях, о которых говорилось выше.

Значение этого выражения является ключевым для выбора из нескольких вариантов. Тело оператора smitch состоит из нескольких операторов, помеченных ключевым словом case с последующим константным-выражением. Следует отметить, что использование целого константного выражения является существенным недостатком, присущим рассмотренному оператору.

Так как константное выражение вычисляется во время трансляции, оно не может содержать переменные или вызовы функций. Обычно в качестве константного выражения используются целые или символьные константы.

Все константные выражения в операторе switch должны быть уникальны. Кроме операторов, помеченных ключевым словом case, может быть, но обязательно один, фрагмент помеченный ключевым словом default.

Список операторов может быть пустым, либо содержать один или более операторов. Причем в операторе switch не требуется заключать последовательность операторов в фигурные скобки.

Отметим также, что в операторе switch можно использовать свои локальные переменные, объявления которых находятся перед первым ключевым словом case, однако в объявлениях не должна использоваться инициализация.

Схема выполнения оператора switch следующая:

- вычисляется выражение в круглых скобках;

- вычисленные значения последовательно сравниваются с константными выражениями, следующими за ключевыми словами case;

- если одно из константных выражений совпадает со значением выражения, то управление передается на оператор, помеченный соответствующим ключевым словом case;

- если ни одно из константных выражений не равно выражению, то управление передается на оператор, помеченный ключевым словом default, а в случае его отсутствия управление передается на следующий после switch оператор.

14. Операторы цикла языка C++: цикл с предусловием while, цикл с постусловием do, итерационный цикл for.

Последовательность инструкций, предназначенная для многократного исполнения, называется телом цикла. Единичное выполнение тела цикла называется итерацией. Выражение определяющее, будет в очередной раз выполняться итерация, или цикл завершится, называется условием выхода или условием окончания цикла (либо условием продолжения в зависимости от того, как интерпретируется его истинность — как признак необходимости завершения или продолжения цикла). Переменная, хранящая текущий номер итерации, называется счётчиком итераций цикла или просто счётчиком цикла. Цикл не обязательно содержит счётчик, счётчик не обязан быть один — условие выхода из цикла может зависеть от нескольких изменяемых в цикле переменных, а может определяться внешними условиями (например, наступлением определённого времени), в последнем случае счётчик может вообще не понадобиться.

Исполнение любого цикла включает первоначальную инициализацию переменных цикла, проверку условия выхода, исполнение тела цикла и обновление переменной цикла на каждой итерации. Кроме того, большинство языков программирования предоставляют средства для досрочного управления циклом, например, операторы завершения цикла, то есть выхода из цикла независимо от истинности условия выхода (в языке Си — break) и операторы пропуска итерации (в языке Си — continue).

Цикл с предусловием — цикл, который выполняется пока истинно некоторое условие, указанное перед его началом. Это условие проверяется до выполнения тела цикла, поэтому тело может быть не выполнено ни разу (если условие с самого начала ложно). В большинстве процедурных языков программирования реализуется оператором while, отсюда его второе название — while-цикл.

На языке Си:

while(<условие>)

{

<тело цикла>

}

Цикл с постусловием

Цикл с постусловием — цикл, в котором условие проверяется после выполнения тела цикла. Отсюда следует, что тело всегда выполняется хотя бы один раз.

На языке Си:

do

{

<тело цикла>

}

while(<условие продолжения цикла>)
В трактовке условия цикла с постусловием в разных языках есть различия. В Паскале и языках, произошедших от него, условие такого цикла трактуется как условие выхода (цикл завершается, когда условие истинно, в русской терминологии такие циклы называют ещё «цикл до»), а в Си и его потомках — как условие продолжения (цикл завершается, когда условие ложно, такие циклы иногда называют «цикл пока»).

Цикл со счётчиком — цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. В большинстве процедурных языков программирования реализуется оператором for, в котором указывается счётчик (так называемая «переменная цикла»), требуемое количество проходов (или граничное значение счётчика) и, возможно, шаг, с которым изменяется счётчик. Например, в языке Оберон-2 такой цикл имеет вид:

 FOR v := b TO e BY s DO

... тело цикла

 END

(здесь v — счётчик, b — начальное значение счётчика, e — граничное значение счётчика, s — шаг).

Неоднозначен вопрос о значении переменной по завершении цикла, в котором эта переменная использовалась как счётчик. Например, если в программе на языке Паскаль встретится конструкция вида:

i := 100;

for i := 0 to 9 do


Begin

... тело цикла

end;

k := i;

15. Оператор безусловной передачи управления goto, оператор возврата из функции return.

В С++ есть несколько операторов, изменяющих естественный порядок выполнения вычислений:

· оператор безусловного перехода goto;

· оператор выхода из цикла break;

· оператор перехода к следующей итерации цикла continue;

· оператор возврата из функции return.

Оператор перехода goto предназначен для безусловной передачи управления в заданную точку программы. Его выполнение заключается в передаче управления оператору, помеченному заданной меткой. Общий вид оператора следующий:

      goto <метка>;               . . . . <метка>: <оператор>

Оператор перехода goto предназначен для безусловной передачи управления в заданную точку программы. В качестве метки используется идентификатор. Метка отделяется от помеченного оператора двоеточием. Передача управления разрешена на любой помеченный оператор в теле функции. Однако существует одно важное ограничение: запрещено "перескакивать" через описания, содержащие инициализацию объектов.

Принятые в настоящее время правила составления программ рекомендуют либо вовсе отказаться от оператора goto, либо свести его применение к минимуму и строго придерживаться следующих рекомендаций:

· не входить внутрь блока извне;

· не входить внутрь условного оператора, то есть не передавать управление операторам, размещенным после служебных слов if или else;

· не входить извне внутрь переключателя switch;

· не передавать управление внутрь цикла.

Следование перечисленным рекомендациям позволяет исключить возможные нежелательные последствия бессистемного использования оператора безусловного перехода. Полностью отказываться от его использования вряд ли стоит. Есть случаи, когда этот оператор обеспечивает наиболее простые и понятные решения. Один из них - это ситуация, когда в рамках текста одной функции необходимо из разных мест переходить к одному участку программы. Если по каким-то причинам эту часть программы нельзя оформить в виде функции (например, это может быть текст на ассемблере), то наиболее простое решение - применение безусловного перехода с помощью оператора goto. Такое положение возникает, например, при необходимости обрабатывать ошибки, выявляемые в процессе выполнения программы.

 Оператор return завершает выполнение данной функции и передает управление вызывающей функции. Операторreturn, появившийся в главной функции main, вызывает завершение выполнения всей программы.

Общий вид оператора return следующий:

return (<выражение>);      или return <выражение>;

Если выражение "не пусто" (то есть оно присутствует), то вычисляется его значение, которое и становится значением вызова функции. Выражение, если оно присутствует, может быть только скалярным.

Выражение в операторе return не может присутствовать в том случае, если возвращаемое функцией значение имеет тип void, чаще всего означающий, что функция не возвращает никакого значения. Достижение "конца" функции (правой закрывающей фигурной скобки) эквивалентно выполнению оператора return без возвращаемого значения (т.е. операторreturn в конце функции может быть опущен). Как правило, он опускается, когда функция имеет тип void.

Если же оператор return не содержит никакого выражения, то значение вызова функции не определено. Заметим, что оператор return позволяет вернуть в основную программу только одно значение. Более подробное знакомство с функциями можно осуществить на 42 шаге .

 17. Указатели и адреса объектов в языке C++. Адресная арифметика, типы указателей и операции над ними.

Указатель - это переменная, содержащая адрес другой переменной, т.е. значением переменной типа указатель является целое число, равное адресу того объекта, на который ссылается указатель.

Указатель существенно связан с типом объекта, на который он ссылается. Если в описании перед обозначением объекта поставить символ "*", то оно будет описывать указатель на объект того же типа и класса памяти, которые соответствуют данному обозначению без звездочки.

Указатели и целые не являются взаимозаменяемыми объектами. Константа - единственное исключение из этого правила: ее можно присвоить указателю и указатель можно сравнить с нулевой константой. Чтобы показать, что это специальное значение для указателя, вместо числа 0, как правило, записывают NULL - константу, определенную, в том числе, в файле stdio.h.

Унарная операция "*", называемая операцией косвенной адресации, рассматривает свой операнд как адрес объекта и обращается по этому адресу, возвращая его содержимое.

Унарная операция "&", называемая операцией нахождения адреса, будучи примененной к переменной, возвращает ее адрес.

Например, рассмотрим последовательность операторов:

int x,y,*px; // Описание целочисленных переменных x,y и

           // указателя на целое значение px.         

px = &x; // Значением переменной px станет адрес переменной x.

y = *px; // Переменная y приобретает значение "того",

           // на что указывает px, т.е. значение переменной x.

Таким образом, два оператора присваивания в примере эквивалентны одному оператору y=x;, и *px может появляться в любом выражении, в котором может встретиться х.

Операция "&" применима только к переменным: конструкции вида &(х-3) или &5 запрещены. А также нельзя получить адрес регистровой переменной.

Ни один "правильный" указатель не может иметь значения 0, поэтому равенство нулю значения указателя может служить сигналом о ненормальном завершении выполнения функции.

Над указателями можно выполнять следующие операции:

Присваивание значения указателя другому указателю того же типа.

"Операция" инициализации указателя. Операторы: char a; char *pa=&a; описывают символьную переменнуюa и указатель pa на объект типа char, а также инициализируют pa так, чтобы он указывал на a.

Операция вычитания указателей одного и того же типа.

Операции сложения и вычитания указателя и целого.

Операции сравнения указателей одного и того же типа. Если p и q - указатели на объекты одного типа, то к ним применимы операции отношения (<, >=, >, <=, !=, ==).

Например:

· отношение p!=q истинно, если p и q указывают на разные объекты;

· отношение p==q истинно, если p и q указывают на один и тот же объект.

Присваивание указателю нуля (NULL) и сравнение указателя с нулем (NULL). Например, отношениеp!=NULL истинно, если указатель p отличен от NULL.

Пример 1. Проиллюстрируем выбор данных из памяти с помощью различных указателей.

#include <iostream.h>

Void main()

{

  unsigned long L=0x12345678L;

char *cp=(char *)&L;

int *ip=(int *)&L;

long *lp=(long *)&L;

cout << hex;   //Шестнадцатеричное представление выводимых значений.

cout << "\n Адрес L, &L=" << &L;

cout << "\ncp = " << (void*)cp << "\t*cp = 0x" << (int)*cp;

cout << "\nip = " << (void *)ip << "\t*ip=0x" << *ip;

cout << "\nlp = " << (void *)lp << "\t*lp=0x" << *lp;

}.

Результат работы программы:

Адрес L, &L=0x243f2256

cp = 0x243f2256 *cp= 0x78

ip = 0x243f2256 *ip= 0x5678

lp = 0x243f2256 *lp= 0x12345678

В программе используется явное приведение типов. Так как адрес &L имеет тип unsigned long *, то при инициализации указателей его значение явно преобразуется соответственно к типам char *, int *, long *. При выводе значений указателей они преобразуются к типу void *, так как требуется вывод значений, а не длин участков памяти, связанных со значениями указателей.

 При выводе значения *cp использовано явное преобразование типа (int), так как при его отсутствии будет выведен не код, а соответствующий ему символ ASCII-кода. Особенность размещения чисел в памяти компьютера заключается в том, что сначала размещаются младшие байты числа, а затем старшие.

18. Массивы и указатели в языке C++. Многомерные массивы, массивы указателей, динамические массивы.

Массив представляет собой последовательность элементов одного типа. Каждому элементу массива соответствует индекс - целое неотрицательное число, определяющее его номер в последовательности. Первому элементу массива соответствует индекс 0. Элементы массива размещаются в памяти последовательно, друг за другом.

 При определении массива ему выделяется память так же, как массивам других алгоритмических языков. Но как только память для массива выделена, имя массива воспринимается как константный указатель того типа, к которому отнесены элементы массива. Однако это справедливо не всегда. Например, при использовании имени массива в операции sizeof, ее результатом является размер в байтах участка памяти, выделенного не для указателя, а для массива в целом. Исключением является и применение операции & (получение адреса) к имени массива. Результат операции - адрес начального (с нулевым индексом) элемента массива. В остальных случаях значением имени является адрес первого элемента массива, которое нельзя изменить.

Для создания массива компилятору необходимо знать тип данных и требуемый класс памяти, т.е. то же самое, что и для простой переменной (называемой скалярной). Кроме того, должно быть известно, сколько элементов имеет массив. Определение одномерного массива имеет вид:

<тип элементов массива> <имя массива> [<количество элементов в массиве>];.

Нумерация элементов в массиве начинается с нуля!

Массивы могут иметь те же типы и классы памяти, что и простые переменные. В некоторых случаях допустимо описание массива без указания количества его элементов, то есть без константного выражения в квадратных скобках. Например: extern unsigned long UL[]; является описанием внешнего массива, который определен в другой части программы, где ему выделена память и (возможно) присвоены начальные значения его элементам. Приведем еще несколько примеров описания массивов:

char alpha[26]; //Внешний массив, содержащий 26 символов.

main ()

{

static int nan[22]; //Статический массив, содержащий 22 целых числа.

extern char alpha[]; //Внешний массив, размер его указан выше.

float a[15];    //Автоматический массив, содержащий 15

                      //вещественных чисел.

}

В языке C++ определены только одномерные массивы. Многомерные массивы строятся на основе рекурсивного правила, согласно которому элементом массива может быть массив. Таким образом, в языке C++ двумерный массив(матрица) - это одномерный массив, каждый элемент которого является одномерным массивом.

При определении массива может выполняться его инициализация, то есть элементы массива получают конкретные значения. Инициализация выполняется по умолчанию, если массив статический или внешний. В этих случаях всем элементам массива компилятор автоматически присваивает нулевые значения. Явная инициализация элементов массива разрешена только при его определении и возможна двумя способами: либо с указанием размера массива в квадратных скобках, либо без явного указания (без конкретного выражения) в квадратных скобках, например:

char СН[ ] = { 'А', 'В', 'С', 'D'}; // Массив из 4 элементов.

int pr[6]= {10,20,30,40};      // Массив из 6 элементов.

char St[ ] = "ABCD"; // Массив из 5 элементов (включая нулевой элемент).

Количество элементов массива CH компилятор определяет по числу начальных значений в списке инициализации, помещенном в фигурных скобках при определении массива. В массиве pr шесть элементов, но только первые четыре из них явно получают начальные значения. При отсутствии константного выражения в квадратных скобках список начальных значений в определении массива обязателен. Если размер массива явно задан, то количество элементов в списке начальных значений не должно превышать размера массива.

 В тех случаях, когда массив не определяется, а описывается, список начальных значений задавать нельзя. В описании массива может отсутствовать и его размер:

extern float E[ ]; // Правильное описание внешнего массива.

Предполагается, что в месте определения массива для него выделена память и выполнена инициализация.

Элементы массива можно задавать в цикле при выполнении программы.

Имя массива является указателем-константой, значением которой служит адрес первого элемента массива (с индексом 0). Таким образом, доступ к первому элементу массива может быть осуществлен так:

<имя массива>[0]    

   или    

*<имя массива> .

В общем случае доступ к заданному элементу массива можно осуществить двумя способами:

<имя массива>[<номер элемента>]

Или

*(<имя массива>+<номер элемента>).

В языке C++ нет специального типа днных "строка". Вместо этого каждая символьная строка в памяти компьютера представляется в виде одномерного массива типа char, последним элементом которого является символ '\0'. Изображение строковой константы может использоваться по-разному. Если строка применяется для инициализации массива типа char, например, так: char array[ ] = "инициализирующая строка";, то адрес первого элемента строки становится значением указателя-константы (имени массива) array. Если строка используется для инициализации указателя типа char *: char *pointer = "инициализирующая строка" ;, то адрес первого элемента строки становится значением указателя-переменной pointer. Если использовать строку в выражении, где разрешено применять указатель, то используется адрес первого элемента строки:

char * string;

string = "строковый литерал";

В данном примере значением указателя string будет не вся строка "строковый литерал", а только адрес ее первого элемента.

Многомерный массив представляет собой массив массивов, то есть массив, элементами которого служат массивы. Определение многомерного массива в общем случае должно содержать сведения о типе, размерности и количествах элементов каждой размерности, например описание: int ARRAY[4][3][6]; определяет массив, состоящий из четырех элементов, каждый из которых - двухмерный массив с размерами 3 на 6. В памяти массив ARRAY размещается в порядке возрастания самого правого индекса, то есть самый младший адрес имеет элемент ARRAY[0][0][0], затем идет элемент ARRAY[0][0][1] и т.д.

С учетом порядка расположения в памяти элементов многомерного массива нужно размещать начальные значения его элементов в списке инициализации. Например, конструкция:

 int ARRAY [4][3][6] = {0,1,2,3,4,5,6,7}

инициализирует только первые 8 элементов этого массива:

ARRAY[0][0][0]=0, ARRAY[0][0][1]=1, ARRAY[0][0][2]=2, ARRAY[0][0][3]=3,

ARRAY[0][0][4]=4, ARRAY[0][0][5]=5, ARRAY[0][1][0]=6, ARRAY[0][1][1]=7.

В языке C++ для создания и обработки массивов обычно используют указатели. Напомним, что указатель - это переменная, содержащая адрес другой переменной или, говоря другими словами, указатель - символическое представление адреса.

Значением объекта типа указатель является целое неотрицательное число, равное адресу того программного объекта (переменной, функции, массива и т.д.), на который ссылается указатель.

Заметим, что в языке C++ гарантируется, что ни один "правильный" указатель не может иметь значение 0. Так что возвращение нуля в качестве значения указателя может служить сигналом о ненормальном завершении функции.

Для того, чтобы начать работу с указателями, необходимо их предварительно описать. Например:

int feet[20], /* Целочисленный массив (20 элементов). */

  *pfeet; /* Указатель на целое.             */

Перед указателем pfeet на массив feet мы употребили символ операции *. Ясно, что *pfeet (содержимое области памяти, в которой расположен элемент массива) имеет тип int.

Напомним, что в данном контексте символ "*" является символом унарной операции косвенной адресации. Унарная операция * рассматривает свой операнд как адрес объекта и обращается по этому адресу, чтобы извлечь его содержимое.

Приведем еще один пример описания массива:

int (*a)[15];

Описан указатель на массив, содержащий 15 целых значений. Круглые скобки здесь необходимы, поскольку скобки [] имеют более высокий уровень старшинства, чем операция *.

Теперь вспомним операцию нахождения адреса &. Например, запись:

&pooh[3]

означает указатель на элемент с индексом 3 массива pooh. Скобки [] имеют более высокий уровень старшинства, чем операция &.

Операция & применима только к переменным!

Имя массива определяет также адрес его первого элемента, т.е. если nise[] является массивом, то nise==&nise[0] и обе части равенства определяют адрес первого элемента массива. Оба обозначения являются константами типа указатель, поскольку они не изменяются на протяжении всей программы. Однако их можно присваивать переменной типа указатель и изменять значение переменной, как показано в следующем примере.

В терминах массивов это означает, что если прибавить к указателю на элемент массива единицу, то мы перейдем к адресу его следующего элемента. Так, после выполнения оператора присваивания:

pr = nise;

Суть добавления 1 к указателю состоит в том, что приращение масштабируется размером памяти того объекта, на который ссылается указатель.

Заметим еще, что компилятор превращает имя массива в указатель, поэтому присваивания:

pa = &a[0];

pa = a;

совершенно равноценны.

Теперь ясно, что i-й элемент массива можно представить в виде a[i], или pa[i], или *(pa+i), или *(a+i), а адрес i-го элемента есть &a[i], либо &pa[i], либо pa+i, либо a+i.

Теперь переделаем программу ввода массива в программу с использованием указателей.

char a[50];

char *pa = a;

описывают символьный массив a[] типа char и указатель pa на объект типа char, а так же инициализирует pa так, чтобы он указывал на начало массива a[].

19. Операторы new и delete языка C++.

В языке программирования C++ оператор delete возвращает память, выделенную оператором new, обратно в кучу. Вызов deleteдолжен происходить для каждого вызова new, дабы избежать утечки памяти. После вызова delete объект, указывающий на этот участок памяти, становится некорректным и не должен больше использоваться. Многие программисты присваивают 0 (нуль-указатель) указателям после использования delete, чтобы минимизировать количество ошибок программирования. Однако нужно отметить, что удаление нуль-указателя фактически не имеет эффекта, так что нет необходимости проверять нуль-указатель перед вызовом delete.

· Фрагмент кода в качестве примера:

· int *p_var = NULL; // объявление нового указателя

· p_var = new int;  // память динамически выделяется

·  

· /* .......

· остальной код

· ........*/

·  

· delete p_var;     // память освобождается

· p_var = NULL;     // указатель заменяется на 0 (нуль-указатель)

· Массивы, созданные (выделенные) при помощи new [], аналогичным образом могут быть уничтожены (оcвобождены) при помощиdelete []:

· int size = 10;

· int *p_var = NULL; // объявление нового указателя

· p_var = new int [size];// память динамически выделяется

·  

· /* .......

· остальной код

· ........*/

·  

· delete [] p_var;  // память освобождается

· p_var = NULL;     // указатель заменяется на 0 (нуль-указатель)

Вызов delete[] для массива объектов приведет к вызову деструктора для каждого объекта перед освобождением памяти, выделенной под массив.

В языке программирования C++, new — оператор, обеспечивающий выделение динамической памяти в куче. За исключением формы, называемой «размещающей формой new», new пытается выделить достаточно памяти в куче для размещения новых данных и, в случае успеха, возвращает адрес свежевыделенной памяти. Однако, если new не может выделить память в куче, то он передаст (throw) исключение типа std::bad_alloc. Это устраняет необходимость явной проверки результата выделения.

Синтаксис new выглядит следующим образом:

p_var = new typename;

где p_var —ранее объявленный указатель типа typename. typename может подразумевать собой любой фундаментальный тип данных или объект, определенный пользователем (включая, enum, class и struct). Если typename — это тип класса или структуры, то он должен иметь доступный конструктор по умолчанию, который будет вызван для создания объекта.

Для инициализации новой переменной, созданной при помощи new нужно использовать следующий синтаксис:

p_var = new type(initializer);

где initializer — первоначальное значение, присвоенное новой переменной, а если type — тип класса, то initializer — аргумент(ы) конструктора.

new может также создавать массив:

p_var = new type [size];

В данном случае, size указывает размерность (длину) создаваемого одномерного массива. Адрес первого элемента возвращается и помещается в p_var, поэтому

p_var[n]

означает значение n-го элемента (считая от нулевой позиции)

Память, выделенная при помощи new, должна быть освобождена при помощи delete, дабы избежать утечки памяти. Массивы, выделенные (созданные) при помощи new[], должны освобождаться (уничтожаться) при помощи delete[].

int *p_scalar = new int(5);

int *p_array = new int[5];

Инициализаторы не могут быть указаны для массивов, созданных при помощи new. Все элементы массива инициализируются при помощи конструктора по умолчанию для данного типа. Если тип не имеет конструктора по умолчанию, выделенная область памяти не будет проинициализирована.

Существует особая форма оператора new, называемая Placement new. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc). Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу. Часто такой метод применяют, когда у класса нет конструктора по умолчанию и при этом нужно создать массив объектов.

Поскольку при выделении памяти тип создаваемого объекта(ов) не был указан, компилятор не будет вызывать деструктор для каждого объекта массива, поэтому это нужно сделать вручную, перед освобождением блока памяти.

В компиляторах, придерживающихся стандарта ISO C++, в случае если недостаточно памяти для выделения, то генерируетсяисключение типа std::bad_alloc. Выполнение всего последующего кода прекращается, пока ошибка не будет обработана в блоке try-catch или произойдет экстренное завершение программы. Программа не нуждается в проверке значения указателя; если не было сгенерировано исключение, то выделение прошло успешно. Реализованные операции определяются в заголовке <new>. В большинстве реализаций C++ оператор new также может быть перегружен для определения особого поведения.

Любая динамическая память выделенная при помощи new должна освобождаться при помощи оператора delete. Существует два варианта: один для массивов, другой — для единичных объектов.

int *p_var = new int;

int *p_array = new int[50];

 

delete[] p_array;

delete p_var;

Необходимо отметить, что компилятор не требует создания диагностического сообщения при некорректном использовании delete; он в общем случае не может знать, когда указатель указывает на одиночный элемент, а когда — на массив элементов. Более того, использование не соответствующего освобождения является неопределённым поведением.

В отличие от функции realloc в языке Си, при помощи оператора new[] невозможно напрямую перераспределить уже выделенную память. Для увеличения или уменьшения размера блока памяти нужно выделить новый блок нужного размера, скопировать данные из старой памяти и удалить старый блок. Стандартная библиотека языка C++ предусматривает поддержку динамического массива, который может быть увеличен или уменьшен в своем шаблонном классе std::vector.

20. Определения, описания и вызовы функций в языке C++.

Каждая функция, вызываемая в программе, должна быть где-то определена (только один раз). Определение функции - это описание функции, в котором приводится тело функции. Например:

extern void swap(int*, int*); // описание

 

void swap(int*, int*)       // определение

{

int t = *p;

   *p =*q;

*q = t;

}

Чтобы избежать расходов на вызов функции, функцию можно описать как inline, а чтобы обеспечить более быстрый доступ к параметрам, их можно описать как register. Оба средства могут использоваться неправильно, и их следует избегать везде, где есть какие-либо сомнения в их полезности.

Описание функции задает имя функции, тип возвращаемого функцией значения (если таковое есть) и число и типы параметров, которые должны быть в вызове функции. Например:

extern double sqrt(double);

  extern elem* next_elem();

extern char* strcpy(char* to, const char* from);

extern void exit(int);
Семантика передачи параметров идентична семантике инициализации. Проверяются типы параметров, и когда нужно производится неявное преобразование типа. Например, если были заданы предыдущие определения, то

double sr2 = sqrt(2);


будет правильно обращаться к функции sqrt() со значением с плавающей точкой 2.0. Значение такой проверки типа и преобразования типа огромно.

Описание функции может содержать имена параметров. Это может помочь читателю, но компилятор эти имена просто игнорирует.

Функция представляет собой набор связанных операторов, которые выполняют определенную задачу, создавая функции внутри программы, вы можете делить большие задачи на небольшие легко управляемые части.
Программы выполняют операторы функций посредством вызова функции. Для вызова функции программы просто обращаются к имени функции, за которым следуют круглые скобки, как показано ниже:
function_name();
Если программа передает информацию (параметры) в функцию, она размещает эту информацию внутри круглых скобок, разделяя ее запятыми:
payroll(employee_name, employee_id, salary);
После того как последний оператор функции завершен, выполнение программы продолжается с первого оператора следующего за вызовом функции.

21. Функции с переменным количеством параметров в языке C++

В языке C++ допустимы функции, у которых количество параметров и их типы при компиляции определения функции не определены. Эти значения становятся известными только в момент вызова функции, когда явно задан список фактических параметров. При определении и описании таких функций, имеющих списки параметров неопределенной длины, спецификация формальных параметров заканчивается многоточием. Формат прототипа функции с переменным списком параметров:

<тип функции> <имя функции> (<спецификация явных параметров>,...); .

Здесь спецификация явных параметров - список спецификаций отдельных параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры называются обязательными. После списка явных (обязательных) параметров ставится необязательная запятая, а затем многоточие, извещающее компилятор, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно.

Каждая функция с переменным списком параметров должна иметь механизм определения их количества и типов. Существует два подхода к решению этой задачи.

Первый подход предполагает добавление в конец списка реально k использованных необязательных фактических параметров специального параметра-индикатора с уникальным значением, которое будет сигнализировать об окончании списка. В теле функции параметры последовательно перебираются, и их значения сравниваются с заранее известным концевым признаком.

Второй подход предусматривает передачу в функцию значения реального количества фактических параметров. Значение реального количества используемых фактических параметров можно передавать в функцию с помощью одного из явно задаваемых (обязательных) параметров.

В обоих подходах - и при задании концевого признака, и при указании числа реально используемых фактических параметров - переход от одного фактического параметра к другому выполняется с помощью указателей, то есть с использованием адресной арифметики.

Комментарии к программе. В обычном случае при описании функции задается список переменных - набор формальных параметров, используя которые осуществляется доступ к переданным значениям. Здесь такого списка нет. Каким же образом получить доступ к перечню переданных параметров? Это осуществляется с помощью указателей:

· сначала в указатель помещается адрес конца или начала списка явных параметров;

· используя это значение, происходит перемещение по переменному списку параметров.

22. Рекурсивные функции в языке C++

Под рекурсией будем понимать обращение функции при вычислении ее значения к себе самой. Использование рекурсивного метода вычисления нужно избегать, когда есть очевидное итерационное решение. Рекурсивные алгоритмы эффективны в тех задачах, где рекурсия использована в определении обрабатываемых данных. Поэтому изучение рекурсивных методов нужно проводить, вводя такие динамические структуры данных, как стеки, деревья, списки, очереди и, в особенности, данные с рекурсивной структурой. Здесь же рассмотрим только основные возможности использования рекурсии.

Различают прямую и косвенную рекурсии. Функция называется косвенно рекурсивной в том случае, если она содержит обращение к другой функции, содержащей прямой или косвенный вызов определяемой (первой) функции. В этом случае по тексту определения функции ее рекурсивность (косвенная) может быть не видна. Если в теле функции явно используется вызов этой функции, то имеет место прямая рекурсия.

23. Подставляемые (inline) функции в языке C++

Некоторые функции в языке C++ можно определить с использованием специального служебного слова inline. Спецификатор позволяет определить функцию как встраиваемую, то есть подставляемую в текст программы в местах обращения к этой функции. Например, следующая функция определена как подставляемая:

inline float module(float x = 0, float у = 0)

  { return sqrt(x * x + у * у); }

Обрабатывая каждый вызов встраиваемой функции, компилятор "пытается" подставить в текст программы код операторов ее тела. Тем самым при многократных вызовах подставляемой функции размеры программы могут увеличиться, однако исключаются затраты на передачи управления к вызываемой функции и возвраты из нее. Кроме того, подстановка функции позволяет проводить оптимизацию кода. Наиболее эффективно использовать подставляемые функции в тех случаях, когда тело функции состоит всего из нескольких операторов.

Перечислим причины, по которым функция со спецификатором inline будет трактоваться как обычная не подставляемая:

· встраиваемая функция велика;

· встраиваемая функция рекурсивна;

· обращение к встраиваемой функции в программе размещено до ее определения;

· встраиваемая функция используется в выражении более одного раза;

· встраиваемая функция содержит цикл, переключатель или оператор перехода.

Если же для функции со спецификатором inline компилятор не может выполнить подстановку из-за контекста, в который помещено обращение к ней, то функция считается статической (static) и выдается предупреждающее сообщение. Еще одна особенность подставляемых функций - невозможность их изменения без перекомпиляции всех частей программы, в которых эти функции вызываются.

24. Использование массивов в качестве параметров функций в языке C++

При решении задач часто требуется использовать массив в качестве параметра функции, кроме того, функции могут возвращать указатель на массив в качестве результата. Рассмотрим реализацию этих возможностей.

При передаче массивов через механизм параметров возникает задача определения в теле функции количества элементов массива, использованного в качестве фактического параметра. При работе со строками, то есть с массивами типа char[], последний элемент каждого из которых имеет значение '\0', анализируется каждый элемент, пока не встретится символ '\0', и это считается концом строки-массива.

Если массив-параметр функции не есть символьная строка, то нужно либо использовать только массивы фиксированного, заранее определенного размера, либо передавать значение размера массива в функцию явным образом. Часто это делается с помощью дополнительного параметра.

Особенность использования массивов в C++ заключается в том, что по имени массива нельзя определить его размерность и размеры по каждому измерению. По определению, многомерный массив не существует, он рассматривается как одномерный массив, каждый элемент которого, в свою очередь, представляет собой массив. При необходимости передать в функцию многомерный массив нужно указать значение каждой размерности, что "отвергается" компилятором.

Указанные ограничения на возможность применения многомерных массивов в качестве параметров можно обойти нескольким путями. Первый путь - подмена многомерного массива одномерным и имитация внутри функции доступа к многомерному массиву. Второй путь - использование вспомогательных массивов указателей на массивы. Одномерные массивы служат в этом случае для представления строк матриц. Так как вспомогательный массив указателей и массивы-строки матрицы являются одномерными, то их размеры могут быть опущены в ее соответствующих спецификациях формальных параметров. Тем самым появляется возможность обработки в теле функции двухмерных, а в более общем случае и многомерных массивов с изменяющимися размерами. Конкретные значения размеров должны передаваться функции либо с помощью дополнительных параметров, либо с пользованием глобальных (внешних) переменных. Третий путь предусматривает применение классов представления многомерных массивов.

Многомерный массив с переменными размерами, сформированный в функции, непосредственно невозможно вернуть в вызывающую программу как результат выполнения функции. Однако возвращаемым функцией значением может быть указатель на одномерный массив указателей на одномерные массивы с элементами известной размерности и заданного типа. В следующей программе функция single_matr() возвращает именно такой указатель, так как имеет тип int **. В тексте функции формируется набор одномерных массивов с элементами типа int и создается массив указателей на эти одномерные массивы. Количество одномерных массивов и их длины определяются значением параметра функции, описанного как int n. Совокупность создаваемых динамических массивов представляет квадратную матрицу порядка n. Диагональным элементам матрицы присваиваются единичные значения, остальным - нулевые, то есть создается единичная матрица. Локализованный в функции single_matr() указатель int** p "настраивается" на создаваемый динамический массив указателей и используется в операторе возврата из функции как возвращаемое значение. В основной программе вводится с клавиатуры желаемое значение порядка матрицы (int n), а после ее формирования печатается результат.

25. Указатели на функции в языке C++

С функцией можно делать только две вещи: вызывать ее и брать ее адрес. Указатель, полученный взятием адреса функции, можно затем использовать для вызова этой функции. Например:

void error(char* p) { /* ... */ }

void (*efct)(char*);           // указатель на функцию

void f()

{

efct = &error;             // efct указывает на error

(*efct)("error");          // вызов error через efct

}
Чтобы вызвать функцию через указатель, например, efct, надо сначала этот указатель разыменовать, *efct. Поскольку операция вызова функции () имеет более высокий приоритет, чем операция разыменования *, то нельзя писать просто *efct("error"). Это означает *efct("error"), а это ошибка в типе. То же относится и к синтаксису описаний (см. также #7.3.4).
Заметьте, что у указателей на функции типы параметров описываются точно также, как и в самих функциях. В присваиваниях указателя должно соблюдаться точное соответствие полного типа функции. Например:

void (*pf)(char*);   // указатель на void(char*)

void f1(char*);      // void(char*)

int f2(char*);      // int(char*)

void f3(int*);       // void(int*)

 

void f()

{

pf = &f1;        // ok

pf = &f2;        // ошибка: не подходит возвращаемый тип

pf = &f3;        // ошибка: не подходит тип параметра

 

(*pf)("asdf");   // ok

(*pf)(1);        // ошибка: не подходит тип параметра

 

int i = (*pf)("qwer"); // ошибка: void присваивается int'у

}
Правила передачи параметров для непосредственных вызовов функции и для вызовов функции через указатель одни и те же.
Часто, чтобы избежать использования какого-либо неочевидного синтаксиса, бывает удобно определить имя типа указатель-на-функцию. Например:

typedef int (*SIG_TYP)();   // из

typedef void (*SIG_ARG_TYP);

SIG_TYP signal(int,SIG_ARG_TYP);


Один из способов оценить огромную мощь указателей на функции - это попробовать написать такую систему не используя их. Меню можно менять в ходе использования программы, внося новые функции в таблицу действий. Во время выполнения можно также легко сконструировать новое меню.
Указатели на функции можно использовать для задания полиморфных подпрограмм, то есть подпрограмм, которые могут применяться к объектам многих различных типов:

 

typedef int (*CFT)(char*,char*);

 

int sort(char* base, unsigned n, int sz, CFT cmp)

/*

Сортирует "n" элементов вектора "base"

в возрастающем порядке

с помощью функции сравнения, указываемой "cmp".

Размер элементов "sz".

 

Очень неэффективный алгоритм: пузырьковая сортировка

*/

{

for (int i=0; iname, Puser(q)->name);

}

 

int cmp2(char*p, char* q)   // Сравнивает числа dept

{

return Puser(p)->dept-Puser(q)->dept;

26. Ссылки в языке C++.

Использование указателей в качестве альтернативного способа доступа к переменным таит в себе опасность - если был изменен адрес, хранящийся в указателе, то этот указатель больше не ссылается на нужное значение.

C++ предлагает альтернативу для более безопасного доступа к переменным через указатели. Объявив ссылочную переменную, можно создать объект, который, как указатель, ссылается на другое значение, но, в отличие от указателя, постоянно привязан к этому значению. Таким образом, ссылка на значение всегда ссылается на это значение.

Ссылку можно объявить следующим образом:

<имя типа>& <имя ссылки> = <выражение>;

           или

<имя типа>& <имя ссылки>(<выражение>);

Раз ссылка является другим именем уже существующего объекта, то в качестве инициализирующего объекта должно выступать имя некоторого объекта, уже расположенного в памяти. Значением ссылки после выполнения соответствующего определения с инициализацией становится адрес этого объекта.

Замечания.
1. В отличие от указателей, которые могут быть объявлены неинициализированными или установлены в нуль (NULL), ссылки всегда ссылаются на объект. Для ссылок не существует аналога нулевого указателя.
2. Ссылки нельзя инициализировать в следующих случаях:

· при их объявлении с ключевым словом extern;

· при использовании в качестве параметров функции;

· при использовании в качестве типа возвращаемого значения функции;

· в объявлениях классов.

Не существует операторов, непосредственно производящих действия над ссылками!

27. Перегрузка функций в языке C++

В языке C++ в одной программе могут использоваться несколько функций с одним и тем же именем, и каждая из этих функций выполняет свой собственный набор действий. Это называется перегрузкой функций.

Перегрузка функций – это свойство языка C++, позволяющее в программе использовать несколько функций с одинаковыми именами, выполняющих различные действия и имеющих различные списки параметров.

Пример перегруженных функций:

void Message(int);//Вывод кода сообщения об ошибке

void Message(char*);//Вывод текста сообщения об //ошибке

void Message(int Code)

{

cout <<"Код ошибки:" << Code << endl;

}

void Message(char* Msg)

{

cout <<"Ошибка:" << Msg << endl;

}

Вызов перегруженных функций:

int main(void)

{

Message(100);

Message("Деление на нуль!");

return 0;

}

Компилятор при обработке вызова каждой функции сравнивает списки фактических аргументов при вызове функции и наборы формальных параметров в прототипе функции и по их совпадению определяет, какая из перегруженных функций соответствует вызову. В нашем примере вызов функции Message(100) содержит один целочисленный параметр, что соответствует функции, имеющей прототипvoid Message(int), а вызов Message("Деление на нуль!") совпадает по списку параметров с прототипом функции void Message(char*).

Результат выполнения программы:

100

Деление на нуль!

Следует заметить, что язык C перегрузку функций не поддерживает. Кроме перегрузки функций в C++ применяется перегрузка операторов.

28. Шаблоны функций в языке C++

Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).

В C++ возможно создание шаблонов функций и классов.

Шаблоны позволяют создавать параметризованные классы и функции. Параметром может быть любой тип или значение одного из допустимых типов (целое число, enum, указатель на любой объект с глобально доступным именем). Например, нам нужен какой-то класс:

class SomeClass{

   int SomeValue;

   int SomeArray[20];

   ...

}

Шаблон функции начинается с ключевого слова template, за которым в угловых скобках следует список параметров. Затем следует объявление функции:

template< typename T >

Параметрами шаблонов могут быть: параметры-типы, параметры обычных типов, параметры-шаблоны.

Для параметров любого типа можно указывать значения по умолчанию.

template< class T1,               // параметр-тип     typename T2,            // параметр-тип     int I,                  // параметр обычного типа     T1 DefaultValue,        // параметр обычного типа     template< class > class T3, // параметр-шаблон     class Character = char  // параметр по умолчанию   >














Параметры-шаблоны

Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:

template< class Type, template< class > class Container > class CrossReferences { Container< Type > mems; Container< Type* > refs; /* ... */ };   CrossReferences< Date, vector > cr1; CrossReferences< string, set > cr2;

Нельзя использовать шаблоны функций в качестве параметров-шаблонов.

29. Структура как тип и совокупность данных в языке C++

Структура - это множество, состоящее из одного или более объектов, возможно имеющих различные типы, объединенных под одним именем.

Тип данных struct - один из основных строительных блоков данных в языке. Он предоставляет удобный способ объединения различных элементов, связанных между собой.

Описание структуры начинается со служебного слова struct, за которым может следовать необязательное имя, называемое именем типа структуры (здесь это date), которое еще называется именем шаблона. Такое имя типа структуры может использоваться в дальнейшем как сокращенная запись подробного описания однотипных структур.

За именем типа структуры идет заключенный в фигурные скобки список элементов структуры, с описанием типа каждого элемента (элементом структуры может быть переменная, массив, структура или объединение). Элементы структуры отделяются друг от друга точкой с запятой. Например:

Struct date

{

int day;

int month;

int year;

int yearday;

char mon_name[5];

};

Не следует полагать, что размер структуры равен сумме размеров ее членов. Вследствие выравнивания объектов разной длины в структуре могут появляться безымянные "дыры". Так, например, если переменная типа char занимает один байт, а int - четыре байта, то для структуры:

Struct

{

char c;

int i;

}

может потребоваться восемь байт, а не пять. Правильное значение возвращает операция sizeof.

Описание структуры, за которым не следует список объектов, не приводит к выделению памяти (как в программе выше); оно только определяет шаблон (форму) структуры. Однако, если такое описание снабжено именем типа (например, date), то это имя типа может быть использовано позднее при определении фактических экземпляров структур (определение структур d и f в программе).

Внешнюю или статическую структуру можно инициализировать, поместив вслед за ее определением список инициализаторов для ее компонент, заключенный в фигурные скобки. В программе таким образом инициализированы структуры d и f (они являются внешними для функции main).

date d = { 0,0,1776,186,"июл" };

date f = { 1,9,1986,0,"сент" }; .

Автоматические структуры инициализации не подлежат!

Элемент структуры может быть указан в выражении с помощью конструкции вида:

<имя структуры>.<имя элемента>.

Структуры могут вкладываться одна в другую, но самовложение структур запрещено!

30. Объединения разнотипных данных в языке C++

одним важным типом представления данных являются объединения.

Это тип данных, который позволяет хранить различные типы данных в одной и той же области памяти. Объединение задается с помощью ключевого слова union подобно структуре. Покажем особенность применения объединений на примере хранения данных, которые могут быть и вещественными и целыми или представлять собой символ. Так как наперед неизвестно какой тип данных требуется сохранять, то в объединении необходимо задать три поля:

union tag_value {

 int var_i;

 double var_f;

 char var_ch; 54

};

Данное объединение будет занимать в памяти 8 байт, ровно столько, сколько занимает переме нная самого большого объема, в данном случае var_f типа double. Все остальные переменные var_i и var_ch будут находиться в той же области памяти, что и переменная var_f. Благодаря такому подходу происходит экономия памяти, но платой за это является возможность хранения значения только одной переменной из трех в определенный момент времени.

Как видно из постановки задачи такое ограничение не будет влиять на работоспособность программы. Если бы вместо объединения использовалась структура tag_value, то можно было бы сохранять значения всех трех переменных одновременно, но при этом требовалось бы больше памяти.

Для того чтобы программа «знала» какой тип переменной содержит

объединение tag_value, необходимо ввести переменную, значение которой будет указывать номер используемой переменной: 0 – var_i, 1 – var_f и 2 – var_ch. Причем эту переменную удобно представить с объединением в виде структуры следующим образом:

struct tag_var {

 union tag_value value;

 short type_var;

};

Таким образом, получаем следующий алгоритм записи разнородной информации в объединение tag_value:

int main()

{

 struct tag_var var[3];

 var[0].type_var = 0;

 var[0].value.var_i = 10;

 var[1].type_var = 1;

 var[1].value.var_f = 2.3;










Последнее изменение этой страницы: 2018-04-12; просмотров: 293.

stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда...