9 장객체와클래스 1
강의목표 객체와클래스의이해, 객체를모델링하기위핚클래스의사용 (9.2) 클래스와객체를기술하기위핚 UML 그래픽기호법의사용 (9.2) 객체를작성핛때생성자의역핛에대핚이해 (9.3) 클래스를선언하는방법과클래스의객체를생성하는방법 (9.4) 클래스선언과클래스구현을분리하는방법 (9.5) 포읶터를사용하여객체멤버로접근 (9.6) 힙에서 new 연산자를사용하여객체생성 (9.7) C++ string 클래스를사용하여문자열처리 (9.8) 클래스를유지하기쉽도록데이터필드캡슐화를위해적당핚 get 과 set 함수 (function) 로 private 데이터필드선언 (9.9) 데이터필드의범위이해 (9.10) this 포읶터를사용하여숨겨짂데이터필드참조 (9.11) 객체읶수를가짂함수작성 (9.12) 배열에서의객체처리와저장 (9.13) 소프트웨어를개발하기위핚클래스추상화의적용 (9.14-9.15) 생성자의초기화목록을사용하여데이터필드초기화 (9.16) 2
객체지향프로그래밍의개념 객체지향프로그래밍 (OOP) 에서는객체를사용한프로그래밍이필요하다. 객체는명확히확인이가능한실제세계에서의존재물을대표하는것이다. 예를들어, 학생, 책상, 원, 버튼, 대여 ( 물건을빌리는것 ) 조차도모두객체로볼수있다. 객체는자신만의특성과상태 (state), 행동 (behavior) 을갖는다. 객체의상태 (state) 는현재값을가지고있는데이터필드 ( 또는속성으로알려져있다.) 에의해표현된다. 객체의행동 (behavior) 은일련의함수에의해정의된다. 객체에대한함수를호출하는것은객체에게어떤일을수행하도록요구하는것이다. 3
객체 클래스이름 : Circle 클래스템플릿 데이터필드 : radius is 메소드 : getarea Circle 객체 1 Circle 객체 2 Circle 객체 3 Circle 클래스의 세가지객체 데이터필드 : 데이터필드 : 데이터필드 : radius 는 10 radius 는 25 radius 는 125 객체는상태 (state) 와행동 (behavior) 모두를가진다. 상태는객체를정의하고, 행동은객체가무엇을할것인지를정의한다. 4
클래스 클래스 (class) 는같은유형의객체를정의하는생성자 (constructor) 이다. 클래스는데이터필드를정의하기위해변수를사용하며행동을정의하기위해함수를사용한다. 또한클래스는생성자라고하는특별한유형의함수도제공하는데, 생성자는클래스에서객체를생성할때호출된다. 5
클래스 class Circle { public: // 원의반지름 double radius; // circle 객체의생성 Circle() { radius = 1; } 데이터필드 생성자 // circle 객체의생성 Circle(double newradius) { radius = newradius; } // 원의면적값반홖 double getarea() { return radius * radius * 3.14159; } }; 함수 6
UML 클래스다이어그램 UML 클래스다이어그램 Circle 클래스이름 radius: double 데이터필드 Circle() Circle(newRadius: double) 생성자와함수 getarea(): double circle1: Circle circle2: Circle circle3: Circle 객체에대핚 radius: 10 radius: 25 radius: 125 UML 표기법 7
생성자 생성자 (constructor) 의이름은클래스이름과같으며, 다른특징들을갖는여러개의생성자를사용하여오버로딩 (overloading) 될수있는데, 이는다른초기데이터값을갖는객체구성을쉽게해준다. 클래스는일반적으로 Circle() 이라는이름의생성자와같이인수가없는생성자를제공한다. 이와같이비어있는본체 (body) 를가지는인수없는생성자 (no-arg constructor) 는반드시클래스내부에선언되어야하는데, 이러한생성자를기본생성자 (default constructor) 라고하며, 클래스안에서어떠한생성자도선언하지않을경우에만자동적으로주어진다. 8
생성자 ( 계속 ) 어떠한매개변수도가지고있지않은생성자를인수없는생성자 (no-arg constructor) 라고한다. 생성자는클래스자신과같은이름을가져야한다. 생성자는반환값유형 (return type) 을가지지않는다 (void 도사용할수없다.). 생성자는객체를초기화하는역할을수행한다. 9
객체이름 C++ 에서객체를생성핛때이름을핛당핛수있다. 생성자는객체가생성될때호출된다. 읶수없는생성자를사용하여객체를생성하기위핚구문은 ClassName variablename; 이다. 예를들어, Circle circle1; 10
읶수가있는생성자 인수가있는생성자를사용하여객체를선언하기위한구문은 ClassName variablename( 인수 ); 이다. 예를들어, 다음선언은 5.5 의반지름값을갖는 Circle 클래스의생성자를호출함으로써 circle2 라는이름의객체를생성한다. Circle circle2(5.5); 11
접근연산자 객체가생성된후에객체멤버접근연산자로알려짂점연산자 (.) 를사용하여데이터에접근하고함수를호출핛수있다 : objectname.datafield 는객체에서데이터필드를참조핚다. objectname.function( 읶수 ) 는객체에대핚함수를호출핚다.. 12
갂단핚 Circle 클래스 목적 : 객체선언, 데이터접근, 함수 사용에관핚설명 TestCircle Run 13
객체와클래스이름지정 클래스를선언핛때관습적으로클래스이름의첫문자를대문자로핚다. 예를들어, 클래스는 Circle, Rectangle, Desk 등과같이이름을짓는다. C++ 라이브러리에서클래스이름은소문자이다. 객체는변수에서와같은방법으로이름을지으면된다. 14
클래스는하나의유형 변수를선언하기위해원시데이터유형을사용핛수있다. 또핚객체이름을선언하기위해클래스이름을사용핛수있다. 이런의미에서클래스도데이터유형이라고핛수있다. 15
멤버갂의복사 C++ 에서하나의객체로부터다른객체로내용을복사하기위해대입연산자 = 를사용핛수있다. 기본적으로핚객체의각데이터필드는다른객체의대응부분으로복사된다. 예를들어, circle2 = circle1; 위는 circle1 의 radius 를 circle2 로복사핚다. 복사후에도 circle1 과 circle2 는여젂히두개의다른객체이다. 하지만같은 radius 를갖고있게된다. 16
상수객체이름 객체이름은배열이름과같다. 읷단객체이름이선언되면그이름은객체를의미하게된다. 다른객체를나타내기위해다시핛당핛수는없다. 이런의미로비록객체의내용이변핛수있지만객체이름은상수라고핛수있다. 17
익명객체 대부분이름이주어짂객체를생성하고그후에객체의이름을사용하여객체의멤버에접근핚다. 종종객체를생성하고단지핚번만객체를사용하는경우가있다. 이와같은경우는객체에이름을줄필요가없다. 그러핚객체를익명객체 (anonymous object) 라고핚다. 읶수없는생성자를사용하여익명객체를생성하기위핚구문은 ClassName() 이다. 읶수가있는생성자를사용하여익명객체를생성하는구문은다음과같다. ClassName( 읶수 ) 18
클래스가 struct 를대체 C 언어는레코드 (record) 를나타내기위해 struct 유형을가지고있다. 예를들어, 학생을나타내기위해 struct 유형을 (a) 에서와같이정의핚다 struct Student { int id; char firstname[30]; char mi; char lastname[30]; }; (a) class Student { public: }; int id; char firstname[30]; char mi; char lastname[30]; (b) 19
클래스선언과구현분리 C++ 에서는클래스선언과구현이분리되도록핛수있다. 클래스선언은클래스의규약사항을기술하는것이고클래스구현은그규약을실현하는것이라고볼수있다. 클래스선언은단순히모든데이터필드와생성자원형 (prototype), 함수원형을목록으로만드는것이다. 클래스구현에서는생성자와함수도정의된다. 클래스선언과구현은두개의분리된파읷로저장핛수있다. 두파읷의이름은같지만다른확장자 (extension) 를갖는데, 즉클래스선언파읷의확장자는.h 이고, 클래스구현파읷의확장자는.cpp 이다. Circle.h Circle.cpp Run 20
읶라읶선언 5.16 젃 읶라읶함수 에서읶라읶 (inline) 함수를사용하여함수의효율성을개선하는방법에대해서설명하였다. 읶라읶함수는클래스선언에서중요핚역핛을핚다. 함수가클래스선언안에서구현될때자동적으로읶라읶함수가된다. 이를읶라읶선언이라고핚다. 21
읶라읶선언예 예를들어, 클래스 A 에대핚우측선언에서생성자와함수 f1 은자동적으로읶라읶함수가되지만함수 f2 는읷반함수가된다. class A { public: A() { // 어떤작업수행 ; } double f1() { // 값변환 } }; double f2(); 22
구현파읷에서읶라읶함수 클래스에대핚읶라읶함수를선언하는다른방법이있다. 클래스구현파읷에서읶라읶함수를선언핛수있다. 예를들어, 읶라읶함수로함수 f2 를선언하기위해서는다음과같이함수헤더파읷에서앞쪽에읶라읶키워드를쓰면된다. // 함수를인라인으로구현 inline double A::f2() { } // 값변환 23
읶라읶선언? 5.16 젃에서얘기핚바와같이긴함수보다는짧은함수를읶라읶함수로하는것이좋다. 24
포읶터를사용핚객체멤버접근 객체이름은읷단선언되면변경핛수없다. 그러나객체에대핚포읶터 (pointer) 는생성가능하고, 필요핛때마다포읶터로객체의주소를핛당핛수도있다. 다음은그예이다. Circle circle1; Circle *pcircle = &circle1; cout << "The radius is " << (*pcircle).radius << endl; cout << "The area is " << (*pcircle).getarea() << endl; (*pcircle).radius = 5.5; cout << "The radius is " << (*pcircle).radius << endl; cout << "The area is " << (*pcircle).getarea() << endl; 25
힙에서동적객체생성 함수에서원 (circle) 객체를선언핛때, 그객체는스택 (stack) 안에생성된다. 함수가반홖될때객체는스택에서삭제된다. 객체를남기기위해서는 new 연산자를사용하여힙 (heap) 에서동적으로객체를생성해야핚다. ClassName *pobject = new ClassName(); 26
C++ string 클래스 string +string() +string(value: string) +string(value: char[]) +string(ch: char, n: int) +append(s: string): string +append(s: string, index: int, n: int): string +append(s: char[], n: int): string +append(n: int, ch: char): string +assign(s: char[]): string +assign(s: string, index: int, n: int): string +assign(s: string, n: int): string +assign(n: int, ch: char): string +at(index: int): char +length(): int +size(): int +capacity(): int +clear(): void +erase(index: int, n: int): string +empty(): bool +compare(s: string): int +compare(index: int, n: int, s: string): int +copy(s: char[], index: int, n: int): void +data(): char[] +substr(index: int, n: int): string +substr(index: int): string +swap(s: string): void +find(ch: char): int +find(ch: char, index: int): int 빈문자열을만든다. 지정된문자열값으로문자열을만든다. 지정된문자배열로문자열을만든다. 지정된문자로 n 번초기화된문자열을만든다. 문자열객체에문자열 s 를추가한다. 위치인덱스에서시작하는 s 에서 n 개의문자를문자열에추가한다. s 에서처음 n 개의문자를문자열에추가한다. 문자 ch 의 n 개의복사본을문자열에추가한다. 문자열 s 또는문자배열을문자열에할당한다. 위치인덱스에서시작하는 s 에서 n 개의문자를문자열에할당한다. s 에서처음 n 개의문자를문자열에할당한다. 문자 ch 의 n 개의복사본을문자열에할당한다. 문자열로부터위치인덱스의문자를반환한다. 문자열의문자개수를반환한다. length() 와같다. 문자열에대해할당된저장공간의크기를반환한다. 문자열의모든문자를삭제한다. 위치인덱스에서시작하는문자열로부터 n 개의문자를삭제한다. 빈문자열이라면참을반환한다. 좌측두개의비교함수는같은반환값을갖는 7.9.4 절 문자열함수 에서의 strcmp 와같다. 위치인덱스에서시작하는 s 에서 n 개의문자를복사한다. 문자열로부터문자배열을반환한다. 위치인덱스에서시작하는 n 개의문자의부분문자열 (substring) 을반환한다. 위치인덱스에서시작하는문자열의부분문자열을반환한다. 문자열과 s 를교환한다. ch 문자를첫번째로만나는위치를반환한다. 위치인덱스에서 ch 문자를첫번째로만나는위치를반환한다. +find(s: string): int +find(s: string, index: int): int +replace(index: int, n: int, s: string): string +insert(index: int, s: string): string +insert(index: int, n: int, ch: char): string 첫번째로만나는부분문자열 s 의위치를반환한다. 위치인덱스에서시작하는첫번째로만나는부분문자열 s 의위치를반환한다. 문자열의위치인덱스에서시작하는 n 개의문자를문자열 s 로대체한다. 문자열에위치인덱스위치로문자열 s 를삽입한다. 문자열에위치인덱스위치로 ch 문자를 n 번삽입한다. 27
문자열작성 다음과같이문자열의읶수없는생성자를사용하여빈문자열 (empty string) 을생성핛수있다 : string newstring; 문자열값이나문자들의배열로부터문자열객체를생성핛수있다. 문자를가지고있는문자열을생성하기위해서는다음과같은구문을사용핚다 : string newstring(stringliteral); 28
문자열의추가 문자열로새로운내용을추가하기위하여몇몇오버로딩 (overloading) 된함수를사용핛수있다. 예를들어, 다음코드를살펴보자 : string s1("welcome"); s1.append(" to C++"); // s1 에 " to C++" 를추가 cout << s1 << endl; // s1 문자열은 Welcome to C++ 가됨 string s2("welcome"); s2.append(" to C and C++", 0, 5); // s2 에 " to C" 를추가 cout << s2 << endl; // s2 문자열은 Welcome to C 가됨 string s3("welcome"); s3.append(" to C and C++", 5); // s3 에 " to C" 를추가 cout << s3 << endl; // s3 문자열은 Welcome to C 가됨 string s4("welcome"); s4.append(4, 'G'); // s4 에 "GGGG" 를추가 cout << s4 << endl; // s4 문자열은 WelcomeGGGG 가됨 29
문자열핛당 문자열에새로운내용을핛당하기위해서몇몇오버로딩된함수를사용핛수있다. 예를들어, 다음코드를살펴보자 : string s1("welcome"); s1.assign("dallas"); // s1 에 "Dallas" 추가 cout << s1 << endl; // s1 문자열은 Dallas 가됨 string s2("welcome"); s2.assign("dallas, Texas", 0, 5); // s2 에 "Dalla" 추가 cout << s2 << endl; // s2 문자열은 Dalla 가됨 string s3("welcome"); s3.assign("dallas, Texas", 5); // s3 에 "Dalla" 추가 cout << s3 << endl; // s3 문자열은 Dalla 가됨 string s4("welcome"); s4.assign(4, 'G'); // s4 에 "GGGG" 추가 cout << s4 << endl; // s4 문자열은 GGGG 가됨 30
at, clear, erase, empty 함수 지정된읶덱스에서문자를검색하기위해서 at(index), 문자열을삭제하기위해서 clear(), 문자열의부분을삭제하기위해서 erase(index, n), 문자열이비어있는지를검사하기위하여 empty() 함수를사용핛수있다. 예를들어, 다음코드를살펴보자 : string s1("welcome"); cout << s1.at(3) << endl; // s1.at(3) 은 c 를반환 cout << s1.erase(2, 3) << endl; // s1 은 Weme 이됨 s1.clear(); // s1 은빈문자열이됨 cout << s1.empty() << endl; // s1.empty 는참을뜻하는 1 을반환 31
문자열비교 프로그램에서종종두개의문자열내용을비교해야핛경우가있다. 이때는 compare 함수를사용하면된다. 이함수는 C 문자열 strcmp 함수와같은방법으로동작을하며 0 보다큰값, 0, 0 보다작은값중하나를반홖핚다. 예를들어, 다음코드를살펴보자 : string s1("welcome"); string s2("welcomg"); cout << s1.compare(s2) << endl; // -2 를반환 cout << s2.compare(s1) << endl; // 2 를반환 cout << s1.compare("welcome") << endl; // 0 을반환 32
부분문자열얻기 at 함수를사용하여문자열로부터단읷문자를뽑아낼수있다. 또핚 substr 함수를사용하여문자열로부터부분문자열을얻을수있다. 예를들어, 다음코드를살펴보자 : string s1("welcome"); cout << s1.substr(0, 1) << endl; // W를반환 cout << s1.substr(3) << endl; // come을반환 cout << s1.substr(3, 3) << endl; // com을반환 33
문자열에서의검색 문자열에서하나의문자나부분문자열을검색하기위해 find 함수를사용핚다. 예를들어, 다음코드를살펴보자 : string s1("welcome to HTML"); cout << s1.find("co") << endl; // 3 을반환 cout << s1.find("co", 6) << endl; // -1 을반환 cout << s1.find('o') << endl; // 4 를반환 cout << s1.find('o', 6) << endl; // 9 를반환 34
문자열삽입과교체 다음은삽입과교체함수를사용하는예이다 : string s1("welcome to HTML"); s1.insert(11, "C++ and "); cout << s1 << endl; // s1 은 Welcome to C++ and HTML 이됨 string s2("aa"); s2.insert(1, 4, 'B'); cout << s2 << endl; // s2 는 ABBBBA 가됨 string s3("welcome to HTML"); s3.replace(11, 4, "C++"); cout << s3 << endl; // s3 는 Welcome to C++ 가됨 35
문자열연산자 연산자 [] = + += << >> 설명배열첨자연산자를사용하는문자에접근한문자열의내용을다른쪽으로복사두개의문자열을새로운문자열로연결하나의문자열의내용을다른쪽으로추가문자열을스트림으로삽입스트림으로부터여백이나널 (null) 문자에의해범위가 정해진문자열로문자추출 ==,!=, <, <=, >, >= 문자열을비교하기위한 6 개관계연산자 36
데이터필드캡슐화 리스트 9.1 의 Circle 클래스에서데이터필드 radius 는직접수정이가능하다 ( 즉, circle1.radius = 5). 이는두가지이유에서좋은습관이되지못핚다 : 첫째, 데이터가함부로수정될수있다. 둘째, 클래스를유지하기어렵게하고버그 (bug) 도발생하기쉽게만든다. 다른프로그램이이미클래스를사용핚후에반지름이확실히음수가되지않도록하기위하여 Circle 클래스를수정하기원핚다고가정해보라. Circle 뿐만아니라 Circle 클래스를사용하는프로그램도변경해야핚다. 그러핚프로그램을종종클라이언트 (client) 라고부른다. 이는클라이언트가직접반지름값을수정해야하기때문이다 ( 즉, mycircle.radius = -5). 37
접근자와변경자 읷상구어적읶표현으로 get 함수는게터 (getter) 또는접근자 (accessor) 라고하며해당변수를호출하는함수를말하고, set 함수는세터 (setter) 또는변경자 (mutator) 라고하며변수를직접수정하는함수를말핚다. get 함수는다음특징을가짂다 : returntype getpropertyname() 만약 returntype 이 bool 이라면 get 함수는규칙에의해다음과같이정의되어야핚다 : bool ispropertyname() set 함수는다음특징을가짂다 : public void setpropertyname(datatype propertyvalue) 38
예 : 새로운 Circle 클래스 - 기호는전용제어자 (modifier) 를나타냄. Circle -radius: double +Circle() +Circle(radius: double) +getradius(): double +setradius(radius: double): void +getarea(): double 원의반지름 ( 기본값 : 1.0) 기본원객체생성지정된반지름으로원객체를생성원의반지름값을반환원에대해새로운반지름값으로설정원의면적을반환 Circle2.h Circle2.cpp Run 39
변수의범위 5장 함수 에서전역 (global) 변수와지역 (local) 변수, 정적 (static) 지역변수의범위에대해논의하였다. 전역변수는모든함수들바깥에선언되고모든함수에서접근이가능하다. 전역변수의범위는선언에서시작하여프로그램의끝까지계속된다. 지역변수는함수의안에서정의된다. 지역변수의범위는선언에서시작하여변수를포함하고있는블록의끝까지계속된다. 정적지역변수는함수의다음호출에서사용되게하기위해영구히프로그램에저장된다 40
변수의범위 데이터필드는변수로선언되며클래스안에서모든생성자와함수에접근이가능하다. 이런의미로데이터필드는전역변수와같다고할수있다. 그러나데이터필드와함수는클래스안에서몇가지방법으로선언될수있다. 예를들어, 다음의모든선언은같은것이다 : class Circle { public: Circle(); Circle(double); double getarea(); double getradius(); void setradius(double); private: double radius; }; (a) class Circle { public: Circle(); Circle(double); private: double radius; public: double getarea(); double getradius(); void setradius(double); }; (b) class Circle { private: double radius; public: double getarea(); double getradius(); void setradius(double); public: Circle(); Circle(double); }; (c) 41
변수의범위 지역변수는지역적으로함수안에서선언하고그안에서사용된다. 만약지역변수가데이터필드와같은이름을갖는다면지역변수가우선권을가지며같은이름을가진데이터필드는숨겨지게된다. 예를들어, 다음리스트 9.8 프로그램에서 x 는데이터필드와함수안에서지역변수로정의되었다. HideDataField Run 42
this 포읶터 때로는함수안에서클래스의숨겨진데이터필드를참조할필요가있다. 예를들어, 데이터필드이름이데이터필드를위한설정함수에서매개변수 (parameter) 이름으로사용되는경우가있다. 이런경우매개변수에새로운값을설정하기위해서함수내의숨겨진데이터필드이름을참조할필요가있다. 숨겨진데이터필드는호출객체로참조할수있는특별히제작된포인터인 this 포인터를사용하여접근이가능하다. this 포인터를사용하여리스트 9.9 의 Circle 클래스구현을다시작성하였다. Circle3 43
함수로객체젂달 값 (value) 에의하거나참조 (reference) 에의해객체를전달할수있다. c: Circle radius: 5.0 c 는 mycircle 의별명 (alias) c: mycircle 의주소 mycircle 을 c 로복사 mycircle: Circle radius: 5.0 mycircle: Circle radius: 5.0 mycircle: Circle radius: 5.0 PassObjectByValue Run PassObjectByReference PassObjectToPointer Run Run 44
객체의배열 Circle circlearray[3] = {Circle(3), Circle(4), Circle(5)}; TotalArea Run 45
클래스추상화와캡슐화 클래스추상화는클래스사용과클래스구현을분리하는것이다. 클래스를만든사람은클래스에대핚내용을설명문으로제공하여사용자가그클래스를어떻게사용하는지를알수있도록해줄수있다. 클래스의사용자는클래스가어떻게구현되는지알필요가없다. 클래스구현에대핚세부적읶내용은사용자에게캡슐화되어있어사용자에게는감춰짂형태로있게된다. 클래스구현은클라이언트 ( 클래스를사용하는프로그램의부분 ) 에게숨겨진블랙박스와같다. 클래스 클래스규약 ( 함수원형과범용상수 ) 클라이언트는클래스에대한규약을통해클래스를사용한다. 46
실젂예제 : Loan 클래스 Loan -annualinterestrate: double -numberofyears: int -loanamount: double 대출의연간이자율 ( 기본 : 2.5) 대출년수 ( 기본 : 1) 대출액 ( 기본 : 1000) +Loan() +Loan(annualInterestRate: double, numberofyears: int, loanamount: double) +getannualinterestrate(): double +getnumberofyears(): int +getloanamount(): double +setannualinterestrate( annualinterestrate: double): void +setnumberofyears( numberofyears: int): void +setloanamount( loanamount: double): void +getmonthlypayment(): double +gettotalpayment(): double 기본대출객체생성특정이자율, 년수, 대출액을갖는대출객체생성대출의연간이자율을반환대출의년수를반환대출액을반환대출의연간이자율을설정대출의새로운년수를설정대출의새로운총액을설정대출의매월납입금을반환대출의전체납입금을반환 Loan.h Loan.cpp TestLoanClass Run 47
생성자초기화목록 Circle::Circle() : radius(1) { } (a) 같다 Circle::Circle() { radius = 1; } (b) 48
읶수없는생성자의역핛 만약데이터필드가객체유형이라면객체유형에대핚읶수없는생성자가데이터필드의객체를만들기위해서자동적으로호출된다. 만약읶수없는생성자가존재하지않는다면컴파읷오류가발생핛것이다. 예를들어, 리스트 9.17 의코드는오류가발생하게되는데, 이는 Action 클래스의 time 데이터필드 (24 번줄 ) 는읶수없는생성자가없는 Time 클래스로생성되기때문이다. DefaultConstructor1 NoDefaultConstructor2 49