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



ЗНАЕТЕ ЛИ ВЫ?

Выражение присваивания. Арифметические операции с целыми и плавающими переменными.

Поиск

Выражение присваивания. Арифметические операции с целыми и плавающими переменными.

Операция и выражение присваивания

Операция присваивания обозначается символом '='
Простейший вид операции присвоения:

v = e

Здесь v - любое выражение, которое может принимать значение, e - произвольное выражение.

Операция присвоения выполняется справа налево, т.е. сначала вычисляется значение выражения e, а затем это значение присваивается левому операнду v. Левый операнд в операции присваивания должен быть т.н. адресным выражением, которое иначе называют - value. Примером адресного, или именующего, выражения является имя переменной.

Не является - value, например, выражение a+b.

Адресным выражением никогда не являются константы.

В языке C++ операция присваивания образует выражение присваивания, т.е.

a = b

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

Отсюда следует, что возможна, например, такая запись:

a = b = c = d = e + 2;

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

int i; char ch;
i=3.14; ch=777;

Здесь i получает значение 3, а значение 777 слишком велико, чтобы быть представленным как char, поэтому значение ch будет зависеть от способа, которым конкретная реализация производит преобразование из большего в меньший целый тип.

Существует так называемая комбинированная операция присваивания вида

a оп =b, здесь оп - знак одной из бинарных операций:
+ - * / % >> << & | ^ && ||.

Присваивание a оп=b эквивалентно a = a оп b, за исключением того, что адресное выражение вычисляется только один раз.

Примеры:

a+=2 означает a=a+2
bottom_count [2*i+3*j+k]* =2 означает bottom_count [2*i+3*j+k] = bottom_count [2*i+3*j+k]*2
s/=a означает s=s/a

Результатом операции присваивания является ее левый операнд; следовательно, ее результат - адресное выражение и поэтому возможна запись

(a=b)+=c;

Это эквивалентно следующим двум операторам: a=b; a=a+c;


Арифметические операции

Бинарными арифметическими операциями являются + - * / %.

(Существуют также унарные + и -).

При делении целых дробная часть отбрасывается.

Так, 10/3 дает 3, в то время как 10/3.0 дает 3.33333...

Операция a % b применяется только к целым операндам и дает остаток от деления a на b, так

10%3 дает 1,
2%3 дает 2,
12%2 дает 0.

Тернарная или условная операция

Тернарная операция, т.е. операция с тремя операндами, имеет форму

Операнд1? операнд2: операнд3

Первый операнд может быть целого или плавающего типа (а также указателем, ссылкой или элементом перечисления). Для этой операции важно, является значение первого операнда нулем или нет. Если операнд1 не равен 0, то вычисляется операнд2 и его значение является результатом операции. Если операнд1 равен 0, то вычисляется операнд3, и его значение является результатом операции. Заметим, что вычисляется либо операнд2, либо операнд3, но не оба.

Пример:

max= a<=b? b:a;

Здесь переменной max присваивается максимальное значение из переменных а и b.

Если в условной операции операнд2 и операнд3 являются адресными выражениями, то тернарная операция может стоять слева от знака присваивания:

a< b? a:b = c*x+d;

Здесь значение выражения c*x+d присваивается меньшей из двух переменных а и b.

Пустой оператор

Пустой оператор состоит из;

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

Оператор-выражение

Любое выражение, за которым следует;, является оператором. Такой оператор называется оператор-выражение.

Примеры:

i ++;
a = b+c;
c + = (a < b)? a:b;
x+y; // Здесь результат не используется
// и будет выдано предупреждение.

Условный оператор

Имеется две формы условного оператора:

1) if (выражение) оператор1

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

Оператор1 выполняется в случае, если выражение принимает ненулевое значение. Если выражение принимает значение 0 (или указатель NULL), то выполняется оператор2.

Примеры:

if (a > b) c = a - b; else c = b - a;
if (i < j) i++; else {j = i - 3; i ++;}

При использовании вложенных операторов if текущий else всегда относится к самому последнему if, с которым еще не сопоставлен ни один else.

void main (){
int a = 2, b = 7, c = 3;
if (a > b) { if (b else c=a; cout<< "c="<< c<< ".\n"; }

Здесь результатом будет вывод строки c=2.

Если опустить фигурные скобки в операторе if, то программа примет вид

void main (){
int a=2, b=7, c=3;
if (a > b)
if (b < c) c=b;
else c = a;
cout<< "c="<< c<< ".\n";
}

Здесь else относится ко второму if.

В результате выведется строка c=3.


Определение указателей

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

px = &x;

присваивает переменной px адрес переменной x. Говорят, что px "указывает" на x. Операция & применима только к адресным выражениям, так что конструкции вида &(x -1) и &3 незаконны.

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

Следовательно, если y тоже имеет тип int, то

y = *px;

присваивает y содержимое того, на что указывает px. Так, последовательность

px = &x;
y = *px;

присваивает y то же самое значение, что и оператор

y = x;

Все эти переменные должны быть описаны:

int x, y;
int *px;

Последнее - описание указателя. Его можно рассматривать как мнемоническое. Оно говорит, что комбинация *px имеет тип int или, иначе, px есть указатель на int. Это означает, что если px появляется в виде *px, то это эквивалентно переменной типа int.

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

y = *px + 2;

присваивает y значение, на 2 больше, чем х.

Заметим, что приоритет унарных операций * и & таков, что эти операции связаны со своими операндами более крепко, чем арифметические операции, так что выражение

y = *px + 2

берет то значение, на которое указывает px, прибавляет 2 и присваивает результат переменной y.

Если px указывает на х, то

*px = 3;

полагает х равным 3, а

*px + = 1;

увеличивает х на 1, также как и выражение

(*px) ++

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

Если py - другой указатель на int, то можно выполнить присвоение py = px;

Здесь адрес из px копируется в py. В результате py указывает на то же, что и px.

Указатели и массивы

Массив - это совокупность элементов одного типа, которые расположены в памяти ЭВМ подряд, один за другим.

Признаком объявления массива являются квадратные скобки. Объявить массив из 10 элементов типа float можно так:

float a[10];

Чтобы обратиться к элементу этого массива, нужно применить операцию индексирования a[ind]. Внутри квадратных скобок помещается целое выражение, которое называется индексом. Нумерация элементов массива начинается с 0 и поэтому вышеприведенное описание говорит о том, что в памяти ЭВМ зарезервировано место под 10 переменных типа float и эти переменные есть a [0], a [1],..., a [9].

Приведем пример с использованием массива.

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

Число пробельных символов будем хранить в nwhite, прочих символов - в nother, а число появлений каждой из цифр - в массиве ndigit:

# include < iostream.h>
void main() {
int c, i, nwhite = 0, nother = 0;
int ndigit [10];
for (i=0; i<10; i++) ndigit[i]=0;
while ((c=cin.get())!=EOF)
if(c>='0' && c<='9') ++ ndigit[c - '0'];
else if (c = = ' '|| c = = '\n' || c = = '\t') ++ nwhite;
else ++ nother;
cout<<" цифра \n";
for(i=0; i<10; i ++)
cout<< i<<" вошла"<< ndigit[i]<< " раз \n";
cout<< " пробельных символов - "<< nwhite << " прочих символов - "
<< nother << "\n"; }

При объявлении массива его можно инициализировать:

int c[ ] = { 1, 2, 7, 0, 3, 5, 5 };
char array[ ] = { 'h', 'e', 'l', 'l', 'o', '\n', '\0'};

Последнюю инициализацию разрешается выполнять проще:

char array[ ] = "hello\n";

Такой синтаксис инициализации разрешен только для строк. Компилятор сам вычислит необходимый размер памяти с учетом автоматически добавляемого в конец строки символа '\0' с кодом 0, который является признаком завершения строки.

В языке С++ имя массива является константным указателем на первый элемент этого массива:

int mas[20];
int *pmas;
pmas = &mas[0];

Последний оператор можно записать и так: pmas = mas;

Операция индексирования массива [ ] имеет 2 операнда - имя массива, т.е. указатель, и индекс, т.е. целое: a[i]. В языке С++ любое выражение указатель[индекс] по определению трактуется как

*(указатель + индекс)

и автоматически преобразуется к такому виду компилятором.

Таким образом, a[3] эквивалентно *(a + 3). Более того, это можно записать даже так 3[a], т.к. это все равно будет проинтерпретировано как *(3+a). Здесь складываются указатель a и целое 3. В связи с этим рассмотрим так называемую адресную арифметику.

Адресная арифметика

Указатель можно складывать с целым.

Если к указателю pa прибавляется целое приращение i, то приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель pa.

Таким образом, pa+i - это адрес i -го элемента после pa, причем считается, что размер всех этих i элементов равен размеру объекта, на который указывает pa.

Итак, если a - массив, то
a+i - адрес i-го элемента этого массива, т.е.
&a[i] равен a+i и a[i] равняется *(a+i).

float b[10];
float *pb=b;
pb++; // Это эквивалентно pb=pb_1.
// Здесь указатель pb будет указывать
// на элемент массива b[1].
pb+=3; // Здесь pb указывает на элемент массива b[4].

Отметим, что нельзя написать b++ или b = b+i, т.к. имя массива b - это константный указатель и его изменять нельзя.

Указатели можно сравнивать.

Если p и q указывают на элементы одного и того же массива, то такие отношения, как < >= и т.д. работают надлежащим образом. Например,

p < q истинно, т.е. = = 1, если p указывает на более ранний элемент массива, чем q. Любой указатель можно сравнить на равенство или неравенство с так называемым нулевым указателем NULL, который ни на что не указывает. Однако не рекомендуется сравнивать указатели, указывающие на различные массивы.

Указатели можно вычитать.

Если p и q указывают на элементы одного и того же массива, то p - q дает количество элементов массива между p и q.


8. Символьные массивы и строки. Указатели и многомерные массивы.

Символьные массивы и строки

Строка являются массивом символов. Значением строки является указатель на первый ее символ:

char *string = "строка\n";

Здесь указатель на символы string будет содержать адрес первого символа 'c' строки "строка\n", которая размещается в некоторой области памяти, начиная с этого адреса:

Здесь string [3] = = 'о'.

Рассмотрим фрагмент программы:

char buffer[ ] =" "; // Инициализация
// строки из 10 пробелов.
char *string = buffer; // string указывает на начало буфера.
string="проба\n"; // Присваивание!

При инициализации создается строка buffer и в нее помещаются символы (здесь 10 пробелов). Инициализация char *string=buffer устанавливает указатель string на начало этой строки.

Операция же присваивания в последней строке не копирует приведенную строку "проба\n" в массив buffer, а изменяет значение указателя string так, что он начинает указывать на строку "проба\n":

Чтобы скопировать строку "проба\n" в buffer, можно поступить так:

char buffer[ ] = " ";
char *p ="проба\n";
int i =0;
while ((buffer[i] = p[i])!= '\0') i++;

Или так:
char buffer[ ] = " "
char * p = "проба\n";
char * buf = buffer;
while (*buf ++ = *p ++);

Здесь сначала *p копируется в *buf, т.е. символ 'п' копируется по адресу buf, который совпадает с адресом buffer, т.е. buffer [0] становится равен 'п'. Затем происходит увеличение указателей p и buf, что приводит к продвижению по строкам "проба\n" и buffer соответственно. Последний скопированный символ будет '\0', его значение - 0 и оператор while прекратит цикл.

Еще проще воспользоваться библиотечной функцией, прототип которой находится в файле string.h:

strcpy(buffer, "проба\n");

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

Указатели и многомерные массивы

Рассмотрим разницу между объектами a и b, описанными следующим образом:

int a[10][10];
int * b[10];

И a и b можно использовать сходным образом в том смысле, что как a [5][5], так и b [5][5] являются обращениями к отдельному значению типа int. Но a - настоящий массив: под него отводится 100 ячеек памяти и для нахождения любого указанного элемента проводятся обычные вычисления с индексами, которые требуют умножения. Для b описание выделяет только 10 указателей. Каждый из них должен быть установлен так, чтобы он указывал на массив целых.

Если предположить, что каждый из них указывает на массив из 10 элементов, то тогда где-то будет отведено 100 ячеек памяти плюс еще 10 ячеек для указателей. Таким образом, массив указателей использует несколько больший объем памяти и может требовать наличие явного шага инициализации. Но при этом возникают 2 преимущества: доступ к элементу осуществляется косвенно через указатель, а не посредством умножения и сложения, и строки массива могут иметь различные длины. Это означает, что каждый элемент b не должен обязательно указывать на вектор из 10 элементов. Эту разницу можно увидеть в следующем примере.

char day [5][12] = {
"понедельник", // В каждой строке 12 символов.
"вторник",
"среда",
"четверг",
"пятница"
};

Здесь константные указатели day[0], day[1], …, day[4] адресуют участки памяти одинаковой длины 12 байт каждый:

char * day1[2] = { “суббота”, // 7 символов + ‘/0’
“воскресенье”}; // 11 символов + ‘/0’

Здесь переменные-указатели day1[0] и day1[1] адресуют участки памяти соответственно в 8 и 12 байт.

 

 

Классы памяти.

В С++ существуют 3 класса памяти.

1) Статическая память - статические данные, размещаемые в сегменте данных;

2) автоматические данные, размещаемые в специальном стеке (сегмент стека) или как частный случай, в регистрах процессора;

3) динамические данные, явно размещаемые в динамической памяти с помощью операций new и delete.

Статические объекты существуют в течение всего времени выполнения программы. К ним относятся глобальные и локальные переменные, объявленные со служебным словом static:

int i=3, j; // глобальные переменные. Класс памяти - static;

void main(){

int a; // Автоматическая переменная;

static float b[1000], c=2.3; // статические переменные;

...

...

}

int f(){int d; // автоматическая переменная;

static int m=2, k; // статические переменные m, k.

...

}

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

Локальные переменные, не объявленные как static, являются автоматическими. Такие объекты начинают свое существование при объявлении его имени в блоке и заканчивают его при завершении этого блока. Если автоматический объект явно не инициализирован, то его значение до присвоения не определено.

void f();

void main(){

for (int i = 3; i > 0; i- -) f();

}

void f(){static int i; int j = 0;

cout<< "i ="<< i++<< " j ="<< j++<< "\n";

}

Здесь будет напечатано: i = 0 j = 0

i = 1 j = 0

i = 2 j = 0

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

 

 

Определение и вызов функции

Программа С++ состоит из одной или нескольких функций. Функции разбивают большие задачи на маленькие подзадачи.

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

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

С использованием функции связаны 3 понятия - определение функции, объявление функции и вызов функции.

Определение функции имеет вид:

тип имя (список описаний аргументов){ операторы }

Здесь имя - это имя функции;

тип - тип возвращаемого функцией значения;

операторы в фигурных скобках { } часто называют телом функции.

Аргументы в списке описаний называют формальными параметрами.

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

int max(int a, int b){ return(a>=b)? a:b; }

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

int max(int a, int b) { return (a >=b)? a:b; }

void main() {

int i = 2, j = 3;

int c = max(i, j);

cout<<" max= "<< c<< "\n";

c = max(i*i, j)*max(5, i - j);

cout<<" max= "<< c<< "\n";}

В этой программе приведено определение функции max и 3 обращения к ней. При обращении указывается имя функции и в круглых скобках список фактических параметров.

Если у функции нет формальных параметров, то она определяется, например, так:

double f(void){тело функции};

или, эквивалентно, double f() {тело функции};

Обращаются в программе к этой функции, например, так: a = b*f() + c;

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

void имя (список описаний аргументов){ операторы }

Вызов такой функции имеет вид: имя (список фактических аргументов);

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

В качестве примера приведем функцию, копирующую одну строку в другую.

void copy (char* to, char* from){

while(* to ++ = *from ++);}

void main(){

char str1[ ]="string1";

char str2[ ]="string2";

copy(str2, str1);

cout<< str<< "\n"; }

Заметим, что библиотечная функция strcpy имеет другое описание и ее заголовок имеет вид:

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

Её действие - копирование строки from в строку to и, кроме того, она возвращает указатель на строку to, т.е. в ней есть оператор return to.

 

Передача аргументов

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

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

int power (int x, int n){

for (int p = 1; n > 0; n - -) p* = x;

return p;

}

Аргумент n используется как временная переменная. Что бы ни происходило с n внутри функции power, это никак не влияет на фактический аргумент, с которым первоначально обратились к этой функции в вызываемой функции:

void main (){

...

int n=6, x=3;

x=power(x, n); // n - не меняется.

Рассмотрим процесс вызова функции более подробно. При вызове функции:

в стеке резервируется место для формальных параметров, в которые записываются значения фактических параметров. Обычно это производится в порядке, обратном их следованию в списке;

при вызове функции в стек записывается точка возврата - адрес той части программы, где находится вызов функции;

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

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

В качестве примера рассмотрим функцию, меняющую местами свои аргументы:

void swap (int* x, int* y){

int t = *x;

*x = *y;

*y = t;}

Обратиться к этой функции можно так:

int a = 3, b = 7;

swap (&a, &b);

Теперь а =7, и b=3.

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

int summa (int array[ ], int size){

int res=0;

for (int i = 0; i < size; i++) res+ = array[i];

return res;

}

В заголовке int array[ ] можно заменить на int* array, а выражение в теле функции array[i] заменить на *(array+i), или даже на *array ++, т.к. array не является именем массива, и следовательно, не является константным указателем. К функции summa можно обратиться так:

int mas[100];

for (int i = 0; i < 100; i++) mas[i] = 2*i + 1;

int j = summa (mas, 100);

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

Указатель на функцию определим следующим образом:

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

Например, int (*fptr) (double);

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

Пример:

void f1(void){

cout<<"\n Выполняется f1().";

}

void f2(void){

cout<<"\n Выполняется f2().";

}

void main(){

void (*ptr)(void);

ptr = f2;

(*ptr)(); //вызов функции f2();

ptr = f1;

(*ptr)(); //вызов f1();

ptr(); //альтернативная запись!

}

Результат: Выполняется f2().

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

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

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

Проиллюстрируем это при решении следующей задачи.

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

// Файл TRAP.CPP

double trap(double a, double b, int n, double (*func)(double)){

double x, h = (b-a)/n, i = ((*func) (a) + (*func) (b))/2;

for (x = a; n >1; n - -) i + = (*func)((x+=h);

return h*i; }

//Файл INTEGRAL.CPP

#include

#include

#include

#include "trap.cpp"

double f1(double x){

return x*x+sin(3+x);}

double f2(double x){

return x/(x*x+1)+exp(-2*x*x);}

void main(){

double a=1, b=3;

double i = trap(a, b, 50, f1);

cout << "интеграл от первой функции = "<< i<<'\n';

i = trap(a, b, 50, f2);

cout << "интеграл от второй функции = "<< i<<'\n';

}

Отметим, что в теле функции trap можно также писать обращения func(a), func(b) и т.д.

 

 

Аргументы по умолчанию

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

void error (char* msg, int level = 0, int kill = 0);

Эта функция может быть вызвана с одним, двумя или тремя аргументами:

error ("Ошибка!"); // Вызывается error ("ошибка", 0, 0);

error ("Ошибка!", 1); // вызывается error ("ошибка", 1, 0);

error ("Ошибка!", 3, 1); // значения аргументов по умолчанию не используются.

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

Если аргумент по умолчанию уже определён в одном объявлении, он не может быть переопределён в другом. Аргументы по умолчанию должны быть объявлены при первом объявлении имени функции и не обязаны быть константами:

int i=8; void func (int = i);

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

Переопределение функций

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

Пусть объявлены следующие функции:

int func(int, int);

int func(char, double);

int func(long, double);

int func(float, …); // Функция с неопределенным числом аргументов.

int func(char*, int);

Рассмотрим, что будет происходить при вызове функции с именем func с некоторым списком аргументов.

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

char string[ ]="Строка - это массив символов";

int i=func (string, 13); // func (char*, int);

int j=func(1995L, 36.6); // func(long, double);

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

Пусть обращение к функции выглядит так: float a=36.6; j=func('a', a);

Применяя указанные стандартные преобразования, найдём, что будет вызвана функция с прототипом func(char, double) и аргумент а будет преобразован к типу double.

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

Так, в операторе int l=func("ГОД:", 2002.3);

будет вызвана функция func (char*, int), фактический аргумент типа double которой будет преобразован к int с отбрасыванием дробной части числа.

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

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

Так, при вызове func (1, 2, 3); подходит лишь одна функция func(float, …).

При обращении

int i, j, n;

n=func(&i, &j); компилятор не найдёт ни одной подходящей функции и выдаст сообщение об ошибке.

Шаблоны функций.

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

В определении шаблонов семейства функций используется служебное слово template, за которым в угловых скобках следует список параметров шаблона. Каждый формальный параметр шаблона обозначается служебным словом class, за которым следует имя параметра.

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

template

type abs(type x){return x >0? x: -x;}

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

Пример (снова функция swap):

template

void swap(T& x, T& y){T z = x; x = y; y = z;}

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

abs(-10, 3)

компилятор сформирует такое определение функции:

double abs(double x){return x > 0? x: -x;}

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

Пример: шаблон функций для поиска в массиве.

#include < iostream.h>

template < class type>

type & r_max (int n, type d[ ]){

int im=0;

for (int i = 1; i < n; i++) im = d[im] > d[i]? im: i;

return d[im];

}

void main(){

int n = 4, x[ ]={10, 20, 30, 5};

cout<< "\n r_max (n, x)="<< r_max (n, x); //Печать максимального

// элемента.

r_max (n, x) = 0; // Замена в целом массиве

// максимального элемента нулем.

for (int i=0; i cout<< "\t x["<< i<< "]="<< x[i];

float f[]={10.3, 50.7, 12.6};

cout<< "\n r_max (3, f)="<< r_max (3, f);

r_max (3, f)=0;

for (i=0; i<3; i++)

cout<< "\t f["<< i<< "]="<< f[i];

}

Результат выполнения программы: r_max (n, x)=30 x[0]=10 x[1]=20 x[2]=0 x[3]=5

r_max (3, f)=50.7 f[0]=10.3 f[1]=0 f[2]=12.6

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

Перечислим основные свойства параметров шаблона.

Имена параметров шаблона должны быть уникальными во всём определении шаблона.

Список параметров шаблона функций не может быть пустым.

В списке параметров шаблона функций может быть несколько параметров. Каждый из них должен начинаться со служебного слова class.

Недопустимо использовать в заголовке шаблона параметры с одинаковыми именами.

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

Все параметры шаблона должны быть обязательно использованы в спецификациях формальных параметров определения функции.

Заметим, что при необходимости можно использовать прототипы шаблона функций. Например, прототип функции swap():

template < class type>

void swap (type&, type&);

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

Так, недопустимо:

int n=5;

double d=4.3;

swap (n, d);

 

 

Перечисления

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

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

Задание типа перечисление начинается ключевым словом enum, после которого идёт имя типа (иногда называемое тегом), за которым в фигурных скобках следует список членов перечисления - перечислителей:

enum chess {king, queen, rook, bishop, knight, pawn};

enum month {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec};

enum operator_CPP{plus='+', minus='-', mult='*', div='/', rem='%'};

Члены перечислений являются константами типов unsigned char или int, в зависимости от их значений и режима компиляции. При использовании перечислителя в выражении его тип всегда преобразуется в int.

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

Все члены перечисления operator_CPP получают явно указанные значения, равные ASCII - кодам символов '+', '-', '*', '/', '%'.

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

const TWO = 2;

enum {first, second=first, next=first+TWO, last =next*next+1}dummy;

Отметим, что в последнем случае не вводится тег перечисления, а сразу описывается переменная dummy, которая в соответствии с шаблоном перечисления может принимать одно из ч



Поделиться:


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

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