공부/C++

C++ 기본 문법, C++ 입출력, string, 레퍼런스, 범위 기반 for문, 함수 오버로딩, 디폴트 매개변수, 연산자 오버로딩, 네임스페이스, 클래스, 접근지정자, this 포인터, 메서드 체이닝

월러비 2025. 6. 18. 18:13

짧은 설명

  • C 컴파일러에는 bool 데이터형이 없다.
    • #include <stdbool.h> 헤더파일을 사용한다.

이름짓기 규칙

  1. 파스칼 : 단어 첫글자를 대문자로 짓는다.
  2. 카멜 : 첫글자를 소문자로 쓰고, 다음 단어부터 대문자를 쓴다.
  3. 스네이크 : 전부 소문자로 작성하고 단어는 _(언더바)로 구분한다.

C++ 기본 문법

입출력

  • C언어: stdio.h의 printf, scanf 사용
  • C++: iostream의 cout, cin 사용
  • std::cout - 표준 출력 스트림
  • std::cin - 표준 입력 스트림
  • std::endl - 줄바꿈 + 버퍼 비우기
  • printf와 달리 형식 지정자(%d, %f 등)가 필요 없음
  • 타입을 자동으로 인식하여 출력
#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;
}
  • #include <iostream>: C++의 입출력을 위한 헤더 파일
    • input output 스트림의 헤더파일이다.
  • std : 표준 라이브러리의 이름이다.
    • sfml의 표준 라이브러리 이름은 sf::로 사용한다.
  • std::cout: 표준 출력 객체 (console output)
    • 전역변수다.
    • cout : 아웃 스트림이라는 의미다.
      • 출력 스트림 연산자다.
  • <<: 출력 연산자 (삽입 연산자)
    • 좌항 : 변수
    • 우항 : 변수에 맞춰 해야할 일
      • 좌항의 변수가 해야할 일의 결과를 리턴한다.
  • std::endl: 줄바꿈 + 출력 버퍼 비우기
    • endline이라는 뜻이다.
  • "Hello, world!" : const character pointer 형이다.
  • 순서 : <<가 있는 좌항 우항, 끝나면 마지막 우항이다.
    1. std::cout << “hello ~~”
    2. std::cout << std::endl;

여러 타입 출력

using namespace std;

int main() {
    int a = 5;
    double b = 3.14159265358979;
    char c = 'd';

    cout << a << ' ' << b << ' ' << c << endl;
}
  • using namespace std;: std:: 생략 가능하게 한다.
  • 여러 값을 연속으로 출력: << 연산자 체이닝
  • 정수, 실수, 문자 모두 형식 지정자 없이 출력
    • 소수점 기본으로 5자리까지만 출력된다.
      • 바꾸고 싶다면 따로 지정해야한다.

cin 입력받기

int main() {
    int a, b;

    cin >> a >> b;
    std::cout << a << " + " << b << " = " << a + b << std::endl;
}
  • cin >> a >> b;: 두 개의 정수 연속 입력
  • >>: 입력 연산자 (추출 연산자)
  • scanf처럼 & 주소 연산자 불필요
  • 공백으로 구분하여 입력
    • 12 233 입력 ⇒ 12와 233이 변수에 저장된다.

연산자 재정의

  • 연산자를 새로 정의해서 사용하는 것이다.
    • 이항, 단항, 왼항 데이터형, 우항 데이터형을 직접 정의할 수 있다.

string

  • C언어: char 배열로 문자열 처리한다.
  • C++: string 클래스로 편리하게 문자열을 처리한다.
  • 크기 자동 조절, 문자열 연산 지원
#include <string>

string name;
string greeting;

cout << "이름 입력: ";
cin >> name;

greeting = "안녕하세요, " + name + " 님!";

cout << greeting << endl;
cout << name << "은 총 " << name.length() << "글자입니다." << endl;
cout << name << "의 첫 글자는 " << name[0] << "입니다." << endl;
  • #include <string>: string 클래스 사용을 위한 헤더
  • string : 스트링 ‘클래스’다.
  • + 연산자로 문자열 연결
  • length() 메서드로 문자열 길이 확인
    • string 클래스의 멤버함수다.
  • [] 연산자로 개별 문자 접근 (0부터 시작)

초기화 방법

int a = 10;        // 복사 초기화
int b(20);         // 직접 초기화
int c{30};         // 유니폼 초기화 (C++11)
int d = {40};      // 복사 리스트 초기화
  • 직접 초기화 : b를 20으로 초기화 한다는 의미
    • 이후에 생성자를 통해서 객체를 초기화할때 생성자 인자에 초기화하는 의미로 사용된다.
  • 유니폼 초기화 : 최근 생긴 문법이다.
    • 중괄호 안에 값을 넣지 않으면 ‘기본값 = 0 or 널포인터’가 들어간다.
      • 제약사항이 가장 많다.
      • 중괄호 {}를 사용
      • 축소 변환(narrowing conversion) 방지
      • 모든 타입에 일관된 문법 적용
        • double → float 로 값을 넣으면 보통은 암묵적 형변환이 일어나지만 유니폼 초기화로는 컴파일 에러가 일어난다.
// 기본적인 초기화
int hp{100};           // 캐릭터 체력
double attackPower{25.5};  // 공격력

// 배열 초기화
int scores[]{100, 85, 90, 95};  // 크기 자동 추론

// 축소 변환 방지 예제
int level = 5.7;       // OK: 5로 변환됨 (소수점 버림)
// int level2{5.7};    // 에러: 축소 변환 불가

// 0으로 초기화
int gold{};            // 0으로 초기화
double exp{};          // 0.0으로 초기화

cout << "HP: " << hp << endl;
cout << "공격력: " << attackPower << endl;
cout << "골드: " << gold << endl;

// 구조체 초기화 (미리보기)
struct Player {
    int level;
    int hp;
    int mp;
};

Player player1{10, 100, 50};  // 유니폼 초기화로 구조체 초기화
  • 중괄호 초기화는 타입 안전성 제공
  • 실수를 정수형에 넣는 실수 방지
  • 빈 중괄호 {}는 0으로 초기화
  • 배열, 구조체 등 모든 타입에 일관되게 사용 가능

레퍼런스(l-value)와 r-value 레퍼런스

  • 간접 참조 / 직접 참조
    • 변수에 바로 읽고 쓰기 : 직접 참조
    • 포인터를 통해 주소에 접근 후 읽고 쓰기 : 간접 참조
  • 레퍼런스(&): 다른 변수의 별명
    • 데이터형 마지막에 붙을때는 의미가 달라진다.
    • 데이터형& : 데이터형 레퍼런스가 된다.
  • 포인터와 유사하지만 더 안전하고 편리
    • 포인터가 더 제약사항이 적어 자유롭다는 뜻이다.
  • 선언 시 반드시 초기화 필요
  • NULL이 될 수 없음
    • 포인터는 유효한지 아닌지는 쓰기 전까지 확인이 불가능하다.
int main() {
  // 기본적인 레퍼런스 사용
  int playerHP = 100;
  int& hp = playerHP;    // hp는 playerHP의 별명

  cout << "원래 HP: " << playerHP << endl;
  cout << "레퍼런스 HP: " << hp << endl;

  hp -= 30;              // 레퍼런스로 값 변경
  cout << "공격 후 HP: " << playerHP << endl;  // 70

  // 레퍼런스와 포인터 비교
  int enemyHP = 80;
  int* pHP = &enemyHP;   // 포인터는 주소를 저장
  int& refHP = enemyHP;  // 레퍼런스는 별명

  *pHP -= 10;            // 포인터로 접근
  refHP -= 10;           // 레퍼런스로 접근 (더 간단)
  cout << "적 HP: " << enemyHP << endl;  // 60

  // 함수 매개변수로 사용
  int gold = 1000;
  // addGold(gold);      // 함수에서 레퍼런스로 받으면 원본 수정 가능
}

// 레퍼런스를 매개변수로 받는 함수
void damagePlayer(int& hp, int damage) {
  hp -= damage;
  if (hp < 0) hp = 0;
}

  • int& : 메모리에 값을 쓸 수 있지만 주소 자체에서로 사용할 수 있다.
    • 포인터의 간접 참조처럼 값 변경이 가능해지는 것이다.
    • 들어온 주소를 참조할 수 있는 메모리 공간이 생기는 것이다.
    • 참조할 대상을 ‘반드시’ 넣어야한다.
      • 포인터처럼 넣지만 &를 쓰지않는다.
  • 일반 변수 사용하듯이 사용할 수 있다.
    • 값 변경도 일어난다.
  • 매개변수로 레퍼런스를 받는다면?
    • 값이 변경된다.
  • 사용처 : 매개변수, 맴버 변수 를 받을 때 사용한다.

레퍼런스 vs 포인터

  • 레퍼런스: 선언시 초기화 필수, NULL 불가, 재할당 불가
  • 포인터: NULL 가능, 재할당 가능, * 연산자 필요
  • 레퍼런스가 더 안전하고 사용하기 편리

r-value 레퍼런스

  • int&& max = 200; 형태다.
  • 상수, 즉 읽기만 가능한 레퍼런스다.
    • 일반 레퍼런스 (l-value)는 읽고 쓰기가 가능하다.

범위 기반 for문

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

    for (int i : arr) {
        cout << i << ' ';
    }
    cout << endl;
}
  • 순회가 가능한 배열에서 사용 가능한 문법이다.
  • for (int i : arr): 배열의 각 요소를 i에 복사
    • 반복 할때마다 i 값은 반복할 때 마다 생기고 사라지기를 반복한다.
    • 값만 같지 다른 메모리이다.
  • 인덱스 관리 불필요
  • 배열 크기를 몰라도 된다.
for (int i : arr) {
    i++;  // arr의 각 칸에 들어있는 값을 1씩 증가시킨다.
}
for (int i : arr) {
    cout << i << ' ';
}

1 2 3 4 5 6 7 8 9 10
  • i는 배열 요소의 복사본
  • i를 수정해도 원본 배열은 변경되지 않음
for (int& i : arr) {  // i를 레퍼런스로 선언하면...
    i++;              // arr의 각 칸을 바꿀 수 있다.
}
for (int i : arr) {   // 여기서는 배열의 내용만 출력하므로 일반 변수로 사용
    cout << i << ' ';
}

2 3 4 5 6 7 8 9 10 11
  • int& i: 레퍼런스로 받으면 원본 수정 가능
  • 읽기만 할 때는 일반 변수 사용
  • 수정이 필요할 때는 레퍼런스 사용

함수 오버로딩

  • 오버 로딩 : 이른은 같지만 매개변수가 다른 여러 함수를 정의하는 기능이다.
    • 매개변수가 달라 다른 함수로 여겨지는 것이다.
    • 인자의 데이터형을 확인하고 해당 함수를 실행시키는 것이다.
void swap(int& a, int& b) {  // int형 교환하는 함수
    int tmp = a;
    a = b;
    b = tmp;
}
void swap(double& a, double& b) {  // double형 교환하는 함수
    double tmp = a;
    a = b;
    b = tmp;
}
void swap(char& a, char& b) {  // char형 교환하는 함수
    char tmp = a;
    a = b;
    b = tmp;
}

int main() {
    int a = 10, b = 20;
    double da = 12.34, db = 56.78;
    char ca = 'A', cb = 'B';

    swap(a, b);    // swap(int &a, int &b) 호출
    swap(da, db);  // swap(double &a, double &b) 호출
    swap(ca, cb);  // swap(char &a, char &b) 호출

    cout << a << " " << b << endl;
    cout << da << " " << db << endl;
    cout << ca << " " << cb << endl;
}
  • 주의점 : int 매개변수 오버로딩과 int& 매개변수 오버로딩을 하려 했을때 같은 데이터형은 컴파일 에러가 생긴다.
  • 매개변수 개수에 따라 다른 동작
  • 함수 이름 하나로 여러 상황 처리

디폴트 매개변수

void f(int a = 1) {
    cout << a << endl;
}

int main() {
    f(123);  // a에 123을 대입.
    f();     // a에 기본값인 1을 대입.
}
  • 위와같은 상황은 초기화만 했기 때문에 오버로딩은 아니다.
void f() ~~

void f(int a = 1) {
    cout << a << endl;
}

int main() {
    f(123);  // a에 123을 대입.
    f();     // a에 기본값인 1을 대입.
}
  • 이런식이면 컴파일 에러가 발생한다.
int inventory[8] = { 0 };
int score = 0;

void getItem(int itemId, int cnt = 1, int sc = 0) {
    inventory[itemId] += cnt;  // cnt를 주지 않은 경우, 아이템 개수는 1로 간주
    score += sc;               // sc를 주지 않은 경우, score는 변화 없음
}

int main() {
    getItem(6, 5);        // 6번 아이템을 5개 획득
    getItem(3);           // 3번 아이템을 하나 획득
    getItem(5, 4, 7000);  // 5번 아이템을 4개 얻고 7000점을 획득

    // 보유하고 있는 아이템 개수 출력
    for (int i : inventory) {
        cout << i << " ";
    }
    cout << endl;
    cout << "점수 : " << score << endl;
}

연산자 오버로딩

  • 피연산자의 데이터형에 따라 연산의 결과가 다르게 호출된다.
    • 위의 &의 위치와 같은 경우다.
    • &변수 : 어드레스 연산자
    • 데이터형& : 레퍼런트 변수

객체와 클래스

  • 객체 == 변수 == 인스턴스
  • 클래스 : 데이터 형

네임스페이스

  • 네임스페이스는 이름 충돌을 방지하기 위한 C++의 기능
    • 같은 이름의 변수나 함수를 서로 다른 영역에서 사용 가능하다.
    • 대규모 프로젝트에서 특히 유용하다.
  • 구조체와는 다르다.
    • 네임스페이스에 선언된 변수와 함수는 ‘전역 변수 및 함수’로 사용된다.
      • namespace의 코드블럭은 전역으로 취급한다.
      • 이름은 같지만 다른 영역으로 취급된다.
  • 네임스페이스 안에 오는것들 : 변수, 함수, 구조체, 네임스페이스 등…
namespace doodle {
    int n;
    void set() {
        n = 20;
    }
}

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

int main() {
    doodle::set();  // doodle 네임스페이스의 set() 호출
    google::set();  // google 네임스페이스의 set() 호출

    cout << doodle::n << endl;  // 20 출력
    cout << google::n << endl;  // 30 출력
}
  • namespace 이름 { } 형태로 정의
  • 네임스페이스::멤버 형태로 접근
  • 같은 이름의 변수 n과 함수 set()이 각각 다른 네임스페이스에 존재
    • 가장 가까운 동일 이름의 메모리에 접근하게 된다.

네임스페이스 외부에서 함수 정의

namespace doodle {
    int n;
    void set();  // 함수 선언만
}

// 네임스페이스 외부에서 함수 정의
void doodle::set() {
    n = 20;
}

int main() {
    doodle::set();
    cout << doodle::n << endl;  // 20 출력
}
  • 함수 선언과 정의를 분리 가능
  • 정의할 때는 네임스페이스::함수명 형태 사용

중첩 네임스페이스

namespace A {
    namespace B {
        int a = 5;
    }
}

int main() {
    cout << A::B::a << endl;  // 5 출력
}
  • 더 중첩도 가능하다.

using문 - 네임스페이스 생략

namespace doodle {
    int a = 10;
    int b = 20;
    int c = 30;
}

int main() {
    // using 선언 - 특정 멤버만 가져옴
    using doodle::a;
    cout << a << endl;  // doodle:: 없이 사용 가능

    // using 지시문 - 네임스페이스 전체를 가져옴
    using namespace doodle;
    cout << b << endl;  // b도 바로 사용 가능
    cout << c << endl;  // c도 바로 사용 가능
}
  • using namespace std;를 헤더 파일에서는 사용하지 않는 것이 좋음
    • 왠만해서는 using을 사용하지 않는게 좋다.
  • 이름 충돌 가능성이 있으므로 주의해서 사용

클래스

  • 클래스: 객체를 만들기 위한 설계도 (붕어빵 틀)
  • 객체: 클래스로부터 만들어진 실체 (붕어빵)
  • C의 구조체를 확장한 개념
  • 접근지정자
    • public : 외부, 내부 모두 접근 가능한 지정자다.
    • private : 내부에서만 접근 가능한 지정자다.
    • 구조체, 클래스 모두에 해당되는 규칙이다.
    • 내부 및 외부는 코드 블럭을 기준으로 한다.
  • 위쪽의 접근권한을 밑의 변수 및 함수가 얻게되는 것이다.
    • 접근권한은 번갈아 가면서 사용할 수 있다.
struct TV {  // struct도 클래스처럼 사용 가능
private:
    bool powerOn;
    int channel;
    int volume;

public:
    void on() {
        powerOn = true;
    }
    void off() {
        powerOn = false;
    }

private:
    bool a;
    int b;
    int c;
};

int main() {
    TV mytv;  // TV 클래스의 객체 생성

    mytv.on();
    mytv.setChannel(10);
    mytv.setVolume(50);

    cout << "현재 채널은 " << mytv.getChannel() << endl;
    cout << "현재 볼륨은 " << mytv.getVolume() << endl;
}

  • private: 클래스 외부에서 접근 불가
  • public: 클래스 외부에서 접근 가능
  • getter/setter 메서드로 private 멤버에 안전하게 접근
  • 데이터 유효성 검사 가능 (채널 범위, 볼륨 범위)

접근 지정자 - 클래스와 구조체 차이

class TV {  // class 키워드 사용
    bool powerOn;    // 기본적으로 private
    int channel;     // 기본적으로 private
    int volume;      // 기본적으로 private

public:  // 여기부터 public
    void on() {
        powerOn = true;
    }
    // ... 나머지 메서드들
};

struct TV2 {  // struct 키워드 사용
    bool powerOn;    // 기본적으로 public
    int channel;     // 기본적으로 public
    int volume;      // 기본적으로 public

    // 메서드들도 기본적으로 public
    void on() {
        powerOn = true;
    }
};
  • class: 기본 접근 지정자가 private
  • struct: 기본 접근 지정자가 public
  • C++에서는 struct도 클래스처럼 메서드를 가질 수 있음
  • 그 외에는 차이점이 많이 없다.
    • 사용처에 따라서 사용하면 된다.
    • 수치화된 데이터, 멤버를 모두 공개해도 되겠다 : 구조체
    • 몬스터, 플레이어, 게임보드 등 멤버 변수 및 함수가 필요한 경우 : 클래스
      • 클래스는 멤버에 직접 접근을 대체로 막기때문에 직접 접근을 해야한다면 구조체로 만드는게 좋다.
      • 모르겠으면 클래스로 만드는게 좋다.

this 포인터

class MyClass {
private:
    int value;

public:
		void getValue()
		{
			return value;
		}

    void setValue(int value) {
        this->value = value;  // this->value는 멤버 변수
                             // value는 매개변수
    }

    void printThis() {
        cout << "this 포인터의 값: " << this << endl;
    }
};

int main() {
    MyClass obj1, obj2;

    cout << "obj1의 주소: " << &obj1 << endl;
    obj1.printThis();
    obj1.setValue(20);

    cout << "obj2의 주소: " << &obj2 << endl;
    obj2.printThis();
}
  • 클래스는 변수에 대한 get, set 함수가 기본이다.
  • 매개변수와 멤버 변수 이름이 같을 때 구분
  • 객체마다 고유한 this 포인터 값을 가짐
    • 해당 함수를 포함하는 객체의 주소를 가져오는 것이다.
    • this에 외부에서 선언한 객체의 주소가 할당되는 것이다.
      • 객체가 선언될때 this의 메모리가 생성된다.
  • 메서드 체이닝에 활용 가능
  • this는 클래스의 멤버 함수 내부에서만 사용 가능하다.

메서드 체이닝

  • *this를 사용하는 문법이다.
class Position
{
private:
	int x, y;

public:
	Position& refReturn()
	{
		return *this;
	}

	void init()
	{
		this->x = 0;
		this->y = 0;
		std::cout << "초기 위치: (" << this->x << ", " << this->y << ")" << std::endl;
	}

	Position& moveX(int x)
	{
		this->x += x;
		return *this;
	}

	Position& moveY(int y)
	{
		this->y += y;
		return *this;
	}

	void getPosition()
	{
		std::cout << "(" << this->x << ", " << this->y << ")" << std::endl;
	}

};

int main()
{
	//연습문제 3 - 7 - Position 클래스 (메서드 체이닝)
	Position pos;
	Position& refPos = pos.refReturn();

	pos.init();

	refPos.moveX(10).moveY(20).moveX(5);
	std::cout << "이동 후 위치: ";
	pos.getPosition();

	return 0;
}
  • *this : this의 결과 즉, 객체를 가리킨다.
    • 반환형이 레퍼런스이니 즉, 객체를 바꿀수있는 형태로 반환하는 것이다.
  • 반환형을 *this.를 반환하면 해당 객체를 레퍼런스 형태로 바꾸게 된다.
    • 여러번 연결시켜서 반환이 가능해진다.