Параметры по умолчанию
В прототипе или при описании функции в её заголовке одному или нескольким формальным параметрам может быть назначено значение по умолчанию по тем же правилам, что при инициализации. При вызове таких функций фактические параметры, соответствующие умалчиваемым, могут быть опущены, и тогда функция будет выполняться с теми значениями, которые указаны в заголовке. Значение по умолчанию можно изменить, записав при вызове фактический вместо умалчиваемого параметра. Остальные фактические параметры должны быть обязательно заданы. Например, функцию с заголовком void fun62 (float f, char ch=’*’, int i=2); можно вызвать одним из следующих способов:
а) fun62 (2.5, ‘-‘, 11); // параметры получат значения: f=2.5, ch=’-‘, i=11;
б) fun62 (2.5, 196); // f=2.5, ch — символ с кодом 196, i=2 по умолчанию;
в) fun62 (196); // f=196 как вещественное, сh=’*’ и i=2 по умолчанию;
г) fun62(0.01, ‘*’, 33); // f=0.01, ch=’*’, i=33;
При передаче параметров по умолчанию необходимо руководствоваться следующими правилами:
1) значение по умолчанию может быть задано либо в прототипе, либо при описании функции, но только в одном месте один раз;
2) в качестве умалчиваемых значений должны быть либо константы, либо глобальные переменные;
3) параметры со значениями по умолчанию должны быть последними в списке;
4) если при вызове функции опустили аргумент для параметра по умолчанию, то не надо писать аргумент и для всех оставшихся в списке. Например, если для функции fun62 надо изменить iна 196,то fun62 (0.11, 196); компилируется из-за совместимости целого и символьного типов, но неправильно выполняется, так как i останется по умолчанию (см. б)), а fun62(0.11 , , 196) не компилируется. Правильным будет следующий вызов: fun62(0.11 , ’*’ , 196);
Поэтому рекомендуется упорядочить умалчиваемые параметры по частоте их изменения. Например, если строка меняется чаще, чем символ, а вероятность изменения целого числа наименьшая, то задаём следующий порядок параметров:
int test=2;
void Out62(float f1, char str2[]="hello", char c3='*', int p4=test )
{ textcolor(12);
cprintf("%f %s %c %d\n\r", f1, str2, c3, p4);
}
int main(int argc, char* argv[])
{ Out62(11,"MMF",65);
Out62(11);
int q1=1111; Out62(q1,";",196 ,9);
// Целое число можно передать вместо вещественного f1
getch(); return 0;
}
Успешная компиляция и выполнение последней программы показывают, что в качестве параметра по умолчанию можно использовать и строку.
Упражнение. Определить результат выполнения последней программы.
Перегрузка функций
В “старом” языке С все имена функций должны быть уникальны в одном проекте. Это плохо и неудобно при работе с функциями, которые выполняют одинаковые или похожие действия с разными типами данных. Классический пример этого — стандартные функции abs(), labs(), fabs(), которые возвращают абсолютное значение, соответственно, целого, длинного целого (longint) и числа с плавающей точкой. В С++ можно определить несколько функций с одним и тем же именем, которые отличаются типами параметров и реже их количеством. Тогда говорят, что функции перегружены.
Например, опишем и будем использовать три функции, которые переставляют значения двух переменных разных типов:
void RR ( int &, int &);
void RR ( float &, float &);
void RR ( char &, char &);
int main()
{ int i1=11, i2=22; RR(i1,i2);
cout<<"\ni1="<<i1<<" i2="<<i2<<endl;
float f1=3.4, f2=5.6; RR(f1,f2);
cout<<"\nf1="<<f1<<" f2="<<f2<<endl;
char c1='a', c2='b'; RR(c1,c2);
cout<<"\nc1="<<c1<<" c2="<<c2<<endl;
getch(); return 0;
}
void RR(int &u, int &v) {int t; t=u; u=v; v=t; }
void RR(float &u, float &v) {float t; t=u; u=v; v=t; }
void RR(char &c1, char &c2) { char c; c=c1; c1=c2; c2=c; }
Какой вариант функции RR из трёх вызывается в main()? Компилятор автоматически выберет необходимую версию функции на основании типа используемых в функции фактических параметров. Первый раз вызывается первый вариант функции для целых параметров, второй раз — для вещественных значений и, наконец, для символьных.
Разрешается перегружать функции, отличающиеся количеством параметров. Тогда конкретный вариант функции компилятор выбирает на основании количества используемых в функции фактических параметров. Например, можно перегрузить функцию для вывода даты в виде строки или в виде трёх целых чисел.
Нельзя, чтобы перегружаемые функции отличались только типом возвращаемых значений. Например, такая перегрузка функций int FUN2(int ); float FUN2 (int); компилятору не понравится !
Упражнение. Перегрузите три функции для нахождения наибольшего из двух целых, вещественных и символьных величин. В последнем варианте найти символ с наибольшим кодом. В головной функции проверьте каждый из вариантов.
Г л а в а 3
ВВЕДЕНИЕ В ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
Изучение основных простейших понятий объектно-ориентированного программирования (ООП) проводится на примерах, которые можно проще запрограммировать без рассматриваемой методики. Из трёх свойств ООП в этой главе изучается только свойство инкапсуляции, которое означает следующее. Переменные (поля) и функции для работы с ними (методы) объединяются вместе с помощью специального структурированного типа данных, который называется класс. Кроме этого предполагается использование механизма защиты данных от несанкционированного доступа. Другими словами, с информацией, включённой в класс, разрешается работать только функциям данного класса.
Реальные задачи, в которых видны преимущества ООП, используют свойства наследования и полиморфизма, которые не учитываются при изложении материала данной главы и рассматриваются на втором курсе.
Примеры
П р и м е р 1. Разработаем простейший класс для работы с двумя целыми числами. В класс включим конструктор и следующие функции: вывод двух целых чисел; вычисление их суммы; проверка, делится ли одно число на другое без остатка; изменение двух целых чисел по правилу: наименьшее число или оба, если они одинаковы, умножаем на величину par, которая передаётся как параметр функции при её вызове.
class FirstCl // Описание класса с именем FirstCl
{ int a,b; // поля класса, т. е. переменные
public: // атрибут доступа(см.§ 2)
/* Заголовок конструктора. Это функция, которая имеет то же имя, что и класс; тип возвращаемого значения не записывается.*/
FirstCl(int x, int y);
/* Заголовки четырёх функций (методов) класса, оформленных как внешние. Они похожи на прототипы обычных самостоятельных функций, только поля класса, т. е. два целых числа, в качестве параметров не записываем */
void MyPrint();
int MySum();
bool MyTest();
/* Метод, который изменяет поля класса. Метод может использовать дополнительные параметры, которые не являются полями класса. У нас это число, на которое умножаем, наименьшее из двух чисел, или оба, если они одинаковы. */
void Change (int );
}; // конец описания класса
/* Текст внешнего конструктора. Перед именем внешнего конструктора и каждой внешней функции записываем имя класса и операцию “::” (разрешение области видимости). */
FirstCl::FirstCl (int x, int y)
{ a=x; b=y;
};
// Тексты четырёх внешних методов.
void FirstCl::MyPrint()
{ cout<<"\nThe first number "<<a<< " The second number "<<b <<endl;
} ;
int FirstCl::MySum()
{ return a+b;
};
/* Логическая функция, возвращающая true или false в зависимости от того, делится ли a на b без остатка (для 10 и 3 — false, 3 и 10 — false, 10 и 2 — true, 2 и 10 — false). */
bool FirstCl::MyTest()
{return !(a%b)? true : false;
};
void FirstCl::Change (int par)
{ if (a>b) b*=par;
else if (a<b) a*=par;
else { b*=par; a*=par;
}
};
/* В головной функции создаём объект, т. е. переменную типа класс, и вызываем включённые в класс методы. */
int main(int argc, char* argv[])
{ FirstCl obj(10,2);
/* Объявили объект obj и одновременно с этим вызвали конструктор, с помощью которого создаётся объект, и в класс передаются числа 10 и 2. Методы (функции) этого объекта будут выполняться для этих чисел. Повторно при вызове методов класса эти числа не передаём. Как и для самостоятельных функций, при объявлении объекта можно в качестве “фактических параметров” для конструктора передать не только константы, а и значения предварительно объявленных и определённых, например, введённых, переменных:
int A,B; cin>>A>>B;
FirstCl obj(A,B ); */
/* При вызове функций записываем имя объекта (obj), но не класса, и после точки имя функции (MyPrint). */
obj.MyPrint();
cout<<"\nSum "<<obj.MySum();
if (obj.MyTest()) cout<<"\n-----";
else cout<<"\n+++++";
obj.Change(10); cout<<"\nChanging ";
obj.MyPrint();
getch(); return 0;
}
При вызове функций класса есть аналогия с обычными самостоятельными функциями, не членами класса. Функции типа void вызываются отдельно (obj.MyPrint(); obj.Change(10);). Вызов функции с возвращаемым с помощью return единственным результатом записывается непосредственно в выражении, а значит, в операторе, в котором используется значение функции
(if (obj.MyTest() ) …).
Ещё раз обратим внимание на следующие особенности вызова методов класса:
· перед именем метода записывается имя объекта (а не класса!) и после точки имя метода. При необходимости в скобках записываем фактические параметры (см. функцию Change);
· поля класса передаются в объект с помощью конструктора. При вызове каждого метода повторно передавать эти числа, как для обычных функций, не надо. Передаём только те параметры, которые не включены в класс (параметр Par для метода Change).
П р и м е р 2. Разработать класс для работы с одномерным массивом наибольшей размерности 20. В отличие от первого примера, все функции оформим как встроенные. Их назначение приведено в комментариях:
#define MaxSize 20
/*Определили макрос, используемый в качестве наибольшей размерности массива */
class ClArray
{ int n;
float A[MaxSize];
public:
/* Конструктор инициализирует только одно поле — размерность массива. При этом осуществляется проверка передаваемого значения. Массив, который является вторым полем класса, определяется с помощью функции MyInp */
ClArray (int m)
{ n=m; if (n>MaxSize || n<1) n=10;
} ;
// Функция (метод) для ввода массива.
void MyInp()
{ int x=1, y; y=wherey();
for(int i=0; i<n; i++, x+=7)
{ gotoxy(x,y); cin>>A[i];
}
} ;
/* Функция (метод) для “цветного” вывода массива. При этом числа num выводятся цветом С1, а остальные — цветом С2 */
void MyOut(int C1, int C2, float num)
{ cout<<endl;
for ( int i=0; i<n; i++)
{ if (A[i]==num) textcolor ( C1 );
else textcolor ( C2 );
cprintf ( "%10.4f", A[i] );
}
}
/* Логическая (булевская) функция (метод), возвращающая true или false в зависимости от того, найдено число t в массиве или нет */
bool Test (float t)
{ for (int i=0; i<n; i++)
if (A[i]==t) return true;
return false;
}
/* Функция (метод), которая в массиве каждое наибольшее число меняет на наименьшее и наоборот. Кроме того, функция возвращает наибольшее и наименьшее значения. */
void MyChange ( float &mx, float &mn)
{ mx=mn=A[0];
for(int i=0; i<n; i++)
{ mx= A[i]>mx? A[i] : mx;
mn= A[i]<mn? A[i] : mn; }
for(int i=0; i<n; i++)
if (A[i]==mx) A[i]=mn;
else if (A[i]==mn) A[i]=mx;
} // end of the last function
} ; // end of class
/* В головной функции в цикле создаём объекты, то есть работаем с несколькими одномерными массивами, пока не введём в качестве размерности число 0. */
int main()
{ int N=5; float num;
do { ClArray ObArr(N);
ObArr.MyInp(); // ввод массива
while(1) // цикл для поиска нескольких чисел в массиве
{ cout<<"\n Number for find (1000 -- exit)"; cin>>num;
if (num==1000) break;
ObArr.MyOut ( 2, 5, num); // вывод массива разным цветом
if ( ObArr.Test ( num ) )
cout <<" \n Find the number " ;
else cout<<"\n Do not find the number ";
cout<<num;
}
float MyMx, MyMn;
/* Изменяем массив и возвращаем в функцию main наибольшее (MyMx) и наименьшее (MyMn) значения. */
ObArr.MyChange(MyMx, MyMn);
/* Вывод массива с выделением другим цветом наибольшего значения (MyMx). */
ObArr.MyOut(2,5,MyMx);
/* Вывод массива с выделением другим цветом наименьшего значения (MyMn). */
ObArr.MyOut(2,5,MyMn);
cout<< "\n Size of array (0 -- exit)"; cin>>N;
} while ( N);
return 0;
}
Дата добавления: 2016-07-18; просмотров: 2157;