You are on page 1of 32

Chúng ta sẽ xem xét các hệ thống kiểu theo nghĩa

chung, thảo luận về các kiểu nguyên thủy và các cơ


chế được sử dụng để định nghĩa các kiểu mới. Trọng
tâm của bài thuyết trình của chúng tôi sẽ là khái
niệm về type safe, sẽ được giới thiệu trong Sect.
8.2. Sau đó, chúng tôi sẽ giải quyết các câu hỏi về
sự tương đương của kiểu và tính tương thích của
các kiểu, đó là các cơ chế cho phép chúng ta sử
dụng một giá trị của một số kiểu trong ngữ cảnh yêu
cầu một kiểu khác. Sau đó chúng ta sẽ thảo luận về
tính Polymorphism và Overloading.Chúng ta sẽ kết
thúc chương với một số câu hỏi về quản lý lưu trữ
(thu gom rác), nói đúng ra không phải là một chủ đề
về kiểu dữ liệu nhưng nó bổ sung tốt cho việc kiểm
tra các con trỏ mà chúng ta phải thực hiện.
8.1 Data Types
Các kiểu dữ liệu hiện diện trong ngôn ngữ lập trình
vì ít nhất ba lý do khác nhau:
1. Ở cấp độ thiết kế, hỗ trợ cho tổ chức khái niệm;
2. Ở cấp độ chương trình, hỗ trợ cho tính đúng đắn;
3. Ở cấp độ dịch thuật, hỗ trợ cho việc thực hiện.
Định nghĩa 8.1 (Kiểu dữ liệu) Kiểu dữ liệu là một tập
hợp đồng nhất của các giá trị, được trình bày một
cách hiệu quả, được trang bị một tập hợp các phép
toán thao tác với các giá trị này.
Một kiểu là một tập hợp các giá trị, như số nguyên
hoặc khoảng tích phân. Tính từ “đồng nhất”, cho
tất cả tính không chính thức của nó, gợi ý rằng các
giá trị này phải chia sẻ bất kỳ đặc tính cấu trúc nào
khiến chúng tương tự với nhau.
Ví dụ, chúng ta hãy lấy làm ví dụ về các số nguyên
từ 5 đến 10, trong khi chúng ta sẽ không coi là một
loại tập hợp bao gồm số nguyên 2, giá trị chân lý
true và số hữu tỉ 3/4.
Phép toán
Ví dụ, cùng với các số nguyên, chúng ta có thể xem
xét các phép toán cộng, trừ, nhân và chia thông
thường; nếu không, cũng xem xét các phép toán ít
phổ biến hơn như phần dư sau khi chia số nguyên và
nâng lên thành lũy thừa.
Các số thực (giá trị "đúng" trong toán học, đó là
trường được sắp xếp theo thứ tự của kiến trúc sư
hoàn chỉnh duy nhất) không khả dụng một cách hiệu
quả vì có những số thực với khai triển thập phân vô
hạn không thể thu được bằng bất kỳ thuật toán nào.
Các xấp xỉ của chúng trong các ngôn ngữ lập trình
(thực hoặc float) chỉ là các tập con của các số hữu
tỉ.
không biểu diễn đc chính xác các số thực -> nên
xấp xỉ thôi.

8.1.1 Types as Support for Conceptual Organisation


Ví dụ, một chương trình xử lý đặt phòng khách sạn,
sẽ xử lý các khái niệm như khách hàng, ngày tháng,
giá cả, phòng, v.v. Mỗi khái niệm này có thể được
mô tả như một kiểu khác nhau, với tập hợp hoạt
động riêng của nó. Người thiết kế có thể xác định
các kiểu mới và liên kết chúng với các khái niệm
khác nhau, ngay cả khi chúng được biểu thị bằng
các giá trị giống nhau. Ví dụ: cả phòng và giá đều có
thể được biểu thị bằng số nguyên trong khoảng thời
gian cụ thể, nhưng việc biểu thị chúng dưới dạng
các loại riêng biệt làm cho sự khác biệt về khái
niệm của chúng trở nên rõ ràng.
effectively controllable (ví dụ kiểu "room" khác kiểu
vs "price")
8.1.2 Types for Correctness
Các ràng buộc như vậy hiện diện trong các ngôn ngữ
vừa để tránh lỗi phần cứng thời gian chạy (ví dụ:
lệnh gọi đến một đối tượng không phải là một hàm
có thể gây ra lỗi địa chỉ), vừa để tránh các loại lỗi
logic thường bị ẩn bên dưới quy tắc loại vi phạm.
Tổng của một số nguyên và một chuỗi hiếm khi
tương ứng với bất kỳ thứ gì hợp lý.
lập trình viên truyền đạt các cách hợp pháp mà các
đối tượng đã cho có thể được sử dụng; nhưng
(không giống như comment), trình biên dịch (hoặc
máy trừu tượng của ngôn ngữ) phát hiện và báo
hiệu mọi nỗ lực sử dụng sai các đối tượng này.

Tuy nhiên, các kiểu đảm bảo độ đúng tối thiểu sẽ


giúp ích đáng kể trong giai đoạn phát triển của một
chương trình.
Nếu công thức là để biểu thị vận tốc, thì phải có
khoảng cách trong một thời gian; nếu có một gia
tốc, thì phải có khoảng cách theo thời gian bình
phương, v.v. Nếu công thức không chính xác về kích
thước, không nên dành thêm thời gian cho nó vì nó
chắc chắn không chính xác. Mặt khác, nếu nó đúng
về mặt kích thước, nó có thể không chính xác và
phải được xử lý về mặt ngữ nghĩa.
Trong trường hợp này, cách giải quyết là áp dụng
các quy tắc gõ phức tạp hơn, mà không từ bỏ bất kỳ
điều khiển nào, cho phép người ta viết một hàm duy
nhất được tham số theo kiểu. Chúng ta sẽ thấy bên
dưới rằng các ngôn ngữ cho phép loại đa hình này
đang trở nên phổ biến
Do đó, chúng ta hãy phân loại các ngôn ngữ lập
trình đang tồn tại ở đâu đó giữa an toàn và không
an toàn đối với các kiểu, theo khả năng có thể có vi
phạm ràng buộc kiểu trong quá trình thực thi
chương trình mà máy trừu tượng không phát hiện
được.
8.1.3 Types and Implementation
Hình thức tối ưu hóa này có thể thực hiện được vì
thông tin được mang theo các loại cho phép xác
định static kích thước phân bổ cho hầu hết mọi đối
tượng, ngay cả đối với các đối tượng được phân bổ
theo động. Chúng ta sẽ sớm thấy rằng một bản ghi
được hình thành từ một tập hợp các trường, mỗi
trường được đặc trưng bởi tên riêng và kiểu riêng
của nó.
Ví dụ:
struct Professor{
char Name[20];
int Course_code;
}

Nếu bây giờ chúng ta có một biến, p, kiểu


Professor, chúng ta có thể truy cập các trường của
nó bằng cách sử dụng tên p.Name hoặc tên
p.Course_Code. Tuy nhiên, đối tượng p được cấp
phát (trong heap hoặc trên stack), việc truy cập vào
các trường của nó sẽ luôn có thể thực hiện được
thông qua việc sử dụng các hiệu số từ địa chỉ bắt
đầu của p trong bộ nhớ.
8.2 Type Systems
hệ thống loại bao gồm những điều sau đây:
1. Tập hợp các loại ngôn ngữ được xác định trước.
2. Các cơ chế cho phép định nghĩa các kiểu mới.
3. Các cơ chế kiểm soát các loại, chúng tôi phân
biệt các cơ chế sau:
• Quy tắc tương đương chỉ định khi nào hai loại
chính thức khác nhau tương ứng đến cùng một loại.
• Các quy tắc tương thích chỉ định khi nào một
giá trị của một kiểu có thể được sử dụng trong
ngữ cảnh trong đó một loại khác sẽ được yêu cầu.
• Các quy tắc và kỹ thuật cho suy luận kiểu chỉ
định cách ngôn ngữ gán một kiểu cho một biểu thức
phức tạp dựa trên thông tin về các thành phần của
nó.
4. Đặc điểm kỹ thuật về việc (hoặc cái nào) các ràng
buộc được kiểm tra tĩnh hay động.
Chúng tôi đã đưa ra nhiều ví dụ, chẳng hạn như
quyền truy cập vào bộ nhớ không được cấp cho
chương trình hoặc lệnh gọi của một giá trị phi chức
năng
Chúng tôi đã định nghĩa một kiểu là một cặp bao
gồm một tập hợp các giá trị và một tập hợp các
phép toán
Theo phân loại mà chúng ta đã thấy trong hộp ở
trang 135, chúng ta có các giá trị
• Denotable, if they can be associated with a
name.
• Expressible if they can be the result of a
complex expression (that is different from a simple
name).
• Storable if they can be stored in a variable

Hệ thống kiểu là một phương pháp cú pháp có thể


điều chỉnh để chứng minh sự vắng mặt của các hành
vi chương trình nhất định bằng cách phân loại các
cụm từ theo loại giá trị mà chúng tính toán.
Các giá trị của kiểu hàm từ int đến int là không thể
thay đổi được trong hầu hết các ngôn ngữ vì một
tên có thể được đặt bằng cách sử dụng một khai
báo.
int succ (int x){
return x+1;
}
Phép gán một tên succ cho hàm tính toán người kế
nhiệm. Các giá trị hàm nói chung không thể diễn đạt
được trong các ngôn ngữ mệnh lệnh thông thường vì
không có biểu thức phức tạp nào trả về một hàm do
kết quả của việc đánh giá chúng.
Giá trị của kiểu số nguyên nói chung là không thể
thay đổi (chúng có thể được kết hợp với hằng số),
có thể biểu thị và lưu trữ.
8.2.1 Static and Dynamic Checking
Một ngôn ngữ có static typing nếu việc kiểm tra các
ràng buộc kiểu của nó có thể được tiến hành trên
văn bản chương trình tại thời điểm biên dịch.
Nếu không, nó là dynamic typing (đó là nếu việc
kiểm tra xảy ra trong thời gian chạy)
Kiểm tra kiểu động giả định rằng mọi đối tượng (giá
trị) đều có một bộ mô tả thời gian chạy chỉ định kiểu
của nó.
Abstract machine chịu trách nhiệm kiểm tra rằng
mọi hoạt động chỉ được áp dụng cho các toán hạng
có kiểu chính xác. Thông thường, điều này có thể
được thực hiện bằng cách làm cho trình biên dịch
tạo ra mã kiểm tra thích hợp được thực thi trước
mỗi hoạt động. Không khó để thấy rằng kiểm tra
kiểu động định vị lỗi kiểu nhưng không hiệu quả, vì
các hoạt động được trộn lẫn với kiểm tra kiểu.
Ngoài ra, lỗi loại có thể xảy ra chỉ được tiết lộ trong
quá trình thực thi khi chương trình có thể đang
hoạt động với người dùng cuối của nó.

Mặt khác, trong kiểm tra kiểu tĩnh, kiểm tra được
thực hiện trong quá trình biên dịch. Trong lược đồ
này, các kiểm tra được thực hiện và báo cáo cho
người lập trình trước khi chương trình được gửi đến
người dùng.
Khi kiểm tra là hoàn toàn tĩnh, hơn nữa, việc duy
trì rõ ràng thông tin kiểu tại thời điểm thực thi là
vô ích vì tính đúng đắn được đảm bảo tĩnh cho mọi
trình tự thực thi. Do đó, việc thực thi hiệu quả hơn,
do không cần kiểm tra trong thời gian chạy. Rõ ràng
là có một cái giá phải trả. Ngay từ đầu, việc thiết kế
một ngôn ngữ định kiểu tĩnh phức tạp hơn so với
một ngôn ngữ động, đặc biệt nếu, cùng với việc
kiểm tra tĩnh, sự an toàn kiểu được đảm bảo cũng
được mong muốn. Ở vị trí thứ hai, quá trình biên
dịch mất nhiều thời gian hơn và phức tạp hơn, một
cái giá mà người ta sẵn sàng trả, vì quá trình biên
dịch chỉ diễn ra một vài lần (liên quan đến số lần
chương trình sẽ được thực thi) và trên hết là do loại
kiểm tra rút ngắn giai đoạn thử nghiệm và gỡ lỗi.
Điều này ít rõ ràng hơn những cái khác nhưng có
liên quan mật thiết với bản chất của kiểm tra kiểu
tĩnh. Các kiểu tĩnh có thể chỉ ra là có lỗi, trong thực
tế, các chương trình không gây ra lỗi kiểu thời gian
chạy.
int x;
if (0==1) x = "pippo";
else x = 3+4;
-> 2 nhánh có kiểu cho x khác nhau.
kiểm tra tĩnh -> báo lỗi.
Nhánh đầu tiên của điều kiện gán biến số nguyên, x,
cho một giá trị không tương thích với kiểu của nó
nhưng việc thực thi đoạn không gây ra lỗi vì điều
kiện không bao giờ được thỏa mãn. Tuy nhiên, mọi
trình kiểm tra kiểu tĩnh sẽ báo hiệu rằng đoạn này
không chính xác vì kiểu của hai nhánh điều kiện
không giống nhau. Kiểm tra tĩnh do đó thận trọng
hơn kiểm tra động
Ví dụ: 1..10 là kiểu của các số nguyên từ 1 đến 10
(bao gồm). Biểu thức của interval type phải được
kiểm tra động để đảm bảo rằng giá trị của nó được
chứa đúng trong khoảng.
Kiểm tra index.
Nói một cách tổng quát hơn, nếu một ngôn ngữ có
mảng muốn kiểm tra xem chỉ mục của mảng có nằm
giữa các giới hạn của mảng đó hay không, thì phải
thực hiện kiểm tra trong thời gian chạy.
8.3 Scalar Types
Scalar types (hoặc đơn giản) là những kiểu mà giá
trị của nó không phải là tổng hợp của các giá trị
khác.
Để dễ hiểu thì viết như này:
type newtype = expression;
Type errors are undecidable
int x;
P;
x = "pippo";
Chỉ khi P kết thúc, trong trường hợp đó, nó sẽ cố
gắng thực hiện phép gán:
x = "pippo"
rõ ràng vi phạm hệ thống kiểu. Mặt khác, nếu P
không kết thúc, nó sẽ không tạo ra lỗi bởi vì quyền
kiểm soát vẫn luôn bên trong P mà không bao giờ
đạt đến nhiệm vụ quan trọng. Do đó, phân đoạn tạo
ra lỗi kiểu khi và chỉ khi P kết thúc
Điều này giới thiệu tên của kiểu, kiểu mới, có cấu
trúc được đưa ra bằng biểu thức. Trong C, chúng
tôi sẽ viết, với cùng một ý nghĩa:

8.3.1 Booleans

Loại giá trị logic, hoặc boolean, bao gồm:


• Giá trị: Hai giá trị sự thật, True và False.
• Phép toán: một lựa chọn thích hợp từ các phép
toán logic chính: and, or, not, equality, exclusive,..
Nếu có (ví dụ C không có kiểu nào được xây dựng
theo kiểu này), các giá trị của nó có thể được lưu
trữ, thể hiện và biểu thị. Vì lý do định địa chỉ, biểu
diễn bộ nhớ không bao gồm một bit đơn lẻ mà là
một byte (hoặc có thể nhiều hơn nếu cần căn
chỉnh).

8.3.2 Characters
Kiểu ký tự bao gồm:
• Giá trị: một bộ mã ký tự, cố định khi ngôn ngữ
được xác định; phổ biến nhất trong số này là ASCII
và UNICODE.
• Hoạt động: phụ thuộc nhiều vào ngôn ngữ; chúng
ta luôn tìm thấy sự bình đẳng, sự so sánh và một số
cách để chuyển từ một ký tự sang ký tự kế nhiệm
(theo mã hóa cố định) và / hoặc sang ký tự tiền
nhiệm của nó.
Giá trị có thể được lưu trữ, thể hiện và biểu thị.
Biểu diễn trong cửa hàng sẽ bao gồm 1 byte đơn
(ASCII) hoặc 2 byte (UNICODE).
8.3.3 Integers
Loại số nguyên bao gồm:
• Giá trị: Một tập hợp con hữu hạn của các số
nguyên, thường được cố định khi ngôn ngữ được
xác định (nhưng có những trường hợp nó được xác
định bởi máy trừu tượng có thể là nguyên nhân của
một số vấn đề về tính di động). Do các vấn đề về
biểu diễn, khoảng [−2^t, 2^t - 1] thường được sử
dụng.
• Phép toán: so sánh và lựa chọn thích hợp các
toán tử số học chính (cộng, trừ „nhân, chia số
nguyên, phần dư sau khi chia, lũy thừa, v.v.).
Các giá trị có thể được lưu trữ, thể hiện và biểu thị.
Biểu diễn trong bộ nhớ bao gồm một số byte chẵn
(thường là 2, 4 hoặc 8), ở dạng bù 2. (Một số ngôn
ngữ bao gồm hỗ trợ cho các số nguyên có độ dài tùy
ý.)
8.3.4 Reals
Cái gọi là kiểu thực (hoặc số dấu phẩy động) bao
gồm:
• Giá trị: một tập hợp con thích hợp của các số hữu
tỉ, thường được cố định khi ngôn ngữ được định
nghĩa (nhưng có những trường hợp nó được cố định
bởi một cỗ máy trừu tượng cụ thể, một vấn đề ảnh
hưởng sâu sắc đến tính di động); cấu trúc (kích
thước, độ chi tiết, v.v.) của một tập hợp con như
vậy phụ thuộc vào cách biểu diễn được chấp nhận.
• Phép toán: so sánh và lựa chọn thích hợp các
phép toán số chính (cộng, trừ, nhân, chia, lũy thừa,
căn bậc hai, v.v.).
Các giá trị có thể được lưu trữ, thể hiện và biểu thị.
Biểu diễn bộ nhớ bao gồm 4, 8 và 10 byte, ở định
dạng dấu phẩy động như được chỉ định bởi tiêu
chuẩn IEEE 754 (cho các ngôn ngữ và kiến trúc từ
năm 1985).
8.3.5 Fixed Point
• Giá trị: một tập hợp con thích hợp của các số hữu
tỉ, thường được cố định khi ngôn ngữ được xác
định; cấu trúc (kích thước, độ chi tiết, v.v.) của
một tập hợp con như vậy phụ thuộc vào cách biểu
diễn được chấp nhận.
• Phép toán: so sánh và lựa chọn thích hợp các
phép toán số chính (cộng, trừ, nhân, chia, lũy thừa,
chiết xuất căn bậc hai, v.v.).
Các giá trị có thể được lưu trữ, thể hiện và biểu thị.
Biểu diễn trong bộ nhớ bao gồm 4 hoặc 8 byte. Các
giá trị được biểu diễn trong phần bù 2, với một số
bit cố định được dành riêng cho phần thập phân. Số
thực ở điểm cố định cho phép biểu diễn nhỏ gọn
trong một khoảng rộng với few precision places.
8.3.6 Complex
Cái gọi là loại phức hợp bao gồm:
• Giá trị: một tập hợp con thích hợp của các số
phức, thường được cố định bởi định nghĩa của ngôn
ngữ; cấu trúc (kích thước, độ chi tiết, v.v.) của tập
hợp con này phụ thuộc vào cách biểu diễn được
chấp nhận.
• Phép toán: so sánh và lựa chọn thích hợp các
phép toán số chính (tổng, trừ, nhân, chia, lũy thừa,
lấy căn bậc hai, v.v.).
Các giá trị có thể được lưu trữ, thể hiện và biểu thị.
Biểu diễn bao gồm một cặp giá trị dấu phẩy động
8.3.7 Void
Trong một số ngôn ngữ, tồn tại một kiểu nguyên
thủy mà ngữ nghĩa của nó là kiểu có một giá trị duy
nhất. Đôi khi nó được ký hiệu là void (ngay cả khi,
về mặt ngữ nghĩa, tốt hơn là nên gọi nó là đơn vị, vì
nó không phải là tập hợp rỗng mà là một đơn vị):
• Giá trị: chỉ một, có thể được viết là ().
• Hoạt động: không có.
Mục đích của một loại loại này là gì? Nó được sử
dụng để biểu thị loại hoạt động sửa đổi trạng thái
nhưng không trả về giá trị. Ví dụ, trong một số ngôn
ngữ, (nhưng không phải trong C hoặc Java), các
phép gán có kiểu void.
8.3.8 Enumerations
Ngoài các kiểu được xác định trước, chẳng hạn
như những kiểu đã giới thiệu ở trên, chúng tôi cũng
tìm thấy trong một số ngôn ngữ những cách khác
nhau để xác định kiểu mới. Enumerations and
intervals là kiểu vô hướng được xác định bởi người
dùng.
An enumeration type bao gồm một tập hợp các
hằng số cố định, mỗi hằng số được đặc trưng bởi tên
riêng của nó. Bằng ngôn ngữ giả của chúng tôi,
chúng tôi có thể viết định nghĩa sau

type Dwarf = {Bashful, Doc, Dopey, Grumpy, Happy,


Sleepy, Sneezy};
trong đó giới thiệu một loại mới với tên Dwarf và là
một tập hợp bảy phần tử, mỗi phần tử được ký hiệu
bằng tên riêng của nó.
Hơn nữa, kiểm tra kiểu có thể được khai thác để
kiểm tra rằng một biến của kiểu liệt kê chỉ giả định
các giá trị đúng.
Giá trị của kiểu liệt kê thường được biểu diễn bằng
số nguyên một byte.
Các giá trị riêng lẻ được biểu thị bằng các giá trị
liền kề, bắt đầu từ 0. Một số ngôn ngữ (ví dụ như C
và Ada) cho phép lập trình viên chọn các giá trị
tương ứng với các phần tử khác nhau của một kiểu
liệt kê.
In C, our definition of Dwarf takes the form:
enum Dwarf {Bashful, Doc, Dopey, Grumpy, Happy,
Sleepy, Sneezy};
Apart from notational variants, the essential point
is that in C (but not in C++), such a declaration is
substantially equivalent to the following:
typedef int Dwarf;
const Dwarf Bashful=0, Doc=1, Dopey=2,
Grumpy=3, Happy=4, Sleepy=5, Sneezy=6;
Nói cách khác, quy tắc tương đương kiểu cho ngôn
ngữ cho phép một số nguyên được sử dụng thay cho
Dwarf và ngược lại. Kiểm tra kiểu không phân biệt
giữa hai kiểu và do đó, tài liệu tốt hơn là tất cả
những gì có được bằng cách sử dụng phép liệt kê;
kiểm tra loại mạnh hơn không thu được.
Số nguyên hay 1 phần tử trong Drawf đều đc.
Kiểu Con trỏ: Con trỏ chỉ là các biến lưu trữ số
nguyên - nhưng những số nguyên đó lại là địa chỉ bộ
nhớ, thường là địa chỉ của các biến khác. Một con
trỏ lưu trữ địa chỉ của một số biến x được cho là trỏ
tới x. Chúng ta có thể truy cập giá trị của x bằng
cách tham chiếu đến con trỏ.
Int x = 5;
Int *ptr = &x;//khi tham khảo đến thay đổi
được việc tham khảo
Kiểu tham khảo: Một biến của kiểu tham chiếu trỏ
đến một đối tượng hoặc một giá trị trong bộ nhớ,
không phải địa chỉ bộ nhớ.
Int y;
Int &x = y; //(khi tham khảo đến là không thay
đổi được việc tham khảo)
2 cái này tạo ra alias bởi vì cùng một ô nhớ nhưng
có thể tham khảo đến thông qua nhiều biến khác.
Câu hỏi đặt ra:
1.Tìm hiểu kỹ hơn về khái niệm Alias
2.Tìm hiểu kỹ hơn về sự khác nhau -> sau đó
demo ví dụ

Một số ghi chú về con trỏ.


8.3.9 Intervals
Review lại rồi tính tiếp.

Deep binding - shallow binding.

Hôm nay học -> nắm đc cơ chế gọi chương trình con
và data types

Chuẩn IEEE 754


Có thể so sánh sự khác biệt giữa các kiểu đc học
này.
https://www.easyexamnotes.com/p/ppl-pointer-
reference-type.html

=====================================
Kiểu con trỏ
Values are memory addresses and apecial value
nil.
Sử dụng
1 truy cập gián tiếp thông qua địa chỉ
2 cách để quản lý bộ nhớ động
• biến heap-động thường không có tên liên
quan (biến ẩn danh)
• chỉ có thể truy cập thông qua một con trỏ
hoặc một tham chiếu
Con trỏ không phải là:
• các kiểu cấu trúc (mặc dù thường được định
nghĩa bằng toán tử kiểu)
• kiểu scalar (giá trị không phải là dữ liệu, mà
là tham chiếu đến các biến)

Hoạt động cơ bản trên con trỏ:


assign (sets value to an andress)
sử dụng toán tử cho các đối tượng bên
ngoài heap
dereferencing (giá trị của biến được trỏ tới)
implicit
explicit(C++ -> *)
Truy cập record fields:
(*p).age / p->age (C, C++)
Quản lý vùng Heap
yêu cầu phân bổ rõ ràng - malloc (C), new (C +
+)
Vấn đề với con trỏ:
Problem 1: Dangling pointers
Con trỏ chứa địa chỉ của một biến đã bị hủy.
Tại sao nó là một vấn đề?
• biến mới có thể được cấp cho cùng một địa chỉ
• quản lý heap có thể sử dụng bộ nhớ "trống"
Tạo một con trỏ treo lơ lửng:
1 biến mới được cấp phát trên heap, được trỏ tới
bởi p1
2 p2: = p1
3 biến được hủy thông qua p1 (p2 is now dangling)
int *arrPtr1;
int *arrPtr2 = new int[100];
arrPtr1= arrPtr2;
delete[] arrPtr2;
Trong C ++, cả arrayPtr1 và arrayPtr2 hiện đang
dangling!
Solution: cấm hủy.

Problem 2: Lost variables (garbage)


Có một biến trên heap, biến này không thể truy cập
được nữa.
• Tạo một lost variable.
1 biến mới, được trỏ tới bởi p1, được cấp phát
trên heap
2 một số địa chỉ khác được gán cho p1
• hậu quả: rò rỉ bộ nhớ
• giải pháp: thu gom rác

• typed
• có thể trỏ đến bất kỳ đâu (như trong hợp ngữ)
• cực kỳ linh hoạt -> do đó cần thận trọng hơn
• các phép toán: * - dereference, & - address của
một biến

ptr + index = ptr + index*sizeof(*ptr)

int list[10]
int *ptr;
ptr = list;
*(ptr+1) = list[1]
*(ptr+index) = list[index]
ptr[index] = list[index]
con trỏ trỏ đến các hàm
• được sử dụng để chuyển các hàm dưới dạng tham
số
con trỏ kiểu void *
• có thể trỏ đến các giá trị thuộc bất kỳ loại nào
(generic pointers)
• không thể được tham chiếu (vì vậy người kiểm tra
loại sẽ không phàn nàn)
• sử dụng: tham số / kết quả của các hàm hoạt
động trên bộ nhớ (ví dụ: malloc)

=====================================
Một biến của kiểu tham chiếu đề cập đến một đối
tượng hoặc một giá trị trong bộ nhớ, không phải địa
chỉ bộ nhớ.
không có điểm để làm số học (không có các
phép toán số học trên nó)
• C ++
• con trỏ không đổi, luôn được tham chiếu
ngầm
• sử dụng: truyền tham số - giao tiếp hai chiều
(lợi thế hơn con trỏ: không cần tham khảo)
• Java
• không hằng số, có thể trỏ đến bất kỳ phiên
bản nào của cùng một lớp
• được sử dụng để tham chiếu các cá thể lớp
• không có explicit deallocation (no dangling
references)
pointers vs references
Việc đưa (con trỏ) của họ vào các ngôn ngữ cấp cao
là một bước lùi mà chúng ta có thể không bao giờ
khôi phục được.

Reference cung cấp một số tính linh hoạt và khả


năng của con trỏ, mà không có mối nguy hiểm của
chúng
================================
Con trỏ chỉ là các biến lưu trữ số nguyên - nhưng
những số nguyên đó lại là địa chỉ bộ nhớ, thường là
địa chỉ của các biến khác. Một con trỏ lưu trữ địa
chỉ của một số biến x được cho là trỏ tới x. Chúng
ta có thể truy cập giá trị của x bằng cách tham
chiếu đến con trỏ.

2.2 Pointer Syntax/Usage

int *ptr = &x;


int * ptr khai báo con trỏ tới một giá trị số nguyên,
mà chúng ta đang khởi tạo thành địa chỉ của x.
2.2.2 Using Pointer Values
Khi một con trỏ được khai báo, chúng ta có thể
tham chiếu nó bằng toán tử * để truy cập giá trị của
nó:
cout << * ptr; // In giá trị được trỏ tới bởi ptr, //
trong ví dụ trên sẽ là giá trị của x
*ptr = 5;//Thiết lập giá trị cho biến x.
Không có toán tử *, định danh x tham chiếu đến
chính con trỏ, không phải giá trị mà nó trỏ tới:
cout << ptr; // Xuất địa chỉ bộ nhớ của x trong cơ
số 16
2.2.3 const Pointers

1.const int * ptr;

khai báo một con trỏ có thể thay đổi thành một số
nguyên không đổi. Không thể thay đổi giá trị số
nguyên thông qua con trỏ này, nhưng con trỏ có thể
được thay đổi để trỏ đến một số nguyên không đổi
khác. (trỏ được đến số nguyên không đổi khác)
2.int * const ptr;
khai báo một con trỏ hằng cho dữ liệu số nguyên có
thể thay đổi. Giá trị số nguyên có thể được thay đổi
thông qua con trỏ này, nhưng con trỏ có thể không
được thay đổi để trỏ đến một số nguyên không đổi
khác.

Ngăn cấm thay đổi địa chỉ mà ptr chứa hoặc giá trị
mà nó trỏ tới.
Con trỏ thường được đặt thành 0 để báo hiệu rằng
chúng hiện không hợp lệ.
1 int * myFunc () {
2 int phantom = 4;
3 return & phantom ;
4}

phantom được hủy khi thoát khỏi hàm myFunc, vì


vậy con trỏ mà hàm trả về không hợp lệ.
================================
3 References
Khi chúng ta viết void f (int & x) {...} và gọi f (y),
biến tham chiếu x sẽ trở thành một tên khác - bí
danh - cho giá trị của y trong bộ nhớ. Chúng ta cũng
có thể khai báo cục bộ một biến tham chiếu:

int y;
int &x = y; //Makes x a reference to , or alias of , y
Thay đổi x và thay đổi y là như nhau.
Tham chiếu chỉ là các con trỏ được tham chiếu mỗi
khi chúng được sử dụng. Cũng giống như con trỏ,
bạn có thể chuyển chúng xung quanh, trả lại chúng,
đặt các tham chiếu khác cho chúng, v.v.
Sự khác biệt duy nhất giữa sử dụng con trỏ và sử
dụng tham chiếu là:
• Các references là loại được tham chiếu trước -
bạn không bỏ qua chúng một cách rõ ràng.
• Bạn không thể thay đổi vị trí mà một tham chiếu
trỏ đến, trong khi bạn có thể thay đổi vị trí mà một
con trỏ trỏ đến. Do đó, các tham chiếu phải luôn
được khởi tạo khi chúng được khai báo.
• Khi viết giá trị mà bạn muốn tham chiếu đến, bạn
không đặt dấu & trước giá trị đó để lấy địa chỉ của
nó, ngược lại bạn cần thực hiện việc này đối với con
trỏ. (tham khảo địa chỉ con trỏ &var)
Còn đối với tham chiếu không cần

You might also like