Top Banner
ი. ხუციშვილი 1 თემა 1. C++ როგორც სტანდარტული C-გაუმჯობესება რა არის პროცედურული დაპროგრამება ობიექტზე ორიენტირებული დაპროგრამების ძირითადი კონცეფციები C++ - პროგრამის დამუშავების ეტაპები შეტანა-გამოტანა C++ -ში მანიპულატორები dec, oct, hex მანიპულატორები setw, fixed, scientific, setprecision სტანდარტული კლასი string ტიპი bool, მოდიფიკატორი const რა არის პროცედურული დაპროგრამება დაპროგრამების ენა C განეკუთვნება .. პროცედურული დაპროგრამების ენებს. პროცედურული დაპროგრამების ძირითადი იდეა ეფუძნება პრინციპს ”გათიშე და იბატონე”: აღწერისთვის რთული ამოცანა უნდა დაიყოს რამოდენიმე ქვეამოცანად და ეს დაყოფა უნდა გაგრძელდეს მანამ, სანამ არ მიიღება საკმარისად მარტივი ამოცანები. კომპიუტერულ პროგრამაში ყოველი ამოცანის ალგორითმი ფორმდება ფუნქციის სახით. ამრიგად, პროცედურული მეთოდის საფუძველზე აგებული პროგრამა წარმოადგენს ფუნქციათა ერთობლიობას. მასში მონაცემები და ფუნქციები გამოყოფილია ერთმანეთისაგან. ყოველი ფუნქცია ასრულებს მონაცემებზე მოქმედებებს და აქვს გამოკვეთილი კავშირი პროგრამის სხვა ფუნქციებთან. მაგალითად, C ენის პროგრამა შედგება პროგრამისტის მიერ შექმნილი და C-სტანდარტული ბიბლიოთეკის ფუნქციებისაგან. C-სტანდარტული ბიბლიოთეკა უზრუნველყოფს ფუნქციათა ფართო სპექტრს მათემატიკური გამოთვლების, სტრიქონებზე და სიმბოლოებზე ოპერაციების, შეტანა- გამოტანისა და უამრავი სხვა სასარგებლო ოპერაციების ჩასატარებლად. ეს, ცხადია, უმარტივებს პროგრამისტს მუშაობას . პროგრამისტი წერს ფუნქციებს, რომლებიც განსაზღვრავენ დასრულებული შინაარსის ამოცანებს და გამოიყენება პროგრამის სხვადასხვა ადგილას. ფუნქციის გააქტიურება (ანუ მასში დაპროექტებული ამოცანის შესრულების დაწყება) ხდება ფუნქციის გამოძახების გზით - მისი სახელისა და არგუმენტების მითითებით. ფუნქციის დასრულების შემდეგ მართვა კვლავ უბრუნდება პროგრამას ფუნქციის გამოძახების წერტილში. C-პროგრამის შესრულება იწყება და მთავრდება main ფუნქციით. ნახატზე ნაჩვენებია C -პროგრამის ფუნქციების ურთიერთქმედების ერთ-ერთი შესაძლო მაგალითი. main func1 func2 func3 func4 func5
187

თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

Mar 12, 2018

Download

Documents

phungduong
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 1

თემა 1. C++ როგორც სტანდარტული C-ს გაუმჯობესება

რა არის პროცედურული დაპროგრამება

ობიექტზე ორიენტირებული დაპროგრამების ძირითადი კონცეფციები

C++ - პროგრამის დამუშავების ეტაპები

შეტანა-გამოტანა C++ -ში

მანიპულატორები dec, oct, hex

მანიპულატორები setw, fixed, scientific, setprecision

სტანდარტული კლასი string

ტიპი bool, მოდიფიკატორი const

რა არის პროცედურული დაპროგრამება

დაპროგრამების ენა C განეკუთვნება ე.წ. პროცედურული დაპროგრამების ენებს.

პროცედურული დაპროგრამების ძირითადი იდეა ეფუძნება პრინციპს ”გათიშე და

იბატონე”: აღწერისთვის რთული ამოცანა უნდა დაიყოს რამოდენიმე ქვეამოცანად და ეს

დაყოფა უნდა გაგრძელდეს მანამ, სანამ არ მიიღება საკმარისად მარტივი ამოცანები.

კომპიუტერულ პროგრამაში ყოველი ამოცანის ალგორითმი ფორმდება ფუნქციის სახით.

ამრიგად, პროცედურული მეთოდის საფუძველზე აგებული პროგრამა წარმოადგენს

ფუნქციათა ერთობლიობას. მასში მონაცემები და ფუნქციები გამოყოფილია ერთმანეთისაგან.

ყოველი ფუნქცია ასრულებს მონაცემებზე მოქმედებებს და აქვს გამოკვეთილი კავშირი

პროგრამის სხვა ფუნქციებთან. მაგალითად, C ენის პროგრამა შედგება პროგრამისტის მიერ

შექმნილი და C-ს სტანდარტული ბიბლიოთეკის ფუნქციებისაგან.

C-ს სტანდარტული ბიბლიოთეკა უზრუნველყოფს ფუნქციათა ფართო სპექტრს

მათემატიკური გამოთვლების, სტრიქონებზე და სიმბოლოებზე ოპერაციების, შეტანა-

გამოტანისა და უამრავი სხვა სასარგებლო ოპერაციების ჩასატარებლად. ეს, ცხადია,

უმარტივებს პროგრამისტს მუშაობას .

პროგრამისტი წერს ფუნქციებს, რომლებიც განსაზღვრავენ დასრულებული შინაარსის

ამოცანებს და გამოიყენება პროგრამის სხვადასხვა ადგილას.

ფუნქციის გააქტიურება (ანუ მასში დაპროექტებული ამოცანის შესრულების დაწყება)

ხდება ფუნქციის გამოძახების გზით - მისი სახელისა და არგუმენტების მითითებით.

ფუნქციის დასრულების შემდეგ მართვა კვლავ უბრუნდება პროგრამას ფუნქციის

გამოძახების წერტილში. C-ს პროგრამის შესრულება იწყება და მთავრდება main ფუნქციით.

ნახატზე ნაჩვენებია C -პროგრამის ფუნქციების ურთიერთქმედების ერთ-ერთი შესაძლო

მაგალითი.

main

func1

func2

func3

func4

func5

Page 2: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 2

პროცედურული დაპროგრამების დროს აუცილებელია თვალყურის მიდევნება იმაზე,

თუ რა რიგით გამოიძახება ფუნქციები და რა ცვლილებებს განიცდიან ამ დროს მონაცემები.

ეს პროცესი მით უფრო რთულდება, რაც უფრო იზრდება პროგრამის მოცულობა (რაც უფრო

რთულია გადასაწყვეტი ამოცანა).

გარდა ამისა, პროცედურულ დაპროგრამებაში არასაკმარისად არის განხორციელებული

პროგრამული კოდის განმეორებადი გამოყენება.

პროცედურული მიდგომის მთავარი ნაკლი კი ისაა, რომ მონაცემებისა და ფუნქციების

ერთმანეთისაგან გამოყოფა ცუდად აღწერს რეალური სამყაროს სურათს. რეალური სამყაროს

ობიექტების (ადამიანების, მანქანების და სხვა) თვისებები და ქცევა განუყოფელია და

მოიაზრება მთლიანობაში.

ადამიანის თვისებებად შეიძლება დავასახელოთ ასაკი, თვალის ფერი, სამსახურის

ადგილი და სხვა, მანქანის თვისებებია - სიჩქარე, ძრავის სიმძლავრე, ბორბლების რაოდენობა

და სხვა. პროგრამაში რეალური ობიექტის თვისებები წარმოდგენილია მონაცემების სახით:

მათ აქვთ გარკვეული მნიშვნელობები, მაგალითად თვალის ფერი შეიძლება იყოს ცისფერი,

მანქანის ბორბლების რიცხვი კი 4.

ქცევა - ეს არის ობიექტის რეაგირება გარე ზემოქმედებაზე. თუ მანქანას

დავამუხრუჭებთ, ის გაჩერდება. მანქანის გაჩერება არის ქცევის მაგალითი. ქცევა წააგავს

ფუნქციას: თქვენ იძახებთ ფუნქციას იმისათვის, რომ შეასრულოთ რაღაც მოქმედება, და ის

ამ მოქმედებას ასრულებს.

ამრიგად, არც ცალკე აღებული თვისებები, არც ცალკე აღებული ქცევა არ ასახავს

რეალური სამყაროს ობიექტებს ადეკვატურად.

პროცედურული დაპროგრამება წარმატებით გამოიყენება ადვილი და მარტივად

რეალიზებადი ამოცანებისთვის. მაგრამ რთული ამოცანის შემთხვევაში, რომელიც მოითხოვს

დიდი მოცულობის პროგრამული უზრუნველყოფის შექმნას, ბევრ რესურსებსა და დროს,

პროცედურული დაპროგრამების მეთოდები საკმარისი არ აღმოჩნდა. გაჩნდა დაპროგრამების

ახალი მეთოდოლოგიების შექმნის აუცილებლობა. ერთ-ერთი ასეთი ახალი მეთოდოლოგია

არის ობიექტზე ორიენტირებული დაპროგრამება (Object Oriented Programming -

OOP).

ობიექტზე ორიენტირებული დაპროგრამების ძირითადი კონცეფციები

ობიექტზე ორიენტირებული დაპროგრამება (OOP) ახდენს რეალური სამყაროს

ობიექტების მოდელირებას ამ ობიექტების პროგრამული ანალოგების (ეკვივალენტების)

საშუალებით.

Page 3: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 3

ინკაფსულაცია. OOP-ის ფუძემდებელი იდეა არის მონაცემებისა და მათზე

გათვალისწინებული მოქმედებების გაერთიანება ე.წ. ობიექტში.

ამ მიზნით განისაზღვრება აბსტრაქტული მონაცემთა ტიპები (მომხმარებლის მიერ

განსაზღვრული ტიპები) - კლასები. კლასი შეიცავს მონაცემებს და მათი დამუშავების

ფუნქციების გარკვეულ კრებულს. სწორედ კლასის საფუძველზე იქმნება ობიექტები.

ობიექტი ”ფლობს ინფორმაციას” თავისი მდგომარეობის შესახებ (მონაცებებს) და ”იცის”

მისთვის განმარტებული ქცევის წესები (იყენებს კლასის ფუნქციებს).

მაგალითად, ობიექტი - ტელევიზორი შეიძლება შეიცავდეს

თვისებებს: ზომა - Size, ფერადობა - Color, ხმა (სიმაღლე) - Sound, არხების

რაოდენობა - Channels

და

ქცევის წესებს : ჩართვა - on(), გამორთვა - off(), ხმის გათიშვა - sound_off(),

არხიდან არხზე გადართვა - switching_channels().

კლასის (ტიპის) სახელი TV

მონაცემები Size, Color, Sound, Channels

ფუნქციები on(), off(),

sound_off(),

switching_channels()

ობიექტის მონაცემებზე პირდაპირი წვდომა აკრძალულია. ეს ნიშნავს, რომ ობიექტის

მონაცემების დამუშავება შეუძლიათ მხოლოდ მისივე ფუნქციებს. პროგრამის არც ერთ სხვა

ფუნქციას (გარდა ე.წ. მეგობარი ფუნქციისა) ობიექტის მონაცემების უშუალოდ გამოყენების

უფლება არა აქვს. ანუ მონაცემები დაცულია გარე ზემოქმედებისაგან, შემთხვევითი

არასასურველი შეცვლისაგან.

OOP -ის ამ კონცეფციას ეწოდება ინკაფსულაცია (encapsulation).

კლასის ფუნქციების ერთობლიობა შეადგენს ინტერფეისს, რომელიც განისაზღვრება

ობიექტის ტიპით (კლასით).

ტიპის სახელი TV

ინტერფეისი

on(),

off(),

sound_off(),

switching_channels()

როგორ ხდება ინტერფეისის გამოყენება?

ვთქვათ, შემოვიღეთ კონკრეტული ობიექტი-ტელევიზორი და დავარქვით მას my_tv,

ანუ პროგრამაში გვაქვს განაცხადი:

TV my_tv;

ამის შემდეგ შეგვიძლია მივმართოთ ჩვენს ობიექტს და გადავცეთ მას გზავნილი,

ვთქვათ, ჩართვის შესახებ:

my_tv.on();

Page 4: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 4

მემკვიდრეობითობა. უკვე არსებული კლასის (ან კლასების) საფუძველზე

შესაძლებელია ახალი კლასების შექმნა. ახალი (მემკვიდრე) კლასის ობიექტები იძენენ

არსებული (წინაპარი) კლასის მემკვიდრეობით გადაცემულ თვისებებსა (მონაცებებს) და

მეთოდებს (ფუნქციებს) და აგრეთვე შეიცავენ საკუთარ უნიკალურ თვისებებსა და

მეთოდებს:

წინაპარი (საბაზო)

კლასი

Point

x_coordinate,

y_coordinate

get_x(), get_y(),

set_x(), set_y(),

move(), show_Point()

მემკვიდრე (წარმოებული)

კლასი

Circle

radius

get_radius(),

set_radius(),

show_Circle()

OOP-ის ეს პრინციპი, რომელსაც მემკვიდრეობითობას (inharitance) უწოდებენ,

ემსახურება პროგრამული კომპონენტების განმეორებადი გამოყენების საქმეს.

იგივეს ემსახურება ახალი კლასის შექმნის კიდევ ერთი მეთოდიკა - კომპოზიცია: უკვე

არსებული კლასის ობიექტის როგორც შემადგენელი ნაწილის ჩართვა ახლად შექმნილ

კლასში. კომპოზიციას ასე გამოსახავენ:

Car

(მანქანა)

Engine

(ძრავი)

პოლიმორფიზმი. მემკვიდრე კლასის ობიექტები სარგებლობენ როგორც საკუთარი

ინტერფეისით, ისე წიმაპარი კლასის ინტერფეისით. ამასთან დასაშვებია წინაპარი კლასის

მეთოდების ხელახალი განსაზღვრა მემკვიდრე კლასებში, ანუ ერთი სახელის მქონე

მეთოდებს მემკვიდრეობითობის იერარქიაში შეიძლება ჰქონდეთ განსხვავებული

რეალიზება. ეს კონცეფცია ცნობილია პოლიმორფიზმის (polymorphism) სახელით.

საზოგადოდ, პოლიმორფიზმი ნიშნავს, რომ ერთი და იგივე სახელი შეიძლება

გამოიყენებულ იქნას ლოგიკურად დაკავშირებული, მაგრამ გამსხვავებული მიზნებისათვის.

მაგალითად, შეიძლება განვსაზღვროთ sum(x,y) ფუნქცია ან განვმარტოთ x+y ოპერაცია

მონაცემთა განსხვავებული სამი ტიპისათვის: მთელი რიცხვებისათვის, კომპლექსური

რიცხვებისათვის და მასივებისათვის. იმის მიხედვით, თუ რა ტიპისა იქნება x და y

ცვლადები, sum ფუნქცია და + ოპერატორი იმუშავებს განსხვავებულად. პოლიმორფიზმი

შეიძლება ავხსნათ ფრაზით ”ერთი ინტერფეისი - მრავალი მეთოდი”.

ობიექტზე ორიენტირებული სტილი თვისობრივად აუმჯობესებს დაპროგრამების

პროცესს და ზრდის პროგრამული უზრუნველყოფის განმეორებადი გამოყენების ხარისხს.

ობიექტზე ორიენტირებული დაპროგრამების მეთოდებით შეიარაღებული პროგრამისტი

უმკვლავდება გაცილებით მეტი მოცულობის პროგრამებს, ვიდრე ადრე.

დაპროგრამების ენა C++, რომელიც წარმოადგენს C-ს გაფართოებას, შეიქმნა 1979 წელს

Bell Laboratories თანამშრომლის Bjarne Stroustrup-ის (ბიერნ სტრაუსტრუპის)

Page 5: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 5

მიერ. პირველად ენას ეწოდებოდა ”C კლასებით” (C with Classes). 1983 წლიდან ენის

დასახელება გახდა C++. C++ უზრუნველყოფს რიგ თვისებას, რომელიც ”აწესრიგებს” C-ს, და,

რაც უფრო მნიშვნელოვანია, იგი იძლევა ობიექტზე ორიენტირებული დაპროგრამების

საშუალებას. ობიექტზე ორიენტირებული დაპროექტება მნიშნელოვნად ზრდის

პროგრამული პროდუქტის დამუშავების ეფექტურობას სტრუქტურული პროგრამირების

ტექნოლოგიასთან შედარებით. ობიექტზე ორიენტირებული პროგრამები უფრო მარტივია

გასაგებად, მათი კორექტირება და მოდიფიკაცია გაცილებით გაადვილებულია. C++ -

ჰიბრიდული ენაა, მასში შესაძლებელია პროგრამირება როგორც ობიექტზე ორიენტირებულ

სტიში, ასევე C-ს სტილშიც და ორივე სტილში ერთდროულად. 90-იანი წლების

დასაწყისიდან C++ გახდა მსოფლიოში ერთ-ერთი ყველაზე აღიარებული დაპროგრამების ენა.

C++ - პროგრამის დამუშავების ეტაპები

C++ -პროგრამის დამუშავება გულისხმობს 6 ეტაპის გავლას: რედაქტირება, წინასწარი

პრეპროცესორული დამუშავება, კომპილირება, ლინკირება (დაკავშირება), ჩატვირთვა და

შესრულება.

პირველი ეტაპი - რედაქტირება - სრულდება ტექსტურ რედაქტორში, მაგალითად,

პროგრამების დამუშავების რომელიმე ინტეგრებული გარემოს (Integrated Development

Environment - IDE) რედაქტორში. პროგრამისტი კრეფს თავისი პროგრამის ტექსტს და,

თუ საჭიროა, შეაქვს მასში ცვლილებები. შემდეგ, იმახსოვრებს პროგრამას გარე

მეხსიერებაში, მაგალითად, დისკზე ან ფლეშზე. C++ - პროგრამის გაფართოებაა .cpp. ყველა

მოყვანილი ამ კონსპექტში პროგრამა დამუშავებულია Dev-C++4.9.9.2 პაკეტის

რედაქტორში. რედაქტორში შექმნილი პროგრამა წარმოადგენს ე.წ. საწყის მოდულს.

შემდეგ ხდება კომპილირება. კომპილერს (compiler) გადაჰყავს პროგრამა მანქანურ

კოდში. კომპილირების ეტაპზე პირველად სრულდება წინასწარი დამუშავების ბრძანებები

Page 6: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 6

(პრეპროცესორის დირექტივები), რომლებიც მიუთითებენ, თუ რა ცვლილებები უნდა

განიცადოს პროგრამამ საბოლოო სახის მიღებამდე: მაგალითად, საჭირო ტექსტური

ფაილების მიერთება, სხვადასხვა ტექსტური ჩანაცვლება. წინასწარი დამუშავების შედეგად

იქმნება ე.წ. ობიექტური მოდული - ეს მანქანურ კოდებში ჩაწერილი პროგრამაა, მაგრამ მისი

უშუალო შესრულება ჯერ ვერ ხერხდება.

შემდეგი ეტაპია დაკავშირება. საწყისი მოდული, როგორც წესი, შეიცავს მიმართვებს

სხვა მოდულებზე (მაგალითად, სტანდარტული ბიბლიოთეკის ფუნქციებზე ან

პროგრამისტების პირადი ბიბლიოთეკების ფუნქციებზე). სისტემური პროგრამა კავშირების

რედაქტორი (linker) აკავშირებს ობიექტურ კოდს ასეთი ფუნქციების კოდებთან და ქმნის

ე.წ. ჩასატვირთ მოდულს. ჩასატვირთი მოდულის შესრულება კომპიუტერს უკვე შეუძლია.

შემდეგ ეტაპზე სისტემური პროგრამა (ჩამტვირთველი) უზრუნველყოფს ჩასატვირთი

მოდულის გადატანას ოპერატიულ მეხსიერებაში, სადაც ხდება პროგრამის შესრულება.

შეტანა-გამოტანა C++ -ში

C++-თან გაცნობა დავიწყოთ იმ საშუალებების მიმოხილვით, რომლებიც არაა პირდაპირ

დაკავშირებული ობიექტზე ორიენტირებულობასთან და განსხვავებულია C-ს მსგავსი

საშუალებებისაგან. OOP –ის ძირითად კონცეფციებს – ინკაფსულაციას, მემკვიდრეობითობასა

და პოლიმორფიზმს – შევისწავლით მოგვიანებით.

პირველ რიგში განვიხილოთ C++ -ის შეტანა-გამოტანის საშუალებები. C++ -ის შეტანა-

გამოტანის სტილი დაფუძნებულია შეტანა-გამოტანის ნაკადის (stream) ცნებაზე.

მონაცემების შეტანა C++ -ში სრულდება cin -ის საშუალებით (cin – console input –

შეტანის სტანდარტული ნაკადის ობიექტი), ჩვეულებრივ, კლავიატურიდან. მონაცემთა

გამოტანა კი ხდება cout -ის მეშვეობით (cout – console output – გამოტანის სტანდარ-

ტული ნაკადის ობიექტი), რომელიც, ჩვეულებრივ, ასოცირდება ეკრანთან. არსებობს

აგრეთვე შეცდომების სტანდარტული ნაკადის ობიექტი cerr, რომელიც, როგორც წესი,

დაკავშირებულია ეკრანთან. cerr გამოიყენება შეცდომების შესახებ გზავნილების

გამოსატანად.

ქვემოთ მოყვანილი უმარტივესი პროგრამა დაგვიდგენს ორი მთელი რიცხვის ჯამს.

რიცხვები შემოგვაქვს კლავიატურიდან, შედეგი გამოიტანება ეკრანზე.

// Ori mteli ricxvis jamis povna

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

int integer1, integer2, sum;

Page 7: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 7

cout<<"ShemoitaneT pirveli mteli ricxvi: ";

cin>>integer1;

cout<<"ShemoitaneT meore mteli ricxvi: ";

cin>>integer2;

sum =integer1+integer2;

cout<<"Jami= "<<sum<<endl;

system("PAUSE");

return 0;

}

გავუშვათ პროგრამა შესრულებაზე. მივიღებთ შედეგს:

სტრიქონი //Ori mteli ricxvis jamis povna წარმოადგენს ე.წ. ერთსტრიქონიან

კომენტარს. აქ // ერთსტრიქონიანი კომენტარის მაჩვენებელია. მის შემდეგ სტრიქონის

ბოლომდე მოთავსებულ ტექსტს კომპილერი უგულვებელყოფს. შევნიშნოთ, რომ თუ

კომენტარი უფრო ვრცელია და მოითხოვს რამოდენიმე სტრიქონში მოთავსებას, უნდა

გამოვიყენოთ /* და */ სიმბოლოების ერთობლიობა. მიღებულია, რომ ყოველი პროგრამა

უნდა იწყებოდეს კომენტარით, რომელშიც განიმარტება პროგრამის შინაარსი.

სტრიქონი #include <iostream> წარმოადგენს პრეპროცესორის ბრძანებას.

iostream ბიბლიოთეკის ჩართვა აუცილებელია ყველა პროგრამაში, სადაც ხორციელდება

შეტანა-გამოტანა C++ -ის სტილში.

#include <cstdlib> - cstdlib ბიბლიოთეკის პროგრამაში ჩართვის ბრძანებაა.

ეს ბრძანება ჩვენს პროგრამაში საჭიროა system ფუნქციის გამოყენებისათვის.

პროგრამის შემდეგი სტრიქონი using namespace std; მიუთითებს კომპილერს

გამოიყენოს სახელების არე std (standard).

სახელების არეები შემოღებულია C++ -ის ბოლო ვერსიაში (Standard C++). C++

დაპროგრამების გარემოში თანაარსებობენ მუდმივების, ცვლადების, ფუნქციებისა და

კლასების უამრავი სახელები. სახელების არეების შემოღებამდე ყველა ეს სახელი

თავსდებოდა სახელების ერთ გლობალურ არეში, რაც ხშირად იწვევდა სახელების

კონფლიქტს (name collisions). კონფლიქტები ხშირდებოდა დიდი პროექტების

დაპროგრამების დროს, როდესაც გამოიყენებოდა სხვადასხვა მწარმოებლების

ShemoitaneT pirveli mteli ricxvi: 45

ShemoitaneT meore mteli ricxvi: -78

Jami= -33

Press any key to continue . . .

Page 8: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 8

სტანდარტული ბიბლიოთეკები და სხვადასხვა პროგრამისტების (პროგრამისტთა ჯგუფების)

მიერ შექმნილი პროგრამული უზრუნველყოფა. namespace საშუალებას იძლევა სახელების

ერთი სიმრავლე შევინახოთ დანარჩენებისაგან ცალკე.

std შეიცავს C++ -ის სტანდარტული ბიბლიოთეკის ფუნქციების, ცვლადების,

კონსტანტების და ა.შ. სახელებზე განაცხადებს. ამიტომ std -ს გამოყენება აადვილებს

სტანდარტულ ბიბლიოთეკაზე წვდომას. დირექტივა using აცნობებს კომპილერს, რომ

თქვენ აპირებთ დასახელებული namespace -ით სარგებლობას (ჩვენს შემთხვევაში std-

თი).

მივაქციოთ ყურადღება, რომ მხოლოდ #include <iostream> ბრძანების პროგრამაში

ჩართვა საკმარისი არ არის. რადგან ამ ბიბლიოთეკის ყველა განაცხადი მოთავსებულია std

სახელების არეში, აუცილებელია შეატყობინოთ კომპილერს, რომ გსურთ ისარგებლოთ ამ

განაცხადებით. ამისათვის უნდა გამოიყენოთ დირექტივა using .

namespace შედარებით ახალი საშუალებაა, ზოგიერთი კომპილერი მას ”არ იცნობს”.

ასეთ შემთხვევაში უნდა დაწეროთ ძველი სტილით: #include <iostream.h>. ამრიგად,

#include <iostream.h>

და

#include <iostream>

using namespace std;

ერთი და იგივე შინაარსის ბრძანებებია.

C++ შეიცავს C -ს ფუნქციების ბიბლიოთეკებსაც. C++ –პროგრამაში დასაშვებია C -ს

ბიბლიოთეკების თავსართი ფაილების გაფორმება ტრადიციულ სტილში, მაგალითად,

stdio.h, math.h, string.h. მაგრამ Standard C++ განმარტავს ასეთი თავსართი

ფაილებისათვის ახალ სტილს: C -ს სტანდარტულ სათაურებს ემატება პრეფიქსი c და

აკლდება გაფართოება .h. მაგალითად, cmath, cstring, cstdlib და ა.შ.

პროგრამის სტრიქონი

cout<<"ShemoitaneT pirveli mteli ricxvi: ";

წარმოადგენს გამოტანის შეტყობინებას. C++ -ში ხორციელდება სიმბოლოების ნაკადის

შეტანა-გამოტანა. მოცემული შეტყობინების შესრულებისას სიმბოლოების ნაკადი

ShemoitaneT pirveli mteli ricxvi: გაეგზავნება cout-ს. << ოპერატორს ეწოდება

ნაკადში მოთავსების ოპერატორი. << ოპერატორის მარჯვენა ოპერანდი მოთავსდება

გამოტანის ნაკადში, ანუ გამოიტანება ეკრანზე.

cin>>integer1; - შეტანის შეტყობინებაა. მასში გამოიყენება cin და ნაკადიდან

ამოღების ოპერატორი >>. შეიძლება ითქვას, რომ ჩვენს შემთხვევაში კლავიატურის

ნაკადიდან ამოიღება მთელი რიცხვი და მიენიჭება >> ოპერატორის მარჯვენა ოპერანდს

integer1-ს.

Page 9: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 9

int integer1, integer2, sum; - ცვლადებზე განაცხადია. C++ -ში განაცხადი

ცვლადებზე შეიძლება განვათავსოთ პროგრამის ნებისმიერ ადგილას, მაგრამ მკაცრად უნდა

იყოს დაცული წესი: განაცხადი ცვლადზე უნდა მოხდეს მის პირველ გამოყენებამდე.

ზემოთქმულის გათვალისწინებით ჩვენი პროგრამის ფრაგმენტი შეიძლება ჩაიწეროს ასედაც:

cout<<"ShemoitaneT pirveli mteli ricxvi: ";

int integer1;

cin>>integer1;

cout<<"ShemoitaneT meore mteli ricxvi: ";

int integer2;

cin>>integer2;

int sum;

sum =integer1+integer2;

შეტყობინება

cout<<"Jami= "<<sum<<endl;

ბეჭდავს ეკრანზე სიმბოლურ სტრიქონს Jami=, შემდეგ sum ცვლადის რიცხვით

მნიშვნელობას, რომელსაც მოყვება ე.წ. ნაკადის მანიპულატორი endl. endl (end of line -

სტრიქონის დასრულება) უზრუნველყოფს გამოსატანი სიმბოლოების დაუყოვნებლივ

გამოტანას ეკრანზე და კურსორის გადატანას ახალ სტრიქონზე. შევნიშნოთ, რომ გამოტანის

მოცემულ შეტყობინებაში სრულდება სხვადასხვა ტიპის მნიშვნელობების გამოტანა: ნაკადში

მოთავსების << ოპერატორმა ”იცის”, როგორ უნდა გამოიტანოს მონაცემთა ყოველი

ერთეული. ამასთამ შესაძლებელია << ოპერატორის მრავალჯერადი გამოყენება.

გამოტანის შეტყობინებაში შეიძლება შევასრულოთ გამოთვლები. მაგალითად,

შეგვეძლო შემდეგი ორი შეტყობინება

sum =integer1+integer2;

cout<<"Jami= "<<sum<<endl;

შეგვეცვალა ერთით

cout<<"Jami= "<<integer1+integer2<<endl;

შესაძლებელია ნაკადიდან ამოღების >> ოპერატორის მრავალჯერადი გამოყენებაც.

მაგალითად, პროგრამის ფრაგმენტი

cout<<"ShemoitaneT pirveli mteli ricxvi: ";

cin>>integer1;

cout<<"ShemoitaneT meore mteli ricxvi: ";

cin>>integer2;

შეიძლება შეიცვალოს შემდეგით:

Page 10: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 10

cout<<"ShemoitaneT mteli ricxvebi: ";

cin>>integer1>>integer2;

ახლა განვიხილოთ მაგალითი, რომელიც დაგვანახებს სიმბოლოების მასივების

გამოტანის თავისებურებას C++ -ში:

// სიმბოლოთა მასივების გადაბმა (კონკატენაცია)

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

cout << "Gamosatani texti imdenad grZelia, rom "

"misi moTavseba redaqtoris erT striqonSi\n"

"problematuria. SegviZlia davyoT texti"

"simboloebis masivebad.\n";

system("pause");

return 0;

}

პროგრამის შესრულების შემდეგ შეგვიძლია დავასკვნათ: ბრჭყალებში ჩასმული

სიმბოლოების მოსაზღვრე მასივებს კომპილერი აერთიანებს ერთ სიმბოლურ მასივში.

მანიპულატორები dec, oct, hex

განვიხილოთ შეტანა-გამოტანის კიდევ რამოდენიმე მაგალითი:

1. // მანიპულატორების გამოყენება

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

cout<< "a number in decimal: "

<< dec << 15 << endl;

cout << "in octal: " << oct << 15 << endl;

Page 11: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 11

cout << "in hex: " << hex << 15 << endl;

cout << "a floating-point number: "

<< 3.14159 << endl;

cout << "non-printing char (escape): "

<< char(27) << endl;

system("pause");

return 0;

}

პროგრამის შედეგები:

ამ მაგალითში სრულდება რიცხვის გამოტანა ათობითი, რვაობითი და თექვსმეტობითი

თვლის სისტემებში. ამას უზრუნველყოფს dec, oct და hex მანიპულატორების ჩასმა

გამოტანის შეტყობინებაში. საზოგადოდ, მანიპულატორები ცვლიან გამოტანის ნაკადის

მდგომარეობას. ჩვენს შემთხვევაში dec, oct და hex მანიპულატორები ”კარნახობენ”

გამოტანის ნაკადს გადაიყვანოს რიცხვი შესაბამის თვლის სისტემაში.

ნამდვილი რიცხვების ფორმატს კომპილერი ადგენს ავტომატურად.

გამოტანის ნაკადს შეიძლება გადავცეთ ნებისმიერი სიმბოლოს კოდი და დავბეჭდოთ ეს

სიმბოლო. ამისათვის კოდთან უნდა გამოვიყენოთ char ტიპზე ცხადი გარდაქმნა.

პროგრამაში char(27) კონსტრუქცია გადასცემს cout -ს სიმბოლო Escape -ს.

2. // ათობითი რიცხვის შეტანა, 8-ობითში და 16-ობითში მისი გარდაქმნა

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

int number;

a number in decimal: 15

in octal: 17

in hex: f

a floating-point number: 3.14159

non-printing char (escape): ←

Press any key to continue . . .

Page 12: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 12

cout << "Enter a decimal number: ";

cin >> number;

cout << "value in octal = 0"

<< oct << number << endl;

cout << "value in hex = 0x"

<< hex << number << endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

პროგრამის cin>>number; შეტყობინება უზრუნველყოფს შეტანის ნაკადიდან

(კლავიატურიდან) მთელი რიცხვის ამოღებას, რომელიც მიენიჭება number ცვლადს.

შემდეგ ეს რიცხვი იბეჭდება ეკრანზე 8-ობით და 16-ობით ფორმატში oct და hex

მანიპულატორების გამოყენებით.

მანიპულატორები setw, fixed, scientific, setprecision

მანიპულატორები fixed და scientific განსაზღვრავენ ნამდვილი რიცხვების

გამოტანის ფორმატს.

fixed უზრუნველყოფს ნამდვილი რიცხვების გამოტანას ჩვეულებრივ წარმოდგენაში:

მთელი ნაწილი, წერტილი, წილადი ნაწილის ექვსი ციფრი. scientific - ექსპონენციალურ

წარმოდგენაში. თუ არც ერთი მანიპულატორი მითითებული არ არის, ნამდვილი რიცხვის

ფორმატს ირჩევს კომპილერი.

მანიპულატორი setprecision(k) განსაზღვრავს ნამდვილი რიცხვის სიზუსტეს, ანუ

წილადი ნაწილის ციფრების k რაოდენობას. setprecision პარამეტრიანი

მანიპულატორია, მისი გამოყენება მოითხოვს #include <iomanip> დირექტივის

პროგრამაში ჩართვას.

მანიპულატორი setw(k) განსაზღვრავს რიცხვის გამოტანის ველს - პოზიციების k

რაოდენობას. გულისხმობის პრინციპით, ბეჭდვისას ხდება რიცხვის მარჯვნივ სწორება.

შემდეგი პროგრამა ახდენს fixed, scientific, setprecision და setw

მანიპულატორების გამოყენების ილუსტრირებას:

#include <iostream>

Enter a decimal number: 100

value in octal = 0144

value in hex = 0x64

Press any key to continue . . .

Page 13: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 13

5.467300e+001 9.500000e-004

54.673000 0.000950

2.8

2.83

2.828

2.8284

2.82843

2.828427

2.8284271

2.82842712

2.828427125

2.8284271247

2.82842712475

#include <cmath>

#include <iomanip>

using namespace std;

int main()

{

float x =54.673, y =0.00095;

cout << scientific << x <<'\t'

<< y << endl;

cout << fixed << x<< '\t' << y << endl;

double d =pow(8, 0.5);

for(int places=1; places<=15; places++)

cout << setprecision(places) << d << endl;

int a =9325;

cout << setw(4) << a << endl

<< setw(7) << a << endl

<< setw(9) << a << endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Page 14: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 14

სტანდარტული კლასი string

C++ -ის სტანდარტული კლასი string განკუთვნილია სტრიქონებთან მუშაობისათვის.

როგორც გვახსოვს, C -ში სტრიქონებზე მოქმედებები - კოპირება, მიბმა, შედარება და სხვა -

სრულდებოდა სპეციალური სტანდარტული ფუნქციების გამოყენებით. C++ -ის სტანდარ-

ტული string კლასი გაცილებით ამარტივებს სტრიქონებთან მუშაობას.

string კლასით სარგებლობისათვის აუცილებელია #include <string> და,

რადგანაც string კლასი განმარტებულია std სახელების არეში, using namespace std;

დირექტივების ჩართვა პროგრამაში.

// C++-ის სტანდარტული string კლასის გამოყენება

#include <string>

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

string s1, s2; // ცარიელი სტრიქონები

string s3 = "Hello,Students."; // სტრიქონის ინიციალიზება

string s4("We learn"); // სტრიქონის ინიციალიზების სხვა ხერხი

s2 = "Today"; // მინიჭება

s1 = s3 + "\n" + s4; // სტრიქონების გაერთიანება

s1 += " C++ "; // დამატება სტრიქონის ბოლოს

cout << s1 + s2 + "!" << endl;

system("pause");

return 0;

}

პროგრამა დაგვიბეჭდავს:

Page 15: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 15

სტრიქონები s1 და s2 შეიქმნება როგორც ცარიელი სტრიქონები. s3 და s4

სტრიქონების განაცხადში მათი ინიციალიზება ხდება ორი განსხვავებული ხერხით.

string კლასის ნებისმიერ ობიექტს - სტრიქონს - შეიძლება მივანიჭოთ ახალი

მნიშვნელობა = ოპერატორის საშუალებით: s2 = "Today"; ამ დროს s2 სტრიქონის

მნიშვნელობა იცვლება = ოპერატორის მარჯვენა მხარეს მოთავსებული ახალი სტრიქონით.

სტრიქონების გაერთიანება (მიბმა) სრულდება + ოპერატორით: s1 =s3 + " " + s4;

string სტრიქონებთან ასევე მუშაობს + = ოპერატორი: s1 +=" C++ ";

შეტყობინების შესრულების შედეგად s1 -ს ბოლოში დაემატება სტრიქონი " C++ ".

string ტიპის მონაცემების შეტანა-გამოტანას ასრულებენ cin და cout ნაკადები.

მაგალითად, cout<<s1 + s2 + "!"<<endl; გამოტანის შეტყობინებაში ”გამოითვლება”

string ტიპის გამოსახულება s1 + s2 + "!" , ანუ მიიღება სტრიქონი-შედეგი და

შემდეგ ის გამოიტანება ეკრანზე.

ტიპი bool, მოდიფიკატორი const

C++ -ის სტანდარტში შემოღებულია ახალი მონაცემთა ტიპი - bool. მოგვიამებით bool

ტიპი შემოვიდა C -ს სტანდარტშიც. ამ ტიპის ცვლადები გამოიყენება ლოგიკურ

მნიშვნელობებთან მუშაობისათვის. განისაზღვრება bool ტიპის ორი კონსტანტა true და

false : ეს ორადორი მნიშვნელობაა, რომელიც შეიძლება მიიღოს bool ტიპის ცვლადმა.

C++ -ში (ისევე, როგორც C -ში) ნებისმიერი არანულოვანი მნიშვნელობა ნიშნავს ჭეშმარიტს,

ხოლო ნულოვანი სიდიდე - მცდარს. ლოგიკურ გამოსახულებაში არანულოვანი

მნიშვნელობა ავტომატურად გარდაიქმნება true -ში, ხოლო ნული - მნიშვნელობაში false.

და პირიქით, არალოგიკურ გამოსახულებაში true გარდაიქმნება რიცხვში 1, false -

რიცხვში 0.

C++ ენის ზოგიერთი ოპერატორი და შეტყობინება არის მორგებული bool ტიპზე:

ენის ელემენტი bool ტიპთან გამოყენება

ლოგიკური ოპერატორები: && || ! ღებულობენ bool ტიპის ოპერანდებს და

გამოიმუშავებენ bool ტიპის შედეგს

შედარების ოპერატორები:

< <= > >= != ==

გამოიმუშავებენ bool ტიპის შედეგს

შეტყობინებები:

if, for, while, do

პირობითი გამოსახულებები გარდაიქმნება bool

ტიპის მნიშვნელობებში

ოპერაციატორი ? : პირველი ოპერანდი გარდაიქმნება bool ტიპზე

განვიხილოთ საილუსტრაციო მაგალითი:

Hello, Students.

We learn C++ Today!

Press any key to continue . . .

Page 16: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 16

#include <cstdlib>

#include <iostream>

using namespace std;

int main(){

bool b;

b = false;

cout<<"b cvladis mnishvneloba udris "<<b<<endl;

b = true;

cout<<"b cvladis mnishvneloba udris "<<b<<endl;

if( b ) cout<<"piroba sruldeba"<<endl;

b = false;

if( b ) cout<<"piroba sruldeba"<<endl;

else cout<<"piroba ar sruldeba"<<endl;

cout<<" 10>9 udris "<<(10>9)<<endl

<<" 10<9 udris "<<(10<9)<<endl;

system("PAUSE");

return 0;

}

პროგრამის შედეგები:

C -ში კონსტანტაზე განაცხადი კეთდება #define პრეპროცესორის დირექტივის

საშუალებით. მაგალითად, #define PI 3.1416 ბრძანების შესრულება ნიშნავს

პროგრამის მთელ ტექსტში სიმბოლური PI სახელის ჩანაცვლებას ტექსტით 3.1416.

ჩანაცვლებას ასრულებს პრეპროცესორი. ამ ხერხით კონსტანტაზე განაცხადი შეიძლება

გაკეთდეს C++ -შიც.

b cvladis mnishvneloba udris 0

b cvladis mnishvneloba udris 1

piroba sruldeba

piroba ar sruldeba

10>9 udris 1

10<9 udris 0

Press any key to continue . . .

Page 17: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 17

ამასთან, C++ -ში გაჩნდა დასახელებული კონსტანტების ცნება. შემდგომში კონსტანტის

ცნება შემოღებულ იქნა C -შიც. კონსტანტის მნიშვნელობის შეცვლა აკრძალულია, სხვა

დანარჩენში კონსტანტა ცვლადის მსგავსია. კონსტანტის აღწერა მოითხოვს const

მოდიფიკატორის მითითებას:

const int x =10;

თუ პროგრამაში აღვწერთ კონსტანტას და შემდეგ შევეცდებით მის შეცვლას, კომპილერი

გამოიტანს გზავნილს შეცდომის შესახებ.

შემდეგი პროგრამის შესრულება

#include <cstdlib>

#include <iostream>

using namespace std;

int main()

{

const int n=10;

const int *p=&n;

cout<<"n ikavebs mexsierebashi "<<sizeof(n)<<" baits"<<endl

<<" misi misamarTia "<<p<<endl

<<" mnishvneloba ki udris "<<*p<<endl;

system("PAUSE");

return 0;

}

მოგვცემს შედეგს:

ჩავამატოთ პროგრამაში n -ის შეცვლის ბრძანება, ვთქვათ, შეტყობინება n += 2; და

კვლავ გავუშვათ პროგრამა შესრულებაზე. მივიღებთ კომპილერის გზავნილს: assignment

of read-only variable 'n'.

და კიდევ ერთი შენიშვნა: როგორც აღვნიშნეთ, ცვლადზე განაცხადი შეიძლება

გაკეთდეს პროგრამის ნებისმიერ ადგილას, ანუ იქ, სადაც ცვლადი დაგვჭირდება.

მაგალითად, განმეორების შეტყობინებაში

n ikavebs mexsierebashi 4 baits

misi misamarTia 0x22ff74

mnishvneloba ki udris 10

Press any key to continue . . .

Page 18: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 18

for(int k=1; k<10; k+=2)

cout<<k<<" ";

რომელიც დაგვიბეჭდავს კენტ რიცხვებს 1 -დან 9 -ის ჩათვლით, k ცვლადზე განაცხადი

int k=1; მოხდა for შეტყობინების ინიციალიზების განყოფილებაში. ამ შემთხვევაში k -ს

მნიშვნელობა for-ის გარეთ ცნობილი არ იქნება. თუ შევეცდებით მის დაბეჭდვას for -ის

დასრულების შემდეგ, კომპილერი შეგვატყობინებს შეცდომის შესახებ.

თემა 2. ფუნქცია

ფუნქციის პროტოტიპი და ტიპების შესაბამისობის კონტროლი

return შეტყობინება

ფუნქციის პარამეტრები (პარამეტრი-მნიშვნელობა - value parameter)

ფუნქციის ცვლადი პარამეტრები (პარამეტრი–პოინტერი – pointer parameter )

ლოკალური და გლობალური ცვლადები. ხილვადობის წესები. მეხსიერების

კლასები

ფუნქციის პროტოტიპი და ტიპების შესაბამისობის კონტროლი.

დიდი პროგრამების შექმნისას საუკეთესო ხერხი არის მათი წარმოდგენა მოდულების

სახით. მოდულებს C++ -ში ეწოდებათ ფუნქციები და კლასები. ჯერჯერობით ჩვენ

განვიხილავთ მხოლოდ პროგრამულ მოდულებს - ფუნქციებს.

ფუნქციის აღწერის ფორმატი შემდეგია:

<დასაბრუნებელი ტიპი> ფუნქციის სახელი (<პარამეტრების სია>)

{

განაცხადი ცვლადებზე

შეტყობინებები

}

<დასაბრუნებელი ტიპი> ფუნქციის სახელი (<პარამეტრების სია>) - ფუნქციის სათაურია. მას

აგრეთვე ეწოდება ფუნქციის პროტოტიპი.

<დასაბრუნებელი ტიპი> განმარტავს ფუნქციის დასაბრუნებელი მნიშვნელობის ტიპს.

რადგანაც ფუნქცია განკუთვნილია კონკრეტული ამოცანის ამოსახსნელად, მისი

შესრულების შედეგი შეიძლება იყოს რიცხვი, სიმბოლო, სტრიქონი და ა.შ. სწორედ ეს არის

ფუნქციის დასაბრუნებელი მნიშვნელობა. თუ <დასაბრუნებელი ტიპი> მითითებული არ

არის, იგულისხმება ტიპი int. C++ -ში გამოიყენება ისეთი ფუნქციებიც, რომლებიც არ

აბრუნებენ მნიშვნელობას (მაგალითად, ფუნქციის დანიშნულებაა ბეჭდვა, ხატვა და სხვა).

ასეთი ფუნქციები void ტიპისაა.

ფუნქციის სახელის საშუალებით კეთდება ფუნქციაზე განაცხადი (მისი პროტოტიპის

მოცემა), ფუნქციის განსაზღვრა (შესასრულებელი მოქმედებების აღწერა) და ფუნქციაზე

მიმართვა (მისი გამოძახება).

Page 19: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 19

ფუნქციათა უმრავლესობას გააჩნია პარამეტრების ჩამონათვალი, რომელიც გამოიყენება

ფუნქციებს შორის ინფორმაციის გაცვლისათვის. ასეთ პარამეტრებს ეწოდებათ ფორმალური

პარამეტრები

<პარამეტრების სია> წარმოადგენს ფუნქციის ფორმალური პარამეტრების ჩამონათვალს

მათი ტიპების მითითებით. ჩამონათვალში პარამეტრები გამოიყოფა მძიმით, ამასთან ტიპი

უნდა მიეთითებოდეს ყოველ პარამეტრს. პარამეტრების საშუალებით ფუნქციას გადაეცემა

მისთვის საჭირო საწყისი მონაცემები (main-იდან ან სხვა ფუნქციიდან). დასაშვებია

ფორმალური პარამეტრების სია იყოს ცარიელი.

ფიგურულ ფრჩხილებში მოთავსებულია ამოსახსნელი ამოცანის რეალიზებისთვის

საჭირო ცვლადებზე განაცხადები და შესრულებადი შეტყობინებები - ფუნქციის ტანი.

ფუნქციის ცვლადებს ეწოდებათ ლოკალური ცვლადები. ლოკალური ცვლადები

ცნობილია მხოლოდ იმ ფუნქციაში, რომელშიც ისინი არიან აღწერილი. ფორმალური

პარამეტრებიც - ლოკალური ცვლადებია.

ვთქვათ, პროგრამაში გვჭირდება ფუნქცია, რომელიც დაადგენს ორი სინბოლოდან

უდიდესს და დააბრუნებს მის კოდს. ასეთ ფუნქციაზე განაცხადს ექნება სახე

int big_code(char p, char q);

სადაც

int - ფუნქციის ტიპია, ანუ მისი დასაბრუნებელი მნიშვნელობის ტიპი;

big_code - ფუნქციის სახელია;

p და q - ფუნქციის ფორმალური პარამეტრებია (ან, უბრალოდ, პარამეტრები).

ფუნქციაზე განაცხადი მოიცემა ფუნქციის პროტოტიპის ჩაწერით. პროტოტიპის შემდეგ

უნდა დავსვათ წერტილ-მძიმე ( ; ), წინააღმდეგ შემთხვევაში მივიღებთ კომპილაციის

შეცდომას.

ფუნქციის გააქტიურება, ანუ მასში დაპროგრამებული ამოცანის შესრულების დაწყება,

ხდება მისი გამოძახების გზით. მაგალითად, big_code ფუნქციის გამოძახების ფრაგმენტი

შესაძლოა ასე გამოიყურებოდეს:

char a ='*', b ='?';

int k = big_code(a, b);

სადაც

a და b -ს ეწოდებათ ფუნქციის ფაქტიური პარამეტრები ან არგუმენტები.

ფუნქციის პროტოტიპის საშუალებით კომპილერი ამოწმებს, რამდენად სწორია

ფუნქციაზე მიმართვა. ფუნქციის გამოძახების დროს მისი არგუმენტები (a და b) უნდა

შეესაბამებოდეს პროტოტიპში აღწერილ პარამეტრებს (p და q): არგუმენტების რაოდენობა,

ტიპები და მათი რიგი უნდა ემთხვეოდეს პროტოტიპში მოცემული პარამეტრების რიცხვს,

Page 20: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 20

თითოეულის ტიპსა და რიგს. წინააღმდეგ შემთხვევაში ფიქსირდება კომპილაციის შეცდომა.

ასევე უნდა ემთხვეოდეს პროტოტიპში მითითებული ფუნქციის ტიპი და k ცვლადის ტიპი.

შეუსაბამობის შემთხვევაში ისევ მივიღებთ კომპილაციის შეცდომას.

პროტოტიპში პარამეტრების სახელების მითითება აუცილებელი არ არის. მაგალითად,

int big_code(char, char );

პროტოტიპის სწორი ჩანაწერია.

big_code ფუნქციის განსაზღვრას შეიძლება ჰქონდეს სახე:

int big_code(char p, char q){

char t; // t - ფუნქციის ლოკალური ცვლადია

if (p > q) t =p;

else t =q;

return t;

}

ფუნქციის განსაზღვრისას მეორდება მისი სათაური და მას უერთდა ფუნქციის ტანი.

მივაქციოთ ყურადღება, რომ ახლა ფუნქციის სათაურის შემდეგ წერტილ-მძიმე ( ; ) არ

იწერება, ხოლო ფუნქციის პარამეტრების სახელები აუცილებლად მიეთითება.

return შეტყობინება

big_code ფუნქციის ტანის ბოლო ინსტრუქციას წარმოადგენს return შეტყობინება.

return შეტყობინების ფორმატია

return;

ან

return <გამოსახულება>;

void ტიპის ფუნქციის ტანში return შეტყობინება ან არ გამოიყენება, ან გამოიყენება

ფორმატით return;

საზოგადოდ, განასხვავებენ ფუნქციიდან პროგრამაში მართვის დაბრუნების სამ

შემთხვევას:

თუ ფუნქცია void ტიპისაა, მართვის დაბრუნება ხდება

ან ფუნქციის ტანის ბოლო შეტყობინების შესრულების შემდეგ (დამამთავრებელი

მარჯვენა ფიგურული ფრჩხილის მიღწევისას),

ან return; შეტყობინების შესრულების შემდეგ.

ორივე შემთხვევაში ფუნქციის დასაბრუნებელი მნიშვნელობა განსაზღვრული არ

იქნება.

თუ ფუნქციამ უნდა დააბრუნოს შედეგი, მაშინ მის ტანში სრულდება შეტყობინება

Page 21: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 21

return <გამოსახულება>;

რომელშიც ჯერ გამოითვლება გამოსახულების მნიშვნელობა და შემდეგ return

დააბრუნებს მას ფუნქციაზე მიმართვის წერტილში.

ფუნქცია შეიძლება შეიცავდეს ერთზე მეტ return –ს.

ჩავწეროთ big_code ფუნქციის ტანი ლოკალური ცვლადის შემოღების გარეშე:

int big_code(char p, char q){

if (p > q) return p;

return q;

}

როგორც ვხედავთ, ფუნქციაში ორი return შეტყობინებაა, რომლიდანაც სრულდება ერთ–

ერთი.

აღსანიშნავია, რომ იგივე ფუნქცია შეიძლება ჩაიწეროს უფრო მოკლედაც:

int big_code(char p, char q){

return (p > q) ? p : q;;

}

ფუნქციის პარამეტრები (პარამეტრი-მნიშვნელობა – value parameter)

შემდეგ მაგალითში განისაზღვრება სამ რიცხვს შორის უმცირესის პოვნის ფუნქცია

minimum. პროგრამაში შეგვაქვს 3 მთელი რიცხვი. შემდეგ ეს რიცხვები გადაეცემა ფუნქციას

minimum, რომელიც დაადგენს მათ შორის უმცირესს. return შეტყობინება დაუბრუნებს

main-ს უმცირესის მნიშვნელობას, და ის დაიბეჭდება.

// სამ მთელ რიცხვს შორის უმცირესის პოვნა

#include <iostream>

#include <cstdlib>

using namespace std;

// ფუნქციის პროტოტიპი

int minimum (int, int, int);

int main()

{

int a, b, c;

cout<<"ShemoiteneT 3 mTeli ricxvi: ";

cin>>a>>b>>c;

cout<<"Minimaluri udris "<< minimum(a,b,c)

Page 22: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 22

<<endl;

system("PAUSE");

return 0;

}

// minimum ფუნქციის განსაზღვრა

int minimum (int x, int y, int z){

int min =x;

if (y < min) min =y;

if (z < min) min =z;

return min;

}

პროგრამის შესრულების შედეგია:

int minimum(int, int, int); წარმოადგენს minimum ფუნქციის პროტოტიპს,

რომელიც გვიჩვენებს, რომ ფუნქცია ”ელოდება” 3 მთელ არგუმენტს და დააბრუნებს მთელს.

შევნიშნოთ, რომ პროტოტიპში ფორმალური პარამეტრების სახელები (x, y და z) არ

მიეთითება - კომპილერი ამ სახელებს უგულვებელყოფს. პროტოტიპის შემდეგ ; -ის

გამოტოვება იწვევს სინტაქსურ შეცდომას.

ფუნქციის გამოძახება, რომელიც არ შეესაბამება პროტოტიპს, ასევე იწვევს სინტაქსურ

შეცდომას. მაგალითად, გამოძახება minimum(a,b) – შეცდომაა, რადგანაც ფუნქციაში

გადაცემული არგუმენტების რაოდენობა არ ემთხვევა პროტოტიპში მოცემული

პარამეტრების რაოდენობას.

სინტაქსური შეცდომა ფიქსირდება მაშინაც, როდესაც შეუსაბამობაა ფუნქციის

პროტოტიპსა და ფუნქციის განსაზღვრის სათაურს შორის. ორივეში უნდა ემთხვევოდეს

როგორც დასაბრუნებელი ტიპი, ისე პარამეტრების რიცხვი და მათი ტიპები.

ფუნქციის გამოძახება ჩვენ მაგალითში სრულდება გამოტანის შეტყობინებაში

cout<<"Minimaluri udris "<< minimum (a,b,c)<<endl;

minimum(a,b,c) გვიჩვენებს, რომ ფუნქციაში გათვალისწინებული მოქმედებები

უნდა შესრულდეს a, b და c მთელ რიცხვებზე. a, b და c - ფუნქციის არგუმენტებია.

minimum(a,b,c) -ს შესრულება შემდეგი ფრაგმენტის შესრულების ეკვივალენტურია:

int x =a; int y =b; int z =c;

ShemoiteneT 3 mTeli ricxvi: 23 7 69

Minimaluri udris 7

Press any key to continue . . .

Page 23: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 23

int min =x;

if (y < min) min =y;

if (z < min) min =z;

return min;

ე.ი. x პარამეტრს მიენიჭება a-ს მნიშვნელობა, y პარამეტრს – b-ს მნიშვნელობა, z პარამეტრს

– c-ს მნიშვნელობა, ხოლო ფუნქციის შეტყობინებები, ფაქტობრივად, შესრულდება a, b -სა

და c -სთვის. უნდა გვახსოვდეს, რომ პარამეტრებსა და არგუმენტებს შორის შესაბამისობა

დგინდება მათი რიგის მიხედვით: პირველ პარამეტრს (x -ს) შეესაბამება პირველი

არგუმენტი (a), მეორე პარამეტრს (y -ს) - მეორე არგუმენტი (b), ხოლო მესამე პარამეტრს

(z -ს) - მესამე არგუმენტი (c).

არგუმენტების ასეთ გადაცემას ფუნქციაში ეწოდება არგუმენტების გადაცემა

მნიშვნელობით, ხოლო პარამეტრებს - პარამეტრ-მნიშვნელობები.

ფუნქციის ცვლადი პარამეტრები (პარამეტრი–პოინტერი – pointer parameter)

ფუნქციაში არგუმენტების გადაცემის 2 ხერხი არსებობს: არგუმენტების გადაცემა

მნიშვნელობით და არგუმენტების გადაცემა მისამართით. არგუმენტების მისამართით

გადაცემა სრულდება პოინტერის ან მითითების გამოყენებით.

ფუნქციის გამოძახების დროს მის პარამეტრებს და ლოკალურ ცვლადებს გამოეყოფათ

ადგილი ოპერატიული მეხსიერების სპეციალურ არეში (სტეკში). როდესაც არგუმენტი

გადაეცემა ფუნქციას მნიშვნელობით, ფუნქციის პარამეტრს ენიჭება ამ არგუმენტის ასლი.

ამიტომ, თუ ფუნქციის ტანში მისი პარამეტრი იცვლის მნიშვნელობას, შესაბამისი არგუმენტი

არ იცვლება.

უამრავ შემთხვევაში საჭიროა ფუნქციაში გადაცემული არგუმენტის შეცვლა.

მაგალითად, როდესაც:

ფუნქციიდან ერთზე მეტი მნიშვნელობის დაბრუნება გვჭირდება. მაშინ ერთ

მნიშვნელობას დააბრუნებს return შეტყობინება, დანარჩენი მნიშვნელობების

დაბრუნება კი უნდა მოხერხდეს პარამეტრების მეშვეობით.

პროგრამაში გვჭირდება ფუნქციის მიერ შეცვლილი არგუმენტის მნიშვნელობა.

ფუნქციას დასამუშავებლად გადაეცემა მონაცემთა დიდი ობიექტი. ასეთი

არგუმენტის გადაცემა მნიშვნელობით (მისი კოპირება პარამეტრში) მოითხოვს

გარკვეულ დროს, რაც ანელებს პროგრამის შესრულებას.

ცხადია, ჩამოთვლილ შემთხვევებში პარამეტრ-მნიშვნელობები არ გამოგვადგება.

მსგავს შემთხვევებში ფუნქციას უნდა გადაეცეს არა არგუმენტის ასლი, არამედ მისი

მისამართი. მაშინ ყველა ცვლილება, რომელსაც ფუნქციაში განიცდის პარამეტრი,

სინამდვილეში ხორციელდება არგუმენტზე. ასეთ პარამეტრებს ეწოდებათ ცვლადი

პარამეტრები.

ცვლადი პარამეტრების მქონე ფუნქციის ”კლასიკური” მაგალითი არის ფუნქცია,

რომელიც ახდენს 2 არგუმენტის გაცვლას. მაგალითად, ქვემოთ მოყვანილი ფუნქცია უცვლის

მნიშვნელობებს ორ მთელ რიცხვს.

void swap (int* x, int* y){

Page 24: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 24

int t;

// t-ში დროებით შევინახეთ x მისამართზე განთავსებული რიცხვი

t = *x;

// y მისამართზე მყოფი რიცხვი ჩავწერეთ x მისამართზე

*x =*y;

// x მისამართზე ადრე მყოფი რიცხვი ჩავწეროთ y მისამართზე

*y = t;

}

ამ ფუნქციის ორივე არგუმენტი იქნება მისამართი, ამიტომ პარამეტრები პოინტერებია.

*x და *y ფუნქციის ტანში - შესაბამისად x და y მისამართებზე განთავსებული

მნიშვნელობებია. ამრიგად, *x = *y; შეტყობინების შესრულების შედეგად y მისამართზე

განთავსებული მნიშვნელობა ჩაანაცვლებს x მისამართზე განთავსებულ მნიშვნელობას. ე.ი.

swap ფუნქციის სამივე შეტყობინების შესრულება გაუცვლის მნიშვნელობებს ფუნქციაში

გადაცემულ ცვლადებს (არგუმენტებს).

შემდეგი პროგრამა დაგვანახებს განსხვავებას პარამეტრ-მნიშვნელობებისა და ცვლადი

პარამეტრების გამოყენებას შორის:

#include <iostream>

#include <cstdlib>

using namespace std;

void swap(int *, int *);

void change(int, int );

int main(){

int a, b;

cout<<"ShemoitaneT 2 mteli ricxvi: ";

cin>>a>>b;

cout<<"Shecvlamde: "

<<a<<" "<<b<<endl;

change(a, b);

cout<<"change funqciis shemdeg: "

<<a<<" "<<b<<endl;

swap(&a, &b);

cout<<"swap funqciis shemdeg: "

<<a<<" "<<b<<endl;

Page 25: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 25

system("pause");

return 0;

}

void swap(int *x, int *y){

int t = *x; *x = *y; *y = t;

}

void change(int x, int y){

int t =x; x =y; y =t;

}

პროგრამის შესრულების შედეგია:

მივაქციოთ ყურადღება swap ფუნქციის გამოძახებას: ფუნქცია “ელოდება” ორი

მისამართის მიღებას, ამიტომ გამოძახების დროს მას უნდა გადავცეთ იმ ცვლადების

მისამართები, რომელთა მნიშვნელობების გაცვლასაც ვაპირებთ: swap(&a, &b);

ლოკალური და გლობალური ცვლადები. ხილვადობის წესები. მეხსიერების კლასები

პროგრამული ობიექტი - ცვლადი ან ფუნქცია - მოიცემა პროგრამაში

იდენტიფიკატორის (სახელის) მითითებით. იდენტიფიკატორი ხასიათდება ტიპით,

მოცულობითა და მნიშვნელობით. გარდა ამისა, ნებისმიერ იდენტიფიკატორს გააჩნია კიდევ

სხვა ატრიბუტებიც, როგორიცაა მეხსიერების კლასი, მოქმედების არე და კავშირების ტიპი.

C++ -ში განასხვავებენ მეხსიერების კლასის ოთხ სპეციფიკაციას: auto, register,

extern და static. მეხსიერების კლასი განსაზღვრავს იდენტიფიკატორის არსებობის

ხანგრძლივობას - პერიოდს, რომლის განმავლობაშიც იგი ინახება მეხსიერებაში. ზოგი

იდენტიფიკატორის არსებობის დრო მცირეა, ზოგი კი არსებობს პროგრამის შესრულების

მთელი დროის მანძილზე.

იდენტიფიკატორის მოქმედების არე განსაზღვრავს პროგრამის იმ არეს (ადგილს),

რომელშიც შესაძლებელია იდენტიფიკატორზე მიმართვა (წვდომა). ერთ იდენტიფიკატორს

ShemoitaneT 2 mteli ricxvi: 7 9

Shecvlamde: 7 9

change funqciis shemdeg: 7 9

swap funqciis shemdeg: 9 7

Press any key to continue . . .

Page 26: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 26

შეიძლება მივმართოთ პროგრამის ნებისმიერ ადგილში, მეორეს კი - მხოლოდ პროგრამის

გარკვეულ ნაწილებში.

საზოგადოდ, C++ –ის პროგრამა შედგება რამოდენიმე ფაილისაგან. იდენტიფიკატორის

კავშირების (ბმის) ტიპი აჩვენებს, არის თუ არა იდენტიფიკატორი ცნობილი მხოლოდ ერთ

მიმდინარე ფაილში, თუ იგი ცნობილია პროგრამის ყველა ფაილში შესაბამისი განაცხადების

შემთხვევაში.

განასხვავებენ მეხსიერების კლასის ორ ჯგუფს: მეხსიერების ავტომატური კლასი

არსებობის ლოკალური ხანგრძლივობით და მეხსიერების სტატიკური კლასი არსებობის

გლობალური ხანგრძლივობით.

მომსახურე auto და register სიტყვები გამოიყენება მხოლოდ არსებობის ლოკალური

ხანგრძლივობის ცვლადებთან, ანუ ლოკალურ ცვლადებთან. მეხსიერების ავტომატური

კლასი - მეხსიერების დაზოგვის საშუალებაა, რადგანაც ამ კლასის ცვლადები იქმნებიან იმ

ბლოკში, სადაც არიან აღწერილი, და ”ნადგურდებიან” ბლოკიდან გამოსვლისას.

ავტომატური კლასის ცვლადების მაგალითებია:

auto int x =37; auto double f; register int k;

ბლოკში აღწერილი ლოკალური ცვლადები გულისხმობის პრინციპით არიან auto, ასე

რომ მომსახურე სიტყვა auto გამოიყენება იშვიათად. ზემოთ მოყვანილ მაგალითებში x და

f ცვლადებზე განაცხადები შეიძლება ყოფილიყო: int x =37; double f;

განვიხილოთ საილუსტრაციო მაგალითი:

#include <iostream>

#include <cstdlib>

using namespace std;

int main(){

for(int i=1; i<=3; i++){

auto int s =0; // ან int s =0;

s +=i;

cout<<"s = "<<s<<endl;

}

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

s = 1

s = 2

s = 3

Press any key to continue . . .

Page 27: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 27

for შეტყობინების ბლოკში აღწერილია ავტომატური ცვლადი s, მისი საწყისი

მნიშვნელობა უდრის 0-ს. პროგრამის შესრულების შედეგი გვიჩვენებს, რომ for –ის ყოველი

ბიჯის დასაწყისში (ბლოკში შესვლისას) s ხდება 0 -ის ტოლი. თუ შევეცდებით s ცვლადის

დაბეჭდვას for –ის შემდეგ, მივიღებთ კომპილაციის შეცდომას: Undefined symbol 's'.

შეიძლება დავასკვნათ: ავტომატური ცვლადი s იქმნება ბლოკში ყოველი შესვლის დროს და

“ქრება” ბლოკის დასრულების შემდეგ.

პროგრამის ცვლადების დამუშავებისას გამოიყენება ცენტრალური პროცესორის

რეგისტრები: პროგრამის ბრძანებები სრულდება ოპერატიული მეხსიერებიდან რეგისტრებში

მოთავსებულ ცვლადებზე, ხოლო შედეგი გაიგზავნება ისევ ოპერატიულ მეხსიერებაში.

სპეციფიკაცია register ავტომატური ცვლადის განაცხადში ნიშნავს მოთხოვნას, რომ ასეთი

ცვლადი შენახული იყოს არა ოპერატიულ მეხსიერებაში, არამედ რეგისტრში. ამით მიიღწევა

ცვლადზე გაცილებით სწრაფი წვდომა და, როგორც შედეგი, პროგრამის სწრაფქმედების

გაზრდა.

კომპილერმა შეიძლება უგულვებელყოს register სპეციფიკაცია: პროცესორის

რეგისტრების რაოდენობა შეზღუდულია და თავისუფალი რეგისტრი შეიძლება არ

აღმოჩნდეს. მაშინ register-ცვლადს კომპილერი განიხილავს როგორც auto-ს.

შემდეგი აღწერა register int counter =1; მიუთითებს, რომ მთელი ცვლადი counter

უნდა განთავსდეს პროცესორის რეგისტრში. მიუხედავად იმისა, ”დაგვიჯერებს” კომპილერი

თუ არა, ცვლადი counter მიიღებს საწყის მნიშვნელობას 1. მომსახურე სიტყვა register

გამოიყენება მხოლოდ ფუნქციის ლოკალურ ცვლადებთან და პარამეტრებთან.

სპეციფიკაცია register ზოგჯერ ჭარბია: C++ -ის თანამედროვე კომპილერები

იმდენად ოპტიმიზირებულია, რომ თავად შეუძლიათ ხშირად ხმარებადი ცვლადების

ამოცნობა და იმის გადაწყვეტა, მოათავსონ თუ არა ეს ცვლადები რეგისტრებში.

მომსახურე static სიტყვით აღწერილი ლოკალური ცვლადები ცნობილია მხოლოდ

იმ ბლოკში, სადაც კეთდება მათზე განაცხადი. მაგრამ auto ცვლადებისაგან განსხვავებით,

ლოკალური static-ცვლადების მნიშვნელობები შეინახება: ბლოკში ყოველი შემდეგი

შესვლის დროს ამ ცვლადებს აქვთ ბლოკში წინა შესვლისას მიღებული მნიშვნელობები.

მაგალითად,

#include <iostream>

#include <cstdlib>

using namespace std;

int main()

{

for(int i=1; i<=3; i++){

Page 28: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 28

static int s =0;

s +=i;

cout<<"s = "<<s<<endl;

}

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

წინა მაგალითისაგან განსხვავებით, აქ for შეტყობინების ბლოკში აღწერილია

სტატიკური ლოკალური ცვლადი s, მისი საწყისი მნიშვნელობა უდრის 0-ს. პროგრამის

შედეგიდან ჩანს, რომ for –ის ყოველი ბიჯის დასაწყისში s –ის მნიშვნელობა არის წინა

ბიჯზე მიღებული მნიშვნელობა. თუ შევეცდებით s ცვლადის დაბეჭდვას for –ის ბლოკის

გარეთ, ისევ მივიღებთ კომპილაციის შეცდომას: Undefined symbol 's'. დასკვნა:

სტატიკური ცვლადი s ინარჩუნებს თავის მნიშვნელობას ბლოკის არსებობის მთელი დროის

მანძილზე, მაგრამ ბლოკის გარეთ ცნობილი არ არის.

ფუნქციის ლოკალური static ცვლადი აგრეთვე ინარჩუნებს თავის მნიშვნელობას

ფუნქციის ერთი გამოძახებიდან მეორემდე. ეს ბუნებრივიც არის, რადგან ფუნქციის ტანი

წარმოადგენს ბლოკს. განვიხილოთ შემდეგი საილუსტრაციო მაგალითი:

// ლოკალური სტატიკური ცვლადის გამოყენება ფუნქციაში

#include <iostream>

#include <cstdlib>

using namespace std;

void func();

int main()

{

for(int x = 1; x < 5; x++)

func();

system("pause");

return 0;

s = 1

s = 3

s = 6

Press any key to continue . . .

Page 29: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 29

}

void func(){

static int i = 0;

cout<<"i = "<< ++i <<endl;

}

პროგრამის შესრულების შედეგია:

func() ფუნქციის ყოველი გამოძახების დროს იბეჭდება i-ს ახალი მნიშვნელობა.

მომსახურე static სიტყვის გარეშე ფუნქცია ყოველთვის დაგვიბეჭდავდა ერთი და იგივე

მნიშვნელობას 1. განაცხადში static int i =0; სტატიკურ i ცვლადს ენიჭება საწყისი

მნიშვნელობა 0.0 მიენიჭება i -ს მხოლოდ ერთხელ, ფუნქციის პირველი გამოძახების დროს.

ფუნქციით შეცვლილი i-ს მნიშვნელობა შეინახება, ანუ ფუნქციის ყოველი შემდეგი

გამოძახების დროს i-ს საწყისი მნიშვნელობა ტოლი იქნება წინა გამოძახებისას მიღებული

მნიშვნელობისა.

შეიძლება გაგვიჩნდეს კითხვა, რატომ არ გამოვიყენეთ ლოკალური static ცვლადის

ნაცვლად გლობალური ცვლადი? ლოკალური სტატიკური ცვლადების ხიბლი ისაა, რომ

ფუნქციის გარეთ (პროგრამის სხვა ფუნქციებიდან) ისინი მიუწვდომელია. ე.ი. გამოირიცხება

ასეთი ცვლადების არასასურველი შეცვლა, რაც უზრუნველყოფს შეცდომების თავიდან

აცილებას.

მომსახურე extern და static სიტყვები განსაზღვრავენ მეხსიერების სტატიკური

კლასის არსებობის გლობალური ხანგრძლივობის ცვლადებსა და ფუნქციებს. გლობალური

ცვლადები არსებობენ პროგრამის შესრულების დასაწყისიდან და მათთვის მეხსიერების

გამოყოფა და მისი ინიციალიზება ხდება პროგრამის გაშვებისთანავე. ფუნქციების სახელებიც

არსებობენ პროგრამის შესრულების საწყისი მომენტიდან ბოლომდე.

გლობალურ ცვლადებზე განაცხადი მოთავსებულია ყველა ფუნქციის (მათ შორის, main

ფუნქციის) გარეთ. ისინი ინახავენ თავიანთ მნიშვნელობებს პროგრამის შესრულების მთელი

დროის მანძილზე. გლობალურ ცვლადზე მიმართვა შეუძლია პროგრამის ნებისმიერ

ფუნქციას. ამან შეიძლება გამოიწვიოს ასეთი ცვლადის არასასურველი შეცვლა რომელიმე

ფუნქციის მიერ. ამიტომ მიზანშეწონილია, რომ ცვლადები, რომლებიც საჭიროა მხოლოდ

ფუნქციის ტანში, აღვწეროთ ლოკალურად და არა გლობალურად.

თუ გლობალურ ცვლადზე განაცხადი კეთდება ერთ ფაილში, ხოლო მეორე ფაილი

შეიცავს მის აღწერას, დაწყებულს extern სიტყვით, მაშინ მასზე წვდომა ნებადართულია

მეორე ფაილშიც.

მაგალითად,

i = 1

i = 2

i = 3

i = 4

Press any key to continue . . .

Page 30: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 30

// გლობალური ცვლადების გამოყენება

// basic .cpp

#include <iostream>

#include "minor.cpp"

using namespace std;

int global; // global ცვლადზე განაცხადი

void func();

int main()

{

global = 12;

cout << global << endl;

func(); // global ცვლადის შეცვლა ფუნქციაში

cout << global << endl;

system("pause");

return 0;

}

// გარე გლობალურ ცვლადზე მიმართვა

// minor.cpp

extern int global; // global ცვლადის აღწერა

// წვდომა ნებადართულია კავშირების რედაქტორის (linker) მიერ

void func(){

global = 47;

}

პროგრამის შესრულების შედეგია:

global ცვლადს მეხსიერება უნაწილდება მასზე განაცხადის საფუძველზე basic.cpp

ფაილში. minor.cpp ფაილში ხდება ამ ცვლადზე მიმართვა. basic.cpp და minor.cpp

ფაილები კომპილირდება ცალცალკე, ამიტომ, კომპილერს წინასწარ უნდა შევატყობინოთ,

რომ minor.cpp ფაილის global ცვლადი უკვე არსებობს სხვაგან. სწორედ ამ მიზანს

12

47

Press any key to continue . . .

Page 31: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 31

ემსახურება

extern int global;

აღწერა minor.cpp ფაილში. ამ აღწერის უქონლობის შემთხვევაში, პროგრამის შესრულების

დროს მივიღებთ შეცდომას: 'global' undeclared (first use this function).

დასაშვებია ფაილში გვქონდეს extern სიტყვით აღწერილი ცვლადი და ამავე ფაილში

კეთდებოდეს მასზე განაცხადი, მხოლოდ მოგვიანებით. იგივე ეხება ფუნქციებსაც.

მაგალითად:

// ფუნქციებზე და ცვლადებზე წინასწარი განაცხადი

#include <iostream>

#include <cstdlib>

using namespace std;

// აქ ცვლადი i და ფუნქცია func არ არიან გარე მონაცემები

// მაგრამ კომპილერმა უნდა ”იცოდეს” მათი არსებობის შესახებ

extern int i;

extern void func();

int main()

{

i = 0;

func();

cout<<endl;

system("pause");

return 0;

}

int i; // ცვლადზე განაცხადი

// ფუნქციის განსაზღვრა

void func(){

i++;

cout << i;

}

შევასრულოთ პროგრამა, მივიღებთ:

Page 32: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 32

როდესაც კომპილერს ხვდება სტრიქონი extern int i; ის ”ელოდება”, რომ i

ცვლადზე განაცხადს შეიცავს სხვა ფაილი. მოგვიანებით კომპილერი აღმოაჩენს int i;

განაცხადს და ”დაასკვნის”, რომ ეს იგივე ცვლადია, რომლის წინასწარი აღწერა იყო ამავე

ფაილში.

გლობალური static ცვლადის ან ფუნქციის ხილვადობის არე არის ფაილი,

რომელშიც კეთდება მასზე განაცხადი. ანუ გლობალური static ცვლადი ან ფუნქცია

ცნობილია მხოლოდ მიმდინარე ფაილის ფარგლებში, ხოლო ამ ფაილის გარეთ მასზე წვდომა

არ არის.

განვიხილოთ მაგალითი:

// ხილვადობის არე - ფაილი

// static_gare.cpp

#include <iostream>

#include <cstdlib>

#include "static_gare1.cpp"+-

using namespace std;

// fs ცვლადი ”ჩანს” მხოლოდ მიმდინარე ფაილში

static int fs;

int main()

{

fs = 1;

system("pause");

return 0;

}

// fs ცვლადზე მიმართვის მცდელობა

// static_gare1.cpp

extern int fs;

void func(){

fs = 100;

}

1

Press any key to continue . . .

Page 33: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 33

მოყვანილი ორი ფაილის კომპილირება გამოიწვევს დაკავშირების (ლინკირების)

შეცდომას: static_gare1.cpp - previous declaration of 'fs'.

მიუხედავად იმისა რომ static_gare1.cpp ფაილში fs ცვლადი აღწერილია როგორც

extern int fs; კავშირების რედაქტორი (linker) მას ვერ პოულობს. ეს ხდება იმიტომ

რომ static_gare.cpp ფაილში fs აღწერილია როგორც static და ცნობილია მარტო ამ

ფაილში.

იდენტიფიკატორის მოქმედების (ხილვადობის) არე - პროგრამის ის ნაწილია,

რომელშიც შეიძლება ამ იდენტიფიკატორზე მიმართვა. მაგალითად, თუ ცვლადი აღვწერეთ

ბლოკში, მასზე მიმართვა შეიძლება მხოლოდ ამ ბლოკში ან ამ ბლოკის ჩადგმულ ბლოკში.

არსებობს იდენტიფიკატორის მოქმედების 4 არე:

- მოქმედების არე - ფუნქცია;

- მოქმედების არე - ფაილი;

- მოქმედების არე - ბლოკი;

- მოქმედების არე - ფუნქციის პროტოტიპი.

გლობალურ იდენტიფიკატორს აქვს მოქმედების არე ფაილი. ასეთი იდენტიფიკატორი

მისი განაცხადის წერტილიდან ფაილის ბოლომდე ”ცნობილია” ყველა ფუნქციისთვის.

გლობალურ ცვლადებს, ფუნქციების პროტოტიპებს და ფუნქციების განსაზღვრას აქვთ

მოქმედების არე ფაილი.

ბლოკში აღწერილი იდენტიფიკატორების მოქმედების არე – ეს ბლოკია. ფუნქციის ტანი

წარმოადგენს ბლოკს. ამიტომ ფუნქციის ლოკალური ცვლადების და მისი ფორმალური

პარამეტრების მოქმედების არე - ფუნქციის ტანია.

ნებისმიერი ბლოკი შეიძლება შეიცავდეს ცვლადების აღწერას. თუ ბლოკში ჩადგმულია

სხვა ბლოკი, და გარე და ჩადგმული ბლოკის ცვლადების სახელები ემთხვევა, შიგა ბლოკის

ცვლადის სახელი ”ჩრდილავს” გარე ბლოკის ცვლადის სახელს შიგა ბლოკის მოქმედების

დამთავრებამდე. ეს ნიშნავს, რომ სანამ სრულდება შიგა ბლოკი, მოქმედებს შიგა ბლოკის

სახელი და არა გარე ბლოკისა. ლოკალური static ცვლადების მოქმედების არე - ბლოკია,

მიუხედავათ იმისა, რომ ასეთი ცვლადები ინარჩუნებენ მნიშვნელობებს პროგრამის

შესრულების მთელი დროის განმავლობაში.

ფუნქციის პროტოტიპში მოცემული პარამეტრების სახელების მოქმედების არე -

ფუნქციის პროტოტიპია. ფუნქციის პროტოტიპის პარამეტრების სახელებს კომპილერი

უგულვებელყოფს: მისთვის არსებით ინფორმაციას წარმოადგენს არა პარამეტრების

სახელები, არამედ მათი ტიპები და რიცხვი. ამოტიმ ფუნქციის პროტოტიპში მითითებული

სახელები შეიძლება გამოვიყენოთ პროგრამის ნებისმიერ სხვა ადგილას.

განვიხილოთ მოქმედების არეების საილუსტრაციო მაგალითი:

#include <iostream>

#include <cstdlib>

using namespace std;

void a();

Page 34: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 34

void b();

void c(void);

int x =1;

int main(){

int x =5;

cout<<"main-is lokaluri x="<<x<<endl;

cout<<"globaluri x="<<::x<<endl;

{

int x =7;

cout<<"blokis lokaluri x="<<x<<endl;

}

cout<<"main-is lokaluri x blokidan gamosvlis shemdeg="

<<x<<endl;

a();

b();

c();

cout<<"main-is lokaluri x="<<x<<endl;

cout<<"globaluri x="<<::x<<endl;

system("pause");

return 0;

}

void a(){

int x =25;

cout<<"\nlokaluri x a-shi shesvlis dros="<<x<<endl;

++x;

cout<<"lokaluri x a-dan gamosvlis win="<<x<<endl;

}

void b(){

static int x =50;

cout<<endl<<"lokaluri x b-shi shesvlis dros="

Page 35: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 35

<<x<<endl;

++x;

cout<<"lokaluri x b-dan gamosvlis win="<<x<<endl;

}

void c(void){

cout<<endl<<"globaluri x c-shi shesvlis dros="

<<x<<endl;

x *=10;

cout<<"globaluri x c-dan gamosvlis win="<<x<<endl;

}

პროგრამის შესრულების შედეგია:

განხილულ მაგალითში : : ოპერატორი ლოკალური ცვლადის სახელის წინ ამავე

სახელის მქონე გლობალურ ცვლადზე მიმართვის საშუალებას იძლევა.

პროტოტიპი void c(void); ნიშნავს, რომ ფუნქცია c არ აბრუნებს მნიშვნელობას და

მას არ გააჩნია პარამეტრები. C++ -ში

void c(void);

და

main-is lokaluri x=5

globaluri x=1

blokis lokaluri x=7

main-is lokaluri x blokidan gamosvlis shemdeg=5

lokaluri x a-shi shesvlis dros=25

lokaluri x a-dan gamosvlis win=26

lokaluri x b-shi shesvlis dros=50

lokaluri x b-dan gamosvlis win=51

globaluri x c-shi shesvlis dros=1

globaluri x c-dan gamosvlis win=10

main-is lokaluri x=5

globaluri x=10

Press any key to continue . . .

Page 36: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 36

void c();

ფუნქციაზე იდენტური განაცხადებია.

თემა 2. (დამატებითი მასალა) ფუნქციის პარამეტრი – მასივი

მასივების გადაცემა ფუნქციაში

ფუნქციის პარამეტრი - სტრიქონი

ფუნქციის პარამეტრი - ერთგანზომილებიანი მასივი

ფუნქციის პარამეტრი - ორგანზომილებიანი მასივი

მასივების გადაცემა ფუნქციაში

C/C++-ში მასივები გადაეცემა ფუნქციას მისამართით, ე.ი. ფუნქციას მიეწოდება მასივის

მიერ დაკავებული მეხსიერების მისამართი. თუ ფუნქცია ცვლის მასივის ელემენტებს, ეს

ცვლილება ხორციელდება რეალური მასივის ელემენტებზე მათ მიერ დაკავებულ

მეხსიერებაში. ამიტომ მასივების ფუნქციაში გადაცემის დროს უნდა გვახსოვდეს, რომ

ფორმალური პარამეტრი-მასივი არის ცვლადი პარამეტრი და თუ არ გვსურს მასივი-

არგუმენტის შეცვლა, გვმართებს სიფრთხილე.

ფუნქციის პარამეტრების სიაში პარამეტრი-მასივის აღწერა შეიძლება 3 ტოლძალოვანი

ფორმით.

ვთქვათ, პროგრამაში გვაქვს განაცხადი მასივზე: int x[10]; და გვინდა x მასივი

გადავცეთ void ტიპის ფუნქციას func. მაშინ func ფუნქციის პროტოტიპი შეიძლება იყოს

void func ( int a[ ] ); ან

void func ( int* a ); ან

void func ( int a[10] );

პროგრამაში func ფუნქციის გამოძახებას ექნება სახე func( x ); ე.ი. ფუნქციის არგუმენტი

- x არის მასივის სახელი.

C-ს სტრიქონების გადაცემა ფუნქციაში ექვემდებარება იგივე წესებს. მაგალითად, თუ

სტრიქონი s, აღწერილი როგორც char s[30]; უნდა გადავცეთ int ტიპის func1

ფუნქციას, მაშინ ამ ფუნქციის პროტოტიპი უნდა გავაფორმოთ ჩამოთვლილთაგან ერთ-ერთი

სახით:

int func1 ( char q[ ] );

int func1 ( char q[30]);

int func1 ( char* q );

აქედან უფრო მიღებულია int func1( char* q );

ორგანზომილებიანი მასივების ფუნქციაში გადაცემის შემთხვევაში, ფუნქციის

პარამეტრების სიაში მასივის პირველი ინდექსის მითითება აუცილებელი არ არის. ვთქვათ,

Page 37: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 37

მასივი float k[5][10]; უნდა გადავცეთ void ტიპის func2 ფუნქციას. მაშინ ფუნქციის

პროტოტიპის შესაძლო სახეა:

void func2(float x[5][10]); ან

void func2(float x[ ][10]);

ხოლო პროგრამაში ფუნქციის გამოძახებას აქვს სახე func2( k );

ფუნქციის პარამეტრი - სტრიქონი

პირველად განვიხილოთ C-ს სტრიქონების ფუნქციაში გადაცემის მაგალითი.

ამოცანა: მოცემული ორი სტრიქონისათვის დავადგინოთ, რომელი შეიცავს მეტ

ხმოვანს.

ცხადია, რომ გამოიკვეთა ქვეამოცანა: დავითვალოთ ხმოვანი ასოების რაოდენობა

სტრიქონში. ამ კონკრეტული ამოცანისთვის დავწეროთ ფუნქცია

int Vowels(char* x){

int i, k =0;

for(i=0; i<strlen(x); i++)

switch(x[i]){

case 'a': case 'e':

case 'i': case 'o':

case 'u': k++;

}

return k;

}

ფუნქციის სათაური int Vowels(char* x) გვიჩვენებს, რომ

ფორმალური პარამეტრი – char* x – წარმოადგენს მიმთითებელს char ტიპზე. ეს

ნიშნავს, რომ ფუნქციის გამოძახების დროს არგუმენტი უნდა იყოს რომელიმე

char -ის მისამართი, კერძოდ კი, შეიძლება იყოს სტრიქონის დასაწყისის მისამართი.

ფუნქციაში ამ სტრიქონის სახელი იქნება x.

ფუნქციის დასაბრუნებელი მნიშვნელობა int ტიპისაა, რადგანაც ფუნქცია ითვლის

სტრიქონში ხმოვანი ასოების რაოდენობას.

იდენტიფიკატორი Vowels – ფუნქციის სახელია.

i და k – ფუნქციის ლოკალური ცვლადებია, ამასთან k აღნიშნავს ხმოვანი ასოების

მთვლელს, ხოლო i-ს გამოვიყენებთ განმეორების შეტყობინების პარამეტრად.

ფუნქციის ტანში ერთადერთი შეტყობინებაა – for შეტყობინება. მისი საშუალებით

მოხდება x სტრიქონის დათვალიერება და მასში ხმოვანი ასოების ძიება. სტრიქონი

წარმოადგენს სიმბოლოების მასივს. ამიტომ სტრიქონის თითოეული სიმბოლო x[i]

შემოწმდება 'a','e','i','o' და 'u' ასოებთან ტოლობაზე. x მასივის ელემენტების

Page 38: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 38

i ინდექსები შეიცვლება 0-დან strlen(x)-1 -მდე. ყოველი აღმოჩენილი ხმოვანი ასოს

შემთხვევაში k გაიზრდება. აღსანიშნავია, რომ k თავიდან აუცილებლად უნდა იყოს

განულებული. ბოლოს, ფუნქციიდან k -ს დააბრუნებს return k;

შესაბამის პროგრამას ექნება სახე:

#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

int Vowels(char* );

int main(){

int n, k;

char s1[100], s2[100];

puts("Shemoitenet I striqoni: ");

gets(s1);

puts("Shemoitenet II striqoni: ");

gets(s2);

n = Vowels(s1);

k = Vowels(s2);

cout<<"xmovanebis raodenoba ";

if( n > k ) cout<<"I striqonshi metia\n";

else

if( n < k ) cout<<"II striqonshi metia\n";

else cout<<"orive striqonshi tolia\n";

system("pause");

return 0;

}

int Vowels(char* x){

int k =0;

for(int i=0; i<strlen(x); i++)

switch(x[i]){

Page 39: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 39

case 'a': case 'e':

case 'i': case 'o':

case 'u': k++;

}

return k;

}

პროგრამის შესრულების შედეგია:

შენიშვნა: ბრძანებები

#include <cstdio>

#include <cstring>

შეგვიძლია პროგრამაში არ ჩავრთოთ: gets და strlen ფუნქციების გამოყენებისთვის

საკმარიცია using namespace std; დირექტივის არსებობა.

შემდეგ მაგალითში ფუნქციის პარამეტრი წარმოადგენს string ტიპის სრტრიქონს.

ამოცანა: სტრიქონი შედგება სიტყვებისაგან, რომლებიც გამოიყოფა ჰარის სიმბოლოთი.

ბოლო სიტყვის შემდეგ დგას წერტილი. დაწერეთ ფუნქცია, რომელიც დაითვლის ერთი და

იგივე ასოთი დაწყებული და დამთავრებული სიტყვების რაოდენობას. ძირითად

პროგრამაში გამოიყენეთ ფუნქცია კლავიატურიდან შემოტანილი სტრიქონისთვის.

შესაბამისი პროგრამის შესაძლო სახეა:

#include <iostream>

#include <string>

using namespace std;

int Words(string ); // ფუნქციის პროტოტიპი

int main(){

string S;

int counter;

Shemoitenet I striqoni:

Giorgi nichieri mxatvaria

Shemoitenet II striqoni:

Keti popularuli momReralia

xmovanebis raodenoba II striqonshi metia

Press any key to continue . . .

Page 40: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 40

cout<<"Shemoitanet striqoni :\n";

getline(cin, S);

counter =Words(S); // ფუნქციის გამოძახება

cout<<"\nAseTi sityvaa "

<<counter<<endl;

system("PAUSE");

return 0;

}

int Words(string x){ // ფუნქციის განსაზღვრა

int i, j, k =0;

char a, b;

for(i=j=0; i < x.size(); ++i){

a =x[j];

if(x[i] != ' ' && x[i] != '.')

b =x[i];

else{

if(a == b) k++;

j =i+1;

}

}

return k;

}

პროგრამა გამოიტანს:

პროგრამაში გვხვდება ორი ახალი ფუნქცია:

getline(cin, s); არის სტანდარტული getline ფუნქციის გამოძახება. ფუნქცია

“კითხულობს”

elene spraits ar svams.

Shemoitanet striqoni :

elene spraits ar svams.

AseTi sityvaa 3

Press any key to continue . . .

Page 41: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 41

სტრიქონს შეტანის სტანდარტული ნაკადიდან (კლავიატურიდან) და ანიჭებს S-ს.

ანალოგიური მოქმედების შეტყობინება cin >> S; ჩაწერდა S -ში მხოლოდ პირველ სიტყვას

elene.

x.size() გამოსახულებაში string ტიპის x სტრიქონი იძახებს size ფუნქციას,

რომელიც ადგენს მის სიგრძეს.

ფუნქციის პარამეტრი - ერთგანზომილებიანი მასივი

ამოცანა: დაწერეთ ფუნქცია void bublle_sort(float* a, int size), რომელიც

size განზომილების a მასივს დაალაგებს ზრდადობით (მეზობელი ელემენტების

გადანაცვლებით). გამოიყენეთ ფუნქცია ძირითად პროგრამაში N ელემენტებიანი numbers

მასივის ზრდადობით დალაგებისათვის. numbers მასივში რიცხვები ჩაწერეთ input.txt

ფაილიდან, რომელიც შეიცავს 7 ნამდვილ რიცხვს. დალაგების ყოველი ბიჯი გამოიტანეთ

ეკრანზე.

პროგრამას ექნქბა შემდეგი სახე:

#include <iostream>

#include <iomanip>

#include <conio.h>

using namespace std;

void fillArray(float* a, int size);

void bublle_sort(float* a, int size);

int main(){

const int N =7;

float numbers[N];

fillArray(numbers, N);

bublle_sort(numbers, N);

getch();

return 0;

}

void fillArray(float* a, int size){

freopen("input.txt", "r", stdin);

for(int i=0; i < size; i++)

cin>>a[i];

}

Page 42: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 42

void swap(float* a,float* b){

float t =*a; *a =*b; *b =t;

}

void bublle_sort(float* a, int size){

void swap(float* ,float* );

void printArray(const float* a, int size);

bool check =true;

for(int k=size; k > 1 && check; k--){

printArray(a, size);

for(int i=0; i < size-1; i++)

if(a[i] > a[i+1]){

swap(&a[i], &a[i+1]);

check = false;

}

check = ! check;

}

}

void printArray(const float* a, int size){

for(int j=0; j<size; j++)

cout<<setw(8)<<fixed

<<setprecision(3)<<a[j];

cout<<endl;

}

პროგრამა დაგვიბეჭდავს:

მივაქციოთ ყურადღება setw, fixed და setprecision მანიპულატორების

1.200 3.400 67.800 0.000 -12.250 45.456 -0.985

1.200 3.400 0.000 -12.250 45.456 -0.985 67.800

1.200 0.000 -12.250 3.400 -0.985 45.456 67.800

0.000 -12.250 1.200 -0.985 3.400 45.456 67.800

-12.250 0.000 -0.985 1.200 3.400 45.456 67.800

-12.250 -0.985 0.000 1.200 3.400 45.456 67.800

Page 43: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 43

გამოყენებას შედეგის უკეთესი აღქმისათვის. setw(8)„კარნახობს“ ნაკადს, რომ ყოველი

a[j]-ს გამოტანას დაეთმოს 8 პოზიცია, ხოლო fixed და setprecision(3) – რომ

ნამდვილი რისხვები დაიბეჭდოს ფიქსირებული წერტილით 3 ათობითი ციფრის ჩვენებით

წილად ნაწილში.

bublle_sort ფუნქცია იძახებს ორივე swap და printArray ფუნქციას, ამიტომ მათზე

განაცხადები კეთდება bublle_sort ფუნქციის ტანში და არა გლობალურად.

ფუნქციის პარამეტრი - ორგანზომილებიანი მასივი

ამოცანა: დაწერეთ ფუნქცია, რომელიც დაითვლის ორგანზომილებიანი მასივის

(მატრიცის) იმ ელემენტების რაოდენობას, რომლებიც უდიდესია როგორც თავის

სტრიქონში, ისე თავის სვეტში და დაბეჭდავს თითოეულის მნიშვნელობასა და ინდექსებს.

ძირითად პროგრამაში გამოიძახეთ ფუნქცია შემთხვევითი რიცხვებით [-10,40]

დიაპაზონიდან შევსებული მატრიცისთვის. მატრიცის N და M განზომილებები (N =3,

M =4)შემოიღეთ გლობალურად. თუ მატრიცას ასეთი ელემენტები არ გააჩნია, დაბეჭდეთ

სათანადო გზავნილი. მატრიცის შექმნა და მისი გამოტანა (შედეგის შესამოწმებლად)

გააფორმეთ ფუნქციების სახით.

შესაბამისი პროგრამას სახეა:

#include <iostream>

#include <iomanip>

#include <ctime>

using namespace std;

const int N =3, M =4;

void inputMatrix(int a[][M]);

void outMatrix(int a[][M]);

int Maximum(int a[][M]);

int main()

{

int matrix[N][M];

int k;

inputMatrix(matrix);

outMatrix(matrix);

k =Maximum(matrix);

if (k != 0)

cout<<"\nElementebis ricxvi= "<<k<<endl;

Page 44: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 44

else

cout<<"\nAseTi elementebi ar iyo"<<endl;

system("PAUSE");

return 0;

}

// ფუნქცია ავსებს მატრიცას შემთხვევითი რიცხვებით

void inputMatrix(int a[][M]){

srand(time(NULL));

for(int i=0; i<N; i++)

for(int j=0; j<M; j++)

a[i][j] =rand()%51-10;

}

// ფუნქცია ბეჭდავს მატრიცას

void outMatrix(int a[][M]){

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

for(int j=0; j<M; j++)

cout<<setw(5)<<a[i][j]<<' ';

cout<<endl;

}

}

int Maximum(int a[][M]){

int max, counter =0;

for(int i=0; i<N; i++)

{

// i-ურ სტრიქონში მაქსიმალური ელემენტის მოძებნა

max =a[i][0];

for(int j=1; j<M; j++)

if(a[i][j] > max)

max =a[i][j];

Page 45: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 45

31 -5 0 21

34 6 -7 23

24 25 34 34

34 [ 1 ][ 0 ]

34 [ 2 ][ 2 ]

34 [ 2 ][ 3 ]

Elementebis ricxvi= 3

Press any key to continue . . .

// i-ური სტრიქონის ყოველი მაქსიმალური ელემენტისთვის

for(int t=0; t<M; t++)

if(a[i][t] == max)

{

bool b =true;

// თუ მის სვეტში უფრო დიდი ელემენტი აღმოჩნდა

for(int r=0; b && r<N; r++)

if(a[r][t] > max)

b =false; // შევწყვიტოთ განმეორება

// თუ სვეტში უფრო დიდი ელემენტი არ აღმოჩნდა

// გავზარდოთ მთვლელი და დავბეჭდოთ ელემენტის

// სიდიდე და მისი ინდექსები

if(b){

cout<<max<<" [ "<<i<<" ]"

<<"[ "<<t<<" ]"<<endl;

counter++;

}

}

}

return counter;

}

პროგრამის შედეგია:

Page 46: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 46

მოყვანილი შედეგი მიღებულია srand ფუნქციის კომენტარში ჩასმოს შემთხვევაში.

თემა 3. ფუნქცია C++ -ში

უპარამეტრო ფუნქციები C++ -ში

მითითება. ფუნქციის პარამეტრი-მითითება (reference parameter)

ფუნქციის პარამეტრების ინიციალიზება (არგუმენტები გაჩუმებით)

ჩადგმადი (inline) ფუნქციები

ფუნქციათა გადატვირთვა

ფუნქციის შაბლონი

უპარამეტრო ფუნქციები C++ -ში

უამრავ შემთხვევაში C++ -ის ფუნქცია არ საჭიროებს საწყის მონაცემებს (მას არ

გადაეცემა არგუმენტები). ამ ფაქტის აღსანიშნავად ენის სინტაქსი ითვალისწინებს ორ

შესაძლებლობას:

პარამეტრების ცარიელი სია.

მაგალითად,

void print();

განაცხადი აღნიშნავს, რომ print ფუნქციას პარამეტრები არა აქვს და ის არც

მნიშვნელობას აბრუნებს.

მომსახურე void სიტყვის გამოყენება.

print ფუნქციაზე ტოლძალოვანი განაცხადია

void print(void);

სადაც მრგვალ ფრჩხილებში ჩაწერილი void ნიშნავს პარამეტრების ცარიელ სიას.

ფუნქციის პროტოტიპი

float Calculation();

აღნიშნახს, რომ Calculation დააბრუნებს ნამდვილ რიცხვს, მაგრამ არგუმენტები არ

სჭირდება.

ქვემოთ მოყვანილ პროგრამაში ფუნქცია Calculation ითვლის [217, 426] შუალედში

13-ის ჯერადი რიცხვების საშუალო არითმეტიკულს.

#include <iostream>

#include <cstdlib>

using namespace std;

float Calculation();

int main()

{

float k;

Page 47: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 47

k =Calculation();

cout<<k<<endl;

system("pause");

return 0;

}

float Calculation(){

int s =0, k =0;

for(int n =426/13*13; n>=217; n -=13){

s +=n;

k++;

}

return (float)s/k;

}

პროგრამა დაბეჭდავს:

მითითება. ფუნქციის პარამეტრი-მითითება (reference parameter)

არგუმენტის გადაცემა ფუნქციაში შეიძლება ორი გზით - მნიშვნელობით და

მისამართით. მეორე შემთხვევაში ფუნქციის პარამეტრი წარმოადგენს ცვლად პარამეტრს.

ცვლადი პარამეტრის აღწერა პოინტერის საშუალებით უკვე განხილული გვაქვს.

C++ -ში არგუმენტის მისამართით გადაცემის ალტერნატიული საშუალება არსებობს -

პარამეტრის აღწერა მითითების (reference) სახით. ტერმინი reference ხშირად

ითარგმნება როგორც მინიშნება ან ფსევდონიმით მიმართვა.

მითითების ცნება C++-ში ძირითადად შემოღებულია არგუმენტების ფუნქციაში მისამართით

გადაცემისა და ფუნქციის დასაბრუნებელი მნიშვნელობა-მითითების გამოყენების თვალსაზრისით.

დასაშვებია ე.წ. დამოუკიდებელი მითითებაც, თუმცა პრაქტიკაზე იგი გამოიყენება იშვიათად.

მითითება წარმოადგენს პროგრამული ობიექტის ფსევდონიმს (alias), ანუ, მარტივად

რომ ვთქვათ, იმ პროგრამული ობიექტის სხვა სახელს, რომელსაც იგი მიუთითებს.

მითითებაზე განაცხადში გამოიყენება & ოპერატორი. რადგან მითითება - პროგრამული

ობიექტის ფსევდონიმია, მითითებაზე განაცხადისთანავე აუცილებლად უნდა მოხდეს მისი

ინიციალიზება. მაგალითად,

int j, k;

int &i = j; // i მითითებაზე განაცხადი

318.5

Press any key to continue . . .

Page 48: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 48

განაცხადი აღნიშნავს, რომ i ირიბად მიუთითებს j-ს, ანუ მთელი ტიპის i ცვლადის

მისამართი იგივეა, რაც მთელი ტიპის j ცვლადისა. გამოდის, რომ i და j - ერთი და იგივე

მეხსიერების სახელებია.

შემდეგი ფრაგმენტი

j = 10;

cout<<j<<" "<< i; // დაიბეჭდება 10 10

k = 121;

i = k;

// i-ს მიენიჭა k-ს მნიშვნელობა, ე.ი. j-ც გახდება k-ს ტოლი

cout<<j<<" "<<i; // დაიბეჭდება 121 121

i++;

cout<<j<<" "<<i; // დაიბეჭდება 122 122

გვარწმუნებს, რომ მითითებასა (i) და იმ ცვლადს შორის, რომელსაც იგი მიუთითებს (j)

არავითარი განსხვავება არ არის.

ფუნქციის პარამეტრი-მითითება წარმოადგენს შესაბამისი არგუმენტის ფსევდონიმს.

პარამეტრი-მითითების აღწერა ხდება & ნიშნის გამოყენებით. მაგალითად,

void f(int & i); ფუნქციის პროტოტიპში i არის int ტიპის პარამეტრი-მითითება.

ამბობენ აგრეთვე, რომ i არის მითითება int ტიპზე.

ვთქვათ, k – მთელი ტიპის ცვლადია. f ფუნქციის გამოძახების f(k); შეტყობინებაში

საკმარისია ჩავწეროთ k არგუმენტის სახელი, და იგი ავტომატურად იქნება გადაცემული

მისამართით. ფუნქციის შესრულების დროს ხდება პარამეტრი-მითითების ავტომატური

განმისამართება, ამიტომ პროგრამისტმა ამაზე აღარ უნდა იზრუნოს. ფუნქციის ტანში i

პარამეტრზე მიმართვა ფაქტიურად ნიშნავს k არგუმენტზე მიმართვას, და i –ს შეცვლა

გამოიწვევს k –ს შეცვლას.

შემდეგ საილუსტრაციო მაგალითში ნაჩვენებია პარამეტრი-მითითების გამოყენება

#include <iostream>

using namespace std;

void f(int &);

int main(){

int value = 1;

cout<<"value cvladis sawyisi mnishvneloba: "

<<value<<'\n';

f(value); // value ცვლადს გადავცემთ მითითებით

Page 49: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 49

cout<<"value cvladis axali mnishvneloba: "

<<value<<'\n';

system("pause");

return 0;

}

void f(int &i){

i = 10; // არგუმენტის შეცვლა

}

პროგრამის შესრულების შედეგი:

ადრე განხილულ ორ მთელს შორის მნიშვნელობების გაცვლის ფუნქციას,

რომელშიც გამოიყენება პარამეტრი-მითითებები, ექნება სახე

void swap(int& x, int& y){

int t = x;

x = y;

y = t;

}

ამ ფუნქციის მუშაობის საილუსტრაციო პროგრამა

#include <iostream>

using namespace std;

void swap(int &, int &);

int main()

{

int a, b;

cout<<"ShemoitaneT 2 mTeli ricxvi:\n";

cout<<"a = ";

cin>>a;

cout<<"b = ";

value cvladis sawyisi mnishvneloba: 1

value cvladis axali mnishvneloba: 10

Press any key to continue . . .

Page 50: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 50

cin>>b;

swap(a, b);

cout<<"Gacvlis shemdeg (parametri-miTiTeba):\n"

<<"a = "<<a<<" b = "<<b<<endl;

system("pause");

return 0;

}

void swap(int& x, int& y){

int t = x; x = y; y = t;

}

დაგვიბეჭდავს:

ფუნქციის პარამეტრების ინიციალიზება (არგუმენტები გაჩუმებით)

C++ -ის სინტაქსის მიხედვით ფუნქციის ფორმალურ პარამეტრებს შეიძლება მივანიჭოთ

საწყისი მნიშვნელობები, ანუ მოვახდინოთ პარამეტრების ინიციალიზება ფუნქციაზე

განაცხადის დროს.

ფუნქციის გამოძახება, ჩვეულებრივ, შეიცავს პარამეტრების კონკრეტულ

მნიშვნელობებს - არგუმენტებს. როგორც გვახსოვს, ფუნქციის არგუმენტებისა და

პარამეტრების რიცხვი უნდა ემთხვევოდეს. თუ ფუნქციის პროტოტიპში პარამეტრებს

მინიჭებული აქვთ საწყისი მნიშვნელობები, ფუნქციის არგუმენტების რაოდენობა შეიძლება

იყოს პარამეტრების რაოდენობაზე ნაკლები. ამ შემთხვევაში გამოტოვებული არგუმენტის

ნაცვლად ფუნქციას ავტომატურად გადაეცემა მისი შესაბამისი ფორმალური პარამეტრის

საწყისი მნიშვნელობა. ასეთ არგუმენტებს ეწოდებათ არგუმენტები გაჩუმებით.

განვიხილოთ საილუსტრაციო მაგალითი, რომელშიც გვექნება პარალელეპიპედის

მოცულობის გამოსათვლელი ფუნქცია Volume. ფუნქცია მოითხოვს 3 არგუმენტს, ხოლო მის

პროტოტიპში სამივე ფორმალურ პარამეტრს მინიჭებული აქვთ საწყისი მნიშვნელობები.

ფუნქციის სხვადასხვა გამოძახების დროს მას გადაეცემა არგუმენტების სხვადასხვა

რაოდენობა.

#include <iostream>

#include <cstdlib>

ShemoitaneT 2 mTeli ricxvi:

a = 17

b = -23

Gacvlis shemdeg (parametri-miTiTeba):

a = -23 b = 17

Press any key to continue . . .

Page 51: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 51

using namespace std;

int Volume(int a=2, int b=3, int c=4);

// a - პარალელეპიპედის ფუძის სიგრძე,

// b - პარალელეპიპედის ფუძის სიგანე,

// c - პარალელეპიპედის სიმაღლე

int main()

{

cout<< Volume()<<endl

<< Volume(1,2)<<endl

<< Volume(1)<<endl

<< Volume(3,2,5)<<endl;

system("pause");

return 0;

}

int Volume(int a, int b, int c){

return a*b*c;

}

პროგრამის შესრულების შედეგია:

ფუნქციის პირველი გამოძახების დროს - Volume() - არგუმენტების სია ცარიელია,

ამიტომაც ფუნქციის a, b და c პარამეტრებს გადაეცემათ მნიშვნელობები 2, 3 და 4

შესაბამისად (პარამეტრების საწყისი მნიშვნელობები).

მეორე გამოძახებისას - Volume(1,2) - გვაქვს 2 არგუმენტი. არგუმენტების ეს

მნიშვნელობები შეესაბამება a და b პარამეტრებს. c პარამეტრს გადაეცემა მნიშნელობა

გაჩუმებით, ანუ პარამეტრების სიაში მითითებული მნიშვნელობა 4.

მესამე გამოძახების დროს - Volume(1) - არგუმენტების სია შეიცავს ერთადერთ

არგუმენტს. მისი მნიშვნელობა გადაეცემა a პარამეტრს, ხოლო b და c პარამეტრების

მნიშვნელობები იქნება 3 და 4 (მნიშვნელობები გაჩუმებით) შესაბამისად.

24

8

12

30

Press any key to continue . . .

Page 52: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 52

Volume(3,2,5) გამოძახებაში მოცემულია სამივე არგუმენტი. ამ შემთხვევაში

მოხდება პარამეტრების საწყისი მნიშვნელობების გადაფარვა, ანუ ფუნქციის a, b და c

პარამეტრებს გადაეცემათ მნიშვნელობები 3, 2 და 5 შესაბამისად.

შესაძლებელია ფორმალური პარამეტრების ნაწილობრივი ინიციალიზება . ამ შემთხვევაში

ინიციალიზება უნდა იწყებოდეს მარჯვნიდან, დაწყებული ბოლო პარამეტრით. მაგალითად,

void func(int a, int b=2, float c=3.75);

სინტაქსურად სწორი პროტოტიპია. ამ ფუნქციის გამოძახებას შესაძლოა ჰქონდეს სახე:

func(10);

func(5,7);

func(1,2,12.7);

ხოლო გამოძახება

func();

გამოიწვევს კომპილაციის შეცდომას.

ფუნქციის პროტოტიპში დასაშვებია პარამეტრების სახელების გამოტოვება.

მაგალითად, სწორი იქნება პროტოტიპი

void func1(int, int=2, float=3.75);

უნდა გავითვალისწინოთ, რომ func1 ფუნქციის განსაზღვრისას პარამეტრების სახელების

მითითება აუცილებელია, ხოლო პარამეტრების საწყისი მნიშვნელობების გამეორება

შეცდომაა. მაგალითად, func1 ფუნქციის განსაზღვრას შესაძლოა ჰქონდეს სახე

void func1(int x, int y, float z){

// შესრულებადი შეტყობინებები

}

დავამატოთ ადრე განხილულ პროგრამაში შედეგის ბეჭდვის Result ფუნქცია,

რომელიც იძახებს პარალელეპიპედის მოცულობის გამოთვლის Volume ფუნქციას. Result

ფუნქციაში მოვახდინოთ პარამეტრების ინიციალიზება. მივაქციოთ ყურადღება Result

ფუნქციის პროტოტიპს და ამ ფუნქციის გამოძახებების სინტაქსს.

#include <iostream>

#include <cstdlib>

using namespace std;

void Result(int=2, int=3, int=4);

int main()

{

int a, b, c;

Result();

Page 53: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 53

Result(1,2);

Result(1);

Result(3,2,5);

system("pause");

return 0;

}

void Result(int a, int b, int c){

int Volume(int, int, int);

cout<<"sigrze="<<a<<" sigane="<<b

<<" simagle="<<c

<<" paralelepipedis moculoba = "

<< Volume(a,b,c)<<endl;

}

int Volume(int a, int b, int c){

return a*b*c;

}

პროგრამის შესრულების შედეგია:

ჩადგმადი (inline) ფუნქციები

ფუნქციის გამოძახება რთული მექანიზმია და მოითხოვს გარკვეულ დროს, რაც

საბოლოო ჯამში იწვევს პროგრამის შესრულების დროის ზრდას. ფუნქციაზე მიმართვის

დროის შემცირების მიზნით C++ -ში შემოღებულია ე.წ. ჩადგმადი (inline) ფუნქციები. მათ

ასევე უწოდებენ ჩასმად ან ჩაშენებად ფუნქციებს.

სიტყვა inline ფუნქციის განაცხადში სთავაზობს (”ურჩევს”) კომპილერს ფუნქციის

გამოძახების მექანიზმის ჩართვის ნაცვლად მოახდინოს ფუნქციის ტანის ასლის გენერირება

(ჩაშენება, ჩასმა) მისი გამოძახების ადგილას.

მაგალითად,

#include <iostream>

sigrze=2 sigane=3 simagle=4 paralelepipedis moculoba = 24

sigrze=1 sigane=2 simagle=4 paralelepipedis moculoba = 8

sigrze=1 sigane=3 simagle=4 paralelepipedis moculoba = 12

sigrze=3 sigane=2 simagle=5 paralelepipedis moculoba = 30

Press any key to continue . . .

Page 54: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 54

using namespace std;

int min(int X, int Y);

int main()

{

cout<< min(5, 6) <<endl;

cout<< min(3, 2) <<endl;

system("pause");

return 0;

}

int min(int X, int Y){

return X < Y ? X : Y;

}

პროგრამაში გვაქვს განაცხადი min ფუნქციაზე, რომელიც ორჯერ გამოიძახება main –ში

ჩვეულებრივი წესით: ფუნქციის ლოკალური ცვლადებისთვის ხდება მეხსიერების

განაწილება სტეკში, განაწილებულ მეხსიერებაში შესაბამისი მნიშვნელობების ჩაწერა,

ფუნქციის შეტყობინებების შესრულება და მნიშვნელობის დაბრუნება გამოძახების

წერტილში.

მარტივი და პატარა min ფუნქცია შესანიშნავი კანდიდატია, რომ იყოს ჩადგმადი:

inline int min(int X, int Y){

return X < Y ? X : Y;

}

ამ შემთხვევაში პროგრამის აკინძვის შედეგად მიიღება მანქანური კოდი, რომელიც

შეესაბამება main –ის შემდეგ სახეს

int main()

{

cout<< (5 > 6 ? 6 : 5) <<endl;

cout<< (3 > 2 ? 2 : 3) <<endl;

system("pause");

return 0;

}

კოდი შესრულდება უფრო სწრაფად, ვიდრე პირველ შემთხვევაში.

Page 55: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 55

კომპილერს შეუძლია უგულვებელყოს inline სპეციფიკაცია და, ჩვეულებრივ, ასეც

იქცევა, თუ ფუნქცია არ არის ძალიან მარტივი და მოკლე.

ამრიგად, თუ ფუნქცია შეიცავს განმეორების შეტყობინებას, ან switch შეტყობინებას,

ან static ლოკალურ პარამეტრებს, ან არის რეკურსიული, inline სპეციფიკაციას მის

განაცხადში აზრი არა აქვს. აქვე აღვნიშნოთ, რომ inline ფუნქციის მრავალჯერადი

გამოძახება ზრდის პროგრამის მოცულობას. ამიტომ, თუ ფუნქციის ტანი მარტივია, მაგრამ

დიდი, მაშინაც არ ღირს ასეთ ფუნქციასთან inline სიტყვის გამოყენება.

დასაშვებია inline ფუნქციის პარამეტრებს მივანიჭოთ საწყისი მნიშვნელობები.

ქვემოთ განხილულ მაგალითში გვაქვს inline ფუნქცია square, რომელიც ითვლის x

გვერდის მქონე კვადრატის ფართობს.

#include <iostream>

using namespace std;

inline float square(float x =2.5);

int main()

{

cout<<"Kvadratis gverdi = 2.5,\nfartobi = "

<<square()<<endl;

cout<<"Sxva kvadratis gverdi = ";

float a;

cin>>a;

cout<<"Kvadratis fartobi = "

<<square(a)<<endl;

system("pause");

return 0;

}

inline float square(float x){

return x*x;

}

პროგრამის შესრულების შედეგი:

Kvadratis gverdi = 2.5, fartobi = 6.25

Sxva kvadratis gverdi = 10

Kvadratis fartobi = 100

Press any key to continue . . .

Page 56: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 56

ფუნქციათა გადატვირთვა

С++ -ში შესაძლებელია ერთი და იგივე სახელი დავარქვათ რამდენივე ფუნქციას იმ

პირობით, რომ ასეთი ფუნქციების პარამეტრების სია იქნება განსხვავებული: განსხვავბული

უნდა იყოს ან პარამეტრების რიცხვი, ან მათი ტიპი, ან პარამეტრების რიცხვიც და ტიპიც.

ფუნქციების ამ თვისებას ეწოდება ფუნქციათა გადატვირთვა (function overloading).

გადატვირთული (overloaded) ფუნქციების გამოძახების დროს С++ -ის კომპილერი

აანალიზებს არგუმენტების რაოდენობას, მათ ტიპსა და რიგითობას და ისე ადგენს

შესასრულებელი ფუნქციის შესაბამის ეკზემპლარს. უნდა აღვნიშნოთ, რომ ფუნქციების

დასაბრუნებელ მნიშვნელობას კომპილერი ”ყურადღებას არ აქცევს”. ე.ი. თუ ფუნქციების

პროტოტიპები განსხვავდება მხოლოდ დასაბრუნებელი მნიშვნელობებით, ასეთი

ფუნქციების გადატვირთვა არ შეიძლება.

განვიხილოთ ფუნქციათა გადატვირთვის საილუსტრაციო მაგალითი.

#include <iostream>

#include <cstdlib>

#include <cstring>

using namespace std;

inline int func(int n){ return n * n; }

inline char func(char x) { return x + 3; }

inline float func(float p){ return p * p; }

inline int func(int k, char* s){

return k * strlen(s);

}

int main(){

int i =4, j =5; float f =1.2;

cout<<func(i)<<" "<<func('A')<<" "

<<func(f)<<" "<<func(j, "abcde")<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

16 D 1.44 25

Press any key to continue . . .

Page 57: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 57

გადატვირთვა, როგორც წესი, გამოიყენება ისეთ ფუნქციებთან, რომლებიც

ასრულებენ მსგავს ამოცანებს სხვადასხვა მონაცემთა ტიპებისათვის.

ქვემოთ მოყვანილია მაგალითი, რომელშიც გადატვირთულია neg ფუნქცია. ფუნქცია

აბრუნებს თავისი ერთადერთი არგუმენტის მოპირდაპირე მნიშვნელობას.

#include <iostream>

#include <cstdlib>

using namespace std;

int neg(int );

float neg(float );

long neg(long );

int main()

{

int i =-10; float f =11.23; long k =90099L;

cout<<"neg(-10): "<<neg(i)<<"\n";

cout<<"neg(90099L): "<<neg(k)<<"\n";

cout<<"neg(11.23): "<<neg(f)<<"\n";

system("pause");

return 0;

}

int neg(int n){

return -n;

}

float neg(float n){

return -n;

}

long neg(long n){

return -n;

}

პროგრამის შესრულების შედეგია:

neg(-10): 10

neg(90099L): -90099

neg(11.23): -11.23

Press any key to continue . . .

Page 58: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 58

პროგრამაში განსაზღვრულია ერთი და იგივე მოქმედების სამი განსხვავებული

ფუნქცია. გადატვირთვის პრინციპული მნიშვნელობა ისაა, რომ სამივე ფუნქციაზე

მიმართვა ხდება ერთი სახელის გამოყენებით. ცალკეული გამოძახების დროს

შესასრულებელი ფუნქციის შერჩევა ევალება კომპილერს: კონკრეტული არგუმენტის

ტიპის მიხედვით კომპილერი აკეთებს კონკრეტული ფუნქციის სწორ არჩევანს.

პროგრამისტს კი ეძლევა საშუალება სამი განსხვავებული სახელის გამოყენებისა და

დამახსოვრების ნაცვლად, გამოიყენოს და დაიმახსოვროს მხოლოდ ერთი. მაგალითი

გვიჩვენებს რამდენად ამარტივებს ფუნქციათა გადატვირთვა დაპროგრამების პროცესს.

ამრიგად, ფუნქციათა გადატვირთვა ახორციელებს ე.წ. პოლიმორფიზმის პრინციპს -

”ერთი ინტერფეისი - მრავალი მეთოდი”.

კიდევ ერთი შენიშვნა: ვთქვათ, გვაქვს ორი პროტოტიპი

float func(int a, float b, char c =50);

და

int func(int x, float y);

რადგანაც ფუნქციათა პარამეტრების სია განსხვავებულია, თითქოს და შეიძლებოდა მათი

გადატვირთვა. მაგრამ პირველ ფუნქციაში char ტიპის პარამეტრი ინიციალიზებულია,

ამიტომ შეიძლება ფუნქციის ამ ეკზემპლარის გამოძახებას ჰქონდეს სახე

func(10, 0.0123); ამ შემთხვევაში კომპილერი ”დაიბნევა” - ის ვერ გაარჩევს, func

ფუნქციის რომელი ეკზემპლარი უნდა შეასრულოს და გამოიტანს შეტყობინებას შეცდომის

შესახებ: Call to undefined function 'func' .

ფუნქციის შაბლონი

როგორც აღვნიშნეთ, გადატვირთული ფუნქციები, ჩვეულებრივ, გამოიყენება მსგავსი

ოპერაციების ჩასატარებლად განსხვავებული ტიპის მონაცემებზე. თუ კი ყოველი მონაცემთა

ტიპისათვის უნდა შესრულდეს იგივე შეტყობინებები (როგორც ადრე განხილულ neg

ფუნქციის შემთხვევაში), მაშინ უფრო მოსახერხებელი არის ე.წ. ფუნქციის შაბლონის

გამოყენება. ამ შემთხვევაში პროგრამისტმა უნდა დაწეროს მხოლოდ ერთი ფუნქციის -

შაბლონის - განსაზღვრა. ფუნქცია-შაბლონის გამოძახების მომენტში კომპილერი

ავტომატურად მოახდენს ფუნქციის ობიექტური კოდის გენერირებას არგუმენტების ტიპის

მიხედვით.

ფუნქციის შაბლონზე განაცხადი იწყება მომსახურე სიტყვით template, რომლის

შემდეგ < > ფრჩხილებში იწერება შაბლონის ფორმალური პარამეტრების სია. ყოველი

ფორმალური პარამეტრის წინ მიეთითება მომსახურე სიტყვა class. მაგალითად,

template < class T >

template < class ElementType >

template < class A, class B >

სიტყვა class ”ხსნის”, რომ მის შემდეგ მოცემული სახელი - ტიპის ზოგადი სახელია

(ნებისმიერი სტანდარტული ან მომხმარებლის მიერ განსაზრვრული ტიპისა). ანუ T,

ElementType, A და B - ტიპის სახელებია, რომლებიც შეიძლება გამოვიყენოთ ფუნქციის

Page 59: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 59

პარამეტრებთან, ფუნქციის დასაბრუნებელ მნიშვნელობასთან და ფუნქციის ლოკალურ

ცვლადებთან.

შემდეგ, შაბლონის სათაურში მოიცემა ფუნქციის ჩვეულებრივი პროტოტიპი.

C++ -ის ბოლო სტანდარტში შემოღებულია ახალი მომსახურე სიტყვა typename,

რომელიც უკეთესად ხსნის შაბლონის პარამეტრის დანიშნულებას და გამოიყენება იგივე

კონტექსტში, რაც class:

template < typename T >

template < typename A, typename B >

განვიხილოთ printArray ფუნქციის შაბლონის მაგალითი. შაბლონში მოიაზრება

ორი პარამეტრი: პირველი - მიმთითებელი მასივის ტიპზე, მეორე - მასივის განზომილება.

ფუნქციის დანიშნულებაა მასივის ბეჭდა:

template < typename T > void printArray (T* array, const int count){

for(int i=0; i < count; i++) cout<<array[i]<<" ";

cout<<endl;

}

T-ს უწოდებენ ტიპის პარამეტრს. ჩვენს მაგალითში T იქნება დასაბეჭდი მასივის ტიპი.

როდესაც პროგრამის ტექსტში კომპილერს შეხვდება printArray ფუნქციის გამოძახება,

იგი შაბლონის განსაზღვრის მთელ არეში შეცვლის T -ს printArray ფუნქციის პირველი

არგუმენტის ტიპით და შექმნის ამ ტიპის მასივის ბეჭდვის ფუნქციას. მაგალითად, თუ

printArray ფუნქცია გამოიძახება მთელი ტიპის მასივისათვის, შაბლონის რეალიზება

მიიღებს სახეს

void printArray (int* array, const int count){

for(int i=0; i < count; i++)

cout<<array[i]<<" ";

cout<<endl;

}

ხოლო ნამდვილ რიცხვთა მასივისათვის მას ექნება სახე

void printArray (float* array, const int count){

for(int i=0; i < count; i++)

cout<<array[i]<< " ";

cout<<endl;

}

ამის შემდეგ შექმნილი ფუნქცია კომპილირდება და სრულდება.

შემდეგი პროგრამა არის ორ ცვლადს შორის უდიდესი მნიშვნელობის პოვნის ფუნქცია-

შაბლონის გამოყენების მაგალითი:

#include <iostream>

using namespace std;

// ფუნქცია-შაბლონზე განაცხადი template <typename A> A Max(A x, A y);

int main()

{

int a =3, b =7, c; c =Max(a,b); cout<<" max(3,7) = "<<c<<endl; float f =3.2, q =2.3; cout<<" max(3.2, 2.3) = "<<Max(f,q)<<endl; char p ='K', h ='B'; cout<<" max('K','B') = "<<Max(h,p)<<endl;

Page 60: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 60

char *s ="programa", *k ="programireba"; cout<<" max('programa', 'programireba') = "

<<Max(s,k)<< "\n\n";

system("pause");

return 0;

}

// ფუნქცია-შაბლონის განსაზღვრა template <typename A> A Max(A x, A y){

return (x > y)? x : y; }

პროგრამის შესრულების შედეგია:

შემდეგ მაგალითში ნაჩვენებია swap ფუნქციის გადატვირთა:

#include <iostream>

using namespace std;

void swap(int &, int &);

void swap(float &, float &);

void swap(char &, char &);

void swap(string &, string &);

int main()

{

int a =3, b =9;

float f =2.3, d =15.47;

char c ='A', q ='Z';

string s1 ="pirveli striqoni",

s2 ="meore striqoni";

cout<<"Shecvlamde :\n"<<a<<" "<<b<<endl

<<f<<" "<<d<<endl

<<c<<" "<<q<<endl

<<s1<<" "<<s2<<endl<<endl;

swap(a, b);

swap(f, d);

max(3, 7) = 7

max(3.2, 2.3) = 3.2

max('K', 'B') = K

max('programa', 'programireba') = programireba

Press any key to continue . . .

Page 61: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 61

swap(c, q);

swap(s1, s2);

cout<<"Shecvlis shemdeg :\n"<<a<<" "<<b<<endl

<<f<<" "<<d<<endl

<<c<<" "<<q<<endl

<<s1<<" "<<s2<<endl<<endl;

system("pause");

return 0;

}

void swap(int& x, int& y){

int t = x; x = y; y = t;

}

void swap(float &x, float &y){

float t = x; x = y; y = t;

}

void swap(char& x, char& y){

char t = x; x = y; y = t;

}

void swap(string& x, string& y){

string t;

t = x; x = y; y = t;

}

პროგრამა გამოიტანს შედეგს:

Shecvlamde :

3 9

2.3 15.47

A Z pirveli striqoni meore striqoni

Shecvlis shemdeg :

9 3 15.47 2.3

Z A

meore striqoni pirveli striqoni

Press any key to continue . . .

Page 62: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 62

Shecvlamde : 123 987 12.37 0.456 K M striqoni_1 striqoni_2

Shecvlis shemdeg : 987 123 0.456 12.37 M K striqoni_2 striqoni_1

Press any key to continue . . .

ქვემოთ მოყვანილია swap ფუნქციის შაბლონის მაგალითი:

#include <iostream>

#include <string>

using namespace std;

// ფუნქცია-შაბლონის პროტოტიპი template <typename T>

void swap(T &, T &);

int main()

{

int a =123, b =987;

float f =12.37, d =0.456;

char c ='K', q ='M';

string s1 ="striqoni_1", s2 ="striqoni_2";

cout<<"Shecvlamde :\n" <<a<<" "<<b<<endl

<<f<<" "<<d<<endl

<<c<<" "<<q<<endl

<<s1<<" "<<s2<<endl<<endl;

swap(a, b);

swap(f, d);

swap(c, q);

swap(s1, s2);

cout<<"Shecvlis shemdeg :\n"<<a<<" "<<b<<endl

<<f<<" "<<d<<endl

<<c<<" "<<q<<endl

<<s1<<" "<<s2<<endl<<endl;

system("pause");

return 0;

}

// ფუნქცია-შაბლონის განსაზღვრა template < typename T > void swap(T& x, T& y){

T temp = x; x = y; y = temp; }

პროგრამის შედეგია:

Page 63: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 63

მიღებულია, რომ:

თუ ფუნქციამ უნდა შეცვალოს არგუმენტის მნიშვნელობა, არგუმენტი გადაეცემა მას

პოინტერის ან მითითების გამოყენებით;

თუ არგუმენტი არ უნდა იცვლებოდეს, იგი გადაეცემა ფუნქციას მნიშვნელობით;

თუ ფუნქციას გადავცემთ დიდ რთული ტიპის არგუმენტს, მისი გადაცემა სრულდება

მითითების გამოყენებით. გამონაკლისს წარმოადგენენ მასივები, რომლებიც

ყოველთვის გადაიცემა პოინტერის მეშვეობით.

თემა 4. მომხმარებლის მიერ განსაზღვრული ტიპები C++-ში ჩამოთვლა (enum)

სტრუქტურა (struct)

გაერთიანება (union)

ჩამოთვლა

ჩამოთვლა წარმოადგენს მონაცემთა ტიპს, რომლის ყველა შესაძლო მნიშვნელობები

მოცემულია განაცხადშივე. ჩამოთვლის აღწერა იწყება მომსახურე სიტყვით enum

(enumeration), შემდეგ მოიცემა ამ ტიპის დასახელება და შესაძლო მნიშვნელობები,

მოთავსებული ფიგურულ ფრჩხილებში. მაგალითად,

enum palitra{ red,yellow,blue,green,white,black };

palitra color;

პირველი სტრიქონი აღწერს ახალ ტიპს palitra. red,yellow,...,black -

palitra ტიპის მნიშვნელობები - წარმოადგენენ palitra ტიპის კონსტანტებს მსგავსად

იმისა, როგორც 7 არის int ტიპის კონსტანტა, ხოლო 'Q' - char ტიპის კონსტანტა.

მეორე სტრიქონი აცხადებს palitra ტიპის ცვლადს color. color-ს შეიძლება

მივანიჭოთ palitra ტიპის ნებისმიერი კონსტანტა. მაგალითად,

color = green;

მნიშვნელოვანია გვესმოდეს, რომ palitra ტიპის კონსტანტები - არ არის სტრიქონები.

კომპილერი აღიქვამს მათ, როგორც მთელ რიცხვებს. გულისხმობის პრინციპით red -ს

შეესაბამება 0, yellow -ს - 1, და ა.შ. black -ს - 5. მაგალითად, შეტყობინება

cout<<red<<" "<< green;

დაგვიბეჭდავს: 0 3.

ჩამოთვლის კონსტანტას შეგვიძლია მივანიჭოთ მთელი მნიშვნელობა ჩვენი

სურვილით. მაგალითად,

Page 64: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 64

enum speeds { low =80, medium =120, high =200 };

თუ ერთ-ერთ კონსტანტას მინიჭებული აქვს მთელი რიცხვი, ხოლო მის შემდეგ მოცემულ

კონსტანტებს - არა, რიგის მიხედვით მათ მიენიჭებათ ამ რიცხვის მომდევნო მნიშვნელობები.

მაგალითად,

enum Cats { cat =20, tiger, lion, puma };

განაცხადში კონსტანტას tiger მიენიჭება 21, lion -ს - 22, puma -ს - 23.

enum ტიპის ცვლადს შეიძლება მივანიჭოთ იგივე ტიპის კონსტანტა ან მთელი რიცხვი:

Cats pet = puma;

palitra color =2; // ანუ blue

ამ ტიპის ცვლადებთან შეიძლება გამოვიყენოთ შედარების ოპერატორები == და != :

pet == tiger

color != yellow

და ასევე დასაშვებია enum ტიპის ცვლადის int-თან შედარება:

pet == 21

მონაცემთა ტიპი enum აკავშირებს რიცხვებს სახელებთან ერთი ძირითადი მიზნით -

გააადვილოს პროგრამის კოდის გაგება.

განვიხილოთ რამდენიმე საილუსტრაციო პროგრამა.

1. #include <iostream>

using namespace std;

enum ShapeType {

circle =1,

square =25,

rectangle

};

int main()

{

ShapeType shape = circle;

cout<<shape<<endl;

shape =square;

cout<<shape<<endl;

shape =rectangle;

cout<<shape<<endl;

Page 65: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 65

system("pause");

return 0;

}

პროგრამა დაგვიბეჭდავს:

ShapeType ტიპის shape ცვლადს ენიჭება მნიშვნელობა circle და ვბეჭდავთ

shape -ს. როგორც იყო მოსალოდნელი, დაიბეჭდა 1 - სწორედ ეს მნიშვნელობა ჩვენ

შეუსაბამეთ circle -ს. შემდეგ shape -ს ენიჭება მნიშვნელობა square და

cout<<shape<<endl;

გამოიტანს 25 -ს (რიცხვს, რომელიც შეესაბამება square -ს განსაზღვრის თანახმად).

შემდეგი მინიჭება ცვლის shape ცვლადის მნიშვნელობას rectangle -ით,

cout<<shape<<endl;

კი გამოიტანს 26 -ს – რადგან ShapeType ტიპის განსაზღვრაში rectangle კონსტანტას

რიცხვითი მნიშვნელობა არ ჰქონდა, ის დგინდება როგორც square -ს შესაბამისი მომდევნო

რიცხვი.

2. #include <iostream>

using namespace std;

enum ShapeType {

circle,

square,

rectangle

};

int main()

{

ShapeType shape = circle;

// ... იგულისხმეთ რაღაც მოქმედებები

switch(shape) {

case circle: cout<<"circle\n"; break;

case square: cout<<"square\n"; break;

1

25

26

Press any key to continue . . .

Page 66: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 66

case rectangle: cout<<"rectangle\n";

}

cout<<(shape == rectangle)<<endl

<<(shape != square)<<endl;

cout<<(shape == 0)<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგად გამოსატანი ეკრანი იქნება:

აქ ShapeType ტიპის shape ცვლადს ენიჭება მნიშვნელობა circle და შემდეგ

switch შეტყობინებაში ხდება shape ცვლადის შედარება კონსტანტურ მნიშვნელობებთან

circle, square და rectangle. შედეგად მართვა გადადის case circle: ბლოკზე და

სრულდება შეტყობინება cout<<"square\n";.

შედარება shape == rectangle დააბრუნებს 0 (მცდარს), shape != square

დააბრუნებს 1 (ჭეშმარიტს), ხოლო shape == 0 – დააბრუნებს 1, რადგან circle ცვლადი

გაიგივებულია 0-თან.

მივაქციოთ ყურადღება, რომ enum ტიპის განსაზღვრა უნდა მთავრდებოდეს ; -ით.

სტრუქტურა

სტრუქტურა წარმოადგენს შედგენილ მონაცემთა ტიპს, რომელიც აიგება სხვა ტიპების

საფუძველზე. ანუ სტრუქტურა არის სხვადასხვა ტიპის მონაცემთა ერთობლიობა.

სრტუქტურაში გაერთიანებულ ცვლადებს ეწოდებათ სტრუქტურის ელემენტები, წევრები ან

ველები. სტრუქტურაზე განაცხადი იწყება struct მომსახურე სიტყვით, შემდეგ მოიცემა

სტრუქტურის დასახელება და ფიგურულ ფრჩხილებში მოთავსებული სტრუქტურის

ელემენტები. მაგალითად, სტრუქტურაზე განაცხადი

srtuct Together {

char c;

int i;

circle

0

1

1

Press any key to continue . . .

Page 67: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 67

float f;

double d;

};

აღწერს მონაცემთა ახალ ტიპს. სტრუქტურის დასახელებას - Together - უწოდებენ

სტრუქტურის ტეგს. C++ -ში სტრუქტურის ტეგი არის ახალი ტიპის სახელი, რომელიც

გამოიყენება ამ ტიპის ცვლადის განაცხადში.

ჩვენს შემთხვევაში განსაზღვრულია ახალი მონაცემთა ტიპი Together.

Together obj, mass[7], *oPtr; განაცხადის ძალით

obj - Together ტიპის ცვლადია,

mass - Together ტიპის 7 ელემენტიანი მასივი, ხოლო

oPtr - არის პოინტერი Together ტიპზე.

Together ტიპის თითოეულ ცვლადს ახასიათებს char, int, float და double

სტანდარტული ტიპის 4 ელემენტი. სტრუქტურის ელემენტზე წვდომა ხდება ოპერატორი

წერტილის (” . ”) ან ოპერატორი ისრის (” -> ”) გამოყენებით:

თუ მოცემულია სტრუქტურის ტიპის ცვლადი, მის ელემენტებზე გადასვლა

სრულდება ოპერატორი წერტილის გამოყენებით, მაგალითად

obj.c ='K';

obj.i =123;

obj.f =4.5;

obj.d =0.000234;

თუ კი მოცემულია პოინტერი სტრუქტურაზე და გვაქვს მინიჭება oPtr = &obj, მაშინ

გამოიყენება ოპერატორი -> :

oPtr ->c ='A';

oPtr ->i =79;

oPtr ->f =0.456;

oPtr ->d =1.1234567;

(*oPtr).c გამოსახულება oPtr->c გამოსახულების ეკვივალენტურია, რადგანაც

(*oPtr).c-ში ჯერ სრულდება პოინტერის განმისამართება (ანუ ვწვდებით oPtr

მისამართზე განთავსებულ სტრუქტურას) და შემდეგ ” . ”-ით გადავდივართ ველებზე.

(*oPtr).c გამოსახულებაში მრგვალი ფრჩხილების გამოტოვება არ შეიძლება - ” * ”

ოპერატორთან შედარებით ოპერატორ ” . ” -ს უფრო მაღალი პრიორიტეტი აქვს.

ამრიგად, სტრუქტურის ელემენტზე წვდომა პოინტერის მეშვეობით ასედაც შეიძლება:

(*oPtr).c ='A';

(*oPtr).i =79;

(*oPtr).f =0.456;

(*oPtr).d =1.1234567;

Page 68: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 68

სტრუქტურაზე განაცხადი უნდა მთავრდებოდეს ” ; ”-ით.

სრტუქტურის ტიპის ცვლადის ან მასზე პოინტერის აღწერა შეიძლება

სტრუქტურაზე განაცხადისთანავე.

მაგალითად,

srtuct Together {

char c;

int i;

float f;

double d;

} a, b, *c;

განვიხილოთ საილუსტრაციო მაგალითები:

1. #include <iostream>

using namespace std;

struct Structure1 {

char c;

int i;

float f;

double d;

};

int main()

{

struct Structure1 s;

s.c = 'a';

s.i = 1;

s.f = 3.14;

s.d = 0.00093;

cout<<s.c<<" "<<s.i<<" "

<<s.f<<" "<<s.d<<endl;

system("pause");

return 0;

}

Page 69: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 69

პროგრამის შესრულების შედეგი:

მაგალითი გვიჩვენებს როგორ მივწვდეთ Structure1 ტიპის s ცვლადის ელემენტებს

” . ”-ის საშუალებით.

2. #include <iostream>

using namespace std;

struct Structure2 {

char c;

int i;

float f;

double d;

};

int main()

{

Structure2 s1;

Structure2 * sp;

sp =&s1;

sp->c = 'B';

sp->i = 37;

sp->f = 2.89;

sp->d = 0.000195;

cout<<s1.c<<" "<<s1.i<<" "

<<s1.f<<" "<<s1.d<<endl;

Structure2 s2 ={'k', 123, 3.14, 0.0005432};

sp =&s2;

cout<<sp->c<<" "<<sp->i<<" "

<<sp->f<<" "<<sp->d<<endl;

cout<<(*sp).c<<" "<<(*sp).i<<" "

<<(*sp).f<<" "<<(*sp).d<<endl;

system("pause");

a 1 3.14 0.00093

Press any key to continue . . .

Page 70: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 70

return 0;

}

პროგრამის შედეგები:

პროგრამაში შემოვიღეთ Structure2 ტიპის s1 ცვლადი და Structure2 ტიპზე

პოინტერი sp. s1 -ს გაუნაწილდა მისი ყველა ელემენტის ზომის განთავსებისათვის საჭირო

მეხსიერება. პოინტერს sp მივანიჭეთ s1 სტრუქტურის მისამართი. ახლა შეგვიძლია

პოინტერის და ” -> ” ოპერატორის გამოყენებით ( sp-> ) ჩავწეროთ სტრუქტურის

ელემენტებში მნიშვნელობები. შემდეგ კი მივწვდეთ ამ მნიშვნელობებს s1 ცვლადის და ” . ”

ოპერატორის საშუალებით ( s1. ).

შემდეგ შევქმენით ახალი s2 სტრუქტურა და განაცხადისთანავე ჩავატარეთ მისი

ელემენტების ინიციალიზება:

Structure2 s2 ={'k',123,3.14,0.0005432};

sp პოინტერი ”მივაბით” s2 სტრუქტურას

sp = &s2;

და მივწვდით მის ველებს sp -სა და ” -> ” ოპერატორის გამოყენებით.

და ბოლოს, s2 -ის მონაცემებზე მოვახდინეთ წვდომა იგივე sp პოინტერით მისი

განმისამართების შემდეგ და ” . ” ოპერატორის საშუალებით:

cout<<(*sp).c<<" "<<(*sp).i<<" "

<<(*sp).f<<" "<<(*sp).d<<endl;

3. განვიხილოთ მაგალითი, რომელშიც Test ტიპის სტრუქტურის მონაცემებს ბეჭდავს

answer ფუნქცია. ფუნქცია answer გადატვირთულია. მის პირველ ვერსიას სტრუქტურა

გადაეცემა პოინტერის საშუალებით, მეორეს კი - ფსევდონიმით (მითითებით).

#include <iostream>

using namespace std;

struct Test {

char c;

int i;

float f;

double d;

B 37 2.89 0.000195

k 123 3.14 0.0005432

k 123 3.14 0.0005432

Press any key to continue . . .

Page 71: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 71

string s;

}A;

void answer(Test *);

void answer(Test &);

int main()

{

Test B;

A.c ='A'; B.c ='Z';

A.i =12; B.i =123;

A.f =5.67; B.f =1.234;

A.d =0.000000023;

B.d =0.123456789;

A.s ="A-s striqoni";

B.s ="B-s striqoni";

answer(&A);

answer(B);

Test* p =&B;

p->c ='M'; p->I =987;

p->f =-5.034; p->d =-12.000876;

answer(p);

(*p).c ='K'; (*p).i =245;

(*p).f =0.34; (*p).d =12.00099;

answer(p);

system("pause");

return 0;

}

void answer(Test* x){

cout<< x->c<<"\t"<<x->i<<"\t"

<<x->f<<"\t"<<x->d<<"\t"

<<x->s<<endl;

}

void answer(Test& x){

Page 72: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 72

cout<< x.c<<"\t"<<x.i<<"\t"

<<x.f<<"\t"<<x.d<<"\t"

<<x.s<<endl;

}

პროგრამის შესრულების შედეგი:

მივაქციოთ ყურადღება answer ფუნქციის გამოძახებას. answer(&A); შემთხვევაში

გამოიძახება answer ფუნქციის პირველი ვერსია ( პროტოტიპით void answer(Test *);)

ამიტომ არგუმენტია &A - A სტრუქტურის მისამართი.

answer(B); შემთხვევაში კი გამოიძახება answer ფუნქციის მეორე ვერსია

(პროტოტიპით void answer(Test &);) და B არგუმენტთან & საჭირო არ არის.

გაერთიანება

union - გაერთიანება - სტრუქტურის მსგავსად იძლება სხვადასხვა ტიპის მონაცემთა

ერთად ჩაწერის საშუალებას. მაგრამ, თუ სრტუქტურის თითოეულ ელემენტს გამოეყოფა

ცალკე მეხსიერება, გაერთიანების ყველა მონაცემს უნაწილდება საერთო მეხსიერება -

union -ის ყველაზე დიდი განზომილების მონაცემისათვის საკმარისი მეხსიერება.

union -ის ველებზე წვდომა ხდება სტრუქტურის ველებზე წვდომის მსგავსად.

მაგალითად,

#include <iostream>

using namespace std;

union Packed {

char i;

short j;

int k;

long l;

float f;

double d;

A 12 5.67 2.3e-008 A-s striqoni

Z 123 1.234 0.123457 B-s striqoni

M 987 -5.034 -12.0009 B-s striqoni

K 245 0.34 12.0009 B-s striqoni

Press any key to continue . . .

Page 73: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 73

};

int main()

{

// Packed-ს გაუნაწილდება 8 ბაიტი, რადგან მისი

/ / ყველაზე დიდი ზომის მონაცემი double ტიპისაა

cout<<"sizeof(Packed)= "

<<sizeof(Packed)<<endl;

Packed x;

x.i = 'c';

cout<< x.i <<endl;

x.d = 3.14159;

cout<< x.d <<endl;

system("pause");

return 0;

}

პროგრამა დაგვიბეჭდავს:

შემდეგი აღწერა

union number{

short x;

float y;

};

ნიშნავს ახალ number ტიპზე განაცხადს. თუ განაცხადი განვათავსეთ main –მდე, number

ტიპის ცვლადზე წვდომა შესაძლებელი იქნქბა პროგრამის ნებისმიერი ფუნქციიდან.

შემოვიღოთ number ტიპის ცვლადი:

number value;

sizeof(Packed) = 8

c

3.14159

Press any key to continue . . .

Page 74: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 74

value ცვლადზე განაცხადის დროს შეიძლება მისი ინიციალიზება, მაგრამ მხოლოდ იმ

მნიშვნელობით, რომელიც union -ის პირველივე ელემენტს შეესაბამება. ე.ი.

number value ={10};

სწორი ინიციალიზებაა, ხოლო

number value ={10.43};

შეცდომაა.

გაერთიანება შეიცავს ორ ან მეტ ელემენტს. დროის ყოველ მომენტში მიმართვა

შეიძლება მხოლოდ ერთ ელემენტზე. პროგრამისტმა თავად უნდა იზრუნოს, რომ

ელემენტზე მიმართვა მოხდეს იმის გათვალისწინებით, თუ მონაცემთა რომელი ტიპია

union -ის მეხსიერებაში მოცემულ მომენტში. თუ number ცვლადის ელემენტს მივმართავთ

როგორც short -ს იმ დროს, როდესაც მეხსიერება უკავია float -ს, მივიღებთ ლოგიკურ

შეცდომას.

საილუსტრაციო

#include <iostream>

using namespace std;

union number{

short x;

float y;

};

int main()

{

number value ={100};

cout<<"value-s mienicha short"<<endl

<<"value-s x elementis misamarTi: "

<<&value.x<<endl

<<"value-s orive elementi:"<<endl

<<"short : "<<value.x<<endl

<<"float : "<<value.y<<endl;

value.y =123.45;

cout<<"value-s mienicha float"<<endl

<<"value-s y elementis misamarTi: "

<<&value.y<<endl

Page 75: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 75

<<"value-s orive elementi:"<<endl

<<"short : "<<value.x<<endl

<<"float : "<<value.y<<endl;

system("pause");

return 0;

}

პროგრამის მუშაობის შედეგია:

შედეგი გვიჩვენებს, რომ გაერთიანების ორივე ელემენტისათვის განაწილდა ერთი და

იგივე მეხსიერება. პირველი მინიჭების შემდეგ ეს მეხსიერება უკავია short ტიპს (float -ის

მნიშვნელობა განსაზღვული არ არის), ხოლო მეორე მინიჭების შემდეგ - float -ს (არ არის

განსაზღვრული short ტიპის მნიშვნელობა).

თავი 6. ინკაპსულაცია (encapsulation)

ახალი მონაცემთა ტიპის შექმნა სტრუქტურის გამოყენებით

ინკაპსულაცია. კლასის ცნება

კლასის მონაცემებზე წვდომა (private, public). კლასის ობიექტი

კონსტრუქტორი. კონსტრუქტორის გადატვირთვა

დესტრუქტორი. კონსტრუქტორის და დესტრუქტორის

გამოძახების დრო და რიგი

ახალი მონაცემთა ტიპის შექმნა სტრუქტურის გამოყენებით

C++-ში ახალი მონაცემთა ტიპის შექმნა შესაძლებელია სტრუქტურის საფუძველზე.

შემდეგ მაგალითში მონაცემთა ტიპი Room იქმნება სტრუქტურის სახით, რომელიც შეიცავს

value-s mienicha short

value-s x elementis misamarTi: 0x22ff74

value-s orive elementi:

short : 100

float : 1.4013e-043

value-s mienicha float

value-s y elementis misamarTi: 0x22ff74

value-s orive elementi:

short : -6554

float : 123.45

Press any key to continue . . .

Page 76: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 76

ორ height და walls ელემენტს. პროგრამაში შემოიღება Room ტიპის MyRoom ცვლადი.

მის ველებს ენიჭებათ მნიშვნელობები, ხოლო ფუნქცია aboutRoom ბეჭდავს ინფორმაციას

ოთახის შესახებ.

#include <iostream>

using namespace std;

struct Room{

float height; // სიმაღლე

int walls; // კედლების რიცხვი

};

void aboutRoom(Room &);

int main(){

Room MyRoom;

cout<<"Data :\n";

aboutRoom(MyRoom);

MyRoom.height =3.5;

MyRoom.walls =4;

cout<<"Correct data :\n";

aboutRoom(MyRoom);

MyRoom.height =300;

MyRoom.walls =119;

cout<<"Incorrect data :\n";

aboutRoom(MyRoom);

system("pause");

return 0;

}

void aboutRoom(Room& R) {

cout<<"My Room has "<<R.walls

<<" walls"<<endl

<<"Height of my Room is "

<<R.height<<endl<<endl;

Page 77: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 77

}

პროგრამის შესრულების შედეგად მივიღებთ:

მაგალითში სტრუქტურა მოცემულია C -ს სტილში: მასში გაერთიანებულია მხოლოდ

მონაცემები. ფუნქცია, რომელიც ამუშავებს ამ მონაცემებს, განსაზღვრულია სტრუქტურის

გარეთ (არის გლობალური ფუნქცია). პროგრამის შედეგი გვიჩვენებს, რომ ახალი Room ტიპის

შექმნას C-ს სტილის სტრუქტურის საფუძველზე აქვს რამოდენიმე უარყოფითი მხარე:

- Room ტიპის ცვლადის საწყისი ინიციალიზება მისი შექმნის მომენტში

გათვალისწინებული არ არის, ე.ი. სტრუქტურის ელემენტებს თავიდანვე შეიძლება

მიენიჭოს ნებისმიერი მნიშვნელობები, მათ შორის, არაკორექტულიც;

- თუ MyRoom ცვლადის ინიციალიზებას მოვახდენთ პროგრამაში, მაშინაც მის

ელემენტებს შეიძლება მიენიჭოს არაკორექტური მნიშვნელობები. ეს ხდება იმიტომ,

რომ სტრუქტურის ველებზე ნებადართულია პირდაპირი წვდომა პროგრამიდან, რაც

წარმოადგენს მნიშვნელოვან შეფერხებას ახალი ტიპების შექმნისათვის სტრუქტურის

გამოყენებით;

- პროგრამაში არ არსებობს საშუალება, რომელიც აუკრძალავდა მომხმარებელს არასწორი

მონაცემების შეტანას. ანუ არ არის გათვალისწინებული ”ინტერფეისი”, რომელიც

გარანტიას იძლევა, რომ მომხმარებელი კორექტულად იყენებს მონაცემთა ტიპს და

მონაცემები არ ”ფუჭდება”.

საზოგადოდ, ვერ ხერხდება სტრუქტურის ტიპის ცვლადის შესახებ ინფორმაციის

გამოტანა მთლიანობაში, ანუ არ შეგვიძლია დავწეროთ cout << MyRoom; - სტრუქტურის

თითოეული ელემენტი უნდა გამოვიტანოთ ცალცალკე. ასევე ვერ ხერხდება სტრუქტურის

ტიპის ცვლადების შედარება მთლიანობაში: ცვლადები უნდა შევადაროთ ცალკეული

შესაბამისი ელემენტის მიხედვით და სხვა.

ზემოთ ჩამოთვლილი პრობლემების აცილება შეგვიძლია შემდეგი გზით:

- შემოვიღოთ ფუნქცია, რომელიც მოახდენს ობიექტის ინიციალიზებას მისი შექმნის

მომენტში. მართალია, შეიძლებოდა სტრუქტურის განაცხადშივე მოგვეხდინა მისი

ველების ინიციალიზება, მაგალითად, ასე

Room MyRoom ={ 3.7, 4};

Data :

My Room has 2 walls

Height of my Room is 5.60519e-044

Correct data :

My Room has 4 walls

Height of my Room is 3.5

Incorrect data :

My Room has 119 walls

Height of my Room is 300

Press any key to continue . . .

Page 78: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 78

მაგრამ ეს შეზღუდავდა პროგრამის ზოგადობას;

- შემოვიღოთ ფუნქცია, რომელშიც შემოწმდება შეტანილი მონაცემების სისწორე;

- აუკრძალოთ პროგრამას მონაცემების პირდაპირი გამოყენება (შემოვიღეთ მონაცემთა

დაცვის მექანიზმი): მონაცემებზე წვდომისათვის შემოვიღოთ სპეციალური ფუნქციები

როგორც მათი მნიშვნელობების შეცვლისათვის, ისე მათი მნიშვნელობების პროგრამაში

გამოყენებისათვის;

- განვსაზღვროთ Room ტიპის ცვლადების შედარების ფუნქცია;

- განვსაზღვროთ Room ტიპის ცვლადების << ოპერატორის გამოყენებით ბეჭდვის

საშუალება

და სხვა.

ზემოთთქმულის გათვალისწინებით, სტრუქტურაზე განაცხადს და ფუნქციების

პროტოტიპებს შეიძლება ჰქონდეს შემდეგი სახე:

#include <iostream>

using namespace std;

struct Room{

float height;

int walls;

};

void init(Room& , float=3.5, int=4);

void check_data(Room &);

void set_height(Room& , float );

void set_walls(Room& , int );

int get_height(Room& );

int get_walls(Room& );

bool compare(Room &, Room &);

void aboutRoom(Room &);

თუ Room ტიპის აღწერაში დავამატებთ ველებს, მაგალითად windows, floor, ceiling

და სხვა, ფუნქციების რიცხვი საგრძნობლად იმატებს.

ამოცანის სირთულის ზრდასთან ერთად იზრდება მონაცემთა დამუშავებისთვის

საჭირო ფუნქციათა რიცხვი, რთულდება ამ ფუნქციების ურთიერთქმედებისა და მონაცემთა

ცვლილების კონტროლი.

ამ პრობლემების გადასაწყვეტად C++ აფართოვებს სტრუქტურის ცნებას - შესაძლებელი

ხდება მონაცემების და მათი დამუშავების ფუნქციების გაერთიანება სტრუქტურის აღწერაში.

Room ტიპზე დანაცხადს C++ -ში შესაძლოა ჰქონდეს სახე:

#include <iostream>

using namespace std;

struct Room{

Page 79: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 79

float height;

int walls;

void init(float=3, int=4);

void check_data( );

void set_height( float );

void set_walls( int );

float get_height ( );

int get_walls( );

bool compare(Room & );

void aboutRoom( );

};

მონაცემების და ფუნქციების გაერთიანებით სტრუქტურის აღწერაში მიიღწევა

პროგრამული ობიექტის აღქმის გაიოლება, ფუნქციათა პროტოტიპების და კოდების

გამარტივება, სახელების კონფლიქტის აცილება, დაპროგრამების პროცესისა და პროგრამის

განახლების პროცესის მნიშვნელოვნად გაადვილება.

გულისხმობის პრინციპით სტრუქტურის ელემენტები ღია ( public ) წვდომისა არიან,

ანუ სტრუქტურის მონაცემთა გამოყენება და შეცვლა შეუძლია პროგრამის ყველა ფუნქციას.

ეს ხშირად შეცდომების წყაროს წარმოადგენს. მაგალითად, მომხმარებელმა უნებლიედ

შეიძლება მიანიჭოს სტრუქტურის მონაცემებს არაკორექტული მნიშვნელობები. გარდა ამისა,

სტრუქტურის ელემენტებს თავიდანვე (მისი შექმნის მომენტში) შეიძლება მიენიჭოს

ნებისმიერი მნიშვნელობები, მათ შორის, არაკორექტულიც.

მონაცემთა დაცვის მიზნით ავკრძალოთ პირდაპირი წვდომა მათზე - გამოვიყენოთ

მონაცემებზე წვდომის private (დახურული) მეთოდი:

#include <iostream>

using namespace std;

struct Room{

// ფუნქციებზე პირდაპირი წვდომის ნებადართვა

public:

void init(float=3, int=4);

void check_data( );

void set_height( float );

void set_walls( int );

Page 80: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 80

float get_height ( );

int get_walls( );

bool compare(Room & );

void aboutRoom( );

// მონაცემებზე პირდაპირი წვდომის აკრძალვა:

// ახლა MyRoom.walls =5; სახის ინსტრუქციები

// main-სა და სხვა ფუნქციებში აკრძალულია

private:

float height;

int walls;

};

შეიძლება ითქვას, რომ C++ -ის სტრუქტურა აკმაყოფილებს ობიექტზე ორიენტირებული

დაპროგრამების (Object Oriented Programming - OOP) ინკაპსულაციის კონცეფციას.

ახალი აბსტრაქტული ტიპების შექმნისთვის C++ -ში შემოდის ახალი ცნება - კლასი. C++

-ის სტრუქტურა და კლასი თითქმის არ განსხვავდებიან. მიუხედავათ ამისა, ახალი ტიპების

შექმნა C++ -ში მიღებულია კლასის აღწერით, ხოლო სტრუქტურა, ჩვეულებრივ, გამოიყენება

C -ს სტილში.

ინკაპსულაცია. კლასის ცნება

ობიექტზე ორიენტირებული დაპროგრამების ფუძემდებელი იდეა არის მონაცემებისა

და მათზე გათვალისწინებული მოქმედებების გაერთიანება ე.წ. ობიექტში, პროგრამა კი

წარმოადგენს ობიექტების ურთიერთქმედებას. OOP –ში რეალური სამყაროს ობიექტები

წარმოდგენილია აბსტრაქტული მონაცემთა ტიპის - კლასის - საფუძველზე.

ერთი კლასის ობიექტებს აქვთ ერთი და იგივე ატრიბუტები და ამ ატრიბუტების

დამუშავების წესები. C++ -ში ასეთი წესების როლს ასრულებენ ფუნქციები. სხვა სიტყვებით,

ობიექტების თვისებებს აღწერენ კლასის მონაცემები, ხოლო ობიექტების ქცევა

რეალიზებულია ფუნქციების სახით. მონაცემები და ფუნქციები მჭიდროდ არიან

დაკავშირებული და განიხილებიან ერთიანობაში. C++ -ის ამ კონცეფციას ეწოდება

ინკაპსულაცია.

კლასის მონაცემებს ასევე ეწოდებათ წევრი მონაცემები, ხოლო კლასის ფუნქციებს -

წევრი ფუნქციები. ობიექტზე ორიენტირებულ სხვა ენებში კლასის ფუნქციებს უწოდებენ

მეთოდებს. ტიპი, რომელიც ერთიანად აღწერს ობიექტის მონაცემებს და ფუნქციებს, C++ -ში

განისაზღვრება მომსახურე სიტყვით class.

შემდეგ მაგალითში განხილულია იგივე Room ტიპი, მაგრამ არა როგორც სტრუქტურა,

არამედ როგორც კლასი. ახსნის თვალსაზრისიდან გამომდინარე, განვიხილავთ Room ტიპის

გამარტივებული ვერსია.

Page 81: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 81

Room ტიპზე განაცხადი:

#include <iostream>

using namespace std;

class Room{

public:

Room( );

void setData(float, int );

void aboutRoom( );

private:

float height;

int walls;

};

კლასის მონაცემებზე წვდომა (private, public). კლასის ობიექტი

public (ღია, საერთო) და private (დახურული, კერძო) ჭდეებს კლასის აღწერაში

ეწოდებათ ელემენტებზე წვდომის სპეციფიკატორები. მონაცემებზე და ფუნქციებზე,

რომელთა განაცხადები მოთავსებულია public სპეციფიკატორის შემდეგ ნებადართულია

წვდომა პროგრამის ნებისმიერი ადგილიდან. private სპეციფიკატორის შემდეგ

განცხადებულ მონაცემებზე და ფუნქციებზე პროგრამიდან წვდომა აკრძალულია. ასეთ

მონაცემებთან და ფუნქციებთან მუშაობა შეუძლიათ მხოლოდ ამავე კლასის ფუნქციებს (და

ე. წ. მეგობარ ფუნქციებს). წვდომის სპეციფიკატორი აუცილებლად მთავრდება ” : ”-ით და

კლასის აღწერაში შეიძლება გამეორდეს რამდენჯერმე ნებისმიერი მიმდევრობით.

მაგალითად, სწორი იქნებოდა Room კლასზე შემდეგი განაცხადი:

#include <iostream>

using namespace std;

class Room{

private:

float height;

int walls;

public:

Room();

void setData(float, int );

void aboutRoom();

Page 82: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 82

};

თუ გავითვალისწინებთ, რომ კლასის მონაცემებიც და ფუნქციებიც გულისხმობის

პრინციპით დახურული წევრებია, მაშინ ზემოთ მოყვანილ განაცხადში შეიძლება

გამოვტოვოთ private სპეციფიკატორი:

#include <iostream>

using namespace std;

class Room{

float height;

int walls;

public:

Room();

void setData(float, int );

void aboutRoom();

};

ყურადღება მივაქციოთ იმას, რომ Room ტიპზე თავდაპირველ განაცხადში private

სპეციფიკატორის გამოტოვება არ შეიძლება, რადგან წინააღმდეგ შემთხვევაში კლასის ყველა

წევრი გახდება ღია წვდომისა.

კლასზე განაცხადის სტილი – პროგრამისტის გემოვნების საკითხია.

Room კლასის მონაცემები height და walls არიან კლასის დახურული ელემენტები

და მათზე მოქმედება შეუძლიათ მხოლოდ კლასის ფუნქციებს Room, setData და

aboutRoom.

Room კლასის public განყოფილებაში მოცემულია სამი - Room, setData და

aboutRoom - ფუნქციის პროტოტიპი. ესენი - კლასის მომსახურების ღია ინტერფეისი ან

კლასის ღია ფუნქციებია. მათ იყენებენ კლასის კლიენტები (ე.ი. პროგრამის სხვა ფუნქციები)

ამ კლასის მონაცემებზე მოქმედებისათვის. კლასის ფუქციების განსაზღვრა ხდება ე.წ.

რეალიზების ნაწილში და უდნა იყოს მოთავსებული კლასის აღწერის გარეთ. უფრო მეტიც,

რეალიზების ნაწილი შეიძლება იყოს გატანილი სხვა ფაილში.

Room ტიპის ცვლადზე განაცხადს შესაძლოა ჰქონდეს სახე

Room MyRoom;

კლასის ტიპის ცვლადს ეწოდება კლასის ეკზემპლარი ან კლასის ობიექტი. ჩვენს

შემთხვევაში MyRoom არის Room კლასის ობიექტი.

კონსტრუქტორი. კონსტრუქტორის გადატვირთვა

პროგრამას, რომელიც იყენებს Room კლასს შესაძლოა ჰქონდეს შემდეგი სახე:

#include <iostream>

Page 83: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 83

using namespace std;

// კლასის აღწერა

class Room{

public:

Room ();

void setData (float, int );

void aboutRoom();

private:

float height;

int walls;

};

// კლასის რეალიზების ნაწილი - კლასის ფუნქციათა განსაზღვრა

Room::Room() {

height =3.7;

walls =4;

}

void Room:: setData(float h, int w){

height =h;

walls =w;

}

void Room:: aboutRoom(){

cout<<"My Room has "<< walls

<<" walls "<<endl

<<"Height of my Room is "

<<height<<endl<<endl;

}

int main()

{

Room MyRoom;

cout<<"Data:\n";

Page 84: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 84

MyRoom.aboutRoom();

MyRoom.setData(2.5, 7);

cout<<endl<<"Data after change:\n";

MyRoom.aboutRoom();

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

პროგრამის შედეგიდან ჩანს, რომ მომხმარებელმა მაინც „მოახერხა“ არაკორექტური

მონაცემების გამოყენება. setData ფუნქციაში გავითვალისწინოთ მისი უპასუხისმგებლო

ქმედების აკრძალვა, მაგალითად ასე:

void Room:: setData(float h, int w){

if(h <= 2.5 || h >= 4) h =3.5;

if(w != 4) w =4;

height =h;

walls =w;

}

კლასის დახურულ height და walls მონაცემებზე კლასის გარეთ წვდომა

აკრძალულია. ანუ main -ში ან პროგრამის სხვა ფუნქციაში არ შეიძლება იყოს

MyRoom.height =2.85; -ის მსგავსი შეტყობინებები. ამბობენ, რომ კლასის რეალიზება

დამალულია კლიენტებისაგან. ასეთ მიდგომას პრინციპული მნიშვნელობა აქვს:

ინფორმაციის დამალვა ხელს უწყობს მონაცემების დაცვას და აიოლებს პროგრამის

მოდიფიცირებას (საჭიროების შემთხვევაში შეგვიძლია რომელიმე ფუნქციის კოდი

შევცვალოთ ისე, რომ პროგრამის დანარჩენ ნაწილებს არ შევეხოთ).

ფუნქცია Room(), რომლის სახელი ემთხვევა კლასის სახელს, არის კლასის

კონსტრუქტორი. კონსტრუქტორი - სპეციალური ფუნქციაა, რომელიც ახდენს ობიექტის

Data:

My Room has 4 walls

Height of my Room is 3.5

Data after change:

My Room has 7 walls

Height of my Room is 2.5

Press any key to continue . . .

Page 85: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 85

მონაცემების საწყის ინიციალიზებას და ავტომატურად გამოიძახება კლასის ყოველი

ობიექტის შექმნის მომენტში. კონსტრუქტორს არ გააჩნია დასაბრუნებელი მნიშვნელობა.

რეალიზების ნაწილში განისაზღვრება კლასის ფუნქციები. მივაქციოთ ყურადღება

ფუნქციის სათაურის სინტაქსს:

void Room:: setData(float h, int w)

აქ Room:: აღნიშნავს, რომ ფუნქცია ეკუთვნის Room კლასს (არის კლასის ხილვადობის

არეში). ფუნქციის სათაურში Room:: -ის გამოტოვება გამოიწვევს კომპილაციის შეცდომას.

განვიხილოთ კიდევ ერთი მაგალითი. შემოვიღოთ მონაცემთა ტიპი, რომელიც

შეესაბამება ცნებას Time - დრო. მისი აღწერა მოვახდინოთ კლასის საშუალებით:

class Time {

public:

Time();

Time(int, int, int);

void showTime();

private:

int hour;

int minute;

int second;

};

კლასის მონაცემები - 3 მთელი რიცხვი hour, minute და second - აღნიშნავენ საათს,

წუთსა და წამს და არიან კლასის დახურული (private) წევრები. hour, minute და second

მონაცემებს მნიშვნელობები შეიძლება მიანიჭოს მხოლოდ კლასის ფუნქციამ (ან კლასის

მეგობარმა ფუნქციამ). ამ ცვლადების საწყისი ინიციალიზება არ შეიძლება კლასის აღწერაში

და არც პროგრამის სხვა ფუნქციაში.

მონაცემთა საწყისი ინიციალიზება სრულდება Time კონსტრუქტორის საშუალებით.

კონსტრუქტორი აუცილებლად უნდა იყოს კლასის ღია (public) ფუნქცია.

კონსტრუქტორი შეიძლება იყოს უპარამეტრო:

Time:: Time(){

hour =12; minute =25; second =40;

}

ან პარამეტრებიანი:

Time:: Time(int h, int m, int s){

hour =h; minute =m; second =s;

}

Page 86: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 86

კლასის აღწერაში დასაშვებია იყოს რამოდენიმე კონსტრუქტორი, მაგალითად, ზემოთ

მოყვანილი ორივე კონსტრუქტორი. ამას ეწოდება კონსტრუქტორის გადატვირთვა.

ვთქვათ, Time კლასის ობიექტებზე განაცხადს აქვს სახე:

Time t1, t2(3,12,30);

ახალი ობიექტის შექმნისას ავტომატურად გამოიძახება კონსტრუქტორი. მისი

მეშვეობით ხდება ობიექტისათვის მეხსიერების განაწილება და ობიექტის მონაცემების

ინიციალიზება. კომპილერი იძახებს კონსტრუქტორის საჭირო ეკზემპლარს ობიექტზე

განაცხადის მიხედვით. t1 ობიექტისათვის გამოიძახება უპარამეტრო კონსტრუქტორი,

რომელიც ჩაწერს hour, minute და second ველებში მნიშვნელობებს 12, 25 და 40.

t2(3,12,30) ობიექტისათვის გამოიძახება პარამეტრებიანი კონსტრუქტორი, რომელსაც

გადაეცემა ობიექტის სახელის შემდეგ მრგვალ ფრჩხილებში მოცემული არგუმენტები და

რომელიც მიანიჭებს მათ t2 ობიექტის შესაბამის ველებს: hour -ს მიენიჭება 3, minute -ს

- 12, second -ს - 30.

უპარამეტრო და პარამეტრებიანი კონსტრუქტორები კლასის განაცხადში შეიძლება

შევცვალოთ ერთი კონსტრუქტორით, რომლის ფორმალურ პარამეტრებს მინიჭებული აქვთ

საწყისი მნიშვნელობები გაჩუმებით (ე.წ. კონსტრუქტორი გაჩუმებით):

Time(int =12, int =25, int =40)

ასეთი კონსტრუქტორის განსაზღვრა რეალიზების ნაწილში უნდა გამოიყურებოდეს

შემდეგნაირად:

Time:: Time(int h, int m, int s){

hour =h; minute =m; second =s;

}

კონსტრუქტორი გაჩუმებით უზრუნველყოფს როგორც t1, ისე t2(3,12,30)

ობიექტის სწორ ინიციალიზებას: t1 ობიექტის hour, minute და second მონაცემებს

მიენიჭებათ მნიშვნელობები გაჩუმებით (12,25 და 40 შესაბამისად), ხოლო t2 ობიექტის

შემთხვევაში მოხდება გაჩუმებით მნიშვნელობების ჩანაცვლება არგუმენტების

მნიშვნელობებით 3, 12 და 30, ე.ი. hour -ს მიენიჭება 3, minute -ს - 12, second -ს - 30.

დავამატოთ კლასში კიდევ ერთი კონსტრუქტორი Time(char* ), რომელსაც

არგუმენტის სახით გადაეცემა დროის შემცველი სტრიქონი, მაგალითად, ”4-17-50”.

კონსტრუქტორმა უნდა მიიღოს სტრიქონიდან და მიანიჭოს ობიექტის მონაცემებს 3 მთელი

რიცხვი 4, 17 და 50:

Time:: Time(char* str){

sscanf(str,"%d%*c%d%*c%d", &hour, &minute,&second);

}

აქ sscanf არის სტრიქონიდან ინფორმაციის წაკითხვის ფუნქცია. sscanf ფუნქციის

პროტოტიპი მოცემულია cstdio თავსართ ფაილში.

Page 87: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 87

ფუნქცია showTime ბეჭდავს დროს სტანდარტულ 12-საათაინ ფორმატში.

Time კლასის აღწერა მიიღებს სახეს:

class Time {

public:

Time (int=12, int=25, int=40);

Time (char *);

void showTime();

private:

int hour;

int minute;

int second;

};

კონსტრუქტორი მუშაობს მხოლოდ ერთხელ - ობიექტის შექმნის მომენტში. იმისათვის,

რომ შესაძლებელი იყოს ობიექტის მონაცემების შეცვლა პროგრამის შესრულების ნებისმიერ

მომენტში, კლასში უნდა გვქონდეს სპეციალური ფუნქციები. თუ პროგრამაში გვჭირდება

ობიექტის მონაცემების მნიშვნელობები, მათ ”ამოკითხვას” აგრეთვე ასრულებენ კლასის

სპეციალური ფუნქციები. კლასის აღწერაში დავამატოთ კლასის private მონაცემებზე

წვდომის ფუნქციები (ე.წ. ფუნქცია-უტილიტები) და განვიხილოთ საილუსტრაციო

მაგალითი.

#include <iostream>

using namespace std;

class Time{

public:

Time(int =12, int =25, int =40);

Time(char* );

int get_h();

int get_m();

int get_s();

void set_h(int);

void set_m(int);

void set_s(int);

void showTime();

Page 88: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 88

private:

int hour;

int minute;

int second;

};

// კლასის ფუნქციების განსაზღვრა

Time:: Time(int h, int m, int s){

hour =h; minute =m; second =s;

}

Time:: Time(char* str){

sscanf(str,"%d%*c%d%*c%d",

&hour,&minute,&second);

}

int Time::get_h(){

return hour;

}

int Time::get_m(){

return minute;

}

int Time::get_s(){

return second;

}

void Time::set_h(int h){

hour =h;

}

void Time::set_m(int m){

minute =m;

}

void Time::set_s(int s){

second =s;

Page 89: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 89

}

void Time:: showTime(){

cout<<((hour == 0 || hour == 12)? 12 : hour%12)

<<":"<<(minute < 10 ? "0" : "" )<<minute

<<":"<<(second < 10 ? "0" : "" )<<second

<<endl;

}

int main()

{

Time t;

Time st("4-17-50"), t1, t2(3,12,30);

cout<<"String-Time ";

st.showTime();

cout<<"t1 = "; t1.showTime();

cout<<"t2 = "; t2.showTime();

t1 =t2;

cout<<"After copy\nt1 = ";

t1.showTime();

Time t3;

t3.set_h(t1.get_h());

t3.set_m(22);

t3.set_s(st.get_s()-2);

cout<<"t3 = "; t3.showTime();

Time t_invalid(16,123,65);

cout<<"Bad Time ";

t_invalid.showTime();

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Page 90: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 90

მივაქციოთ ყურადღება შეტყობინებას t1 =t2; იმისათვის, რომ ერთი ობიექტი

მივანიჭოთ მეორეს, გამოიყენება მინიჭების = ოპერატორი. ამ დროს სრულდება ე.წ.

ბიტური მინიჭება: ერთი ობიექტის ყოველი ელემენტის კოპირება მეორე ობიექტის შესაბამის

ელემენტში (t2 ობიექტის თითოეული ბიტის კოპირება t1 ობიექტის შესაბამის ბიტში).

უნდა გვახსოვდეს, რომ კოპირება უსაფრთხოა მხოლოდ ისეთი ობიექტებისათვის,

რომლებიც არ მოითხოვენ დინამიკური მეხსიერების განაწილებას.

განხილულ Time კლასში არ სრულდება შეტანილი მონაცემების სისწორის შემოწმება.

მონაცემების დასაშვები დიაპაზონიდან გასვლის შემოწმება უნდა ხდებოდეს ორივე

კონსტრუქტორში და ყველა “set”-ტიპის ფუნქციაში.

ქვემოთ მოყვანილ პროგრამაში აგებულია და ტესტირებული Time კლასის

გამარტივებული ვარიანტი

class Time{

public:

Time(int =12, int =25, int =40);

void showTime();

private:

int hour;

int minute;

int second;

void checkTime(int& , int& , int& );

};

რომელშიც მონაცემების კორექტულობა მოწმდება კონსტრუქტორში. ფუნქცია checkTime

ამოწმებს რამდენად სწორია second, minute და hour მონაცემების სიდიდე და

საჭიროების შემთხვევაში ასწორებს მათ. ფუნქციას იყენებს მხოლოდ კლასის

კონსტრუქტორი, ამიტომაც checkTime არის კლასის private ფუნქცია.

String-Time 4:17:50

t1 = 12:25:40

t2 = 3:12:30

After copy

t1 = 3:12:30

t3 = 3:22:48

Bad Time 4:123:65

Press any key to continue . . .

Page 91: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 91

#include <iostream>

using namespace std;

class Time{

public:

Time(int =12, int =25, int =40);

void showTime();

private:

int hour;

int minute;

int second;

void checkTime(int &, int &, int &);

};

// კლასის რეალიზების ნაწილი

Time:: Time(int h, int m, int s)

{

checkTime(h, m, s);

hour =h; minute =m; second =s;

}

void Time:: showTime()

{

cout<<((hour==0||hour==12)? 12:hour%12)

<<":"<<(minute<10 ? "0" : "" )<<minute

<<":"<<(second<10 ? "0" : "" )<<second<<endl;

}

void Time:: checkTime(int& h, int& m, int& s){

int s1 =0, m1 =0;

if(s < 0 ) s =-s;

if(m < 0 ) m =-m;

if(h < 0 ) h =-h;

if(s > 60){

Page 92: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 92

s1 =s/60; s %=60;

m +=s1;

}

if(m > 60) {

m1 =m/60; m %=60;

h +=m1;

}

if(h > 12) h %=12;

}

int main()

{

Time t1, t2(3,12,30);

cout<<"t1 = ";

t1.showTime();

cout<<"t2 = ";

t2.showTime();

t1 =t2;

cout<<"After copy\nt1 = ";

t1.showTime();

Time t_invalid(-12,66,-61);

cout<<"Valid Time ";

t_invalid.showTime();

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

t1 = 12:25:40

t2 = 3:12:30

After copy

t1 = 3:12:30

Valid Time 1:07:01

Press any key to continue . . .

Page 93: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 93

დესტრუქტორი. კონსტრუქტორის და დესტრუქტორის გამოძახების დრო და რიგი

დესტრუქტორი ეწდობა კლასის ფუნქციას, რომელიც ემსახურება კლასის ობიექტების

“განადგურებას”. იგი ავტომატურად გამოიძახება მაშინ, როდესაც ობიექტი გადის

მოქმედების არიდან: დესტრუქტორი მონიშნავს ასეთი ობიექტის მიერ დაკავებულ

მეხსიერებას, როგორც თავისუფალს. დესტრუქტორის სახელი შედგება სიმბოლოსა და

კლასის სახელისგან. დესტრუქტორი არ აბრუნებს მნიშვნელობას და მას არა აქვს

პარამეტრები. მაგალითად, Time კლასის დესტრუქტორის პროტოტიპი იქნება Time();

კლასში უნდა იყოს აღწერილი მხოლოდ ერთი დესტრუქტორი - დესტრუქტორების

გადატვირთვა ნებადართული არ არის. როგორც წესი, დესტრუქტორების გამოძახება

სრულდება კონსტრუქტორების გამოძახების შებრუნებული რიგით.

გლობალური ობიექტისათვის კონსტრუქტორი გამოიძახება პროგრამის შესრულების

დასაწყისში, დესტრუქტორი კი - პროგრამის დასრულების შემდეგ.

ლოკალური auto ობიექტისათვის კონსტრუქტორი გამოიძახება მასზე განაცხადის

მომენტში, ხოლო დესტრუქტორი - იმ ბლოკის დასრულების შემდეგ, სადაც ობიექტი

აღწერილია (როდესაც ობიექტი გადის მოქმედების არიდან).

static ლოკალური ობიექტისათვის კონსტრუქტორი გამოიძახება მხოლოდ ერთხელ

ამ ობიექტის ”გაჩენის” მომენტში. შესაბამისად, ასეთი ობიექტის დესტრუქტორი მუშაობს

ერთხელ - პროგრამის დამთავრების შემდეგ.

განვიხილოთ საილუსტრაციო მაგალითი, რომელიც დაგვანახებს რა რიგით

გამოიძახება სხვადასხვა ობიექტებისათვის კონსტრუქტორი და დესტრუქტორი:

#include <iostream>

using namespace std;

class Create_Destroy{

public:

Create_Destroy(int, string); // კონსტრუქტორი

~Create_Destroy();// დესტრუქტორი

private:

int objectID; // ობიექტის ნომერი

string message; // ობიექტის აღწერა

};

// გლობალური ფუნქცია

void function();

// გლობალური ობიექტი

Page 94: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 94

Create_Destroy first(1,"(global before main)");

int main()

{

cout << "\nMAIN: BEGINS" << endl;

Create_Destroy second(2,"(local automatic in main)");

static Create_Destroy third(3,"(local static in main)");

function();

cout<<"\nMAIN: EXECUTION"<<endl;

Create_Destroy fourth(4,"(local automatic in main)");

cout<<"\nMAIN: ENDS"<< endl;

system("pause");

return 0;

}

// გლობალური ფუნქციის განსაზღვრა

void function()

{

cout<<"\nFUNCTION: BEGINS"<<endl;

Create_Destroy fifth(5,"(local automatic in function)");

static Create_Destroy sixth(6,"(local static in function)");

cout<<"\nFUNCTION: ENDS"<< endl;

}

// კლასის რეალიზება

Create_Destroy::Create_Destroy(int ID, string mes){

objectID =ID;

message =mes;

cout<<"Object "<<objectID<<" constructor "

<<message<<endl;

}

Create_Destroy::~Create_Destroy(){

cout<<"Object "<<objectID<<" destructor "

<<message<<endl;

}

Page 95: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 95

პროგრამის შესრულების შედეგია:

თემა 6. კლასები, ობიექტები და ფუნქციები

ობიექტების გადაცემა ფუნქციაში

ობიექტი, როგორც ფუნქციის დასაბრუნებელი მნიშვნელობა

მეგობარი ფუნქცია. მეგობარი კლასები.

მიმთითებელი this

კომპოზიცია: კლასის ობიექტები როგორც სხვა კლასის მონაცემები

ობიექტების გადაცემა ფუნქციაში

კლასის მარტივი წევრი ფუნქცია შეიძლება იყოს განსაზღვრული კლასზე განაცხადის

გაკეთების დროს. ამ შემთხვევაში იგი განიხილება როგორც inline ფუნქცია. კლასში

განსაზღვრულ ფუნქციასთან inline მომსახურე სიტყვის გამოყენება აუცილებელი არ არის,

თუმცა შეცდომას არ წარმოადგენს. ტრადიციულად ჩასმად ფუნქციებს წარმოადგენენ

კლასის კონსტრუქტორები, დესტრუქტორები და მოკლე და მარტივი კოდის მქონე წევრი

ფუნქციები. კლასის inline ფუნქციების გამოყენებაზე ვრცელდება იგივე შეზღუდვები, რაც

ჩვეულებრივი inline ფუნქციების გამოყენებაზე.

განვიხილოთ კლასი Date - თარიღი. Date ტიპზე განაცხადს შესაძლოა ჰქონდეს სახე:

class Date{

Object 1 constructor (global before main)

MAIN: BEGINS

Object 2 constructor (local automatic in main)

Object 3 constructor (local static in main)

FUNCTION: BEGINS

Object 5 constructor (local automatic in function)

Object 6 constructor (local static in function)

FUNCTION: ENDS

Object 5 destructor (local automatic in function)

MAIN: EXECUTION

Object 4 constructor (local automatic in main)

MAIN: ENDS

Object 4 destructor (local automatic in main)

Object 2 destructor (local automatic in main)

Object 6 destructor (local static in create)

Object 3 destructor (local static in main)

Object 1 destructor (global before main)

Press any key to continue . . .

Page 96: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 96

public:

Date(int=1, int=1, int=2010);

void pr();

~Date();

private:

int month; // 1-12

int day; // 1-31

int year; // ნებისმიერი

};

Date კლასის აღწერა შეგვიძლია ასედაც:

class Date{

public:

Date(int m=1, int d=1, int y=2010){

month =m;

day =d;

year =y;

}

void pr(){

cout<<month<<'/'<<day

<<'/'<<year<<endl;

}

~Date(){ }

private:

int month, day, year;

};

კლასის ობიექტი შეიძლება გადავცეთ ფუნქციას. ობიექტის გადაცემა ფუნქციაში ხდება

ჩვეულებრივი წესით: ფორმალური პარამეტრი ამ შემთხვევაში იქნება კლასის ტიპისა, ხოლო

ფუნქციის გამოძახების დროს არგუმენტი იქნება ამ კლასის ობიექტი. გაჩუმების პრინციპით

ობიექტები გადაეცემა ფუნქციას მნიშვნელობით, ანუ იქმნება არგუმენტის ასლი და იგი

გადაეცემა ფუნქციას. ასლის შექმნისას კონსტრუქტორი არ გამოიძახება: ამ დროს სრულდება

არგუმენტის ბიტური კოპირება ფორმალურ პარამეტრში. მაგრამ ფუნქციის მუშაობის

Page 97: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 97

დამთავრების შემდეგ ობიექტის ასლი ნადგურდება, რისთვისაც გამოიძახება

დესტრუქტორი.

განვიხილოთ საილუსტრაციო მაგალითი:

#include <iostream>

using namespace std;

class samp{

public:

samp(int n){

i=n;

cout<<"In constructor\n";

}

~samp(){

cout<<"In destructor\n";

}

int get_i(){

return i;

}

private:

int i;

};

// ფუნქცია sqr_i ითვლის ob ობიექტის მონაცემის კვადრატს

// და არის გლობალური ფუნქცია

int sqr_i(samp ob){

return ob.get_i()*ob.get_i();

}

int main()

{

{

samp a(10);

cout<<sqr_i(a)<<endl;

Page 98: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 98

In constructor // შეიქმნა a ობიექტი

100

In destructor // sqr_i ფუნქციაში განადგურდა a-ს ასლი

In destructor //განადგურდა a ობიექტი

Press any key to continue . . .

}

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

როგორც სხვა ტიპის ცვლადების შემთხვევაში, ფუნქციას შეიძლება გადავცეთ არა

ობიექტის მნიშვნელობა, არამედ მისი მისამართი. მაგალითად, პარამეტრ-მიმთითებლის

საშუალებით. ამ შემთხვევაში ფუნქციამ შეიძლება შეცვალოს ობიექტი-არგუმენტი.

მაგალითად,

#include <iostream>

using namespace std;

class samp{

public:

samp(int n){ i =n; }

void set_i(int n){ i =n; }

int get_i(){ return i; }

private:

int i;

};

void sqr_i(samp * ob) {

ob->set_i(ob->get_i()*ob->get_i());

}

int main(){

samp a(10);

cout<<"Object a in main is: "

<<a.get_i()<<endl;

sqr_i(&a);

Page 99: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 99

Object a in main is: 10

Object a in main changed: 100

Press any key to continue . . .

cout<<"Object a in main changed: "

<<a.get_i()<<endl;

system("pause");

return 0;

}

პროგრამის შედეგია:

ობიექტი შეიძლება გადავცეთ ფუნქციას მითითების გამოყენებითაც:

#include <iostream>

using namespace std;

class samp{

public:

samp(int n){ i=n; }

void set_i(int n){ i=n; }

int get_i(){ return i; }

private:

int i;

};

void sqr_i(samp & k){

k.set_i(k.get_i()*k.get_i());

}

int main()

{

samp a(7);

cout<<"Object a in main is: "

<<a.get_i()<<endl;

sqr_i(a);

cout<<"Object a in main changed: "

Page 100: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 100

Object a in main is: 7

Object a in main changed: 49

Press any key to continue . . .

<<a.get_i()<<endl;

system("pause");

return 0;

}

პროგრამა დაგვიბეჭდავს:

ობიექტი, როგორც ფუნქციის დასაბრუნებელი მნიშვნელობა

ფუნქციამ შეიძლება დააბრუნოს კლასის ობიექტი. ასეთი ფუნქციის დასაბრუნებელი

მნიშვნელობის ტიპი უნდა იყოს კლასის ტიპი. დასაბრუნებელი მნიშვნელობისათვის

ავტომატურად იქმნება დროებითი ობიექტი. იმის შემდეგ, რაც ფუნქცია დააბრუნებს

ობიექტს და დაამთავრებს მუშაობას, დროებითი ობიექტი “ნადგურდება”. მაგალითად,

#include <iostream>

using namespace std;

class MyClass{

public:

MyClass(int i){

val=i;

cout<<"In constructor\n";

}

~MyClass(){

cout<<"In destructor\n";

}

int get_val(){

return val;

}

// ფუნქცია აბრუნებს კლასის ობიექტს

MyClass BigObject(){

MyClass tmp(val*2);

return tmp;

}

Page 101: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 101

a obieqtis sheqmnamde.

In constructor // a ობიექტის შექმნა

a obieqtis sheqmnis shemdeg.

display funqciis gamodzaxebamde

10 // a ობიექტის val ველის მნიშვნელობა

In destructor // display ფუნქციის ფორმალური პარამეტრის „განადგურება“

display funqciidan dabrunebis shemdeg.

BigObject funqciis gamodzaxebamde

private:

int val;

};

// ფუნქცია display არის გლობალური ფუნქცია.

// მას პარამეტრად გადაეცემა კლასის ობიექტი

void display(MyClass ob){

cout<<ob.get_val()<<endl;

}

int main(){

cout<<"a obieqtis sheqmnamde.\n";

MyClass a(10);

cout<<"a obieqtis sheqmnis shemdeg.\n\n";

cout<<"display funqciis gamodzaxebamde\n";

display(a);

cout<<"display funqciidan dabrunebis shemdeg.\n\n";

cout<<"BigObject funqciis gamodzaxebamde\n";

a =a.BigObject();

cout<< "BigObject funqciidan dabrunebis shemdeg.\n\n";

cout<<"display funqciis meore gamodzaxebamde\n";

display(a);

cout<<"display funqciidan dabrunebis shemdeg.\n\n";

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Page 102: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 102

დასაბრუნებელი მნიშვნელობის - ობიექტის - შემთხვევაში ფრთხილად უნდა ვიყოთ:

თუ კლასის ობიექტს უნაწილდება დინამიკური მეხსიერება, ხოლო კლასში

გათვალისწინებულია დესტრუქტორი, მაშინ დროებითი ობიექტის ხილვადობის არიდან

გასვლისას (ფუნქციის მუშაობის დამთავრებისას) იმუშავებს დესტრუქტორი, რომელიც

გაათავისუფლებს დროებითი ობიექტის მიერ დაკავებულ დინამიკურ მეხსიერებას. ამან კი

რიგ შემთხვევაში შეიძლება გამოიწვიოს სერიოზული შეცდომა. ამ ეფექტს განვიხილავთ

მოგვიანებით, როდესაც გავეცნობით დინამიკური მეხსიერების განაწილების მექანიზმს.

მეგობარი ფუნქცია. მეგობარი კლასები

საზოგადოდ, კლასის კერძო (private) წევრებზე წვდომა აქვთ მხოლოდ კლასის

ფუნქციებს. მაგრამ ზოგ შემთხვევაში კლასის private წევრებზე წვდომა უნდა შეეძლოს

ფუნქციას, რომელიც არ არის კლასის წევრი ფუნქცია. ამ მიზნით C++-ში შემოღებულია ე.წ.

მეგობარი ფუნქციები (friend functions). ანუ მეგობარი ფუნქცია არ არის კლასის

ფუნქცია, მაგრამ მას შეუძლია მიმართვა კლასის დახურულ ელემენტებზე.

მეგობარი ფუნქცია განისაზღვრება კლასის გარეთ როგორც ჩვეულებრივი გლობალური

ფუნქცია. კლასის აღწერაში კი უნდა იყოს მოცემული მეგობარი ფუნქციის პროტოტიპი,

რომელიც იწყება მომსახურე სიტყვა friend-ით.

ქვემოთ მოგვყავს მარტივი მაგალითი, რომელიც გვიჩვენებს მეგობარი ფუნქციის

გამოყენებას. ფუნქცია Denom ადგენს, აქვს თუ არა კლასის a და b private მონაცემებს

საერთო მამრავლი.

#include<iostream>

using namespace std;

class MyClass{

// კლასის მეგობარი ფუნქციის პროტოტიპი

friend int Denom(MyClass x);

public:

MyClass(int i=1, int j=1){

a =i; b =j;

}

Page 103: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 103

private:

int a, b;

};

// მეგობარი ფუნქციის განსაზღვრა.

// ფუნქციას გადაეცემა MyClass-ის x ობიექტი

int Denom(MyClass x){

/* რადგანაც Denom - MyClass-ის მეგობარი ფუნქციაა,

მას აქვს წვდომა კლასის a და b კერძო მონაცემებზე */

int max =x.a > x.b ? x.a : x.b;

for(int i=2; i<=max/2; i++)

if(x.a%i==0 && x.b%i==0)

return i;

return 1;

}

int main(){

MyClass n(27,111);

int pasuxi;

pasuxi =Denom(n);

cout<<"SaerTo mamravli udris "<<pasuxi<<"\n";

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

მივაქციოთ ყურადღება, რომ მეგობარი ფუნქცია გამოიძახება ჩვეულებრივი წესით და

არა ობიექტის სახელისა და ”წერტილის” ოპერატორის გამოყენებით.

შენიშვნა: განხილულ მაგალითში უფრო ბუნებრივი იქნებოდა Denom ფუნქცია

შემოგვეღო როგორც კლასის ფუნქცია და არა როგორც კლასის მეგობარი ფუნქცია.

SaerTo mamravli udris 3

Press any key to continue . . .

Page 104: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 104

ახლა განვიხილოთ საილუსტრაციო მაგალითი, რომელიც დაგვანახებს მეგობარი

ფუნქციების შემოღების ერთ-ერთ მიზეზს: ჩვენ დაგვჭირდება ფუნქცია, რომელსაც უნდა

ჰქონდეს წვდომა ორი სხვადასხვა კლასის private წევრებზე.

ვთქვათ, მოცემულია ორი კლასი Cube და Cylinder. ორივე გეომეტრიულ ფიგურას

აქვს მახასიათებელი - ფერი. იმისათვის, რომ შევძლოთ კუბისა და ცილინდრის ფერის

შედარება, შემოვიღოთ ფუნქცია-”მეგობარი”, რომელსაც ექნება წვდომა ორივე

გეომეტრიული ფიგურის შესაბამის მონაცემზე. ფუნქცია დააბრუნებს მნიშვნელობას

ჭეშმარიტი, თუ ფერი ემთხვევა და მნიშვნელობას მცდარი წინააღმდეგ შემთხვევაში.

#include<iostream>

using namespace std;

enum colors { red, green, yellow };

class Cylinder; // წინასწარი განაცხადი კლასზე

class Cube{

friend bool sameColor(Cube x, Cylinder y);

public:

Cube (colors c=green) { feri =c; }

private:

colors feri;

};

class Cylinder{

friend bool sameColor(Cube x, Cylinder y);

public:

Cylinder (colors c=red) { feri =c; }

private:

colors feri;

};

// მეგომარი ფუნქციის განსაზღვრა

bool sameColor(Cube x, Cylinder y){

if (x.feri == y.feri)

return true;

return false;

}

int main()

Page 105: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 105

{

Cube cub_1(yellow), cub_2(red);

Cylinder cy_2;

if(sameColor(cub_1, cy_2))

cout<<"Obieqtebis feri emtxveva\n";

else

cout<<"Obieqtebi sxvadasxva ferisaa\n";

if(sameColor(cub_2, cy_2))

cout<<"Obieqtebis feri emtxveva\n";

else

cout<<"Obieqtebi sxvadasxva ferisaa\n";

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

ფუნქცია sameColor წარმოადგენს ორივე კლასის მეგობარ ფუნქციას. ამ პროგრამაში

ნაჩვენებია C++-ის ერთი მნიშვნელოვანი ელემენტი - წინასწარი განაცხადი (forward

declaration), რომელსაც აგრეთვე უწოდებენ წინ მითითებას (forward reference).

რადგანაც sameColor ფუნქციას გადაეცემა ორივე კლასის ობიექტი, მის პროტოტიპში

მოცემულია ორივე კლასის დასახელება. შეუძლებელია ამ ორივე კლასის აღწერა წინ

უსწრებდეს sameColor ფუნქციის პროტოტიპის გამოჩენას. ამიტომ აუცილებელია სხვა

საშუალება, რომ შევატყობინოთ კომპილერს ერთ-ერთი კლასის დასახელება ამ კლასის

აღწერამდე. ამ საშუალებას წარმოადგენს წინასწარი განაცხადი class Cylinder;

ფუნქცია შეიძლება იყოს ერთი კლასის წევრი ფუნქცია და ამავე დროს მეორე კლასის

friend-ფუნქცია. იმავე მაგალითისთვის განვმარტოთ ფუნქცია sameColor, როგორც Cube

კლასის ფუნქცია და Cylinder კლასის ”მეგობარი”.

#include<iostream>

using namespace std;

enum colors { red, green, yellow };

Obieqtebi sxvadasxva ferisaa

Obieqtebis feri emtxveva

Press any key to continue . . .

Page 106: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 106

class Cylinder; // წინასწარი განაცხადი კლასზე

class Cube{

public:

Cube (colors c=green) { feri =c; }

// ფუნქცია არის Cube კლასის წევრი

bool sameColor(Cylinder y);

private:

colors feri;

};

class Cylinder{

public:

Cylinder (colors c=red) { feri=c; }

// ფუნქცია Cube::sameColor არის Cylinder კლასის მეგობარი

friend bool Cube:: sameColor(Cylinder y);

private:

colors feri;

};

// ფუნქციის განსაზღვრა

bool Cube :: sameColor(Cylinder y){

if (feri == y.feri)

return true;

return false;

}

int main()

{

Cube cub_1(yellow), cub_2(red);

Cylinder cy_2;

if(cub_1.sameColor(cy_2))

cout<<"Obieqtebis feri emtxveva\n";

Page 107: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 107

else cout<<"Obieqtebi sxvadasxva ferisaa\n";

if(cub_2.sameColor(cy_2))

cout<<"Obieqtebis feri emtxveva\n";

else cout<<"Obieqtebi sxvadasxva ferisaa\n";

system("pause");

return 0;

}

პროგრამის შესრულების შედეგი:

ერთი კლასი შესაძლოა იყოს მეორე კლასის მეგობარი. ეს ხდება მაშინ, როდესაც ერთი

კლასის ყველა ფუნქცია წარმოადგენს მეორე კლასის მეგობარ ფუნქციას. მაგალითად,

class One{

. . .

int f1();

float f2();

};

class Two{

. . .

friend int One:: f1();

friend float One:: f2();

};

ასეთი შემთხვევებისათვის არსებობს ჩაწერის მოკლე ფორმა:

class Two {

. . .

friend class One;

};

აქ კლასი One არის Two კლასის ”მეგობარი”. friend class One; განაცხადი ხდის One

კლასის ყველა ფუნქციას Two კლასის მეგობარ ფუნქციად.

Obieqtebi sxvadasxva ferisaa

Obieqtebis feri emtxveva

Press any key to continue . . .

Page 108: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 108

this მიმთითებელი

C++ -ში შემოღებულია სპეციალური მიმთითებელი this. this არის მიმთითებელი,

რომელიც ავტომატურად გადაეცემა კლასის ნებისმიერ ფუნქციას და მიუთითებს იმ

ობიექტს, რომლისთვისაც ეს ფუნქცია გამოიძახება. მაგალითად, თუ ob - ობიექტის სახელია,

ხოლო შესაბამის კლასში არსებობს ფუნქცია f(), მაშინ ინსტრუქცია ob.f() ნიშნავს ob

ობიექტისათვის f() ფუნქციის გამოძახებას. ამ შემთხვევაში f() ფუნქციას ავტომატურად

გადაეცემა მიმთითებელი ob ობიექტზე. ეს არის this მიმთითებელი. შეიძლება ითქვას, რომ

this - არაცხადი პარამეტრია, რომელსაც ღებულობს ყოველი კლასის ფუნქცია.

მიმთითებელი this შეიძლება ცხადადაც გამოვიყენოთ კლასის ნებისმიერ ფუნქციაში

გამომძახ ობიექტძე მითითებისათვის.

განვიხილოთ საილუსტრაციო მაგალითი:

#include<iostream>

using namespace std;

class Test{

public:

Test(int =3);

void print();

private:

int t;

};

// კლასის რეალიზების ნაწილი

Test::Test(int k){ t =k; }

void Test::print(){

cout<<"t udris "<<t<<endl

<<"this->t aris "<<this->t<<endl

<<"(*this).t aris "<<(*this).t<<endl;

}

int main()

{

Test ob, ob1(15);

ob.print();

ob1.print();

Page 109: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 109

system("pause");

return 0;

}

პროგრამა გამოიტანს:

კომპოზიცია: კლასის ობიექტები როგორც სხვა კლასის მონაცემები

კლასის მონაცემი შეიძლება იყოს სხვა კლასის ობიექტი.

განვიხილოთ მაგალითი, რომელშიც აიგება კლასი Student. ეს კლასი შეიცავს

private მონაცემებს firstName (სახელი), lastName (გვარი) და birthDate (დაბადების

თარიღი). მონაცემი birthDate არის Date კლასის ობიექტი. Date კლასი, თავის მხრივ,

შეიცავს private მონაცემებს month, day და year. main()-ში იქმნება Student კლასის

ობიექტი გარკვეული საწყისი მნიშვნელობებით. ვგულისხმობთ, რომ საწყისი

მნიშვნელობები კორექტულია.

#include <iostream>

using namespace std;

class Date{

public:

Date(int=1, int=1, int=2010);

void print();

private:

int month, day, year;

};

class Student{

public:

Student(string, string, int, int, int);

void print();

private:

t udris 3

this->t aris 3

(*this).t aris 3

t udris 15

this->t aris 15

(*this).t aris 15

Press any key to continue . . .

Page 110: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 110

string firstName;

string lastName;

Date birthDate;

};

// რეალიზების ნაწილი

void Date::print(){

cout<<month<<'/'<<day<<'/'<<year<<endl;

}

Date::Date(int m, int d, int y){

month =m; day =d; year =y;

}

Student::Student(string fn, string ln, int fm, int fd, int fy)

: birthDate(fm,fd,fy){

firstName =fn; lastName =ln;

}

void Student::print(){

cout<<firstName<<" "

<<lastName<<endl<<"Daibada:" ;

birthDate.print();

cout<<endl;

}

int main(){

cout<<"Student klasis objectis sheqmna:"<<endl<<endl;

Student S("GoderZi","Maxarashvili",2,25,1900) ;

cout<<"Student Object\n"<<endl;

S.print();

cout<<"\nDate klasis obieqtebis sheqmna:\n\n";

Date d1;

d1.print();

system("pause");

Page 111: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 111

Student klasis objectis sheqmna:

Student Object

GoderZi Maxarashvili

Daibada:2/25/1900

Date klasis obieqtebis sheqmna:

1/1/2010

Press any key to continue . . .

return 0;

}

პროგრამის შესრულების შედეგია:

მივაქციოთ ყურადღება Student კლასის კონსტრუქტორის სინტაქსს:

Student::Student(string fn, string ln, int fm, int fd, int fy):

birthDate(fm,fd,fy)

კონსტრუქტორი ღებულობს 5 არგუმენტს

(string fn, string ln, int fm, int fd, int fy)

”:” გამოყოფს პარამეტრების სიას და birthDate ელემენტის ინიციალიზატორს.

ინიციალიზატორი birthDate(fm,fd,fy) გვიჩვენებს, რომ Student კლასის

კონსტრუქტორის არგუმენტები fm, fd და fy გადაეცემა Date კლასის კონსტრუქტორს

birthDate ობიექტის შექმნისათვის.

Student კლასის print() ფუნქციაში month, day და year მონაცემებს ბეჭდავს

Date კლასის print() ფუნქცია, რომელსაც იძახებს birthDate ობიექტი:

birthDate.print();

ქვემოთ მოცემულია იგივე მაგალითის სხვა ვარიანტი, რომელშიც მოწმდება საწყისი

მონაცემების კორექტულობა. შევთანხმდეთ, რომ month და day ელემენტების

არაკორექტული მნიშვნელობების შემთხვევაში, ამ ელემენტებს მივანიჭებთ 1 –ს. ფუნქცია

checkDay ამოწმებს რამდენად შეესაბამება day (დღის) მნიშვნელობა month (თვისა) და

year (წელიწადის) მნიშვნელობებს. ფუნქციას იყენებს მხოლოდ კლასის კონსტრუქტორი,

ამიტომაც checkDay არის კლასის private ფუნქცია.

#include <iostream>

using namespace std;

class Date{

public:

Page 112: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 112

Date(int=1, int=1, int=2010);

void print();

private:

int month, day, year;

int checkDay(int );

};

class Student{

public:

Student(string, string, int, int, int);

void print();

private:

string firstName;

string lastName;

Date birthDate;

};

// კლასის რეალიზების ნაწილი

void Date::print(){

cout<<month<<'/'<<day<<'/'<<year<<endl;

}

Date::Date(int m, int d, int y){

if(m>0 && m<=12) month =m;

else {

month =1;

cout<<"Month "<<m

<<" invalid. Set to Month 1\n";

}

year =y;

day =checkDay(d);

cout<<"Date Object Constructor for date " ;

Page 113: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 113

print();

cout<<endl;

}

int Date::checkDay(int testDay){

static int

Mdays[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};

if(month != 2){

if(testDay > 0 && testDay <= Mdays[month])

return testDay;

}

else {

int k=(year%400==0||(year%4==0 && year%100!=0))? 29:28;

if(testDay > 0 && testDay <= k)

return testDay;

}

cout<<"Day "<<testDay<<" invalid. Set to Day 1\n";

return 1;

}

Student::Student(string fn, string ln, int fm, int fd, int fy)

: birthDate(fm,fd,fy){

firstName =fn;

lastName =ln;

cout<<"Student Object Constructor for firstName and

lastName:\n"

<<firstName<<" , "<<lastName<<endl<<endl;

}

void Student::print(){

cout<<firstName<<" "

<<lastName<<endl<<"Daibada:" ;

birthDate.print();

Page 114: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 114

Student klasis objectis sheqmna:

Date Object Constructor for date 2/25/1900

Student Object Constructor for firstName and

lastName:

GoderZi , Maxarashvili

Student Object

GoderZi Maxarashvili

Daibada:2/25/1900

Date klasis obieqtebis sheqmna:

Date Object Constructor for date 1/1/2010

Date Object Constructor for date 4/16/2010

Invalid Date: 16/35/2009

Month 16 invalid. Set to Month 1

Day 35 invalid. Set to Day 1

Date Object Constructor for date 1/1/2009

Press any key to continue . . .

cout<<endl;

}

int main()

{

cout<<"Student klasis objectis sheqmna:"<<endl<<endl;

Student S("GoderZi","Maxarashvili",2, 25, 1900) ;

cout<<"Student Object\n"<<endl;

S.print();

cout<<"\nDate klasis obieqtebis sheqmna:\n\n";

Date d1, d2(4,16,2010);

cout<<"Invalid Date: 16/35/2009\n\n";

Date d_invalid(16,35,2009);

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Page 115: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 115

გაარჩიეთ პროგრამა და ჩაატარეთ მისი ტესტირება სხვადასხვა (როგორც კორექტული,

ისე არაკორექტული) მონაცემებისთვის.

თემა 7. ოპერატორების გადატვირთვა

რას ნიშნავს ოპერატორების გადატვირთვა?

ფუნქცია-ოპერატორი როგორც კლასის ფუნქცია

და როგორც მეგობარი ფუნქცია

ნაკადიდან ამოღებისა და ნაკადში ჩასმის ოპერატორების გადატვირთვა

ერთადგილიანი და ორადგილიანი ოპერატორების გადატვირთვა

რას ნიშნავს ოპერატორების გადატვირთვა

C++ -პროგრამაში მონაცემები წარმოდგენილია ცვლადების სახით. ცვლადი აღიწერება

სახელით და ტიპით. ტიპი განმარტავს ცვლადზე ჩასატარებელ ოპერაციათა სიმრავლეს.

პროგრამისტი იყენებს ენის მონაცემთა საბაზისო ტიპებს (int, float, double, char და

ა.შ.). მონაცემთა საბაზისო ტიპებისათვის C++ -ში შემოღებულია ოპერატორების ფართო

სპექტრი. პროგრამისტს აგრეთვე შეუძლია ახალი ტიპების განსაზღვრა და გამოყენება.

მართალია, მომხმარებლის მიერ განსაზღვრული ტიპებისათვის ახალი ოპერატორების

შემოღება C++ -ში დაშვებული არ არის, მაგრამ შესაძლებელია ენის უკვე არსებული

ოპერატორების გადატვირთვა (operator overloading). ოპერატორის გადატვირთვა

ნიშნავს, რომ მომხმარებლის მიერ განსაზღვრული ტიპის ობიექტთან მიმართებაში ეს

ოპერატორი იძენს ახალ შესაბამის აზრს.

C++ -ის ზოგიერთი ოპერატორის გადატვირთვა იკრძალება. ესენია 5 ოპერატორი:

. , . * , : : , ? : და sizeof.

დავუშვათ, აღწერილი გვაქვს კლასი Vector, რომელიც შეიცავს მთელი ტიპის x, y

და z კომპონენტს. ორი ვექტორის (კლასის ობიექტის) შეკრების add ფუნქცია შეიძლება ასე

გამოიყურებოდეს:

Vector Vector::add (Vector& m){

Vector temp;

temp.x =x + m.x;

temp.y =y + m.y;

Page 116: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 116

temp.z =z + m.z;

return temp;

}

ფუნქცია add განსაზღვრულია როგორც კლასის ფუნქცია.

ვთქვათ, პროგრმაში გვაქვს განაცხადი Vector a,b,c; და შეტყობინება

c = a.add(b); მაშინ c ვექტორის ყოველი კომპონენტი გახდება a და b ვექტორების

შესაბამისი კომპონენტების ჯამის ტოლი.

სასურველია იგივე მოქმედებისათვის შეგვეძლოს c = a + b; ჩანაწერის გამოყენება.

ამისათვის საკმარისია განვსაზღვროთ ფუნქცია-ოპერატორი, რომლის სახელი იქნება

operator +, ხოლო ტანი – იგივე, რაც add ფუნქციისა. კლასის ასეთი ფუნქცია-

ოპერატორის განსაზღვრას ექნება სახე:

Vector Vector:: operator + (Vector& m) {

Vector temp;

temp.x =x + m.x;

temp.y =y + m.y;

temp.z =z + m.z;

return temp;

}

პროგრამაში a+b -ს შესრულებისთვის კომპილერი მოახდენს operator + ფუნქციის

გამოძახების გენერირებას შემდეგი სახით: a.operator +(b).

ამრიგად, ოპერატორის გადატვირთვისას ფუნქცია განისაზღვრება ჩვეულებრივი წესით

(სათაურისა და ტანის ჩვენებით) იმ განსხვავებით, რომ ფუნქციის სახელი შედგება

მომსახურე operator სიტყვისაგან, რომლის შემდეგ ჩაიწერება გადასატვირთი ოპერატორის

ნიშანი.

ფუნქცია-ოპერატორის ფორმატი შემდეგია:

< ტიპი> <კლასის სახელი> :: operator # (<პარამეტრების სია>)

{

მოქმედებები კლასის ობიექტზე

}

აქ გადასატვირთი ოპერატორი პირობითად აღნიშნულია ”#” სიმბოლოთი. <ტიპი>

წარმოადგენს ფუნქცია-ოპერატორის დასაბრუნებელ ტიპს, რომელიც, უმეტეს შემთხვევაში,

ემთხვევა კლასის სახელს (არის კლასის ტიპისა), თუმცა შეიძლება იყოს ნებისმიერი ტიპისა.

რაც შეეხება პარამეტრების სიას, იგი დამოკიდებულია რამოდენიმე ფაქტორზე.

Page 117: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 117

ფუნქცია-ოპერატორი როგორც კლასის ფუნქცია და როგორც მეგობარი ფუნქცია

ფუნქცია-ოპერატორი შეიძლება იყოს კლასის ფუნქცია ან კლასის მეგობარი ფუნქცია.

1. თუ კლასის ფუნქცია-ოპერატორის საშუალებით ვახდენთ:

ერთოპერანდიანი (უნარული) ოპერატორის გადატვირთვას, მაშინ პარამეტრების სია

რჩება ცარიელი: ასეთ ფუნქციას ერთადერთ ოპერანდზე წვდომისათვის არაცხადად

გადაეცემა this მიმთითებელი.

მაგალითად, გადავტვირთოთ Vector კლასისთვის ოპერატორი ++ (პრეფიქსული

ინკრემენტი ):

Vector & Vector :: operator ++( )

{

x++; y++; z++;

return *this;

}

მოყვანილი განსაზღვრის თანახმად ამ ფუნქცია-ოპერატორის შინაარსი შემდეგია:

ვექტორის თითოეული კომპონენტი იზრდება ერთი ერთეულით. მივაქციოთ ყურადღება

operator ++ ფუნქციის დასაბრუნებელ მნიშვნელობას - *this. რადგანაც operator ++

ფუნქციას იძახებს კლასის ობიექტი, ფუნქციამ უნდა დააბრუნოს ეს ობიექტი შესაბამისი

შეცვლის შემდეგ, ანუ *this (this-ის მისამართზე განთავსებული ობიექტი). ამით

აიხსნება დასაბრუნებელი მნიშვნელობის ტიპიც - Vector &;

თუ პროგრამაში გვაქვს განაცხადი Vector k; და შეტყობინება ++k; მისი

შესრულებისთვის კომპილერი ავტომატურად გამოძახებს operator ++ ფუნქციას

შემდეგი სახით: k.operator ++(). ანუ k ვექტორის ყველა კომპონენტი გაიზრდება

ერთი ერთეულით.

ოროპერანდიანი (ბინარული) ოპერატორის გადატვირთვას, მაშინ ფუნქცია-

ოპერატორი თავის პირველ ოპერანდზე (რომელიც მას იძახებს) წვდომისათვის

არაცხადად იყენებს this მიმთითებელს, ხოლო მეორე ოპერანდს ღებულობს

არგუმენტის სახით. ე.ი. ასეთი ფუნქცია-ოპერატორის პარამეტრების სიაში გვექნება ერთი

პარამეტრი. მსგავსი ფუნქცია-ოპერატორის მაგალითია ადრე განხილული Vector

კლასის operator + ფუნქცია. განვიხილოთ კიდევ ერთი ფუნქცია–ოპერატორი,

რომელიც შეადარებს Vector კლასის ორ ობიექტს (ორ სამგანზომილებიან ვექტორს)

ტოლობაზე:

bool Vector::operator == (Vector m)

{

if(x == m.x && y == m.y && z == m.z)

return true;

return false;

}

Page 118: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 118

ვთქვათ, პროგრმაში გვაქვს განაცხადი Vector a,b; და შეტყობინება

cout<<(a == b)<<endl; მაშინ, თუ a და b ვექტორების ყველა კომპონენტი ტოლია,

დაიბეჭდება 1, წინააღმდეგ შემთხვევაში დაიბეჭდება 0.

2. თუ კლასის მეგობარი ფუნქცია-ოპერატორის საშუალებით ვახდენთ:

ერთოპერანდიანი (უნარული) ოპერატორის გადატვირთვას, კლასის მეგობარ

ფუნქცია-ოპერატორს მიმთითებელი this არ გადაეცემა. მაშინ ერთადერთ არგუმენტს

ფუნქცია ღებულობს პარამეტრის სახით, ხოლო პარამეტრების სია შედგება ერთი

პარამეტრისაგან;

ოროპერანდიანი (ბინარული) ოპერატორის გადატვირთვას, მაშინ ორივე ოპერანდს

ფუნქცია ღებულობს არგუმენტების სახით. ანუ პარამეტრების სიაში იქნება ორი

პარამეტრი.

ზოგი ოპერატორის გადატვირთვა ხდება მხოლოდ კლასის ფუნქცია-ოპერატორის

საშუალებით, მაგალითად, = (მინიჭების ოპერატორის), ( ), [ ], ->, ++ და სხვა.

დანარჩენი ოპერატორების გადატვირთვისას შეიძლება გამოვიყენოთ როგორც კლასის

ფუნქცია–ოპერატორი, ისე მეგობარი ფუნქცია-ოპერატორი. როგორი ფუნქცია-ოპერატორის -

კლასის ფუნქციისა თუ კლასის მეგობარი ფუნქციის - მეშვეობით უმჯობესია მოვახდინოთ

გადატვირთვის რეალიზება? ამისათვის არსებობს მარტივი წესი.

როდესაც ოპერატორის მარცხენა (ან ერთადერთი) ოპერანდი არის მოცემული კლასის

ობიექტი, ფუნქცია-ოპერატორი უნდა იყოს ამ კლასის ფუნქცია.

თუ ფუნქცია-ოპერატორის მარცხენა ოპერანდი არის სხვა კლასის ობიექტი ან საბაზისო

ტიპის ცვლადი (ანუ არ არის მოცემული კლასის ობიექტი), ფუნქცია-ოპერატორი უნდა იყოს

რეალიზებული როგორც მოცემული კლასის მეგობარი ფუნქცია.

ნაკადიდან ამოღებისა და ნაკადში ჩასმის ოპერატორების გადატვირთვა

ახლა განვიხილოთ ისეთი ორი ოპერატორის გადატვირთვა, რომლისთვისაც ფუნქცია-

ოპერატორი აუცილებლად უნდა იყოს კლასის მეგობარი ფუნქცია. ესენია ოპერატორები >>

და <<.

ნაკადში ჩასმის >> და ნაკადიდან ამოღების << ოპერატორები C++ -ის სტანდარტული

ტიპებისათვის გადატვირთულია შეტანა-გამოტანის კლასების ბიბლიოთეკებში, რომლებიც

თან ერთვის კომპილერს. >> და << ოპერატორების გადატვირთვა შეიძლება აგრეთვე

მომხმარებლის მიერ განსაზღვრული ტიპებისათვის ისე, რომ შეტანა-გამოტანა მოხდეს იმავე

სტილში, რაც სტანდარტული ტიპებისა. მაგალითად, Vector კლასის a ობიექტის შეტანა

რომ შეიძლებოდეს cin>>a; ხოლო გამოტანა cout<<a; შეტყობინებების შესრულებით.

შეტანის ოპერატორის ფორმატს აქვს სახე:

istream & operator >>(istream & stream, class_type & obj)

{

ფუნქცია-ოპერატორში ჩასატარებელი ოპერაციები

Page 119: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 119

return stream;

}

მაგალითად, Vector კლასისათვის operator >> გამოიყურება ასე:

istream & operator >>(istream & in, Vector & f)

{

in >> f.x >> f.y >> f.z;

return in;

}

აქ istream - შეტანის სტანდარტული კლასის დასახელებაა, ფუნქვიას operator >>

არგუმენტების სახით გადაეცემა istream-ზე მითითება და Vector კლასზე მითითება.

ფუნქცია აბრუნებს მითითებას istream-ზე. როდესაც კომპილერი ”ხედავს” main()-ში

შეტყობინებას cin>>a; იგი ახდენს operator >>(cin, a) ფუნქციის გენერირებას.

ფუნქცია operator >> აბრუნებს istream ტიპის მითითებას in (ანუ არგუმენტის

გათვალისწინებით, cin-ს), რაც >> ოპერატორის მრავალჯერადი გამოყენების საშუალებას

იძლევა. მაგალითად, Vector ტიპის a და c ობიექტების შეტანა შეიძლება ერთი

cin>>a>>b; შეტყობინების გამოყენებით.

გამოტანის ოპერატორის ფორმატი შემდეგია:

ostream & operator <<(ostream & stream, class_type & obj)

{

ფუნქცია-ოპერატორში ჩასატარებელი ოპერაციები

return stream;

}

Vector კლასის ფუნქცია operator << გამოიყურება ასე:

ostream & operator <<(ostream & out,Vector & f)

{

out<<f.x<<" "<<f.y<<" "<<f.z<<endl;

return out;

}

სადაც ostream - გამოტანის სტანდარტული კლასის დასახელებაა. ფუნქციიდან მითითების

დაბრუნება უზრუნველყოფს << ოპერატორის მრავალჯერად გამოყენებას. გამოსახულებას

cout<<a კომპილატორი ცვლის operator <<(cout, a) ფუნქციით. არგუმენტები კი

გადაეცემა operator << ფუნქციას მითითებით (რათა ავიცილოთ არგუმენტების კოპირება

ფუნქციის პარამეტრებში).

Page 120: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 120

განვიხილოთ მაგალითი, რომელშიც აიგება კლასი Student. კლასის მონაცემებია –

სტუდენტის გვარი და მისი შეფასებები 5 საგანში; კლასის ფუნქციებია – უპარამეტრო

კონსტრუქტორი, საშუალო შეფასების გამოთვლის ფუნქცია. კლასის ობიექტისთვის

გადატვირთულია შეტანისა >> და გამოტანის << ოპერატორები. პროგრამაში შეგვაქვს

ინფორმაცია 3 სტუდენტის შესახებ, ვითვლით მათ საშუალო შეფასებებს და ვბეჭდავთ

სტუდენტების სიას საშუალო შეფასებების კლების მიხედვით.

#include<iostream>

using namespace std;

class Student{

string gvari;

int nish[5];

public:

Student();

float sash_nish();

friend ostream & operator<<(ostream &, Student &);

friend istream & operator>>(istream &, Student &);

};

// რეალიზების ნაწილი

Student::Student(){

gvari="Arevadze";

for(int i=0; i<5; i++)nish[i]=100;

}

ostream & operator<<(ostream & out, Student & s){

out<<s.gvari<<" nishnebiT : ";

for(int i=0; i<5; i++)

out<<s.nish[i]<<" ";

out<<" da sashualo nishniT "

<<s.sash_nish()<<endl;

return out;

}

istream& operator>>(istream& in, Student& s){

cout<<"Studentis gvari : ";

Page 121: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 121

in>>s.gvari;

cout<<"Nishnebi 5 saganSi : ";

for(int i=0; i<5; i++)

in>>s.nish[i];

return in;

}

float Student::sash_nish(){

float s =0;

for(int i=0; i<5; i++) s +=nish[i];

return s/5;

}

int main()

{

Student x, y, z;

cin >> x >> y >> z;

Student* d, *s, *p, *t;

d =&x; s =&y; p =&z;

if(d->sash_nish() < s->sash_nish()){

t =d; d =s; s =t;

}

if(s->sash_nish() < p->sash_nish()){

t =s; s =p; p =t;

}

if(d->sash_nish() < s->sash_nish()){

t =d; d =s; s =t;

}

cout<<"\nStudentebis sia nishnebis klebiT :\n\n";

cout<<*d;

cout<<*s;

cout<<*p;

system("pause");

Page 122: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 122

return 0;

}

პროგრამის შესრულების შედეგი:

ერთადგილიანი და ორადგილიანი ოპერატორების გადატვირთვა

ქვემოთ მოყვანილ საილუსტრაციო მაგალითში განიხილება კლასი Vector. კლასში

გადატვირთულია უნარული და ბინარული ოპერატორები სამგანზომილებიან ვექტორებთან

სამუშაოდ.

#include <iostream>

using namespace std;

class Vector{

friend ostream& operator << (ostream& ,Vector &);

friend istream& operator >> (istream&, Vector& );

public:

Vector(int a=1, int b=1, int c=1){

x =a; y =b; z =c;

}

Vector operator +(Vector& );

Vector & operator =(Vector );

void show(){

cout<<x<<" "<<y<<" "<<z<<endl;

}

Studentis gvari : Kobakhidze

Nishnebi 5 saganSi : 74 69 89 94 53

Studentis gvari : Dadiani

Nishnebi 5 saganSi : 97 91 89 100 83

Studentis gvari : Melkadze

Nishnebi 5 saganSi : 81 76 73 92 67

Studentebis sia nishnebis klebiT :

Dadiani nishnebiT : 97 91 89 100 83 da sashualo nishniT 92

Melkadze nishnebiT : 81 76 73 92 67 da sashualo nishniT 77.8

Kobakhidze nishnebiT : 74 69 89 94 53 da sashualo nishniT 75.8

Press any key to continue . . .

Page 123: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 123

Vector & operator ++();

Vector operator ++(int);

bool operator ==(Vector );

private:

int x,y,z;

};

// << და >> ოპერატორებოს გადატვირთვა

ostream& operator<<(ostream& out,Vector& f){

out<<f.x<<" "<<f.y<<" "<<f.z<<endl;

return out;

}

istream& operator>>(istream& in, Vector& f){

in>>f.x>>f.y>>f.z;

return in;

}

// ვექტორების შეკრების გადატვირთვა

// შეიძლება მრავალჯერადი შეკრება

Vector Vector::operator +(Vector& m)

{

Vector temp;

temp.x =x+m.x; temp.y =y+m.y; temp.z =z+m.z;

return temp;

}

// ვექტორების მინიჭების დატვირთვა

// შეიძლება მრავალჯერადი მინიჭება

Vector& Vector::operator =(Vector m)

{

x = m.x; y = m.y; z =m.z;

return *this;

}

Page 124: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 124

// პრეფიქსული ინკრემენტის გადატვირთვა

Vector & Vector::operator ++()

{

x++; y++; z++;

return *this;

}

// ორი ვექტორის ტოლობაზე შემოწმება

bool Vector::operator ==(Vector m)

{

if(x == m.x && y == m.y && z == m.z)

return true;

return false;

}

// პოსტფიქსური ინკრემენტის გადატვირთვა

Vector Vector::operator ++(int)

{

Vector tmp;

// ჯერ შეინახება ობიექტის მნიშვნელობა

tmp =*this;

// შემდეგ ობიექტი იცვლება

x++; y++; z++;

// ფუნქცია დააბრუნებს ობიექტის წინა მნიშვნელობას

return tmp;

}

int main()

{

Vector a, b(1,2,3), g, c;

c = a+b+g;

cout<<"c =a+b+g operaciis shemdeg:"<<endl;

Page 125: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 125

a.show();

b.show();

g.show();

c.show();

Vector m, n, k(2,5,9);

m.show();

n.show();

k.show();

m = n = k;

cout<<"m = n = k operaciis shemdeg:"<<endl;

cout<<m<<n<<k;

++k;

cout<<"++k operaciis shemdeg k=";

k.show();

Vector d, e, f;

d=e++;

cout<<"d =e++ operaciis shemdeg:"<<endl;

d.show();

e.show();

Vector p, q;

cout<<"ShemoitaneT veqtorebi p da q:"<<endl;

cin>>p>>q;

if(p == q)

cout<<"p udris q-s\n";

else

cout<<"p ar udris q-s\n";

system("pause");

return 0;

}

პროგრამის შედეგია:

Page 126: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 126

ოპერატორების გადატვირთვა წარმატებით გამოიყენება ე.წ. მათემატიკურ კლასებთან.

ავაგოთ ჩვეულებრივი წილადების კლასი, გადავტვირთოთ მასში ორადგილიანი

ოპერატორები +, - , * , / , + = და შედარების ოპერატორი == როგორც კლასის

c =a+b+g operaciis shemdeg:

1 1 1 // a ვექტორის კომპონენტები

1 2 3 // b ვექტორის კომპონენტები

1 1 1 // g ვექტორის კომპონენტები

3 4 5 // c ვექტორის კომპონენტები

1 1 1 // m ვექტორის კომპონენტები

1 1 1 // n ვექტორის კომპონენტები

2 5 9 // k ვექტორის კომპონენტები

m = n = k operaciis shemdeg:

2 5 9 // m ვექტორის კომპონენტები

2 5 9 // n ვექტორის კომპონენტები

2 5 9 // k ვექტორის კომპონენტები

++k operaciis shemdeg k =3 6 10

d=e++ operaciis shemdeg:

1 1 1 // d ვექტორის კომპონენტები

2 2 2 // e ვექტორის კომპონენტები

ShemoitaneT veqtorebi p da q:

4 5 6

4 5 6

p udris q-s

Press any key to continue . . .

Page 127: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 127

ფუნქცია-ოპერატორები, ხოლო * = და < ოპერატორები - როგორც კლასის მეგობარი

ფუნქცია-ოპერატორები. აგრეთვე გავითვალისწინოთ კლასში >> და << ოპერატორების

გადატვირთვა კლასის ობიექტის კლავიატურიდან შეტანისათვის და ეკრანზე

გამოტანისათვის. პროგრამა გავაფორმოთ პროექტის სახით.

// წილადების კლასის heder ფაილი wiladi.h

#ifndef wiladi_h

#define wiladi_h

#include <iostream>

using namespace std;

class wiladi{

private:

int a,b;

wiladi shekv();

public:

// კონსტრუქტორი გაჩუმებით

wiladi(int x=1, int y=1);

// წილადების შეკრეფა

wiladi operator + (wiladi );

// გამრავლება

wiladi operator * (wiladi );

// გაყოფა

wiladi operator / (wiladi );

// გამოკლება

wiladi operator - (wiladi );

// მინიჭება

wiladi & operator = (wiladi );

// ოპერატორი + =

wiladi & operator += (wiladi );

// ტოლობაზე შედარება

bool operator == (wiladi );

Page 128: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 128

// მეგობარი ფუნქცია-ოპერატორი ”ნაკლებია”

friend bool operator < (wiladi& ,wiladi& );

// მეგობარი ფუნქცია-ოპერატორი * =

friend wiladi operator *= (wiladi& , wiladi& );

// გამოტანის << ოპერატორის გადატვირთვა

friend ostream& operator << (ostream&, wiladi& );

// შეტანის >> ოპერატორის გადატვირთვა

friend istream& operator >> (istream&, wiladi& );

};

#endif

// რეალიზების ნაწილი - wiladi.cpp ფაილი.

// აქ მოცემულია კლასის წევრი ფუნქციებისა და

// კლასის მეგობარი ფუნქციების განსაზღვრებები

#include "wiladi.h"

// კონსტრუქტორის განსაზღვრა

wiladi::wiladi (int x, int y) {a =x; b =y;}

// კლასის ობიექტისთვის გამოტანის << ოპერატორის გადატვირთვა

ostream& operator<<(ostream& x, wiladi& t){

return x<<t.a<<"/"<<t.b<<endl;

}

// კლასის ობიექტისთვის შეტანის >> ოპერატორის გადატვირთვა

istream& operator>>(istream& x, wiladi& t){

return x>>t.a>>t.b;

}

// წილადების გამოკლებისათვის - ოპერატორის გადატვირთვა

wiladi wiladi::operator - (wiladi m){

wiladi t;

t.a =a*m.b - b*m.a;

Page 129: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 129

t.b =b*m.b;

// დასაბრუნებელი ობიექტი t იძახებს ფუნქციას shekv( )

t.shekv();

return t;

}

// წილადების გაყოფის გადატვირთული / ოპერატორი

wiladi wiladi::operator / (wiladi m){

// ლოკალური t -სთვის გამოიძახება კონსტრუქტორი

wiladi t (a*m.b, b*m.a);

return t.shekv();

}

// წილადების გამრავლების გადატვირთული * ოპერატორი

wiladi wiladi::operator * (wiladi m){

wiladi t (a*m.a, b*m.b);

return t.shekv();

}

// წილადების შეკრება. შესაძლებელია მრავალჯერადი შეკრება

wiladi wiladi::operator + (wiladi m){

wiladi t (m.b*a + b*m.a, b*m.b);

return t.shekv();

}

// მინიჭების ოპერატორის გადატვირთვა.

// შესაძლებელია მრავალჯერადი მინიჭება

wiladi & wiladi::operator = (wiladi m){

a = m.a; b = m.b;

return *this;

}

// += ოპერატორის გადატვირთვა

wiladi & wiladi::operator += (wiladi m){

Page 130: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 130

a =a*m.b + b*m.a;

b =b*m.b;

this->shekv();

return *this;

}

// მეგობარი ფუნქცია-ოპერატორი *= . მას პარამეტრებად

// გადაეცემა მითითებები პირველ და მეორე წილადზე

wiladi operator *= (wiladi& p, wiladi& m){

p.a *= m.a;

p.b *= m.b;

return p.shekv();

}

// ტოლობაზე შედარების კლასის ფუნქცია-ოპერატორი ==.

bool wiladi::operator ==(wiladi t){

if(a == t.a && b == t.b)

return true;

return false;

}

// მეგობარი ფუნქცია-ოპერატორი < . ადგენს ნაკლებია

// თუ არა მისი პირველი პარამეტრი მეორეზე

bool operator < (wiladi& p, wiladi& m){

if(p.a*m.b < p.b*m.a)

return true;

return false;

}

// კერძო (private) ფუნქცია-წევრი. გამოიყენება

// კლასის ფუნქციების მიერ.

// ახორციელებს წილადის შეკვეცას

wiladi wiladi::shekv(){

Page 131: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 131

int usg =1;

int min =a;

if(a > b) min =b;

for(int k=1; k<=min; k++)

if(a%k == 0 && b%k == 0)

usg =k;

a /=usg; b /=usg;

return *this;

}

// wiladi კლასის სატესტო პროგრამა Test_wiladi.cpp

#include <cstdlib>

#include <iostream>

#include "wiladi.h"

using namespace std;

int main(int argc, char *argv[])

{

wiladi x, y, z, c;

cout<<"ShemoitaneT wiladi x: ";

cin>>x;

cout<<"ShemoitaneT wiladi y: ";

cin>>y;

c =x+y;

cout<<"x+y="<<c<<endl;

cout<<"ShemoitaneT wiladi z: ";

cin>>z;

c =x+y+z;

cout<<"x+y+z="<<c<<endl;

Page 132: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 132

c =x*y;

cout<<"x*y="<<c<<endl;

c =x/y;

cout<<"x/y="<<c<<endl;

cout<<"x="<<x<<"y="<<y;

c =x-y;

cout<<"x-y="<<c<<endl;

cout<<"y="<<y<<"c=x=y operaciis shemdeg:"<<endl;

c =x =y;

cout<<"c="<<c<<"x="<<x<<"y="<<y<<endl;

wiladi p, q;

cout<<"ShemoitaneT wiladi p: ";

cin>>p;

cout<<"ShemoitaneT wiladi q: ";

cin>>q;

p +=q;

cout<<"p+=q operaciis shemdeg:"<<endl;

cout<<"p="<<p;

cout<<"ShemoitaneT wiladi p: ";

cin>>p;

cout<<"ShemoitaneT wiladi q: ";

cin>>q;

p *=q;

cout<<"Megobari funqcia p*=q cvlis p-s:"<<endl<<"p="<<p;

cout<<"ShemoitaneT wiladi p: ";

cin>>p;

cout<<"ShemoitaneT wiladi q: ";

cin>>q;

if(p == q)cout<<"p udris q-s\n";

else cout<<"p ar udris q-s\n";

Page 133: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 133

cout<<"ShemoitaneT wiladi p: ";

cin>>p;

cout<<"ShemoitaneT wiladi q: ";

cin>>q;

if(p < q)cout<<"p naklebia q-ze\n";

else

if(p == q)cout<<"p udris q-s\n";

else cout<<"p metia q-ze\n";

cout<<"ok!"<< endl;

system("PAUSE");

return EXIT_SUCCESS;

}

პროგრამის შესრულების შედეგია:

ShemoitaneT wiladi x: 1 3

ShemoitaneT wiladi y: 1 2

x+y=5/6

ShemoitaneT wiladi z: 3 6

x+y+z=4/3

x*y=1/6

x/y=2/3

x=1/3

y=1/2

x-y=-1/6

y=1/2

c=x=y operaciis shemdeg:

c=1/2

x=1/2

y=1/2

ShemoitaneT wiladi p: 2 5

ShemoitaneT wiladi q: 3 4

p+=q operaciis shemdeg:

Page 134: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 134

თემა 8. მეხსიერების დინამიკური განაწილება

ოპერატორები new და delete გამოყენების მაგალითები

მაგალითი - დინამიკური მასივის კლასი

კოპირების კონსტრუქტორი კლასის სტატიკური ელემენტები

ოპერატორები new და delete

ჩვენს მიერ აქამდე განხილული როგორც საბაზისო ტიპების (int, float, double,

char), ისე რთული ტიპების (მასივები, სტრუქტურები, კლასები) მონაცემები

წარმოადგენდნენ ფიქსირებული განზომილების მონაცემებს. ასეთი პროგრამული

ობიექტების - ე.წ. სტატიკური ობიექტების - მეხსიერების განაწილებას ახდენს კომპილერი,

ამიტომ მათი ზომა კონსტანტურია და ცნობილია ჯერ კიდევ კომპილაციის ეტაპზე. მათი

აღწერის მიხედვით განსაზღვრავს კომპილერი თუ რამდენი მეხსიერება უნდა გაუნაწილოს

მათ. მაგალითად, აღწერა int m[15]; აღნიშნავს, რომ პროგრამაში შემოიღება სტატიკური

ობიექტი - 15 მთელი რიცხვის შემცველი მასივი m და მისთვის 60 ბაიტი უნდა განაწილდეს.

პროგრამირებაში ხშირად გვაქვს საქმე ისეთ პროგრამულ ობიექტებთან, რომელთა ზომა

თავიდანვე ცნობილი არ არის ან იცვლება პროგრამის შესრულების დროს. უფრო მეტიც,

შეიძლება საერთოდ არ იყოს ცნობილი, დაგვჭირდება თუ არა ესა თუ ის პროგრამული

ობიექტი. ასეთ პროგრამულ ობიექტებს მეხსიერება უნაწილდებათ პროგრამის შესრულების

მომენტში (დინამიკურად) სპეციალურ დინამიკურ მეხსიერებაში, და მათ ეწოდებათ

დინამიკური ობიექტები. დინამიკური ობიექტებისათვის მეხსიერების განაწილება

ხორციელდება C++ -ის სპეციალური ოპერატორის - new ოპერატორის - გამოყენებით.

მნიშვნელოვანია, რომ დინამიკური მეხსიერება, რომელიც აღარ არის საჭირო,

გათავისუფლდეს, რომ არ მოხდეს მეხსიერების გადავსება. ამისათვის გამოიყენება C++ -ის

ოპერატორი delete.

ოპერატორი new უნაწილებს დინამიკურ ობიექტს მისთვის საჭირო ბაიტების

რაოდემობას და აბრუნებს მიმთითებელს ამ მეხსიერების დასაწყისზე. ოპერატორი delete

მონიშნავს დინამიკური ობიექტის მიერ დაკავებულ მეხსიერებას როგორც თავისუფალს.

ვთქვათ, ჩვენი ამოცანაა მთელი ტიპის ერთგანზომილებიანი მასივის ცნებას

შეუსაბამოთ პროგრამული ეკვივალენტი. ამასთან გვსურს, რომ პროგრამაში შესაძლებელი

იყოს სხვადასხვა განზომილების მასივების დამუშავება. ასეთი პროგრამული ეკვივალენტი

შეიძლება იყოს კლასი, რომლის მონაცემს-მასივს არა აქვს დადგენილი მუდმივი

განზომილება. ცხადია, რომ კლასის ყოველი კონკრეტული ობიექტისათვის განზომილება

ირკვევა შექმნის მომენტში, ანუ ახლად შექმნილ მასივს მეხსიერება უნდა გაუნაწილდეს

დინამიკურად მისი ელემენტების რიცხვის გათვალისწინებით. მართალია, შეიძლებოდა

კლასის აღწერაში გაგვეთვალისწინებინა მასივის მაქსიმალური შესაძლო განზომილება (ანუ

უარი გვეთქვა მეხსიერების დინამიკურ გამოყოფაზე). მაგრამ მაშინ შეზღუდულები

ვიქნებოდით როგორც ძალიან დიდ მასივებთან მუშაობის მხრივ, ასევე პროგრამით

შექმნილი მასივების რაოდენობის მხრივ.

Page 135: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 135

როგორც უკვე აღინიშნა, დინამიკურ ობიექტებს მეხსიერება გამოეყოფათ ე.წ.

დინამიკურ მეხსიერებაში - გროვაში (heap) და მათთან მუშაობისათვის გამოიყენება C++ -ის

ოპერატორები new და delete.

new ოპერატორით სარგებლობის ზოგადი ფორმატია

p = new type;

სადაც type - იმ ობიექტის ტიპია, რომლისთვისაც გამოვყოფთ მეხსიერებას გროვაში, ხოლო

p წარმოადგენს მიმთითებელს type-ზე, ანუ p-ზე განაცხადს აქვს სახე

type* p;

დაკავებული მეხსიერების გათავისუფლება სრულდება

delete p;

შეტყობინების შესრულებით.

new ოპერატორით დინამიკური მეხსიერების მოთხოვნისას შესაძლოა საკმარისი

მეხსიერება heap-ში არ აღმოჩნდეს. მაშინ კომპილერის ზოგ რეალიზებაში new დააბრუნებს

ნულოვან მიმთითებელს NULL-ს (0-ს), ზოგში კი - მოახდენს ე.წ. გამონაკლისი სიტუაციის

გენერირებას. გამონაკლისი სიტუაცია - ეს არის დინამიკური ანუ შესრულების დროის

შეცდომა (Run Time Error), რომელსაც კომპილერი ამუშავებს გარკვეული წესით.

გამოყენების მაგალითები

ახლა განვიხილოთ რამოდენიმე მარტივი მაგალითი, რომელიც დაგვანახებს new და

delete ოპერატორების გამოყენებას.

მაგალითი 1.

ერთი მთელი რიცხვის განთავსება დინამიკურ მეხსიერებაში.

#include <iostream>

using namespace std;

int main()

{

int* p;

// მთელი რიცხვისათვის დინამიკური მეხსიერების მოთხოვნა

p =new int;

// თუ new დააბრუნებს 0-ს, დავბეჭდოთ გზავნილი დინამიკური

// მეხსიერების გამოყოფის შეცდომის შესახებ და დავასრულოთ პროგრამა

if( ! p ){

cout<<"Mexsierebis gamoyofis shecdoma\n";

return 1;

Page 136: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 136

p-s misamarTze ganTavsebulia 100

Press any key to continue . . .

}

// p-ს მისამართზე ჩავწეროთ მნიშვნელობა 100

*p =100;

cout<<"p-s misamarTze ganTavsebulia "<< *p<<"\n";

delete p; // დინამიკურ მეხსიერებაზე უარის თქმა

system("pause");

return 0;

}

პროგრამის შედეგი:

დინამიკური მეხსიერების განაწილების დროს შეიძლება მოვახდინოთ ობიექტის

ინიციალიზება - ჩავწეროთ გამოყოფილ მეხსიერებაში საწყისი მნიშვნელობა. მაგალითად,

int * q; float * t;

განაცხადების პირობებში

q = new int (9);

შეტყობინების შესრულება ნიშნავს, რომ new ოპერატორი გაუნაწილებს დინამიკურ

მეხსიერებას ერთ მთელ რიცხვს, ჩაწერს ამ მეხსიერებაში მნიშვნელობას 9 და მიანიჭებს q-ს ამ

მეხსიერების დასაწყისის მისამართს.

ხოლო

t = new float(3.14159);

შეტყობინების შესრულების შედეგად new ოპერატორი გამოუყოფს დინამიკურ მეხსიერებას

ერთ ნამდვილ რიცხვს, ჩაწერს მასში მნიშვნელობას 3.14159 და მიანიჭებს t-ს ამ

მეხსიერების მისამართს.

თუ heap-ში უნდა განვათავსოთ type ტიპის size განზომილების მასივი, უნდა

გვქონდეს განაცხადი

type* p;

და შესრულდეს შეტყობინება

p = new type[size];

ამ შეტყობინების წარმატებით (new არ დააბრუნებს NULL-ს) შესრულების შემდეგ p

მიუთითებს განაწილებული მეხსიერების დასაწყისს, ხოლო მეხსიერება საკმარისია type

ტიპის size ელემენტისათვის. ამ შემთხვევაში p - დინამიკური მასივის სახელია.

დინამიკურად განთავსებული მასივის საწყისი ინიციალიზება ვერ ხერხდება.

Page 137: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 137

p მასივის მიერ დაკავებული მეხსიერების გათავისუფლება ხდება

delete [] p;

შეტყობინების შესრულებით.

მაგალითი 2.

განვათავსოთ დინამიკურ მეხსიერებაში ნამდვილ რიცხვთა მასივი. სულ 10 რიცხვი.

მასივის უარყოფითი ელემენტები შევცვალოთ მნიშვნელობით 3, დადებითები კი

გავზარდოთ 2-ით. დავბეჭდოთ შეცვლილი მასივი.

#include <iostream>

using namespace std;

int main()

{

float* ptr;

// 10-ელემენტიანი float ტიპის მასივისათვის

// დინამიკური მეხსიერების მოთხოვნა

ptr = new float[10];

// თუ new ოპერატორი დააბრუნებს NULL-ს, დინამიკური

// მეხსიერების გამოყოფის შეცდომის შესახებ გზავნილის

// გამოტანა და პროგრამის დასრულება

if(ptr == NULL){

cout<<"Mexsierebis gamoyofis shecdoma\n";

return 1;

}

// განაწილებულ მეხსიერებაში 10 ნამდვილი რიცხვის ჩაწერა

for(int i=0; i<10; i++)

cin >> ptr[i];

// ამოცანის პირობის შესრულება

for(int i=0; i<10; i++)

if(*(ptr + i) < 0) ptr[i] =3;

else ptr[i] +=2;

// შეცვლილი მასივი ბეჭდვა

for(int i=0; i<10; i++) cout<<*(ptr + i)<<' ';

cout<<endl;

Page 138: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 138

1 3 -2 4 -5 -6 7 8 9 -12

3 5 3 6 3 3 9 10 11 3

Press any key to continue . . .

// მასივის მიერ დაკავებული მეხსიერების გათავისუფლება

delete [ ] ptr;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგი:

მაგალითი 3.

ქვემოთ მოცემული Test კლასის ობიექტი განვათავსოთ დინამიკურ მეხსიერებაში,

ჩავატაროთ ობიექტის ინიციალიზება და დავბეჭდოთ ობიექტის მონაცემების ნამრავლი.

#include <iostream>

using namespace std;

class Test{

public:

void set_ij(int a, int b){

i=a;

j=b;

}

int get_product(){

return i*j;

}

private:

int i, j;

};

int main()

{

Test *p; // Test ტიპის მიმთითებელზე განაცხადი

Page 139: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 139

Obieqtis monacemebis namravli :

20

Press any key to continue . . .

// Test კლასის ობიექტისათვის დინამიკური მეხსიერების მოთხოვნა

p = new Test;

if( ! p) {

cout<<"Mexsierebis gamoyofis shecdoma\n";

return 1;

}

// თუ მეხსიერება განაწილდა, ჩავატაროთ ობიექტის ინიციალიზება

p->set_ij(4,5);

cout<<" Obieqtis monacemebis namravli : "

<<p->get_product()<<endl;

delete p; // განაწილებული მეხსიერების გათავისუფლება

system("pause");

return 0;

}

პროგრამის შედეგი:

კლასის ობიექტის დინამიკური განთავსების დროს შესაძლებელია მისი საწყისი

ინიციალიზება. მაგალითად, Test კლასში რომ დავამატოთ კონსტრუქტორი

Test::Test(int a, int b) {

i =a; j =b;

}

შეტყობინება p = new Test (4,5); არა მხოლოდ განათავსებს Test კლასის ობიექტს

დინამიკურ მეხსიერებაში, არამედ ჩაწერს ობიექტის i და j მონაცემებში მნიშვნელობებს 4

და 5 შესაბამისად.

მაგალითი 4.

შევქმნათ Test კლასის ობიექტების დინამიკური მასივი, ჩავატაროთ მასივის

ელემენტების - ობიექტების - ინიციალიზება და დავბეჭდოთ თითოეული ობიექტის

მონაცემების ნამრავლი.

#include <iostream>

using namespace std;

Page 140: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 140

class Test{

public:

void set_ij(int a, int b){ i=a; j=b; }

int get_product(){ return i*j; }

private:

int i, j;

};

int main()

{

Test *p;

int i, a, b;

const int n =5;

// ობიექტების n-ელემენტიანი მასივისთვის

// დინამიკური მეხსიერების მოთხოვნა

p = new Test[n];

if(p == NULL){

cout<<"Mexsierebis gamoyofis shecdoma\n";

return 1;

}

// ობიექტების ინიციალიზება მეხსიერების

// წარმატებით განაწილების შემთხვევაში

cout<<"SeavseT masivi :\n";

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

cout<<"p ["<<i<<"] obieqtis monacemebi : ";

cin >> a >> b;

p[i].set_ij(a,b);

}

cout<<endl;

//ყოველი ობიექტის მონაცემების ნამრავლის ბეჭდვა

for(i=0; i<n; i++)

Page 141: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 141

p [0] obieqtis monacemebi : 3 2

p [1] obieqtis monacemebi : 1 3

p [2] obieqtis monacemebi : 4 5

p [3] obieqtis monacemebi : 2 4

p [4] obieqtis monacemebi : 5 6

p [0] obieqtis monacemebis namravli : 6

p [1] obieqtis monacemebis namravli : 3

p [2] obieqtis monacemebis namravli : 20

p [3] obieqtis monacemebis namravli : 8

p [4] obieqtis monacemebis namravli : 30

Press any key to continue . . .

cout<<"p ["<<i<<"] obieqtis monacemebis namravli : "

<<p[i].get_product()<<endl;

//დინამიკური მასივის მიერ დაკავებული მეხსიერების გათავისუფლება

delete [ ] p;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგი:

მაგალითი - დინამიკური მასივის კლასი

ავაგოთ მთელი ტიპის ერთგანზომილებიანი მასივის კლასი. მასივისთვის დავამატოთ

შემდეგი სასარგებლო თვისებები: ელემენტის ინდექსის საზღვრებს გარეთ გასვლის

შემოწმება, მასივების მინიჭება, მასივების შედარება == და != ოპერატორების საშუალებით,

შეტანა-გამოტანა >> და << ოპერატორების გამოყენებით. პროგრამაში უნდა შეგვეძლოს

სხვადასხვა ზომის მასივების დამუშავება. ამიტომ კლასის ობიექტს მეხსიერება უნდა

გაუნაწილდეს დინამიკურად.

ამოცანა გავაფორმოთ პროექტის სახით.

// დინამიკური მასივის კლასის heder-ფაილი Array.h

#include<iostream>

using namespace std;

#ifndef ARRAY_H

Page 142: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 142

#define ARRAY_H

class array{

public:

array(int =10); //კოსტრუქტორი გაჩუმებით

array(const array& ); // კოპირების კოსტრუქტორი

~array(); // დესტრუქტორი

int getSize(); // მასივის განზომილების დაბრუნება

// მინიჭების ოპერატორის გადატვირთვა

array& operator =(const array& );

// == ოპერატორის გადატვირთვა

int operator ==(const array& );

// ! = ოპერატორის გადატვირთვა

int operator !=(const array& );

// [ ] ოპერატორის გადატვირთვა

int& operator[](int);

// კლასის სტატიკური ფუნქცია

static int getArrayCount();

private:

int *ptr; // მიმთითებელი მასივის ნულოვან ელემენტზე

int size; // მასივის განზომილება

static int arrayCount; // შექმნილი მასივების რაოდენობა

// >> ოპერატორის გადატვირთვა

friend istream& operator>>(istream& ,array& );

// << ოპერატორის გადატვირთვა

friend ostream& operator<<(ostream& , array& );

};

#endif

// დინამიკური მასივის კლასის რეალიზების ნაწილი - Array.cpp

#include "Array.h"

Page 143: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 143

#include <assert.h>

#include <conio.h>

using namespace std;

int array::arrayCount=0;

int array::getArrayCount(){

return arrayCount;

}

array::array(int asize){

++arrayCount;

size =asize;

ptr =new int[size];

assert(ptr != 0);

for(int i=0; i<size; i++)

ptr[i]=0;

}

array::array(const array& init){

++arrayCount;

size =init.size;

ptr =new int[size];

assert(ptr != 0);

for(int i=0; i<size; i++)

ptr[i]=init.ptr[i];

}

array::~array(){

--arrayCount;

delete [] ptr;

}

int array::getSize(){

return size;

}

int& array::operator[](int ind){

if(ind < 0 ||ind >= size){

Page 144: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 144

cout<<"Incorrect index!!!"

<<"\nin fail "<<__FILE__<<" in line "<<__LINE__;

getch();

exit(1);

}

return ptr[ind];

}

int array::operator == (const array& right){

if(size != right.size) return 0;

for(int i=0; i<size; i++)

if(ptr[i]!=right.ptr[i]) return 0;

return 1;

}

int array::operator != (const array &right){

if(size != right.size) return 1;

for(int i=0; i<size; i++)

if(ptr[i]!=right.ptr[i]) return 1;

return 0;

}

array& array::operator =(const array& right){

if(&right != this){

delete[]ptr;

size =right.size;

ptr =new int[size];

assert(ptr != 0);

for(int i=0; i<size; i++)

ptr[i] =right.ptr[i];

}

return *this;

}

Page 145: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 145

istream& operator>>(istream& in, array& a){

for(int i=0; i<a.size; i++)

in>>a.ptr[i];

return in;

}

ostream& operator<<(ostream& out, array& a){

int i;

for(i =0; i < a.size; i++){

out << a.ptr[i] << " ";

if((i+1)%10 == 0)out<<endl;

}

if(i%10 != 0)out<<endl;

return out;

}

// array კლასის სატესტო პროგრამა Test_array.cpp

#include <iostream>

#include "Array.h"

using namespace std;

int main(int argc, char *argv[])

{

cout<<"Masivebis raodenoba="

<<array::getArrayCount()<<'\n';

array a1(7), a2;

cout<<"Masivebis raodenoba="

<<array::getArrayCount()<<'\n';

cout<<"Size of a1= "<<a1.getSize()<<endl

<<"a1 : \n"<< a1;

cout<<"Size of a2= "<<a2.getSize()

<<endl<<"a2 : \n"<< a2;

cout<<"Input 17 inteders\n";

cin >> a1 >> a2;

Page 146: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 146

cout<<"\nAfter input arrays contains:\n"

<<"a1 : "<< a1

<<"a2 : "<< a2;

array a3(a1);

cout<<"Size of a3= "<<a3.getSize()

<<endl<<"a3 : \n"<< a3;

cout<<"Shemocmeba: a1 != a2 ?\n";

if(a1 != a2)

cout<<"a1 ar udris a2-s\n";

else

cout<<"a1 udris a2-s\n";

cout<<"a1-s mivanichoT a2\n";

a1 = a2;

cout<<"a1 : "<< a1<<"a2 : "<< a2;

cout<<"Shemocmeba: a1 == a2 ?\n";

if(a1 == a2)

cout<<"a1 udris a2-s\n";

else

cout<<"a1 ar udris a2-s\n";

cout<<"a1[5]= "<< a1[5];

cout<<"\nChavsvaT 1000 a1[5]-shi\n";

a1[5] =1000;

cout<<"a1 : "<< a1;

cout<<"\nChavsvaT 500 a1[15]-shi\n";

a1[15 ]=500;

cout<<endl;

system("PAUSE");

return EXIT_SUCCESS;

}

პროგრამის შესრულების შედეგია:

Page 147: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 147

Masivebis raodenoba=0

Masivebis raodenoba=2

Size of a1= 7

a1 :

0 0 0 0 0 0 0

Size of a2= 10

a2 :

0 0 0 0 0 0 0 0 0 0

Input 17 inteders

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

After input arrays contains:

a1 : 1 2 3 4 5 6 7

a2 : 8 9 10 11 12 13 14 15 16 17

Size of a3= 7

a3 :

1 2 3 4 5 6 7

Shemocmeba: a1!=a2 ?

a1 ar udris a2-s

a1-s mivanichoT a2

a1 : 8 9 10 11 12 13 14 15 16 17

a2 : 8 9 10 11 12 13 14 15 16 17

Shemocmeba: a1==a2 ?

a1 udris a2-s

a1[5] udris 13

ChavsvaT 1000 a1[5]-shi

a1 : 8 9 10 11 12 1000 14 15 16 17

ChavsvaT 500 a1[15]-shi

Incorrect index!!!

in fail Array.cpp in line 35

Press any key to continue . . .

კოპირების კონსტრუქტორი

კლასის აღწერაში ვხედავთ ე.წ. კოპირების კონსტრუქტორის პროტოტიპს.

განვიხილოთ, რისთვის გამოიყენება კოპირების კონსტრუქტორი.

C++ -ში განიმარტება 2 ტიპის სიტუაცია, როდესაც ერთი ობიექტის მნიშვნელობა

გადაეცემა მეორეს: ეს არის მინიჭება და ინიციალიზება.

ინიციალიზებას აქვს ადგილი შემდეგ შემთხვევებში:

1. როდესაც ახალი ობიექტი იქმნება როგორც უკვე არსებული ობიექტის ასლი.

მაგალითად, თუ მოცემულია array x =y; ან მისი ეკვივალენტური array x(y);

Page 148: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 148

განაცხადი, სადაც y - array კლასის უკვე არსებული ობიექტია, მაშინ y ახდენს

x -ის ინიციალიზებას;

2. როდესაც ობიექტი მნიშვნელობით გადაეცემა ფუნქციას. მაგალითად,

თუ ფუნქციის პროტოტიპია

void func(array x);

ხოლო მის გამოიძახებას აქვს სახე

func(y);

მაშინ y ახდენს func ფუნქციის x პარამეტრის ინიციალიზებას;

3. როდესაც ფუნქციის დასაბრუნებელი მნიშვნელობა არის ობიექტი. მაგალითად, თუ

ფუნქციის პროტრტიპია array func1();

შევნიშნოთ, რომ სამივე განხილულ შემთხვევაში საქმე გვექნება დინამიკურ

მეხსიერებაში განთავსებულ ობიექტებთან.

განვიხილოთ თითოეული შემთხვევა.

1. array x =y; განაცხადი მოითხოვს არსებული y ობიექტის კოპირებას x ობიექტში.

რადგანაც y არსებობდა ამ განაცხადის გაკეთებამდე, მისთვის დინამიკური მეხსიერება

უკვე იყო განაწილებული. ამ მეხსიერების დასაწყისის მისამართი მინიჭებული ჰქონდა

y-ის ptr ველს. ინიციალიზების ჩასატარებლად კომპილერი ავტომატურად

აგენერირებს კოდს (ქმნის ე.წ. კოპირების კონსტრუქტორს გაჩუმებით), რომელიც

თანრიგ-თანრიგ გადაწერს y ობიექტის ინფორმაციას x-ში. ე.ი. x-ის ptr ველში

ჩაიწერება იგივე მისამართი, რაც იყო y-ის ptr-ში. ამრიგად, x-ისათვის განკუთვნილი

მეხსიერება იქნება იგივე, რაც ადრე გაუნაწილდა y-ს, ანუ x და y - ერთი და იგივე

ობიექტია, და არა ორი სხვადასხვა. ჩვენ კი გვსურდა x-ის სახით მიგვეღო ახალი

ობიექტი - y-ის ასლი. ვინაიდან x და y იკავებს ერთ მეხსიერებას, y-ის ყოველი შეცვლა

ნიშნავს x-ის იგივე შეცვლას და პირიქით. ასევე y-ის განადგურება (დესტრუქტორის

გამოძახებით) გამოიწვევს x-ის ptr-ისთვის გამონაწილებული მეხსიერების

გათავისუფლებასაც

2. როგორც ვიცით, ფუნქციაში ობიექტის მნიშვნელობით გადაცემის დროს სრულდება

არგუმენტის ბიტური კოპირება ფორმალურ პარამეტრში, ე.ი. ავტომატურად იქმნება

კოპირების კონსტრუქტორი გაჩუმებით, რომელიც y არგუმენტის ყოველ ბიტს

(თანრიგს) გადაწერს x პარამეტრის შესაბამის ბიტში. ჩვენს შემთხვევაში ორივე ობიექტი

x და y იყენებს დინამიკურ მეხსიერებას. ასეთი კოპირების შემდეგ x-ის მიმთითებელი

ptr მიუთითებს იგივე მეხსიერებას, რაც y-ის მიმთითებელი ptr. ე.ი. x-ის შეცვლის

შემთხვევაში y-იც შეიცვლება! ჩვენ კი y-ის შეცვლას არ ვგეგმავდით. გარდა ამისა,

ფუნქციის დასრულების შემდეგ იმუშავებს დესტრუქტორი, რომელიც გამოიძახება x

პარამეტრის გაუქმებისათვის. დესტრუქტორი გაათავისუფლებს x-ით დაკავებულ

დინამიკურ მეხსიერებას. ამან კი შეიძლება გამოიწვიოს არასასურველი თანამდევი

ეფექტები, რაც შემდგომში იმოქმედებს y-ზე.

3. მსგავსი სიტუაცია წარმოიშვება, თუ ფუნქციის დასაბრუნებელი მნიშვნელობა

ობიექტია, რომელიც იყენებს დინამიკურ მეხსიერებას. დასაბრუნებელი მნიშვნელობის

შენახვისათვის კომპილერი ავტომატურად ქმნის დროებით ობიექტს. როგორც კი

ფუნქცია დააბრუნებს მნიშვნელობას, დროებითი ობიექტი გადის ხილვადობის არიდან

და მისთვის გამოიძახება დესტრუქტორი. დესტრუქტორი ათავისუფლებს დროებითი

ობიექტის დინამიკურ მეხსიერებას, რაც არასასურველად იმოქმედებს ფუნქციით

დაბრუნებულ მნიშვნელობაზე (ობიექტზე).

Page 149: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 149

array კლასში აღწერილი კოპირების კონსტრუქტორი განკუთვნილია ჩამოთვლილი

პრობლემების ასაცილებლად. მისი განსაზღვრა შემდეგია:

array::array(const array& init)

{ // const კრძალავს init მასივის შეცვლას

++ arrayCount; // კლასის სტატიკური ელემენტის გაზრდა

size =init.size; // შესაქმნელი მასივის განზომილების დადგენა

ptr = new int[size]; // მეხსიერების გამოყოფა ასლისათვის

assert(ptr != 0); // დინამიკური მეხსიერების გამოყოფის შემოწმება

// განაწილებულ მეხსიერებაში init ობიექტის შიგთავსის კოპირება

for(int i=0; i<size; i++) ptr[i]=init.ptr[i];

}

array კლასის კოპირების კონსტრუქტორში ასლისათვის განაწილდა ცალკე

მეხსიერება. ამიტომ ადრე არსებული და ახლად შექმნილი ობიექტები სხვადასხვა

მეხსიერებას იკავებენ და ერთ-ერთის შეცვლა არ მოქმედებს მეორეზე.

საზოგადოდ, კოპირების კონსტრუქტორი გამოიძახება მხოლოდ ინიციალიზების დროს

და არა მინიჭების შემთხვევაში. მაგალითად,

array a(20), b(30);

cin >> a;

b = a;

ფრაგმენტში b = a არის მინიჭების ოპერაცია, და ამიტომ კოპირების კონსტრუქტორი მისი

შესრულებისას არ გამოიძახება.

როგორც ცნობილია, ერთი ობიექტისათვის მეორე ობიექტის მინიჭება C++ -ში

სრულდება ავტომატურად, მეორე ობიექტის ყოველი ელემენტის ბიტური კოპირებით

პირველის შესაბამის ელემენტში. დინამიკური ობიექტების მინიჭების დროს ასევე შეიძლება

წარმოიშვას სერიოზული პრობლემები. ამ პრობლემების მიზეზი იგივეა, რაც ზემოთ

განხილული. ამიტომ მინიჭების = ოპერატორი დინამიკური ობიექტის აღმწერ კლასში

უნდა იყოს გადატვირთული, ანუ უნდა იყოს განსაზღვრული თუ რა წესით სრულდება

მინიჭება. განვიხილოთ ეს ფუნქცია დაწვრილებით:

array & array::operator =(const array &right)

{ // const კრძალავს right მასივის შეცვლას

if(&right != this) // თუ ადგილი არა აქვს თვითმინიჭებას

{

// შესაცვლელი მასივით დაკავებული მეხსიერების გათავისუფლება

delete[ ] ptr;

Page 150: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 150

// შესაცვლელი მასივის ახალი განზომილების დადგენა

size = right.size;

// მისთვის ახალი მეხსიერების გამოყოფა

ptr = new int[size];

// დინამიკური მეხსიერების გამოყოფის შემოწმება

assert(ptr != 0);

// განაწილებულ მეხსიერებაში right ობიექტის შიგთავსის კოპირება

for(int i=0; i<size; i++)ptr[i]=right.ptr[i];

}

return *this; // შეცვლილი მასივის დაბრუნება

}

b = a; შეტყობინების შესრულება ნიშნავს b მასივის შეცვლას არსებული a მასივის ასლით.

b = a გამოსახულების შესრულება გამოიწვევს b.operator =(a) ფუნქციის გენერირებას.

operator = ფუნქციაში მოწმდება, ხომ არ არის თვითმინიჭების მცდელობა. თუ მასივები b

და a განსხვავდებიან, ოპერატორი delete გაათავისუფლებს b მასივით ადრე დაკავებულ

მეხსიერებას, b მასივის size ველში ჩაიწერება a მასივის size ველის მნიშვნელობა, b-ს

გამოეყოფა დინამიკური მეხსიერება და, თუ მეხსიერების განაწილება დასრულდა

წარმატებით, მოხდება a მასივის ელემენტების კოპირება b-ში.

რაც შეეხება თვითმინიჭების მცდელობას, მას არა მხოლოდ აზრი არა აქვს (ობიექტი

უკვე არსებობს), იგი სახიფათოც არის: operator = -ში შესაბამისი შემოწმება რომ არ

ყოფილიყო, ფუნქციის მუშაობა დაიწყებოდა b მასივის მიერ დაკავებული მეხსიერების

გათავისუფლებით. თვითმინიჭების შემთხვევაში კი b და a - ერთი და იგივე ობიექტია.

ამიტომ a მასივიც დაზიანდებოდა.

ფუნქცია operator = დააბრუნებს შექმნილ ობიექტს, როგორც მითითებას, რაც

შესაძლებელს ხდის მრავალჯერად მინიჭებას ( x = y = z; სადაც x, y და z – array ტიპის

ობიექტებია).

განვიხილოთ array კლასის კონსტრუქტორი:

array::array(int asize)

{ // asize - შესაქმნელი მასივის განზომილებაა. გაჩუმებით უდრის 10-ს

++ arrayCount; // კლასის სტატიკური ელემენტის გაზრდა

// განზომილების ჩაწერა ახალი მასივის size ველში

size = asize;

// ახალი მასივისათვის დინამიკური მეხსიერების გამოყოფა

ptr = new int[size];

Page 151: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 151

// დინამიკური მეხსიერების გამოყოფის შემოწმება

assert(ptr != 0);

// ახალი მასივის განულება

for(int i=0; i<size; i++) ptr[i]=0;

}

და დესტრუქტორი

array:: ~ array()

{

-- arrayCount; // კლასის სტატიკური ელემენტის შემცირება

delete [ ] ptr; // დინამიკური მეხსიერების გათავისუფლება

}

კლასის სტატიკური ელემენტები

კლასის ყველა განხილულ ფუნქციაში მონაწილეობს კლასის სტატიკური ელემენტი

arrayCount. როგორც წესი, კლასის სხვადასხვა ობიექტების მონაცემებს განსხვავებული

მნიშვნელობები აქვთ. მაგრამ ზოგ შემთხვევაში საჭიროა, რომ მონაცემის მნიშვნელობა იყოს

ყველა ობიექტისათვის ერთი და იგივე. ამ მიზნისათვის გამოიყენება კლასის ე.წ. სტატიკური

მონაცემები. ჩვენს მაგალითში arrayCount სტატიკური მონაცემის შინაარსი შემდეგია: იგი

ინახავს პროგრამით შექმნილი მასივების რაოდენობას. ამიტომ კლასის კონსტრუქტორშიც,

კოპირების კონსტრუქტორშიც და მინიჭების გადატვირთულ ფუნქციაშიც arrayCount

იზრდება ერთით, ხოლო დესტრუქტორში - მცირდება ერთით.

სტატიკურ მონაცემზე განაცხადი იწყება მომსახურე სიტყვით static. გლობალური

ცვლადებისგან განსხვავებით კლასის სტატიკური მონაცემების მოქმედების არე კლასია.

static მონაცემი შეიძლება იყოს public, private ან protected წვდომისა. მისი საწყისი

ინიციალიზება შეიძლება ფაილის მოქმედების არეში მხოლოდ ერთხელ, მაგალითად,

int array::arrayCount = 0;

array კლასში arrayCount აღწერილია private განყოფილებაში და ამიტომ მასზე

წვდომა შეუძლიათ მხოლოდ ამ კლასის ღია ფუნქციებს ან მეგობარ ფუნქციებს. კლასის ღია

ფუნქცია ამ შემთხვევაში უნდა იყოს static, მაგალითად

static int getArrayCount();

ამ ფუნქციის განსაზღვრა იქნება:

int array::getArrayCount(){

return arrayCount;

}

Page 152: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 152

კლასის სტატიკური მონაცემები არსებობენ მაშინაც, როდესაც კლასის არც ერთი

ობიექტი ჯერ არ არსებობს. მაგალითად, main ფუნქციის ქვემოთ მოცემულ ფრაგმენტში

getArrayCount ფუნქცია პირველად გამოიძახება მანამ, სანამ შეიქმნა კლასის ობიექტი,

ხოლო მეორედ - როდესაც კლასის 2 ობიექტი უკვე შექმნილია:

int main()

{

cout<<"Masivebis raodenoba = "

<< array :: getArrayCount()<<'\n';

array a1(7), a2;

cout<<"Masivebis raodenoba = "

<<array :: getArrayCount()<<'\n';

. . .

}

ფრაგმენტის მუშაობის შედეგად მივიღებთ პასუხს:

Masivebis raodenoba = 0

Masivebis raodenoba = 2

მივაქციოთ ყურადღება getArrayCount ფუნქციის გამოძახებას:

array :: getArrayCount()

რადგან ფუნქციას არ იძახებს არც ერთი კლასის ობიექტი, აუცილებელია მისი სახელის წინ

array :: -ის მითითება. უნდა აღინიშნოს, რომ კლასის ნებისმიერ ობიექტს შეუძლია

getArrayCount ფუნქციაზე მიმართვა, ანუ სწორი იქნებოდა შემდეგი შეტყობინება:

cout<<a1. getArrayCount()<<endl;

კლასის კონსტრუქტორში, კოპირების კონსტრუქტორში და მინიჭების ფუნქციაში

ვხედავთ უტილიტას assert.

assert განმარტებულია assert.h ფაილში და ამოწმებს მრგვალ ფრჩხილებში

მოცემული გამოსახულების ჭეშმარიტებას. თუ გამოსახულების მნიშვნელობა უდრის 0-ს

(მცდარია), assert ბეჭდავს შეტყობინებას შეცდომის შესახებ და იძახებს stdlib.h

ბიბლიოთეკის ფუნქციას abort, რომელიც, თავის მხრივ, ამთავრებს პროგრამის

შესრულებას.

ჩვენი მაგალითის ფუნქციებში assert გამოიყენება იმის დასადგენად, თუ რამდენად

წარმატებით დასრულდა დინამიკური მეხსიერების განაწილება: assert( ptr != 0 );

Page 153: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 153

გამოსახულება ptr != 0 მცდარია, თუ new ოპერატორმა დააბრუნა ნული, ანუ

დინამიკური მეხსიერება ვერ განაწილდა.

assert ფუნქციის გამოყენება შეგვეძლო array კლასის კიდევ ერთ ფუნქციაში -

ინდექსაციის [ ] ოპერატორის გადატვირთვის ფუნქციაში, მაგალითად ასე

int& array::operator [](int index)

{

assert(index >= 0 && index < size);

return ptr[index];

}

ფუნქცია operator [] პარამეტრად ღებულობს მასივის ელემენტის ინდექსს.

ფუნქციაში მოწმდება ინდექსის სისწორე. თუ ინდექსი გადის შესაძლო საზღვრებს გარეთ,

გამოსახულება index >= 0 && index < size მცდარია და მოქმედებას იწყებს assert.

ჩვენს პროგრამაში ინდექსის შემოწმება სრულდება ანალოგიურად:

if( index < 0 || index >= size ) {

cout<<" Incorrect index !!!";

exit(1);

}

stdlib.h -ში განმარტებული ფუნქცია exit იწვევს პროგრამის დასრულებას. მისი

პარამეტრი აჩვენებს პროცესის დამთავრების სტატუსს: პარამეტრის მნიშვნელობა 0

(EXIT_SUCCESS - Normal program termination) ნიშნავს პროგრამის ნორმალურ

დასრულებას, არანულოვანი მნიშვნელობა (EXIT_FAILURE - Abnormal program

termination) კი აცნობებს ოპერაციულ სისტემას, რომ პროგრამა დასრულდა შეცდომით.

თუ index -ის მნიშვნელობა დასაშვებია, ფუნქცია operator[] დააბრუნებს მასივი-

ობიექტის შესაბამის ელემენტს როგორც int& მითითებას. ეს საშუალებას გვაძლევს

გამოვიყენოთ ინდექსირებული ცვლადი ოპერატორის როგორც მარჯვენა, ისე მარცხენა

მხარეს. მაგალითად, შემდეგი ფრაგმენტის

array a(7);

cin >> a;

a[2] = 50;

cout << a[2];

cout << a;

a[2]=50; შეტყობინებაში ინდექსირებული ცვლადი a[2] გამოიყენება მინიჭების

ოპერატორის მარცხენა მხარეში (L-value), ხოლო cout << a[2]; შეტყობინებაში -

გამოტანის ოპერატორის მარჯვენა მხარეში (R-value).

Page 154: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 154

array კლასის დანარჩენი ფუნქციები სირთულეს არ წარმოადგენენ.

თემა 9. მემკვიდრეობითობა

საბაზო და წარმოებული კლასები. კლასის დაცული (protected) ელემენტები

მარტივი პირდაპირი მემკვიდრეობითობა. საბაზო კლასის მიმთითებლის

დაყვანა მემკვიდრე კლასის მიმთითებელზე

კონსტრუქტორებისა და დესტრუქტორების გამოყენება საბაზო და

წარმოებულ კლასებში

საბაზო და წარმოებული კლასები. კლასის დაცული (protected) ელემენტები

მემკვიდრეობითობა ობიექტზე ორიენტირებული დაპროგრამების ერთ-ერთი

ძირითადი პრინციპია. მემკვიდრეობითობა წარმოადგენს პროგრამული უზრუნველყოფის

განმეორებადი გამოყენების მექანიზმს, როდესაც ახალი კლასები წარმოიშვებიან უკვე

არსებული კლასების საფუძველზე. ახლად შექმნილი კლასები მემკვიდრეობით ღებულობენ

არსებული კლასების მონაცემებსა და ფუნქციებს (ინტერფეისს) და ამატებენ საკუთარ,

მათთვის აუცილებელ მონაცემებსა და ამ მონაცემების დამუშვების ფუნქციებს. ამ

თვალსაზრისით ახლად შექმნილი კლასი უფრო ”ფართეა” საწყის კლასთან შედარებით.

მაგალითად, კლასი Point შეიძლება საწყისი გახდეს Circle კლასისათვის, ვინაიდან წრე

სავსებით აღიწერება მისი ცენტრისა (წერტილის) და რადიუსის საშუალებით.

მემკვიდრეობითობის გამოყენება უზრუნველყოფს ახალი კლასების შექმნაზე დროის

დანახარჯის შემცირებას, იძლევა უკვე შემოწმებული და გამართული პროგრამული

უზრუნველყოფის გამოყენების საშუალებას და ამცირებს საექსპლუატაციო პრობლემებს,

როდესაც სისტემა იწყებს ფუნქციონირებას.

არსებული კლასის საფუძველზე შექმნილ ახალ კლასს ეწოდება წარმოებული

(derived) ან მემკვიდრე (child) კლასი. ხოლო იმ კლასს, რომლის საფუძველზეც შეიქმნა

ახალი, უწოდებენ საბაზო (base) ან წინაპარ კლასს. ყოველი მემკვიდრე კლასი თავად

შეიძლება გახდეს საბაზო კლასი მომავალი მემკვიდრე კლასებისათვის.

მარტივი მემკვიდრეობითობის დროს წარმოებული კლასი მიიღება მხოლოდ ერთი

საბაზო კლასის საფუძველზე. რთული (მრავლობითი) მემკვიდრეობითობის შემთხვევაში

ერთი წარმოებული კლასი იქმნება რამდენიმე კლასის საფუძველზე ან ერთი საბაზო კლასის

საფუძველზე იქმნება რამდენიმე მემკვიდრე კლასი. მემკვიდრეობითობის მექანიზმის

გამოყენებით აიგება რთული იერარქიული სტრუქტურები. მაგალითად, გეომეტრიული

ფორმის (Shape) ცნების აღმწერი კლასების იერარქია შეიძლება გამოიყურებოდეს

შემდეგნაირად:

ორგანზომილებიანი ფორმა სამგანზომილებიანი ფორმა

ფორმა

Page 155: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 155

წრე კვადრატი სამკუთხედი სფერო კუბი ტეტრაედრი

C++ -ში არსებობს კლასის ელემენტებზე წვდომის 3 რეჟიმი: ღია, საერთო (public);

დახურული, კერძო (private) და დაცული (protected). მესამე - protected - წვდომის

რეჟიმი შემოდის მემკვიდრეობითობასთან დაკავშირებით.

ის ფაქტი, რომ Circle კლასი წარმოიშვა Point კლასის საფუძველზე აღინიშნება

ასე:

class Circle : public Point{

. . .

} ;

სათაური class Circle : public Point განმარტავს ე.წ. ღია მემკვიდრეობითობას

(public inheritance). ღია მემკვიდრეობითობის დროს საბაზო კლასის public და

protected ელემენტები წარმოებულ კლასშიც არიან public და protected, ხოლო საბაზო

კლასის private ელემენტებზე წარმოებულ კლასს წვდომა არა აქვს.

იმისათვის, რომ გავერკვეთ, რას აღნიშნავს protected რეჟიმი, განვიხილოთ

მაგალითი:

#include <iostream>

using namespace std;

/ / საბაზო კლასი

class base{

public:

int get_i();

int get_j();

void set_ij(int, int);

void show();

private:

int i, j;

};

/ / წარმოებული კლასი

class derived : public base{

public:

void make_k();

void show();

Page 156: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 156

private:

int k;

};

/ / რეალიზების ნაწილი

int base::get_i() { return i; }

int base::get_j() { return j; }

void base::set_ij(int a, int b){ i =a; j =b; }

void base::show(){ cout<<"i="<<i<<" j="<<j; }

void derived::make_k(){ k =get_i()*get_j(); }

void derived::show(){

base::show();

cout<<" k="<<k<<endl;

}

int main(){

base b;

b.set_ij(2,3);

cout<<"Object b of base class : ";

b.show();

derived d;

d.set_ij(5,6);

d.make_k();

cout<<"\n\nObject d of derived class : ";

d.show();

system("pause");

return 0;

}

პროგრამის შედეგი:

მივაქციოთ ყურადღება იმ ფაქტსაც, რომ base კლასის set_ij ფუნქციის გამოყენება

შეუძლია როგორც ამ კლასის b ობიექტს, ისე derived კლასის d ობიექტს.

Object b of base class : i=2 j=3

Object d of derived class : i=5 j=6 k=30

Press any key to continue . . .

Page 157: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 157

derived კლასი არის base კლასის მემკვიდრე და მიიღება ღია მემკვიდრეობით:

class derived : public base { . . . };

base კლასის ყველა ელემენტი მემკვიდრეობით გადაეცემა derived კლასს. ეს ნიშნავს,

რომ derived კლასის ობიექტის მონაცემებია i, j და k. derived კლასის ღია

ინტერფეისი შეიცავს base კლასის ღია წევრ ფუნქციებს get_i, get_j, set_ij და show

(ანუ ამ ფუნქციების გამოყენება შეუძლიათ derived კლასის ობიექტებს) და საკუთარ წევრ

ფუნქციებს make_k და show.

base კლასის i და j მონაცემები აღწერილია როგორც კლასის private (დახურული)

წევრები. ამიტომ მემკვიდრე derived კლასში მათზე პირდაპირი წვდომა აკრძალულია

(ისევე, როგორც პროგრამის ნებისმიერ ფუნქციაში). derived კლასის ამ წევრებზე

წვდომისათვის უნდა იყოს გამოყენებული base კლასის ფუნქციები get_i და get_j :

void derived :: make_k(){

k = get_i() * get_j();

}

თუ იგივე მაგალითში base კლასის i და j მონაცემებზე წვდომის რეჟიმს შევცვლით

protected -ზე, მაშინ წარმოებულ კლასს i და j ველებზე ექნება პირდაპირი წვდომა:

void derived :: make_k(){

k = i * j;

}

ამავე დროს პროგრამის დანარჩენი ფუნქციებისათვის i და j ისევ დახურული

მონაცემებია, ანუ მათზე კლასის კლიენტებს (პროგრამის ფუნქციებს) წვდომა არა აქვთ.

#include <iostream>

using namespace std;

class base{

public:

int get_i();

int get_j();

void set_ij(int, int);

void show();

protected:

int i, j;

};

class derived : public base {

public:

Page 158: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 158

void make_k();

void show();

private:

int k;

};

int base::get_i() {return i;}

int base::get_j() {return j;}

void base::set_ij(int a, int b){ i =a; j =b; }

void base::show(){ cout<<"i="<<i<<" j="<<j;}

void derived::make_k(){ k =i*j; }

void derived::show(){

base::show();

cout<<" k="<<k<<endl;

}

int main(){

derived d;

d.set_ij(3,7);

d.make_k();

cout<<"Object d of derived class : ";

d.show();

system("pause");

return 0;

}

პროგრამის შედეგი:

base და derived კლასებში კეთდაბა განაცხადი ერთი და იგივე სახელის მქონე show

ფუნქციებზე. რეალიზების ნაწილში მათი განსაძღვრაა:

void base::show(){

cout<<"i="<<i<<" j="<<j;

}

და

void derived::show() {

Object d of derived class : i=3 j=7 k=21

Press any key to continue . . .

Page 159: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 159

base::show();

cout<<" k="<<k<<endl;

}

derived კლასის show ფუნქცია ბეჭდავს ამ კლასის ყველა მონაცემს, ამასთან i და j

მონაცემების დასაბეჭდად გამოიძახება base კლასის show ფუნქცია - base::show(); აქ

აუცილებელია იმის მითითება, რომ გამოძახებული show ფუნქცია არის base კლასის

ხილვადობის არეში - base::

დასკვნა:

public მემკვიდრეობითობის შემთხვევაში საბაზო კლასის public ელემენტებზე

წვდომა აქვს პროგრამის ყველა ფუნქციას. საბაზო კლასის private წევრებზე წვდომა აქვთ

მხოლოდ საბაზო კლასის წევრ ფუნქციებსა და მეგობარ ფუნქციებს.

protected (დაცული) წვდომის დონე წარმოადგენს დაცვის შუალედურ დონეს

public და private წვდომის დონეებს შორის. საბაზო კლასის protected ელემენტებზე

წვდომა აქვთ მხოლოდ: საბაზო კლასის ფუნქციებს და მეგობარ ფუნქციებს და მემკვიდრე

კლასის ფუნქციებსა და მეგობარ ფუნქციებს.

მემკვიდრე კლასის ფუნქციებს შეუძლიათ საბაზო კლასის public და protected

ელემენტებზე მიმართვა მათი სახელების მითითებით. ამასთან უნდა გვახსოვდეს, რომ

protected წვდომის მონაცემები არღვევენ საბაზო კლასის ინკაპსულაციას: საბაზო კლასის

დაცული ელემენტების შეცვლამ შეიძლება გამოიწვიოს ყველა მემკვიდრე კლასის

მოდიფიცირების აუცილებლობა.

მარტივი პირდაპირი მემკვიდრეობითობა. საბაზო კლასის მიმთითებლის დაყვანა

მემკვიდრე კლასის მიმთითებელზე

საბაზო და მემკვიდრე კლასის ობიექტებს აქვთ საერთო ნაწილი - საბაზო კლასის

მონაცემები და ფუნქციები. ამიტომ public მემკვიდეობითობით მიღებული მემკვიდრე

კლასის ობიექტი შეიძლება განვიხილოთ, როგორც საბაზო კლასის ობიექტიც. მაგრამ საბაზო

კლასის ობიექტი საზოგადოდ მემკვიდრე კლასის ობიექტს არ წარმოადგენს.

განვიხილოთ პირდაპირი ღია მემკვიდრეობითობის მაგალითი. მაგალითში აიგება

კლასების იერარქია Point->Circle.

#include <iostream>

using namespace std;

/ / საბაზო კლასი

class Point{

friend ostream& operator<<(ostream& , Point& );

public:

/ / კონსტრუქტორი გაჩუმებით

Point(float=0.0, float=0.0);

float getX(){ return x; }

Page 160: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 160

float getY(){ return y; }

void setPoint(float, float);

protected:

/ / საბაზო კლასის მონაცემები

float x, y;

};

/ / წარმოებული კლასი, ღია მემკვიდრეობითობა

class Circle : public Point{

friend ostream& operator<<(ostream& , Circle& );

public:

/ / მემკვიდრე კლასის კონსტრუქტორი

Circle(float=0.0, float=0.0, float=0.0);

void setRadius(float);

float getRadius(){ return radius; }

float area();

protected:

/ / მემკვიდრე კლასის მონაცემი

float radius;

};

/ / Point კლასის რეალიზების ნაწილი

/ / Point კლასის კონსტრუქტორი

Point::Point(float a, float b){ setPoint(a, b); }

/ / კოორდინატების შეცვლის ფუნქცია

void Point::setPoint(float a, float b){ x =a; y =b; }

/ / << ოპერაციის გადატვირთვა Point კლასისთვის

ostream& operator<<(ostream& out, Point& p){

out<<"( "<<p.x<<", "<<p.y<<" )";

return out;

}

/ / Circle კლასის რეალიზების ნაწილი

// Circle კლასის კონსტრუქტორი იძახებს Point კლასის კონსტრუქტორს

Circle::Circle(float a, float b, float c): Point(a, b)

{ radius =c; }

/ / რადიუსის შეცვლის ფუნქცია

Page 161: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 161

void Circle::setRadius(float a){ radius =a; }

/ / ფართობის გამოთვლის ფუნქცია

float Circle::area() {return 3.14159*radius*radius;}

/ / << ოპერაციის გადატვირთვა Circle კლასისთვის

ostream& operator<<( ostream& out, Circle& c){

out<<" Center = ( "<<c.x<<", "<<c.y<<" )"<<endl

<<"Radius = "<<c.radius;

return out;

}

int main(){

Point *pointPtr, p(2.5,3.7);

Circle *circlePtr, c(1.2, 2.3, 3.4);

cout<<"Point p :"<<p<<endl

<<"Circle c :"<<c<<endl;

/ / Circle განიხილება როგორც Point (მხოლოდ საბაზო კლასის ნაწილი)

/ / pointPtr-ს მიენიჭება Circle კლასის c ობიექტის მისამართი

pointPtr =&c;

cout<<endl<<"Circle c : (*pointPtr-is sashualebiT)"

<<*pointPtr<<endl;

/ / Circle განიხილება როგორც Point (ტიპების დაყვანის გამოყენებით)

/ / pointPtr-ს მიენიჭება Circle კლასის c ობიექტის მისამართი

pointPtr =&c;

/ / საბაზო მიმთითებლის დაყვანა წარმოებულის ტიპზე

circlePtr =(Circle *)pointPtr;

cout<<endl<<"Circle c : (*circlePtr-is sashualebiT) "

<<*circlePtr<<endl

<<"Area of c : (circlePtr-is sashualebiT)"

<<circlePtr->area()<<endl;

/ / Point განიხილება როგორც Circle

/ / pointPtr-ს მიენიჭება Point კლასის p ობიექტის მისამართი

pointPtr =&p;

/ / საბაზო მიმთითებლის დაყვანა წარმოებულის ტიპზე

circlePtr =(Circle *)pointPtr;

Page 162: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 162

cout<<endl<<"Point p : (*circlePtr-is sashualebiT)"

<<*circlePtr<<endl

<<"Area of object, on which reference circlePtr : "

<<circlePtr->area()<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგი:

პროგრამაში აღწერილია Point კლასის p ობიექტი, Point-ზე pointPtr

მიმთითებელი, Circle კლასის c ობიექტი და Circle-ზე circlePtr მიმთითებელი.

p და c ობიექტების მონაცემები იბეჭდება Point და Circle კლასების << გადატვირთული

ოპერატორების მეშვეობით.

საბაზო კლასის pointPtr მიმთითებელს ენიჭება მემკვიდრე კლასის მიმთითებელი

(c ობიექტის მისამართი), Circle კლასის c ობიექტი იბეჭდება Point კლასის

გადატვირთული << ოპერატორისა და pointPtr-ის განმისამართების (*pointPtr)

საშუალებით. შევნიშნოთ, რომ გამოიტანება c ობიექტის მხოლოდ ის ნაწილი, რომელიც

წარმოადგენს საბაზო კლასის ნაწილს. ანუ ამ შემთხვევაში საბაზო კლასის მიმთითებელი

”ხედავს” Circle კლასის c ობიექტის მხოლოდ საბაზო კლასის ნაწილს (წრის ცენტრს).

მემკვიდრე კლასის მიმთითებლის მინიჭება საბაზო კლასის მიმთითებლისათვის

pointPtr = &c; ყოველთვის სრულდება სწორად, ვინაიდან მემკვიდრე კლასის c ობიექტი

Point p :( 2.5, 3.7 )

Circle c : Center = ( 1.2, 2.3 )

Radius = 3.4

Circle c : (*pointPtr-is sashualebiT)( 1.2, 2.3 )

Circle c : (*circlePtr-is sashualebiT) Center = ( 1.2, 2.3 )

Radius = 3.4

Area of c : (circlePtr-is sashualebiT) 36.3168

Point p : (*circlePtr-is sashualebiT) Center = ( 2.5, 3.7 )

Radius = 2.8026e-045

Area of object, on which reference circlePtr : 0

Press any key to continue . . .

Page 163: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 163

ამავე დროს საბაზო კლასის ობიექტიც არის. ამ შემთხვევაში კომპილერი ასრულებს

მემკვიდრე კლასის მიმთითებლის არაცხად გარდაქმნას საბაზო კლასის მიმთითებელზე.

შემდეგ საბაზო კლასის pointPtr მიმთითებელს ენიჭება წარმოებული კლასის

c ობიექტის მისამართი და სრულდება pointPtr -ის დაყვანა უკან წარმოებული კლასის

მიმთითებელზე: (Circle *)pointPtr. ამ ოპერაციის შედეგი ენიჭება circlePtr -ს.

Circle კლასის c ობიექტი იბეჭდება Circle კლასის გადატვირთული << ოპერატორისა და

circlePtr-ის განმისამართების (*circlePtr) გამოყენებით. რადგანაც circlePtr

მიუთითებს Circle კლასის ობიექტს, ინფორმაცია c ობიექტის შესახებ იბეჭდება სრულად.

c ობიექტის ფართობი გამოიტანება circlePtr-ის მეშვეობით: circlePtr -> area().

შედეგი შეიცავს ფართობის სწორ მნიშვნელობას.

circlePtr = (Circle *)pointPtr; შეტყობინებაში pointPtr-ის დაყვანის დროს

მემკვიდრე კლასის circlePtr მიმთითებელზე დაგვჭირდა ცხადი გარდაქმნის ოპერაცია

(Circle *). საქმე ისაა, რომ საბაზო კლასის მიმთითებლის უშუალო მინიჭება მემკვიდრე

კლასის მიმთითებლისათვის არ შეიძლება: კომპილერი ”თვლის”, რომ მემკვიდრე კლასის

მიმთითებლები უნდა მიუთითებდნენ მხოლოდ მემკვიდრე კლასის ობიექტებს. მსგავს

შემთხვევაში კომპილერი არაცხად გარდაქნმას არ ასრულებს. (Circle *) ცხადი

გარდაქმნის ოპერაციის გამოყენება აცნობებს კომპილერს, რომ პროგრამისტი იღებს სრულ

პასუხისმგებლობას მიმთითებლის არასწორ გამოყენებაზე.

და ბოლოს, პროგრამაში ნაჩვენებია pointPtr-ისათვის საბაზო კლასის p ობიექტის

მისამართის მინიჭება pointPtr = &p; და pointPtr-ის შემდგომი დაყვანა მემკვიდრე

კლასის მიმთითებელზე circlePtr = (Circle *)pointPtr. Point კლასის p ობიექტი

იბეჭდება Circle კლასის გადატვირთული << ოპერატორისა და circlePtr-ის

განმისამართების (*circlePtr) საშუალებით. ახლა circlePtr მიუთითებს Point

კლასის ობიექტს. რადგანაც Point კლასის p ობიექტს radius ელემენტი არ გააჩნია,

პროგრამას გამოაქვს radius-ის შემთხვევითი მნიშვნელობა, რომელსაც circlePtr

”აღმოაჩენს” მეხსიერების იმ არეში, სადაც ”ელოდება” radius მონაცემის განთავსებას. Point

კლასის p ობიექტის ფართობი აგრეთვე გამოიტანება circlePtr-ის მეშვეობით:

circlePtr -> area(). როგორც პროგრამის შედეგიდან ჩანს, ფართობის მნიშვნელობა

უდრის 0-ს. ეს შედეგი მოსალოდნელიც იყო: ფართობის გამოთვლა იყენებს არარსებული

radius მონაცემის მნიშვნელობას. ცხადია, რომ წვდომა მონაცემებზე, რომლებიც

სინამდვილეში არ არსებობენ, ამ შემთხვევაში სახიფათო არ არის. მაგრამ არარსებული

ფუნქცია-წევრების გამოძახებამ შეიძლება გამოიწვიოს პროგრამის სერიოზული შეცდომა.

მემკვიდრე კლასის შექმნისას მემკვიდრეობითობის ტიპი შეიძლება იყოს როგორც

public, ასევე protected და private.

public მემკვიდრეობითობის დროს საბაზო კლასის

public წვდომის ელემენტები მემკვიდრე კლასშიც რჩებიან public - მათზე

პირდაპირი წვდომა აქვთ წარმოებული კლასის ფუნქცია-წევრებს, მეგობარ ფუნქციებს

და პროგრამის ფუნქციებს;

protected ელემენტები მემკვიდრე კლასშიც რჩებიან protected - მათზე პირდაპირი

წვდომა აქვთ მემკვიდრე კლასის ფუნქცია-წევრებს და მეგობარ ფუნქციებს;

Page 164: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 164

private ელემენტებზე პირდაპირი წვდომა მემკვიდრე კლასს არა აქვს. მათზე წვდომა

ხორციელდება საბაზო კლასის ღია ან დაცული ფუნქციების მეშვეობით.

protected მემკვიდრეობითობის დროს საბაზო კლასის

public ელემენტები მემკვიდრე კლასში ხდებიან protected - მათზე პირდაპირი

წვდომა აქვთ მხოლოდ მემკვიდრე კლასის ფუნქცია-წევრებსა და მეგობარ ფუნქციებს;

protected ელემენტები მემკვიდრე კლასშიც რჩებიან protected;

private ელემენტებზე პირდაპირი წვდომა მემკვიდრე კლასში არ არის. მათზე წვდომა

ხორციელდება საბაზო კლასის public ან protected ფუნქციების საშუალებით.

private მემკვიდრეობითობის დროს საბაზო კლასის

public ელემენტები მემკვიდრე კლასში ხდებიან private - მათზე პირდაპირი წვდომა

აქვთ მხოლოდ მემკვიდრე კლასის ფუნქცია-წევრებსა და მეგობარ ფუნქციებს;

protected ელემენტები მემკვიდრე კლასში ხდებიან private;

private ელემენტები მემკვიდრე კლასში ”არ ჩანან”. მათზე წვდომა ხერხდება საბაზო

კლასის public ან protected ფუნქციების გამოყენებით.

კონსტრუქტორებისა და დესტრუქტორების გამოყენება საბაზო და წარმოებულ კლასებში

მემკვიდრე კლასი მემკვიდრეობით ღებულობს საბაზო კლასის ელემენტებს. ამიტომ

მემკვიდრე კლასის ობიექტის შექმნა იწყება იმ ელემენტების ინიციალიზებით, რომლებიც

წარმოადგენენ საბაზო კლასის ელემენტებს. ამისათვის უნდა გამოიძახებოდეს საბაზო

კლასის კონსტრუქტორი:

// Point კლასის კონსტრუქტორის

გამოძახება

Circle::Circle(float a, float b, float c): Point(a, b)

{ radius= c; } // Circle კლასის საკუთარი წევრის ინიციალიზება

Circle კლასის radius წევრის ინიციალიზება შეიძლებოდა სხვა სტილიდაც -

Point(a, b) კონსტრუქტორის ინიციალიზების მსგავსად:

Circle::Circle(float a, float b, float c): Point(a, b), radius(c){ }

შევნიშნოთ, რომ ამ შემთხვევაში Circle კლასის კონსტრუქტორის ტანი რჩება

ცარიელი, მაგრამ მიუხედავად ამისა, აუცილებლად უნდა მიეთითებოდეს კონსტრუქტორის

განსაზღვრაში.

მემკვიდრე კლასის კონსტრუქტორი ყოველთვის პირველად იძახებს თავისი საბაზო

კლასის კონსტრუქტორს. რაც შეეხება დესტრუქტორებს, მათი გამოიძახება ხდება

შებრუნებული რიგით: მემკვიდრე კლასის დესტრუქტორი გამოიძახება საბაზო კლასის

დესტრუქტორზე ადრე.

ამის დასადასტურებლად განვიხილოთ საილუსტრაციო მაგალითი:

#include <iostream>

using namespace std;

class Point{

Page 165: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 165

public:

Point(float=0.0, float=0.0);

~ Point();

protected:

float x, y;

};

class Circle : public Point{

public:

Circle(float=0.0, float=0.0, float=0.0);

~ Circle();

protected:

float radius;

};

/ / რეალიზების ნაწილი

Point::Point(float a, float b){

x = a; y = b;

cout<<"Constructor Point:( "<<x<<" , "<<y<<" )"<<endl;

}

Point::~ Point(){

cout<<"Destructor Point:( "<<x<<" , "<<y<<" )"<<endl;

}

Circle::Circle(float r, float a, float b): Point(a, b){

radius =r;

cout<<"Constructor Circle: radius = "

<<radius<<" , Center ( "<<x<<" , "<<y<<" )"<<endl;

}

Circle::~ Circle(){

cout<<"Destructor Circle: radius = "

<<radius<<" , Center ( "<<x<<" , "<<y<<" )"<<endl;

}

int main()

Page 166: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 166

{

{

Point p(1.1, 2.2);

}

cout<<endl;

{

Circle c1(4.5, 6.7, 8.9);

cout<<endl;

Circle c2(10, 7, 3);

cout<<endl;

}

system("pause");

return 0;

}

პროგრამის შესრულების შედეგი:

თემა 10. მრავლობითი (რთული) მემკვიდრეობითიბა

სასწავლო მაგალითი: წერტილი, წრე, ცილინდრი

მემკვიდრე კლასის შექმნა ორი ან მეტი საბაზო კლასის საფუძველზე

სასწავლო მაგალითი: წერტილი, წრე, ცილინდრი.

Constructor Point: ( 1.1 , 2.2 )

Destructor Point: ( 1.1 , 2.2 )

Constructor Point: ( 6.7 , 8.9 )

Constructor Circle: radius= 4.5 , Center ( 6.7 , 8.9 )

Constructor Point: ( 7 , 3 )

Constructor Circle: radius= 10 , Center ( 7 , 3 )

Destructor Circle: radius= 10 , Center ( 7 , 3 )

Destructor Point: ( 7 , 3 )

Destructor Circle: radius= 4.5 , Center ( 6.7 , 8.9 )

Destructor Point: ( 6.7 , 8.9 )

Press any key to continue . . .

Page 167: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 167

განვიხილოთ მარტივი პირდაპირი მემკვიდრეობითობის მაგალითი. მაგალითში

საბაზო Point კლასის საფუძველძე აიგება კლასი Circle, რომელიც, თავის მხრივ, წარმოადგენს საბაზო კლასს Cylinder კლასისათვის.

#include <iostream>

using namespace std;

class Point{ / / საბაზო კლასი

friend ostream &operator<<(ostream &, Point &);

public:

Point (float=0.0, float=0.0); // კონსტრუქტორი გაჩუმებით

float getX(); // x კოორდინატის მიღება

float getY(); // y კოორდინატის მიღება

void setPoint(float, float); // კოორდინატების ჩასმა

protected: // წვდომა აქვს მემკვიდრე კლასს

float x, y; // Point-ის კოორდინატები

};

class Circle : public Point{ // Point-ის მემკვიდრე კლასი

friend ostream &operator<<(ostream &, Circle &);

public:

Circle(float=0.0, float=0.0, float=0.0);// კონსტრუქტორი გაჩუმებით

void setRadius(float); // რადიუსის ჩასმა

float getRadius(); // რადიუსის მიღება

float area(); // წრის ფართობის გამოთვლა

protected: // წვდომა აქვს მემკვიდრე კლასს

float radius; // Circle-ის რადიუსი

};

class Cylinder: public Circle{ // Circle-ის მემკვიდრე კლასი

friend ostream &operator<<(ostream &, Cylinder &);

public:

// კონსტრუქტორი გაჩუმებით

Cylinder(float=0.0, float=0.0, float=0.0, float=0.0);

void setHeight(float); // სიმაღლის ჩასმა

float getHeight(); // სიმაღლის მიღება

float area(); // ცილინდრის ზედაპირის ფართობის გამოთვლა

Page 168: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 168

float volume(); // ცილინდრის მოცულობის გამოთვლა

private: // კერძო წევრები

float height; // ცილინდრის სიმაღლე

};

// ფუნქციების განსაზღვრება

// Point კლასის კონსტრუქტორი

Point::Point(float a, float b){ setPoint(a, b);}

// კოორდინატების ჩასმა

void Point::setPoint(float a, float b){ x=a; y=b; }

// x კოორდინატის მიღება

float Point::getX() { return x; }

// y კოორდინატის მიღება

float Point::getY() { return y; }

// Point კლასის ობიექტებისათვის << ოპერატორის გადატვირთვა

ostream &operator<<(ostream &out, Point &p)

{

out<<"( "<<p.x<<", "<<p.y<<" )";

return out;

}

// Circle კლასის კონსტრუქტორი a და b–სთვის იძახებს Point კლასის კონსტრუქტრს

Circle::Circle(float a, float b, float c):Point(a,b)

{ radius=c; } / / და ანიჭებს წრის radius-ს c პარამეტრის მნიშვნელობას

// რადიუსის ჩასმა

void Circle::setRadius(float a){ radius=a; }

/ / რადიუსის მიღება

float Circle::getRadius() { return radius; }

// წრის ფართობის გამოთვლა

float Circle::area() { return 3.14159*radius*radius; }

// Circle კლასის ობიექტებისათვის << ოპერატორის გადატვირთვა

ostream &operator<<(ostream &out, Circle &c)

{

out<<" Center = ( "<<c.x<<", "<<c.y<<" )"<<endl

<<"Radius = "<<c.radius;

return out;

Page 169: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 169

}

// Cylinder კლასის კონსტრუქტორი იძახებს Circle კლასის კონსტრუქტრს r, a და b-სთვის

Cylinder::Cylinder(float h,float r,float a,float b):Circle(r,a,b)

{ height=h; } / /და ანიჭებს ცილინდრის სიმაღლეს h პარამეტრის მნიშვნელობას

// ცილინდრის სიმაღლის ჩასმა

void Cylinder::setHeight(float h) { height=h; }

// სიმაღლის მიღება

float Cylinder::getHeight(){ return height; }

// ცილინდრის ზედაპირის ფართობის გამოთვლა

float Cylinder::area(){

return 2*Circle::area()+ 2*3.1416*radius*height;

}

// ცილინდრის მოცულობის გამოთვლა

float Cylinder::volume(){ return Circle::area()*height; }

// Cylinder კლასის ობიექტებისათვის << ოპერატორის გადატვირთვა

ostream &operator<<(ostream &k, Cylinder &c)

{

k<<" Center = ( "<<c.x<<", "<<c.y<<" )"<<endl

<<"Radius = "<<c.radius<<

"\nHeight = "<<c.height;

return k;

}

int main()

{

Cylinder cyl(5, 2, 1.2, 2.3);

cout<<"Cylindris Tavdapirveli monacemebi :\n";

cout<<"x= "<<cyl.getX()<<" y= "<<cyl.getY()

<<"\nRadius= "<<cyl.getRadius()

<<"\nHeight= "<<cyl.getHeight()<<endl;

cyl.setHeight(10); cyl.setRadius(2); cyl.setPoint(1,1);

cout<<"\nCylindris axali monacemebi :\n";

cout<<cyl<<"\nArea= "<<cyl.area()<<"\nVolume= "

Page 170: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 170

<<cyl.volume()<<endl;

Point& pRef =cyl;

cout<<"Cylinder as a Point is : "<<pRef<<endl;

Circle& cRef =cyl;

cout<<"Cylinder as a Circle is : "<<cRef<<endl

<<"Area : "<<cRef.area()<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

პროგრამაში იქმნება Cylinder კლასის cyl ობიექტი. მისი თავდაპირველი

მონაცემების მნიშვნელობები იბეჭდება Point კლასის getX და getY, Circle კლასის getRadius და Cylinder კლასის getHeight წევრი ფუნქციების გამოყენებით. შემდეგ

setHeight, setRadius და setPoint ფუნქციების მეშვეობით ხდება cyl ობიექტის

ხელახალი ინიციალიზება. ცილინდრის მონაცემების ახალი მნიშვნელობები გამოიტანება Cylinder კლასისთვის გადატვირთული << ოპერატორის საშუალებით. ცილინდრის

ფართობისა და მოცულობის დასადგენად ობიექტი cyl იძახებს Cylinder კლასის ფუნქციებს area და volume.

შემდეგ იქმნება მითითება Point კლასზე pRef, რომელსაც ენიჭება cyl ობიექტის მისამართი. pRef მიუთითებს cyl -ს, მაგრამ რადგანაც pRef არის მითითება Point

Cylindris Tavdapirveli monacemebi :

x= 2 y= 1.2

Radius= 2.3

Height= 5

Cylindris axali monacemebi :

Center = ( 1, 1 )

Radius = 2

Height = 10

Area= 150.797

Volume= 125.664

Cylinder as a Point is : ( 1, 1 )

Cylinder as a Circle is : Center = ( 1, 1 )

Radius = 2

Area : 12.5664

Press any key to continue . . .

Page 171: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 171

კლასზე, იგი ”ფიქრობს”, რომ cyl - Point კლასის ობიექტია. ამიტომ pRef -ის ბეჭდვისას

ინფორმაცია გამოიტანება, როგორც Point კლასის ობიექტის შესახებ.

და ბოლოს, იქმნება მითითება Circle კლასზე cRef და მას ენიჭება cyl ობიექტის

მისამართი, ანუ cRef მიუთითებს cyl -ს. cRef არის მითითება Circle კლასზე და ამიტომ იგი ”ფიქრობს”, რომ cyl წარმოადგენს Circle კლასის ობიექტს. სრულდება cRef -ის

ბეჭდვა. ცხადია, რომ ინფორმაცია გამოიტანება, როგორც Circle კლასის ობიექტის შესახებ. იბეჭდება აგრეთვე Circle კლასის ობიექტის ფართობი.

მემკვიდრე კლასის შექმნა ორი ან მეტი საბაზო კლასის საფუძველზე

აქამდე ჩვენ ვიხილავდით მარტივ მემკვიდრეობითობას, როდესაც ყოველი მემკვიდრე კლასი წარმოიშვებოდა ერთი საბაზო კლასიდან. კლასი შეიძლება შეიქმნას რამოდენიმე

საბაზო კლასის საფუძველზე. ასეთ მემკვიდრეობითობას უწოდებენ რთულს (ან მრავლობითს). რთული მემკვიდრეობითობის დროს წარმოებული კლასი მემკვიდრეობით

ღებულობს რამოდენიმე საბაზო კლასის ელემენტებს.

განვიხილოთ რთული მემკვიდრეობითობის მაგალითი.

#include <iostream>

using namespace std;

class Base1{ / / პირველი საბაზო კლასი

public:

Base1 (int x=1){ value=x; } // კონსტრუქტორი გაჩუმებით

int getValue(){ return value; } // value მონაცემის მიღება

protected:

int value; // Base1 კლასის მონაცემი

};

class Base2{ // მეორე საბაზო კლასი

public:

Base2(char ch='a'){ letter=ch; } // კონსტრუქტორი გაჩუმებით

char getLetter(){ return letter; } // letter მონაცემის მიღება

protected:

char letter; // Base2 კლასის მონაცემი

};

class Derived : public Base1, public Base2{ // ორივე კლასის მემკვიდრე

// << ოპერატორის გადატვირთვა

friend ostream &operator<<(ostream &, Derived &);

public:

Page 172: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 172

Derived(int=3, char='x', float=1.2); // კონსტრუქტორი გაჩუმებით

float getReal(); // real მონაცემის მიღება

private:

float real; // Derived კლასის მონაცემი

};

// რეალიზების ნაწილი

// მემკვიდრე კლასის კონსტრუქტორი იძახებს ორივე საბაზო კლასის კონსტრუქტორს

Derived::Derived(int i, char c, float f) : Base1(i), Base2(c)

{ real=f; } // და ახდენს საკუთარი მონაცემის ინიციალიზებას

// მემკვიდრე კლასის real მონაცემის მიღების ფუნქცია

float Derived::getReal(){ return real; }

// მემკვიდრე კლასის ობიექტისთვის << ოპერაციის გადატვირთვა

ostream &operator<<(ostream &out, Derived &d){

return out<<" Mteli = "<<d.value<<endl

<<" Simbolo = "<<d.letter<<endl

<<"Namdvili = "<<d.real<<endl;

}

int main()

{

Base1 n1, n2(10); Base2 c1, c2('M');

cout<<"Object n1 : "<<n1.getValue()<<endl

<<"Object n2 : "<<n2.getValue()<<endl

<<"Object c1 : "<<c1.getLetter()<<endl

<<"Object c2 : "<<c2.getLetter()<<endl;

Derived d1, d2(79, 'A', 123.456);

cout<<"\nObject d1 :\n"<<d1<<endl

<<"Object d2 :\n"<<d2<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Page 173: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 173

მემკვიდრე კლასის დასახელებაში ”:”-ს შემდეგ მოიცემა მძიმით გამოყოფილი ყველა მისი საბაზო კლასის დასახელება (მემკვიდრეობითობის რეჟიმის ჩვენებით):

class Derived : public Base1, public Base2 { . . . };

მემკვიდრე კლასის კონსტრუქტორი იძახებს ყოველი საბაზო კლასის კონსტრუქტორს და ანიჭებს მნიშვნელობებს საკუთარ მონაცემებს. თითოეული საბაზო კლასის კონსტრუქტორი ახდენს მემკვიდრე კლასის იმ მონაცემების ინიციალიზებას, რომლებსაც გადასცემს მას მემკვიდრეს:

Derived :: Derived(int i,char c,float f) : Base1(i), Base2(c)

{ real = f; }

Derived კლასის << გადატვირთული ოპერატორი გამოიყენება value, letter და real მონაცემემბის დასაბეჭდად. ფუნქცია operator<< არის Derived კლასის მეგობარი ფუნქცია. ამიტომ მას აქვს პირდაპირი წვდომა Derived კლასის დახურულ (private) მონაცემზე real და აგრეთვე Base1 და Base2 კლასების დაცულ (protected) მონაცემებზე value და letter .

პროგრამაში იქმნება Base1 კლასის ორი ობიექტი n1, n2 და Base2 კლასის ორი ობიექტი c1, c2. n1 -ის value ველს მიენიჭება მნიშვნელობა გაჩუმებით (1), n2 -ის value ველში ჩაიწერება მნიშვნელობა 10, c1 -ის letter ველი შეიცავს მნიშვნელობას გაჩუმებით ('a'), ხოლო c2 -ის letter ველს მიენიჭება მნიშვნელობა 'M'. შექმნილი ობიექტების მონაცემები იბეჭდება getValue (n1 და n2 -სთვის) და getLetter (c1 და c2 -სთვის) წევრი ფუნქციების საშუალებით: main() -ს ამ ობიექტების მონაცემებზე პირდაპირი წვდომა არა აქვს.

Object n1 : 1

Object n2 : 10

Object c1 : a

Object c2 : M

Object d1 :

Mteli = 3

Simbolo = x

Namdvili = 1.2

Object d2 :

Mteli = 79

Simbolo = A

Namdvili = 123.456

Press any key to continue . . .

Page 174: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 174

შემდეგ იქმნება Derived კლასის ორი ობიექტი d1 და d2(79,'A',123.456). ამ ობიექტების მონაცემების გამოტანა სრულდება გადატვირთული << ოპერატორის მეშვეობით. პროგრამის პასუხიდან ჩანს, რომ d1 ობიექტი შეიცავს მონაცემების მნიშვნელობებს გაჩუმებით, ხოლო d2 ობიექტის მონაცემებს მიენიჭათ განაცხადის დროს მითითებული მნიშვნელობები.

ახლა ავაგოთ მრავლობითი მემკვიდრეობითობის მაგალითი, რომელშიც Cylinder კლასი იქმნება Point და Circle საბაზო კლასების საფუძველზე:

#include <iostream>

using namespace std;

class Point { // პირველი საბაზო კლასი

// << ოპერაციის გადატვირთვა Point კლასის ობიექტებისათვას

friend ostream &operator<<(ostream &, Point &);

public:

Point (float=0.0, float=0.0); // Point კლასის კონსტრუქტორი

float getX(); // x კოორდინატის მიღება

float getY(); // y კოორდინატის მიღება

void setPoint(float, float); // კოორდინატების ჩასმა

float area(){return 0;} // Point კლასის ობიექტის ფართობი უდრის 0-ს

float volume(){return 0;} // Point კლასის ობიექტის მოცულობა უდრის 0-ს

protected:

float x, y; // Point-ის ობიექტის კოორდინატები

};

class Circle { // მეორე საბაზო კლასი

// Circle კლასის ობიექტებისათვას << ოპერაციის გადატვირთვა

friend ostream &operator<<(ostream &, Circle &);

public:

Circle(float=0.0); // Circle კლასის კონსტრუქტორი

void setRadius(float); // radius-ის ჩასმა

float getRadius(); // radius-ის მიღება

float area(); // Circle კლასის ობიექტის ფართობის გამოთვლა

float volume(){return 0;} // Circle კლასის ობიექტის მოცულობა უდრის 0-ს

protected:

Page 175: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 175

float radius; // Circle კლასის ობიექტის რადიუსი

};

class Cylinder: public Point, public Circle { // რთული მემკვიდრეობითობა

// Cylinder კლასის ობიექტებისათვას << ოპერაციის გადატვირთვა

friend ostream &operator<<(ostream &, Cylinder &);

public:

// Cylinder კლასის კონსტრუქტორი

Cylinder(float=0.0, float=0.0, float=0.0, float=0.0);

void setHeight(float); // სიმაღლის მნიშვნელობის ჩასმა

float getHeight(); // სიმაღლის მიღება

float area(); // Cylinder კლასის ობიექტის ფართობის გამოთვლა

float volume(); // Cylinder კლასის ობიექტის მოცულობის გამოთვლა

protected:

float height; // Cylinder კლასის ობიექტის სიმაღლე

};

// რეალიზების ნაწილი

// Point კლასის ფუნქციების განსაზღვრა

Point::Point(float a, float b){ setPoint(a, b);}

void Point::setPoint(float a, float b){ x =a; y =b; }

float Point::getX() { return x; }

float Point::getY() { return y; }

// Point კლასის მეგობარი << ფუნქცია-ოპერატორის განსაზღვრა

ostream &operator<<(ostream &out, Point &p){

out<<"( "<<p.x<<", "<<p.y<<" )";

return out;

}

// Circle კლასის ფუნქცების განსაზღვრა

Circle::Circle(float c) { radius =c; }

void Circle::setRadius(float a){ radiu s =a; }

float Circle::getRadius() { return radius; }

float Circle::area() {

return 3.14159*radius*radius;

}

// Circle კლასის მეგობარი << ფუნქცია-ოპერატორის განსაზღვრა

Page 176: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 176

ostream &operator<<(ostream &out, Circle &c){

out<<"Radius = "<<c.radius;

return out;

}

// Cylinder კლასის წევრი ფუნქციების განსაზღვრა

// Cylinder კლასის კონსტრუქტორი იძახებს ორივე საბაზო კლასის კონსტრუქტორს

Cylinder::Cylinder(float h,float r,float a,float b) :Point(a,b),Circle(r)

{ height =h; } // და ანიჭებს საკუთარ height ველს h პარამეტრის მნიშვნელობას

void Cylinder::setHeight(float h){

height =h;

}

float Cylinder::getHeight(){

return height;

}

// ცილინდრის სრული ზედაპირის ფართობის გამოთვლა

float Cylinder::area(){

return 2*Circle::area()+2*3.1416*radius*height;

}

// ცილინდრის მოცულობის გამოთვლა

float Cylinder::volume(){

return Circle::area()*height;

}

// Cylinder კლასის მეგობარი << ფუნქცია-ოპერატორის განსაზღვრა

ostream &operator<<(ostream &k, Cylinder &c){

k<<"Center = ( "<<c.x<<", "<<c.y<<" )"<<endl

<<"Radius = "<<c.radius<<

"\nHeight = "<<c.height;

return k;

}

int main()

{

Cylinder cyl(5, 2, 1.2, 2.3);

cout<<"Data of Cylinder :\n";

cout<<"x= "<<cyl.getX()<<" y= "<<cyl.getY()

Page 177: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 177

<<"\nRadius= "<<cyl.getRadius()

<<"\nHeight= "<<cyl.getHeight()<<endl;

cout<<"\nNew Data of Cylinder, Area and Volume :\n";

cyl.setHeight(10); cyl.setRadius(2); cyl.setPoint(1,1);

cout<<cyl<<"\nArea= "<<cyl.area()<<"\nVolume ="

<<cyl.volume()<<endl;

Point& pRef =cyl;

cout<<"\nCylinder as a Point is : "<<pRef<<endl

<<"Area : "<<pRef.area()<<endl

<<"Volume= "<<pRef.volume()<<endl;

Circle& cRef =cyl;

cout<<"\nCylinder as a Circle is : "<<cRef<<endl

<<"Area : "<<cRef.area()<<endl

<<"Volume= "<<cRef.volume()<<endl;

system("pause");

return 0;

}

პროგრამის შესრულების შედეგია:

Data of Cylinder :

x= 1.2 y= 2.3

Radius= 2

Height= 5

New Data of Cylinder, Area and Volume :

Center = ( 1, 1 )

Radius = 2

Height = 10

Area= 150.797

Volume= 125.664

Cylinder as a Point is : ( 1, 1 )

Area : 0

Volume= 0

Cylinder as a Circle is : Radius = 2

Area : 12.5664

Volume= 0

Page 178: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 178

პროგრამაში იქმნება Cylinder კლასის ობიექტი cyl(5,2,1.2,2.3). ობიექტის მონაცემები იბეჭდება getX, getY (Point კლასის), getRadius (Circle კლასის) და getHeight (Cylinder კლასის) ფუნქციების გამოყენებით. შემდეგ cyl ობიექტი ღებულობს ახალ მონაცემებს და Cylinder კლასის გადატვირთული << ოპერატორის საშუალებით სრულდება ობიექტების მონაცემების გამოტანა. აგრეთვე იბეჭდება cyl ობიექტის ფართობისა და მოცულობის მნიშვნელობები.

შემდეგ იქმნება pRef მითითება Point კლასზე, რომელსაც ენიჭება cyl ობიექტის მისამართი. pRef წარმოადგენს Point კლასის ობიექტს და ამიტომ

cout<<pRef;

ინსტრუქციის შესრულებით გამოიტანება pRef ობიექტის მხოლოდ წერტილის კოორდინატები, ხოლო pRef ობიექტის ფართობი და მოცულობა უდრის 0-ს.

განაცხადი

Circle& cRef =cyl;

ქმნის Circle კლასის cRef ობიექტს, რომელიც არის მითითება cyl -ზე. cRef -ის მონაცემების გამოტანა სრულდება Circle კლასის გადატვირთული << ოპერატორის გამოყენებით: იბეჭდება cRef ობიექტის (წრის) რადიუსი. შემდეგ იბეჭდება cRef ობიექტის ფართობის მნიშვნელობა. რაც ეხება წრის მოცულობას, იგი უდრის 0-ს.

თემა 11. რთული მემკვიდრეობითიბა

ერთი საბაზო კლასის საფუძველზე ორი ან მეტი მემკვიდრე კლასის შექმნა

ერთი საბაზო კლასის საფუძველზე ორი ან მეტი მემკვიდრე კლასის შექმნა

ახლა განვიხილოთ რთული მემკვიდრეობითობის მაგალითი, რომელშიც ერთი საბაზო Shape კლასის საფუძველზე აიგება სამი Triangle, Rectangle და Circle მემკვიდრე კლასი.

#include <iostream>

#include <cmath>

using namespace std;

class Shape{

protected:

float x, y;

Page 179: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 179

public:

Shape(float a=0,float b=0):x(a),y(b){}

void prName(){cout<<"I'm Shape"<<endl;}

float area(){

cout<<"Area is't define here!\nArea of Shape : ";

return 0;

}

void print(){

cout<<"Pirveli monacemi = "<<x<<' '

<<"Meore = "<<y<<endl;

}

};

class Triangle:public Shape{

float z;

public:

Triangle(float a=3,float b=4,float c=5): Shape(a,b),z(c){ }

void prName(){cout<<"I'm Triangle"<<endl;}

float area(){

float p=(x+y+z)/2;

return sqrt(p*(p-x)*(p-y)*(p-z));

}

void print(){

cout<<"Pirveli gverdi = "<<x<<' '

<<"Meore = "<<y<<' '

<<"Mesame = "<<z<<endl;

}

};

class Rectangle:public Shape{

public:

Rectangle(float a=3,float b=4) : Shape(a,b){ }

Page 180: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 180

void prName(){cout<<"I'm Rectangle"<<endl;}

float area(){

return x*y;

}

void print(){

cout<<"SigrZe = "<<x<<' '

<<"Sigane = "<<y<<endl;

}

};

class Circle:public Shape{

public:

Circle(float a=3) : Shape(a){ }

void prName(){cout<<"I'm Circle"<<endl;}

float area(){

return 3.14*x*x;

}

void print(){

cout<<"Radiusi = "<<x<<endl;

}

};

int main()

{

Shape obj;

obj.prName();

obj.print();

cout<<obj.area()<<endl<<endl;

Triangle t;

t.prName();

t.print();

cout<<"Area of Triangle : "<<t.area()<<endl<<endl;

Page 181: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 181

Rectangle r;

r.prName();

r.print();

cout<<"Area of Rectangle : "<<r.area()<<endl<<endl;

Circle c;

c.prName();

c.print();

cout<<"Area of Circle : "<<c.area()<<endl<<endl;

system("pause");

return 0;

}

პროგრამა დაბეჭდავს:

აღვნიშნოთ, რომ თითოეული მემკვიდრე კლასი არის მიღებული საბაზო Shape

კლასიდან ღია მემკვიდრეობით. Shape კლასი განსაზღვრავს იერარქიის სახეს: მისი

ინტერფეისი შეიცავს მემკვიდრე კლასებში გამოსაყენებელ ფუნქციებს, ხოლო ამ კლასის

მონაცემებს თითოეული მემკვიდრე კლასი იყენებს თავისებურად:

Triangle კლასი განიხილავს Shape –ის x და y ველებს როგორც სამკუთხედის

I'm Shape

Pirveli monacemi = 0 Meore = 0

Area is't define here!

Area of Shape : 0

I'm Triangle

Pirveli gverdi = 3 Meore = 4 Mesame = 5

Area of Triangle : 6

I'm Rectangle

SigrZe = 3 Sigane = 4

Area of Rectangle : 12

I'm Circle

Radiusi = 3

Area of Circle : 28.26

Press any key to continue . . .

Page 182: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 182

პირველ და მეორე გვერდს და ამატებს z მონაცემს მესამე გვერდის აღსანიშნავად. ამიტომ ამ

კლასის კონსტრუქტორი ასე ჩაიწერება:

Triangle(float a=3,float b=4,float c=5): Shape(a,b),z(c){ }

Triangle კლასის prName, area და print ფუნქციები ემსახურებიან ამ კლასს და,

შესაბამისად, prName ბეჭდავს სამკუთხედის სახელს, area ითვლის სამკუთხედის ფართობს,

ხოლო print ბეჭდავს სამკუთხედის გვერდებს.

Rectangle კლასი განიხილავს Shape –ის x და y ველებს როგორც მართკუთხედის გვერდებს და საკუთარ მონაცემებს არ ამატებს:

Rectangle(float a=3,float b=4) : Shape(a,b){ }

ამ კლასის prName ფუნქცია ბეჭდავს მართკუთხედის სახელს, area ითვლის მართკუთხედის ფართობს, ხოლო print ბეჭდავს მართკუთხედის გვერდებს.

Circle კლასი იყენებს Shape კლასის მხოლოდ ერთ ( x ) მონაცემს და განიხილავს მას როგორც წრის რადიუსს. ამიტომაც მისი კონსტრუქტორი ასე განისაზღვრება:

Circle(float a=3) : Shape(a){ }

რაც შეეხება Circle კლასის ფუნქციებს, prName ბეჭდავს წრის სახელს, area ითვლის მის ფართობს, ხოლო print ბეჭდავს წრის რადიუსს.

საბაზო Shape კლასის კონსტრუქტორი ახდენს კლასის ორივე მონაცემის ინიციალიზებას:

Shape(float a=0,float b=0):x(a),y(b){}

ყურადღება მივაქციოთ Shape კლასის area ფუნქციას:

float area(){

cout<<"Area is't define here!\nArea of Shape : ";

return 0;

}

რადგან Shape კლასი შემოღებულია როგორც კლასების იერარქიის განმსაზღვრელი კლასი, იგი არ აღწერს არც ერთ კონკრეტულ გეომეტრიულ ფიგურას. ამიტომ area ფუნქციამ „არ იცის“, როგორ დაიტვალოს Shape –ის ფართობი და აბრუნებს 0 –ს.

main –ში წარმატებით შექმნილია როგორც საბაზო კლასის, ისე მემკვიდრე კლასების ობიექტები და ჩატარებულია prName, area და print ფუნქციების მუშაობის ტესტირება. პროგრამის შედეგიდან ჩანს, რომ მიუხედავად ფუნქციების ერთნაირი სახელებისა, ობიექტები „არ იბნევიან“ : ყოველი კლასის ობიექტი მუშაობს საკუთარ ფუნქციასთან.

შემდეგ მაგალითში წარმოადგენილია კლასების იერარქია, სადაც მემკვიდრე და კლასების წარმოიშვებიან წინაპარი კლასიდან. პროგრამა გაფორმებულია პროექტის სახით:

// Vehicle.h თავსართი ფაილი (ინტერფეისი)

#ifndef VEHICLE_H

#define VEHICLE_H

using namespace std; // საჭიროა string ტიპის ცნობისთვის

class Vehicle{

Page 183: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 183

protected:

float speed;

float range;

public:

Vehicle(float=100.0, float=120.0);

void set_speed(float);

float get_speed();

void printInfo(string);

};

class Trailer : public Vehicle{

int wheels;

public:

Trailer(float=120.0, float=30.0, int=14);

float get_wheels();

void printInfo(string);

};

class Bus : public Vehicle{

int passengers;

public:

Bus(float=90.0, float=1.0, int=60);

int get_passengers();

void printInfo(string);

};

bool sameSpeed(Trailer T, Bus B);

void Message(Trailer T, Bus B);

#endif

// Vehicle.cpp რეალიზების ფაილი (იმპლემენტაცია)

#include <iostream>

Page 184: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 184

#include "Vehicle.h"

using namespace std;

Vehicle::Vehicle(float s, float r):

speed(s), range(r){}

float Vehicle::get_speed(){

return speed;

}

void Vehicle::set_speed(float s){

speed =s;

}

void Vehicle::printInfo(string name){

cout<<name<<"'s speed is "

<<speed<<endl

<<"Its range is equal "

<<range<<endl<<endl;

}

Trailer::Trailer(float s, float r, int w):

Vehicle(s, r), wheels(w){}

float Trailer::get_wheels(){

return wheels;

}

void Trailer::printInfo(string name){

Vehicle::printInfo(name);

cout<<"Wheels quantity = "

<<wheels<<endl<<endl;

}

Bus::Bus(float s, float r, int p):

Vehicle(s, r), passengers(p){}

int Bus::get_passengers(){

return passengers;

}

Page 185: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 185

void Bus::printInfo(string name){

Vehicle::printInfo(name);

cout<<"Passengers quantity = "

<<passengers <<endl<<endl;

}

bool sameSpeed(Trailer T, Bus B){

return B.get_speed()>=T.get_speed();

}

void Message(Trailer T, Bus B){

if( sameSpeed(T, B) )

cout<<"The bus, reduce speed!!\n"<<endl;

else cout<<"All's OK!\n"<<endl;

}

// main.cpp სატესტო ფაილი

#include <iostream>

#include "Vehicle.h"

using namespace std;

int main(int argc, char *argv[])

{

Vehicle V;

V.printInfo("Vehicle");

Bus B;

B.printInfo("Bus");

Trailer T;

T.printInfo("Trailer");

Message(T, B);

B.set_speed(120);

B.printInfo("Bus");

Page 186: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 186

Message(T, B);

system("PAUSE");

return EXIT_SUCCESS;

}

პროგრამის შესრულების შედეგია:

საბაზო კლასი Vehicle (სატრანსპორტო საშუალება) შეიცავს: მონაცემებს – speed (სიჩქარე), range (ტვირთამწეობა) და კლასის ფუნქციებს – კონსტრუქტორს, მონაცემების ბეჭდვის printInfo ფუნქციას, სიჩქარის მიღების get_speed ფუნქციასა და სიჩქარის შეცვლის set_speed ფუნქციას.

Vehicle კლასის მემკვიდრე კლასში Trailer (ტრეილერი) შემოიღება კიდევ ერთი

მონაცემი wheels (ბორბლების რაოდენობა) და კლასის წევრი ფუნქციები – კონსტრუქტორი,

კლასის მონაცემის მიღების get_wheels ფუნქცია, კლასის სამივე მონაცემის ბეჭდვის

printInfo ფუნქცია.

Vehicle კლასის მემკვიდრე კლასი Bus (ავტობუსი) ამატებს საკუთარ მონაცემს

passengers (მგზავრების რაოდენობა) და შეიცავს ფუნქციებს – კონსტრუქტორს,

Vehicle's speed is 100

Its range is equal 120

Bus's speed is 90

Its range is equal 1

Passengers quantity = 60

Trailer's speed is 120

Its range is equal 30

Wheels quantity = 14

All's OK!

Bus's speed is 120

Its range is equal 1

Passengers quantity = 60

The bus, reduce speed!!

Press any key to continue . . .

Page 187: თემა 1. C++ როგორც სტანდარტული Cს გაუმჯობესება · PDF fileი. ხუციშვილი 1 თემა 1. c++

ი. ხუციშვილი 187

მგზავრების რიცხვის მიღების get_passengers ფუნქციას და Bus კლასის სამივე მონაცემის

ბეჭდვის ფუნქციას.

პროგრამაში მონაწილეობს ორი გლობალური ფუნქცია: პირველი ფუნქცია –

sameSpeed – ადარებს ავტობუსის და ტრეილერის სიჩქარეებს, მეორე – Message – იყენებს

პირველს და ბეჭდავს გამაფრთხილებელ გზავნილს, თუ ავტობუსის სიქარე აღემატება

ტრეილერის სიჩქარეს.

ძირითად პროგრამაში კეთდება განაცხადი სამივე კლასის ობიექტებზე და იბეჭდება

ინფორმაცია თითოეულის შესახებ. გლობალური ფუნქცია Message ბეჭდავს გზავნილს T

ტრეილერის და B ავტობუსის სიჩქარეების შედარების შედეგად. შემდეგ B ავტობუსის

სიჩქარე იცვლება (ხდება დაუშვებლად დიდი), იბეჭდება ავტობუსის ახალი მონაცემები,

ხოლო Message ფუნქცია „მოუწოდებს“ ავტობუსს შეამციროს სიჩქარე.