10.1주차
실습 3
문제 설명
자연수 n이 매개변수로 주어집니다. n을 x로 나눈 나머지가 1이 되도록 하는 가장 작은 자연수 x를 return 하도록 solution 함수를 완성해주세요. 답이 항상 존재함은 증명될 수 있습니다.
제한사항
3 ≤ n ≤ 1,000,000
입출력 예
n | result |
---|---|
10 | 3 |
12 | 11 |
내 코드
#include <string>
#include <vector>
using namespace std;
int solution(int n) {
int answer = 0;
for (int i = 2;; i++)
{
if (n % i == 1)
{
answer = i;
break;
}
}
return answer;
}
교수님 코드
풀이 생략
Operator overloading
원래 존재하는 타입인 int, double과 같은 operand에 대해서만 사용이 가능했다. 하지만 C++ 철학이 다형성이기 때문에 비슷하지만 함수 이름이 다른 정의는 피하자는 주의기 때문에 operator 역시 오버로딩을 가능하게 하자!
예를 들어서 더한다라는 개념은 내가 원하는 대로 구현이 가능하도록 하자. 근데 그렇게 된다면 너무 많은 이름이 생길 것 같다. 따라서 오버로딩을 가능하게 하자
모든 사람들이 친숙하게 알고있는 +, -, %, /, ^, « 와 같은 연산자를 user defined가 가능하도록 지원해주자.
오버로딩 VS 오버라이딩
- 오버로딩: 함수 이름은 동일하고, 매개변수 개수 또는 타입 등이 차이가 난다.
- 오버 라이딩: 함수 이름 뿐만 아니라 매개변수 개수와 타입까지 동일하다 -> 클래스 상속과 관련(나중에 다룸)
코드 예시
#include <iostream>
using namespace std;
class Negative
{
};
class Sample
{
private:
int n;
int m;
public:
Sample();
Sample(int x, int y);
~Sample();
void setN(int x);
int getN() const;
void setM(int y);
int getM() const;
// 연산자 오버로딩
Sample operator+(const Sample &a);
friend Sample operator-(const Sample &a, const Sample &b);
};
Sample::Sample()
{
n = 0;
cout << "Sample() is Called" << endl;
}
Sample::Sample(int n, int m)
{
this->n = n;
this->m = m;
cout << "Sample() is Called" << endl;
}
void Sample::setN(int x)
{
n = x;
}
int Sample::getN() const
{
return n;
}
void Sample::setM(int y)
{
m = y;
}
int Sample::getM() const
{
return m;
}
Sample::~Sample()
{
cout << n << " ~Sample() is Called" << endl;
}
// +만이 함수 이름이 아니라 operator+를 통해서 오버로딩을 해야 된다.
Sample Sample::operator+(const Sample &a)
{
Sample x;
x.m = this->m + a.m;
x.n = this->n + a.n;
return x;
}
Sample operator-(const Sample &a, const Sample &b)
{
Sample x;
x.m = a.m - b.m;
x.n = a.n - b.n;
return x;
}
int main()
{
Sample x;
x.setN(5);
x.setM(2);
Sample y(10, 20);
Sample y2(y);
Sample y3;
y3 = y2 + y;
cout << "x.getM(): " << x.getM() << endl;
cout << "y.getM(): " << y.getM() << endl;
cout << "y2.getM(): " << y2.getM() << endl;
cout << "y3.getM(): " << y3.getM() << endl;
}
main
int main()
{
Sample x;
x.setN(5);
x.setM(2);
Sample y(10, 20);
Sample y2(y);
Sample y3;
y3 = y2 + y;
cout << "x.getM(): " << x.getM() << endl;
cout << "y.getM(): " << y.getM() << endl;
cout << "y2.getM(): " << y2.getM() << endl;
cout << "y3.getM(): " << y3.getM() << endl;
}
+함수를 보게 되면 두 개의 파라미터가 필요할 것 같은데 실제로는 하나의 파라미터가 있다. 왜? 디폴트로 나 자신이 있고, 추가로 나와 더해질 하나만 더 필요하기 때문이다.
(센스 + 1) 그 다음 리턴되는 값을 더해지는 파라미터의 데이터형과 동일하게 하여 연속적인 덧셈을 가능하게 해준다. 즉, Sample + Sample = Sample이 될 수 있도록 해라.
Dynamic Memory
// new
pointer = new type
pointer = new type[number_of_elements]
// delete
delete pointer; // 배열로서 동적 할당을 받았다면 첫 번째 값만 지워지게 된다.
delete [] pointer; // 배열을 전체 다 지우기 위해서는 []를 꼭 써야된다.
이라고 할 때, Dynamic 메모리로 힙 영역에 메모리를 잡게 된다.
사용 예시 111
#include <iostream>
#include <new> // 없어도 에러는 안뜸.
using namespace std;
int main()
{
int i, n;
int *p; // 포인터 new를 통해서 int[i] 만큼 배열을 만듬.
cout << "How many numbers would you like to type? ";
cin >> i;
p = new (nothrow) int[i];
if (p == 0)
{
cout << "Error: memory could not be allocated";
}
else
{
for (n = 0; n < i; n++)
{
cout << "Enter number: ";
cin >> p[n];
}
cout << "You have entered: ";
for (n = 0; n < i; n++)
{
cout << p[n] << ", ";
}
delete[] p; // 배열 전체 삭제
}
return 0;
}
>>>
How many numbers would you like to type? 5
Enter number: 1
Enter number: 2
Enter number: 3
Enter number: 4
Enter number: 5
You have entered: 1, 2, 3, 4, 5,
- nothrow: 워래 메모리 할당 실패하면 예외 처리를 던지는데, 이 예제에서는 예외 없이 하려고 nothrow로 처리한 것이다.
사용 예시 222
중요한 문제 -> 스택이나 힙 중에 어느 영역에 어떤 변수가 쌓였는가?
#include <iostream>
using namespace std;
class CRectangle
{
int width, height;
public:
void set_values(int, int);
int area(void)
{
return width * height;
}
};
void CRectangle::set_values(int a, int b)
{
width = a;
height = b;
}
int main()
{
CRectangle a, *b, *c; // 1
CRectangle *d = new CRectangle[2]; // 2
b = new CRectangle; // 3
c = &a; // 4
a.set_values(1, 2);
b->set_values(3, 4);
d->set_values(5, 6); // = d[0].set_Values(같은 메모리를 뜻함.)
d[1].set_values(7, 8);
cout << "a area: " << a.area() << endl;
cout << "*b area: " << b->area() << endl;
cout << "*c area: " << c->area() << endl;
cout << "d[0] area: " << d[0].area() << endl;
cout << "d[1] area: " << d[1].area() << endl;
delete[] d; // 5
delete b; // 6
return 0;
}
1번 코드: a는 스택에 메모리가 잡히게 되고, b와 c는 포인터 변수로서 스택 메모리에 잡히게 된다. 따라서 1개의 인스턴스만 메모리에 잡히게 된다.
2번 코드: 이 경우에 인스턴스는 총 3개가 만들어졌다. a는 스택 에 있고, d가 가리키고 있는 힙 영역 에 CRectangle 2개가 연속적으로 메모리에 잡혀있다. CRectangle에 대한 인스턴스는 스택에, b, c, d 포인터 변수는 스택에, d에 2개의 인스턴스가 연속되어서 힘 영역에 메모리가 잡혀있다.
3번 코드: 총 메모리를 잡고 있는 인스턴스의 개수는 4개가 된다. 3개 중에서 array로 잡은 메모리 공간은 연속된 메모리 공간으로 d가 가리키고 있고, b의 경우는 연속된 메모리 공간은 아니지만 힙 영역에 잡혀있다.
4번 코드: 여전히 메모리 공간을 차지하고 있는 인스턴스는 4개이다. 스택에 잇는 것 (a), 힙에 3개(배열(2개), 1개)이고 c는 그냥 스택의 주소를 가리키는 포인터일 뿐이다.
가장 중요한 것은 결국 포인터 변수이게 된다면 무조건 화살표로 변수를 가르키는 것이 맞다.
5번 코드: []를 안하면 첫 번째 요소만 날려버림. []를 통해서 메모리를 날리는 것이 아니면 메모리 릭이 발생할 수 있다.
c를 딜리트 하지 않은 이유. c는 어차피 스택을 가리키고 있었으므로 스택은 delete 해줄 이유가 없다. delete는 힙에 잇는 메모리를 없앨 때만 사용한다.
main
int main()
{
CRectangle a, *b, *c; // 1
CRectangle *d = new CRectangle[2]; // 2
b = new CRectangle; // 3
c = &a; // 4
a.set_values(1, 2);
b->set_values(3, 4);
d->set_values(5, 6); // = d[0].set_Values(같은 메모리를 뜻함.)
d[1].set_values(7, 8);
cout << "a area: " << a.area() << endl;
cout << "*b area: " << b->area() << endl;
cout << "*c area: " << c->area() << endl;
cout << "d[0] area: " << d[0].area() << endl;
cout << "d[1] area: " << d[1].area() << endl;
delete[] d; // 5
delete b; // 6
return 0;
}
static Members
어떤 변수에 대해서는 공동으로 share 하고 싶다는 것이 있다. 그러한 변수를 만들 때, static을 붙혀준다.
클래스에 공동된 하나의 변수가 잡히고 모든 인스턴스가 공유하도록 할 수 있다. 이걸 class 변수라고 부르기도 한다.
#include <iostream>
using namespace std;
class CDummy
{
public:
static int n;
int m;
CDummy() { n++; }
~CDummy() { n--; }
static int getN() { return n; }
int getM() { return m; }
};
// n = 0으로 설정. start 초기화를 1회만 하는 게 일반적이다.
int CDummy::n = 0;
int main()
{
CDummy::getN(); // 1
CDummy a; // 스택에 잡힘: n = 1
CDummy b[5]; // 스택에 잡힘 대신 연속된 메모리 공간: n = 6
CDummy *c = new CDummy; // n = 7
cout << a.n << endl;
delete c; // n = 6
// a.n과 같은 의미이지만 static이라면 일반적으로 CDummy::n이라고 표현
cout << CDummy::n << endl;
cout << sizeof(a) << endl; // 2
return 0;
}
>>>
7
6
4
2번 코드: 클래스에 static 변수만 있을 때, sizeof가 1이 나온다. 근데 static 변수와 int 변수를 추가하게 된다면 일반적인 사이즈인 4로 출력이 된다.
즉, 일반적으로 sizeof에 static 변수는 포함시키지 않아. 하지만 클래스 내에 static 변수 외의 다른 변수가 없다면 sizeof = 1을 출력하게 된다.
1번 코드: 중요한 내용! 그렇다면 함수는 static이라는 개념이 없을 텐데, 왜 static 함수라는게 있을까? CDummy에 대한 오브젝트가 단 한개도 정의되어 있지 않는 상태에서 static 함수는 호출이 가능하지만 그냥 멤버 함수의 경우는 오브젝트가 호출되어 있지 않다면 호출할 수 없다.
static int getN() { return n + m; }
으로 정의된 함수가 있다면 m은 오브젝트가 생성된 후에 사용이 가능하므로 이 코드는 에러가 된다. 결국 static 함수에는 static 변수만 가능하다. 누구의 m인지를 특정할 방법이 없기 때문이다.
반대로 int getM() {return n + m;}
은 가능하다.
Friend Functions
Friend 함수
private 및 protected 멤버에 접근할 수 있는 권한을 부여할 수 있다. friend 기능을 클래스 단위가 아닌 멤버 함수 단위로 지정해주는 것이다.
#include <iostream>
using namespace std;
class CRectangle
{
int width, height;
public:
void set_values(int, int);
int area()
{
return width * height;
}
friend CRectangle duplicate(CRectangle);
};
void CRectangle::set_values(int a, int b)
{
width = a;
height = b;
}
CRectangle duplicate(CRectangle rectparam)
{
CRectangle rectres;
rectres.width = rectparam.width * 2;
rectres.height = rectparam.height * 2;
return rectres;
}
int main()
{
CRectangle rect, rectb;
rect.set_values(2, 3);
rectb = duplicate(rect);
cout << rectb.area();
return 0;
}
friend 는 클래스 내의 멤버 함수는 아니므로 :: 는 없다. 하지만 friend이므로 class 안에 private와 protected는 사용이 가능하다. friend이니까 제한적으로 open 하는 것이다. c -> c++에 대한 문화적 충격 완화용,,, 하나에 대해서만 private를 오픈할 수 있을 때 friend가 유용.
friend 함수는 멤버 함수가 아닌데 왜 굳이 저 안에 클래스에 넣는거야? => 그렇게 해야 CRectanlge 클래스의 this->width와 this->height를 public 함수 없이 사용할 수 있기 때문.
Friend 클래스
friend로 선언된 다른 클래스의 private 및 protected 멤버에 접근할 수 있다.
#include <iostream>
using namespace std;
class CSquare;
class CRectangle
{
int width, height;
public:
int area()
{
return width * height;
}
void convert(CSquare a);
};
class CSquare
{
private:
int side;
public:
void set_side(int a)
{
side = a;
}
// CRectangle과 친구이므로 CRectangle은 CSquare 변수를 사용 가능.
friend class CRectangle;
};
void CRectangle::convert(CSquare a)
{
width = a.side;
height = a.side;
}
int main()
{
CSquare sqr;
CRectangle rect;
sqr.set_side(4);
rect.convert(sqr);
cout << rect.area() << endl;
return 0;
}
왜 첫 부분에 class 선언을 하는 이유. convert 함수에 대해서 CSquare 클래스를 사용하고 싶은데, 컴파일러는 쓰이기 전에 선언함을 알아야 되므로 그래서 미리 선언을 한 것이다. 어느 한쪽이 일방적으로 사용하는 것은 순서를 조정하면 되지만 상호간에 크로스하여 불러야할 경우에는 위에 미리 선언을 해주고 사용을 하면 된다.
댓글남기기