공부/C++

함수, 스코프, 반환값, Call-by-value, Call-by-address, 프로토 타입, 배열을 매개변수로 넘기기, 이차원 배열 매개변수, 재귀함수, 팩토리얼, 피보나치, fork, git, SFML 첫 시작 - 배경 출력

월러비 2025. 6. 10. 17:38

함수

  • 함수의 시작 : 받은 매개변수의 초기화부터 시작한다.

스코프

  • 중괄호(코드 블럭)
  • 스코프 내에서는 같은 이름의 변수가 존재하면 안된다.
  • 스코프마다 이름이 같은 변수가 있어도 다른 변수이다.

반환값

  • 함수가 작업을 완료한 후 호출한 곳으로 돌려주는 값
  • return 키워드를 사용하여 반환
  • 함수의 반환 타입과 일치해야 함

반환값이 있는 함수 예시

#include <stdio.h>

int add(int a, int b) {
    int result = a + b;
    return result;  // 계산 결과를 반환
}

double divide(int a, int b) {
    if (b == 0) {
        printf("0으로 나눌 수 없습니다.\\\\n");
        return 0.0;  // 오류 상황에서의 반환
    }
    return (double)a / b;  // 나눗셈 결과를 반환
}

int main() {
    int sum = add(5, 3);
    printf("5 + 3 = %d\\\\n", sum);

    double quotient = divide(10, 3);
    printf("10 / 3 = %.2f\\\\n", quotient);

    return 0;
}

반환값이 없는 함수 (void)

#include <stdio.h>

void printMessage() {
    printf("안녕하세요!\\\\n");
    // return 문이 없어도 함수 끝에서 자동으로 반환
}

void printNumber(int num) {
    printf("입력받은 숫자: %d\\\\n", num);
    return;  // void 함수에서 명시적으로 반환 (선택사항)
}

int main() {
    printMessage();
    printNumber(42);
    return 0;
}

반환의 특징

  • return 문 실행 시 함수 즉시 종료
  • 하나의 함수에서 여러 개의 return 문 가능
  • void 함수는 반환값 없음
    • void 형도 return을 쓰면 함수가 종료된다.
  • 반환 타입과 실제 반환값의 타입 일치 필요

Call-by-value, Call-by-address, Call-by-reference

  • 보통은 매개변수로 받더라도 다른 스코프에 있는 변수는 접근이 안되지만, ‘포인터’를 매개변수로 받는다면 다른 스코프에 있는 변수라도 접근하여 변경이 가능하다.
    • 함수를 사용하여 외부에서 값 변경이 가능해진다.
  • C언어에서는 주로 Call-by-value와 Call-by-address를 사용 - 값형식
  • C++에서는 Call-by-reference도 지원 - 참조 형식

Call-by-value (값에 의한 호출)

void swapValue(int a, int b) 
{
    int temp = a;
    a = b;
    b = temp;
    printf("함수 내부: a = %d, b = %d\\\\n", a, b);
}
  • 매개변수의 변수값은 매개변수로 들어온 값으로 ‘초기화’되는 것이다.
    • 함수에 들어온 값은 함수 종료시 삭제된다.
  • 변수가 넘어가는 것이 아니라 변수에 저장된 값이 넘어가는 것이다.
    • 넘어간 값의 변경은 원본의 값과 관계가 없다.

Call-by-address (주소에 의한 호출)

void swapAddress(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    printf("함수 내부: *a = %d, *b = %d\\\\n", *a, *b);
}
  • 메모리에 접근하여 변경하는 것이기에 바뀐값이 유지된다.

전달 방식 비교

구분 Call-by-value Call-by-address

전달 내용 변수의 값 변수의 주소
원본 변경 불가능 가능
메모리 사용 복사본 생성 주소만 전달
안전성 높음 (원본 보호) 낮음 (원본 변경 가능)
사용 시기 값만 필요한 경우 원본 수정이 필요한 경우

프로토 타입

  • 함수의 선언부만 미리 작성
    • 프로토 타입 등 다 같은 말이다.
  • 컴파일러에게 함수의 존재를 알려줌
  • 함수의 반환 타입, 이름, 매개변수 정보 포함
  • 헤더 파일에 주로 선언된다.
    • 물론, 소스코드에서도 많이 사용된다.
    • 앞으로 가급적이면 프로토 타입으로 함수를 작성해라
  • 헤더 파일 : 선언문, 소스코드 : 구현부
// 함수 프로토타입 선언
int add(int a, int b);
void printResult(int result);

int main() {
    int sum = add(5, 3);  // 프로토타입이 있어 컴파일 가능
    printResult(sum);
    return 0;
}

// 함수 정의는 main 함수 뒤에 위치
int add(int a, int b) {
    return a + b;
}

void printResult(int result) {
    printf("결과: %d\\\\n", result);
}

  • 함수의 정의가 없고, 선언만 되어있다면? : 컴파일 오류가 생긴다.
  • 이름, 매개변수 데이터형, 반환하는 데이터형(반환값을 받는 메인함수의 변수)가 필요하다.
    • 반환값이 없다면 마지막거는 상관없다.

프로토타입 작성 규칙

// 매개변수 이름 포함
int add(int a, int b);

// 매개변수 이름 생략
int add(int, int);

// 둘 다 유효한 프로토타입
  • 매개변수 이름은 생략 가능 (타입만 명시)
  • 헤더 파일에 주로 작성
  • 세미콜론(;)으로 종료

배열을 매개변수로 넘기기

  • 배열은 항상 주소로 전달됨 (Call-by-address)
    • 시작 주소를 넘기는 것이다.
  • 배열의 크기 정보는 별도로 전달 필요
  • 함수 내에서 배열 원소 수정 시 원본 배열도 변경됨
void printArr(int parr[4]) {
    for (int i = 0; i < 4; i++) {
        printf("%d ", parr[i]);
    }
    printf("\\\\n");
}

int main() {
    int arr[4] = { 1, 2, 3, 4 };
    printArr(arr);
    return 0;
}
  • int parr[4] ⇒ int* parr로 넘기는것이 좋다.
  • 사이즈가 안적혀있는데 사이즈를 같이 넘기는것이 좋다.
    • 반드시 사이즈도 넘겨라
    • sizeof(arr) / sizeof(int) ⇒ 배열의 갯수 확인 ⇒ 배열 크기를 함께 전달하는 방법

배열 매개변수의 다양한 표현

// 모두 동일한 의미
void printArr(int parr[4]);     // 크기 명시
void printArr(int parr[]);      // 크기 생략
void printArr(int* parr);       // 포인터로 표현

이차원 배열 매개변수

  • 첫 번째 차원 크기는 생략 가능
  • 두 번째 차원 크기는 반드시 명시 필요
  • 메모리 레이아웃을 알기 위해 열 크기 정보 필수
// 올바른 선언
void func(int arr[][4], int rows);    // 열 크기 명시
void func(int arr[3][4]);             // 행, 열 크기 모두 명시

// 잘못된 선언
void func(int arr[][], int rows);     // 열 크기 생략 불가

  • int arr[][4] == int (*arr)[4]와 같은 의미다.
  • int* ptr, int rows, int cols로 매개변수를 받을 수도 있다.
    • ptr[i * cols + j]로 행렬을 출력한다.
    • print2DArray(matrix[0][0], 3, 4); 이런식으로 자주 쓰게된다.

재귀함수

  • 자기 자신을 호출하는 함수
  • 복잡한 문제를 간단한 하위 문제로 분할
  • 수학적 정의나 반복적 구조에 유용
void countdown(int n) {
    if (n <= 0) {  // 기저 조건 (Base Case)
        printf("발사!\\\\n");
        return;
    }

    printf("%d\\\\n", n);
    countdown(n - 1);  // 재귀 호출
}

int main() {
    countdown(5);
    return 0;
}

재귀 함수 필수 요소

  1. 기저 조건 (Base Case)
    • 재귀 호출을 멈추는 조건
    • 반드시 필요 (없으면 무한 재귀)
  2. 재귀 호출 (Recursive Call)
    • 자기 자신을 호출
    • 매개변수가 기저 조건에 가까워져야 함

팩토리얼 계산 예시

#include <stdio.h>

int factorial(int n) {
    if (n <= 1) {  // 기저 조건
        return 1;
    }
    return n * factorial(n - 1);  // 재귀 호출
}

int main() {
    int num = 5;
    printf("%d! = %d\\\\n", num, factorial(num));
    return 0;
}

피보나치 수열 예시

#include <stdio.h>

int fibonacci(int n) {
    if (n <= 1) {  // 기저 조건
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);  // 재귀 호출
}

int main() {
    for (int i = 0; i < 10; i++) {
        printf("fibonacci(%d) = %d\\\\n", i, fibonacci(i));
    }
    return 0;
}

  • 재귀함수 단점 : 반복문보다 큰 오버헤드 발생

재귀 vs 반복문

구분 재귀 함수 반복문

가독성 높음 (수학적 정의와 유사) 보통
메모리 사용 많음 (스택 사용) 적음
실행 속도 느림 (함수 호출 오버헤드) 빠름
구현 복잡도 간단 복잡할 수 있음

공유 저장소(git, fork) 사용

포크

  • 실행 - File - preference - Git : 본인 깃허브 아이디 연결
  • File - preference - General : 부모 폴더 연결
  • 브런치 란? : 독립 작업을 위한 공간?이다.
    • 작업 후 merge로 다시 하나의 브런치로 모은다.
  • local changes : 로컬 환경(내 컴퓨터)에서 수정하거나 변경한 내용이다.
    • unstaged : 내가 commit하지 않을 내용을 모아놓은 곳
    • staged : 내가 commit 할 내용을 모아놓은 곳
      • unstaged 파일들을 드래그해서 옮긴다.
    • commit subject : 커밋할 바뀐 내용을 적는 텍스트창
  • 커밋 : 로컬 저장소에서 현재 시점의 변경된 파일을 저장하는 행위다.
    • 언제든 커밋 한 시점으로 돌아갈 수 있다.
    • 원하는 파일만 묶어서 커밋할 수 있다.
  • 푸쉬 : 커밋한 로컬 저장 파일을 원격 저장소(github)에 업로드한다.
  • 풀 : 업데이트된 파일들이 있는 원격 저장소(github)에서 로컬 저장소(내 컴퓨터, 현재 작업 폴더)로 불러온다.
  • 원격 저장소 : 아무 곳에서나 사용할 수 있는 저장소
  • 로컬 저장소 : 현재 내가 사용하는 저장소(컴퓨터)
  • 브런치 히스토리 깃 아이콘 : 원격 저장소(깃허브) 위치
  • 브런치 메인(마스터) 아이콘 : 로컬 저장소(현재 작업 컴퓨터) 위치
    • 차이가 난다면 현재 작업과 저장된 작업의 진행이 다른것이다.

  • 레포지토리 생성
  • gitignore : visualstudio
  • https 주소 복사
  • 포크 - file - clone - 생성

gitignore

  • 버전관리에 무시, 빼야하는 파일 및 폴더를 모아놓은 파일이다.
    • : 모든 내용 포함 명령
  • / : 해당 폴더 선택

SFML

  • 비주얼 스튜디오 sfml 연결 튜토리얼 : https://www.sfml-dev.org/tutorials/2.6/start-vc.php
  • 라이브러리 : 누군가 갖다 사용해놓을 수 있게 특정 기능들을 공개해놓은 기능이다.
  • sfml : 사운드, 그래픽 등 공개 라이브러리다.
    • 교육용으로 사용되고있다.
    • win 대용으로 사용할 수 있다.
  • 객체지향 설계로 게임을 개발할 수 있게 도와준다.
  1. 프로젝트 생성
  2. .git 있는 폴더에 sln 포함 파일들을 복붙한다.
  • 라이브러리 가져오는 법
    • 라이브러리가 있는 경로 설정
    • 라이브러리 관리자 툴, 어플리케이션 사용

사용법

  • 절대경로 : 드라이브부터 폴더 경로가 처음부터 끝까지 고정되어있는 경로다.
    • 왠만해서는 이거 말고 상대경로로 써야한다.
  • 상대경로 : 현재 경로를 기준으로 폴더 경로를 표시하는 것이다.
    • 절대경로에 포함되어있는 현재 경로까지는 안적어도 되고, 그 다음 경로부터 작성되는 경로다.
    • . : 현재 형로를 의미한다.
      • 현재 폴더안의 파일을 사용할때 사용한다.
    • .. : 현재 경로 기준으로 부모 폴더를 의미한다.
      • 부모 폴더 안의 파일을 사용할때 사용한다.
    • ..\.. : 부모폴더의 부모 폴더를 의미한다.
  1. 튜토리얼 main 코드 복붙
  2. 다운받은 sfml 압축 풀기
  3. 깃 프로젝트 폴더에 복사
    1. 4번에서 생성된 bin 폴더에 SFML 폴더의 bin 폴더 파일들 전부 복붙
  4. 프로젝트 우클릭 - 디버그 설정 수정 - 릴리즈 설정도 똑같이 설정한다.
    1. C/C++
      1. 일반
        1. 추가/포함 디렉토리
          1. SFML-2.6.2\include
    2. 링커
      1. 일반
        1. 추가 라이브러리 디렉터리
          1. SFML-2.6.2\lib
      2. 입력
        1. 추가 종속성
          1. sfml-graphics-d.lib;sfml-window-d.lib;sfml-system-d.lib;sfml-network-d.lib;sfml-audio-d.lib
          2. 릴리즈 만 설정 : sfml-graphics.lib;sfml-window.lib;sfml-system.lib;sfml-network.lib;sfml-audio.lib
    3. 구성 속성
    4. 구성 속성
      1. 일반
        1. 출력 디렉터리
          1. ..\$(MSBuildProjectName)-bin\
        2. 대상 이름 - 디버깅만
          1. 뒤에 -d 붙이기
      2. 디버깅
        1. 작업 디렉터리
          1. ..\$(MSBuildProjectName)-bin\

bin 폴더 레포지토리 만들기

  • 포크 - file - init new repogitory - 폴더 선택 - 15개 파일 Stage로 옮기고 commit
  • 깃허브 - 레포지토리 생성 - 이름 : sfml_timber_bin 작성 후 아무 설정 안건들고 생성 - 깃허브 주소 복사
  • 포크 - 좌측 Remotes 우클릭 - Add new repogitory 클릭 - 복사한 주소 복붙되있으면 생성 - push

SFML 시작

  • 생성자 : 클래스로 만들어지는 변수들이 프로그램 시작 시 초기화하는 함수다.
  • 멤버 변수 및 함수 : 클래스 안에 선언되어있는 함수 및 변수다.
    • . : 멤버 접근 연산자 ⇒ 클래스에 선언 및 정의된 변수 및 함수에 접근하는 연산자다.
  • 이벤트 : 입력이 들어오는 것에 반응하는 클래스다.