Professional Documents
Culture Documents
GIỚI THIỆU
Tà i liệ u được trình bà y dưới hình thức tự họ c .
Bao gò m cá c nọ i dung cơ bả n nhá t củ a Fortran 90 .
Thực hà nh lạ p trình ở cuó i mõ i chương .
Kết hợp phần mềm GNUPLOT để vễ đò thị .
2018
LỜI NÓI ĐẦU
Tài liệu “ Giới thiệu NGÔN NGỮ LẬP TRÌNH Fortran 90 “ được biên soạn nhằm giúp
các bạn sinh viên các khối, ngành khoa học kỹ thuật, khoa học tự nhiên quan tâm đến ngôn ngữ
Fortran nắm được phương pháp căn bản để viết văn bản chương trình và chạy các chương trình
tính toán bằng Fortran 90, phục vụ yêu cầu học tập , thực hiện các bài tập lớn ,luận văn tốt nghiệp.
Fortran có lịch sử phát triển lâu đời qua nhiều thế hệ máy tính với các phiên bản khác nhau
, đến nay vẫn là ngôn ngữ có nhiều ưu thế trong lĩnh vực tính toán số . Fortran được xem là ngôn
ngữ lập trình rất phố biến đối với các sinh viên, kỹ sư, khoa học gia thuộc các lĩnh vực khoa học ,
kỹ thuật tại các nước Âu – Mỹ. Việc học tập Fortran còn giúp các bạn sinh viên có điều kiện học
tập ở nước ngoài , đọc hiểu các chương trình viết bằng ngôn ngữ Fortran được trình bày trong các
sách , tài liệu , bài báo nước ngoài .
Hai phiên bản Fortran được sử dụng phổ biến nhất hiện nay là FORTRAN 77 và Fortran 90
, trong đó phiên bản Fortran 90 được xem là một phiên bản quan trọng trong lịch sử phát triển
Fortran . Fortran 90 có rất nhiều điểm mới so với các phiên bản trước như văn bản chương trình
được phép viết theo kiểu “ định dạng tự do “ ( free format ) khác với kiểu “ định dạng cố định theo
cột “ ( fixed format ) , các cấu trúc điều khiển , xử lý mảng , xử lý vào / ra , chương trình con , cấp
phát động …tạo nhiều thuận lợi hơn cho người lập trình .Từ kiến thức Fortran 90 , các bạn có thể
dễ dàng tiếp cận các phiên bản tiếp theo như Fortran 95 , Fortran 2003, Fortran 2008 .
Tài liệu này sử dụng các trình biên dịch miễn phí như G77/GFortran của GNU Compiler
Collection (GCC) và G95 thông qua phần mềm Force 2.0 là một môi trường phát triển tích hợp (
IDE ) gọn nhẹ dùng để viết văn bản chương trình , tiến hành biên dịch , liên kết và chạy thử chương
trình . Tài liệu cũng giới thiệu thêm trình biên dịch Fortran g95 và Silverfrost FTN95 Personal
Edition để bạn đọc tham khảo , chạy kiểm chứng chương trình .
Phần phụ lục giới thiệu tổng quát phần mềm GNUPLOT dùng để vẽ các đồ thị 2D, 3D lấy
dữ liệu từ kết quả tính toán của Fortran và một chương trình mẫu trong bộ LINPACK dùng để giải
hệ phương trình tuyến tính là các ví dụ về các tiện ích miễn phí từ các tổ chức phát triển Fortran
cung cấp cho người sử dụng . Các bạn sinh viên cần tham khảo , sử dụng các chương trình tiện ích
rất phong phú đã được lập trình sẵn từ các thư viện Fortran trên Internet , phục vụ cho việc học tập,
nghiên cứu .
Mặc dù đã cố gắng rà soát kỹ các nội dung trong tài liệu cũng như chạy kiểm tra các chương
trình ví dụ nhưng khó tránh khỏi có những sai sót hoặc những điểm trình bày chưa phù hợp . Tác
giả rất mong và xin cám ơn sự đóng góp của các bạn đọc , các bạn sinh viên gần xa để tài liệu
ngày càng hoàn thiện hơn .
La Văn Hiển
Email : hienbk58@yahoo.com
Cập nhật : 16/06/2019.
1
CHƯƠNG 1 : GIỚI THIỆU CHUNG VỀ NGÔN NGỮ LẬP TRÌNH FORTRAN
1.1 Sơ lược về lịch sử ngôn ngữ lập trình Fortran .
1.1.1 Nguồn gốc .
Fortran ( hay FORTRAN ) được viết tắt từ cụm từ Mathematical FORmula TRANslating
System ( hệ thống dịch công thức toán học ) , là ngôn ngữ lập trình cấp cao đầu tiên , kiểu biên
dịch ( compiler ) , do John Backus ( 1924 – 2007 ) cùng một nhóm kỹ sư điện toán thuộc tập đoàn
công nghiệp máy tính IBM ( International Business Machines ) thiết kế và phát triển từ giữa những
năm 50 của thế kỷ XX . Ban đầu tên ngôn ngữ được viết là FORTRAN , nhưng xu hướng hiện
nay được viết chuẩn hóa thành Fortran ( chỉ viết hoa kí tự đầu ) .
1.1.2 Phạm vi ứng dụng .
Ngôn ngữ lập trình Fortran hiện nay có đầy đủ tính năng của một ngôn ngữ lập trình cấp
cao hiện đại, hỗ trợ nhiều kiểu lập trình , có khả năng ứng dụng trên nhiều lĩnh vực khác nhau ,
tuy nhiên Fortran thích hợp hơn cho các ứng dụng tính toán số trong lĩnh vực khoa học kỹ thuật ,
khoa học tự nhiên và được xem là ngôn ngữ lập trình rất phổ biến đối với các sinh viên, kỹ sư,
khoa học gia thuộc các lĩnh vực nêu trên tại các nước Âu Mỹ .
1.1.3 Các phiên bản của ngôn ngữ Fortran .
FORTRAN (1957) , FORTRAN II ( 1958) , FORTRAN III (1958) , FORTRAN IV (1961)
, FORTRAN 66 (1966) được xem là các phiên bản cũ , lỗi thời .
Các phiên bản được sử dụng phổ biến hiện nay là FORTRAN 77 , Fortran 90 và Fortran
95 . Phiên bản Fortran 90 , được xem là cột mốc quan trọng trong lịch sử phát triển ngôn ngữ
Fortran , có rất nhiều điểm mới so với FORTRAN 77 như văn bản chương trình hay tập tin nguồn
( source code ) được phép viết theo kiểu “ định dạng tự do “ ( free format ) , khác với kiểu “ định
dạng cố định theo cột “ ( fixed format ) của các phiên bản trước ; các cấu trúc điều khiển , xử lý
mảng , xử lý vào/ra , cấp phát động , chương trình con , đơn vị chương trình module tạo nhiều
thuận lợi hơn cho người lập trình . Phiên bản Fortran 95 bổ sung một số ít các nội dung mới vào
Fortran 90 và bỏ đi một vài điểm lỗi thời vẫn còn giữ trong Fortran 90 . Hai phiên bản sau cùng là
Fortran 2003 ( hỗ trợ lập trình hướng đối tượng ) và Fortran 2008.
Một số công ty, tổ chức phát triển ngôn ngữ Fortran đã cho ra đời nhiều biến thể khác nhau
từ Fortran chuẩn ( Standard Fortran ) bằng cách thêm vào một số chức năng mở rộng . Các phiên
bản Fortran chuẩn được ANSI ( American National Standards Institute - Viện tiêu chuẩn quốc gia
Hoa Kỳ ) và ISO ( International Organization for Standardization - Tổ chức quốc tế về tiêu chuẩn
hóa ) xem xét và công bố . Chương trình viết bằng Fortran chuẩn đảm bảo tính khả chuyển (
portable ) , chạy ổn định trên các hệ máy tính khác nhau có cài đặt trình biên dịch Fortran .
Phần trình bày trong tài liệu này tập trung vào phiên bản Fortran 90 . Phần thực hành minh
họa các chương trình Fortran 90 , chúng ta sử dụng phần mềm miễn phí Force 2.0 ,version 2.0.9p
, được download tại trang web :
http://force.lepsch.com/2009/05/downloads.html
Tên tập tin download : Force209G95Setup.exe , kích thước 3,55 MB . Phần mềm Force là
một môi trường phát triển tích hợp IDE ( Integrated Development Environment ) gọn nhẹ , có
chức năng biên dịch và chạy các chương trình viết bằng FORTRAN 77 hoặc Fortran 90 . Force sử
dụng trình biên dịch miễn phí G77/GFortran của GNU Compiler Collection (GCC) và G95.
Khi cài đặt xong , chúng ta mở file Force2.exe , sẽ thấy hiển thị cửa sổ môi trường phát
triển tích hợp IDE của Force 2.0. Trong môi trường này , chúng ta sẽ soạn tập tin nguồn ( văn bản
chương trình dạng text ) , tiến hành biên dịch , liên kết ( lệnh compile ) và nếu không có lỗi sẽ tiến
hành chạy thử ( lệnh run ) chương trình .
Ngoài ra , chúng ta có thể chọn các trình biên dịch Fortran khác như sau để chạy kiểm tra
cùng một chương trình nguồn :
2
Sử dụng môi trường IDE của SilverFrost qua chương trình FTN90 Personal Edition (
chương trình miễn phí phục vụ học tập , chạy test ) với file có tên ftn95_personal.exe ( kích
thước 70, 996 MB ) , được download tại trang web :
https://www.silverfrost.com/32/ftn95/ftn95_personal_edition.aspx
Khi download về máy tính xong , chúng ta chạy file ftn95_personal.exe để cài đặt chương
trình vào một thư mục tùy chọn , ví dụ D :\FTN95 . Sau đó chúng ta chạy file plato.exe để mở
môi trường IDE Plato . Từ đây , có thể soạn thảo , biên dịch , liên kết và chạy các chương trình
Fortran .
Để có thể biên dịch trực tiếp tại cửa sổ Command Prompt của Windows , chúng ta có thể
vào trang web The Fortran Company có địa chỉ như sau để download về trình biên dịch g95 qua
một file cài đặt có tên g95-Mingw_201210.exe ( 5381 KB ) :
http://www.fortran.com/the-fortran-company-homepage/whats-new/g95-windows-
download/
Phần hướng dẫn sơ lược sử dụng các trình biên dịch này sẽ được trình bày ở cuối chương.
1.3 Phương thức chung tạo tập tin thi hành (executable file) từ văn bản chương trình .
a) Trong môi trường DOS hoặc Windows , các ngôn ngữ lập trình cấp cao ( như Fortran, C,
Pascal,…) tạo tập tin thi hành có đuôi *.exe theo hai bước :
Bước 1 : Biên dịch ( compiling ) tập tin nguồn ( source code , file dạng text do người lập
trình soạn thảo ) sang tập tin đối tượng ( object code ) dạng nhị phân ( binary code ) .
3
Bước 2 : Liên kết ( linking ) một hoặc vài tập tin đối tượng và các tập tin thư viện có liên
quan để tạo thành tập tin thi hành được ( executable file ) .
Tập tin thi hành *.exe sau đó có thể chạy độc lập bên ngoài môi trường IDE.
b) Sơ đồ minh họa :
Tập tin nguồn Tập tin đối tượng Tập tin thi hành
( Source code ) ( Object code ) ( Executable code )
Biên dịch Liên kết
( Compiling ) ( Linking )
File1.f90 File1.obj
Thư viện
Libraries
Phần đánh số dòng trong cửa sổ soạn thảo là tự động nhằm giúp người lập trình dễ dàng
tham chiếu đến các dòng lệnh . Khi chúng ta soạn thảo tập tin nguồn , các từ khóa trong văn bản ,
các dòng ghi chú và một số đơn vị cú pháp trong văn bản sẽ tự động có màu thích hợp giúp chúng
ta dễ phân biệt các đối tượng khác nhau trong văn bản . Trong văn bản ví dụ dưới đây , các từ khóa
( keyword ) của Fortran được in đậm .
program HinhTron
implicit none
! phan khai bao
real :: r,chuvi,dientich ! khai bao bien
real,parameter :: pi = 3.14159 ! khai bao hang
! phan tinh toan
print*,'Cho biet ban kinh r :' ; read*,r
chuvi = 2.0*r*pi
dientich = r*r*pi
4
! ket qua
print*,'Chu vi : ',chuvi
print*,'Dien tich : ',dientich
read* ! tam dung man hinh de xem ket qua
end program HinhTron
Hình 1.2 : Soạn thảo tập tin nguồn trong cửa sổ Force .
Hình 1.3 : Cửa sổ Process thông báo việc biên dịch hoàn tất , không có lỗi .
5
Trường hợp xảy ra lỗi cú pháp ( syntax error ) sẽ có thông báo thích hợp tại cửa sổ phía
dưới vùng soạn thảo để hướng dẫn người lập trình chỉnh sửa lại tập tin nguồn , sau đó chạy lại trình
biên dịch . Ví dụ dòng thứ bảy của văn bản trên , chúng ta viết read , r thay vì viết đúng là read*,
r . Cửa sổ Process sẽ báo lỗi và có thông báo chỉ ra vị trí ( dòng ) bị lỗi .
In file D:\work\hinhtron.f90:7
print*,'Cho biet ban kinh r :' ; read,r
Error: Syntax error in READ statement at (1)
Hình 1.4 : Cửa sổ Process thông báo lỗi và vị trí có lỗi cú pháp .
Bước chạy thử tập tin thi hành : chọn menu Run, mục Run ( phím tắt F9). Máy tính sẽ tạm
thời thoát khỏi môi trường Force và cho chạy tập tin hinhtron.exe trên một cửa sổ màu đen riêng .
Tại đây , con trỏ chương trình sẽ dừng lại sau câu Cho biet ban kinh r : và đợi chúng ta nhập một
giá trị trực tiếp từ bàn phím ( ví dụ 3.0 ) và nhấn phím Enter để xác nhận . Các kết quả tính toán
được hiển thị trên màn hình . Sau khi xem xong kết quả , chúng ta nhấn phím Enter để thoát khỏi
cửa sổ chạy chương trình và trở về môi trường IDE của Force .
Để chạy file *.exe trực tiếp trong thư mục có lưu file nguồn *.f90 như ví dụ trên , trên thanh
menu chọn Options , chọn Environment Options , chọn tiếp thẻ Run , chọn Run with EXE
(direct ) . Trường hợp chọn Console , chương trình sẽ được chạy trên cửa sổ cmd ( command
prompt ) của Windows .
Hình 1.6 : Chọn cửa sổ chạy chương trình đã được biên dịch và liên kết .
Trường hợp có lỗi khi chạy chương trình ( run time error ) hoặc kết quả không đúng với kết
quả mà chúng ta đã biết trước ( chạy test chương trình ) thì cần quay lại văn bản chương trình để
6
rà soát lại thuật giải , các biểu thức tính toán , nội dung các câu lệnh để chỉnh sửa , sau đó thực hiện
lại việc biên dịch, liên kết và chạy chương trình như trên .
Để lưu kết quả của cửa sổ màu đen này vào file Word hoặc Notepad ,chúng ta để con trỏ
lên thanh tiêu đề hinhtron.exe và bấm phím phải con chuột , khi xuất hiện menu ,chọn Edit , chọn
Select All , nội dung chọn được chiếu sáng , bấm Enter ( tương đương lệnh Copy) để đưa nội dung
này vào clipboard sau đó dán vào file Word đã mở sẵn ( trong Word bấm phím phải chuột , chọn
Paste hoặc dùng tổ hợp phím Ctrl – V ) .
program tên_chương_trình
implicit none
! < các câu lệnh không thực thi , khai báo các đối tượng [ biến ,hằng, mảng,…] sử dụng
trong chương trình , luôn luôn phải đặt trước các câu lệnh thực thi .>
! <các câu lệnh thực thi : nhập/xuất dữ liệu , tính toán , so sánh , gọi chương trình con …>
end program tên_chương_trình ! có thể viết đơn giản là end
Các từ program , end program, implicit none thuộc bộ từ khóa ( keyword ) của Fortran.
Một số quy định về soạn thảo văn bản chương trình Fortran 90 :
Văn bản chương trình gồm các dòng text , mỗi dòng có không quá 132 kí tự .
Nội dung văn bản bên phải sau dấu chấm than ! là phần chú thích , dùng để giải thích
các câu lệnh hoặc các phần khác nhau của chương trình nhằm giúp người đọc văn bản hiểu rõ các
câu lệnh . Trình biên dịch sẽ bỏ qua các chú thích này.
Trình biên dịch bỏ qua các dòng trống ( dòng trắng ) trong văn bản chương trình .
Thông thường một câu lệnh được đặt trên một dòng văn bản riêng biệt , nếu viết hai hoặc
nhiều câu lệnh trên cùng một dòng thì phải dùng dấu chấm phẩy ; để phân cách các câu lệnh . Ví
dụ : print*,'Cho biet ban kinh r :' ; read*, r ! dòng có 2 câu lệnh .
Câu lệnh dài nếu cần phải viết trên nhiều dòng liên tiếp thì phải dùng dấu nối & đặt ở cuối
dòng và có thể phải đặt ở đầu dòng tiếp theo ( trường hợp xuống dòng ở giữa xâu kí tự thì bắt buộc
phải đặt dấu nối & ở đầu dòng tiếp theo ) . Ví dụ :
integer :: dai , rong , &
chuvi , dien tich
print*, ‘ Tri so chu vi cua &
& duong tron la : ‘ , chuvi ! bắt buộc phải có dấu & ở đầu dòng tiếp theo.
dientich = r*r &
*pi ! không cần dấu & ở đầu dòng , nếu có cũng không có lỗi .
Số dòng tiếp theo cho phép : 39 dòng . ( Thay đổi theo trình biên dịch ) .
Các khoảng trống có thể chèn vào giữa các đơn vị văn bản như từ khóa, tên biến , tên hằng
, các biểu tượng ( giúp cho chương trình dễ đọc ) …mà không ảnh hưởng đến kết quả ( trừ trường
hợp xuất hiện trong xâu kí tự ) . Tuy nhiên không được chèn khoảng trống vào giữa các kí tự của
một đơn vị văn bản ( từ khóa, tên ) hoặc biểu tượng . Ví dụ :
7
Viết real :: r,chuvi,dientich giống như real :: r, chuvi, dientich ; tuy nhiên lưu ý dấu
:: không được viết tách rời ra thành : : . Không được viết re al thay cho real . Không được viết
> = thay cho >= ( kí hiệu so sánh lớn hơn hay bằng ) .
Nội dung của một xâu kí tự được đặt giữa hai dấu nháy đơn ‘ string ‘ , hoặc hai dấu nháy
kép “ string “ có giá trị như nhau .Phải dùng thống nhất một kiểu dấu nháy giữa một xâu kí tự . Để
thể hiện It’s a book , chúng ta viết ‘It’’s a book.’ (đặt hai dấu nháy đơn trong ‘string’) , để thể hiện
Say “Hello !” , viết ‘Say “Hello !” ‘ hoặc “ Say “”Hello !”” “ .
1.5.2 Câu lệnh implicit none .
Câu lệnh implicit none là câu lệnh tùy chọn ( optional ) , được đặt ngay sau từ khóa program
, buộc người lập trình phải khai báo rõ ràng ( explicitly ) tất cả các biến , hằng ,mảng … tham gia
vào chương trình cùng kiểu dữ liệu tương ứng ( số nguyên, số thực , số phức , kí tự , logic …) .
Fortran 90 vẫn chấp nhận quy tắc ngầm định về kiểu biến số học như các phiên bản trước ,
nghĩa là có thể không cần phải khai báo biến trong văn bản chương trình , mà chỉ căn cứ vào kí tự
đầu của tên biến để biết biến thuộc loại dữ liệu nào : cụ thể những biến có tên bắt đầu bằng các chữ
I,J,K,L,M,N thuộc loại số nguyên mặc định ( integer ) và có tên bắt đầu bằng các chữ khác thuộc
loại số thực mặc định ( real ) .
Tuy nhiên , việc khai báo đầy đủ các đối tượng cùng kiểu dữ liệu ngay từ đầu chương trình
là một thói quen lập trình tốt , tránh được nhiều sai sót khó phát hiện khi biên dịch chương trình .
Trong tài liệu này , chúng ta sử dụng câu lệnh implicit none trong văn bản chương trình .
1.5.3 Khai báo biến ( variable) .
Biến dùng để lưu trữ dữ liệu , kết quả tính toán trong bộ nhớ máy tính .Khai báo biến gồm
khai báo tên biến ( variable name ) và kiểu dữ liệu ( data type ) của biến . Fortran quy định tên biến
là xâu kí tự từ 1 đến 31 kí tự , chỉ gồm chữ viết, chữ số và kí tự nối chân , nhưng tên biến phải bắt
đầu bằng chữ viết ( letter ) . Tên biến không được có khoảng trống chen giữa hoặc kí tự đặc biệt .
Ví dụ về tên biến đúng : ChuVi, chu_vi, dientich, NhanVien_01, x, y, z
Ví dụ về tên biến đặt sai : 1x , dien tich , chu-vi
Fortran không phân biệt chữ hoa với chữ thường trong tên biến . Các tên biến : DienTich ,
dientich , Dientich là như nhau . Quy tắc đặt tên biến còn được áp dụng cho việc đặt tên ( định
danh , identifier ) cho các đối tượng khác như hằng , mảng, tên chương trình ...
Fortran không cấm việc dùng tên các từ khóa ( keyword) để đặt tên ( identifier ) cho các đối
tượng biến, hằng , mảng …như một số ngôn ngữ lập trình khác , nhưng chúng ta nên tránh việc
làm này vì dễ gây ra các nhầm lẫn .
Câu lệnh real :: r ,chuvi ,dientich , khai báo 3 biến kiểu số thực ( từ khóa real ) có tên là
r, chuvi, dientich ; giữa real và dãy tên biến là dấu :: viết liền nhau . Trong trường hợp đơn giản
này dấu :: có thể bỏ mà không ảnh hưởng đến kết quả ( real r,chuvi,dientich ) . Lưu ý các tên
biến phải được phân cách bằng dấu phẩy .
1.5.4 Khai báo hằng (named constant ) .
Câu lệnh real , parameter :: pi = 3.14159 , khai báo pi là một hằng kiểu số thực có giá trị
được gán là 3,14159 . Ở đây thuộc tính parameter là một từ khóa báo cho biết biến pi được khai
báo là một hằng . Hằng có giá trị không đổi trong suốt chương trình . Lưu ý dấu :: trong trường
hợp này bắt buộc phải có . Việc khai báo hằng giúp cho chương trình dễ đọc , dễ hiểu câu lệnh và
dễ chỉnh sửa giá trị của hằng theo yêu cầu của bài toán .
1.5.5 Viết dữ liệu ra màn hình ( thiết bị xuất chuẩn ) .
Câu lệnh print *, <đối tượng xuất ra màn hình . > dùng để xuất ( viết ) ra màn hình nội
dung xâu kí tự , giá trị biến , giá trị biểu thức . Dấu * cho biết các đối tượng được viết theo định
dạng ( format ) tự động ( mặc định ) , phụ thuộc vào cấu trúc máy tính . Các đối tượng xuất khác
nhau được phân cách bởi dấu phẩy và được viết ra trên một dòng màn hình .
8
Câu lệnh print* không kèm đối tượng xuất dùng để tạo một dòng trống (blank line) trên
màn hình .
Câu lệnh print tổng quát : print format , < list > , trong đó format là phần định dạng cho
các đối tượng xuất , list là danh sách các đối tượng xuất ra màn hình .
Câu lệnh write (*,*) < đối tượng xuất ra màn hình . > có ý nghĩa tương đương lệnh print*,
<đối tượng xuất .> . Dấu * đầu tiên cho biết kênh xuất là màn hình ,dấu * thứ hai quy định kiểu
định dạng là tự động.
Ví dụ : write (*,*) ‘Cho biet ban kinh r : ‘ print*, ’Cho biet ban kinh r : ‘
Ghi chú : lệnh print chỉ dùng để xuất ra màn hình , trong khi lệnh write có nhiều lựa chọn
hơn như dùng để xuất ra màn hình , tập tin ...
1.5.6 Nhập dữ liệu từ bàn phím ( thiết bị nhập chuẩn ) .
Câu lệnh read *, < danh sách tên biến . > dùng để nhập giá trị trực tiếp từ bàn phím và gán
cho các biến theo thứ tự trong danh sách . Khi chương trình thực hiện đến câu lệnh read*, r thì
máy tính sẽ dừng lại chờ chúng ta nhập một giá trị là bán kính r từ bàn phím , ví dụ nhập số 3.0 ,
xong nhấn Enter để xác nhận . Biến r sẽ lấy giá trị 3.0 trong các biểu thức tính toán tiếp theo .
Câu lệnh read (*,*) < tên các biến > có ý nghĩa tương tự read*,< tên các biến> .
Dấu * đầu tiên trong read (*,*) cho biết kênh nhập dữ liệu là bàn phím , dấu * thứ hai cho
biết kiểu định dạng dữ liệu là kiểu tự động . Ví dụ : read (*,*) r read*, r
Nếu x,y,z là ba biến kiểu số thực thì khi gặp câu lệnh read*, x,y,z , chúng ta có thể nhập
giá trị cho từng biến , sau mỗi giá trị nhập phải nhấn Enter ; hoặc có thể nhập cùng lúc ba giá trị
vào trên cùng một hàng nhưng lúc này mỗi số phải được viết cách nhau bằng dấu phẩy hoặc cách
nhau một hay vài khoảng trống , xong nhấn Enter để kết thúc việc nhập liệu cho cả ba số .
Lệnh read* không có đối số làm tạm dừng màn hình chờ chúng ta xem kết quả , xong nhấn
phím Enter để chương trình tiếp tục hoặc kết thúc . Nếu không có câu lệnh này thì khi chương
trình được thực thi xong , máy tính lập tức quay trở lại môi trường IDE Force làm chúng ta không
kịp xem kết quả . Tuy nhiên một số trình biên dịch như Plato lại không cần câu lệnh này ; khi
chương trình chạy xong , xuất hiện câu “ Press return to close window … “ hoặc “ Press any key
to continue “ , lúc này sau khi xem kết quả xong , chúng ta nhấn phím Enter hoặc phím bất kỳ để
đóng cửa sổ thực thi chương trình , trở về môi trường soạn thảo .
Hai câu lệnh như print*, “ Cho biet gia tri cua x va y : “ ; read *, x,y được dùng kết hợp
với nhau để hướng dẫn việc nhập số liệu cho các biến x và y .
Lưu ý : khi sử dụng các lệnh print* hoặc read* , phải có dấu phẩy giữa lệnh và các đối tượng
xuất , trong khi dùng lệnh write (*,*) và read (*,*) thì không có .
9
( Nếu viết 2 thì kết quả vẫn tương tự , lúc này Fortran sẽ tự chuyển đổi số nguyên thành số thực
trước khi tính toán biểu thức , chúng ta sẽ tìm hiểu thêm vấn đề này ở chương 3 ) .
Trong chương trình trên , do chúng ta sử dụng lệnh xuất theo kiểu định dạng (format) tự
động cho dữ liệu kiểu real ( số thực độ chính xác đơn, thể hiện 6 hoặc 7 chữ số thập phân có nghĩa
) nên các chữ số thập phân xuất hiện thêm ở vị trí cuối có thể không thể hiện đúng kết quả * ( giá
trị diện tích đúng ở ví dụ là 28.27431 ) . Tuy nhiên nếu sử dụng lệnh print với kiểu định dạng (
format ) phù hợp ( sẽ được trình bày ở các chương sau ) , chúng ta sẽ chủ động và khắc phục được
vấn đề này . Khi thay các dòng lệnh xuất có định dạng ( dòng 11, 12 ) , được kết quả như sau :
Ghi chú : + Xâu kí tự định dạng ‘(a,f10.5)’ sau từ khóa print dùng để sắp xếp viết ra một xâu kí tự
( kí hiệu định dạng là chữ a ) và một số thực được viết dưới dạng dấu chấm tĩnh , chiếm 10 ô ( kí tự ) màn
hình , trong đó phần lẻ thập phân chiếm 5 ô ( kí hiệu định dạng là f10.5 ) . Số được viết căn lề phải .
2 8 . 2 7 4 3 1
+ * Sự chuyển đổi tự động giữa hệ thập phân ( người dùng ) và hệ nhị phân ( máy tính ) gây ra sai số do
việc cắt cụt phần đuôi của số thực ( truncation error ) .
program ChuNhat
! Chuong trinh tinh dien tich hinh chu nhat biet hai canh x,y
implicit none
real :: x,y,dientich
print *,” Tinh dien tich hinh chu nhat “
print *
print ‘(a,$)’, ” Vao gia tri canh x : “ ; read *, x
print ‘(a,$)’, ” Vao gia tri canh y : “ ; read *, y
dientich = x*y ! Tinh dien tich hinh chu nhat
print *
print *,” Dien tich hinh chu nhat co &
& gia tri la : “, dientich
read*
end program ChuNhat ! Co the viet don gian la end
Ghi chú :
10
a.Trong câu lệnh print ‘(a,$)’, ” Vao gia tri canh x : “ , xâu kí tự ‘(a,$)’ là phần định dạng
cho đối tượng xuất ra gồm một xâu kí tự ( kí hiệu định dạng là chữ a ) và sau đó giữ con trỏ không
xuống dòng ( kí hiệu điều khiển là $ ) ,và đợi nhập một giá trị trực tiếp từ lệnh read*, x tiếp theo.
b.Trong trường hợp đơn giản chỉ có chương trình chính ( main program ) , chỉ thị kết thúc
chương trình có thể viết đơn giản là end .
c. Mỗi câu lệnh trong Fortran đều bắt đầu bằng một từ khóa , ngoại trừ câu lệnh gán hoặc
câu lệnh bắt đầu bằng một nhãn ( label ) . Cuối một câu lệnh không có dấu chấm phẩy .
d. Bên trái phép gán, chỉ được phép viết duy nhất một tên biến .
1.6.2 Thực hành biên dịch , liên kết và chạy chương trình trong môi trường Force.
Sử dụng các lệnh trong menu Run để biên dịch, liên kết ( phím tắt Ctrl – F9 ) và chạy
chương trình ( phím tắt F9) .
1.7 Sử dụng trình biên dịch Fortran G95 ( phần tham khảo ) .
1.7.1 Cài đặt .
+ Chạy file g95-Mingw_201210.exe để cài đặt G95. Khi cửa sổ g95_Mingw_Installer
Setup : Installation Folder hiện ra , chúng ta chọn thư mục để lưu chương trình g95 trong ô
Destination Folder , ví dụ C : \ g95 hoặc E :\g95 . Dung lượng yêu cầu của G95 : 16,1 MB . Bấm
nút Install để cài đặt .
+ Trong quá trình cài đặt , chúng ta bấm nút Yes hay OK theo đề nghị của chương trình cài
đặt ( đặt các biến PATH , cài đặt các tiện ích, thư viện …).
+ Khi cài đặt thành công , thư mục g95 sẽ có 3 thư mục con là bin , doc, lib và file uninstall-
g95 . Chương trình chính g95 nằm trong thư mục bin .
1.7.2 Sử dụng G95.
+ Sau khi soạn xong tập tin nguồn chunhat.f90 ( sử dụng Notepad) và lưu tại thư mục
d:\work . Để biên dịch và liên kết tập tin này thành tập tin thi hành có tên chunhat và cũng lưu tại
d:\work , thực hiện như sau :
Hình 1.7 : Các cửa sổ xuất hiện khi cài đặt chương trình G95
11
Vào cửa sổ lệnh của windows ( command prompt window ) , đánh dòng lệnh như sau :
c:\> g95 –o d:\work\chunhat d:\work\chunhat.f90 , xong nhấn Enter .
g95 : tên chương trình biên dịch .
-o d:\work\chunhat : chỉ định tên file kết quả ( output file , executable file ) gồm cả đường dẫn .
d:\work\chunhat.f90 : tên file nguồn gồm cả đường dẫn.
Nếu biên dịch và liên kết thành công , chúng ta có file chunhat.exe và thể chạy test
chương trình ngay trong của sổ lệnh ( chạy file output ) c:\> d:\work\chunhat <Enter> .
+ Mẫu các câu lệnh dùng để biên dịch , liên kết :
Lệnh g95 –c file.f90 : biên dịch file.f90 thành tập tin đối tượng file.o ; có thể biên dịch
nhiều file nguồn cùng một lúc . ( Chữ c : compile ) .
Lệnh g95 –o file file.f90 : biên dịch và liên kết file.f90 thành tập tin thi hành có tên file
Lệnh g95 –o file file01.f90 file02.f90 file03.f90 : biên dịch và liên kết cùng lúc nhiều
file nguồn thành một tập tin thi hành có tên file . ( Chữ o : output )
Hình 1.8 : Biên dịch , liên kết và chạy chương trình trong cửa sổ lệnh của windows.
1.8 Trình biên dịch của Silverfrost FTN95 Personal Edition ( phần tham khảo ) .
Khi chạy file plato.exe xong , cửa sổ môi trường Plato sẽ hiện ra và chúng ta chọn menu
File , chọn tiếp New , cửa sổ New File hiện ra như sau :
Trong phần Type , chúng ta chọn Free format Fortran file xong nhấn nút Open . Khung
soạn thảo hiện ra và chúng ta có thể soạn văn bản chương trình , ví dụ chương trình tính thể tích
hình trụ như dưới đây và lưu tập tin có tên hinhtru.f90 . Để biên dịch và liên kết , chúng ta vào
menu Build , chọn Build .
12
Hình 1.10 : Cửa sổ IDE của Plato và vị trí menu Build.
Nếu không có lỗi , trong cửa sổ output phía dưới có nội dung sau :
Compiling and linking file: hinhtru.f90
Creating executable: C:\Users\Administrator\Documents\hinhtru.EXE
Compilation and linking completed.
Sau đó chúng ta chạy chương trình này : chọn menu Build , lệnh Start Run ( Ctrl – F5) .
Một thông báo hiện ra vài giây cho biết về các giới hạn khi sử dụng phiên bản personal
edition của phần mềm này trước khi chương trình chạy thực sự .
Kết quả sẽ hiển thị trên một màn hình màu đen riêng . Sau khi xem xong , nhấn Enter để
đóng cửa sổ này và quay trở về môi trường Plato . Chúng ta cũng có thể copy vào clipboard toàn
bộ nội dung trên cửa sổ màu đen này , cách thực hiện tương tự như trong phần mềm Force .
Trong menu Build còn có lệnh Compile dùng để biên dịch tập tin nguồn thành tập tin đối
tượng .Tất cả các hướng dẫn chi tiết sử dụng phần mềm Plato được trình bày trong menu Help .
1.9 Văn bản chương trình Fortran 90 viết theo kiểu định dạng cố định ( tham khảo ) .
Chúng ta xem ví dụ sau đây về cách viết văn bản chương trình Fortran 90 tính chu vi ,
diện tích hình tròn theo kiểu định dạng cố định ( fixed format ) .
13
Hình 1.12 : Văn bản chương trình circle.f viết theo định dạng cố định .
Lợi ích : sử dụng , khai thác các văn bản chương trình FORTRAN 77 đã có sẵn , sau đó sử dụng
trình biên dịch Fortran 90 để biên dịch và chạy các chương trình này .
Quy tắc viết văn bản theo định dạng cố định :
+ Mỗi dòng văn bản chứa 72 kí tự ( từ cột 1 đến cột 72 , được đánh số thứ tự từ trái qua phải ) ,
từ cột 73 trở đi trình biên dịch sẽ bỏ qua .
+ Phân chia các khu vực theo cột như sau :
Khu vực viết câu lệnh : từ cột số 7 đến cột 72 .
Đối với câu lệnh dài cần viết trên nhiều dòng thì mỗi dòng tiếp theo cần đặt dấu * hoặc dấu
& tại cột số 6.
Khu vực đặt nhãn ( label , tối đa 5 chữ số ) cho câu lệnh ( áp dụng cho câu lệnh được gắn
nhãn ) : từ cột số 1 đến cột số 5 .
Dòng có ghi chữ C tại cột số 1 là dòng ghi chú hoặc dòng để trống .
+ Ngoài ra đối với Fortran 90 , khi viết văn bản theo định dạng cố định , được phép sử dụng hai
nội dung bổ sung như sau :
Cho phép viết nhiều lệnh trên cùng một dòng văn bản , dùng dấu chấm phẩy ; để phân cách
câu lệnh .
Cho phép viết nội dung ghi chú sau dấu chấm than !
Lưu ý : văn bản chương trình viết theo định dạng cố định phải được lưu với phần đuôi tập
tin là *.f , ví dụ circle.f , thay vì *.f90 được áp dụng cho văn bản viết theo định dạng tự do như
trình bày ở các phần trên .
14
Hình 1.13 : Kết quả chạy chương trình circle.exe
*****
15
CHƯƠNG 2 : CÁC KIỂU DỮ LIỆU CỦA FORTRAN
Các kiểu integer , real , complex thuộc nhóm dữ liệu số ( numeric ) ; các kiểu logical ,
character thuộc nhóm dữ liệu non-numeric .
Đối với mỗi kiểu dữ liệu trên , chúng ta sẽ tìm hiểu cách khai báo biến, hằng , miền giá trị
( range ) cùng kích thước trong bộ nhớ của kiểu dữ liệu , các phép tính có liên quan và kết quả trả
về .
Trên cơ sở các kiểu dữ liệu do Fortran cung cấp , chúng ta còn có thể xây dựng kiểu dữ liệu
do người dùng tự định nghĩa (user defined type ) . Kiểu dữ liệu hỗn hợp này ( tương tự như kiểu
bản ghi trong một số ngôn ngữ lập trình ) bao gồm nhiều thành phần , mỗi thành phần có tên và có
kiểu dữ liệu riêng .
2.1.2 Khai báo biến và hằng .
a. Mẫu chỉ thị khai báo biến :
Tên_kiểu_dữ_liệu :: tên_biến1 , tên_biến2, tên_biến3 ( dấu :: có thể không có ).
b. Mẫu chỉ thị khai báo biến và gán luôn giá trị ban đầu :
Tên_kiểu_dữ_liệu :: tên_biến1 , tên_biến2 = giá trị ( biến2 được gán giá trị
ban đầu . Dấu :: bắt buộc phải có ) .
c. Mẫu chỉ thị khai báo hằng ( named constant ) :
Tên_kiểu_dữ_liệu , parameter :: tên_hằng = giá trị ( dấu :: phải có ) .
Khi khai báo hằng phải dùng từ khóa parameter đi sau tên kiểu dữ liệu .
Ghi chú : + Trong chỉ thị khai báo biến , nếu có gán giá trị ban đầu hay có khai báo thuộc
tính như parameter , dấu :: bắt buộc phải có . Dấu :: luôn luôn phải viết liền nhau .
+ Một biến chưa được gán giá trị ban đầu sẽ có một giá trị không xác định khi chương trình
bắt đầu được thực thi ( unassigned variable ) .
Ví dụ về cách viết giá trị trực tiếp ( literal constant ) kiểu số nguyên integer :
16
-345 0 +1975 2018
Số nguyên dương có thể không cần có dấu + ở trước .
Ngoài cách viết số nguyên theo hệ thập phân thông thường , Fortran còn cho phép viết số
nguyên theo các hệ 2, 8, 16 , sử dụng các kí tự ở đầu là b , o , z như ví dụ sau :
b’1011’ (hệ nhị phân ) o’234’ (hệ bát phân) z’23af’ (hệ thập lục phân)
(Tương ứng với các số 11 , 156 , 9135 trong hệ thập phân ) .
2.2.2 Phân loại và khai báo các kiểu số nguyên :
Các kiểu số nguyên có kích thước 1,2,4,8 byte , có phạm vi thể hiện (range) như sau :
Trong ngôn ngữ Fortran 90, việc khai báo kiểu số nguyên có chỉ định rõ miền giá trị ( range
) được thực hiện thông qua tham số kind là một số nguyên .Đối với một số trình biên dịch số kind
được gán trực tiếp bằng với số byte ( kích thước kiểu dữ liệu trong bộ nhớ ) và được khai báo như
sau :
integer (kind = 1) :: k ! khai báo biến k là số nguyên 1 byte .
integer (kind = 2) :: m ! khai báo biến m là số nguyên 2 byte .
integer (kind = 4) :: n ! khai báo biến n là số nguyên 4 byte .
integer (kind = 8) :: n8 ! khai báo biến n8 là số nguyên 8 byte .
Lưu ý : trong trường hợp này , để viết giá trị trực tiếp của số nguyên 2 byte , ví dụ 213 ,
chúng ta viết như sau : 213_2 ( số nguyên , dấu nối chân , số kind ) . Nếu viết 213 thì đây là số
nguyên kiểu integer mặc định ( 4 byte ) . Để kiểm tra , chúng ta dùng hàm kind (213) và kind
(213_2) sẽ thấy kết quả trả về lần lượt là 4 và 2 ( print *, kind(213) , kind(213_2) ) .
Chúng ta có thể khai báo số kind thông qua một hằng ik , sau đó gán kind = ik như sau :
integer, parameter :: ik = 2 ! khai báo hằng nguyên ik có giá trị bằng 2 .
integer (kind = ik ) :: m,n = 15_ik ! khai báo hai biến nguyên m,n có số kind
! là ik = 2 , biến n được gán giá trị đầu là 15_2
! phạm vi biểu diễn : -32768 .. 32767
Khai báo số kind thông qua một hằng có ưu điểm là dễ chỉnh sửa phạm vi ( miền giá trị )
của biến cho phù hợp với yêu cầu của bài toán ( chỉ cần thay đổi giá trị hằng ik ) .
Tuy nhiên không bắt buộc tất cả các trình biên dịch Fortran đều quy định số kind bằng với
số byte như trên . Một số trình biên dịch quy định số kind riêng cho từng kiểu số nguyên (chúng ta
cần tham khảo sổ tay của trình biên dịch cụ thể hoặc chạy test , dùng hàm kind ( ) , để in ra số
kind của các kiểu dữ liệu ) .Trong trường hợp này , chương trình nguồn viết bằng Fortran có thể
không tương thích khi chạy trên các trình biên dịch khác nhau khi chúng ta gán số kind bằng một
giá trị cụ thể như trên .
Để giải quyết tính khả chuyển ( portable ) về phạm vi thể hiện đối với dữ liệu kiểu số
nguyên giữa các trình biên dịch khác nhau ( văn bản chương trình tương thích với các trình biên
dịch khác nhau ) , Fortran cung cấp hàm đã được định nghĩa sẵn selected_int_kind ( r ) dùng để
cung cấp số kind cho kiểu số nguyên có phạm vi thay đổi từ – 10r đến +10r do người lập trình yêu
17
cầu . Ví dụ kiểu số nguyên có phạm vi thay đổi từ -104 đến +104 ( -10 000 n 10 000 ) được
khai báo như sau để lấy số kind phù hợp :
interger ,parameter :: ikind = selected_int_kind (4) ! lấy số kind qua hằng ikind.
integer (kind = ikind ) :: m,n ! khai báo biến nguyên có số kind = ikind .
Lưu ý : nếu miền giá trị của biến do người lập trình yêu cầu không được đáp ứng , hàm
selected_int_kind ( ) sẽ trả về giá trị – 1.
2.2.3 Ví dụ .
Để làm rõ các nội dung trên, chúng ta xem qua chương trình testint.f90 , chạy trên phần
mềm Force 2.0 , như sau ( Trong Force , số kind được gán bằng số byte ) :
program testint
implicit none
integer :: n ! khai bao so nguyen mac dinh
integer (kind = 2) :: n2 ! khai bao so nguyen 2 byte
integer (kind = 4 ):: n4 ! so nguyen 4 byte
integer (kind = 8 ):: n8 ! so nguyen 8 byte
! Lay so kind kieu so nguyen theo mien gia tri yeu cau
integer, parameter :: ikind = selected_int_kind (4) ! pham vi -104.. +104 .
integer (kind = ikind) :: m
! Phan thuc thi
! in ra cac gia tri lon nhat cua tung kieu so nguyen
print*,’Max so nguyen mac dinh :’, huge(n)
print*,’Max so nguyen 2 byte :’, huge(n2)
print*,’Max so nguyen 4 byte :’, huge(n4)
print*,’Max so nguyen 8 byte :’, huge(n8)
print*,’So kind cua m : ‘, ikind
print*,’Max cua kieu so nguyen cua m : ‘, huge(m)
! Phep chia hai so nguyen
print*,’Phep chia 14/4 = ‘ , 14/4
read* ! Tam ngung de xem ket qua
end program testint
Kết quả :
Max so nguyen mac dinh : 2147483647
Max so nguyen 2 byte : 32767
Max so nguyen 4 byte : 2147483647
Max so nguyen 8 byte : 9223372036854775807
So kind cua m : 2
Max cua kieu so nguyen cua m : 32767
Phep chia 14/4 = 3
18
3. Phép chia hai số nguyên 14/4 = 3 cho kết quả là một số nguyên là phần nguyên của phép
chia . ( Lưu ý : 1/3 cho kết quả là số 0 ) . Đây là một đặc điểm cần chú ý khi chia hai số nguyên
trong Fortran .Nếu muốn lấy phần dư ( dư số ) của phép chia hai số nguyên m/n , chúng ta dùng
hàm mod (m,n) , ví dụ mod (14,4) 2
4. Hàm kind (m) trả về số kind của m . Ví dụ : kind (15_2) → 2 , kind (15) 4
Chúng ta xem chương trình sau dùng để in ra số kind của kiểu số nguyên mặc định và số
nguyên có phạm vi thể hiện từ -104 đến 104 , chương trình này chạy trên phần mềm FTN95 của
Silverfrost ( đã giới thiệu tại chương 1 ) .
program testint_kind
implicit none
integer :: n = 12 , k ! so nguyen mac dinh
k = selected_int_kind(4) ! so nguyen co pham vi tu -104 den 104
print *,'So kind cua kieu integer : ', kind(n)
print *,'So kind cua kieu integer -10**4 den 10**4 : ', k
end
Kết quả :
So kind cua kieu integer : 3
So kind cua kieu integer -10**4 den 10**4 : 2
Nhận xét : số kind của kiểu số nguyên mặc định trong FTN95 là 3 , khác với số kind ( bằng
4 ) của phần mềm Force 2.0 ở trên .
Lưu ý : số kind của phần mềm FTN95 đối với số nguyên 1,2,4,8 byte lần lượt là 1,2,3,4 .
Khi tính toán với các số nguyên , cần kiểm soát giá trị thể hiện lớn nhất và nhỏ nhất của
kiểu dữ liệu , nếu kết quả tính toán ( trung gian hoặc cuối cùng ) vượt ra ngoài miền này , kết quả
trả về là một giá trị không phù hợp.
Ví dụ : trong phạm vi số nguyên 2 byte ( giá trị max = 32767 ), nếu m = 15000 và n = 20000
thì kết quả tổng k = m + n sẽ có giá trị trả về là -30536 thay vì 35000 ( do kết quả vượt quá giá trị
max của kiểu dữ liệu ) .
Cách viết giá trị trực tiếp ( literal constant ) số thực kiểu real :
a) Viết dưới dạng thông thường (dạng dấu chấm tĩnh ) gồm phần dấu ( dấu + có thể bỏ ) ,
phần nguyên , dấu chấm ( thay cho dấu phẩy theo kiểu viết số Việt Nam , bắt buộc phải có ) , phần
lẻ thập phân .
19
Ví dụ :
-123.34 -10. 0. .35 4.0 +152.18
b) Viết dưới dạng dấu chấm động , hay dạng bậc (lũy thừa cơ số 10) , dùng kí tự E hay e .
Dạng mExx m*10xx , trong đó m là phần định trị (gồm dấu , phần nguyên , dấu chấm, phần
lẻ thập phân ) , phần mũ xx gồm phần dấu và một số nguyên có hai chữ số nằm trong miền giá
trị cho phép . Nếu phần định trị 0.1 m < 1. thì số thực được gọi là chuẩn hóa .
Ví dụ : 2.25E+04 2.25e+4 0.225e5 225e+02 22500.
-1.5E-01 -15e-02 - 0.15
-123.45 -0.12345E+3 (dạng chuẩn hóa )
Khi cần sử dụng số thực có độ chính xác cao hơn ( số chữ số thập phân thể hiện lớn hơn 7 )
, chúng ta sử dụng kiểu số thực có độ chính xác kép hoặc độ chính xác gấp bốn .
Cách viết giá trị trực tiếp số thực kiểu double precision :
Chỉ được viết dưới dạng bậc ( lũy thừa cơ số 10 ) và dùng kí tự D hay d thay cho kí tự E
hay e trong cách viết cho kiểu số thực độ chính xác đơn . Ví dụ :
4.25D+04 4.25d4 425d+02 ( 42500.)
-2.5D-01 -25d-02 ( - 0.25 )
-36.25 -0.3625D+2 ( dạng chuẩn hóa )
Số thực độ chính xác kép 3.14159 3.14159d0 ( do 100 = 1 ) .
Tuy nhiên , từ Fortran 90 trở về sau , giá trị trực tiếp kiểu số thực chính xác kép thường
được viết dưới dạng kèm theo số kind phù hợp như trình bày dưới đây .
Ghi chú : Do sự chuyển đổi giữa hệ thập phân ( người sử dụng ) và hệ nhị phân ( máy tính
) nên dữ liệu kiểu số thực chỉ thể hiện gần đúng số thực ( do sai số cắt cụt : truncation error ) . Độ
chính xác của kiểu số thực ( số chữ số thập phân có nghĩa được thể hiện ) sẽ tùy thuộc vào kích
thước khai báo của kiểu số thực được sử dụng trong bộ nhớ .
2.3.4 Khai báo kiểu số thực có kèm tham số kind .
Từ Fortran 90 trở về sau , để chỉ rõ độ chính xác ( precision ) và miền giá trị ( range ) của
một kiểu số thực , một số trình biên dịch dùng tham số kind , là một số nguyên , và gán số kind
này bằng với số byte , cách khai báo như sau :
real (kind = 4) :: x ! khai báo số thực độ chính xác đơn , giống kiểu real mặc định.
real (kind = 8) :: y ! khai báo số thực độ chính xác kép (double precision ).
real (kind = 16) :: z ! số thực độ chính xác gấp bốn ( quadruple precision) .
Lúc này , các giá trị trực tiếp đối với số thực , ví dụ số thực độ chính xác kép được viết có
kèm theo phần đuôi nhận dạng gồm dấu nối chân (underscore) và số kind (là số 8 ) . Ví dụ : theo
khai báo như trên , số thực độ chính xác kép 2.45 được viết : 2.45_8 ( giá trị_số kind ) . Nếu viết
20
2.45 thì đây là số thực độ chính xác đơn ( kiểu mặc định ) . Chúng ta có thể dùng hàm kind (2.45)
và kind (2.45_8) để in ra số kind tương ứng với kiểu số thực .
Để có thể dễ dàng chuyển đổi kiểu số thực trong chương trình , chúng ta khai báo số kind
thông qua một hằng nguyên như sau :
integer , parameter :: r = 8 ! khai báo hằng nguyên r = 8
real ( kind = r ) :: x, y = 2._r ! khai báo số thực có kind = r , độ chính xác kép.
( Biến y = 2._r được gán luôn giá trị ban đầu với số kind = r ) .
Tuy nhiên không phải tất cả các trình biên dịch Fortran đều quy định số kind bằng với số
byte như trên . Chúng ta cần tham khảo sổ tay của trình biên dịch hoặc chạy một chương trình test
, sử dụng hàm kind ( ) , để biết số kind tương ứng với từng kiểu số thực . Ví dụ chúng ta cho chạy
dòng lệnh : print*, kind (1.), kind(1.d0) để tìm ra số kind của kiểu số thực chính xác đơn và chính
xác kép .
Việc gán số kind bằng một giá trị cụ thể có thể phù hợp với trình biên dịch này nhưng lại
không phù hợp với trình biên dịch khác . Ví dụ đối với trình biên dịch FTN95 của Silverfrost , số
kind của kiểu số thực chính xác đơn là 1 , kiểu chính xác kép là 2 .
Để đảm bảo tính khả chuyển ( portable ) của văn bản chương trình Fortran giữa các trình
biên dịch khác nhau , Fortran cho phép lấy số kind ( là một số nguyên) của kiểu dữ liệu số thực
qua hàm định nghĩa sẵn selected_real_kind (p,r) trong đó đối số p (precision) quy định độ chính
xác ( số chữ số thập phân có nghĩa ) và đối số r (range) quy định miền giá trị của kiểu dữ liệu nằm
trong khoảng (-10r , -10-r ) (10 -r , 10r ) . Ví dụ :
+ Đối với kiểu số thực có độ chính xác đơn , khai báo như sau :
integer , parameter :: rs = selected_real_kind (6,37) ! lấy số kind qua hằng rs
! p = 6 và r = 37 , phạm vi ± (10-37 đến 1037)
real (kind = rs) :: x, y = 2._rs ! khai báo số thực có kind = rs , chính xác đơn .
Hoặc chúng ta chỉ cần khai báo số chữ số thập có nghĩa p trong hàm selected_real_kind :
integer , parameter :: r6 = selected_real_kind (6) ! lấy số kind qua hằng r6
real (kind = r6) :: x, y = 2._r6 ! số thực có 6 chữ số thập phân có nghĩa , tương đương
! số thực chính xác đơn .
+ Đối với số thực có độ chính xác kép :
integer , parameter :: rd = selected_real_kind (15,307)
real (kind = rd) :: x, y = 2.34_rd ! khai báo số thực có kind = rd ,chính xác kép.
Hoặc chúng ta chỉ cần khai báo tham số đầu tiên p , xác định số chữ số thập có nghĩa :
integer , parameter :: r15 = selected_real_kind (15) ! lấy số kind số thực có số chữ số
! thập phân có nghĩa là 15
real (kind = r15) :: x, y = 2.34_r15 ! khai báo số thực có kind = r15 ,chính xác kép.
+ Đối với số thực độ chính xác gấp bốn ( quadruple precision ) :
integer , parameter :: rq = selected_real_kind (33,4931)
real (kind = rq) :: x, y = 3.4567_rq
Hoặc chỉ khai báo tham số p = 33 :
integer , parameter :: r33 = selected_real_kind (33)
real (kind = r33) :: x, y = 3.4567_r33
21
Một cách tổng quát, nếu trong chương trình , chúng ta cần hai biến số thực x,y có độ
chính xác là p = 12 chữ số có nghĩa và phạm vi thể hiện ( r = 50) từ ± (10 -50 đến 1050 ),
lúc này khai báo như sau :
integer , parameter :: rc = selected_real_kind (12,50) ! lấy số kind qua hằng rc
real (kind = rc) :: x, y=3.14_rc ! khai báo số thực có kind = rc .
print*,’So kind r :’, rc ! in ra số kind .
print*,kind (x) ! rc
Trong trường hợp cụ thể này ( p = 12, r = 50) , số kind sẽ có giá trị bằng 8 ( nếu sử dụng
phần mềm Force ) , đồng nhất với kiểu số thực có độ chính xác kép .
Hàm selected_real_kind ( ) sẽ trả về giá trị -1 nếu yêu cầu về độ chính xác của kiểu số thực
không được trình biên dịch hỗ trợ , trả về giá trị -2 nếu phạm vi thể hiện không được hỗ trợ .
Chúng ta cần nắm vững việc sử dụng hai hàm selected_int_kind ( ) và selected_real_kind (
) để lấy số kind phù hợp với kiểu dữ liệu ( độ chính xác , phạm vi biểu diễn ) theo yêu cầu của bài
toán cần giải quyết .
2.3.5 Ví dụ .
+ Chúng ta xem chương trình realtest.f90 về phép nhân hai số thực có độ chính xác đơn và
chính xác kép như sau :
program realtest
implicit none
real :: u,v,w ! khai bao so thuc chinh xac don ( mac dinh )
real (kind = 8) :: x,y,z ! khai bao so thuc chinh xac kep
u = 2.34567 ; v = 3.45678
print*, u,v
x = 2.34567_8 ! gia tri truc tiep so thuc chinh xac kep
y = 3.45678_8 ! viet co so kind di kem
print*,x,y
w = u*v ; z = x*y
! Ket qua phep nhan hai so thuc
print*,’Ket qua nhan so thuc chinh xac don :’
print’(e20.12)’,w
print*,’Ket qua nhan so thuc chinh xac kep :’
print’(d20.12)’,z
read*
end program realtest
Kết quả :
2.34567 3.45678
2.34567 3.45678
Ket qua nhan so thuc chinh xac don :
0.810846519470E+01 ( chính xác chỉ đến 8 số thập phân : 0.81084651 )
Ket qua nhan so thuc chinh xac kep :
0.810846514260D+01 ( chính xác )
Kết quả trên cho thấy chúng ta cần chọn kiểu số thực phù hợp ( tương ứng với kích thước
kiểu dữ liệu , khai báo qua số kind ) khi có yêu cầu tính toán với độ chính xác cao .
Ghi chú : Trong lệnh print , kí hiệu định dạng ‘(d20.12)’ quy định số thực độ chính xác kép
được thể hiện ( viết ra ) theo dạng bậc trong 20 ô (kí tự ) trong đó phần lẻ thập phân ( thuộc phần
định trị ) chiếm 12 ô. Tương tự cho kí hiệu ‘(e20.12)’ áp dụng cho số thực độ chính xác đơn .
+ Chúng ta xem tiếp chương trình realtest2.f90 , lấy số kind qua hàm selected_real_kind (
p,q) như sau :
22
program realtest2
implicit none
integer,parameter :: rs = selected_real_kind(6,37) ! lay so kind do chinh xac don
integer,parameter :: rd = selected_real_kind(15,307) ! so kind do chinh xac kep
integer,parameter :: rc = selected_real_kind(15,50) ! so kind theo yeu cau
real (kind = rs) :: x ! khai bao so thuc chinh xac don
real (kind = rd) :: y ! so thuc chinh xac kep
real (kind = rc) :: z ! so thuc co do chinh xac va mien gia tri theo yeu cau
! In so kind rs,rd,rc
print*,’So kind : ‘
print*,rs,rd,rc
! In mien gia tri Min Max cua kieu du lieu .
print*,’Mien gia tri min max :’
print’(2e20.8)’, tiny(x), huge(x)
print’(2e20.8)’, tiny(y), huge(y)
print’(2e20.8)’, tiny(z), huge(z)
read*
end program realtest2
Kết quả :
So kind :
488
Mien gia tri min max :
0.11754944E-37 0.34028235E+39 ! kiểu số thực chính xác đơn.
0.22250739-307 0.17976931+309 ! kiểu số thực chính xác kép.
0.22250739-307 0.17976931+309 ! kiểu số thực theo yêu cầu.
Trong chương trình trên chúng ta có sử dụng hàm tiny(x) và huge(x) để lấy giá trị nhỏ nhất
và lớn nhất của kiểu dữ liệu của x ( về giá trị tuyệt đối ) .
Lệnh print ‘(2e20.8)’ có phần định dạng kiểu bậc cho 2 số thực thể hiện ra màn hình theo
kiểu chính xác đơn , mỗi số thực chiếm độ rộng 20 ô trong đó phần lẻ thập phân chiếm 8 ô .
Chúng ta thấy số kind của kiểu dữ liệu số thực lấy qua hàm selected_real_kind (15,50) với
yêu cầu 15 chữ số có nghĩa và miền giá trị ( về trị tuyệt đối ) thể hiện từ 10-50 đến 1050 là 8, bằng
với số kind của kiểu số thực có độ chính xác kép .
Khi tính toán với kiểu dữ liệu số thực cần chú ý đến miền giá trị thể hiện của kiểu dữ liệu ,
nếu kết quả tính toán ( trung gian hoặc sau cùng ) vượt quá giới hạn trên ( về trị số tuyệt đối ) sẽ
có lỗi overflow ( tràn số ) , kết quả trả về là Inf ( Infinity ) , hoặc nhỏ hơn giới hạn dưới ( underflow)
kết quả trả về là số 0. Hình vẽ dưới đây thể hiện trục số thực và các miền giá trị của kiểu dữ liệu
số thực (các đoạn vẽ nét đậm ) .
Hình 2.1 : Trục số thực và miền giá trị kiểu dữ liệu số thực .
23
2.4 Dữ liệu kiểu số phức ( complex ) .
2.4.1 Tổng quát về kiểu số phức .
Một dữ liệu kiểu số phức bao gồm hai số thực là phần thực và phần ảo . Các số thực trong
thành phần tạo nên số phức thông thường có kiểu mặc định real ( độ chính xác đơn ) hoặc real
(kind = 8 ) tương ứng với số thực độ chính xác kép .
Cách viết giá trị trực tiếp kiểu số phức :
(2.5 , 3.e+1) thể hiện số phức 2.5 + 30.i ; (0. , 1.) thể hiện số i , đơn vị ảo i2 = -1
Lưu ý : giá trị trực tiếp phần thực và phần ảo được viết trong dấu ngoặc đơn và phân cách
bởi dấu phẩy :
z = ( phần_thực , phần_ảo ) .
2.4.2 Khai báo kiểu số phức.
Kiểu số phức được khai báo với từ khóa complex , đây là kiểu số phức mặc định có phần
thực và phần ảo là các số thực kiểu real mặc định .
Ví dụ : khai báo biến, hằng kiểu số phức có thành phần thực và ảo có kiểu số thực độ chính
xác kép :
integer, parameter :: r = 8 ! khai báo hằng nguyên r = 8
complex ( kind = r ) :: z ! khai báo biến z kiểu số phức có số kind r = 8
! tương ứng với phần thực , phần ảo là số thực có độ chính xác kép.
complex , parameter :: i = (0._r,1._r) ! khai báo hằng số phức i , đơn vị ảo.
z = (1.2_r, 0.5_r)*i ! kết quả (-0.5 , 1.2 )
Chúng ta xem chương trình sau đây về kiểu số phức mặc định :
program testcom
implicit none
integer,parameter :: r = 4 ! khai bao hang r
complex( kind = r) :: z ! khai bao bien phuc z co so kind = 4
! phan thuc, phan ao la so thuc kieu real mac dinh.
complex, parameter :: i = (0._r,1._r) ! khai bao hang so phuc i , don vi ao.
z = (1.2_r,0.5_r)*i ! nhan hai so phuc
print*,’So phuc z : ‘ , z
print*,’Phan thuc : ‘,real(z) ! ham lay phan thuc
print*,’Phan ao : ‘,aimag(z) ! ham lay phan ao
print*,’So lien hop : ‘,conjg(z) ! ham lay so phuc lien hop
print ‘(a,f8.4)’,’Modun : ‘,abs(z) ! ham lay modun so phuc
read*
end program testcom
Kết quả :
So phuc z : (-0.5,1.2)
Phan thuc : -0.5
Phan ao : 1.2
So lien hop : (-0.5,-1.2)
Modun : 1.3000
Nếu muốn chuyển chương trình trên sang làm việc với số phức có kind = 8 , chúng ta chỉ
cần thay đổi giá trị của hằng r = 8 ở câu lệnh khai báo hằng .
24
Các hàm chuẩn của Fortran trong chương trình trên :
Hàm real(z) trả về phần thực của số phức z .
Hàm aimag(z) trả về phần ảo của số phức z .
Hàm conjg(z) trả về số phức liên hợp z = a – ib của số phức z = a + ib .
Hàm abs(z) trả về mô-đun ( suất ) của số phức z .
Kí hiệu f8.4 là kiểu định dạng cho số thực với dấu chấm tĩnh có độ dài là 8 ô trong đó phần
lẻ thập phân chiếm 4 ô. Số được căn lề phải .
+ Số phức z còn được gán thông qua hàm cmplx (x,y ) , trong đó x,y là các số thực kiểu real
mặc định ( x,y có thể là các biểu thức số học ) :
complex :: z ! khai báo số phức z kiểu mặc định .
real :: x = 1. , y = 3.
z = cmplx(x,y) ! z = (1.,3.)
z = cmplx(x/2.,y/3.) ! z = (0.5,1.)
+ Trường hợp x,y là các số thực có độ chính xác kép , chúng ta sử dụng hàm dcmplx (x,y)
để tạo số phức có kind = 8 z = dcmplx (x,y) ! khai báo complex (kind = 8) :: z
program testchar
implicit none
! khai bao bien kieu ki tu
character (len = 8) :: city1,city2 ! chieu dai bien 8 ki tu
character yes_no ! the hien chi mot ki tu don ( len = 1)
city1 = ‘Da Lat’ ! them cac khoang trong ben phai khi viet ra
city2 = ‘Nha Trang’ ! bi cat cut
yes_no = ‘Yes’ ! chi lay ki tu dau Y
print*,’--’//city1//’--‘
print*,’--’//trim(city1)//’--‘
print*,’--’//city2//’--‘
print*,’--’//yes_no//’--‘
print*,len(trim(city1))
print*,’It’’s a book’ Hình 2.2 : Kết quả chương trình testchar.
read*
end program testchar
26
Trong chương trình testchar :
1. Kí hiệu // là phép nối hai xâu kí tự ; ví dụ : “abc”//”de” trở thành “abcde”
2. Hàm trim( string) trả về xâu kí tự đã bị cắt bỏ các khoảng trống ở cuối xâu .
3. Hàm len( bien) trả về chiều dài của biến ( chiều dài của biến khi khai báo).
Lệnh len(trim(bien)) trả về chiều dài của biến đã cắt bỏ các khoảng trống ở cuối .
4. Xâu ‘Da Lat’ khi viết ra màn hình có thêm hai kí tự trống bên phải.
5. Xâu ‘Nha Trang’ bị cắt cụt chữ ‘g’. ( do thiếu chỗ thể hiện ) .
program testchar2
implicit none
character(len = 12) :: dao = “Phu Quoc” , so
print*,’--’//dao//’--‘
print*,len(dao) ! chieu dai bien ki tu dao
print*,’--’//trim(dao)//’--‘ ! bien dao bi cat khoang trong ben phai
print*,len(trim(dao)) ! chieu dai bien bi cat cut khoang trong ben phai
print*,’--’//adjustl(dao)//’--‘ ! dua bien ki tu dao ve sat ben trai
print*,’--’//adjustr(dao)//’--‘ ! dua ve sat ben phai
print*,dao (1:3) ! in 3 ki tu dau
print*,dao(5 : ) ! in tu ki tu thu 5 den cuoi
print*,index(dao,”Quoc”) ! vi tri xau “Quoc”
! trong xau dao .
write (so,*) 36 ! bien ki tu so lay gia tri ‘36’
print*,”So : “//so
read*
end program testchar2
Hình 2.3 : Kết quả chương trình testchar2
Trong chương trình testchar2 :
1.Hàm adjustl( ) đặt biến kí tự về sát bên trái , hàm adjustr( ) đặt biến về sát bên phải .
2.Cách viết các xâu kí tự con , trích từ biến xâu kí tự dao :
dao(1:3) thể hiện 3 kí từ đầu của biến dao dao( : 3)
dao(5: ) thể hiện từ kí tự thứ 5 đến cuối .
dao (2:2) thể hiện đúng kí tự thứ 2
3. Hàm index (dao,”Quoc”) cho biết vị trí xâu con “Quoc” trong biến dao . Hàm index
(string,substring) trả về số 0 nếu xâu con substring không được tìm thấy trong xâu string .
4. Câu lệnh write (so,*) 36 chuyển đổi số nguyên 36 thành xâu kí tự ‘36’ và gán cho biến
kí tự so .
Một số ghi chú về kiểu kí tự :
a.Chúng ta xem khai báo về hằng kiểu kí tự như sau :
character (len = *), parameter :: ThuDo = “Ha Noi”
Khi đặt len = * , chiều dài của hằng kí tự sẽ được để tự do , trình biên dịch sẽ tự xác định
theo giá trị được gán cụ thể ở cuối câu lệnh . Hàm len(ThuDo) lúc này sẽ có giá trị 6 . Lưu ý , khi
khai báo biến kí tự trong chương trình chính , chúng ta không được khai báo len = * .
b. Khi trích một phần từ xâu kí tự có tên là xaukt , chúng ta dùng cách viết xaukt(m : n)
với m, n là hai ( chỉ ) số nguyên xác định vị trí đầu và cuối , để chỉ xâu kí tự con lấy từ vị trí thứ m
đến vị trí thứ n trong xâu xaukt , kể cả các khoảng trống ( tính từ trái qua phải ) . Nếu m không có
thì xem m = 1 , nếu n không có thì xem n = len(xaukt) .
27
2.7 Dữ liệu kiểu hỗn hợp .
Từ các kiểu dữ liệu cơ sở như đã trình bày ở trên, chúng ta có thể định nghĩa ( xây dựng )
kiểu dữ liệu hỗn hợp gồm nhiều thành phần . Mỗi thành phần có tên và có kiểu dữ liệu riêng . (
Tương tự kiểu bản ghi gồm các trường trong một số ngôn ngữ lập trình ) . Chúng ta xem qua ví dụ
sau :
program main
implicit none
! khai bao kieu du lieu moi co ten la HoSo gom 03 truong ( 03 thanh phan ).
type HoSo
character (len = 20) :: HoTen
integer :: NamSinh
real :: HeSo ! he so luong
end type
! ket thuc khai bao kieu du lieu moi co ten HoSo
type (HoSo) :: nv1 ! khai bao bien nv1 co kieu HoSo
nv1 = HoSo(“Nguyen Van A”,1976 ,5.75) ! gan toan bo du lieu mot lan
nv1%NamSinh = 1976 ! gan rieng truong NamSinh
print*, nv1%HoTen ! viet ra man hinh truong HoTen
print*, nv1%NamSinh ; print*, nv1%HeSo
read*
end program main
Kết quả :
Nguyen Van A
1976
5.75
Lưu ý : để truy xuất một trường , chúng ta viết : tên_biến%tên_trường
2.8.2 Một số hàm truy vấn dữ liệu kiểu số thực, số nguyên .
Dưới đây là một số hàm dùng để tra cứu thông tin của kiểu dữ liệu số cũng như lấy số kind
của kiểu dữ liệu số nguyên, số thực theo các yêu cầu của người lập trình ( đã trình bày chi tiết ở
các phần trên ) . Đối số x là số thực .
Bảng 2.2 : Một số hàm truy vấn dữ liệu kiểu số học.
epsilon (x) * Trả về số dương nhỏ nhất khi cộng vào 1 sẽ thành một số lớn hơn 1.
precision (x) Trả về một số nguyên p thể hiện độ chính xác của kiểu dữ liệu : số chữ số
thập phân có nghĩa.
range (x) Trả về một số nguyên r thể hiện phạm vi biểu diễn của kiểu dữ liệu :
-10r x 10r
tiny (x) Số dương nhỏ nhất của kiểu dữ liệu.
huge (x) Số dương lớn nhất của kiểu dữ liệu.
kind ( ) Lấy số kind ( là số nguyên ) của kiểu dữ liệu số , kiểu logical .
kind (1.) trả về số kind của số thực chính xác đơn .
kind (1.d0) trả về số kind của số thực chính xác kép .
selected_int_kind ( r ) Lấy số kind của kiểu dữ liệu số nguyên có phạm vi từ -10r đén +10r
selected_real_kind (p,r) Lấy số kind của kiểu dữ liệu số thực có độ chính xác là p ( số chữ số thập
p,r : nguyên dương . phân có nghĩa ) và có phạm vi thể hiện ( về trị tuyệt đối ) từ 10-r đén 10r.
Ít nhất một trong hai tham số p,r phải được đưa vào danh sách đối số .
* Ghi chú về số epsilon : Bạn đọc có thể in ra các giá trị epsilon cho kiểu số thực chính xác đơn ,
chính xác kép như sau :
print *, epsilon (1.) 0.119209 e -06 ; print*,epsilon (1.d0) 0.222045e-15 ( chỉ lấy 6 số lẻ
thập phân ) . Ta có biểu thức logical (1. + epsilon (1.) > 1.) cho giá trị .true.
Trong lập trình , chúng ta thường thay biểu thức so sánh bằng nhau giữa số thực x với số 0. ( x ==
0.) bằng biểu thức abs(x) < epsilon (1.) , trong đó abs ( ) là hàm lấy giá trị tuyệt đối .
29
2.8.3 Mẫu đầy đủ khai báo biến có kiểu số ( nguyên, thực, phức ) , kiểu logical hoặc
kiểu xâu kí tự ( character ) .
Mẫu khai báo biến có kiểu số hay kiểu logical :
<Kiểu_dữ_liệu> [, <thuộc_tính>] [::] <danh_sách_biến> [ = <giá_trị_đầu> ]
dt
dt
dn
dt cắt cọc BTCT.
Hình 2.4 : Mặt
*****
30
CHƯƠNG 3 : PHÉP TOÁN VÀ BIỂU THỨC
3.1 Mở đầu .
Trong các chương trước , chúng ta đã làm quen với phát biểu gán theo dạng :
Trước tiên , biểu thức bên vế phải được tính theo quy tắc do Fortran quy định và trả về một
giá trị thuộc một kiểu dữ liệu nào đó , sau đó giá trị này được gán cho biến bên vế trái .
Các loại biểu thức trong Fortran :
Biểu thức số học : kết quả trả về là một số nguyên , số thực hoặc số phức .
Biểu thức logic : kết quả trả về là một giá trị kiểu logical , là .true. hoặc .false.
Biểu thức kí tự : kết quả trả về là một kí tự đơn hoặc một xâu kí tự .
Để thực hiện việc tính một biểu thức , Fortran cung cấp các phép toán số học ( arithmetic
operators) , phép toán so sánh hay quan hệ ( relational operators ) , phép toán logic ( logical
operators ) , phép nối xâu kí tự ( concatenation operator ) .
3.2 Các phép toán số học và biểu thức số học .
3.2.1 Các phép toán số học .
Phép toán số học xuất hiện trong biểu thức số học , thực hiện các phép tính ( operator ) với
các toán hạng ( operand ) có thể là các giá trị trực tiếp , biến, hằng, phần tử mảng ,giá trị trả về của
hàm …. Trong biểu thức số học có thể có các biểu thức số học khác được đặt trong cặp dấu ngoặc
( ) và các hàm . Ví dụ về biểu thức số học :
(-b + sqrt (b**2 – 4.*a*c)) /(2.*a) ; b*b + c*c – 2.*b*c*cos (gocA)
Trong biểu thức ví dụ trên có hàm lấy căn bậc hai sqrt ( ) , hàm cosin cos( ) . Các hàm này
được Fortran cung cấp để sử dụng ngay gọi là intrinsic functions ( danh sách các hàm thông dụng
được liệt kê ở mục 3.6 ) .
Phép toán hai ngôi :
Phép cộng ( kí hiệu + ) , phép trừ ( - ) , phép nhân ( * ) , phép chia ( / ) .
Lưu ý : 1) Trong phép chia a/b , yêu cầu mẫu số b 0 . Phép chia hai số nguyên m/n cho kết
quả là một số nguyên là phần nguyên của phép chia , ví dụ : 5/3 cho kết quả là 1 ; 1/3 cho kết quả
là số 0 , trong khi phép chia hai số thực 1. /3. cho kết quả là 0.3333 .
2) Hai toán hạng a,b trong phép toán hai ngôi , như a + b , có thể có kiểu hay số kind khác
nhau , lúc này Fortran sẽ áp dụng việc chuyển đổi theo quy tắc sao cho hai toán hạng có cùng kiểu
dữ liệu và có cùng số kind trước khi thực hiện phép tính .
Phép lũy thừa ( kí hiệu ** ) .
Ví dụ : 23 được viết trong Fortran 2**3 8 . Lưu ý với số mũ nguyên âm : 2**(-3) =
1/(2**3) = 1/8 = 0 , do vậy cần viết đúng là 2.**(-3) = 1/8. = 0.12500 . Fortran cho phép viết a**n
với a là một số thực và n là số nguyên dương hoặc âm . Trường hợp a**r , với r là số thực thì số
thực a phải thỏa điều kiện a > 0 , lúc này a**r được tính qua công thức exp(r*ln(a)) có sử dụng
hàm logarit Nêpe và hàm mũ ex ( theo công thức ar = erLna , với a > 0 ) .
Phép toán một ngôi :
Phép lấy số đối ( kí hiệu - ) , ví dụ : - x . Kí hiệu + cũng có thể xem là phép toán một ngôi .
3.2.2 Quy tắc ưu tiên của các phép toán số học .
Khi tính một biểu thức số học trong đó có các phép toán khác nhau , Fortran quy định mức
độ ưu tiên của các phép toán theo thứ tự từ cao đến thấp như sau :
31
Phép lũy thừa ( ** ) : có mức ưu tiên cao nhất .
Phép nhân ( * ) , phép chia ( / ) : có mức ưu tiên như nhau .
Phép toán một ngôi + và - : có mức ưu tiên như nhau .
Phép toán hai ngôi phép cộng ( + ) , phép trừ ( - ) : có mức ưu tiên như nhau .
Trường hợp trong biểu thức số học có chứa những biểu thức số học khác được đặt trong
cặp dấu ngoặc ( ) , thì giá trị của những biểu thức này được ưu tiên tính trước ( cặp dấu ngoặc
trong cùng nhất sẽ được tính trước ) để trở thành một giá trị đơn . Do vậy , trong trường hợp nghi
ngờ , chúng ta cần dùng thêm các cặp dấu ngoặc để biểu thức trở nên dễ đọc , dễ kiểm soát việc
tính toán . Lưu ý trong một biểu thức , số dấu ngoặc mở phải bằng số dấu ngoặc đóng .
Nếu biểu thức có chứa hàm thì giá trị trả về của hàm cũng được tính trước để trở thành một
toán hạng trong biểu thức .
Đối với các phép toán có mức ưu tiên như nhau như phép nhân , phép chia hoặc phép cộng
,phép trừ , việc tính các phép toán này được thực hiện từ trái qua phải . Riêng phép lũy thừa lại
thực hiện từ phải qua trái .
Ví dụ : 5*4/10 (5*4) /10 2
32
ở trên . Giá trị này được chuyển đổi theo kiểu dữ liệu và số kind của biến bên trái và sau đó
gán cho biến này . Quy tắc gán này không tuân theo thứ bậc của kiểu dữ liệu số học .
Ví dụ : biến_integer = biểu thức có kiểu real
Giá trị biểu thức bên vế phải ( kiểu số thực ) sẽ bị cắt phần thập phân , sau đó gán cho biến
số nguyên bên vế trái.
Ví dụ : x = 123456789*2 ! print*x 2.4691358e+8 ( biến real x chỉ thể hiện gần đúng
số nguyên 246913578 của vế phải và được làm tròn chữ số cuối 8).
Giải thích các quy tắc chuyển đổi của Fortran về kiểu dữ liệu :
+ Dòng số 6 : x = 2.5 + n . Biến nguyên n = 2 được chuyển đổi thành số thực 2. , biểu thức
bên phải trở thành phép cộng hai số thực và trả về số thực 4.5 gán cho biến x .
+ Dòng số 7 : y = x + m/n . Phép chia hai số nguyên m/n ( 5/2 ) , cho kết quả là 2, được
thực hiện trước do phép chia ưu tiên hơn phép cộng . Sau đó số nguyên 2 được chuyển đổi thành
số thực 2. và cộng vào x được kết quả là số thực 6.5 và được gán cho biến y .
+ Dòng số 9 , hàm kind ( x + m/n ) trả về số kind = 4 , chứng tỏ biểu thức trong ngoặc có
kiểu số thực chính xác đơn .
+ Dòng số 10 : p = x + m/n . Kiểu dữ liệu của biến vế trái ( số nguyên ) khác với kiểu dữ
liệu của biểu thức vế phải ( số thực ) . Lúc này kiểu của biểu thức bị ép chuyển đổi theo kiểu của
biến vế trái và sau đó gán cho biến . Biến nguyên p sẽ nhận giá trị là 6 ( sau khi cắt bỏ phần thập
phân của số thực 6.5 , là kết quả của biểu thức bên vế phải ) .
+ Dòng số 13, hàm kind ( x + u ) trả về giá trị kind = 8 , chứng tỏ biểu thức trong ngoặc có
kiểu số thực chính xác kép . Biến số thực chính xác đơn x được chuyển đổi thành số thực chính
xác kép trước khi phép toán + được thực hiện .
c) Quy tắc chuyển đổi kiểu ngầm định như trên có thể làm sai lệch kết quả của chương trình
nếu chúng ta không cẩn thận .Trong thực hành , đối với câu lệnh gán biểu thức số học cho biến ,
chúng ta cần tạo thói quen kiểm soát kỹ kiểu dữ liệu , số kind của từng toán hạng trong biểu thức ,
trình tự thực hiện các phép tính ( dùng thêm các dấu ngoặc khi cần ) , kiểu và số kind của biểu
thức và kiểu , số kind của biến được gán bên vế trái . Trong trường hợp cần sự an toàn , chúng ta
có thể chủ động dùng các hàm chuyển đổi kiểu như real ( ) : đổi số thành số thực mặc định , dble
( ) : đổi số thành số thực có độ chính xác kép .
33
3.3 Biểu thức logic .Các phép toán so sánh ( quan hệ ) . Các phép toán logic .
Biểu thức logic được hình thành từ các toán hạng có kiểu logical ( chỉ có hai giá trị là .true.
hoặc .false. ) . Các toán hạng có thể là biến , hằng , phần tử mảng , giá trị của hàm kiểu logical ,
biểu thức so sánh và có thể được kết hợp với nhau bằng các phép toán logic . Kết quả trả về của
một biểu thức logic là .true. hoặc .false .
3.3.1 Các phép toán so sánh .
Fortran cung cấp sáu phép toán so sánh dùng để so sánh giá trị ( nguyên, thực ) của hai biểu
thức số học với nhau , kết quả trả về của phép so sánh là một giá trị kiểu logical ( .true. hoặc .false.
) . Cách viết phép toán so sánh dạng chữ của FORTRAN 77 , ví dụ .gt. ( greater than ) , vẫn được
sử dụng trong Fortran 90 .
Lưu ý : các kí hiệu như <= phải viết liền nhau ( không được viết tách ra < = ). Phép so
sánh bằng nhau phải dùng hai dấu bằng liên tiếp == , trong khi dấu = được sử dụng cho phép gán
. Kiểu dữ liệu số nguyên, số thực ở hai vế có thể khác nhau , Fortran sẽ chuyển đổi thành cùng kiểu
với kiểu dữ liệu có thứ bậc cao hơn trước khi tiến hành so sánh .
Ví dụ : 5.6 <= 3 sẽ có kết quả là .false. ; ( 2**3 +2 ) == 10. sẽ có kết quả là .true.
Đối với số phức , chỉ được sử dụng phép so sánh bằng nhau == và khác nhau /= .
Trong Fortran , xét về mức độ ưu tiên tính toán, các phép toán so sánh có mức ưu tiên như
nhau và được thực hiện sau phép toán số học nhưng trước các phép toán logic .
3.3.2 Các phép toán logic .
Fortran có bốn phép toán logic hai ngôi dùng để kết hợp hai biểu thức logic với nhau và
một phép toán logic một ngôi ( phép phủ định ) tương tự như phép tính đại số Boolean . Kí hiệu
các phép toán logic như sau :
Kí hiệu Ý nghĩa
.not. Phép phủ định .
.and. Phép giao .
.or. Phép hội.
.eqv. Phép so sánh tương đương .
.neqv. Phép so sánh không tương đương . Phép loại
trừ.
Bảng 3.2 : Các phép toán logic .
Cho p, q là hai biểu thức logic , các biểu thức .not.p ( phủ định của p ) , p .and. q ( p và q )
, p .or. q ( p hay q ) , p .eqv. q ( p tương đương q ) , p .neqv. q ( p không tương đương q ; p hoặc
q ) có bảng thực trị ( true table ) như sau :
34
p q .not. p p .and. q p .or. q p .eqv. q p .neqv. q
t t f t t t f
t f f f t f t
f t t f t f t
f f t f f t f
Kí hiệu : t .true. ; f .false.
Bảng 3.3 : Bảng thực trị các phép toán logic .
Quy tắc về phép toán logic :
a) .not. p cho giá trị sai khi p đúng và cho giá trị đúng khi p sai .
b) p .and. q chỉ đúng khi p , q cùng đúng , các trường hợp còn lại đều sai.
c) p .or. q chỉ sai khi p , q cùng sai , các trường hợp còn lại đều đúng .
d) p .eqv. q chỉ đúng khi p , q có cùng giá trị ( cùng đúng hoặc cùng sai ) .
e) p .neqv. q chỉ đúng khi p , q có giá trị khác nhau ( p đúng , q sai hay p sai, q đúng ).
Khi muốn viết phép toán phủ định của một biểu thức logic , ví dụ p .and. q , chúng ta phải
sử dụng cặp dấu ngoặc bao lấy biểu thức logic này và viết .not. ( p .and. q ) do phép toán .not. có
mức ưu tiên cao hơn phép .and.
Ví dụ về biểu thức logic : a, b, c là ba số thực > 0 và test là biến logical . Biểu thức logic
trong câu lệnh gán như sau dùng để kiểm tra xem ba số này có thể tạo thành ba cạnh của một tam
giác hay không ? :
test = (a+b > c) .and. (b+c > a) .and. (a+c > b)
+ Phép toán nối hai xâu kí tự liền nhau được kí hiệu là // .
Ví dụ : “Tên : “ // “Nguyen Van A “ sẽ cho kết quả là xâu “ Tên : Nguyen Van A “
‘123’//’456’//’789’ ‘123456789’
+ Hàm repeat (string ,n) sẽ trả về xâu string được viết lặp lại n lần nối tiếp nhau . Ví dụ :
print*, repeat (‘*’, 20) sẽ viết ra màn hình hai mươi dấu * liền nhau .
+ Bảng mã ASCII ( American Standard Code for Information Interchange ) gồm 128 kí tự
( chữ viết, chữ số, kí tự đặc biệt ,kí tự điều khiển …) được sắp xếp theo nhóm và đánh số thứ tự
từ số 0 đến số 127. Khi so sánh hai xâu kí tự có chiều dài khác nhau , xâu ngắn sẽ được chèn thêm
vào các khoảng trống bên phải sao cho hai xâu có chiều dài bằng nhau .Việc so sánh hai xâu kí tự
bằng các phép toán so sánh ( xem 3.3.1 ) được thực hiện bằng cách xét từng cặp kí tự lần lượt từ
trái qua phải và tuân theo thứ tự được quy định trong bảng ASCII . Trường hợp một xâu là xâu kí
tự con của xâu kí tự còn lại thì xâu con được xem là nhỏ hơn .
Ví dụ : biểu thức so sánh ‘Fortran’ < ‘Fortran90’ sẽ cho giá trị là .true. do “Fortran’ là xâu
kí tự con của ‘Fortran90’ .
‘Pascal’ < ‘Fortran’ sẽ cho giá trị là .false. do kí tự ‘P’ có mã ( số thứ tự ) là 80 và kí tự ‘F’
có mã là 70 .
‘Fortran’ > ‘ForTran’ sẽ cho giá trị là .true. do kí tự ‘t’ có mã là 116 và kí tự ‘T’ có mã là
84 .
35
+ Để chuyển đổi một số nguyên ( số thứ tự trong bảng ASCII ) thành kí tự tương ứng chúng
ta dùng hàm : achar (integer) . Ví dụ : achar (70) ‘F’ .
+ Ngược lại , để lấy số thứ tự của một kí tự trong bảng ASCII chúng ta dùng hàm : iachar
(character) . Ví dụ : iachar (‘P’) 80 .
3.5 Bảng tổng hợp các phép toán và mức độ ưu tiên trong Fortran .
Các phép toán trong Fortran và mức độ ưu tiên khi thực hiện tính toán trong một biểu thức
được tóm tắt trong bảng sau ( được thể hiện từ mức ưu tiên cao đến thấp ) . Khi các phép toán số
học , logic có mức ưu tiên như nhau thì các phép tính được thực hiện từ trái qua phải , trừ phép lũy
thừa và phép .not. được thực hiện từ phải qua trái .
Bảng 3.4 : Các phép toán trong Fortran và mức độ ưu tiên .
3.6 Một số hàm toán học thông dụng có sẵn trong Fortran ( intrinsic functions ) .
Để có thể viết các chương trình Fortran trong đó có sử dụng các hàm toán học thông dụng ,
chúng ta sử dụng các hàm chuẩn đã được định nghĩa sẵn trong Fortran như sau :
Tên hàm Ý nghĩa Số đối số Kiểu của đối số Kiểu kết quả
36
acos Hàm y = arccos (x) với 1 R/D R/D
-1.0 x 1.0 Đơn vị : radian
0. y
atan Hàm y = arctan (x) với 1 R/D R/D
-/2 < y < /2 Đơn vị : radian
Bảng 3.5 : Các hàm toán học thông dụng trong Fortran .
Ghi chú : Trong Fortran 90, nhiều hàm có tính chất generic , nghĩa là một tên hàm ( generic
name ), ví dụ như hàm lấy giá trị tuyệt đối abs ( ) , sẽ tương ứng với các chương trình con khác
nhau ,tùy thuộc vào kiểu của đối số đầu vào mà trả về một giá trị có kiểu phù hợp .
Trong các phiên bản trước , Fortran có các hàm lấy giá trị tuyệt đối có tên khác nhau ( gọi
là specific name ) như iabs (integer ) dùng cho đối số là số nguyên , abs (real) dùng cho số thực ,
dabs(real*8) dùng cho số thực chính xác kép , cabs (complex*8) dùng cho đối số là số phức , trả
về một số thực real là mô đun ( suất ) của số phức .
program tamgiac
implicit none
real :: xa = 3.,ya = 0.,xb = 3., yb =4. ! Toa do diem A , diem B .
real :: a,b,ab,p,s,gocO,gocB
real,parameter :: pi = 3.14159
a = sqrt(xa**2 + ya**2) ! sử dụng công thức tính khoảng cách d x 2 y 2
b = sqrt(xb**2 + yb**2)
ab = sqrt((xb - xa)**2 + (yb - ya)**2)
print*," Ba canh tam giac : ",a," ",b," ",ab
p = (a + b + ab) /2. ! nua chu vi
print'(a,f8.2)'," Chu vi : ", 2.*p
s = sqrt(p*(p-a)*(p-b)*(p-ab)) ! công thức s p( p a)( p b)( p ab)
print'(a,f8.2)'," Dien tich : " , s Y
gocO = asin(ab/b)*180./pi ! sử dụng hàm y = asin(x) B (3,4)
gocB = asin(a/b)*180./pi
print'(a,f8.4)'," Goc O (do) : " , gocO
print'(a,f8.4)'," Goc B (do) : " , gocB
read*
A(3,0)
end program tamgiac
X
O (0,0)
38
Kết quả :
Ba canh tam giac : 3. 5. 4.
Chu vi : 12.00
Dien tich : 6.00
Goc O (do) : 53.1301
Goc B (do) : 36.8699
Bạn đọc biên dịch , chạy chương trình trên và xem kết quả . Sau đó phát triển thành
một chương trình cho phép nhập trực tiếp tọa độ 3 điểm A,B,C của một tam giác ABC bất
kỳ , tính các cạnh, các góc , chu vi , diện tích, các đường cao , các trung tuyến , bán kính
đường tròn ngoại tiếp , đường tròn nội tiếp của tam giác .
2. Viết chương trình nhập 5 số thực a,b,c,d,e là điểm số của 5 môn học ( thang điểm 10 )
từ bàn phím .Xuất ra tổng điểm , điểm trung bình , điểm số lớn nhất , điểm số nhỏ nhất.
Áp dụng cho dãy điểm số : 6.5 5.5 7.3 7.8 9.2
program diemso
implicit none
real :: a,b,c,d,e,S
print '(a,$)',"Vao gia tri 5 diem so : "
read*,a,b,c,d,e
S = a+b+c+d+e
print*,"Tong diem : ", S
print '(a,f7.2)'," Diem trung binh :", S/5
print*, "Diem so lon nhat : ",max(a,b,c,d,e)
print*, "Diem so nho nhat : ",min(a,b,c,d,e)
read*
end program diemso
Kết quả :
Vao gia tri 5 diem so : 6.5 5.5 7.3 7.8 9.2
Tong diem : 36.3
Diem trung binh : 7.26
Diem so lon nhat : 9.2
Diem so nho nhat : 5.5
Lưu ý : 1) Khi nhập liệu cho một dãy số trên cùng một hàng , các số phải cách nhau
bằng khoảng trắng hoặc dùng dấu phẩy . Sau khi kiểm tra xong , chúng ta nhấn phím Enter.
2) Giá trị max, min của một dãy số chúng ta dùng hàm max ( danh sách ) , min ( danh
sách ) .
3) Biểu thức S/5 có dạng hỗn hợp real/integer sẽ trả về kiểu real .
3. Chương trình lấy ngày giờ của hệ thống . Trong chương trình này , có câu lệnh :
call date_and_time (dateinfo, timeinfo) , dùng để gọi chương trình Fortran đã có sẵn có tên
date_and_time với hai đối là dateinfo , kiểu character (len = 8), và timeinfo , kiểu character
(len 10) . Các xâu kí tự này chứa kết quả trả về của hệ thống dưới dạng yyyymmdd ( năm-
tháng-ngày) và hhmmss.sss (giờ-phút-giây) . Sau đó chúng ta tách từ các xâu này các thông
tin chi tiết .
39
Bạn đọc phân tích và chạy chương trình sau :
program test_ngay_gio
implicit none
character (len = 8) :: dateinfo ! Xâu chứa thông tin năm, tháng,ngày
character (len = 10) ::timeinfo ! Xâu chứa thông tin giờ,phút,giây
character (len=4) :: year
character (len=2) :: month,day,hour,minute
character (len=6) :: second
call date_and_time (dateinfo,timeinfo) ! Gọi chương trình của Fortran
! Ngay, thang, nam
year = dateinfo(1:4) ! Lấy 4 kí tự đầu tiên cho năm
month = dateinfo(5:6) ! Lấy kí tự thứ 5,6 cho tháng
day = dateinfo(7:8) ! Lấy kí tự thứ 7,8 cho ngày
! Gio,phut,giay
hour = timeinfo (1:2)
minute = timeinfo (3:4)
second = timeinfo (5:10)
print*,day,'-',month,'-',year ! xuất ra dạng ngày-tháng-năm
print*,hour,'-',minute,'-',second ! xuất ra giờ-phút-giây
read*
end
Kết quả :
10-06-2019
07-12-08.643
*****
40
CHƯƠNG 4 : CÁC CẤU TRÚC ĐIỀU KHIỂN
Ngôn ngữ lập trình Fortran cung cấp đầy đủ các cấu trúc điều khiển có khả năng lựa chọn
thực hiện một lệnh hay khối lệnh phù hợp với tình huống xảy ra khi chạy chương trình cũng như
khả năng thực hiện một khối lệnh lặp đi lặp lại nhiều lần cho đến khi một điều kiện nào đó được
đáp ứng .
Ngôn ngữ Fortran 90 có ba dạng cấu trúc điều khiển chính như sau :
Cấu trúc lựa chọn đơn với câu lệnh if , trong đó lệnh hoặc khối lệnh được thực hiện phụ thuộc
vào một điều kiện logic ( có giá trị đúng hoặc sai ) .
Cấu trúc lựa chọn đa nhánh với câu lệnh select case .
Cấu trúc vòng lặp với câu lệnh do .
Mỗi cấu trúc có các dạng cú pháp áp dụng khác nhau tùy theo yêu cầu của bài toán và có
thể được đặt lồng vào nhau ( nested ) , được đặt tên (named) nhằm giúp người đọc văn bản chương
trình dễ dàng theo dõi cấu trúc của chương trình cũng như hướng dẫn chương trình nhảy đến đúng
các vị trí xác định .
Ngoài ra , Fortran còn có các lệnh không tuân theo sự thực hiện tuần tự các câu lệnh trong
chương trình như các lệnh exit , cycle , goto .
Chú thích : [name :] , [name] là tên tự đặt cho khối if ( optional ) .
Khi vào khối if , biểu thức logic được xét giá trị , nếu có giá trị đúng thì khối lệnh 1 trong
phần then sẽ được thực hiện ; ngược lại nếu có giá trị sai thì khối lệnh 2 trong phần else sẽ được
thực hiện . Khối if có thể được đặt tên ( không bắt buộc ) , tên tuân theo quy tắc đặt tên biến của
41
Fortran. Ví dụ : chương trình giải phương trình bậc nhất ax + b = 0 sử dụng cấu trúc dạng đầy đủ
if then else end if như sau :
program ptbn
implicit none
real :: a, b, x
print*," Nhap hai so a, b : " ; read*, a, b
if ( a /= 0.) then
x = -b/a
print*,"Nghiem so x = " , x
else
print*," Phuong trinh vo nghiem ." ! truong hop a = 0.
end if
read*
end program ptbn
Ví dụ : chương trình nội suy tuyến tính giữa hai điểm M1(x1,y1) , M2(x2,y2) với x1 < x2 và
điểm M (X,Y) có hoành độ X cho trước thỏa : x1 < X < x2 .Chương trình sẽ tính tung độ Y.
M2(x2,y2) M1(x1,y1)
Y Y M (X,Y)
M(X,Y)
M1(x1,y1) M2(x2,y2)
X X
Trường hợp : y2 > y1 Trường hợp : y2 < y1
program noisuy
implicit none
real :: x1,y1,x2,y2,X,Y
! Nhap toa do hai diem M1(x1,y1),M2(x2,y2)
! Dieu kien x1 < X < x2
print *," Toa do M1:";read*,x1,y1 Toa do M1:
print *," Toa do M2:";read*,x2,y2 1.,4.
print *," Hoanh do M "; read*,X Toa do M2:
if (y2>y1) then 6.,1.
Y = y1 + (X-x1)*(y2-y1)/(x2-x1) Hoanh do M
print '(a,2f8.3)',"Toa do M(X,Y) :",X,Y 4.
else ! Truong hop y2 < y1 Toa do M(X,Y) : 4.000 2.200
Y = y2 + (X-x2)*(y2-y1)/(x2-x1)
print '(a,2f8.3)',"Toa do M(X,Y) :",X,Y
end if
read*
end
Nhận xét : với cấu trúc if dạng đầy đủ như trên , chúng ta chỉ cần xét biểu thức (điều kiện)
logic chỉ một lần và thực hiện khối lệnh phù hợp với giá trị của biểu thức logic là đúng hoặc sai .
Từ khóa end if có thể viết liền thành endif . Phần khối lệnh then hoặc khối lệnh else gồm các câu
lệnh đơn giản hoặc các câu lệnh có cấu trúc .
42
Trong cấu trúc ( if then else end if ) , phần else có thể không có như sau ( tương tự câu
lệnh if đơn giản như 4.1.1 ) :
if ( biểu thức logic ) then
< khối lệnh > ! thực hiện nếu biểu thức logic đúng .
end if
Nếu biểu thức logic có giá trị đúng thì khối lệnh được thực hiện , nếu sai thì chương trình
bỏ qua câu lệnh if này và nhảy đến thực thi câu lệnh sau từ khóa end if .
Ví dụ : if ( x >= 0. ) then
y = sqrt (x)
print*,” Can bac hai cua “ , x , “ la : “ , y
end if
Ghi chú :
+ Từ khóa else if có thể được viết liền thành elseif . Khối lệnh gồm các câu lệnh đơn giản
hoặc câu lệnh có cấu trúc.
+ Phần else if (bt logic) then < khối lệnh > có thể được lặp lại nhiều lần tùy theo yêu cầu
của bài toán .
+ Phần else có thể không có .
Ví dụ : chương trình giải phương trình bậc hai ax2 + bx + c = 0 trong R với các hệ số a,b,c
nhập từ bàn phím ( giả sử a 0) được viết như sau :
program ptbh
implicit none
real (kind = 8) :: a,b,c,delta,x1,x2 ! dung so thuc chinh xac kep
print*," Hay nhap cac he so a, b,c : "
read*, a,b,c
delta = b*b - 4.*a*c
print*,delta ! Kiem tra gia tri va dau cua delta
test : if ( abs(delta) < epsilon(1.d0) ) then
x1 = -b/(2.*a)
print '(a,f10.4)'," Phuong trinh co nghiem kep : " , x1
43
else if (delta > 0.) then test ! Truong hop delta > 0.
x1 = (-b + sqrt(delta)) /(2.*a)
x2 = -b/a - x1
print*, " Phuong trinh co hai nghiem : "
print '(f10.4,a,f10.4)', x1, " va", x2
else test ! Truong hop delta < 0.
print*,"Phuong trinh vo nghiem . "
end if test
read*
end program ptbh
Bạn đọc chạy chương trình ptbh trên với các hệ số a,b,c khác nhau để kiểm tra chương trình .
Lưu ý quan trọng : trường hợp so sánh delta bằng 0. , chúng ta không nên dùng trực tiếp điều
kiện so sánh (delta == 0.) mà dùng điều kiện abs (delta) < epsilon (1.d0) , do kết quả của phép
tính biểu thức delta ( kiểu real ) do máy tính trả về có thể là một số thực rất nhỏ nhưng khác 0. và
có mang dấu ( Về số epsilon , bạn đọc xem lại bảng 2.2 , chương 2 . )
Ví dụ : Phương trình x2 – 2.468x + 1.522756 = ( x – 1.234) 2 có kết quả như sau :
Hay nhap cac he so a, b,c :
1. -2.468 1.522756
-1.3400738851920835E-16 ! Giá trị delta do máy biểu diễn là một số âm có giá trị tuyệt đối
! nhỏ hơn epsilon(1.d0) = 2.220446...E-16
Phuong trinh co nghiem kep : 1.2340 ( phù hợp ) .
Nếu dùng phép so sánh if ( delta == 0.) then ... sẽ có kết quả là phương trình vô nghiệm , do
delta có giá trị âm :
Hay nhap cac he so a, b,c :
1 -2.468 1.522756
-1.3400738851920835E-16 ! Giá trị delta âm .
Phuong trinh vo nghiem . ( Kết quả không phù hợp ) .
+ Khối if trong ví dụ trên được đặt tên là test thật sự không cần thiết . Việc đặt tên chỉ
thuận tiện cho việc đọc văn bản chương trình khi chúng ta có các khối if lồng vào nhau như ví dụ
sau :
program ifnested
implicit none
real :: x = 1.5 , y = -3.5
test1 : if (x >= 0.) then
test2 : if (y >=0.) then
print*,"Hai so x,y la so duong."
else test2
print*,"x la so duong , y la so am ngat . "
end if test2
else test1
print*," x la so am ngat ."
end if test1
read*
end program ifnested
Lưu ý : a) Các tập giá trị thuộc phần case không được có phần tử chung và phải có cùng kiểu
dữ liệu với kiểu của biểu thức chọn nằm trong cặp dấu ngoặc của từ khóa select case .
b) Phần case default có thể không có trong cấu trúc select case .
c) Tập giá trị nằm trong case (…) có thể là một giá trị đơn ; nhiều giá trị được liệt kê cách
nhau bằng dấu phẩy hoặc một đoạn con có kèm theo cận trên , cận dưới . Ví dụ :
integer :: n
….
select case (n)
case ( : 0 )
<khối lệnh > ! được thực hiện nếu n 0 ,
case (1:5)
< khối lệnh > ! được thực hiện nếu 1 n 5
case (6 : )
< khối lệnh > ! được thực hiện nếu n 6
….
Ví dụ : chúng ta xem chương trình xếp loại học sinh như sau , điểm đầu vào là một số
nguyên từ 0 đến 10 . Biểu thức quyết định sự lựa chọn là một biến kiểu integer 1 byte (điểm số ).
program xeploai
implicit none
integer (kind = 1) :: diem
print '(a,$)',"Cho biet diem so : " ; read*,diem
select case (diem)
case(9,10)
print*,"Xep loai GIOI . "
case(7,8)
print*,"Xep loai KHA . "
case(5,6)
print*,"Xep loai TRUNG BINH ."
45
case (0:4) ! truong hop 0 diem 4
print*,"Xep loai KEM ."
case default
print*,"Diem vao khong hop le . "
end select
read*
end program xeploai
Kết quả :
Cho biet diem so : 7
Xep loai KHA .
Lưu ý quan trọng : khi điểm đầu vào có phần lẻ như 7.5 , 9.2 … chúng ta không dùng được
cấu trúc select case như trên mà phải dùng cấu trúc if tổng quát với biến điểm kiểu real như đoạn
chương trình sau ,cần phải xét từ điểm cao xuống điểm thấp không được xét theo chiều ngược lại.
…
real :: diem
print '(a,$)',"Cho biet diem so : " ; read* ,diem
if ((diem >= 9.) .and. (diem <= 10.)) then
print*," Xep loai GIOI ."
else if ((diem >= 7.) .and. (diem <9.)) then
print*," Xep loai KHA ."
else if ((diem >= 5.) .and. (diem < 7.)) then
print*," Xep loai TRUNG BINH ."
else if ((diem >= 0.) .and. (diem < 5.)) then
print*," Xep loai KEM ."
else
print*," Diem vao khong hop le."
end if
+ Cấu trúc select case còn có thể được lồng vào nhau và được đặt tên như chương trình
cho biết số ngày trong một tháng như sau :
program songay
implicit none
integer :: thang, nam
logical :: nhuan
print '(a,$)' ," Cho biet thang : " ; read*, thang
print '(a,$)' ," Cho biet nam : " ; read*, nam
test1 : select case (thang)
case (1,3,5,7,8,10,12) test1
print*,"Thang ",thang,"co 31 ngay."
case (4,6,9,11) test1
print*,"Thang ",thang,"co 30 ngay."
case (2) test1 ! Thang hai .
nhuan = ( mod(nam,400) == 0).or. &
&( mod(nam,4) == 0 .and.mod(nam,100 ) /= 0 )
test2 : select case (nhuan)
46
case(.true. ) test2 ! nam nhuan
print*,"Thang 2 nam ",nam, "co 29 ngay."
case(.false. ) test2 ! khong phai nam nhuan
print*,"Thang 2 nam ",nam, "co 28 ngay."
end select test2
case default test1
print*,"So lieu vao khong dung ."
end select test1
read*
end program songay
Trong chương trình ví dụ trên , đối với tháng 2 , chúng ta còn phải xét thêm năm đó có là
năm nhuận hay không ( theo điều kiện của lịch Gregorius ) và lồng cấu trúc select case có tên test2
với biểu thức lựa chọn kiểu logical nằm bên trong phần case (2) . Khối select case có tên test2
không có phần case default .
Biểu thức lựa chọn trong select case có thể có kiểu character như trong ví dụ sau :
program quocgia
implicit none
character (len = 15) :: nuoc
print*," Cho biet ten quoc gia : "
read*, nuoc
select case (nuoc)
case('Viet Nam' , 'Thai Lan' ,'Nhat', 'Han Quoc')
print*,’Nuoc ‘,trim(nuoc), ‘ thuoc Chau A .’
case('Anh' , 'Phap' ,'Duc', 'Y', 'Bi' )
print*,’Nuoc ‘,trim(nuoc), ‘ thuoc Chau Au .’
case('Nam Phi' , 'Ai Cap' ,'Angieri', 'Nigieria')
print*,’Nuoc ‘,trim(nuoc), ‘ thuoc Chau Phi .’
case('My' , 'Canada' ,'Mehico', 'Braxin', 'Achentina' )
print*,’Nuoc ‘,trim(nuoc), ‘ thuoc Chau My .’
case('Uc' , 'Niu Dilan' )
print*,’Nuoc ‘,trim(nuoc), ‘ thuoc Chau Dai Duong .’
case default
print*, ‘So lieu vao khong phu hop .’
end select
read*
end program quocgia
47
4.3 Cấu trúc vòng lặp do .
Khi cần thực hiện một lệnh hay một khối lệnh lặp đi lặp lại nhiều lần , chúng ta sử dụng
cấu trúc vòng lặp do . Số lần thực hiện khối lệnh trong vòng lặp có thể được biết trước hoặc không
được biết trước .
4.3.1 Cấu trúc vòng lặp xác định .
Đây là dạng cấu trúc mà số lần thực hiện lệnh hoặc khối lệnh bên trong vòng lặp được
biết trước . Thực hiện xong một vòng còn gọi là hoàn thành một chu trình .
Cấu trúc vòng lặp xác định có dạng như sau ( phần tên là tùy chọn và theo quy tắc đặt tên
biến của Fortran ) :
[name :] do biến đếm kiểu integer = giá trị đầu , giá trị cuối , số gia
< khối lệnh >
end do [name ]
+ Giá trị đầu, giá trị cuối , số gia : biểu thức kiểu integer .
+ Biến đếm ( biến điều khiển vòng lặp ) phải được khai báo ở đầu chương trình ( phù hợp
với câu lệnh implicit none ) . Giá trị biến đếm có thể sử dụng trong các lệnh thuộc khối lệnh , nhưng
không được có câu lệnh làm thay đổi giá trị của biến đếm này .
+ Cấu trúc do end do có thể được đặt lồng vào nhau và được đặt tên .
+ Từ khóa end do có thể viền liền nhau thành enddo
Quy luật hoạt động của vòng do xác định như sau :
Trường hợp 1 : giá trị đầu < giá trị cuối , số gia > 0
+ Đầu tiên biến đếm lấy giá trị đầu và thực hiện các lệnh trong khối lệnh .
+ Biến đếm sau đó được tăng lên một lượng bằng số gia và so sánh với giá trị cuối , nếu
biến đếm < = giá trị cuối thì thực hiện tiếp khối lệnh lần thứ hai ( chu trình 2 ) .
+ Các chu trình sau được thực hiện tuần tự theo cách tương tự như trên.
+ Vòng lặp kết thúc khi biến đếm lớn hơn giá trị cuối . Chương trình sẽ tiếp tục thực hiện
câu lệnh sau phần end do .
+ Nếu số gia không có thì xem số gia = 1 ( giá trị mặc định ) .
Ví dụ :
integer :: i i= 1
do i = 1,5 i= 2
print*,"i = ", i i= 3
end do i= 4
Kết quả : i= 5
integer :: i i= 1
do i = 1,5,2 i= 3
print*,"i = ",i i= 5
end do
Trường hợp 2 : giá trị đầu > giá trị cuối , số gia < 0
Lưu ý trong trường hợp này số gia âm phải được viết ra , nếu không chương trình sẽ bỏ
qua câu lệnh do .
+ Đầu tiên biến đếm lấy giá trị đầu và thực hiện các lệnh trong khối lệnh .
48
+ Biến đếm sau đó được giảm một lượng bằng trị tuyệt đối của số gia và so sánh với giá trị
cuối , nếu biến đếm >= giá trị cuối thì thực hiện tiếp khối lệnh lần thứ hai ( chu trình 2 ) .
+ Các chu trình sau được thực hiện tuần tự theo cách tương tự như trên.
+ Vòng lặp kết thúc khi biến đếm nhỏ hơn giá trị cuối . Chương trình sẽ thực hiện tiếp câu
lệnh sau phần end do .
Ví dụ :
integer :: i
do i = 5,1,-2
print*,"i = ",i i= 5
end do i= 3
Kết quả : i= 1
+ Ví dụ : chương trình sau sẽ tính tổng n số tự nhiên đầu tiên : S = 1+2+3+...+n
program tong
implicit none
integer :: i,n,s
print*,"Cho biet so so hang n : "; read*,n
s=0
do i = 1,n
s=s+i
end do
print*,"Tong cua",n,"so tu nhien dau tien la :",s
read*
end program tong
Kết quả : Cho biet so so hang n :
20
Tong cua 20 so tu nhien dau tien la : 210
+ Ví dụ về vòng lặp xác định được lồng vào nhau và được đặt tên.
program nestdo
implicit none
integer :: i,j
49
ext : do i = 1,3
int : do j = 1,3
print'(i2,$)', (i+j)
end do int 234
print* 345
end do ext 456
read* Kết quả :
end program nestdo
+ Với mỗi giá trị của biến đếm i ở vòng do ngoài ( được đặt tên là ext ) , biến đếm j ở vòng do
trong ( được đặt tên là int ) lấy lần lượt tất cả các giá trị từ 1 đến 3 và xuất ra tổng (i+j) trên cùng
một hàng .
+ Xâu định dạng ‘(i2,$)’ dùng để xuất một số nguyên chiếm 2 ô màn hình ( kí hiệu định dạng là i2
), số được viết căn lề phải và con trỏ không xuống dòng .
+ Chương trình xuất ra màn hình bình phương các số từ 1 đến 10 ( biểu thức i*i ).
program do_ngam
implicit none
integer :: i
print '(10(1x,i4))', (i*i,i=1,10) ! vòng do ngầm với biến i chạy từ 1 đến 10 .
read*
end
50
4.3.2 Cấu trúc vòng lặp không xác định .
Đây là dạng vòng lặp có số lần thực hiện lệnh hoặc khối lệnh bên trong vòng lặp không
được biết trước mà tùy thuộc vào một điều kiện ( biểu thức logic ) . Cú pháp một số dạng vòng
lặp không xác định như sau :
a) Cấu trúc vòng lặp do while .
+ Đầu tiên biểu thức logic sau từ khóa while được kiểm tra trước , nếu nó có giá trị đúng
(.true.) thì các lệnh thuộc khối lệnh bên trong vòng do mới được thực hiện . Nếu biểu thức logic có
giá trị sai thì chương trình bỏ qua khối do và nhảy đến thực hiện câu lệnh sau phần end do. Khối
do while vì thế có thể không được thực hiện lần nào .
+ Sau mỗi chu trình , biểu thức logic lại được kiểm tra , nếu đúng thì thực hiện chu trình kế
tiếp , nếu sai thì chương trình thoát khỏi khối do while .
+ Cấu trúc do while thuộc loại “ tiền kiểm “ ( kiểm tra điều kiện logic trước khi thực hiện
các lệnh trong vòng do ) .
+ Lưu ý : để tránh vòng lặp vô tận , cần phải có câu lệnh bên trong khối lệnh làm thay đổi
giá trị các toán hạng của biểu thức logic đầu vào sau mỗi bước thực hiện vòng lặp .
Ví dụ : Xét chuỗi điều hòa s = 1 + 1/2 + 1/3 + 1/4 + … + 1/n và một số thực a > 2 cho
trước ( nhập từ bàn phím ) . Tìm số số hạng nhỏ nhất của chuỗi để s >= a .
program dieuhoa
implicit none
integer :: i = 1
real :: s, a
print*,"Cho biet so a :"; read*,a
s = 1.
do while (s < a)
i= i+1
s = s + 1. /i ! hoặc s = s + 1. /real (i)
end do
print*," So so hang nho nhat i = " , i
read*
end program dieuhoa
Kết quả :
Cho biet so a :
4.
So so hang nho nhat i = 31
Lưu ý : trong chương trình trên nếu viết s = s + 1/i thay vì viết đúng là s = s + 1./i ( hoặc
có thể viết s = s + 1./real(i) ) thì chúng ta sẽ rơi vào vòng lặp vô tận do phép chia hai số nguyên 1/
i ( i 2) sẽ luôn bằng 0 và s = 1. không đổi .
+ Chúng ta xem chương trình tính ước số chung lớn nhất và bội số chung nhỏ nhất của hai
số nguyên dương m , n sử dụng vòng lặp do while như sau :
51
program usc_bsc
implicit none
integer :: m,n,ms,ns,uscmax,bscmin
print*," Cho hai so nguyen duong m , n : " ; read*, m , n
ms = m ; ns = n ! luu cac so m , n
do while (m /= n)
if ( m > n ) then
m=m-n
else ! truong hop n > m
n = n -m
end if
end do
uscmax = m ; bscmin = (ms*ns)/m
print*,"Uoc so chung lon nhat :",uscmax
print*,"Boi so chung nho nhat :",bscmin
read*
end program usc_bsc
Kết quả :
Cho hai so nguyen duong m , n :
64 24
Uoc so chung lon nhat : 8
Boi so chung nho nhat : 192
Nhận xét : Khối lệnh bên trong vòng lặp do là khối lệnh if then else endif .
b) Cấu trúc vòng lặp không xác định dạng “ hậu kiểm “ ( điều kiện logic được
kiểm tra sau ) :
[ name :] do
< lệnh >
< lệnh >
…
if (biểu thức logic kiểm tra ) exit ! Nếu đúng thì thoát ra.
end do [ name ]
+ Đầu tiên các lệnh trong vòng do được thực hiện lần lượt cho đến khi gặp câu lệnh kiểm
tra if ở cuối chu trình , nếu biểu thức logic có giá trị đúng thì chương trình thoát khỏi vòng do (
thực thi lệnh exit ) và thực hiện tiếp các lệnh sau phần end do . Ngược lại nếu có giá trị sai thì
chương trình quay trở lại thực hiện chu trình tiếp theo của vòng do .
+ Các lệnh thuộc cấu trúc này được thực hiện ít nhất một lần . ( Trong khi vòng do while có
thể không được thực hiện lần nào ) .
+ Lệnh exit dùng để thoát khỏi vòng do , sẽ được nói rõ thêm ở phần sau .
+ Lưu ý : để tránh vòng lặp vô tận , cần phải có câu lệnh bên trong vòng do làm thay đổi giá
trị các toán hạng của biểu thức logic kiểm tra sau mỗi bước thực hiện vòng lặp .
Trong ví dụ về vòng lặp dưới đây , chỉ khi chúng ta nhập giá trị ‘n’ hay ‘N’ cho biến traloi
( kiểu character 1 kí tự ) thì chương trình mới thoát khỏi vòng do .
program test
implicit none
character traloi
do
print*,'Hello ! '
52
print*,'Welcome to Viet Nam ! '
print*,' Ban muon tiep tuc y/n ' ; read*, traloi ! Nhap n hoac N de thoat .
if ((traloi == 'n') .or. (traloi == 'N')) exit
end do
read*
end
[ name :] do
< khối lệnh>
end do [ name ]
[name : ] do
< lệnh >
…
if ( biểu thức logic1 ) cycle ! trở lại đầu vòng lặp ,
! thực hiện chu trình kế tiếp.
…
if ( biểu thức logic 2 ) exit ! thoát khỏi vòng do .
end do [ name ]
…
Trong cấu trúc tổng quát nêu trên : khi gặp lệnh cycle , chương trình sẽ không thực hiện
tiếp các lệnh của chu trình hiện thời sau lệnh cycle mà quay trở lại thực hiện từ đầu các lệnh của
chu trình kế tiếp ( sau từ khóa do ) .
Khi gặp lệnh exit , chương trình thoát khỏi vòng do có chứa lệnh exit này và thực hiện tiếp
các lệnh sau phần end do .
Lưu ý : Khi chương trình ‘lỡ‘ rơi vào vòng lặp vô tận , cần nhấn tổ hợp phím Crtl – C để
dừng việc thực thi chương trình . Trường hợp không hiệu quả , chúng ta có thể nhấn phím có logo
windows , sau đó chọn Restart trong nhóm Shut down để khởi động lại máy tính .
Ví dụ : Chương trình kiểm tra xem một số nguyên dương m nhập từ bàn phím có phải là số
nguyên tố hay không ? Chúng ta kiểm tra tuần tự phép chia số m cho số 2 đến số nguyên lớn hơn
hay bằng căn bậc hai của m , nếu gặp phép chia có số dư bằng 0 ( chia chẵn ) thì m không phải là
số nguyên tố . Lúc này lệnh exit sẽ kết thúc sớm vòng do và thực hiện tiếp các lệnh sau phần end
do .
program nguyento
implicit none
integer :: i,m,n
53
logical :: test = .true.
print*," Cho so nguyen duong m : " ; read*,m
n = sqrt(real(m)) + 1.
do i = 2,n
if (mod(m,i) == 0) then
test = .false.
exit ! thoat khoi vong do
end if
end do
if (test) then
print*," So ", m," la so nguyen to. "
else
print*," So ", m," khong phai la so nguyen to. "
end if
read*
end program nguyento
+ Chúng ta thấy có sự tương đương giữa hai dạng vòng do như sau :
do while ( bt logic ) do
… if (.not.(bt logic) ) exit
…
< khối lệnh > < khối lệnh >
end do end do
Nhận xét : khi bt logic đúng thì các lệnh trong vòng do mới được thực hiện .
+ Ví dụ : chúng ta xem vòng lặp xác định in ra các số từ 1 đến 7 nhưng không in ra các số
4 và 5 như sau ( sử dụng lệnh cycle ) :
program cycle
implicit none
integer :: i i= 1
do i = 1,7 i= 2
if ((i == 4).or.(i == 5)) cycle i= 3
i= 6
print*," i = ", i
i= 7
end do
read*
end
54
Nhận xét : Khi i = 4 hoặc i = 5 lệnh cycle có hiệu lực , chương trình sẽ bỏ qua lệnh in các
số này và thực hiện chu trình kế tiếp .
+ Chúng ta xem cấu trúc vòng lặp do được lồng vào nhau và được đặt tên như dưới đây ,
các lệnh cycle , exit có thể kèm theo tên của vòng lặp và sẽ có các ứng xử khác khau .
loop_ext : do
! Chương trình nhảy đến đây khi gặp lệnh cycle loop_ext
loop_int : do
! Chương trình nhảy đến đây khi gặp lệnh cycle
< lệnh >
…
If (bt logic 1 ) cycle
If (bt logic 2 ) cycle loop_ext
…
If (bt logic 3 ) exit
If (bt logic 4 ) exit loop_ext
end do loop_int
! Chương trình nhảy đến đây khi gặp lệnh exit
end do loop_ext
! Chương trình nhảy đến đây khi gặp lệnh exit loop_ext
Nhận xét : các lệnh exit , cycle chỉ có tác dụng trong phạm vi vòng do chứa các lệnh này
ngoại trừ trường hợp lệnh có kèm theo tên của vòng lặp như sơ đồ nêu trên . Lệnh exit , cycle được
phép sử dụng trong mọi kiểu vòng do.
Vòng lặp do kết hợp với các lệnh cycle , exit có thể dùng để kiểm tra việc nhập số liệu : nếu
số liệu nhập chưa đúng thì lệnh cycle yêu cầu phải nhập lại , nếu nhập đúng rồi thì lệnh exit sẽ kết
thúc vòng do , thực thi các lệnh sau chỉ thị end do .
program kiemtra
implicit none
real :: x
do
print*," Nhap so duong x "; read*,x
if ( x <0.) cycle ! Nhập lại
if ( x >= 0.) exit ! Nhập đúng thoát ra khỏi vòng do
end do
print*," So x : ", x ; read*
end
+ Chương trình kiểm tra việc nhập điểm :
program kiemtradiem
implicit none
integer :: diem
do
print*," Vao diem tu 0 den 10 : "; read*,diem
if ((diem <= 10) .and. (diem >= 0)) exit ! Nhập đúng thì thoát ra
print*," Diem vao khong dung .Nhap lai." ! Nhập lại
end do
print*,"Diem da vao hop le :", diem
55
read*
end
Vao diem tu 0 den 10 :
12
Diem vao khong dung .Nhap lai.
Vao diem tu 0 den 10 :
8
Diem da vao hop le : 8
4.4 Lệnh goto .
+ Cú pháp : goto nhãn
Nhãn (label) là một số nguyên dương khác 0 ( không có dấu ) có từ 1 đến 5 chữ số .Nhãn
do người lập trình tự chọn và không cần xếp đặt theo thứ tự . Ví dụ : 10 , 20 , 100 ...
+ Lệnh goto sẽ chuyển hướng chương trình đến thực hiện câu lệnh có gắn nhãn tương ứng
với nhãn của lệnh goto , nằm ở một vị trí nào đó trong chương trình . Nhãn được đặt ngay trước
câu lệnh liên kết và cách câu lệnh một hoặc vài khoảng trống . Lệnh goto thường đi kèm với một
biểu thức logic. Ví dụ :
do
< lệnh >
…
if ( biểu thức logic ) goto 100
…
end do
…
100 y = x*x ! thực hiện sau lệnh goto 100 nếu biểu thức logic đúng.
+ Nhận xét : lệnh goto phá vỡ cấu trúc tuần tự của chương trình và nếu chương trình có quá
nhiều lệnh goto như các phiên bản Fortran trước sẽ làm cho chúng ta khó đọc , khó theo dõi cấu
trúc của văn bản chương trình . Do Fortran 90 đã cung cấp đầy đủ các cấu trúc điều khiển , tạo sự
thuận tiện cho người lập trình nên lệnh goto được khuyến cáo hạn chế sử dụng .
Ví dụ :
do
< lệnh >
if ( bt logic ) then
print*,” Chuong trinh khong the tiep tuc .“
read*
stop ! chuong trinh ket thuc tai day.
end if
…
56
end do
...
4.6 Thực hành .
4.6.1 Bạn đọc ứng dụng cấu trúc if tổng quát để viết chương trình tính thuế thu nhập cá nhân
theo biểu thuế suất như sau :
+ Bạn đọc phân tích và chạy test chương trình đáp án sau :
program thue_tncn
implicit none
real :: thunhap , thue
print*,"Don vi tinh : TRIEU DONG."
print*,"Cho biet thu nhap chiu thue : "
read*, thunhap
if (thunhap > 80.) then
thue = (thunhap - 80.)*0.35 + 18.15
else if (thunhap > 52.) then
thue = (thunhap - 52.)*0.30 + 9.75
else if (thunhap > 32.) then
thue = (thunhap - 32.)*0.25 + 4.75
else if (thunhap > 18.) then
thue = (thunhap - 18.)*0.20 + 1.95
else if (thunhap > 10.) then
thue = (thunhap - 10.)*0.15 + 0.75
else if (thunhap > 5.) then
thue = (thunhap - 5.) *0.10 + 0.25
else if (thunhap <= 5.) then
thue = thunhap*0.05
else
print*,"So lieu vao khong dung "
end if
print'(a,f9.3)', " ** Thue thu nhap : " , thue
read*
end
Kết quả :
Don vi tinh : TRIEU DONG.
Cho biet thu nhap chiu thue :
48.5
** Thue thu nhap : 8.875
57
4.6.2 Ứng dụng vòng do xác định để tính gần đúng tích phân xác định bằng công thức hình
thang :
b 1
s f ( x)dx 1 x 2 dx h = (b – a)/n ; xi = a + i*h → yi = f(xi) ( i = 0..n)
a 0
s = [ (y0 + yn) * 0.5 + y1 + y2 +.. + yn-1 ] * h
Các cận a,b và số đoạn chia n được nhập từ bàn phím .
Bạn đọc phân tích và chạy chương trình sau :
program tichphan
implicit none
real :: a,b,x,y,h,s
integer :: i,n
print*," Vao cac so a,b,n "
read*,a,b,n
h = (b-a)/n
s = (sqrt(1+a*a) + sqrt(1+b*b))*0.5
do i = 1, (n-1)
x = a + i*h
s = s + sqrt(1+x*x)
end do
s=s*h
print'(a,f10.4)'," Ket qua : " , s
read*
end
Áp dụng :
Vao cac so a,b,n
0 1 100
Ket qua : 1.1478
+ Dựa vào bài tập trên , bạn đọc lập trình tính các tích phân xác định sau :
1 2
e e
x2 x2
dx ; dx
0 0
4.6.3 Ứng dụng vòng lặp không xác định do while để tính giá trị của hàm mũ ex theo chuỗi
số sau :
ex = 1 + x/1! + x2/2! + x3/3! + … + xn/n! n! =1*2*3*….*(n-1)*n
58
program ham_mu
implicit none
real (kind = 8) :: s,t,x ! dung so thuc chinh xac kep
integer :: n
print*," Cho gia tri x : " ; read*,x
s = 1._8
t=x
n=1
do while ( t >= 1.d-10) ! ngắt bỏ các số hạng < 10-10
s=s+t
n=n+1
t = t*(x/n)
end do
print*," So so hang cua chuoi n : " , n
print*, " Tri so exp(x) : "
print '(d18.10)', s
read*
end
4.6.4 Lập trình tính số hạng thứ n >2 của dãy số Fibonacci : 1 1 2 3 5 8 13 21 ...
u1 = 1 ; u2 = 1 ; un = un-1 + un-2 ( n 3)
program fibonacci
implicit none
integer :: i,n,a,b,u
a = 1 ; b = 1 ! hai gia tri u1 , u2
print ‘(a,$)’, “Cho so n > 2 :” ; read*,n
do i = 3,n
u = a+b ! gia tri u(i)
a=b;b=u
end do
print*,”** So hang thu”,n,” bang :”,u
read* ; end
Kết quả :
Cho so n > 2 : 8
** So hang thu 8 bang : 21
4.6.5 Lập chương trình chuyển đổi nhiệt độ từ độ Farenheit sang độ Celcius và ngược lại .
Sử dụng các công thức : C =5./9.*(F-32.) và F = 9./5.*C+32. Tạo menu và sử dụng cấu trúc
select case với biến chọn kiểu integer để lựa chọn công thức chuyển đổi phù hợp .
program doinhietdo
implicit none
integer :: chon
59
real :: c,f
character :: traloi
do
print*,"**** CHUONG TRINH DOI NHIET DO ****"
print*,"==================================="
print*,"1.Doi tu do F ra do C : bam phim so 1" !Tạo menu .
print*,"2.Doi tu do C ra do F : bam phim so 2"
print '(a,$)'," Ban chon phim so : "; read*,chon
print*
select case (chon)
case (1)
print '(a,$)'," Cho nhiet do F :" ; read*,f
c = 5./9.*(f-32.)
print '(a,f6.2)'," Nhiet do C la : ",c
case (2)
print '(a,$)'," Cho nhiet do C :" ; read*,c
f = 9./5.*c + 32.
print '(a,f6.2)'," Nhiet do F la : ",f
case default
print*," So lieu nhap chua dung!"
end select
print*,"------------------------------------------------------------"
print*,"Tiep tuc bam phim y hoac Y."
print*,"Thoat khoi chuong trinh bam n hoac N ."
print '(a,$)'," Tiep tuc hay thoat ? "; read*,traloi
if ((traloi == 'n').or.(traloi == 'N')) exit ! Thoát khỏi vòng lặp do end do
print*
call system ('cls') ! Lau man hinh , clear screen
end do
end program doinhietdo
Ghi chú : Khi menu hiện ra , chúng ta chọn phím số 1 hoặc số 2 để chọn kiểu chuyển đổi . Vào
nhiệt độ F hoặc C theo yêu cầu . Để tiếp tục chương trình chúng ta bấm phím y hoặc Y hoặc phím chữ bất
kỳ khác với phím n ,N ; để thoát khỏi chương trình chúng ta bấm phím n hoặc N .
Lệnh call system (‘cls’) gọi lệnh lau màn hình cls của hệ thống ( clear screen ) , các nội dung cũ bị
xóa và bắt đầu một phiên làm việc mới . Nếu không có lệnh này các kết quả tính toán sẽ nối tiếp liên tục
trên màn hình kết quả . Toàn bộ các lệnh nằm trong khối do end do và chỉ khi điều kiện logic trong lệnh
if ( ) được thỏa thì lệnh exit mới cho phép thoát khỏi vòng lặp .
*****
60
Chương 5 : CÁC LỆNH NHẬP / XUẤT VÀ ĐỊNH DẠNG DỮ LIỆU
Các lệnh nhập / xuất dữ liệu cho phép sự trao đổi ( truyền ) dữ liệu giữa bộ nhớ máy tính
và các thiết bị ngoại vi như màn hình , bàn phím , máy in , các thiết bị ghi /đọc bên ngoài như ổ
đĩa cứng , usb ... Quá trình truyền dữ liệu sẽ làm phát sinh một số vấn đề do sự chuyển đổi dữ liệu
từ dạng kí tự text thông thường sang dạng nhị phân (mã máy ) và ngược lại . Ngoài ra , việc xuất /
nhập dữ liệu theo một định dạng được sắp xếp trước sẽ giúp cho việc trình bày hoặc nhập dữ liệu
được chính xác , gọn gàng và mỹ thuật .
hoặc sử dụng kết hợp với câu lệnh format có gắn nhãn đứng riêng như sau :
write (*, nhãn ) < các đối tượng xuất.> print nhãn ,< các đối tượng xuất. >
nhãn format ( phần định dạng ) ! nhãn ( label ) là số nguyên dương khác 0 ,
! có từ 1 đến 5 chữ số do chúng ta tự đặt .
Phần định dạng nằm trong cặp dấu ngoặt đơn sẽ cho biết số lượng , kiểu dữ liệu , kiểu định
dạng , vị trí của các đối tượng xuất , cũng như lệnh điều khiển con trỏ . Có sự tương ứng giữa nội
dung phần định dạng và các đối tượng xuất . Ví dụ lệnh xuất ra màn hình có định dạng cho 3 đối
tượng như sau :
61
print ‘(a ,f8.2, a)’, “Chieu cao H = “, h ,” m” hoặc tương đương với :
write (*,’(a ,f8.2, a)’) “Chieu cao H = “, h ,” m”
Phần định dạng (a,f8.2,a) tương ứng với 03 đối tượng xuất như sau :
+ a “Chieu cao H = ” ! a dùng để định dạng xâu kí tự với số ô biểu diễn xâu bằng
chiều dài thực tế của xâu .
+ f8.2 h ! f8.2 dùng định dạng cho số thực h = 18.165 , được viết ra theo dạng dấu
chấm tĩnh trong 8 ô ( gồm cả phần dấu , phần nguyên, dấu chấm thập phân , phần lẻ thập phân
và các ô trống phía trước ) trong đó phần lẻ chiếm 2 ô . Số được căn lề phải và có thể được làm
tròn chữ số cuối cùng . ( Định dạng fw.d ) .
w=8
1 8 . 1 7
d=2
+ a “m” ! a dùng để định dạng cho xâu kí tự thứ hai .
Chi tiết viết phần định dạng sẽ được trình bày ở các phần sau .
Ghi chú : Fortran dành số 6 cho thiết bị xuất chuẩn là màn hình nên :
+ Lệnh xuất write (6,*) tương đương với lệnh write (*,*) ;
+ Lệnh write (6,’(định dạng )’) tương đương với lệnh write (*,’(định dạng)’)
Khi gặp lệnh read , chương trình sẽ dừng lại chờ cho chúng ta nhập các giá trị phù hợp từ
bàn phím .Nếu nhập một lần cho cả 4 biến , thực hiện như sau :
2.345 2018 ‘Le Van A’ .true. <Enter> hoặc
2.345 , 2018 , ‘Le Van A’ , .true. <Enter>
5.3 Cách viết phần định dạng trong các lệnh xuất / nhập dữ liệu .
Phần trình bày một số các mẫu định dạng thông dụng như dưới đây được minh họa bằng
các lệnh xuất ra màn hình cho dễ quan sát , các mẫu định dạng này còn được dùng để nhập/xuất dữ
liệu sử dụng phương tiện lưu trữ dạng tập tin ( file ) .
5.3.1 Định dạng cho số nguyên .
a) Mẫu định dạng : niw
n : số lần lặp lại mẫu định dạng iw . i : số kiểu integer
w : width , tổng số ô (kí tự ) dùng biểu diễn số nguyên bao gồm phần dấu , phần số và các
khoảng trống phía trước số . Số được viết căn lề phải ( right justified ) . Ví dụ m = 6 , n = -12 với
lệnh xuất dữ liệu :
print ‘(i3,i4)’ , m , n write (*,’(i3,i4)’) m , n , thể hiện ra như sau :
m n
w=3 w=4
6 - 1 2
k l
w=4 w=4
2 5 - 4 0
Lưu ý : Định dạng ‘(2i4)’ ‘(i4,i4)’ . Nếu số chữ số ( kể cả dấu ) của số nguyên lớn hơn
w ( thiếu chỗ ) , chương trình sẽ in ra w dấu * .
Khi nhập hai số nguyên k = 25 , l = - 40 có định dạng i4 bằng bàn phím :
Lệnh read (*,’(2i4)’) k,l
Chúng ta phải nhập vào đúng dạng như sau : ^^25^-40 <Enter> , trong đó kí tự ^ tượng
trưng cho một kí tự trống trên màn hình ( dùng phím space của bàn phím ) .Lệnh read có phần
định dạng thường dùng với dữ liệu dạng tập tin , khi nhập dữ liệu từ bàn phím chúng ta chỉ dùng
kiểu định dạng mặc định : read *, k, l .
5.3.2 Định dạng cho số thực dạng dấu chấm tĩnh .
Mẫu định dạng : nfw.d
n : số lần lặp lại mẫu định dạng fw.d f : kiểu số thực dạng dấu chấm tĩnh .
w : tổng số ô dùng biểu diễn số thực bao gồm phần dấu , phần nguyên , dấu chấm thập
phân , phần lẻ thập phân và các khoảng trống phía trước số ( nếu có ) .
d: số ô dành cho phần lẻ thập phân . Số được viết căn lề phải và có thể được làm tròn ở chữ
số cuối cùng .
Ví dụ số thực -57.2867 ; lệnh write (*,’(f10.3)’) viết ra như sau :
w = 10
- 5 7 . 2 8 7
d=3
Lưu ý : nếu a = 123.456 , với định dạng f10.7 ( w = 10, d = 7 ) sẽ không đủ chỗ thể hiện số
a , lúc này chương trình in ra 10 dấu * .
Cần kiểm tra : w (d + 1[dấu chấm] ) + (số chữ số phần nguyên + 1 [ dấu ] ).
w d + 2 + số chữ số phần nguyên .
5.3.3 Định dạng cho số thực dạng bậc ( lũy thừa 10 ) .
a) Mẫu định dạng : new.d
n : số lần lặp lại mẫu định dạng ew.d e : kiểu số thực dạng bậc
w : tổng số ô dùng biểu diễn số thực bao gồm phần định trị (mantissa) [ dấu , phần nguyên
, dấu chấm , phần lẻ thập phân ] ; phần bậc ( gồm : chữ e, dấu , phần mũ ) , các khoảng trống
phía trước ( Tổng quát : w d + 7 ) .
d: số ô dành cho phần lẻ thập phân thuộc phần định trị . Số được viết căn lề phải và phần
định trị m có dạng chuẩn hóa ( 0.1 m < 1 ) .
d
0 . e
w d +7
Ví dụ : pi = 3.1415926 0.31416 101, lệnh write (*,’(e12.5)’) viết ra như sau :
d=5
0 . 3 1 4 1 6 e + 0 1
w = 12
64
b) Mẫu định dạng : new.dee
Trong định dạng này , số e ở cuối cho biết số chữ số dành cho phần mũ .
d e
0 . e
w 5 +d + e
Ví dụ : Chúng ta xem chương trình sau về xuất dữ liệu số thực ra màn hình :
program testinout3
implicit none
real :: x = 2.71828
real (kind = 8) :: y = 2.718281828_8 ! so thuc do chinh xac kep .
write(*,*) " Format tu dong "
write(*,*) x,y
write(*,*)
write(*,*) " Format theo mau dinh dang "
write(*,'(f12.5 ,f16.9)') x ,y ! Dinh dang Fw.d
write(*,'(e15.6, d20.10)') x,y ! Dinh dang Ew.d , Dw.d
write(*,'(e17.6e4, d22.10e4)') x,y ! Dinh dang Ew.dEe , Dw.dEe
write(*,'(g15.6,g18.10)') x,y ! Dinh dang Gw.d
read*
end Format tu dong
2.71828 2.718281828
Kết quả :
Format theo mau dinh dang
2.71828 2.718281828
0.271828E+01 0.2718281828D+01
0.271828E+0001 0.2718281828D+0001
2.71828 2.718281828
65
Khi xuất , sẽ có (w-1) kí tự trống trước kí tự T ( nếu biến logical có giá trị .true.) hoặc kí tự
F ( nếu biến có giá trị .false.) .
Giá trị trực tiếp xâu kí tự có thể đưa vào trong phần định dạng (format ) , dùng để giải thích,
ghi chú như ví dụ sau :
integer :: i = 15
write (*,’( “Gia tri cua i = “ , i4 )’) i ! Gia tri cua i = ^^15
5.3.6 Các nội dung định dạng khác ( điều khiển vị trí con trỏ ) .
Mẫu định dạng : nx
Con trỏ sẽ bỏ qua n ô [ kí tự ] khi đọc hoặc con trỏ nhảy về bên phải n ô trống trước khi
xuất dữ liệu . ( Dịch chuyển theo phương ngang ) .
Ví dụ : x = 3.4 và y = 9.45 có kiểu real .
write ( * ,’(f6.2, 3x ,f5.3)’) ^^3.40^^^9.450 ! có 3 ô trống giữa 2 số ( định dạng 3x) .
Xuất dữ liệu xong , con trỏ giữ nguyên vị trí , không xuống dòng
write (*,’(2i3,$)’) m , n ^^9^16_ ! dùng dấu $ trong phần định dạng .
hoặc write (*,’(2i3)’, advance = ‘no’) m , n ! dùng option advance = ‘no’
66
Ghi chú : option advance = ‘no’ không được dùng đi kèm với định dạng fmt = *
print ‘(a,$)’, “ Nhap gia tri cho x = “ Nhap gia tri cho x = _ ! con trỏ chờ nhập
read*, x ! dữ liệu trên cùng dòng .
+ Dấu điều khiển xuống dòng / :
Khi gặp dấu / , con trỏ nhảy xuống dòng phía dưới .
write (*,’(i3, /,i4)’) m , n ^^9
^^16
5.4 Ghi /đọc dữ liệu sử dụng tập tin ( file ) .
5.4.1 Giới thiệu chung về tập tin.
Ngôn ngữ Fortran cho phép sử dụng phương tiện tập tin ( file ) còn gọi là tập tin ngoài (
external file ) để lưu trữ dữ liệu trên ổ đĩa cứng , ổ đĩa di động …Chúng ta có thể xuất kết quả tính
toán ra tập tin thay vì xuất ra màn hình , hoặc nhập dữ liệu đầu vào đã được sắp xếp theo một định
dạng xác định từ các tập tin thay vì nhập trực tiếp từ bàn phím . Khi khối lượng dữ liệu xử lý lớn ,
chúng ta phải sử dụng tập tin .
Mỗi tập tin phải có một tên riêng , tên tập tin gồm 2 phần : phần tên và phần mở rộng dùng
để nhận dạng loại tập tin . Ví dụ : toado.dat , solieu.txt , ptbh.f90 , ptbh.exe …Thông thường , các
tập tin chứa dữ liệu dạng text ( gồm các kí tự chữ viết, chữ số , dấu nối …trong bộ mã ASCII )
được tạo bằng các trình soạn thảo văn bản như Notepad hay phần soạn thảo ngay trong môi trường
IDE của các phần mềm biên dịch Fortran , chúng thường có phần mở rộng *.dat , *.txt ...để chúng
ta dễ nhận dạng .
Phân loại tập tin theo kiểu định dạng :
Tập tin thuộc kiểu được định dạng ( formatted ) nếu chứa các dữ liệu dạng text ( có thể mở
ra xem bằng Notepad ) , trái lại tập tin thuộc kiểu không được định dạng ( unformatted ) nếu chứa
các dữ liệu được ghi ở dạng nhị phân ( mã máy ) .
Phân loại tập tin theo cách truy cập dữ liệu :
Tập tin thuộc loại truy cập tuần tự ( sequential ) : để truy cập nội dung một mẫu ghi
(record) trên tập tin , chương trình sẽ phải đọc tất cả các mẫu ghi ở phía trước mẫu ghi này . Các
mẫu ghi có thể có kích thước khác nhau .
Tập tin thuộc loại truy cập trực tiếp ( direct ) : các mẫu ghi trên tập tin có kích thước
giống nhau ( ví dụ 4 byte cho một mẫu ghi kiểu real ) , được đánh số thứ tự và mẫu ghi được truy
cập trực tiếp thông qua số thứ tự của mẫu ghi .
5.4.2 Ví dụ về dữ liệu dạng tập tin.
Chúng ta xem và phân tích chương trình như sau :
program main
implicit none
real :: x,y,z
! Mo tap tin , ghi du lieu va dong tap tin .
open (10, file = "d:\work\file01.dat") ! mở file
write (10,'(3f5.2)') 1.2 , 2.3 , 3.4 ! ghi 3 số thực có định dạng vào file
close (10) ! đóng file
! Mo, doc du lieu va dong tap tin .
open (10, file = "d:\work\file01.dat", status = “old”) ! mở file đã có
read (10,*) x,y,z ! đọc từ file và gán cho biến .
close (10) ! đóng file
! Xuat du lieu ra man hinh de kiem tra
print '(3f5.2)',x,y,z
read*
end program main
67
Lưu ý quan trọng : Trước khi ghi dữ liệu lên tập tin hoặc đọc dữ liệu từ tập tin , tập tin này
phải được mở ra bằng lệnh open và được gán ( kết nối ) với một đơn vị logic ( tương tự với khái
niệm biến kiểu file trong một số ngôn ngữ lập trình ) thông qua một số nguyên dương do chúng ta
tự đặt ( từ 1 đến 2 147 483 647 ) . Con số này gắn liền với tập tin khi tập tin được mở và được xem
như số của kênh truyền dữ liệu dành riêng cho một tập tin cụ thể . Thông thường kênh số 5 được
dành cho bàn phím , kênh số 6 dành cho màn hình và số 0 dành cho hệ thống xử lý . Sau khi mở
tập , các lệnh write ( ghi ) / read ( đọc ) / close ( đóng tập ) tiếp theo phải được ghi rõ số của kênh
truyền dữ liệu tương ứng với tập tin mà chúng ta muốn truy cập . Số này phải là duy nhất cho một
tập tin và các tập tin khác nhau sẽ kết nối với các đơn vị logic khác nhau . Trong ví dụ trên , chúng
ta gán unit = 10 cho đơn vị logic hay kênh truyền dữ liệu kết nối với tập tin trên ổ đĩa d , thư mục
work “ d :\work\file01.dat “ ( ghi đầy đủ cả đường dẫn ) .
Các lệnh liên quan đến tập tin của chương trình trên :
Lệnh mở tập tin , dạng rút gọn : open (10, file = "d:\work\file01.dat")
hoặc dạng có ghi các tham số thích hợp :
open (unit = 10, file = "d:\work\file01.dat", status = “unknown”)
+ unit = số của đơn vị logic liên kết ( số của kênh truyền dữ liệu ) do chúng ta tự đặt và
tránh các số 0, 5, 6 khi sử dụng tập tin ngoài .
+ file = “ tên tập tin .” ( ghi đầy đủ cả đường dẫn nếu cần ) .
+ status = “----“ ( tình trạng của tập tin ) có thể có các giá trị sau :
“ unknown “ : đây là giá trị mặc định ( default ) , tình trạng của tập tin phụ thuộc vào trình
biên dịch . Nếu chúng ta định mở một tập tin chưa có trên đĩa để ghi dữ liệu , thì hệ thống sẽ tự
động tạo ra một tập tin mới . Nếu tập tin đã có thì nội dung mới sẽ được ghi đè lên tập tin ( nội
dung cũ bị mất ) .
“old” : tập tin bắt buộc phải có trên đĩa . Nếu không có , chương trình sẽ báo lỗi . Thường
sử dụng cho việc đọc tập tin đã có trên đĩa .
“new” : tập tin bắt buộc phải chưa có trên đĩa . Thường sử dụng cho việc ghi / xuất dữ liệu
ra một tập tin hoàn toàn mới .
“replace” : nếu tập tin đã có , nó sẽ bị xóa và một tập tin cùng tên sẽ được tạo ra . Nếu tập
tin chưa có , một tập mới sẽ được tạo ra .
Ghi chú : Lệnh open tổng quát có rất nhiều tham số đi kèm , chúng ta sẽ tìm hiểu dần ở các
phần tiếp theo .
Lệnh ghi dữ liệu lên tập tin ( k là số của kênh truyền dữ liệu ) :
write ( k , * ) < danh sách dữ liệu . > ! định dạng tự động .
write ( k , ‘(phần định dạng )’ ) < danh sách dữ liệu . > ! dữ liệu được ghi
! theo định dạng được mô tả trong phần định dạng .
Lệnh đọc dữ liệu từ tập tin ( k là số của kênh truyền ) và gán cho các biến :
read ( k , * ) < danh sách các biến . > ! định dạng tự động .
read ( k , ‘(phần định dạng )’ ) < danh sách các biến . > ! dữ liệu được đọc
! theo định dạng được mô tả trong phần định dạng .
Lưu ý : danh sách các biến phải tương ứng với dữ liệu sắp xếp trên tập tin.
Lệnh đóng tập tin : sau khi ghi / đọc dữ liệu xong , tập tin phải được đóng lại bằng lệnh
close (k) để bảo vệ dữ liệu .
Như vậy , chương trình ví dụ trên đầu tiên sẽ mở một tập tin trên đĩa có tên kèm theo đường
dẫn là d :\work\file01.dat và gán số 10 cho tập tin này là số của đơn vị logic liên kết ( số của kênh
truyền dữ liệu ) . Tập tin này có thể chưa có hoặc đã có trên đĩa ( xem phần status = “unknown” ở
68
trên ) . Lệnh write tiếp theo sẽ ghi 03 số thực có định dạng f5.2 vào tập ( được ghi trên một dòng
) , sau đó là lệnh đóng tập close (10) , kết thúc phần ghi lên tập tin .
Lệnh open thứ hai sẽ mở lại tập tin nói trên ( lúc này tham số status có thể gán là old vì tập
tin đã có ) , chương trình đọc dữ liệu từ tập tin này , gán cho các biến thực x, y, z và đóng tập ,
kết thúc phần đọc . Lệnh print* xuất giá trị các biến x,y,z từ bộ nhớ ra màn hình để kiểm tra .
5.4.4 Các tham số đi kèm với lệnh mở tập tin open .
Status = Tình trạng Unknown (mặc định ) ; Old ( đã có ) ; New ( chưa có ) ; Replace
tập tin. (thay thế ) .
Action = Thao tác với ReadWrite (mặc định) ; Read ( chỉ để đọc ) ; Write ( chỉ để ghi )
tập tin .
Form = Kiểu tập tin Formatted (mặc định , file dạng text ) ; Unformatted ( file dạng nhị
phân ) .
Access = Kiểu truy Sequential (mặc định , truy cập tuần tự ) ; Direct ( truy cập trực tiếp
cập . )
Recl = n n : kích Bắt buộc phải có tham số này nếu kiểu truy cập là Direct .
thước mẫu
ghi tính
bằng byte .
Position = Vị trí con trỏ Dùng với kiểu truy cập tuần tự .
file . Asis ( mặc định ) : mở file và đứng ở vị trí mặc định sau khi file được
kết nối .
Append : tiếp tục ở sau mẫu ghi cuối cùng . ( Nối đuôi ) .
Rewind : trở về đầu tập tin.
Iostat =ierr Quản lý lỗi Biến nguyên ierr có giá trị = 0 nếu mở tập tin không có lỗi , có giá
khi mở file. trị 0 nếu mở tập tin bị lỗi. Phải khai báo ierr ở đầu chương trình .
Err = nhãn Quản lý lỗi Nếu có lỗi khi mở tập tin, chương trình nhảy đến dòng lệnh có gắn
khi mở file. nhãn tương ứng với tham số err .
69
Ví dụ về quản lý lỗi khi mở tập tin .
implicit none
integer :: ierr
…
open (unit = 20 , file = “ data02.dat “, iostat = ierr )
if (ierr /= 0) then
print* , “ Co loi khi mo file “ ! câu thông báo lỗi khi mở file .
read* ! dung lai de doc thong tin .
stop ! ket thuc chuong trinh
end if
….
5.4.5 Đọc dữ liệu từ tập tin .
Mẫu ví dụ về câu lệnh đọc dữ liệu từ tập tin và gán cho biến :
read (10,*) x ! đọc file liên kết với kênh số 10 với định dạng tự động
! và gán cho biến x .
read (10,*) x,y,z read ( unit = 10 , fmt = * ) x,y,z
! đọc file liên kết với kênh số 10 với định dạng tự động và gán cho các biến x,y,z
read (unit = 20, fmt = ‘(a)’ ) dong ! biến dong có kiểu character .
! Đọc file liên kết với kênh số 20 với định dạng là xâu kí tự ( file gồm các dòng text ).
! Đọc từng dòng của file xong gán cho biến dong ( có len khai báo phù hợp với chiều dài
dòng text trên file )
Tham số unit có thể được gán :
+ Số của kênh truyền dữ liệu kết nối với tập tin cần truy cập.
+ Dấu * , khi đọc dữ liệu từ bàn phím .
+ Xâu kí tự , dùng để chuyển đổi nội dung xâu kí tự thành dữ liệu số : read (xkt ,* )
x ( xem thêm phần tập tin trong ( internal file ) ) .
Tham số format fmt có thể được gán :
+ Dấu * : định dạng tự động .
+ Xâu kí tự định dạng ‘(phần định dạng )’, quy định rõ phần định dạng được sắp xếp
trong tập tin .
Các tùy chọn về quản lý lỗi khi đọc dữ liệu từ tập tin . Các mẫu như sau :
read (10,*, iostat = ierr ) x ! khi có lỗi , biến nguyên ierr có giá trị 0 .
read (10,*, err = 100) x ! chương trình nhảy đến câu lệnh có nhãn 100 khi có lỗi.
read (10,*, end = 200) x ! chương trình nhảy đến câu lệnh có nhãn 200
! khi đọc đến cuối file ( hết dữ liệu để đọc ) .
70
Tham số format fmt có thể được gán :
+ Dấu * : ghi theo định dạng tự động .
+ Xâu kí tự định dạng ‘(phần định dạng )’, quy định rõ phần định dạng khi ghi dữ
liệu vào tập tin .
Lưu ý : đối với file dạng text ( formatted ) mỗi lệnh write tương ứng với việc ghi dữ liệu ra
một dòng xong con trỏ xuống dòng kế tiếp . Trường hợp viết xong không muốn xuống dòng , trong
phần định dạng phải thêm kí hiệu $ hoặc đưa vào option advance = “no” . Ví dụ :
write (* , fmt = ‘(2f10.2,$ )’) x,y
write (* , fmt = ‘(2f10.2)’, advance = “no” ) x,y
Tùy chọn advance = “no” không được dùng chung với fmt = *
Có thể đưa dòng text ( giá trị trực tiếp xâu kí tự ) vào phần định dạng của lệnh write :
nmax = 100
write (*,’(“nmax = “,i4)’) nmax ! nmax = ^100
Hoặc để phần định dạng riêng ở câu lệnh format liên kết :
write (*,300) nmax ! phần định dạng nằm ở câu lệnh có nhãn 300
300 format (“namx = “, i4)
5.4.7 Lệnh rewind (k) hoặc rewind ( unit = k) .
Có tác dụng đưa con trỏ file về đầu tập tin để có thể đọc/ ghi dữ liệu từ vị trí đầu tiên của
tập tin . Có thể đặt lệnh này ở một dòng riêng hoặc đưa vào option position = “rewind” của lệnh
open . Ví dụ :
…
open (10, file = “ solieu01.txt” , status = “old” )
rewind (10) ! đưa con trỏ file về vị trí đầu tiên của tập tin . Đọc dữ liệu từ đầu.
read (10,’(3f5.2)’ ) x,y,z
…
Lưu ý : khi muốn ghi tiếp tục vào một tập tin đã có , chúng ta sử dụng option position = “
append “ trong lệnh open ( đưa con trỏ file về cuối tập tin ) .
Ví dụ : Đọc từ biến xâu kí tự ra dữ liệu số :
…
character ( len = 9) :: day = ‘123456789’
integer :: n
real :: x
…
read (day,’(i3,f6.2)’) n,x
write (*,’(“n = “, i3 ,” x = “, f7.2)’) n, x n = 123 x = 4567.89
Ví dụ về đọc file theo từng dòng , xong viết ra màn hình
program main
implicit none
integer :: ierr
character (len = 50) :: dong ! co toi da 50 ki tu
open (10,file ='d:\danhsach.txt', status = 'old', &
*************************
& iostat = ierr , err = 100)
Dai Hoc Bach Khoa TP.HCM
if (ierr /= 0 ) then Dai Hoc Kien Truc TP.HCM
print*,' Co loi khi mo file - dung tham so iostat ' Dai hoc Kinh Te TP.HCM
read* Dai hoc Tong Hop TP.HCM
stop ! Ket thuc chuong trinh Trung Tam Ngoai Ngu DHSP
end if Trung Tam day nghe Q.5
************************
do
read (10,'(a)',end = 30) dong ! khi doc het file nhay file d:\danhsach.txt
! den dong co nhan 30
write (*,'(a)') dong ! viet ra man hinh theo tung dong.
end do
30 close (10)
read*
stop
100 print*,' Co loi khi mo file - dung tham so err '
read*
end program main
72
Lưu ý : vòng lặp do sẽ đọc từng dòng của tập tin d :\danhsach.txt ( được soạn sẵn bằng
Notepad ) và xuất ra màn hình . Khi đến cuối file ( hết dữ liệu để đọc ) chương trình sẽ nhảy đến
câu lệnh có nhãn 30 và đóng tập tin lại , kết thúc chương trình .
Nếu tập tin không có trên đĩa , chương trình sẽ xuất ra dòng thông báo lỗi :
Co loi khi mo file - dung tham so err
5.4.10 Tập tin không được định dạng ( unformatted file )
Chúng ta có thể ghi / đọc dữ liệu sử dụng tập tin kiểu không được định dạng ( unformatted
) , dữ liệu được ghi ở dạng mã máy ( hệ nhị phân ) .
Trong lệnh open phải đưa vào option form = “ unformatted “.
Ví dụ :
open ( 10, file = “ketqua.dat “ , form = “unformatted “ )
…
write (10) x,y
…
read (10) x,y
close (10)
Bạn đọc phân tích và chạy chương trình sau về ghi và đọc dữ liệu từ file kiểu truy cập trực
tiếp , mỗi mẫu ghi là một số thực real ( 4 byte ) :
program main
real :: x
integer :: i
open (10,file = "ketqua.dat",access = "direct", recl = 4 , form = "unformatted")
do i =1,20
x=i
write (10,rec = i) x ! ghi 20 so thuc vao file
end do
close (10)
open (10,file = "ketqua.dat",access = "direct", recl =4 , form = "unformatted")
do i = 5,10
read (10,rec = i) x ! doc tu mau ghi so 5 den so 10
73
print '(f5.1,$)', x
end do 5.0 6.0 7.0 8.0 9.0 10.0
close (10)
read* Kết quả ghi ra màn hình .
end
5.4.12 Sử dụng namelist để chứa dữ liệu .
Chúng ta có thể tạo ( khai báo ) một danh sách các biến trong một namelist , được đặt một
tên , và gán các giá trị cho các biến này :
namelist / my_NML/ x,y,m,n ! x,y biến real và m,n biến integer
my_NML là tên của namelist do chúng ta tự đặt .
Sau khi liên kết với một tập tin đã mở , các lệnh write ( k, nml = my_NML) hoặc read (k,
nml = my_NML ) sử dụng tham số nml = < tên namelist> , có thể ghi danh sách này lên tập tin
hoặc đọc từ tập tin đã có danh sách này . Ví dụ :
program main
real :: x,y
integer :: m,n
namelist /my_NML / x,y,m,n ! tạo danh sách có tên là my_NML chứa 4 biến
x =1.23 ; y = 2.34 ; m = 5 ; n = 10 ! gán giá trị cho các biến
open (10,file = "vidu05.dat")
write (10, nml = my_NML) ! ghi namelist có tên my_NML vào tập tin
close (10)
open (10,file = "vidu05.dat")
read (10,nml = my_NML) ! Đọc namelist có tên my_NML từ tập tin vào bộ nhớ
close (10)
print '(2f6.2,2i4)' , x,y,m,n ! viết ra màn hình 1.23 2.34 5 10
read*
end
Hình 5.2 : Nội dung namelist my_NML được ghi vào tập vidu05
*****
75
Chương 6 : CẤU TRÚC DỮ LIỆU KIỂU MẢNG ( ARRAY )
76
real ,dimension (1:3,1:2 ) :: matran real ,dimension ( 3, 2 ) :: matran
Chỉ số hàng 1 : 3 được khai báo trước chỉ số cột 1 : 2
Dimension 2
+ Trong khai báo mảng , kiểu của các phần tử có thể là số nguyên, số thực, số phức , kiểu
logical , xâu kí tự , dữ liệu dạng hỗn hợp ( bản ghi ) .
+ Mỗi phần tử của mảng như vecto (2) , matran (2,2) trong các ví dụ trên được xử lý như
một biến đơn có kiểu real trong các biểu thức , phép gán .
+ Các chỉ số có thể viết ở dạng biểu thức nguyên , có giá trị phù hợp với phạm vi của chỉ số
, ví dụ vecto ( i ) ; matran (2+i , j*3 )
+ Khi có ma trận vuông cấp 3 : matran (1:3,1:3) , chúng ta có thể trích ra các vectơ hàng ,
vectơ cột , ma trận con dùng cách viết sử dụng chỉ số như sau :
matran (1, :) vectơ hàng thứ 1 matran (: , 2 ) vectơ cột thứ 2
matran (1:2, 1:2) ma trận con 2 x 2 , góc trái phía trên .
Các ma trận A,B,C khai báo như sau có cùng shape là ma trận 5 hàng , 3 cột ; nhưng chỉ
số có các cận khác nhau.
real, dimension (-4:0 , 0:2) :: A
real , dimension (5,3) :: B real , dimension (1: 5,1: 3) :: B
real, dimension (3:7 , 2:4) :: C
77
B(1,1) B(1,3)
C(3,2) C(3,4)
A(-4,0) A(-4,2)
A(-2,1)
A(0,0) A(0,2)
B(5,1) B(5,3)
C(7,2) C(7,4)
Hình 6.1 : Vị trí tương ứng các phần tử của ma trận A,B,C cùng dạng
6.4 Khai báo mảng theo dạng cấp phát động ( dynamic array )
Chúng ta khai báo một vectơ trong R3 và một ma trận vuông cấp 3 theo dạng cấp phát động
( cấp phát vùng nhớ cho mảng khi chạy chương trình ) như sau :
integer :: n
real,dimension (:) ,allocatable :: vecto ! khai báo thuộc tính allocatable cho vecto
real,dimension (: , :) ,allocatable :: matran ! và matran
n=3
78
allocate ( vecto (1: n) , matran (1: n, 1: n) ) ! chỉ định việc cấp phát vùng nhớ .
if (allocated (vecto)) print *,” vecto da duoc cap “ ! test việc cấp phát .
if (allocated (matran)) print *,” matran da duoc cap “
…
deallocate (vecto, matran ) ! thu hồi lại vùng nhớ .
program main
implicit none
integer :: n
real,dimension (:), allocatable :: vecto
real,dimension (:,:), allocatable :: matran
n=3
allocate (vecto (1:n), matran (1:n,1:n))
if (allocated (vecto)) print*,"vecto Yes"
if (allocated (matran)) print*,"matran Yes" vecto Yes
vecto = (/1.,2.,3./) ! gan gia tri matran Yes
print*,vecto 1. 2. 3.
deallocate (vecto,matran) Da thu hoi vung nho !
print*," Da thu hoi vung nho ! " vecto No
if (allocated (vecto)) then
print*," vecto Yes " Kết quả .
else
print *, " vecto No "
end if
read*
end
Trong phần khai báo biến mảng cấp phát động , chúng ta sử dụng thuộc tính allocatable (
từ khóa ) ; các chỉ số của dimension được để trống .
Chỉ thị allocate ( ) thực hiện việc cấp phát vùng nhớ cho các biến mảng và chỉ thị deallocate
( ) thu hồi lại các vùng nhớ đã cấp phát .
Hàm allocated ( ) có kiểu logical , dùng để test xem biến mảng có được cấp phát vùng nhớ
hay chưa ?
Khai báo mảng cấp phát động có kèm theo tham số stat để quản lý lỗi :
integer :: n = 3, ierr
real,dimension (:), allocatable :: vecto
allocate ( vecto (1:n), stat = ierr )
if (ierr /= 0) print*,” Co loi khi cap phat .”
…
Nếu bộ nhớ không đủ hay mảng đã được cấp phát rồi , ierr sẽ có giá trị 0 .
Chúng ta xem chương trình sau : cấp phát vùng nhớ cho một biến mảng một chiều , kiểu số
thực chính xác kép , có số phần tử tăng dần cho đến khi bộ nhớ không đủ để cấp phát ( biến ierr
0) . Qua đó biết được vùng nhớ khả dụng của bộ nhớ .
program testmemory
implicit none
integer, parameter :: r =8
integer :: ierr,n
real (kind = r ),dimension (:),allocatable :: vecto
n= 0 ; ierr = 0
do while (ierr == 0)
n = n + 1024*1204
79
if (allocated (vecto)) deallocate (vecto)
allocate ( vecto (1:n) , stat = ierr )
end do
n = n - 1024*1024
print*, " Vung nho kha dung (MB) : ", (n*r)/(1024*1024)
read*
end
Kết quả : Vung nho kha dung (MB) : 1854
6.5 Nhập giá trị cho các phần tử của mảng .
Nhập ‘ thủ công ‘ cho từng phần tử của mảng .
Gán giá trị cho ma trận 3 hàng 2 cột các số thực :
1. 4.
real, dimension (1:3, 1:2) :: matran
matran 2. 5.
3. 6.
matran (1,1) = 1. ; matran (2,1) = 2. ; matran (3,1) = 3.
matran (1,2) = 4. ; matran (2,2) = 5. ; matran (3,2) = 6.
Nhập các phần tử liên tiếp của mảng khi chạy chương trình bằng chỉ thị read*, các phần
tử được liệt kê cách nhau dấu phẩy hoặc khoảng trống ( dùng phím cách space ) .
real , dimension (1:3,1:2) :: a ! Ma trận 3 hàng, hai cột
real, dimension (3) :: v ! Vectơ trong R3
print*,' Vao cac phan tu cua ma tran a (3,2) : ' ; read*,a
! Vào 1.,2.,3.,4.,5.,6. hoặc 1. 2. 3. 4. 5. 6. xong Enter .
print*,' Vao cac phan tu cua vecto v (3) : ' ; read*,v
! Vào 1.,2.,3. hoặc 1. 2. 3. xong Enter .
Nhập qua một công thức ( nếu thiết lập được công thức ) cho từng phần tử của mảng .
real, dimension (1:3, 1:2) :: matran
integer :: i,j
…
do j = 1,2
do i = 1,3
matran ( i,j ) = i + (j-1) *3 ! dùng vòng lặp do
end do
end do
Nhập qua một file :
real, dimension (1:3, 1:2) :: matran
integer :: i,j
..
open ( 10, file = “ solieu.dat “ )
do i = 1,3
read ( 10 ,* ) matran (i,1) , matran (i,2) ! đọc theo từng hàng .
end do
close (10) 1. 4.
Hoặc : 2. 5.
real, dimension (1:3, 1:2) :: matran 3. 6.
integer :: i,j
..
open ( 10, file = “ solieu.dat “ ) file : solieu.dat
do i = 1,3
read ( 10 ,* ) (matran (i,j) , j =1,2) ! sử dụng vòng lặp ngầm cho biến j
80
end do
close (10)
Nhận xét : các số liệu được đọc theo từng hàng ứng với mỗi chu trình của vòng do .
Trong lệnh read (10,* ) ( matran (i,j), j = 1,2) , ứng với mỗi giá trị của biến đếm i ,biến đếm j
bên trong lấy các giá trị từ 1 đến 2 , số phần tử của mảng được đọc là 2 cho mỗi lần đọc.
6.6 Sử dụng công cụ array constructor để nhập giá trị cho mảng .
Chúng ta xem ví dụ sau về việc nhập giá trị cho một vectơ có 6 phần tử .
integer :: i
real, dimension (1: 6) :: vecto
vecto = (/2.,4.,6.,8.,10.,12./) ! nhập các phần tử 2. 4. 6. 8. 10. 12. cho vecto .
Lưu ý : các giá trị được viết giữa các dấu (/ /) ; hai dấu (/ hoặc /) phải được viết liền
nhau không có khoảng trống giữa ( và / . Các phần tử liệt kê cách nhau dấu phẩy .
Dòng lệnh gán như trên còn có thể được viết dưới dạng công thức có kèm theo biến vòng
lặp hoặc kết hợp liệt kê với công thức :
vecto = (/ (2.*i, i =1,6) /) vecto = (/2.,4.,(2.*i , i = 3,5) ,12. /)
Nếu vecto = (/ (1.*i , i = 6,1,-1) /) vecto có các phần tử 6. 5. 4. 3. 2. 1.
Việc gán các giá trị cho biến mảng vectơ có thể thực hiện ngay trong phần khai báo :
real, dimension (1:3) :: vecto_a = (/2.,4.,6. /)
Đối với mảng nhiều chiều như ma trận ,chúng ta sử dụng hàm reshape như sau :
real, dimension (1:3,1:2) :: matran
matran = reshape ( (/1.,2.,3.,4.,5.,6./) , (/3,2/) )
matran = reshape ( (/ (1.*i, i=1,6 ) /) , (/3,2/) )
+ Đối số thứ nhất của hàm reshape là
danh sách giá trị các phần tử của ma trận , được viết
theo quy luật của chỉ số ( điền theo cột ).
Điền giá trị theo cột trước
+ Đối số thứ hai là dạng ( shape ) của ma trận : 3 hàng 2 cột.
Chúng ta có thể thay đổi thứ tự việc đưa giá trị vào ma trận :
real, dimension (1:3,1:2) :: matran
matran = reshape ( (/1.,2.,3.,4.,5.,6./) , (/3,2/) , order = (/2,1/) )
Tham số order = (/2,1/) chỉ định chỉ số thứ 2 ( cột ) sẽ thay đổi trước , thứ tự điền như sau
: matran (1,1) , matran (1,2) , matran (2,1). matran (2,2) , matran (3,1), matran (3,2)
Trường hợp dãy danh sách không đầy đủ để điền vào ma trận , tham số pad = (/…/) sẽ
cho biết quy luật điền các giá trị còn thiếu .
real, dimension (1:3,1:2) :: matran
matran = reshape ( (/1.,2.,3./) , (/3,2/) , order = (/2,1/) , pad = (/0./) )
Các số thực , số nguyên có thể kết hợp với từng phần tử của mảng có dạng bất kỳ ( có
cùng kiểu dữ liệu với số hoặc tự động chuyển đổi kiểu theo quy luật của Fortran ) qua các phép
toán số học : +, - , *, / , ** . Các biến mảng được xem như một toán hạng trong biểu thức, các giá
trị số cũng xem như một mảng cùng dạng và phép tính được thực hiện trên tất cả các phần tử của
mảng.
program arraytest
implicit none
real , dimension (1:3) :: vecto = (/1.,2.,3./)
real , dimension (1:3,1:2) :: matran,matran1 3. 4. 5.
matran = reshape((/1.,2.,3.,4.,5.,6./), (/3,2/)) 2. 4. 6.
print*,vecto + 2. 0.5 1. 1.5
print*,vecto * 2. ! giống như 2*vecto 1. 4. 9.
print*,vecto / 2.
print*,vecto ** 2 3. 4. 5. 6. 7. 8.
print* 2. 4. 6. 8. 10. 12.
print*,matran + 2. 0.5 1. 1.5 2. 2.5 3.
print*,matran * 2. 1. 4. 9. 16. 25. 36.
print*,matran / 2. 36.
matran1 = matran **2
print*,matran1
print*,matran1 (3,2)
read*
end
82
Các biến mảng ( vectơ, matrận ) cùng dạng có thể xử lý như một toán hạng , tính toán
bình thường qua các phép toán số học . Việc tính toán được thực hiện trên tất cả các phần tử tương
ứng ( cùng vị trí ) của mảng . Kết quả trả về là một mảng có cùng dạng .
real,dimension (1:3) :: vecto1,vecto2, vecto
real , dimension (1:3,1:2) :: matran
matran = reshape((/1.,2.,3.,4.,5.,6./), (/3,2/))
vecto1 = (/1.,2.,3./)
vecto2 = (/2.,4.,6./)
vecto = vecto1 + vecto2 ! cộng 2 véctơ
3. 6. 9.
print*,vecto 8. 16. 24.
vecto = 2.* vecto1 + 3.* vecto2 ! tổ hợp tuyến tính -1. -2. -3.
print*,vecto 2. 8. 18.
vecto = vecto1 - vecto2 ! trừ 2 véctơ 2. 2. 2.
print*,vecto 1. 16. 729.
vecto = vecto1 * vecto2 ! nhân 2 véctơ 1. 4. 9. 16. 25. 36.
print*,vecto 1. 1. 1. 1. 1. 1.
print* , vecto2 / vecto1 ! chia 2 véctơ
print*, vecto1**vecto2 ! lũy thừa
print*, matran * matran ! nhân từng phần tử tương ứng của ma trận .
! không phải là phép nhân ma trận trong đại số .
print*, matran / matran ! chia từng phần tử tương ứng của ma trận .
read*
end
6.8 Mảng và cách sử dụng chỉ số để truy cập các bộ phận của mảng .
Để truy cập một bộ phận ( phần tử, hàng, cột, mảng con …) của mảng , chúng ta sử dụng
tên mảng kèm theo các chỉ số theo dạng :
< chỉ số đầu > : <chỉ số cuối > : < số gia > cho mỗi chiều của mảng .
Ví dụ : real , dimension (1:3) : vecto
print *, vecto (1:3: 2) ! sẽ xuất ra các phần tử : vecto (1) , vecto (3)
+ Trường hợp chỉ số đầu không ghi : lấy giá trị cận dưới.
+ Trường hợp chỉ số cuối không ghi : lấy giá trị cận trên .
+ Trường hợp số gia không ghi : lấy giá trị = 1
+ Số gia có thể là số âm . 1. 3.
real , dimension (1:3) :: vecto = (/1.,2.,3./) 4. 5. 6.
real , dimension (1:3,1:3) :: matran 3. 6. 9.
matran = reshape((/1.,2.,3.,4.,5.,6.,7.,8.,9./), (/3,3/)) 1. 3. 4. 6. 7. 9.
print*, vecto (1:3:2) ! phan tu 1 va 3 3. 1. 6. 4. 9. 7.
print*,matran (:,2) ! cot thu 2
1. 2. 4. 5.
print*,matran (3,:) ! hang thu 3
print*,matran (1:3:2,1:3) ! hang 1,3
print*,matran (3:1:-2,1:3) ! hang 3,1 Kết quả .
print*,matran (:2,:2) ! ma tran 2 x 2 goc tren , ben trai
Ghi chú : các đối tượng như matran (:,1) vectơ cột 1 ; matran (2,:) vectơ hàng 2 trở thành
các mảng một chiều và được xử lý như vectơ trong các biểu thức tính toán.
Chúng ta có thể khai báo một mảng các số nguyên làm danh sách các chỉ số .
program array
implicit none
integer :: i
integer,dimension (1:3) :: chiso = (/1,3,5/)
integer,dimension (1:5) :: chuyenvi = (/2,3,4,5,1/)
83
real,dimension (1:5,1:5) :: matran ! Kết quả
matran = reshape ( (/ (i*1.,i=1,25) /), (/5,5/) )
print*, matran (chiso,2) ! xem hinh 6.2 6. 8. 10.
print*, matran (chiso,chiso) ! xem hình 6.3 1. 3. 5. 11. 13. 15. 21. 23. 25.
print*
print*, matran (chuyenvi,2) ! xem hình 6.4 7. 8. 9. 10. 6.
read* ; end
Các biểu thức và phép gán như sau là hợp lệ , lưu ý các mảng phải có dạng giống nhau
trong các biểu thức hoặc phép gán .
matran = matran1 * matran 2 ! nhân từng cặp phần tử có vị trí giống nhau
! kết quả là một ma trận có cùng dạng .
! không phải là phép nhân ma trận trong đại số.
Kết quả : matran (1,1) = matran1(1,1) * matran2(-1,0)
matran (2,1) = matran1(2,1) * matran2(0,0) …
matran = sin (matran1) ! lấy sin trên tất cả các phần tử của matran1.
! xem biến matran1 là đối số của hàm sin ( ).
Kết quả : matran (1,1) = sin (matran1(1,1) )
matran (2,1) = sin (matran1(2,1) ) …
Tương tự chúng ta có thể viết : cos (matran) ; exp (matran ) Các đối số của hàm còn
có thể là các mảng .
matran1 ( : ,1) = sqrt (vecto (1:3)) ! tính toán như các vectơ cùng dạng .
matran1 ( : ,1) = matran2 (0, :) + vecto ( : ) ! các toán hạng có cùng dạng .
Hình 6.5 : Ví dụ mảng được sử dụng làm đối số của hàm exp ( ) .
84
6.10 Lệnh Where .
Trong phép gán cho biến mảng , chúng ta có thể sử dụng lệnh where đi kèm với một
điều kiện logic, chỉ phần tử nào của mảng thỏa điều kiện , phép gán mới được thực hiện :
real, dimension (1:2,1:2) :: matran
matran = reshape ((/100.,0.01 , 10. , 0./), (/2,2/) )
where (matran > 0.) matran = log10 (matran) ! phép gán áp dụng
! cho các phần tử > 0.
print*, matran
100. 10. 2. 1
0.01 0. 2. 0.
6.11 Một số hàm chuẩn ( intrinsic functions ) có đối số là mảng .
Hàm lấy tổng và tích các phần tử của một ma trận , một vectơ.
1. 4. 7. print*, sum (matran ) 45. ! tổng các phần tử của ma trận .
print*, sum (matran, dim = 1) 6. 15. 24. ! tổng theo từng cột .
matran 2. 5. 8.
3. 6. 9. print*, sum (matran, dim = 2) 12. 15. 18. ! tổng theo từng hàng.
print*, sum (matran (:,2)) 15. ! tổng các phần tử cột thứ 2
print*, sum (matran (3,:)) 18. ! tổng các phần tử hàng thứ 3
print*, sum ( (/ (matran(i,i), i=1,3) /) ) 15. ! tổng các phần tử trên đường chéo chính
print*, product (matran) 362 880. ! tích các phần tử của ma trận .
print*, product (matran, dim = 1) 6. 120. 504. ! tích theo từng cột.
print*, product (matran, dim = 2) 28. 80. 162. ! tích theo từng hàng .
print*, product ( (/ (matran(i,i), i=1,3) /) ) 45. ! tích các phần tử trên đường chéo chính
85
Phép tính véctơ , ma trận .
Tích vô hướng của 2 vectơ . Ví dụ : vecto = (/1.,2.,3./)
print*, dot_product (vecto,vecto) ! (1.)(1.) + (2.)(2.) + (3.)(3.) = 14.
n
Tích hai ma trận A.B = C cij aik bkj
k 1
Điều kiện nhân được : số cột của ma trận A phải bằng số hàng của ma trận B .
1. 4.
1. 3. 5.
A B 2. 5. print *, matmul (A,B) 22. 28. 49. 64.
2. 4. 6. 3. 6.
22. 49.
Tích hai ma trận A.B =
28. 64.
Hàm trả về ma trận chuyển vị ( đổi cột thành hàng, hàng thành cột ) .
(Transpose of matrix ) .
1. 4. 7. 1. 2. 3.
A = 2. 5. 8. A = 4. 5. 6.
T
3. 6. 9. 7. 8. 9.
print*, transpose (matran) 1. 4. 7. 2. 5. 8. 3. 6. 9.
Hàm trả về phần tử có giá trị bé nhất , lớn nhất .
Giá trị bé nhất :
print*,minval (matran) 1.
print*,minval (matran,dim =1) 1. 4. 7. ! min theo từng cột
print*,minval (matran,dim =2) 1. 2. 3. ! min theo từng hàng
Giá trị lớn nhất :
print*,maxval (matran) 9.
print*,maxval (matran,dim =1) 3. 6. 9. ! max theo từng cột
print*,maxval (matran,dim =2) 7. 8. 9. ! max theo từng hàng
+ Trả về giá trị bé nhất có kèm theo điều kiện : Ví dụ :
print*, minval (matran, matran >= 5.) 5.
+ Trả về vị trí ( location ) của phần tử có giá trị bé nhất, lớn nhất :
print *, minloc (matran ) 1 1 ! phần tử bé nhất có vị trí (1,1)
print *, maxloc (matran ) 3 3 ! phần tử lớn nhất có vị trí (3,3)
print *, maxloc (matran , dim = 1) 3 3 3 ! phần tử lớn nhất theo từng cột
! trả về vị trí hàng có phần tử lớn nhất
Các hàm truy vấn có kiểu logical .
all ( điều kiện ): xem xét tất cả các phần tử theo điều kiện ; any (điều kiện ) : xem xét ít
nhất có một phần tử đạt điều kiện .
if (all (matran >= 0.)) print* ,” Tat ca phan tu deu la so duong.”
if (any (matran >= 0.)) print* ,” Co it nhat mot phan tu la so duong.”
print*, all (matran >= 5.,dim = 1) F F T ! chỉ có cột 3 thỏa .
print*, all (matran >= 5.,dim = 2) F F F ! không có hàng nào thỏa.
print*, any (matran >= 5.,dim = 1) F T T ! cột thứ 2,3 thỏa
print*, any (matran >= 5.,dim = 2) T T T ! cả 3 hàng đều thỏa
86
Hàm count ( điều kiện ) , đếm số phần tử của ma trận thỏa điều kiện .
print*, count (matran >= 0.) 9 ! đếm số phần tử của ma trận theo điều kiện 0.
print*, count ( matran >2. .and. matran <6. ) 3
Hàm pack trả về một danh sách các phần tử theo một điều kiện .
1. 4. 7.
print*, pack (matran, matran >= 6.) 6. 7. 8. 9.
matran 2. 5. 8.
3. 6. 9.
Chúng ta xem chương trình chuyển đổi dạng ( shape ) từ ma trận sang vectơ và ngược
lại . Lưu ý : số phần tử của vectơ và ma trận phải bằng nhau .
program changeshape
implicit none
real , dimension (1:4) :: vecto ! vecto co 4 phan tu .
real , dimension (1:2,1:2) :: matran, matran2 ! matran 2 x 2 co 4 phan tu .
matran = reshape ( (/1.,2.,3.,4./), (/2,2/))
vecto = reshape (matran , (/4/)) 1.
print*,vecto ,' ',vecto(3:4) 1. 2. 3. 4. 3. 4.
1. 3. 2.
matran2 = reshape (vecto, (/2,2/))
3.
print*,matran2 , ' ', matran2 (:,1) 1. 2. 3. 4. 1. 2. 2. 4.
read*
end 4.
Hàm trả về các chỉ số cận dưới , cận trên của mảng .
lbound (lower bound ) : cận dưới ; ubound ( upper bound ) : cận trên .
dimension ( lb1:ub1 , lb2:ub2) ! sắp xếp 4 chỉ số cận dưới, cận trên .
program main
implicit none
real , dimension (-1:1,0:2) :: matran ! matran 3 x 3
matran = reshape ( (/1.,2.,3.,4.,5.,6.,7.,8.,9./), (/3,3/))
print*, lbound(matran) -1 0
(1, 0) (1,1) ( 1, 2)
print*, ubound(matran) 1 2
print*,lbound(matran,dim = 1) theo chỉ số thứ 1 -1 (0, 0) (0,1) (0, 2)
(1, 0) (1, 2)
print*,ubound(matran, dim =1) theo chỉ số thứ 1 1 (1,1)
print*,lbound(matran,dim =2) theo chỉ số thứ 2 0 Vị trí các chỉ số .
print*,ubound(matran, dim =2) theo chỉ số thứ 2 2
print*,size(matran) số phần tử của ma trận : 9
print*,size(matran,dim = 1) số phần tử theo chỉ số thứ 1 : 3
print*,size(matran,dim = 2) số phần tử theo chỉ số thứ 2 : 3
print*,shape(matran) dạng 3 3
read*
end
Viết / đọc dữ liệu kiểu mảng một chiều theo từng dòng có định dạng .
Chương trình viết /đọc một mảng 100 số thực theo từng dòng ,sử dụng chỉ thị format , mỗi
dòng có 10 phần tử .
program test100
implicit none
real,dimension (100) :: x , y
integer :: i
87
x = (/ (1.*i ,i = 1,100) /) ! Tạo giá trị mảng x từ 1. đến 100.
open (1,file = 'd:/data05.txt')
write (1,20) x ! ghi vào file theo định dạng được quy định bởi câu lệnh format có nhãn 20
20 format (10f5.0) ! viết 10 phần tử trên một dòng với định dạng f5.0
close (1)
open (1,file = 'd:/data05.txt')
read (1,20) y ! đọc giá trị từ file và gán cho mảng y hoặc dùng lệnh read (1,*) y
close (1)
write(*,20) y ! viết mảng y ra màn hình theo format (10f5.0) để kiểm tra.
read*
end
Hình 6.6 : Kết quả xuất dữ liệu ra màn hình theo từng dòng 10 phần tử .
+ Dựa vào chương trình trên , bạn đọc tự viết chương trình chuyển đổi dãy nhiệt độ Celsius
, từ 1 đến 1000 C, thành dãy nhiệt độ Fahrenheit tương ứng .
6.12.2 Chương trình sắp xếp một dãy số thực thành một dãy số có giá trị từ lớn đến nhỏ. Ví
dụ sắp xếp điểm trung bình của n học sinh .
88
program sort
implicit none
integer ::i,j
integer , parameter :: n = 10
real , dimension (n) :: a 7.8 2.5 8.9 3.6 5.7 6.9 7.3 3.5 4.9 9.2
real :: tgian 9.2 8.9 7.8 7.3 6.9 5.7 4.9 3.6 3.5 2.5
a = (/ 7.8,2.5,8.9,3.6,5.7,6.9,7.3,3.5,4.9,9.2 /)
print*,a ! Xuat day a chua sap xep .
do i = 1,(n-1)
do j = (i+1),n
if (a(i) < a(j)) then
tgian = a(i) ; a(i) = a(j); a(j) = tgian ! Doi cho hai phan tu .
end if
end do
end do
print*,a ! Xuat day a da sap xep tu lon den nho.
read*
end program sort
+ Dựa vào chương trình trên , bạn đọc viết một chương trình sắp xếp điểm từ thấp đến cao
, đồng thời xuất ra điểm số cao nhất, thấp nhất ( sử dụng các hàm maxval , minval ) , số
học sinh đạt điểm trung bình ( 5. và ≤ 7.) , khá ( >7. và <9. ), số học sinh giỏi ( 9.) ( sử
dụng hàm count có kèm điều kiện ) .
6.12.3 Một công ty sản xuất xe gắn máy tại 03 nhà máy , số xe sản xuất của từng nhà máy
theo từng quý được cho trong bảng sau . Đơn giá mỗi xe là 20 triệu đồng.
Đơn vị : xe
Quý I Quý II Quý III Quý IV
Nhà máy 1 550 1270 1550 2000
Nhà máy 2 380 850 1020 1200
Nhà máy 3 650 1100 1650 1900
program sanxuat
implicit none
integer ,dimension ( 3,4) :: sp
integer , parameter :: gia = 20 ! don gia mot xe , don vi trieu dong
sp = reshape ( (/550,380,650,1270,850,1100,1550,1020,1650 &
,2000,1200,1900 /), (/3,4 /))
print*,'Tong so xe sx trong nam : ',sum(sp)
print*,'So xe sx theo tung quy : ',sum (sp,dim = 1)
print*,'So xe sx trong nam cua tung nha may : ',sum (sp,dim = 2)
print*,'Gia tri san xuat trong nam ( trieu dong ) : ',sum (20*sp)
read*
end
89
Kết quả :
Tong so xe sx trong nam : 14120
So xe sx theo tung quy : 1580 3220 4220 5100
So xe sx trong nam cua tung nha may : 5370 3450 5300
Gia tri san xuat trong nam ( trieu dong ) : 282400
6.12.4 Giải hệ n phương trình tuyến tính n ẩn bằng phương pháp lặp đơn ( điều kiện
hội tụ được đáp ứng ) .
Kết quả :
Buoc lap 1 0.947 -1.933 2.893
Buoc lap 2 1.008 -2.009 3.024
Buoc lap 3 0.997 -1.997 2.997
Buoc lap 4 1.000 -2.000 3.001
Buoc lap 5 1.000 -2.000 3.000
Buoc lap 6 1.000 -2.000 3.000
Ket qua :
x1 = 1.000
x2 = -2.000
x3 = 3.000
+ Bạn đọc tự giải hệ phương trình sau Ax = b bằng phương pháp lặp đơn.
2. 3. 5. 2.
Đặt A 3. 2.5 4. 10. là ma trận mở rộng , chúng ta sẽ biến đổi thành dạng tam giác
4. 3. 2. 2.
1. a12 a13 a14
* * *
*
. Kết quả :
* *
: A = 0. 1. a23 a24
0. 0. 1. a34*
x3 = a*34 ; x2 = a*24 – a*23x3 ; x1 = a*14 – a*13x3 –a*12x2
Bạn đọc tự phân tích và chạy chương trình sau :
program gauss
implicit none
real,dimension (3,4) :: a
real,dimension (3) :: x
91
integer :: i,j
a = reshape( (/2.,3.,-4.,3.,-2.5,3.,5.,4.,2.,2.,10.,2. /) , &
(/3,4/))
print*,'Ma tran mo rong :'
do i =1,3
print '(4f6.2)',a(i,:) ! kiem tra he so ma tran A theo hang
end do
do i =1,3
a(i,:) = a(i,:)/a(i,i)
do j = i+1,3
a(j,:) = a(j,:) - a(i,:)*a(j,i)
end do
end do
print*,'Ma tran tam giac :'
do i =1,3
print '(4f6.2)',a(i,:) ! kiem tra he so ma tran tam giac
end do
x(3) = a(3,4)
x(2) = a(2,4) - a(2,3)*x(3)
x(1) = a(1,4) - a(1,3)*x(3) - a(1,2)*x(2)
print*
print*,'Cac nghiem so x1,x2,x3 : '
print '(3f6.2)',x
read*
end program gauss
92
+ Ghi chú : Trường hợp tổng quát để giải hệ n phương trình tuyến tính bằng phương pháp
khử Gauss , bạn đọc tham khảo văn bản chương trình ở phụ lục B .
6.12.6 Phân tích số liệu thống kê : số trung bình , độ lệch tiêu chuẩn :
Cho vectơ x có n phần tử , sử dụng các công thức sau để tính :
n
1 n ( x(i) x) 2
Bạn đọc phân tích và chạy test chương trình sau :
program ThongKe
implicit none
real,dimension (:),allocatable :: x
integer :: n
real :: tb , do_lech
print*,"Cho biet so phan tu : " ; read*,n
allocate (x(n))
print*, " Vao",n, "gia tri : " ; read *, x
tb = sum(x)/size(x) ! so trung binh
do_lech = sqrt ( sum((x-tb)**2)/(size(x)-1.) ) ! do lech tieu chuan
print '(a,f8.3)'," So trung binh : ",tb
print '(a,f8.3)'," Do lech tieu chuan : " , do_lech
deallocate (x)
read*
end
Nhận xét : trong chương trình trên , vectơ x được cấp phát động , số phần tử khai báo lúc
chạy chương trình . Các biểu thức tính số trung bình , độ lệch tiêu chuẩn là các biểu thức
mảng . Chú ý việc sử dụng các hàm sum , size .
Kết quả chạy test :
Hình 6.8 : Kết quả tính số trung bình , độ lệch tiêu chuẩn .
Trong trường hợp dãy số x có các số bằng 0. ( hoặc số vượt quá phạm vi xem xét nào đó )
và chúng ta muốn loại bỏ chúng , không muốn đưa vào dãy số liệu để tính toán . Lúc này chúng
ta dùng hàm sum , count có kèm điều kiện như ví dụ sau ( loại bỏ các số 0. ) :
program ThongKe2
implicit none
real,dimension (:),allocatable :: x
integer :: n , i
real :: tb , do_lech ,A,B
93
print*,"Cho biet so phan tu : " ; read*,n
allocate (x(n)) Cho biet so phan tu :
print*, " Vao",n, "gia tri : " ; read *, x 7
tb = sum(x, x/=0.)/count(x /= 0.) Vao 7 gia tri :
A = sum((x - tb)**2,x /= 0.) 1.1 0.0 1.3 1.4 0.0 1.6 1.7
B = count(x /= 0.) -1. So trung binh : 1.420
do_lech = sqrt( A/B) Do lech tieu chuan : 0.239
print '(a,f8.3)'," So trung binh : ",tb So phan tu khac 0.: 5
print '(a,f8.3)'," Do lech tieu chuan : " , do_lech Vi tri phan tu so voi so 0.: T F T T F T T
print*," So phan tu khac 0.: ",count(x /= 0.)
print* , " Vi tri phan tu so voi so 0.: ", ( x /= 0.)
deallocate (x)
read*
end
6.12.7 Bạn đọc lập trình tính và in ra các hệ số của tam giác Pascal như sau ( n = 10
dòng đầu tiên ) :
Hình 6.9 : Các hệ số của tam giác Pascal ( 10 dòng đầu ) .
Đáp án đề nghị :
program Pascal
implicit none
integer :: i,j
integer,parameter :: n = 10
integer, dimension (1:n,1:n) :: p
p(1,1) = 1 ! Hàng 1
p(2,1) = 1 ; p(2,2) = 1 ! Hàng 2
do i = 3,n ! Hàng 3 đến hàng n
p(i,1) = 1; p(i,i) = 1 ! Hệ số đầu , cuối mỗi hàng bằng 1
do j = 2 , i-1
p(i,j) = p(i-1,j) + p(i-1,j-1) ! Công thức tổng quát cho hệ số nằm bên trong
end do
end do
do i = 1,n ! Xuất ra màn hình
do j = 1,i
print ‘(i4,$)’,p(i,j)
end do
print*
end do
read *
end
*****
94
CHƯƠNG 7 : CHƯƠNG TRÌNH CON ( SUBPROGRAM )
7.1 Khái niệm về chương trình con.
Trong lập trình , để tránh việc viết những đoạn chương trình thực hiện cùng một công việc
nào đó nhiều lần , chúng ta sẽ chuyển đoạn chương trình này thành một chương trình con (
subprogram ) và từ chương trình chính ( main program ) , khi cần, sẽ gọi chương trình con này ra
để thực hiện .
Đối với một chương trình lớn , cần giải quyết một vấn đề phức tạp , người lập trình thường
phải nghiên cứu , phân chia vấn đề thành các vấn đề nhỏ hơn để dễ giải quyết , dễ kiểm soát theo
một sơ đồ phân cấp từ trên xuống dưới với mức độ khó giảm dần . Khi viết chương trình sẽ bắt đầu
thực hiện theo trình tự ngược lại từ dưới lên trên , giải quyết lần lượt vấn đề dễ trước khó sau .
Mỗi vấn đề nhỏ sẽ được giải quyết bằng một chương trình con . Lúc này , trong chương trình
chính chỉ thể hiện cấu trúc khối tổng quát của chương trình, còn toàn bộ chi tiết đều được bố trí
trong các chương trình con .
Chúng ta còn có thể xây dựng các chương trình con mẫu ; người sử dụng chỉ biết truyền dữ
liệu đầu vào và lấy kết quả ở đầu ra mà không quan tâm đến chi tiết thuật giải bên trong chương
trình . Các tổ chức phát triển ngôn ngữ Fortran đã xây dựng nhiều thư viện chứa hàng trăm chương
trình mẫu thông dụng ở nhiều lĩnh vực tính toán khác nhau , tạo sự thuận lợi cho cộng đồng lập
trình Fortran.
Chương trình con kiểu hàm ( function ) trả về một giá trị thông qua tên hàm , tên hàm cùng
các đối số ( arguments ) phù hợp được sử dụng trong các biểu thức tính toán tương tự như cách sử
dụng các hàm chuẩn của Fortran ( intrinsic functions ) .
Chương trình con kiểu thủ tục ( subroutine ) không trả về một giá trị thông qua tên của
subroutine và việc thực hiện subroutine được gọi từ chương trình chính bằng lệnh call kèm theo
tên của subroutine cùng các đối ( arguments ) nếu có . Thông thường kết quả trả về của subroutine
được gửi vào các đối đầu ra của subroutine .
95
Một hay nhiều Kết quả *: Một giá
đối số . Function trị .
Ghi chú : * Subroutine có thể không có đối đầu vào . Kết quả của Function có thể là một mảng.
! Văn bản các chương trình con internal được đặt ở đây .
! Các chương trình con internal được quyền truy cập các biến
! của chương trình chính ( trừ trường hợp có sự trùng tên biến ).
! Văn bản các chương trình con external được đặt ở đây .
! Các chương trình con external hoàn toàn độc lập với chương trình chính .
96
Nhận xét : chương trình con subroutine Test thuộc loại internal , không có đối hình thức ,
truy cập được giá trị của biến toàn cục ( biến i ) của chương trình chính . Trái lại , biến địa phương
( biến cục bộ ) j của chương trình con chỉ có tác dụng trong phạm vi chương trình con này , nếu
chúng ta đặt câu lệnh print*, j ở chương trình chính thì chương trình sẽ báo lỗi .
Chương trình sau có biến toàn cục và biến cục bộ trùng tên :
program main
implicit none
integer :: i = 6
call Test2 ! Kết quả trên màn hình : 8 , là giá trị của biến i cục bộ
read*
stop
contains
subroutine Test2
integer :: i = 8 ! Biến cục bộ trùng tên với biến toàn cục .
print*, i ! Biến cục bộ được ưu tiên truy cập và in ra .
end subroutine Test2
end program main
Trong trường hợp biến toàn cục và biến cục bộ có tên trùng nhau như ví dụ trên , chương
trình con sẽ ưu tiên truy cập biến cục bộ , giá trị xuất ra màn hình lúc này sẽ là 8.
Trong hai ví dụ nêu trên , chương trình chính và chương trình con loại internal sẽ được biên
dịch đồng thời . Tất cả các chỉ thị , câu lệnh của chương trình con subroutine sẽ được thực hiện tại
vị trí gọi chương trình con trong chương trình chính ( in – lining ) .
7.3.2 Cú pháp của chương trình con subroutine .
+ Cú pháp của chương trình con subroutine :
subroutine <tên_subroutine> ( các đối hình thức )
! Các chỉ thị khai báo các đối hình thức ( dummy arguments )
! Các chỉ thị khai báo các biến, đối tượng cục bộ (local variables, local objects )
! sử dụng trong subroutine
! Các lệnh thực thi .
end subroutine <tên_subroutine>
Tên subroutine được đặt theo quy tắc đặt tên biến.
Các đối hình thức có thể là tên biến , tên mảng , tên chương trình con .Subroutine có thể
không có đối .
Tên các đối hình thức có thể khác với tên của các đối thực sự trong lệnh call , dùng để gọi
thực thi một subroutine .
Chỉ thị return trong chương trình con cho phép thoát ra khỏi subroutine tại bất cứ vị trí nào
của chương trình .
Subroutine còn có thể chứa các chương trình con bên trong khai báo qua từ khóa contains.
+ Các đối hình thức là biến, mảng trong subroutine được khai báo với các thuộc tính sau :
intent(in) : dành cho các biến mà giá trị đầu vào được cung cấp từ chương trình chính (
chương trình gọi subroutine ) . Các giá trị này được sử dụng trong subroutine nhưng không được
có lệnh làm thay đổi giá trị của các biến có thuộc tính này .
intent(out) : dành cho các biến mà giá trị đầu vào không được sử dụng trong subroutine ,
tuy nhiên giá trị của biến được thay đổi trong subroutine . Thông thường đây là biến chứa kết quả
tính toán của subroutine và gửi ra chương trình gọi .
97
intent(inout) : dành cho biến có thuộc tính cả hai loại nêu trên . Biến nhận giá trị đầu vào
từ chương trình gọi , bị biến đổi trong subroutine và chứa kết quả trả về ( khác với giá trị ban đầu
) khi ra khỏi subroutine.
Chúng ta xem chương trình ví dụ sau về khai báo thuộc tính cho các đối hình thức của
subroutine :
program main
implicit none
real :: a,b,c
a = 2. ; b = 1. ; c = 4.
print*,a,b,c ! 2. 1. 4.
call Test3(a,b,c)
print*,a,b,c ! 2. 6. 12.
read*
stop a x : chỉ nhận giá trị đầu vào .
contains b y : chứa giá trị kết quả đầu ra .
subroutine Test3(x,y,z) c z : nhận giá trị đầu vào , chứa giá trị
real,intent(in) :: x kết quả đầu ra .
real,intent(out) :: y
real,intent(inout) :: z
y = 3.*x
z = x + y +z
end subroutine Test3
end program main
+ Cú pháp gọi chương trình con subroutine từ chương trình chính ( chương trình chủ ) như
sau :
call < Tên_subroutine > ( danh sách các đối thực sự )
Các đối thực sự ( actual arguments ) phải tương ứng 1-1 với các đối hình thức nhưng tên có
thể khác nhau . Trong trường hợp các đối là các biến , chúng phải có cùng kiểu dữ liệu và có cùng
số kind .
subroutine Test3(x,y,z)
implicit none
real,intent(in) :: x
real,intent(out) :: y Chương trình độc lập.
real,intent(inout) :: z Khai báo bên ngoài chương trình chính
y = 3.*x ( sau từ khóa end program ) .
z = x + y +z
end subroutine Test3
+ Văn bản chương trình con subroutine dạng bên ngoài (external ), được bố trí bên ngoài
chương trình chính ( dưới dòng end program main ) . External subroutine là đơn vị chương trình
độc lập .
+ Bên trong chương trình chính, chúng ta khai báo phần interface ( giao diện ) chứa tên
của external subroutine kèm các đối hình thức , khai báo kiểu dữ liệu cùng thuộc tính của các đối
hình thức này . Phần nội dung các lệnh thi hành trong subroutine không đưa vào interface .Trình
biên dịch cần thông tin của khối interface khi biên dịch chương trình chính , đảm bảo cho chương
trình chính và chương trình con dạng external có số lượng , kiểu và thuộc tính của các đối tương
thích với nhau .
99
7.3.6 Thuộc tính save áp dụng cho các biến cục bộ
Đối với các biến cục bộ trong chương trình con subroutine được gán thuộc tính save , giá
trị của biến sẽ được lưu lại trong bộ nhớ sau khi subroutine được thực hiện xong . Ở các lần thực
hiện subroutine tiếp theo, giá trị biến cục bộ này sẽ lấy giá trị cập nhật trong bộ nhớ .
program main
implicit none
integer :: n
do n =1,5
call testsave
end do
read*
stop
contains i= 1
subroutine testsave i= 2
integer,save :: i = 0 Kết quả : i= 3
i = i+1 i= 4
print*,’i = ‘,i i= 5
end subroutine testsave
end program main
Trong chương trình trên , subroutine testsave được gọi 5 lần từ chương trình chính và giá
trị biến cục bộ i ( có gán thuộc tính save ) được in ra theo giá trị cập nhật được lưu trong bộ nhớ
sau mỗi lần subroutine được thực hiện . Một số trình biên dịch ( như Force 2.0 ) thực hiện tự động
việc lưu giá trị các biến cục bộ.
Nếu chúng ta khai báo lại biến cục bộ trong subroutine trên như sau :
integer :: i
i=0
i = i+1 ; print*,’i = ‘,i
Thì kết quả i = 1 sẽ xuất hiện năm lần trên màn hình .
100
function Tong (x,y,z)
real :: Tong ! khai báo kiểu dữ liệu trả về của hàm qua tên hàm .
real,intent (in) :: x,y,z ! khai báo các đối số hình thức
Tong = x+y+z
end function Tong
end program main
Trong chương trình function ở trên , chúng ta định nghĩa hàm Tong (x,y,z) có 3 đối số hình
thức là x,y,z. Các đối số này được gán thuộc tính intent(in) , nghĩa là chỉ tiếp nhận giá trị đầu vào
từ chương trình chính , không được phép thay đổi các giá trị này trong chương trình con. Câu lệnh
cuối cùng : Tong = x + y +z , cho biết kết quả của hàm , được trả về qua tên hàm là Tong. Lưu ý :
tên hàm Tong phải được khai báo kiểu dữ liệu , số kind như là một biến .
Các hàm bên trong (internal function) chỉ được sử dụng trong phạm vi đơn vị chương trình
có chứa các hàm này.
Chỉ thị return cho phép ra khỏi chương trình con function tại bất cứ vị trí nào .
101
a = 3.;b = 4.;c = 5.
p = Tong (a,b,c) * 2.
print*,p ! kết quả 24.
print*,Tong(5.,10.,15.) ! kết quả : 30.
read*
end program main
+ External function được khai báo bên ngoài chương trình chính . Trong chương trình chính
chúng ta cần khai báo phần interface cho external function.
+ Giống như phần khai báo interface cho subroutine , interface cho function gồm tên
function kèm các đối số hình thức , khai báo kiểu dữ liệu cho hàm ; kiểu dữ liệu , thuộc tính cho
các đối số hình thức .Phần chi tiết các lệnh thi hành trong function không đưa vào interface.
+ Trình biên dịch cần thông tin của khối interface khi biên dịch chương trình chính .
+ Các đối số hình thức của hàm thường được gán thuộc tính intent(in) , nhận giá trị đầu vào
từ chương trình chính .
Chúng ta xem chương trình tính diện tích hình tròn ,sử dụng đồng thời chương trình con
kiểu thủ tục và chương trình con kiểu hàm , dạng internal như sau :
program ct_con
implicit none
real :: r,dt
real,parameter :: pi =3.14159 ! sử dụng chung cho các chương trình con internal
print ‘(a,$)’,’Nhap gia tri ban kinh : ‘
read*,r
call dt_htron(r,dt) ! Gọi subroutine
print ‘(f8.4)’,dt ! Kết quả của subroutine lấy qua biến dt .
print ‘(f8.4)’,dtht(r) ! Kết quả của hàm lấy qua tên hàm.
read*
stop Nhap gia tri ban kinh : 2.
contains 12.5664
! internal subroutine 12.5664
subroutine dt_htron(ra,s)
real,intent(in) ::ra
real,intent(out) ::s
s = ra*ra*pi ! Truy cập được hằng pi của chương trình chính .
end subroutine dt_htron
! internal function
function dtht (r)
real :: dtht
real,intent(in) :: r
dtht = r*r*pi ! Truy cập được hằng pi của chương trình chính .
end function dtht
end program ct_con
102
Nhận xét : Trong chương trình chính , kết quả của subroutine dt_htron được chứa trong
biến dt , trong khi hàm dtht thì dùng ngay tên hàm kèm theo đối số thực sự để lấy kết quả .
Chúng ta viết lại chương trình tính diện tích hình tròn , sử dụng chương trình con dạng
external function , trong chương trình chính chúng ta cần khai báo interface cho chương trình con
external :
program dt_hinhtron
implicit none
interface
function dtht (r)
real :: dtht
real ,intent(in) :: r
end function dtht Vao gia tri ban kinh : 3.
end interface Dien tich : 28.2743
real :: ra
print ‘(a,$)’,” Vao gia tri ban kinh : “ Kết quả .
read *,ra
print ‘(a,f8.4)’,”Dien tich : “,dtht(ra)
read*
end program dt_hinhtron
! Khai bao external function ,la chuong trinh doc lap .
function dtht(r)
implicit none
real :: dtht
real,intent(in) :: r
real,parameter :: pi = 3.14159 ! khai báo hằng pi .
dtht = r*r*pi
end function dtht
103
read*
end program funcradi
function radi(do)
implicit none
real :: radi, pi
real,intent(in) :: do ! external function
pi = 4.0*atan(1.)
radi = do*pi/180.
end function radi
Lưu ý : tên hàm radi trong trường hợp này phải được khai báo như một biến kiểu real trong
chương trình chính , phù hợp với câu lệnh implicit none .
Cũng có thể khai báo tên hàm riêng như sau :
real , external :: radi !Từ khóa external báo cho biết radi là một chương trình bên ngoài .
Giá trị số pi được tính qua công thức : 4.0*atan(1.) .
Từ phiên bản Fortran 90 trở về sau , việc khai báo rõ ràng ( explicitly ) các chương trình
con dạng external qua khối interface trong chương trình chính sẽ có lợi điểm là giúp cho trình biên
dịch kiểm soát tốt tên các chương trình con được sử dụng trong chương trình chính ; sự tương
thích về số lượng , kiểu dữ liệu , tham số kind , thuộc tính giữa các đối hình thức và các đối thực
sự khi gọi sử dụng các chương trình con . Khai báo interface cho các external procedure là một
đặc điểm an toàn của Fortran 90 . Tương tự như việc khai báo đầy đủ các biến ( tên, kiểu dữ liệu ,
tham số ) tham gia vào chương trình phù hợp với chỉ thị implicit none ở đầu một đơn vị chương
trình ( bỏ qua quy tắc ngầm định của Fortran ) .
Với công cụ module ( chương 8 ) , sẽ hiệu quả hơn khi chúng ta đưa các chương trình con
external vào một module và khi cần sử dụng trong chương trình chính chúng ta chỉ cần khai báo
sử dụng module có chứa chương trình mà chúng ta cần sử dụng . Các thư viện Fortran đều lưu các
chương trình mẫu dưới dạng các module .
7.5 Chương trình con có đối hình thức là mảng ( vectơ, ma trận, …).
7.5.1 Khai báo đối số là mảng có kích thước rõ ràng trong chương trình con.
Chúng ta xem ví dụ về chương trình con kiểu hàm tính tích vô hướng của hai vectơ , có
đối số hình thức là vectơ có 03 phần tử được khai báo tường minh :
program array_arg
implicit none
real,dimension(1:3) :: a,b ! vectơ có 3 thành phần số .
a = 2. ; b = 3.
print*,”Tich vo huong : “,TichVoHuong(a,b) ! Kết quả : Tich vo huong : 18.
read*
stop
contains
function TichVoHuong(vec1,vec2)
real :: TichVoHuong
real,dimension(1:3),intent(in) :: vec1,vec2 ! Kích thước mảng được khai báo rõ ràng.
TichVoHuong = sum(vec1*vec2)
end function TichVoHuong
end program array_arg
104
Lưu ý : các đối số thực sự phải tương thích với các đối hình thức về kiểu dữ liệu , số kind
và phải có cùng dạng ( shape ) .
7.5.2 Khai báo đối số là mảng có kích thước thể hiện bằng một biến nguyên .
program array_arg
implicit none
real,dimension(1:10) :: a,b ! vectơ có 10 phần tử .
a = 1. ; b = 2.
print*,”Tich vo huong : “,TichVoHuong(a,b,10) ! Tich vo huong : 20.
read*
stop
contains
function TichVoHuong(vec1,vec2,n ) ! khai thêm đối số n là kích thước mảng
real :: TichVoHuong
integer,intent(in) :: n ! kích thước mảng tùy vào biến n
real,dimension(1: n),intent(in) :: vec1,vec2
TichVoHuong = sum(vec1*vec2)
end function TichVoHuong
end program array_arg
Chương trình con function này có thêm đối số n là kích thước của mảng.
Trong trường hợp này , kích thước mảng trong function được để tự do , dùng kí hiệu ( : ) ,
khi sử dụng hàm ở chương trình chính ,các đối số thực sự sẽ truyền kích thước mảng sang các đối
hình thức. Trong chương trình con, chúng ta có thể khai báo một biến nguyên n kiểu integer và
gán n = size(vec1) , để lấy kích thước mảng của các đối thực sự , phục vụ cho các tính toán trong
chương trình con khi cần đến kích thức này ( xem ví dụ dưới đây ) .
Chúng ta xem ví dụ về chương trình con function dist(OM) tính khoảng cách từ gốc tọa độ
O đến điểm M (x,y,z) : OM x 2 y 2 z 2
program Test
implicit none
real,dimension (1:3) :: a,b
105
a = 2.
b = 3.
print ‘(a,f8.4)’,’Khoang cach OA : ‘, dist(a)
print ‘(a,f8.4)’,’Khoang cach OB : ‘, dist(b)
print ‘(a,f8.4)’,’Khoang cach OC : ‘, dist((/ 3.,4./)) ! vectơ OC chỉ có hai thành phần số .
read*
stop
contains
function dist(OM)
real :: dist
real,dimension ( : ) , intent(in) :: OM ! Kích thước mảng được để tự do .
integer :: i,n
real :: temp
n = size(OM) ! Lấy kích thước của đối số thực sự .
temp = 0.
do i = 1,n
temp = temp + OM(i)**2
end do
dist = sqrt(temp)
end function
end program Test Hình 7.2 : Kết quả program Test
Lưu ý : vòng lặp do ở chương trình function sử dụng chỉ số cuối n = size (OM) phụ thuộc
vào kích thước của đối số thực sự trong hàm .
106
Trong ví dụ sau , kết quả trả về của hàm là một mảng có kích thước bằng với kích thước
biến mảng ( đối hình thức có kích thước tự do ) :
program main
implicit none
real,dimension(10) :: List = 1.
print*,Transform (List)
read*
stop
contains
function Transform (vecto)
real,dimension(:),intent(in):: vecto ! Kích thước biến mảng được để tự do .
real,dimension (size(vecto)) :: Transform ! Kích thước mảng hàm bằng kích thước biến mảng.
integer :: i,n
n = size(vecto)
do i = 1,n
Transform(i) = i*vecto (i)
end do
end function Transform
end program main
Nhận xét : dòng khai báo tên hàm phải có từ khóa recursive đồng thời kết quả trả về của
hàm trong chương trình con được gửi vào biến res ( có cùng kiểu với hàm ) đi kèm với từ khóa
result .
Kiểu lập trình đệ qui thường tốn bộ nhớ hơn cách sử dụng vòng lặp do thông thường.
107
7.7 Chương trình con có đối hình thức là xâu kí tự .
Các đối hình thức trong chương trình con có thể là xâu kí tự , lúc này chiều dài của xâu hình
thức có thể được để tự do ( đặt tham số len = * ) . Khi được truyền đối số thực sự từ chương trình
chính , xâu kí tự trong chương trình con sẽ lấy chiều dài theo xâu của đối đầu vào .Chúng ta xem
ví dụ sau :
program ChaoBan
implicit none Cho biet ten ban :
character (len = 10) :: name Hien
print*,’Cho biet ten ban :’ Hello Hien
read*,name
print*, hello(name) Kết quả .
read*
stop
contains
function hello(ten)
character(len = *),intent(in) ::ten ! chiều dài xâu ten được để tự do.
character(len = len(ten)+6) :: hello ! chiều dài của hàm phụ thuộc chiều dài xâu ten
hello = “Hello “//trim(adjustl(ten)) ! Kết quả của hàm kiểu xâu kí tự.
end function hello
end program ChaoBan
7.8 Chương trình con có đối hình thức được gán thuộc tính optional .
Các đối hình thức trong chương trình con có thể được gán thuộc tính optional , trong trường
hợp này các đối thực sự tương ứng với các đối optional có thể xuất hiện hoặc không xuất hiện trong
danh sách các đối đầu vào được gọi từ chương trình chính . Chúng ta xem ví dụ sau về chương
trình con external function tính hàm y = xn :
program main
implicit none
real :: x
interface
function y(x,n)
real :: y
real,intent(in) :: x
integer,intent(in),optional :: n
end function y
end interface
x = 2.
print ‘(4f6.2)’, y(x), y(x,2),y(x,3),y(x,6) ! Kết quả : → 2.00 4.00 8.00 64.00
read*
end program main
!external function
function y(x,n)
implicit none
real :: y
real,intent(in) :: x
integer,intent(in),optional :: n ! biến n có thuộc tính optional
integer :: ns ! biến cục bộ của hàm
ns = 1
if (present(n)) ns = n ! hàm present (n) kiểu logical
y = x**ns
end function y
108
+ Hàm present (n) có kiểu logical dùng để test xem đối số n , mang thuộc tính optional ,
có xuất hiện trong danh sách đối số đầu vào của hàm hay không ?
+ Do đối số n là optional nên chúng ta có thể viết : y(x) , y(x,3) , y(x,5) …
7.9 Chương trình con có đối hình thức là subroutine hoặc function.
Chương trình ví dụ sau có chương trình con function ( dạng external) tính tích phân xác
định theo công thức hình thang ( xem bài thực hành 4.6.2 ) . Trong danh sách các đối số hình thức
của function tính tích phân này, có đối là một hàm f nào đó mà chúng ta phải khai báo phần interface
trong chương trình con tính tích phân . Các biểu thức hàm cụ thể ( do người lập trình định nghĩa )
để đưa vào chương trình con tính tích phân được khai báo dưới dạng external function . Toàn bộ
các chương trình con external function được khai báo trong block interface ở chương trình chính .
Đối với các hàm đã được định nghĩa sẵn trong Fortran ( intrinsic functions ) như hàm sin , cos ,exp
…, chúng ta chỉ cần khai báo từ khóa intrinsic kèm theo tên hàm là có thể đưa vào sử dụng như
đối số của chương trình con .
Các tích phân xác định được tính trong chương trình :
1 1 1
0 0 0 0
program main
implicit none
! khai bao block interface cho 3 function.
interface
function tichphan(f,a,b,n) ! hàm f là đối hình thức .
real :: tichphan
real,intent(in) :: a,b ! Khai báo interface cho hàm tichphan có
integer,intent(in) :: n ! chứa đối số là chương trình con.
interface
function f(x)
real :: f ! Khai báo interface cho hàm f là đối hình thức .
real,intent(in) :: x
end function f
end interface
end function tichphan
function g(x)
real :: g ! Khai báo interface cho hàm g(x) do người lập trình định nghĩa.
real,intent(in) :: x
end function g
function h(x)
real :: h ! Khai báo interface cho hàm h(x) do người lập trình định nghĩa.
real,intent(in) :: x
end function h
end interface ! Ket thuc khai bao block interface .
intrinsic :: sin , exp ! Khai báo sử dụng hàm chuẩn của Fortran ( sin(x) ; exp(x) )
print ‘(f10.5)’,tichphan(g,0.,1.,1000 ) kết quả : 2.00000
print ‘(f10.5)’,tichphan(h,0.,1.,1000) kết quả : 3.19453
print ‘(f10.5)’,tichphan(exp,0.,1.,1000) 1.71828
print ‘(f10.5)’,tichphan(sin,0.,3.14159,1000) 2.00000
109
read*
end program main
! Khai bao cac external function
function tichphan(f,a,b,n) Công thức hình thang :
real :: tichphan
real,intent(in) :: a,b f (a) f (b) n 1
b
integer,intent(in) :: n a f ( x ) dx h f (a ih)
interface 2 i 1
function f(x) ba
h
real :: f n
real,intent(in) :: x
end function f
end interface ! khai báo interface cho đối là chương trình con function
real :: s, h
integer :: i
h = (b-a)/n ; s = 0.
do i = 1,n-1
s = s + f(a+i*h)
end do
tichphan = h*( (f(a)+f(b))/2. + s) ! công thức hình thang .
end function tichphan
function g(x) ! Hàm g(x) do người lập trình định nghĩa.
real :: g
real,intent(in) :: x
g = 2.*x + 3.*x*x ! g(x) = 2x + 3x2
end function g
function h(x) ! Hàm h(x) do người lập trình định nghĩa.
real :: h
real,intent(in) :: x
h = exp(2*x) ! h(x) = e2x
end function h
Bạn đọc cần phân tích kỹ chương trình nêu trên và chạy test với các tích phân xác định khác
nhau .
7.10.4 Tìm trị gần đúng của nghiệm riêng của phương trình vi phân cấp 1 y’ = f(x,y) với
điều kiện đầu y = y0 khi x = x0 bằng phương pháp Euler .
112
Phương trình : y’ = 3x – y/x ; đk đầu : y = 1 ( x = 1) , tính y = ? khi x = 5 .
Dựa vào công thức gần đúng để tính đạo hàm ở 7.10.3 , có các công thức sau :
y (i) y (i 1)
y '(i 1) f ( x(i 1), y (i 1)) (1)
h
y (i ) y (i 1) f ( x(i 1), y (i 1))h (2)
Giữa x0 = 1 và x = 5 , chia làm 1000 đoạn . Sử dụng công thức (2) để lập dãy số y(i) với i
= 0..1000 . Giá trị y (1000 ) tương ứng với y (5) .
program Euler
implicit none
integer,parameter :: n = 1000, r = 8
integer :: i
real (kind = r),dimension (0:n):: x,y
real ::a,b,h 0.24990401D+02
a = 1._r ; b = 5._r ; h = (b-a)/n
x = (/ 1._r, (1._r+ i*h , i=1,n) /) ! Day so x Kết quả y tại x = 5 .
y(0) = 1._r ! gia tri dau (1,1)
do i = 1,n
y(i) = y(i-1) + f(x(i-1),y(i-1))*h ! Cong thuc Euler
end do
print ‘(d18.8)’ , y(n) ! Tri so nghiem rieng tai x = 5
read*
stop
contains
function f(x,y)
real (kind = r) :: f
real ( kind = r),intent(in) :: x,y
f = 3._r*x –y/x
end function f
end program Euler
Ghi chú : nghiệm riêng của phương trình vi phân trên là y = x2 ; tại x = 5 , y = 25 .
+ Bạn đọc dùng phương pháp Euler như trên tính trị gần đúng tại x = 2 của nghiệm riêng
phương trình vi phân y’ = xy2 +1 với đk đầu y = 0 ( x = 0) .
7.10.5 Lập trình tính 3 góc của một tam giác ABC có các cạnh a = BC , b = AC, c= AB bằng
cách lập function goc(b,c,a) tính góc A kẹp giữa hai cạnh b ,c ( lưu ý thứ tự các cạnh )
a2 = b2 + c2 – 2bc cos (A) A = acos ((b2 + c2 –a2) /(2bc)) . Góc được tính ra độ .
program tinh_goc_tam_giac
implicit none
real :: a,b,c,gocA,gocB,gocC
print*,"***CHUONG TRINH TINH GOC CUA TAM GIAC***"
print*,"***Canh a/b/c doi dien gocA/gocB/gocC***"
print*
print '(a,$)',"Vao ba canh cua tam giac a,b,c :"
read*,a,b,c
113
! Tinh cac goc , don vi la do
gocA = goc(b,c,a)
gocB = goc(a,c,b)
gocC = goc(a,b,c)
print '(a,f7.3)',"Goc A : ",gocA
print '(a,f7.3)',"Goc B : ",gocB
print '(a,f7.3)',"Goc C : ",gocC
read*
contains
real function goc(x,y,z)
! Tra ve goc giua hai canh x,y
intrinsic :: acos,max,atan ! khai bao su dung ham chuan cua Fortran (optional ) .
real ,intent(in) :: x,y,z
real :: gocr ,pi
pi = 4.*atan(1.)
! Kiem tra 3 canh x,y,z co tao thanh tam giac hay khong ?
if (2*max(x,y,z) < (x+y+z) ) then
gocr = acos ((x**2+y**2-z**2)/(2.*x*y)) ! goc tinh bang radian
goc = gocr*180./pi ! Doi ra do
else ! Khong phai tam giac
goc = 0.0
end if
end function goc
end program tinh_goc_tam_giac
Kết quả :
***CHUONG TRINH TINH GOC CUA TAM GIAC***
***Canh a/b/c doi dien gocA/gocB/gocC***
A
Vao ba canh cua tam giac a,b,c :5. 4. 3.
Goc A : 90.000
Goc B : 53.130 c b
Goc C : 36.870
a
B C
Ghi chú : +Trường hợp các cạnh a,b,c không tạo thành tam giác , hàm goc sẽ trả về trị số 0.0 :
***CHUONG TRINH TINH GOC CUA TAM GIAC***
***Canh a/b/c doi dien gocA/gocB/gocC***
Vao ba canh cua tam giac a,b,c :5. 2. 1.
Goc A : 0.000
Goc B : 0.000
Goc C : 0.000
+ Câu lệnh khai báo intrinsic :: <danh sách các hàm > nhằm giúp cho người đọc chương trình
biết các hàm chuẩn được sử dụng trong chương trình . Ngoài ra nếu trình biên dịch không hỗ trợ một hàm
nào đó trong danh sách thì sẽ có lỗi khi chạy chương trình .
*****
114
CHƯƠNG 8 : ĐƠN VỊ CHƯƠNG TRÌNH MODULE
8.1 Khái niệm về module .
Trong Fortran , module là một đơn vị chương trình độc lập dùng để chia sẻ thông tin , các
chương trình sử dụng chung … , người lập trình có thể đưa vào module các nội dung như sau :
_ Khai báo các hằng thể hiện độ chính xác của kiểu biến số học ( lưu số kind ) .
_ Khai báo các hằng số vật lý, toán học , hệ số chuyển đổi thông dụng .
_ Khai báo các dữ liệu như biến, mảng sử dụng chung cho nhiều chương trình ( global
data ) .
_ Khai báo các giao diện ( interface ) của các chương trình .
_ Các chương trình tiện ích được sử dụng chung ( gồm các hàm ,thủ tục : gọi là module
procedures ) . Các module procedure này lại có thể chứa các internal procedures bên trong qua từ
khóa contains , giống như cấu trúc main program đã biết ở chương 7 .
_ Các chương trình định nghĩa hàm, phép tính mới do người lập trình thiết lập .
Module có thể được xem như là một đơn vị chương trình được đóng gói (encapsulation ) .
Thông tin trong một module sẽ được chia sẻ cho các đơn vị chương trình khác nhau có yêu cầu sử
dụng toàn bộ hay một phần các nội dung chứa trong module này . Một số tổ chức phát triển Fortran
đã xây dựng nhiều module chứa các chương trình tiện ích nhằm giúp người lập trình có thêm công
cụ giải quyết nhanh các vấn đề thường gặp .
Khi một đơn vị chương trình muốn sử dụng một module nào đó thì đặt chỉ thị như sau
ngay dưới từ khóa Program hoặc Module nhưng trước từ khóa implicit none :
Program/Module < Tên >
Use < tên_module >
Implicit none
Chúng ta sẽ tìm hiểu module qua các ví dụ .
8.3 Module chứa các hằng số , dữ liệu dùng chung, khai báo interface .
8.3.1 Ví dụ 01 .
module kinds ! lưu số kind của số thực chính xác kép qua hằng r .
implicit none
integer,parameter :: r = selected_real_kind (15,307) Cho biet so feet va so inch :
end module kinds 30 10
module doi_dv ! lưu các hệ số chuyển đổi đơn vị Chieu dai (m): 0.939800D+01
use kinds ! khai báo sử dụng module kinds
implicit none
real(kind = r), parameter :: in = 0.0254_r , ft = 0.3048_r ,& Kết quả .
& yd = 0.9144_r, lb = 0.4536_r
end module doi_dv
115
program main
use kinds
use doi_dv , only : in,ft ! Chỉ sử dụng hằng in , ft trong mudule doi_dv
implicit none
real (kind = r) :: so_ft,so_in,chieudai
print*,'Cho biet so feet va so inch :'
read*, so_ft, so_in
chieudai = so_ft*ft + so_in*in
print '(a,d15.6)','Chieu dai (m): ',chieudai ;
read*
end program main
Đơn vị module có thể được biên dịch riêng và phải được biên dịch trước đơn vị chương
trình có sử dụng module này .Khi sử dụng trình biên dịch g95 , chúng ta sắp xếp như sau để tạo
file ketqua.exe từ một file module01 và một file chương trình chính main có khai báo sử dụng
module01 :
module kinds ! Khai báo hằng r là số kind của số thực chính xác kép .
implicit none
integer,parameter :: r = selected_real_kind (15,307)
end module kinds
module const ! chứa hằng số pi
use kinds
implicit none
real (kind = r), parameter :: pi = 3.141592654_r
end module const
116
module common_data ! sử dụng dữ liệu chung là hai biến bk,cao
use kinds
implicit none
real(kind = r), save :: bk,cao ! được gán thuộc tính save .
end module common_data
program main
use kinds
use common_data Trong luong (kgf) : 0.565487D+06
implicit none
interface Kết quả .
function TL(gamma)
use kinds
real(r) :: TL
real(r),intent(in) ::gamma
end function TL
end interface
real (r) :: gamma = 1000_r ! Trọng lượng riêng của nước .
bk = 6._r ; cao = 5._r ! sử dụng hai biến bk, cao có trong module common_data
print '(a,d14.6)','Trong luong (kgf) : ', TL(gamma)
read*
end program main
! Khai báo external function .Sử dụng các module đã khai báo .
function TL(gamma)
use kinds
use const
use common_data
implicit none
real(r) :: TL
real(r),intent(in) ::gamma
TL = gamma*pi*bk*bk*cao ! Trọng lượng khối chất lỏng hình trụ tròn.
end function TL
Chương trình ví dụ 02 dùng để tính trọng lượng khối nước hình trụ tròn có 03 module ,
ngoài module kinds đã biết , còn có module const khai báo hằng số pi và module common_data
khai báo 2 biến kiểu số thực chính xác kép là bk ( bán kính ) , cao ( chiều cao ) được sử dụng
chung cho các đơn vị chương trình có sử dụng module này . Lưu ý hai biến này phải được gán
thuộc tính save .Trong chương trình chính có khai báo interface cho hàm external TL(gamma) ,
với đối số gamma là trọng lượng riêng của chất lỏng .
Như vậy module còn có thể chứa các dữ liệu ( biến, mảng ,…) dùng chung cho các đơn vị
chương trình có khai báo sử dụng module này .
118
real (kind = r),parameter :: pi = 3.141592654_r
end module const
module circle
use kinds
use const
implicit none
contains
subroutine HinhTron(ra,dt,cv)
real(r),intent(in) :: ra
real(r),intent(out) :: dt,cv
dt = pi*ra*ra
cv = 2._r*ra*pi
end subroutine HinhTron
function KhoiTru(ra,h)
real(r):: KhoiTru
real(r),intent(in) :: ra,h
KhoiTru = pi*ra*ra*h
end function KhoiTru
end module circle
program main
use kinds
use circle ! sử dụng các chương trình của module circle
implicit none
real(r) :: bk,dt,cv
print*,'Cho biet ban kinh :' ; read*,bk
call HinhTron(bk,dt,cv) ! Gọi subroutine HinhTron trong module circle
print '(a,2d16.8)','Dien tich & Chu vi hinh tron : ',dt,cv
print '(a,d16.9)' ,'The tich khoi tru : ', KhoiTru(5._r,8._r) ! bán kính 5,chiều cao 8
read*
end program main
Kết quả :
Cho biet ban kinh :
5.
Dien tich & Chu vi hinh tron : 0.78539816D+02 0.31415927D+02
The tich khoi tru : 0.628318531D+03
Trong chương trình chính chúng ta có thể gọi subroutine HinhTron ( ) hoặc dùng hàm
KhoiTru ( ) là các chương trình đã có khai báo chi tiết trong module circle . Sử dụng module có
chứa các chương trình tiện ích là một giải pháp rất linh hoạt trong lập trình Fortran . Bản thân một
module procedure cũng có thể có các internal procedures nằm bên trong nó được bố trí dưới từ
khóa contains .
Các module này có thể được biên dịch riêng hoặc biên dịch chung với chương trình chính .
Trong phần mềm FTN95 , chúng ta tạo một project chứa file main riêng và các file module có
liên quan nằm trong một thư mục , đưa các file này vào phần source code , xong build và run sẽ
tạo file *.exe của project , tương tự như chúng ta đặt văn bản các module ở trước program main
trong phần mềm Force ( giống các ví dụ nêu trên ) , sau đó biên dịch và run .
interface
function f(x)
real :: f
real ,intent(in) :: x ! khai báo interface cho đối hình thức là chương trình con .
end function f
end interface
real :: s,h
integer :: i
h = (b-a)/n ; s = 0.
do i = 1,n-1
s = s + f(a+i*h)
end do
tichphan = h*( (f(a)+f(b))/2. + s)
end function tichphan
end module integral
!!
program main
use integral
implicit none
interface ! khai báo giao diện cho các hàm sử dụng trong main program .
function g(x)
real:: g
real,intent(in) :: x
end function g
function h(x)
real:: h
real,intent(in) :: x
end function h
end interface
intrinsic :: sin ! khai báo sử dụng hàm chuẩn của Fortran
print '(a,e15.6)','Ket qua : ',tichphan(g,0.,1.,1000 )
print '(a,e15.6)','Ket qua : ',tichphan(h,0.,1.,1000 )
print '(a,e15.6)','Ket qua : ',tichphan(sin ,0.,3.14159,1000 )
read*
end program main
! external function
function g(x)
real :: g
real,intent(in) :: x
g = sqrt(1. + x*x)
end function g Kết quả .
120
function h(x)
real :: h
real,intent(in) :: x
h = exp(x*x)
end function h
Chúng ta viết lại ví dụ 05 bằng cách thêm module hamso , chứa khai báo các hàm g(x),
h(x) dưới dấu tích phân . Chương trình chính lúc này rất gọn , không cần phải viết phần external
function và khai báo interface cho các hàm này . Lưu ý : trong chương trình chính chúng ta cần
thêm chỉ thị use hamso .
module integral
implicit none
contains
function tichphan(f,a,b,n)
real:: tichphan
real,intent(in) :: a,b
integer,intent(in) :: n
interface
function f(x)
real :: f
real ,intent(in) :: x
end function f
end interface
real :: s,h
integer :: i
h = (b-a)/n ; s = 0.
do i = 1,n-1
s = s + f(a+i*h)
end do
tichphan = h*( (f(a)+f(b))/2. + s)
end function tichphan
end module integral
! *****
module hamso
implicit none
contains ! khai báo các hàm dưới dấu tích phân .
function g(x)
real :: g
real,intent(in) :: x
g = sqrt(1. + x*x)
end function g
function h(x)
real :: h
real,intent(in) :: x
h = exp(x*x)
end function h
end module hamso
program main
use integral
use hamso
intrinsic :: sin
print '(a,e15.6)','Ket qua : ',tichphan(g,0.,1.,1000 )
121
print '(a,e15.6)','Ket qua : ',tichphan(h,0.,1.,1000 )
print '(a,e15.6)','Ket qua : ',tichphan(sin ,0.,3.14159,1000 )
read*
end program main
8.5 Lập hàm có tính chất generic do người lập trình định nghĩa.
Trong Fortran 90, nhiều hàm có tính chất generic , nghĩa là một tên hàm dùng chung cho
các chương trình khác nhau tùy thuộc vào kiểu , số kind , rank (vectơ, ma trận …) của đối số đầu
vào . Ví dụ như hàm sin ( ) qua ví dụ sau có đối số là số thực chính xác đơn và số thực chính xác
kép sẽ cho kết quả khác nhau :
program testsin
implicit none
print*,sin(1._4) sin(1._4) = 0.84147096
print*,sin(1._8) sin(1._8) = 0.8414709848078965
read*
end
Chúng ta sẽ viết một module cho hàm exsin (x) có tên là esin (x) có tính chất generic , tên
hàm esin (x) sẽ dùng chung cho hai chương trình tùy thuộc vào đối số đầu vào là số thực có độ
chính xác đơn ( chương trình esin_sp ) hay số thực có độ chính xác kép ( chương trình esin_dp ) .
Bạn đọc cần phân tích kỹ cấu trúc của module này .
module rkind ! Lưu số kind
implicit none
integer,parameter :: rs = selected_real_kind (6,37) ! so thuc chinh xac don .
integer,parameter :: rd = selected_real_kind (15,307) ! so thuc chinh xac kep .
end module rkind
module mod_esin
use rkind
implicit none
interface esin ! Khai báo interface cho hàm generic esin
module procedure esin_sp ,esin_dp ! Khai báo tên hai chương trình trong module
end interface esin
contains
function esin_sp(x) ! dùng cho đối số là số thực chính xác đơn .
real(rs),intent(in) :: x
real(rs) :: esin_sp
esin_sp = exp(x)*sin(x)
end function esin_sp
function esin_dp(x) ! dùng cho đối số là số thực chính xác kép .
real(rd),intent(in) :: x
real(rd) :: esin_dp
esin_dp = exp(x)*sin(x)
end function esin_dp 2.2873552
end module mod_esin 2.2873552871788423
program main
use rkind Kết quả .
use mod_esin
implicit none
print*,esin(1._rs) ; print*, esin(1._rd) ! Xuất kết quả hàm exsin(x)
read*
end program main
122
8.6 Định nghĩa phép tính mới .
Trong chương trình sau , chúng ta sẽ viết module TichCoHuong dùng để định nghĩa một
phép tính có tên là .cross. dùng tính tích có hướng của hai vectơ như sau :
module TichCoHuong
implicit none
interface operator (.cross.) ! .cross. là tên phép tính .
module procedure TCH ! TCH là tên của chương trình nằm trong module
end interface
contains ! Khai báo interface cho phép tính .cross.
+ Tên phép tính phải có dấu chấm trước và sau : .cross.
+ Trong module , phép tính .cross. được khai báo qua phần interface :
interface operator (.cross.)
module procedure TCH
end interface
operator (.cross.) cho biết tên của phép tính là .cross.
module procedure TCH là function TCH (a,b) cho biết tên của chương trình trong
module là TCH dùng để tính kết quả trả về của phép tính a.cross.b
+ Trong chương trình chính sau khi có khai báo use TichCoHuong , chúng ta được quyền
sử dụng tên phép tính trong các biểu thức như z = x .cross.y
123
toán trong nội bộ module .Chúng ta xem chương trình đổi đơn vị áp suất tính bằng psi ( lb/in 2) ra
kPa hoặc kgf/cm2 như sau :
module kinds
implicit none
integer,parameter :: r = selected_real_kind (15,307)
end module kinds
module doi_apsuat
! Doi ap suat tu lb/in2 ( psi ) ra kPa va kgf/cm2
use kinds
implicit none
real(kind = r), parameter , private :: in = 2.54_r , ft = 30.48_r ,&
& lb = 0.4536_r
real(kind = r),parameter :: k_kpa = (lb/(in*in))*98._r ! biến public
real(kind = r),parameter :: k_kg_cm2 = (lb/(in*in)) ! biến public
end module doi_apsuat
program main
use kinds
use doi_apsuat
implicit none
real(r) :: p
print '(a,$)','Cho biet ap suat ( lb/in2) : ' ;read*,p
print'(a,d16.6)','Ap suat (kPa) : ', p*k_kpa
print'(a,d16.6)','Ap suat (kgf/cm2) : ', p*k_kg_cm2
read*
end
Kết quả :
Cho biet ap suat ( lb/in2) : 100.
Ap suat (kPa) : 0.689020D+03
Ap suat (kgf/cm2) : 0.703081D+01
Trong module doi_apsuat , các hằng in, ft, lb được khai báo thuộc tính private , chỉ có giá
trị trong phạm vi module này ( dùng tính toán trong nội bộ module ,không chia sẻ các giá trị này
ra bên ngoài module ) . Nếu trong chương trình chính chúng ta khai báo câu lệnh : print*, in thì
chương trình sẽ báo lỗi .
n n n
a xi2 b xi xi yi
i 1 i 1 i 1
n n
a xi bn yi
i 1 i 1
Bạn đọc lập các module cần thiết để tính các hệ số a,b .
124
Hướng dẫn : Chúng ta sẽ lập các module sau :
Module chứa hàm tính định thức cấp 2 .
Module chứa subroutine nhận biến đầu vào là hai mảng x,y và xuất biến đầu ra là hai hệ
số a,b .Trong module này sẽ thiết lập các hệ số của hệ hai phương trình tuyến tính nêu trên ( sử
dụng các hàm dot_product , sum đã biết ) và giải hệ thống bằng phương pháp định thức .
module DinhThuc
implicit none
contains
function det2(a)
real :: det2
real,dimension(2,2),intent(in) :: a
det2 = a(1,1)*a(2,2) - a(1,2)*a(2,1)
end function det2
end module DinhThuc
module BPBN
use DinhThuc
implicit none
contains
subroutine linear(x,y,k,m)
! Quan he y = kx+m
real,dimension(:),intent(in) :: x,y
real,intent(out) :: k,m
real,dimension(1:2,1:3) :: c ! ma trận mở rộng các hệ số 2 hàng 3 cột .
c(1,1) = dot_product(x,x)
c(1,2) = sum(x)
c(1,3) = dot_product(x,y)
c(2,1) = c(1,2)
c(2,2) = size(x)
c(2,3) = sum(y)
k = det2(c(:,3:2:-1))/ det2(c(:,1:2))
m = det2(c(:,1:3:2))/ det2(c(:,1:2))
end subroutine linear
end module BPBN
program ppbpbn
use BPBN
implicit none Quan he y = ax+b
integer , parameter :: n = 5 a = 0.425 b = 1.175
real,dimension(n) :: x,y
real :: a,b Kết quả .
x = (/-2.,0.,1.,2.,4. /) ! vào dãy số x
y = (/0.5,1.,1.5,2.,3. /) ! vào dãy số y
125
call linear(x,y,a,b)
print*,'Quan he y = ax+b '
print '(a,f6.3,a,f6.3)','a = ',a,' b = ',b
read*
end
Nhận xét : trong module BPBN , kích thước các mảng ( vectơ ) x, y được để tự do dùng ký
hiệu ( : ) ; để lấy kích thước của các mảng thực sự, chúng ta dùng hàm size ( ) .
8.8.2 Ứng dụng tích có hướng để tính diện tích tam giác ABC :
Công thức : s 0.5* AB ^ AC
Bạn đọc sử dụng module TichCoHuong với phép tính .cross. để tính diện tích tam giác ABC
khi biết tọa độ các điểm A,B,C .
Chương trình tham khảo :
module TichCoHuong
implicit none
interface operator (.cross.)
module procedure TCH
end interface
contains
function TCH (a,b)
real,dimension(3),intent(in) :: a,b y
real,dimension(3) :: TCH A
TCH(1) = a(2)*b(3) - a(3)*b(2)
TCH(2) = a(3)*b(1) - a(1)*b(3)
TCH(3) = a(1)*b(2) - a(2)*b(1)
end function TCH B C
end module TichCoHuong x
program main O
use TichCoHuong
implicit none
real,dimension(3) :: a,b,c,z
real :: s
a = (/3.,3.,0. /)
b = (/1.,1.,0. /)
c = (/7.,1.,0. /)
z = (b-a) .cross. (c-a)
s = 0.5*sqrt(dot_product(z,z))
print*,'Dien tich tam giac : ', s ! Dien tich tam giac : 6.
read*
end
126
n n
pi xi py i i
Xg i 1
n
; Yg i 1
n
p
i 1
i p
i 1
i
Bạn đọc lập một module chứa subroutine center (x,y,p,xg,yg) tính tọa độ trọng tâm của dãy
chất điểm có tọa độ (xi,yi) có kèm theo trọng lượng pi . Các mảng đầu vào x,y,p có cùng số phần
tử . Tọa độ trọng tâm ( xg,yg) được tính theo công thức nêu trên .
Chương trình tham khảo :
module TrongTam
implicit none
contains
subroutine center(x,y,p,xg,yg)
real,dimension ( : ) ,intent(in) :: x,y,p ! kích thước mảng được để tự do .
real,intent(out) :: xg,yg
xg = (dot_product(p,x))/sum(p)
yg = (dot_product(p,y))/sum(p) ! Công thức tính trọng tâm .
end subroutine center
end module TrongTam
program main
Trong tam Xg Yg : 6.061 12.239
use TrongTam
implicit none
integer , parameter :: n = 10 Kết quả ngẫu nhiên .
real,dimension(n) :: x,y,p,r
real :: xg,yg
call random_seed ! Gọi thủ tục gieo số mầm ( seed value) .
call random_number ( r ) ! Gọi thủ tục tạo dãy số ngẫu nhiên 0. r < 1.
x = 10.*r ! Dãy x phân bố từ 0. x < 10.
call random_number ( r )
y = 20.*r ! Dãy y phân bố từ 0. y < 20.
call random_number ( r )
p = 5.*r ! Dãy p phân bố từ 0. p < 5.
call center(x,y,p,xg,yg)
print ‘(a,2f10.3)’,”Trong tam Xg Yg :”,xg,yg
read*
end
Trong chương trình trên , để tạo dãy số thực ngẫu nhiên cho các dãy số x, y , p ,dùng tính
toán trong chương trình chính , chúng ta gọi đến hai thủ tục :
random_seed là thủ tục dùng để gieo số mầm ( seed value ) và khởi động bộ tạo số ngẫu
nhiên .
random_number ( r ) là thủ tục tạo dãy số ngẫu nhiên nằm giữa 0. r < 1.
Các dãy số x,y,p được phân bố ngẫu nhiên trong phạm vi [0,10) , [0,20), [0,5) dựa vào giá
trị của số ngẫu nhiên r nhân với các cận của phạm vi phân bố .
Do sử dụng dữ liệu đầu vào thay đổi nên tọa độ trọng tâm Xg,Yg sẽ khác nhau ở mỗi lần
chạy chương trình .
127
8.8.4 Bạn đọc phân tích và chạy chương trình đổi tọa độ từ tọa độ cực sang tọa độ Descartes ( và
ngược lại ) như sau :
module changecoor
implicit none
contains
subroutine cart_to_polar( x, y, rad, phi )
real, intent(in) :: x, y
real, intent(out) :: rad, phi
rad = sqrt( x ** 2 + y ** 2 )
if ( x /= 0.0 .or. y /= 0.0 ) then
phi = atan2(y,x)
else
phi = 0.0
endif
end subroutine cart_to_polar
subroutine polar_to_cart( rad, phi, x, y )
real, intent(in) :: rad, phi
real, intent(out) :: x, y
x = rad * cos(phi)
y = rad * sin(phi)
end subroutine polar_to_cart
end module changecoor
program doitoado
use changecoor
implicit none
real, parameter :: pi = 3.14159
real :: rad,phi,x,y
rad = 1. ; phi = pi/4.
call polar_to_cart (rad,phi,x,y)
print '(2f7.4)', x,y
x = 1. ; y = 1.
call cart_to_polar ( x,y,rad,phi)
print '(2f7.4)', rad ,phi
read*
end program doitoado
Kết quả :
0.7071 0.7071
1.4142 0.7854
+ Bạn đọc tự viết module chuyển đổi từ tọa độ cầu sang tọa độ Descartes và ngược lại , sử dụng
các đoạn subroutine như sau :
128
subroutine cart_to_spherical ( x, y, z, rad, phi, theta )
real, intent(in) :: x, y, z
real, intent(out) :: rad, phi, theta
rad = sqrt ( x ** 2 + y ** 2 + z ** 2 )
theta = asin(z/rad)
if ( x /= 0.0 .or. y /= 0.0 ) then
phi = atan2(y,x)
else
phi = 0.0
endif
end subroutine cart_to_spherical
*****
129
CHƯƠNG 9 : KHÁI NIỆM VỀ CON TRỎ ( POINTER )
Giải thích :
+ Dòng số 3 : khai báo hai biến integer a , b được gán thuộc tính pointer là các con trỏ . Hai
biến này có thể trỏ đến các biến mục tiêu là số nguyên . Các chỉ thị a => null( ) , b => null ( ) đặt
tình trạng ban đầu cho các con trỏ không trỏ đến bất kỳ đối tượng nào .
+ Dòng số 4 : khai báo biến integer c được gán thuộc tính target .
+ Dòng số 7 : a => c , con trỏ a trỏ đến biến mục tiêu c , cả hai biến a và c cùng có một giá
trị là số 5 . Con trỏ a lúc này có thể được truy xuất như một biến nguyên thông thường .
+ Dòng 8 : in ra giá trị hiện thời của a là số 5 .
+ Dòng 10 : biến mục tiêu c được gán giá trị mới c = 10 .
+ Dòng 11 : b => c , con trỏ b trỏ đến biến c , lúc này cả hai biến con trỏ a, b và biến mục
tiêu c cùng có giá trị giống nhau là số 10 .
+ Dòng 12 : in ra giá trị hiện thời của a và b đều là số 10 .
+ Dòng 13 : d = a + b , hai con trỏ a , b đang trỏ đến biến c, hoạt động như hai biến nguyên
thông thường . Biến d sẽ có giá trị 10 + 10 = 20 .
+ Dòng 14 : in ra các giá trị a,b,c,d .
130
+ Ví dụ 02 : Fortran cung cấp hàm định nghĩa sẵn associated (biến_con_trỏ) có kiểu logical
dùng để test xem con trỏ đã được liên kết với đối tượng nào chưa ? . Chúng ta xem ví dụ sau :
program testpointer2
implicit none
integer, pointer :: a => null() , b => null()
integer, target :: c
integer :: d
print*,associated (a)
print*,associated (b) F
c=5 F
a => c a b c d : 10 10 10 20
c = 10 T
b => c T
d=a+b
print*,' a b c d : ',a,b,c,d Kết quả .
print*,associated (a)
print*,associated (b)
read*
end
Lúc đầu , các con trỏ a, b ở tình trạng null , không liên kết đến bất cứ đối tượng mục tiêu
nào nên các hàm associated ( ) trả về giá trị .false.
Sau khi các con trỏ a, b trỏ đến biến c , các hàm associated ( ) trả về giá trị .true.
Lưu ý : khi biến con trỏ a ở tình trạng null , a => null ( ) , nếu chúng ta cố tình truy xuất biến
a ( ví dụ bằng lệnh print*, a ) thì trình biên dịch sẽ báo lỗi .
Hàm nullify (biến_con_trỏ) có tác dụng đưa con trỏ về tình trạng null .
+ Ví dụ 03 : chúng ta có thể cấp phát vùng nhớ cho biến con trỏ a bằng lệnh allocate (a) ,
sau đó có thể sử dụng biến a trong các câu lệnh thông thường , lệnh deallocate (a ) phóng thích
vùng nhớ đã cấp phát cho biến a .
program testpointer3
implicit none
integer, pointer :: a => null() , b => null()
integer, target :: c
integer :: d
allocate (a) ! cấp phát vùng nhớ cho biến con trỏ a
a=5 ! sử dụng a như một biến nguyên bình thường .
c = 10
b => c
d=a+b
print*,' a b c d : ',a,b,c,d
deallocate (a) ! phóng thích vùng nhớ cho biến a .
read*
end
Kết quả :
a b c d : 5 10 10 15
131
Lưu ý : khi khai báo các biến con trỏ , chúng ta cần đặt các biến này vào tình trạng null và
tiến hành cấp phát ( allocate ) vùng nhớ một cách tường minh trước khi xử lý các biến này như
các biến thông thường . Lệnh deallocate ( ) thu hồi các vùng nhớ cấp phát cho con trỏ .
Chúng ta xem ví dụ sau :
program testpointer32
integer , pointer :: a => null () , b => null ()
integer, target :: c
integer :: d
allocate (a)
allocate (b)
a = 100
b = 200
print *,a,b ! kết quả in ra 100 và 200
c=1
a => c
c=2
b => c
d=a+b
print*,a,b,c,d ! in ra 2 2 2 4
read*
end
Nhận xét : Khi các biến con trỏ a, b trỏ đến biến mục tiêu c , các biến a, b này từ bỏ các giá
trị 100 , 200 khi chúng được gán ở phần đầu chương trình .
+ Ví dụ 04 : Bạn đọc phân tích chương trình dưới đây có con trỏ list là mảng ( một chiều )
số thực và các biến mục tiêu là các mảng số thực được cấp phát động .
program testpointer4
implicit none
real,dimension ( : ), pointer :: list ! mảng con trỏ kiểu số thực .
real,dimension ( : ), allocatable,target :: list1,list2 ! mảng mục tiêu được cấp phát động .
allocate ( list1 (1:100) , list2 (1:200) )
list => list1 ; print*,size (list) 100
list => list2 ; print*,size (list) 200
if (associated(list)) then Con tro da duoc lien ket .
print*,'Con tro da duoc lien ket .' Con tro chua duoc lien ket .
else
print*,'Con tro chua duoc lien ket .'
end if
nullify (list) ! tương đương list => null ( )
deallocate (list1,list2) ! phóng thích vùng nhớ .
if (associated (list)) then
print*,'Con tro da duoc lien ket .'
else
print*,'Con tro chua duoc lien ket .'
end if
read*
end
132
+ Ví dụ 05 : Bạn đọc phân tích chương trình sau và có nhận xét về các phép tính , phép gán
có toán hạng là các con trỏ :
program testpointer5
implicit none
integer,target :: i = 2,j = 3
integer,pointer :: pi,pj,pk
pi => i
pj => j
pk => i
print*,'i j pi pj pk : ',i,j,pi,pj,pk ! i j pi pj pk : 2 3 2 3 2
pi = 4.6 ! pi nhận giá trị int(4.6) cùng với i là 4 .
pj = pi + 10 ! pj nhận giá trị 14 cùng với j .
pk => j ! pk trỏ đến j nhận giá trị 14
print*,'i j pi pj pk : ',i,j,pi,pj,pk ! i j pi pj pk : 4 14 4 14 14
pi => pj ! pi trỏ đến pj , pj => j , pi trỏ đến số 14
print*,'pi pj pk : ',pi,pj,pk ! pi pj pk : 14 14 14
read*
end
+ Ví dụ 06 : Một số trình biên dịch Fortran cung cấp hàm loc ( biến ) dùng để in ra địa chỉ (
trong bộ nhớ ) của các biến . Chúng ta xem ví dụ sau :
program testloc
integer , pointer :: a => null () , b => null ()
integer, target :: c
integer :: d
allocate (a)
allocate (b) 100 200
a = 100 3150048
b = 200 3150088
print *,a,b 2293452
print*,loc (a) 2293456
print*,loc (b)
2224
print*,loc (c) 2293452
print*,loc (d) 2293452 a,b,c có cùng một địa chỉ ( của biến c).
c=1 2293452
a => c 2293456
c=2
b => c Kết quả chương trình testloc .
d=a+b
print *
print*,a,b,c,d
print*,loc (a)
print*,loc (b)
print*,loc (c)
print*,loc (d)
read*
end
*****
133
PHỤ LỤC A : SỬ DỤNG GNUPLOT ĐỂ VẼ ĐỒ THỊ 2D / 3D
Để có thể vẽ được các đồ thị thể hiện các mối quan hệ này , chúng ta có thể sử dụng các thư
viện ( các module ) đồ họa của các trình biên dịch Fortran ( thường là phần mềm có bản quyền )
hoặc một giải pháp đơn giản là dùng phần mềm đồ họa miễn phí gnuplot để thể hiện các đồ thị
2D/3D với các định dạng khác nhau . Chúng ta có thể download phần mềm này tại địa chỉ như sau
:
https://sourceforge.net/projects/gnuplot/files/latest/download
File dùng cài đặt : gp522-win64-mingw.exe ( 25,2 MB ) ; chúng ta chạy file này và chọn
thư mục chứa chương trình ( gnuplot có thể có các phiên bản mới hơn phiên bản nói trên ) .
Sau khi cài đặt xong , để khởi động chương trình gnuplot , chúng ta nhấn kép lên icon của
gnuplot hoặc vào thư mục gnuplot/bin/ chạy file wgnuplot.exe
134
Tập lệnh của gnuplot rất phong phú , cung cấp rất nhiều kiểu đồ thị với các định dạng
khác nhau , ở đây chúng ta chỉ thực hành những nội dung cơ bản nhất .
Hàm sin(x)
Dòng lệnh trên ra lệnh vẽ đồ thị hàm sin(x) với phạm vi thay đổi của đối số hình thức x từ
0 đến 2 .Cách viết các hàm , phép tính , hằng số trong gnuplot tương tự như trong văn bản
Fortran . Lưu ý : Gnuplot có phân biệt x và X là khác nhau .
3.2 Giả sử chúng ta muốn vẽ hàm sin2x , với -2 x 2 :
Nhấn phím mũi tên lên ( up arrow key ) trên bàn phím để lấy lại câu lệnh vừa mới vào ,
dùng các phím mũi tên phải, trái di chuyển con trỏ và sửa ( edit) lại nội dung như sau :
plot [-2*pi :2*pi] sin (2*x) xong <Enter>
135
Các phím mũi tên lên, xuống cho phép lấy lại các dòng lệnh đã vào , giúp tiết kiệm thời
gian vào lệnh .
136
Để đặt lại phạm vi thay đổi của x hoặc y , dùng các lệnh sau :
set xrange [x1: x2] hoặc set yrange [y1:y2]
Lệnh replot vẽ lại đồ thị hiện hành với tất cả các lệnh , nội dung vừa mới cập nhật .
3.4 Để xuất một đồ thị ra một file ảnh , ví dụ file có phần đuôi là *.jpeg , thực hiện như
sau :
gnuplot> set title 'Do thi y = sqrt(x*x+1) ‘
gnuplot> set xlabel 'X'
gnuplot> set ylabel 'Y'
gnuplot> set grid
gnuplot> plot [-4:4] [0:6] sqrt(x*x +1) xuất đồ thị ra màn hình để xem trước.
gnuplot> set terminal png ( chuyển hướng xuất đồ thị .)
gnuplot> set output ‘d:\work\dothi01.jpeg’ ( đặt tên file ảnh và đường dẫn)
gnuplot> replot ( xuất đồ thị ra tập tin ảnh )
gnuplot> set terminal windows ( trở về terminal windows , lệnh plot sẽ xuất kết quả ra
màn hình )
Lưu ý : terminal mặc định là windows ( xuất đồ thị ra màn hình máy tính ), ngoài ra có thể
set terminal postscript với file output có đuôi là *.ps . dãy lệnh như sau :
set terminal postscript
set output ‘d:\work\dothi02.ps’
replot
3.5 Xuất nhiều đồ thị trên cùng một cửa sổ .
Gnuplot cho phép vẽ nhiều đồ thị trên cùng một cửa sổ . Ví dụ :
gnuplot> set title 'Do thi ba ham : sinx, cosx, xsinx'
gnuplot> set grid
gnuplot> plot [-2*pi:2*pi] sin(x),cos(x),x*sin(x)
Lưu ý : các hàm viết cách nhau dấu phẩy .
137
4. Vẽ đồ thị từ kết quả được sắp xếp theo dạng cột trong một tập tin.
Chúng ta có file ‘d:\dataplot.dat’ từ chương trình Fortran sau :
program main
implicit none
real,dimension(10) :: x,y1,y2,y3
integer :: i
do i = 1,10
x(i) = real(i)
end do
y1 = x*x ! y1 = x2
y2 = x*x +3.*x ! y2 = x2 + 3x
y3 = sqrt(x*x +x ) ! y3 = x 2 x
open (10,file = 'd:\dataplot.dat')
do i = 1,10
write (10,'(4f10.2)') x(i),y1(i),y2(i),y3(i) ! ghi vào file d:\dataplot.dat
write (*,'(4f10.2)') x(i),y1(i),y2(i),y3(i) ! xuất ra màn hình để kiểm tra
end do
close(10)
read*
end
Ghi chú : các cột 1,2,3,4 trong file kết quả tương ứng với dãy số x,y1,y2,y3.
+ Để vẽ đồ thị x y1(x) bằng cách nối các điểm (x,y1(x)) , dùng lệnh sau :
set grid
set title ‘ Quan he y1=x**2 ‘
plot ‘d:\dataplot.dat ‘ using 1:2 with lines
Nhận xét : sau lệnh plot là tên file trong dấu ngoặc đơn ( kể cả đường dẫn ) ; cụm từ using
1:2 cho biết đồ thị dùng cột 1 (dãy x) và cột 2 ( dãy y1) ; cụm từ with lines cho biết đồ thị dùng
các đoạn thẳng để nối các điểm (x,y) lại với nhau .
138
Tương tự chúng ta có đồ thị thể hiện mối quan hệ giữa cột 1 và cột 3 :
set grid
set title ‘ Quan he y2=x**2 + 3*x ‘
set ylabel ‘y2’
plot ‘d:\dataplot.dat ‘ using 1:3 with linespoints
Ở đồ thị này , cụm từ with linespoints vừa dùng đoạn thẳng để nối , vửa có ghi dấu điểm
tại các tọa độ trong tập tin .
Đồ thị dưới đây vẽ cả 3 hàm trên cùng một cửa sổ , dùng mẫu lệnh :
plot ‘tên_file’ using 1:3 with linespoints , ‘tên_file’ using 1:2 with lines , \
‘tên_file’ using 1:4 with linespoints < Enter >
139
Lưu ý : Trong trường hợp câu lệnh dài cần phải viết ở dòng tiếp theo , chúng ta đặt dấu \
ở cuối câu xong <Enter> và viết tiếp câu lệnh .
Ghi chú : dòng có dấu # ở đầu là dòng ghi chú , gnuplot sẽ bỏ qua các dòng này .
Các lệnh đã trình bày ở các phần trước được nhóm lại thành một tập lệnh ( file dạng text )
và đặt vào một tập tin có tên d:\dothi001.txt .
Để thi hành tập lệnh này , tại dấu nhắc của gnuplot , đánh lệnh sau :
gnuplot> load ‘d:\dothi001.txt’ <Enter>
Lúc này toàn bộ các lệnh của tập tin được thực thi lần lượt giống như chúng ta vào lệnh ở
dòng command line trong chế độ tương tác . Nếu câu lệnh có lỗi, gnuplot sẽ đưa ra câu thông báo.
Kết quả sẽ có một đồ thị trên màn hình và một file ảnh y2y4.jpeg trên ổ đĩa D .
Cú pháp nạp một tập tin vào gnuplot : > load ‘<tên tập tin >’ [Enter]
140
Trong chương trình Fortran ở mục 4 , chúng ta có thể gọi gnuplot và nạp tập tin
dothi001.txt , dồ thị sẽ hiển thi khi chạy chương trình :
…..
open (10,file = 'd:\dataplot.dat')
do i = 1,10
write (10,'(4f10.2)') x(i),y1(i),y2(i),y3(i) ! ghi dữ liệu vào tập tin ở dạng cột.
write (*,'(4f10.2)') x(i),y1(i),y2(i),y3(i) ! ghi ra màn hình
end do
close(10)
call system ('d:\gnuplot\bin\wgnuplot d:\dothi001.txt')
read*
end
Dùng thủ tục call system với đối số là tên tập tin chương trình wgnuplot cùng đường dẫn
và tập tin script d:\dothi001.txt chứa tập lệnh dùng để vẽ đồ thị . Lưu ý chúng ta cần phải đặt lệnh
pause 10 ( dừng lại 10 giây ) ngay sau lệnh plot trong file script này để có đủ thời gian quan sát
đồ thị hoặc lệnh pause -1 , lúc này sau khi quan sát xong , chúng ta nhấn <Enter> để chương trình
tiếp tục .
# Tap tin d:\dothi001.txt
# Du lieu laytu file d:\dataplot.dat
# Ket qua xuat ra file anh d:\func_y2y4.jpeg
set title 'Quan he y2(x), y4(x)'
set ylabel 'y2 & y4'
set xlabel 'Thoi gian'
set grid
plot 'd:\dataplot.dat' using 1:2 with linespoints ,\
'd:\dataplot.dat' using 1:4 with linespoints
# lenh pause 10 : dung 10 giay de xem hinh hoac dung lenh pause -1
pause 10
# Tao file anh jpeg de luu do thi
set terminal png
set output 'd:\func_y2y4.jpeg'
replot
# Tro ve terminal windows
set terminal windows
141
z = -x3-y
142
7. Vẽ mặt thể hiện mảng hai chiều .
+ Để vẽ mặt thể hiện mảng 2 chiều f(i,j) , với i,j : 1..n , ví dụ hàm z = x2 + y2 với x,y lấy
các giá trị 1,2,3,4 , chúng ta sắp xếp file dữ liệu như sau , lưu ý sau khi ghi các giá trị của cột thứ
1 của ma trận f (i,j) , chúng ta phải để một dòng trống .
# z = x**2 + y **2
# d:examsp.dat
1 1 2
2 1 5
3 1 10
4 1 17
! Dòng này phải để trống.
1 2 5
2 2 8
3 2 13
4 2 20
! Dòng này phải để trống.
1 3 10
2 3 13
3 3 18
4 3 25
! Dòng này phải để trống.
1 4 17
2 4 20
3 4 25
4 4 32
Ghi chú : gnuplot cho phép viết tắt trong câu lệnh , ví dụ u thay cho using , w thay cho with .
143
8. Vẽ véc tơ trong mặt phẳng 2D .
Để vẽ một trường véc tơ , tập tin dữ liệu phải bố trí số liệu thành 04 cột : cột 1 và 2 cho
biết tọa độ điểm gốc của véc tơ , cột 3 và 4 cho biết thành phần số ( tọa độ ) của véc tơ theo
phương X và phương Y theo định dạng như sau :
Tọa độ x Tọa độ y Thành phần số X Thành phần số Y
Lệnh vẽ các véc tơ trong tập tin ‘d:\vevecto.dat’ như sau :
plot ‘d:\vevecto.dat’ using 1:2:3:4 with vectors head filled lt 3 lw 3
Vẽ vectơ .
Ngoài các đồ thị vẽ theo hệ tọa độ Descartes thông thường , gnuplot còn cho phép vẽ các
đường cong tham số , đường cong trong hệ tọa độ cực , biểu diễn số liệu có kèm theo sai số , biểu
diễn số liệu theo dạng thống kê, tạo ảnh động ( animation ) ... Bạn đọc quan tâm chi tiết , có thể
download các tài liệu mới nhất về gnuplot trên internet để nghiên cứu , tham khảo thêm .
*****
144
PHỤ LỤC B : CHƯƠNG TRÌNH LINPACK GIẢI HỆ n
PHƯƠNG TRÌNH TUYẾN TÍNH Ax = b BẰNG PP KHỬ GAUSS
module kinds
implicit none
integer,parameter :: r = selected_real_kind (15,307)
end module kinds
module ppgauss
implicit none
contains
subroutine gauss(a,n,b,x,suybien)
use kinds
implicit none
!Khai bao cac doi so (arguments )
integer,intent(in) :: n
real(r),intent(inout) :: a(:,:),b(:)
real(r),intent(out) :: x(:)
logical,intent(out) :: suybien
!Khai bao bien cuc bo
integer :: i,j,k,pivot_row
real(r):: pivot,sum,trunggian
real(r),parameter :: eps = 1.e-13_r
! Xu ly theo cot
do k = 1,n-1
! Tim dong co phan tu lon nhat cua cot k de pivot
pivot_row = maxval(maxloc(abs(a(k:n,k)))) + k - 1
! Test xem ma tran a co suy bien
! Neu co thi tro lai main program
if (abs(a(pivot_row,k)) <= eps) then
suybien = .true.
return
else
suybien = .false.
end if
! Doi cho phan tu o cot k neu phan tu
! lon nhat khong tren duong cheo chinh
if (pivot_row /= k) then
trunggian = a(pivot_row,k)
a(pivot_row,k) = a(k,k)
a(k,k) = trunggian
145
trunggian = b(pivot_row)
b(pivot_row) = b(k)
b(k) = trunggian
end if
! Xu ly cac phan tu duoi duong cheo
a(k+1:n,k) = a(k+1:n,k) /a(k,k)
! Khu theo hang duoc thuc hien theo cot
do j = k+1,n
pivot = a(pivot_row,j)
if (pivot_row /= k) then
a(pivot_row,j) = a(k,j)
a(k,j) = pivot
end if
a(k+1:n,j) = a(k+1:n,j) - pivot*a(k+1:n,k)
end do
b(k+1:n) = b(k+1:n) - a(k+1:n,k)*b(k)
end do
! Tinh nghiem : thay the theo chieu nguoc
do i = n,1,-1
sum = 0.0
do j = i + 1,n
sum = sum + a(i,j)*x(j)
end do
x(i) = (b(i)-sum)/a(i,i)
end do
end subroutine gauss
end module ppgauss
program main
use kinds
use ppgauss
implicit none
integer :: i,n
real(r),allocatable :: a(:,:), b(:),x(:)
logical :: suybien
print*,'Cho biet so phuong trinh : '
read*,n
allocate (a(1:n,1:n),b(1:n),x(1:n))
do i = 1,n
print*,'Vao phan tu dong so ',i,' cua ma tran A :'
read*,a(i,1:n)
print*,'Vao phan tu so ',i,' cua vecto b :'
read*,b(i)
end do
call gauss(a,n,b,x,suybien)
if (suybien) then
print*,'Matran a suy bien.'
else
print*,'Nghiem so x : '
print*,x(1:n) ! Xuat ket qua n nghiem so
end if
read*
end program main
146
2. Chạy test chương trình .
*****
147
TÀI LIỆU THAM KHẢO
1.Alexandre Mayer . Cours de programmation Fortran 90. Université de Namur - FUNDP ,
Bruxelles – Belgique .
3. Ian Chivers – Jane Sleightholme . Introduction to programming with Fortran , with coverage of
Fortran 90,95,2003,2008 and 77 . Springer .2012 .
4. Hans Lee – Paul Munsell .The design and implementation of programs in FORTRAN 77 .
Prentice Hall International Editions. 1990.
5. Võ Văn Hoàng . Ngôn ngữ lập trình Fortran .Nhà xuất bản Giáo dục . 2007.
6. Thomas Williams – Colin Kelley . Gnuplot 5.0 : An interactive Plotting Program . 2017 .
*****
148
MỤC LỤC
CHƯƠNG 5 : CÁC LỆNH NHẬP / XUẤT VÀ ĐỊNH DẠNG DỮ LIỆU .
5.1 Các lệnh xuất dữ liệu ra màn hình .
5.2 Các lệnh nhập dữ liệu từ bàn phím .
5.3 Cách viết phần định dạng trong các lệnh xuất / nhập dữ liệu .
5.3.1 Định dạng cho số nguyên
5.3.2 Định dạng cho số thực dạng dấu chấm tĩnh .
5.3.3 Định dạng cho số thực dạng bậc .
5.3.4 Định dạng cho giá trị kiểu logical .
5.3.5 Định dạng cho giá trị kiểu xâu kí tự .
5.3.6 Các nội dung định dạng khác .
5.4 Ghi / đọc dữ liệu sử dụng tập tin ( file ).
5.4.1 Giới thiệu chung về tập tin .
5.4.2 Ví dụ về dữ liệu dạng tập tin .
5.4.3 Quản lý các lỗi khi mở tập tin .
5.4.4 Các tham số đi kèm với lệnh mở tập tin open .
5.4.5 Đọc dữ liệu từ tập tin .
5.4.6 Ghi dữ liệu ra tập tin .
150
5.4.7 Lệnh rewind .
5.4.8 Lệnh đóng tập tin .
5.4.9 Khái niệm về tập tin trong (internal file ) .
5.4.10 Tập tin không được định dạng ( unformatted file )
5.4.11 Tập tin truy cập trực tiếp .
5.4.12 Sử dụng namelist để chứa dữ liệu .
5.4.13 Sử dụng lệnh inquire để truy vấn thông tin .
5.5 Thực hành .
PHỤ LỤC B : CHƯƠNG TRÌNH LINPACK GIẢI HỆ n PHƯƠNG TRÌNH TUYẾN TÍNH
Ax = b BẰNG PP KHỬ GAUSS .
1.Văn bản chương trình .
2.Chạy test chương trình .
MỤC LỤC .
*****
152