Tập tin lập trình trên Free Pascal sẻ có phần mở rộng mặc định là
Chắc suất Đại học top - Giữ chỗ ngay!!ĐĂNG BÀI NGAY để cùng trao đổi với các thành viên siêu nhiệt tình & dễ thương trên diễn đàn. Người viết: Thầy Nguyễn Thanh Tùng Mã: Câu trả lời là: TP là môi trường lập trình 16 bit trên HĐH DOS do đó nó có nhiều hạn chế. Han chế thứ nhât là kích thước của biến và kiểu &l; 64KB, trong đó có biến mảng và kiểu mảng. Đó là do dùng số 16 bit thì chỉ có thể chỉ số hoá được 216 = 64K giá trị thôi. Khi ta khai báo max = 1000 thì mảng L của ta có kích thước 1000x1000x2 (2 là kích thước kiểu integer)=2.106>>64K nên TP báo lỗi "structure too large" (kiểu cấu trúc quá lớn) là đúng rồi. Vậy bây giờ thay vì khai báo mảng L là mảng 2 chiều, ta sẽ khai báo L thành rất nhiều mảng nhỏ hơn. Bạn cứ thử thế mà xem. Nếu TP không báo lỗi "structure too large" thì cũng báo lỗi là "too many varibles". Đó là do hạn chế thứ 2 của TP: tổng kích thước các biến toàn cục (global) cũng ≤ 64KB . Bạn có chia L thành bao nhiêu mảng con thì TP vẫn bắt tổng kích thước của chúng ≤ 64KB. Vẫn còn giải pháp nữa: thay vì dùng mảng tĩnh thì dùng mảng động. Khai báo L là mảng 1000 con trỏ, mỗi con trỏ trỏ đến một mảng 1000 phần tử. (L:Array[1...max] of ^mang1). May quá, TP không báo lỗi khi dịch. Nhưng khi chạy thì ôi thôi, lỗi "Heap overflow" (tràn heap). Nguyên nhân là hạn chế của DOS: toàn bộ bộ nhớ DOS có thể sử dụng ≤ 640 KB. Mà các chương trình hệ thống và IDE của TP cũng chiếm mất hơn 300KB rồi. Tức là chương trình của bạn dù có tận dụng hết bộ nhó còn lại cũng chỉ được 300KB nữa thôi. (Khi bạn nhấn F9 để dịch, TP báo xxxKB free memory, đó chính là phần heap tối đa hệ thống có thể cấp phát cho các biến động đó). Vẫn còn nhiều giải pháp có thể giải quyết: dùng 2 mảng một chiều tính lẫn nhau và đánh dấu lần vết bằng bit; ghi ra file; dùng mảng răng lược… Nhưng dù sao thì cũng chỉ là giải pháp tình thế, hơn nữa lại rất phức tạp. Giải pháp tốt nhất là dùng một môi trường lập trình mạnh hơn. Và IOI đã chọn FP. Tôi đem chương trình trên với khai báo max =1000 chạy trên FP và mọi chuyện đều ổn, chẳng có lỗi nào xảy ra hết. Đối với FP, bộ nhớ không bị hạn chế bởi con số 64KB nữa (free mà). Điều đó có được là nhờ những đặc tính tuyệt vời của FP: a. FP là môi trường lập trình 32 bit. Dùng một số 32 bit thì có thể chỉ số hoá được 232 = 4G giá trị, vậy nên biến trong FP có thể có kích thước 4GB. Các bạn chú ý: 4GB=4x1024MB. Trong khi đó máy tính chúng ta thường dùng thường có chừng 128MB RAM. Mảng L kích thước ≤ 2MB thì nhằm nhò gì. b. FP là môi trường lập trình chạy trên nền các HĐH 32 bit (Windows, Linux, BeOS, OS/2… và cả DOS nữa. Nhưng đó là phiên bản DOS 32 bit mở rộng). Đây cũng là điều quan trọng. Vì nếu cho FP chạy trên DOS 16 bit (nếu có chạy được), thì với bộ nhớ chật hẹp 640KB, FP cũng phải bó tay không phát huy được tài năng. Ngược lại do TP là 16 bit, nên có cho chạy trên Windows 32 bit, thì cũng chỉ phát huy được tài năng đến mức của 16 bit mà thôi. Chạy trên môi trường 32 bit, ngoài RAM (đã rất nhiều), HĐH còn có có chế bộ nhớ ảo (virtual memory) sử dụng một phần HĐ làm bộ nhớ tạm nên FP có thể cung cấp cho bạn dung lượng nhớ có thể nói là thoải mái (free mà). c. FP là tương thích hoàn toàn với TP. Đây cũng là một điều thú vị. Chương trình ở phàn trên tôi viết trong TP, đem sang FP vẫn chạy ngon lành, chẳng phải sửa đổi gì hết (thực ra thì có sửa giá trị max từ 100 thành 1000). IDE của FP thì giống hệt TP (tất nhiên có nhiều chức năng tiên tiến hơn, nhưng những gì bạn làm được với TP đều làm được trên FP). Tôi nghĩ vậy là quá đủ để chúng ta thay thế TP bằng FP. Nếu bạn còn băn khoăn, hãy đợi các bài viết tiếp theo để tìm hiểu tiếp về những điều kì diệu mà FP có còn TP thì không. Còn nếu bạn háo hức muốn dùng thử, hãy làm theo chỉ dẫn cài đặt dưới đây. 2. Cài đặt và sử dụng FP a. Tải bộ cài đặt FP có rất nhiều phiên bản, cả các phiên bản đã sử dụng chính thức và phiên bản còn đang phát triển. Theo tôi thì các bạn nên sử dụng các phiên bản chính thức vì chúng ổn định hơn. (Tôi hiện đang dùng phiên bản 1.0.6). Để cài đặt bạn vào website của ISM (http://www.thnt.com.vn), hoặc nếu thích có thể đến thẳng website của FP (http://www.freepascal.org), vào mục download và tải file zip chứa bộ cài. Chú ý là có nhiều phiên bản của FP cho các hệ điều hành khác nhau nên bạn phải chú ý: - Tải bộ cài các phiên bản chính thức. Các phiên bản chính thức có chữ số cuối cùng là số chẵn (như bản của tôi là 1.0.6, có thể bây giờ có nhiều bản mới hơn rồi). - Tải bộ cài cho HĐH Windows và DOS. Bạn có thể nhận biết điều này qua tên file zip. Như file của tôi là dosw32106full.zip, có nghĩa là bộ cài đầy đủ cho HĐH Windows và DOS, phiên bản 1.0.6. b. Unzip và Install Sau khi đã tải được bộ cài (tất cả ở trong một file zip), bạn unzip file đó ra một folder rồi chạy file Install.exe. Giao diện cài đặt hiện ra, yêu cầu bạn lựa chọn thư mục cài đặt cho FP (mặc định là C Chú ý: với kiểu Integer, mặc định FP dùng kích thước 2 byte. Vì vậy khi muốn dùng kiểu nguyên lớn, ta nên khai báo rõ ràng. 2. Kiểu string lớn Khi lập trình, chúng ta rất nhiều lần gặp vấn đề với các xâu tối đa 255 kí tự của TP (chẳng hạn bài toán xâu đối xứng, bài toán đếm từ…). Ta có thể giải quyết vấn đề bằng mảng kí tự (array of char) nhưng khi đó ta lại không thể dùng các phép toán trên xâu rất mạnh của Pascal. Không chỉ có cải tiến về kiểu nguyên, kiểu string trong FP cũng rất tuyệt vời. String trong FP không còn hạn chế 255 kí tự của TP nữa mà có kích thước tối đa là 2.. tỉ kí tự. Hơn nữa FP còn hỗ trợ kiểu xâu Unicode (WideString). Nếu bạn vẫn thích kiểu String cũ của TP, bạn có thể dùng kiểu ShortString. Bây giờ bạn có thể viết chương trình giải bài xâu đối xứng, xâu con chung với kiểu string của trên FP và hạn chế n cỡ 1000 một cách rất dễ dàng. Chúng ta sẽ tìm hiểu kĩ hơn về xâu trong FP ở một bài báo khác. 3. Viết hàm thuận lợi hơn FP có rất nhiều cải tiến trong cách viết các hàm. Để so sánh, chúng ta sẽ xem xét một số ví dụ. Trong TP, chúng ta viết hàm tính giai thừa như sau: Mã: Tại sao ta lại phải thêm một biến tg để lưu trữ kết quả trung gian? Đó là do trong TP với tên hàm ta chỉ được sử dụng duy nhất lệnh gán trị. Nếu đưa tên hàm vào biểu thức thì sẽ thực hiện lời gọi hàm.Điều này đã được FP làm cho free bằng cách cho phép sử dụng tên hàm như một biến (giống như Object Pascal dùng biến Result). Khi đó tên hàm có thể xuất hiện ở trong cách biểu thức tính toán ngay trong thân hàm mà không tạo ra lời gọi đệ quy. Hàm giai thừa trong FP có thể viết rất tiết kiệm biến như sau: Mã: Vậy khi ta muốn gọi đệ quy thì sao? Thì chỉ việc thêm cặp dấu () và truyền tham số cần thiết. FP sẽ biết ta muốn gọi đệ quy khi ta có thêm cặp (). Hàm giai thừa viết kiểu đệ quy như sau:Mã:
Chẳng hạn hàm tìm vị trí của phần tử x trong mảng a có n phần tử. Viết trong TP ta phải viết như sau: Mã: Hàm này viết trong FP thì ngắn ngọn hơn nhiều:Mã: 4. Kết quả trả lại của hàm có thể là kiểu cấu trúcRất nhiều ngôn ngữ lập trình thông dụng như C, VB… chỉ cho phép kết quả trả lại của hàm là các kiểu cơ sở như: số nguyên, số thực, kí tự, con trỏ… Riêng TP thì có thêm kiểu xâu kí tự. Nếu muốn trả lại kết quả là kiểu cấu trúc như mảng hay bản ghi thì bạn chỉ có cách dùng tham biến thôi. Một số NNLT hướng đối tượng như C++, Java, Object Pascal có cho phép kết quả trả lại là một đối tượng, nhưng như vậy vẫn không free như FP: FP cho phép kết quả của hàm có thể là kiểu cấu trúc. Để hiểu hơn chúng ta cùng làm bài toán sau (Đề thi OLP2004): Gọi X là tập tất cả các xâu nhị phân không có 2 bit 1 liền nhau. Các xâu được đánh thứ tự theo trình tự được sinh ra (từ nhỏ đến lớn, bit 0 đứng trước bit 1). Chẳng hạn với n=5 ta có các xâu sau: Bài toán đặt ra: hãy xác định xâu nhị phân n bit ứng với số thứ tự m cho trước. Hạn chể: n <= 200. Bài toán này có thuật giải như sau: Gọi L[k] là số các xâu nhị phân như vậy có k bit. Nếu bit thứ k của nó là bit 0 thì k-1 bit còn lại là tự do (tức là ta có L[k-1] dãy). Nếu thứ k của nó là bit 1 thì bit k -1 phải là 0, và k-2 bit còn lại free. Vậy ta có: L[k] = L[k-1] + L[k-2]. Chú ý: L[1]=2 và L[2]=3. Có công thức đó, ta tính số các xâu có n bit. Để xác định xâu nhị phân n bit có thứ tự m cho trước ta có nhận xét: nếu m > L[n-1] thì nhất định bit thứ n phải là 1 (vì thứ tự của xâu có bit 0 đứng trước xâu có bit 1, và có đúng L[n-1] xâu có bit thứ n là bit 0). Xâu n-1 bit còn lại có sẽ thứ tự là m-L[n-1]. Ngược lại thì bit thứ n là bit 0 và xâu n-1 bit còn lại có thứ tự là m. Do đó ta có thể làm như sau: Mã: Tuy nhiên n có thể bằng 200 nên m và các giá trị L có thể xấp xỉ 2200, tức là cỡ 1070 (vì 210 xấp xỉ 103 ). Ta không thể dùng các kiểu số có sẵn mà phải tự xây dựng một kiểu số lớn hơn.Có nhiều người thích dùng xâu để biểu diễn số lớn, nhưng tôi thấy dùng mảng thì thích hợp hơn. Ta dùng mảng biểu diễn số nguyên lớn, mỗi phần tử của mảng là một chữ số. Các chữ số lưu trữ trong mảng theo chiều từ trái sang phải: chữ số hàng đơn vị là phần tử 1, chữ số hàng chục là phần tử 2… Ta khai báo kiểu số lớn đó như sau: Mã: Để cộng, trừ 2 số nguyên lớn biểu diễn bằng mảng, ta dùng thuật toán cộng kiểu thủ công (cộng các chữ số từ bậc thấp đến bậc cao, có nhớ). Các thủ tục cộng, trừ viết trong TP như sau:Mã: Ta khai báo L là mảng 200 phần tử kiểu BigInt. Gán L[1] là 2, L[2] là 3 rồi tính các phần tử khác bằng câu lệnh như sau:for i:=3 to n do cong(L[i-1],L[i-2],L); Viết như vậy thì chương trình hoạt động tốt, nhưng không trực quan lắm. Nếu có thể viết L:=cong(L[i-1],L[i-2]) thì sẽ trong sáng hơn nhiều. Trong TP thì không thể, nhưng trong FP hoàn toàn có thể viết được như vậy. Sau đây là toàn bộ chương trình nguồn giải bài toán này viết bằng FP: Mã:
Trong chương trình này, ngoài 2 hàm cộng, trừ, tôi còn viết thêm 1 hàm để chuyển một xâu biểu diễn số nguyên thành một số nguyên kiểu BigInt và một hàm để so sánh 2 số kiểu BigInt. Mã: 0Vấn đề tạm thời được giải quyết. Tuy nhiên lại gặp một vấn đề mới là đối số truyền cho hàm area bắt buộc sẽ phải có kiểu TArr1. Vấn đề thứ hai là khai báo mảng phải xác định trước số phần tử, sẽ gây bất tiện khi số phần tử thực sự nhiều hơn hoặc ít hơn.FP có một cải tiến là cho phép khai báo tham số của chương trình là kiểu mảng mở. Chẳng hạn hàm area trên có thể khai báo trong FP như sau: function areăx,y: array of real): real; Vậy khi chạy làm thế nào để xác định được số phần tử thực sự của tham số? Cách đơn giản nhất là chúng ta thêm một tham số nữa để mô tả số phần tử. Nếu không thích như vậy, chúng ta có thể dùng hàm Low và High để xác định chỉ số đầu và cuối của mảng. Thông thường thì với mảng mở, chỉ số đầu là 0, chỉ số cuối là n-1 với n là số phần tử của mảng (giống như trong ngôn ngữ C). Sau đây là một ví dụ về hàm tính diện tích đa giác đơn bằng phương pháp hình thang viết trong FP: Mã: 1Cách viết hàm có sử dụng một số cải tiến của FP: toán tử C-like, biến result là giá trị trả lại của hàm.Trong TP cũng có kiểu mảng mở như vậy (không tin bạn có thể thử khai báo). Tuy nhiên mảng mở trong FP có một điều thú vị mà trong TP không có, đó là mảng tạo trong khi chạy. Để dễ hiểu, bạn hãy quan sát lời gọi hàm area của tôi đối với một tứ giác có 4 đỉnh là Ăx1,y1), B(x2,y2), C(x3,y3), D(x4,y4). s := areă[x1,x2,x3,x4], [y1,y2,y3,y4]); Với lời gọi đó, khi chạy, chương trình sẽ tạo một mảng x gồm 4 phần tử x1,x2,x3,x4 và mảng y với 4 phần tử y1,y2,y3,y4. Nếu không có cách viết này, ta sẽ phải khai báo 2 mảng x,y và gán các phần tử vào một cách thủ công. Cách viết [a1,a2,...,an] được FP hiểu là một mảng mở ngoài cách hiểu thông thường là một tập hợp. 2. Con trỏ Ai đã từng sử dụng ngôn ngữ C, nếu không cảm thấy khó khăn thì sẽ rất thích thú với sự uyển chuyển của con trỏ trong C: chẳng hạn có thể thực hiện các phép toán số học cộng, trừ với con trỏ, có thể dùng con trỏ như mảng. Con trỏ trong TP thì không được uyển chuyển như vậy. Trong TP ta chỉ có thể thực hiện một số phép toán như: gán trị cho con trỏ, so sánh 2 con trỏ hay truy cập vào phần tử mà con trỏ trỏ đến. FP đã cải tiến và con trỏ trong FP bây giờ uyển chuyển như trong C. Chúng ta có thể hiểu con trỏ trong FP như một biến nguyên 32 bit, giá trị của nó là một địa chỉ trong bộ nhớ. Do đó FP có một số phép toán đối với con trỏ như sau: Phép cộng : nếu p là một con trỏ kiểu X, i là một số nguyên, thì p+i cũng là một con trỏ kiểu X và p+i trỏ đến biến có địa chỉ cách biến p trỏ đến i phần tử kiểu X. Chẳng hạn nếu X là kiểu Int64, thì p+1 sẽ trỏ đến biến Int64 tiếp theo biến mà p trỏ đến. Phép trừ : nếu p, q là 2 con trỏ cùng kiểu thì p-q là độ lệch của chúng, có giá trị bằng số phần tử trong khoảng đó nhân với kích thước của kiểu mà chúng trỏ đến. Phép tham chiếu : nếu p là một con trỏ thì p^ là biến mà p trỏ đến. Ta cũng có p+i là con trỏ nên (p+i)^ cũng là biến mà p+i trỏ đến. Chú ý là cách viết này rất thường gặp trong C, nhưng trong TP thì hoàn toàn không có. FP còn cải tiến cho phép viết (p+i)^ dạng p, nghĩa là hoàn toàn giống C. Chú ý là p^ có thể viết là p[0]. Như vậy ta có thể coi một con trỏ như một mảng động. Ta có thể dùng các lệnh GetMem, FreeMem để cấp phát động bộ nhớ và dùng cú pháp của mảng để truy cập. Chương trình sau demo tính năng sử dụng con trỏ như mảng trong FP: Mã: 2Với tính năng này, chúng ta có thể sử dụng các con trỏ như các mảng động (dynamic array), tức là khai báo mảng mà không cần xác định trước số phần tử, sau đó mảng được cấp phát động trong quá trình thực thi.Trong TP chúng ta dùng từ khoá absolute để xác định một vị trí cố định trong bộ nhớ. Khai báo đó gọi là địa chỉ tuyệt đối. Chẳng hạn để đo thời gian chạy của chương trình, người ta khai báo biến time kiểu longint tại địa chỉ 0:$46C, bởi vì vị trí đó là nơi máy tính lưu bộ đếm của đồng hồ. Tần số cập nhật của nó là 18.2 lần/giây. Một chương trình ví dụ của TP như sau: Mã: 3Trên máy tính của tôi, chương trình đơn giản này chạy mất gần 6 giây (5.814 giây).FP chạy trong môi trường 32 bit, ở chế độ bảo vệ. Trong chế độ bảo vệ, không có khái niệm địa chỉ tuyệt đối (để giải thích kĩ càng về điều này, chắc cần một bài báo rất dài). Để sử dụng khai báo absolute, ta phải khai báo sử dụng unit go32. Unit go32 cho phép thao tác vào vùng nhớ của DOS. Tất nhiên là chỉ có chương trình được dịch để chạy trên DOS 32 bit (target là DOS GO32v2) thì mới sử dụng được. Chương trình trên được sửa để chạy trên FP như sau: Mã: 4Thật kinh ngạc: trên máy tính của tôi chương trình đơn giản này chạy mất 0.000 giây!!! Tôi tăng hằng số 10000000 lên 10 lần (thêm một chữ số 0 vào) thì chương trình chạy trong 0.440 giây. Như vậy so với TP, FP nhanh hơn rất nhiều. Tôi chỉ có thể phỏng đoán là FP sinh ra chương trình mã 32 bit, lại được tối ưu hoá mã cho Pentium nên nhanh hơn TP vốn chỉ sinh mã 16 bit và hoàn toàn không có chức năng tối ưu mã. Đây có lẽ cũng là một lí do rất thuyết phục để thay thế TP bằng FP.3. Đồ hoạ trong FP TP phiên bản cuối cùng là 7.0 ra đời năm 1992. Vào hồi đó, phần cứng đồ hoạ còn khá yếu nên TP 7.0 chỉ hỗ trợ chế độ đồ hoạ cao nhất là 640x480x4 bit (16 màu). Bây giờ là năm 2004, đồ hoạ máy tính đã rất mạnh, nhưng nếu dùng TP thì ta cũng chỉ dùng được chế độ cao nhất đó thôi. Nếu có driver SVGA (SVGA256.BGI) thì ta có thể sử dụng được các chế độ 256 màu. Nhưng nếu dùng FP, chúng ta có thể có chế độ đồ hoạ cao hơn nhiều. Theo thử nghiệm thì tôi đã dùng được chế độ 800x600x16bit (64K màu) trong FP. Nghĩa là chẳng không thua kém nhiều các môi trường lập trình trên Windows. Unit Graph của FP tương thích hoàn toàn TP. Như vậy chúng ta vẫn có thể dùng các chương trình đồ hoạ được viết trên TP để dịch lại và chạy trong FP mà không cần sửa đổi gì. Hơn nữa, chúng ta có thể sử dụng những mode đồ hoạ với độ phân giải và số màu nhiều hơn với những thao tác vẽ đơn giản và quen thuộc của TP. Bảng sau là các driver và mode đồ hoạ mới trong FP. Chú ý là do FP là đa môi trường (multi platform), tức là sinh mã cho nhiều hệ điều hành và hệ máy khác nhau, do đó có thể những chế độ đồ hoạ không được một số hệ nào đó hỗ trợ. Mã: 54. Xử lí lỗi ngoại lệPhần lớn các ngôn ngữ lập trình cao cấp đều có hỗ trợ xử lí lỗi ngoại lệ. Chẳng hạn Visual Basic có lệnh On Error, C++, Java có nhóm lệnh try catch, Delphi, FP có nhóm lệnh try... except. Nói một cách đơn giản là: trong khi phương pháp xử lí lỗi truyền thống né tránh lỗi (tức là kiểm tra trước để đảm bảo không có lỗi thì mới làm) thì phương pháp lập trình xử lí lỗi ngoại lệ là thực hiện bình thường và dự phòng trước tình huống nếu gặp lỗi. Chúng ta có thể xét một ví dụ là thực hiện một phép chia biến a cho biến b. Trong TP ta sẽ viết như sau: if b <> 0 then c := a/b else write ('Error: b=0.'); Trong FP, sử dụng tính năng xử lí lỗi ngoại lệ, chúng ta có thể viết như sau: try c := a/b; except on EDivByZero do begin c := 0; write('Error.'); end; end;
Các bạn có thể nói: như vậy rõ ràng phức tạp, rắc rối hơn. Tất nhiên, tôi đồng ý. Nhưng hãy nhìn vào ưu điểm của phương pháp xử lí lỗi ngoại lệ so với phương pháp truyền thống: - Phương pháp kiểm tra không phải lúc nào cũng lường trước được mọi tình huống và thường phải thực hiện những phép kiểm tra rất tốn thời gian. Phương pháp xử lỗi ngoại lệ không cần những kiểm tra như vậy nên đơn giản hơn. Hơn nữa có những tình huống phải thực hiện rồi mới biết là có lỗi (chẳng hạn đọc file, cộng 2 số..) - Phương pháp truyền thống xử lí lỗi một cách không thống nhất: ở mức nào của chương trình bạn cũng phải có những đoạn trình xử lí riêng và mỗi lỗi cũng phải có đoạn trình xử lí riêng. Với phương pháp xử lí lỗi ngoại lệ ta có thể chủ động đưa lỗi lên mức xử lí cao nhất, thậm chí tự gây ra lỗi để chuyển quyền điều khiển (bằng lệnh raise).
|