Jump to content

SUBIECTE NOI
« 1 / 5 »
RSS
Zapp fix

Rulment pt diferential 4motion

Lipire filtru la baterie ikea

Meserias nu mai vine sa termine l...
 Soferii prinsi bauti sau drogati ...

geometrie autorulota

Sfat achiziție laptop ~4500 ...

A fost adoptata legea privind alc...
 La multi ani Costi

Vreau sa ofer imprumut pentru con...

Usa de garaj la o magazie existenta

The worst of evil (2023)
 Proiect de lege: Romanii vor achi...

Dopuri / casti amortizare zgomot ...

Ce componente trebuie sa schimb d...

2 job-uri de 8 ore simultan.
 

De la C la C++

- - - - -
  • Please log in to reply
44 replies to this topic

#1
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
De la C la C++

Știu că pare ciudat, dar voi incepe acest tutorial printr-o negare:

NU, a programa în C++ nu înseamnă a folosi cout în loc de printf. E ca și cum am apela la un TIR pentru a muta 5 kg de nisip dintr-o curte in alta.

Acest tutorial are ca scop stârnirea curiozității în ceea ce privește C++ și clarificarea unor des întâlnite confuzii. Pentru însușirea limbajului e nevoie de multă lectură + exerciții, un punct de plecare fiind http://www.cplusplus.com/doc/tutorial/ .

Întâi a fost puiul, apoi a urmat găina

Limbajul C a apărut înaintea lui C++. Să ne amintim ce curpinde:

  • Variabile primitive: int, float, double, ...
  • Structuri de date, enumerari, uniuni
  • Instrucțiuni if, for, while, ...
  • Funcții
  • Pointeri
  • Macro-uri

... și cam atât. Toate acestea, împreună cu biblioteciile puse la dispoziție de sistemul de operare și, opțional, de terți, ne permit să facem cam orice.

A putea face însă orice, nu înseamnă însă că e și ușor, sau productiv când lucrurile se complică.

Limitări ale limbajului C:

  • Nu permite definirea mai multor funcții cu același nume

    Dacă dorim o funcție ce scrie ceva într-un log, nu putem crea două funcții ce acceptă parametrii diferiți, dar, care să aibe acelasi nume:

    write_log(1);
    write_log("ceva");
    
    

    Va fi nevoie de funcții cu nume diferit, mai greu de ținut minte:

    write_log_int(1);
    write_log_string("ceva");
    
    

    În C++ este posibilă și prima variantă.

  • La "nivel global", C nu permite nici un mod de a grupa diverse elemente. Să presupunem că dorim să folosim două biblioteci diferite, a căror headere definesc câte o constantă VERSION. Imediat ce includem al doilea header, compilatorul ar urla că s-a definit deja VERSION. Soluția ar fi ca fiecare bibliotecă să-si definească într-un mod cât mai unic constantele/variabilele globale/structurile/ ... încât să fie minimă șansa unei coliziuni.

    Ca o soluție la această problemă C++ introduce spațiile de nume, namespaces. Acestea ne permit definirea unor cataloage ce elimină ambiguitățiile.

    header1.h
    namespace mere
    {
    	const int VERSION = 1;
    }
    
    

    header2.h
    namespace pere
    {
    	const int VERSION = 20;
    }
    
    

    #include "header1.h"
    #include "header2.h"
    
    ...
    int x = mere::VERSION;
    int y = pere::VERSION;
    ...
    
    

    #include "header1.h"
    
    using namespace mere; //folosim doar mere și nu dorim sa scriem mere:: înainte de fiecare element
    
    ...
    int x = VERSION;
    ...
    
    

  • Utilizarea excesivă a pointerilor

    Deseori se întâmplă în C să avem o funcție ce primește un pointer spre o valoare, pentru a putea modifica acea valoare.

    void functie(int* a) 
    {
    	if (a != NULL)
    	{
    		*a = 10;
    	}
    }
    
    int main()
    {
    	int a;
    	functie(&a);
    	printf("%d", a); //10
    };
    
    

    Această metodă este una ce poate conduce foarte ușor la erori:

    • Pointerul trebuie întotdeauna verificat dacă nu e nul
    • Suntem tentați să scriem a = 10, în loc de *a = 10, mai ales dacă e vorba de structuri mai complexe

    C++ aduce o soluție la aceste probleme prin introducerea referințelor. Acestea fac codul mult mai lizibil și reduc posibilitatea de a greși:

    void functie(int& a) 
    {
    	a = 10;	
    }
    
    int main()
    {
    	int a;
    	functie(a);
    	printf("%d", a); //10
    };
    
    


  • Atașarea unui comportament datelor

    Atunci când scriem o aplicație, creăm structuri de date care modelează situații reale. În C accentul cade însă pe cum sunt stocate aceste date în memorie, și nu pe comportamentul asociat lor.

    Fie drept exemplu o structură ce stochează informații despre o persoană

    struct Persoana 
    {
    	char nume[128];
    	char prenume[128];
    	int varsta;
    }
    
    

    Structura, în actuala ei formă, doar stochează informații despre o persoană. Pentru a atașa un comportament acestor date, se pot defini în cadrul structurii pointeri spre funcții ce operează cu aceste date.

    struct Persoana 
    {
    	char nume[128];
    	char prenume[128];
    	int varsta;
    	
    	void (*set_nume)(struct Persoana*, const char*);
    	char* (*get_nume)(struct Persoana*);
    	void (*set_prenume)(struct Persoana*, const char*);
    	char* (*get_prenume)(struct Persoana*);
    	void (*set_varsta)(struct Persoana*, int);
    	int (*get_varsta)(struct Persoana*);	
    	int (*e_majora)(struct Persoana*);
    }
    
    //o astfel de functie trebuie să primească ca parametru un pointer spre o instanță a structurii
    int persoana_e_majora(struct Persoana* persoana)
    {
    	... //verificare daca persoana e null
    	return persoana->varsta >= 18;
    }
    
    //la inițializarea unei noi instanțe, trebuie specificați pointerii spre fiecare funcție
    
    void init_persoana(struct Persoana* persoana)
    {
    	... //verificare daca persoana e null
    	...
    	persoana->e_majora = persoana_e_majora;
    }
    
    //structura se va folosi astfel
    ...
    Persoana persoana;
    init_persoana(&persoana);
    ...
    if (persoana->e_majora(&persoana))
    {
    	...
    }
    ...
    
    

    Până acum pare că se descurcă destul de bine C-ul, pare mai complicat, dar am făcut treaba folosindu-l. Problema apare când dorim să definim o nouă structură care să aducă noi informații față de una existentă.

    Fie de exemplu o structură Elev. Un elev este totodată o persoană (are nume, prenume, vârstă, etc.), dar are și caracteristici speciale, ca de exemplu clasa în care se află sau numele instituții de învățământ pe care o frecventează. Crearea acestei structuri, fără a rescrie tot codul de mai sus aferent unei persoane generice, se poate realiza în felul următor:

    struct Elev
    {
    	struct Persoana persoana;	
    	int clasa;
    	void (*set_clasa)(struct Elev*, int);
    	int (*get_clasa)(struct Elev*);
    	...
    }
    
    void init_elev(struct Elev* elev)
    {
    	... //verificare daca elev e null
    	init_persoana(elev);
    	...
    	elev->set_clasa = elev_set_clasa;
    	...
    }
    
    

    Întrucât, în memorie, un Elev are primii sizeof(Peroana) bytes la fel cu cei ai unei Persoane, putem trimite un pointer spre un Elev, unei funcții ce se asteaptă la un pointer spre o Persoana. Se pare că și această problemă s-a rezolvat destul de ușor în C.

    Până acum, structura Persoana doar a adăugat elemente noi față de structura Persoana. Problema apare când structura copil nu doar adaugă un comportament nou, ci și modifică unul existent în structura mamă. Fie o funcție munceste prezente atât în Persoană cât și în Elev, funcție al cărei cod este diferit pentru cele două situații. Prin setarea adecvată a pointerilor și apelarea

    persoana->munceste(&persoana);
    elev->munceste(&elev);
    
    

    se poate obține un comportament adecvat fiecărei situații, însă dorința este ca atunci când o funcție primește un pointer spre o Persoana să apeleze comportamentul unei persoane dacă acel pointer țintește spre doar o Persoana, însă să apeleze comportamentul unui Elev, dacă pointerul țintește defapt spre un Elev.

    Aici intervine C++ în ecuație, împreună cu polimorfismul.

    Quote

    De ce aș dori să fac așa ceva? Nu pot apela manual funcția adecvată în fiecare caz?

    În practică acest lucru este aproape imposibil. De ce? Fiindcă atunci când scriu codul unei funcții ce interacțiunează cu o Persoana, n-am de unde să știu câte alte structuri se vor mai defini, derivate din Persoana. Să modific la definirea fiecărei noi astfel de structuri, a tuturor acestor funcțiilor iar nu este fezabil, mai ales dacă ele provin chiar dintr-o bibliotecă terță.

  • Protecția membriilor structurilor

    În C, oricine poate accesa orice membru al unei structuri. Să presupunem însă că definesc o structură ce conține următoarele informații:

    struct Elev
    {
    	...
    	int varsta;
    	...
    };
    
    

    Deoarece acea vârstă trebuie să respecte anumite restricții (ex. nu poți fi elev la vârsta de 1 an), definesc o funcție ce verifică aceste condiții și salveaza valoarea doar dacă le îndeplinește.

    void set_varsta_elev(struct Elev* elev, int valoare) 
    {
    	...
    	if (valoare > 5 && ...)
    	{
    		elev->varsta = valoare;
    	}
    }
    
    

    Cu ce mă ajută însă această funcție, dacă un utilizator rău-intenționat sau chiar neatent, setează direct valoarea câmpului structurii, ocolind funcția mai sus definită?

    C++ vine în ajutor, introducând encapsularea datelor. Astfel se poate stabili care membrii ai structurii vor putea fi accesați din exterior și care nu:

    struct Elev
    {
    	...
    	private:
    		int varsta;
    	...
    };
    
    

    • public - Datele sunt accesibile din exterior
    • private - Datele sunt accesibile doar din cadrul structurii/clasei care le-a definit
    • protected - Datele sunt accesibile din cadrul structurii/clasei care le-a definit cât și din cadrul structurilor/claselor ce o moștenesc

    Astfel, în C++, o structură are toți membrii implicit publici, pe când o clasă îi are privați.

Programarea orientată pe obiect sau un alt mod de a vedea lucrurile

C fiind un limbaj destul de low-level, ne face să ne gândim întotdeauna la ce se petrece la nivel de memorie, chiar de instructiuni. Nu e nimic rău în asta, mai ales când învățăm, dar ce ne facem atunci când lucrăm la o aplicație complexă, a cărei multitudine de cerințe ne dă destul de gândit încât să nu mai dorim să ne batem capul la fiecare pas și cu reprezentarea lucrurilor în memorie?  

Programarea orientată pe obiect (POO, OOP) se bazează foarte mult pe gândirea în termenii problemei de rezolvat și nu a modului de funcționare al calculatorului. Ea ne face să gândim lucrurile în termen de componente, fiecare rezolvând o anumită problemă, iar utilizarea combinată a acestor componente ne va aduce soluția la problema noastră. Fie următoarea cerință:

Quote

Să se scrie un program care citește de pe un site, al cărui nume este primit din partea utilizatorului, valoarea cursului valutar al EURO pentru data curentă și care stochează această valoare într-un fișier.

În C o astfel de abordare ar arăta în felul următor:

struct ConsoleReader reader;
struct HTTPDownloader downloader;
struct ExchangeRateParser parser;
struct FileWriter writer;

init_ConsoleReader(&reader);
init_HTTPDownloader(&downloader);
init_ExchangeRateParser(&parser);
init_File_Writer(&writer, "euro.txt");

char* siteURL = ConsoleReader_prompUser(&reader);
char* siteContent = HTTPDownloader_get(&downloader, siteURL);
double euroExchangeRate = ExchangeRateParser_extract(&parser, siteContent, "euro");
FileWriter_write(&writer, euroExchangeRate);


Astfel avem o structură pentru fiecare obiect și o gramadă de funcții ce operează cu date din aceste structuri, fiind astfel necesar să transmitem de fiecare dată un pointer spre structura în cauză.

C++ simplifică lucrurile permitând definirea funcțiilor în interiorul structurilor/claselor, funcții ce vor primi un prim argument ascuns de tip pointer spre instanța structurii/clasei în cauză, argument denumit this. De asemenea o structură/clasă în C++ poate avea 1..n funcții de inițializare (constructor) și o funcție de curățenie (destructor).

Pe lângă această simplificare, C++ asigură consistenţa datelor din obiect. Acestea vor fi întotdeauna inițializate corect și vor păstra valori valide pentru logica obiectului, grație codului scris de dezvoltatorul obiectului. În caz, această sarcină cădea parțial și în sarcina utilizatorului, acesta trebuind să fie atât șofer cât și mecanic.

Structura FileWriter poate fi implementată în C++ în felul următor:

struct FileWriter
{
	private:
		FILE* file;
	public:
		FileWriter(const char* name) //constructor, are loc la creerea unei noi instanțe a obiectului
		{
			this->file = fopen(name, 'w'); //this poate fi omis, compilatorul intelegand despre ce e vorba
			... //verificare daca fisierul s-a deschis cu succes, etc
		}
		~FileWriter() //destructor, functie executata la distrugerea instanței obiectului
		{
			... //verificare daca file e valid, etc...
			fclose(fisier);
		}
		void write(const char* value)
		{
			... //verificare daca file e valid, etc...
			fprintf(this->file, "%s", value);
		}
}


Atunci când se dorește alocare dinamică a memoriei în C++, nu se va folosi malloc()/free() deoarece acestea doar alocă memorie pentru o structură, nu și apelează funcțiile constructor/destructor.

Se vor folosi nou introdușii operatori new respectiv delete:

FileWriter* fileWriter = new FileWriter("a.txt");
...
delete fileWriter;



Astfel, o posibilă rezolvare a acestei probleme în C++ arată în felul următor:

ConsoleReader reader;
HTTPDownloader downloader;
ExchangeRateParser parser;
FileWriter writer("euro.txt");

String siteURL = reader.promptUser();
String siteContent = downloader.get(siteURL);
double euroExchangeRate = parser.extract(siteContent, "euro");
writer.write(euroExchangeRate);


Avem astfel 4 clase care fiecare rezolvă o parte a problemei, iar împreună ne rezolvă întreaga problemă. Ce e important de observat și reținut e că aceste clase indeplinesc fiecare câte o singură problemă și că nu sunt legate între ele (putând fiecare din ele să fie folosită și într-o altă aplicație - reutilizarea codului):

  • HTTPDownloader descarcă conținutul unui site, nu-l interesează cum s-a aflat adresa acestuia (adica nu se ocupă tot el de interogarea utilizatorului). Astfel poate fi la fel de bine folosită clasa și pentru a descărca conținutul unui site a cărui adresă a fost citită dintr-un fișier.
  • ExchangeRateParser caută valoarea cursului valutar într-un șir de caractere, indiferent de unde a provenit acel șir de caractere. Poate proveni la fel de bine și de pe disc.
  • FileWriter scrie rezultatul într-un fișier, indiferent de unde provine valoarea.

În POO se pune foarte mult accent și pe abstractizare. Astfel eu poate nici nu știu cum se descarcă conținutul unui site, componenta HTTPDownloader fiind scrisă de altcineva. Mi-e suficient însă să știu ce face această clasă și cum să o folosesc pentru a-mi rezolva treaba cu ajutorul ei.

Quote

Bun bun, dar dacă nu ne gândim la aspectele low-level, aplicațiile nu vor mai rula optim, va dura mai mult execuția lor!

Parțial adevărat. Lucrurile în aplicațiile "concrete" sunt mult mai complexe decât în cazul problemelor de liceu/olimpiade, în cazul cărora, alegerea unui algoritm greșit poate face diferența dintre rezolvarea unei probleme în 0.5 secunde sau în 15 secunde.

Dar hai să analizăm mai atent problema de mai sus.

În acest caz, se poate observa foarte repede că cea mai lentă etapă va fi descărcarea efectivă a informațiilor, să presupunem că ar dura o secundă. Urmează extragerea efectivă a valorii cursului valutar. Să presupunem că cel mai eficient algoritm extrage acea valoare în 1 ms, pe când unul mai rudimentar o va face în 10 ms. Vorbim astfel de un timp de 10x mai lung, dar contează totuși? Per total avem o diferență între 1.001 ms VS 1.010 ms, adică o nimica toată.

Morala: înainte de a investi timp în găsirea/implementarea unui algoritm mai eficient, trebuie văzut dacă căștigul adus este pe măsura timpului investit.

TEMĂ PROPUSĂ

Să se scrie o clasă String pentru lucrul cu șiruri de caractere, clasă ce va permite să scriu următorul cod:

...
String a;
a = "Hello ";
int sizeA = a.length();
char litera = a[1]; //e

String b("World");
String c = a + b; //Hello World
c += " din C++";

String d = c.subString(3); //lo World din C++

printf("%s", c.toCharPointer());
...


Clasa se va găsi în namespace-ul MyLib și va stoca intern datele folosind un char* cu acces privat.

Edited by dani.user, 17 July 2013 - 18:01.


#2
OriginalCopy

OriginalCopy

    I'm harmful, fear me please! :))

  • Grup: Senior Members
  • Posts: 27,268
  • Înscris: 10.08.2006
Înainte de punctul "Protecția membriilor structurilor" ai putea să bagi un punct care explică de ce structurile sunt atât de importante (i.e. o definiţie şi eventual showcase a noţiunii de "obiect" construită doar cu noţiuni din C, via function pointers). Ar face mai lesne de înţeles de ce în punctul ulterior, "Protecția membriilor structurilor", ţi se pune pata tocmai pe structuri de date.

Ar fi şi procedura mai naturală de a explica de unde a apărut nevoia de OOP - ataşarea unui comportament unor date între care există o relaţie "supraetajată", şi nu cum cred novicii pe dos: "gruparea de funcţii".

După paragraful "C++ simplifică lucrurile permitând" aş mai pune un paragraf de genul: Pe lângă această simplificare, C++ asigură consistenţa datelor din obiect.

#3
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
Mersi de feedback, am actualizat materialul.

#4
andreim77

andreim77

    Senior Member

  • Grup: Senior Members
  • Posts: 4,239
  • Înscris: 11.04.2006
Adaug cateva idei:

1. tot ce se poate face in C++ se poate face si in C, chiar daca mai greu de aceea... pct 2.
2. nu e obligatoriu sa invete cineva intai C, apoi C++. Se poate incepe si ramane linistit doar cu C++ chiar daca e mai greu.

#5
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
1. Nu doar mai greu ci si mai susceptibil la erori. Cand ai un proiect mare care-ti crapa aleator, ti-ai dori din tot sufletul sa fi primit defapt ceva erori de compilare.
2. Pana nu intelege & stapaneste treaba cu obiectele, se poate minti toata ziua ca invata C++, ca defapt tot C invata, chiar daca foloseste cout in loc de printf.

#6
Paullik

Paullik

    Active Member

  • Grup: Members
  • Posts: 1,760
  • Înscris: 05.07.2008

 dani.user, on 20 iunie 2013 - 17:36, said:

2. Pana nu intelege & stapaneste treaba cu obiectele, se poate minti toata ziua ca invata C++, ca defapt tot C invata, chiar daca foloseste cout in loc de printf.
Asta se petrecea in scoala mea :)

#7
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
Din pacate nu doar in a ta :)

#8
Paullik

Paullik

    Active Member

  • Grup: Members
  • Posts: 1,760
  • Înscris: 05.07.2008

 dani.user, on 20 iunie 2013 - 17:46, said:

Din pacate nu doar in a ta Posted Image

Mda, si nu-i tocmai ce se numeste o "scoala de cartier", totusi nu se facea calitate la orele de info.

#9
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
Având în vedere că lumea a intrat în vacanță, propun următorul exercițiu:

  • Deoarece una-i să vezi câte lucruri noi aduce C++ și alta e să și reușești să faci uz de ele, propun tuturor celor interesați să vină cu o problemă rezolvată de ei în C/C++ cu cin/cout pentru a vedea cum ar arăta o soluție orientată pe obiect.

    Preferabil să nu fie probleme gen implementațî bubble sort fiindcă în acest caz nu se vor observa diferențe prea mari.


    Pe seară aduc și eu un exemplu.

Oricine e liber să propună soluții, important e să se discute pe seama lor.

#10
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
Pe un topic cerea cineva o soluție pentru implementarea operațiilor cu polinoame în C++.

Majoritatea ar defini o structură pentru a ține coeficienții polinomului, sau poate nici atât, ar declara un vector de numere și l-ar boteza "polinom1", iar apoi ar scrie tot felul de funcții gen aduna_polinoame, imparte_polinoame etc. Iar apoi ar mai face uz de cout (că doar suntem în C++), iar, ca tacâmul să fie complet, ar apela cout direct din funcția de adunare, pentru a afișa rezulatul...

Quote

Ain't that cute ... but it's wrooooong

Pai dacă tot avem avantajul că ni se cere o rezolvare în C++, de ce să nu beneficiem la maxim de ce ne pune C++-ul pe tavă, și să creem o clasă Polinom care să ne facă, în primul rând nouă, plăcere să o refolosim ori de câte ori mai avem de rezolvat câte o problemă ce implică polinoame?

OK ... să începem atunci. Primul, și poate cel mai important, aspect legat de această problemă este că, în lumea C++, atunci când adunăm două lucruri, ne place să scriem a + b indiferent că a și b sunt numere, mașini, sau, în acest caz, polinoame.

Prin urmare clasa, pe care o vom crea, ar fi fain să o putem folosi la modul următor:

#include <iostream>
#include "polinom.h"

using namespace std;

int main()
{
   Polinom a(1, 1, 4, 2, 1); //1x^4+2x^3+4x^2+1x+1
   Polinom b(-1, 1); //1x-1
   cout << "a = " << a << endl;
   cout << "b = " << b << endl;

   cout << "a + b = " << (a + b) << endl;
   cout << "a - b = " << (a - b) << endl;
   cout << "a * 5 = " << (a * 5) << endl;
   cout << "a / 5 = " << (a / 5) << endl;
   cout << "a * b = " << (a * b) << endl;
   cout << "a / b = " << (a / b) << " REST " << (a % b) << endl;

   return 0;
}


Astfel avem o mulțime de avantaje:

  • Avem o clasă foarte ușor de folosit, foarte natural, putând astfel să ne concentrăm pe ce problemă rezolvăm cu ajutorul polinoamelor, nu pe cum efectuăm operații elementare
  • Nu mai stăm să ne tot amintim cum se numea funcția de înmulțit polinoamele
  • Inițializăm coeficienții cu valori citite de oriunde, nu neapărat de la tastatură
  • Afișăm polinomul cu ușurință folosind cout, la fel ca și cum am afișa o simplă valoare numerică. Aici se observă un mare avantaj al cout față de printf: pe cout îl putem învăța, pe parcurs, să afișeze orice tip de date, iar, dacă avem grijă, stream-ul pentru scriere în fișiere va învăța și el același lucru (grație mecanismelor OOP)

Codul necesar pentru a implementa o astfel de clasă este prezentat mai jos:

polinom.h
#ifndef POLINOM_H
#define POLINOM_H

#include <iostream>

class Polinom
{
   public:
	  Polinom(double a, double b = 0, double c = 0, double d = 0, double e = 0, double f = 0, double g = 0, double h = 0);
	  Polinom(const Polinom& p);
	  unsigned int rang() const;
	  double& operator[](unsigned int index) const;
	  Polinom operator=(const Polinom& p);
	  Polinom operator+(const Polinom& p) const;
	  Polinom operator-(const Polinom& p) const;
	  Polinom operator*(const double v) const;
	  Polinom operator*(const Polinom& p) const;
	  Polinom operator/(const double v) const;
	  Polinom operator/(const Polinom& p) const;
	  Polinom operator%(const Polinom& p) const;
	  virtual ~Polinom();
	  friend std::ostream& operator<<(std::ostream& s, const Polinom& p);
   protected:
	  double* valori;
	  unsigned int nrValori;
   private:
};

std::ostream& operator<<(std::ostream& s, const Polinom& p);

#endif // POLINOM_H


polinom.c

#include <cmath>
#include "polinom.h"

using namespace std;

Polinom::Polinom(double a, double b, double c, double d, double e, double f, double g, double h)
{
   //alocam memorie pentru valori
   valori = new double[nrValori = 8];
   valori[0] = a;
   valori[1] = b;
   valori[2] = c;
   valori[3] = d;
   valori[4] = e;
   valori[5] = f;
   valori[6] = g;
   valori[7] = h;
}

Polinom::Polinom(const Polinom& p)
{
   //copiem alt polinom
   valori = new double[nrValori = p.nrValori];
   for (unsigned int i = 0; i < nrValori; i++)
   {
	  valori[i] = p[i];
   }
}

Polinom::~Polinom()
{
   //stergem vectorul alocat dinamic
   delete valori;
}

unsigned int Polinom::rang() const
{
   for (int i = nrValori - 1; i >= 0; i--)
   {
	  if (abs(valori[i]) > 0.0001) //valorile double nu se compara cu ==
	  {
		 return i;
	  }
   }
   return 0;
}

Polinom Polinom::operator=(const Polinom& p)
{
   delete valori;
   //copiem alt polinom
   valori = new double[nrValori = p.nrValori];
   for (unsigned int i = 0; i < nrValori; i++)
   {
	  valori[i] = p[i];
   }

   return *this;
}

double zero;

double& Polinom::operator[](unsigned int index) const
{
   zero = 0;
   if (index >= nrValori) return zero;
   return valori[index];
}

Polinom Polinom::operator+(const Polinom& p) const
{
   Polinom rezultat(*this); //copie a polinomului curent
   for (unsigned int i = 0; i < max(this->nrValori, p.nrValori); i++)
   {
	  rezultat[i] += p[i];
   }
   return rezultat;
}

Polinom Polinom::operator-(const Polinom& p) const
{
   Polinom rezultat(*this); //copie a polinomului curent
   for (unsigned int i = 0; i < max(this->nrValori, p.nrValori); i++)
   {
	  rezultat[i] -= p[i];
   }
   return rezultat;
}

Polinom Polinom::operator*(const double v) const
{
   Polinom rezultat(*this); //copie a polinomului curent
   for (unsigned int i = 0; i <= this->rang(); i++)
   {
	  rezultat[i] *= v;
   }
   return rezultat;
}

Polinom Polinom::operator*(const Polinom& p) const
{
   Polinom rezultat(0); //copie a polinomului curent
   for (unsigned int i = 0; i <= this->rang(); i++)
   {
	  for (unsigned int j = 0; j <= p.rang(); j++)
	  {
		 rezultat.valori[i + j] += valori[i] * p[j];
	  }
   }
   return rezultat;
}

Polinom Polinom::operator/(const double v) const
{
   return *this * (1.0 / v);
}

Polinom Polinom::operator/(const Polinom& p) const
{
   Polinom curent(*this);
   Polinom rezultat(0);
   Polinom temp(0);

   while(curent.rang() >= p.rang())
   {
	  temp = Polinom(0);
	  temp[curent.rang() - p.rang()] = curent[curent.rang()] / p[p.rang()];
	  rezultat = rezultat + temp;
	  curent = curent - temp * p;
   }

   return rezultat;
}

Polinom Polinom::operator%(const Polinom& p) const
{
   return *this - p * (*this / p);
}

ostream& operator<<(ostream& s, const Polinom& p)
{
   bool totiCoeficientiiZero = true;
   bool primulCoeficient = true;

   for (int i = p.nrValori - 1; i >= 0; i--)
   {
	  double coeficient = p.valori[i];
	  if (abs(p[i]) > 0.0001) //valorile double nu se compara cu ==
	  {
		 totiCoeficientiiZero = false;
		 if ( coeficient > 0 && ! primulCoeficient)
		 {
			s << "+";
		 }
		 if (i == 0)
		 {
			s << coeficient;
		 }
		 else if (p[i] > 0 && abs(p[i] - 1.0) > 0.0001)
		 {
			s << coeficient;
		 }
		 else if (abs(p[i] + 1.0) < 0.0001)
		 {
			s << "-";
		 }

		 primulCoeficient = false;
		 if (i > 0)
		 {
			s << "x";
			if (i > 1)
			{
			   s << "^" << i;
			}
		 }
	  }
   }

   if (totiCoeficientiiZero)
   {
	  s << 0;
   }

   return s;
}


Ei, v-am stârnit interesul? Vedeți ce înseamnă o problemă rezolvată în C++? Înțelegeți de ce oftăm când vedem cerințe în C++, iar soluțiile nu seamănă deloc cu asta?

Clasa de mai sus nu este perfectă. Odată ce a-ți înteles conceptele folosite, o puteți îmbunătăți:

  • Clasa să poată stoca un polinom de grad oricât de mare
  • Clasa să poată fi construită și atunci când valorile coeficiențiilor sunt oferite sub formă de tablouri sau vectori
  • etc...


#11
OriginalCopy

OriginalCopy

    I'm harmful, fear me please! :))

  • Grup: Senior Members
  • Posts: 27,268
  • Înscris: 10.08.2006
Aş mai adăuga nişte matematică cu sens:

 dani.user, on 22 iunie 2013 - 19:10, said:

Clasa de mai sus nu este perfectă. Odată ce a-ți înteles conceptele folosite, o puteți îmbunătăți:
  • Clasa să poată fi instanţiată via sparse vectors pe un tip de date dat (coeficienţii pot fi din N, Z, R, etc), indiciu: templates.
  • Clasa să poată fi instanţiată dintr-o mulţime de puncte prin interpolare.
  • etc...
nu prostiile fără sens care se dau prin şcoli (gen rezolvarea programatică a unei probleme care are rezolvare matematică în O(1))

#12
Paullik

Paullik

    Active Member

  • Grup: Members
  • Posts: 1,760
  • Înscris: 05.07.2008
Codul nu stie sa imparta cu polinomul nul, deci am modificat rang() sa returneze -1 pt. polinomul nul (defapt prin conventie e -inf, dar aici conventia mea e -1) si verific ca nu cumva sa am de a face cu polinomul nul sau cu numarul 0 cand fac impartirile si daca am primit asa ceva ca argument arunc o exceptie:

#include <stdexcept>

int Polinom::rang() const
{
   for (int i = nrValori - 1; i >= 0; i--)
   {
	  if (abs(valori[i]) > 0.0001) //valorile double nu se compara cu ==
	  {
		 return i;
	  }
   }

   return -1;
}

Polinom Polinom::operator/(const double v) const
{
	if(0 == v){
		throw invalid_argument("Impartire cu zero!");
	}

	return *this * (1.0 / v);
}

Polinom Polinom::operator/(const Polinom& p) const
{
	if(-1 == p.rang()){
		throw invalid_argument("Impartire cu polinomul nul!");
	}

	Polinom curent(*this);
	Polinom rezultat(0);
	Polinom temp(0);

	while(curent.rang() >= p.rang())
	{
		temp = Polinom(0);
		temp[curent.rang() - p.rang()] = curent[curent.rang()] / p[p.rang()];
		rezultat = rezultat + temp;
		curent = curent - temp * p;
	}

	return rezultat;
}



Evident ca am modificat header-ul si toate locurile unde aparea rang() ca sa nu fac comparatii intre signed si unsigned, in rest nu cred ca am afectat cu ceva codul.

De asemenea o sugestie pt. cei ce vor folosi clasa asta mai departe e sa optimizeze rang()  facand o memoizare.


Si acum o intrebare, ce ati modifica voi sau ce ctor ati adauga ca sa permiteti instantierea unui polinom de orice grad, nu neparat <=8?

Eu as scapa de ctor-ul curent (care ar fi prost pt. backwards compatibility) si as face unul care primeste un std::vector<T> (cu asta am impuscat si template-ul), ce ziceti?

Un exercitiu bun ar putea fi si implementarea unei clase pt. numere complexe, pt. a aplica ce ne-a aratat aici dani.user.

PS: Nu stiu ce face forumul, dar face ceva, ca la mine in editor e perfect indentat codul, aici, e ca la nebuni.

Edited by Paullik, 23 June 2013 - 18:01.


#13
OriginalCopy

OriginalCopy

    I'm harmful, fear me please! :))

  • Grup: Senior Members
  • Posts: 27,268
  • Înscris: 10.08.2006

 Paullik, on 23 iunie 2013 - 17:31, said:

Eu as scapa de ctor-ul curent (care ar fi prost pt. backwards compatibility) si as face unul care primeste un std::vector<T> (cu asta am impuscat si template-ul), ce ziceti?
Un iterator e şi mai sus pe scala abstractizării.

 Paullik, on 23 iunie 2013 - 17:31, said:

PS: Nu stiu ce face forumul, dar face ceva, ca la mine in editor e perfect indentat codul, aici, e ca la nebuni.
Clic pe butonul "Cod", paste cod, press OK, şi apoi nu te mai atingi de el. Se dă peste cap dacă modifici direct.

#14
Paullik

Paullik

    Active Member

  • Grup: Members
  • Posts: 1,760
  • Înscris: 05.07.2008

 OriginalCopy, on 24 iunie 2013 - 07:03, said:

Un iterator e şi mai sus pe scala abstractizării.
Oh, interesant.
Intr-adevar daca ma gandesc la STL, metodele erase, insert, etc. primesc iteratori.

 OriginalCopy, on 24 iunie 2013 - 07:03, said:

Clic pe butonul "Cod", paste cod, press OK, şi apoi nu te mai atingi de el. Se dă peste cap dacă modifici direct.
Mda, dupa mi-am dat seama ca-mi modifica spatierea in cod daca editez postul, porcarie.

Edited by Paullik, 24 June 2013 - 20:52.


#15
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
Uita-te mai atent la algoritmi generici ce accepta iteratori diversi.
Gandeste-te si la o metoda sa pot initializa un polinom cu n valori fara sa creez inainte un vector/array.

#16
Paullik

Paullik

    Active Member

  • Grup: Members
  • Posts: 1,760
  • Înscris: 05.07.2008

 dani.user, on 24 iunie 2013 - 21:16, said:

Uita-te mai atent la algoritmi generici ce accepta iteratori diversi.
Ceva exemple, te rog?

 dani.user, on 24 iunie 2013 - 21:16, said:

Gandeste-te si la o metoda sa pot initializa un polinom cu n valori fara sa creez inainte un vector/array.
Constructor cu numar variabil de parametrii folosind cstdarg. Totusi din moment ce urmarim mai mult C++ aici, as face overload la <<, astfel incat sa pot scrie:
Polinom f;
f<<5<<0<<0<<1; //5x^3 + 1


Nu?

Edited by Paullik, 24 June 2013 - 22:05.


#17
dani.user

dani.user

    Guru Member

  • Grup: Senior Members
  • Posts: 30,259
  • Înscris: 24.02.2007
http://www.cplusplus...ric/accumulate/

A face overload la << e aiurea fiindca il poti folosi si dupa ce initializezi polinomul, creand un comportament aiurea.
Apoi clasa a fost gandita pentru a fi immutable.

#18
TS030

TS030

    Guru Member

  • Grup: Senior Members
  • Posts: 15,193
  • Înscris: 25.06.2012
Observ ca mentionezi programarea orientata obiect, dar nu povestesti despre ea si nu exemplifici Posted Image
De asemenea, incerci sa vorbesti despre prea multe lucruri deodata, iar structura are de suferit.

Altfel, felicitari pentru initiativa!

Anunturi

Chirurgia endoscopică a hipofizei Chirurgia endoscopică a hipofizei

"Standardul de aur" în chirurgia hipofizară îl reprezintă endoscopia transnazală transsfenoidală.

Echipa NeuroHope este antrenată în unul din cele mai mari centre de chirurgie a hipofizei din Europa, Spitalul Foch din Paris, centrul în care a fost introdus pentru prima dată endoscopul în chirurgia transnazală a hipofizei, de către neurochirurgul francez Guiot. Pe lângă tumorile cu origine hipofizară, prin tehnicile endoscopice transnazale pot fi abordate numeroase alte patologii neurochirurgicale.

www.neurohope.ro

1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users

Forumul Softpedia foloseste "cookies" pentru a imbunatati experienta utilizatorilor Accept
Pentru detalii si optiuni legate de cookies si datele personale, consultati Politica de utilizare cookies si Politica de confidentialitate