캠프 4일차
캠프 4일차
Blueprint 라이브세션 4회차
Player HUD
User Interface - Widget Blueprint
Canvas Panel
4K 해상도로 만들어야한다. 큰 걸 기준으로 만드는게 가장 안정적이다.
Progress Bar
Anchors를 지정하지 않으면 의도하지 않게 배치될 수 있다. 항상 무언가를 배치할때 Anchors 먼저 지정해줘야한다.
Text
Size To Content - 글자의 사이즈에 맞게 칸을 조절해준다.총알: 과 is Variable이 체크된 총알개수를 넣어준다.
BP_Character 에서 BeginPlay에 연결된 Add Mapping Context의 실행핀을 Create Widget 노드와 연결해준다. Create Widget의 실행핀은 Add to Viewport에 연결하고, WB_HUD 클래스를 지정해준다.
크로스헤어
Image를 Canvas 가운데에 넣어준다.
Alignment 는 0.5 / 0.5로 설정해주면 정가운데로 위치해진다.
Detail → Appearance → Image 선택
크로스헤어 Anchor가 안보이면 콘텐츠 브라우저의 세팅에서 Show Engine Content를 체크해준다.
Fire 함수
총알 수를 30발로 보여주고, 총알이 떨어지면 더 이상 쏠 수 없게 만들어줘야한다.
적 AI 구현
자습중
C언어 라이브세션 4회차
함수
특정 작업을 수행하는 코드 블록을 의미한다.
정의해두면 필요할 때마다 호출해서 재사용할 수 있다.
함수 사용 이유
- 재사용성
- 같은 로직을 반복 작성할 필요가 없다.
- 가독성
- 코드를 의미 단위로 쪼개서 읽기 쉬워진다.
- 유지보수
- 수정이 필요하면 함수 하나만 고치면 된다.
- 추상화
- 내부 구현을 몰라도
이름만 보고 쓸 수 있다.
- 내부 구현을 몰라도
printf(), scanf(), strlen() 모두 함수이다.
선언
#include <stdio.h>
int add(int a, int b); // 선언
int main(void) {
printf("%d\n", add(1, 2)); // 위에서 선언을 해서 OK
// printf도 위의 stdio.h에 들어있다. 정의는 이미 컴파일된 라이브러리 안에 있다.
return 0;
};
int add(int a, int b) { // add 함수 정의
return a + b;
};매개변수와 인자
- 매개변수(parameter): 함수를 정의할 때 선언하는 변수 (형식 매개변수)
- 인자(argument): 함수를 호출할 때 실제로 넘기는 값 (실인자)
int square(int n) { // n은 매개변수
return n * n;
}
int main(void) {
int result = square(7); // 7은 인자
return 0;
}매개변수가 없다면 void를 명시적으로 두어야한다. 인자 개수를 체크하지 않겠다 라는 의미로 "인자 없음"의 정확한 표현이다.
반환값
return은 두 가지 역할을 한다.
값을 돌려주는 것, 그리고 함수 실행을 즉시 종료하는 것
int abs_value(int n) {
if (n < 0) {
return -n; // 종료
}
return n; // n >= 0 이면 대기
}반환할 것이 없으면 void를 쓴다.
반환 타입이 void가 아닌 함수에서 return을 안쓰면 미정의 동작(undefined behavior)이다.
쓰레기 값이 반환되거나 컴파일러가 경고를 보낸다.
변수의 범위(Scope)와 수명(Lifetime)
#include <stdio.h>
int global = 100; // 전역 변수
void demo(void) {
int local = 10; // 지역 변수
static int count = 0; // 정적 변수
count++;
printf("count = %d\n", count);
}
int main(void) {
demo(); // count = 1
demo(); // count = 2
demo(); // count = 3
// printf("%d", local); // local변수는 demo 안에서만 존재하기 때문에 에러가 발생한다.
return 0;
}전역 변수
- 어디서든 접근 가능, 프로그램 종료까지 유지
지역 변수
- 해당 함수(선언된 블록) 안에서만 존재
정적 변수 (static)
- 함수 안에서만 보이지만 바뀌고 함수가 종료되어도 그 값은 초기화 되지 않고 유지됨
재귀 함수 (Recursion)
함수가 자기 자신을 호출하는 것이며 반드시 종료 조건(base case)이 있어야 한다.
int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
// factorial(4)
// → 4 * factorial(3)
// → 4 * 3 * factorial(2)
// → 4 * 3 * 2 * factorial(1)
// → 4 * 3 * 2 * 1
// → 24재귀의 장단점
- 장점: 수학적 정의를 그대로 코드로 옮길 수 있어 직관적이다.
- 단점: 호출마다 스택 프레임이 쌓여 메모리 사용이 증가하고, 깊이가 깊으면
스택 오버플로우발생 가능
실무에서는 성능이 중요한 경우 재귀를 반복문으로 바꾸는 것이 일반적이다.
포인터
포인터는 메모리 주소를 저장하는 변수다.
일반 변수가 "값"을 담는다면, 포인터는 "그 값이 어디에 있는지"를 담는다.
사용 이유
- 함수에서 원본 수정 — 값 복사가 아니라 주소를 넘겨서 원본을 직접 바꿀 수 있다
- 효율적인 데이터 전달 — 큰 구조체를 통째로 복사하지 않고 주소만 전달한다
- 동적 메모리 관리 —
malloc으로 런타임에 메모리를 할당하고 포인터로 접근한다 - 배열, 문자열 처리 — C에서 배열과 문자열은 본질적으로 포인터와 연결되어 있다
주소와 포인터의 기본
모든 변수는 메모리 어딘가에 저장된다. & 연산자로 그 주소를 꺼낼 수 있다.
#include <stdio.h>
int main(void) {
int x = 42;
printf("x의 값: %d\n", x); // 42
printf("x의 주소: %p\n", &x); // 0x7ffd5e8a3b2c (실행마다 다름)
return 0;
}
int x = 42;
int *p = &x; // p는 x의 주소를 저장하는 포인터| 표현 | 의미 | 값 |
|---|---|---|
x | 변수 그 자체 | 42 |
&x | x의 메모리 주소 | 0x7ffd... |
p | 포인터 변수 (주소를 담고 있음) | 0x7ffd... (= &x) |
*p | p가 가리키는 곳의 값 (역참조) | 42 (= x) |
선언과 초기화
*의 위치는 스타일 차이일 뿐이다. int p / int p int * p 모두 동일하다.
int *a, b; → a는 int 포인터, b는 그냥 int다.
int *a, *b; → 둘다 모두 포인터
초기화 되지 않은 포인터는 쓰레기 주소를 가진다. 역참조하면 프로그램이 죽는다.
int *p; → 초기화가 되지 않아 위험하다.
int *p = NULL; → 아직 가리킬 곳이 없으면 NULL로 초기화 시켜주어야 한다.
주소 연산자
& (주소 연산자, address-of)
- 변수의 주소를 꺼낸다.
int a = 10;
int *p = &a; // 주소를 담는 p에 a의 주소를 저장
printf("%d\n", *p); // 역참조가 되어 원래의 값이 나온다.
*p = 25; // p가 가리키는 곳에 25를 저장한다.* 와 &는 서로 반대 동작이다. *(&a)는 a이고, &(*p)는 p다.
문제풀이
문제 1. 개념 확인
다음 빈칸을 채우시오.
(a) 포인터는 ____을(를) 저장하는 변수이다.
→ 메모리 주소
(b) & 연산자는 변수의 ____을(를) 꺼내는 연산자이다.
→ 주소값
(c) * 연산자는 포인터가 가리키는 곳의 ____에 접근하는 연산자이다.
→ 실제값
(d) 아직 가리킬 곳이 없는 포인터는 ____로 초기화하는 것이 안전하다.
→ NULL
문제 2. 선언 구분
다음 선언에서 각 변수의 타입을 쓰시오.
int *a, b; // → 정수형 포인터 a, 정수형 변수 b
int *c, *d; // → 정수형 포인터 c, d
double *e; // → 실수형 포인터 e| 변수 | 타입 | 정답 |
|---|---|---|
| a | ? | 정수 * |
| b | ? | 정수 |
| c | ? | 정수 * |
| d | ? | 정수 * |
| e | ? | 실수 * |
문제 3. 코드 추적
다음 코드의 출력 결과를 쓰시오.
#include <stdio.h>
int main(void) {
int x = 5;
int *p = &x;
printf("%d\n", *p); // → 5
*p = 20;
printf("%d\n", x); // → 20
x = 100;
printf("%d\n", *p); // → 100
return 0;
}문제 4. 코드 추적 심화
다음 코드의 출력 결과를 쓰시오.
#include <stdio.h>
int main(void) {
int a = 10;
int b = 20;
int *p = &a;
int *q = &b;
printf("%d %d\n", *p, *q); // → 10, 20
*p = *q;
printf("%d %d\n", a, b); // → *p는 a의 실제값인데 *q의 값으로 변경하면 20, 20
p = q;
*p = 99;
printf("%d %d\n", a, b); // → p가 참조하는 메모리 주소 자체를 변경하였으니 여기서 더 이상 a를 가리키는 곳은 없어짐 따라서 b가 바뀐다. 20, 99
return 0;
}문제 5. 오류 찾기
다음 코드에는 문제가 있다. 무엇이 위험한지 설명하시오.
int main(void) {
int *p; // → 포인터는 항상 NULL로 초기화를 해줘야한다. 안그러면 프로그램이 터질 수 있다.
*p = 42; // → 포인터가 초기화가 되지 않은 상태에서는 어떤 메모리 주소를 가리키는지 알 수가 없다. 그런데 42라는 값을 넣겠다고 하니 정의되지 않은 동작이 발생하게 된다.
return 0;
}문제 6. 코드 완성
변수 x의 값을 포인터를 통해 두 배로 만드는 코드의 빈칸을 채우시오.
#include <stdio.h>
int main(void) {
int x = 15;
int *p = ________; // → &x
________ = ________ * 2; // → x, x or *p, *p or *p, x or x, *p 모두 가능
printf("%d\n", x); // 30이 출력되어야 함
return 0;
}문제 7. O/X 판별
각 문장이 맞으면 O, 틀리면 X를 쓰시오.
(a) int *p = &x; 에서 *p와 x는 같은 메모리를 가리킨다. ( ) → O
(b) 포인터 변수의 크기는 가리키는 타입의 크기와 같다. ( ) → X, 포인터 크기는 32비트인지 64비트인지. 시스템에 따라 결정된다. 32면 4바이트 64면 8바이트
(c) &와 *는 서로 반대 동작이다. ( ) → O
(d) int *a, *b; 는 a와 b 모두 int 포인터로 선언한다. ( ) → O
(e) 초기화하지 않은 포인터를 역참조해도 0이 나온다. ( ) → X, 쓰레기값을 가진다. 역참조하면 정의되지 않은 동작 발생
