c/c++ 정리

손현수·2023년 1월 30일

자료형

  • 1byte: 8bit, 컴퓨터에서 데이터를 처리하는 가장 작은 단위
  • 정수형
    - char(1byte): 문자를 저장
    - short(2byte)
    - int(4byte)
    - long(4byte)
    - long long(8byte)
  • 실수형
    - float(4byte)
    - double(8byte)
  • sizeof(x): x의 크기를 알려줌

char형

	char a;

	scanf("%c", &a);

	printf("당신이 입력한 문자는 %c입니다.\n", a);
  • 위의 코드에서 a에 R을 입력했을 때의 코드의 흐름: scanf가 %c로 a를 받으므로 R의 아스키코드값 82가 a에 저장된다. 그 후에 printf함수가 %c의 형태로 a를 출력하므로 아스키코드값이 82에 대응되는 R이 출력된다.

전치와 후치의 차이

b = a++;	//후치의 경우 b에 a의 값을 할당한 후 a의 값을 1증가시킴
b = ++a;	//전치의 경우 먼저 a의 값을 1증가시키고 b에 a의 값을 할당함

switch구문

int main(void) {
	int choice;

	printf("새 게임: 1\n");
	printf("불러오기: 2\n");
	printf("설정: 3\n");
	printf("크레딧: 4\n");

	printf("원하는 설정을 입력하세요.:");
	scanf("%d", &choice);

	switch (choice) {
	case 1:
		printf("새 게임\n");
		break;
	case 2:
		printf("불러오기\n");
		break;
	case 3:
		printf("설정\n");
		break;
	case 4:
		printf("크레딧\n");
		break;
	default: 
		printf("잘못 입력하셨습니다.\n");
		break;
	}
}

자연수의 약수를 구하는 문제

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	int num;

	printf("자연수를 입력하세요.:");
	scanf("%d", &num);

	if (num <= 0) {
		printf("잘못 입력하셨습니다.\n");
	}
	else {
		for (int i = 1; i <= num; i++) {
			if (num % i == 0) {
				printf("%d ", i);
			}
		}
	}
	printf("\n");
}

포인터 연산

  • 포인터 변수에다가 어떤 숫자를 더하거나 빼면 포인터가 가리키고 있는 형의 byte 수 * 어떤 숫자만큼 더해지거나 빼진다.

배열 포인터

	int main(void) {
	int arr[3] = { 1, 2, 3 };

	int (*ptr_arr)[3];//길이가 3인 int형 배열을 가리키는 포인터를 선언
	ptr_arr = &arr;

	for (int i = 0; i < 3; i++) {
		printf("%d\n", (*ptr_arr)[i]);
	}//1, 2, 3 출력
}

2차원 배열을 가리키는 포인터

	int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

	int(*ptr)[3];//2차원 배열을 가리킬 때는 배열 포인터와 가리킬 배열의 열 정보가 필요하다.
	ptr = arr;
	
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			printf("%d ", ptr[i][j]);
		}
		printf("\n");
	}

포인터 배열

	int arr[4] = { 1, 2, 3, 4 };
	int* ptr[4];

	for (int i = 0; i < 4; i++) {
		ptr[i] = &arr[i];
	}

	for (int i = 0; i < 4; i++) {
		printf("%d ", *ptr[i]);
	}
	printf("\n");

배열 포인터와 포인터 배열의 선언 차이

  • 배열 포인터: int (*arr)[3] //길이가(열이) 3인 배열을 가리키는 포인터
  • 포인터 배열: int* arr[3] //포인터 변수들을 저장하는 배열

문자열이 저장되어 있는 배열을 가리키는 포인터 배열

	char arr[3][10] = { "Hello", "World", "Doodle" };
	char* ptr_str[3];
	
	for (int i = 0; i < 3; i++) {
		ptr_str[i] = arr[i];
	}

	for (int i = 0; i < 3; i++) {
		printf("%s\n", ptr_str[i]);
	}

2차원 배열 + 포인터 예제

	int arr[3][3] = {0};

	printf("%d\n", &arr);//arr[0][0]의 주소를 출력
	printf("%d\n", arr);//arr == &arr[0]이므로 arr[0][0] 주소를 출력
	printf("%d\n", *arr);//arr이 가리키는 것은 첫번째 행 전체, 즉 일차원 배열 전체를 가리킴 이때 일차원 배열 전체를 출력하면 그 일차원 배열의 주소값이 출력됨

	printf("%d\n", &arr[0]);//첫번째 행의 주소를 의미, 첫번째 행의 주소는 arr[0][0]의 주소와 같음
	printf("%d\n", arr[0]);//arr[0] == &arr[0][0] 따라서 arr[0][0]의 주소를 출력
	printf("%d\n", *arr[0]);//arr[0][0]의 값을 출력

	printf("%d\n", &arr[0][0]);//arr[0][0]의 주소를 출력

	printf("%d\n", arr[0][0]);//arr[0][0]의 값을 출력
    
    //------------------------------------------------
    int arr[3][3] = { 0 };

	printf("%d\n", &arr[0][0]);//arr[0][0] 주소 출력

	printf("%d\n", arr[0] + 1);//arr[0][0]의 주소 + 4 출력
	printf("%d\n", &arr[0] + 1);//arr[1][0]의 주소 출력, arr[0][0] + 12 출력
	printf("%d\n", arr + 1);//arr[1][0]의 주소 출력, arr[0][0] + 12 출력
	printf("%d\n", &arr + 1);//arr[0][0] + 36 출력

c++ 스타일 입력, 출력

#include <iostream>
#include <string>

using namespace std;

int main(void) {
	int a;

	cin >> a;
	cout << a << endl;
}

범위 기반 for문

#include <iostream>
#include <string>

using namespace std;

int main(void) {
	int arr[] = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3 };

	for (int n : arr) {//변수 n에 arr의 원소를 하나씩 대입하고 마지막 원소까지 대입이 끝나면 for문을 빠져나감
		cout << n << endl;
	}//주의할 점은 n이 reference 변수가 아니므로 실제 배열의 값을 변경하는 것은 불가능함

	for (int& n : arr) {//n을 reference 변수로 선언함으로써 이제는 배열의 값을 변경하는 것이 가능함
		n++;
		cout << n << endl;
	}
}

범위 기반 for문으로 이차원 배열 출력하기

int main(void) {
	int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

	for (int(&ln)[3] : arr) {
		for (int& col : ln) {
			cout << col << ' ';
		}
		cout << endl;
	}
}

디폴트 매개변수

int item[64] = { 0 };
int score = 0;

void getItem(int itemId, int cnt = 1, int sc = 0) {//cnt 또는 sc의 값이 들어오지 않는다면 기본적으로 설정되어 있는 값을 변수에 할당하여 함수의 연산을 진행함
	score += sc;
	item[itemId] += cnt;
}

int main(void) {
	getItem(6, 5);
	getItem(3, 2);
	getItem(3);
	getItem(11, 34, 7000);

	cout << score << endl;
	for (int i = 0; i < 16; i++) {
		cout << item[i] << ' ';
	}
	cout << endl;
}
  • 디폴트 매개변수에서 주의해야 할 점은 디폴트 값이 설정되어 매개변수들을 오른쪽으로 몰아서 함수를 선언해야 함

namespace

  • namespace의 용도는 같은 이름의 함수를 구분하기 위해서 사용한다.
int n;

void set() {
	::n = 10;//::을 붙여줌으로써 n이 전역변수임을 표현할 수 있다.
}

namespace doodle {
	int n;
	void set() {
		doodle::n = 20;//doodle에 있는 n에 20을 할당해라
	}
}

namespace goodle { 
	int n;
	void set() {
		goodle::n = 30;
	}
}

int main(void) {
	::set();
	doodle::set();
	goodle::set();

	cout << ::n << endl;
	cout << doodle::n << endl;
	cout << goodle::n << endl;
}
  • 함수를 선언할 때 상단에 선언부를 작성하고 코드 후반에 함수를 완성하듯이 namespace에 함수의 선언부만 작성을 해두고 뒷쪽에서 함수를 완성시키는 것이 가능하다.
int n;

void set();

namespace doodle {
	int n;
	void set();
}

namespace goodle { 
	int n;
	void set();
}

int main(void) {
	::set();
	doodle::set();
	goodle::set();

	cout << ::n << endl;
	cout << doodle::n << endl;
	cout << goodle::n << endl;
}

void ::set() {
	::n = 10;//::을 붙여줌으로써 n이 전역변수임을 표현할 수 있다.
}

void doodle::set() {
	doodle::n = 20;//doodle에 있는 n에 20을 할당해라
}

void goodle::set() {
	goodle::n = 30;
}

namesapce 안의 namespace

int n;

void set() {
	n = 10;
}

namespace doodle {
	int n;
	void set() {
		n = 20;
	}

	namespace google {
		int n;
		void set() {
			n = 30;
		}
	}
}

int main(void) {
	::set();
	doodle::set();
	doodle::google::set();

	cout << ::n << endl;
	cout << doodle::n << endl;
	cout << doodle::google::n << endl;
}
  • 위의 코드에서 doodle과 google namespace의 set() 함수를 보면 어떤 n에 값을 할당하는 것인지 명확하게 구분하고 있지 않다. 이런 경우에 google namespace의 함수가 값을 할당할 때는 같은 namespace 안에 있는 변수를 기준으로 값을 할당한다.

클래스

  • 클래스 == 자료 저장 + 자료 처리 == 변수 + 함수
  • 클래스: 특정한 용도를 수행하기 위한 변수와 함수를 모아둔 틀(자료형)
  • 객체(오브젝트): 클래스라는 틀을 이용하여 찍어낸 개체(변수, 메모리 상의 공간)
struct Tv {
	bool powerOn;
	int channel;
	int volume;

	void setVolume(int vol) {
		if (vol >= 0 && vol <= 100) {
			volume = vol;
		}
	}
};

int main(void) {
	Tv lg;
	lg.powerOn = true;
	lg.channel = 10;
	lg.setVolume(50);
}

접근 제어 지시자

  • private, protected, public
struct Tv {
private:
	bool powerOn;
	int channel;
	int volume;

public:
	void on() {
		powerOn = true;
		cout << "TV를 켰습니다." << endl;
	}

	void off() {
		powerOn = false;
		cout << "TV를 껐습니다." << endl;
	}

	void volumeSet(int vol) {
		if (vol >= 0 && vol <= 100) {
			volume = vol;
		}
	}

	void channelSet(int cnl) {
		if (cnl >= 0 && cnl <= 999) {
			channel = cnl;
		}
	}
};

int main(void) {
	Tv lg;

	lg.on();
	lg.channelSet(10);
	lg.volumeSet(50);
}
  • 위의 코드에서 Tv 구조체를 보면 powerOn, channel, volume 변수는 private의 영향을 받는 범위 내에서 선언된 변수이다. 따라서 Tv 구조체를 벗어난 외부에서 위의 변수들에 접근하는 것이 불가능하다. private의 범위 내의 변수나 함수는 오로지 구조체 내에서만 접근이 가능하다. 그러므로 main 함수에서 Tv 구조체의 변수에 접근하는 것을 조건부로 허용하기 위해서 public을 선언하고 제한적으로 변수에 접근하는 것이 가능하게 함수를 만들 수 있다.
  • 위의 코드에서처럼 구조체 내부의 변수를 외부로부터 보호하기 위해 private으로 감싸는 행위를 "캡슐화"라고 한다.
  • 구조체에서 아무런 지시자 없이 변수를 선언하면 public으로 간주함
  • 만약 위의 코드에서 struct를 class로 바꿔서 선언하면 아무런 지시자가 없는 변수들이 private이 된다.

생성자를 호출하는 여러가지 방법

class Complex {
public:
	Complex(double real_ = 0, double unreal_ = 0) {
		real = real_;
		unreal = unreal_;
	}
	double getReal() {
		return real;
	}
	void setReal(double real_) {
		real = real_;
	}
	double getUnreal() {
		return unreal;
	}
	void setUnreal(double unreal_) {
		unreal = unreal_;
	}
private:
	double real;
	double unreal;
};

int main(void) {
	Complex c1;
	Complex c2 = Complex(2, 3);
	Complex c3(2, 3);
	Complex c4 = { 2, 3 };
	Complex c5 = Complex{ 2, 3 };
	Complex c6{ 2, 3 };
}

생성자 위임

  • 사용하는 이유: 비슷한 형태의 생성자가 반복될 경우 반복 작업을 코드에서 간소화할 수 있다.
class Time {
public:
	Time() : h(0), m(0), s(0) {}
	Time(int s_) : Time(){
		s = s_;
	}
	Time(int m_, int s_) : Time(s_){
		m = m_;
	}
	Time(int h_, int m_, int s_) : Time(m_, s_){
		h = h_;
	}
	int h;
	int m;
	int s;
};

int main(void) {
	Time t1;
	Time t2(5);
	Time t3(3, 16);
	Time t4(2, 42, 15);

	cout << "t1: " << t1.h << ":" << t1.m << ":" << t1.s << endl;
	cout << "t2: " << t2.h << ":" << t2.m << ":" << t2.s << endl;
	cout << "t3: " << t3.h << ":" << t3.m << ":" << t3.s << endl;
	cout << "t4: " << t4.h << ":" << t4.m << ":" << t4.s << endl;
}
  • 위의 코드에서 감안해서 볼 점은 일반적으로 클래스의 멤버 변수는 private으로 선언이 되어있어서 직접 접근이 불가능하다. 간단한 예시를 들기 위해서 멤버 변수를 public의 범위에 삽입하고 직접 접근한 것. 멤버 변수의 값을 얻고 싶을 때는 클래스의 함수를 통해서 접근하는 것이 바람직하다.

정적인 함수(static)를 사용하는 경우

  • 함수가 클래스와 밀접한 관련이 있고 클래스의 private field 멤버 변수를 사용해야 하는 경우에 함수를 클래스의 멤버로 포함시키고 static으로 선언하면 클래스명::함수명 형태로 메인 함수에서 호출하는 것이 가능하다.

메소드의 상수화

	int ViewMoney() const {
		return money;
	}
  • 어떤 클래스 안의 멤버 메소드에 const를 붙이면 멤버 변수를 수정할 수 없는 메소드로 간주한다.

매개변수의 상수화

	void Deposit(const int d) {
		money += d;
		cout << d << "원을 예금했다." << endl;
	}
  • Deposit 메소드 안에서 d의 값을 새로운 값으로 할당하는 실수를 저지르는 것을 방지하기 위해서 매개변수를 const로 선언한다.

연산자 오버로딩

class Vector2 {
public:
	Vector2();
	Vector2(float x, float y);

	float GetX() const;
	float GetY() const;

	Vector2 operator+(const Vector2 rhs) const;
	Vector2 operator-(const Vector2 rhs) const;
	Vector2 operator*(const float rhs) const;
	Vector2 operator/(const float rhs) const;
	float operator*(const Vector2 rhs) const;
private:
	float x;
	float y;
};

Vector2 sum(Vector2 a, Vector2 b) {
	return Vector2(a.GetX() + b.GetX(), a.GetY() + b.GetY());
}

int main(void) {
	Vector2 a(2, 3);
	Vector2 b(-1, 4);
	Vector2 c1 = a - b;
	Vector2 c2 = a * 1.6;
	Vector2 c3 = a / 2;
	float c4 = a * b;

	cout << a.GetX() << ", " << a.GetY() << endl;
	cout << b.GetX() << ", " << b.GetY() << endl;
	cout << c1.GetX() << ", " << c1.GetY() << endl;
	cout << c2.GetX() << ", " << c2.GetY() << endl;
	cout << c3.GetX() << ", " << c3.GetY() << endl;
	cout << c4 << endl;
}

Vector2::Vector2() : x(0), y(0) {}
Vector2::Vector2(float x, float y): x(x), y(y) {}

float Vector2::GetX() const { return x; }
float Vector2::GetY() const { return y; }

Vector2 Vector2::operator+ (const Vector2 rhs) const {
	return Vector2(x + rhs.x, y + rhs.y);
}
Vector2 Vector2::operator- (const Vector2 rhs) const {
	return Vector2(x - rhs.x, y - rhs.y);
}
Vector2 Vector2::operator* (const float rhs) const {
	return Vector2(x * rhs, y * rhs);
}
Vector2 Vector2::operator/ (const float rhs) const {
	return Vector2(x / rhs, y / rhs);
}
float Vector2::operator* (const Vector2 rhs) const {
	return (x * rhs.x, y * rhs.y);
}
  • 일반적으로 객체와 객체를 더하거나 빼는 연산이 불가능하지만 operator+, operator- 등과 같이 연산자 오버로딩을 하면 객체와 객체를 더하거나 빼는 것이 가능하다.

c++ 스타일 동적할당

	int* num = new int(5);//new가 하는 일은 메모리 공간에 5를 저장할 공간을 만들어서 5를 저장하고 그 메모리 공간의 주소를 반환하는 것
	int len;
	int* arr;

	cout << "배열의 크기를 입력하세요.";
	cin >> len;

	arr = new int[len];//c++에서는 malloc 대신 new를 사용하여 동적할당을 한다.
    delete num;
    delete[] arr;//우리가 할당한 공간은 반드시 해제해야 한다

얕은 복사, 깊은 복사

	int* a = new int(5);
	int* b = new int(3);

	a = b;//얕은 복사
	*a = *b;//깊은 복사

	delete a;
	delete b;

깊은 복사를 이해하기 위한 String 클래스 만들어 보기

class String {
public:
	String() {
		cout << "String() 생성자 호출" << endl;
		strData = NULL;
		len = 0;
	}

	String(const char* str) {
		cout << "String(char* str) 생성자 호출" << endl;
		len = strlen(str);
		strData = new char[len + 1];
		cout << "strData 할당: " << (void*)strData << endl;
		strcpy(strData, str);
	}

	String(const String &rhs) {
		cout << "String(String &str) 생성자 호출" << endl;
		len = rhs.len;
		strData = new char[len + 1];
		cout << "strData 할당:" << (void*)strData << endl;
		strcpy(strData, rhs.strData);
	}

	~String() {
		cout << "~String() 소멸자 호출" << endl;
		delete[] strData;
		cout << "strData 해제됨" << endl;
		strData = NULL;
	}

	String& operator=(String& rhs) {
		if (this != &rhs) {
			delete[] strData;
			cout << "String(String &str) 생성자 호출" << endl;
			len = rhs.len;
			strData = new char[len + 1];
			cout << "strData 할당:" << (void*)strData << endl;
			strcpy(strData, rhs.strData);

			return *this;//this는 그 객체의 주소를 가지고 있는 변수. 따라서 *this를 하게 되면 그 객체 자체를 의미한다.
		}
	}
	char* GetstrData() {
		return strData;
	}
	int Getlen() {
		return len;
	}
private:
	char* strData;
	int len;
};

int main(void) {
	String s1("Hello world");
	String s2(s1);
	String s3("Hello");
	s3.operator=(s1);
	//s3가 operator= 함수를 호출했기 때문에 operator= 함수의 this에는 s3의 주소가 저장되어 있다. 그 후 return으로 s3 객체가 주어지지만 현재 코드에서 아무런 작업도 하지 않고 있으므로 무시가 된다.

	cout << s1.GetstrData() << endl;
	cout << s2.GetstrData() << endl;
	cout << s3.GetstrData() << endl;
}

얕은 복사, 깊은 복사를 이해하기 위한 Polygon 클래스 만들어 보기

struct Point {
	int x, y;
};

class Polygon {
public:
	Polygon() {
		nPoints = 0;
		points = NULL;
	}
	Polygon(const int nPoints, const Point* points): nPoints(nPoints) {
		this->points = new Point[nPoints];
		for (int i = 0; i < nPoints; i++) {
			this->points[i] = points[i];
		}
	}

	Polygon(Polygon &x) {//복사 생성자
		nPoints = x.nPoints;
		points = new Point[nPoints];
		for (int i = 0; i < nPoints; i++) {
			points[i] = x.points[i];
		}
		cout << "깊은 복사" << endl;
	}

	Polygon(Polygon&& rhs) {//이동 생성자
		nPoints = rhs.nPoints;
		points = rhs.points;
		rhs.points = NULL;
		cout << "얕은 복사" << endl;
	}

	~Polygon() {
		delete[] points;
	}

	Polygon& operator=(const Polygon& rhs) {//복사 대입 연산자
		if (this != &rhs) {
			delete[] points;
			nPoints = rhs.nPoints;
			points = new Point[nPoints];
			for (int i = 0; i < nPoints; i++) {
				points[i] = rhs.points[i];
			}
		}
		cout << "깊은 복사" << endl;
		return *this;
	}

	Polygon& operator=(Polygon&& rhs) {//이동 대입 연산자
		if (this != &rhs) {
			nPoints = rhs.nPoints;
			delete[] points;
			points = rhs.points;
			rhs.points = NULL;
		}
		cout << "얕은 복사" << endl;
		return *this;
	}

	int GetnPoints() const {
		if (nPoints == 0) return NULL;
		return nPoints;
	}

	Point* Getpoints() {
		return points;
	}
private:
	int nPoints;
	Point* points;
};

Polygon getSquare() {
	Point points[4] = { {0, 0}, {0, 1}, {1, 0}, {1, 1} };
	Polygon p(4, points);
	return p;
}

int main(void) {
	Polygon a;
	a = getSquare();//얕은 객체 복사 2회
	Polygon b = a;//깊은 객체 복사 1회
	Polygon c;
	c = a;//깊은 객체 복사 1회

	int nPoints = c.GetnPoints();
	Point* points = c.Getpoints();
	for (int i = 0; i < nPoints; i++) {
		cout << "(" << points[i].x << "," << points[i].y << ")" << endl;
	}
	//main 함수의 흐름: a = getSquare()가 실행이 되면 getSquare 함수에서 Point 배열을 메모리 공간에 생성함. 현재 p의 points가 Point 배열을 가리키고 있는 상태.
	//이때 getSquare 함수가 return을 하게 되면 임시 객체가 생성이 되고 얕은 객체 복사가 한번 일어나게 됨. 임시 객체에 points2라는 변수가 있다고 가정하면 points2가 Point 배열을 가리키게 한다.
	//성공적으로 복사가 이루어지면 getSquare 함수가 종료될 때 p의 소멸자가 Point 배열을 소멸시키는 것을 방지하기 위해서 p의 points와 Point 배열의 연결을 끊어버림
	//그 이후에 임시 객체와 a 사이에서 위의 과정이 반복됨. 이 작업을 이동 대입 연산자를 사용하지 않고 진행하면 Point 배열이 3개가 생성되어 메모리 공간 낭비가 발생한다.
}

벡터

int main(void) {
	vector<int> v1 = { 1, 2, 3, 4 };

	v1.push_back(9);
	v1.push_back(9);
	v1.pop_back();
	v1.shrink_to_fit();//vector의 늘어난 공간을 사이즈에 맞게 줄인다.
	v1.insert(v1.begin(), 5);
	v1.erase(v1.begin());

	cout << v1.front() << endl;
	cout << v1.back() << endl;
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;//capacity는 vector의 현재 용량, size는 vector가 현재 가지고 있는 요소의 수
	

	for (int& v : v1) {
		cout << v << endl;
	}

}

클래스 상속에서의 접근 제어자

  • public: 외부 접근 가능, 자식 클래스에서 사용 가능
  • protected: 외부 접근 불가능, 자식 클래스에서 사용 가능
  • private: 외부 접근 불가능, 자식 클래스에서 사용 불가능

간단한 클래스 상속해보기(메시지 관리 클래스)

class Image {
public:
	operator string() {
		return "사진";
	}
};

class Message {
public:
	Message(int sendtime, string name) : sendtime(sendtime), name(name) {}

	int Getsendtime() const {
		return sendtime;
	}
	string Getname() const {
		return name;
	}
private:
	int sendtime;
	string name;
};

class TextMessage: public Message {
public:
	TextMessage(int sendtime, string name, string sendMessage)
		: Message(sendtime, name), sendMessage(sendMessage) {}

	string GetsendMessage() const {
		return sendMessage;
	}
private:
	string sendMessage;
};

class ImageMessage: public Message {
public:
	ImageMessage(int sendtime, string name, Image* image)
		: Message(sendtime, name), image(image) {}

	Image* GetImage() const {
		return image;
	}
private:
	Image* image;
};

int main(void) {
	Image* p_dogImage = new Image();
	TextMessage* hello = new TextMessage(10, "doodle", "hello");
	ImageMessage* dog = new ImageMessage(20, "doodle", p_dogImage);

	cout << "보낸 시간: " << hello->Getsendtime() << endl;
	cout << "보낸 사람: " << hello->Getname() << endl;
	cout << "내용: " << hello->GetsendMessage() << endl;
	cout << endl;

	cout << "보낸 시간: " << dog->Getsendtime() << endl;
	cout << "보낸 사람: " << dog->Getname() << endl;
	cout << "내용: " << (string)*dog->GetImage() << endl;
	cout << endl;

	delete hello;
	delete dog;
	delete p_dogImage;
}

부모 클래스와 자식 클래스에 동일한 이름의 변수가 존재할 때 처리 방식

class Base {
public:
	int a = 10;
};

class Derived : public Base {
public:
	int a = 20;
};

int main(void) {
	Base b;
	Derived d;

	cout << b.a << endl;//10 출력
	cout << d.a << endl;//20 출력
	cout << d.Base::a << endl;//10 출력
	cout << d.Derived::a << endl;//20 출력
}
  • 오버라이드: 위의 코드에서 Derived 클래스는 부모 클래스로부터 상속받은 a와 자기 자신의 고유한 a가 멤버 변수로 존재한다. 이때 d.a를 출력하게 되면 두개의 a 중에 자기 자신의 고유한 a가 우선되어 출력된다. 이것을 오버라이드라고 한다. 이 개념은 함수에도 똑같이 적용됨

정적바인딩

class Base {
public:
	int a = 10;
	void print() {
		cout << "From Base!!" << endl;
	}
};

class Derived : public Base {
public:
	int a = 20;
	void print() {
		cout << "From Derived" << endl;
	}
};

class Derived2 : public Base {

};

int main(void) {
	Base* b = new Derived();//Base 클래스가 Derived 클래스의 부모 클래스이므로 부모 클래스 타입의 포인터 변수가 자손 클래스를 참조하는 것이 가능
	b->print();//b가 Derived를 참조하는 것은 맞지만 정적바인딩에 의해 b는 Base 타입의 변수이므로 Base에 존재하는 print 함수를 호출
	b = new Derived2();
	b->print();//Derived2 클래스에는 print 함수가 없음에도 코드에 에러가 나지 않는 이유는 정적바인딩 때문이다.
}

동적바인딩, 가상 함수(virtual)

class Weapon {
public:
	Weapon(int power): power(power) {}
	
	virtual void Use() {//가상 함수
		//virtual을 붙여주면 정적바인딩이 아닌 동적바인딩이 가능해진다. 
		//정적바인딩에서와 달리 부모 클래스 타입의 포인터 변수가 자손 클래스를 참조하고 있는 경우에 자손 클래스에 Use 함수가 존재한다면 자손 클래스에 있는 Use 함수를 실행시킨다.
		cout << "Weapon::Use()" << endl;
	}
private:
	int power;
};

class Sword : public Weapon {
public:
	Sword(int power): Weapon(power) {}
	void Use() {
		cout << "Sword::Use()" << endl;
		Swing();
	}
private:
	void Swing() {
		cout << "Swing Sword." << endl;
	}
};

class Magic : public Weapon {
public:
	Magic(int power, int manaCost) : Weapon(power), manaCost(manaCost) {}

	void Use() {
		cout << "Magic::Use()" << endl;
		Cast();
	}
private:
	int manaCost;

	void Cast() {
		cout << "Cast Magic." << endl;
	}
};

int main(void) {
	Sword mySword(10);
	Magic myMagic(15, 7);

	mySword.Use();
	myMagic.Use();

	Weapon* currentWeapon;
	currentWeapon = &mySword;
	currentWeapon->Use();//부모 클래스인 Weapon 클래스에서 Use 함수에 virtual이 붙어있으므로 자손 클래스에 Use 함수가 있는지 확인 후 있다면 자손클래스의 함수를 실행시킴
	currentWeapon = &myMagic;
	currentWeapon->Use();
}

동적바인딩의 편리함

class Image {
public:
	operator string() {
		return "사진";
	}
};

class Message {
public:
	Message(int sendtime, string name) : sendtime(sendtime), name(name) {}

	int Getsendtime() const {
		return sendtime;
	}
	string Getname() const {
		return name;
	}
	virtual string GetContent() const {
		return "";
	}
private:
	int sendtime;
	string name;
};

class TextMessage: public Message {
public:
	TextMessage(int sendtime, string name, string sendMessage)
		: Message(sendtime, name), sendMessage(sendMessage) {}

	string GetsendMessage() const {
		return sendMessage;
	}
	string GetContent() const {
		return sendMessage;
	}
private:
	string sendMessage;
};

class ImageMessage: public Message {
public:
	ImageMessage(int sendtime, string name, Image* image)
		: Message(sendtime, name), image(image) {}

	Image* GetImage() const {
		return image;
	}
	string GetContent() const {
		return (string)*image;
	}
private:
	Image* image;
};

void printMessage(Message* m) {
	cout << "보낸 시간: " << m->Getsendtime() << endl;
	cout << "보낸 사람: " << m->Getname() << endl;
	cout << "내용: " << m->GetContent() << endl;
	cout << endl;
}

int main(void) {
	Image* p_dogImage = new Image();

	Message* messages[] = {
		new TextMessage(10, "doodle", "hello"),
		new TextMessage(11, "doodle", "hello"),
		new TextMessage(12, "doodle", "hello"),
		new ImageMessage(20, "doodle", p_dogImage)
	};

	for (Message* m : messages) {
		printMessage(m);
	}

	delete p_dogImage;
}

순수 가상 함수, 추상 클래스

class Shape {//추상 클래스
public:
	virtual double getArea() = 0;//순수 가상 함수
	virtual void Resize(int x) = 0;//순수 가상 함수
};

class Rectangle : public Shape {
public:
	Rectangle(double a, double b): a(a), b(b) {}

	double getArea() {
		return a * b;
	}
	void Resize(int x) {
		a *= x;
		b *= x;
	}
private:
	double a, b;
};

class Circle : public Shape {
public:
	Circle(double r) : r(r) {}

	double getArea() {
		return PI * r * r;
	}
	void Resize(int x) {
		r *= x;
	}
private:
	double r;
};

int main(void) {
	Shape* shapes[] = {
		new Rectangle(20, 30),
		new Circle(10)
	};

	for (Shape* s : shapes) {
		s->Resize(2);
	}

	for (Shape* s : shapes) {
		cout << s->getArea() << endl;
	}
}
  • 순수 가상 함수: 가상 함수의 구현부가 없는 상태
  • 추상 클래스: 순수 가상 함수를 하나라도 가지고 있는 클래스는 추상 클래스이다.
  • 추상 클래스는 객체를 생성할 수 없다.

인터페이스

  • 특정 기능을 구현할 것을 약속
  • 순수 가상 함수만 멤버로 가지고 있다.
  • 모든 멤버는 public 범위에 선언
  • c++에서는 인터페이스 형식을 제공하지 않음
#define interface struct
profile
안녕하세요.

0개의 댓글