C++
C++ 학습을 위한 준비[편집]
- C와 C++의 차이점: C++은 C에 객체지향 개념을 도입한것. 현재는 STL과 Boost 덕분에 두 언어는 완전히 다르다고 볼 수 있다.
- 절차지향 프로그램: C나 Basic으로 작성하는 프로그램은 함수를 위주로 하는 구조화된 방식이었음. 프로그램의 시작을 주도하는 main()함수의 내용을 순차적으로 수행함. 일반적으로 함수를 중심으로 프로그램을 설계하고 거기에 필요한 데이터를 정의함.
- 객체지향 프로그램: 객체지향 프로그래밍은 프로그램이 일정한 순서에 의해 진행되지 않음. 객체들로 이루어진 프로그램에서 '사건'이 일어나면 그에 따른 처리를 하는 식으로 프로그램이 진행됨. 객체지향 프로그래밍은 프로그램을 작성하기 위해 필요한 오브젝트들을 먼저 생각함. 객체는 결국 클래스 설계가 중점임.
객체지향 프로그래밍의 특징[편집]
- Encapsulation: 안의 내용을 몰라도 api문서만 가지고 거의 완성된 프로그램을 다룰수 있다는 느낌을 줌.
- 다형성, 함수 오버로드: 함수나 연산자가 자료구조에 따라 다르게 동작할 수 있음.
- 상속: 특정 객체의 성격을 다른 객체가 상속받을 수 있다. 비슷한 오브젝트를 여러번 재정의할 필요가 없다.
C++ 컴파일러 설치와 사용법[편집]
<source lang="bash"> sudo apt-get install g++ </source>
C++ 프로그램 구조 파악하기[편집]
- 다들 예상했겠지. 간닷 헬로월드
<source lang="c++"> // Hello World: Defines the entry point for the console application
- include <iostream>
int main() {
printf("hello world");
return 0;
} </source>
- main(): 엔트리 포인트는 결국 main()이당
- { }: 뭉탱이. 함수나 반복문이나 조건문같은건 뭉탱이 단위로 실행하게 됨.
- //, /* */: 주석. 컴파일러가 해석 안할 내용. 사람한테만 필요한 내용 적을때 씀.
- #include: 파일을 불러와서 덧붙이라는 내용.
- printf 함수는 문자열을 화면에 출력하는 객체임.
- ; : 끝에 붙여주는 마침표같은거. 문장의 끝이라고 알려주지 않으면 컴파일러는 문장의 끝인지 모른다.
C++ 콘솔 출력[편집]
- iostream을 써서 콘솔 입출력을 해보자.
- cout은 ostream class 안에 정의되어 있음.
<source lang="c++"> // File Name: cout.cpp
- include <iostream>
void main() {
int a = 5; float b = 3.4; char c = 'a'; std::cout << a << std::endl; // << 연산자로 정수형 출력 std::cout << b << std::endl; // << 연산자로 실수형 출력 std::cout << c << std::endl; // << 연산자로 문자형 출력
} </source>
- 연산자 오버로드: << 연산자 하나로 다양한 자료형태를 모두 출력할 수 있도록 ostream 클래스에 << 연산자가 여러번 정의되어 있다.
- std::cout, std::endl: 화면에 출력하기 위한 cout 클래스 앞에 std::가 붙어있는데, 이건 namespace다. iostream에 선언되어 있는 cout은 std라는 namespace 안에 존재한다.
- 귀찮다면 main 함수 앞에 다음과 같은 선언을 해서 조금 덜 귀찮을 수 있다. 별로 추천하지는 않지만.
<source lang="c++"> using namespace std; </source>
- 이렇게 선언을 하면 다음과 같이 사용이 가능하다.
<source lang="c++"> cout << a << endl; </source>
- 간결한 것도 좋지만, 사실 귀찮아도 명확한게 좋다.
- 출력 포맷 지정
- C의 printf처럼 출력지정을 할 수 있는데, 그럴 경우 스트림 조작기를 사용한다. 스트림조작기는 iomanip 헤더에 정의되어 있다.
- dec - 10진수
- hex - 16진수
- oct - 8진수
- endl - 줄바꿈
- ends - 널문자 삽입
- setfill(int c) - 채우기 문자 설정
- setprecision(int n) - 부동소수점의 유효 자리 설정
- setw(int n) - 필드 폭 설정
- setiosflags(long f) - 형식 플래그 설정
- resetiosflags(long f) - 형식 플래그 초기화
- setiosflags()랑 resetiosflags()를 위한 형식플래그
- ios::left - set() 폭 안의 출력을 좌측으로 정돈
- ios::right - set() 폭 안의 출력을 우측으로 정돈
- ios::scientfic - 부동소수점을 지수형태로 표시(ex: 1.2345E2)
- ios::fixed - 부동소수점을 소수형태로 표시(ex: 123.45)
- ios::dec - 정수형을 10진수로 표시
- ios::hex - 정수형을 16진수로 표시
- ios::oct - 정수형을 8진수로 표시
- ios::uppercase - 16진수와 지수를 대문자로 표시
- ios::showbase - 수치베이스 접두문자를 출력
- ios::showpos - 양수 표기에 +를 출력
- ios::showpoint - 필요하면 0도 출력
- 진법 변환 스트림 조작기 예제
<source lang="c++"> // Stream Controller using namespace std; void main() {
int a = 100; cout << "10진수->"<<dec<<a<<endl; cout << "16진수->"<<hex<<a<<endl; cout << " 8진수->"<<oct<<a<<endl;
} </source>
- 자릿수 지정을 위한 스트림 조작 예제
<source lang="c++"> // Stream Controller 2
- include <iostream>
- include <iomanip>
using namespace std; void main() {
int a = 10000; double b = 3.123497; cout << "123456789012345678901234567890" << endl; cout << setw(8) << a << endl; cout << setw(10) << a << endl;
cout << setiosflags(ios::fixed); cout << setprecision(2) << b << endl; cout << setw(10) << setprecision(3) << b << endl;
cout << setw(10) << a << "." << endl; cout << setiosflags(ios::left); cout << setw(10) << a << "." << endl;
} </source>
- cout은 객체라서 메소드를 통한 출력이 가능하다.
- 메소드 문자 출력 예제
<source lang="c++"> // Method Character Print
- include <iostream>
using namespace std; void main() {
cout.put('S'); cout.put('t').put('u'); cout.put(101); cout.put(121.3); cout.put('\n');
} </source>
- 메소드로 문자열 출력
<source lang="c++"> // Method String Print
- include <iostream>
- include <cstring>
using namespace std; void main() {
char *str = "Apple"; int len = strlen(str); cout.write(str, len); cout << endl;
} </source>
C++ 콘솔 입력[편집]
- cin을 활용한 입력
- cin은 istream 클래스로 정의되어 있음
- 예제를 한번 보자.
<source lang="c++"> // cin
- include <iostream>
using namespace std; void main() {
int a; float b; char c; cout << "정수형 데이터 입력 ->"; cin >> a; cout << "실수형 데이터와 문자형 데이터 입력 ->"; cin >> b >> c; cout << setw(5) << a << setw(5) << b << stew(5) << c << endl;
} </source>
- cin의 입력 함수
- cin도 cout같은 객체니까 안에 함수가 있다. 문자 계열을 읽기 위한 함수로 get(), getline()이 있다.
<source lang="c++"> istream & get(char &); </source>
- get() 메소드로 문자를 여러개 입력받으려면 while로 받을 수 있다.
<source lang="c++"> while(cin.get(ch))
cout.put(ch);
</source>
- 문자 입력을 다 했다면 Ctrl+Z를 누르면 된다.
- 예제로 한번 보자.
<source lang="c++"> // Character Input Example
- include <iostream>
using namespace std; void main() {
int cnt = 0; // 문자 카운팅을 위한 변수 char ch; // 문자을 읽어올 변수 while(cin.get(ch)) { // 문자 읽기 cout.put(ch); // echo cnt++; } cout << "\n문자의 갯수는->" << cnt << endl;
} </source>
- 문자열을 읽을수도 있다.
<source lang="c++"> istream & get(char *s, int n, char ch = '\n'); </source>
- s - 문자열을 저장할 포인터변수
- n - 읽을 최대 문자열 길이
- ch - 해당 문자를 만날때까지 읽는다(생략하면 \n까지
null 같은데..)
- 최대로 읽을 수 있는 문자 갯수를 지정해서 입력받는 예제
<source lang="c++"> // Cin Method Example
- include <iostream>
using namespace std; voiid main() {
char str[10]; cout << "#을 만나기 전까지의 문자열 9개까지만 입력받는다." << endl; cin.get(str, 10, '#'); cout << endl << "입력한 문자열은->" << str << endl;
} </source>
- getline()으로도 문자열을 읽어올 수 있다.
<source lang="c++"> istream& getline(signed char*, int, char = '\n'); istream& getline(signed char*, int); </source>
- getline()으로 문자열 읽어오는 예제
<source lang="c++"> // Getline Example
- include <iostream>
using namespace std; void main() {
char str[10];
cout << "엔터가 눌리기 전까지 문자열 입력->"; cin.getline(str, 10); // 10개까지만 받음 cout << endl; "입력받은 문자열 ->" << str << endl;
} </source>
구조체, 레퍼런스 변수[편집]
구조체[편집]
- 구조체는 여러가지 데이터형으로 구성된 여러 항목이 모여 만드는것.
- 시, 분, 초를 멤버로 갖는 time은 다음과 같다.
<source lang="c++"> struct time // 구조체 이름 {
int hour; // 멤버변수 int minute; // 멤버변수 int second; // 멤버변수
} </source>
- 예약어 struct 다음에 기술된 time은 구조체 이름이고, 정의된 구조체 time은 템플릿이라 메모리에 들어가지는 않는다.
- 뭔말이냐면, time이라는 새로운 자료형이 만들어졌다는 얘기.
- hour, minute, second는 구조체를 구성하고 있는 구성요소로, 필드나 멤버변수라고 불린다.
- 구조체는 멤버 변수들의 크기만큼 메모리 할당이 된다.
+--------+--------+--------+ | 4byte | 4byte | 4byte | +--------+--------+--------+ hour minute second timer 구조체 하나당 12byte의 메모리가 할당됨
- 구조체 멤버에 접근할때는 . 연산자를 사용한다.
<source lang="c++"> first.hour = 9; first.minute = 51; first.second = 20;
cout << first.hour << " 시 " << first.minute << " 분 " << first.second << " 초 " << endl; </source>
- 예제로 보면 다음과 같다.
<source lang="c++"> // Struct Example
- include <iostream>
using namespace std;
struct tiime {
int hour; int minute; int second;
};
void main() {
struct time first;
first.hour = 0; first.minute = 51; first.second = 20;
cout << first.hour << " 시 " << first.minute << " 분 " << first.second << " 초 " << endl;
} </source>
- 구조체 변수를 초기화하려면 구조체 변수 선언할때 각 멤버에 대응하는 초기값을 대입연산자 다음 {} 안에 나열한다.
<source lang="c++"> struct time first = { 11, 3, 10 }; </source>
- 구조체 변수를 만들면서 구성원인 hour, minute, second에 각각 11, 3, 10을 기억시켜놓으라는 의미.
- 이걸 구조체 초기화라 한다. 근데 리스트의 갯수가 구조체의 변수 갯수보다 적은 경우에는 나머지 멤버에 기본값(0)이 채워진다.
구조체 포인터[편집]
- 포인터 변수는 다음과 같은 형식으로 선언.
<source lang="c++"> 자료형 *포인터 변수; </source>
- 구조체 포인터도 동일한 방법으로 선언하고, 자료형 위치에 구조체 태그를 올리면 됨.
<source lang="c++"> struct time first = { 11, 3, 10 }; struct time *ptr; ptr = &first; </source>
- 포인터변수 ptr은 특정 기억공간의 주소를 저장하는 용도로 사용되니까 선언만 하고 사용하면 그 아름다운 Segmentation Fault를 보게 됨.
빡침 - 포인터변수는 반드시 대입연산자로 특정 변수의 주소를 저장하고 있어야 함. 위에처럼 하면 time 구조체 변수인 first의 주소를 저장하게 된다.
- 구조체 변수 first는 3개의 멤버변수를 갖도록 메모리가 할당됨. 위의 구조체 챕터에서 썼던거.
- ptr은 first의 주소값을 가지므로 구조체 변수 first를 가리키고 있는 구조.
- 포인터 변수에 주소가 저장되어 있을 경우 * 연산자를 포인터 변수에 적용하면 그 주소가 가리키는 곳의 저장된 값을 가져오게 됨.
- 즉, 포인터 변수 ptr에 포인터 연산자 *를 사용하면 포인터 변수 first를 참조할 수 있다.
레퍼런스라고 하던가? 이거 - 참조를 할 수 있긴 한데, 연산자 우선순위때문에 좀 빡칠수 있음.
<source lang="c++">
- ptr.hour = 11; // 우리가 볼땐 직관적이지만 컴터가 볼땐 우선순위때문에 에러 뜸
</source>
- 포인터 연산자 *를 먼저 수행하도록 하면 원하는 결과가 나옴.
<source lang="c++"> (*ptr).hour = 11; </source>
- 근데 이렇게 쓰면 복잡하고, 사실 프로그래머들은 괄호같은거 좀 쓰기 싫어하는 경향이 있어서 새로 연산자를 만들어놨는데 헷갈려하는 사람이 많다.
뉴비들 빡침의 원흉요즘 IDE들은 다 알아서 변환해주더라뉴비들 그것땜에 더 빡침
<source lang="c++"> ptr->hour = 11; </source>
- 그럼 구조체 포인터 사용하는 예제를 보자.
<source lang="c++"> // Struct Pointer
- include <iostream>
using namespace std;
struct time {
int hour; int minute; int second;
};
void main() {
struct time first = { 11, 3, 10 }; struct time *ptr; ptr = &first;
cout << (*ptr).hour << " 시 " << (*ptr).minute << " 분 " << (*ptr).second << " 초 " << endl; cout << ptr->hour << " 시 " << ptr->minute << " 분 " << ptr->second << " 초 " << endl;
} </source>
- 근데 이 예제까지 보고도 이상한점이 있을거다. 당췌 이놈의 구조체포인터를 왜 빡쳐가면서까지 쓰는걸까?
- 보통 구조체포인터를 쓸 필요 없는 경우는 다음과 같다.
<source lang="c++"> struct time first; </source>
- 이렇게 변수 선언에 의해서 생성된 구조체는 스택에 메모리가 할당되고, 그냥 쓰면 된다.
- 문제는 대표적으로 런타임시 동적할당할 경우인데, 그 경우 Heap에 메모리가 할당되고 포인터만 받게 된다.
- 아래와 같은 식으로 보통 메모리를 받아 쓰게 되는 경우가 많다.
<source lang="c++"> struct time *ptr = new struct time; </source>
- 이에 대한 예제를 보자.
<source lang="c++"> // Struct Pointer
- include <iostream>
using namespace std; strcut time {
int hour; int minute; int second;
};
void main() {
struct time *ptr; ptr = new struct time; ptr->hour = 0; ptr->minutes = 51; ptr->second = 20; cout << ptr->hour << " 시 " << ptr->minute << " 분 " << ptr->second << " 초 " << endl;
} </source>
구조체를 매개변수로 갖는 함수[편집]
- 구조체를 함수에 전달하는 방법에는 두가지가 있다.
- 구조체 자체를 넘기는 방법(call by value)
- 구조체 포인터를 넘기는 방법(call by address)
구조체 단위로 데이터 저장[편집]
- 지금까지 구조체 멤버 변수 단위로 데이터 처리했었는데, 이제 구조체단위로 데이터를 처리해보자.
- 구조체에 저장된 값을 다른 구조체에 복사하려면 다음과 같이 하면 된다.
<source lang="c++"> struct time first = { 11, 3, 10 }; // 구조체 선언하면서 초기화 struct time temp; temp = first; </source>
- 여기서 first는 초기화를 해서 값이 제대로 들어가 있는 상태인데, temp 안에는 이상한게 들어가있다
에러의 원흉 - 같은 구조체가 있다면 이런식으로 그냥 대입해서 초기화시킬수 있다. 근데 다른 구조체끼리는 안된다.
- 대입이 안되면 아래와 같이 해야 할거다.
<source lang="c++"> temp.hour = first.hour; temp.minute = first.minute; temp.second = first.second; </source>
- 예제로 살펴보자.
<source lang="c++"> // Struct Copy
- include <iostream>
using namespace std; struct time {
int hour; int minute; int second;
};
void main() {
struct time first = { 11, 3, 10 }; struct time temp; temp = first; cout << first.hour << " 시 " << first.minute << " 분 " << first.second << " 초 " << endl; cout << temp.hour << " 시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
} </source>
- 지금까지 예제에서 똑같은 부분이 많은데 그런건 원래 함수로 돌리라고 있는거지.
- 출력을 담당하는걸 함수로 만들어보자.
<source lang="c++"> void prn(struct time temp) {
cout << temp.hour << " 시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
} </source>
- 이렇게 그냥 전달하는걸 Call by value라 부름.
- 이런식으로 구조체 출력을 할 수 있음
<source lang="c++"> prn(first); prn(now); </source>
- 예제로 보자.
<source lang="c++"> // Struct Print Function
- include <iostream>
using namespace std; struct time {
int hour; int minute; int second;
};
void prn(struct time tempp); // Define Function Prototype void main() {
struct time first = { 11, 3, 10 }; struct time now = { 12, 5, 20 }; prn(first); prn(now);
}
void prn(struct time temp) {
cout << temp.hour << " 시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
} </source>
구조체 포인터로 함수 전달[편집]
- 함수는 기본적으로 Call by Value로 사용한다.
- 그런데 Call by value로 전달하면 함수에서 새로 메모리를 할당하기 때문에 함수를 빠져나가면 함수의 내용이 반영되지 않음.
<source lang="c++"> void main() {
strcut time first = { 10, 70, 89 }; setEmpty(first); // 함수호출 prn(first);
}
void setEmpty(struct time temp) {
temp.hour = 0; temp.min = 0; temp.sec = 0;
} </source>
- 이걸 돌려보면 원래 내용이 전혀 바뀌지 않는걸 확인할 수 있다.
- 포인터를 이용하면 내용 변경이 가능하다.
<source lang="c++"> void main() {
struct time first = { 10, 70, 89 }; setEmpty(&first); prn(first);
}
void setEmpty(struct time *ptr) {
ptr->hour = 0; ptr->min = 0; ptr->second = 0;
} </source>
- 그럼 이제 좀 쓸모있는거.. 시간을 standardization해주는걸 하나 만들어보자.
- 그게 뭐냐면.. 뭐 이게 몇만초면 몇시간 몇분 몇초다.. 뭐 그런거..
- 그거 하고 나서 원래 값이 안 바뀌어 있으면 말짱 도로목이니 주소를 넘겨서 원래 내용을 변경하도록 해야 한다.
<source lang="c++"> // Time Standardization
- include <iostream>
using namespace std; struct time {
int hour; int minute; iint second;
};
void prn(struct time temp); void normalize(struct time *ptr);
void main() {
struct time first = { 10, 70, 89 }; prn(first); normalize(&first); prn(first);
}
void prn(struct time temp) {
cout << temp.hour << " 시 " << temp.minutes << " 분 " << temp.second << " 초 " << endl;
}
void normalize(struct time *ptr) {
int quot; if(ptr->second > 60) { mok = ptr->second / 60; ptr->minute = ptr->minute + quot; ptr->second = ptr->second - 60 * quot; } if(ptr->minute > 60) { mok = ptr->minute / 60; ptr->hour = ptr->hour + quot; ptr->minute = ptr->minute - 60 * quot; }
} </source>
함수 리턴값이 구조체일 경우[편집]
- 함수는 결과값을 리턴할 수 있는데, 구조체로 뽑아올 수 있다.
<source lang="c++"> // Combine Time
- include <iostream>
using namespace std; struct time {
int hour; int minute; int second;
};
void normalize(struct time *ptr); void prn(struct time temp); struct time total(struct time one, struct time two);
int main() {
struct time one = { 10, 50, 19), two = { 2, 30, 50), res; /// 구조체 3개 res = total(one, two); prn(res); return 0;
}
struct time total(struct time one, struct time two) {
strcut time temp; temp.hour = one.hour + two.hour; temp.minute = one.minute + two.minute; temp.second = one.second + two.second; normalize(&temp); return temp;
}
void normalize(struct time *ptr) {
int quot; if(ptr->second > 60) { quot = ptr->second / 60; ptr->minute += quot; ptr->second -= 60*quot; } if(ptr->minute > 60) { quot = ptr->minute / 60; ptr->hour += quot; ptr->minute -= 60 * quot; }
}
void prn(struct time temp) {
cout << temp.hour << " 시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
} </source>
레퍼런스 변수[편집]
- C++에서 추가된것중에 하나가 레퍼런스 변수
- 레퍼런스는 선언 후에 또 선언해줘야 한다.
- 리눅스의 ln(하드링크)같은 개념이라고 보면 될라나.
<source lang="c++"> 자료형 & 별명 = 선언되어 있는 변수; </source>
- 주의할건 이게 포인터연산자가 아니라는거다.
- 예제가 있어야 좀 이해가 쉽겠지.
<source lang="c++"> // Reference Variable
- include <iostream>
using namespace std;
int main() {
int a; a = 10; int &b = a; cout << " a = " << a << ", b = " << b << endl; b = 30; cout << " a = " << a << ", b = " << b << endl;
return 0;
} </source>
- b는 a랑 같다. 뭐 이름하고 별명이 있는 친구라고 보면 됨.
- 별명을 지었는데 누군지 모르면 별명이 아니잖아.
<source lang="c++"> int &b; </source>
레퍼런스 변수 전달[편집]
- 근데 이 레퍼런스 변수란놈은 선언할 때 좀 신경쓰고 함수를 쓸 땐 그냥 써도 포인터를 통하는것처럼 쓸 수 있게 된다.
- 그래서 C 유저가 이걸 덕지덕지 발라놓은 C++ 코드를 볼때 개빡치는 경험을 할 수 있다. 이렇게.
<source lang="c++"> // Reference Variable Function
- include <iostream>
using namespace std; void sub(int &b); int main() {
int a = 10; cout << " a = " << a << endl; cout << " address of a = " << &a << endl; // 이건 포인터 연산자 sub(a); cout << " a = " << a << endl;
}
void sub(int &b) {
cout << " b = " << b << endl; cout << " address of b = " << &b << endl;
b = 30; cout << " b = " << b << endl;
} </source>
구조체에 레퍼런스 변수 적용[편집]
- Call by Address에다가 레퍼런스 변수를 쓰면 Call by Reference가 된다.
<source lang="c++"> void main() {
struct time first = { 10, 70, 89 }; setEmpty(first);
}
void setEmpty(struct time &ref) {
ref.hour = 0; ref.min = 0; ref.secnond = 0;
} </source>
- 여기에 나오는 main 함수 안에 first랑 setEmpty 함수 안에 ref랑 같은거다.
- Call by value는 메모리를 다시 할당받으니까 구조체 크기가 크면 메모리 낭비가 심하다.
- 근데 Call by reference는 메모리 할당을 새로 받지 않아도 되니까 좀 더 빠르고 메모리 낭비를 줄일 수 있다.
- Call by value는 원본 내용을 함수가 변경할 수 없다. 안정성 짱임.
- 근데 Call by reference를 쓰면서도 안정성을 확보하는 방법이 있다.
<source lang="c++"> void prn(const struct time &temp) {
cout << temp.hour << "시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
} </source>
- 이렇게 const를 붙여버리면 함수 안에서 값을 변경하지 못하게 되니까 성능도 잡고 안정성도 잡고 프로그래머의 멘탈은 나가고..
- 그래서 결국은 이렇게 만들게 되는거지.
<source lang="c++"> // Call by Reference
- include <iostream>
using namespace std; struct time {
int hour; int minute; int second;
};
void prn(const struct time &temp); void normalize(struct time &ref);
int main() {
struct time first = { 10, 70, 89 }; prn(first); normalize(first); prn(first);
return 0;
}
void prn(const struct time &temp) {
cout << temp.hour << " 시 " << temp.minute << " 분 " << temp.second << " 초 " << endl;
}
void normalize(struct time &ref) {
int quot; if(ref.second > 60) { quot = ref.second / 60; ref.minute = ref.minute + quot; ref.second = ref.second - 60 * quot; } if(ref.minute > 60) { quot = ref.minute / 60; ref.hour = ref.hour + quot; ref.minute = ref.minute - 60 * quot; }
} </source>
클래스와 객체[편집]
클래스 설계[편집]
- 객체지향 프로그램이면 보통 자료형을 설계할때 데이터의 저장측면만 고려하지는 않고 데이터를 처리할 메소드(멤버함수)를 같이 제공하드라.
<source lang="c++"> 자료형 = 데이터를 저장할 변수 + 데이터를 처리할 메소드 </source>
- 이런 자료형을 구현하기 위해서는 Class라는걸 쓰게 된다.
- 간단하게, Class는 다음과 같다.
<source lang="c++"> 클래스 = 멤버변수 + 멤버함수 </source>
- 2차원 평면 컴퓨터 화면에 특정 위치에 대한 정보를 저장하고, 이를 처리하는 클래스를 정의해보자.
- 특정 위치는 뭐 다들 익숙한 x, y 좌표값으로 표현하자. 우리가 만들어 볼 클래스는 x, y좌표에 대한 정보를 저장할 멤버 변수가 있어야겠지.
내부 외부 x putx(); y puty(); putxy(); getx(); gety(); getxy();
- 멤버변수를 보호하는 느낌이 좀 드나?
클래스 정의[편집]
- C++ 프로그램은 객체가 중심이 되는 프로그래밍이라고 한다. 뭐 요즘은 좀 다른거 같지만.
- 객체를 만들려면 뭐 다른 방법도 있기야 하겠지만 클래스로 보통 만들게 되지.
- 그럼 선언 형식을 보자.
<source lang="c++"> // 클래스 정의 class 클래스이름 { [액세스 지정자:]]
자료형 멤버변수;
[액세스 지정자:]]
자료형 멤버함수();
}; // 멤버 함수 정의 자료형 클래스이름::멤버함수() { } </source>
액세스 지정자[편집]
- 클래스를 정의할때 각 멤버들은 그들이 어떤 접근 권한을 갖게 되는지 정하기 위해 액세스 지정자를 사용하게 된다.
- private는 같은 클래스 소속 멤버함수로만 접근이 가능하기 때문에 뭔가 좀 안전해진 느낌을 받는다.
느낌만 받는다. 하지만 현실은 포인터 이 개.. - public은 다른 외부 객체에서 접근해도 되도록 하는 액세스 지정자다. 내부의 내용을 전달해줄 때 쓰겠지?
- 그럼 좌표를 표현할 Point라는 클래스를 만들어보자.
<source lang="c++"> class Point { private:
int x; int y;
public:
void SetPoint(int argx, int argy); int GetX(void); int GetY(void);
}; </source>
- 클래스 내에 멤버를 선언할때 지정자 생략하면 private임
<source lang="c++"> class Point {
int x; int y;
}; </source>
- 위처럼 지정하면 Private로 지정된다.
클래스 내 변수에 접근하기 위한 멤버 함수 정의[편집]
- 위에서 정의한거에 대한 실제 함수를 만들어보자.
- 클래스 소속을 지정해주지 않으면 컴파일러는 혼란스러워한다. 컴파일러가 혼란스러우면 에러.
- 클래스명과 함수명 사이에는 스코프 연산자 ::를 넣어줘야 함
<source lang="c++"> void Point::SetPoint(int argx, int argy) {
x = argx; y = argy;
} int Point::GetX(void) {
return x;
} int Point::GetY(void) {
return y;
} </source>
객체 생성과 멤버함수 호출[편집]
- 클래스는 구조체처럼 자료형으로 볼 수 있다. 쓸라믄 선언해줘야 함.
<source lang="c++"> Point startPt, endPt; </source>
- Point는 자료형이고, startPt, endPt가 실제 쓰이는 객체라고 보면 된다.
- 초기화를 안해줬으니 이상한 값이 들어가 있을거다. Set으로 초기화를 시키면 되지.
<source lang="c++"> startPt.SetPoint(10, 10); endPt.SetPoint(50, 50); </source>
- 멤버로 접근할때는 구조체처럼 .연산자를 사용하면 된다.
- 다음은 Point라는 객체에 저장된 값을 출력하는 코드임
<source lang="c++"> cout << startPt.GetX( ) << ", " << startPt.GetY( ) << endl; cout << endPt.GetX( ) << ", " << startPt,GetY( ) << endl; </source>
- 좌표값을 저장하고 있는 x, y 변수를 사용하지 않고 GetX(), GetY() 함수로 좌표값을 알아내 출력해야 한다. 값을 저장하고 있는 멤버변수가 private로 선언되어 있기 때문에 각 멤버의 값을 알고 싶을때 값을 알려주는 멤버함수도 생각하면서 클래스를 정의해야 함.
<source lang="c++"> // Class Design, Using Objects
- include <iostream>
using namespace std; class Point { private:
int x; int y;
public:
void SetPoint(int argx, int argy); int GetX(void); int GetY(void);
}; void Point::SetPoint(int argx, int argy) {
x = argx; y = argy;
} int Point::GetX(void) {
return x;
} int Point::GetY(void) {
return y;
} void main() {
Point startPt, endPt; startPt.SetPoint(10, 10); endPt.SetPoint(50, 50);
cout << startPt.GetX( ) << ", " << startPt.GetY( ) << endl; cout << endPt.GetX( ) << ", " << endPt.GetY( ) << endl;
} </source>
클래스 내부에 멤버함수 정의하기[편집]
- 멤버 함수의 정의가 짧으면 클래스 선언 내부에 직접 함수를 정의할 수 있다. 일단 인라인 함수라는것부터 살펴보자.
인라인 함수[편집]
- C++에서는 함수가 호출되면 그 호출당한 함수로 제어가 넘어가서 수행되고, 함수 내용을 모두 수행한 후에는 원래 수행되던 곳으로 제어가 다시 되돌아옴.
스택 - 원래 수행되던 곳으로 되돌아오기 위해서는 호출당한 함수로 제어가 넘어가기 전에 지금 수행중이던 위치에 대한 정보를 저장하였다가 다시 되돌아오게 되는데 이게 시간이 좀 걸릴 수 있다고 한다. 그럴때 스택없이 호출하는게 인라인 함수다.
- 타임크리티컬한 프로그램을 작성한다면 inline 키워드를 앞에 붙여서 이득을 꾀할 수 있다.
<source lang="c++"> inline void Point::SetPoint(int argx, int argy) {
x = argx; y = argy;
} </source>
자동 인라인[편집]
- 멤버함수 정의가 짧으면 클래스 선언 내부에 직접 함수를 정의할 수 있다. 클래스 내부에 정의된 함수는 함수 선언 앞에 inline을 안붙여도 자동으로 인라인 함수가 된다.
- 클래스 내부에 정의된 함수는 다 인라인 함수다.
<source lang="c++"> // Define inline function
- include <iostream>
using namespace std; class Point { private:
int x; int y;
public:
void SetPoint(int argx, int argy); int GetX(void) { return x; } int GetY(void) { return y; }
};
inline void Point::SetPoint(int argx, int argy) {
x = argx; y = argy;
}
void main( ) {
Point startPt, endPt;
startPt.SetPoint(10, 10); endPt.SetPoint(50, 50); cout << startPt.GetX( ) << ", " << startPt.GetY( ) << endl; cout << endPt.GetX( ) << ", " << endPt.GetY( ) << endl;
} </source>
- 인라인 함수는 프로그램 처리속도를 보강하기 위한거지만 함수가 재사용이 되지 않으니 바이너리 크기가 늘어난다.
const 예약어와 const 멤버함수[편집]
- const에 대해 알아보기 전에 변수와 상수를 간단하게 비교해보자.
- 변수는 값을 변경할 수 있고, 상수는 값을 변경할 수 없다.
- 상수는 영어로 constant임.
- C++에서는 프로그램을 수행하는 동안 변수값 변경 못하게 const란 예약어가 제공됨.
- 즉, 변순데 상수처럼 만드는거임. 컴파일할때 대입하는거 찾아서 다 막으니까 상수처럼 쓸일이 있으면 유용함.
const 예약어[편집]
- 변수를 선언할때 const를 타입앞에 넣어주면 변경불가한 변수가 만들어짐.
<source lang="c++"> const double pi = 3.14; </source>
- const는 변수라고 생각 안하는게 편함.
- 이름있는 상수라고도 불림
- C에서 #define 거는거 대신 쓰기도 함
클래스, 객체, 인스턴스[편집]
- 용어 정리. 예를 들어 아파트가 있다면 설계도가 클래스, 실제 지은 아파트가 인스턴스다.
- 선언 해주고, 함수랑 다 만들어도 메모리에 아직 안 올라간 프로그램이 클래스.
- 실제 메모리에 올라간게 인스턴스. 객체는 올라갈 내용이라고 보면 되겠다.
Capsulization과 지정자[편집]
- 좌표값을 설정하기 위해서 멤버 변수로 접근하는건 불가능. 멤버 변수의 액세스 지정자는 private으로 선언되어 있기 때문.
- 멤버변수에 값을 설정하기 위해서는 한참 돌아가는 느낌이 들어도 액세스 지정자가 public으로 선언된 멤버함수를 사용한다.
- 객체지향의 3가지 특징중에 캡슐화라는게 있는데, 객체가 어떻게 구성되었는지 구체적으로 드러내지 않고 원하는 처리 결과를 제공받기 위해 객체의 연산자나 멤버함수를 사용하는게 바로 Capsulization임.
- Capsulization은 액세스 지정자에 의해 구체적으로 구현됨. 필요한 함수만 public으로 선언해서 Capsulization를 하는게 좋다.