일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 가상면접사례로배우는대규모시스템설계기초
- 해시
- 카카오
- 구조체배열
- 코테
- 시스템호출
- python
- @Autowired
- nestJS
- 카카오 알고리즘
- 프로그래머스
- 스프링
- AWS
- nestjs auth
- nestjs typeorm
- thymeleaf
- C언어
- C++
- TypeORM
- @Component
- spring boot
- OpenCV
- 코딩테스트
- 파이썬
- git
- Nodejs
- Spring
- 컴포넌트스캔
- 알고리즘
- 카카오 코테
- Today
- Total
공부 기록장 💻
[C++] 함수 중복 / 오버로딩 (Function Overloading), 디폴트 매개 변수 (Default Parameter), static 멤버 본문
[C++] 함수 중복 / 오버로딩 (Function Overloading), 디폴트 매개 변수 (Default Parameter), static 멤버
dream_for 2021. 4. 15. 02:24( 명품 C++ 프로그래밍 Ch6 )
함수 중복 (Function Overloading)
동일한 이름의 함수를 여러개 만들 수 있는데, 이것을 함수 중복(function overloading)이라 부른다.
- 다형성을 추구하는 방법(같은 이름으로 여러가지의 형태를 정의해놓고, 각각의 기능을 수행하도록 만드는 것)
int sum(int a, int b); // 1
int sum(int a, int b, int c); // 2
double sum(double a, double b); // 3
// 호출 방법
sum(2,5,33); // 2 호출
sum(12.5, 33.6); // 3 호출
sum(2,6); // 1 호출
위 예제에서는 총 3개의 동일한 이름을 가진 sum함수가 있다.
1. int형 변수 두 개를 매개 변수로 받아 int형을 리턴
2. int형 변수 세 개를 매개 변수로 받아 int형을 리턴
3. double형 변수 새 개를 매개 변수로 받아 double형을 리턴
컴파일러 가 동일한 이름의 서로 다른 중복 함수들을 구분한다.
위 sum 함수의 호출문에서 매개 변수 개수와 타입에 따라 중복된 함수를 찾아 연결한다.
함수 호출이 편리하고, 비슷하지만 다른 이름의 함수를 잘못 호출하는 오류를 줄일 수 있다.
- 함수 중복은 컴파일 시에 이루어지므로, 함수 중복으로 인한 실행 시간 저하는 없다.
( 컴파일: 실행하는 과정이 아닌, 실행 가능한 파일로 만드는 컴파일 과정에서 함수 중복에 대한 판별이 이루어짐)
예제
1. big 함수 (매개 변수 - 정수 변수, 정수 배열)
#include <iostream>
using namespace std;
int big(int a, int b) { return a > b ? a : b; }
int big(int a[], int size) {
int max = a[0];
for (int i = 1;i < size;i++) if (max < a[i])max = a[i];
return max;
}
int main() {
int array[] = { 2,4,6,5,8 };
cout << "big(3,5) = " << big(3, 5) << endl;
cout << "big(array, size) = " << big(array, sizeof(array) / sizeof(array[0])) << endl;
}
중복 함수의 조건
- 동일한 함수 이름
- 매개 변수 타입 혹은 매개 변수의 개수가 달라야 함
- 리턴 타입은 고려 x
이 때, 리턴 타입을 고려하지 않는다는 뜻은
아래 예제와 같이 이름이 동일하고, 매개 변수의 자료형과 개수가 모두 똑같은 동시에,
반환형만 다른 경우에는 함수 중복이 실패한다는 의미이다.
int sum(int a, int b){ return a + b; }
double sum(int a, int b){ return (double)(a + b); }
생성자 함수 중복
객체를 생성할 때 매개 변수를 통해 다양한 형태로 초깃값을 전달할 수 있다.
다음 예제와 같이, 기본 생성자와 매개 변수가 있는 두 생성자 Circle(), Circle(int r) 은 중복 작성되었다.
main 함수에서 매개 변수의 유무에 따라 호출되는 생성자가 다르다.
class Circle {
int radius;
public:
Circle();
Circle(int r);
...
};
int main() {
Circle donut(); // 기본 생성자 Circle() 호출
Circle donut(20); // 반지름 값에 20 전달
}
C++ 표준의 string 클래스 또한 다음과 같이 다양한 생성자를 제공하여
다양한 초깃값으로 string 객체를 생성할 수 있도록 하는 것이다.
class String {
...
public:
string(); // 빈 문자열을 가진 스트링 객체 생성
string(char* s); // null('\0') 값으로 끝나는 C-스트링 문자 배열을 스트링 객체로 생성
string(string& str); // str을 복사한 새로운 스트링 객체 생성
};
int main() {
string str; // string() 호출
string address("서울 성북구"); // string(char *s) 호출
string copyAddress(address); // string(string& str) 호출: address 문자열을 복사한 별도의 copyAddress 생성
}
소멸자는 매개 변수를 가지지 않으므로, 한 클래스 당 오직 하나만 존재하므로 함수 중복이 불가능하다.
디폴트 매개 변수 (Default Parameter)
함수에가 호출될 때, 매개 변수에 값이 넘어오지 않는 경우
미리 정해진 디폴트 값을 받도록 선언된 매개 변수(default parameter), 기본 매개 변수를 통해
함수 중복을 구현할 수 있도록 한다.
변수에 초깃값을 지정하는 것과 유사하다.
디폴트 매개 변수 선언
예제 1
다음은 총 3개의 int형을 매개 변수로 가지고 있는 sum 함수이다.
c는 디폴트 값 0을 가지도록 선언되었으므로, 함수를 호출하는 쪽에서 세번째 인자가 포함되지 않은 경우에는
자동적으로 0이 값이 매개 변수로 전달되어 함수가 실행된다.
int sum(int a, int b, int c = 0) { return (a + b + c); }
int main() {
cout << "sum(1,2) = " << sum(1, 2) << endl;
cout << "sum(1,2,3) = " << sum(1, 2, 3) << endl;
}
첫번째 함수 호출 부분에서는 매개 변수로 a와 b에 해당하는 1과 2의 값만 전달된다.
세번째 매개 변수가 실인자로 전달되지 않았으므로, 디폴트 값인 0이 그대로 전달되어 함수를 실행한다.
두번째 함수 호출 부분에서는 세번째 매개 변수의 값이 3으로 전달되었다.
예제 2 : 문자열
매개변수로 정수 id와 string 객체 모두 디폴트 값이 지정되어 있는 함수에 대해 각각 다른 방법으로 호출을 하였다.
처음에는 실인자 값 없이, 두번째는 첫번째 인자의 값만, 세번째는 두 실인자 값 모두 포함하여 호출을 하였다.
void msg(int id=3, string text="Hello"){
for (int i = 0;i < id;i++)
cout << text << endl;
}
int main() {
msg(); // id=3, text="Hello"
cout << endl;
msg(5); // id=5, text="Hello"
cout << endl;
msg(5, "Hi"); // id=5, text="Hi"
}
디폴트 매개 변수 조건과 규칙
- 디폴트 매개 변수는 모두 오른쪽 끝 쪽에 몰려 선언되어야 한다.
// int sum(int a = 0, int b, int c) { return (a + b + c); };
// int sum(int a, int b = 0, int c) { return (a + b + c); }
위 두 함수가 호출되면 컴파일 오류가 난다.
디폴트 매개 변수의 장점 - 함수 중복 간소화
디폴트 매개 변수로 인해서 중복 생성자들을 디폴트 매개 변수를 가진 하나의 생성자로 간소화할 수 있다.
다음의 Circle의 생성자를 작성한 부분을 살펴보자.
class Circle {
int radius;
public:
Circle(int r = 1) { radius = r; }
// Circle() { radius = 1; }
// Circle(int r) { this->radius = r; }
...
};
기존의 작성에서는 기본 생성자인 Circle()과, 한 개의 int 형 매개 변수를 전달 받는 Circle(int r) 생성자,
총 두 개의 생성자를 작성했어야 했다.
하지만 디폴트 매개 변수를 작성함으로써 두 개의 생성자에서 radius 멤버 변수를 초기화 하는 과정을
하나의 생성자를 이용하여 간소화 하였다.
함수 중복의 모호성
함수 중복의 조건을 갖추었더라도, 중복된 함수에 대한 호출이 모호(ambiguous)한 경우 컴파일 오류를 발생시킨다.
함수 중복의 모호성에는 3가지 종류가 있다.
1. 형 변환(Type Conversion)으로 인한 모호성
기본적으로 함수의 매개 변수 타입과 호출하는 곳에서의 실인자 타입이 일치하지 않을 때,
컴파일러는 자동적으로 형 변환을 시도한다.
double square(double a); // double 형 매개 변수
...
square(3); // 실인자 int형 전달
위의 예시와 같이, 실제로 double형을 매개 변수로 받는 square 함수에 대해여
3이라는 정수 값을 실인자로 전달하여 함수를 호출하면
컴파일러는 double 타입으로 형 변환을 하기 때문에 컴파일 오류가 발생하지 않는다.
char - > int -> long -> float - > double 컴파일러는 작은 타입을 큰 타입으로 자동 형 변환한다. 왼쪽에 있는 타입은 오른쪽의 어떤 타입으로든 자동 형변환 가능하다. |
그러나 모호성이 발생하는 사례는 다음과 같다.
float square(float a);
double square(double a);
위와 같은 두 함수가 중복되어 선언되어 있다고 할 때,
int형 실인자를 전달하는 square(3)이 호출되면 컴파일 오류가 발생한다.
컴파일러는 정수 3을 float으로 변환할지 double 타입으로 변환할지 모호하다.
2. 참조 매개 변수로 인한 모호성
참조 매개 변수로 선언된 함수가 있는 경우, 호출하는 곳에서 어떤 함수를 호출하는 것인지 판단할 수 없기 때문에 함수 중복의 모호성이 발생한다.
다음과 같이 매개 변수 b가 일반 변수와 참조 변수로 다르게 선언되어 있는 두 add 함수에 대하여,
add() 함수를 실인자 값을 전달하여 호출하면
둘 중 어느 함수를 호출해야 되는지에 대한 모호성이 존재하므로 컴파일 오류가 발생한다.
int add(int a, int b);
int add(int a, int &b);
3. 디폴트 매개 변수로 인한 모호성
디폴트 매개 변수를 가진 함수가 보통의 매개 변수를 가진 함수와 중복 작성될 때 모호성이 존재할 수 있다.
int sum(int a, int b) { return a + b; }
int sum(int a, int b, int c = 0) { return a + b + c; }
int main() {
cout << "sum(1,2) = " << sum(1, 2) << endl; // 컴파일 오류 발생
cout << "sum(1,2,3) = " << sum(1, 2, 3) << endl;
}
오버 로드된 함수 "sum"의 인스턴스 두 개에 대한 오류창이 뜨며 위 프로그램은 실행되지 않는다.
sum(1, 2)가 호출될 때, 첫번째 함수와 두번째 함수 중 어느 것을 호출해야 되는지에 대한 모호성이 존재하기 때문이다.
static 멤버
non-static 멤버
- 각 객체마다 별도로 생성되는 인스턴스(instance) 멤버
- 객체가 생성될 때 생성되고, 각 객체마다 별도로 생성된다. 객체 소멸 시 함께 소멸한다.
static 멤버
- 클래스 당 하나만 생기고, 모든 객체들이 공통으로 함께 공유하는 클래스(class) 멤버
- 객체가 생기기 전에 이미 생성되어 있고, 객체가 사라져도 소멸되지 않는다. 프로그램이 종료될 때 함께 소멸한다.
static 멤버 선언
- 모든 객체가 공유하도록 하기 위해 static을 지정하고자 하는 대상인 멤버 함수, 멤버 변수 앞에 static 지정자를 붙인다.
- 모든 멤버들이 static으로 선언될 수 있고, static 멤버들은 priave. public, protected 등 어떤 접근 지정도 가능하다.
static 멤버 변수에 대한 공간 할당(+초기화)
생성된 static 멤버에 대한 '변수 공간'을 할당할 때에는 클래스 바깥의 외부 전역(global) 변수로 선언되어야 한다.
변수의 공간을 할당받는 선언문이 추가적으로 필요하다는 뜻이다.
아래 예시와 같이 멤버 변수의 공간을 할당받아 초깃값을 지정해주는
static 멤버 선언문은 클래스 바깥 전역 공간에서 작성되어야 한다.
class Person {
public:
// non-static 멤버 선언
int money;
void addMoney(int money) { this->money = money; }
static int sharedMoney; // static 멤버 변수 선언
static void addShared(int n) { sharedMoney += n; } // static 멤버 함수 선언
};
// static 멤버 변수 sharedMoney 를 클래스 외부 전역 공간에서 10으로 초기화
int Person::sharedMoney = 10;
non-static 멤버 변수로는 money, 멤버 함수로는 addMoney가 있고,
static 멤버 변수로는 sharedMoney, 멤버 함수로는 addShared가 있다.
money는 인스턴스 멤버로, 각 Person 객체의 개인 소유의 돈을 표현하는 반면
sharedMoney는 static 멤버이므로 생성되는 모든 Person 객체들이 공유하는 공금을 의미한다.
sharedMoney는 객체의 개수와 상관없이 단 한 개만 생성된다.
클래스 외부에서 static 멤버 변수인 sharedMoney가 10으로 초기화되었다.
static 멤버 접근과 사용
1. 객체의 멤버(.)와 객체 포인터(->)로 접근하는 방법
위 예제에 언급된Person 클래스에 대하여
객체를 생성하고 객체로 static으로 선언된 멤버 변수와 멤버 함수에 접근하였다.
객체 lee에 대해서는 객체의 이름으로, 객체 kim에 대해서는 이 객체에 대한 포인터 p로 static 멤버에 접근하였다.
int main() {
Person lee, kim; // 객체 생성
lee.sharedMoney = 500; // 객체 이름으로 멤버 변수 접근, sharedMoney=500
lee.addShared(300); // 객체 이름으로 멤버 함수 접근, sharedMoney=800
cout << lee.sharedMoney << endl;
Person* p = &kim; // 객체 포인터 생성
p->addShared(300); // 객체 포인터로 멤버 함수 접근, sharedMoney=1100
cout << p->sharedMoney << endl;
}
2. 클래스명과 범위지정 연산자(::)로 접근하는 방법
static 멤버는 클래스 당 하나만 존재하므로 클래스의 이름과 범위 지정 연산자(::)를 사용하여 접근할 수도 있다.
객체가 생성되기 전부터 static 멤버에 접근 가능하다.
Person::sharedMoney = 200; // sharedMoney=200
Person::addShared(200); // sharedMoney=400
cout << Person::sharedMoney << endl;
** 그러나 non-static 멤버는클래스 명으로 접근할 수 없다. (객체, 이름, 객체 포인터로만 접근 가능)
static의 활용
1. 전역 변수나 전역 함수를 클래스에 캡슐화
- 변수와 함수에 대한 전역 선언 대신, 클래스 내부에 static 멤버로 선언하여 모두 캡슐화 시키는 것이 바람직하다.
(객체 지향 언어에서 추구하는 핵심 가치가 캡슐화 이기 때문! 전역 변수, 함수는 C언어 형태이다.)
2. 객체들 사이에 '공유'의 목적으로 사용할 변수를 만들어 활용
static 멤버 함수의 특징
1. static 멤버 함수는 static 멤버들만 접근
- 일반 멤버에 접근하면, 객체가 생성되기도 전에 접근하게 되는 오류를 범하게 되기 때문
- 일반 멤버 함수는 static 멤버들에 접근 가능
#include <iostream>
using namespace std;
class PersonError {
int money;
public:
static int sharedMoney;
static int getMoney() { return money; } // 컴파일 오류 - static 멤버 함수가 non-statc멤버에 접근
void setMoney(int money) { this->money = money; }
int total(){return money + sharedMoney; } // non-static 함수가 static 멤버 변수에 접근 가능
};
int main() {
int n = PersonError::getMoney(); // 객체가 생성되기도 전에 money 변수에 접근
PersonError errorKim;
errorKim.setMoney(100);
}
2. static 멤버 함수는 this 사용 불가능
- 객체가 생성되기도 전에 static 멤버 함수는 호출 가능하므로, this 사용할 수 없도록 제약함
'# Language & Tools > C++' 카테고리의 다른 글
[C/C++] const 지정자 (0) | 2021.04.15 |
---|---|
명품 C++ Programming 6장 실습 문제 - 함수/생성자 중복 정의, 디폴트 매개 변수, static 멤버, 참조 매개 변수, 난수 생성 (0) | 2021.04.15 |
명품 C++ Programming 5장 실습 문제 - 참조 매개 변수, 참조 객체, 복사 생성자, 참조 리턴 (0) | 2021.04.14 |
[C++] 객체 전달/치환/반환, 함수 참조, 얕은 복사/깊은 복사, 묵시적 복사 생성자 (0) | 2021.04.10 |
[C++] 동적 메모리 사용 - 포인터/배열, 객체 동적 생성, new/delete 연산자, 동적 할당과 반납, this 포인터 (0) | 2021.04.10 |