PDA

View Full Version : Трудна задача по информатика



ritobis
12-11-2011, 16:21
Някой може ли да ми разясни как работи тази програма.Трябва да събира 2 дроби.И ако може да ми обясни как работи конструктора.И ако се сещате да ми препоръчате свестен учебник да си купя(нещо като помагало).благодаря предварително

#include <iostream>
using namespace std;
class Frac
{
int Num, Den;
bool Corr;
void norm()
{
if(!Corr) return;
if(Num==0)
{
Den=1;
return;}
int a=Num , b=Den, c;
if(a<0) a=-a;
if(a>b)
{
c=a;a=b;b=c;}
do
{c=a%b;
if(c==0) break;
a=b;
b=c;
}
while (true);
Num/=b;
Den/=b;}
public:
int getNum() const{return Num;};
int getDen() const{return Den;};
Frac sum(Frac a=0, Frac b=1)
{
n,d,n2;
n=a.Num*b.Den;
n2=b.Num*a.Den;
n+=n2;
d=a.Den*b.Den;
return Frac(n,d);
}
Frac(int a,int b)
{
Num=a;
Den=b;
Corr=b>0;
norm();
}
void setNum(int a)
{
Num=a;
norm();
}
setDen(int a)
{
Den=a;
Corr=a>0;
norm();
}
void show()
{
if(Corr)
{
cout<<Num;
if(Den>1) cout<<"/"<<Den;
cout<<endl;
else cout<<"Incorrect";
};
Frac operator +(const Frac &a, const Frac &b)
{
int n,d;
n=a.getNum()*b.getDen+b.getNum()*a.getDen();
d=b.getDen()*a.getDen();
return Frac(n,d);
}
int main()
{
Frac f(1,2), g(3,4);
Frac c=f+g;
c.show();
return 0;
}

MaGuSs
12-11-2011, 16:48
Толкова много код за 2 дроби :X. Предпочитам сам да си го напиша отколкото да използвам готово от интернет...

ritobis
12-11-2011, 17:22
е да де ама то хватката беше да стане с класове.Някой ако може да ми обясни как по принцип работи един конструктор
и какво прави - set<NE6TO>

asp1r1n94
12-11-2011, 20:55
Е ако го подредиш може и някой да ти обърне внимание...

Chacho
12-16-2011, 00:28
Така, доста неща има да понапиша. Понеже ми липсва условие, ще дам моя вариант на програмата, към която се целим. Искаме да създадем клас дроби, които да имат следната функционалност:

-С помощта на методите getDen() и getNum() да извличаме стойностите съответно на знаменателя и на числителя
-С помощта на методите setDen() и setNum() да задаваме нови стойносто съответно на знаменателя и на числителя
-С помощта на метода sum() да събираме две дроби
-Да дефинираме оператор "+", които ще ни позволи да събираме две дроби а и b, записвайки a+b
-С помощта на метода show() да извеждаме дробта в четлив вид - "числител/знаменател", стига дробта да съществува (знаменателят да е различен от 0)

За да направим програмата ни качествена, избираме обектно-ориентираният подход, които носи със себе си известен брой принципи за програмиране. Една от главните идеи на ООП (обектно-ориентираното програмиране) е да създадем една "черна кутия". В нея ще сложим нещата, които са нужни, за да работи програмата, но не са нужни на програмиста, който ще седне след нас да чете кода. Той ще работи със всичко извън кутията, без да я отваря и да разглежда какво има вътре. В конкретния случай, той трябва да може да използва дробите безпроблемно, без да го вълнува как точно си реализирал този клас на езика С++.

Една от техниките, чрез която постигаме ефекта на черната кутия, е капсулирането. Капсулирането е идеята, да лишим потребителите от достъп до съответни части от програмата (по-точно от класовете). Това става с помощта на три думички (макар, че в случая ще ползваме две). Думите са: public, private и protected. На нас са ни нужни първите две. Public позволява външния (извънкласния) достъп до данните и методите, които се намират след него. Private пък го забранява и достъп до данните след тази дума имаме само в дефиницията на класа.

За да реализираме дробите на С++ според принципите на ООП, ще подходим така:

Създаваме клас Frac, в които недостъпните данни ще са целочислените числител и знаменател - int Num и int Den. За да не загубим функционалност, ще създадем подходящи функции, които ще ни позволят да работим спокойно с Num и Den, но и ще се погрижат за евентуалните грешки, които могат да се появят при работа с двете данни. За да засичаме грешките, ще имаме нужда и от една булева променлива Corr, която ни казва, дали дробта е съществуваща.

За да можем да създаваме обекти от класа Frac, като им задаваме стойности за числителят и знаменателят, ще ни е нужно да си дефинираме конструктор. Той се грижи за това, да се създават обекти. Можеш да гледаш на него като на функция, която инициализира обектът. Конструкторите имат един много важен частен случай - конструкторът по подразбиране. Този вид конструктор има ролята да създава обекти, без да са му подадени параметри (без да даваме стойности на числителя и знаменателя), като ги създава със стойности, които са зададени по default (примерно числител=0, знаменател = 1).

За да можем да бърникаме по дробта и да променяме стойностите на чис. и знам. дори и след създаването на обекта, ще създадем два метода за работа с тези данни setNum() и setDen(). Още ги наричаме модификатори. Ако имаме обект f1 от класа Frac, то f1.setNum(5) ще е начинът, по който ще променим стойността на числителят на f1 на 5.

Сигурен съм, че вече схващаш идеята. Обектно-ориентираният подход не е задължителен и е съвсем възможно да напишеш всичко праволинейно, без да лишаваш потребителите от достъп до данни/функции , без да намесваш класове и пр. Но сигурно сам забелязваш колко по-стегнато, по-подредено и най-вече - по-сигурно е всичко. Сериозните резултати идват тогава, когато се захванеш с нещо много по-голямо и лишиш ли кода си от подреденост, се обричаш на провал.


Сега ще разгледаме и малко от математическата част на задачата, след което ще сме готови за работа. Понеже ще събираме дроби е нужно да видим точно как се събират.

Събиране:
Ако имаме две дроби а/b и c/d, то сборът им е (a/b) + (c/d) = (a.d+c.b) / (b.d)

Другият по-интересен и по-сложен въпрос е, как да съкращаваме числителят и знаменателят. За целта ще ни е нужно да намираме най-големия общ делител (НОД), след което ще разделяме числителят и знаменателят на него. Ще си създадем една функция int GCD(int a, int b), която ще ни намира НОД-а. Понеже тази функция ни е нужна за реализирането на функцията norm(), която ще съкращава дробта, ние можем да поставим и GCD() в private частта, за да постигнем по-добро капсулиране. А от това, че искаме дробта да ни е в съкратен вид, когато се захванем с нея, следва да сложим и norm() в private частта.

Как намираме НОД? Евклид ни е дал един достатъчно качествен алгоритъм, който всъщност представлява и доказателството си за коректност. Според теоремата за деление с остатък на цели числа имаме, че за всеки а и b е изпълнено:
а=b.q1+r1, където q1 наричаме частно, а r1 - oстатък.
След това представяне, ако r=/=0, представяме във същият вид делението на b с r1:
b=r1.q2+r2
Повтаряме процедурата, докато не стигнем до oстатък 0.
Без да изпадаме в подробности защо, получаваме, че последният ненулев остатък е най-големият общ делител.

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



int GCD(int a, int b)
{
if (b==0)
{return a;}
else
{return gcd(b, a % b);}
}


Последното нещо, на което ще обърна внимание, се отнася до записа на дробта. Тъй като ще я извеждаме във вида а/b, то (-а)/(-b) е екивалентен на a/b, но по-нечетлив и ще трябва да го обработим този случай (в метода show())

Сега вече сме готови да преминем към писането на код. С помощта на коментари ще известявам коя част на коя идея отговаря.


#include <iostream>
#include <cmath>
using namespace std;

class Frac
{
private:
//Danni
int Num, Den;
bool Corr;
//Metodi
int GCD(int a, int b) //funkciqta za namirane na NOD (izpolzvame absolutna stoinost nakraq, za da izbegnem problemite pri namirane na otricatelen NOD)

{
if (b==0)
{return a;}
else
{return abs(GCD(b, a%b));}
}

void Norm() //funkciqta za sukrashtavane na drobta
{
if (Corr)
{
Num/=GCD(Num, Den);
Den/=GCD(Num, Den);
}
}

public:

//Publichni metodi

int GetNum()
{
return Num;
}

int GetDen()
{
return Den;
}

void setNum(int a)
{
Num=a;
if (Num<=0 && Den<0)
{
Num=-Num; Den=-Den;
}
Norm();
}

void setDen(int b)
{
if(b!=0)
Den=b;
if (Num<=0 && Den<0)
{
Num=-Num; Den=-Den;
}
Corr=(b!=0);
Norm();
}

Frac(int a=0, int b=1) //Konstruktor, koito vurshi rolqta i na konstruktor po podrazbirane
{
Num=a;
if (b!=0)
Den=b;
if (Num<=0 && Den<0)
{
Num=-Num; Den=-Den;
}
Corr=(b!=0);
Norm();
}

friend Frac operator+(Frac &, Frac &);


void show()
{
if (Corr) //Ako drobta sushtestvuva
{
cout<<Num<<"/"<<Den;
}
else //Ako drobta ne sushtestvuva
{
cout<<"Ne moje znamenatelqt da e 0";
}
}
};


int main()
{
int x;

Frac a(325,5), b(4), c;

a.show();
cout<<endl;
b.show();
cout<<endl;
c.show();
cout<<endl;

c=a+b;
c.show();
cout<<endl;

b.setDen(5);
a.setNum(4);

b.show();
cout<<endl;
a.show();
cout<<endl;

c=a+b;
c.show();
cout<<endl;

a.setDen(-4);
a.setNum(-3);
a.show();

cin>>x;

return 0;

}

Frac operator+(Frac & a, Frac & b)
{
Frac result(a.GetNum() * b.GetDen() + a.GetDen() * b.GetNum(), a.GetDen() * b.GetDen());
return result;
}






Можеш да ползваш много учебници:

-Програмиране на С++ на Магдалина Тодорова Част 2 (Част 1 също е добър, но ООП-то е в Част 2)
-Програмиране на С и С++, малък формат книжка, учебник от ТУ София
-Stroustrup: The C++ Programming Language (Third Edition)

По тракерите има много учебници. Трябва ти един с основните неща и доста писане на код. След това можеш да минеш на по-специализирана тематика, като стандартната библиотека на С++, дискретна математика и теория на графите, литература за С++ за напреднали. Един страхотен учебник е "Програмиране=++Алгоритми" на Преслав Наков и още един автор.

ohluv
12-18-2011, 17:04
чако толкова ли тие било скучно ве :D

Chacho
12-19-2011, 11:23
Често ми се случва :> Компенсирам с това, че ме мързи от няколко месеца да ида до университета.

ritobis
12-19-2011, 13:53
Chacho, имаш една бира от мен.Малко ми е кофти щото е ООП щото го учим от скоро пък бяхме изостанали със стария даскал и сега тва всичкото наведнъж трябва да го вземем.Мерси

Chacho
12-19-2011, 15:02
Не се притеснявай! Въпрос на свикване е и скоро ще ти е удобно да пишеш и обектно-ориентиран код. :)