함수 오버로딩
C++에서는 동일한 이름의 함수를 여러 개 정의할 수 있다.
C언어는 함수 이름으로만 함수를 구분하지만(함수의 이름이 같으면 똑같은 함수), C++은 함수 이름과 매개변수 타입 정보를 함께 사용해 구분하기 때문이다.(반환 타입, 이름, 인자가 모두 같아야 똑같은 함수)
똑같은 이름의 함수를 구분하기 위해 내부적으로 고유한 이름을 부여하는 것을 네임 맹글링(Name Mangling) 이라고 한다.
단, 함수의 반환형 만으로는 오버로딩이 성립하지 않는다.
함수 오버로딩을 적용하려면, 이름이 같아도 각 함수가 명확히 구분되어야 한다.
함수 오버로딩이 유효해지는 조건을 몇 가지 보면 다음과 같다.
1. 매개변수 타입이 다른 경우
// 목적: 함수 오버로딩의 개념을 매개변수 타입이 다른 경우로 이해하기
#include <iostream>
using namespace std;
void print(int a)
{
cout << "정수 출력: " << a << endl;
}
void print(double a)
{
cout << "실수 출력: " << a << endl;
}
int main()
{
print(10); // 정수 출력
print(3.14); // 실수 출력
return 0;
}
// 출력결과:
// 정수 출력: 10
// 실수 출력: 3.14
같은 이름의 print함수에 인자 값을 10과 3.14를 넣었을 때 인자의 타입을 기준으로 어떤 함수가 실행될지 결정이 된다.
2. 매개변수의 개수가 다른 경우
// 목적: 매개변수의 개수가 달라서 함수 오버로딩이 유효한 경우를 확인하기
#include <iostream>
using namespace std;
int add(int a, int b) // 매개변수 2개
{
return a + b;
}
int add(int a, int b, int c) // 매개변수 3개
{
return a + b + c;
}
int main()
{
cout << add(1, 2) << endl; // 두 개의 인자 사용
cout << add(1, 2, 3) << endl; // 세 개의 인자 사용
return 0;
}
// 출력결과:
// 3
// 6
같은 이름의 add함수에 인자 개수를 다르게 넣었을 때 함수의 매개변수 개수 기준으로 어떤 함수가 실행될지 결정이 된다.
함수 오버로딩이 되지 않는 경우
함수 오버로딩이 되려면, 호출되는 함수가 분명해야 한다.
만약 호출 시점에 호출할 함수가 명확하지 않으면 컴파일러는 애매모호성 오류를 발생시킨다.
대표적인 애매모호한 경우는 아래의 경우가 있다.
1. 타입 변환이 가능한 매개변수로 인해 두 개 이상의 오버로딩된 함수가 호출 후보가 되는 경우
// 목적: 타입 변환 가능한 매개변수로 인해 애매모호성이 발생하는 예시
#include <iostream>
using namespace std;
void print(double a)
{
cout << "double: " << a << endl;
}
void print(long a)
{
cout << "long: " << a << endl;
}
int main()
{
// print(10); // 10(int)은 long과 double 모두로 변환 가능하므로 애매모호
return 0;
}

print의 인자 값이 두 print 함수의 매개변수로 둘 다 받을 수 있어서 어떤 함수를 호출할지 모르겠다는 오류이다.
2. 디폴트 매개변수로 인해 함수 호출 형태가 중복되는 경우
// 목적: 디폴트 매개변수로 인해 호출 형태가 중복되는 경우
#include <iostream>
using namespace std;
void display(int a, int b = 5) // b에 디폴트 매개변수를 넣어줘서 인자 값을 1개만 받아도됨
{
cout << a << ", " << b << endl;
}
void display(int a)
{
cout << a << endl;
}
int main()
{
// display(10); // 디폴트 매개변수로 인해 두 함수 모두 호출 가능하므로 애매모호
return 0;
}

3. 매개변수의 타입만 포인터와 배열로 다른 경우
// 목적: 매개변수의 타입이 포인터와 배열일 때 애매모호성 발생 예시
#include <iostream>
using namespace std;
void print(int* arr) // 매개변수 타입이 포인터
{
cout << "포인터 호출됨" << endl;
}
void print(int arr[]) // 매개변수 타입이 배열
{
cout << "배열 호출됨" << endl;
}
int main()
{
int data[3] = {1, 2, 3};
// print(data); // 포인터와 배열은 같은 타입으로 취급되어 애매모호
return 0;
}
배열의 이름에는 배열의 첫 원소 주소값을 가지고 있어서 배열 이름을 인자로 넣을 시 포인터에서도 받고 배열에서도 받을 수 있다.

4. 함수의 반환 타입만 다른 경우
// 목적: 반환 타입만 다른 함수 오버로딩으로 애매모호성 발생
#include <iostream>
using namespace std;
// int getValue() {
// return 10;
// }
// double getValue() {
// return 3.14;
// }
int main() {
// cout << getValue(); // 반환 타입만으로는 함수를 구별할 수 없으므로 애매모호
return 0;
}
함수의 반환 타입이 다를 경우 메인함수에서 getValue() 를 호출했을 때 두 함수를 구별할 수 없어서 에러메세지가 뜬다.
함수 오버로딩의 순서
컴파일러는 최대한 변환할 수 있는 함수를 찾으려고 노력합니다.
C++에서는 아래와 같은 명확한 우선순위 규칙에 따라 호출할 함수를 결정합니다.
아래와 같은 우선순위를 기억하시면 됩니다.

1. 정확한 매개변수 타입 일치 : 호출 인자 타입과 매개변수 타입이 정확히 일치하는 경우

// 목적: 정확한 매개변수 타입이 우선적으로 선택됨을 학습
#include <iostream>
using namespace std;
void print(int a)
{
cout << "정확한 타입(int) 호출됨" << endl;
}
void print(double a)
{
cout << "double 타입 호출됨" << endl;
}
int main()
{
print(10); // 정확한 int 타입 일치
return 0;
}

2. 타입 승격 변환 : 값이 손실되지 않는 방향으로 변환하는 것을 승격이라고 한다.
char or short -> int
float -> double
bool -> int

// 목적: 정확한 타입이 없으면 타입 승격 변환이 우선적으로 선택됨을 학습
#include <iostream>
using namespace std;
void print(int a)
{
cout << "int 타입 호출됨" << endl;
}
void print(char a)
{
cout << "char 타입 호출됨" << endl;
}
int main()
{
short s = 10;
print(s); // short에서 int로 승격 변환
return 0;
}

3. 표준 타입 변환 : 승격보다는 조금 더 광범위하다. 값 손실이 발생하는 경우도 있다.
int -> double
double -> int
double -> float

// 목적: double 값을 오버로딩된 int 또는 string 함수에 전달할 때, 변환 우선순위 확인
#include <iostream>
#include <string>
using namespace std;
void print(int a)
{
cout << "int 버전 호출됨: " << a << endl;
}
void print(string a)
{
cout << "string 버전 호출됨: " << a << endl;
}
int main()
{
double num = 10.99;
print(num); // double → int 변환이 발생하여 int 버전 호출
return 0;
}

4. 사용자 정의 타입 변환
클래스의 생성자 타입 변환 함수 등을 통해 이뤄지는 변환. 심화 개념으로 예시만 대략 보고 넘어간다.
// 목적: 표준 변환이 불가능하면 사용자 정의 변환이 호출됨을 학습
#include <iostream>
using namespace std;
class MyNumber
{
public:
operator int() const
{
return 42;
}
};
void print(int a)
{
cout << "int 타입 호출됨, 값: " << a << endl;
}
void print(double a)
{
cout << "double 타입 호출됨, 값: " << a << endl;
}
int main()
{
MyNumber num;
print(num); // 사용자 정의 변환을 통해 MyNumber→int로 변환됨
return 0;
}
// 출력결과:
// int 타입 호출됨, 값: 42
템플릿
타입에 관계없이 일반화된 코드를 작성하기 위한 문법이다.
템플릿을 이용한 일반화된 함수는 template <typename T>와 같은 형태로 정의한다.
이 의미는 어떤 타입이 올지 모르겠으나, 그 타입을 T 라고 부르겠다는 의미이다.
이후에는 일반화 하려는 타입 자리에 실제 타입대신 T를 사용하면 된다.
두 수를 더하는 add(a, b) 함수에 add타입, a타입, b타입이 같을 때 모두 T로 일반화 해서 작성할 수 있다.

// 목적: 함수 템플릿을 이용해 두 값을 더하는 일반화된 함수 작성하기
#include <iostream>
using namespace std;
template <typename T>
T add(T a, T b) // 타입에 T를 뒀음. 값은 같은 타입이 들어와야됨.
{
return a + b;
}
int main() {
cout << add(3, 5) << endl; // 정수 더하기
cout << add(2.5, 4.1) << endl; // 실수 더하기
return 0;
}
// 출력결과:
// 8
// 6.6
// 목적: 함수 템플릿을 이용하여 일반화된 최대값 찾기
#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
// 목적: 함수 템플릿으로 배열 원소 출력 일반화하기
#include <iostream>
using namespace std;
template <typename T>
void printArray(T arr[], int size) // 일반화 할 배열 타입만 T로 작성하고 size는 정수가 확실하기 때문에 int타입을 씀.
{
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
int main()
{
int intArr[3] = {1, 2, 3};
char charArr[4] = {'A', 'B', 'C', 'D'};
printArray(intArr, 3); // 정수 배열 출력
printArray(charArr, 4); // 문자 배열 출력
return 0;
}
// 출력결과:
// 1 2 3
// A B C D
//값을 서로 교환 하는 함수를 템플릿으로 구현
#include <iostream>
using namespace std;
template <typename T>
void swapValues(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int x = 10, y = 20;
swapValues(x, y);
cout << "Swapped int values: x = " << x << ", y = " << y << endl;
double p = 3.14, q = 2.71;
swapValues(p, q);
cout << "Swapped double values: p = " << p << ", q = " << q << endl;
return 0;
}
// 출력 값:
// Swapped int values: x = 20, y = 10
// Swapped double values: p = 2.71, q = 3.14
// 두 값이 같은지 확인하는 함수를 템플릿으로 구현
#include <iostream>
using namespace std;
template <typename T>
bool isEqual(T a, T b)
{
return a == b;
}
int main()
{
cout << boolalpha; // true/false 출력 활성화
cout << "Are 10 and 10 equal? " << isEqual(10, 10) << endl; // 정수 비교
cout << "Are 3.5 and 2.7 equal? " << isEqual(3.5, 2.7) << endl; // 실수 비교
cout << "Are 'a' and 'b' equal? " << isEqual('a', 'b') << endl; // 문자 비교
return 0;
}
템플릿 클래스
함수뿐만 아니라, 클래스도 템플릿으로 사용해 일반화할 수 있다.
예를 들어, 배열에 원소를 추가하고 삭제하는 기능을 가진 클래스를 정의할 때도 템플릿을 활용하여 다양한 타입의 데이터에 대해 동작하도록 일반화할 수 있습니다.
요구사항에 맞는 템플릿 클래스 Array를 만들어 보자.
1. 내부에 최대 100개 원소를 저장할 수 있는 배열과, 원소 개수를 나타내는 변수를 갖는다.
2. 생성자에서 초기 원소 개수는 0으로 한다.
3. add() 메서드를 구현한다. 인자를 받고 원소를 배열 맨 끝에 추가한다. 100개를 초과하면 더 이상 원소를 받지 않는다.
4. remove() 메서드를 구현한다. 현재 저장된 원소가 1개 이상이면, 마지막 원소를 제거한다.
5. print() 메서드를 구현한다. 현재 저장된 모든 원소를 출력한다.

// 목적: 클래스 템플릿으로 배열을 일반화하여 원소 추가 및 삭제 기능 구현하기
#include <iostream>
using namespace std;
template <typename T> // 클래스를 템플릿으로 정의함.
class Array
{
T data[100];
int size; // 배열의 사이즈
public:
Array() : size(0) {} // 생성자에서 초기 원소 개수는 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
'C++ 공부' 카테고리의 다른 글
| C++ 공부 8일차(응집도, 결합도, SOLID 원칙 5가지) (0) | 2025.08.20 |
|---|---|
| C++ 공부 8일차(STL, 컨테이너, 벡터, 맵, 알고리즘, 반복자) (0) | 2025.08.20 |
| C++ 공부 7일차(스택 메모리, 힙 메모리 , 댕글링 포인터, 스마트 포인터, 얕은 복사와 깊은 복사, 언리얼엔진의 메모리관리와 리플렉션 시스템) (0) | 2025.08.19 |
| C++ 4일차(상속, protected, 멤버 초기화 리스트, 다형성, 가상 함수(virtual), 순수 가상 함수) (0) | 2025.08.14 |
| C++ 공부 3일차(Class, 접근제어, getter, setter, 생성자, 헤더 파일, 소스 파일) (0) | 2025.08.13 |