25/05/2018, 09:28

Chương trình con loại thủ tục

Trong chương 8 chúng ta đã nghiên cứu về các hàm chuẩn, các hàm lệnh và các hàm do người lập trình tự xây dựng. Trong khi một hàm chỉ giới hạn ở việc tính ra một giá trị, thì các thủ tục chương trình con (hay còn gọi là thủ tục do người lập trình tự xây ...

Trong chương 8 chúng ta đã nghiên cứu về các hàm chuẩn, các hàm lệnh và các hàm do người lập trình tự xây dựng. Trong khi một hàm chỉ giới hạn ở việc tính ra một giá trị, thì các thủ tục chương trình con (hay còn gọi là thủ tục do người lập trình tự xây dựng) có thể tính ra một số giá trị, hoặc thực hiện một số thao tác. Trong bài này ta học cách viết các thủ tục và sử dụng các thủ tục trong các bài toán ứng dụng.

Nhiều quy tắc viết và sử dụng các thủ tục chương trình con giống như các quy tắc đối với các hàm chương trình con. Dưới đây liệt kê những khác biệt giữa các thủ tục và các hàm.

1) Một thủ tục không biểu diễn một giá trị, do đó tên của nó chỉ là đại diện cho một đoạn chương trình, không chỉ định kiểu của dữ liệu đầu ra.

2) Dòng lệnh đầu tiên trong một thủ tục thông báo tên thủ tục và danh sách đối số

SUBROUTINE Tên thủ tục (danh sách đối số)

3) Chương trình chính gọi một thủ tục bằng lệnh CALL có dạng tổng quát như sau:

CALL Tên thủ tục (danh sách đối số)

4) Thủ tục dùng danh sách đối số không chỉ cho đầu vào mà cả cho những giá trị gửi ra chương trình chính gọi nó. Các đối số của thủ tục được dùng trong lệnh CALL là những đối số thực tế, còn các đối số sử dụng trong thủ tục là những đối số hình thức. Các đối số trong lệnh CALL phải phù hợp về kiểu, số lượng và thứ tự với những đối số trong thủ tục.

5) Một thủ tục có thể tính ra một giá trị, nhiều giá trị hoặc không giá trị nào cả. Một thủ tục có thể sử dụng một giá trị đầu vào, nhiều giá trị đầu vào hoặc không có giá trị đầu vào.

6) Nhãn lệnh, tên biến trong thủ tục được chọn độc lập với chương trình chính. Những biến dùng trong thủ tục mà không phải là các đối số của thủ tục gọi là các biến cục bộ, các giá trị của chúng không xử lý được trong chương trình chính.

7) Cần đặc biệt thận trọng khi sử dụng các mảng nhiều chiều trong các thủ tục. Nên chỉ định cả kích thước khai báokích thước sử dụng thực tế với các mảng hai hoặc nhiều chiều.

8) Giống như các hàm, lệnh RETURN ở cuối các thủ tục dùng để chuyển điều khiển trở về chương trình chính, lệnh END để báo kết thúc thủ tục.

9) Trong lưu đồ khối các thao tác được thực hiện bên trong thủ tục được ký hiệu bằng biểu tượng đồ họa sau đây:

10) Một thủ tục có thể dùng các hàm con khác hoặc gọi các thủ tục khác, nhưng nó không thể tự gọi chính nó. (Trong Fortran 90 cho phép dùng các thủ tục đệ quy có thể tự gọi chính mình.)

Những thí dụ dưới đây giúp chúng ta học cách viết các thủ tục và sử dụng nó trong chương trình chính như thế nào.

Thí dụ 27: Chương trình tính các đặc trưng thống kê: trung bình, phương sai và độ lệch chuẩn của chuỗi x size 12{x} {} gồm n size 12{n} {} số liệu quan trắc. Các công thức sau tính như sau:

mx=∑i=1nxin , Dx=∑i=1nxi2n−1−mx2, σx=Dx size 12{m rSub { size 8{x} } = { { Sum cSub { size 8{i=1} } cSup { size 8{n} } {x rSub { size 8{i} } } } over {n} } " , "D rSub { size 8{x} } = { { Sum cSub { size 8{i=1} } cSup { size 8{n} } {x rSub { size 8{i} } rSup { size 8{2} } } } over {n - 1} } - m rSub { size 8{x} } rSup { size 8{2} } ", "σ rSub { size 8{x} } = sqrt {D rSub { size 8{x} } } } {}.

Ta thấy rằng mỗi đại lượng trên có thể tính được bằng một hàm riêng biệt. Nhưng ta cũng có thể tính luôn một lúc cả ba đại lượng bằng cách tổ chức tính chúng trong một thủ tục. Chương trình dưới đây cho phép đọc vào kích thước n size 12{n} {} và các giá trị của chuỗi x size 12{x} {}. Sau đó gọi thủ tục STAT để tính các đặc trưng thống kê. Cuối cùng là in ra kết quả.

Thấy rằng thủ tục STAT có tất cả 5 đối số hình thức, trong đó hai đối số đầu vào là mảng một chiều X và kích thước mảng N, ba đối số đầu ra là AVER, VARI và STDV. Khi gọi thủ tục này trong chương trình chính, ta gửi vào các đối số thực tế là X, N, TBINH, PSAI và DLC. Kết quả tính trung bình, phương sai và độ lệch chuẩn trong thủ tục chương trình con được lưu vào các biến TBINH, PSAI, DLC của chương trình chính. Hãy chú ý rằng: vì thủ tục chương trình con là môđun độc lập, nên tên các đối số của nó có thể trùng với tên của các biến trong chương trình chính, trong thí dụ này là đối số X và N. Ở đây ta thấy, trong chương trình con, có thể định nghĩa kích thước của mảng bằng biến N (trong lệnh REAL X(N)). Nhớ rằng điều này chỉ cho phép trong chương trình con.

PROGRAM THKE
INTEGER N, I
REAL X(99), TBINH, PSAI, DLC
PRINT *, ' NHAP DO DAI CHUOI (<100)'
READ *, N
PRINT *, ' NHAP CAC GIA TRI CUA X:'
5 FORMAT (1X, ' X(', I2, '): ')
DO I = 1, N
WRITE (*, 5) I
READ *, X (I)
ENDDO
CALL STAT (X, N, TBINH, PSAI, DLC)
WRITE(*, 8) TBINH, PSAI, DLC
8 FORMAT (1X, ' T. BINH = ', F7.2, ' PH. SAI = ',
* F7.2, ' DL CHUAN = ', F7.2)
END
SUBROUTINE STAT (X, N, AVER, VARI, STDV)
REAL X (N), AVER, VARI, STDV
INTEGER N, I
AVER = 0.0
VARI = 0.0
DO I = 1, N
AVER = AVER + X (I)
VARI = VARI + X (I) * X (I)
END DO
AVER = AVER / REAL (N)
VARI = VARI / REAL (N-1) - AVER * AVER
STDV = SQRT (VARI)
RETURN
END

Thí dụ 28: Xử lý ngày tháng. Trong thực tế xử lý số liệu quan trắc khí tượng thủy văn, ta thường hay phải để ý đến ngày tháng của số liệu. Trong mục này sẽ xét một thí dụ về xử lý ngày tháng với thí dụ sau: Viết chương trình nhập vào một ngày tháng năm bất kỳ, in ra ngày tháng năm của ngày hôm sau. Việc xác định ngày tháng năm của ngày hôm sau so với ngày tháng năm hiện tại sẽ được thực hiện trong một chương trình con thủ tục, ta gọi tên là thủ tục HOMSAU. Vì ở đây kết quả sẽ cho ra ba giá trị nguyên tuần tự chỉ ngày, tháng và năm. Chương trình có thể như sau:

PROGRAM TGNGAY
INTEGER ID, IM, IY
PRINT *, 'HAY NHAP NGAY THANG NAM BAT KY'
READ *, ID, IM, IY
CALL HOMSAU (ID, IM, IY)
PRINT 20, ID, IM, IY
20 FORMAT (1X, 'NGAY HOM SAU LA ', I2, '-', I2 , '-', I4)
END
SUBROUTINE HOMSAU (D, M, Y)
INTEGER D, M, Y
D = D + 1
IF (D .GT. SNTT (M, Y)) THEN
D = 1
M = M + 1
IF (M .GT. 12) THEN
M = 1
Y = Y + 1
END IF
END IF
RETURN
END
INTEGER FUNCTION SNTT (M, Y)
INTEGER M, Y
IF (M .EQ. 2) THEN
SNTT = 28
IF ((MOD(Y,100) .NE. 0 .AND. MOD(Y,4) .EQ. 0) .OR.
* (MOD (Y,100) .EQ. 0 .AND. MOD (Y/100, 4) .EQ .0))
* SNTT = 29
ELSE IF (M.EQ.4.OR.M.EQ.6.OR.M.EQ.9.OR.M.EQ.11) THEN
SNTT = 30
ELSE
SNTT = 31
ENDIF
RETURN
END

Các thao tác để chuyển thành hôm sau được thực hiện trong chương trình con thủ tục HOMSAU. Hãy chú ý rằng trong thủ tục con HOMSAU lại gọi thực hiện một hàm con khác là SNTT để tính số ngày của tháng đang xét. Hàm này đã được nhắc tới trong thí dụ 26, trang 142, ở đây ghi lại để sinh viên tiện theo dõi.

Dưới đây ta xét một thí dụ về xử lý số liệu khí tượng có liên quan tới ngày tháng.

Thí dụ 29: Tính các giá trị trung bình tháng của một yếu tố khí tượng thủy văn. Giả sử với file dữ liệu về các yếu tố khí tượng thủy văn đã mô tả trong thí dụ 20 (trang 124).

Nhớ lại rằng file HONDAU.MAT có quy cách ghi như sau: Dòng trên cùng ghi tên trạm. Dòng thứ 2 có hai số nguyên viết cách nhau lần lượt chỉ tổng số ngày quan trắc và số yếu tố được quan trắc. Dòng thứ ba có 6 số nguyên viết cách nhau lần lượt chỉ ngày, tháng, năm đầu và ngày, tháng, năm cuối quan trắc. Dòng thứ 4 là tiêu đề cột liệt kê tên tất cả các yếu tố được quan trắc, mỗi tên được ghi với độ rộng 8 vị trí. Các dòng tiếp theo lần lượt ghi giá trị của các yếu tố, mỗi dòng một ngày. Các giá trị ngày của nhiệt độ không khí được ghi ở cột thứ hai của file này. Viết chương trình tính giá trị trung bình tháng của nhiệt độ không khí trong tất cả các năm quan trắc.

Ở đây ta thấy, muốn tính trung bình tháng chỉ việc cộng tất cả các giá trị ngày và chia tổng cho số ngày của tháng đang xét. Nhưng vì số ngày trong mỗi tháng có thể khác nhau, nên ta cần có những thủ tục xử lý ngày tháng. Chương trình sau đây sẽ thực hiện kiểu xử lý như vậy.

REAL X, TONG, TB (100, 12)
INTEGER D1, M1, Y1, D2, M2, Y2, Y, M, I
OPEN (1, FILE = ‘HONDAU.MAT’, STATUS = ‘OLD’)
READ (1, *)
READ (1, *) N
READ (1, *) D1, M1, Y1, D2, M2, Y2
READ (1, *)
Y = Y1
I = 1
2 IF (Y1 .GT. Y2) GOTO 4
READ (1, *) X, X
IF (D1 .EQ. 1) THEN
TONG = 0.0
M = SNTT (M1, Y1)
END IF
TONG = TONG + X
IF (D1 .EQ. M) THEN
TB (I, M1) = TONG / M
IF (M1.EQ.12) I = I + 1
END IF
CALL HOMSAU (D1, M1, Y1)
GOTO 2
4 CLOSE (1)
PRINT *, ‘ NHIET DO KHONG KHI TRUNG BINH THANG’
PRINT ‘(A5, 12I5)’, ‘NAM’, (J, J = 1, 12)
K = 1
DO I = Y, Y2
PRINT ‘(A5, 12F5.1)’, I, (TB (K, J), J =1, 12)
K = K + 1
END DO
END

Ta thấy trong chương trình này đã sử dụng hàm SNTT và thủ tục HOMSAU mà chúng ta đã xây dựng trong thí dụ 28. Hàm SNTT được gọi vào mỗi ngày đầu tháng để tính số ngày của tháng đó và gán vào biến M chuẩn bị cho việc tính trung bình sau khi giá trị nhiệt độ ngày cuối cùng của tháng được cộng vào biến TONG. Còn thủ tục HOMSAU được gọi liên tục để tăng ngày hiện hành lên một ngày sau khi một số liệu được đọc.

Qua thí dụ 20 và thí dụ này, sinh viên cũng cần chú ý ghi nhớ cách đọc số liệu kiểu bỏ qua một số cột trong file chứa bảng số liệu có nhiều cột.

Dưới đây là hai thí dụ liên quan tới các thủ tục thao tác chuỗi thường có thể rất có ích trong thực tiễn xử lý thống kê chuỗi số liệu khí tượng thủy văn.

Thí dụ 30: Chèn một giá trị vào danh sách. Trong thí dụ này, ta viết một thủ tục cho phép chèn một giá trị mới vào một danh sách đã sắp xếp. Các đối số của thủ tục gồm mảng một chiều X, biến COUNT chỉ số giá trị dữ liệu thực tế trong mảng, biến LIMIT chứa kích thước mô tả của mảng và biến NEW chứa giá trị cần chèn vào mảng. Trường hợp phần tử mới được chèn vào mảng đã đầy, tức giá trị COUNT bằng giá trị LIMIT thì giá trị cuối cùng trong mảng sẽ bị cắt bỏ.

SUBROUTINE INSERT (LIMIT, NEW, COUNT, X)
INTEGER LIMIT, NEW, COUNT, X (LIMIT), J, K
LOGICAL DONE
DONE = .FALSE.
J = 1
5 IF (J .LE. COUNT .AND. .NOT. DONE) THEN
IF (X (J) .LT. NEW) THEN
J = J + 1
ELSE
DONE = .TRUE.
END IF
GOTO 5
END IF
IF (J .GT. COUNT) THEN
IF (COUNT .LT. LIMIT) THEN
COUNT = COUNT + 1
X (COUNT) = NEW
END IF
ELSE
IF (COUNT .LT. LIMIT) COUNT = COUNT + 1
DO K = COUNT, J+1, -1
X (K) = X (K - 1)
END DO
X (J) = NEW
END IF
RETURN
END

Thí dụ 31: Xóa một giá trị khỏi danh sách. Trong trường hợp này, ta tìm trong danh sách giá trị bằng giá trị định xóa và loại giá trị đó khỏi danh sách. Khác với thủ tục chèn, trong thủ tục xóa không cần đối số LIMIT vì khi xóa một giá trị khỏi danh sách thì số phần tử thực của mảng chỉ có thể nhỏ đi, không sợ chỉ số mảng vượt quá kích thước mô tả của mảng.

SUBROUTINE DELETE (OLD, COUNT, X)
INTEGER OLD, COUNT, X (COUNT), J, K
LOGICAL DONE
DONE = .FALSE.
J = 1
5 IF (J .LT. COUNT .AND. .NOT. DONE) THEN
IF (X (J) .LT. OLD) THEN
J = J + 1
ELSE
DONE = .TRUE.
END IF
GOTO 5
END IF
IF (J .GT. COUNT .OR. X (J) .GT. OLD) THEN
PRINT *, 'GIA TRI XOA KHONG CO TRONG DANH SACH'
ELSE
COUNT = COUNT - 1
DO K = J, COUNT
X (K) = X (K + 1)
END DO
END IF
RETURN
END

1) Kiểm tra xem các biến, các biểu thức trong danh sách đối số ở lệnh CALL có phù hợp về kiểu và thứ tự như trong lệnh khai báo thủ tục không.

2) Nên khai báo tường minh tất cả các biến trong thủ tục để tránh định kiểu ngầm định sai.

3) Sử dụng lệnh PRINT trong thủ tục để định vị lỗi.

4) Kiểm tra thử từng thủ tục trước khi gộp chúng vào chương trình chính cùng với những chương trình con khác.

5) Kiểm tra từng thủ tục với một số tập dữ liệu để phát hiện những điều kiện đặc biệt gây lỗi.

6) Phong cách lập trình:

- Khi quyết định sử dụng chương trình con thủ tục hãy chọn tên thủ tục sao cho nó có tính gợi nhớ.

- Hãy sử dụng tên các biến trong danh sách đối số của thủ tục cùng tên với các biến là đối số thực tế trong chương trình chính. Trong trường hợp thủ tục được gọi nhiều lần với những đối số thực tế khác nhau, hãy chọn tên trong danh sách đối số khác biệt để tránh nhầm với các biến trong chương trình chính.

- Trong danh sách đối số, nên liệt kê riêng các đối số đầu vào trước, sau đó mới đến các đối số đầu ra.

Bài tập

1. Giả sử có mảng một chiều X với 100 giá trị thực. Hãy viết một thủ tục tạo ra mảng Y theo cách mỗi phần tử của mảng Y bằng phần tử tương ứng của mảng X trừ đi phần tử nhỏ nhất.

2. Viết một thủ tục nhận một mảng giá trị thực X với 50 hàng và 2 cột và trả lại chính mảng đó nhưng dữ liệu được sắp xếp lại theo chiều tăng dần của cột thứ 2.

3. Viết một thủ tục nhận một mảng giá trị thực X với n size 12{n} {} dòng m size 12{m} {} cột và trả về một mảng Y cùng số dòng, số cột nhưng dữ liệu được biến đổi sao cho các phần tử tương ứng của cột thứ nhất và cột thứ J được đổi chỗ cho nhau.

4. Giả sử cho trước hai ma trận A size 12{A} {} ( n size 12{n} {} dòng, m size 12{m} {} cột) và ma trận B size 12{B} {} ( m size 12{m} {} dòng, l size 12{l} {} cột). Tích AB size 12{ ital "AB"} {} sẽ là ma trận C size 12{C} {} ( n size 12{n} {} dòng, l size 12{l} {} cột) với các phần tử được tính theo công thức

ci,j=∑k=1mai,kbk,j(i=1,...,n;j=1,...,l) size 12{c rSub { size 8{i,j} } = Sum cSub { size 8{k=1} } cSup { size 8{m} } {a rSub { size 8{i,k} } b rSub { size 8{k,j} } } " " ( i=1, "." "." "." ," "n; j=1, "." "." "." ," "l ) } {}.

Viết thủ tục TICHMT (A, B, N, M, L, C) với các đối số đầu vào là ma trận A size 12{A} {}, ma trận B size 12{B} {}, các tham số N, M, L và đối số đầu ra là ma trận C size 12{C} {}.

5. Hệ phương trình đại số tuyến tính n size 12{n} {} ẩn

a 11 x 1 + a 12 x 2 + . . . + a 1n x n b 1 a 21 x 1 + a 22 x 2 + . . . + a 2n x n b 2 . . . . . . . . . . . . . . . a n1 x 1 + a n2 x 2 + . . . + a nn x n b n } size 12{ left none matrix { a rSub { size 8{"11"} } x rSub { size 8{1} } {} # +{} {} # a rSub { size 8{"12"} } x rSub { size 8{2} } {} # +{} {} # "." "." "." {} # +{} {} # a rSub { size 8{1n} } x rSub { size 8{n} } {} # ={} {} # b rSub { size 8{1} } {} ## a rSub { size 8{"21"} } x rSub { size 8{1} } {} # +{} {} # a rSub { size 8{"22"} } x rSub { size 8{2} } {} # +{} {} # "." "." "." {} # +{} {} # a rSub { size 8{2n} } x rSub { size 8{n} } {} # ={} {} # b rSub { size 8{2} } {} ## "." "." "." {} # {} # "." "." "." {} # {} # "." "." "." {} # {} # "." "." "." {} # {} # "." "." "." {} ## a rSub { size 8{n1} } x rSub { size 8{1} } {} # +{} {} # a rSub { size 8{n2} } x rSub { size 8{2} } {} # +{} {} # "." "." "." {} # +{} {} # a rSub { size 8{ ital "nn"} } x rSub { size 8{n} } {} # ={} {} # b rSub { size 8{n} } {} } " " right rbrace } {}

được viết dưới dạng ma trận như sau

A x = b

trong đó

A=aij=a11a12...a1na21a22...a2n............an1an2...ann size 12{A= left (a rSub { size 8{ ital "ij"} } right )= left (" " matrix { a rSub { size 8{"11"} } {} # a rSub { size 8{"12"} } {} # "." "." "." {} # a rSub { size 8{1n} } {} ## a rSub { size 8{"21"} } {} # a rSub { size 8{"22"} } {} # "." "." "." {} # a rSub { size 8{2n} } {} ## "." "." "." {} # "." "." "." {} # "." "." "." {} # "." "." "." {} ## a rSub { size 8{n1} } {} # a rSub { size 8{n2} } {} # "." "." "." {} # a rSub { size 8{ ital "nn"} } {} } " " right )} {}; b=b1b2...bn size 12{b= left (" " matrix { b rSub { size 8{1} } {} ## b rSub { size 8{2} } {} ## "." "." "." {} ## b rSub { size 8{n} } } " " right )} {}; x=x1x2...xn size 12{x= left (" " matrix { x rSub { size 8{1} } {} ## x rSub { size 8{2} } {} ## "." "." "." {} ## x rSub { size 8{n} } } " " right )} {}.

Hãy viết thủ tục GAUSS (A, B, N, X) nhận vào các mảng A, B, số ẩn N của hệ và tính ra mảng X theo phương pháp loại biến của Gauss. Xem giải thích về phương pháp Gauss trong phụ lục 2.

0