So sánh hàm vouws chương trình con năm 2024

Trong thực tế lập trình giải các bài toán khoa học kỹ thuật nhiều khi đòi hỏi những hàm chưa có trong danh sách các hàm chuẩn của Fortran. Nếu tính toán hay lặp lại thường xuyên và đòi hỏi một số bước, ta nên thực hiện như là một hàm thay vì mỗi lần cần lại phải viết ra các lệnh tính toán. Fortran cho phép chúng ta tự xây dựng những hàm của riêng mình theo hai cách: hàm lệnh (statement function) và hàm chương trình con (function subprogram). Nếu tính toán có thể viết trong một lệnh gán duy nhất, thì ta sử dụng hàm lệnh; ngược lại, nếu phải thực hiện nhiều tính toán hay thao tác mới dẫn tới một giá trị kết quả, thì ta dùng hàm chương trình con.

Hàm lệnh

Dạng tổng quát của hàm lệnh là

Tên hàm (Danh sách đối số) = Biểu thức

Những quy tắc phải tuân thủ khi viết và dùng hàm lệnh:

  1. Hàm lệnh được định nghĩa ở đầu chương trình, cùng với các lệnh khai báo kiểu dữ liệu.
  1. Định nghĩa hàm lệnh gồm tên của hàm, sau đó đến các đối số nằm trong cặp dấu ngoặc đơn ở vế bên trái của dấu bằng; biểu thức tính giá trị hàm ở vế bên phải của dấu bằng.
  1. Tên hàm có thể khai báo trong lệnh khai báo kiểu; nếu không thì kiểu của hàm sẽ được xác định theo cách định kiểu ẩn.

Thí dụ 23: Diện tích của tam giác có thể tính theo hai cạnh và góc xen giữa chúng:

DiÖn tÝch=0,5×c¹nh 1×c¹nh 2 × sin (gãc) size 12{ bold "DiÖn tÝch"="0,5" times bold "c¹nh 1" times bold "c¹nh 2 " times " sin " \( "gãc" \) } {}.

Viết chương trình đọc độ dài ba cạnh của một tam giác và các góc đối diện mỗi cạnh. Tính và in diện tích của tam giác theo ba phương án: trong mỗi phương án sử dụng một cặp cạnh và góc tương ứng.

Trong bài tập này ta phải tính diện tích tam giác ba lần, do đó có thể dùng hàm lệnh để tính diện tích tam giác. Chương trình có thể như sau:

PROGRAM DTTG REAL CA, CB, CC, A, B, C, DT, DT1, DT2, DT3, * C1, C2, GOC DT (C1, C2, GOC) = 0.5 * C1 * C2 * SIN (GOC) PRINT *, ' Nhap ba canh tam giac theo thu tu sau:' PRINT *, ' Canh A Canh B Canh C' READ *, CA, CB, CC PRINT *, ' Nhap ba goc (radian) theo thu tu sau:' PRINT *, ' Doi dien: canh A canh B canh C' READ *, A, B, C DT1 = DT (CB, CC, A) DT2 = DT (CC, CA, B) DT3 = DT (CA, CB, C) PRINT * PRINT *, 'Cac dien tich tinh theo ba phuong an la:' PRINT 5, DT1, DT2, DT3 5 FORMAT (1X, 3F7.2) END

Nhận xét rằng trong chương trình này hàm tính diện tích tam giác được định nghĩa ở đầu chương trình bởi tên DT và ba đối số hình thức C1, C2, GOC và giá trị của hàm được tính chỉ bằng một lệnh gán (dòng lệnh thứ ba). Trong chương trình, ở các dòng lệnh thứ 10-12 ta gọi hàm ba lần, mỗi lần ta chuyển các biến khác nhau vào vị trí của các đối số hình thức. Kiểu dữ liệu của hàm DT được mô tả tường minh tại lệnh mô tả REAL ở đầu chương trình. Trong chương trình chính các góc được cho bằng rađian. Nếu các góc nhập vào được cho bằng độ và để không cần chuyển đổi thành rađian trước khi gọi hàm DT tính các diện tích, ta có thể định nghĩa lại hàm DT như sau:

DT(C1, C2, GOC) = 0.5*C1*C2*SIN (GOC * 3.14159 / 180.0)

Hàm chương trình con

Thực chất của hàm chương trình con là một hàm do người lập trình tự xây dựng, do đó người ta còn gọi là hàm do người dùng định nghĩa. Hàm loại này khác với hàm lệnh ở chỗ nó được tính không phải bằng một lệnh gán duy nhất mà bằng một số lệnh. Hàm chương trình con bắt đầu với lệnh không thực hiện để đặc tả hàm bằng một tên và một danh sách đối số như sau

FUNCTION Tên hàm (danh sách đối số)

Sau các lệnh mô tả và tính toán, lệnh RETURN chuyển điều khiển về chương trình chính và lệnh END báo cho chương trình dịch sự kết thúc của chương trình con. Tên hàm được chọn theo quy tắc như tên hằng, tên biến của Fortran. Tên hàm có ý nghĩa mô tả ẩn kiểu giá trị của hàm nếu trong chương trình chính chưa khai báo tường minh. Trong danh sách đối số nếu có từ hai đối số trở lên thì các đối số cách nhau bởi dấu phảy. Tên các đối số cũng có ý nghĩa mô tả ẩn kiểu dữ liệu của đối số. Tuy nhiên, nên mô tả tường minh các đối số của hàm trong phần khai báo các biến của hàm. Trong phần khai báo này, ngoài các đối số còn có thể khai báo các biến khác được dùng chỉ trong nội bộ hàm chương trình con. Vậy hình dáng tổng quát của một hàm chuơng trình con như sau:

FUNCTION Tên (đối số 1, đối số 2, ...)

Các lệnh mô tả các đối số, các biến cục bộ

Các lệnh thực hiện

RETURN

END

Các hàm chương trình con được viết tách ra khỏi chương trình chính và nằm sau lệnh END của chương trình chính. Trong chương trình chính, khi cần tới hàm con người ta thường dùng lệnh gán để gán giá trị tính được bởi hàm con vào một biến hoặc dùng trực tiếp tên hàm con trong các biểu thức. Những giá trị của các đối số thực tế gửi vào các đối số hình thức phải phù hợp về kiểu và đúng tuần tự như trong danh sách đối số. Ta xét thí dụ về xây dựng hàm con và cách dùng nó trong chương trình chính qua thí dụ 24 dưới đây.

Thí dụ 24: Các mô hình số thường tính ra các giá trị của các thành phần kinh hướng Vk size 12{V rSub { size 8{k} } } {} và vĩ hướng Vv size 12{V rSub { size 8{v} } } {} của tốc độ gió ở những điểm khác nhau. Từ những cặp giá trị thành phần kinh hướng và vĩ hướng cần tính ra tốc độ V size 12{V} {} và hướng gió d size 12{d} {}. Tốc độ gió tính bằng công thức

V=Vk2+Vv2 size 12{V= sqrt {V rSub { size 8{k} } rSup { size 8{2} } +V rSub { size 8{v} } rSup { size 8{2} } } } {},

còn hướng gió (góc giữa vectơ gió và hướng bắc) tính theo công thức

α nÕu V v ≥ 0, V k > 0 180 − α nÕu V v ≥ 0, V k < 0 180 + α nÕu V v < 0, V k < 0 360 − α nÕu V v < 0, V k > 0 d = { { { size 12{d=alignl { stack { left lbrace α" nÕu "V rSub { size 8{v} } >= 0," "V rSub { size 8{k} } >0 {} # right none left lbrace "180" - α" nÕu "V rSub { size 8{v} } >= 0," "V rSub { size 8{k} } <0 {} # right none left lbrace "180"+α" nÕu "V rSub { size 8{v} } <0," "V rSub { size 8{k} } <0 {} # right none left lbrace "360" - α" nÕu "V rSub { size 8{v} } <0," "V rSub { size 8{k} } >0" " {} # right no } } lbrace } {}

trong đó α=180πarctg ∣VvVk∣ size 12{α= { {"180"} over {π} } "arctg " lline { {V rSub { size 8{v} } } over {V rSub { size 8{k} } } } rline } {}.

Giả sử các giá trị của các thành phần kinh hướng và vĩ hướng của tốc độ gió đã lưu trong file GIOKV.KQ1 thành hai cột, dòng đầu tiên của file ghi số dòng dữ liệu có trong file. Viết chương trình đọc file GIOKV.KQ1 và ghi kết quả tính tốc độ và hướng gió vào file mới GIO.KQ2 thành 4 cột dạng sau:

TT Vk size 12{V rSub { size 8{k} } } {}Vv size 12{V rSub { size 8{v} } } {} m/s HUONG

XX XX.X XX.X XXX XXX

XX XX.X XX.X XXX XXX

. . .

Khi lập chương trình giải quyết nhiệm vụ này ta nhận thấy cần tính mô đun của tốc độ gió và hướng gió nhiều lần. Vậy có thể sử dụng các hàm, ngoài ra, để tính tốc độ gió có thể dùng loại hàm lệnh, để tính hướng gió dùng hàm chương trình con. Chương trình có thể như sau:

REAL GIOK (200), GIOV (200), V, H, TOCDO, HUONG INTEGER I, N C Mô tả hàm lệnh tính mô đun tốc độ gió TOCDO (VK, VV) = SQRT (VK*VK+VV*VV) OPEN (1, FILE = 'GIO.KQ1', STATUS = 'OLD') READ(1,*) N DO I = 1, N READ(1,*) GIOK (I), GIOV (I) END DO CLOSE (1) OPEN (1, FILE = 'GIO.KQ2', STATUS = 'UNKNOWN') WRITE (1, 4) ‘TT’, ‘VK’, ‘VV’, ‘M/S’, ‘HUONG’ 4 FORMAT(1X, I3, 4F7.1) DO I = 1, N V = TOCDO (GIOK (I), GIOV (I)) H = HUONG (GIOV (I), GIOK (I)) WRITE (1, 5) I, GIOK (I), GIOV (I), V, H 5 FORMAT (1X, I3, 4F7.1) END DO END C Hàm chương trình con FUNCTION HUONG (VV, VK) REAL VV, VK, HG IF (VK .EQ. 0.0) THEN IF (VV .GE. 0.0) THEN HG = 90.0 ELSE HG = 270.0 ENDIF ELSE G = ATAN (ABS (VV / VK)) / 3.14159 * 180.0 IF (VK .GT. 0.0) THEN IF (VV .GE. 0.0) THEN HG = G ELSE HG = 360.0 - G ENDIF ELSE IF (VV .GE. 0.0) THEN HG = 180.0 - G ELSE HG = 180.0 + G ENDIF ENDIF ENDIF HUONG = HG RETURN END

Trong thí dụ này, ta thấy việc tính mô đun tốc độ và hướng được thực hiện nhiều lần. Do đó đã tổ chức tính chúng trong các hàm. Vì giá trị mô đun tính đơn giản bằng một biểu thức nên đã dùng loại hàm lệnh, đó là hàm TOCDO được định nghĩa ở dòng lệnh thứ ba của chương trình chính. Việc tính hướng phải thực hiện nhờ một số phép tính và thao tác, do đó đã dùng loại hàm chương trình con HUONG. Kiểu dữ liệu của hai hàm này được khai báo tường minh ở phần khai báo trong chương trình chính.

Thí dụ 25: Ước lượng nghiệm của đa thức bậc 4

f ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 size 12{f \( x \) =a rSub { size 8{0} } +a rSub { size 8{1} } x+a rSub { size 8{2} } x rSup { size 8{2} } +a rSub { size 8{3} } x rSup { size 8{3} } +a rSub { size 8{4} } x rSup { size 8{4} } } {}

trên khoảng [-5, 5].

Để giải bài toán này, ta sử dụng hai phương pháp tìm nghiệm của phương trình là phương pháp tìm hẹp dần và phương pháp chia đôi.

Trong phương pháp tìm hẹp dần, miền tìm nghiệm được chia thành những khoảng đủ nhỏ sao cho thực tế có thể xem rằng trong một khoảng nào đó chỉ có một nghiệm. Xét các khoảng từ trái sang phải, ta sẽ tìm những chỗ mà đồ thị của đa thức cắt trục x size 12{x} {}: bằng cách tính các giá trị của đa thức tại các đầu mút của khoảng, nếu dấu của các giá trị của đa thức tại các đầu mút khác nhau, thì đồ thị cắt trục x size 12{x} {} và ít nhất có một nghiệm trong khoảng đó. Sau đó khoảng chứa nghiệm lại được chia tiếp thành các phụ khoảng nhỏ hơn và quá trình tìm lại bắt đầu từ đầu trái cho đến khi xác định được khoảng chứa nghiệm. Quá trình chia khoảng và tìm lặp lại cho đến khi nghiệm được xác định đủ độ chính xác.

Phương pháp chia đôi bắt đầu với một khoảng đã được biết là chứa một nghiệm. Khác với phương pháp tìm hẹp dần chia khoảng chứa nghiệm thành nhiều phụ khoảng trước khi tìm, phương pháp chia đôi chỉ chia khoảng chứa nghiệm thành hai nửa, sau đó xác định nửa nào chứa nghiệm. Sự chia đôi tiếp tục cho đến khi tìm được nghiệm với độ chính xác mong muốn.

Trong thí dụ này, ta kết hợp hai phương pháp: phương pháp tìm hẹp dần để xác định khoảng chứa nghiệm. Sau đó phương pháp chia đôi xác định nghiệm với độ chính xác cần thiết. Giả sử phương pháp chia đôi tiếp tục lặp cho đến khi nửa khoảng nhỏ hơn 0,01 và nghiệm tìm được nếu giá trị tuyệt đối của đa thức không lớn hơn 0,001.

INTEGER I, N REAL A (0 : 4) REAL TRAI, PHAI, GIUA, KHOANG, NGHIEM LOGICAL XONG PRINT * , ' NHAP CAC HE SO A0, A1, A2, A3, A4 ' READ*, A PRINT 5, A 5 FORMAT (/, ' DA THUC:' * / 1X, 9X, '4', 11X, '3', 11X, '2' / 1X, 4(F7.3, ' X + '), F7.3) N = 0 DO I = 1, 40 TRAI = -5.0 + REAL (I-1) * 0.25 PHAI = TRAI + 0.25 IF (ABS(F(A, TRAI)) .LT. 0.001) THEN PRINT 15, TRAI, F(A, TRAI) 15 FORMAT (1X, 'NGHIEM = ', F7.3, 3X, * 'F(NGHIEM) = ', F7.3) N = N + 1 ELSE IF (F(A, TRAI)*F(A, PHAI) .LT. 0.0) THEN XONG = .FALSE. KHOANG = PHAI - TRAI 20 IF (KHOANG .GT. 0.01 .AND. .NOT. XONG) THEN GIUA = 0.5 *(TRAI + PHAI) IF (ABS (F(A, GIUA)) .LT. 0.001) THEN XONG = .TRUE. ELSE IF (F(A, GIUA)*F(A, TRAI) .LT. 0.0) THEN PHAI = GIUA ELSE TRAI = GIUA END IF KHOANG = PHAI - TRAI GOTO 20 END IF IF (KHOANG .GT. 0.01) THEN NGHIEM = GIUA ELSE NGHIEM = 0.5 *(TRAI + PHAI) END IF PRINT 15, NGHIEM, F(A, NGHIEM) N = N + 1 END IF END DO TRAI = 5.0 IF (ABS (F (A, TRAI)) .LT. 0.001) THEN PRINT 15, TRAI, F (A, TRAI) N = N + 1 END IF IF (N .EQ. 0) THEN PRINT *, ' KHONG NGHIEM TRONG KHOANG [-5,5]' END IF END REAL FUNCTION F (A, X) REAL A(0 : 4), X F=A(0) + A(1)*X + A(2)*X**2 + A(3)*X**3 + A(4)*X**4 RETURN END

Trong chương trình này, ta đã chia miền tìm nghiệm [-5, 5] thành 40 khoảng, mỗi khoảng dài 0,25 và thực hiện việc kiểm tra từ trái sang phải xem trong những khoảng nào có thể có nghiệm bằng phương pháp tìm hẹp dần bằng vòng DO. Trong mỗi khoảng, nếu giá trị đa thức ở đầu mút trái của khoảng không khác không quá 0,001 thì nhận nghiệm bằng đầu mút trái và chuyển sang xét khoảng tiếp sau ở bên phải. Còn nếu giá trị đa thức ở hai đầu mút của khoảng đang xét khác dấu, thì ta tìm nghiệm theo phương pháp chia đôi. Quá trình lặp để liên tiếp chia đôi khoảng thực hiện bằng vòng lặp IF lôgic và lệnh GOTO vô điều kiện cho đến khi khoảng trở nên nhỏ hơn hoặc bằng 0,01 hoặc giá trị tuyệt đối của đa thức ở giữa khoảng không lớn hơn 0,001. Việc tính giá trị đa thức thực hiện nhiều lần với những giá trị x size 12{x} {} khác nhau nên ta đã tổ chức hàm F để chuyên làm việc này.

Thí dụ 26: Viết chương trình đọc liên tiếp từ bàn phím ba số nguyên, kiểm tra xem chúng có tuần tự chỉ ngày tháng năm hợp lý không và in ra thông báo phù hợp. Chương trình kết thúc khi ta nhập ngày tháng năm đều là những số không.

PROGRAM KTNGAY INTEGER ID, IM, IY 10 PRINT *, 'HAY NHAP BA SO NGUYEN' READ *, ID, IM, IY IF (ID .NE. 0 .AND. IM .NE. 0 .AND. IY .NE. 0) THEN IF (OKDATE (ID, IM, IY)) THEN PRINT*, 'CO THE LA NGAY THANG NAM HOP LY' ELSE PRINT*, ‘KHONG THE LA ’, * ‘NGAY THANG NAM HOP LY’ ENDIF GOTO 10 ENDIF 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 LOGICAL FUNCTION OKDATE (D, M, Y) INTEGER D,M,Y,NNGAY IF (D.LT.1.OR.D.GT.31.OR.M.LT.1.OR.M.GT.12) THEN OKDATE = .FALSE. ELSE NNGAY = SNTT (M, Y) OKDATE = D.LE.NNGAY ENDIF RETURN END

Trong chương trình này dùng hai hàm con: hàm OKDATE và hàm SNTT. Hàm OKDATE có ba đối số nguyên D, M, Y và đưa ra giá trị lôgic là .TRUE. nếu D, M, Y là những số nguyên chỉ ngày tháng hợp lý. Hàm SNTT có hai đối số nguyên và đưa ra giá trị nguyên là số ngày của tháng đang xét. Nhận thấy rằng chương trình chính gọi hàm con OKDATE, về phần mình hàm con OKDATE trong khi thực hiện lại gọi hàm con SNTT.