Подставляемые (инлайн-) функции 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Подставляемые (инлайн-) функции



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

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

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

}

Функция module () возвращает значение типа float, равное "расстоянию" от начала координат на плоскости до точки с координатами (х,у), определяемыми значениями фактических параметров. В теле функции вызывается библиотечная функция sqrt() для вычисления вещественного значения квадратного корня положительного аргумента. Так как подкоренное выражение в функции всегда неотрицательно, то специальных проверок не требуется. Обрабатывая каждый вызов встраиваемой функции, компилятор "пытается" подставить в текст программы код операторов ее тела. Спецификатор inline для функций, не принадлежащих классам, определяет для функций внутреннее связывание. Во всех других отношениях подставляемая функция является обычной функцией, т.е. спецификатор inline в общем случае не влияет на результаты вызова функции, она имеет обычный синтаксис определения и описания, подчиняется всем правилам контроля типов и области действия. Однако вместо команд передачи управления единственному экземпляру тела функции компилятор в каждое место вызова функции помещает соответствующим образом настроенные команды кода операторов тела функции. Тем самым при многократных вызовах подставляемой функции размеры программы могут увеличиться, однако исключаются затраты на передачи управления к вызываемой функции и возвраты из нее. Как отмечает проект стандарта Си++, кроме экономии времени при выполнении программы, подстановка функции позволяет проводить оптимизацию ее кода в контексте, окружающем вызов, что в ином случае невозможно.

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

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

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

• встраиваемая функция слишком велика, чтобы выполнить ее подстановку;

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

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

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

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

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

 

 

Функции и массивы

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

Прежде всего следует сказать, что всё, что мы ранее узнали о передаче данных в функцию с помощью аппарата формальных и фактических параметров, а именно:

при компиляции программы проверяется строгое соответствие типов формальных и фактических параметров (если типы не совпадают и для этих типов не определено преобразование типов по умолчанию, то компилятор выдаёт ошибку);

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

в точке вызова функции создаётся неименованный объект, совпадающий с типом функции, который инициализируется значением выражения, стоящего после оператора return;

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

Ниже приведён пример функции len(), в которой формальный параметр z является не переменной, а именем массива char [] (константным указателем). Как видно из примера первое требование о совпадении типов формального z и фактического f параметров выполнено, поэтому компилятор по поводу не совпадения типов не выдаст сообщения об ошибке. При вызове функции len(f) формальному параметру z присваивается значение фактического параметра f, т.е. z настраивается на начало символьного массива "Язык Си++", определенного в теле функции main(). Таким образом, функция len() получает через свой формальный параметр z доступ к массиву (фрагменту памяти), определённому в другом месте программы и может делать с ним всё что угодно, если при выполнении операций над этим фрагментом она не выходит за его пределы. Такой способ передачи данных в функцию и называется передачей данных по указателю.

К сожалению, нельзя определить длину массива, передаваемого через формальный параметр, применяя, например, операцию sizeof(z) к имени формального параметра. Поэтому при передаче массивов через механизм параметров возникает задача определения в теле функции количества элементов массива (длины фрагмента памяти), использованного в качестве фактического параметра. При работе со строками, т.е. с массивами типа char[], последний элемент каждого из которых имеет значение '\0', затруднений практически нет. Анализируется каждый элемент, пока не встретится символ ' \0', и это считается концом строки-массива (фрагмента памяти). В следующей программе функция len() для определения длины строки (фрагмента памяти), передаваемой в функцию с помощью параметра z, перебирает элементы массива до тех пор пока не найдёт элемент со значением ' \0':

//Программа 6.5

#include "stdafx.h"

#include <iostream>

int len(char z[]){

int m = 0;

while (z[m++]);

return m-1;

}

void main() {

char f[] = "Language C++";

std::cout<<"\nThe length of string \"Language C++\" equal "<<len(f);

}

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

//Программа 6.6

#include "stdafx.h"

#include <iostream>

#include <math.h>

float scalar(int n, float x[], float y[]){

float a = 0;

for(int i = 0; i < n; i++) a += x[i]*y[i];

return a;

}

void main(){

float E[] = { 1, 1, 1, 1, 1, 1, 1};

float G[] = {-1, -1, -1, -1, -1, -1, -1};

std::cout <<"\n (E, G) = "<< scalar (7, E, G);

getchar();

}

Результат выполнения программы: (E, G) = -7

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

//Программа 6.7

#include "stdafx.h"

#include <iostream>

void max_vect(int n, int *x, int *y, int* z){

for(int i =0;i<n; i++) z[i] = x[i] > y[i]? x[i]: y[i];

}

void main(){

int a[] = { 1, 2, 3, 4, 5, 6, 7};

int b[] = { 7, 6, 5, 4, 3, 2, 1};

int c[7];

max_vect(7,a,b,c);

for (int i = 0; i < 7; i++)

std:: cout <<"\t"<< c[i];

getchar();

}

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

Как и в функции scalar(), параметр int n служит для определения размеров массивов-параметров.

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

//Программа 6.8

#include "stdafx.h"

#include <iostream>

int *fusion(int n, int* a, int m, int* b){

int* f = new int[n + m]; /* Создание динамического массива*/

int ia = 0, ib = 0, ix = 0, i;

for (i = 0; i < n; i++) f[i] = a[i];

for (; i < n+m; i++) f[i] = b[i-n];

return f;

}

void main(void) {

int c[] = { 1, 3, 5, 7, 9 };

int d[] = { 0, 2, 4, 5 };

int *h; // Указатель для массива с результатом

int kc=sizeof(c)/sizeof(c[0]);/*Количество элементов в с[]*/

int kd = sizeof(d)/sizeof(d[0]);/*Количество элементов в d[]*/

h = fusion(kc, c, kd, d);

std::cout<< "\nresult of integration of arrays:\n";

for (int i = 0; i < kc + kd; i++) std::cout << " " << h[i];

delete [] h; //Освобождение динамической памяти

getchar();

}

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

Результат объединения массивов:

1 3 5 7 9 0 2 4 5

На примере функции fusion() подробно рассмотрим, что происходит, если типом функции является указатель.

При выполнении тела функции fusion() оператор new динамический выделяет фрагмент памяти (массив) размером (n+m)*sizeof(int) байт и возвращает адрес начала этого фрагмента в памяти. Этот адрес запоминается в локальном указателе f. Далее обращаясь к элементам динамического массива с помощью f[], мы присваиваем им необходимые нам значения. При выполнении оператора return и выходе из функции fusion() в точке вызова создаётся неименованный объект типа int*, который инициализируется значением хранящимся в f (адресом начала динамического массива в памяти), после чего все локальные (но не динамические) объекты функции fusion() (n, a, m, b, f, ia, ib, ix, i) уничтожаются. Затем значение неименованного объекта типа int* (адрес начала динамического массива) присваивается указателю h.

Таким образом, функция fusion() через указатель вернула результат своей работы в вызывающую функцию main(). Эти результаты h[i] мы печатаем на экране. Если динамический массив нам уже больше не нужен, то мы можем его уничтожить, как показано в программе с помощью оператора delete [] h.

Большой ошибкой была бы попытка использовать не динамический, а статический локальный массив, т.е. вместо строки программы int* f = new int[n + m] написать строку int f[100]. Потому что локальный массив после выхода из функции fusion() в отличие от динамического будет уничтожен и попытка обратиться к нему из main() c помощью h[i] приведёт к зависанию программы. К сожалению, во время компиляции такие ошибки не обнаруживаются.

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

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

double prim[6][4][2];

то мы описываем не трехмерный массив, а одномерный массив с именем prim, включающий шесть элементов, каждый из которых имеет тип double[4] [2]. В свою очередь, каждый из этих элементов есть одномерный массив из четырех элементов типа double [2]. И, наконец, каждый из этих элементов является массивом из двух элементов типа double.

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

Наивное, неверное и очевидное решение - определить заголовок функции таким образом:

void transponir(double x[][], int n).

Здесь n - предполагаемый порядок квадратной матрицы; double х [ ][ ] - попытка определить двухмерный массив с заранее неизвестными размерами. На такую попытку транслятор отвечает гневным сообщением:

Error...: Size of type is unknown or zero

И он прав - при описании массива (и при спецификации массива-параметра) неопределенным может быть только первый (самый левый) размер. Вспомним - массив всегда одномерный, а его элементы должны иметь известную и фиксированную длину. В массиве х [][] не только неизвестно количество элементов одномерного массива, но и ничего не сказано о размерах этих элементов.

Примитивнейшее разрешение проблемы иллюстрирует следующая программа:

//Программа 6.9

#include "stdafx.h"

#include <iostream>

void transp(int n, float d[][3]){

float r;

int i, j;

for (i = 0; i < n - 1; i++)

for(j = i + 1; j < n; j++){

r = d[i][j];

d[i] [j] = d[j][i];

d[j][i] = r;

}

}

void main(){

float x[3][3] = { 0, 1, 1, 2, 0, 1, 2, 2, 0 };

int n = 3;

transp(3,x);

for (int i = 0; i < n; i++) {

std::cout <<"\n Cтрока " <<(i+1) <<":";

for (int j = 0; j < n; j++) std::cout<< "\t" << x[i][j];

}

getchar();

}

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

Строка 1: 0 2 2

Строка 2: 1 0 2

Строка 3: 1 1 0

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

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

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

Следующая программа иллюстрирует один из способов передачи в функцию информации о двухмерном массиве, размеры которого заранее неизвестны. Функция trans() выполняет транспонирование квадратной матрицы, определенной вне тела функции в виде двухмерного массива. Параметры функции: int n - порядок матрицы; double *p[] - массив указателей на одномерные массивы элементов типа double. В теле функции обращение к элементам обрабатываемого массива осуществляется с помощью двойного индексирования. Здесь p[i] - указатель на одномерный массив (на строку матрицы с элементами типа double), p[i][j] - обращение к конкретному элементу двухмерного массива. Текст программы:

//Программа 6.10

#include "stdafx.h"

#include <iostream>

void trans(int n, double *p[]){

double x;

for (int i = 0; i < n - 1; i++)

for (int j = i + 1; j < n; j++) {

x = p[i][j];

p[i][j] = p[j][i];

p[j][i] = x;

}

}

void main(){

double A[4][4]={ 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44 };

double *ptr[] = { (double *)&A[0], (double *)&A[1],

(double *)&A[2], (double *)&A[3] };

int n = 4;

trans(n, ptr);

for (int i = 0; i < n; i++){

std::cout <<"\n row " << (i+1) <<":";

for (int j =0; j < n; j++) std::cout<< "\t" <<A[i][j];

}

getchar();

}

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

строка 1: 11 21 31 41

строка 2: 12 22 32 42

строка 3: 13 23 33 43

строка 4: 14 24 34 44

В основной программе матрица представлена двухмерным массивом с фиксированными размерами double A[4][4]. Такой массив нельзя непосредственно использовать в качестве фактического параметра вместо формального параметра со спецификацией double *р[], поэтому вводится дополнительный вспомогательный массив указателей double *ptr [ ]. В качестве начальных значений элементам этого массива присваиваются адреса строк матрицы, т.е. &А[0], &A[l], &А[2], &А[3], преобразованные к типу double*. Дальнейшее очевидно из текста программы.

В следующей программе матрица формируется в основной программе как совокупность одномерных динамических массивов строк матрицы и динамического массива указателей на эти массивы-строки. Элементы массива указателей имеют тип int*, с массивом указателей в целом связывается указатель int **pi. Для простоты опущены проверки правильности выделения памяти и выбрано фиксированное значение (n==3) порядка матрицы. Функция fill() присваивает элементам квадратной матрицы значения "подряд": а[0][0] = 0, а[0][1] =1... и т.д., т.е. a[i] [j] = (i * n) + j, где n - порядок матрицы. В иллюстративных целях указатель mat на массив указателей на строки матрицы специфицирован в заголовке без использования квадратных скобок: int** mat. Первый из параметров функции fill() со спецификацией int n определяет размеры квадратной матрицы. Текст программы:

//Программа 6.11

#include "stdafx.h"

#include <iostream>

void fill(int n, int** mat){

int k = 0;

for (int i = 0; i < n; i++)

for (int j = 0; j < n; j++) mat[i][j] = k++;

}

void main() {

int **pi;

int m = 3, i;

pi = new int* [m];

for (i = 0; i < m; i++) pi[i] = new int [m];

fill(m, pi);

for (i = 0; i < m; i++) {

std::cout<< "\n строка "<< (i+1)<<":";

for (int j = 0; j < m; j++) std::cout<< "\t"<< pi[i][j];

}

for (i = 0; i < m; i++) delete pi[i];

delete pi;

getchar();

}

 

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

строка 1: 0 1 2

строка 2: 3 4 5

строка 3: 6 7 8

Так как матрица создается как набор динамических массивов, то в конце программы помещены операторы delete для освобождения памяти.

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

//Программа 6.12

#include "stdafx.h"

#include <iostream>

int **single_matr(int n) { // n - нужный размер матрицы

int** p; // Вспомогательный указатель на формируемую матрицу:

p = new int* [n];/*Массив указателей на строки - одномерные массивы:*/

if (p == NULL){

std::cout <<"He создан динамический массив!";

exit(1);

}

for (int i = 0; i < n; i++){ /* Формирование строки элементов типа int: */

p[i] = new int [n];

if (p[i] == NULL){

std::cout <<"He создан динамический массив!";

exit(1);

}

for (int j = 0; j < n; j++)

if (j!= i) p[i][j] = 0;

else p[i][j] = 1;

}

return p;

}

void main(){

int n; // Порядок матрицы

std::cout <<"\n Input order of matrix: ";

std::cin >>n;

int **matr, i; // Указатель для формируемой матрицы

matr = single_matr(n);// Создание единичной матрицы:

for (int i = 0; i < n; i++) {

std::cout << "\n row " << (i + 1) <<":"; /* Цикл печати элементов строки: */

for (int j = 0; j < n; j++)

std::cout <<"\t" <<matr[i][j];

}// Очистка памяти от динамических массивов:

for (i = 0; i < n; i++) delete matr[i];

delete[] matr;

getchar();

}

 

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

Введите порядок матрицы: 4_ <Enter>

строка 1: 1 0 0 0

строка 2: 0 1 0 0

строка 3: 0 0 1 0

строка 4: 0 0 0 1

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

 

Указатели на функции

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

тип_функции (* имя_указателя) (список_формальных_параметров);

Например:

int (*funclPtr) (char); - определение указателя funclPtr на функцию с параметром типа char, возвращающую значение типа int.

Если приведенную синтаксическую конструкцию записать без первых круглых скобок, т.е. в виде int *fun (char); то компилятор воспримет ее как прототип функции с именем fun и параметром типа char, возвращающей значение указателя типа int

Второй пример:

char * (*func2Ptr) (char *,int); - определение указателя func2Ptr на функцию с параметрами типа указатель на char и типа int, возвращающую значение типа указатель на char.

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

//Программа 6.13

#include "stdafx.h"

#include <iostream>

void f1(void) { // Определение f 1

std::cout<< "\nExecute f1()";

}

void f2(void) { // Определение f2

std::cout<<"\nExecute f2()";

}

void main(){

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

ptr = f2; // Присваивается адрес f2()

(*ptr)(); // Вызов f2() по ее адресу

ptr = f1; // Присваивается адрес f1()

(*ptr) (); // Вызов f1() no ее адресу

ptr(); // Вызов эквивалентен (*ptr)();

}

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

Выполняется f2 () Выполняется f1() Выполняется f1()

В программе описан указатель ptr на функцию, и ему последовательно присваиваются адреса функций f2 и f1. Заслуживает внимания форма вызова функции с помощью указателя на функцию:

(*имя_указателя)(список_фактических_параметров);

Здесь значением имени_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr(). Дело в том, что операция () имеет более высокий приоритет, нежели операция обращения по адресу *. Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.

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

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

char f1(char) {... } // Определение функции

char f2(int) (...) // Определение функции

void f3(float) (...) // Определение функции

int* f4(char *){...} // Определение функции

char (*ptl)(int); // Указатель на функцию

char (*pt2)(int); // Указатель на функцию

void (*ptr3)(float) = f3; // Инициализированный указатель

void main(){

ptl — f1; // Ошибка - несоответствие сигнатур

pt2 = f3; // Ошибка - несоответствие типов

// (значений и сигнатур)

ptl = f4; // Ошибка - несоответствие типов

ptl = f2; // Правильно

pt2 = ptl; // Правильно

char с = (*ptl)(44); // Правильно

с = (*pt2)('\t'); // Ошибка - несоответствие сиг-натур

}

Указатели на функции могут быть объединены в массивы. Например, float (*ptrArray)(char)[4]; - описание массива с именем ptrArray из четырех указателей на функции, каждая из которых имеет параметр типа char и возвращает значение типа float. Чтобы обратиться, например, к третьей из этих функций, потребуется такой оператор:

float а = (*ptrArray[2])('f');

Как обычно, индексация массива начинается с 0, и поэтому третий элемент массива имеет индекс 2.

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

typedef float (*PTF)(float);

typedef char *(*PTC)(char);

typedef void (*PTFONC)(PTF, int, float);

Здесь ptf - имя типа "указатель на функцию с параметром типа float, возвращающую значение типа float". ptc - имя типа "указатель на, функцию с параметром типа char, возвращающую указатель на тип char”. ptfunc - имя типа "указатель на функцию, возвращающую пустое значение (типа void)". Параметрами для этой функции служат: ptf - указатель на функцию float имя (float), выражение типа int и выражение типа float. (В определение имени типа ptfunc вошел только что определенный тип с именем ptf.)

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

PTF ptfloat1, ptfloat2[5]; //Указатель и массив указателей

//на функции float имя(float)

PTC ptchar; // Указатель на функцию char *(char)

PTFUNC ptfunc[8]; // Массив указателей на функции

Опыт работы на языках Си и Си++ показал, что даже не новичок в области программирования испытывает серьезные неудобства, раз­бирая синтаксис определения конструкций, включающих указатели на функции. Например, не каждому сразу становится понятным такое определение прототипа функции [6,11]:

void qsort(void *base, size_t nelem, size_t width,

int (*fcmp)(const void *pl, const void *p2));

Это прототип функции быстрой сортировки, входящей в стандартную для системы UNIX и для языка ANSI Си библиотеку функций. Прототип находится в заголовочном файле stdlib.h. Разберем элементы прототипа и напишем программу, использующую указанную функцию. Функция qsort() сортирует содержимое таблицы однотипных элементов, постоянно вызывая функцию сравнения, подготовленную пользователем. Для вызова функции сравнения ее адрес должен заместить указатель fcmp, специфицированный как формальный параметр. Итак, для использования qsort() программист должен подготовить таблицу сортируемых элементов в виде одномерного массива фиксированной длины и написать функцию, позволяющую сравнивать два любых элемента сортируемой таблицы. Остановимся на параметрах функции qsort ():

base - указатель на начало таблицы сортируемых элементов (адрес 0-го элемента массива);

nelem - количество элементов в таблице (целая величина, не больше размера массива);

width - размер элемента таблицы (целая величина, определяющая в байтах размер одного элемента массива);

fcmp - указатель на функцию сравнения, получающую в качестве параметров два указателя pi, р2 на элементы таблицы и возвращающую в зависимости от результата сравнения целое число:

если *pl < *р2, femp возвращает целое < 0;

если *pl = *p2, fcmp возвращает 0;

если *pl > *p2, fcmp возвращает целое > 0.

При сравнении символ "меньше чем" (<) означает, что после сортировки левый элемент отношения *pl должен оказаться в таблице перед правым элементом *р2, т.е. значение *pi должно иметь меньшее значение индекса в массиве, нежели *р2. Аналогично (но обратно) определяется расположение элементов при выполнении соотношения "больше чем" (>).

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

//Программа 6.14

#include "stdafx.h"

#include <iostream>

#include <stdlib.h> // Для функции qsort()

#include <string.h> // Для сравнения строк-.strcmpO

// Определение функции для сравнения:

int sravni(const void *a, const void *b) {

unsigned long *pa = (unsigned long *)a,

*pb = (unsigned long *)b;

return strcmp((char *)*pa, (char *)*pb);

}

void main() {

char *pc[] = {"Ivanov", "Cidorov", "Petrov", "Antonov"};

int i, n = sizeof(pc)/sizeof(pc[0]);

std::cout << "\n before sorter:\n";

for(int i = 0; i < n; i++) std::cout<<"\t"<< pc[i];

qsort((void *)pc, // Адрес начала сортируемой "Таблицы

n, // Количество элементов сортируемой таблицы

sizeof(pc[0]), // Размер одного элементе

sravni); // Имя функции сравнения (указатель)

std::cout << "\n After sorter:\n";

for(i = 0; i < n; i++) std::cout<<"\t"<< pc[i];

getchar();

}

 

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

До сортировки:

Иванов Сидоров Петров Антонов

После сортировки:

Антонов Иванов Петров Сидоров

 

Для выполнения сравнения строк (а не элементов массива рс[ ]) в функции sravni() использована библиотечная функция strcmp(), прототип которой в заголовочном файле string. h имеет вид

int strcmp(const char *sl, const char *s2);

Функция strcmp() выполняет беззнаковое сравнение строк, связанных с указателями s1 и s2. Сравнение выполняется без учета регистров набора букв латинского алфавита. Функция выполняет сравнение посимвольно, начиная с начальных символов строк и до тех пор, пока не встретятся несовпадающие символы либо не закончится одна из строк.

Прототип функции strcmp() требует, чтобы параметры имели тип (const char *). Входные параметры функции sravni() имеют тип (const void *), как предусматривает определение функции qsort(). Необходимые преобразования для наглядности выполнены в два этапа. В теле функции sravni() определены два вспомогательных указателя типа (unsigned long *), которым присваиваются значения адресов элементов сортируемой таблицы (элементов массива рс[ ]) указателей). В свою очередь, функция strcmp() получает адреса символьных строк. Таким образом, выполняется сравнение не элементов массива char* рс[ ], а тех строк, адреса которых являются значениями pc[i]. Однако функция qsort() работает с массивом рс[] и меняет местами только значения его элементов. Последовательный перебор массива рс[] позволяет в дальнейшем получить строки в алфавитном порядке, что иллюстрирует результат выполнения программы. Так как pc[i] - указатель на некоторую строку, то его разыменование в операции вывода «в поток cout выполняется автоматически.

Если не использовать вспомогательных указателей ра, рb, то функцию сравнения строк можно вызвать из тела функции sravni() таким оператором:

return strcmp((char *)(*(unsigned long *)a), (char *)(*(unsigned long *)b));

Здесь каждый родовой указатель (void *) вначале преобразуется к типу (unsigned long *). Последующее разыменование "достает" из четырех смежных байтов значение соответствующего указателя pc[i]. И уж затем преобразование (char *) формирует указатель на строку, который нужен функции strcmp().

 

Проектные задания

1. Набрать и отладить программу 6.1, выполнить её в пошаговом режиме, в функции print задать значение по умолчанию для параметра vilue.

2. Набрать и отладить программу 6.2, объяснить, как функция с переменным количеством параметров обменивается данными при её вызове.

3. Набрать и отладить программу 6.3, объяснить чем способ передачи данных в функцию в программе 6.3 отличается от программы 6.2

4. Набрать и отладить программу 6.4, нарисовать её алгоритм, рассказать об быстрой сортировки

5. Написать на языке Си++ несколько примеров подставляемых функций

6. Набрать и отладить программу 6.5, объяснить чем отличается способ передачи данных в функцию по указателю от передачи данных по значению.

7. Набрать и отладить программу 6.6 и 6.7, написать свою функцию, вычисляющую сумму элементов массива.

8. Напишите функцию, принимающую в качестве параметров указатели (или ссылки) на две строки и возвращающую указатель на новую строку, являющуюся результатом слияния этих строк.

9. Приведите примеры передачи функции как параметра динамического и статического массивов памяти.

10. Напишите функцию, принимающую указатель (или ссылку) на матрицу в качестве параметра и возвращающую указатель на копию этой матрицы, повернутую по часовой стрелке.

11. Напишите функцию, принимающую указатель (или ссылку) на матрицу в качестве параметра и возвращающую указатель на копию этой матрицы, повернутую против часовой стрелки.

12. Напишите функцию, принимающую указатель (или ссылку) на матрицу в качестве параметра и возвращающую указатель на копию этой матрицы, транспонированную относительно неглавной диагонали.

13. Напишите функцию, принимающую указатель (или ссылку) на матрицу в качестве параметра и возвращающую указатель на копию этой матрицы, повернутую зеркально вниз.

14. Напишите функцию, принимающую указатель (или ссылку) на матрицу в качестве параметра и возвращающую указатель на копию этой матрицы, повернутую зеркально вправо

 

Тесты рубежного контроля

1. Какой тип компоновки имеют функции в языке Си++

a) Внутренний тип компоновки

b) Внешний тип компоновки

2. Могут ли имена функций совпадать

a) Нет

b) Да

3. Нужно ли в описании функции приводить имена формальных параметров

a) Да

b) нет

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

a) Никаких



Поделиться:


Последнее изменение этой страницы: 2016-08-10; просмотров: 337; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.223.172.252 (0.253 с.)