
2 семестр ФИБС / Информатика / Lecture_4
.pdf4. ССЫЛКИ В С++.
Функции, получающие аргументы при вызове, создают внутренние копии этих аргументов. Этот механизм называется передачей по значению. При вызове функции выделяется временная область памяти для хранения аргументов во время выполнения функции. После завершения функции выделенная память освобождается и аргументы сохраняют свои прежние значения.
Пример:
void swap (int i, int j) { int tmp;
tmp= i; i=j; j=tmp ;}
Т.е. изменение локальных переменных i, j не отражается на внешнем объекте, поскольку они занимают разные области памяти.
Когда необходимо изменить значение, параметры объявляют как указатели при помощи символа *. В определенном смысле указатель является «псевдонимом» для другой переменной. Таким образом, если вместо обычного значения передать функции указатель, фактически передаются псевдонимы переменных, что позволяет функции модифицировать их значения.
Пример:
void swap (int *i, int *j) { int tmp; tmp=*; *i=*j;
*j=tmp ;} swap (&a, &b);
При вызове используются указатели на аргументы.
Указатели в С и С++ работают примерно одинаково, но в С++ появился новый способ передачи адресов функциям. В С++ имеется возможность сообщить компилятору о необходимости генерировать вызов по ссылке другим способом. Перед параметрами в прототипе и в объявлении функции ставится & – ссылочный параметр.
void swap (int &i, int &j) {int tmp; tmp=i; i=j; j=tmp ;}
swap (a, b);
Следовательно, передача по ссылке позволяет функциям изменять внешние объекты, как и передача указателей.
Возможно объявление ссылочных переменных, когда они не являются параметрами функции, например, непараметрические ссылки или независимые ссылки. Независимая ссылка должна быть инициализирована при её объявлении, то есть ссылке должен быть присвоен адрес переменной, объявленный до неё. Между переменной и ссылкой нет различия, ссылка становится псевдонимом переменной.
Пример:
#include <iostream> using namespace std; main ()
{int j, k; int & i=j; j=10;
cout << i; k=121; i=k; cout <<j;
}
i –не зависимая или не параметрическая ссылка, которая является псевдонимом элемента j.
5.ОПРЕДЕЛЕНИЕ ПЕРЕМЕННЫХ НЕПОСРЕДСТВЕННО ПЕРЕД ИСПОЛЬЗОВАНИЕМ.
Между С и С++ существуют значительные различия относительно определения переменных. Оба языка требуют, чтобы переменные определялись перед использованием, но С заставляет определять все переменные в начале области видимости, чтобы компилятор мог выделить память для этих переменных.
Язык С++ позволяет определять переменные в произвольной точке области видимости, то есть переменная может определяться непосредственно перед использованием. Кроме того, возможна инициализация переменных в точке определения, что предотвращает некоторые коды ошибок.
Переменные также могут определяться в управляющих выражениях циклов for и while, в условиях команд if и в критериях выбора команды switch. Рассмотрим пример определения переменных непосредственно перед использованием.
#include <iostream> using namespace std; main () {
{//Начало новой области видимости int q;
// Определение переменных перед использованием for(int i=0; i<100; i++) {
q++;
//Определение в конце области видимости int p=12;
}
int p=1; //Другая переменная p
} //Завершение области видимости, содержащей q и внешнюю переменную p
while (char c=getch() != ‘q’) cout << c;
}
Во внутренней области видимости переменная p определяется перед концом области видимости. Это объявление абсолютно бесполезно, но оно показывает, что переменные действительно могут определяться в любой точке области видимости. То же самое можно сказать о переменной p из внешней области видимости.
Определение i в управляющем выражении цикла for – пример определения переменной прямо в момент использования. Область видимости i ограничивается данным циклом for, поэтому ничто не помешает заново определить переменную с именем i для следующего цикла for.
Использование определения переменных в командах while, if, switch происходит значительно реже определения в циклах for. Возможно, это объясняется ограничениями синтаксиса. Например, выражением не может содержать круглых скобок, т.е. следующая конструкция невозможна:
while ((char c=getch()) != ‘q’)
Без круглых скобок результат выглядит не так, как следовало ожидать. Проблемы возникают из-за того, что оператор != обладает более высоким приоритетом, чем =, поэтому переменной char в конечном счете присваивается значение bool, приведенное к типу char.
6. ПЕРЕДАЧА ОБЪЕКТОВ, КАК АРГУМЕНТОВ ФУНКЦИИ.
Объекты передаются таким же образом, как и переменные любого другого типа. При передаче по значению создаётся локальная копия объекта и
любые изменения, происходящие с элементами объекта в функции, не изменяют его самого.
Пример:
#include <iostream.h> class OBY {int i; public:
void set_i (int x) {i=x;}
void out_i (void) {cout <<i ;} }; void f (OBY x);
main (void)
{
OBY A; A.set_i (10); f (A); A.out_i ();
}
void f (OBY x)
{
x.out_i(); x.set_i(100); x.out_i();
}
Можно передать параметр объекта по ссылке, иными словами передать адрес объекта, в таком случае все изменения в функции изменят сам объект.
При передаче объекта в качестве параметра по значению создаётся локальная копия объекта. Может быть вызван конструктор и деструктор, динамически выделена память.
7. МАССИВЫ ОБЪЕКТОВ.
Массивы объектов создаются так же, как массивы элементов, поэтому можно создавать многомерные массивы объектов.
При создании массивов объекта в классе обязательно должен присутствовать конструктор, имеющий все параметры по умолчанию, конструктор без параметров, или, или вообще конструктора может не быть.
data md [10];
data day (30, 3, 2007); md [0]. set (1, 1, 2000);
….
md [9]. set (1, 1, 2007);
8. УКАЗАТЕЛЬ НА ОБЪЕКТ.
Как и на другие типы данных ссылаться на объект можно через указатель, при этом доступ к элементам объекта осуществляется при помощи операции “→”, аналогично структура и объединение.
Пример:
class example {…}; main (void)
{
example ob,*p; ob.set_num (1); p=&ob; p→set_num (2);
…
}
9. КЛЮЧЕВОЕ СЛОВО THIS
При вызове функции–члена класса передаётся ещё один неявный параметр – указатель на объект класса, который вызывает данную функцию. Это указатель this.
Функции–члены класса имеют доступ к private–данным. Обращаться к ним можно по имени, а можно по ссылке.
Пример:
# include <iostream.h> class cl {
int i;
public: void load i (int val)
{i=val;} ≡ {this -> i=val; }
int get_i(void) {return this -> I;}}; main (void)
{cl c; c.load_i(100); cout <<c.get_i();
}
То есть указатель на объект, для которого вызвана функция–член, является скрытым параметром функции. Главным образом this используется при написании функций–членов, которые манипулируют непосредственно указателями.
10.ПЕРЕГРУЗКА ФУНКЦИЙ И ОПЕРАЦИЙ
Полиморфизм – это свойство, позволяющее использовать одно имя для обозначения действий, общих для родственных классов. При этом конкретизация выполняемых действий осуществляется в зависимости от типа обрабатываемых данных.
Формы полиморфизма в C++:
—перегрузка функций и операций
—виртуальные функции
—обобщённые функции (шаблоны).
Перегрузка функций – это использование одноимённых функций, выполняющих аналогичные действия над данными разных. Перегрузку функций и операций можно определить как статический полиморфизм, поскольку он поддерживается на этапе компиляции.
10.1. Перегруженные функции.
Перегрузка позволяет задействовать одно имя для нескольких функций. Две или более функций могут иметь одно и тоже имя и отличаться количеством или типом параметров. Обычно перегруженные функции используются для реализации похожих действий над данными разного типа.
В Си есть стандартные функции преобразования строки в число типа int, float, long (atoi, atof, atoll). В С++ есть аналог этих функций, перегруженная функция atonum для всех типов числовых данных.
Пример:
#include <iostream.h> int sqr_it (int i);
float sqr_it (float d); long sqr_it (long l); main ()
{
int i=7; float d=1,5; long l=36000; cout<<sqrt_it (i);
cout<<sqrt_it (d); cout<<sqrt_it (l); unsigned int u=4; cout<<sqrt_it (u);
}
int sqrt_it (int i) { return i*i; }
float sqrt_it (float d)
{return d*d; } long sqrt_it (long l)
{return l*l; }
Перегруженные функции не могут отличаться только типом возвращаемого значения.
int sqrt_it (long i); long sqrt_it (long i); sqrt_it (67000);
При обращении перед компилятором встаёт неразрешимая проблема, какую из перегруженных функций выбрать.
10.2 Перегрузка конструктора
Перегрузка конструктора класса осуществляется просто объявлением конструкторов с различным набором параметров.
Пример. class date{
int month, day, year; public: date (int, int, int); date (char *);
date (int); date (void);
…}; date begin;
date today (23);
date my_day (05,06,2000); date xmas (“02 января”);
В зависимости от того, какие параметры заданы выбирается соответствующий конструктор.
10.3 Выбор экземпляра функции
При выборе требуемого экземпляра функции осуществляется сравнение типов и числа параметров и аргументов:
–параметры – формальные параметры функции,
– аргументы – реальные значения при вызове функций. Существует три возможных варианта:
1.точное соответствие типов аргументов параметрам одного из экземпляров функции.
2.соответствие аргументов параметрам может быть достигнуто путём преобразования типов аргументов к типам параметров только для одного экземпляра функции. В этом случае компилятор пытается сначала выполнить стандартные преобразования, которые определёны пользователем.
3.соответствие может быть достигнуто более чем для одного экземпляра функции. При этом необходимо иметь ввиду, что преобразование типов, определённое пользователем, имеет меньший приоритет по сравнению со стандартным преобразованием. Если соответствие типов достигается путём равноправных преобразований, компилятор выдаёт сообщение об ошибке: «невозможно определить однозначно экземпляр функции».
void f1(char); |
f1(‘b’); |
void f1(unsigned int); |
f1(723); |
void f1(char*); |
f1(“Привет”); |
Здесь наблюдается точное соответствие аргументов параметрам для одного из экземпляров функций.
Преобразование типов char, short, const ≡ int float ≡ double
void f2(long); void f2(char *);
f2(10); |
int –> long |
Осуществляется |
стандартное преобразование типов аргументов в |
параметр.
Замечание: аргумент любого числового типа можно преобразовать стандартным путём к любому числовому типу, любой указатель – к указателю на void.
void f3(long); void f3(int);

void f3(double);
Преобразования типов равноправны – компилятор выдаст ошибку.
10.4 Перегрузка стандартных операций
Перегрузку стандартных операций можно рассматривать как разновидность перегрузки функций, при которой в зависимости от типов данных, участвующих в выражениях, вызывается требуемый экземпляр операции.
Примером такой перегрузки может являться операция сложения.
C++ позволяет расширить область применимости этой операции, например, для сложения векторов в трёхмерном пространстве. Для этого нужно определить новое поведение стандартной операции “+” применительно к новым типам данных. Это допускается, если хотя бы один из операндов операции является объектом определённого пользователем класса.
Не могут быть переопределены или перегружены:
1.операция условия «?»
2.уточнение области видимости «::»
3.sizeof
Переопределение операции выполняется с помощью определения операторной функции следующего формата:
Тип_результат operator # (список аргументов) {операторы тела функции;}
Функция операции должна быть или членом класса, или дружественной функцией. В случае внешнего определения (за рамками класса) операция функции примет следующий формат:
Тип_результат класс ::operator # (список параметров) {…};
Пример: реализовать перегрузку операций сложения и присваивания относительно класса vector
# include <iostream.h> class vector {int x,y,z;
public: vector operator+(vector t); vector operator = (vector t); void show(void);
void assign (int mx, int my, int mz);}; vector vector :: operator + (vector t)
{vector temp; temp.x=x+t.x;
temp.y=x+t.y;
temp.z=x+t.z; return temp;}
vector vector :: operator=(vector t) {x=t.x;
y=t.y;
z=t.z;
return *this;}
void vector :: show(void) {cout <<x<<y<<z;}
void vector :: assigned (int mx, int my, int mz) {x=mx;
y=my;
x=mz;} main (void)
{vector a,b,c; a.assign(1,2,3); b.assign(10,10,10); c=a+b;
c=a+b+c;
c=b=a;
b.show();
c.show();
c.show();
c.show(); b.show(); }
Каждая из функций операции имеет только один параметр, в то время как сами функции определяют бинарные операции. Другой аргумент неявно передаётся с this-указателем. Здесь this ассоциируется с объектом, предшествующим знаку операции. Если используются функции-члены, то не нужны параметры для унарных операций, и требуется лишь один параметр для бинарных операций.
Возвращаемое значение имеет тип vector. Это позволяет использовать
выражения типа |
|
|
a+b+c |
либо |
c=b=a. |
Операция сложения не изменяет своих операндов; операция присваивания модифицирует операнд, стоящий слева.