Параметры шаблона, не являющиеся типами

 

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

 

// Листинг 10.4

#include <iostream>

using namespace std;

 

template<int n, int m, typename T>

void InitMas(T mas[n][m]) { // Передача массива

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

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

mas[i][j] = T();

}

 

int main() {

const int n = 3, m = 2;

int g[n][m];

 

//Вызов ф-ции с явным заданием параметров шаблона

InitMas<n,m>(g);

 

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

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

cout << g[i][j] << '\t';

cout << endl;

}

return 0;

}

 

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

0 0

0 0

0 0

 

Если в шаблоне функции использовать ссылку на массив:

 

template<int n, int m, typename T>

void InitMas(T (& mas)[n][m]) { // Ссылка на массив

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

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

mas[i][j] = T();

}

 

то вызвать функцию можно так:

 

int g[n][m];

// Вызов без явного задания параметров шаблона

InitMas(g);

 

Явная специализация шаблонной функции

 

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

В некоторых случаях для конкретного варианта типов аргументов может потребоваться особое поведение шаблонной функции. Особое в данном случае означает отличное от того поведения, которое предусмотрено алгоритмом тела шаблонной функции. При этом программист может написать отдельное определение шаблона функции, которое называют явной специализацией шаблонной функции. Новый способ определе­ния специализации содержит конструкцию template<>. Типы данных, для которого предназначена специализация, указывают­ся внутри угловых скобок после имени функции. Должны быть специализированы все параметры шаблона, частичная специали­зация шаблона функций запрещена. В этом случае используется перегрузка шаблона другим шаблоном или обычной функцией.

Рассмотрим программу с явной специализацией шаблонной функции.

 

// Листинг 10.5

// Использование шаблона функций

#include <iostream>

#include <cstring>

using namespace std;

 

template <typename T>

T maximum(T v1, T v2, T v3) {

T max = v1;

if (v2 > max) max = v2;

if (v3 > max) max = v3;

return max;

}

 

// Эта функция замещает обобщенную версию функции

// maximum для нахождения максимальной среди строк

template<>

char* maximum<char*>(char *v1, char* v2, char* v3)

{// Выполнение лексикографического сравнения строк

char * max = v1;

if (strcmp(v2,max) > 0) max = v2;

if (strcmp(v3,max) > 0) max = v3;

return max;

}

 

int main() {

int i1 = 3, i2 = 1, i3 = 10;

// Версия int

cout << "max = " << maximum(i1,i2,i3) << "\n";

 

double d1 = -3.2, d2 = -5.1, d3 = -2.4;

// Версия double

cout << "max = " << maximum(d1, d2,d3) << "\n";

 

char c1 = 'C', c2 = 'Z', c3 = 'a';

// Версия char

cout << "max = " << maximum(c1,c2,c3) << "\n";

 

char *s1 = "Victorov", *s2 = "Antonov",

*s3 = "Victor";

// Версия char*

cout << "max = " << maximum(s1,s2,s3) << "\n";

return 0;

}

 

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

max = 10

max = -2.4

max = a

max = Victorov

 

Рассмотрим программу, заполняющую массив из 20 вещественных чисел случайными значениями из диапазона от -500 до 500 с использованием генератора случайных чисел (см. Приложение 3).

 

// Листинг 10.6

#include <iostream>

#include <stdlib.h>

#include <time.h>

using namespace std;

 

int main() {

const int n = 20;

int i;

double mas[n];

 

srand(time(NULL));

 

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

mas[i] = 1000*(double)rand()/RAND_MAX - 500;

cout << mas[i] << "\n";

}

return 0;

}

 

Задание.

 

1. Реализовать на языке С++ программу, в которой оформ­лены в виде шаблонов функций (массив передавать в функцию в качестве параметра) следующие действия:

1) изменение элементов массива согласно своему варианту;

2) вычисление максимального элемента массива с явной специализацией шаблона для строк (тип char*);

3) вычисление суммы элементов массива с явной специализацией для строк (тип char*) (для строк под суммой элементов будем понимать конкатенацию (сцепление) всех строк – элементов массива);

4) возвращение в виде результата вновь созданного дина­мического массива (увеличенного размера на 2 эл-та по срав­нению с передаваемым массивом) и удаление переданного ди­намического массива; массив-результат должен содержать все элементы передаваемого в функцию массива, а в конец мас­сива-результата добавляются максимальный элемент (функция 2) и сумма элементов передаваемого массива (функция 3).

Можно добавить другие шаблоны функций, например, для ввода/вывода массивов.

 

2. В функции main создать одномерные динамические массивы:

1) Массив целых чисел (число элементов n = 50). Массив заполнить, используя генератор случайных чисел (см. Приложение 5).

2) Массив вещественных чисел (n = 25). Массив заполнить, считывая информацию из заранее созданного текстового файла.

3) Массив строк – фамилий слушателей (n = 15). Массив заполнить, считывая информацию из заранее созданного текстового файла.

 

Изменить элементы массивов целых и вещественных чисел согласно своему варианту (функция 1). Увеличить все три массива, используя функцию 4. Результаты вывести на экран.

 

Вариант 1

Заменить все нули в массиве средним арифметическим всех элементов массива.

 

Вариант 2

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

 

Вариант 3

Вычесть из каждого элемента массива значение минимального элемента массива.

 

Вариант 4

Умножить каждый элемент массива на минимальный элемента массива.

 

Вариант 5

Умножить все нечётные по абсолютной величине элементы массива на среднее арифметическое элементов массива.

 

Вариант 6

Вычесть из каждого элемента массива сумму всех элементов массива.

 

Вариант 7

Умножить каждый третий элемент массива на удвоенную сумму первого и последнего элементов.

 

Вариант 8

Добавить к каждому элементу массива минимальный по абсолютной величине элемент массива.

 

Вариант 9

Умножить каждый элемент массива на половину минимального элемента массива.

 

Вариант 10

Умножить каждый элемент на полусумму максимального и минимального элементов массива.

 

Вариант 11

Заменить все положительные элементы массива квадратом минимального элемента массива.

 

Вариант 12

Добавить к каждому элементу массива среднее арифметическое наименьшего и наибольшего по абсолютной величине элементов массива.

 

Вариант 13

Умножить все элементы массива на минимальный элемент массива и к результату добавить максимальный элемент.

 

Вариант 14

Заменить все положительные элементы массива минимальным элементом массива.

 

Вариант 15

Заменить каждый чётный по величине элемент массива разностью максимального и минимального элементов массива.

 

Вариант 16

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

 

Вариант 17

Заменить каждый третий положительный элемент массива средним арифметическим всех элементов массива.

 

Вариант 18

Добавить к каждому элементу массива среднее арифметическое элементов массива.

 

Вариант 19

Домножить каждый элемент массива на полусумму первого и последнего элементов.

 

Вариант 20

Заменить каждый второй элемент массива квадратом минимального элемента массива.

Практическое занятие 11. Препроцессорные средства языков С и С++

 

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

 

Стадии препроцессорной обработки программ на C/C++:

 

1) все системно-зависимые значения (например, индикатор конца строки) перекодируются в стандартные коды;

2) каждая пара из символов «\» и «конец строки» убираются (строки соединяются);

3) каждый комментарий заменяется пробелом;

4) выполняются директивы препроцессора и производятся макроподстановки;

5) ESC-последовательности (например, ‘\n’) заменяются соответствующими числовыми кодами;

6) смежные символьные строки конкатенируются (“…” ”…”).

 

Инструкции, которыеуправляют работой препроцессора, называются директивами; они должны начинаться с символа «#» (hash), перед которым могут быть только пробельные символы.

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

 

Директива #include

 

Препроцессорная директива #include вставляет содержимое указанного файла в другой файл (в точку использования директивы).

 

Форматы описания:

 

#include <имя_файла> поиск файла осуществляется в стандартных системных каталогах;  
#include"спецификация_файла" поиск файла осуществляется в соответствии со спецификацией.

 

спецификация_файла – это имя файла, которому может предшествовать путь. Синтаксис спецификации файла зависит от операционной системы, в которой компилируется программа. Если спецификация_файла представлена просто именем файла, то поиск осуществляется сначала в текущем каталоге (каталоге "родительского" файла – файла, содержащего директиву #include), а затем в системных каталогах.

Допускается вложение включения файлов до 10 уровней. При обработке вложенных директив #include препроцессор всегда осуществляет вставку в первоначальный исходный файл.

 

// файл test.cpp

#include <iostream>

#include "sort.h" // Файл с описанием функции

// сортировки одномерного массива

using namespace std;

...

int main() {

...

sort(mas, 20); // Вызов функции сортировки

...

}

 

Директива #define

 

Препроцессорная директива #define определяет подстановку (макроопределение, макрос) в тексте программы, существует негласное соглашение об использовании в именах макроопреде­лений преимущественно заглавных букв. Целесообразнее помещать все директивы #define в начале исходного файла или в отдельном заголовочном файле.

Директива используется в трёх вариантах для определения:

 

1) символических констант

(препроцессорных переменных, макроимён):

 

#define имя текст_подстановки

 

 

Текст_подстановки должен заканчиваться символом перехода на новую строку. При препроцессорной обработке текста программы все использования идентификатора имя заменяются текстом подстановки. Замена не распростра­няется на строки, символьные константы и комментарии (внутри “ ”, ‘ ’, /* */ и после //). Например:

 

// Листинг 11.1

#include <iostream>

using namespace std;

 

#define LEFT 0

#define RIGHT 10

 

int main() {

cout << LEFT <<"\t"<< RIGHT <<"\t"

<< (RIGHT + 1) << endl;

return 0;

}

 

Выполняя директивы, препроцессор заменит имена символических констант LEFT и RIGHT в исходном тексте программы значениями 0 и 10 соответственно. Программа выведет на экран числа 0 10 11.

 

В программе может быть определено имя макроса, которое используется для определения другого макроса:

 

// Листинг 11.2

#include <iostream>

using namespace std;

 

#define ONE 1

#define TWO ONE+ONE

#define THREE ONE+TWO

 

int main() {

cout << ONE << "\t" << TWO << "\t"

<< THREE << endl;

return 0;

}

 

Результатом препроцессорной обработки будет текст:

 

#pragma once // Установка компилятору

 

... // Содержимое включённых файлов

 

using namespace std;

 

int main() {

cout << 1 << "\t" << 1+1 << "\t" << 1+1+1

<< endl;

return 0;

}

 

На экран в результате запуска программы выведутся числа 1 2 3.

 

Если идентификатор является частью строки, заклю­чённой в кавычки, макроподстановка не производится. Например, фрагмент программы

 

#define DEBUG "Test"

...

cout << "DEBUG " << DEBUG << endl;

 

выведет на экран: DEBUG Test.

 

Если текст_подстановки занимает несколько строк, в конце каждой из них следует поставить символ переноса строки – обратную косую черту:

 

#define LONG_STRING "В этом примере \

используется очень длинная строка"

 

2) макроимён, управляющих условной компиляцией:

 

#define имя Определяет препроцессорное имя, используется вместе с директивами #ifdef и #ifndef

Это использование директивы #define будет рассмот­рено ниже, при изучении условной компиляции.

 

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

 

#define имя(список_формальн_параметров)


Текст_подстановки

 

 

Макросы подобны встраиваемым (inline) функциям. Каждый раз, когда в тексте программы встречается имя макроса, его формальные параметры заменяются фактичес­кими. Такой вид макроса называется функциональным. Имя макроса (со скобками) не должно содержать пробелов. Макросы пригодны для обработки параметров любого типа, однако, компилятор при этом не контролирует корректность использования типов.

Примеры макросов:

 

#define MAX(a,b) (a < b? b: a)

#define ABS(x) (x < 0? –(x): x)

 

Используя MAX(p,10), в подстановке имеем

(p < 10? 10: p).

 

Пример неверного макроса (отсутствие круглых скобок может привести к неправильному порядку вычисления):

 

#define MAX(a,b) a < b? b: a

 

Используя int k = 3*MAX(5,10), в подстановке имеем

int k = 3*5 < 10? 10: 5, и вместо ожидаемого k = 30 получим k = 5.

 

// Листинг 11.3

#include <iostream>

using namespace std;

 

#define sqr(x) (x)*(x)

 

int main() {

double x = 7;

cout << sqr(x+3) << endl;

return 0;

}

 

Результатом препроцессорной обработки будет текст:

 

#pragma once // Установка компилятору

 

... // Содержимое включённых файлов

 

using namespace std;

 

int main() {

double x = 7;

cout << (x+3)*(x+3) << endl;

return 0;

}

 

На экран в результате запуска программы выведется 100.

 

Рассмотрим на примере некоторые ограничения и возмож­ности макросов:

 

// Листинг 11.4

#include <iostream>

using namespace std;

 

#define MAX(a, b) (a < b? b: a)

#define F(c) c*2

#define PRINT(d) cout << '\n' << #d << " = " << d

 

int main(){ int x = 1; PRINT(MAX(++x,++x)); PRINT(F(x)); PRINT(F(x+x)); PRINT(F(x+x)/4); return 0; } В результате получим:   MAX(++x,++x) = 4 // см.(*) F(x) = 8 // x*2 F(x+x) = 12 // x+x*2 F(x+x)/4 = 6 // x+x*2/4

 

MAX(++x, ++x) º (++x < ++x? ++x: ++x) (*)

 

+1 +1 +1

 

Чтобы в подстановке корректно выполнялись все действия над параматрами макроса (например, при использовании F(c) удваивалось значение любого аргумента), необходимо каждый параметр в строке замещения заключать в скобки:

 

#define F(c) (c)*2

 

Препроцессорные операторы # и ##

 

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

 

· оператор преобразования в строку #

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

#define print(a) cout << #a << " = " << a

имея print(sin(x));

получим cout <<"sin(x)" << " = " << sin(x);

 

· оператор конкатенации ##

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

 

#define one(a,b,c,d) a(bcd)

#define two(a,b,c,d) a(b c d)

#define three(a,b,c,d) a(b##c##d)

 

макровызов: результат макроподстановки:
one(sin,x,+,y) two(sin,x,+,y) three(sin,x,+,y) sin(bcd) // bcd – идентификатор sin(x + y) sin(x+y)

 

Рассмотрим программу, в которой средствами языка С в виде макроса определён аналог шаблона функций для перестановки первого и последнего элементов одномерного массива. Параметр макроопределения определяет тип элементов массива. Парамет­ры формируемой макроопределением функции – одномерный массив и количество элементов этого массива. Используя в макрообращениях имена разных типов, получаем формируемые препроцессором тексты определений функций с разными именами (в отличие от инстанцирования шаблона в С++).

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

 

// Листинг 11.5

#include <iostream>

using namespace std;

 

// Макрос – С-аналог шаблона функции в С++

#define template_swap(Type) \

void swap_##Type(Type * mas, int n) {\

Type buf = mas[0]; \

mas[0] = mas[n-1]; \

mas[n-1] = buf; \

}

 

// Обращение к макросу (макровызовы).

 

// Формирование функции swap_double

template_swap(double);

// Формирование функции swap_int

template_swap(int);

 

int main() {

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

int n = 5;

double * a = new double[n];

cout << "Input double array" << endl;

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

cin >> a[i];

cout << endl;

 

swap_int(d, n);

cout << "Int array after working:" << endl;

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

cout << d[i] << " ";

cout << endl;

 

swap_double(a,n);

cout << "Double array after working:" << endl;

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

cout << a[i] << " ";

cout << endl;

return 0;

}

 

Для ознакомления с результатом препроцессорной обработки программы необходимо сформировать текстовый файл *.i, изменив установки препроцессора (см. Приложение 2).

После обработки препроцессором текст программы (листинг 4) имеет вид:

 

#pragma once // Установка компилятору

 

... // Содержимое включённых файлов

 

using namespace std;

 

void swap_double(double * mas, int n) { double buf

= mas[0]; mas[0] = mas[n-1]; mas[n-1] = buf; };

void swap_int(int * mas, int n) { int buf

= mas[0]; mas[0] = mas[n-1]; mas[n-1] = buf; };

 

int main() {

... // Текст функции main без изменений

}

 

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

 

Input double array

0.33 -2.5 6.87 33.12 -5.6

 

Int array after working

5 2 3 4 1

Double array after working

-5.6 -2.5 6.87 33.12 0.33

 

Директива #undef

 

Замены в тексте можно отменять с помощью директивы

 

#undef имя

 

Например:

 

N = 2; // Основной текст

 

#define N k

...

k = 10; // Включенный текст

...

#undef N

 

a = N; // a = 2

 

#define A B

#define C A

#undef A

 

C // Генерируется запись A, а не B, так как

// макровызовы в директивах #define

// игнорируются.

 

Директивы #define…#undef удобно использовать при разработке больших программ коллективом программистов (могут встречаться одинаковые обозначения разных объектов у разных авторов частей программы).

 


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: