Con trỏ lớp C++ tới chính nó

Một khai báo lớp có thể chứa đối tượng tĩnh của kiểu tự, nó cũng có thể có con trỏ tới kiểu tự, nhưng nó không thể có đối tượng không tĩnh kiểu tự

Ví dụ, chương trình sau hoạt động tốt




    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
65

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
66

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
68
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
69
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
70

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
72
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
73

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
660
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
661
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
662

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
665

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
667

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
669
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
670

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
671

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
673

________ 174 ________ 1675 ________ 1676

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
678
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
679

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
680

Và chương trình sau đây cũng hoạt động tốt




    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
681

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
66

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
68
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
69
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
70

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
72
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
73

________ 174 ________ 1691 ________ 1692

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
665

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
667

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
669
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
670

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
671

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
673

________ 174 ________ 1675 ________ 1676

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
678
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
679

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
680

Nhưng chương trình sau tạo ra lỗi biên dịch "trường `self' có kiểu không đầy đủ"




    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
671

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
66

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
68
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
69
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
70

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
72
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
73

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
721
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
722

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
665

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
667

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
67

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
669
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
670

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
671

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
673

________ 174 ________ 1675 ________ 1676

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
74
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
678
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
679

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
680

Nếu một đối tượng không tĩnh là thành viên thì phần khai báo của lớp không đầy đủ và trình biên dịch không có cách nào tìm ra kích thước của các đối tượng trong lớp.
Biến tĩnh không đóng góp vào kích thước của đối tượng. Vì vậy, không có vấn đề gì trong việc tính toán kích thước với các biến tĩnh kiểu tự.
Đối với trình biên dịch, tất cả các con trỏ đều có kích thước cố định bất kể loại dữ liệu mà chúng đang trỏ tới, vì vậy cũng không có vấn đề gì với điều này.

Cảm ơn Manish Jain và Venki vì sự đóng góp của họ cho bài đăng này. Vui lòng viết bình luận nếu bạn thấy bất cứ điều gì không chính xác hoặc bạn muốn chia sẻ thêm thông tin về chủ đề thảo luận ở trên

Tồn tại các tình huống mà chúng ta không cần một con trỏ tới một đối tượng mà là một con trỏ tới các thành viên của một lớp. Con trỏ tới các thành viên có thể được sử dụng một cách hữu ích để định cấu hình hành vi của các đối tượng của lớp. Tùy thuộc vào thành viên nào mà con trỏ tới thành viên trỏ tới đối tượng sẽ hiển thị hành vi nhất định

Mặc dù con trỏ tới các thành viên có công dụng của chúng, tính đa hình thường có thể được sử dụng để thực hiện hành vi có thể so sánh được. Hãy xem xét một lớp học có một thành viên

char const * ( String::somefun ) () const
2 thực hiện một trong một loạt các hành vi thay thế. Thay vì chọn hành vi lựa chọn tại thời điểm xây dựng đối tượng, lớp có thể sử dụng giao diện của một số lớp cơ sở (trừu tượng), chuyển một đối tượng của một số lớp dẫn xuất tới hàm tạo của nó và do đó có thể định cấu hình hành vi của nó. Điều này cho phép cấu hình dễ dàng, có thể mở rộng và linh hoạt, nhưng việc truy cập vào các thành viên dữ liệu của lớp sẽ kém linh hoạt hơn và có thể yêu cầu sử dụng các khai báo `friend'. Trong những trường hợp như vậy, con trỏ tới các thành viên thực sự có thể được ưu tiên hơn vì điều này cho phép cấu hình (hơi kém linh hoạt hơn) cũng như truy cập trực tiếp vào các thành viên dữ liệu của một lớp

Vì vậy, sự lựa chọn rõ ràng là giữa một bên là cấu hình dễ dàng và mặt khác là dễ dàng truy cập vào các thành viên dữ liệu của lớp. Trong chương này, chúng ta sẽ tập trung vào các con trỏ tới các thành viên, điều tra xem những con trỏ này cung cấp những gì

16. 1. Con trỏ đến các thành viên. một ví dụ

Biết cách con trỏ tới biến và đối tượng được sử dụng không trực quan dẫn đến khái niệm con trỏ tới thành viên. Ngay cả khi các kiểu trả về và kiểu tham số của các hàm thành viên được tính đến, vẫn có thể dễ dàng gặp phải những điều bất ngờ. Ví dụ, hãy xem xét lớp sau.
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };

Đối với lớp này, không thể để

char const * ( String::somefun ) () const
3 trỏ đến hàm thành viên
char const * ( String::somefun ) () const
4 vì không thể cho
char const * ( String::somefun ) () const
5 địa chỉ của hàm thành viên
char const * ( String::somefun ) () const
6

Một trong những lý do tại sao điều này không hoạt động là biến

char const * ( String::somefun ) () const
5 có phạm vi toàn cầu (nó là một con trỏ tới một hàm, không phải là một con trỏ tới một hàm trong
char const * ( String::somefun ) () const
8), trong khi hàm thành viên
char const * ( String::somefun ) () const
6 được định nghĩa trong lớp
char const * ( String::somefun ) () const
8 và . Việc
char const * ( String::somefun ) () const
5 là thành viên dữ liệu của lớp
char const * ( String::somefun ) () const
8 không liên quan ở đây. Theo định nghĩa của
char const * ( String::somefun ) () const
5, nó trỏ đến một hàm tồn tại ở đâu đó bên ngoài lớp

Do đó, để định nghĩa một con trỏ tới một thành viên (dữ liệu hoặc hàm, nhưng thường là hàm) của một lớp, phạm vi của con trỏ phải chỉ ra phạm vi lớp. Làm như vậy, một con trỏ tới thành viên

char const * ( String::somefun ) () const
4 được định nghĩa như thế này

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
5

Vì vậy, bằng cách thêm tiền tố vào thành viên dữ liệu con trỏ

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
06 bởi
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
07, nó được định nghĩa là một con trỏ trong ngữ cảnh của lớp
char const * ( String::somefun ) () const
8. Theo định nghĩa của nó, nó là một con trỏ tới một hàm trong lớp
char const * ( String::somefun ) () const
8, không mong đợi các đối số, không sửa đổi dữ liệu của đối tượng và trả về một con trỏ tới các ký tự không đổi

16. 2. Xác định con trỏ tới các thành viên

Con trỏ tới các thành viên được xác định bằng cách đặt trước ký hiệu con trỏ bình thường với lớp thích hợp cộng với toán tử phân giải phạm vi. Do đó, trong phần trước, chúng tôi đã sử dụng
char const * ( String::somefun ) () const
00 để chỉ ra rằng
char const * ( String::somefun ) () const
5
  • là một con trỏ (
        class String
        {
            char const *(*d_sp)() const;
    
            public:
                char const *get() const;
        };
    
    06);
  • chỉ vào thứ gì đó trong lớp
    char const * ( String::somefun ) () const
    
    8 (
    char const * ( String::somefun ) () const
    
    04);
  • là một con trỏ tới một hàm
    char const * ( String::somefun ) () const
    
    05, trả về một
    char const * ( String::somefun ) () const
    
    06 (
    char const * ( String::somefun ) () const
    
    00)

Do đó, nguyên mẫu của một chức năng phù hợp là

char const * ( String::somefun ) () const
8

đó là bất kỳ hàm không tham số

char const * ( String::somefun ) () const
05 nào trong lớp
char const * ( String::somefun ) () const
8, trả về một giá trị
char const * ( String::somefun ) () const
06

Khi xác định con trỏ tới các thành viên, quy trình chuẩn để xây dựng con trỏ tới hàm vẫn có thể được áp dụng

  • đặt dấu ngoặc quanh tên hàm đủ điều kiện (i. e. , tiêu đề của hàm, bao gồm tên lớp của hàm).
    char const * ( String::somefun ) () const
    
  • Đặt một ký tự con trỏ (dấu sao (______331)) ngay trước tên hàm.
        class String
        {
            char const *(*d_sp)() const;
    
            public:
                char const *get() const;
        };
    
    0
  • Thay tên hàm bằng tên biến con trỏ.
    char const * ( String::somefun ) () const
    
    0

Đây là một ví dụ khác, xác định một con trỏ tới một thành viên dữ liệu. Giả sử lớp

char const * ( String::somefun ) () const
8 chứa một thành viên
char const * ( String::somefun ) () const
33. Làm thế nào để xây dựng một con trỏ tới thành viên này?

  • đặt dấu ngoặc đơn xung quanh tên biến đủ điều kiện.
    char const * ( String::somefun ) () const
    
    3
  • Đặt một ký tự con trỏ (dấu sao (______331)) ngay trước tên biến.
    char const * ( String::somefun ) () const
    
    0
  • Thay tên biến bằng tên của biến con trỏ.
    char const * ( String::somefun ) () const
    
    1

    Trong trường hợp này, dấu ngoặc đơn là thừa và có thể bỏ qua

    char const * ( String::somefun ) () const
    
    2

Ngoài ra, một quy tắc ngón tay cái rất đơn giản là

  • Xác định một bình thường (i. e. , toàn cầu) biến con trỏ,
  • Đặt tiền tố tên lớp cho ký tự con trỏ, sau khi bạn chỉ vào nội dung nào đó bên trong lớp
Ví dụ, con trỏ sau tới một hàm toàn cục____150

trở thành một con trỏ tới một hàm thành viên sau khi thêm tiền tố vào phạm vi lớp

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
51

Không có gì buộc chúng tôi phải xác định con trỏ tới các thành viên trong các lớp mục tiêu (

char const * ( String::somefun ) () const
8) của chúng. Con trỏ tới các thành viên có thể được định nghĩa trong lớp đích của chúng (để chúng trở thành thành viên dữ liệu) hoặc trong lớp khác hoặc dưới dạng biến cục bộ hoặc biến toàn cục. Trong tất cả các trường hợp này, con trỏ tới biến thành viên có thể được cung cấp địa chỉ của loại thành viên mà nó trỏ tới. Phần quan trọng là một con trỏ tới thành viên có thể được khởi tạo hoặc gán mà không yêu cầu sự tồn tại của một đối tượng của lớp đích của con trỏ

Việc khởi tạo hoặc gán địa chỉ cho một con trỏ như vậy chỉ cho biết con trỏ trỏ tới thành viên nào. Đây có thể được coi là một số loại địa chỉ tương đối; . Không có đối tượng nào được yêu cầu khi con trỏ tới các thành viên được khởi tạo hoặc gán. Mặc dù được phép khởi tạo hoặc gán một con trỏ cho thành viên, nhưng (tất nhiên) không thể gọi các thành viên đó mà không chỉ định một đối tượng thuộc loại chính xác

Trong ví dụ sau đây, việc khởi tạo và gán cho con trỏ tới các thành viên được minh họa (vì mục đích minh họa, tất cả các thành viên của lớp

char const * ( String::somefun ) () const
36 được định nghĩa là
char const * ( String::somefun ) () const
37). Trong ví dụ này, toán tử
char const * ( String::somefun ) () const
38 được sử dụng để xác định địa chỉ của các thành viên. Các toán tử này cũng như phạm vi lớp được yêu cầu. Ngay cả khi được sử dụng bên trong triển khai thành viên

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
52Điều này không liên quan gì đặc biệt. Sự khác biệt với con trỏ ở phạm vi toàn cầu là chúng ta hiện đang giới hạn bản thân trong phạm vi của lớp
char const * ( String::somefun ) () const
36. Do hạn chế này, tất cả các định nghĩa con trỏ và tất cả các biến có địa chỉ được sử dụng phải được cung cấp phạm vi lớp
char const * ( String::somefun ) () const
36

Con trỏ tới thành viên cũng có thể được sử dụng với hàm thành viên

char const * ( String::somefun ) () const
01. Không yêu cầu cú pháp đặc biệt khi trỏ đến các thành viên ảo. Việc xây dựng, khởi tạo và gán con trỏ được thực hiện giống hệt với cách thực hiện với các thành viên không ảo

16. 3. Sử dụng con trỏ tới các thành viên

Sử dụng con trỏ tới thành viên để gọi một hàm thành viên yêu cầu sự tồn tại của một đối tượng của lớp thành viên mà con trỏ tới thành viên tham chiếu tới. Với các con trỏ hoạt động ở phạm vi toàn cầu, toán tử hủy hội nghị
char const * ( String::somefun ) () const
31 được sử dụng. Với con trỏ tới đối tượng, toán tử bộ chọn trường hoạt động trên con trỏ (
char const * ( String::somefun ) () const
03) hoặc bộ chọn trường hoạt động trên đối tượng (
char const * ( String::somefun ) () const
04) có thể được sử dụng để chọn thành viên thích hợp

Để sử dụng một con trỏ tới thành viên kết hợp với một đối tượng, bộ chọn trường con trỏ tới thành viên (

char const * ( String::somefun ) () const
05) phải được chỉ định. Để sử dụng một con trỏ tới một thành viên thông qua một con trỏ tới một đối tượng, `bộ chọn trường con trỏ tới thành viên thông qua một con trỏ tới một đối tượng' (
char const * ( String::somefun ) () const
06) phải được chỉ định. Hai toán tử này kết hợp các khái niệm về lựa chọn trường (các phần
char const * ( String::somefun ) () const
04 và
char const * ( String::somefun ) () const
03) để tiếp cận trường thích hợp trong một đối tượng và hủy bỏ tham chiếu. một hoạt động hủy đăng ký được sử dụng để tiếp cận hàm hoặc biến con trỏ tới thành viên trỏ tới

Sử dụng ví dụ từ phần trước, hãy xem cách chúng ta có thể sử dụng con trỏ tới hàm thành viên và con trỏ tới thành viên dữ liệu

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
53Chúng tôi lưu ý
  • Tại (1) đối tượng
    char const * ( String::somefun ) () const
    
    36 và (ở dòng tiếp theo) một con trỏ tới đối tượng đó được xác định
  • Tại (2) chúng tôi chỉ định một đối tượng (và do đó là toán tử
    char const * ( String::somefun ) () const
    
    05) để tiếp cận thành viên
    char const * ( String::somefun ) () const
    
    11 trỏ tới. Thành viên này được cho một giá trị
  • Tại (3) cùng một thành viên được gán một giá trị khác, nhưng lần này sử dụng con trỏ tới đối tượng
    char const * ( String::somefun ) () const
    
    36. Do đó, chúng tôi sử dụng toán tử
    char const * ( String::somefun ) () const
    
    06
  • Tại (4)
    char const * ( String::somefun ) () const
    
    05 và
    char const * ( String::somefun ) () const
    
    06 được sử dụng một lần nữa, lần này để gọi một hàm thông qua một con trỏ tới thành viên. Vì danh sách đối số của hàm có mức độ ưu tiên cao hơn con trỏ tới toán tử bộ chọn trường thành viên, toán tử sau phải được bảo vệ bằng dấu ngoặc đơn
Con trỏ tới các thành viên có thể được sử dụng một cách hữu ích trong các tình huống trong đó một lớp có một thành viên hành xử khác nhau tùy thuộc vào cài đặt cấu hình. Xem xét một lần nữa lớp
char const * ( String::somefun ) () const
16 từ phần 9. 3.
char const * ( String::somefun ) () const
16 định nghĩa các thành viên dữ liệu chứa tên, địa chỉ và số điện thoại của một người. Giả sử chúng tôi muốn xây dựng cơ sở dữ liệu
char const * ( String::somefun ) () const
16 của nhân viên. Cơ sở dữ liệu nhân viên có thể được truy vấn, nhưng tùy thuộc vào loại người truy vấn cơ sở dữ liệu, tên, tên và số điện thoại hoặc tất cả thông tin được lưu trữ về người đó sẽ được cung cấp. Điều này ngụ ý rằng một hàm thành viên như
char const * ( String::somefun ) () const
19 phải trả về một cái gì đó như `
char const * ( String::somefun ) () const
20' trong trường hợp người truy vấn cơ sở dữ liệu không được phép xem địa chỉ của người đó và địa chỉ thực trong các trường hợp khác

Cơ sở dữ liệu nhân viên được mở chỉ định một đối số phản ánh trạng thái của nhân viên muốn thực hiện một số truy vấn. Trạng thái có thể phản ánh vị trí của họ trong tổ chức, chẳng hạn như

char const * ( String::somefun ) () const
21,
char const * ( String::somefun ) () const
22,
char const * ( String::somefun ) () const
23 hoặc
char const * ( String::somefun ) () const
24. Hai loại đầu tiên được phép xem tất cả thông tin về nhân viên,
char const * ( String::somefun ) () const
23 được phép xem số điện thoại của nhân viên, trong khi
char const * ( String::somefun ) () const
24 chỉ được phép xác minh xem một người có thực sự là thành viên của tổ chức hay không

Bây giờ chúng tôi xây dựng một thành viên

char const * ( String::somefun ) () const
27 trong lớp cơ sở dữ liệu. Một triển khai tiêu chuẩn của lớp này có thể là

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
54

Mặc dù không mất nhiều thời gian, nhưng dù sao thì

char const * ( String::somefun ) () const
28 vẫn phải được đánh giá mỗi khi
char const * ( String::somefun ) () const
29 được gọi. Thay vì sử dụng một switch, chúng ta có thể định nghĩa một thành viên
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
500 như một con trỏ tới một hàm thành viên của lớp
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501 trả về một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
502 và mong đợi một con trỏ tới một
char const * ( String::somefun ) () const
16 làm đối số của nó

Thay vì đánh giá công tắc, con trỏ này có thể được sử dụng để trỏ tới

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
504,
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
505 hoặc
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
506. Hơn nữa, hàm thành viên mà con trỏ trỏ tới sẽ được biết vào thời điểm đối tượng
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501 được xây dựng và do đó, giá trị của nó chỉ cần được xác định một lần (tại thời điểm xây dựng đối tượng PersonData)

Đã khởi tạo

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
500, chức năng thành viên
char const * ( String::somefun ) () const
29 hiện được triển khai đơn giản như

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
55

Thành viên

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
500 được định nghĩa như sau (trong lớp
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501, bỏ qua các thành viên khác)

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
56

Cuối cùng, hàm khởi tạo khởi tạo

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
500. Điều này có thể được thực hiện bằng cách sử dụng một công tắc đơn giản

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
57

Lưu ý cách xác định địa chỉ của các hàm thành viên. Phạm vi của lớp

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501 phải được chỉ định, mặc dù chúng ta đã ở trong một hàm thành viên của lớp
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501

Vì các giá trị

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
515 đã được biết, nên cũng có thể dễ dàng tránh được giá trị
char const * ( String::somefun ) () const
28 trong hàm tạo ở trên bằng cách xác định một mảng tĩnh gồm các con trỏ tới các hàm. Lớp
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
501 định nghĩa mảng tĩnh

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
58

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
518 có thể được khởi tạo thời gian biên dịch

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
59

Hàm tạo, thay vì sử dụng

char const * ( String::somefun ) () const
28, giờ đây gọi trực tiếp thành phần được yêu cầu từ phần tử mảng thích hợp

char const * ( String::somefun ) () const
80

Một ví dụ sử dụng con trỏ tới thành viên dữ liệu được cung cấp trong phần 19. 1. 61, trong ngữ cảnh của thuật toán di truyền ________ 1520

16. 4. Con trỏ tới các thành viên tĩnh

Các thành viên tĩnh của một lớp có thể được sử dụng mà không cần có sẵn một đối tượng của lớp của chúng. Các thành viên tĩnh công khai có thể được gọi giống như các hàm miễn phí, mặc dù tên lớp của chúng phải được chỉ định khi chúng được gọi

Giả sử một lớp

char const * ( String::somefun ) () const
8 có hàm thành viên tĩnh công khai
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
522, trả về số lượng đối tượng chuỗi được tạo cho đến nay. Sau đó, không sử dụng bất kỳ đối tượng
char const * ( String::somefun ) () const
8 nào, hàm
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
524 có thể được gọi

char const * ( String::somefun ) () const
81

Các thành viên tĩnh công khai có thể được gọi giống như các hàm tự do (nhưng xem thêm phần 8. 2. 1). Các thành viên tĩnh riêng tư chỉ có thể được gọi trong ngữ cảnh của lớp của chúng, bởi các hàm thành viên hoặc bạn bè của lớp chúng

Vì các thành viên tĩnh không có đối tượng liên quan, địa chỉ của chúng có thể được lưu trữ trong các biến con trỏ hàm thông thường, hoạt động ở cấp độ toàn cầu. Con trỏ tới thành viên không thể được sử dụng để lưu trữ địa chỉ của thành viên tĩnh. Thí dụ

char const * ( String::somefun ) () const
82

16. 5. kích thước con trỏ

Một đặc điểm thú vị của con trỏ tới thành viên là kích thước của chúng khác với kích thước của con trỏ 'bình thường'. Xét chương trình nhỏ sau.
char const * ( String::somefun ) () const
83Trên kiến ​​trúc 32 bit, một con trỏ tới hàm thành viên yêu cầu tám byte, trong khi các loại con trỏ khác yêu cầu bốn byte (Sử dụng trình biên dịch g++ của GNU)

Kích thước con trỏ hiếm khi được sử dụng một cách rõ ràng, nhưng kích thước của chúng có thể gây nhầm lẫn trong các câu lệnh như

char const * ( String::somefun ) () const
84

Tất nhiên,

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
525 có thể không phải là công cụ phù hợp để hiển thị giá trị của các con trỏ cụ thể C++ này. Các giá trị của các con trỏ này có thể được chèn vào các luồng khi sử dụng một _____1526, diễn giải lại các con trỏ 8 byte dưới dạng một chuỗi các giá trị size_t ____

char const * ( String::somefun ) () const
85

Nhưng tại sao kích thước của chúng khác với kích thước của con trỏ thông thường? . Nó có nguồn gốc từ

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
529 và
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
530. Do đó, một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
531 chứa cả một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
532 và một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
533. Một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
531 sẽ được tổ chức như trong hình 23

Con trỏ lớp C++ tới chính nó
Hình 23. tiêu chuẩn. tổ chức đối tượng fstream

Trong

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
535, lớp cơ sở đầu tiên là
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
536 và lớp cơ sở thứ hai là
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
530. Nhưng cũng có thể là ngược lại, như minh họa trong
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
538. đầu tiên là
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
530, sau đó là
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
529. Và đó là mấu chốt của bánh quy

Nếu chúng ta có một đối tượng

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
541 và thực hiện
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
542, thì chúng ta gọi hàm
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
543. Nhưng nếu chúng ta làm
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
544, thì chúng ta gọi hàm
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
545. Các chức năng này có địa chỉ riêng của chúng, giả sử &seekg và &seekp. Nhưng khi chúng ta gọi một hàm thành viên (như
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
544) thì những gì chúng ta thực sự đang làm là
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
547

Nhưng vấn đề ở đây là

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
548 không thể hiện đúng địa chỉ đối tượng.
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
549 hoạt động trên một
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
533, và đối tượng đó không bắt đầu từ
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
548, vì vậy (trong
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
535), tại
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
553

Vì vậy, trình biên dịch, khi gọi một hàm thành viên của lớp bằng cách sử dụng tính kế thừa, phải thực hiện hiệu chỉnh vị trí tương đối của một đối tượng mà chúng ta đang gọi các thành viên.

Tuy nhiên, khi chúng tôi xác định một cái gì đó như

char const * ( String::somefun ) () const
86

và sau đó thực hiện

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
554, trình biên dịch không còn biết chức năng nào thực sự được gọi. nó chỉ nhận địa chỉ của hàm. Để giải quyết vấn đề của trình biên dịch, dịch chuyển (đối với vị trí của đối tượng ofstream) hiện được lưu trữ trong chính con trỏ thành viên. Đó là một lý do tại sao trường dữ liệu bổ sung là cần thiết khi sử dụng con trỏ hàm

Dưới đây là một minh họa cụ thể. trước tiên, chúng tôi xác định 2 cấu trúc, mỗi cấu trúc có một hàm thành viên (tất cả nội tuyến, sử dụng triển khai một dòng để tiết kiệm dung lượng)

char const * ( String::somefun ) () const
87

Sau đó, chúng tôi xác định C, có nguồn gốc từ cả A (đầu tiên) và B (tiếp theo) (có thể so sánh với

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
531, nhúng
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
532 và
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
533)

char const * ( String::somefun ) () const
88

Tiếp theo, trong

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
558, chúng tôi xác định các đối tượng của hai hiệp hội khác nhau và gán địa chỉ của
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
559 cho các trường
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
560 của chúng, nhưng
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
561 xem nó như một thành viên trong thế giới
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
562, trong khi
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
563 xem nó như một thành viên trong thế giới
    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
564

Khi các trường con trỏ của liên kết đã được gán, các mảng value[] của chúng được sử dụng để hiển thị nội dung của các trường ptr (xem bên dưới)

char const * ( String::somefun ) () const
89

Khi chạy chương trình này, chúng ta thấy

char const * ( String::somefun ) () const
0

(giá trị địa chỉ của bạn (giá trị đầu tiên trên hai dòng) có thể khác nhau). Lưu ý rằng địa chỉ của các hàm giống nhau, nhưng vì trong thế giới C, đối tượng B nằm ngoài đối tượng A và đối tượng A lớn 4 byte, chúng ta phải thêm 4 vào giá trị của con trỏ `

    class String
    {
        char const *(*d_sp)() const;

        public:
            char const *get() const;
    };
565' khi gọi hàm . Đó chính xác là những gì mà giá trị shift trong trường thứ hai của con trỏ nói với trình biên dịch

Một lớp có thể có một con trỏ tới chính nó không?

Một khai báo lớp có thể chứa đối tượng tĩnh của kiểu tự, nó cũng có thể có con trỏ tới kiểu tự , nhưng nó không thể có kiểu khác. .

Một lớp có thể là thành viên của chính nó không?

Cụ thể, mọi phần tử của một lớp là một tập hợp, vì vậy một lớp riêng không thể là phần tử của bất kỳ lớp nào (bao gồm cả chính nó). Tuy nhiên, hoàn toàn hợp lý khi cho phép các tập hợp là các phần tử của chính chúng.

Có từ khóa self trong C++ không?

Trong quá trình triển khai một phương thức, mã định danh Self tham chiếu đến đối tượng mà phương thức đó được gọi . Biến Self là một tham số ẩn cho từng phương thức đối tượng. Một phương thức có thể sử dụng biến này để tham chiếu đến lớp sở hữu của nó.

Bạn có thể gán lại con trỏ không?

Như bạn đã biết, địa chỉ của một đối tượng trong C++ có thể được lưu trữ thông qua tham chiếu hoặc thông qua con trỏ. Mặc dù có vẻ như chúng đại diện cho các khái niệm tương tự, nhưng một trong những điểm khác biệt quan trọng là bạn có thể gán lại một con trỏ để trỏ đến một địa chỉ khác , nhưng bạn không thể .