PHP 정적 링크. 최적화로서의 정적 연결. Java의 초기 및 후기 바인딩




이 단락은 간결함에도 불구하고 매우 중요합니다. 거의 모든 전문적인 내용입니다. 프로그램 작성 Java에서는 다형성 사용을 기반으로 합니다. 동시에 이 주제는 학생들이 이해하기 가장 어려운 주제 중 하나입니다. 그러므로 이 단락을 여러 번 주의 깊게 다시 읽어 보는 것이 좋습니다.

클래스 메소드는 프로그램 코드를 컴파일할 때 정적 수정자로 표시됩니다. 정적 연결. 이는 소스 코드에 메소드 이름이 지정된 클래스의 컨텍스트에서 컴파일된 코드에 있는 해당 클래스의 메소드에 대한 링크가 배치됨을 의미합니다. 즉, 수행된다. 메소드 이름 바인딩통화 장소에서 실행 가능한 코드로이 방법. 때때로 정적 연결~라고 불리는 초기 바인딩, 이는 프로그램의 컴파일 단계에서 발생하기 때문입니다. 정적 연결 Java에서는 클래스가 final 수정자("final", "final")로 선언될 때 또 다른 경우에 사용됩니다.

Java의 객체 메소드는 동적입니다. 즉, 동적 연결. 이는 프로그램 실행 단계에서 메소드 호출 중 직접 발생하며, 이 메소드를 작성하는 단계에서는 어느 클래스에서 호출할지 미리 알 수 없습니다. 이는 이 코드가 작동하는 개체 유형, 즉 개체가 속한 클래스, 메서드가 호출되는 클래스에 따라 결정됩니다. 이 바인딩은 메서드 코드가 컴파일된 지 오랜 후에 발생합니다. 따라서 이러한 유형의 바인딩을 종종 호출합니다. 후기 바인딩.

호출 기반 프로그래밍 코드 동적 방법, 속성이 있습니다 다형성– 동일한 코드라도 호출하는 객체의 유형에 따라 다르게 작동하지만, 메서드의 소스 코드와 관련된 추상화 수준에서는 동일한 작업을 수행합니다.

처음 읽을 때는 명확하지 않은 이 단어를 설명하기 위해 이전 단락의 예제인 moveTo 메소드의 작업을 고려해 보겠습니다. 경험이 없는 프로그래머는 모든 하위 클래스에서 이 메서드를 재정의해야 한다고 생각합니다. 이것은 실제로 수행할 수 있으며 모든 것이 올바르게 작동합니다. 그러나 그러한 코드는 극도로 중복될 것입니다. 결국 메소드의 구현은 모든 하위 클래스에 있을 것입니다. 수치정확히 동일합니다:

public void moveTo(int x, int y)( hide(); this.x=x; this.y=y; show(); );

게다가 이 경우는 다형성을 활용하지 않습니다. 그래서 우리는 그렇게 하지 않을 것입니다.

그 이유는 종종 의아해하기도 합니다. 추상 수업 수치이 메소드의 구현을 작성하십시오. 결국, 여기에 사용된 hide 및 show 메소드에 대한 호출은 언뜻 보기에 호출이어야 합니다. 추상 메소드– 즉, 전혀 일을 할 수 없는 것 같습니다!

그러나 hide 및 show 메소드는 동적입니다. 이는 이미 알고 있듯이 메소드 이름과 해당 실행 코드의 연결이 프로그램 실행 단계에서 수행된다는 것을 의미합니다. 따라서 이러한 메서드가 클래스 컨텍스트에 지정된다는 사실 수치, 클래스에서 호출된다는 의미는 아닙니다. 수치! 또한 이 클래스에서는 hide 및 show 메서드가 호출되지 않도록 보장할 수 있습니다. Dot 유형의 변수 dot1 과 Circle 유형의 Circle1 변수가 있고 해당 유형의 객체에 대한 참조가 할당됩니다. dot1.moveTo(x1,y1) 및 Circle1.moveTo(x2,y2) 호출이 어떻게 작동하는지 살펴보겠습니다.

dot1.moveTo(x1,y1)을 호출하면 클래스에서 호출이 발생합니다. 수치 moveTo 메소드. 실제로 Dot 클래스의 이 메서드는 재정의되지 않습니다. 즉, Dot 클래스에서 상속된다는 의미입니다. 수치. moveTo 메소드에서 첫 번째 명령문은 동적 hide 메소드에 대한 호출입니다. 이 메소드의 구현은 이 메소드를 호출하는 dot1 객체가 인스턴스인 클래스에서 가져옵니다. 즉, Dot 클래스에서 온 것입니다. 따라서 요점이 숨겨집니다. 그런 다음 객체의 좌표가 변경된 후 호출됩니다. 동적 방법보여주다. 이 메소드의 구현은 이 메소드를 호출하는 dot1 객체가 인스턴스인 클래스에서 가져옵니다. 즉, Dot 클래스에서 온 것입니다. 따라서 새 위치에 점이 표시됩니다.

Circle1.moveTo(x2,y2) 호출의 경우 모든 것이 완전히 유사합니다. 동적 메소드 hide 및 show는 Circle1 객체가 인스턴스인 클래스, 즉 Circle 클래스에서 호출됩니다. 그리하여 옛 곳에 감춰지고 새 곳에 드러나는 것이 원이다.

즉, 객체가 점인 경우 점이 이동합니다. 그리고 물체가 원이면 원이 움직입니다. 게다가 언젠가 누군가 Circle의 자손인 Ellipse 클래스를 작성하여 객체를 생성한다면 타원 타원=새 타원(…), 그런 다음 ellipse.moveTo(…)를 호출하면 타원이 새 위치로 이동됩니다. 그리고 이는 Ellipse 클래스에서 hide 및 show 메소드가 구현되는 방식에 따라 발생합니다. 클래스의 컴파일된 다형성 코드는 오래 전에 작동할 것입니다. 수치. 이러한 메소드에 대한 참조가 컴파일 타임에 moveTo 메소드의 코드에 배치되지 않는다는 사실로 다형성이 보장됩니다. 즉, moveTo 메소드가 호출되는 즉시 호출 객체의 클래스에서 해당 이름을 가진 메소드로 구성됩니다.

객체 지향 프로그래밍 언어에는 두 가지 유형이 있습니다. 동적 방법– 실제로 역동적이고 가상. 작동 원리에 따르면 완전히 유사하며 구현 기능만 다릅니다. 부르다 가상 메소드더 빠르게. 동적 호출은 속도가 느리지만 서비스 테이블은 동적 방법(DMT – Dynamic Methods Table)은 테이블보다 약간 적은 메모리를 차지합니다. 가상 메소드(VMT – 가상 방법 테이블).

도전처럼 보일 수도 있지만 동적 방법이름을 검색하는 데 시간이 오래 걸리기 때문에 시간 효율적이지 않습니다. 실제로 호출 중에는 이름 조회가 수행되지 않지만 언급된 가상(동적) 메서드 테이블을 사용하면 훨씬 더 빠른 메커니즘이 사용됩니다. 그러나 Java에서는 이러한 유형의 메소드 간에 구별이 없기 때문에 이러한 테이블 구현의 세부 사항에 대해서는 다루지 않을 것입니다.

6.8. 기본 클래스 객체

Object 클래스는 모든 Java 클래스의 기본 클래스입니다. 따라서 해당 필드와 메서드는 모두 상속되어 모든 클래스에 포함됩니다. Object 클래스에는 다음 메서드가 포함되어 있습니다.

  • 공개 부울 같음(객체 obj)– 메소드가 호출된 객체의 값과 매개변수 목록의 obj 참조를 통해 전달된 객체의 값이 동일한 경우 true를 반환합니다. 객체가 동일하지 않으면 false가 반환됩니다. Object 클래스에서 동등성은 참조 동등성으로 처리되며 비교 연산자 "==" 와 동일합니다. 그러나 자손에서는 이 방법을 재정의할 수 있으며 내용을 기준으로 개체를 비교할 수 있습니다. 예를 들어, 이는 쉘 숫자 클래스의 객체에서 발생합니다. 이는 다음과 같은 코드로 쉽게 확인할 수 있습니다.

    더블 d1=1.0,d2=1.0; System.out.println("d1==d2 ="+(d1==d2)); System.out.println("d1.equals(d2) ="+(d1.equals(d2)));

    출력의 첫 번째 줄은 d1==d2 =false 이고, 두 번째 줄은 d1 입니다. 같음(d2)=참

  • 공개 int hashCode()– 문제 해시 코드물체. 해시 코드는 요소와 관련된 조건부 고유 숫자 식별자입니다. 보안상의 이유로 객체의 주소를 응용 프로그램에 제공할 수 없습니다. 따라서 Java에서는 어떤 목적으로 객체 주소 테이블을 저장해야 하는 경우 해시 코드가 객체 주소를 대체합니다.
  • 보호된 개체 복제() CloneNotSupportedException 발생 – 메서드가 객체를 복사하고 객체의 생성된 복제본(중복)에 대한 링크를 반환합니다. Object 클래스의 자손에서는 이를 재정의하고 해당 클래스가 Clonable 인터페이스를 구현함을 나타내야 합니다. 복제할 수 없는 개체에서 메서드를 호출하려고 하면 다음과 같은 문제가 발생합니다. 예외 발생 CloneNotSupportedException("복제는 지원되지 않습니다."). 인터페이스와 예외 상황은 나중에 논의됩니다.

    복제에는 원본 객체의 필드 값을 일대일로 복제본에 복사하는 얕은(shallow) 복제와 다음 필드에 대해 새 객체를 생성하는 깊은(deep) 복제의 두 가지 유형이 있습니다. 참조 유형, 원래 필드가 참조하는 객체를 복제합니다. 얕은 복제에서는 원본과 복제본이 모두 동일한 개체를 참조합니다. 객체에 필드만 있는 경우 기본 유형, 얕은 복제와 깊은 복제 사이에는 차이가 없습니다. 복제 구현은 클래스를 개발하는 프로그래머가 수행합니다. 자동 복제 메커니즘은 없습니다. 그리고 선택할 복제 옵션을 결정해야 하는 것은 클래스 개발 단계입니다. 대부분의 경우에는 필수입니다. 심층 복제.

  • 공개 최종 클래스 getClass()– 클래스 유형의 메타객체에 대한 참조를 반환합니다. 도움을 받으면 객체가 속한 클래스에 대한 정보를 얻고 해당 클래스 메서드와 클래스 필드를 호출할 수 있습니다.
  • 보호된 무효 완료() Throwable – 객체가 파괴되기 전에 호출됩니다. 객체를 삭제하기 전에 몇 가지 보조 작업(파일 닫기, 메시지 표시, 화면에 무언가 그리기 등)을 수행해야 하는 Object의 하위 항목에서 재정의되어야 합니다. 이 방법은 해당 단락에 자세히 설명되어 있습니다.
  • 공개 문자열 toString()– 객체의 문자열 표현을 (가능한 한 적절하게) 반환합니다. Object 클래스에서 이 메서드는 개체의 정규화된 이름(패키지 이름 포함), "@" 문자, 개체의 16진수 해시 코드로 구성된 문자열을 생성합니다. 대부분의 표준 클래스는 이 메서드를 재정의합니다. 숫자 클래스의 경우 숫자의 문자열 표현이 반환되고, 문자열 클래스의 경우 문자열의 내용이 반환되고, 문자 클래스의 경우 문자 자체가 반환됩니다(해당 코드의 문자열 표현이 아닙니다!). 예를 들어 다음 코드 조각은

    객체 obj=new Object(); System.out.println(" obj.toString()은 "+obj.toString())을 제공합니다. 더블 d=new Double(1.0); System.out.println(" d.toString()은 "+d.toString())을 제공합니다. 문자 c="A"; System.out.println("c.toString()은 "+c.toString())을 제공합니다.

    결론을 내릴 것이다

    obj.toString()은 java.lang.Object@fa9cf를 제공합니다. d.toString()은 1.0을 제공합니다. c.toString()은 A를 제공합니다.

방법도 있습니다 통지(), 통지모두()및 메소드의 여러 오버로드된 변형 기다리다, 스레드와 함께 작동하도록 설계되었습니다. 이에 대해서는 스레드 섹션에서 설명합니다.

6.9. 디자이너. 예약어 super 및 this. 초기화 블록

이미 언급했듯이 Java의 객체는 new 예약어와 생성자(객체를 생성하고 생성된 객체의 필드를 초기화하는 특수 서브루틴)를 사용하여 생성됩니다. 반환 유형이 지정되지 않았으며 객체 메서드(객체가 아직 존재하지 않을 때 클래스 이름을 통해 호출됨)도 아니고 클래스 메서드(객체와 해당 필드는 this 참조를 통해 생성자에서 액세스할 수 있음)도 아닙니다. . 실제로 생성자는 new 연산자와 함께 생성되는 개체에 대한 참조를 반환하며 클래스 메서드와 개체 메서드의 기능을 결합한 특별한 유형의 메서드로 간주될 수 있습니다.

객체를 생성할 때 추가 초기화가 필요하지 않은 경우 기본적으로 모든 클래스에 존재하는 생성자를 사용할 수 있습니다. 매개변수 목록 없이 클래스 이름 뒤에 빈 괄호가 옵니다. 클래스를 개발할 때 자동으로 존재하는 생성자를 지정할 필요가 없습니다.

초기화가 필요한 경우 일반적으로 매개변수 목록이 있는 생성자가 사용됩니다. Dot 및 Circle 클래스에 대한 이러한 생성자의 예를 살펴보았습니다. Dot 및 Circle 클래스는 다음에서 상속되었습니다. 추상 수업, 생성자가 없었습니다. 비추상 클래스, 즉 이미 생성자가 있는 클래스(기본 생성자 포함)에서 상속이 있는 경우 특정성이 발생합니다. 생성자의 첫 번째 문은 슈퍼클래스에서 생성자를 호출해야 합니다. 하지만 이 클래스의 이름을 통하지 않고 예약어를 사용하여 수행됩니다. 감독자("슈퍼클래스"에서), 그 뒤에 상위 생성자에 필요한 매개변수 목록이 옵니다. 이 생성자는 슈퍼클래스(이전 상위 클래스 포함)에서 상속된 데이터 필드를 초기화합니다. 예를 들어 Circle 의 자손인 FilledCircle 클래스를 작성해 보겠습니다. 이 클래스의 인스턴스는 색칠된 원으로 그려집니다.

패키지 java_gui_example; import java.awt.*; public class FilledCircle extends Circle( /** FilledCircle의 새 인스턴스를 생성합니다 */ public FilledCircle(Graphics g,Color bgColor, int r,Color color) ( super(g,bgColor,r); this.color=color; ) public void show())( 색상 oldC=graphics.getColor(); 그래픽.setColor(색상); 그래픽.setXORMode(bgColor); 그래픽.fillOval(x,y,크기, 크기); 그래픽.setColor(oldC); 그래픽 . setPaintMode(); ) public void hide())( Color oldC=graphics.getColor();graphics.setColor(color);graphics.setXORMode(bgColor);graphics.fillOval(x,y,size, size) .setColor (oldC); 그래픽.setPaintMode();

일반적으로 복잡한 객체를 생성하는 논리는 객체의 상위 부분이 먼저 생성되고 초기화되어 Object 클래스에서 상속된 부분부터 시작하여 계층 구조를 따라 클래스 자체에 속한 부분으로 끝납니다. 이것이 일반적으로 생성자의 첫 번째 명령문이 조부모 생성자 super( 매개변수 목록), 상위 클래스에 속한 객체의 초기화되지 않은 부분에 액세스하면 예측할 수 없는 결과가 발생할 수 있기 때문입니다.

이번 수업에서는 이전 수업에 비해 좀 더 발전된 도형 그리기 및 "숨기기" 방법을 사용합니다. XOR(exclusive or) 그리기 모드를 기반으로 합니다. 이 모드는 setXORMode 메소드를 사용하여 설정됩니다. 이 경우 동일한 위치에 도형을 반복적으로 출력하면 출력 영역에서는 원본 이미지가 복원됩니다. 일반 페인팅 모드로의 전환은 setPaintMode 메소드를 사용하여 수행됩니다.

생성자에서 매우 자주 사용됩니다.

극단적인 수준의 정적 통신이 애플리케이션과 시스템 성능에 큰 긍정적인 영향을 미칠 수 있는 시스템이 점점 늘어나고 있습니다.

나는 종종 "임베디드 시스템"이라고 불리는 것에 대해 이야기하고 있는데, 그 중 다수는 현재 점점 더 범용 운영 체제를 사용하고 있으며 이러한 시스템은 상상할 수 있는 모든 것에 사용됩니다.

매우 일반적인 예는 Busybox를 사용하는 GNU/Linux 시스템을 사용하는 장치입니다. 저는 커널과 루트 파일 시스템을 모두 포함하는 부팅 가능한 i386(32비트) 시스템 이미지를 생성하여 NetBSD를 사용하여 이 작업을 최대한 수행했습니다. 이 이미지에는 모든 프로그램에 대한 하드 링크와 함께 정적으로 링크된(crunchgen을 통해) 바이너리 하나가 포함되어 있습니다. 포함하다 모두(마지막 개수는 274개) (툴체인을 제외한 대부분) 20개 미만입니다. 메가바이트(그리고 아마도 64MB의 메모리가 있는 시스템에서 매우 편안하게 실행될 것입니다(루트 파일 시스템이 압축되지 않고 완전히 RAM에 있는 경우에도). 하지만 테스트하기에 그렇게 작은 것을 찾을 수는 없습니다).

이전 게시물에서는 정적 링크 바이너리의 시작 시간이 더 빠르다고 언급했지만(훨씬 더 빠를 수 있음) 이는 전체 그림의 일부일 뿐이며, 특히 모든 개체 코드가 동일한 파일에 링크되어 있는 경우에는 더욱 그렇습니다. 운영 체제는 실행 파일에서 직접 스왑 요청 코드를 지원합니다. 이 이상적인 시나리오에서는 거의 모든 코드 페이지가 이미 메모리에 있고 요청된 프로그램이 한 번도 실행되지 않은 경우에도 셸(및 실행 중인 다른 백그라운드 프로세스에서 초기화)에서 사용되기 때문에 프로그램 시작 시간은 말 그대로 무시할 수 있습니다. 프로그램의 런타임 요구 사항을 충족하기 위해 로드된 메모리 페이지는 하나만 있을 수 있기 때문에 부팅 이후 실행되었습니다.

그러나 이것이 전부는 아닙니다. 또한 저는 일반적으로 모든 바이너리를 정적으로 연결하여 전체 개발 시스템을 위한 NetBSD 운영 체제 설치를 구축하고 사용합니다. 여기에는 엄청난 양의 디스크 공간이 필요하지만(툴체인 및 X11 정적 링크를 포함한 모든 항목을 포함하는 x86_64의 경우 총 ~6.6GB)(특히 ~2 .5GB에 대해 모든 프로그램에서 사용할 수 있는 전체 디버그 기호 테이블을 유지하는 경우) 결과는 전체적으로 여전히 더 빠르며 일부 작업은 라이브러리 코드 페이지를 교환하도록 설계된 일반적인 동적으로 링크된 시스템보다 적은 메모리를 사용합니다. 디스크는 저렴하고(빠른 디스크라도) 자주 사용하는 파일을 디스크에 캐싱하기 위한 메모리도 상대적으로 저렴하지만 CPU 주기는 실제로 그렇지 않으며 시작할 때마다 시작되는 각 프로세스에 대해 ld.so의 초기 수익을 지불하는 데 몇 시간이 걸립니다. 특히 개발 시스템의 컴파일러와 같이 동일한 프로그램이 반복해서 사용될 때 많은 프로세스를 실행해야 하는 작업의 CPU 주기 시간이 늘어납니다. 정적 번들 소프트웨어 프로그램은 전체 시스템에 대한 멀티캐스트 아키텍처를 생성하는 데 필요한 시간을 몇 시간 안에 줄일 수 있습니다. 아직 단일 크런치 생성 바이너리에 툴체인을 빌드하지 않았지만 그렇게 하면 CPU 캐시 이득으로 인해 빌드 시간이 더 많이 절약될 것이라고 생각합니다.

C++에서 바인딩

C++ 프로그래밍 언어 개발의 두 가지 주요 목표는 메모리 효율성과 실행 속도였습니다. 이는 특히 객체 지향 응용 프로그램을 위한 C 언어의 개선을 위한 것이었습니다. C++의 기본 원칙: 프로그래머가 이 속성을 사용하지 않는 경우 어떤 언어 속성도 추가 오버헤드(메모리 및 속도 모두에서)로 이어져서는 안 됩니다. 예를 들어, C++의 객체 지향이 모두 무시된다면 나머지는 기존 C만큼 빨라야 합니다. 따라서 C++의 대부분의 메서드가 동적으로(런타임 시) 아닌 정적으로(컴파일 시) 링크된다는 것은 놀라운 일이 아닙니다.

이 언어의 메소드 바인딩은 매우 복잡합니다. 일반 변수(포인터나 참조가 아닌)의 경우 이는 정적으로 수행됩니다. 그러나 포인터나 참조를 사용하여 개체를 지정할 때는 동적 바인딩이 사용됩니다. 후자의 경우 정적 또는 동적 유형의 메서드를 선택하는 결정은 해당 메서드가 virtual 키워드를 사용하여 선언되었는지 여부에 따라 결정됩니다. 이런 방식으로 선언된 경우 메시지 검색 메서드는 동적 클래스를 기반으로 하며 그렇지 않은 경우 정적 클래스를 기반으로 합니다. 동적 바인딩이 사용되는 경우에도 요청의 유효성은 수신자의 정적 클래스를 기반으로 컴파일러에 의해 결정됩니다.

예를 들어, 클래스 및 전역 변수에 대한 다음 설명을 고려하십시오.

printf("말을 할 수 없습니다");

개 클래스 : 공개 포유류

printf("우프우프");

printf("우프우프도 마찬가지입니다");

포유류 *fido = 새로운 개;

fred.speak() 표현식은 "말할 수 없습니다"를 인쇄하지만, Mammal 클래스의 해당 메서드가 virtual로 선언되지 않았기 때문에 fido->speak()를 호출하면 "말할 수 없습니다"도 인쇄됩니다. fido의 동적 유형이 Dog인 경우에도 컴파일러에서는 fido->bark() 표현식을 허용하지 않습니다. 그러나 변수의 정적 유형은 Mammal 클래스일 뿐입니다.

virtual이라는 단어를 추가하면:

가상 무효 말하기()

printf("말을 할 수 없습니다");

그런 다음 fido->speak() 표현식의 출력에서 ​​예상된 결과를 얻습니다.

C++ 언어의 비교적 최근 변경 사항은 객체의 동적 클래스를 인식하는 기능이 추가된 것입니다. 이는 RTTI(런타임 유형 식별) 시스템을 형성합니다.

RTTI 시스템에서 각 클래스에는 클래스에 대한 다양한 정보를 인코딩하는 typeinfo 유형의 관련 구조가 있습니다. 이 구조체의 데이터 필드 중 하나인 이름 데이터 필드에는 클래스 이름이 텍스트 문자열로 포함됩니다. typeid 함수는 데이터 유형 정보를 구문 분석하는 데 사용할 수 있습니다. 따라서 다음 명령은 fido에 대한 동적 데이터 유형인 "Dog" 문자열을 인쇄합니다. 이 예에서는 인수가 포인터 자체가 아니라 포인터가 참조하는 값이 되도록 fido 포인터 변수를 역참조해야 합니다.

시합<< «fido is a» << typeid(*fido).name() << endl;

before 멤버 함수를 사용하여 데이터 형식 정보가 있는 구조가 다른 구조와 연결된 클래스의 하위 클래스인지 여부를 물어볼 수도 있습니다. 예를 들어, 다음 두 문은 true와 false를 생성합니다.

if (typeid(*fido).before (typeid(fred)))…

if (typeid(fred).before (typeid(lassie)))…

RTTI 시스템 이전에는 표준 프로그래밍 트릭은 인스턴스가 될 메소드를 클래스 계층 구조에 명시적으로 코딩하는 것이었습니다. 예를 들어, Animal 유형의 변수 값을 테스트하여 Cat 유형인지 Dog 유형인지 확인하려면 다음 메소드 시스템을 정의할 수 있습니다.

가상 정수 isaDog()

가상 정수 isaCat()

개 클래스 : 공개 포유류

가상 정수 isaDog()

클래스 고양이: 공개 포유류

가상 정수 isaCat()

이제 fido->isaDog() 명령을 사용하여 fido의 현재 값이 Dog 유형의 값인지 확인할 수 있습니다. 0이 아닌 값이 반환되면 변수 유형을 원하는 데이터 유형으로 캐스팅할 수 있습니다.

정수가 아닌 포인터를 반환함으로써 하위 클래스 테스트와 유형 캐스팅을 결합합니다. 이는 우리가 간략하게 설명할 Dynamic_cast라는 RTTI 시스템의 또 다른 부분과 유사합니다. Mammal 클래스의 함수가 Dog에 대한 포인터를 반환하는 경우 Dog 클래스를 미리 선언해야 합니다. 할당 결과는 널 포인터이거나 Dog 클래스에 대한 유효한 참조입니다. 따라서 결과를 계속 확인해야 하지만 유형 캐스팅이 필요하지 않습니다. 이는 다음 예에 나와 있습니다.

개 클래스; // 예비 설명

가상 개* isaDog()

가상 고양이* isaCat()

개 클래스 : 공개 포유류

가상 개* isaDog()

클래스 고양이: 공개 포유류

가상 고양이* isaCat()

운영자 lassie = fido->isaDog(); 이제 우리는 항상 그렇게 할 것입니다. 결과적으로 lassie는 fido에 동적 클래스 Dog가 있는 경우에만 0이 아닌 값으로 설정됩니다. Dog가 fido를 소유하지 않은 경우 lassie에는 널 포인터가 할당됩니다.

lassie = fido->isaDog();

... // fido는 실제로 Dog 유형입니다.

... // 할당이 작동하지 않았습니다.

... // fido는 Dog 유형이 아닙니다.

프로그래머는 이 방법을 사용하여 다형성을 역전시킬 수 있지만 이 방법의 단점은 부모 클래스와 자식 클래스 모두에 메서드를 추가해야 한다는 것입니다. 많은 하위 항목이 하나의 공통 상위 클래스에서 파생되는 경우 메서드가 다루기 어려워집니다. 상위 클래스에서 변경이 허용되지 않으면 이 기술은 전혀 불가능합니다.

이러한 문제가 자주 발생하므로 일반적인 해결책을 찾았습니다. Dynamic_cast 템플릿 함수는 유형을 템플릿 인수로 사용하고 위에 정의된 함수와 마찬가지로 인수 값(유형 캐스트가 적합한 경우) 또는 null 값(유형 캐스트가 불법인 경우)을 반환합니다. 이전 예제에서 수행한 것과 동일한 할당을 다음과 같이 작성할 수 있습니다.

// fido가 개인 경우에만 변환

아가씨 = Dynamic_cast< Dog* >(피도);

// 캐스트가 성공했는지 확인합니다.

C++에는 세 가지 캐스트 유형(static_cast, const_cast 및 reinterpret_cast)이 추가되었지만 특수한 경우에 사용되므로 여기서는 설명하지 않습니다. 프로그래머는 이전 유형의 캐스팅 메커니즘 대신 더 안전한 옵션으로 사용하는 것이 좋습니다.

2. 디자인 부분

제본- 특정 기능 호출을 프로그램 코드로 대체 - 수업 방법. 파생 클래스에만 의미가 있습니다.

일반적으로 컴파일러에는 어떤 함수가 의미되는지 결정하는 데 필요한 정보가 있습니다. 예를 들어, 프로그램이 obj.f()에 대한 호출을 발견하면 컴파일러는 대상 obj의 유형에 따라 고유하게 f() 함수를 선택합니다. 프로그램이 클래스 인스턴스(ptr->f())에 대한 포인터를 사용하는 경우 함수 - 클래스 메서드의 선택은 포인터 유형에 따라 결정됩니다.

함수 선택이 컴파일 타임에 완료되면 다음을 처리합니다. 정적 연결.

이 경우 기본 클래스에 대한 포인터에 파생 클래스 인스턴스의 주소 값이 할당되더라도 기본 클래스에 대한 포인터에 대해 기본 클래스의 메서드인 함수가 호출됩니다.

프로그램 실행 단계에서 기능 선택을 한다면, 동적 연결.

이 경우 프로그램 실행 중에 기본 클래스에 대한 포인터에 기본 클래스 인스턴스의 주소가 할당되면 기본 클래스 메서드가 호출됩니다. 기본 클래스에 대한 포인터에 파생 클래스 인스턴스의 주소가 할당되면 파생 클래스의 메서드가 호출됩니다.

가상 기능

기본적으로 파생 클래스는 정적으로 연결됩니다. 클래스 메소드에 동적 바인딩을 사용하려면 해당 메소드를 선언해야 합니다. 가상 .

가상 기능:

    기본 클래스의 프로토타입에 virtual 키워드가 있습니다.

    필수 클래스 멤버 기능:

    모든 파생 클래스에는 동일한 프로토타입이 있어야 합니다. 파생 클래스에 virtual이라는 단어를 지정할 필요는 없습니다.

파생 클래스의 메서드가 기본 클래스와 이름은 같지만 매개변수 목록이 다른 경우 함수가 오버로드된 것입니다.

예: 점 및 원 클래스.

가상 무효 인쇄();

클래스 서클: 공개 포인트(

무효 인쇄(); // 가상 무효화 가능 print();

무효 포인트::인쇄()

시합<< "Point (" << x << ", " << y << ")";

무효 서클::인쇄()

시합<< "Circle with center in "; Point::print();

시합<< "and radius " << rad;

용법:

포인트 p1(3,5), p2(1,1), *pPtr;

원 c1(1), c2(p2, 1);

pPtr = pPtr->print(); // 가져오기: 점(3, 5)

pPtr = pPtr->print(); // 얻다:

점 (1, 1)에 중심이 있고 반지름이 1인 원

동적 바인딩 예: 목록

동적 바인딩의 가장 일반적인 용도는 기본 클래스에 대한 포인터를 포함하는 컨테이너 클래스와 함께 사용하는 것입니다. 이러한 컨테이너 클래스에는 기본 클래스와 파생 클래스 모두에 관련된 정보가 포함될 수 있습니다.

점과 원을 모두 포함하는 목록이라는 예를 생각해 봅시다.

// 생성자

항목():정보(NULL), 다음(NULL)()

항목(포인트 *p):정보(p), next(NULL)()

목록():머리(NULL)()

void insert(Point *p)(p->next = 헤드; 헤드 = p;)

무효 목록::인쇄()

for(항목 *cur = 선두; cur; cur = 현재->다음)(

현재->정보->print();

시합<< endl;

수업 사용 :

포인트 *p = new Point(1,2);

mylist.insert(p);

p = 새로운 사이클(1,2,1);

mylist.insert(p);

점 (1, 2)에 중심이 있고 반경이 1인 원

2010년 3월 23일 12월 5일

PHP 5.3에는 후기 정적 바인딩이라는 흥미로운 기능이 도입되었습니다. 다음은 공식 매뉴얼의 설명을 약간 자유롭게 번역한 것입니다.

PHP 5.3.0부터 언어에는 정적 상속 컨텍스트에서 호출 가능한 클래스를 참조하는 데 사용할 수 있는 후기 정적 바인딩이라는 기능이 도입되었습니다.

이 기능을 "후기 정적 바인딩"이라고 합니다. "늦은 바인딩"은 static::이 메서드가 정의된 클래스를 기준으로 확인되지 않고 런타임에 평가된다는 것을 의미합니다. "정적 바인딩"은 정적 메서드 호출에 사용할 수 있음을 의미합니다(단, 이에 국한되지는 않음).

제한 사항 자체::

예제 #1: self:: 사용

예제는 다음을 출력합니다:

후기 정적 바인딩 사용

나중에 정적 바인딩에서는 런타임에 원래 호출된 클래스를 참조하는 키워드를 도입하여 이 제한 사항을 해결하려고 합니다. 즉, 이전 예의 test()에서 B를 참조할 수 있도록 하는 키워드입니다. 새로운 단어를 도입하지 않고 이미 예약된 static 을 사용하기로 결정했습니다.

예제 #2: static::의 간단한 사용

예제는 다음을 출력합니다:

참고: static::은 $this처럼 정적 메소드에서 작동하지 않습니다! $this->는 상속 규칙을 따르지만 static::은 그렇지 않습니다. 이 구별은 아래에서 명확히 설명됩니다.

예제 #3: 비정적 컨텍스트에서 static:: 사용

시험(); ?>

예제는 다음을 출력합니다:

참고: 후기 정적 바인딩은 통화 해결 프로세스를 중지합니다. parent:: 또는 self:: 키워드를 사용한 정적 호출은 호출 정보를 전달합니다.

예 #4: 통화 전달 및 전달 안 함

예제는 출력됩니다

엣지 케이스

콜백이나 매직 메소드 등 PHP에서 메소드를 호출하는 방법은 다양합니다. 늦은 정적 바인딩은 런타임에 해결되므로 소위 극단적인 경우에 예상치 못한 결과가 발생할 수 있습니다.

예제 #5 매직 메소드의 후기 정적 바인딩

푸; ?>