A i tương đương với con trỏ là gì năm 2024

Các biến sử dụng trong một chương trình được coi như những chiếc hộp không bao giờ rỗng. Chúng được lấp đầy một số nội dung do người lập trình viên hoặc nếu chưa được khởi tạo, thì nó được khởi tạo bởi hệ điều hành. Một biến như vậy có ít nhất hai thuộc tính: giá trị và ví trị của biến trong bộ nhớ máy tính. Giá trị này có thể là một số, một kí tự hoặc một thành phần phức tạp hơn như là cấu trúc. Tuy nhiên, giá trị này cũng có thể là vị trí của một biến khác, các biến có giá trị như vậy được gọi là con trỏ. Con trỏ thường là biến phụ trợ cho phép chúng ta truy cập vào giá trị của biến khác một cách gián tiếp. Một con trỏ tương tự như một đèn báo giao thông chỉ chúng ta đến một địa điểm nhất định hoặc một tờ giấy được ghi địa chỉ trên đó:

Ví dụ, khai báo sau:

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0,

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

1 là các biến số và

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2,

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

3 là các con trỏ chỉ đến biển có dữ liệu kiểu số. Dấu sao phía trước

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 và

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

3 cho biết chức năng của chúng. Giả sử địa chỉ của các biến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0,

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

1,

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 và

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

3 lần lượt là 1080, 1082, 1084 và 1086, sau đó gán giá trị 15 cho biến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0 trong khai báo, vị trị và giá trị của biến ở trong bộ nhớ máy tính ở trong hình 1.1a.

A i tương đương với con trỏ là gì năm 2024

Bây giờ, chúng ta có thể thực hiện gán

for (sum = *a, p = a+1; p < a+5; p++)
    sum += *p;

1 (hoặc

for (sum = *a, p = a+1; p < a+5; p++)
    sum += *p;

2 nếu trình biên dịch báo lỗi), nhưng biến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 được tạo ra để lưu địa chỉ của một biến số nguyên, chứ không phải giá trị của nó. Do đó, gán biến đúng cách là

for (sum = *a, p = a+1; p < a+5; p++)
    sum += *p;

4, trong đó dấu và ở phía trước

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0 có nghĩa là địa chỉ của

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0 chứ không phải là nội dung của nó. Hình 1.1b minh họa cho trường hợp này. Ở hình 1.1c, mũi tên chỉ từ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 đến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0 là một con trỏ chưa địa chỉ của

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0.

Chúng ta có khả năng phân biệt được giá trị của

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2, là một địa chỉ, với giá trị của vị trị có địa chỉ mà con trỏ đang trỏ đến. Ví dụ, để gán giá trị 20 cho biến được trỏ bới

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2, câu lệnh gán là:

Dấu sao (*) là một toán tử điều hướng buộc hệ thống phải truy xuất nội dung của

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 trước tiên, sau đó truy cập vào vị trí có địa chỉ được lấy từ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2, sau đó gán giá trị 20 cho vị trí này (hình 1.1d). Hình 1.1e đến 1.1n cung cấp thêm một số ví dụ về các câu lệnh gán và cách lưu giá trị trong bộ nhớ máy tính.

A i tương đương với con trỏ là gì năm 2024

Trong thực tế, con trỏ nó cũng giống như tất cả các biến cũng có 2 thuộc tính: nội dung và địa chỉ. Địa chỉ này có thể lưu ở biến khác, sau đó trở thành một con trỏ đến một con trỏ.

Trong hình 1.1, địa chỉ của biến đã được gán cho một con trỏ. Tuy nhiên, con trỏ được xem là các địa chỉ ẩn danh, chỉ có thể truy cập thông qua địa chỉ của chúng chứ không phải như biến, truy cập bằng tên của chúng. Các địa chỉ này phải được bởi hệ thống quản lí bộ nhớ. Xuyên suốt thời gian chạy của chương trình, không giống như biến, các địa chỉ được phân phát tại thời điểm biên dịch.

A i tương đương với con trỏ là gì năm 2024

Để cấp phát động và phân bố bộ nhớ, hai hàm được sử dụng. Hàm đầu tiên là

for (sum = p[0], i = 1; i < n; i++)
    sum += p[i];

4, lấy từ bộ nhớ nhiều không gian cần thiết để lưu trữ một đối tượng có kiểu theo sau

for (sum = p[0], i = 1; i < n; i++)
    sum += p[i];

4. Ví dụ:

chương trình sẽ yêu cầu đủ không gian để lưu trữ một số nguyên, từ hệ thống quản lí bộ nhớ, và địa phần đầu của bộ nhớ này được lưu trử trong

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2. Bây giờ giá trị có thể gán cho khối bộ nhớ được trỏ tới bởi

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2một cách gián tiếp thông qua con trỏ khác, hoặc con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 hoặc con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

3 bất kì nào khác đã được gán địa chỉ lưu trữ trong

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 với phép gán

for (sum = *p, i = 1; i < n; i++)
    sum += *(p+i);

1.

Nếu ô nhớ đã bị chiếm giữ bới một số nguyên có thể truy cập từ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 nhưng không còn cần thiết nữa, nó có thể trả về vùng bộ nhớ trống do hệ điều hành quản lý bằng cách thực hiện lệnh sau:

A i tương đương với con trỏ là gì năm 2024

Tuy nhiên, sau khi thực hiện câu lệnh

for (sum = *p, i = 1; i < n; i++)
    sum += *(p+i);

3, địa chỉ của khối bộ nhớ vẫn còn trong

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2, mặc dù khối không còn tồn tại nữa. Nó giống như địa chỉ của một ngôi nhà đã bị phá hủy. Tương tự, nếu sau khi thực hiện câu lệnh

for (sum = *p, i = 1; i < n; i++)
    sum += *(p+i);

3 chúng ta không xóa địa chỉ biến con trỏ, dẫn đến sự tiềm tàng nguy hiểm về kết quả của chương trình, và chúng ta có thể làm lỗi chương trình trình khi cố cắng truy cập vào địa chỉ không tồn tại, đặc biệt đói với các đối tượng phức tạp hơn là cái giá trị số. Vấn đề này gọi là con trỏ lơ lững. Để tránh vấn đề này, một địa chỉ phải được gán cho một con trỏ, nếu nó không phải là địa chỉ của bất kỳ vị trí nào, nó phải là một giá trị rỗng (NULL), đơn giản là 0. Sau đó thực hiện nhiệm vụ

Chúng ta có thể không nói rằng

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 tham chiếu đến NULL hoặc trỏ đến NULL nhưng

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 có giá trị là NULL hoặc 0.

Một vấn đề khác liên quan đến việc xóa bộ nhớ là rò rỉ bộ nhớ. Xét hai mã lệnh sau:

p = new int;
p = new int;

Sau khi phân bố bộ nhớ cho một số nguyên, cùng một con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 được dùng đẻ cấp phát cho một ô khác. Sau khi thực hiện nhiệm vụ thứ hai, ô đầu tiên không thể truy cập được và cũng không khả dụng trong lần cấp phát tiếp theo xuyên suốt chương trình. Vấn đề là không giải phóng bộ nhớ với lệnh delete, bộ nhớ có thể truy cập từ biến p trước đó khi thực hiện nhiệm vụ thứ 2. Đoạn code nên cài đăt như sau:

p = new int;
delete p;
p = new int;

Vấn đề rò rỉ bộ nhớ có thể trở nên nghiêm trọng khi một chương trình sử dụng nhiều và nhiều hơn nữa bộ nhớ không giải phóng nó, cuối cùng làm hết bộ nhớ và dẫn đến kết thúc chương trình đột ngột. Điều này đặt biệt quan trọng trong các chương trình được thực thi trong một thời gian dài, chẳng hạn như các chương trình ở servers.

1.4.1 Pointers and Arrays - Con trỏ và mảng

Trong phần trước, con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 tham chiếu đến một khối bộ nhớ chứa một số nguyên. Một tình huống thú vị hơn là khi một con trỏ tham chiếu đến một cấu trúc dữ liệu động đã được tạo và sửa đổi. Đây là một tình huống mà chúng ta cần phải vượt qua các hạn chế do mảng áp đặt. Mảng trong C++ và hầu hết trong các ngôn ngữ lập trình, phải được khai báo trước, do đó, kích thước của mảng cần phải biết trước khi chương trình bắt đầu. Điều này có nghĩa là người lập trình cần có kiến thức tốt về vấn đề sẽ được lập trình để chọn kích thước mảng phù hợp. Nếu kích thước quá lớn, thì mảng sẽ chiếm không gian bộ nhớ một cách không cần thiết, đơn giản là lãng phí bộ nhớ. Nếu kích thước quá nhỏ, thì dẫn đến việc tràn mảng và chương trình sẽ bị hủy bỏ. Đôi khi việc dự đoán kích thước của mảng không hề đơn giản, do đó quyết định trì hoãn cho đến thời gian chạy, và sau đó cấp phát đủ bộ nhớ để giữ mảng.

Vấn đề này được giải quyết khi sử dụng con trỏ. Xét hình 1.1b, trong hình này, con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 trỏ đến vị trí 1080. Nhưng nó cũng cho phép truy cập vị trí 1082, 1084 và vân vân bởi vì các vị trí cách đều nhau. Ví dụ, để truy cập giá trị của biến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

1, đứng bên cạnh

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

0, lấy địa chỉ của ô hiện tại cộng với kích thước của ô hiện tại để sang ô nhớ tiếp theo. Và về cơ bản thì đây là cách C++ xử lí mảng.

Xét khai báo sau:

Khai báo trên xác định

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 là một con trỏ đến khối bộ nhớ và có thể chứa 5 số nguyên. Con trỏ

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 là cố định, nghĩa là,

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 phải được xem là một hằng số để bất kì nổ lực nào gán một giá trị cho

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3, như là:

Hoặc

được xem như là một lỗi biên dịch. Bời vì

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 là một con trỏ, ký hiệu con trỏ có thể sử dụng truy cập các ô của mảng

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3. Ví dụ, một kí hiệu mảng được sử dụng trong vòng lặp cộng tất cả các số trong mảng

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3:

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

có thể được thay thế bằng kí hiệu con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

hoặc

for (sum = *a, p = a+1; p < a+5; p++)
    sum += *p;

Lưu ý rằng

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

0 là vị trí của ô nhớ tiếp theo của mảng

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 có nghĩa là

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

0 tương đương

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

3. Do đó, nếu

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 bằng 1020, thì

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

0 khong phải là 1021 mà là 1022 bởi vì con trỏ số học phụ thuộc vào loại thực thể con trỏ. Ví dụ, khai báo sau:

giả định

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

6 bằng 1050 và

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

7 bằng 1055,

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

8 bằng 1051 bởi vì một kí tự chiếm 1 byte, và

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

9 bằng 1059 bởi vì một số chiếm 4 byte. Lý do cho kết quả này của con trỏ số học là biểu thức

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

0 là biểu diễn địa chỉ bộ nhớ

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

1.

Trong cuộc thảo luận này, mảng

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

3 được khai báo tĩnh bằng cách khai báo 5 ô nhớ. Kích thước của mảng được cố định trong thời gian mà chương trình chạy. Nhưng một mảng có thể được khai báo động. Để khai báo mảng động, phải sử dụng biến con trỏ. Ví dụ, khai báo sau:

phân bố đầy đủ chỗ để lưu trữ

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 số nguyên. Con trỏ

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 có thể xem như là một biến mảng để các kí hiệu mảng có thể sử dụng. Ví dụ, tổng các số trong mảng

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 có thể được tìm thấy với đoạn mã sử dụng kí hiệu mảng:

for (sum = p[0], i = 1; i < n; i++)
    sum += p[i];

một kí hiệu con trỏ là sự biểu diễn của vòng lắp trước đó,

for (sum = *p, i = 1; i < n; i++)
    sum += *(p+i);

hoặc kí hiệu con trỏ sử dụng với 2 con trỏ

for (sum = *p, q = p+1; q < p+n; q++)
    sum += *q;

Bởi vì

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2là một biến, nó có thể được gán cho một mảng mới. Nhưng nếu mảng hiện tại được trỏ tới

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 mà không còn cần thiết nữa, nó nên được xử lý theo hướng dẫn sau

Lưu ý việc sử dụng dấu ngoặc vuông trong hướng dẫn. Dấu ngoặc [] cho biết

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 trỏ đến một mảng. Ngoài ra, delete nên được sử dụng với các con trỏ đã được gán một giá với từ khóa new. Vì lý do này, hai ứng dụng của delete sau đây có thể dẫn đến sự cố của chương trình:

    int a[10], *p = a;
    delete [] p;
    int n = 10, *q = &n;
    delete q;

Một loại mảng rất quan trọng đó là chuỗi, hoặc một mảng kí tự. Nhiều hàm được định nghĩa trước ở trên chuỗi. Tên của các hàm này bắt đầu với từ

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

9, như là

p = new int;
delete p;
p = new int;

00 để tìm độ dài của chuỗi

p = new int;
delete p;
p = new int;

01 hoặc

p = new int;
delete p;
p = new int;

02 để sao chép chuỗi

p = new int;
delete p;
p = new int;

03 đến chuỗi

p = new int;
delete p;
p = new int;

04. Điều quan trọng cần nhớ là tất cả các hàm đều giả định chuỗi kết thúc bằng kí tự

p = new int;
delete p;
p = new int;

05 ‘\0’. Ví dụ,

p = new int;
delete p;
p = new int;

02 sẽ tiếp tục sao chép cho đến khi tìm thấy kì tự

p = new int;
delete p;
p = new int;

07 ở

p = new int;
delete p;
p = new int;

03. Nếu một người lập trình không đưa kí tự này vào trong

p = new int;
delete p;
p = new int;

03, việc sao chép sẽ dừng lại khi lần xuất hiện đầu tiền của kí tự này được tìm thấy trong bộ nhớ máy tính sau vị trí

p = new int;
delete p;
p = new int;

03. Điều này có nghĩa là việc sao chép được thực hiện đến các vị trí bên ngoài

p = new int;
delete p;
p = new int;

04, điều này có thể dẫn đến sự cố của chương trình.

1.4.2 Poninter and Copy Constructors

Một số vấn đề có thể nảy sinh khi các thành viên dữ liệu con trỏ không được xử lý đúng cách khi sao chép dữ liệu từ đối tượng này qua đối tượng khác. Xét định nghĩa sau:

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

Mục đích của khai báo:

p = new int;
delete p;
p = new int;

0

là tạo đối tượng

p = new int;
delete p;
p = new int;

12, gán giá trị cho hai thành viên dữ liệu trong

p = new int;
delete p;
p = new int;

12, và sau đó tạo

p = new int;
delete p;
p = new int;

14 có các thành viên dữ liệu giống như

p = new int;
delete p;
p = new int;

12. Các đối tượng này phải là các thực thể độc lập để gán giá trị cho một trong số chúng sẽ không bị ảnh hướng đến các giá trị khác. Tuy nhiên, sau mệnh đề

p = new int;
delete p;
p = new int;

1

lệnh in màn hình:

p = new int;
delete p;
p = new int;

2

Hình 1.2

A i tương đương với con trỏ là gì năm 2024

đầu ra chung: “Wendy 30 Wendy 20”

Tuổi khác nhau, nhưng tên ở trong hai đối tượng đều giống nhau. Điều gì sẽ xảy ra? Vấn đề là định nghĩa của

p = new int;
delete p;
p = new int;

16 không cung cấp hàm sao chép constructor

điều này vô cùng cần thiết để thực hiện khai báo

p = new int;
delete p;
p = new int;

17 để khởi tạo

p = new int;
delete p;
p = new int;

12. Nếu hàm constructor tạo bản sao người dùng bị thiếu, thì constructor sẽ được tạo tự đổng bởi trình biên dịch. Nhưng trình biên dịch tạo ra constructor thực hiện sao chép từng thành viên. bởi vì

p = new int;
delete p;
p = new int;

19 là một con trỏ, constructor sap chép địa chỉ chuỗi

p = new int;
delete p;
p = new int;

20 đến

p = new int;
delete p;
p = new int;

21, không phải là nội dung chuỗi, do đó ngay sai khi thực hiện khai báo, dẫn đến tình trạng như trong hình 1.2a. Bây giờ nếu mệnh đề

p = new int;
delete p;
p = new int;

1

được thực hiện,

p = new int;
delete p;
p = new int;

22 được cập nhật đúng cách, nhưng chuỗi “Roger” được trỏ bởi thành viên

p = new int;
delete p;
p = new int;

19 của cả hai đối tượng được ghi đè bởi “Wendy”, đều được trỏ bởi hai con trỏ (hình 1.2b). Để ngăn chặn điều này xảy ra, người dùng phải định nghĩa một hàm constructor thích hợp, như dưới đây:

p = new int;
delete p;
p = new int;

4

Với constructor, khai báo

p = new int;
delete p;
p = new int;

17 tạo ra một bản sao khác của

p = new int;
delete p;
p = new int;

25 được trỏ đến bởi

p = new int;
delete p;
p = new int;

21 (hình1.2c), và các nhiệm vụ cho thành viên viên dữ liệu trong một đối tượng không có ảnh hướng đến các thành viên trong các đối tượng khác, do đó sau khi thực hiện các lệnh

p = new int;
delete p;
p = new int;

1

đối tượng

p = new int;
delete p;
p = new int;

12 vẫn không thay đổi, hình minh họa 1.2d.

Lưu ý rằng một vấn đề tương tự được đưa ra bởi phép toán gán. Nếu một định nghĩa của toán tử gán không được cung cấp bởi người dùng, phép gán

thực hiện copy từng thành viên, dẫn đến vấn đều giống như trong hình 1.2a-b. Để tránh vấn đề này, toán tử gán phải được thực hiện bởi người dùng. Đối với

p = new int;
delete p;
p = new int;

16, việc thực hiện toán tử gán như sau:

p = new int;
delete p;
p = new int;

6

Trong đoạn code này, một con trỏ đặc biệt this đã được sử dụng. Mỗi đối tượng có thể truy cập địa chỉ của chính nó thông qua con trỏ this.

1.4.3 Pointers and Destructors

Điều gì sẽ xảy ra với các đối tượng được định nghĩa cục bộ của kiểu

p = new int;
delete p;
p = new int;

16? Giống như tất cả các mục cục bộ, chúng bị hủy bỏ theo nghĩa là chúng trở nên không khả dụng bên ngoài khối mà chúng được định nghĩa, và các vùng nhớ bị chiếm giữ cũng được giải phóng. Nhưng mặc dù một bộ nhớ bị chiếm giữ bởi một đối tượng của kiểu

p = new int;
delete p;
p = new int;

16 được giải phóng, không phải là tất cả bộ nhớ liên quan đến đối tượng này đều trở nên khả dụng. Một trong những thành viên dữ liệu của đối tượng là một con trỏ trỏ đến một chuỗi, do đó, bộ nhớ bị chiếm giữ bởi con trỏ thành viên dữ liệu được giải phóng, nhưng bộ nhớ được lấy bởi chuổi thì không. Sau khi đối tượng được hủy bỏ, chuỗi khả dụng có sẵn trước đó từ thành viên dữ liệu

p = new int;
delete p;
p = new int;

19 trở nên không thể truy cập được (nếu không gán

p = new int;
delete p;
p = new int;

19 cho một số đối tượng khác hoặc một biến chuỗi) và bộ nhớ bị chiếm giữ bởi chuỗi này không thể giải phóng được nữa, dẫn đến rò rĩ bộ nhớ. Đây là vấn đề với các đối tượng có các thành viên dữ liệu trỏ đến các vị trí được phân bố động. Để tránh vấn đề này, một lớp nên bao gồm một định nghĩa về destructor. Destructor là một hàm tự động được gọi khi một đối tượng bị hủy bỏ, diễn ra khi thoát khỏi khối bộ nhớ trong đó đối tượng được định nghĩa hoặc theo lệnh gọi

for (sum = *p, i = 1; i < n; i++)
    sum += *(p+i);

3. Destructors không nhận các tham số và không trả về giá trị nào chỉ có thể có một destructor cho một lớp. Ví dụ lớp

p = new int;
delete p;
p = new int;

16, Destructor có thể được định nghĩa như sau:

p = new int;
delete p;
p = new int;

7

1.4.4 Poniters and Reference Variables

Xét các khác báo sau:

p = new int;
delete p;
p = new int;

8

Biến

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

2 được khai báo kiểu

p = new int;
delete p;
p = new int;

36, một con trỏ trỏ đến một số nguyên, và

p = new int;
delete p;
p = new int;

37 là kiểu

p = new int;
delete p;
p = new int;

38, một biến số nguyên được tham chiếu. Một biến tham chiếu phải được khởi tạo trong khai báo của nó như một tham chiếu đến một biến cụ thể, và tham chiếu này không thể thay đổi. Điều này có nghĩa là biến tham chiếu không được trống. Một biến tham chiếu

p = new int;
delete p;
p = new int;

37 có thể được coi là một tên khác của một biến tham chiếu

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 để nếu

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 thay đổi thì

p = new int;
delete p;
p = new int;

37 cũng thay đổi theo. Điều này là do một biến tham chiếu được trình bày như một con trỏ hằng.

Sau ba khai báo, thực hiện in

p = new int;
delete p;
p = new int;

9

Đầu ra là là 5 5 5. Sau đó gán

cùng một câu lệnh in đầu ra là 7 7 7. Ngoài ra, một phép gán

đầu ra 9 9 9, và thực hiện phép gán

nó cũng dẫn đến đầu ra là 10 10 10. Những lệnh trên chỉ ra rằng về mặt kí hiệu, những gì chúng ta có thể thực hiện với việc bỏ tham chiếu các biến con trỏ được thi hành mà không cần bỏ tham chiếu với các biến tham chiếu. Điều này không phải là tình cờ, như đã đề cập, các biến tham chiếu được triển khai dưới dạng con trỏ hằng. Thay vì khai báo

chúng ta có thể sử dụng khai báo

trong đó

p = new int;
delete p;
p = new int;

37 là một con trỏ hằng đến một số nguyên, có nghĩa là phép gán

trong đó

for (sum = *a, i = 1; i < 5; i++)
    sum += *(a + i);

3 là một con trỏ khác, là một lỗi vì giá trị của

p = new int;
delete p;
p = new int;

37 không thể thay đổi. Tuy nhiên, phép gán

được chấp nhận nếu

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 là một số nguyên không đổi.

Điều quan trọng là cần lưu ý sự khác biệt giữa kiểu int *const và kiểu const int *. Cái sau là một kiểu con trỏ tới một số nguyên không đổi.

sau đó gán

trong đó

p = new int;
delete p;
p = new int;

47 là mốt số nguyên (cho dù là hằng số hay không) là có thể chấp nhận được, nhưng phép gán

là sai, ngay cả khi m là một hằng số.

Các biến tham chiếu được dử dụng để truyền các tham số bằng cách tham chiếu đến cái lời gọi hàm. Truyền bằng tham chiếu là yêu cầu bắt buộc nếu một tham số thực tế sẽ được thay đổi vĩnh viên trong quá trình thực thi hàm. Điều này có thể thực hiện đối với các con trỏ (và trong C, đây là cơ chế có sẵn duy nhất để chuyển qua tham chiếu) hoặc với tham chiếu biến. Ví dụ, sau khi khai báo một hàm

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

0

giá trị của các biến

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

1

sau khi thực hiện lời gọi

là n1 = 4, n2 =2, n3 = 3.

Kiểu tham chiếu cũng được sử dụng để chỉ ra kiểu trả về của hàm. ví dụ, chúng ta có định nghĩa hàm

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

2

và khai bảo mảng

chúng ta có thể sử dụng hàm f2() ở bất khì phía nào của toán tử. Ví dụ, ở phía bên phải

hoặc phía bên trái

khi gán 6 cho

p = new int;
delete p;
p = new int;

48 có nghĩa là

p = new int;
delete p;
p = new int;

49. Lưu ý rằng chúng ta có thể thực hiện giống như một con trỏ, nhưng không được tham chiếu một cách rõ ràng

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

3

và sau đó

Các biến tham chiếu và kiểu trả về phải được sử dụng một cách thận trọng bởi vì nó có khả năng ảnh hưởng đến nguyên tắc che giấu thông tin khi chúng được sử dụng không đúng cách. Xét class C:

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

4

và có các lệnh:

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

5

Mặc dù

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 được khai bảo private, sau phép gán đầu tiên nó có thể truy cập theo ý muốn từ bên ngoài thông qua k và được gán bất kì giá trị nào. Một hành vi cũng có thể được thực hiện thông qua getRefN();

1.4.5 Pointer to Functions

Như đã thảo luận trong phần

p = new int;
delete p;
p = new int;

51, một trong những thuộc tính của của một biến là một địa chỉ của nó cho biết vị trí của nó trong bộ nhớ máy tính. Điều này cũng đúng với các hàm. Một trong những thuộc tính của fhafmunction là địa chỉ của nó cho biết vị trí của phần thân hàm trong bộ nhớ. Khi gọi một hàm, hệ thống chuyển quyền điều khiển đến vị trí này để thực hiện hàm. Vì lý do này, nó có khả năng sử dụng con trỏ trỏ đến các hàm. Những con trỏ này vô cũng hữu ích trong việc thực thi các hàm (nghĩa là các hàm nhận các đối số) chẳng hạn như một tích phân.

Xét một hàm đơn giản sau:

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

6

Với định nghĩa này,

p = new int;
delete p;
p = new int;

52 là một con trỏ tới function

p = new int;
delete p;
p = new int;

53,

p = new int;
delete p;
p = new int;

54 là một function của chính nó, và

p = new int;
delete p;
p = new int;

55 là lời gọi hàm.

Bây giờ hãy xem xét viết một hàm C++ để tính tổng sau:

\[\sum_{i=n} ^ {m} f(i) \]

Để tính tổng, chúng ta phải cung cấp

struct Node {
    char *name;
    int age;
    Node(char *n = "", int a = 0) {
        name = strdup(n);
        age = a;
    }
};

3 và

p = new int;
delete p;
p = new int;

47 không giới hạn, nhưng còn cung cấp thêm hàm

p = new int;
delete p;
p = new int;

52. Do đó, mong muốn thực hiện nên cho phép truyền số không chỉ ở dạng tham số, mà còn ở dạng hàm. Điều này được thực hiên trong C++ theo cách sau:

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

7

Trong định nghĩa của

p = new int;
delete p;
p = new int;

59, khai báo tham số chính đầu tiên

có nghĩa là

p = new int;
delete p;
p = new int;

52 là một con trỏ đến một hàm với một tham số double và trả về giá trị double. Lưu ý rằng cần dấu ngoặc đơn

p = new int;
delete p;
p = new int;

61 xung quanh

p = new int;
delete p;
p = new int;

54. Bởi vì dấu ngoặc đơn được ưu tiên hơn toán tử tham chiếu

p = new int;
delete p;
p = new int;

63, biểu thức

khai báo một hàm và trả về giá trị double.

Bây giờ, có thể gọi hàm

p = new int;
delete p;
p = new int;

59 bằng bất kì cách xây dựng nào hoặc người dùng xác định các hàm double nhận các đối số trả về kiểu double, như

for (sum = a[0], i = 1; i < 5; i++)
    sum += a[i];

8

Một ví dú khác về một hàm tìm root của một hàm liên tục trong một khoảng. Root được tìm thấy bằng cách lặp đi lặp lại chia đôi một khảng và tìm một điểm giữa của khoảng hiện tại. Nếu giá trị hàm của điểm ở giữa bằng 0 hoặc khoảng nhỏ hơn một số giá trị, điểm ở giữa được trả về. Nếu giá trị của hàm ở giới hạn bên trái của khoảng hiện tại và điểm ở giữa có dấu hiệu ngược lại, tiếp tục tìm kiếm trong nửa khoảng bên trái hiện tại, nếu không, khoảng hiện tại sẽ là nửa khoảng bên phải. Đây là một triên khai của thuật toán này: