ТОП 10:

Объекты, возвращаемые функцией (методом)



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

class Distance {

___ " ___ (всё тоже самое)

 

Distance plus(Distance a2);

};

//---------------------------------------------------------------------------

int main() {

Distance d1, d2(11, 6.25), d3;

d1.getdist();

d3=d1.plus(d2);

cout << "\n d1 = "; d1.showdist();

cout << "\n d2 = "; d2.showdist();

cout << "\n d3 = "; d3.showdist();

getch(); return 0;

}

//---------------------------------------------------------------------------

Distance Distance::plus(Distance a2) {

Distance s;

s.inch = inch + a2.inch;

s.feet = 0;

if(s.inch >= 12.0) {

s.inch = s.inch - 12.0;

s.feet++;

}

s.feet += feet + a2.feet;

return s;

}

В отличие от предыдущей программы, в данной программе в качестве аргумента в функцию plus(Distance) передаётся лишь один аргумент: объект d2, который складывается с объектом d1, к которому относится вызываемый метод plus(Distance). Результат сложения возвращается в главную функцию и присваивается объекту d3. Обратите внимание на то, что значение переменной d1 не изменяется, а всего лишь используется методом plus().

Рассмотрим следующий НЕРАБОТАЮЩИЙ пример, демонстрирующий отличие методов класса от функций.

Distance summa(Distance a1, Distance a2);

};

//---------------------------------------------------------------------------

int main() {

Distance d1, d2(11, 6.25), d3;

d1.getdist();

d3=summa(d1, d2); // нельзя, т.к. метод не может быть вызван без объекта (метод не понимает для кого он работает)

D3.summa(d1, d2); // для такой записи нужен void –метод, а не Distance

cout << "\n d1 = "; d1.showdist();

cout << "\n d2 = "; d2.showdist();

cout << "\n d3 = "; d3.showdist();

getch(); return 0;

}

//---------------------------------------------------------------------------

Distance Distance:: summa(Distance a1, Distance a2) {

Distance s;

s.inch = a1.inch + a2.inch;

s.feet = 0;

if(s.inch >= 12.0) {

s.inch = s.inch - 12.0;

s.feet++;

}

s.feet += a1.feet + a2.feet;

return s;

}

Структуры и классы

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

class F { private: int data1; public: void func(); }; struct F { void func(); private: int data1; };

 

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

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

Статические данные класса

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

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

class Stat {

private:

static int count; // îáùåå ïîëå äëÿ âñåõ îáúåêòîâ

public:

Stat() { count++; } // èíêðåìåíòèðîâàíèå ïðè ñîçäàíèè îáúåêòà

int getcount() { return count; }

};

int Stat::count = 0; // îïðåäåëåíèå count необходимо делать именно вне класса

//---------------------------------------------------------------------------

int main() {

Stat f1, f2, f3; //ñîçäàíèå òð¸õ îáúåêòîâ

cout << "Chislo ob'ektov: " << f1.getcount() << endl;

cout << "Chislo ob'ektov: " << f2.getcount() << endl;

cout << "Chislo ob'ektov: " << f3.getcount() << endl;

getch(); return 0;

}

В этом примере конструктор класса при создании объекта класса будет инкрементировать значение поля count. В главной функции мы определяем три объекта класса Stat. Вопрос: Сколько раз происходит инкрементирование поля count, и каков будет вывод программы? Ответ: Поскольку конструктор в этом случае вызывается трижды, инкрементирование поля count также происходит трижды. Метод getcount() возвращает значение count. Метод вызывается для каждого из объектов, и во всех случаях он возвращает одну и ту же величину: 3 (если бы мы использовали не статическое, а автоматическое поле count, то конструктор увеличивал бы на единицу значение этого поля для каждого объекта, и результатом вывода программы была бы 1).

Рассмотрим теперь строку: int Stat::count = 0;. Определение статических полей класса происходит не так, как для обычных полей. Обычные поля объявляются и определяются при помощи одного оператора. Для статических полей эти два действия выполняются двумя разными операторами: объявление поля находится внутри определения класса, а определение поля, как правило, располагается вне класса и зачастую представляет собой определение глобальной переменной. Помещая определение статического поля вне класса, мы обеспечиваем однократное выделение памяти под это поле до того, как программа будет запущена на выполнение и статическое поле в этом случае станет доступным всему классу.

Константные методы

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

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

class Distance {

… идентично предыдущей программе

void showdist() const // вывод длины на экран

{ cout << feet << "'-" << inches << "''"; }

Distance plus(const Distance&) const; // прототип

};

//---------------------------------------------------------------------------

Distance Distance:: plus (const Distance& a2) const

{

… идентично предыдущей программе

}

//---------------------------------------------------------------------------

int main()

{

… идентично предыдущей программе

}

В данной программе мы передаём аргумент в функцию (plus(const Distance&а2)) по ссылке (экономим память, т.к. ссылка это псевдоним) и хотим быть уверенными в том, что функция не изменит значения этого аргумента, в качестве которого при вызове выступает переменная d2 главной функции. Для этого параметр а2 функции plus(const Distance& а2) указывается с модификатором const в её объявлении и определении.

Модификатор const можно применять и для объектов класса (например, const Distance pole(300,0)). Если объект класса объявлен с модификатором const, он становится недоступным для изменения. Это означает, что для такого объекта можно вызывать только константные методы, поскольку только они гарантируют, что объект не будет изменён.

Деструкторы

Как мы уже видели, особый метод класса – конструктор – вызывается при создании объекта. Следовательно, можно догадаться, что существует другая функция, автоматически вызываемая при уничтожении объекта и называемая деструктором. Деструктор имеет имя, совпадающее с именем конструктора (а следовательно, и с именем класса) и предваряющееся символом ~ (тильда): class Primer {

private:

int data;

public:

Primer() : data(0) // конструктор

{ }

~Primer() // деструктор

{ }

};

Деструктор: не имеет аргументов и возвращаемого значения; не может быть объявлен как const или static; не наследуется; может быть виртуальным. Деструкторы применяются для освобождения памяти, занимаемой объектом. Если деструктор явным образом не определён, то компилятор автоматически создаёт пустой деструктор. Описывать в классе деструктор явным образом требуется в случае, когда объект содержит указатели на память, выделяемую динамически – иначе (если этого не сделать) при уничтожении объекта память, на которую ссылались его поля указатели, не будет помечена как свободная.

Указатель на деструктор определить нельзя. Без необходимости явно вызывать деструктор объекта не рекомендуется. Необходимо помнить, что деструктор вызывается автоматически, когда объект выходит из области видимости:

– для локальных объектов деструктор вызывается при выходе из блока, в котором они объявлены;

– для глобальных объектов деструктор вызывается как часть процедуры выхода из main;

– для объектов, заданных через указатели, деструктор вызывается неявно при использовании операции delete.

Лекция 5(11 стр.)

МАССИВЫ И КЛАССЫ

 

На примере класса Tovar мы уже видели, что массивы могут быть использованы в качестве полей класса. Рассмотрим пример класса, который моделирует структуру данных – стек.

class Stack {

private:

static const int MAX=5; // enum { MAX = 5 }; можно заменить

int st[MAX]; // ñòåê â âèäå ìàññèâà

int top; // âåðøèíà ñòåêà

public:

Stack( ) { top = 0; } //

void push(int a) { // положить в стек

st[top] = a;

if(top < MAX-1) top++;

}

int pop( ) { // изъять из стека

int res=st[top];

top--;

return res;

}

int max() { return MAX; } //возвращает, сколько в стеке элементов

};

//---------------------------------------------------------------------------

int main ( ) {

Stack s1;

int i;

for(i=1; i<=s1.max(); i++) s1.push(i); //вызываем метод для класса

for(i=0; i<s1.max(); i++) cout << s1.pop() << endl;

getch(); return 0;

}

Важным членом этого класса является массив st. Переменная top хранит индекс последнего элемента, положенного в стек. Этот элемент располагается на вершине стека.

Стандарт С++ позволяет объявлять константы внутри класса. Таким образом вместо строки enum { MAX = 5 }; мы использовали запись static const int MAX = 5; , но имейте ввиду, что некоторые компиляторы (в отличие от С++Builder) не позволяют использовать эту конструкцию.

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

 

Массивы объектов

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

class Distance { //êëàññ àíãëèñêèõ ìåð äëèíû

private:

int feet;

float inch;

public:

void getdist() { //ïîëó÷åíèå èíôîðìàöèè ïðè ââîäå

cout << "\nVvedi futy: "; cin >> feet; //ввод. Dist[0].feet = то, что user ввел

cout << "Vvedi duimy: "; cin >> inch; //вывод Dist[0].inch = то, что user ввел

}

void showdist() const { //âûâîä èíôîðìàöèè

cout << feet << "'-" << inch << endl;

}

};

//------------------------------------------------------------------

int main() {

const int MAX = 100;

Distance dist[MAX]; //ìàññèâ îáúåêòà

int n=0; //ñ÷¸ò÷èê äàííûõ

char ans; //îòâåò ïîëüçîâàòåëÿ ('y' èëè 'n')

do {

cout << "Vvod dliny N " << n+1;

dist[n].getdist(); // ф-я вызова строк ввода-вывода для массива

n++;

cout << "\nProdol. vvod (y/n)?: "; cin >> ans;

}while(ans == 'y' && n<MAX);

for(int j=0; j<n; j++) { //ïîêàçàòü âñå ââåä¸ííûå çíà÷åíèÿ äëèí

cout << "\nDlina N " << j+1 << ": ";

dist[j].showdist(); //выводит на экрна результат для текущего значения

}

getch(); return 0;

}

Д/З Измените данную программу таким образом, чтобы она позволяла вычислять сумму элементов массива dist.
Ответ class Distance { private: int feet; float inch; public: Distance() { feet=0; inch=0.0; } void getdist() { cout << "\nVvedi futy: "; cin >> feet; cout << "Vvedi duimy: "; cin >> inch; } void showdist() const { cout << feet << "'-" << inch << endl; } Distance plus(Distance a2) { // Distance s; s.inch = inch + a2.inch; s.feet = 0; if(s.inch >= 12.0) { s.inch = s.inch - 12.0; s.feet++; } s.feet += feet + a2.feet; return s; } }; //------------------------------------------------------------------ int main() { const int MAX = 100; Distance dist[MAX]; int n=0, j; char ans; //ответ от пользователя do { cout << "Vvod dliny N " << n+1; dist[n].getdist(); n++; cout << "\nProdol. vvod (y/n)?: "; cin >> ans; }while(ans == 'y' && n<MAX); for(j=0; j<n; j++) { cout << "\nDlina N " << j+1 << ": "; dist[j].showdist(); } cout << "\n Vychislit summu elementov(y/n): "; cin >> ans; if(ans=='y') { Distance sum; //типа sum=0 sum.feet=0 sum.inch=0 for(j=0; j<n; j++) sum = sum.plus(dist[j]); cout << "\n Summa elementov = "; sum.showdist(); } getch(); return 0; }

 

Рассмотрим теперь класс Tovar на примере программы, позволяющей: 1) создавать динамический массив типа Tovar и дозаписывать его в бинарный файл; 2) считывать бинарный файл и динамически формировать массив типа Tovar.

const int N=30;

class Tovar {

private: //свойства

char name[N];

int number;

float cena;

public:

void getdata() { //получить данные с клавы

cin.get();

cout << "\n Vvedite naimenovanie tovara: "; cin.getline(name, N);

cout << " Vvedite nomer tovara: "; cin >> number;

cout << " Vvedite stoimost tovara: "; cin >> cena;

}

void showdata() {

cout << name << " " << number << " " << cena;

cout << endl;

}

void savefile(char* fname) { //сохранить,

ofstream fout(fname, ios::binary | ios::app);//создать поток для записи

if(!fout) { cout << "\nOshibka zapisi faila!"; getch(); return; } //все ли норм с файлом

fout.write(name, N);

fout.write(reinterpret_cast<char*>(&number), sizeof(int));

fout.write(reinterpret_cast<char*>(&cena), sizeof(float));

fout.close();

}

void readfile(char* fname, int sme) {

ifstream fin(fname, ios::binary);

if(!fin) { cout << "\nOshibka chtenia faila!"; getch(); return ; }

fin.seekg(sme, ios::beg);

fin.read(name, N);

fin.read(reinterpret_cast<char*>(&number), sizeof(int));

fin.read(reinterpret_cast<char*>(&cena), sizeof(float));

}

};

//---------------------------------------------------------------------------

int main() { //главная функция

int K, i, dfb, dlz, KZ, sm;

char otv, *filename="c:\\os\\tovar_class\\tovar3\\tovarbd.dat";

cout << "Skolko zapisei hotite dobavit: "; cin >> K;

Tovar *tm = new Tovar [K]; //создаем динам. Массив

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

cout << "\nZapis " << i+1 << ":" << endl; //просим ввести данные

tm[i].getdata();

}

cout << "\n Sohranit dannye?(y/n) "; cin >> otv;

if(otv=='y') {

for(i=0; i<K; i++) tm[i].savefile(filename);

cout << "\n Fail zapisan!" << endl;

}

delete [] tm;

 

ifstream fin(filename, ios::binary); //создали поток. Откуда читать. Что файл бинарный

if(!fin) { cout << "\nOshibka chtenia faila!"; getch(); return 1; }

fin.seekg(0, ios::end); dfb = fin.tellg(); fin.seekg(0, ios::beg); //сдвигаем, определяем байты, сдвигаем обратно, узнаем сколько товаров в записи записано

dlz = N + sizeof(int) + sizeof(float);

KZ=dfb/dlz; //вот узнаем сколько

fin.close();

Tovar *mas = new Tovar [KZ];

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

sm=i*dlz; //смещение чтобы смещать указатель на считанную строку

mas[i].readfile(filename, sm);

}

cout << "\n Fail sodergit: " << endl;

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

mas[i].showdata();

}

getch(); return 0;

}

 

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

const int N=30;

//---------------------------------------------------------------------------

class Tovar {

private:

char name[N];

int number;

float cena;

public:

void dobav_zap() {

cout << "\n Vvedite naimenovanie tovara: "; cin >> name;

cout << " Vvedite nomer tovara: "; cin >> number;

cout << " Vvedite stoimost tovara: "; cin >> cena;

}

void show() {

cout << name << " " << number << " " << cena << endl;

}

char pop(int i) { //возвращает текущее имя товара

return name[i];

}

char* getname() {

return name;

}

};

void sort(Tovar mas[], int R);

void redakt(Tovar ma[], int K);

void savefile(char* fname, Tovar *mm, int& K);

//---------------------------------------------------------------------------

int main() {

char ans;

char *fn = "c:\\os\\tovar_class\\tovar_cl.dat";

int KZ=0, dfb=0;

Tovar *m;

cout << "\n Dobavit zapis v fail?(y/n) "; cin >> ans;

if(ans=='y') {

Tovar a1;

a1.dobav_zap();

ofstream fout(fn, ios::binary | ios::app);

if(!fout) { cout << "\nOshibka faila!"; getch(); return 1; }

fout.write(reinterpret_cast<char*>(&a1), sizeof(Tovar));

fout.close();

}

cout << "\n Otsortirovat i pokazat zapisi?(y/n) "; cin >> ans;

if(ans=='y') {

ifstream fin(fn, ios::binary);

if(!fin) { cout << "\nOshibka faila!"; getch(); return 1; }

fin.seekg(0, ios::end); dfb=fin.tellg(); fin.seekg(0, ios::beg);

KZ = dfb/sizeof(Tovar);

m = new Tovar [KZ];

fin.read(reinterpret_cast<char*>(m), dfb);

fin.close();

sort(m, KZ);

for(int i=0; i<KZ; i++) m[i].show();

cout << "\n Budete redaktirovat zapisi?(y/n) "; cin >> ans;

if(ans=='y') {

redakt(m, KZ);

cout << "\n Sohranit dannye?(y/n) "; cin >> ans;

if(ans=='y') savefile(fn, m, KZ);

}

}

getch(); return 0;

}

//---------------------------------------------------------------------------

void sort(Tovar mas[], int R) {

int i, j;

for(i=0; i<R-1; i++) {

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

if(mas[j].pop(0) < mas[i].pop(0)) {

Tovar a = mas[i]; mas[i] = mas[j]; mas[j] = a;

}

}

}

}

//---------------------------------------------------------------------------

void redakt(Tovar ma[], int K) {

char namer[N];

cout << "\n Ukagite naimenovanie tovara: "; cin >> namer;

int l = strlen(namer);

int ks=0;

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

if(strncmp(namer, ma[i].getname(), l)==NULL) {

ma[i].dobav_zap();

ks++;

}

}

if(ks==0) { cout << "\n Takogo reisa net !"; }

}

//---------------------------------------------------------------------------

void savefile(char* fname, Tovar *mm, int& K) {

ofstream fout(fname, ios::binary);

if(!fout) { cout << "\nOshibka faila!"; getch(); return ; }

fout.write(reinterpret_cast<char*>(mm), sizeof(Tovar)*K);

cout << "\n Dannye sohraneny.";

fout.close();

}

 

СТРОКИ







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

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