1. 함수 오버로딩
1) 함수 오버로딩?
C++에서는 동일한 이름의 함수를 여러 개 정의할 수 있는데, 함수 이름과 매개변수 타입 정보를 함께 사용해 구분하기 때문이다. 이렇게 함수 이름 구분을 위해 내부적으로 고유한 이름을 부여하는 것을 네임 맹글링(Name Mangling) 이라고 한다.
함수 오버로딩을 적용하려면 다시말해 각 함수가 명확히 구분되어야 한다. 매개변수 타입을 다르게 하거나, 매개변수 개수를 다르게 하거나 하는 식이다.
2) 함수 오버로딩 호출
호출되는 함수가 분명해야 함수 오버로딩이 된다. 분명하지 않으면 오류가 발생한다.
대표적으로 분명하지 않은 경우는 다음과 같다.
-1. 타입 변환이 가능한 매개변수로 인해 두 개 이상의 오버로딩된 함수가 호출 후보가 되는 경우
-2. 디폴트 매개변수로 인해 함수 호출 형태가 중복되는 경우
-3. 매개변수의 타입만 포인터와 배열로 다른 경우(포인터와 배열은 같은 타입으로 취급됨)
-4. 함수의 반환 타입만 다른 경우(반환 타입만으로는 함수를 구별할 수 없음)
3) 함수 오버로딩의 순서
우선순위. 이것만 기억하자.
정확한 매칭 -> 타입 승격 반환 -> 표준 타입 변환 -> 사용자 정의 타입 변환
타입 승격 반환?: 값이 손실되지 않는 방향으로 변환하는 것을 승격이라고 한다.
표준 타입 변환?: 승격보다는 광범위하게 변환한다. 값이 손실될 수도 있다. 예를 들어 int -> double, double -> int 식이다.
사용자 정의 타입 변환?: 클래스 타입의 변환 함수나 생성자 등을 통해 이뤄지는 변환이다.
그러니까 동일한 이름의 함수가 여러개 있으니까, 호출할 때 뭐를 먼저 호출할지 정하는 우선순위라고 생각하자. 정확하게 호출한 것부터가 우선이다.
2. 템플릿
1) 템플릿?
타입에 관계없이 일반화된 코드를 작성하기 위한 문법이다. 예를 들어 template <typename T> 의 형태로 정의한다. 어떤 타입이 올지는 모르겠으나 그 타입을 T 라고 부르겠다는 의미이다. 일반화하려는 타입 자리에 실제 타입 대신 T를 사용할 수 있다.
2) 사용 방법
두 값 중에서 큰 값을 반환하는 함수를 템플릿으로 구현하는 연습을 해보았다.
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << getMax(10, 20) << endl; // 정수 비교
cout << getMax(3.5, 2.7) << endl; // 실수 비교
cout << getMax(string("apple"), string("banana")) << endl; // 문자열 비교
return 0;
}
// 출력결과:
// 20
// 3.5
// banana
이처럼 굳이 타입을 신경쓰지 않고도 정수를 비교할 수 있고, 실수를 비교할 수 있고, 문자열도 비교할 수 있다. C++가 똑똑하니까 알아서 해석하게 놔둘 수 있게 된다는 뜻이다. main() 앞에서 타입 T를 선언했고 T getMax(T a, T b) {} 형식으로 사용한다.
그런데, 문자열 비교할 때는 string("") 꼴을 반드시 써줘야 한다. 그냥 큰따옴표로 처리해버리면 두 문자열이 저장된 메모리주소가 같은지를 비교하게 되어 다른 결과가 나오게 된다. ...그정도로 똑똑하지는 않은 것 같기도 하다. 어쨌든 명령을 내릴 때는 잘 내려줘야 하겠다.
편해보이는데, 왜 막 쓰면 안될까?
-1. 실제로 만들어지는 실행파일의 용량과 메모리 사용량이 커질 수 있다.
-2. 원칙적으로 헤더 파일 안에 구현부까지 전부 작성해야 되어 코드가 지저분해지고 cpp로 분리가 불가능해서 내부 로직이 그대로 노출된다.
-3. 내가 직접 만든 타입은 인식이 불가능하다. 즉 템플릿 함수에 내부에서 사용하는 기능을 해당 타입이 지원해야 한다(이 문제는 연산자 오버로딩으로 해결할 수 있다)
코드가 다양한 타입에서 똑같은 논리로 재사용될 코드인지 확신이 생기면 쓰는 게 좋을 것 같다.
3) 템플릿 클래스
함수 뿐만 아니라, 클래스도 템플릿으로 일반화할 수 있다.
예를 들어, 템플릿 클래스 Array를 만들어보았다.
// 목적: 클래스 템플릿으로 배열을 일반화하여 원소 추가 및 삭제 기능 구현하기
#include <iostream>
using namespace std;
template <typename T>
class Array {
T data[100];
int size;
public:
Array() : size(0) {}
void add(const T& element) {
if(size < 100)
data[size++] = element;
}
void remove() {
if(size > 0)
size--;
}
void print() {
for(int i = 0; i < size; i++)
cout << data[i] << " ";
cout << endl;
}
};
int main() {
Array<int> arr; // 정수형 배열 생성
arr.add(10);
arr.add(20);
arr.add(30);
arr.print();
arr.remove();
arr.print();
return 0;
}
// 출력결과:
// 10 20 30
// 10 20
-->
T 타입의 100칸 배열을 만들었고, 100칸 중에 데이터가 몇 개가 들어있는지 기억할 수 있는 int size;
Array()는 처음에 비어있으므로 데이터 개수를 의미하는 size()를 0으로 초기화하고 시작한다.
add() 함수는 새로운 데이터를 맨 끝에 넣는 함수다.
data[size] 자리에 element를 넣는다. 이 때 element는 const T&. 원본을 복사하지 않고 원본을 그냥 참조만하겠다는 뜻이다. 그러면 처음엔 size가 0이니까 data[0]에 element가 들어가게 된다.
remove()의 이용. 진짜로 메모리에서 데이터를 지우는게 아니라 뒤의 조건문에서 볼 수 있듯 size--; 를 활용해서 size 숫자를 하나씩 줄인다. '논리적 삭제'라고 부르는데 이렇게 되면 마지막의 데이터를 지우는게 아니라 그냥 빼놓고 보여주게 되어 속도가 빨라지게 된다.
메인 함수에서 실제 사용할 때.
클래스 템플릿을 써서 객체를 만들 때에는 Array<int> arr; 처럼 <>(꺾쇠) 안에 타입을 명시해 주는 것이 일반적. 이렇게 하면 이 클래스 전체를 int로 출력하게 된다.
아래부터는 arr.add()를 이용해서 생성된 배열에 10, 20, 30 을 추가하고, 출력하고, 맨 마지막을 지우고(정확히는 size만 줄이고), 다시 출력한다.
오늘부터는 매일 코드카타 진행하는 것도 TIL에 작성하기로 했다.
<오늘의 코드카타 : 짝수와 홀수, 각도기, 배열의 평균값>
1. 홀짝 구분하기.
num이 정수일 때, 짝수면 "Even", 홀수면 "Odd"
처음에 malloc이라는 동적할당이 뭔지를 몰라서 고생했다.
memory allocation의 약자로 동적 메모리 할당을 수행하는 표준 라이브러리 함수였다.
쓰기 위해서는 #include <stdlib>가 필요하다.
배웠던 개념을 사용해보자면 힙 메모리에서 필요한 만큼 공간을 만드라고 하는 것과 같다.
바이트(byte) 크기만큼의 연속된 메모리 공간을 할당하고 void* 포인터를 반환한다. 따라서 사용할 때에는 형변환이 필요하다.
그래서 아래같은 경우 char* answer = (char*)malloc(5-num%2); 즉, (char*) 로 형변환을 해준 것이다.
malloc()으로 빌린 메모리는 다 쓴 후에 반드시 free() 함수를 사용해서 명시적으로 반환해야 한다. 그렇지 않으면 메모리 누수가 발생한다.
삼항연산자
char* solution(int num) {
return num%2==0 ? "Even":"Odd";
}
동적할당해야하는 경우(malloc)
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
char* solution(int num) {
// 리턴할 값은 메모리를 동적 할당해주세요
char* answer = (char*)malloc(5-num%2);
if(num%2==0) {
answer="Even";
}
else {
answer="Odd";
}
return answer;
}
(char*)malloc(5-num%2);
글자가 들어갈 자리를 만들어줄 때(동적할당), 짝수면 Even + null 값까지 해서 5자리를 만들고, 홀수면 Odd null 값까지 해서 4자리를 만들라는 뜻이다. 이렇게 하면 메모리 누수를 최소화할 수 있다.
그러나 사실 여기에는 문제점이 있다. 문자열이든 숫자든 포인터 변수 자체에 값을 직접 대입 (=) 하면 메모리 누수가 발생하게 된다. malloc()으로 빌린 메모리 주소를 다른 값으로 덮어씌워서 잃어버리는 것이다. 이렇게 되면 나중에 free() 함수를 사용해서 반납할 수도 없게 된다.
그래서 malloc()으로 빌린 공간에 숫자나 문자를 넣고 싶으면 answer에 직접 값을 대입하는 것이 아니라 * 역참조 연산자나 [] 배열 인덱스, 또는 strcpy를 사용해서 직접 주소에 찾아가서 값을 넣어야 한다.
1) 역참조 연산자 : *answer = 5;
2) 배열 인덱스 : answer[0] =5;
3) strcpy
//...
#include <string.h> // strcpy를 사용하기 위해 추가해야함
char* solution(int num) {
char* answer = (char*)malloc(5 - num % 2);
if(num % 2 == 0) {
strcpy(answer, "Even");
}
else {
strcpy(answer, "Odd");
}
return answer;
}
추가하자면 num이 음수일 때 num % 2 = -1 의 결과가 나올수도 있으므로 위에서 malloc(5- abs(num) % 2); 처럼 절대값으로 처리하는 것이 좋다.
참고로 strcpy는 문자열을 한번에 통째로 넣을 때 사용하고, *, []는 개별 데이터(글자 하나, 숫자 하나)를 다룰 때 사용한다.
실무적으로는 strcpy (#include <string.h>) 가 더 유용할 것 같다. 다만 strcpy는 오직 문자열 전용이기 때문에 숫자를 담고 싶으면 반드시 []를 사용하는게 좋다.
2. 각도기
각에서 0도 초과 90도 미만은 예각 = 1, 직각은 2, 90도 초과 180도 미만은 둔각 = 3, 180도는 평각 = 4를 return하도록 solution 함수 작성하기.
나는 0 < angle < 180 이라는 조건이 있길래 이렇게 작성했다.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int solution(int angle) {
int answer = 0;
if (0 < angle && angle < 90) {
answer = 1;
}
else if (angle == 90) {
answer = 2;
}
else if (90 < angle && angle < 180) {
answer = 3;
}
else {
answer = 4;
}
return answer;
}
삼항연산자를 활용해서 이렇게 작성할 수도 있었다.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
int solution(int ang) {
int ans = 1;
return ang<90?1:ang==90?2:ang<180?3:4;
}
3. 배열의 평균값.
정수 배열 numbers가 매개변수로 주어지고, numbers의 원소 평균값을 return하도록 solution함수 완성하기.
0 <= numbers의 원소 <= 1000
1 <= numbers의 길이 <= 100
정답의 소수 부분이 .0 또는 .5인 경우만 입력으로 주어진다.
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
// numbers_len은 배열 numbers의 길이입니다.
double solution(int numbers[], size_t numbers_len) {
double answer = 0;
int sum = 0;
for(size_t i = 0; i < numbers_len; i++) {
sum += numbers[i];
}
answer = (double)sum / numbers_len;
return answer;
}
여기서 보완할 점.
1) 배열의 길이가 0이면 0으로 나누는 에러가 발생한다.
--> if (numbers_len == 0) { return 0; } 이라는 조건을 추가한다.
2) 만약 제약이 없이 배열의 평균값을 구한다면
std::vector를 사용한다.
#include <vector>
#include <numeric>
double solution(const std::vector<int>& numbers) {
if (numbers.empty()) return 0.0;
double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);
return sum / numbers.size();
}
#include <numeric> 은 std::accumulate를 사용하기 위한 C++헤더. C++에서 배열이나 벡터의 합을 구할 때 사용하는 표준 함수이다. 이거를 쓰게되면, 0.0 처럼 accumulate의 세 번째 인자인 초기값을 0.0으로 주게 할 수 있고 합산을 double 자료형으로 자동으로 진행한다. 즉 강제 형변환을 하지 않아도 된다.
std::vector를 쓰면 배열의 크기를 numbers.size()를 사용해서 따로 넘겨줄 필요도 없고 메모리 관리도 수월해진다.
그리고 const & 은 습관처럼 사용해보는 연습을 하자.
'게임 프로그래밍 공부 > 게임 개발을 위한 C++' 카테고리의 다른 글
| [C++학습] 8. 객체지향적 설계 (0) | 2026.05.08 |
|---|---|
| [C++학습] 7. STL (0) | 2026.05.07 |
| [C++학습] 5. 자원 관리 (0) | 2026.05.05 |
| [C++학습] 4. 객체지향 프로그래밍, 객체지향적 설계 (0) | 2026.05.04 |
| [C++학습] 3. Class 개념, 객체지향 프로그래밍 (0) | 2026.05.02 |
