AcceleratedC++/Chapter8 | AcceleratedC++/Chapter10 |
1. Chapter 9 Defining new types ¶
기본 타입 | char, int, double 등 기본언어의 일부 |
클래스 타입 | string, vector, istream 등 기본언어를 가지고 구현된 타입 |
1.1. 9.1 Student_info revisited ¶
4.2.1절 Student_info 구조체를 다루는 함수를 작성하고, 이를 한개의 헤더파일로 통합을 하는 것은 일관된 방법을 제공하지 않기 때문에 문제가 발생한다.
본 장에서는 기존의 구조체를 확장하여 함수를 작성하고 이런식의 문제를 해결할 수 있는 방법을 알려준다.
본 장에서는 기존의 구조체를 확장하여 함수를 작성하고 이런식의 문제를 해결할 수 있는 방법을 알려준다.
1.2. 9.2 Class types ¶
~cpp struct Student_info { std::string name; double midterm, final; std::vector<double> homework; };
프로그래머는 구조체를 다루기 위해서 구조체의 각 멤버를 다루는 함수를 이용해야한다. (Student_info 를 인자로 갖는 함수는 없기 때문에)
그러나 프로그래머는 직접적으로 이런 구조를 다루기 보다는 세부적인 구현을 감추고 싶을 수 있다.
왜 using-선언문을 사용하지 않는가?
string, vector 와 같은 것들은 Student_info의 내부 구현시에 필요한 사항이기 때문에 Student_info를 사용하는 프로그램의 또다른 프로그래머에게까지 vector, string을 std::에 존재하는 것으로 쓰기를 강요하는 것은 옳지않다.
그러나 프로그래머는 직접적으로 이런 구조를 다루기 보다는 세부적인 구현을 감추고 싶을 수 있다.
string, vector 와 같은 것들은 Student_info의 내부 구현시에 필요한 사항이기 때문에 Student_info를 사용하는 프로그램의 또다른 프로그래머에게까지 vector, string을 std::에 존재하는 것으로 쓰기를 강요하는 것은 옳지않다.
1.2.1. 9.2.1 멤버 함수 ¶
상기의 구조체안에 Student_info 를 다룰 수 있는 멤버함수를 추가한 것
~cpp struct Student_info { std::string name; double midterm, final; std::vector<double> homework; std::istream& read(std::istream&); //입력 스트림으로 부터 입력을 받아서 4개의 멤버변수를 초기화한다. double grade() const; //내부 멤버변수를 활용하여 점수를 계산하고 double 형으로 리턴한다. //함수 명의 뒤에 const를 붙이면 멤버 변수의 변형을 할 수 없는 함수가 된다. (구조적으로 좋다) };
- s:Student_info 라면 멤버함수를 호출하기 위해서는 s.read(cin), s.grade() 와 같이 함수를 사용하면서 그 함수가 속해있는 객체를 지정해야함. 암묵적으로 특정객체가 그 함수의 인자로 전달되어 그 객체의 데이터로 접근이 가능하게 된다.
~cpp istream & Student_info::read(istream& in) { in>>name>>midterm>>final; read_hw(in, homework); return in; }
이 함수에서 알아야할 3가지
- 함수의 이름이 Student_info::read이다
- Student_info의 멤버함수이므로 Student_info 객체를 정의할 필요도 인자로 넘길 필요도 없다.
- 객체 내부의 데이터를 직접적으로 접근 가능하다. 예를 들어서 s.name으로 접근할 필요없다.
- 함수의 이름이 Student_info::read이다
~cpp double Student_info::grade() const { return ::grade(midterm, final, homework); }::를 사용함으로써 호출되는 grade를 객체의 멤버함수가 아니라 전역 grade의 형으로 사용하는 것이 가능하다.
객체에서는 const 객체 내부의 데이터들을 const형으로 다룰 수는 없지만, 멤버함수를 const 멤버함수로 만듦으로써 변형에 제한을 줄 수 있다.
- const 객체에 대해서는 const 멤버함수가 아닌 함수는 호출 할 수 없다.
1.2.2. 9.2.2 비멤버 함수들 ¶
이전에 사용되었던 compare와 같은 함수를 어떤식으로 정의해야 할 것인가?
compare함수는 동일한 형의 2개의 Student_info를 받아서 서로를 비교하는 역할을 한다. 이런함수를 처리하는 일반적인 방법이 있는데, 9.5, 11.2.4, 11.3.2, 12.5, 13.2.1 에서 배우게됨.
friend 함수를 의미하는 것
compare함수는 동일한 형의 2개의 Student_info를 받아서 서로를 비교하는 역할을 한다. 이런함수를 처리하는 일반적인 방법이 있는데, 9.5, 11.2.4, 11.3.2, 12.5, 13.2.1 에서 배우게됨.
friend 함수를 의미하는 것
1.3. 9.3 Protection ¶
read, grade를 정의함으로써 Student_info에 직접적인 접근을 하지 않고도 데이터를 다룰 수 있었다.
그런데 더 나가서 이제는 아예 이 객체의 내부멤버를 프로그램의 다른 부분에서 다루는 것을 금지시키고 싶다면?
private 레이블 뒤에 존재하는 변수, 함수들은 비 멤버함수에서 접근할 수 없다. 반면 public은 접근이 가능하다.
※ 이것을 보면 C++에서 클래스의 구현은 C에서 구조체의 구현을 확장시킨 것으로 볼 수 있는 증거가 된다.
다음은 각기 동일한 표현식을 나타낸 2가지의 경우이다.
그런데 더 나가서 이제는 아예 이 객체의 내부멤버를 프로그램의 다른 부분에서 다루는 것을 금지시키고 싶다면?
~cpp class Student_info { public: //interface is here double grade() const; std::istream& read(std::istream&); private: //implementation is here std::string name; double midterm, final; std::vector<double> homework; };struct 키워드 대신 class 키워드 사용. 보호레이블(protection label) 사용. 레이블은 순서없이 여러분 중복으로 나와도 무관함.
private 레이블 뒤에 존재하는 변수, 함수들은 비 멤버함수에서 접근할 수 없다. 반면 public은 접근이 가능하다.
class 키워드를 사용한 클래스 | 기본 보호모드가 private 으로 동작한다. |
struct 키워드를 사용한 클래스 | 기본 보호모드가 public 으로 동작한다. |
~cpp class Student_info { public: double grade() const; }; struct Student_info { dobule grade() const; };
~cpp class Student_info { std::string name; public: double grade() const; }; struct Student_info { private: std::string name; public: dobule grade() const; };일반적으로 자료구조가 간단할 때에는 struct를 이용한다. 그러나 2가지 키워드의 사용의 차이는 존재하지 않는다. 단지 문서화를 어떻게 하느냐에 의해 차이가 생길 뿐이다.
1.3.1. 9.3.1 접근 함수 ¶
~cpp class Student_info { public: double grade() const; std::istream& read(std::istream&); std::string name() const {return n;} private: std::string n; double midterm, final; std::vector<double> homework; };name 멤버 변수에 직접적으로 접근하는 대신에 name()함수를 만들어서 접근하도록 만들었다. const 함수이므로 멤버변수의 변경을 불허한다. 리턴값이 복사된 것이기 때문에 변경을 하더라도 멤버변수에 영향을 주지는 않는다.
name 멤버 함수와 같이 클래스 내부에 정의를 하는 것은 inline으로 함수를 정의하는 것도 동일한 효과를 가진다. 이렇게 멤버변수에 접근을 위해서 만들어지는 함수를 통칭 접근함수(accessor function)이라고 부른다.
접근함수는 캡슐화의 기본개념이 반하는 것으로 다른 인터페이스 일부로서만 사용해야한다.
~cpp bool compare(const Student_info& x, const Student_info& y) { return x.name() < y.name(); }인자로 받은 변수들을 직접 접근하지 않고 그 멤버함수로 접근한다.
1.3.2. 9.3.2 비어있는지 테스트 ¶
만약 s:Student_info 에 read(istream&)을 통해서 데이터를 입력하지 않고서 s.grade()를 사용한다면 프로그램을 에러를 낼 것이다.
~cpp class Student_info { public: double grade() const; std::istream& read(std::istream&); std::string name() const {return n;} bool valid() const { return !homework.empty(); } // 사용자에게 그 객체가 유효한 데이터를 가졌는지를 알려준다. private: std::string n; double midterm, final; std::vector<double> homework; };상기와 같은 방식으로 통해서 grade를 호출하기 전에 객체의 유효성을 검사한다면 에러를 없애는 것이 가능하다.
1.4. 9.4 The Student_info class ¶
~cpp class Student_info { public: double grade() const; std::istream& read(std::istream&); std::string name() const {return n;} bool valid() const { return !homework.empty(); } // 사용자에게 그 객체가 유효한 데이터를 가졌는지를 알려준다. private: std::string n; double midterm, final; std::vector<double> homework; }; bool compare(const Student_info& x, const Student& y);동일한 일을 하지만 프로그래머는 실제로 객체의 내부의 구현을 감추는 것이 가능하다.
'''※ 이런 식으로 헤더파일을 제공하고 실제의 구현된 소스파일은 이미 컴파일된 obj파일로 제공한다면 실제로 판매되는 컴파일러라도 그 컴파일러가 제공하는 함수의 실제구현은 볼 수가 없다.
헤더파일은 단지 컴파일러가 컴파일을 할때 이런 함수가 존재한다고 가정만 하고, 실제로 그 함수의 코드가 존재하는 것을 확인 할 수 잇는 것은 모든 관련 오브젝트 파일을 링크한뒤에 호출할때 없다는 것을 확인할 수 밖엔 없다.'''
1.5. 9.5 Constructors ¶
생성자(Constructor)
객체가 어떤게 초기화되는지를 정의하는 특별한 멤버 함수이다. 명시적인 호출을 지원하지 않으며, 오로지 객체가 생성될 때에만 실행된다.
만약 프로그래머가 생성자를 명시적으로 지정하지 않으면 컴파일러는 Default Constructor를 만들어주는데, 이 기본 생성자는 명시적인 초기화를 지원하지 않으며, 오로지 동일한 형의 다른 객체로의 초기화만을 제공한다.
생성자는 클래스의 이름과 동일한 이름으로 함수의 이름을 갖는다. 또한 리턴타입이 없다.
생성자의 규칙
객체가 어떤게 초기화되는지를 정의하는 특별한 멤버 함수이다. 명시적인 호출을 지원하지 않으며, 오로지 객체가 생성될 때에만 실행된다.
만약 프로그래머가 생성자를 명시적으로 지정하지 않으면 컴파일러는 Default Constructor를 만들어주는데, 이 기본 생성자는 명시적인 초기화를 지원하지 않으며, 오로지 동일한 형의 다른 객체로의 초기화만을 제공한다.
생성자는 클래스의 이름과 동일한 이름으로 함수의 이름을 갖는다. 또한 리턴타입이 없다.
생성자의 규칙
- 하나이상의 생성자가 존재한다면 현재 상황에 맞는 생성자를 선택하여 실행된다.
- 내장 타입 객체인 경우 값지정 초기화시에는 0으로 디폴트 초기화 시에는 정의되지 않은 값으로 세팅된다.
- 생성자를 갖지 않는 타입인 경우. 객체를 어떤 식으로 초기화했는 가에 따라서 값지정, 디폴트 초기화를 재귀적으로 행함.
~cpp class Student_info { public: Student_info(); Student_info(std::istream&); };
1.5.1. 9.5.1 기본 생성자 ¶
Default Constructor?
아무런 인자를 갖지 않는 생성자. 기본 생성자는 보통 그 객체의 데이터 멤버들을 적당히 초기화시키기만 함.
생성자 초기 설정자(constructor initializer)
상기에서 보듯이 : { 사이에 생성자를 명시적으로 사용함으로써 객체를 초기화하는 작업을 하는 중에 0으로 초기화 함으로써 오버헤드를 줄이는 것이 가능하다.
※만약 객체의 멤버로 const 멤버가 존재한다면 그 멤버는 반드시 이 표현으로만 초기화가 가능하다. 객체가 초기화 되는 시기는 생성자 코드로 진입되기 이전인데 일단 초기화된 const 멤버는 컴파일러가 그 값의 수정을 허용하지 않기 때문이다.
아무런 인자를 갖지 않는 생성자. 기본 생성자는 보통 그 객체의 데이터 멤버들을 적당히 초기화시키기만 함.
~cpp Student_info::Student_info():midterm(0). final(0) {} // n, homework 의 경우 STL에서 제공되는 디폴트 생성자가 재귀적으로 호출
객체의 생성 순서 |
* 구현 시스템이 객체를 담을만한 메모리를 할당함. * 초기 설정자 목록에 의해 객체들을 초기화 * 생성자 본체를 실행 |
※만약 객체의 멤버로 const 멤버가 존재한다면 그 멤버는 반드시 이 표현으로만 초기화가 가능하다. 객체가 초기화 되는 시기는 생성자 코드로 진입되기 이전인데 일단 초기화된 const 멤버는 컴파일러가 그 값의 수정을 허용하지 않기 때문이다.
1.5.2. 9.5.2 인자를 사용하는 생성자 ¶
~cpp Student_info::Student_info(istream& is) {read(is);}이 생성자는 실질적인 작업을 read 함수에 위임합니다.
1.5.3. 9.5.3 Entire Source File ¶
~cpp //Student_info.h #ifndef GUARD_Student_info #define GUARD_Student_info #include <string> #include <vector> class Student_info { public: Student_info(); // construct an empty `Student_info' object Student_info(std::istream&); // construct one by reading a stream std::string name() const { return n; } bool valid() const { return !homework.empty(); } // as defined in 9.2.1/157, and changed to read into `n' instead of `name' std::istream& read(std::istream&); double grade() const; // as defined in 9.2.1/158 private: std::string n; double midterm, final; std::vector<double> homework; }; bool compare(const Student_info&, const Student_info&); #endif
~cpp //Student_info.cpp #include <iostream> #include <vector> #include "grade.h" #include "Student_info.h" using std::istream; using std::vector; double Student_info::grade() const { return ::grade(midterm, final, homework); } bool compare(const Student_info& x, const Student_info& y) { return x.name() < y.name(); } Student_info::Student_info(): midterm(0), final(0) { } Student_info::Student_info(istream& is) { read(is); } // 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(); // 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; } istream& Student_info::read(istream& in) { in >> n >> midterm >> final; read_hw(in, homework); return in; }
1.6. 9.6 Using the Student_info class ¶
~cpp //grading_prog.cpp #include <algorithm> #include <iomanip> #include <ios> #include <iostream> #include <stdexcept> #include <string> #include <vector> using std::max; #include "Student_info.h" #include "median.h" using std::cin; using std::cout; using std::domain_error; using std::endl; using std::setprecision; using std::setw; using std::sort; using std::streamsize; using std::string; using std::vector; int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; // read and store the data while (record.read(cin)) { // changed maxlen = max(maxlen, record.name().size()); // changed students.push_back(record); } // 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) { cout << students[i].name() // this and the next line changed << string(maxlen + 1 - students[i].name().size(), ' '); try { double final_grade = students[i].grade(); // changed streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } } return 0; }
AcceleratedC++