안녕하세요. BlockDMask입니다.
오늘은 C++11, 14에서 추가된 lambda 표현식에 대해 알아보겠습니다.
<목차>
1. 람다 표현식
2. 람다 표현식 사용 방법과 구조
3. 람다의 필요성, 사용 예제
1. C++ 람다 표현식
lambda는 람다 표현식, 람다 함수, 익명 함수 등의 이름으로 불립니다.
일단 람다 함수가 어떻게 생겨먹었는지 먼저 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include<iostream>
#include<string>
using namespace std;
// 일반 함수 정의
void sum1(int a, int b)
{
cout << "sum1 func : " << a + b << endl;
}
int main(void) {
//일반 함수 호출
sum1(10, 20);
//람다 함수
[](int a, int b)
{
cout << "sum2 lambda : " << a + b << endl;
}(30, 40);
return 0;
}
|
cs |
동일한 동작을 하는 함수를 일반 함수와 람다 함수로 만들어보았습니다.
위 코드를 보면 우리가 일반적으로 알고 있는 함수의 모양인
반환형 함수이름 (매개변수)
{
// 함수동작
}
이런 식으로 함수가 이루어져 있는 걸 볼 수 있는데요.
아래 람다함수를 보면
[] (int a, int b) { cout << " " << endl; } (30, 40);
이런 식으로 함수의 모양이 이상한 것을 볼 수 있습니다.
함수의 이름과 반환형이 보이지 않는 것을 볼 수 있습니다.
[] (매개변수) { // 함수 동작 } (호출 시 인자) ;
이런 식으로 람다 함수가 이루어진 것을 볼 수 있습니다.
이렇게 우리가 아는 일반 함수에서 함수 이름이 없어지고 동작만 있는 함수를 람다 함수, 이름 없는 함수, 람다 표현식이라고 합니다.
좀 더 자세한 것은 아래에서 확인해보겠습니다.
2. C++ lambda 사용법과 구조
2-1) 람다 기본 모양과 사용법
람다 함수는 아래와 같이 대괄호[], 소괄호(), 중괄호{}, 소괄호() 이런 모양으로 생겼습니다.
여기서 생략이 가능한 건 소괄호들 뿐입니다.
[] ( ) { } ( )
[캡처] (매개변수) { 함수 동작 } (호출인자)
첫 번째 [] : 캡처
두 번째 () : 매개변수 선언 부분 (생략 가능 - 매개변수 필요 없을 때)
세 번째 {} : 함수 동작 부분
네 번째 () : 함수 호출 시 인자 (생략 가능 - 호출 시에만 사용)
이런 식으로 함수가 이루어져 있습니다. 아까 예로 들었던 매개변수가 2개 있던 함수를 보면
[] (int a, int b) { cout << a + b << endl; } (10, 20)
이런식으로 람다 표현식이 정의되어있었습니다.
사실 정확하게 이야기하면 표현식은 앞에 파란 부분 [] (int a, int b) { cout << ... << endl; } 이 부분까지가 람다 표현식이고 뒤에 (10, 20)은 람다 함수를 호출할 때 사용하는 것입니다.
살짝 정리를 해보면 아래와 같습니다.
람다 표현식 생성. 즉, 함수를 만들기만 한 거
[](int a, intb) {return a + b};
람다 표현식 사용. 즉 함수를 만들고 호출한 것
[](int a, intb) {return a + b} (10, 20);
일반 함수와 마찬가지로 매개변수가 있는 경우, 매개변수가 없는 경우, 반환이 있는 경우, 반환이 없는 경우 모두 구현이 가능합니다.
매개변수 없는 람다 표현식 : [] { cout << " 인자 없음 " << endl; };
매개변수 있는 람다 표현식 : [] (int a, int b, int c) { cout << a << b << c << endl; };
매개변수 없고 반환 있는 람다 : [] { return 200; };
매개변수 있고 반환 있는 람다 : [] (int a, int b) { return a * b; } ;
이런 식으로 4종류의 람다 함수가 존재합니다.
*그리고 람다 표현식도 식이기 때문에 끝에는 세미콜론을 붙여주어야 합니다.
위에서는 함수를 호출하지 않았으므로 중괄호 뒤에 () 이런 식이 없는 것을 확인할 수 있습니다.
앞서 이야기했듯이 매개변수가 있는 람다를 호출하기 위해서는 [] (int a, int b) { return a * b; } (10, 20) ; 이런 식으로 맨 뒤에 매개변수에 맞게 호출부를 작성해주어야 합니다.
2-2) 람다 캡처 사용법
이제 람다 모양 [] () {} () 중 맨 앞에 있던 [] 대괄호에 대해서 설명해보겠습니다.
일단 이 대괄호 부분을 캡쳐라고 부릅니다.
캡처는 람다 외부에 정의되어있는 변수나 상수를 람다 내부에서 사용하기 위해서 사용합니다.
"어 그럼 매개변수로 넘겨서 사용하면 되는 거 아님?" 이럴 수 있지만,
모든 변수, 상수를 매개변수로 전달할 수도 없고, STL에서 사용할 때는 매개변수 제약이 있을 수 있기 때문에 이런 상황에서 캡처를 사용하라고 캡쳐를 만들어 둔 것으로 판단됩니다.
캡처도 방식이 두 가지가 있는데요. 우리가 배웠던 call by value, call by reference 이것입니다.
즉 변수를 참조로 캡처하느냐 , 복사로 캡쳐하느냐 이 차이입니다.
참조를 할 때는 & 기호를 붙이고, 복사를 할때는 그냥 변수 이름을 사용하면 됩니다.
만약 모든 외부 변수에 대해서 참조를 하려면 [&]라고 사용하면 되고
모든 외부 변수에 대해서 복사를 하려면 [=]으로 사용하면 됩니다.
외부에 변수가 result1, result2, result3, result4 가 있다고 하면 아래와 같은 의미가 됩니다.
[result1, result2] () {} () // 변수 result1, result2를 복사해서 람다 함수 내부에서 사용
[&result1, &result2] () {} () // 변수 result1, result2 를 참조해서 람다 함수 내부에서 사용
[result3, &result4] () {} () // 변수 result3은 복사 result4는 참조해서 람다 함수 내부에서 사용
[=] () {} () // 모든 외부 변수 result1, result2, result3, result4를 복사해서 람다 함수 내부에서 사용
[&] () {} () // 모든 외부 변수 result1, result2, result3, result4 를 참조해서 람다 함수 내부에서 사용
[&, result3] () {} () // 모든 외부 변수 (result1,2,4)은 참조로 사용하지만, result3만 복사로 사용
[=, &result3] () {} () // 모든 외부 변수 (result1,2,4)은 복사로 사용하지만, result3만 참조로 사용
바로 코드로 확인해보겠습니다.
예제 1) 람다 캡처 일부 복사, 일부 참조
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
32
33
34
35
36
|
#include<iostream>
#include<string>
using namespace std;
int main(void) {
int result1 = 1;
int result2 = 2;
int result3 = 3;
int result4 = 4;
// 일부 복사
cout << "1. 특정변수 복사" << endl;
[result1, result2](int a, int b) {
cout << "result1, result2 : " << result1 << ", " << result2 << endl;
cout << "result1 + a + b : " << result1 + a + b << endl;
// 복사한 변수에는 대입 불가능
// result1 = a + b;
// result2 = 99999;
}(10, 20);
// 일부 참조
cout << "2. 특정변수 참조" << endl;
[&result3, &result4](int a, int b) {
result3 = 22222;
cout << "(내부) result3 : " << result3 << endl;
result4 = a + b;
}(10, 20);
cout << "(외부) result3, result4 : " << result3 << ", " << result4 << endl;
return 0;
}
|
cs |
이런 식으로 외부 변수를 변경하지 않고 사용만 할 것이라면 [변수] 캡처를 이용해서 복사를 해서 사용하면 되고,
외부 변수를 가지고 와서 변경을 하려면 [&변수] 캡처를 이용해서 참조하면 됩니다.
예제 2) 전체 참조, 전체 복사
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
32
33
34
35
36
37
38
39
40
41
42
|
#include<iostream>
#include<string>
using namespace std;
int main(void) {
int result1 = 1;
int result2 = 2;
int result3 = 3;
int result4 = 4;
// 전체 복사
cout << "3. 전체 복사" << endl;
[=](int x)
{
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
cout << "매개변수 : " << x << endl;
}(30);
cout << endl;
// 전체 참조
[&](int y)
{
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
cout << "매개변수 : " << y << endl;
result1 += y;
result2 += y;
result3 += y;
result4 += y;
}(99);
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
return 0;
}
|
cs |
[=] 이런 캡처를 통해서 외부 변수를 전부 복사해서 람다 함수 내부에서 사용할 수 있습니다.
복사한 변수에 대입을 하려 하는 건 불가능합니다. (rvalue)
[&] 이런 캡처를 통해서 외부 변수를 전부 참조하여 람다 함수 내부에서 사용할 수 있습니다.
참조 변수에 대입을 하거나, 하는 변경을 할 수 있으며, 당연하게도 그것은 람다 함수 외부에서도 적용이 됩니다.
예제 3) 전체 참조 + 일부 복사 / 전체 복사 + 일부 참조
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
#include<iostream>
#include<string>
using namespace std;
int main(void) {
int result1 = 1;
int result2 = 2;
int result3 = 3;
int result4 = 4;
// 전체 참조, 일부 복사
cout << "5. 전체 참조, 일부 복사" << endl;
[=, &result3, &result4](int x)
{
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
cout << "매개변수 : " << x << endl;
//result1 += x; // 전체 복사
//result2 += x; // 전체 복사
result3 += x; // 일부 참조
result4 += x; // 일부 참조
}(30);
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
cout << endl;
// 전체 복사, 일부 참조
cout << "6. 전체 복사, 일부 참조" << endl;
[&, result1, result2](int y)
{
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
cout << "매개변수 : " << y << endl;
//result1 += y; // 일부 복사
//result2 += y; // 일부 복사
result3 += y; // 전체 참조
result4 += y; // 전체 참조
}(99);
cout << "result1, 2 : " << result1 << ", " << result2 << endl;
cout << "result3, 4 : " << result3 << ", " << result4 << endl;
return 0;
}
|
cs |
위 예제에서 보신 것처럼 [=, &변수, &변수] 이런 식으로 전체 외부 변수를 복사 하되, "일부" 변수만 참조로 가지고 올 수 있습니다.
또한 반대로 [&, 변수, 변수] 모든 변수를 참조로 가지고 오고, "일부" 변수만 복사로 가지고 올 수 있습니다.
단, [&, &변수] 이런식으로 모든 변수 참조하고 "일부"도 참조하려 하거나,
[=, 변수, 변수] 이런 식으로 모든 변수 복사하고 "일부"도 복사하려는 것은 당연하게도 오류가 발생합니다.
3. C++ 람다의 필요성과 사용 예제
3-1) 람다와 auto
람다 함수의 정의를 우리는 auto를 이용해서 특정 변수에 넣어둘 수 있습니다.
auto func1 = [] (int a, int b) { return a * b };
이런 식으로 함수를 정의한 부분을 func1 이라는 변수에 집어 넣어서 실제로 사용할때는
fun1(10, 20); 이런식으로 사용이 가능합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include<iostream>
using namespace std;
int main(void) {
// 람다 함수1
auto func1 = [](int a, int b) {return a * b; };
cout << "func1(2, 10) : " << func1(2, 10) << endl << endl;
// 람다 함수2
int num = 20;
auto func2 = [&num](int a) { num += a; };
func2(100); // num = num + 100
cout << "num : " << num << endl;
return 0;
}
|
cs |
auto를 통해서 람다 함수를 변수에 받아 넣을 수 있습니다.
3-2) 함수의 파라미터가 되는 람다
자 이제 람다를 조금 배워봤는데, '그래서 람다 어디에 쓰지?' 하시는 분들이 계실 것입니다.
저는 람다 함수는 아래와 같은 상황에 많이 사용합니다.
우리가 std::sort 함수에서 정렬을 위해서 3번째 매개변수로 함수를 넘겨주는 경우가 있습니다.
이렇게 함수는 필요한데 이번 한번 사용하고 땡처리되는 1회성 함수가 필요할 때 사용합니다.
즉, "함수가 필요한데 많이 쓰일 것 같진 않고, 애매~할 때" 사용합니다.
아래와 같이 sort 함수에서 세 번째 인자로 함수를 넣을 때 딱 이번에만 사용할 건데 함수를 만들어야 하는 그런 상황에 람다 함수를 사용하면 아주 유용합니다.
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
32
33
34
35
36
37
38
39
40
41
42
43
|
#include<iostream>
#include<algorithm>
#include<array>
using namespace std;
bool compare(int a, int b) { return a > b; }
int main(void) {
std::array<int, 10> arr1 = { 5, 4, 2, 1, 100, 32, 2, 4, 6, 9 };
std::array<int, 10> arr2 = { 5, 4, 2, 1, 100, 32, 2, 4, 6, 9 };
std::array<int, 10> 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;
}
|
cs |
첫 번째는 sort함수의 기본 사용이며, 이 경우 디폴트로 오름차순으로 정렬이 됩니다.
두 번째 예제는 sort() 함수에서 세 번째 인자에 정렬 기준을 정하는 함수를 넣음으로써 내림차순으로 정렬하도록 하는 예제입니다.
sort(arr2.begin(), arr2.end(), compare); 여길 보시면 세 번째 인자로 함수를 넣은 것을 볼 수 있습니다.
기존에는 이런 식으로 sort 함수를 이용해왔는데 람다 함수를 이용하면 좀 더 간편하게 처리할 수 있습니다.
세 번째 예제를 보면 sort(arr3.begin(), arr3.end(), [](int a, int b){return a > b;}); 이런 식으로 sort 함수의 세 번째 인자에 일반 함수 대신, 람다 함수를 집어넣은 것을 볼 수 있습니다.
이런 식으로 함수가 필요한 곳에 함수 대신 람다 함수를 사용할 수 있습니다.
* sort 함수
: 배열의 시작 주소와 마지막 다음 주소를 받아서 요소를 퀵 소트 알고리즘으로 정렬하는 C++ 표준 함수
: 3번째 인자로 함수를 전달하면 요소의 비교 정책을 변경할 수 있습니다.
더 다양한 sort 함수 예제가 궁금하시다면 [바로가기]
이상으로 C++11, 14의 람다 표현식, 람다 함수에 대해서 알아보았습니다. 감사합니다
'<개인공부> > [C++]' 카테고리의 다른 글
[C++] RTTI, 타입 정보 얻기 (1) | 2021.05.19 |
---|---|
[C++] 연산자 재정의 기본 (overator overloading) (1) | 2021.05.18 |
[C++] 멤버 초기화 리스트 (member initializer lists) (0) | 2021.02.19 |
[C++] nullptr (널 포인터 리터럴) (3) | 2021.02.17 |
[C++] 디폴트 매개변수 (default parameter) (0) | 2021.02.15 |
[C++] constexpr 상수 (0) | 2021.02.11 |
[C++] 인라인 함수 (inline function) (1) | 2021.02.10 |
[C++] 2진수 8진수 16진수 표기법, 자릿수 표기법 (0) | 2021.02.09 |