개발자 면접 공부/C-C++

C++11,14 람다식

chogyujin 2023. 6. 14. 18:35
728x90

1. 개요

오늘은 람다식에 대해 공부하도록 하겠습니다.


2. 람다

람다식은 C++11,14에 추가된 표현법으로 보통 익명 함수라고 불립니다.
람다를 저는 학부생때 Java시간때 처음 알게 되었습니다.
그때는 그냥 함수가 간편하게 사용되는 구조인것만으로 끝을 냈습니다.
하지만 이러한 이유때문에 람다를 사용하는경우가 있습니다.
 
1. 함수 객체와는 다르게 class를 선언할 필요가 없다.
(코드의 길이가 줄어듬)
 
2. 함수 포인터의 단점은 "함수의 인라인화가 불가능합니다"
여기서 인라인은 컴파일러가 함수를 굳이 스택에 쌓아서 부를필요없이 메인함수에서 그 함수만 가지고와 실행하는 기법입니다.
하지만 람다는 "함수의 인라인"이 가능합니다.


3. 람다 표현식

보통 람다표현식은 다음과 같습니다.

#include<iostream>

using namespace std;
//일반 함수
void Sum1(int a, int b)
{
	cout << "Sum1 함수" << a + b << '\n';
}

int main()
{
	//일반 함수 Call
	Sum1(10, 20);

	//람다 함수
	[](int a, int b)
	{
		cout << "LamDa 함수" << a + b << '\n';
	}(30, 40);

	return 0;
}

위 코드를 보면 우리가 일반적으로 알고있는

반환형 함수이름(매개변수)
{
  // 동작
}

이러한 내용이 있어야하지만
 
람다함수는

[](매개변수){//함수 내용}(인자);

로 이루어져있습니다.
 
모양이 매우 이상한거같습니다.

이렇게 우리가 아는 일반 함수에서 함수 이름이 없어지고 동작만 있는 함수를 람다 함수, 이름없는 함수, 람다 표현식이라고 합니다.


4. 람다 사용법과 구조

람다 함수는 아래와 같이 대괄호[], 소괄호(),중괄호{},소괄호()이런 모양으로 생겼습니다.
여기에서 생략이 가능한 건 소괄호들 뿐입니다.
 
[] ( ) {  } ( )
[캡처] (매개변수) { 함수 동작 } (호출인자) 로 표현됩니다.
 
첫 번째 [] : 캡처
두 번째 () : 매개변수 선언 부분 (생략 가능 - 매개변수 필요 없을 때)
세 번째 {} : 함수 동작 부분
네 번째 () : 함수 호출 시 인자 (생략 가능 - 호출 시에만 사용)
 
람다 표현식 생성. 즉, 함수를 만들기만 한 거
[](int a, intb) {return a + b};

람다 표현식 사용. 즉 함수를 만들고 호출한 것
[](int a, intb) {return a + b} (10, 20);
 

이제 람다의 캡쳐 사용법에 대해 알아보겠습니다.

이제 람다 모양중 대괄호[] 이부분에 대해 설명을 하도록 하겠습니다.
이 부분을 보통 캡쳐라고 불리며
 
캡쳐는 람다 외부에 정의되어있는 변수나 상수를 람다 내부에서 사용하기 위해서 사용됩니다.
 
그럼 우리는 이러한 생각을 할수 있습니다.
"매개변수로 다 넘겨서 사용하면 되는것을 굳이?"
모든 변수, 상수를 매개변수로 전달할 수도 없고, STL에서 사용할 떄는 매개변수 제약이 있을 수 있기 때문에 이런 상황에서 캡쳐를 사용하라고 캡쳐를 만든것으로 판단됩니다.
 
캡처도 방식이 두 가지가 있는데요. 우리가 배웠던 call by value, call by reference 이것입니다.
즉 변수를 참조로 캡처하느냐 , 복사로 캡쳐하느냐 이 차이입니다.
 
참조를 할 때는 & 기호를 붙이고, 복사를 할때는 그냥 변수 이름을 사용하면 됩니다.
만약 모든 외부 변수에 대해서 참조를 하려면 [&]라고 사용하면 되고
모든 외부 변수에 대해서 복사를 하려면 [=]으로 사용하면 됩니다.


외부에 변수가 a, b, c, d 가 있다고 하면 아래와 같은 의미가 됩니다.
[a, b] () {} () // 변수 a, b를 복사해서 람다 함수 내부에서 사용
[&a, &b] () {} () // 변수 a, b 를 참조해서 람다 함수 내부에서 사용
[c, &d] () {} () // 변수 c은 복사 d는 참조해서 람다 함수 내부에서 사용
[=] () {} () // 모든 외부 변수 a, b, c, d를 복사해서 람다 함수 내부에서 사용
[&] () {} () // 모든 외부 변수 a, b, c, d 를 참조해서 람다 함수 내부에서 사용
[&, c] () {} () // 모든 외부 변수 (a,b,d)은 참조로 사용하지만, c만 복사로 사용
[=, &c] () {} () // 모든 외부 변수 (a,b,d)은 복사로 사용하지만, c만 참조로 사용

코드를 보면서 확인해 보겠습니다.
 

#include<iostream>

using namespace std;

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;

	//일부 복사
	cout << "1. 일부 특정 변수 복사" << '\n';
	[a,b](int num1, int num2)
	{
		cout << "a,b :" << a << ',' << b << '\n';
		cout << "a+num1+num2 :" << a + num1 + num2<<'\n';
	}(10, 20);


	//일부 참조
	cout << "2 일부 특정 변수 참조" << '\n';
	[&c,&d](int num1, int num2)
	{
		c = 100000;
		cout << "(내부) c : " << c << '\n';
		d = num1 + num2;
	}(10, 20);
	cout << "(외부) c,d :" << c << ',' << d << '\n';

	a = 1;
	b = 2;
	c = 3;
	d = 4;

	//전체 복사
	cout << "3 전체 변수 참조" << '\n';
	[=](int num1, int num2)
	{
		cout << "a,b,c,d :" << a << ',' << b << ',' << c << ',' << d << '\n';
	}(10, 20);

	//전체 참조
	cout << "3 전체 변수 참조" << '\n';
	[&](int num1, int num2)
	{
		a = 50;
		b = 60;
		c = 70;
		d = 90;
		cout << "a,b,c,d :" << a << ',' << b << ',' << c << ',' << d << '\n';
	}(10, 20);
	return 0;
}

이러한 방식도 가능함니다.
람다에 auto

#include<iostream>

using namespace std;

int main()
{
	// 람다 함수1
	auto func1 = [](int a, int b) {return a * b; };
	cout << "func1(10,20) : " << func1(10, 20) << '\n';

	// 람다 함수2
	int num = 20;
	auto func2 = [&num](int a) { num += a; };

	func2(100); // num = num + 100 

	cout << "num : " << num << endl;
	return 0;
}

그러면 이제 여러분들은 그래서 람다가 도대체 어디에서 쓸건지가 궁금할겁니다.
람다 함수는 아래와 같은 상황에 많이 사용합니다.
우리가 std::sort 함수에서 정렬을 위해서 3번째 매개변수로 함수를 넘겨주는 경우가 있습니다.
이렇게 함수는 필요한데 이번 한번 사용하고 땡처리되는 1회성 함수가 필요할 때 사용합니다.
즉, "함수가 필요한데 많이 쓰일 것 같진 않고, 애매~할 때" 사용합니다.
아래와 같이 sort 함수에서 세 번째 인자로 함수를 넣을 때 딱 이번에만 사용할 건데 함수를 만들어야 하는 그런 상황에 람다 함수를 사용하면 아주 유용합니다.
 

#include<iostream>
#include<algorithm>
#include<array>
#include<vector>
using namespace std;
bool compare(int a, int b)
{
	return a > b;
}
int main()
{
	vector<int> arr1 = { 5, 4, 2, 1, 100, 32, 2, 4, 6, 9 };
    vector<int> arr2 = { 5, 4, 2, 1, 100, 32, 2, 4, 6, 9 };
    vector<int> arr3 = { 5, 4, 2, 1, 100, 32, 2, 4, 6, 9 };
 
    // sort 함수
    sort(arr1.begin(), arr1.end());
    cout << "std::sort(arr1, arr1 + 10)" << endl;
    for (int val : arr1)
    {
        cout << val << " ";
    }
    cout << endl << endl;
 
 
    // sort 함수와 일반 함수 이용
    cout << "std::sort(arr, arr + 10, compare) : " << endl;
    sort(arr2.begin(), arr2.end(), compare);
    for (int val : arr2)
    {
        cout << val << " ";
    }
    cout << endl << endl;
 
 
    // sort 함수와 람다 함수 이용
    cout << "std::sort(arr, arr + 10, [](int a, int b) {return a > b; })" << endl;
    sort(arr3.begin(), arr3.end(), [](int a, int b) {return a > b; });
    for (int val : arr3)
    {
        cout << val << " ";
    }
	return 0;
}

보시면 보통 sort함수는 정렬을 해주는 함수입니다.
보통 오름차순이죠 하지만 내림차순을 하기위해 greater를 사용하거나 함수 외부에 빼서 bool리언을 사용합니다.
하지만 람다 함수를 사용하여 저렇게 쉽게 만들수도 있고 코드가 더 간결해 보일수도있습니다.
 

#include<iostream>
#include<algorithm>
#include<array>
#include<vector>
using namespace std;

int main()
{

	int (*p)(int, int);
	p = [](int a, int b) {return a + b; };
	
	cout << p(10, 20);
	return 0;
}

이렇게도 사용이 가능합니다 함수 포인터 형식으로


5. Ref

https://coding-factory.tistory.com/638

[C언어/C++] 함수 포인터 사용법 & 예제 총정리

함수의 주소 변수를 선언하면 메모리 공간이 할당되고 그 공간의 위치가 주소로 존재하듯이 함수를 선언해도 변수와 마찬가지로 메모리에 공간이 할당되며 그 위치를 표현하는 주소가 생겨납

coding-factory.tistory.com

https://blockdmask.tistory.com/491

[C++] 람다 표현식, lambda에 대해서

안녕하세요. BlockDMask입니다. 오늘은 C++11, 14에서 추가된 lambda 표현식에 대해 알아보겠습니다. 1. 람다 표현식 2. 람다 표현식 사용 방법과 구조 3. 람다의 필요성, 사용 예제 1. C++ 람다 표현식 lambda

blockdmask.tistory.com