… End ; Procedure AB ; Var M,N : Integer ; Begin ... End ; Begin … End ; (* Procedure A *) (* -------------------------------------------- *) Procedure B ; Var X,Z : Integer ; Procedure BA ; Begin … End ; Begin … End ; (* Procedure B *) (* --------------------------------------------- *) BEGIN … END. (* Chương trình chính *) Như ta đã biết, các biến được khai báo trong chương trình chính được gọi là biến toàn cục. Các biến này có thể được dùng ở mọi nơi trong chương trình. Các biến được khai báo trong một CTC được gọi là các biến địa phương và nó chỉ có tác dụng trong phạm vi CTC đó hay trong Bloc đó. Khi CTC kết thúc thì các biến này cũng mất tác dụng theo. Để diễn tả tầm tác dụng của các biến, của các khai báo, người ta đưa ra khái niệm mức : chương trình chính có mức 0, các chương trình tiếp theo có mức là 1,2,… tùy theo vị trí khai báo. Trong hình 2, chương trình con A và B có mức là 1, chương trình con AA, AB, BA có mức là 2. Sau đây là một số quy tắc sử dụng : 36 Võ Văn Dũng - Ngôn ngữ lập trình Pascal. + Tầm tác dụng của 1 tên (biến, hằng, kiểu…) được xác định bằng mức Bloc trong đó tên được khai báo và bằng các mức Bloc khác có mức cao hơn và nằm trong Bloc chứa khai báo. Trong ví dụ vừa rồi, biến Y được khai báo trong CTC A (có mức là 1). Như vậy biến Y có thể được sử dụng ở trong CTC AA và AB (là 2 CTC có mức cao hơn và nằm trong CTC A). Ngoài ra Y không thể sử dụng ở CTC B, BA, BB vì chúng không phải là CTC của A. + Tầm quan trọng của các biến khai báo ở mức 0 (chương trình chính)là toàn bộ chương trình. + Ở các mức khác nhau của các CTC, ta có thể khai báo 1 biến có cùng tên với biến ở mức khác. Tên biến này không phải là một biến duy nhất mà là hai biến khác nhau với tầm quan trọng khác nhau. Ví dụ trong hình 2, CTC B có biến địa phương X và trong chương trình chính có biến toàn cụa cũng có là X. Khi đó trong CTc thì biến X địa phương có tác dụng, còn khi CTC kết thúc thì biến toàn cục lại lấy lại tác dụng của nó. Hãy xét ví dụ cụ thể như sau : Program Tam_Tac_Dung) ; Var I : Integer ; (* Biến I toàn cục *) (* ------------------------------------------------- *) Procedure Dia_Phuong ; Var I : Integer ; (* Biến I địa phương *) Begin I := 7 ; Writeln (I : 6) ; End ; (* ------------------------------------------------ *) BEGIN I :=5 ; Writeln(I : 6) ; Dia_Phuong ; Writeln(I : 6) ; END. Kết quả cho ra : 5 (* giá trị của I toàn cục *) 7 (* giá trị của I địa phương *) Võ Văn Dũng - Ngôn ngữ lập trình Pascal. 37 5 (* giá trị của I toàn cục *) Tên biến I được dùng cho cả biến toàn cục và biến địa phương. Đầu tiên biến I toàn cục nhận giá trị bằng 5. Sau đó thủ tục Dia_Phuong được gọi, vì thủ tục này cũng có biến là I (biến địa phương) nên biến I toàn cục được xem như tạm bị treo không dùng đến. Biến địa phương lấy giá trị bằng 7. Sau khi kết thúc chương trình con, biến I địa phương bị mất và biến I toàn cục lại được khôi phục lại tác dụng. Tất nhiên nó vẫn giữ giá trị bằng 5 là giá trị có được trước khi gọi thủ tục Dia_phuong. Trong trường hợp trong thủ tục Dia_phuong, ta muốn chiếu đến biến I toàn cục, ta vẫn có thể dùng nó bằng cách chỉ rõ tên chương trình ngoài tên biến : Tam_tac_dung.i. Cách tham chiếu như trên cũng tương tự như khi ta chỉ ra đường dẫn trực tiếp trên DOS. IV/ Tính đệ qui của chương trình con Trong Procedure và Function có thể có lời gọi của chính nó. Tính chất này dược gọi là tính đệ qui. Thí dụ tính giai thừa qua định nghĩa : N! = 1 x 2 x... x ( N - 1 ) x N hoặc định nghĩa : 1 khi N = 0 N! = N x ( N - 1 )! khi N >= 1 Khi đó, hàm Giai_thua được định nghĩa như sau : Function Giai_thua( N : Integer ) : Integer ; Begin If N = 0 Then Giai_thua := 1 ; Else Giai_thua := N * Giai_thua( N-1 ) ; End ; Một điều cần lưu ý là ta phải hết sức thận trọng lường trước việc kết thúc của quá trình đệ qui này. Trong thí dụ trên, lệnh gán : K := Giai_thua( -1 ) ; sẽ khởi động một quá tình đệ qui rất dài về mặt lý thuyết vì tham số âm bị xử lý sai. 38 Võ Văn Dũng - Ngôn ngữ lập trình Pascal. Ở thí dụ trên, ta đã khai báo giai_thua là Integer nên sẽ bị một hạn chế : chỉ có thể tính với N < 8 vì nếu N >= 8, Giai_thua sẽ mang giá trị lớn hơn 32767 là giới hạn trên của số nguyên. Một trong các biện pháp khắc phục là ta khai báo Giai_thua là Real. Function Giai_thua( N : Integer ) : Real ; Khi sử dụng Giai_thua là Real, ta phải chú ý sử lý thêm một ít. Ví dụ, để viết giá trị của Giai_thua là số thực sang số nguyên, ta phải sử dụng cách viết có quy cách với phần thập phân bị cắt : Writeln ( Giai_thua(12) : 0 : 0 ) ; Thí dụ tính giai thừa ở trên về phương diện ví dụ nó rất đơn giản và dể hiểu. Song về phương diện kĩ thuật lập trình thì đấy là một thí dụ không đẹp lắm vì người ta có thể tính giai thừa một cách tiết kiệm hơn bằng chương trình sau khi sử dụng lệnh lặp While : Function Giai_thua( N : Integer ) : Integer ; Var I, K :Integer ; Begin I := 0 ; K := 1 ; {Phải dùng biến địa phương K để chứa kết quả trung gian} While I < N Do Begin I := I + 1 ; K := K * I ; End ; Giai_thua := K ; (* Gán kết quả từ biến trung gian K vào tên hàm*) End ; Trong cách dùng sau, ta chỉ mất hai ô nhớ địa phương tương ứng với hai biến I và K. Còn trong cách dùng trước, mỗi lần dùng Giai_thua(N), máy lại phải bố trí thêm một ô nhớ chứa kết quả Giai_thua trung gian. Nói chung nên tránh dùng đệ qui khi mà ta có thể dùng phép lặp để tính toán. Võ Văn Dũng - Ngôn ngữ lập trình Pascal. 39 |