|| ["AcceleratedC++/Chapter3"] || ["AcceleratedC++/Chapter5"] || = 목차 = ||[[TableOfContents]]|| = Chapter 4 Organizing programs and data = * 어떤 종류의 문제를 푼다. * 다른 것들과 독립적이다. * 이름을 가지고 있어야 한다. 3장까지 봤던 것은 첫번째것만 있고, 나머지 것은 없다. 프로그램이 작을때는 별로 상관없지만, 커지기 시작하면서부터 2,3번째 것이 결여되면, 나중엔 제어가 불가능해진다. C++에서는 다른 언어와 마찬가지로 함수 + 자료구조를 제공함으로써, 프로그램을 구조화시킬수 있는 방법을 제공한다. 또한 함수 + 자료구조를 묶어서 가지고 놀 수 있는 class라는 도구를 제공해준다.(Chapter9부터 자세히 살펴본다.) 그러므로 이번장부터는 프로그램을 나눠서, 서로 다른 파일에 저장, 컴파일하는 법과, 나중에 합치는 법 등을 공부할 것이다. == 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라 한다. === 4.1.1. Finding medians === * 앞에서 우리는 vector에 들어가 있는 값에서 중간값 찾는 걸 했었다. Chapter8에서는 vector에 들어가 있는 type에 관계없이 작동하게 하는 법을 배울 것이다. 지금은 vector로만 한정짓자. median 구하는 루틴을 함수로 빼보자. {{{~cpp double median(vector vec) { typedef vector::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이면 그냥 프로그램을 종료시켜버렸지만, 여기서는 예외처리라는 신기술을 사용했다. 이 방법은 여기서 끝내지 않고 다음 부분으로 넘어간다. 를 포함시켜 주면 된다. * 또한, 아까 함수 호출하면서 parameter로 넘겨줄때에는 그 값을 복사를 한다고 했다. 저렇게 함수를 호출함으로써, 원래 vector를 손상시키지 않고, 복사본 vector에서 sort를 해서 작업을 처리해 줄수가 있다. 비록 시간이 좀 더 걸리긴 하지만.. === 4.1.2 Reimplementing out grading policy === * 이쯤 와서 함수의 유용함을 알았다면, 우리의 성적 매기는 방법을 따로 함수로 뽑아내고 싶을 것이다. 다음 예제를 보자. {{{~cpp double grade(double midterm, double final, const vector& hw) { if(hw.empty()) // 책에서는 hw.size()==0 이라고 되어 있지만 // empty()메소드를 호출하는 것이 더 효율적이라고 하더군요. throw domain_error("student has done no homework"); return grade(midterm, final, median(hw)); } }}} * 여기서 눈여겨볼 세가지 사항이 있다. * const vector& hw : 이것을 우리는 double형 const vector로의 참조라고 부른다. reference라는 것은 어떠한 객체의 또다른 이름을 말한다. 또한 const를 씀으로써, 저 객체를 변경하지 않는다는 것을 보장해준다. 또한 우리는 reference를 씀으로써, 그 parameter를 복사하지 않는다. 즉 parameter가 커다란 객체일때, 그것을 복사함으로써 생기는 overhead를 없앨수 있는 것이다. {{{~cpp vector homework; vector& hw = homework; // hw는 homework와 같다. 즉, 이름은 다르지만, hw를 고치면 homework도 같이 고쳐진다. 왜냐? 같으니까 }}} * grade() function : 우리는 아까 grade라는 함수를 만들었었다. 그런데 이번에 이름은 같으면서 parameter는 조금 다른 grade()를 또 만들었다. 이런게 가능한가? 가능하다. 이러한 것을 함수의 overloading이라고 한다. 함수 호출할때 어떤게 호출될까는 따라가는 parameter lists에 의해 결정된다. * exception : 사용자에게 무엇이 잘못되었는가를 알려주는 exception을 던져준다. === 4.1.3 Reading homework grades === * 이제 우리가 풀어야 할 문제는, 숙제의 등급을 vector로 읽어들이는 것이다. 여기에는 두가지의 문제점이 있다. 바로 리턴값이 두개여야 한다는 점이다. 하나는 읽어들인 등급들이고, 또 다른 하나는 그것이 성공했나 하는가이다. 하나의 대안이 있다. 바로 리턴하고자 하는 값을 리턴하지 말고, 그것을 reference로 넘겨서 변경해주는 법이다. const를 붙이지 않은 reference는 흔히 그 값을 변경할때 쓰인다. reference로 넘어가는 값을 변경해야 하므로 어떤 식(expression)이 reference로 넘어가면 안된다.(lvalue가 아니라고도 한다. lvalue란 임시적인 객체가 아닌 객체를 말한다.) {{{~cpp istream& read_hw(istream& in, vector& 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& hw) { if(in) { hw.clear(); double x; while(in >> x) hw.push_back(x); in.clear(); } return in; } }}} DeleteMe) 두번째가 잘 이해가 안가네.. 누가 잘 아시는 분은 명쾌한 설명 부탁드립니다. === 4.1.4 Three kinds of function parameters === * 지금까지 만든 세개의 함수 median, grade, read_hw 를 살펴보자. * median 함수를 보면, vector 파라메터가 참조로 넘어가지 않는다. 학생수가 많아질수록 매우 큰 객체가 될텐데 낭비가 아닌가? 하고 생각할수도 있지만, 어쩔수 없다. 함수 내부에서 소팅을 해버리기 때문에 참조로 넘기면, 컨테이너의 상태가 변해버린다. * grade 함수를 보면, vector는 const 참조로 넘기고, double은 그렇지 않다. int나 double같은 기본형은 크기가 작기 때문에, 그냥 복사로 넘겨도 충분히 빠르다. 뭐 값을 변경해야 할때라면 참조로 넘겨야겠지만... const 참조는 가장 일반적인 전달방식이다. * read_hw 함수를 보면, 복사는 하지 않고, 그 값을 변경하기 위해 참조만 썼다. * const가 아닌 참조형 파라메터는 lvalue여야만 한다. === 4.1.5 Using functions to calculate a student's grade === * 소스를 보면 새로운게 눈에 띈다. 바로 try이다. 다음과 같이 쓴다. {{{~cpp try { // 하다가 예외 발생하면, 중단하고 } catch(domain_error) { // 이리로 온다. 만약에 try 안에서 예외 안 뜨면 catch 안은 수행안한다. } }}} ---- 여기까지 최종소스 {{{~cpp #include #include #include #include #include #include #include using namespace std; double grade(double midterm, double final, double homework); double grade(double midterm, double final, const vector& hw); double median(vector vec); istream& read_hw(istream& in, vector& 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 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& hw) { if(hw.size() == 0) throw domain_error("Student has done no homework"); return grade(midterm, final, median(hw)); } double median(vector vec) { typedef vector::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& hw) { if(in) { hw.clear(); double x; while(in >> x) hw.push_back(x); in.clear(); } return in; } }}} == 4.2 Organizing data == * 여태까지는 한 학생의 데이터만 가지고 놀았다. 하지만 성적 처리 프로그램은, 모든 학생의 데이터를 대상으로 한다. 이에 다시 체계적인 데이터 구조가 필요하다. === 4.2.1 Keeping all of a student's data together === * 학생의 데이터를 묶어 보자. 여기서 struct란 새로운 구문이 나온다. {}안에 멤버로 넣고 싶은 변수들을 쫙 써주면 된다. 접근할라면 . 써주고 변수 쓰면 된다. {{{~cpp struct Student_info { string name; double midterm, final; vector 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); }}} === 4.2.3. Generating the report === * max(같은 타입의 파라메터 두개) : 뭐 큰값을 리턴해 주겠지 * string(길이,'문자') : 길이만큼의 '문자'를 생성해준다. * catch(domain_error e) 한 다음에 무슨 예외 났는지 알고 싶으면 e.what()을 해준다. == 4.3. Putting it all together == * 분리 컴파일이랑 헤더 파일 이야기 나온다. 그냥 넘어가자. * 헤더 파일에서는 using namespace 이런거 쓰지 말고 그냥 풀 네임 써주자. std:vector 이렇게... * 두번 이상 포함되는 것을 방지 하기 위해, #ifndef, #define, #endif 이런거 해주자. 그러면서 전처리기 이야기가 나온다. {{{~cpp #ifndef GURAD #define GUARD /* ... ... ... */ #endif }}} == 4.4. Partitioning the grading program == * 그냥 쪼개는 거다. == 4.5. The revised grading program == * 그냥 보기 좋아졌다는 얘기하고 있다. ------ ["AcceleratedC++"]