[UE5 TIL] Day 8 - C/C++ 수강

JungHoon Eum·2026년 3월 5일

키워드

  • 스코프 & 수명: 지역/전역, static(정적 변수), const
  • 함수 기본기: 함수 선언/정의, 전방 선언, 매개변수 전달 방식
  • 배열·포인터 묶음: 포인터, &/*, 포인터 연산, 배열-포인터 관계(arr[i] == *(arr+i))
  • 파일 구조: 헤더/소스 분리, 분할 컴파일
  • 클래스 기초: 접근 지정자(public/private/protected), 멤버(변수/함수)
  • 생성자/초기화: 생성자, 기본 인자, 멤버 이니셜라이저 리스트(: member(value)), this
  • 입출력/연산자 실수 포인트: std::cout/cin, << >>, std::endl, == vs =, += vs =+
  • 다형성 핵심: virtual, 동적 바인딩(가상 테이블 개념), 포인터/참조 업캐스팅, 순수 가상 함수(=0)

외워야 할 포인트 (연산자/문법)

  • . vs -> : 객체 멤버 접근은 ., 포인터로 멤버 접근은 ->
  • :: : 클래스 밖에서 멤버함수 구현할 때 Student::getAvg()
  • & / * : 주소 얻기 / 역참조(포인터 값 접근), 배열-포인터 관점의 핵심 }
  • << / >> : cout 출력, cin 입력(스트림 방향)
  • = vs == : 대입 vs 비교(조건문에서 가장 많이 터짐)
  • += vs =+ : 누적 더하기 vs “그냥 +값 대입”
  • =0 : 순수 가상 함수(추상 클래스)
  • 생성자 초기화: : member(value)(멤버 이니셜라이저 리스트)

C언어 복습강의

배열

  • 배열은 정적 자료구조: 선언한 후에 절대 그 크기를 변경 불가능
  • 시작 Index는 무조건 0. 시작주소로부터의 거리 로 처리된다
  • 반복문 활용하면 입력, 출력도 편하다

함수

  • 함수-매크로-이벤트. 재활용 가능한 코드뭉치를 Main에서 빼내기
  • 변수: 자료형 변수명 = 값;
    함수: 반환자료형 함수명(매개변수자료형 매개변수명)
    {...return 반환값;}
    포인터: 자료형* 변수명 = 메모리주소값;
    포인터: 자료형* 변수명 = &변수명;
  • 함수 선언 줄 int Sum(int A, int B){ 부터 지역 스코프 적용됨
  • Add() 함수 과제 2
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int Add(int a, int b);

int main(void)
{
	int c;
	int d;
	int Print;
	scanf("%d", &c);
	scanf("%d", &d);
	Print = Add(c, d);
	printf("%d ", Print);
	return 0;
}

int Add(int a, int b) //int c, d에서 전달받음
{
	int Sum = a + b;
	printf("%d ", Sum);

	return Sum; //return a+b도 가능
}
  • 천천히 읽기 하면:
    1. stdio.h 라이브러리 가져옴 _CRT_SECURE_NO_WARNINGS 실행
    2. 일단 main 실행
    3. int c, d, Print 선언
    4. scanf로 c에 입력값 대입
    5. scanf로 d에 입력값 대입
    6. Print에 Add(c, d) 값 대입하기 위해 Add 함수 호출
    7. 매개변수 a, b에 c, d 값 대입
    8. int Sum 선언 및 c+d 값 대입
    9. Sum 값을 10진법으로 출력
    10. return에서 함수 종료하며 Sum 값 반환
    11. main에서 Print에 Sum값 대입
    12. Print를 10진법으로 출력
    13. return 0; 으로 main 종료.
  • 전방 선언: 나중에 분할 컴파일용. int add(int a, int b){} 함수를 맨 위에다 int add(int a, int b); 식으로 선언만 해둔다.
  • 정적 지역 변수 - 전역 변수의 차이: 정적 지역 변수도 함수 끝나도 남아있지만, 실제 접속이 가능한 건 해당 함수나 블럭 내부에서만. 함수 접속 횟수같은거 카운팅할 때 좋음
  • 정적 전역 변수: 해당 '파일' 내에서만 접근 가능
  • const: 변경 불가능

포인터

  • 배열 같은 걸 '들고 다닌다' -> 다 베껴가면 메모리적으로 어려움이 있다.
  • 배열 포인터 들고가면 시작 주소만 들고가서 +index 로 배열 내 모든 메모리 접속 가능
  • 포인터 = 메모리 주소를 저장하는 변수

C++ 강의수강

Class 개념

객체지향 으로 코드 구현하기 용이하게 만든 게 C++.
업데이트, 유지보수 쉽게. 재사용 용이하게 만드는 "객체지향 프로그래밍" 용 Class 문법들

Class 없이 성적 관리 프로그램 만들면:

  • 그냥 한 파일 내부에서 함수 계속 선언하고 main에서 함수 호출해서 쓰면 됨. But...
  • 과목 하나 추가하면? 모든 코드들 다 수정해야 함
  • 데이터 관리하는 코드 int kor[3]; int eng[3] 같은 게 main에 노출되어서 선언되어 있음. 다른 데서 수정될 가능성 다분

Class의 정의

  • 자동차 - 악셀 - 브레이크 사용할 때: 자동차 자체에서 제공하는 기능 이외에 세부 사항을 제어하지 못하게 해야 함
  • 에러의 가능성 줄이고, 예외조항 줄이고. 코드 간단화할 수 있음
  • 사용자가 제어해야 할 것들만 노출하고, 세부사항은 최대한 줄이기 -> 멤버 변수, 멤버 함수를 가지고 숨기고 노출을 조절할 수 있는 class 사용
    • 멤버 함수: 필요한 동작 정의. 외부에서도 접근 가능한 경우 많음
    • 멤버 변수: 세부 데이터. 멤버 함수에서 필요한 정보, 내부 연산용 정보 - 외부에 숨기는 경우가 많다

성적 관리 프로그램용 "학생 클래스" 만들면?

class Student
{
    //동작 정의(이를 멤버함수라고 합니다)
    double getAvg();
    int getMaxNum();
    
    //데이터 정의(이를 멤버변수라고 합니다.)
    int kor[3];
    int eng[3];
    int math[3];
};

Class 내부 구현

  • Class 멤버 함수를 구현하는 방법
    1. 클래스 내부에서 멤버 함수 본문까지 직접 정의
    2. 클래스 내부에선 멤버 함수 선언만 작성, 외부에서 구현하기 <<이거 더 많이씀
  • 외부에서 멤버함수 구현법
#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>
using namespace std;
class Student
{
    //동작 정의(이를 멤버함수라고 합니다)
    double getAvg(); // 여기서 "이거 멤버함수로 쓴다~" 라고 말해주는 격
    int getMaxNum();
    //데이터 정의(이를 멤버변수라고 합니다.)
    int kor;
    int eng;
    int math;
};

double Student::getAvg() // 여기서 실제로 함수를 정의하기
{
    return (kor + eng + math) / 3.0;
}
int Student::getMaxNum()
{
    return max(max(kor, eng), math);
    // 다른 방법 return max({ kor, eng, math });
}
  • C언어에서 함수 선언만 해두고 밑에서 정의하는 거랑 비슷.

접근제어

  • 접근 연산자 . 사용해서 멤버 함수/변수에 접근. C++ 접근 지정자 public, private, protected 사용 가능.

  • class 키워드 사용 시, 명시하지 않으면 디폴트는 private. 클래스 외부에서 직접 접근할 경우 컴파일 에러

    • public은 클래스 외부에서 접근 연산자(.) 로 접근 가능. Like
      int main()
      {
        Student s; //student 클래스의 변수(객체) s 선언 -> "인스턴스화" 라고도 부름
        s.getAvg();//s 에서 getAvg() 멤버 함수 접근. public으로 미리 선언되어 있음
        
        return 0;
      }
  • 클래스 내부에서 private, public 변수, 함수 선언법

private:
    //데이터 정의(이를 멤버변수라고 합니다.)
    int kor;
    int eng;
    int math;

getter와 setter

  • 각각이"멤버 함수" 멤버 변수를 조작할 때 setter, 값을 가져올 때 getter - C++ 코딩 작문에서 하나의 "관례" 비스무리한 것. 멤버 변수를 숨기고 이를 조작할 때 멤버함수를 이용하게 함으로써 검사를 용이하게, 오류를 줄이게 할 수 있다.
int main()
{
    Student s;

    s.setEngScore(32); //Student의 멤버함수 setEngScore
    s.setKorScore(52);
    s.setMathScore(74);

    //평균 최대점수 출력
    cout << s.getAvg() << endl;
    cout << s.getMaxScore() << endl;

    return 0;
}

생성자

  • class는 설계도에 가까움. 딴 곳에서 선언될 때 하나의 인스턴스(객체)가 생성됨. 이 때 쓰는 게 생성자
 // 기본 생성자
    Person() {
        name = "Unknown";
        age = 0;
    }

이게 class "내부에" 존재하고 class 선언될 때 가장 먼저 소환됨.

  • 매개변수가 있는 생성자의 경우
    Person p("Alice", 25); 로 선언하고 클래스 내부에선
Person(string n, int a) {
        name = n;
        age = a;
    }

로 존재한다.

  • 기본매개변수가 있다면? 클래스 내부에서 Person(string n = "DefaultName", int a = 18) 이렇게 표현해 놓는다.

Student 구현을 위해 코드 나누기

  • 클래스의 "선언부"(헤더) 와 구현부 (소스 파일) 분리한 후 필요한 곳에서 헤더를 #include"" 하는 방식을 자주 쓴다.
  • 헤더는 목차. 구현부는 실제 내용. 구분을 용의하기 -> 그리고 함수 중복 정의 에러를 피하기 위해(호출한 함수만 쓴다)

과제 1 - C++로 배터리 관리 클래스 만들어 보기

  • 기능:
    • 배터리 잔량 관리(0~100)
    • 배터리 사용 및 충전
    • 초기 배터리는 100
  • 메서드
    • 배터리 잔량 반환하는 메서드
    • 배터리를 사용하는 메서드 (호출될 때마다 잔량 5씩 감소)
    • 배터리 충전하는 메서드 (호출될 때마다 잔량 7씩 충전)
  • 초기 상태: 잔량 기본값 100. 해당 값은 생성자에서 설정

헤더:

#ifndef BTRMANAGE_H_
#define BTRMANAGE_H_

class BtrManage
{
public:
	BtrManage(int startBattery = 100) //  Battery(int initialCharge = 100) : charge(initialCharge) { 객체가 생성되면서 초기화
	{
		battery = startBattery; //객체가 생성된 후에 초기화. battery가 const int 면 초기화가 안될 수 있다.
	}
	int ReturnBattery();
	void UseBattery();
	void ChargeBattery();

private:
	int battery;


};

#endif

소스코드:

#include <iostream>
#include "BtrManage.h"

using namespace std;

int BtrManage::ReturnBattery()
{

	return battery;
}

void BtrManage::UseBattery()
{
	if (battery > 5)
	{
		battery -= 5;
		cout << "Used battery! Currently: " << battery << endl;
	}
	else if(battery>0)
	{
		battery = 0;
		cout << "Used remaining battery. Battery depleted!" << endl;
	}
	else
	{
		battery = 0;
		cout << "Cannot use battery. Already Depleted." << endl;
	}
	return;
}

void BtrManage::ChargeBattery()
{
	
	if (battery < 93)
	{
		battery += 7;
		cout << "Charged battery! Currently: " << battery << endl;
		
	}
	else if (battery < 100)
	{
		battery = 100;
		cout << "Charged to Full!!" << endl;
	}
	else
	{
		battery = 100;
		cout << "Cannot charge battery. Already Full." << endl;
	}
	return;
}

메인:

using namespace std;

int main()
{
	BtrManage btr;
	BtrManage btr1(15);

	cout << btr.ReturnBattery() << endl;
	btr.UseBattery();
	btr.ChargeBattery();
	cout << btr.ReturnBattery() << endl;

	cout << btr1.ReturnBattery() << endl;
	btr1.UseBattery();
	btr1.ChargeBattery();
	cout << btr1.ReturnBattery() << endl;

	return 0;
}
  • 중요 내용:
    생성자 만들 때 Battery(int initialCharge = 100) : charge(initialCharge) 로 쓰는 게 낫다. 이건 "새 객체 생성하면서 초기화" 라 const 값도 지정해 줄 수 있음.
    coutusing namespace std; 를 썼으면 문제가 없긴 하지만 std::cout 을 써 버릇 하는 게 좋다 -> 나중에 using namespace std 까먹어서 멘탈붕괴하는 상황이 "무조건" 온다.

과제 2 - 분수 곱셈

  • 두 분수의 곱셈을 하는 클래스를 만들어 봅시다

  • 분자는 numerator, 분모는 denominator

  • 두 분수의 곱의 결과는 기약분수로 출력

  • 구현할 메서드

    • 기본 생성자를 만듦 (0/1)
    • 분자와 분모의 수를 입력받는 생성자 구현
    • 현재 분수를 출력하는 함수 display
    • 기약분수 형태로 만드는 simplify

    실제 함수 구현만 붙여넣자면:

#include "Fraction.h"
#include <iostream>


int Fraction::setGcd(int a, int b)
{
	while (b != 0) {
		int temp = b;
		b = a % b;
		a = temp;
	}
	return a;
}

void Fraction::Display()
{
	std::cout << "Numerator is: " << numerator << " and denominator is: " << denominator << "\nIn full: " << numerator << "/" << denominator << std::endl;
	return;
}

void Fraction::Simplify()
{
	int gcd = 1; //혹시몰라서 1로
	gcd = setGcd(numerator, denominator);
	numerator = numerator / gcd;
	denominator = denominator / gcd;
	std::cout << "Simplified to: " << numerator << "/" << denominator << std::endl;
	return;
}

Fraction Fraction::Multiply(Fraction other) //const Fraction& other 로 받으면 더 깔끔하다. 레퍼런스만 받기+수정 불가능
{
	int newNumerator;
	int newDenominator;
	newNumerator = numerator * other.numerator;
	newDenominator = denominator * other.denominator;
	Fraction f3(newNumerator, newDenominator);
	f3.Simplify();
	std::cout << "Multiplication complete!" << std:: endl;
	return f3;
}
  • 포인트:

    • Fraction Fraction::Multiply(const Fraction& Other) 형식으로 받아오는 Fraction 클래스의 경우 &으로 레퍼런스로, const로 수정불가능하게 가져오는 게 안전
    • Multiply 내부에서 너무 변수선언 많이했다 싶으면
    {
    	Fraction Result(numerator*other.numerator, denominator*other.denominator);
      	Result.Simplify();
      	return Result;
    }

    이렇게도 가능

    • 클래스에 클래스 대입 f3 = f1.Multiply(f2); 이런 방식으로도 만들 수 있다.

객체지향 프로그래밍

  • class 는 C언어의 struct 를 확장해서 객체지향적 개념 추가한 것
  • 상속다형성 에 대해 알아볼 것
  • protected 멤버 변수: private 와 달리 상속받은 클래스에선 접근 가능

상속

  • i. e.) 자동차: 공통적으로 가지는 속성 속도/색상 이 있다 치자.
    이런 공통특성을 모든 차량에 개별적으로 개별 구현하는 대신, 하나의 기본 클래스를 정의하고 이를 활용해서 유지보수를 용이하게 한다.

  • 멤버 초기화: 아까 위에서 한 Battery(int initialCharge = 100) : charge(initialCharge) 같이 생성자 코드보다 먼저 멤버 변수를 초기화(값 설정) 해줄 수 있는 방법.

  • 자식 클래스의 생성자는 부모 클래스의 생성자를 호출 가능

    • Base 클래스, Derived 클래스(자손 클래스) 가 있다 치자. 각각 생성자가 있을 텐데
      Derived():Base(3,4) 같이 생성자를 쓰면 자식 클래스의 생성자를 생성하기 전에 위에 멤버 초기화 한 것처럼 3, 4를 인자로 갖는 Base 클래스를 호출하고 Derived 생성자를 호출한다.
      Bicycle(string c, int s, bool basket) : Vehicle(c, s), hasBasket(basket) {}
      Vehicle의 자손 Bicycle. colorspeed에 고유한 값을 가지는 Vehicle을 호출하고 그 값을 가져와서 속도와 색상이 지정된 클래스가 될 수 있다.
      • 좀 더 자세히 설명하자면, Vehicle에서 string과 integer 받아서 객체 생성하는 기능을 구현해 뒀다. 이 기능을 자손클래스에서 쓰기 위해 같은 형식의 인자를 받는 생성자를 만들고, 해당 인자들을 고대로 Vehicle에서 인자로 받아서 만드는 형식.

다형성

  • Lion, Dog, Wolf가 있다 치면, 셋 다 "운다" 라는 기능을 하긴 함. 근데 우는 방식은 다 다름.
  • 부모 클래스에서 함수 정의 하고 virtual 을 앞에 붙여주면 virtual int func()
    • 자식들에서 그 함수 int func() 를 각자 "구현" 할 수 있게 됨.
  • 함수 입력, 출력 자료형 이런 건 부모에서 정의. 자손에서 실제 내용 구현.
  • 이러고 포인터/ 레퍼런스 활용해서 동적 변환(dog 클래스에 대한 포인터가 자동으로 Animal 포인터처럼 동작함) 하면
void print(Animal* animal)
{
	animal->bark();
}
  • 이런 식으로 모든 종류의 animal에 대한 bark 기능을 원샷으로 해결 가능함.

추가 고찰

  • Is A 테스트 도입해 보기
    • Lion is an animal. Animal is not an Animal. 따라서 Animal이 기본 클래스
  • 순수 가상 함수란?
    • 위에서 말한 virtual int func() 뒤에 =0; 만 붙여주면 바로 순수가상함수
    • 이게 하나라도 포함되어 있는 부모 클래스는 "추상" 클래스가 되서 인스턴스 생성 불가
    • Animal의 울음소리 자체는 구현 할 필요도 없고 구현되어 있지도 않음. 자식 클래스들(Lion, Wolf, Dog...) 에선 이걸 "무조건" 구현해 줘야 함. 구현 안해주면 자식도 추상클래스임
  • -> 연산자: 클래스에서 멤버 접근할 땐 mydog.makesound() 같이 . 써줬다
    포인터에서(myAnimal = &myDog) 해당 주소 클래스의 멤버에 접근할 땐 myAnimal->makeSound();
  • 부모-자식, 기본-파생, 인터페이스-인터페이스 구현 : 다 비슷한 관계를 설명하는 단어. Animal-Dog 관계를 저 단어들 다 써도 된다.
profile
개발지망생

0개의 댓글