C++문법 정리

jade·2025년 2월 13일

DoIt_Algorithm_C++

목록 보기
1/7
post-thumbnail

공부한 유튜브 사이트:
[하루10분|C++] 0-1. C++을 학습하여야 하는 이유

1. & 참조 연산자

  • “참조”는 기존 변수의 별명(Alias)을 만드는 것
  • 참조는 실제 데이터를 복사하지 않고, 기존 변수의 메모리 주소를 공유함.
  • 선언할 때 변수 타입 뒤에 &를 붙이면 참조 변수가 됨.
#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int& ref = a;  // ref는 a의 참조 (별명)

    cout << "a: " << a << ", ref: " << ref << endl;

    ref = 20;  // ref를 변경하면 a도 변경됨
    cout << "a: " << a << ", ref: " << ref << endl;

    return 0;
}
/* 출력결과
a: 10, ref: 10
a: 20, ref: 20
  • 매개변수로 참조연산자를 쓰는 경우가 많은데
    → 객체를 복사하지 않고 주소만을 이용해 객체의 내용을 수정할 수 있기 때문 → 효율적!

2. 클래스

1) 기본 구조

class 클래스이름 {
private:   // 비공개 멤버 (외부 접근 불가)
    데이터_타입 멤버변수;

public:    // 공개 멤버 (외부 접근 가능)
    반환형 함수이름(매개변수) {
        // 함수의 구현
    }
};
  • 접근 지정자
public어디서든 접근 가능 (외부에서도 접근 가능)
private클래스 내부에서만 접근 가능 (기본값)
protectedprivate과 비슷하지만 상속받은 클래스에서도 접근 가능

2) 클래스 선언/ 사용

  • 클래스 선언
#include <iostream>
using namespace std;

class Car {  // 클래스 선언
public:
    string brand;  // 멤버 변수
    int speed;     

    void showInfo() {  // 멤버 함수
        cout << "브랜드: " << brand << ", 속도: " << speed << "km/h" << endl;
    }
};
  • 클래스 사용
int main() {
    Car myCar;  // 객체 생성
    myCar.brand = "Hyundai";  // 멤버 변수 값 설정
    myCar.speed = 120;

    myCar.showInfo();  // 멤버 함수 호출

    return 0;
}

3) 생성자

  • 생성자란?
    • 객체가 생성될 때 자동으로 호출되는 함수.
    • 객체 초기화 역할을 함.
    • 클래스 이름과 동일한 이름을 가짐.
    • 반환형이 없음.
class Car {
	public:
	    string brand;
	    int speed;
	
	    **// 매개변수 없는 생성자**
	    Car() {
	        brand = "Unknown";
	        speed = 0;
	        cout << "자동차 객체가 생성되었습니다." << endl;
	    }
	    **// 매개변수가 있는 생성자**
	    Car(string b, int s) {
	        brand = b;
	        speed = s;
	    }
		
	    void showInfo() {
	        cout << "브랜드: " << brand << ", 속도: " << speed << "km/h" << endl;
	    }
};

int main() {
    Car myCar;  **// 생성자 자동 호출**
    Car myCar("BMW", 220);  **// 매개변수가 있는 생성자 호출**
    myCar.showInfo();
    return 0;
}

4) 소멸자

  • 객체가 소멸될 때 자동으로 호출되는 함수.
  • ~클래스이름() 형태로 선언.
  • 주로 동적 할당된 메모리를 해제하는 용도로 사용.
class Car {
public:
    Car() { cout << "자동차 객체 생성!" << endl; }
    ~Car() { cout << "자동차 객체 소멸!" << endl; }
};

int main() {
    Car myCar;
    return 0;
}
/*출력결과
자동차 객체 생성!
자동차 객체 소멸!
*/

5) 클래스 함수의 외부 정의

  • 클래스 내부가 너무 길어지면, 클래스 외부에서 멤버 함수를 정의할 수 있음.
class Car {
	private:
	    int speed;
	public:
	    **void setSpeed(int s); // 함수 선언**
	    **int getSpeed();**
};

**// 함수 외부 정의**
void Car::setSpeed(int s) { speed = s; }
int Car::getSpeed() { return speed; }

int main() {
    Car myCar;
    myCar.setSpeed(200);
    cout << "현재 속도: " << myCar.getSpeed() << "km/h" << endl;
    return 0;
}
//출력: 현재 속도: 200km/h

6) this

  • this 란? → 클래스의 멤버 함수 내부에서 객체 자신을 가리키는 포인터
  • 사용법
    • this는 멤버 함수 내에서만 사용 가능.
    • 현재 객체의 주소를 가리키는 포인터.
    • 멤버 변수와 지역 변수의 이름이 같을 때 구별하는 데 사용.
    • 멤버 함수에서 자기 자신을 반환할 때 사용.
  • 코드1 설명
    • setBrand(string brand) 함수에서 매개변수와 멤버 변수 이름이 같음.
    • this->brand = brand;에서 this->brand객체의 멤버 변수, brand매개변수를 의미.
    • this가 없다면 지역 변수와 멤버 변수를 구분할 수 없음.
#include <iostream>
using namespace std;

class Car {
	private:
	    string brand;
	
	public:
	    void setBrand(string brand) {
	        this->brand = brand;  // 멤버 변수와 매개변수 구분
	    }
	
	    void showInfo() {
	        cout << "브랜드: " << brand << endl;
	    }
};

int main() {
    Car myCar;
    myCar.setBrand("Hyundai");
    myCar.showInfo();

    return 0;
}
//출력: 브랜드: Hyundai
  • 코드2
class Car {
	private:
	    string brand;
	    int speed;
	
	public:
	    Car* setBrand(string brand) {
	        this->brand = brand;
	        **return this;  // 자기 자신(객체의 주소) 반환**
	    }
	
	    Car* setSpeed(int speed) {
	        this->speed = speed;
	        **return this;**
	    }
	
	    void showInfo() {
	        cout << "브랜드: " << brand << ", 속도: " << speed << "km/h" << endl;
	    }
};

int main() {
    Car myCar;
    myCar.setBrand("BMW")->setSpeed(220)->showInfo(); 

    return 0;
}
//출력: 브랜드: BMW, 속도: 220km/h

7) 클래스 배열

  • 클래스로 배열도 만들 수 있음
#include <iostream>
#include "Stock.cpp"

using namespace std;
int main() {
	Stock s[4] = {
		Stock("A",10,10000); //s[0]
		Stock("A",10,10000); //s[1]
		Stock("A", 10, 10000); //s[2]
		Stock("A", 10, 10000); //s[3]

	}
	s[0].show();
	return 0;
}

3. 연산자 오버로딩

  • 기존 연산자(+, -, *, /, == 등)의 기능을 사용자 정의 클래스에 맞게 재정의하는 기능
  • 즉, 기본 자료형(int, double 등)에서 사용하던 연산자를 사용자 정의 타입(class, struct 등)에서도 사용할 수 있도록 만듭니다.
  • 연산자 오버로딩의 두가지 방식
#include <iostream>

class Point {
	public:
		int x, y;
	//생성자, point에 아무값도 안주면 a=b=0으로 설정
	Point(int a = 0, int b = 0) : x(a), y(b) {}
	
	// + 연산자 오버로딩
	Point operator+(const Point& other) {
	    return Point(x + other.x, y + other.y);
}

void print() {
    std::cout << "(" << x << ", " << y << ")" << std::endl;
};

int main() {
	Point p1(2, 3);
	Point p2(4, 5);
	Point p3 = p1 + p2;  // **p1.operator+(p2) 호출**
	p3.print();  // (6, 8) 출력
}
//this 포인터를 이용한 연산자 오버로딩
class Car {
	private:
	    string brand;
	    int speed;
	
	public:
	    Car(string b, int s) : brand(b), speed(s) {}
	
	    **// 대입 연산자 오버로딩**
	    Car& operator=(const Car& other) {
	        if (this == &other) return *this; // 자기 자신과의 대입 방지
	        this->brand = other.brand;
	        this->speed = other.speed;
	        return *this;  // 자기 자신 반환
	    }
	
	    void showInfo() {
	        cout << "브랜드: " << brand << ", 속도: " << speed << "km/h" << endl;
	    }
};

int main() {
    Car car1("Audi", 180);
    Car car2("Mercedes", 200);

    car1 = car2;  // 대입 연산자 호출
    car1.showInfo();  

    return 0;
}

4. Friend 개념

  • friend를 사용하면 특정 함수나 다른 클래스가 해당 클래스의 비공개 멤버에 접근가능
  • friend 함수
#include <iostream>
using namespace std;

class MyClass {
private:
    int secret;

public:
    MyClass(int s) : secret(s) {}

    // friend 함수 선언
    friend void showSecret(const MyClass& obj);
};

// friend 함수 정의
void showSecret(const MyClass& obj) {
    cout << "Secret: " << obj.secret << endl;  **// private 멤버에 접근 가능**
}

int main() {
    MyClass obj(42);
    showSecret(obj);  // friend 함수를 통해 private 멤버 접근 가능
    return 0;
}
  • Friend 클래스
#include <iostream>
using namespace std;

class A {
private:
    int secret;

public:
    A(int s) : secret(s) {}

    **// B 클래스가 A의 private 멤버에 접근할 수 있도록 허용
    friend class B;**
};

class B {
public:
    void showSecret(const A& obj) {
        cout << "Secret from B: " << obj.secret << endl;  // A의 private 멤버 접근 가능
    }
};

int main() {
    A objA(77);
    B objB;
    objB.showSecret(objA);
    return 0;
}
//출력결과: Secret from B: 77

5. 스트림 추출 연산자 <<

  • << 연산자를 오버로딩하면, cout << 객체; 형태로 객체를 출력이 가능하다

#include <iostream>
using namespace std;

class MyClass {
private:
    int data;

public:
    MyClass(int d) : data(d) {}  // 생성자

    // << 연산자 오버로딩을 위해 friend 함수 선언**
    friend ostream& operator<<(ostream& os, const MyClass& obj);
};

// << 연산자 오버로딩 함수 정의**
ostream& operator<<(ostream& os, const MyClass& obj) {
    os << "MyClass data: " << obj.data;  // private 멤버 data에 접근 가능
    return os;
}

int main() {
    MyClass obj(100);
    cout << obj << endl;  // << 연산자가 오버로딩되어 있어 가능!
    return 0;
}
//출력결과: MyClass data: 100

6. 상속

  • 기초 클래스 → 상속 → 파생 클래스
  • 기초클래스에 저장되어 있는 것들이 모두 파생클래스에 들어감
  • 파생클래스에서 새로운 요소들을 추가하는 것도 가능
//상속
//Time에 저장되어있는 것들이 NewTime에 들어감
//NewTime -> 파생클래스형
//NewTime에서 새로운 것들을 추가할 수 있음
class NewTime :public Time {
	private:
		int day;
	public:
		NewTime();
		NewTime(int, int, int);
		void print();
};
  • 그러나, 파생클래스에서는 기초클래스의 private 요소에 직접 접근할 수 없음
  • ⇒ 기초클래스 생성자의 결과가 파생클래스 생성자의 매개변수로 들어가면 됨!
//파생클래스는 기초클래스Time의 private요소들에는 직접적으로 접근불가
//-> 해결법은 : Time()
//Time() 생성자의 결과가 NewTime()의 매개변수로 들어감
NewTime::NewTime():Time() {//***
	day = 0;
}
NewTime::NewTime(int,int,int) **:Time(h, m)**{
	day = d;
}
void NewTime::print() {
	std::cout << "일 : " << day << std << endl;
	show(); //파생클래스의 함수
}

7. public 다형상속/가상메서드

1) public 다형상속

  • CABpublic으로 상속받았기 때문에, ABpublic 멤버에 자유롭게 접근할 수 있음.
  • 객체 obj를 통해 A, B, C의 멤버 함수들을 모두 호출할 수 있음.
#include <iostream>
using namespace std;

class A {
	public:
	    void showA() {
	        cout << "Class A" << endl;
	    }
};

class B {
	public:
	    void showB() {
	        cout << "Class B" << endl;
	    }
};

// C가 A와 B를 동시에 상속받음**
class C : public A, public B {
	public:
	    void showC() {
	        cout << "Class C" << endl;
	    }
};

int main() {
    C obj;
    obj.showA();  // A의 함수 호출
    obj.showB();  // B의 함수 호출
    obj.showC();  // C의 함수 호출
    return 0;
}

2) “다이아몬드”문제 발생 가능!

  • D 클래스는 BC를 상속받는데, BC가 각각 A를 상속받았기 때문에 D 안에는 A 객체가 두 개 존재하게 됨.
  • obj.show(); 호출 시, 어떤 Ashow()를 호출해야 할지 컴파일러가 결정할 수 없음 (모호성 문제 발생).
#include <iostream>
using namespace std;

class A {
public:
    void show() {
        cout << "Class A" << endl;
    }
};

class B : public A {};  // A를 상속
class C : public A {};  // A를 상속

// D가 B와 C를 다중 상속
class D : public B, public C {};

int main() {
    D obj;
    obj.show();  // 컴파일 오류 발생! A가 두 번 상속됨**
    return 0;
}

📌 해결 방법: virtual 키워드를 사용하여 공통 조상(A)이 한 번만 상속되도록 만들어야 함.

3) vitual 키워드

  • virtual 키워드는 BCA를 상속할 때 선언해야 함.
  • D에서 따로 virtual을 선언하지 않아도 자동으로 A가 한 번만 상속됨.
#include <iostream>
using namespace std;

class A {
public:
    void show() {
        cout << "Class A" << endl;
    }
};

**// 가상 상속을 적용**
class B : virtual public A {};
class C : virtual public A {};

class D : public B, public C {};

int main() {
    D obj;
    obj.show();  // 문제 없이 실행됨!
    return 0;
}
  • vitual 을 쓸때는 생성자 생성의 흐름(순서)를 잘 알아두는 것이 중요하다
#include <iostream>
using namespace std;

class A {
	public:
	    A() { cout << "A 생성자 호출" << endl; }
};

class B : virtual public A {
	public:
	    B() { cout << "B 생성자 호출" << endl; }
};

class C : virtual public A {
	public:
	    C() { cout << "C 생성자 호출" << endl; }
};

class D : public B, public C {
	public:
	    D() { cout << "D 생성자 호출" << endl; }
};

int main() {
    D obj;
    return 0;
}
//출력: A 생성자 호출 B 생성자 호출 C 생성자 호출 D 생성자 호출

0개의 댓글