wn42
코딩이랑 이것저것
wn42
전체 방문자
오늘
어제
  • 분류 전체보기 (113)
    • 프로그래머스 (23)
      • LV1 (11)
      • LV2 (1)
      • LV3 (3)
      • 연습 (8)
    • 딥러닝 공부 (0)
      • 머신러닝&딥러닝 이론 (0)
    • 임베디드 (17)
      • Adventure Design (1)
      • 센서기반모바일로봇 (5)
      • ROS (9)
      • Google Coral (2)
    • C++ (38)
      • C++ 기초 (34)
      • 자료구조 및 알고리즘 (4)
    • Python (14)
      • 기본 파이썬 문법 (6)
      • Python 기초 (8)
    • 빅데이터 (9)
      • 빅데이터 첫걸음 시작하기(국비지원) (5)
      • 빅데이터 공부 (4)
    • 알고리즘 공부 (2)
      • 기본 알고리즘 (2)
    • 전자공학 (10)
      • 반도체 공정 (3)
      • 무선데이터통신 (7)
      • 반도체공학 (0)
    • C# (0)
      • C# 기본 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Python
  • 노드
  • 큐
  • 프로그래머스
  • 파이썬
  • 상속
  • 패스트캠퍼스
  • 빅데이터 첫걸음 시작하기
  • 인스턴스
  • 빅데이터
  • 클래스
  • 스택/큐
  • 데이터분석
  • numpy
  • stl
  • google coral
  • Queue
  • 조건문
  • c++
  • K디지털크레딧
  • 소멸자
  • 스택
  • 반복문
  • 내일배움카드
  • ROS
  • 바이트디그리
  • 딥러닝
  • 변수
  • 정렬
  • 데이터분석 인강

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
wn42

코딩이랑 이것저것

[C++] 함수(Function)
C++/C++ 기초

[C++] 함수(Function)

2022. 12. 26. 15:18

 함수 

하나의 특별한 목적의 작업을 수행하기 위해 독립적으로 설계된 프로그램 코드의 집합

함수 f(x)

 

함수의 생성

함수의 정의

  • 반환 값이 없는 함수(void 형)와 반환 값이 있는 함수(void를 제외한 데이터형)로 나눌 수 있다.
  • 함수의 이름은 되도록 함수의 기능을 나타내도록 설정하는 것이 좋다.
  • 아래와 같이 함수를 구현하는 것을 "함수를 정의한다."라고 한다.
// 반환값이 없는 함수
void func(){
    /*실행구문*/;
}

// 반환값이 있는 함수
int func(){
    /*실행구문*/;
    return 0;	// return을 이용하여 값을 반환
}

 

함수 호출

  • 함수를 생성하면, 함수의 이름을 적는 것으로 함수를 호출할 수 있다.
  • 함수를 호출하면, 해당 함수의 기능을 수행하고 다시 원래 있던 코드로 복귀한다.
  • 다음은 간단한 출력 함수를 생성하여 호출한 예제이다.
#include <iostream>

void print_hello() {
	std::cout << "Hello World!!" << std::endl;
}

int main() {
	print_hello();
	return 0;
}
Hello World!!

 

return의 기능

  • return은 함수를 종료하는 기능을 포함한다. 어디에서든 return을 만나게 되면 함수가 종료된다.
  • 반환값이 없는 함수를 정의하는 void도 반환값을 따로 적지 않는다면 return을 사용할 수 있다.
#include <iostream>

void print_hello() {
	std::cout << "Hello World!!" << std::endl;
	return;
	std::cout << "Hello World!!" << std::endl;
}

int print_zero() {
	std::cout << "0" << std::endl;
	return 0;
	std::cout << "0" << std::endl;
}

int main() {
	print_hello();
	print_zero();
	return 0;
}
Hello World!!
0

 

함수 매개변수(인자)

  • 프로그램 코드에서 {} 중괄호 내부에서 생성된 변수들은 해당 중괄호에서만 사용할 수 있다. ▶ 지역변수
  • 다음의 {} 중괄호 내부의 변수를 밖에서 사용하려는 경우의 예제이다.

 

Ex) a 출력하기

#include <iostream>
using namespace std;

void print_a() {
	cout << a << endl;
}

int main() {
	int a = 10;
	print_a();
	return 0;
}

  • main에서 생성한 변수 a를 출력하는 함수를 생성하였으나, 호출하니 오류가 발생하였다.
  • 이는 함수는 정확히 a가 무엇인지 알지 못하기 때문에 발생한 문제이다.
  • 이 문제를 해결하는 2가지 방법이 있다.

 

해결법 1) 전역변수화

#include <iostream>

int a = 10;    // a를 전역변수로 둔다.

void print_a() {
	std::cout << a << std::endl;
}

int main() {
	print_a();
	return 0;
}
10
  • a를 전역변수로 두는 경우 이 문제를 해결할 수 있다.

 

해결법 2) 매개변수

#include <iostream>

void print_a(int number) {
	std::cout << number << std::endl;
}

int main() {
	int a = 10;
	print_a(a);
	
	return 0;
}
10
  • 함수에 number라는 매개변수를 생성하고, main에서 number라는 매개변수에 a의 값을 전달한다.
  • 이렇게 하면 함수는 a의 값을 정확히 알기 때문에 함수 호출 시 정상적으로 동작이 이루어진다.

 

매개변수 = 복사의 개념

  • 매개변수로 값을 전달하는 것은 복사의 개념으로, 매개변수로 전달된 값과 함수에서 사용된 값은 서로 다른 메모리 공간을 점유한다. 즉, 두 값은 서로 다른 데이터이다.
  • 다음의 예시를 통해 이를 이해해보자.

 

Ex) 10을 더하는 함수

#include <iostream>

int plus_ten(int number) {
	number += 10;
	return 0;
}

int main() {
	int a = 10;
	std::cout << "a(호출 전): " << a << std::endl;

	plus_ten(a);
	std::cout << "a(호출 후): " << a << std::endl;

	return 0;
}
a(호출 전): 10
a(호출 후): 10
  • 10을 더하는 함수를 호출하여 실행시켰음에도 a의 값이 전혀 바뀌지 않음을 알 수 있다.
  • 함수에서 매개변수를 전달한다는 것은 실제 데이터 자체를 전달하는 것이 아니라, 실제 데이터에 해당하는 값을 전달하여 새로운 메모리 공간에 할당하는것이기 때문이다.
  • 그럼 함수를 통해서는 값을 영영 바꿀 수 없는 것인가? ▶ 이때 사용되는 것이 포인터(Pointer)이다. (후술)

 

 

Call-by-value / Call-by-reference

  • 함수에서 매개변수를 전달하는 방식은 2가지이다.
  • 1. Call-by-value (값에 의한 호출) ▶ 값을 복사하여 처리
  • 2. Call-by-reference (참조에 의한 호출) ▶ 값의 주소를 참조하여 처리

 

Call-by-value

  • Call-by-value는 매개변수로 받은 값을 복사(새로운 메모리 공간을 할당하여)하여 처리하는 방식이다.
  • 바로 위에서 함수를 호출했음에도 값이 변경되지 않은 예제가 이와 같은 처리 방식이다.

 

Ex) 10을 더하는 함수 (Call-by-value)

#include <iostream>

int plus_ten(int number) {
	number += 10;
	std::cout << "함수 내에서 a의 값: " << number << std::endl;
	std::cout << "함수 내 a의 주소: " << &number << std::endl << std::endl;
	return 0;
}

int main() {
	int a = 10;
	std::cout << "a(호출 전): " << a << std::endl;
	std::cout << "main에서 a의 주소: " << &a << std::endl << std::endl;

	plus_ten(a);
	std::cout << "a(호출 후): " << a << std::endl;

	return 0;
}
a(호출 전): 10
main에서 a의 주소: 00B4F74C

함수 내에서 a의 값: 20
함수 내 a의 주소: 00B4F678

a(호출 후): 10
  • main에서 선언된 변수 a의 메모리 주소와 함수에서 사용된 변수의 메모리 주소가 상이함을 알 수 있다.
  • 이처럼 단순히 값을 인자로 넘기는 경우에는 Call-by-value로 동작하기 때문에 값을 변경할 수 없다.

 

Call-by-reference

  • Call-by-reference는 매개변수로 사용되는 데이터의 주소를 전달하여 처리하는 방식이다.
  • Call-by-reference를 사용하기 위해서 데이터의 주소를 가리키는 변수인 포인터를 이용한다. (C와 달리 C++에서는 레퍼런스 변수를 이용하는 방법도 있지만 이는 다음에 따로 작성하겠다.)

 

Ex) 10을 더하는 함수 (Call-by-reference)

#include <iostream>

int plus_ten(int* number) {
	*number += 10;
	std::cout << "함수 내에서 a의 값: " << *number << std::endl;
	std::cout << "함수 내 a의 주소: " << number << std::endl << std::endl;
	return 0;
}

int main() {
	int a = 10;
	std::cout << "a(호출 전): " << a << std::endl;
	std::cout << "main에서 a의 주소: " << &a << std::endl << std::endl;

	plus_ten(&a);    // 주소를 매개변수로 전달
	std::cout << "a(호출 후): " << a << std::endl;

	return 0;
}
a(호출 전): 10
main에서 a의 주소: 00EBFD04

함수 내에서 a의 값: 20
함수 내 a의 주소: 00EBFD04

a(호출 후): 20
  • 놀랍게도 이번에는 함수 호출 이후 a의 값이 변경되었다.
  • 함수의 매개변수를 포인터 변수 int*로 설정하였고, main에서는 a가 아닌 a의 메모리 주소인 &a를 인자로 전달한다.
  • 즉, 함수가 직접적으로 변수 a의 메모리 주소를 참조하여 처리하기 때문에 값을 변경할 수 있는 것이다.

 

Swap 함수

  • 변수의 값을 교환하는 함수인 Swap을 Call-by-value, Call-by-reference 2가지 방식으로 구현한다.

 

Ex) Swap (Call-by-value)

#include <iostream>

int swap(int a, int b) {
	int temp = a;
	a = b;
	b = temp;

	return 0;
}

int main() {
	int i = 5, j = 7;

	std::cout << "swap 호출 전 >> i = " << i << ", j = " << j << std::endl;

	// swap 호출
	swap(i, j);
	std::cout << "swap 호출 후 >> i = " << i << ", j = " << j << std::endl;

	return 0;
}
swap 호출 전 >> i = 5, j = 7
swap 호출 후 >> i = 5, j = 7
  • 역시나 Call-by-value 방식으로는 변수의 값을 교환할 수 없었다.

 

Ex) Swap (Call-by-reference)

#include <iostream>

int swap(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;

	return 0;
}

int main() {
	int i = 5, j = 7;

	std::cout << "swap 호출 전 >> i = " << i << ", j = " << j << std::endl;

	// swap 호출
	swap(&i, &j);
	std::cout << "swap 호출 후 >> i = " << i << ", j = " << j << std::endl;

	return 0;
}
swap 호출 전 >> i = 5, j = 7
swap 호출 후 >> i = 7, j = 5
  • Call-by-reference 방식으로 swap을 구현하였을 때는 정상적으로 값이 교환되었음을 알 수 있다.

 

 

프로토타입(prototype)

  • 프로토타입 == 함수의 원형
  • 프로토타입이란 함수의 정의(함수의 구현)를 하기 전에 함수에 대한 원형을 main 함수 앞에 선언하는 것을 말하며 다음과 같이 구현한다.
// main 함수 앞에 프로토타입(함수 원형) 선언
int func(int a);    // 꼭 ;(세미콜론)을 적어야 함. 변수명은 꼭 적지 않아도 됨.(int만 적어도됨)

// main 함수
int main(){

}

// 함수의 정의
int func(int a){
}
  • 99% 이상의 프로그래머는 main 앞에 함수의 원형을 선언하고, main 뒤에 함수를 정의하는 방식을 이용한다. ▶ 프로그램의 코드를 작성할 때 수많은 함수를 구현할 것이기 때문에, 함수를 계속 main 앞에서 정의하게 되면 main 함수가 계속해서 뒤로 밀리게 될 것이다. 그렇기 때문에 main 앞에 함수의 원형을 선언하고, 뒤에서 함수를 구현함으로써 깔끔하게 코드를 작성할 수 있다.
  • C에서는 main 함수 뒤에 함수를 정의하더라도 프로그램 실행이 가능했다. 그러나 C++에서는 main 뒤에 함수를 정의하고, 함수의 원형을 선언하지 않으면 오류가 발생하여 실행이 불가능하다.

 

Ex) main 뒤에 함수를 정의하는 경우 (함수 원형 선언 X)

#include <iostream>

int main() {
	int i = 5;
	print_i(i);

	return 0;
}

int print_i(int num) {
	std::cout << "i의 값: " << num;
	return 0;
}

  • 오류가 발생하여 프로그램이 실행되지 않는다.
  • 이러한 문제가 발생하지 않도록 main 앞에 함수의 원형을 선언하는 부분, 즉 프로토타입을 적어주어야 한다. ▶ 컴파일러가 이를 통해 함수가 있음을 확인하며, 동시에 매개변수가 제대로 설정되어 있는지 등을 확인하여 오류를 검토한다.

 

Ex) main 뒤에 함수를 정의하는 경우 (함수 원형 선언 O)

#include <iostream>

int print_i(int num);

int main() {
	int i = 5;
	print_i(i);

	return 0;
}

int print_i(int num) {
	std::cout << "i의 값: " << num;
	return 0;
}
i의 값: 5
  • 프로토타입을 선언함으로써 오류없이 프로그램이 동작하였다.

 

 

배열을 매개변수로 전달하기

  • 배열 또한 함수의 인자(매개변수)로 받아서 사용할 수 있다.
  • 배열을 매개변수로 넘기는 것은 기본 동작이 Call-by-reference이다. ▶ 기본적으로 배열은 그 자체로 포인터이기 때문에 배열을 매개변수로 넘기는 것은 포인터, 즉 데이터의 주소를 넘기는 것과 같음.
  • Call-by-reference로 배열을 넘기기 싫다면, vector을 이용하여 배열을 생성하고 매개변수로 전달하면 된다.

 

배열 매개변수를 Call-by-reference로 넘기기

  • 배열을 매개변수로 넘기는 표현 방식은 2개가 있다.
1)  [ ] 연산자 이용하기
2)  * 연산자 이용하기

 

Ex 1) [ ] 연산자 이용하여 매개변수 전달

#include <iostream>

int print_array(int arr[]);

int main() {
	int arr[5] = { 1, 2, 3, 4, 5 };

	std::cout << "함수 호출 전 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << arr[i] << " ";
	}
	std::cout << std::endl << std::endl;

	print_array(arr);

	std::cout << "함수 호출 후 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << arr[i] << " ";
	}
	std::cout << std::endl;

	return 0;
}

int print_array(int arr[]) {
	for (int i = 0; i < 5; i++) {
		arr[i] += 10;
	}
	std::cout << "함수 내에서 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << arr[i] << " ";
	}
	std::cout << std::endl << std::endl;

	return 0;
}
함수 호출 전 arr 출력
1 2 3 4 5

함수 내에서 arr 출력
11 12 13 14 15

함수 호출 후 arr 출력
11 12 13 14 15
  • Call-by-reference에 의해 arr의 값이 변경되었음을 알 수 있다.

 

Ex 2) * 연산자 이용하여 매개변수 전달

#include <iostream>

int print_array(int* parr);

int main() {
	int arr[5] = { 1, 2, 3, 4, 5 };

	std::cout << "함수 호출 전 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << arr[i] << " ";
	}
	std::cout << std::endl << std::endl;

	print_array(arr);    // 혹은 &arr[0]

	std::cout << "함수 호출 후 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << arr[i] << " ";
	}
	std::cout << std::endl;

	return 0;
}

int print_array(int* parr) {
	for (int i = 0; i < 5; i++) {
		*(parr + i) += 10;
	}
	std::cout << "함수 내에서 arr 출력" << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << *(parr + i) << " ";
	}
	std::cout << std::endl << std::endl;

	return 0;
}
함수 호출 전 arr 출력
1 2 3 4 5

함수 내에서 arr 출력
11 12 13 14 15

함수 호출 후 arr 출력
11 12 13 14 15
  • Ex 1과 동일한 동작이 이루어졌다.

 

배열 포인터를 이용하여 2차원 배열을 매개변수로 넘기기

  • 2차원 배열 또한 당연하게도 매개변수로 넘길 수 있다. 이 또한 2가지 방식으로 이루어진다.
1)  [ ] 연산자 이용하기
2)  배열 포인터 이용하기

 

Ex 1) [ ] 연산자 이용하여 매개변수 전달

#include <iostream>

int print_array(int arr[][3]);

int main() {
	int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

	std::cout << "함수 호출 전 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl << std::endl;

	print_array(arr);

	std::cout << "함수 호출 후 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl;

	return 0;
}

int print_array(int arr[][3]) {    // 2차원 배열에서 열의 개수는 정확히 명시해야 함.
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			arr[i][j] += 10;
		}
	}
	std::cout << "함수 내에서 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl << std::endl;

	return 0;
}
함수 호출 전 arr 출력
1 2 3
4 5 6


함수 내에서 arr 출력
11 12 13
14 15 16


함수 호출 후 arr 출력
11 12 13
14 15 16
  • Call-by-reference에 의해 2차원 배열 arr의 값이 변경되었다.

 

Ex 2) 배열 포인터 이용하여 매개변수 전달

#include <iostream>

int print_array(int(*parr)[3]);    // 배열 포인터

int main() {
	int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

	std::cout << "함수 호출 전 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl << std::endl;

	print_array(arr);

	std::cout << "함수 호출 후 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl;

	return 0;
}

int print_array(int(*parr)[3]) {    // 배열 포인터 역시 열의 개수를 명시해야 한다.
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			parr[i][j] += 10;
		}
	}
	std::cout << "함수 내에서 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << parr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl << std::endl;

	return 0;
}
함수 호출 전 arr 출력
1 2 3
4 5 6


함수 내에서 arr 출력
11 12 13
14 15 16


함수 호출 후 arr 출력
11 12 13
14 15 16
  • 배열 포인터를 이용하여 2차원 배열을 매개변수로 전달할 수 있다.

 

 

상수 매개변수

  • 인자로 넘기는 변수를 상수처리하여 전달할 수 있다.
  • 당연하게도 const 키워드를 사용한 매개변수를 변경하려고 하면 오류가 발생한다.
#include <iostream>

int plus_a(const int* num);

int main() {
	int a = 10;
	std::cout << "a(호출 전): " << a << std::endl;

	plus_a(&a);
	std::cout << "a(호출 후): " << a << std::endl;

	return 0;
}

int plus_a(const int* num) {
	*num += 10;    // 값을 변경하려고 하면 오류가 발생
	return 0;
}

 

 

함수 포인터

  • 함수가 저장되어 있는 메모리의 시작주소를 가리키는 포인터
  • 함수 또한 코드 자체가 메모리 상에 저장되어 있다.
  • 배열과 마찬가지로, 함수의 이름은 함수의 시작 주소값을 나타낸다.
  • 함수 포인터는 다음과 같이 정의한다.
// (함수 리턴형) (*포인터명)(인자 타입1, 인자 타입2, ...);
// 인자가 없는 함수라면 int (*pfunc)(); 처럼 공백으로 두면 됨
int (*pfunc)(int);
  • 함수의 return 값은 int이므로, int 형 포인터를 선언하고 매개변수에 맞게 데이터형을 가리킨다.

 

Ex) 단순한 함수 포인터

#include <iostream>

int plus_a(int* num);

int main() {
	int a = 10;
	int (*pfunc)(int*);    // 함수 포인터

	pfunc = plus_a;    // 함수의 이름은 함수의 시작주소를 나타냄
	std::cout << "a(호출 전): " << a << std::endl;

	pfunc(&a);
	std::cout << "a(호출 후): " << a << std::endl;

	return 0;
}

int plus_a(int* num) {
	*num += 10;
	return 0;
}
a(호출 전): 10
a(호출 후): 20

 

Ex) 다차원 배열을 인자로 갖는 함수에 대한 함수 포인터

  • 다차원 배열을 인자로 갖는 함수의 함수 포인터는 다음과 같이 인자의 이름만 빼버리면 된다.
#include <iostream>

int plus_array(int(*parr)[3]);

int main() {
	int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };
	int(*pfunc)(int(*)[3]);    // 함수 포인터 정의

	pfunc = plus_array;

	std::cout << "함수 호출 전 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl << std::endl;

	pfunc(arr);

	std::cout << "함수 호출 후 arr 출력" << std::endl;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			std::cout << arr[i][j] << " ";
		}
		std::cout << std::endl;
	}
	std::cout << std::endl;

	return 0;
}

int plus_array(int(*parr)[3]) {
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			parr[i][j] += 10;
		}
	}
	return 0;
}
함수 호출 전 arr 출력
1 2 3
4 5 6


함수 호출 후 arr 출력
11 12 13
14 15 16

 

 

main 함수

  • 당연하게도, main 또한 함수이다.
  • main은 프로그램의 시작점이며, 컴퓨터는 프로그램을 실행할 때 가장 먼저 main을 확인하게 된다.
  • main은 프로그램 내에서 단 1개 존재하며, 무조건 1개는 존재하여야 한다.
  • main 함수의 반환값은 컴퓨터의 운영체제가 받으며, 정상 종료시 0을, 비정상 종료시 1을 반환하는 것으로 규정되어 있다.
    'C++/C++ 기초' 카테고리의 다른 글
    • [C++] typedef
    • [C++] 디버깅(Debugging)
    • [C++] 포인터와 배열
    • [C++] 포인터(Pointer) 기초
    wn42
    wn42
    코딩이랑 이것저것 하는 블로그

    티스토리툴바