Organizing Program and Data

Kategori: C++ , 11 Kasım 2019 , JanFranco


Şu ana kadar çok küçük problemleri çözen programlar yazdık fakat problemimiz çok büyük olduğunda, kod okunabilirliği ve program esnekliğini sağlamak adına bazı düzenlemeler yapmalıyız. C++ bize bu düzenlemeler için fonksiyonları, veri yapılarını ve sınıfları sunuyor. Bu yazımda önceki programı fonksiyonlar kullanarak tekrar düzenleyeceğiz.

Fonksiyonların bir adı, parametre listesi ve return değerleri (opsiyonel) vardır. Kendisine midterm, final ve ödev notları girildiğinde, ortalamayı hesaplayan bir fonksiyon yazalım:


double grade(double midterm, double final, double homework) {
    return 0.2 * midterm + 0.4 * final + 0.4 * homework;
}
Burada fonksiyonun tipi, double tip return ettiğinden fonksiyonun tipi de double oldu. return anahtar kelimesi ile bu fonksiyonu kullandığımız her yerde bize ortalamayı verecek. Fonksiyonu kullanalım:


cout << "Your final grade is " << setprecision(3)
<< grade(midterm, final, sum / count)
<< setprecision(prec) << endl;
Burada parantez içinde gönderilen değerlere argüman denir. Bu argümanlar, fonksiyonun parametre listesindeki değerlere kopyalanır, işlem yapılır ve return değeri ile fonksiyon ortalamayı döndürür. Medyan hesabı yapan bir fonksiyon yazalım:


double median(vector<double> vec) {
    typedef vector<double>::size_type vec_sz;
    vec_sz size = vec.size();
    if (size == 0)
	throw domain_error("median of an empty vector");
    sort(vec.begin(), vec.end());
    vec_sz mid = size / 2;
    return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}
Bu fonksiyon double değer döndüreceği için tipi de double olmak zorunda. Parametre listesinde bir vektör mevcut. Bu fonksiyonu çağırdığımızda vektörün tamamı, parametre listesindeki vektöre kopyalanacaktır. Fonksiyonun içindeki diğer hesaplamaları daha önce zaten görmüştük fakat burada yeni bir ifade görüyoruz: throw. Fonksiyonu kimin kullanacağını bilmiyoruz bu nedenle vektör boş olduğunda lütfen ödevlerinizi girin tarzı bir mesaj yazmamız mantıklı değildir. throw anahtar keliemsi ile hata mesajı fırlatabiliriz. domain_error, fonksiyonun argümanı kabul edilebilen değer dışında ise kullanılabilir. Ayrıca bir string göndererek, hatayı okunabilir bir hata yapabiliriz. Şimdi bir fonksiyon daha oluşturalım:


double grade(double midterm, double final, const vector<double>& hw) {
    if (hw.size() == 0)
	throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
}
Burada bir kaç yeni özellik görüyoruz. Bunlardan ilki parametre listesindeki 3. eleman olan const vector& hw. Biliyoruz ki fonksiyona gönderilen değerler, parametre listesine kopyalanır. Bu basit bir int değer için sıkıntı yaratmasa da, 1000000 elemanlı bir vektörün kopyalanması bize sıkıntı yaratabilir. Bu gibi durumlar için vektörün kendisini yollamak yerine referansını yollayabiliriz:


vector<double> homework;
vector<double>& hw = homework;
Burada hw vektörü, homework vektörünün kendisidir. hw vektöründe bir değişiklik yaptığımızda homework vektöründe de değişiklik yapılmış olur. Eğer başına const ifadesini eklersek, referans üzerinde yazma işlemi yapamayacağımızı belirtiririz:


const vector<double>& chw = homework;
Burada chw vektörü, homework vektörünün referansıdır fakat chw vektörüne yeni eleman ekleyip, silemeyiz sadece bu vektörün değerlerini okuyabiliriz. Fonksiyonda yeni gördüğümüz bir özellik daha mevcut: overloading. grade isimli bir fonksiyonumuz zaten vardı ve aynı isimle yeniden bir fonksiyon tanımladık. Bu yeni fonksiyonun öncekinden farkı, 3. parametresi farklıdır. Bu şekilde aynı isimli tanımlamara overloading denir. Notları okuyacak bir fonksiyon yazalım:


istream& read_hw(istream& in, vector<double>& hw) {
    if (in) {
	// get rid of previous contents
	hw.clear();
	// read homework grades
	double x;
	while (in >> x)
	    hw.push_back(x);
	// clear the stream so that input will work for the next student
	in.clear();
    }
    return in;
}
Bu fonksiyonda return değeri istream referansı tipindedir bu nedenle fonksiyonun tipi de istream referansı şeklinde olmalıdır. Bir fonksiyondan aynı anda iki değer return edemeyiz. Ancak biz bu fonksiyonun hem başarılı bir şekilde input aldığının hem de vektörün return edilmesini istiyoruz. Bu problemde vektörü return etmek yerine, referans olarak fonksiyona geçirip, modifiye edebiliriz. Böylelikle vektörün değeri fonksiyonda da fonksiyonun dışında da değişmiş olur. Fonksiyonda dikkatimizi clear() methodları çekiyor. Vektörü temizlememizin sebebi önceki değerleri almak istememiz. Fonksiyonu genel olarak yazıyoruz, kimin nasıl kullandığını bilmiyoruz. Fonksiyon her bir öğrenci için sırayla çalıştırabilir. Bu durumda vektörü temizlemeliyiz. Ayrıca her input sonrasında, input akışını bir sonraki öğrenci için temizlemeliyiz.

Programımız şu an itibariyle tamamlandı. Tek bir öğrenci için not hesabı yapıyoruz. Bu not hesabını tek bir öğrenci için değil, bir çok öğrenci için yapalım. Bunun için öğrencinin bilgilerini bir arada tutan bir struct yazalım:


struct Student_info{
    string name;
    double midterm, final_ex;
    vector<double> homework;
};
Student_info adında bir tip tanımladık. vector gibi bu tipi kullanarak da bir vector tanımlayabiliriz. Tanımladığımız öğrencileri bu vektörde depolayabiliriz. Bunu while döngüsü ile yapacağız. Ayrıca programın çıktısının düzenli gözükmesi için maxlen adında bir değişken tanımlayalım. Bu değişkene maksimum uzunluklu ismin uzunluğunu atayacağız. Öğrencilerin yanında gözüken not ile öğrenci ismi arasındaki boşluğu bu uzunluğa göre belirleyeceğiz. Ayrıca isimlere göre sıralama yapacağız. Bunun için sort() fonksiyonunu kullanabiliriz fakat sort() foksiyonu ilkel veri tiplerini sıralayabilir. Bizim belirlediğimiz tipi sıralayabilmesi için ayrı bir fonksiyon oluşturalım:


bool compare(const Student_info& x, const Student_info& y){
    return x.name < y.name;
}
sort() fonksiyonuna bu yazdığımız fonksiyonu gönderebiliriz: sort(students.begin(), students.end(), compare). İsimlere göre sıralama yaptıktan sonra, bir for döngüsü ile vektörde gezinerek, öğrencilerin isimlerini konsola bastıracağız. Daha sonra en uzun isme göre boşluk bırakarak, daha önceden oluşturduğumuz grade() fonksiyonunu çağırarak notları hesaplayacağız:


int main()
{
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    while(read(cin, record)){
        maxlen = max(maxlen, record.name.size());
        students.push_back(record);
    }

    sort(students.begin(), students.end(), compare);

    for(vector<Student_info>::size_type i=0; i!=students.size(); i++){
        cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');
        try{
            double final_grade = grade(students[i]);
            streamsize prec = cout.precision();
            cout << setprecision(3) << final_grade << setprecision(prec);
        }catch(domain_error e){
            cout << e.what();
        }
    }

    cout << endl;

    return 0;
}
Programın tam hali:


#include <iostream>
#include <string>
#include <iomanip>
#include <ios>
#include <vector>
#include <algorithm>

using std::cout;            using std::cin;
using std::string;          using std::endl;
using std::setprecision;    using std::streamsize;
using std::vector;          using std::sort;
using std::istream;         using std::domain_error;
using std::max;

struct Student_info{
    string name;
    double midterm, final_ex;
    vector<double> homework;
};

istream& read_hw(istream& in, vector<double>& hw) {
    if(in){
        hw.clear();
        double x;
        cout << "Enter homework grades: ";
        while (in >> x){
            hw.push_back(x);
        }
        in.clear();
    }
    return in;
}

double median(vector<double> vec) {
    typedef vector<double>::size_type vec_sz;
    vec_sz size = vec.size();
    if (size == 0)
	throw domain_error("median of an empty vector");
    sort(vec.begin(), vec.end());
    vec_sz mid = size / 2;
    return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}

double grade(double midterm, double final_ex, double homework){
    return 0.2 * midterm + 0.4 * final_ex + 0.4 * homework;
}

double grade(double midterm, double final_ex, const vector<double>& hw) {
    if (hw.size() == 0)
        throw domain_error("Student has done no homework");
    return grade(midterm, final_ex, median(hw));
}

double grade(const Student_info& s) {
    return grade(s.midterm, s.final_ex, s.homework);
}

istream& read(istream& is, Student_info& s){
    cout << "Enter name, midterm, final: ";
    is >> s.name >> s.midterm >> s.final_ex;
    read_hw(is, s.homework);
    return is;
}

bool compare(const Student_info& x, const Student_info& y){
    return x.name < y.name;
}

int main()
{
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    while(read(cin, record)){
        maxlen = max(maxlen, record.name.size());
        students.push_back(record);
    }

    sort(students.begin(), students.end(), compare);

    for(vector<Student_info>::size_type i=0; i!=students.size(); i++){
        cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');
        try{
            double final_grade = grade(students[i]);
            streamsize prec = cout.precision();
            cout << setprecision(3) << final_grade << setprecision(prec);
        }catch(domain_error e){
            cout << e.what();
        }
    }

    cout << endl;

    return 0;
}


Sonraki Yazı: Using Sequential Containers and Analyzing Strings
Yorumlar

Henüz bir yorum bulunmuyor.
Yorum bırakın