C++ vượt qua giá trị hoặc hiệu suất tham chiếu

Con trỏ, Tham chiếu và Cấp phát bộ nhớ động là những tính năng mạnh mẽ nhất của ngôn ngữ C/C++, cho phép người lập trình thao tác trực tiếp với bộ nhớ để quản lý hiệu quả bộ nhớ - tài nguyên khan hiếm và quan trọng nhất trong máy tính - nhằm mang lại hiệu suất tốt nhất. Tuy nhiên, "con trỏ" cũng là tính năng phức tạp và khó nhất trong ngôn ngữ C/C++

Con trỏ cực kỳ mạnh vì chúng cho phép bạn truy cập địa chỉ và thao tác với nội dung của chúng. Nhưng chúng cũng cực kỳ phức tạp để xử lý. Sử dụng chúng một cách chính xác, chúng có thể cải thiện đáng kể hiệu quả và hiệu suất. Mặt khác, sử dụng chúng không đúng cách có thể dẫn đến nhiều vấn đề, từ mã không đọc được và không bảo trì được, đến các lỗi nổi tiếng như rò rỉ bộ nhớ và tràn bộ đệm, có thể khiến hệ thống của bạn bị hack. Nhiều ngôn ngữ mới (chẳng hạn như Java và C#) loại bỏ con trỏ khỏi cú pháp của chúng để tránh những cạm bẫy của con trỏ, bằng cách cung cấp khả năng quản lý bộ nhớ tự động

Mặc dù bạn có thể viết chương trình C/C++ mà không cần sử dụng đến con trỏ, tuy nhiên, khó có thể không nhắc đến con trỏ trong việc giảng dạy ngôn ngữ C/C++. Con trỏ có lẽ không dành cho người mới và người giả

Biến con trỏ

Một vị trí bộ nhớ máy tính có một địa chỉ và chứa một nội dung. Địa chỉ là một số (thường được biểu thị bằng hệ thập lục phân), khó cho người lập trình sử dụng trực tiếp. Thông thường, mỗi vị trí địa chỉ chứa 8-bit (i. e. , 1 byte) dữ liệu. Người lập trình hoàn toàn có thể diễn giải ý nghĩa của dữ liệu, chẳng hạn như số nguyên, số thực, ký tự hoặc chuỗi

Để giảm bớt gánh nặng lập trình sử dụng địa chỉ số và dữ liệu do lập trình viên giải thích, các ngôn ngữ lập trình ban đầu (chẳng hạn như C) đưa ra khái niệm biến. Một biến là một vị trí được đặt tên có thể lưu trữ một giá trị của một loại cụ thể. Thay vì địa chỉ số, tên (hoặc số nhận dạng) được gắn vào một số địa chỉ nhất định. Ngoài ra, các loại (chẳng hạn như

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
6,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7) được liên kết với nội dung để dễ giải thích dữ liệu

Mỗi vị trí địa chỉ thường chứa 8-bit (i. e. , 1 byte) dữ liệu. Giá trị 4 byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 chiếm 4 vị trí bộ nhớ. Hệ thống 32 bit thường sử dụng địa chỉ 32 bit. Để lưu địa chỉ 32 bit, cần có 4 vị trí bộ nhớ

Sơ đồ sau đây minh họa mối quan hệ giữa địa chỉ và nội dung bộ nhớ của máy tính;

C++ vượt qua giá trị hoặc hiệu suất tham chiếu

Biến con trỏ (hoặc con trỏ)

Biến con trỏ (hay gọi tắt là con trỏ) về cơ bản cũng giống như các biến khác, có thể lưu trữ một phần dữ liệu. Không giống như biến thông thường lưu trữ một giá trị (chẳng hạn như một

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, một
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
6, một
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7), một con trỏ lưu trữ một địa chỉ bộ nhớ

khai báo con trỏ

Con trỏ phải được khai báo trước khi chúng có thể được sử dụng, giống như một biến thông thường. Cú pháp khai báo một con trỏ là đặt một

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
2 trước tên. Một con trỏ cũng được liên kết với một loại (chẳng hạn như
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 và
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
6)

Ví dụ,

Hãy lưu ý rằng bạn cần đặt một ____ 72 trước mỗi biến con trỏ, nói cách khác, ____ 72 chỉ áp dụng cho tên theo sau.

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
2 trong câu lệnh khai báo không phải là toán tử, nhưng chỉ ra rằng tên theo sau là một biến con trỏ. Ví dụ,

Quy ước đặt tên của con trỏ. Bao gồm "

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
8" hoặc "
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
9" làm tiền tố hoặc hậu tố, e. g. , ________ 150, ________ 151, ________ 152, ________ 153

Khởi tạo con trỏ thông qua Toán tử địa chỉ (&)

Khi bạn khai báo một biến con trỏ, nội dung của nó không được khởi tạo. Nói cách khác, nó chứa một địa chỉ "ở đâu đó", tất nhiên đây không phải là một địa điểm hợp lệ. Điều này nguy hiểm. Bạn cần khởi tạo một con trỏ bằng cách gán cho nó một địa chỉ hợp lệ. Điều này thường được thực hiện thông qua địa chỉ của toán tử (

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54)

Toán tử địa chỉ của (

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54) hoạt động trên một biến và trả về địa chỉ của biến. Ví dụ: nếu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56 là một biến
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, thì
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
58 trả về địa chỉ của biến
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56

Bạn có thể sử dụng toán tử địa chỉ của để lấy địa chỉ của một biến và gán địa chỉ cho một biến con trỏ. Ví dụ,

C++ vượt qua giá trị hoặc hiệu suất tham chiếu

Như minh họa, biến

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56, bắt đầu từ địa chỉ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
72, chứa giá trị
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
74. Biểu thức
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
58 trả về địa chỉ của biến
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56, là
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
72. Địa chỉ này sau đó được gán cho biến con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
52, làm giá trị ban đầu của nó

Địa chỉ của toán tử (

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54) chỉ có thể được sử dụng trên RHS

Indirection hoặc Dereferencing Operator (_______72)

Toán tử gián tiếp (hoặc toán tử hội nghị) (

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
2) hoạt động trên một con trỏ và trả về giá trị được lưu trong địa chỉ được lưu trong biến con trỏ. Ví dụ: nếu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
52 là một con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, thì
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
74 trả về giá trị
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 "được trỏ tới" bởi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
52

Ví dụ,

Hãy lưu ý rằng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
52 lưu trữ một vị trí địa chỉ bộ nhớ, trong khi đó
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
74 đề cập đến giá trị được lưu trữ trong địa chỉ được lưu giữ trong biến con trỏ hoặc giá trị được trỏ tới bởi con trỏ

Như minh họa, một biến (chẳng hạn như

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56) tham chiếu trực tiếp đến một giá trị, trong khi một con trỏ gián tiếp tham chiếu đến một giá trị thông qua địa chỉ bộ nhớ mà nó lưu trữ. Tham chiếu một giá trị một cách gián tiếp thông qua một con trỏ được gọi là gián tiếp hoặc dereferencing

Toán tử gián tiếp (

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
2) có thể được sử dụng trong cả RHS (
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
61) và LHS (
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
62) của câu lệnh gán

Lưu ý rằng ký hiệu

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
2 có ý nghĩa khác nhau trong câu lệnh khai báo và trong biểu thức. Khi nó được sử dụng trong một khai báo (e. g. ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
64), nó biểu thị rằng tên theo sau là một biến con trỏ. Trong khi đó khi nó được sử dụng trong một biểu thức (e. g. ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
66), nó đề cập đến giá trị được trỏ tới bởi biến con trỏ

Con trỏ cũng có Loại

Một con trỏ được liên kết với một loại (của giá trị mà nó trỏ tới), được chỉ định trong khi khai báo. Một con trỏ chỉ có thể chứa một địa chỉ của kiểu khai báo;

Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2

ghi chú. Các giá trị địa chỉ mà bạn nhận được có thể không giống với địa chỉ của tôi. Hệ điều hành tải chương trình vào các vị trí bộ nhớ trống có sẵn, thay vì các vị trí bộ nhớ cố định

Con trỏ chưa được khởi tạo

Đoạn mã sau có lỗi logic nghiêm trọng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3

Con trỏ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
50 được khai báo mà không khởi tạo, i. e. , nó đang trỏ đến "một nơi nào đó" tất nhiên là một vị trí bộ nhớ không hợp lệ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
68 làm hỏng giá trị của "ở đâu đó". Bạn cần khởi tạo một con trỏ bằng cách gán cho nó một địa chỉ hợp lệ. Hầu hết các trình biên dịch không báo lỗi hoặc cảnh báo cho con trỏ chưa được khởi tạo?

Con trỏ rỗng

Bạn có thể khởi tạo một con trỏ tới 0 hoặc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
69, i. e. , nó chỉ ra không có gì. Nó được gọi là con trỏ null. Hủy bỏ một con trỏ null (
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
70) gây ra một ngoại lệ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
71

Khởi tạo một con trỏ thành null trong khi khai báo là một thực hành kỹ thuật phần mềm tốt

C++11 giới thiệu một từ khóa mới có tên là

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
72 để biểu thị con trỏ null

Biến tham chiếu

C++ đã thêm cái gọi là biến tham chiếu (hay gọi tắt là tham chiếu). Tham chiếu là bí danh hoặc tên thay thế cho một biến hiện có. Ví dụ: giả sử bạn đặt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
73 làm tham chiếu (bí danh) cho
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
74, bạn có thể gọi người đó là
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
73 hoặc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
74

Công dụng chính của tham chiếu là đóng vai trò là tham số chính thức của hàm để hỗ trợ chuyển qua tham chiếu. Trong một biến tham chiếu được truyền vào một hàm, hàm này hoạt động trên bản gốc (thay vì một bản sao nhân bản ở dạng truyền theo giá trị). Các thay đổi bên trong chức năng được phản ánh bên ngoài chức năng

Một tham chiếu tương tự như một con trỏ. Trong nhiều trường hợp, một tham chiếu có thể được sử dụng thay thế cho con trỏ, đặc biệt là đối với tham số chức năng

Tài liệu tham khảo (hoặc Bí danh) (&)

Nhớ lại rằng C/C++ sử dụng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54 để biểu thị địa chỉ của toán tử trong một biểu thức. C++ gán một ý nghĩa bổ sung cho
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54 trong phần khai báo để khai báo một biến tham chiếu

Ý nghĩa của ký hiệu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54 khác nhau trong biểu thức và trong khai báo. Khi nó được sử dụng trong một biểu thức,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54 biểu thị địa chỉ của toán tử, trả về địa chỉ của một biến, e. g. , nếu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56 là biến
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 thì
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
58 trả về địa chỉ của biến
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
56 (điều này đã được trình bày ở phần trên)

Tuy nhiên, khi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
54 được sử dụng trong một khai báo (bao gồm các tham số chính thức của hàm), nó là một phần của định danh kiểu và được sử dụng để khai báo một biến tham chiếu (hoặc tham chiếu hoặc bí danh hoặc tên thay thế). Nó được sử dụng để cung cấp một tên khác, hoặc một tham chiếu khác hoặc bí danh cho một biến hiện có

Cú pháp như sau

Nó sẽ được đọc là "

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
36 là tham chiếu đến
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
37" hoặc "
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
38 là bí danh của
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
39". Bây giờ bạn có thể gọi biến là
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
36 hoặc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
301

Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
C++ vượt qua giá trị hoặc hiệu suất tham chiếu

Cách hoạt động của tham chiếu?

Một tham chiếu hoạt động như một con trỏ. Một tham chiếu được khai báo như một bí danh của một biến. Nó lưu trữ địa chỉ của biến, như minh họa

C++ vượt qua giá trị hoặc hiệu suất tham chiếu

Tài liệu tham khảo so với. con trỏ

Con trỏ và tham chiếu là tương đương, ngoại trừ

  1. Một tham chiếu là một hằng số tên cho một địa chỉ. Bạn cần khởi tạo tham chiếu trong quá trình khai báo. Khi một tham chiếu được thiết lập cho một biến, bạn không thể thay đổi tham chiếu này để tham chiếu đến một biến khác
  2. Để lấy giá trị được chỉ bởi một con trỏ, bạn cần sử dụng toán tử hủy hội nghị
    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
    2 (e. g. , nếu
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    52 là một con trỏ
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    5, thì
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    74 trả về giá trị được trỏ tới bởi
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    52. Nó được gọi là dereferencing hoặc indirection). Để gán một địa chỉ của một biến vào một con trỏ, bạn cần sử dụng địa chỉ của toán tử
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    54 (e. g. ,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    308).
    Mặt khác, việc tham chiếu và hủy bỏ tham chiếu được thực hiện hoàn toàn trên các tham chiếu. Ví dụ: nếu
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    309 là một tham chiếu (bí danh) đến một biến
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    5 khác, thì
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    309 trả về giá trị của biến. Không nên sử dụng toán tử hủy hội nghị rõ ràng
    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
    2. Hơn nữa, để gán địa chỉ của một biến cho một biến tham chiếu, không cần địa chỉ của toán tử
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    54.

Ví dụ,

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

Biến tham chiếu cung cấp tên mới cho biến hiện có. Nó được hủy đăng ký hoàn toàn và không cần toán tử hủy đăng ký

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
2 để truy xuất giá trị được tham chiếu. Mặt khác, một biến con trỏ lưu trữ một địa chỉ. Bạn có thể thay đổi giá trị địa chỉ được lưu trữ trong một con trỏ. Để truy xuất giá trị được chỉ bởi một con trỏ, bạn cần sử dụng toán tử gián tiếp
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
2, được gọi là hủy bỏ hội nghị tường minh. Tham chiếu có thể được coi là một con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316. Nó phải được khởi tạo trong quá trình khai báo và không thể thay đổi nội dung của nó

Tham chiếu có liên quan chặt chẽ với con trỏ. Trong nhiều trường hợp, nó có thể được sử dụng thay thế cho con trỏ. Một tham chiếu cho phép bạn thao tác với một đối tượng bằng cách sử dụng con trỏ, nhưng không có cú pháp con trỏ của tham chiếu và hủy bỏ tham chiếu

Ví dụ trên minh họa cách thức hoạt động của tham chiếu, nhưng không hiển thị cách sử dụng điển hình của nó, được sử dụng làm tham số chính thức của hàm để chuyển qua tham chiếu

Truyền tham chiếu vào các hàm với các đối số tham chiếu so với. Đối số con trỏ

Pass-by-Giá trị

Trong C/C++, theo mặc định, các đối số được truyền vào hàm theo giá trị (ngoại trừ mảng được coi là con trỏ). Nghĩa là, một bản sao của đối số được tạo và chuyển vào hàm. Các thay đổi đối với bản sao bên trong hàm không ảnh hưởng đến đối số ban đầu trong trình gọi. Nói cách khác, hàm được gọi không có quyền truy cập vào các biến trong trình gọi. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5

Đầu ra cho thấy rõ ràng rằng có hai địa chỉ khác nhau

Pass-by-Reference với Pointer Arguments

Trong nhiều tình huống, chúng tôi có thể muốn sửa đổi trực tiếp bản sao gốc (đặc biệt là khi truyền đối tượng hoặc mảng lớn) để tránh chi phí nhân bản. Điều này có thể được thực hiện bằng cách chuyển một con trỏ của đối tượng vào hàm, được gọi là chuyển qua tham chiếu. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7

Hàm được gọi hoạt động trên cùng một địa chỉ và do đó có thể sửa đổi biến trong trình gọi

Pass-by-Reference với Reference Arguments

Thay vì chuyển con trỏ vào hàm, bạn cũng có thể chuyển tham chiếu vào hàm, để tránh cú pháp vụng về của tham chiếu và hủy bỏ hội nghị. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7

Một lần nữa, đầu ra cho thấy hàm được gọi hoạt động trên cùng một địa chỉ và do đó có thể sửa đổi biến của người gọi

Lưu ý tham chiếu (trong trình gọi) và hủy bỏ tham chiếu (trong chức năng) được thực hiện hoàn toàn. Sự khác biệt mã hóa duy nhất với truyền theo giá trị là trong khai báo tham số của hàm

Nhớ lại rằng các tham chiếu sẽ được khởi tạo trong khi khai báo. Trong trường hợp tham số chính thức của hàm, các tham chiếu được khởi tạo khi hàm được gọi, đối số của người gọi

Tham chiếu chủ yếu được sử dụng để chuyển tham chiếu vào/ra khỏi hàm để cho phép hàm được gọi truy cập trực tiếp vào các biến trong trình gọi

"const" Tham số chức năng/Tham số con trỏ

Không thể sửa đổi tham số chính thức của hàm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 bên trong hàm. Sử dụng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 bất cứ khi nào có thể vì nó bảo vệ bạn khỏi việc vô tình sửa đổi tham số và bảo vệ bạn khỏi nhiều lỗi lập trình

Tham số hàm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 có thể nhận cả đối số
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 và không phải ____5316. Mặt khác, tham số con trỏ/tham chiếu hàm non-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 chỉ có thể nhận đối số non-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
6

Truyền giá trị trả về của hàm

Truyền giá trị trả về làm tham chiếu

Bạn cũng có thể chuyển giá trị trả về dưới dạng tham chiếu hoặc con trỏ. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7
Bạn không nên chuyển biến cục bộ của Hàm thành giá trị trả về theo tham chiếu
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
3

Chương trình này có lỗi logic nghiêm trọng, vì biến cục bộ của hàm được trả về dưới dạng giá trị trả về theo tham chiếu. Biến cục bộ có phạm vi cục bộ trong hàm và giá trị của nó bị hủy sau khi hàm thoát. Trình biên dịch GCC đủ tốt để đưa ra cảnh báo (nhưng không báo lỗi)

Sẽ an toàn khi trả về một tham chiếu được truyền vào hàm dưới dạng đối số. Xem các ví dụ trước đó

Truyền bộ nhớ được phân bổ động dưới dạng giá trị trả về theo tham chiếu

Thay vào đó, bạn cần phân bổ động một biến cho giá trị trả về và trả về giá trị đó theo tham chiếu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2

Tóm lược

Con trỏ và tham chiếu rất phức tạp và khó nắm vững. Nhưng họ có thể cải thiện đáng kể hiệu quả của các chương trình

Đối với người mới, tránh sử dụng con trỏ trong chương trình của bạn. Sử dụng không đúng cách có thể dẫn đến lỗi logic nghiêm trọng. Tuy nhiên, bạn cần hiểu cú pháp của tham chiếu chuyển qua với con trỏ và tham chiếu, bởi vì chúng được sử dụng trong nhiều hàm thư viện

  • Trong truyền theo giá trị, một bản sao được tạo và chuyển vào hàm. Bản sao của người gọi không thể được sửa đổi
  • Trong pass-by-reference, một con trỏ được truyền vào hàm. Bản sao của người gọi có thể được sửa đổi bên trong chức năng
  • Trong pass-by-reference với các đối số tham chiếu, bạn sử dụng tên biến làm đối số
  • Trong tham chiếu chuyển qua với các đối số con trỏ, bạn cần sử dụng
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    324 (một địa chỉ) làm đối số

Cấp phát bộ nhớ động

mới và xóa Toán tử

Thay vì định nghĩa biến

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 (
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
326) và gán địa chỉ của biến cho con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 (
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
328), bộ lưu trữ có thể được cấp phát động trong thời gian chạy, thông qua toán tử
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
329. Trong C++, bất cứ khi nào bạn cấp phát động một phần bộ nhớ thông qua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
329, bạn cần sử dụng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
331 để xóa bộ nhớ (i. e. , để trả lại bộ nhớ cho đống)

Hoạt động

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
329 trả về một con trỏ tới bộ nhớ được cấp phát. Toán tử
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
331 lấy một con trỏ (trỏ tới bộ nhớ được cấp phát qua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
329) làm đối số duy nhất của nó

Ví dụ,

Quan sát rằng các toán tử

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
329 và
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
331 hoạt động trên con trỏ

Để khởi tạo bộ nhớ được cấp phát, bạn có thể sử dụng một trình khởi tạo cho các kiểu cơ bản hoặc gọi một hàm tạo cho một đối tượng. Ví dụ,

Bạn có thể cấp phát lưu trữ động cho các con trỏ toàn cục bên trong một hàm. Lưu trữ được phân bổ động bên trong chức năng vẫn còn ngay cả sau khi chức năng thoát. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
31

Sự khác biệt chính giữa phân bổ tĩnh và phân bổ động là

  1. Trong cấp phát tĩnh, trình biên dịch sẽ tự động cấp phát và giải phóng bộ nhớ, đồng thời xử lý việc quản lý bộ nhớ. Trong khi ở cấp phát động, bạn, với tư cách là lập trình viên, tự mình xử lý việc cấp phát và hủy cấp phát bộ nhớ (thông qua toán tử
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    329 và
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    331). Bạn có toàn quyền kiểm soát địa chỉ con trỏ và nội dung của chúng, cũng như quản lý bộ nhớ
  2. Các thực thể được phân bổ tĩnh được thao tác thông qua các biến được đặt tên. Các thực thể được phân bổ động được xử lý thông qua các con trỏ

toán tử mới[] và xóa[]

Mảng động được phân bổ trong thời gian chạy thay vì thời gian biên dịch, thông qua toán tử

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
339. Để xóa bộ lưu trữ, bạn cần sử dụng toán tử
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
340 (thay vì chỉ đơn giản là
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
331). Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
32

C++03 không cho phép bạn khởi tạo mảng được cấp phát động. C++11 thực hiện với việc khởi tạo cú đúp, như sau

Con trỏ, Mảng và Hàm

Mảng được coi là con trỏ

Trong C/C++, tên của mảng là một con trỏ, trỏ tới phần tử đầu tiên (chỉ số 0) của mảng. Ví dụ, giả sử rằng

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
342 là một mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
342 cũng là một con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, chỉ vào phần tử đầu tiên của mảng. Nghĩa là,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
342 giống như
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
347. Do đó,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
348 là
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
349;

Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
33

Số học con trỏ

Như đã thấy ở phần trước, nếu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
342 là một mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, thì nó được coi là một con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 trỏ đến phần tử đầu tiên của mảng.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
355 chỉ tới
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 tiếp theo, thay vì có địa chỉ tuần tự tiếp theo. Hãy lưu ý rằng một
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 thường có 4 byte. Đó là
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
355 tăng địa chỉ lên 4 hoặc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
359. Ví dụ,

Mảng sizeof

Hoạt động

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
360 trả về tổng số byte của mảng. Bạn có thể lấy được độ dài (kích thước) của mảng bằng cách chia nó cho kích thước của một phần tử (e. g. phần tử 0). Ví dụ,

Truyền mảng vào/ra hàm

Một mảng được truyền vào một hàm dưới dạng một con trỏ tới phần tử đầu tiên của mảng. Bạn có thể sử dụng ký hiệu mảng (e. g. ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
361) hoặc ký hiệu con trỏ (e. g. ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
362) trong phần khai báo hàm. Trình biên dịch luôn coi nó là con trỏ (e. g. ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
362). Ví dụ, các khai báo sau là tương đương

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
34

Chúng sẽ được trình biên dịch coi là

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
362, như sau. Kích thước của mảng được đưa ra trong
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
365 bị bỏ qua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
35

Mảng được truyền theo tham chiếu vào hàm, bởi vì một con trỏ được truyền thay vì một bản sao. Nếu mảng được sửa đổi bên trong hàm, các sửa đổi được áp dụng cho bản sao của người gọi. Bạn có thể khai báo tham số mảng là

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 để ngăn không cho mảng bị sửa đổi bên trong hàm

Kích thước của mảng không phải là một phần của tham số mảng và cần được chuyển vào một tham số

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 khác. Trình biên dịch không thể suy ra kích thước mảng từ con trỏ mảng và không thực hiện kiểm tra giới hạn mảng

Ví dụ. Sử dụng ký hiệu mảng thông thường

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
36

Lưu ý rằng bạn có thể sửa đổi nội dung của mảng người gọi bên trong hàm, vì mảng được truyền theo tham chiếu. Để ngăn việc vô tình sửa đổi, bạn có thể áp dụng hạn định

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 cho tham số của hàm. Nhớ lại rằng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 thông báo cho trình biên dịch rằng không nên thay đổi giá trị. Ví dụ: giả sử rằng hàm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
370 in nội dung của mảng đã cho và không sửa đổi mảng, bạn có thể áp dụng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 cho cả tên mảng và kích thước của mảng, vì chúng sẽ không bị thay đổi bên trong hàm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
37

Trình biên dịch báo lỗi "chỉ định vị trí chỉ đọc" nếu nó phát hiện giá trị

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
316 sẽ bị thay đổi

Ví dụ. Sử dụng ký hiệu con trỏ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
38

Pass-by-Reference và sizeof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
38
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0

Địa chỉ của các mảng trong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
373 và hàm giống nhau, như mong đợi, vì mảng được truyền theo tham chiếu

Trong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
373, mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
375 là 20 (4 byte cho mỗi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5, độ dài là 5). Bên trong hàm,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
375 là 4, là con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
375
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5 (địa chỉ 4 byte). Đây là lý do tại sao bạn cần chuyển kích thước vào hàm

Hoạt động trên một phạm vi của một mảng

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
3

Ghi chú chương trình

  • Để viết một hàm hoạt động trên một phạm vi của mảng đã cho, bạn có thể truyền con trỏ bắt đầu và con trỏ kết thúc vào hàm. Theo quy ước, hoạt động sẽ bắt đầu từ con trỏ bắt đầu, cho đến con trỏ kết thúc, nhưng không bao gồm con trỏ kết thúc
  • Trong "
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    380",
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    70 (nội dung trỏ đến) là hằng số, nhưng
    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
    8 không phải là hằng số

Chuỗi C và Con trỏ

Chuỗi C (của ngôn ngữ C) là một mảng ký tự, được kết thúc bằng ký tự null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
383. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2

Lưu ý rằng đối với hàm C-String, chẳng hạn như

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
384 (trong tiêu đề
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
385, được chuyển từ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
386 của C), không cần chuyển độ dài mảng vào hàm. Điều này là do Chuỗi C bị chấm dứt bởi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
383. Hàm có thể lặp qua các ký tự trong mảng cho đến khi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
383. Ví dụ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3

* Thông tin thêm về con trỏ

Con trỏ hàm

Trong C/C++, các hàm, giống như tất cả các mục dữ liệu, có một địa chỉ. Tên của hàm là địa chỉ bắt đầu nơi hàm nằm trong bộ nhớ và do đó, có thể được coi là một con trỏ. Chúng ta cũng có thể chuyển một con trỏ hàm vào hàm. Cú pháp khai báo con trỏ hàm là

Ví dụ
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
3

Con trỏ chung hoặc Con trỏ trống (void *)

Con trỏ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
389 có thể chứa địa chỉ của bất kỳ loại dữ liệu nào (ngoại trừ con trỏ hàm). Chúng tôi không thể thao tác trên đối tượng được trỏ bởi con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
389, vì loại không xác định. Chúng ta có thể sử dụng một con trỏ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
389 để so sánh với một địa chỉ khác

Chuyển theo tham chiếu hoặc giá trị có nhanh hơn không?

Tóm lại. Việc chuyển các loại gốc (int, float, double) theo giá trị hầu như luôn hiệu quả hơn so với theo tham chiếu .

Truyền theo giá trị có hiệu quả hơn truyền theo tham chiếu không?

Truyền theo tham chiếu hiệu quả hơn truyền theo giá trị vì nó không sao chép đối số. Tham số chính thức là bí danh cho đối số. Khi hàm được gọi đọc hoặc ghi tham số hình thức, nó thực sự đọc hoặc ghi chính đối số đó.

Là C chuyển theo giá trị hoặc chuyển theo tham chiếu?

C luôn sử dụng ' truyền giá trị ' để truyền đối số cho hàm (một thuật ngữ khác là 'gọi theo giá trị', có nghĩa là .

Chuyển qua tham chiếu có chậm hơn không?

cả hai sẽ nhanh như nhau. Truyền theo giá trị nhanh hơn một chút vì bạn không cần mức độ gián tiếp khi truy cập dữ liệu đã truyền. Nếu dữ liệu của bạn là một cấu trúc lớn, chuyển qua tham chiếu sẽ nhanh hơn vì bạn không phải sao chép toàn bộ cấu trúc trước khi chuyển nó, bạn chỉ cần .