본문 바로가기
Language/C++

04.Function

by 아무키 2023. 12. 22.
반응형

C++ 프로그램의 함수

C++ 표준 라이브러리(함수와 클래스)

써드 파티 라이브러리(함수와 클래스)

직접 구현한 함수와 클래스

 

함수 → 모듈화 → 재사용성

코드를 독립적인 연산으로 분할

연산들을 재사용

int main(){
	read_input();
	process_input();
	write_output();
	
	return 0;
}

 

큰 업무를 작은 단위의 업무(함수)로 분할

   함수의 기능, 필요로 하는 정보, 리턴하는 것, 어떤 오류가 발생하는지, 성능상의 제약에 대해 이해해야 함

 

함수의 정의에 필요한 요소

1. 이름

…함수의 이름

…변수의 명명 규칙과 동일

… 의미가 있는 이름이어야 한다

 

2. 매개변수 리스트

…함수에 전달되는 값(인자)들

…타입이 명시되어야 함

 

3 리턴 타입

…연산 결과의 반환 타입

 

4 본문(body)

…함수가 호출되었을 때 실행되는 명령문

…대괄호(”{ }”) 내부

//함수의 정의

int func_name(int a){
	statements;
	return 0;
}

//함수의 호출

int main(){
	for(int i = 0; i <10; i++){
		func_name();
	}
	reutnr 0;
}

 

함수 프로토타입

 

컴파일러는 함수의 호출 이전에 함수의 정의를 알아야 함

함수의 호출 이전에 함수를 정의해서 해결한다.

→작은 프로그램에서는 괜찮은 방법이다.

→일반적으로 효율적인 방법은 아니다.

 

함수 프로토타입을 사용

→함수의 전체 정의가 아닌 컴파일러가 알아야할 부분만을 미리 알려주는 개념

→전방 선언(forward declaration)이라고도 명칭

→프로그램의 초기에 위치

→헤더 파일(.h)의 활용

int func_name();

int main(){
	func_name();
}

int func_name(){
	statements;

	return 0;
}

 

함수 매개변수 (Function Parameters)

함수를 호출할 때, 데이터를 전달 할 수 있다.

→함수의 호출에 있어서 전달하는 값을 인수(argument)라 함

→함수의 정의에 있어서 전달하는 값은 인자 또는 매개 변수(parameter)라고 함

인수와 매개변수는 개수, 순서와 타입이 일치해야 함

int add_n(int ,int); //prototype

int main(){
	int result = 0;
	result = add_n(100, 200); // function call with argument

	return 0;
}

int add_n(int a, int b){
	return a + b;
}

 

값 전달 (Pass-by-value)

함수에 데이터를 전달할 때는 값으로 전달(pass-by-value)됨

...데이터 값이 복사되어 전달

...전달된 인수는 함수를 통해 변화되지 않음

→ 실수로 값이 변화하는 것을 방지

값을 변화시키는 것이 필요하거나, 복사 비용이 높을 때를 위한 방법 존재(포인터 / 참조자)

 

형식 매개변수(formal parameter) : 함수 선언의 매개변수

실제 매개변수(actual parameter) : 함수 호출시 실제로 사용되는 인자

 

void param_test(int formal){
	cout << formal << '\n'; // 50
	formal = 100;
	cout << formal << '\n; // 100
}

int main(){
	int actual = 50;
	cout << actual << '\n'; // 50
	param_tesxt(actual);
	cout << actual << '\n'; // 50

 

return문

반환(return)

→ void형 반환인 경우 return문 생략 가능

return문은 함수 내 어느 곳에서나 정의 가능

→ 마지막 줄에 정의하는 것이 의미적으로 명확함

return문을 통해 함수는 즉각적으로 종료

 

기본 인수(default argument)

 

함수의 선언에서 정의한 모든 매개변수가 전달되어야 함

기본 인수를 사용하면 인수가 주어지지 않을 시 기본값을 사용하도록 정의 가능

→ 동일한 값을 자주 사용할 경우

기본값은 함수 프로토타입 또는 정의부에 선언

→ 프로토타입에 선언하는 것이 기본

→ 둘다 선언해서는 안됨

여러개의 기본값을 사용할 경우 오른쪽부터 선언

double calc_cost(double base_cost,
		double tax_rate = 0.06, 
                double shipping = 3.5);

int main(){
	double cost = 0;
	cost = calc_cost(100.0, 0.08, 4.5);
	cost = calc_cost(100.0, 0.08);
	cost = calc_cost(200.0);

	return 0;
}

double calc_cost(double base_cost, double tax_rate, double shipping){
	return base_cost +=(base cost * tax_rate) + shipping;
}

 

오버로딩 (Function Overloading)

 

서로 다른 매개변수 리스트를 갖는 동일한 이름의 함수 정의

추상화의 한 예

다형성의 한 예

→유사한 개념의 함수를 다른 타입에 대해 정의

객체지향 프로그램 구현을 위한 중요한 기법 중 하나

컴파일러는 주어진 인수와 함수들의 파라메터 정의를 기반으로 개별적인 함수를 구분할 수 있어야 한다.

 

int add_n(int, int);
double add_n(double, double);

int main(){
	cout << add_n(10, 20) << '\n';
	cout << add_n(10.0, 20.0) << '\n';
	
	return 0;
}

int add_n(int a, int b){
	return a + b;
}

double add_n(double a, double b){
	return a + b;
}

 

반환 타입에 따르는 구분은 할 수 없다는 것 주의

int get_value();
double get_value();

cout << get_value() << '\n'; // error

 

함수 호출의 동작 방식

 

지역범위

블록{ } 내의 범위

함수의 매개변수는 함수 본체 내에서만 존재함

따라서 함수의 (복사된) 인자 및 지역 변수들은 함수의 실행 중에만 존재함

 

static 지역 변수

static 한정어를 사용해 예외 변수 지정 가능함

초기화가 필요하다.

void static_local_increment(){
	static int n = 1;
	cout << "num : " << n << '\n';
	n++;
	cout << "num : " << n << '\n';
}

int main(){
	static_local_increment(); // 1 2
	static_local_increment(); // 2 3
	static_local_increment(); // 3 4

 

전역 범위

함수 밖에 정의된 변수는 어디서나 접근 가능

전역 변수는 사용하지 않는 것이 좋음

→전역 상수는 OK

 

함수 호출의 동작방식

Function call stack

LIFO(Last in first out)

Stack Frame(Activation Record)

→ 함수의 호출이 발생할 때마다 구분선이 생성됨

→ 함수의 지역 변수와 매개변수는 그 구분선 내에 생성됨

→ 함수의 호출이 끝나면 구분선 내의 메모리가 자동으로 해제됨

 

스택은 유한하고, stack overflow 발생할 수 있다.

 

스택 메모리

코드 공간

Static 변수, 전역 변수, String 리터럴

힙(Heap) : 힙 메모리는 크지만 느림, 메모리를 직접 해제해주어야함

스택(Stack) : 스택 메모리는 빠르지만 작음, 메모리가 자동으로 해제됨

int func2(int x, int y, in z){
	x += y + z;
	return x;
}

int func1(int a, int b){
	int result;
	result = a + b;
	result = func2(result, a, b);
	return result;
}

int main(){      //int result 0x
	int x = 10;    //int z 0x
	int y = 20;    //int y 0x
	int z;         //int x 0x
	z = func1(x, y);
	cout << z << '\n';

	return 0;
}

 

Debug - Window- Call Stack에서 스택 구분선 확인 가능

 

 

배열의 전달과 pass-by-reference

 

배열을 함수로 전달

형식적 매개변수 정의에 “[ ]”를 사용하여 배열을 전달 가능

주의) 배열의 요소들은 복사되지 않는다

배열의 이름은 배열의 첫 요소의 메모리를 가리킴

→ 실제 매개변수에 복사되는 것은 이 메모리

따라서, 배열에 얼마나 많은 요소가 저장되어 있는지 함수는 알지 못함

void print_a(const int numbers[], int size);

void zero_a(int numbers[], int size(){
	for(int i = 0 ; i < size; i++){
		numbers[i] = 0;
	}
}

int main(){
	int my_n[]{1,2,3,4,5};
	print_a(my_n, 5);
	zero_a(my_n, 5);
	print(my_n, 5); // 0 0 0 0 0
	
	return 0;
}

void print_a(const int numbers[], int size){
	for(int i = 0 ; i < size; i++){
		cout << numbers[i] << '\n';
	}
}

 

변화를 방지하는 안전한 코드를 위해 const 키워드 사용가능

→ const int numbers[]

 

참조자로 전달 (Pass-by-reference)

함수 내에서 값의 변환이 필요할 경우 사용

값의 변환을 위해서는 매개변수의 주소값이 필요

배열이 아닌 경우에도 C++에서는 참조자(reference)를 통해 가능

형식 매개변수를 실제 매개변수의 별명처럼 사용하는 개념

//참조자 & 기호 사용

void scale_n(int& n);

int main(){
	int n = 1000;
	scale_n(n);
	cout << n << '\n';

	return 0;
}

void scale_n(int& n){
	if(n > 100){
		n = 100;
	}
}
//참조자 swap 예제

void swap(int& a, int& b)

int main(){
	int x = 10, y = 20;
	cout << x << ' ' << y << '\n'; // 10 20
	swap(x, y);
	cout << x << ' ' << y << '\n'; // 20 10
	
	return 0;
}

void swap(int& a, int& b){
	int tmp = a;
	a = b;
	b = tmp;
}

 

참조자를 사용하지 않는 경우에는 print를 위해 메모리 2배 사용

참조자를 사용하되, 값의 변경이 필요 없을 경우에는 const로 안정성 확보

 

void print(const vector<int> &v);

int main(){
	vector<int> v{1, 2, 3, 4, 5};
	print(v);
	
	return 0;
}

void print(const vector<int> &v){
	for(auto n : v){
		cout << n << '\n';
	}
}

 

inline함수

 

함수의 호출에는 어느 정도 오버헤드가 존재

→ Activation stack 생성, 파라메터 처리, pop stack, 리턴 값 처리등

함수를 inline으로 정의하면 컴파일 단계에서 함수내의 명령문으로 함수 호출이 대체

→ 일반적인 함수 호출보다 빠름

→ 바이너리 파일의 용량이 커질수 있다

→ 컴파일러 내부적으로 알아서 inline으로 처리하기도 함

inline int add_numbers(int a, int b){
	return a + b;
}

int main(){
	int result;
	result = add_numbers(100, 200); //함수 본문인 100 + 200으로 대체

	return 0;
}

 

 

재귀함수(recursive)

 

스스로를 호출하는 함수

Factorial

→ 재귀 호출을 끝내는 base case가 반드시 실행되어야 한다. (stack overflow 주의)

unsinged long long factorial(unsigned long long n){
	if(n == 0){ // base case
		return 1;
	}
	return n * factorial(n - 1);
}

int main(){
	cout << factorial(5) << '\n';
	
	return 0;
}

 

Multiple return values

 

참조자 / 포인터를 인자로 전달

인자의 전달에 복사가 발생하지 않아 효율적

같은 타입인 경우 (array, vector등)을 활용

std::tuple활용

std::make_pair(), std::get()

struct / class 활용

 

Memory layout additions

release 모드에서 컴파일하면 지역변수들이 연속된 메모리 공간에 할당되나, 디버그 모드에서 컴파일 하면 중간중간에 공백이 존재

반응형

'Language > C++' 카테고리의 다른 글

06.참조자  (0) 2023.12.22
05.포인터  (0) 2023.12.22
03.basic-syntax  (1) 2023.12.22
02. 변수와 상수  (1) 2023.12.22
01. C++ 프로그램의 구조  (0) 2023.12.21

댓글