AcceleratedC++/Chapter3 AcceleratedC++/Chapter5

목차

2. Chapter 4 Organizing programs and data

  • 어떤 종류의 문제를 푼다.
  • 다른 것들과 독립적이다.
  • 이름을 가지고 있어야 한다.

    3장까지 봤던 것은 첫번째것만 있고, 나머지 것은 없다. 프로그램이 작을때는 별로 상관없지만, 커지기 시작하면서부터 2,3번째 것이 결여되면, 나중엔 제어가 불가능해진다. C++에서는 다른 언어와 마찬가지로 함수 + 자료구조를 제공함으로써, 프로그램을 구조화시킬수 있는 방법을 제공한다. 또한 함수 + 자료구조를 묶어서 가지고 놀 수 있는 class라는 도구를 제공해준다.(Chapter9부터 자세히 살펴본다.)
    그러므로 이번장부터는 프로그램을 나눠서, 서로 다른 파일에 저장, 컴파일하는 법과, 나중에 합치는 법 등을 공부할 것이다.

2.1. 4.1 Organizing computations

  • 3장까지의 프로그램을 보면 등급을 메기는 부분이 있다. 이 부분을 함수로 추출해보자. 함수로 추출함으로써, 나중에 똑같은 내용을 또 코딩할 필요가 없고, 알아보기도 쉬워진다. 또한 등급 메기는 방법을 바꿀때 그 함수 부분에만 문제를 한정지을 수가 있다. 또한 함수에 이름을 지어줌으로써, 더욱 추상화시킬수가 있게 된다.
  • 앞장에서 등급을 출력하는 부분을 살펴보면,
    ~cpp 
    	cout << "Your final grade is " << setprecision(3)
    		<< 0.2 * midterm + 0.4 * final + 0.4 * median
    		<< setprecision(prec) << endl;
     
    이렇게 생겼다. 둘째줄의 등급 계산하는 부분을 다음과 같이 함수로 뽑아낼 수가 있다.
    ~cpp 
    double grade(double midterm, double final, double homework)
    {
        return 0.2 * midterm + 0.4 * final + 0.4 * homework;
    }
    /* ... */
    int main() 
    {
    /* ... */
    	cout << "Your final grade is " << setprecision(3)
    		<< grade(midterm, final, sum / count)
    		<< setprecision(prec) << endl;
    
    	return 0;
    }
    
     

  • 함수 만드는 법을 요약해보자면
    ~cpp 
    return_type function_name(parameter lists...) { 함수 내에서 할 일들 }
     
    이렇게 하면 된다.

  • 함수를 호출할때에는 함수를 만들때 주어졌던 parameter lists를 충족시키는 값들을 넣어줘야 한다. 물론 순서도 맞춰줘야 한다. arguments라고도 한다. arguments는 식이 될수도 있다. 그 뒤에 함수로 넘어간 parameter들은 함수 내에서 지역 변수(함수가 끝나면 소멸되는)처럼 작동하게 된다. 즉 그 값들을 복사하게 되는 것이다. 이를 call by value라 한다.

  • 2.1.1. 4.1.1. Finding medians

  • 앞에서 우리는 vector에 들어가 있는 값에서 중간값 찾는 걸 했었다. Chapter8에서는 vector에 들어가 있는 type에 관계없이 작동하게 하는 법을 배울 것이다. 지금은 vector<double>로만 한정짓자. median 구하는 루틴을 함수로 빼보자.
    ~cpp 
    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];
    }
     
  • 여기서 살펴볼 게 몇가지 있다. 지난번에는 vector의 크기가 0이면 그냥 프로그램을 종료시켜버렸지만, 여기서는 예외처리라는 신기술을 사용했다. 이 방법은 여기서 끝내지 않고 다음 부분으로 넘어간다. <stdexcept>를 포함시켜 주면 된다.
  • 또한, 아까 함수 호출하면서 parameter로 넘겨줄때에는 그 값을 복사를 한다고 했다. 저렇게 함수를 호출함으로써, 원래 vector를 손상시키지 않고, 복사본 vector에서 sort를 해서 작업을 처리해 줄수가 있다. 비록 시간이 좀 더 걸리긴 하지만..

  • 2.1.2. 4.1.2 Reimplementing out grading policy

  • 이쯤 와서 함수의 유용함을 알았다면, 우리의 성적 매기는 방법을 따로 함수로 뽑아내고 싶을 것이다. 다음 예제를 보자.
    ~cpp 
    double grade(double midterm, double final, const vector<double>& hw)
    {
    	if(hw.empty())		// 책에서는 hw.size()==0 이라고 되어 있지만 
    				// empty()메소드를 호출하는 것이 더 효율적이라고 하더군요.
    		throw domain_error("student has done no homework");
    	return grade(midterm, final, median(hw));
    }
     
  • 여기서 눈여겨볼 세가지 사항이 있다.
    • const vector<double>& hw : 이것을 우리는 double형 const vector로의 참조라고 부른다. reference라는 것은 어떠한 객체의 또다른 이름을 말한다. 또한 const를 씀으로써, 저 객체를 변경하지 않는다는 것을 보장해준다. 또한 우리는 reference를 씀으로써, 그 parameter를 복사하지 않는다. 즉 parameter가 커다란 객체일때, 그것을 복사함으로써 생기는 overhead를 없앨수 있는 것이다.

  • ~cpp 
    vector<double> homework;
    vector<double>& hw = homework;
    // hw는 homework와 같다. 즉, 이름은 다르지만, hw를 고치면 homework도 같이 고쳐진다. 왜냐? 같으니까
     
    • grade() function : 우리는 아까 grade라는 함수를 만들었었다. 그런데 이번에 이름은 같으면서 parameter는 조금 다른 grade()를 또 만들었다. 이런게 가능한가? 가능하다. 이러한 것을 함수의 overloading이라고 한다. 함수 호출할때 어떤게 호출될까는 따라가는 parameter lists에 의해 결정된다.
    • exception : 사용자에게 무엇이 잘못되었는가를 알려주는 exception을 던져준다.

    2.1.3. 4.1.3 Reading homework grades

  • 이제 우리가 풀어야 할 문제는, 숙제의 등급을 vector로 읽어들이는 것이다. 여기에는 두가지의 문제점이 있다. 바로 리턴값이 두개여야 한다는 점이다. 하나는 읽어들인 등급들이고, 또 다른 하나는 그것이 성공했나 하는가이다. 하나의 대안이 있다. 바로 리턴하고자 하는 값을 리턴하지 말고, 그것을 reference로 넘겨서 변경해주는 법이다. const를 붙이지 않은 reference는 흔히 그 값을 변경할때 쓰인다. reference로 넘어가는 값을 변경해야 하므로 어떤 식(expression)이 reference로 넘어가면 안된다.(lvalue가 아니라고도 한다. lvalue란 임시적인 객체가 아닌 객체를 말한다.)
    ~cpp 
    istream& read_hw(istream& in, vector<double>& hw)
    {
    	double x;
    	while(in >> x)				// 차차 살펴볼테지만 이건 잘못되었다.
    		hw.push_back(x);
    }
    
    read_hw(cin, homework);	// 호출
     
  • 위에서 잘못된 부분을 찾아보자.
    • hw가 넘어오면서 hw안에 아무것도 없다는 것을 보장할수 있겠는가? 먼저번에 쓰던 학생들의 점수가 들어있을지도 모른다. 확실하게 없애주기 위해 hw.clear()를 해주자.
    • 저 while 루프가 언제 멈출지 알수 있는가? 파일의 끝에 도달했거나, 입력받은게 등급이 아닐때일 것이다.
      • 파일의 끝에 도달했다는 것 = 읽기 실패
      • 입력받은게 등급이 아닐때(점수를 입력해야 되는데 이상한 것을 입력했을때) istream 객체 in은 실패 상태가 된다. 또한 그것을 읽지 않는다. 파일의 끝에 도달한것 처럼...
    • 이 상황을 해결하기 위해, 그냥 in객체의 상태를 초기화해줘버리자. in.clear() 모든 에러 상태를 리셋시켜준다.

  • ~cpp 
    istream& read_hw(istream& in, vector<double>& hw)
    {
    	if(in)
    	{
    		hw.clear();
    		double x;
    		while(in >> x)
    			hw.push_back(x);
    		in.clear();
    	}
    	return in;
    }
     
    DeleteMe) 두번째가 잘 이해가 안가네.. 누가 잘 아시는 분은 명쾌한 설명 부탁드립니다.

    2.1.4. 4.1.4 Three kinds of function parameters

  • 지금까지 만든 세개의 함수 median, grade, read_hw 를 살펴보자.
    • median 함수를 보면, vector<double> 파라메터가 참조로 넘어가지 않는다. 학생수가 많아질수록 매우 큰 객체가 될텐데 낭비가 아닌가? 하고 생각할수도 있지만, 어쩔수 없다. 함수 내부에서 소팅을 해버리기 때문에 참조로 넘기면, 컨테이너의 상태가 변해버린다.
    • grade 함수를 보면, vector는 const 참조로 넘기고, double은 그렇지 않다. int나 double같은 기본형은 크기가 작기 때문에, 그냥 복사로 넘겨도 충분히 빠르다. 뭐 값을 변경해야 할때라면 참조로 넘겨야겠지만... const 참조는 가장 일반적인 전달방식이다.
    • read_hw 함수를 보면, 복사는 하지 않고, 그 값을 변경하기 위해 참조만 썼다.
  • const가 아닌 참조형 파라메터는 lvalue여야만 한다.

  • 2.1.5. 4.1.5 Using functions to calculate a student's grade

  • 소스를 보면 새로운게 눈에 띈다. 바로 try이다. 다음과 같이 쓴다.
    ~cpp 
    	try {
    		// 하다가 예외 발생하면, 중단하고
    	}
    	catch(domain_error) {
    		// 이리로 온다. 만약에 try 안에서 예외 안 뜨면 catch 안은 수행안한다.
    	}
     

  • 여기까지 최종소스

    ~cpp 
    #include <ios>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    #include <stdexcept>
    
    using namespace std;
    
    double grade(double midterm, double final, double homework);
    double grade(double midterm, double final, const vector<double>& hw);
    double median(vector<double> vec);
    istream& read_hw(istream& in, vector<double>& hw);
    
    int main()
    {
    	cout << "Please enter your first name: ";
    	string name;
    	cin >> name;
    	cout << "Hello, " << name << "!";
    
    	cout << "Please enter your midterm and final exam grades: ";
    	double midterm, final;
    	cin >> midterm >> final;
    
    	cout << "Enter all your homework grades, "
    			"follewd by end-of-file: ";
    
    	vector<double> homework;
    
    	read_hw(cin, homework);
    
    	try
    	{
    		double final_grade = grade(midterm, final, homework);
    
    		streamsize prec = cout.precision();
    		cout << "Your final grade is " << setprecision(3)
    			<< final_grade << setprecision(prec) << endl;
    	}
    	catch(domain_error)
    	{
    		cout << endl << "You must enter your grades. "
    			"Please try again." << endl;
    		return 1;
    	}
    
    	return 0;
    }
    
    double grade(double midterm, double final, double homework)
    {
    	return 0.2 * midterm + 0.4 * final + 0.4 * homework;
    }
    
    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));
    }
    
    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];
    }
    
    istream& read_hw(istream& in, vector<double>& hw)
    {
    	if(in)
    	{
    		hw.clear();
    
    		double x;
    		while(in >> x)
    			hw.push_back(x);
    
    		in.clear();
    	}
    	return in;
    }
    

    2.2. 4.2 Organizing data

    • 여태까지는 한 학생의 데이터만 가지고 놀았다. 하지만 성적 처리 프로그램은, 모든 학생의 데이터를 대상으로 한다. 이에 다시 체계적인 데이터 구조가 필요하다.

    2.2.1. 4.2.1 Keeping all of a student's data together

  • 학생의 데이터를 묶어 보자. 여기서 struct란 새로운 구문이 나온다. {}안에 멤버로 넣고 싶은 변수들을 쫙 써주면 된다. 접근할라면 . 써주고 변수 쓰면 된다.
    ~cpp 
    struct Student_info {
    	string name;
    	double midterm, final;
    	vector<double> homework;
    };
     
  • 데이터 구조가 바뀌었으니, 우리의 프로그램도 약간 변경을 해야할 것이다. 먼저 read
    ~cpp 
    istream& read(istream& is, Student_info& s)
    {
    	is >> s.name >> s.midterm >> s.final;
    	read_hw(is, s.homework);
    	return is;
    }
     
    • is >> 을 보면, string을 읽었다가 double을 읽기도 한다. 이게 가능한 이유는 오버로딩 때문이다.
    • Student_info형 변수 s의 값을 변경시키기 위해 참조로 넘겨줬다.
  • 다음엔 grade. 옛날 버젼은 인자로 midterm, final, homework등을 받았지만, 오버로딩을 이용해서, Student_info 하나만을 받도록 해보자.
    ~cpp 
    double grade(const Student_info& s)
    {
    	return grade(s.midterm, s.final, s.homework);
    }
     
  • 마지막으로 sort. 우리는 다음과 같이 했었다.
    ~cpp 
    sort(vec.begin(), vec.end());
     
    • 저 vec은 double형 값을 담고 있었기 떄문에, 순서대로 sort하면 되었었다. 하지만 우리가 만든 Student_info는 어떻게 sort를 할까?

  • ~cpp 
    sort(students.begin(), students.end());       // 과연? #!$%#@^#@$#
     
    • 무엇을 기준으로 sort를 할것인가? 이름? midterm? final? 알수가 없다. 따라서 우리는 predicate라는 것을 정의해 주어야 한다. 다음과 같이 해주면 된다.

    ~cpp 
    bool compare(const Student_info& x, const Student_info& y)
    {
    	return x.name < y.name;
    }
    
    sort(students.begin(), students.end(), compare);
     

    2.2.2. 4.2.3. Generating the report

  • max(같은 타입의 파라메터 두개) : 뭐 큰값을 리턴해 주겠지
  • string(길이,'문자') : 길이만큼의 '문자'를 생성해준다.
  • catch(domain_error e) 한 다음에 무슨 예외 났는지 알고 싶으면 e.what()을 해준다.

  • 2.3. 4.3. Putting it all together

  • 분리 컴파일이랑 헤더 파일 이야기 나온다. 그냥 넘어가자.
  • 헤더 파일에서는 using namespace 이런거 쓰지 말고 그냥 풀 네임 써주자. std:vector 이렇게...
  • 두번 이상 포함되는 것을 방지 하기 위해, #ifndef, #define, #endif 이런거 해주자. 그러면서 전처리기 이야기가 나온다.
    ~cpp 
    #ifndef GURAD
    #define GUARD
    /* ...
       ...
       ... */
    #endif
     

  • 2.4. 4.4. Partitioning the grading program

  • 그냥 쪼개는 거다.

  • 2.5. 4.5. The revised grading program

  • 그냥 보기 좋아졌다는 얘기하고 있다.


  • Retrieved from http://wiki.zeropage.org/wiki.php/AcceleratedC++/Chapter4
    last modified 2021-02-07 05:22:25