edongashi Shënimet e ushtrimeve

Klasat në C++

Hyrje

Supozojmë një strukturë të të dhënave Pika e cila paraqet dyshen e pikave (x, y) në rrafshin kartezian:

struct Pika {
  double x;
  double y;
};

Shpesh kemi funksione të cilat ndërveprojnë me këtë strukturë:

#include <iostream>
#include <math.h>
#include <string>
#include <sstream>
using namespace std;

struct Pika {
  double x;
  double y;
};

Pika konstrukto(double x, double y) {
  Pika p = { x, y };
  return p;
}

string toString(Pika p) {
  stringstream ss;
  ss << "(" << p.x << ", " << p.y << ")";
  return ss.str();
}

bool baraz(Pika p1, Pika p2) {
  return p1.x == p2.x && p1.y == p2.y;
}

double distanca(Pika p1, Pika p2) {
  return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
}

int main() {
  Pika p1 = konstrukto(6.0, 3.5);
  Pika p2 = konstrukto(4.2, 7.1);

  cout << "Distanca mes pikes p1=" << toString(p1)
       << " dhe pikes p2=" << toString(p2)
       << " eshte d=" << distanca(p1, p2)
       << endl;

  if (baraz(p1, p2)) {
    cout << "Pikat jane te barabarta.";
  } else {
    cout << "Pikat jane te ndryshme.";
  }

  return 0;
}

Stili i tillë i programimit njihet si programim procedural.

Ekziston një stil tjetër i programimit i cili grupon të dhënat dhe funksionet mbi to në një njësi të vetme që quhet klasë. Stili i programimit me klasa quhet programimi i orientuar në objekte (POO).

Programimi i orientuar në objekte

Te programimi i orientuar në objekte klasa përshkruan strukturën e të dhënave si dhe ofron funksione të cilat veprojnë mbi ato të dhëna. Të dhënat e klasës njihen si fusha ndërsa funksionet e klasës njihen si metoda.

Programi i sipërm mund të rishkruhet si në vijim:

#include <iostream>
#include <math.h>
#include <string>
#include <sstream>
using namespace std;

class Pika {
private:
  double x;
  double y;

public:
  Pika(double px, double py) {
    x = px;
    y = py;
  }

  string toString() {
    stringstream ss;
    ss << "(" << x << ", " << y << ")";
    return ss.str();
  }

  bool baraz(Pika tjeter) {
    return x == tjeter.x && tjeter.y == tjeter.y;
  }

  double distanca(Pika tjeter) {
    return sqrt(pow(x - tjeter.x, 2) + pow(y - tjeter.y, 2));
  }
};

int main() {
  Pika p1 = Pika(6.0, 3.5);
  Pika p2 = Pika(4.2, 7.1);

  cout << "Distanca mes pikes p1=" << p1.toString()
       << " dhe pikes p2=" << p2.toString()
       << " eshte d=" << p1.distanca(p2)
       << endl;

  if (p1.baraz(p2)) {
    cout << "Pikat jane te barabarta.";
  } else {
    cout << "Pikat jane te ndryshme.";
  }

  return 0;
}

Vërejeni se si parametri i parë i funksionit sikur p1baraz(p1, p2) tash bëhet implicit duke marrë formën p1.baraz(p2).

Variablat tipi i të cilave është klasë i quajmë objekte ose instanca.

Qasja përmes pikës a.b ka kuptimin e leximit të anëtarit b në kontekst të objektit a.

Shembull: Thirrja e funksionit toString(p1) transformohet në p1.toString(). Funksionaliteti është i njejtë, veçse metoda toString() në mënyrë implicite e kap argumentin p1.

Argumenti implicit që paraqet referencën kontekstuale të objektit aktual në POO quhet this.

Në thirrjen p1.toString() nënkuptohet që this është p1.

Disa funksione që pranojnë dy parametra të tipit Pika, siç është distanca(p1,p2) ose baraz(p1,p2) transformohen në metoda që marrin vetëm një parametër. Arsyeja pse ndodh kjo është sepse parametri i parë kapet në formë implicite si në rastin p1.distanca(p2).

Në këtë rast krahasimi bëhet ndërmjet this dhe that, pra dy objekte të ndryshme. Sapo mësuam që this paraqet objektin aa.b, ndërsa për objektin tjeter s’kemi mundësi implicite prandaj duhet ta dërgojmë si parametër në metodën distanca:

double distanca(Pika tjeter) {
  return sqrt(pow(x - tjeter.x, 2) + pow(y - tjeter.y, 2));
}

Vërejeni se si x nuk ka prapashtesë pasi që është e nënkuptuar nga konteksti i ekzekutimit të metodës. Të rikujtojmë që metoda b gjithmonë duhet të ekzekutohet në kontekst të ndonjë instance a në formën a.b().

Realisht, kodi i mësipërm duket si në vijim, por kompajlleri e rregullon qasjen në fusha automatikisht në rastet kur nuk kemi dykuptimësi:

double distanca(Pika tjeter) {
  return sqrt(pow(this->x - tjeter.x, 2) + pow(this->y - tjeter.y, 2));
}

Konstruktorët

Konstruktori është një funksion i veçantë i cili thirret gjatë krijimit të instancës. Zakonisht përdoren për të inicializuar gjendjen e klasës duke i dhënë vlera fushave.

Konstruktori shkruhet si metodë e cila ka emrin e klasës dhe nuk ka tip kthimi. Konstruktorët mund të mbingarkohen.

class Pika {
public:
  Pika(double x, double y) {
    ...
  }
};

Kontrollimi i qasjes (private, public)

Te shembulli i parë i strukturës Pika gjithmonë qasja u bënte nga një funksion i jashtëm f.

Kur një bllok i kodit i cili gjendet jashtë përshkrimit të tipit ka të drejtë të qasjes në ndonjë fushë të tipit atëherë ajo fushë cilësohet publike.

Në disa raste dëshirojmë t’i mbajmë private fushat me qëllim të ndalimit të qasjes nga blloqet e jashtme. Kjo është një veçori kritike për arritjen e enkapsulimit.

Ideja e enkapsulimit është që gjendja e objektit duhet të menaxhohet vetëm nga metodat e ekspozuara të klasës, e jo nga palët e jashtme të cilat pa njohuri të implementimit mund ta shtyejnë objektin në një gjendje jo-konsistente.