Download presentation
Presentation is loading. Please wait.
1
프로그램과 데이터의 구조화 Organizing programs and data
Chapter 4 프로그램과 데이터의 구조화 Organizing programs and data
2
4 장에서는… 3.2.2절의 프로그램이 생각했던 것보다 긴가요? 하지만 프로그램이 커지면, 4장은,
vector나 string, sort 가 없었다면 훨씬 더 길어졌을 것입니다. 표준 라이브러리의 힘입니다. 프로그램이 커지면, 프로그램을 여러 독립적인 부분으로 분할하고 개별 관리 C++는 이런 방법을 제공: 함수와 자료구조 그리고 함수와 자료구조를 하나의 이름으로 묶는 클래스 제공 (클래스 만드는 법은 9장에서 공부) 4장은, 함수와 자료구조 (struct)를 이용하여 프로그램의 구조화 방법 프로그램을 여러 파일로 나누어 독립적으로 컴파일 하는 방법
3
4.1 함수를 이용한 구조화하기 만일 같은 계산을 하는 코드가 여러 곳에 있다면… 함수를 사용 성적 계산 방식이 바뀌면…
수십만 라인의 프로그램의 곳곳에 퍼져 있다면, 재앙… 함수를 사용 계산을 매번 반복하지 않고, 함수를 만들고, 그 함수를 이용한다 변경하고 싶을 때 바꾸기가 쉽다 int main( ) { … cout …<< 0.2*midterm+0.4*final+0.4*sum/count ; return 0; }
4
함수의 구조 함수는 이름, 매개변수, 몸체, 그리고 리턴 타입으로 구성
ret-type: 함수가 결과로 리턴하는 값의 타입 function-name: 함수의 이름 param-decls: 함수의 매개변수. 여러 개 일 때 콤마(,)로 구분 함수는 이름, 매개변수, 몸체, 그리고 리턴 타입으로 구성 함수 호출을 할 때, 인자는 개수와 타입이 일치해야 한다 값에 의한 호출 (call-by-value): 매개변수가 지역변수로 생성되고, 인자의 값이 1:1로 복사된다 ret-type function-name (param-decls) { // 함수 정의 // 함수 본체 }
5
함수 만들기: 성적 계산 함수 성적계산 grade() 함수 매개변수 (parameter) 인자 (argument)
double grade(double midterm, double final, double homework) { return 0.2*midterm+0.4*final+0.4*homework; } 매개변수를 지역변수로 보고, 인자 전달 과정을 지역변수 초기화로 볼 수 있음 int main( ) { … cout …<< grade(midterm, final, sum/count) ; return 0; } 인자 (argument) grade()함수 호출
6
함수 만들기: 중앙값 찾기 무엇을 받고 무엇을 리턴 해야 할까? : 함수 설계의 첫 단계
무엇을 받고 무엇을 리턴 해야 할까? : 함수 설계의 첫 단계 여러 값을 저장한 vector 객체를 받고, 중앙값을 리턴 // 이 함수를 호출하면 vector 인자 전체를 복사한다는 것에 유념 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=siz/2; return size%2==0? (vec[mid]+vec[mid-1])/2: vec[mid]; } 매개변수 복사에 시간이 소요됨 vector 에 담겨있는 값이 많을수록 많은 시간이 걸린다 sort()에 의해 값이 변경되므로, 시간이 걸리더라도 복사가 유용함
7
예외 처리 (Exception Handling)
오류가 났을 때 어떻게 대처해야 하나? 3.2.2절 (p.98)에서는 그곳에서 즉시 오류 처리했음 여기서는 예외상황(exception)을 throw(발생)시킴 domain_error 객체를 throw 했음: <stdexcept> 헤더에 정의됨 그러면 median()을 호출한 곳에서 throw된 것을 catch하여 적절히 오류 처리함 예외가 발생하면, throw 위치에서 실행을 멈추고, 예외상황 객체 (exception object)를 가지고, 나머지 부분을 건너뜀. 이 예외상황 객체는 호출한 곳에서 적절히 대처하는데 필요한 정보를 담고 있음
8
함수 만들기: 성적 계산식 다시 구현하기 또 다른 grade() 함수 몇 가지 생각해 볼 것은…
여러 과제 성적을 받아, 중앙값을 계산하고, 그것으로 최종 성적을 계산하는 기능 이전 grade()함수는 이미 계산된 중앙값 또는 평균값을 받았음 // 중간, 기말, 과제성적을 이용하여 전체 성적 계산 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)); } 몇 가지 생각해 볼 것은… 이전에 이미 사용한 grade()라는 함수 이름을 중복해 써도 되나? 세 번째 매개 변수에 있는 &는 무엇인가? grade()가 grade()를 호출했네? 순환 호출 (recursive call)인가?
9
레퍼런스(reference) 변수 레퍼런스 또는 참조 변수는 객체에 대한 또 다른 이름
vector<double> homework; vector<double>& hw=homework; // hw는 homework에 대한 별칭 vector<double>& hw1 = hw; // hw1은 ” const vector<double>& chw=homework; // chw, chw1는 homework에 대한 const vector<double>& chw1=chw; // 읽기전용 별칭 참조에 의한 호출 (call-by-reference) 함수 매개변수에 레퍼런스 &를 넣으면, 해당 인자를 복사하지 않으므로, 복사에 따른 간접비용 없음 const를 넣으면 인자의 값을 바꾸지 않겠다고 구현시스템에 알려줌
10
call by reference 참조에 의한 호출 예. swap() 함수 예 void foo(int & r) { r = 10;
} void main() int x = 20; foo(x); cout << x; swap() 함수 예 // 옳게 작동 안 함 void swap(int a, int b) { int tmp; tmp=a; a=b; b=tmp; } // 옳게 작동 (레퍼런스 버전) void swap(int& a, int& b) { int tmp; tmp=a; a=b; b=tmp; }
11
함수오버로딩(overloading) 오버로딩: 함수 오버로딩과 연산자 오버로딩 함수 오버로딩
여러 함수가 같은 이름을 가질 수 있다. 예. 두 개의 서로 다른 grade() 함수 double grade(double midterm, double final, double homework) { … } double grade(double midterm, double final, const vector<double>& hw) 호출되었을 때, 컴파일러는 어떤 것을 기동할지 어떻게 알지?
12
함수 오버로딩 예 함수 오버로딩에서는 두 함수의 매개변수의 순서, 개수 또는 타입이 달라야 한다.
void foo(int) { ... }; // 1 void foo(double) { ... }; // 2 void foo(int, int) { ... }; // 3 void foo(int, double) { ... }; // 4 int foo(int); // Compiler Error! void main() { foo(3); // Calls 1 foo(5, 3.14); // Calls 4 }
13
과제 성적 읽기 읽어 들이는 기능을 함수로 만들어 보면, 함수의 골격은 아래와 같이… 리턴해야 할 것이 두 가지
읽은 값을 리턴 해야 하고, 또한 제대로 읽었는지 (오류 여부) 리턴 해야 함. 함수는 하나만 리턴 할 수 있다 레퍼런스 매개 변수를 이용하여 읽은 값을 리턴 하자! 함수의 골격은 아래와 같이… // 입력 스트림으로부터 vector<double>에 과제 성적을 읽어 들임 istream& read_hw( istream& in, vector<double>& hw) { // 채워야 할 곳… return in; } int main() { vector<double> homework; if( read_hw(cin, homework) ) { /*… */ } }
14
과제 성적 읽기: 함수 구현 처음 시도해 본 코드. 아래와 같이 하면 될까? 문제점은
hw가 깨끗하다는 보장이 있나? (hw는 함수를 호출한 곳에서 넘겨준 것) 이전에 사용되어 벡터에 성적이 담겨있다면? 만일 입력 도중에 오류가 난다면? 이후에 다른 함수에서 일어나는 입력 기능이 제대로 될까? 입력 실패: (1) EOF 만났을 때 (2) 성적이 아닌 값을 입력되었을 때 in이 깨끗할까? 만일 in이 이전에 사용되어 무엇이 남아 있다면? 입력 실패 후 in의 내부에 있는 오류 상태 리셋 필요 다음 학생 성적 입력 전에 정리 필요 istream& read_hw(istream& in, vector<double>& hw) { double x; while( in>>x ) hw.push_back(x); return in; }
15
과제 성적 읽기: 함수 구현 read_hw() 완전한 코드
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; hw벡터: 빈 벡터로 시작하도록 함 istream 객체: 오류상태 리셋, 입력 작업을 계속하도록 함
16
함수 매개변수 양식 세가지 어떤 경우에 무엇을 쓸지 판단해야 한다. call-by-value:
복사하면 간접비용 든다. 하지만 그래야 할 경우가 있다. call-by-reference: 이 함수에서 인자의 값을 변경할 의도가 있다. 인자는 lvalue(변수)이어야 함 const인 call-by-reference: 이 함수에서 인자의 값을 변경하지 않고, 읽기만 한다. 어떤 경우에 무엇을 쓸지 판단해야 한다. double median( vector<double> vec) { …} double grade(double midterm, double final, double homework) { …} double grade(double midterm, double final, const vector<double>& hw) { …} istream& read_hw( istream& in, vector<double>& hw) { …}
17
함수를 이용한 성적계산 - 전체프로그램 #include <algorithm>
#include <iomanip> #include <iostream> #include <stdexcept> #include <string> #include <vector> using namespace std; // compute the median of a `vector<double>' // note that calling this function copies // the entire argument `vector' double median(vector<double> vec) { typedef std::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]; }
18
// compute a student's overall grade from midterm and
// final exam grades and homework grade double grade(double midterm, double final, double homework) { return 0.2 * midterm * final * homework; } // compute a student's overall grade from midterm and final // exam grades and vector of homework grades. // this function does not copy its argument, because `median‘ // does so for us. 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));
19
// read homework grades
// from an input stream into a `vector<double>' istream& read_hw(istream& in, vector<double>& hw) { if (in) { // get rid of previous contents hw.clear(); double x; while (in >> x) hw.push_back(x); // clear the stream so that input will work for next student in.clear(); } return in;
20
int main() { // ask for and read the student's name cout << "Please enter your first name: "; string name; cin >> name; cout << "Hello, " << name << "!" << endl; // ask for and read the midterm and final grades cout << "Please enter your midterm and final exam grades: "; double midterm, final; cin >> midterm >> final; // ask for the homework grades cout << "Enter all your homework grades, followed by end-of-file: "; vector<double> homework; // read the homework grades read_hw(cin, homework); // compute and generate the final grade, if possible 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;
21
프로그램 뜯어 보면 함수 호출 순서를 잘 살펴보자. 누가 누구에게 일을 시키는지 잘 따져봐야 합니다.
main()이 read_hw()을 호출하여 데이터 읽어들이고, grade()를 호출하고, grade()는 인자 위치에서 median()호출하고 이어 (이름은 같지만 매개변수가 다른) grade()를 호출
22
try와 catch에 의한 오류 처리 int main() { … 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; <stdexcept> header exception logic_error domain_error invalid_argument length_error out_of_range runtime_error range_error overflow_error underflow_error 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)); }
23
try와 catch에 의한 오류 처리 A를 try(시도)하다가 try { A } catch (domain_error) { B
오류(예외)가 발생하면, 오류 발생지점에서 예외상황 객체를 throw한다 catch가 그것을 받아 B를 실행하여 오류 처리한다 (대부분의 경우, A에서 호출되는 함수 속에서 throw함) 예외상황이 발생한 그 다음의 코드는 실행이 안 된다. 오류가 발생하지 않으면, catch의 B를 무시하고, 다음 문으로 이동한다. try { A } catch (domain_error) { B }
24
try와 catch에 의한 오류 처리 - 부적절
아래와 같이 하면 어떤 문제가 숨어 있나? 문제점.. << 연산자가 왼쪽에서 오른쪽 순서로 실행하지만, 피연산자들을 그 순서대로 계산(평가)하지는 않는다. 출력스트림의 정밀도를 3으로 설정한 후, 원래 값으로 되돌리는 두번째 호출을 실행하지 못할 수도 있다. 전적으로 구현시스템에 의존 try { streamsize prec=cout.precision(); cout<< “Your grade is “ <<setprecision(3) <<grade(midterm,final,homework)<<setprecision(prec); } catch (domain_error) { … }
25
예외 처리 예외란 우리가 당연하다고 가정한 상황이 거짓이 되는 경우를 말한다.
대표적인 예외의 경우 컴퓨터에 사용 가능한 메모리가 부족한 경우 당연히 있을 것이라 생각한 파일이 없는 경우 사용자가 잘못된 값을 입력하는 경우 예외 처리란 이러한 상황이 발생한 경우에도 프로그램이 올바르게 동작할 수 있도록 처리하는 것을 말한다. 예) 컴퓨터에 사용 가능한 메모리가 부족해서 동적 메모리 할당이 실패한 경우에 예외 처리를 잘 하지 못한 프로그램은 비정상 종료할 수 있다. 반면에 예외 처리를 잘 한 프로그램은 사용자가 작업 중이던 정보를 잘 보관한 후에 정상적으로 종료할 수 있다.
26
예외처리 메커니즘 C++ allows you to throw value of any data type,
throw "Something went wrong"; throw domain_error("reason comes here"); C++ allows you to throw value of any data type, including standard library's exception classes and your own exception classes.
27
예외 처리 예 예외가 발생하면 try 블록의 나머지 부분 무시 예외 처리 후 catch 블록 이후부터 실행
int main(void) { int a, b; cout<<"두 개의 숫자 입력 : "; cin>>a>>b; try{ cout<<"try block start"<<endl; if(b==0) throw b; cout<<"a/b의 몫 : "<<a/b<<endl; cout<<"a/b의 나머지 : "<<a%b<<endl; cout<<"try block end"<<endl; } catch(int exception){ cout<<"catch block start"<<endl; cout<<exception<<" 입력."<<endl; cout<<"입력오류! 다시 실행 하세요."<<endl; cout<<"THANK YOU!"<<endl; return 0; 예외가 발생하면 try 블록의 나머지 부분 무시 예외 처리 후 catch 블록 이후부터 실행
28
4.2 데이터 구조화하기 지금까지는 한 학생 성적 계산하는 프로그램 실제에서는 여러 학생 성적 처리 필요
그냥 계산기로 처리하는 게 더 간편할 듯 실제에서는 여러 학생 성적 처리 필요 입력 Smith Carpenter … 출력 Carpenter 86.8 Smith 문제가 복잡해 졌다: 각 학생들의 이름과 성적들을 함께 저장해야 한다 학생 이름 순서대로 출력 점수 위치를 맞추어서 출력
29
데이터 구조화하기 여러 학생의 정보를 저장하기 위한 자료구조 필요
한 학생은 <이름, 중간, 기말, 과제 성적>이 있다 한 학생의 과제 성적을 저장할 때, 과제성적이 모두 double이므로 vector<double> 타입이면 OK 이제는 vector<???> ???에 {이름, 중간, 기말, 과제 성적}을 담는 무엇인가가 필요 게다가 여러 학생의 데이터를 저장하는 데에도 vector 필요
30
학생의 데이터를 모두 함께 저장하기 학생 데이터를 위한 자료구조 struct Student_info { string name;
double midterm, final; vector<double> homework; }; Student_info는 struct 타입이다 이 타입은 네 개의 데이터 멤버를 가지고 있음 타입이므로 이런 것이 된다. Student_info s; // 한 명의 학생 정보 vector<Student_info> students; // 여러 명의 학생 정보
31
학생레코드 관리하기 한 학생의 레코드를 읽어 들이려면, 최종 성적을 계산하려면,
istream& read(istream& is, Student_info& s) { is>>s.name>>s.midterm>>s.final; read_hw(is, s.homework); return is; } 매개변수를 Student_info& s로 한 이유는? const Student_info& s로 한 이유는? double grade( const Student_info& s) { return grade(s.midterm, s.final, s.homework); }
32
학생레코드 관리하기-이름으로 정렬 이름 순으로 정렬해야 하는데, 이렇게 하면 될까?
sort(students.begin(), students.end()); // 안된다. 왜? // 도대체 무엇을 기준으로 정렬하란 말인가? 이 상황에서는 비교할 방법을 알려줘야 한다. 이름 순으로 정렬한다면, 우선 compare() 함수를 정의한다. 라이브러리 함수인 sort()의 세 번째 인자로 전달해서 호출한다 sort( students.begin(), students.end(), compare); bool compare(const Student_info& x, const Student_info& y) { return x.name < y.name; }
33
보고서 양식 출력하기 몇 군데 주목해 볼만한 곳은, 위치 맞추어 출력하기 (가장 긴 이름을 기준으로)
Carpenter 86.8 Smith 오류 처리새로운 형태의 catch int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; // read and store all the records, and find the length of the longest name while (read(cin, record)) { maxlen = max(maxlen, record.name.size()); students.push_back(record); }
34
// alphabetize the records
sort(students.begin(), students.end(), compare); for (vector<Student_info>::size_type i = 0; i != students.size(); ++i) { // write the name, // padded on the right to `maxlen' `+' `1‘ characters cout << students[i].name << string(maxlen students[i].name.size(), ' '); // compute and write the grade 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(); // call what() member function of domain_error } cout << endl; return 0;
35
독립 컴파일 (separate compile)
프로그램이 커지면 독립 컴파일이 필수적이다. 관련있는 함수와 자료구조를 모아 독립된 파일로 만든다 ___.cpp와 ___.h 파일 각 파일을 독립적으로 컴파일 한 후, 목적 파일을 링크하여 하나의 실행 파일을 만든다 예. median() 함수를 독립된 파일로 해 보자. 파일 이름은 median.cpp와 median.h (함수 이름을 따는 것이 관례)
36
중앙값 계산 소스파일 - median.cpp median()이 필요로 하는 헤더를 모두 include 컴파일러에게
// source file for the `median' function #include <algorithm> // to get the declaration of `sort' #include <stdexcept> // to get the declaration of `domain_error' #include <vector> // to get the declaration of `vector' using std::domain_error; using std::sort; using std::vector; #include "median.h” // compute the median of a `vector<double>' double median(vector<double> vec) { … } 컴파일러에게 이 위치에 median.h의 내용을 복사해서 넣어라는 의미 C++ 구현시스템이 제공하는 표준 헤더 < > 우리가 만든 헤더 ””
37
헤더 파일 median.h 헤더 파일에는 함수의 선언문 (프로토타입)만 적는다 헤더 파일에는 꼭 필요한 이름만 선언한다
using 사용 자제해야 한다. 왜? – 함수에서 using을 사용하지 않을수도 있음. 초기 버전 #ifndef 지시문을 사용해서 정의해야 한번 이상 include되어도 안전!! 이렇게 하지 않으면 위험한가? 왜? 최종 버전 #include <vector> double median(std::vector<double>); // 함수 선언부에서 vector를 사용하므로 #ifndef GUARD_median_h #define GUARD_median_h #include <vector> double median(std::vector<double>); #endif // 전처리기 변수
38
계속해 보자. 이제 무엇을 묶을까? Student_info와 관련 함수를 묶자 무엇이 남아 있나?
함수: compare(), read(), read_hw(), 오버로드 된 grade()들 이들 각각을 하나의 파일로 할까? 아니면 관련 있는 것들을 모아 하나로 할까? Student_info와 관련 함수를 묶자 Student_info compare(), read(), read_hw(),
39
Student_info 헤더 파일- Student_info.h
#ifndef GUARD_Student_info #define GUARD_Student_info // `Student_info.h' header file #include <iostream> #include <string> #include <vector> struct Student_info { std::string name; double midterm, final; std::vector<double> homework; }; bool compare(const Student_info&, const Student_info&); std::istream& read(std::istream&, Student_info&); std::istream& read_hw(std::istream&, std::vector<double>&); #endif
40
Student_info 소스파일 - Student_info.cpp
// source file for `Student_info'-related functions #include "Student_info.h" using std::istream; using std::vector; bool compare(const Student_info& x, const Student_info& y) { return x.name < y.name; } istream& read(istream& is, Student_info& s) … istream& read_hw(istream& in, vector<double>& hw)
41
따져 보자 이들을 하나로 묶은 것은 잘한 일인가? .cpp에 .h를 include하면, 논리적으로 관련성이 있나?
compare()는 어떤가? .cpp에 .h를 include하면, .cpp 파일 안에 함수의 선언 (declaration)과 정의 (definition)를 모두 갖게 되는데, 적절한 일인가? 이 구조체를 사용할 때에만 사용하는 함수들이므로 구조체 정의와 함께 모아 놓는 것이 좋다 중복은 문제 될 것이 없다. 사실 좋은 아이디어다. 왜냐하면 컴파일러는 선언부와 정의부가 일치하는지 체크할 수 있기 때문. 불완전하다.
42
성적 계산 프로그램 – grade.h #ifndef GUARD_grade_h #define GUARD_grade_h
#include <vector> #include "Student_info.h" double grade(double, double, double); double grade(double, double, const std::vector<double>&); double grade(const Student_info&); #endif 오버로드 된 grade() 함수를 한 눈 에 볼 수 있어서 좋다. 왜 Student_info.h를 include했을까?
43
성적 계산 프로그램 – grade.cpp #include <stdexcept>
#include <vector> #include "grade.h" #include "median.h" #include "Student_info.h" using std::domain_error; using std::vector; double grade(double midterm, double final, double homework) { … } double grade(double midterm, double final, const vector<double>& hw) double grade(const Student_info& s)
44
이제 main()을 완성하면 끝– main.cpp
#include <algorithm> #include <iomanip> #include <ios> #include <iostream> #include <stdexcept> #include <string> #include <vector> #include "grade.h" #include "Student_info.h" int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; // the length of the longest name // read and store all the students' data. // Invariant: `students' contains all the student records // read so far, `maxlen' contains the length of the longest name in `students‘ while (read(cin, record)) { // find length of longest name maxlen = max(maxlen, record.name.size()); students.push_back(record); } 주석문 다는 방법에 주목해 보자.
45
// alphabetize the student records
sort(students.begin(), students.end(), compare); // write the names and grades for (vector<Student_info>::size_type i = 0; i != students.size(); ++i) { // write the name, padded on the right to `maxlen' `+' `1' characters cout << students[i].name << string(maxlen students[i].name.size(), ' '); // compute and write the grade 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;
Similar presentations