const 관련 추가

const 객체와 const 객체의 특성

 다음과 같이 변수를 상수화 할 수도 있고, 

  • const int num = 10;

 이렇게 객체를 상수화 할 수도 있다.

  • const Simple simple(20);

 이것은 객체의 const 선언이 "이 객체의 데이터 변경을 허용하지 않겠다"와 같은 의미를 가지기 때문이다.

 

const와 함수 오버로딩

 함수의 오버로딩이 성립하려면 매개변수의 수나 자료형이 달라야하며 아래와 같이 const의 선언 유무도 함수 오버로딩의 조건에 해당된다.

  • void SimpleFunc() {}
  • void SimpleFunc() const {}

 

 

클래스와 함수에 대한 friend 선언

클래스의 friend 선언

 C++의 friend 선언이 의미하는 바는 아래와 같다.

  • A 클래스가 B 클래스를 대상으로 friend 선언을 하면, B 클래스는 A 클래스의 private 멤버에 직접 접근이 가능하다.
  • 단, A 클래스도 B 클래스의 private 멤버에 직접 접근이 가능하려면, B 클래스가 A 클래스를 대상으로 friend 선언을 해줘야한다.
Class A {
private:
	int num;
    friend class B;
public:
	A(int n) : num(n) 
    {}
    ...
};

 위 클래스는 B 클래스를 friend 클래스로 선언하였다. 따라서 B 클래스 내에서 A 클래스의 모든 private 멤버에 직접 접근이 가능하다. 추가로 friend 선언은 클래스 내에 어떤 영역에 위치해 있어도 상관없다.

 

Class B {
private:
	int n;
public:
	void showInfo(A &frn) {
    	cout << frn.num << '\n';
};

 또한 위 A 클래스의 friend class B; 선언은 B가 클래스 이름임을 선언하는 역할도 한다.

 

friend 선언은 언제?

 위에서 볼 수 있듯 friend 선언은 객체 지향의 특징 중 하나인 '정보은닉'을 무너뜨리는 문법이다.

그렇기에 지나치게 사용하면 굉장히 위험해질 수 있으며, 그렇기에 필요한 상황에서 소극적으로 사용해야한다.

 

함수의 friend 선언

 전역함수와 클래스의 멤버함수를 대상으로도 friend 선언이 가능하며 friend 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근이 가능하다. 또한 "friend void ShowInfo(const Point&);"와 같이 선언된 경우에는 void ShowInfo(const Point&);라는 함수원형 선언이 포함되어 있듯, friend 선언을 위해 별도의 함수원형 선언이 필요치는 않다.

 

 

C++에서 static

c언어에서 static

  • 전역변수에 선언된 static → 선언된 파일 내에서만 참조를 허용하겠다는 의미
  • 함수 내에 선언된 static → 한 번만 초기화되고, 지역변수와 달리 함수를 빠져나가도 소멸하지 않음

 

static 멤버변수(클래스 변수)

 static 멤버변수는 '클래스 변수'라고도 하며, 이는 일반적인 멤버변수와 달리 클래스당 하나씩만 생성되기 때문이다.

class Simple{
private:
	static int num;
public:
	Simple() {
    	num++;
    }
};

 위의 코드에 선언된 static 변수는 객체가 생성될 때마다 함께 생성되는 것이 아닌, 객체를 생성하건 생성하지 않건, 메모리 공간에 딱 하나만 할당이 되어서 공유되는 변수이다.

 예를 들어 아래와 같이 총 3개의 객체가 생성되면,

int main() {
	Simple simple1;
    Simple simple2;
    Simple simple3;
    ...
}

 세 개의 객체가 static 변수를 공유하게 된다. 또한 static 변수는 생성자에서 초기화하면 안되는데, 객체가 생성될 때 동시에 생성되는 변수가 아니고 이미 메모리 공간에 할당이 이루어진 변수기 때문에 객체가 0으로 초기화되기 때문이다.

  그리고 static 멤버가 위에서 처럼 private으로 선언되면, 해당 클래스의 객체들만 접근이 가능하지만, public으로 선언되면, 클래스의 이름 또는 객체의 이름을 통해 어디서든 접근이 가능하다.

 

 

static 멤버함수

 static 멤버함수 역시 그 특성이 static 멤버변수와 동일하므로 다음과 같은 특성들이 그대로 적용된다.

  • 선언된 클래스의 모든 객체가 공유한다.
  • public으로 선언이 되면, 클래스의 이름을 이용해서 호출이 가능하다.
  • 객체의 멤버로 존재하는 것이 아니다.

 여기서 객체의 멤버로 존재하지 않는다는 것 때문에 static 멤버함수에서 멤버변수에 접근하면 컴파일 에러가 발생한다.

이는 아래와 같이 이해가 가능하다.

  • 객체의 멤버가 아닌데, 어떻게 멤버변수에 접근을 할까?
  • 객체생성 이전에도 호출이 가능한데, 그럼 어떻게 멤버변수에 접근 할 수 있을까?
  • 멤버변수에 접근을 한다 쳐도, 어떤 객체의 멤버변수에 접근을 해야할까?

 이렇듯, static 멤버함수 내에서는 static으로 선언되지 않은 멤버변수의 접근도, 멤버함수의 호출도 불가능함을 알 수 있다.

즉, "static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능하다."

 

cont static 멤버

 클래스 내에 선언된 const 멤버변수(상수)의 초기화는 이니셜라이저를 통해서 해야만 했지만, const static으로 선언되는 멤버변수(상수)는 다음과 같이 선언과 동시에 초기화가 가능하다.

  • const static int num = 1;

 

키워드 mutable

 mutable 키워드를 통해 const 함수 내에서의 값의 변경을 예외적으로 허용할 수 도 있다.

class A{
private:
	int num1;
    mutable int num2; // const 함수에 대해 예외를 둔다

 

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] Chapter 01 정리  (0) 2023.03.22

복사 생성자(Copy Constructor)

다음과 같은 생성자를 '복사 생성자(Copy Constructor)'라 한다.

Simple(const Simple &copy) : num1(copy.num1), num2(copy.num2) {
	cout << "Called Simple(Simple &copy)" << endl;
}

 

디폴트 복사 생성자

 복사 생성자의 삽입 없이도 멤버 대 멤버의 복사가 진행 가능하며 이것은 복사 생성자를 정의하지 않으면, 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입되기 때문이다.

이 때문에 많은 경우에서는 복사 생성자를 따로 정의하지 않아도 된다.

 

explicit

 explicit 키워드를 이용해 복사 생성자의 묵시적 호출을 허용하지 않을 수도 있다.

explicit Simple(const Simple &copy) : num1(copy.num1), num2(copy.num2) {
	
}

 이렇게 될 경우 묵시적 변환이 발생하지 않아 아래와 같이 대입 연산자를 이용한 객체의 생성 및 초기화가 불가능해진다.

  • Simple simple1 = simple2;

 

'깊은 복사'와 '얕은 복사'

 디폴트 복사 생성자는 멤버 대 멤버의 복사를 진행하고, 이러한 방식의 복사를 가리켜 '얕은 복사(Shallow Coply)'라 한다. 이는 멤버변수가 힙의 메모리 공간을 참조하는 경우에 문제가 발생한다.

 

디폴트 복사 생성자의 문제점

 하나의 문자열을 두 개의 객체가 동시에 참조하는 것과 같은 경우, 객체의 소멸 과정에서 하나의 객체가 먼저 소멸된 뒤, 남아있는 객체가 소멸될 시 이미 지워진 문자열을 대상으로 delete 연산을 하기 때문에 문제가 발생한다.


'깊은 복사'를 위한 복사 생성자의 정의

 다음과 같이 복사 생정자를 정의하면, 객체 별로 각각의 문자열을 참조하기 때문에 객체의 소멸 과정에서 문제가 발생하지 않는다. 

Person(const Person& copy) : age(copy.age) {
	name = new char[strlen(copy.name) + 1];
    strcpy(name, copy.name);
}

 위의 생성자가 하는 일은 다음 두 가지이다.

  • 멤버변수 age의 멤버 대 멤버 복사
  • 메모리 공간 할당 후 문자열 복사, 그리고 할당된 메모리의 주소 값을 멤버 name에 저장

이러한 형태의 복사를 가리켜 '깊은 복사(Deep Copy)'라 한다.

 

복사 생성자의 호출시점

 복사 생성자가 호출되는 시점은 크게 아래 세 가지로 구분할 수 있다.

  • 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우
  • Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
  • 객체를 반환하되, 참조형으로 반환하지 않는 경우

 이것들은 모두 객체를 새로 생성하며, 생성과 동시에 동일한 자료형의 객체로 초기화해야 한다는 공통점이 있다.

이를 설명하기 위해 설명할 메모리 공간이 할당과 동시에 초기화 되는 상황들은 아래와 같다.

  • int n1 = n2;
  • Func(n); // 호출되는 순간 매개변수가 할당과 동시에 초기화
  • return n; // 반환하는 순간 메모리 공간이 할당되면서 동시에 초기화

이러한 상황은 객체를 대상으로 해도 달라지지 않으며, 위 복사 생성자 호출 시점에 각각 대응됨을 알 수 있다.

 

임시객체(Temporary Object)

 객체의 반환 시, 임시객체라는 것이 생성되고 임시객체의 참조 값이 반환된다. 그리고 임시객체는 참조자에 의해 참조되지 않는 다면 다음 행으로 넘어가면서 즉시 소멸된다.

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 06 정리  (0) 2023.04.10
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] Chapter 01 정리  (0) 2023.03.22

정보은닉(Information Hiding)

좋은 클래스가 되기 위한 최소한의 조건은 '정보은닉'과 '캡슐화'이다.

 

 멤버변수가 public으로 선언된다면 어디서든 접근이 가능해지기 때문에 문제가 발생할 수 있다.

 이러한 문제를 막기 위해 제한된 방법으로의 접근만 허용하여 잘못된 값이 저장되지 않도록 해야하며, 또한 실수가 쉽게 발견될 수 있도록 해야한다.

class Car {
private:
	int fuel;
    int speed;
public:
	void InitCarState(int fuel, int speed);
    int GetFuel() const;
    bool SetFuel(int fuel);
    void Accel();
};

 

 이렇게 멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버변수의 접근을 유도하는 것이 '정보은닉'이다.

 

 위의 코드에서 아래와 같이 'Get멤버변수명', 'Set멤버변수명'으로 정의된 함수들을 '액세스 함수(Access Function)'라 하며 이러한 액세스 함수들은 멤버변수를 private으로 선언하면서 클래스 외부에서 멤버변수로의 접근을 목적으로 정의되는 함수들이다.

int GetFuel() const;
bool SetFuel(int fuel);

 추가로 앞에서 선언된 함수에 const 선언이 추가되어 있는 경우가 있는데, 이는 해당 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다는 의미이다. 또한 이렇게 선언 시 const 함수 내에서는 const가 아닌 함수의 호출이 제한됨으로써 코드의 안정성을 높일 수 있다.

 

 

캡슐화(Encapsulation)

 정보은닉과 함께 객체지향 기반의 클래스 설계에서 가장 기본이면서도 중요한 원칙들이다.

 

 캡슐화를 하게 되면 상호관계가 복잡했던 것을 줄일 수 있게 되고 프로그램을 간결하게 만들 수 있으나 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문에 어려운 개념이기도 하다. 그렇기 때문에 다양한 사례를 접하며 클래스를 캡슐화 시키는 능력을 기를 필요가 있다.

 

 

생성자(Constructor)와 소멸자(Destructor)

 지금까지 멤버함수를 통해 멤버변수를 초기화 시켰지만, '생성자'를 이용해 객체를 생성과 동시에 초기화할 수 있다.

class SimpleClass {
private:
    int num;
public:
    SimpleClass(int n) { // 생성자(Constructor)
        num = n;
    }
    int GetNum() const {
        return num;
    }
};

 

 위의 클래스에서 클래스의 이름과 함수의 이름이 동일하고, 반환형이 선언되어 있지 않고, 실제로 반환하지 않는 함수가 있다. 이러한 유형의 함수를 가리켜 '생성자(Constructor)'라 하며, 이 함수는 개체 생성시 딱 한 번 호출된다. 

 

 생성자 또한 함수의 일종이므로 오버로딩이 가능하며, 매개변수의 디폴트 값 설정도 가능하다.

class SimpleClass {
public:
	int num1;
	int num2;
private:
	SimpleClass() {
		num1 = 0;
		num2 = 0;
	}
	SimpleClass(int n) {
		num1 = n;
		num2 = 0;
	}
	SimpleClass(int n1, int n2) {
		num1 = n1;
		num2 = n2;
	}
};

 

위에서 정의된 생성자를 통해 객체를 생성하려면 아래와 같이 문장이 구성된다.

  • SimpleClass sc2(100);
  • SimpleClass * ptr2 = new SimpleClass(100);
  • SimpleClass sc3(100, 200);
  • SimpleClass * ptr3 = new SimpleClass(100, 200);

그러나 아래와 같은 문장은 허용되지 않는다.

  • SimpleClass sc1();

대신 아래와 같이 구성해야 한다.

  • SimpleClass sc1;
  • SimpleClass * ptr1 = new SimpleClass;
  • SimpleClass * ptr1 = new SImpleClass();

 이는 main 함수내에서 Simpleclass sc();와 같은 문장이 선언되면 객체를 생성하기 위한 객체생성문인지 함수의 원형선언인지 구분할 수 없기 때문에 C++에서는 이를 함수의 원형선언에만 사용하기로 약속하였다.

 

 

멤버 이니셜라이저(Member Initializer)

 

  객체의 생성과정에서 그 객체의 멤버변수로 선언된 객체의 생성자를 호출하여 멤버변수로 선언된 객체를 초기화할 수 있으며 이에 사용되는 것이 '멤버 이니셜라이저(Member Initializer)'이다.

class Rectangle {
private:
	Point upLeft;
    Point lowRight;
public:
	Rectangle(cont int &x1, const int &y1, const int &x2, const int &y2);
    void ShowRecInfo() const;
};
Rectangle::Rectangle(cont int &x1, const int &y1, const int &x2, const int &y2):upLeft(x1, y1), lowRight(x2, y2) {
	//Empty
}

 

 위 코드에서 :upLeft(x1, y1), lowRight(x2, y2)에 해당하는 부분이 '멤버 이니셜라이저'이며 이것은 아래의 의미를 가진다.

  • 객체 upLeft의 생성과정에서 x1, y1을 인자로 전달받는 생성자를 호출
  • 객체 lowRight의 생성과정에서 x2, y2를 인자로 전달받는 생성자를 호출

 멤버 이니셜라이저를 사용하다 보면, 생성자의 몸체가 비어있는 경우가 발생하나 그냥 무시하면 된다.

 

 멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에서도 사용할 수 있으며 아래와 같은 두 가지 장점이 있기 때문에 이니셜라이저를 통해 멤버번수를 초기화 하는 것이 선호된다.

  • 초기화의 대상을 명확히 인식할 수 있다.
  • 성능에 약간의 이점이 있다.

 또한, 이니셜라이저를 이용하면 선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성되므로 const 멤버변수도이니셜라이저를 통해 초기화가 가능하며 이러한 특징은 멤버변수로 참조자를 선언할 수 있게 한다.

 

 

디폴트 생성자(Default Constructor)

 객체가 생성되기 위해서는 반드시 하나의 생성자가 호출되어야 한다. 이를 위해 생성자를 정의하지 않은 클래스에는 C++ 컴파일러에 의해 디폴트 생성자라는 것이 자동으로 삽입된다.

 

private 생성자

 객체의 생성이 클래스의 외부에서 진행되면 생성자는 public으로 선언되어야 한다. 하지만 클래스 내부에서 객체를 생성한다면, private으로 선언될 수 있다.

 

소멸자의 이해와 활용

 객체 생성 시 반드시 호출되는 것이 생성자라면, 객체 소멸 시 만드시 호출되는 것을 소멸자라 하고, 아래와 같은 형태를 갖는다.

  • 클래스의 이름 앞에 '~'가 붙은 형태의 이름을 갖는다.
  • 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
  • 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다.

 소멸자도 생성자와 마찬가지로 소멸자가 정의되어 있지 않으면 디폴트 소멸자가 자동으로 삽입된다.

 

 소멸자는 대체로 생성자에서 할당한 리소스의 소멸에 사용되며, 생성자에서 new 연산자를 이용해 할당해 놓은 메모리 공간이 있다면, 소멸자에서는 delete 연산자를 이용해서 메모리 공간을 소멸한다.

 

 

클래스와 배열 그리고 this 포인터

객체 배열

 객체 기반의 배열은 다음의 형태로 선언한다.

  • SimpleClass arr[10];
  • SimpleClass * ptrArr = new SimpleClass[10];

this 포인터

 멤버함수 내에서는 this라는 이름의 포인터를 사용할 수 있고, 이는 객체 자신을 가리키는 용도로 사용되는 포인터이다.

class SimpleClass {
private:
	int num;
public:
	SimpleClass * GetThisPointer( {
    	return this;
    }
    void ThisFunc(int num) {
    	this->num = 200;
        num = 100;
    }
};

 

 이러한 특성을 이용해 멤버변수와 매개변수의 이름을 동일하게 짓고, this 포인터를 이용해서 이 둘을 구분하기도 한다.

 

Self-Reference의 반환

 Self-Reference란 객체 자신을 참조할 수 있는 참조자를 의미하며, this 포인터를 이용해 객체가 자신의 참조에 사용할 수 있는 참조자의 반환문을 구성할 수 있다.

 

참조의 정보(참조 값)에 대한 이해

int main() {
	int num = 7;
    int &ref = num;
    ...

 이것은 다음과 같이 표현하기도 한다.

"변수 num을 참조할 수 있는 참조 값이 참조자 ref에 전달되어, ref가 변수 num을 참조하게 된다."

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 06 정리  (0) 2023.04.10
[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] Chapter 01 정리  (0) 2023.03.22

C++에서의 구조체

구조체란 연관 있는 데이터를 묶을 수 있는 문법적 장치이다.

 

C++에서 구조체는 아래의 예시와 같이 정의될 수 있다.

struct Car {
	char name[20];
    int speed;
    int fuel;
};

 

 C언어에서는 구조체 변수 선언 시에 키워드 struct가 앞에 삽입되거나 별도의 typedef 선언이 추가되어야하지만 C++에서는 아래와 같이 별도의 키워드나 typedef 선언 없이 구조체 변수를 선언할 수 있다.

//C
struct Car car1;
struct Car car2;

//C++
Car car1;
Car car2;

 

 또한 구조체 내부에 함수가 삽입되는 것을 허용하며 외부로 뺄 수도 있고, 열거형 enum을 이용해 구조체 내에서만 유효한 상수를 구조체 내부에서 선언할 수도 있다.

struct Car {
	enum {
		ID_LEN = 20,
		MAX_SPEED = 200
	};

	char name[ID_LEN];
	int speed = 0;
	int fuel;

	void ShowCarState() {
		cout << "현재속도 : " << speed << "\n";
		cout << "연료량 : " << fuel << "\n";
	}

	void Accel();
};

void Car::Accel() {
	speed += 10;
}

 

 

클래스(Class)와 객체(Object)

클래스와 구조체의 차이점

  • 키워드 struct를 이용해 정의하는 구조체와 달리 키워드 class를 이용해 정의한다.
  • 접근제어 지시자를 사용하지 않았을 때 클래스는 private으로 클래스는 public으로 선언된다.

접근제어 지시자는 아래 세 가지가 있다.

  • public : 어디서든 접근허용
  • protected : 상속관계에 놓여있을 때, 유도 클래스에서의 접근허용
  • private : 클래스 내(클래스 내에 정의된 함수)에서만 접근허용
class Car {
private:
	char name[20];
	int fuel;
	int speed;
public:
	void ShowCarState();
	void Accel();
};

void Car::ShowCarState() {
	cout << "현재속도 : " << speed << "\n";
	cout << "연료량 : " << fuel << "\n";
}

void Car::Accel() {
	speed += 10;
}

 

위 예제를 작성해보고, 지금까지 설명한 내용을 종합해보면 클래스의 정의에는 아래와 같은 특징이 있다.

  • 접근제어 지시자 A가 선언되면, 그 이후에 등장하는 변수나 함수는 A에 해당하는 범위 내에서 접근가능
  • 새로운 접근제어 지시자 B가 선언되면, 그 이후로 등장하는 변수나 함수는 B에 해당하는 범위 내에서 접근가능
  • 함수의 정의를 클래스 밖으로 빼도, 이는 클래스의 일부기에 함수 내에서 private으로 선언된 변수에 접근가능
  • class를 이용해 정의된 클래스에 선언된 변수와 함수에 별도의 접근제어 지시자를 선언하지 않으면 자동으로 private으로 선언

 

객체(Object), 멤버변수, 멤버함수

  • 객체(Object) : 사물 또는 대상
  • 멤버변수 : 클래스를 구성하는(클래스 내에 선언된) 변수
  • 멤버함수 : 클래스를 구성하는(클래스 내에 정의된) 함수

C++에서의 파일분할

 어떤 프로그램이건 하나의 파일에 모든 것을 담지는 않는다. 특히 C++에서는 클래스 별로 헤더파일과 소스 파일을 생성해서 클래스의 선언과 정의를 분리하는 경우가 많아 많은 수의 파일이 만들어진다.

 클래스 Car를 대상으로 파일을 나눌 때 보통 다음과 같이 파일을 구분한다.

  • Car.h : 클래스의 선언을 담는다.
  • Car.cpp : 클래스의 정의(멤버함수의 정의)를 담는다.

 즉, Car 클래스와 관련된 문장의 컴파일 정보로 사용되는 '클래스의 선언'은 헤더파일에 저장하여, 필요한 위치에 쉽게 포함될 수 있도록 해야 하며, '클래스의 정의'는 소스 파일에 저장해서, 컴파일이 되도록 하면 된다.

 단, 인라인 함수의 정의는 헤더파일에 포함되어야한다.

 

 이를 기반으로 하나의 파일을 아래 3개의 파일로 나눠볼 수 있다.

Car.h, Car.cpp, main.cpp

 

 

객체지향 프로그래밍의 이해

 C++은 객체지향 언어이며, 여기서 객체는 'Object', 즉 "사물, 또는 대상"을 의미하고, 객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 행동을 있는 그대로 실체화 시키는 형태의 프로그래밍이다. 

 객체는 하나 이상의 상태 정보(데이터)와 하나 이상의 행동(기능)으로 구성이 되며, 상태 정보는 변수를 통해서, 기능은 함수를 통해서 표현된다.

class Car {
private:
	char name[20];   //상태 정보
	int fuel;		 //
	int speed;		 //
public:
	void ShowCarState();	//행동
	void Accel();		    //
};

 

 객체를 생성하기 위해선 객체 생성을 위한 '틀'을 먼저 만들어야하고 여기서 '틀'을 만드는 것이 위에서 언급되던 클래스의 정의이다.

 클래스를 기반으로하는 객체생성 방법에는 두 가지가 있으며 아래와 같다.

  • ClassName objName;                              // 일반적인 변수의 선언방식
  • ClassName * ptrObj = new ClassName;  // 동적 할당방식(힙 할당방식)

 

 또한 아래와 같이 한 객체에서 다른 객체의 함수를 호출하여 메시지를 전달할 수도 있으며, 이처럼 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은 함수호출을 기반으로 하고, 이러한 형태의 함수호출을 '메시지 전달(Message Passing)이라고 한다.

class lubricator {
private:
	int price;
public:
	void initStatus(int price) {
		this->price = price;
	}
	
	int SaleFuels(int money) {
		int fuel = money / price;

		return fuel;
	}
};

class Car {
private:
	int fuel;
	int money;
public:
	void InitStatus(int fuel, int money) {
		this->fuel = fuel;
		this->money = money;
	}

	void BuyFuels(lubricator& seller, int money) {
		fuel += seller.SaleFuels(money);
		this->money -= money;
	}
};

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] Chapter 01 정리  (0) 2023.03.22
[C++] C++ 공부 정리 관련  (0) 2023.03.22

bool 자료형

논리 자료형이라고도 하며 true(1), false(0)를 값으로 가지는 자료형이고 1byte의 크기를 가진다.

#include <iostream>

using namespace std;

int main() {
	bool isTrue = true;
	bool isFalse = false;

	cout << boolalpha;
	cout << isTrue << "\n";
	cout << isFalse << "\n";

	cout << noboolalpha;
	cout << isTrue << "\n";
	cout << isFalse << "\n";

	return 0;
}

 

 

참조자(Reference)의 이해

참조자는 하나의 할당된 메모리 공간에 다른 이름을 붙이는 것이다. 즉, 변수에 별명을 붙여주는 것과 같다고 볼 수 있다.

 

참조자의 선언에는 아래의 조건들이 있다.

  • 참조자는 변수에 대해서만 선언이 가능하다.
  • 미리 참조자만 선언하고 후에 참조하는 것은 불가능하며, 참조의 대상을 바꾸는 것도 불가능하다
  • NULL로 초기화 하는 것도 불가능하다

즉, 참조자는 무조건 선언과 동시에 변수를 참조하도록 해야 한다.

#include <iostream>

using namespace std;

int main() {
	int num1 = 1020;
	int& num2 = num1;

	num2 = 3047;
	cout << "VAL: " << num1 << endl;
	cout << "REF: " << num2 << endl;

	cout << "VAL: " << &num1 << endl;
	cout << "REF: " << &num2 << endl;

	return 0;
}

 

 

참조자(Reference)와 함수

함수의 호출방식은 아래의 두 가지가 있다.

  • Call-by-value: 값을 인자로 전달하는 함수의 호출방식
  • Call-by-reference: 주소 값을 인자로 전달하는 함수의 호출방식

Call-by-value 기반의 함수는 함수 내부에서 함수 외부에 선언된 변수에 접근이 불가능하다.

void SwapByValue(int a, int b) {
	int tmp = a;
    a = b;
    b = tmp;
}

int main() {
	int a = 10;
    int b = 10;
    
    SwapByValue(a, b);
    
    cout << a << b;
    
    return 0;
}
//출력결과를 확인하면 a, b에 저장된 값이 바뀌지 않았음을 알 수 있다

 

반면 Call-byreference 기반의 함수는 주소 값을 받아서, 그 주소 값이 참조하는 영역에 저장된 값을 직접 변경한다.

void SwapByRef(int* ptr1, int* ptr2) {
	int tmp = *ptr1;
	*ptr1 = *ptr2;
	*ptr2 = tmp;
}

int main() {
	int a = 10;
	int b = 10;

	SwapByRef(&a, &b);

	cout << a << b;

	return 0;
}
//출력결과를 확인하면 a, b에 저장된 값이 바뀌었음을 알 수 있다

 

이렇듯 주소 값을 이용한 Call-byreference 이외에도 C++에서는 참조자를 기반으로도 Call-by-reference(참조에 따른 호출)의 함수호출을 진행할 수 있다.

void SwapByRef2(int &ref1, int &ref2) {
	int tmp = ref1;
	ref1 = ref2;
	ref2 = tmp;
}

int main() {
	int a = 10;
	int b = 10;

	SwapByRef2(a, b);

	cout << a << b;

	return 0;
}
//마찬가지로 출력결과를 확인하면 a, b에 저장된 값이 바뀌었음을 알 수 있다

 

위 함수가 호출된 순간 참조자 ref1, ref2는 메인 함수의 a, b의 또 다른 이름이 되고 이 두 참조자를 통해 값을 교환했기 때문에 실제로 a와 b의 값의 교환이 발생한다.

추가로 위의 함수에 선언된 매개변수는 초기화가 되지 않은 것이 아니라, 함수호출 시에 전달되는 인자로 초기화를 하겠다는 의미이다.

 

 

new & delete

C에서 동적할당을 하기 위해선 아래와 같은 불편한 점이 있었다.

  • 할당할 대상의 정보를 무조건 바이트 크기 단위로 전달해야한다.
  • 반환형이 보이드형 포인터이기 때문에 형 변환을 거쳐야 했다.

그러나 C++에서 제공하는 new와 delete를 사용하면 위와 같은 불편한 점이 사라진다.

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstring>
#include <stack>

using namespace std;

char* MakeStrAdr(int len) {
	char* str = new char[len];

	return str;
}

int main() {
	char* str = MakeStrAdr(20);
	
	strcpy(str, "I am so happy~");
	
	cout << str << "\n";

	delete[] str;
	
	return 0;
}

 

 

C++에서 C언어의 표준함수 호출하기

C C++
#include <stdio.h> #include <cstdio>
#include <stdlilb.h> #include <cstdlib>
#include <math.h> #include <cmath>
#include <string.h> #include <cstring>

 

위처럼 헤더파일 확장자인 .h를 생략하고 앞에 c를 붙이면 C언어에 대응하는 C++의 헤더파일 이름이 된다.

C언어의 헤더파일을 그대로 사용해도 되지만, C++ 문법 기반으로 개선된 형태의 라이브러리 구성 등의 이유로 C++의 헤더파일을 이용해서 함수를 호출하는 것이 좋다.

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 01 정리  (0) 2023.03.22
[C++] C++ 공부 정리 관련  (0) 2023.03.22

C++에서 입/출력 방식

C++에서는 C언어와 달리 기본 헤더 파일이 <stdio.h>가 아닌 <iostream>이고, printf와 scanf를 대체하는 std::cout, std::cin이 존재한다. 그리고 cin/cout을 통한 입/출력에는 별도의 포맷 지정이 필요하지 않다.

#include <iostream>

int main() {
	int num = 20;

	std::cout << "Hello World!" << std::endl;
	std::cout << "Hello " << "World!\n";

	std::cout << num << " A";
	std::cout << " 3.14\n";

	return 0;
}

 

 

지역 변수 선언

C언어에서는 지역변수의 선언이 가장 먼저 등장해야했지만 C++에서는 지역변수의 선언 위치에 제한을 두지 않는다.

#include <iostream>

int main() {
	int val1, val2;
	int result = 0;

	std::cout << "두 개의 숫자입력: ";
	std::cin >> val1 >> val2;

	if (val1 < val2) {
		for (int i = val1 + 1; i < val2; i++)
			result += i;
	}
	else {
		for (int i = val2 + 1; i < val1; i++)
			result += i;
	}

	std::cout << "두 수 사이의 정수 합: " << result << std::endl;

	return 0;
}

 

 

함수 오버로딩(Function Overloading)

C언어에서 함수는 함수명으로 구분되지만 C++에서는 매개변수의 타입과 개수로 구분되기 때문에 동일한 이름의 함수 정의가 허용되고, 이를 함수 오버로딩(Function Overloading)이라고 한다. 

#include <iostream>

void swap(int& a, int& b) {
	int tmp;

	if (a < b) {
		tmp = a;
		a = b;
		b = tmp;
	}
}

void swap(char& a, char& b) {
	char tmp;

	if (a < b) {
		tmp = a;
		a = b;
		b = tmp;
	}
}

void swap(double& a, double& b) {
	double tmp;

	if (a < b) {
		tmp = a;
		a = b;
		b = tmp;
	}
}

 

 

매개변수의 디폴트 값(Default Value)

함수를 사용할 때 함수의 매개변수 선언에 값을 지정함으로 써 해당 인자가 전달되지 않으면 지정된 값이 전달된 것으로 판단하여 작동할 수 있다.

매개변수의 디폴트 값을 지정할 때에는 아래의 규칙을 지켜야한다.

  • 함수의 선언 부분에 위치해야한다.
  • 선언된 매개변수의 수보다 적은 수의 인자가 전달되었을 시 전달되는 인자는 왼쪽에서부터 채워지기 때문에 디폴트 값은 오른쪽부터 채워나가야한다.
#include <iostream>

int BoxVolume(int length, int width = 1, int height = 1);

int main() {
	std::cout << "[3, 3, 3] : " << BoxVolume(3, 3, 3) << std::endl;
	std::cout << "[5, 5, D] : " << BoxVolume(5, 5) << std::endl;
	std::cout << "[7, D, D] : " << BoxVolume(7) << std::endl;
	//모든 값에 디폴트 값이 지정된 것이 아니기에 아래는 컴파일 에러가 발생한다.
	//std::cout << "[D, D, D] : " << BoxVolume() << std::endl;

	return 0;
}

int BoxVolume(int length, int width, int height) {
	return length * width * height;
}

 

 

인라인(Inline) 함수

 C언어의 매크로 함수를 일반 함수처럼 정의할 수 있도록 만든 것이다.

#include <iostream>

inline int SQUARE(int x) {
	return x * x;
}

int main() {
	std::cout << SQUARE(5) << std::endl;
	std::cout << SQUARE(12) << std::endl;

	return 0;
}

 

 

이름공간 / 네임스페이스(namespace)

 이름공간, 즉 네임스페이스란 함수 이름이 같아서 발생하는 이름충돌을 방지하기 위해 만들어진 문법적인 요소이다.

네임스페이스는 아래의 특성을 가진다.

  • 동일한 네임스페이스에 정의된 함수를 호출할 때는 네임스페이스를 명시할 필요가 없다.
  • 네임스페이스는 다른 네임스페이스 안에 삽입될 수 있다. 즉, 네임스페이스는 중첩될 수 있다.
  • 범위 지정 연산자 :: 을 이용해 네임스페이스를 지정한다.
  • using을 통해 네임스페이스을 지정하지 않고 호출하겠다는 것을 명시할 수있다. 
#include <iostream>

namespace Bcom {
    void SimpleFunc(void);
}
 
namespace Bcom {
    void PrettyFunc(void);
}
 
namespace Pcom {
    void SimpleFunc(void);
}
 
int main() {
    Bcom::SimpleFunc();
 
    return 0;
}
 
void Bcom::SimpleFunc() {
    std::cout << "Bcom이 정의한 함수" << std::endl;
    PrettyFunc();\
    Pcom::SimpleFunc();\
}
 
void Bcom::PrettyFunc() {
    std::cout << "So Pretty~" << std::endl;
}
 
void Pcom::SimpleFunc() {
    std::cout << "Pcom이 정의한 함수" << std::endl;
}

 

 

using

네임스페이스의 지정 없이 함수를 호출하겠다는 것을 선언하는 데 사용되는 키워드이다.

#include <iostream>

using namespace std;

int main() {

	cout << "범위 지정 연산자 없이도 네임스페이스 std에 정의된 함수를 호출";

	return 0;
}

 

 

개인적인 추가 내용

C++에서도 <stdio.h>나 <cstdio> 헤더파일을 사용해 printf와 scanf를 사용할 수 있으며 아래 그림을 통해 알 수 있듯 printf와 scanf에 비해 cout, cin은 현저히 느린 속도를 가진다.

이는 cin/cout이 stdio.h와 iostream 두 개의 버퍼를 같이 사용하여 동기화 유지하는 작업이 추가되어 있기 때문인데 이를 해결하기 위해 printf / scanf를 사용하거나 아래 코드를 통해 cin / cout의 stdio.h와의 동기화를 해제시켜주면 된다. 

ios::sync_with_stdio(false);
cin.tie(NULL);

단, 이 방식을 사용할 경우 c의 입출력방식(printf, scanf 등)과 같이 사용할 수 없게 되고, 싱글 스레드 환경에서만 사용 가능하기 때문에 실무에서는 사용해선 안되고 코딩테스트같은 알고리즘 문제에서만 사용해야한다.

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] C++ 공부 정리 관련  (0) 2023.03.22

 C++ 복습 겸 윤성우의 열혈 C++ 프로그래밍 책을 구매해 공부한 것을 정리할 예정이다.

책의 내용대로 총 16 챕터, 각 챕터별로 공부한 내용을 블로그에 정리할 것이다. 

'개인 공부 > C++' 카테고리의 다른 글

[C++] Chapter 05 정리  (0) 2023.04.05
[C++] Chapter 04 정리  (0) 2023.03.29
[C++] Chapter 03 정리  (0) 2023.03.28
[C++] Chapter 02 정리  (0) 2023.03.23
[C++] Chapter 01 정리  (0) 2023.03.22

+ Recent posts