문제

나는 지금에 대해 학습 함수 포인터이고,내가 읽기 K&R 장,주제에 첫 번째 것은 나를했다,"Hey,이것은 좀처럼 마감." 나는 이러한 가정은 근본적으로 잘못된 어떻게든 후에 온라인으로 검색 찾지 못한 정보의 분석이 비교입니다.

왜 C-style 함수 포인터를 근본적으로 다른 폐쇄 또는 람다?멀리로 말할 수 있다는 사실과 함께 할 수 있는 함수 포인터이 여전히 포인트를 정의(이름)함수와는 반대로의 연습으로 익명으로 정의하는 기능입니다.

왜 전달하는 기능을 함으로 볼 수 있는 더 강력한 두 번째 경우에는 이름,처음보다는 그냥 보통,일상적인 기능이 통과되고 있는?

시 말해 어떻게 그리고 왜 나는 잘못을 비교하는 두 그래서이다.

감사합니다.

도움이 되었습니까?

해결책

람다(나 폐쇄)캡슐화 기능 포인터 변수이다.이 때문에,C#,당신이 할 수 있는 일:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

내가 사용하는 익명의 대리자가 있으로 폐쇄(이 구문을 좀 더 명확하고 가까이 C 이상에 해당하는 람다)하는 작음(스 변수)으로 마감.할 때 클 평가,작음(의 스택이 있을 수 있습니다.이 파괴되었)계속 참조할 수 있습니다.변경하는 경우 작음,다음 변경 비교

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

lessThanTest(99); // returns true
lessThan = 10;
lessThanTest(99); // returns false

C 이 불법:

BOOL (*lessThanTest)(int);
int lessThan = 100;

lessThanTest = &LessThan;

BOOL LessThan(int i) {
   return i < lessThan; // compile error - lessThan is not in scope
}

가 누구인지 알고 싶었을 정의하는 함수 포인터,2 인수:

int lessThan = 100;
BOOL (*lessThanTest)(int, int);

lessThanTest = &LessThan;
lessThanTest(99, lessThan); // returns true
lessThan = 10;
lessThanTest(100, lessThan); // returns false

BOOL LessThan(int i, int lessThan) {
   return i < lessThan;
}

그러나,지금은 전달하는 2 인수할 때 내가 평가합니다.면 나는 바랬는 이 함수 포인터를 다른 기능 lessThan 지 않는 범위에서 난 것이 하나 있을 수동으로 살아있는 유지 하기로 전달하여 그것은 각 기능에 체인에서,또는 홍보하여 글로벌.

하지만 대부분의 주요 언어로 지원하는 폐쇄에 사용하는 익명의 기능에 대한 요구 사항은 없다.할 수 있습 마감없이의 함수와 익명의 기능을 마감.

요약:폐쇄 조합의 함수 포인터+캡쳐 변수입니다.

다른 팁

'실제'폐쇄 유무에 관계없이 언어에 대한 컴파일러를 작성한 사람으로서 위의 답변에 동의하지 않습니다. LISP, 계획, ML 또는 Haskell 폐쇄 새로운 기능을 동적으로 생성하지 않습니다. 대신 기존 기능을 재사용합니다 그러나 그렇게합니다 새로운 자유 변수. 자유 변수 모음을 종종 환경, 적어도 프로그래밍 언어 이론가에 의해.

폐쇄는 함수와 환경을 포함하는 집계 일뿐입니다. 뉴저지 컴파일러의 표준 ML에서 우리는 하나를 레코드로 표현했습니다. 한 필드에는 코드에 대한 포인터가 포함되어 있고 다른 필드에는 자유 변수의 값이 포함되어 있습니다. 컴파일러 새로운 폐쇄 (기능이 아님)를 동적으로 만들었습니다 포인터가 포함 된 새로운 레코드를 할당함으로써 같은 코드이지만 다른 자유 변수의 값.

이 모든 것을 C로 시뮬레이션 할 수는 있지만 엉덩이의 고통입니다. 두 가지 기술이 인기가 있습니다.

  1. 함수 (코드)와 자유 변수에 대한 별도의 포인터에 대한 포인터를 전달하여 폐쇄가 두 C 변수에 걸쳐 분할되도록합니다.

  2. 구조물에 무료 변수의 값과 코드에 대한 포인터가 포함 된 구조물에 포인터를 전달하십시오.

기술 #1은 어떤 종류의 시뮬레이션을 시도 할 때 이상적입니다. 다형성 C와 당신은 환경의 유형을 밝히고 싶지 않습니다. 예를 들어, Dave Hanson을보십시오 C 인터페이스 및 구현. 기능 언어의 기본 코드 컴파일러에서 발생하는 작업과 더 유사한 기술 #2는 다른 친숙한 기술과 비슷합니다. 가상 멤버 기능이있는 C ++ 객체. 구현은 거의 동일합니다.

이 관찰은 Henry Baker의 현명성으로 이어졌습니다.

Algol/Fortran World의 사람들은 수년간 미래의 효율적인 프로그래밍에서 가능한 사용 기능 폐쇄가 무엇인지 이해하지 못했다고 불평했습니다. 그런 다음`객체 지향 프로그래밍 '혁명이 일어 났고, 이제 모든 사람들은 기능 폐쇄를 사용하는 프로그램을 사용합니다.

C에서는 기능을 인라인으로 정의 할 수 없으므로 실제로 폐쇄를 만들 수 없습니다. 당신이하는 일은 사전 정의 된 방법에 대한 참조를 전달하는 것입니다. 익명의 메소드/클로저를 지원하는 언어에서는 방법의 정의가 훨씬 유연합니다.

가장 간단한 용어로는 기능 포인터는 그와 관련된 범위가 없지만 (글로벌 범위를 계산하지 않는 한), 폐쇄에는이를 정의하는 메소드의 범위가 포함됩니다. Lambdas를 사용하면 메소드를 작성하는 메소드를 작성할 수 있습니다. 클로저를 사용하면 "일부 인수는 함수에 묶고 결과적으로 낮은 기능을 얻을 수 있습니다." (토마스의 의견에서 가져온). 당신은 C에서 그것을 할 수 없습니다.

편집 : 예제 추가 (지금은 Actionscript-ish 구문을 사용하겠습니다.

다른 방법을 인수로 취하는 방법이 있지만, 호출 할 때 매개 변수를 해당 메소드에 전달하는 방법을 제공하지 않습니까? 예를 들어, 전달한 메서드를 실행하기 전에 지연을 일으키는 몇 가지 방법 (바보 같은 예제이지만 간단하게 유지하고 싶습니다).

function runLater(f:Function):Void {
  sleep(100);
  f();
}

이제 객체의 일부 처리를 지연시키기 위해 endlater ()를 사용하고 싶다고 말합니다.

function objectProcessor(o:Object):Void {
  /* Do something cool with the object! */
}

function process(o:Object):Void {
  runLater(function() { objectProcessor(o); });
}

Process ()에 전달하는 기능은 더 이상 정적으로 정의 된 기능이 아닙니다. 동적으로 생성되며 메소드가 정의되었을 때 범위에있는 변수에 대한 참조를 포함시킬 수 있습니다. 따라서 글로벌 범위에 있지 않더라도 'O'및 'ObjectProcessor'에 액세스 할 수 있습니다.

나는 그것이 말이되기를 바랍니다.

클로저 = 논리 + 환경.

예를 들어이 C# 3 메소드를 고려하십시오.

public Person FindPerson(IEnumerable<Person> people, string name)
{
    return people.Where(person => person.Name == name);
}

Lambda 표현식은 로직 ( "이름 비교")뿐만 아니라 매개 변수 (예 : 로컬 변수) "이름"을 포함한 환경을 캡슐화합니다.

이것에 대한 자세한 내용은 내 폐쇄에 관한 기사 C# 1, 2 및 3을 통해 클로즈가 어떻게 쉬워지는지를 보여줍니다.

C에서 함수 포인터는 함수에 인수로 전달 될 수 있고 함수의 값으로 반환 될 수 있지만 함수는 최상위 수준에서만 존재합니다. 서로 내에서 기능 정의를 중첩 할 수는 없습니다. C가 외부 기능의 변수에 액세스 할 수있는 중첩 함수를 지원하는 데 필요한 것이 무엇인지 생각하면서도 기능 포인터를 통화 스택을 위아래로 보낼 수 있습니다. (이 설명을 따르려면 C 및 가장 유사한 언어로 기능 호출이 어떻게 구현되는지에 대한 기본 사항을 알아야합니다. 전화 스택 Wikipedia 입력.)

중첩 된 기능에 대한 포인터는 어떤 종류의 객체입니까? 코드의 주소 일 수는 없습니다. 코드를 호출하면 외부 기능의 변수에 어떻게 액세스합니까? (재귀 때문에 한 번에 외부 기능의 여러 호출이있을 수 있음을 기억하십시오.) 이것을 Funarg 문제, 그리고 두 가지 하위 문제가 있습니다 : 하향 Funargs 문제와 위쪽 Funargs 문제.

아래쪽으로 funargs 문제, 즉, 당신이 호출하는 함수에 대한 인수로서 "스택 아래로"함수 포인터를 보내는 것은 실제로 c와 GCC와 호환되지 않습니다. 지원합니다 중첩 된 기능은 하향 Funargs로 기능합니다. GCC에서는 중첩 된 기능에 대한 포인터를 만들 때 실제로 포인터를 얻을 수 있습니다. 트램폴린, 동적으로 구성된 코드를 설정하는 코드 정적 링크 포인터 그런 다음 정적 링크 포인터를 사용하여 외부 기능의 변수에 액세스하는 실제 함수를 호출합니다.

상향 Funargs 문제는 더 어렵습니다. GCC는 외부 기능이 더 이상 활성화되지 않은 후 트램폴린 포인터가 존재하지 않으면 (통화 스택에 기록이 없음) 정적 링크 포인터가 쓰레기를 가리킬 수 있습니다. 활성화 레코드는 더 이상 스택에 할당 할 수 없습니다. 일반적인 솔루션은 힙에 이들을 할당하고 중첩 된 기능을 나타내는 기능 객체가 외부 기능의 활성화 레코드를 가리 키게하는 것입니다. 이러한 대상은 a라고합니다 폐쇄. 그러면 언어는 일반적으로 지원해야합니다 쓰레기 수집 더 이상 포인터를 가리키지 않으면 기록이 해제 될 수 있습니다.

람다 (Lambdas) (익명 기능)는 실제로 별도의 문제이지만 일반적으로 비행에서 익명 기능을 정의 할 수있는 언어는 또한 기능 값으로 반환 할 수 있도록하여 결국 폐쇄가됩니다.

람다는 익명입니다. 동적으로 정의되었습니다 기능. 당신은 c에서 그것을 할 수 없습니다 ... 폐쇄 (또는 둘의 유죄 판결)와 관련하여 일반적인 LISP 예제는 다음의 선을 따라 무언가를 보게됩니다.

(defun get-counter (n-start +-number)
     "Returns a function that returns a number incremented
      by +-number every time it is called"
    (lambda () (setf n-start (+ +-number n-start))))

C 용어로는 어휘 환경 (스택)을 말할 수 있습니다. get-counter 익명 기능에 의해 포착되고 있으며 다음 예에서 볼 수 있듯이 내부적으로 수정됩니다.

[1]> (defun get-counter (n-start +-number)
         "Returns a function that returns a number incremented
          by +-number every time it is called"
        (lambda () (setf n-start (+ +-number n-start))))
GET-COUNTER
[2]> (defvar x (get-counter 2 3))
X
[3]> (funcall x)
5
[4]> (funcall x)
8
[5]> (funcall x)
11
[6]> (funcall x)
14
[7]> (funcall x)
17
[8]> (funcall x)
20
[9]> 

클로저는 함수 정의 지점의 일부 변수가 미니 옥시를 즉석에서 선언 할 수있는 것과 같은 함수 로직과 함께 결합되어 있음을 의미합니다.

C와 클로저의 중요한 문제 중 하나는 폐쇄가 그들을 가리키고 있는지 여부에 관계없이 현재 범위를 떠날 때 스택에 할당 된 변수가 파괴된다는 것입니다. 이것은 사람들이 부주의하게 포인터를 로컬 변수로 반환 할 때 얻는 버그의 종류로 이어질 것입니다. 클로저는 기본적으로 모든 관련 변수가 힙에 ref- 계산되거나 쓰레기 수집 품목임을 암시합니다.

나는 모든 언어의 람다가 폐쇄임을 확신하지 못하기 때문에 람다를 폐쇄와 동일시하지 않습니다. 때로는 람다가 변수의 바인딩없이 로컬로 정의 된 익명 기능 (Python pre 2.1?)이 방금 현지화 된 기능을 한 것으로 생각합니다.

GCC에서는 다음 매크로를 사용하여 Lambda 기능을 시뮬레이션 할 수 있습니다.

#define lambda(l_ret_type, l_arguments, l_body)       \
({                                                    \
    l_ret_type l_anonymous_functions_name l_arguments \
    l_body                                            \
    &l_anonymous_functions_name;                      \
})

예제 원천:

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
     lambda (int, (const void *a, const void *b),
             {
               dump ();
               printf ("Comparison %d: %d and %d\n",
                       ++ comparison, *(const int *) a, *(const int *) b);
               return *(const int *) a - *(const int *) b;
             }));

이 기술을 사용하면 응용 프로그램이 다른 컴파일러와 함께 작동 할 가능성이 제거되며 YMMV가 "정의되지 않은"동작입니다.

폐쇄 캡처 무료는 변수환경.환경에 여전히 존재할 것이다,비록 주변의 코드가 손상될 수도 있습니다 더 이상 활성화됩니다.

예를 들어에서 일반적인 Lisp,가 MAKE-ADDER 을 반환하는 새로운 마감.

CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta)))
MAKE-ADDER

CL-USER 54 > (compile *)
MAKE-ADDER
NIL
NIL

를 사용하여 위의 기능:

CL-USER 55 > (let ((adder1 (make-adder 0 10))
                   (adder2 (make-adder 17 20)))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder1))
               (print (funcall adder1))
               (describe adder1)
               (describe adder2)
               (values))

10 
20 
30 
40 
37 
57 
77 
50 
60 
#<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(60 10)
#<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(77 20)

Note DESCRIBE 기능는 것을 보여줍 함수 개체폐쇄 이 동일하지만, 환경 은 다릅니다.

Common Lisp 은 모두를 폐쇄하고 순수한 함수 개체(이들지 않고 환경)모두 기능 과 수출 모두 동일한 방식으로 사용하기 FUNCALL.

주요 차이점은 C에서 어휘 범위의 부족으로 인해 발생합니다.

함수 포인터는 코드 블록에 대한 포인터입니다. 참조하는 비 스택 변수는 전역, 정적 또는 유사합니다.

폐쇄 인 Otoh는 '외부 변수'또는 'upvalues'형태의 자체 상태를 가지고 있습니다. 어휘 스코핑을 사용하여 원하는만큼 비공개 또는 공유 할 수 있습니다. 동일한 함수 코드로 많은 클로저를 만들 수 있지만 다른 변수 인스턴스를 만들 수 있습니다.

몇 가지 폐쇄는 일부 변수를 공유 할 수 있으므로 OOP 의미에서 객체의 인터페이스가 될 수 있습니다. C에서이를 만들려면 구조를 기능 테이블 포인터와 연관시켜야합니다 (C ++가하는 일입니다. 클래스 vtable).

요컨대, 클로저는 기능 포인터와 일부 상태입니다. 더 높은 수준의 구성입니다

대부분의 응답은 클로저가 기능 포인터, 아마도 익명 함수에 대한 기능 포인터가 필요하지만 마크는 썼다 명명 된 기능으로 클로저가 존재할 수 있습니다. 다음은 Perl의 예입니다.

{
    my $count;
    sub increment { return $count++ }
}

폐쇄는 $count 변하기 쉬운. 그것은 만 사용할 수 있습니다 increment 서브 루틴과 통화 사이에 지속됩니다.

C에서 함수 포인터는 동기를 불러 일으킬 때 함수를 호출하는 포인터입니다. 폐쇄는 함수의 논리와 환경 (변수 및 그들이 바인딩 된 값)을 포함하는 값입니다. 실제로 이름이없는 함수입니다. C는 일류 값이 아니기 때문에 전달할 수 없으므로 대신 포인터를 전달해야하지만 기능 언어 (예 : 체계)에서는 다른 값을 전달하는 것과 같은 방식으로 함수를 전달할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top