Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы по экзамену ЯП.doc
Скачиваний:
3
Добавлен:
25.08.2019
Размер:
534.02 Кб
Скачать

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[].

  1. Операторы 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 // параметр по умолчанию

>