Ví dụ các biến tĩnh đợc khai báo trong chơng trình chính sẽ tồn tại trong suốt thời gian chơng trình chạy, các biến tĩnh đợc khai báo trong chơng trình con sẽ tồn tại mỗi khi chơng trình
Trang 1Chơng i Kiểu con trỏ và biến động
1 Khái niệm
- Các biến có kiểu cấu trúc (mảng, tập hợp, bản ghi … ) đã nghiên cứu từ tr ) đã nghiên cứu từ tr ớc đến nay đợc gọi là tĩnh vì chúng đợc xác định và cấp phát bộ nhớ khi biên dịch (Mô tả kiểu và khai báo biến)
- Các biến thuộc kiểu dữ liệu đã học nh mảng, tập hợp, bản ghi, … ) đã nghiên cứu từ tr ợc gọi là biến tĩnh vì đ chúng đợc xác định rõ ràng khi khai báo và sau đó đợc dùng thông qua tên của chúng Thời gian tồn tại của các biến tĩnh cũng là thời gian tồn tại của khối chơng trình có chứa khai baó các biến này (Ví dụ các biến tĩnh đợc khai báo trong chơng trình chính sẽ tồn tại trong suốt thời gian chơng trình chạy, các biến tĩnh đợc khai báo trong chơng trình con sẽ tồn tại mỗi khi chơng trình con đó đợc gọi) Do đó trong nhiều chơng trình, việc sử dụng một số lợng lớn các biến sẽ gây ra hậu quả không đủ bộ nhớ (phạm vi ô nhớ dành cho tất cả các biến tĩnh trên máy vi tính họ IBM PC là 64KB).
Ví dụ khi có khai báo Var DS : array [1 Max] of Real;
Ta phải xác định số phần tử của mảng, nghĩa là trị số của max phải đ ợc xác định khi mô tả Vùng nhớ mà biến DS chiếm là cố định trong thời gian chơng trình làm việc Do đó ở thời
điểm lập trình ta phải hình dung đợc độ lớn của mảng và thờng khai báo d, gây lãng phí bộ nhớ.
- Để khắc phục trờng hợp lãng phí bộ nhớ nh trên, Turbo Pascal cho phép dùng biến động
đ-ợc lu trữ trong vùng HEAP Biến động có thể đđ-ợc tạo ra khi cần thiết và xoá đi khi không dùng Do đó số biến này hoàn toàn không xác định trớc Biến động không có tên và do con trỏ quản lý (Việc đặt tên thực chất là gán cho nó một địa chỉ xác định)
- Việc tạo ra biến động và xoá nó đi nhờ NEW và DISPOSE.
- Việc truy nhập các biến động đợc tiến hành nhờ các biến con trỏ.
2 Biến con trỏ (Pointer Variable)
- Biến con trỏ là một loại biến đặc biệt có kích thớc 2 byte, không dùng để chứa dữ liệu mà
là chứa địa chỉ của các biến động.
- Các giá trị của biến con trỏ không thể đọc vào từ bàn phím hay in ra trực tiếp trên màn hình, máy in (không dùng Write/ Read).
a Khai báo biến con trỏ (Pointer)
- Để khai báo biến con trỏ P dùng để chứa địa chỉ của các biến động có kiểu dữ liệu T với kiểu con trỏ Tp tơng ứng ta khai báo nh sau :
* Khai báo gián tiếp :
Var i , j : songuyen ;
P, Q : HS ;Thì i, j, P, Q là các biến con trỏ.
2) Type Point = ^Nhan_Su;
Nhan_Su = Record
Ten : String [25] ;Tuoi : Integer ;Luong : Real ;End;
1
Trang 2Var P : Point ; {P là biến kiểu con trỏ chứa địa chỉ của biến động có kiểu là Nhan_Su} Pascal cho phép mô tả con trỏ trớc cả mô tả kiểu của biến động.
Viết nh sau cũng đợc:
Ten : String [25] ;Tuoi : Integer ;Luong : Real ; End;
- Hệ số
Somu : Byte ;Heso : Integer ;End ;
Point = ^Dathuc ;Var P : Point;
* Khai báo trực tiếp :
Var i,j : ^Integer;
P : ^T;
Q : ^Real;
* Khai báo tổng quát :
- Khai báo tổng quát cho phép con trỏ có thể trỏ đến bất kỳ vị trí nào trong bộ nhớ Để khai báo biến con trỏ mà không nhất thiết chỉ ra kiểu dữ liệu các biến động do nó quản lý, ta khai báo nh sau :
Var <Tên Biến> : Pointer;
Ví dụ : P : Pointer ; (con trỏ tổng quát)
Q : Pointer ; Chú ý : Để truy nhập đến biến động do con trỏ P quản lý, ta viết nh sau: P^.
Mô tả:
Biến con trỏ P chứa địa chỉ của biến động P^
Trờng hợp P^ chứa nhiều trờng
b) Các thao tác đối với con trỏ
P^
Trang 3Có nghĩa là cho con trỏ Q trỏ vào vùng biến động mà P đang trỏ.
- Nil là một giá trị đặc biệt dành cho các biến con trỏ để báo con trỏ không trỏ vào đâu cả.
Có thể gán Nil cho bất kỳ biến con trỏ nào.
Ví dụ: Gán P := Nil thì P không trỏ đến dữ liệu nào cả.
3 Biến động (Dynamic variable)
3.1 Cấp phát vùng nhớ cho biến động
- Để cấp phát vùng nhớ cho các biến động do con trỏ P quản lý, ta dùng thủ tục New(P) ;
Khi đó máy sẽ tạo ra một vùng nhớ có kiểu và kích thớc do P qui định, hớng con trỏ P trỏ tới vùng biến động trên.
- Nếu trong một chơng trình dùng N lần thủ tục New(P); liên tục thì tạo ra n biến động, song con trỏ P sẽ chỉ vào biến động đợc tạo ra lần cuối cùng.
New (P) ; {tạo ra biến động có địa chỉ trong P}
WITH P^ DO {Câu lệnh WITH đợc phép dùng với biến động}
Begin
Ten := ‘Nguyen An’ ; Tuoi := 20 ;
Dtb := 7.0 ;End;
Le Binh 21 6.5
P
Biến động
đ ợc tạo ra lần 1
Biến động
đ ợc tạo ra lần 2
Trang 4Nh vậy đã tạo ra đợc 2 biến động kiểu HS, sau lần gọi New(P) lần 2, biến động đầu tiên mặc dù vẫn còn nằm trong ô nhớ của nó nhng con trỏ P không chứa địa chỉ của nó nữa mà P
sẽ trỏ vào biến động vừa đợc tạo ra.
Tóm lại : Sau mỗi lần dùng New(P) thì con trỏ P sẽ chứa địa chỉ của biến động P^ vừa
đ-ợc tạo ra, còn các biến động đđ-ợc tạo ra bằng New(P) trớc đó sẽ không đđ-ợc P trỏ đến.
Muốn truy nhập vào các biến động đợc tạo ra trớc đó ta phải có các biện pháp lu trữ địa chỉ chúng lại (phần sau).
3.2 Giải phóng hay thu hồi ô nhớ của biến động
* Đối với 1 biến động:
Để giải phóng hay thu hồi ô nhớ của biến động con trỏ P quản lí ta thực hiện thủ tục DISPOSE (P);
Ví dụ 1 : Var P1, P2 : ^Integer ;
Begin New (P1) ; {tạo ra một biến động kiểu nguyên có địa chỉ trong P1}
P1^ := 1976 ; P2 := P1 ; Writeln('Nam sinh ',P2^) ; {xuất hiện màn hình Nam sinh 1976}
Dispose (P2) ; {giải phóng biến động}
* Đối với nhiều biến động
Để giải phóng hay thu hồi ô nhớ của nhiều biến động (có thể tất cả các biến động) ta dùng cặp thủ tục MARK và RELEASE.
Thủ tục MARK (Pvar) : trong đó PVar là biến con trỏ.
Thủ tục này đánh dấu vị trí đầu tiên của vùng ô nhớ cần giải phóng sau này Sau thủ tục MARK có thể dùng một loạt thủ tục NEW để tạo ra các biến động khác và chúng sẽ chiếm ô nhớ kể từ vị trí đợc đánh dấu.
Thủ tục RELEASE (Pvar) : sẽ xoá tất cả các biến động đã tạo từ khi đánh dấu MARK.
Ví dụ : MARK (R) ;
NEW (P) ; NEW (Q) ; NEW (K) ; RELEASE (R) ;
Trang 5ớc của biến động mới đợc tạo ra.
- STACK là vùng nhớ dành cho các biến cục bộ đợc chơng trình con sử dụng Kích thớc mặc định của nó là 16KB Tuy vậy có thể dùng mục Memory sizes trên Option menu để thay
đổi kích thớc vùng nhớ dành cho STACK.
- Cách phân bố chơng trình, STACK, HEAP trong bộ nhớ của PC.
MemAvail và MaxAvail là 2 hàm kiểm soát HEAP.
- MemAvail cho tổng số byte còn rỗi trên Heap.
- MaxAvail cho số byte liên tục lớn nhất còn rỗi trên Heap.
Các vùng nhớ còn rỗi trên Heap thờng bị phân thành các khối nhỏ do máy cấp phát và giải toả các vùng nhớ trên Heap không theo một trật tự nào Khi tạo biến cấp phát động trên Heap thì điều cần quan tâm là kích thớc của khối chứ không phải là tổng số vùng nhớ còn rỗi trên Heap.
Để tránh tràn Heap trớc khi cấp phát bộ nhớ cho một đối tợng có kích thớc lớn (nh mảng, bản ghi) ta nên dùng hàm MaxAvail để kiểm tra có đủ bộ nhớ không.
Until (n>=1);
New(M); M^:=0;
5
Vùng ô nhớ còn tự do
Các biến động
đã đ ợc tạo ra
Vùng bộ nhớ Stack
Bộ nhớ ch ơng trình và biến tĩnh
Heap : Vùng ô nhớ dành cho biến động
Con trỏ của Stack
Con trỏ của Heap
12 + 22 2!
12 + 22 + 32 2!
12 + 22 + … ) đã nghiên cứu từ tr + n2 2!
S = 1 + + + … ) đã nghiên cứu từ tr… ) đã nghiên cứu từ tr +
Trang 6Cách 2: Program VD4;
Uses Crt;
Type Nguyen = ^Integer;
Var A : Array[1 10] of Nguyen;
Write('Nhap so thu ',i);Readln(A[i]^);
Writeln('Ket qua la S = ',S:10:4); Readln;
END
Ví dụ 2: Lập chơng trình nhập vào 1 danh sách sinh viên (sử dụng các biến con trỏ có thể).
Mỗi sinh viên đợc quản lí bởi các trờng:
Hoten - Họ và tên DM1 - Điểm môn 1 DM2 - Điểm môn 2
Trang 8Ví dụ 5 : Sử dụng biến con trỏ để tính tổng: S1 = 1 + 2 + … ) đã nghiên cứu từ tr + n; S2 = 2 + 4 + … ) đã nghiên cứu từ tr + 2 *n
Readln;
END
Trang 9Chơng iv Tạo th viện các chơng trình con (unit)
1 Cấu trúc chung của một Unit
Unit <Tên_Unit>;
Interface { Khai báo các Unit khác } { Khai báo các hằng, biến, kiểu } { Khai báo các chơng trình con } Implementation
{ Khai báo hằng, tên, biến, kiểu cục bộ } { Cài đặt các chơng trình con }
BEGIN { Các lệnh khởi tạo } END.
Một Unit bao gồm 5 thành phần cơ bản sau:
1) Phần khai báo : Nhằm mục đích khai báo cho chơng trình dịch biết đây là một Unit, nó
đợc định nghĩa nh sau : Unit <Tên_Unit> ; Trong đó <Tên_Unit> phải trùng với phần chính của tên tệp chứa Unit.
Ví dụ : Tên tệp chứa là MyUnit.Pas thì ta phải định nghĩa là : Unit MyUnit;
2) Phần giao tiếp : Khai báo tất cả những gì mà các chơng trình hoặc các Unit khác có thể
sử dụng đợc Phần này đợc bắt đầu bởi từ khoá Interface.
3) Phần cài đặt : Bao gồm các lệnh cụ thể nhằm định nghĩa các thủ tục và hàm đợc khai
báo ở phần giao tiếp Tất cả các khai báo hằng, biến, kiểu trong phần này chỉ có giới hạn trong Unit mà thôi Phần cài đặt đợc bắt đầu bởi từ khoá Implementation.
4) Phần khởi tạo : Bao gồm các lệnh mà các lệnh này sẽ đợc thực hiện trớc khi 1 thủ tục
hoặc hàm của Unit đợc sử dụng Phần này có thể có hoặc không trong Unit và nó đợc bắt
đầu bởi từ khoá BEGIN.
5) Phần kết thúc : Chỉ bao gồm 1 từ khoá END nhằm báo cho chơng trình dịch biết vị trí
kết thúc của một Unit Cũng nh tất cả các chơng trình Pascal khác, tất cả những phần nằm sau END đều không có ý nghĩa.
2 Dịch và sử dụng Unit
a Dịch Unit
- Chọn mục Compile trên menu.
- ấn Enter tại mục Destination để chuyển sang Memory Disk.
Ví dụ : Uses A, B; Trong đó A và B đều chứa thủ tục có tên là M.
Khi viết M; Thực hiện M của B.
A.M; Thực hiện M của A.
Ví dụ : Lập Unit chứa các hàm sau :
- Tính ớc sô chung lớn nhất của hai số nguyên.
- Tính bội số chung nhỏ nhất của hai số nguyên.
Trang 10Function UCLN (a,b : Integer) : Integer;
Function BCNN (a,b : Integer) : Integer;
Writeln('Uoc chung lon nhat cua ',a,' va ',b,' la UCLN = ',UC);
Writeln('Boi chung lon nhat cua ',a,' va ',b,' la BCNN = ',UC);
I Danh sách liên kết đơn theo cấu trúc FIFO (First In First Out) (~Quece hàng đợi) – hàng đợi)
Nguyên tắc tổ chức danh sách liên kết nh sau:
Vùng liên kết của phần tử thứ i chứa địa chỉ của phần tử thứ i+1 (i = 1 n-1), vùng liên kết của phần tử cuối cùng n mang giá trị NIL.
Mỗi phần tử trong danh sách liên kết FIFO gồm 2 vùng chính:
Vùng chứa dữ liệu (Data).
Trang 11Vùng chứa địa chỉ của phần tử khác (ký hiệu: Link / Next).
Ngoài ra còn có các biến chỉ điểm First, Last (kiểu Pointer) để chứa địa chỉ của phần
tử đầu, cuối.
Minh hoạ cách tổ chức danh sách liên kết:
(Việc đa thêm Last thuận lợi cho việc bổ sung thêm phần tử mới)
1 Khai báo kiểu dữ liệu
Mỗi phần tử đợc khai báo kiểu dữ liệu nh sau:
Type
DataType = <Kiểu dữ liệu> ;
Ptr = Item ; Item = Record
Data : DataType;
Next : Ptr ; End ;
Var First, Last, P : Ptr ;
Ví dụ 1: Viết khai báo cho 1 danh sách liên kết FIFO mà mỗi phần tử dùng để chứa 1 số
nguyên.
C1 : Type Nguyen = Integer;
Ptr = ^Item ;
Item = Record Data : Nguyen;
Next : Ptr ;End;
Var First, Last, P : Ptr ;
11
Data Link … ) đã nghiên cứu từ tr Data Link Data Link Data
… ) đã nghiên cứu từ tr … ) đã nghiên cứu từ tr
First
NILLast
Var First, Last, P : Ptr ;
Trang 122 Khởi tạo danh sách liên kết
- Danh sách rỗng First = Nil;
- Các bớc khởi tạo :
Bớc 1 : First = Nil;
Bớc 2 : Tạo 1 biến động mới và gán dữ liệu cho nó (sử dụng New(P)).
Bớc 3 : Nếu First = Nil thì First := P ;
Ngợc lại Last^.Next := P ; Bớc 4 : Last := P ;
Bớc 5 : Quay lại bớc 2 cho đến khi thoả mãn điều kiện kết thúc.
Ví dụ 2 : Lập chơng trình nhập vào một danh sách các số nguyên cho đến khi số nguyên đợc
Var First, Last, P : Pt; m : Integer;
BEGIN First := Nil;
Repeat Write(' Nhap so nguyen : '); Readln(m);
Trang 13- Nhập vào danh sách các số nguyên cho đến khi số nguyên đợc nhập bằng 0.
- In ra màn hình các số nguyên dơng có trong danh sách.
Begin First := Nil;
Repeat Write(' Nhap so nguyen : '); Readln(m);
Trang 14Nhap ; Inds (First);
Readln;
END
4 Tìm kiếm một phần tử trong danh sách.
- Giả sử phần tử cần tìm kiếm có vùng dữ liệu Data là X Phép tìm kiếm đ ợc thực hiện từ phần tử đầu tiên cho đến khi tìm thấy phần tử có vùng dữ liệu X hoặc đến cuối danh sách (không tìm thấy).
- Hàm Tim (X) trả về địa chỉ của phần tử đầu tiên hoặc trả về giá trị Nil nếu không tìm thấy.
- Phân biệt hai trờng hợp:
+ Danh sách cha đợc sắp xếp.
+ Danh sách đợc sắp xếp tăng dần.
a) Danh sách cha đợc sắp xếp.
Phép tìm kiếm chỉ kết thúc khi tìm thấy phần tử đầu tiên hoặc hết danh sách.
Function Tim(X: Kieu_Dl): Point;
Trang 15If F= Nil then Writeln(' Danh sach rong!')
Else Begin P:=f;
While P<> Nil do Begin Write(P^.Sn,' ');
End;
End;
(****************)
BEGIN
Nhap; Inds(First);
Write(' Nhap so nguyen can tim : '); Readln(M);
If Tim(M)<> Nil then Writeln (' Tim thay ') Else Writeln(' Khong tim thay');
Readln;
END
b) Danh sách đợc sắp xếp tăng dần.
Chỉ cần tìm cho đến khi gặp phần tử đầu tiên hoặc khoá của phần tử hiện tại lớn hơn X.
Function Tim(X : Kieu_Dl) : Point;
Var P : Point;
Begin P := First;
While (P<> Nil) and (P^.Data <> X) do P := P^.Link;
If (P<>Nil) and (P^.Data >X) then P:= Nil
Tim := P;
End;
5 Bổ sung một phần tử vào danh sách
a) Bổ sung vào đầu danh sách
Giả sử phần tử cần bổ sung vào danh sách do con trỏ P quản lí
Cần thực hiện các bớc sau:
B1) Cho vùng liên kết của P trỏ vào First (P^.Link := First;)
B2) Cho First trỏ vào P (First := P;)
Thủ tục :
Procedure BSd;
Var P: Point;
Begin New (P);
P^.Data := gia_tri;
P^.Link := First;
First := P;
End;
b) Bổ sung vào sau phần tử trỏ bởi q cho trớc:
Gọi P là phần tử có nội dung New cần chèn vào danh sách, Q là phần tử đứng ngay trớc P.
B1) Cho vùng liên kết của P trỏ vào vùng liên kết của Q (P^.Link := Q^.Link;) B2) Cho vùng liên kết của Q trỏ vào P (Q^.Link := P;) - Nếu Q là phần tử cuối cùng thì Last trỏ vào P (Last := P;) 15 New (1) (2) P First
P New .
(1) (2)
Q
Trang 166 Lo¹i bá mét phÇn tö khái danh s¸ch
a Lo¹i bá phÇn tö P ë ®Çu danh s¸ch
ChØ cÇn cho con trá First trá vµo vïng liªn kÕt cña P.
Trang 17b Loại bỏ phần tử P đứng ngay sau phần tử trỏ bởi Q
Ta cho vùng liên kết của Q trỏ đến vùng liên kết của P.
Ví dụ 8: Lập thủ tục loại bỏ phần tử đầu tiên chứa số nguyên X nhập từ bàn phím
Procedure Loaibo (X : Integer);
If P = First then First := P^.Next
Else Q^.Next := P^.Next;
If P = Last then Last := Q;
Dispose(P);
End;
End;
7 Sắp xếp danh sách
a Thủ tục sắp xếp mảng (tăng hoặc giảm)
Procedure Sapxep(chieu : Integer);
Var i,j,Tg : Integer;
- Sapxep(-1) thì đợc dãy sắp xếp giảm
Trang 18a Sao chép toàn bộ danh sách
Hàm sao chép viết dới dạng đệ quy sẽ trả về địa chỉ phần tử đầu tiên của danh sách mới
đợc sao chép bởi danh sách đã cho
Function Saochep (F: Point) : Point;
Var P : Point;
Begin
If F = Nil then Saochep := Nil
Else Begin New(P);
P^.Data := F^.Data;
P^.Next := Saochep(F^.Next);
Saochep := P;
Trang 19End;
End;
b Sao chép phần tử thoả mãn điều kiện A nào đó:
Function Saochep(F : Point): Point;
Ví dụ 9: Lập chơng trình thực hiện sao chép các phần tử chứa số nguyên dơng từ danh sách
số nguyên đợc nhập thành một danh sách mới
End Else Saochep := Saochep(F^.Next);End;
(**************)Procedure Inds(F:Pt);
End;
End;
End;
(**************)BEGIN
1 Lập chơng trình thực hiện đầy đủ các thao tác sau:
- Nhập 1 danh sách các số nguyên cho đến khi số nguyên đợc nhập bằng 0.
- Tính số lợng các phần tử của danh sách.
- Sắp xếp danh sách các số nguyên theo thứ tự tăng dần.
- In ra màn hình danh sách trớc và sau khi sắp xếp.
19