Đây khôngphải là một sản phẩm mới như ta tưởng mà trong nó còn tồn tại những lỗi khôngthể bỏ qua được, vì ngay MS-DOS 6.0 cũng chỉ là sự khắc phục hạn chế của MS-DOS 3.3 ban đầu Trong th
Trang 1MỤC LỤC
CHƯƠNG 1: TỔNG QUAN VỀ PHƯƠNG PHÁP LẬP TRÌNH CÓ CẤU
TRÚC 3
1.1 SƠ LƯỢC VỀ LỊCH SỬ LẬP TRÌNH CÓ CẤU TRÚC 3
1.2 CẤU TRÚC LỆNH, LỆNH CÓ CẤU TRÚC, CẤU TRÚC DỮ LIỆU 5
1.2.1 Cấu trúc lệnh (cấu trúc điều khiển) 5
1.2.2 Lệnh có cấu trúc 7
1.2.3 Cấu trúc dữ liệu 7
1.3 NGUYÊN LÝ TỐI THIỂU 9
1.3.1 Tập các phép toán 9
1.3.2 Tập các lệnh vào ra cơ bản 12
1.3.3 Thao tác trên các kiểu dữ liệu có cấu trúc 12
1.4 NGUYÊN LÝ ĐỊA PHƯƠNG 14
1.5 NGUYÊN LÝ NHẤT QUÁN 16
1.6 NGUYÊN LÝ AN TOÀN 17
1.7 PHƯƠNG PHÁP TOP-DOWN 20
1.8 PHƯƠNG PHÁP BOTTOM-UP 24
CHƯƠNG 2: NGUYÊN TẮC LẬP TRÌNH, GỠ RỐI VÀ CẢI TIẾN HIỆU SUẤT CHƯƠNG TRÌNH 29
2.1 Phong cách lập trình 29
2.2 Các nguyên tắc lập trình 29
2.3 Các chuẩn trong lập trình 35
2.4 Gỡ rối chương trình 36
2.4.1 Ý đồ thiết kế sai 36
2.4.2 Phân tích các yêu cầu không đầy đủ và lệnh lạc (xảy ra ở giai đoạn 1)37 2.4.3 Hiểu sai về chức năng 37
2.4.4 Lỗi tại các đối tượng chịu tải 38
2.4.5 Lỗi lây lan 39
2.4.6 Lỗi cú pháp 39
2.4.7 Hiệu ứng phụ (Side Effect) 40
2.5 Cải tiến hiệu xuất chương trình 41
2.5.1 Tốc độ xử lý 41
Trang 2CHƯƠNG 3: DỮ LIỆU KIỂU CẤU TRÚC VÀ SẮP XẾP 52
3.1 Dữ liệu kiểu cấu trúc 52
3.1.1 Kiểu cấu trúc 52
3.1.2 Con trỏ kiểu cấu trúc 59
3.1.3 Mảng cấu trúc 62
3.1.4 Các cấu trúc tự trỏ và danh sách liên kết 76
3.2 Thuật toán sắp xếp 85
3.2.1 Đặt vấn đề 85
3.2.2 Các giải thuật sắp xếp cơ bản 86
3.2.3 Các giải thuật sắp xếp nhanh 95
Bài tập chương 3 99
CHƯƠNG 4: ĐỆ QUY VÀ TÌM KIẾM 103
4.1 Thuật toán đệ qui 103
4.1.1 Định nghĩa bằng đệ qui 103
4.1.2 Giải thuật đệ qui 104
4.1.3 Một số ví dụ minh họa 105
4.2 Tìm kiếm (Searching) 107
4.2.1 Tìm kiếm tuần tự (Sequential Searching) 108
4.2.2 Tìm kiếm nhị phân (Binary Searching) 109
Tài liêụ tham khảo: 113
2
Trang 3CHƯƠNG 1: TỔNG QUAN VỀ PHƯƠNG PHÁP LẬP TRÌNH CÓ
CẤU TRÚC
Nội dung chính của chương này tập trung làm sáng tỏ những nguyên lý,phương pháp cơ bản của lập trình có cấu trúc Nắm được các nguyên lý này khôngchỉ giúp người học tiếp cận ngôn ngữ lập trình nhanh chóng, hiệu quả mà còn giúp
họ cách tư duy, tạo phong cách lập trình khi xây dựng hệ thống ứng dụng Cácnguyên lý cơ bản được giới thiệu bao gồm:
Nguyên lý lệnh- lệnh có cấu trúc- cấu trúc dữ liệu
Nguyên lý tối thiểu
Nguyên lý địa phương
Nguyên lý an toàn
Nguyên lý nhất quán
Nguyên lý top- down
Nguyên lý bottton- up
1.1 SƠ LƯỢC VỀ LỊCH SỬ LẬP TRÌNH CÓ CẤU TRÚC
Lập trình là một trong những công việc nặng nhọc nhất của khoa học máytính Có thể nói, năng suất xây dựng các sản phẩm phần mềm là rất thấp so vớicác hoạt động trí tuệ khác Một sản phẩm phần mềm có thể được thiết kế và cài đặttrong vòng 6 tháng với 3 lao động chính Nhưng để kiểm tra tìm lỗi và tiếp tụchoàn thiện sản phẩm đó phải mất thêm chừng 3 năm Đây là hiện tượng phổ biếntrong tin học của những năm 1960 khi xây dựng các sản phẩm phần mềm bằng kỹthuật lập trình tuyến tính Để khắc phục tình trạng lỗi của sản phẩm, người ta chechắn nó bởi một mành che mang tính chất thương mại được gọi là Version Thựcchất, Version là việc thay thế sản phẩm cũ bằng cách sửa đổi nó rồi công bố dướidạng một Version mới, giống như: MS-DOS 4.0 chỉ tồn tại trong thời gian vàitháng rồi thay đổi thành MS-DOS 5.0, MS-DOS 5.5, MS-DOS 6.0 Đây khôngphải là một sản phẩm mới như ta tưởng mà trong nó còn tồn tại những lỗi khôngthể bỏ qua được, vì ngay MS-DOS 6.0 cũng chỉ là sự khắc phục hạn chế của MS-DOS 3.3 ban đầu
Trong thời kỳ đầu của tin học, các lập trình viên xây dựng chương trìnhbằng các ngôn ngữ lập trình bậc thấp, quá trình nạp và theo dõi hoạt động củachương trình một cách trực tiếp trong chế độ trực tuyến (on-line) Việc tìm và sửalỗi (debbugging) như ngày nay là không thể thực hiện được Do vậy, trước nhữngnăm 1960, người ta coi việc lập trình giống như những hoạt động nghệ thuật
Trang 4nhuộm màu sắc cá nhân hơn là khoa học Một số người nắm được một vài ngônngữ lập trình, cùng một số mẹo vặt tận dụng cấu hình vật lý cụ thể của hệ thốngmáy tính, tạo nên một số sản phẩm lạ của phần mềm được coi là một chuyên gianắm bắt được những bí ẩn của nghệ thuật lập trình
Các hệ thống máy tính trong giai đoạn này có cấu hình yếu, bộ nhớ nhỏ, tốc
độ các thiết bị vào ra thấp làm chậm quá trình nạp và thực hiện chương trình.Chương trình được xây dựng bằng kỹ thuật lập trình tuyến tính mà nổi bật nhất làngôn ngữ lập trình Assembler và Fortran Với phương pháp lập trình tuyến tính,lập trình viên chỉ được phép thể hiện chương trình của mình trên hai cấu trúclệnh, đó là cấu trúc lệnh tuần tự (sequential) và nhảy không điều kiện (goto) Hệthống thư viện vào ra nghèo nàn làm cho việc lập trình trở nên khó khăn, chi phícho các sản phẩm phần mềm quá lớn, độ tin cậy của các sản phẩm phần mềmkhông cao dẫn tới hàng loạt các dự án tin học bị thất bại, đặc biệt là các hệ thốngtin học có tầm cỡ lớn Năm 1973, Hoare khẳng định, nguyên nhân thất bại màngười Mỹ gặp phải khi phóng vệ tinh nhân tạo về phía sao Vệ nữ (Sao Kim) là dolỗi của chương trình điều khiển viết bằng Fortran Thay vì viết:
DO 50 I = 12, 523
(Thực hiện số 50 với I là 12, 13, , 523)
Lập trình viên (hoặc thao tác viên đục bìa) viết thành:
DO 50 I = 12.523
(Dấu phảy đã thay bằng dấu chấm)
Gặp câu lệnh này, chương trình dịch của Fortran đã hiểu là gán giá trị thực12.523 cho biến DO 50 I làm cho kết quả chương trình sai
Để giải quyết những vướng mắc trong kỹ thuật lập trình, các nhà tin học lýthuyết đã đi sâu vào nghiên cứu tìm hiểu bản chất của ngôn ngữ, thuật toán vàhoạt động lập trình, nâng nội dung của kỹ thuật lập trình lên thành các nguyên lýkhoa học ngày nay Kết quả nổi bật nhất trong giai đoạn này là Knuth xuất bản bộ
3 tập sách mang tên “Nghệ thuật lập trình” giới thiệu hết sức tỉ mỉ cơ sở lý thuyếtđảm bảo toán học và các thuật toán cơ bản xử lý dữ liệu nửa số, sắp xếp và tìmkiếm Năm 1968, Dijkstra công bố lá thư “Về sự nguy hại của toán tử goto” Trongcông trình này, Dijkstra khẳng định, có một số lỗi do goto gây nên không thể xácđịnh được điểm bắt đầu của lỗi Dijkstra còn khẳng định thêm: “Tay nghề củamột lập trình viên tỉ lệ nghịch với số lượng toán tử goto mà anh ta sử dụngtrong chương trình”, đồng thời kêu gọi huỷ bỏ triệt để toán tử goto trong mọingôn ngữ lập trình ngoại trừ ngôn ngữ lập trình bậc thấp Dijkstra còn đưa ra khẳngđịnh, động thái của chương trình có thể được đánh giá tường minh qua các cấu trúc
4
Trang 5lặp, rẽ nhánh, gọi đệ qui là cơ sở của lập trình cấu trúc ngày nay.
Những kết quả được Dijikstra công bố đã tạo nên một cuộc cách mạngtrong kỹ thuật lập trình, Knuth liệt kê một số trường hợp có lợi của goto như vònglặp kết thúc giữa chừng, bắt lỗi , Dijkstra, Hoare, Knuth tiếp tục phát triển tưtưởng coi chương trình máy tính cùng với lập trình viên là đối tượng nghiên cứucủa kỹ thuật lập trình và phương pháp làm chủ sự phức tạp của các hoạt động lậptrình Năm 1969, Hoare đã phát biểu các tiên đề phục vụ cho việc chứng minhtính đúng đắn của chương trình, phát hiện tính bất biến của vòng lặp bằng cáchcoi chương trình vừa là bản mã hoá thuật toán đồng thời là bản chứng minh tínhđúng đắn của chương trình Sau đó Dahl, Hoare, Dijiksta đã phát triển thành ngônngữ lập trình cấu trúc
Năm 1978, Brian Barninghan cùng Denit Ritche thiết kế ngôn ngữ lập trình
C với tối thiểu các cấu trúc lệnh và hàm khá phù hợp với tư duy và tâm lý củacủa người lập trình Đồng thời, hai tác giả đã phát hành phiên bản hệ điều hànhUNIX viết chủ yếu bằng ngôn ngữ C, khẳng định thêm uy thế của C trong lập trình
hệ thống
Khi làm quen với một ngôn ngữ lập trình nào đó, không nhất thiết phải lệthuộc quá nhiều vào hệ thống thư viện hàm của ngôn ngữ, mà điều quan trọnghơn là trước một bài toán cụ thể, chúng ta sử dụng ngôn ngữ để giải quyết nó thếnào, và phương án tốt nhất là lập trình bằng chính hệ thống thư viện hàm củariêng mình Do vậy, đối với các ngôn ngữ lập trình, chúng ta chỉ cần nắm vữngmột số các công cụ tối thiểu như sau:
1.2 CẤU TRÚC LỆNH, LỆNH CÓ CẤU TRÚC, CẤU TRÚC DỮ LIỆU
1.2.1 Cấu trúc lệnh (cấu trúc điều khiển)
Mỗi chương trình máy tính về bản chất là một bản mã hoá thuật toán.Thuật toán được coi là dãy hữu hạn các thao tác sơ cấp trên tập đối tượng vào(Input) nhằm thu được kết quả ra (output) Các thao tác trong một ngôn ngữ lậptrình cụ thể được điều khiển bởi các lệnh hay các cấu trúc điều khiển, còn các đốitượng chịu thao tác thì được mô tả và biểu diễn thông qua các cấu trúc dữ liệu
Trong các ngôn ngữ lập trình cấu trúc, những cấu trúc lệnh sau được sửdụng để xây dựng chương trình Dĩ nhiên, chúng ta sẽ không bàn tới cấu trúc nhảykhông điều kiện goto mặc dù ngôn ngữ lập trình cấu trúc nào cũng trang bị cấutrúc lệnh goto
Trang 6Hình 1.1: Cấu trúc tuần tự và cấu trúc rẽ nhánh dạng đầy đủ
Hình 1.2 Các cấu trúc lặp
A, B : ký hiệu cho các câu lệnh đơn hoặc lệnh hợp thành Mỗi lệnh đơn lẻđược gọi là một lệnh đơn, lệnh hợp thành là lệnh hay cấu trúc lệnh được ghép lạivới nhau theo qui định của ngôn ngữ, trong Pascal là tập lệnh hay cấu trúc lệnh
6
Trang 7được bao trong thân của begin end; trong C là tập các lệnh hay cấu trúc lệnhđược bao trong hai ký hiệu { }.
E, E1, E2, E3 là các biểu thức số học hoặc logic Một số ngôn ngữ lập trình coi giátrị của biểu thức logic hoặc đúng (TRUE) hoặc sai (FALSE), một số ngôn ngữlập trình khác như C coi giá trị của biểu thức logic là đúng nếu nó có giá trị khác
0, ngược lại biểu thức logic có giá trị sai
Cần lưu ý rằng, một chương trình được thể hiện bằng các cấu trúc điềukhiển lệnh : tuần tự, tuyển chọn if else, switch case default, lặp với điều kiệntrước while , lặp với điều kiện sau do while, vòng lặp for bao giờ cũng chuyểnđược về một chương trình, chỉ sử dụng tối thiểu hai cấu trúc lệnh là tuần tự và lặpvới điều kiện trước while Phương pháp lập trình này còn được gọi là phương pháplập trình hạn chế
1.2.2 Lệnh có cấu trúc
Lệnh có cấu trúc là lệnh cho phép chứa các cấu trúc điều khiển trong nó Khitìm hiểu một cấu trúc điều khiển cần xác định rõ vị trí được phép đặt một cấu trúcđiều khiển trong nó, cũng như nó là một phần của cấu trúc điều khiển nào Điềunày tưởng như rất tầm thường nhưng có ý nghĩa hết sức quan trọng trong khi xâydựng và kiểm tra lỗi có thể xảy ra trong chương trình Nguyên tắc viết chươngtrình theo cấu trúc: Cấu trúc con phải được viết lọt trong cấu trúc cha, điểm vào
và điểm ra của mỗi cấu trúc phải nằm trên cùng một hàng dọc Ví dụ sau sẽ minhhọa cho nguyên tắc viết chương trình:
1.2.3 Cấu trúc dữ liệu
Các ngôn ngữ lập trình cấu trúc nói chung đều giống nhau về cấu trúc lệnh vàcấu trúc dữ liệu Điểm khác nhau duy nhất giữa các ngôn ngữ lập trình cấu trúc là
Trang 8phương pháp đặt tên, cách khai báo, cú pháp câu lệnh và tập các phép toán đượcphép thực hiện trên các cấu trúc dữ liệu cụ thể Nắm bắt được nguyên tắc này,chúng ta sẽ dễ dàng chuyển đổi cách thể hiện chương trình từ ngôn ngữ lập trìnhnày sang ngôn ngữ lập trình khác một cánh nhanh chóng mà không tốn quánhiều thời gian cho việc học tập ngôn ngữ lập trình.
Thông thường, các cấu trúc dữ liệu được phân thành hai loại: cấu trúc dữliệu có kiểu cơ bản (Base type) và cấu trúc dữ liệu có kiểu do người dùng địnhnghĩa (User type) hay còn gọi là kiểu dữ liệu có cấu trúc Kiểu dữ liệu cơ bản baogồm: Kiểu kí tự (char), kiểu số nguyên có dấu (signed int), kiểu số nguyên khôngdấu (unsigned int), kiểu số nguyên dài có dấu (signed long), kiểu số nguyên dàikhông dấu (unsigned long ), kiểu số thực (float) và kiểu số thực có độ chính xácgấp đôi (double)
Kiểu dữ liệu do người dùng định nghĩa bao gồm kiểu xâu kí tự (string),kiểu mảng (array), kiểu tập hợp (union), kiểu cấu trúc (struct), kiểu file, kiểu contrỏ (pointer) và các kiểu dữ liệu được định nghĩa mới hoàn toàn như kiểu danh sáchmóc nối (link list), kiểu cây (tree)
Kích cỡ của kiểu cơ bản đồng nghĩa với miền xác định của kiểu với biểudiễn nhị phân của nó, và phụ thuộc vào từng hệ thống máy tính cụ thể Để xác địnhkích cỡ của kiểu nên dùng toán tử sizeof( type) Chương trình sau sẽ liệt kê kích cỡcủa các kiểu cơ bản
Ví dụ Kiểm tra kích cỡ của kiểu
printf(“\n Kích cỡ kiểu kí tự:%d”, sizeof(char));
printf(“\n Kích cỡ kiểu kí tự không dấu:%d”, sizeof(unsigned char));
printf(“\n Kích cỡ kiểu số nguyên không dấu:%d”, sizeof(unsigned int));printf(“\n Kích cỡ kiểu số nguyên có dấu:%d”, sizeof(signed int));
printf(“\n Kích cỡ kiểu số nguyên dài không dấu:%d”, sizeof(unsignedlong ));
printf(“\n Kích cỡ kiểu số nguyên dài có dấu:%d”, sizeof(signed long ));printf(“\n Kích cỡ kiểu số thực có độ chính xác đơn:%d”, sizeof(float ));printf(“\n Kích cỡ kiểu số thực có độ chính xác kép:%d”, sizeof(double ));
8
Trang 9}
Kích cỡ của các kiểu dữ liệu do người dùng định nghĩa là tổng kích cỡ củamỗi kiểu thành viên trong nó Chúng ta cũng vẫn dùng toán tử sizeof(tên kiểu) đểxác định độ lớn tính theo byte của các kiểu dữ liệu này
Một điểm đặc biệt chú ý trong khi lập trình trên các cấu trúc dữ liệu là cấutrúc dữ liệu nào thì phải kèm theo phép toán đó, vì một biến được gọi là thuộckiểu dữ liệu nào đó nếu như nó nhận một giá trị từ miền xác định của kiểu và cácphép toán trên kiểu dữ liệu đó
1.3 NGUYÊN LÝ TỐI THIỂU
Hãy bắt đầu từ một tập các nguyên tắc và tối thiểu các phương tiện là các cấutrúc lệnh, kiểu dữ liệu cùng các phép toán trên nó và thực hiện viết chương trình.Sau khi nắm chắc những công cụ thì tiến hành mở rộng sang hệ thống thư viện tiệních của ngôn ngữ
Khi làm quen với một ngôn ngữ lập trình nào đó, không nhất thiết phải lệthuộc quá nhiều vào hệ thống thư viện hàm của ngôn ngữ, mà điều quan trọng hơn
là trước một bài toán cụ thể, chúng ta sử dụng ngôn ngữ để giải quyết nó thế nào, vàphương án tốt nhất là lập trình bằng chính hệ thống thư viện hàm của riêng mình,vậy đối với mỗi ngôn ngữ lập trình chúng ta phải nắm vững một số công cụ tối thiểunhư sau:
1.3.1 Tập các phép toán
Tập các phép toán số học: + (cộng); - (trừ); * (nhân); % (lấy phần dư); /
(chia)
Tập các phép toán số học mở rộng:
++a a=a+1 // tăng giá trị của biến a lên một đơn vị
a a=a-1 // giảm giá trị của biến nguyên a đi một đơn vị
a+=n a=a+n // tăng giá trị của biến nguyên a lên n đơn vị
a-=n a=a-n // giảm giá trị của biến nguyên a n đơn vị
a%=na=a%n // lấy giá trị biến nguyên a môdul với n
a/=n a=a/n // lấy giá trị của biến nguyên a chia cho n
a*=n a=a*n // lấy giá trị của biến a chia cho n
Tập các phép toán so sánh: >, <, >=, <=, ==, != ( lớn hơn, nhỏ hơn,
lớn hơn hoặc bằng, nhỏ hơn hoặc bằng, đúng bằng, khác) Qui tắc viết được thể hiện như sau:
if ( a>b) { } // nếu a lớn hơn b
Trang 10if ( a<b) { } // nếu a nhỏ hơn b
if ( a>=b) { } // nếu a lớn hơn hoặc bằng b
if ( a<=b) { } // nếu a nhỏ hơn hoặc bằng b
if ( a==b) { } // nếu a đúng bằng b
if ( a!=b) { } // nếu a khác b
Tập các phép toán logic: &&, ||, ! (và, hoặc, phủ định)
&&: Phép và logic chỉ cho giá trị đúng khi hai biểu thức tham gia đều có giá trị đúng (giá trị đúng của một biểu thức trong C được hiểu là biểu thức có giá trị khác 0)
||: Phép hoặc logic chỉ cho giá trị sai khi cả hai biểu thức tham gia đều
có giá trị sai
!: Phép phủ định cho giá trị đúng nếu biểu thức có giá trị sai và ngượclạicho giá trị sai khi biểu thức có giá trị đúng Ngữ nghĩa của các phép toánđược minh họa thông qua các câu lệnh sau:
unsigned int a=3, b=5, c; clrscr();
c = a & b; printf(“\n c = a & b=%d”,c);
Trang 11c = ~a; printf(“\n c = ~a =%d”,c);
c = a << b; printf(“\n c = a << b=%d”,c);
c = a >>b; printf(“\n c = a >> b=%d”,c);
getch();
Toán tử chuyển đổi kiểu: Ta có thể dùng toán tử chuyển đổi kiểu để nhận
được kết quảtính toán như mong muốn Qui tắc chuyển đổi kiểu được thực hiện theo qui tắc: (kiểu) biến
Ví dụ 1.3: Tính giá trị phép chia hai số nguyên a và b.
ưu tiên tính toán của các phép toán số học và phép toán so sánh
Trang 12Nhập dữ liệu từ tệp: fscanf( file_pointer,”format_string, ”, ¶meter, .);Nhận một ký tự từ bàn phím: getch(); getchar();
Nhận một ký tự từ file: fgetc(file_pointer, character_name);
Nhập một string từ bàn phím: gets(string_name);
Nhận một string từ file text:fgets(string_name, number_character,file_pointer);
Xuất dữ liệu ra màn hình: printf(“format_string ”, parameter );
Xuất dữ liệu ra file : fprintf(file_pointer, “format_string ”, parameter .);Xuất một ký tự ra màn hình: putch(character_name);
1.3.3 Thao tác trên các kiểu dữ liệu có cấu trúc
Tập thao tác trên string:
char *strchr(const char *s, int c): tìm ký tự c đầu tiên xuất hiện trong xâu s; char *stpcpy(char *dest, const char *src): copy xâu scr vào dest;
int strcmp(const char *s1, const char *s2): so sánh hai xâu s1 và s2 theo
thứ tự từ điển, nếu s1 < s2 thì hàm trả lại giá trị nhỏ hơn 0 Nếu s1>s2 hàm trả lại giá trị dương Nếu s1==s2 hàm trả lại giá trị 0
char *strcat(char *dest, const char *src) : thêm xâu scr vào sau xâu dest char *strlwr(char *s) : chuyển xâu s từ ký tự in hoa thành ký tự in thường char *strupr(char *s): chuyển xâu s từ ký tự thường hoa thành ký tự in hoa char *strrev(char *s): đảo ngược xâu s.
char *strstr(const char *s1, const char *s2): tìm vị trí đầu tiên của xâu s2
trong xâu s1
int strlen(char *s): cho độ dài của xâu ký tự s.
Tập thao tác trên con trỏ:
Thao tác lấy địa chỉ của biến: & parameter_name;
Thao tác lấy nội dung biến (biến có kiểu cơ bản): *pointer_name;
Thao tác trỏ tới phần tử tiếp theo: ++pointer_name;
Thao tác trỏ tới phần tử thứ n kể từ vị trí hiện tại: pointer_name=
pointer_name +n;
Thao tác trỏ tới phần tử sau con trỏ kể từ vị trí hiện tại: pointer_name;
Thao tác trỏ tới phần tử sau n phần tử kể từ vị trí hiện tại:
Pointer_name = pointer_name - n;
Thao tác cấp phát bộ nhớ cho con trỏ:
void *malloc(size_t size);
void *calloc(size_t nitems, size_t size);
12
Trang 13Thao tác cấp phát lại bộ nhớ cho con trỏ : void *realloc(void *block, size_t
size);
Thao tác giải phóng bộ nhớ cho con trỏ: void free(void *block);
Tập thao tác trên cấu trúc:
Tập thao tác trên file:
Khai báo con trỏ file: FILE * file_pointer;
Thao tác mở file theo mode: FILE *fopen(const char *filename,const char
*mode);
Thao tác đóng file: int fclose(FILE *stream);
Thao tác đọc từng khối trong file:
size_t fread(void *ptr, size_t size,size_t n, FILE *stream);
Thao tác ghi từng dòng vào file: int fputs(const char *s, FILE *stream);
Thao tác ghi từng khối vào file:
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);
Thao tác kiểm tra sự tồn tại của file: int access(const char *filename, int
amode);
Thao tác đổi tên file: int rename(const char *oldname,const char *newname); Thao tác loại bỏ file: int unlink(const char *filename);
1.4 NGUYÊN LÝ ĐỊA PHƯƠNG
Các biến địa phương trong hàm, thủ tục hoặc chu trình cho dù có trùng tên với biến toàn cục thì khi xử lý biến đó trong hàm hoặc thủ tục vẫn không làm thay đổi giá trị của biến toàn cục.
Tên của các biến trong đối của hàm hoặc thủ tục đều là hình thức.
Trang 14 Mọi biến hình thức truyền theo trị cho hàm hoặc thủ tục đều là các biến địa phương.
Các biến khai báo bên trong các chương trình con, hàm hoặc thủ tục đều là biến địa phương
Khi phải sử dụng biến phụ nên dùng biến địa phương và hạn chế tối đa việc sử dụng biến toàn cục để tránh xảy ra các hiệu ứng phụ.
Ví dụ hoán đổi giá trị của hai số a và b sau đây sẽ minh họa rõ hơn về nguyên
Kết quả thực hiện chương trình:
Kết quả thực hiện trong thủ tục a = 5 b=3
Kết quả sau khi thực hiện thủ tục a = 1 b =8
Trong ví dụ trên a, b là hai biến toàn cục, hai biến a, b trong thủ tục Swap làhai biến cục bộ Các thao tác trong thủ tục Swap gán cho a giá trị 3 và b giá trị 5sau đó thực hiệnđổi giá trị của a =5 và b =3 là công việc xử lý nội bộ của thủ tục
mà không làm thay đổi giátrị của biến toàn cục của a, b sau thi thực hiện xong thủtục Swap Do vậy, kết quả sau khithực hiện Swap a = 1, b =8; Điều đó chứng tỏtrong thủ tục Swap chưa bao giờ sử dụng tớihai biến toàn cục a và b Tuy nhiên,trong ví dụ sau, thủ tục Swap lại làm thay đổi giá trị củabiến toàn cục a và b vì nó
14
Trang 15thao tác trực tiếp trên biến toàn cục.
Ví dụ 2 Đổi giá trị của hai biến a và b
temp=a; a=b; b=temp;// đổi giá trị của a và b
printf(“\n Kết quả thực hiện trong thủ tục a=%5d b=%5d:,a,b);
Kết quả thực hiện chương trình:
Kết quả thực hiện trong thủ tục a = 8 b=1
Kết quả sau khi thực hiện thủ tục a = 1 b =8
dữ liệu Tuy nhiên, trên thực tế có nhiều lỗi nhập nhằng giữa phép toán và cấu trúc
dữ liệu mà chúng ta cần hiểu rõ
Đối với kiểu ký tự, về nguyên tắc chúng ta không được phép thực hiện cácphép toánsố học trên nó, nhưng ngôn ngữ C luôn đồng nhất giữa ký tự với sốnguyên có độ lớn 1byte Do vậy, những phép toán số học trên các ký tự thực chất
Trang 16là những phép toán số họctrên các số nguyên Chẳng hạn, những thao tác nhưtrong khai báo dưới đây là được phép:
ký tự space
Chúng ta có thể thực hiện được các phép toán số học trên kiểu int, long, float,double Nhưng đối với int và long, chúng ta cần đặc biệt chú ý phép chia hai sốnguyên cho ta mộtsố nguyên, tích hai số nguyên cho ta một số nguyên, tổnghai số nguyên cho ta một sốnguyên mặc dù thương hai số nguyên là một sốthực, tích hai số nguyên hoặc tổng hai sốnguyên có thể là một số long int Dovậy, muốn nhận được kết quả đúng, chúng ta cần phảichuyển đổi các biến thuộccùng một kiểu trước khi thực hiện phép toán Ngược lại, ta khôngthể lấy modul củahai số thực hoặc thực hiện các thao tác dịch chuyển bít trên nó, vì nhữngthao tác
đó không nằm trong định nghĩa của kiểu
Lỗi nặng nhất nằm ở mức cao nhất (mức ý đồ thiết kế) và ở mức thấp nhấtthủ trong khi soạn thảo chương trình, chúng ta có thể viết sai các từ khoá ví dụ thay
vì viết là int chúng ta soạn thảo sai thành Int (lỗi chữ in thường thành in hoa), hoặcviết sai cú pháp các biểu thức như thiếu các dấu ngoặc đơn, ngoặc kép hoặc dấuchấm phảy khi kết thúc một lệnh, hoặc chưa khai báo nguyên mẫu cho hàm
Điều tương tự cũng xảy ra với các string Trong Pascal, phép toán so sánhhai string hoặc gán trực tiếp hai Record cùng kiểu với nhau là được phép, ví dụStr1>Str2, Str1 :=Str2; Nhưng trong C thì các phép toán trên lại không được địnhnghĩa, nếu muốn thực hiện nó, chúng ta chỉ có cách định nghĩa lại hoặc thực hiện
nó thông qua các lời gọi hàm
Trang 17trướcCác loại lỗi thường xảy ra trong khi viết chương trình có thể được tổng kết lại như sauxác hoàn toàn tại nơi xuất hiện lỗi
Lỗi xảy ra trong quá trình liên kết: lỗi này thường xuất hiện khi ta sử dụng
tới các lỗi này thường xuất hiện khi ta sử dụng tới các lời gọi hàm, nhưngnhững hàm đó mới chỉ tồn tại dưới dạng nguyên mẫu (function prototype)
mà chưa được mô tả chi tiết các hàm, hoặc những lời hàm gọi chưa đúng vớitên của nó Lỗi này được khắc phục khi ta bổ sung đoạn chương trình con mô tả chitiết cho hàm hoặc sửa đổi lại những lời gọi hàm tương ứng.Ta quan niệm, lỗi cúpháp (error), lỗi cảnh báo (warning) và lỗi liên kết (linker) là lỗitầm thường vìnhững lỗi này đã được Compiler của các ngôn ngữ lập trình phát hiện được.Đểkhắc phục các lỗi loại này, chúng ta chỉ cần phải đọc và hiểu được những thôngbáo lỗi thường được viết bằng tiếng Anh Cũng cần phải lưu ý rằng, do mức
độ phức tạp của chương trình dịch nên không phải lỗi nào cũng được chỉ ra mộtcách tường minh và chínhxác hoàn toàn tại nơi xuất hiện lỗi.Loại lỗi cuối cùng màcác compiler không thể phát hiện nổi đó là lỗi do chính lậptrình viên gây nêntrong khi thiết kế chương trình và xử lý dữ liệu Những lỗi này khôngđượccompiler thông báo mà nó phải trả giá bằng quá trình tự test hoặc chứng minhđược tính đúng đắn của chương trình Lỗi có thể nằm ở chính ý đồ thiết kế, hoặclỗi do khônglường trước được tính chất của mỗi loại thông tin vào Ví dụ sauminh họa cho lỗi thường xảy ra thuộc loại này
Lỗi được thông báo bởi từ khoá Warning (lỗi cảnh báo): lỗi này thường
xảy ra khita khai báo biến trong chương trình nhưng lại không sử dụng tới chúng,hoặc lỗi trong cácbiểu thức kiểm tra khi biến được kiểm tra không xác định đượcgiá trị của nó, hoặc lỗi do thứ tự ưu tiên các phép toán trong biểu thức Hai loạilỗi error và warning được thông báongay khi dịch chương trình thành file *.OBJ.Quá trình liên kết (linker) các file *.OBJ đểtạo nên file chương trình mã máy
*.EXE chỉ được tiếp tục khi chúng ta hiệu đính và khử bỏmọi lỗi error
Lỗi được thông báo bởi từ khoá error (lỗi cú pháp): loại lỗi này thường
xảy ratrong khi soạn thảo chương trình, chúng ta có thể viết sai các từ khoá ví dụthay vì viết là intchúng ta soạn thảo sai thành Int (lỗi chữ in thường thành in hoa),hoặc viết sai cú pháp cácbiểu thức như thiếu các dấu ngoặc đơn, ngoặc kép hoặcdấu chấm phảy khi kết thúc mộtlệnh, hoặc chưa khai báo nguyên mẫu cho hàm
Ví dụ 1.6 Tính tổng hai đa thức A bậc n, đa thức B bậc m.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
Trang 18#define MAX 100
typedef float dathuc[MAX];
void In(dathuc A, int n, char c){
void Init( dathuc A, int *n, dathuc B, int *m){
int i;float temp;
printf("\n Nhap n="); scanf("%d", n);*n=*n+1;
printf("\n Nhap m="); scanf("%d",m); *m=*m+1;
printf("\n Nhap he so da thuc A:");
Trang 19for(i=0; i<n; i++)
1.7 PHƯƠNG PHÁP TOP-DOWN
Quá trình phân tích bài toán được thực hiện từ trên xuống dưới Từ vấn đềchung nhất đến vấn đề cụ thể nhất Từ mức trừu tượng mang tính chất tổngquan tới mức đơn giản nhất là đơn vị chương trình.
Một trong những nguyên lý quan trọng của lập trình cấu trúc là phươngpháp phântích từ trên xuống (Top - Down) với quan điểm “thấy cây khôngbằng thấy rừng”, phảiđứng cao hơn để quan sát tổng thể khu rừng chứ không thểđứng trong rừng quan sát chính nó
Quá trình phân rã bài toán được thực hiện theo từng mức khác nhau Mứcthấp nhấtđược gọi là mức tổng quan (level 0), mức tổng quan cho phép ta nhìntổng thể hệ thốngthông qua các chức năng của nó, nói cách khác mức 0 sẽ trả lờithay cho câu hỏi “Hệ thốngcó thể thực hiện được những gì ?” Mức tiếp theo làmức các chức năng chính Ở mức này,những chức năng cụ thể được mô tả Một
hệ thống có thể được phân tích thành nhiều mứckhác nhau, mức thấp được phép sử
Trang 20dụng các dịch vụ của mức cao Quá trình phân tích tiếp tục phân rã hệ thống theotừng chức năng phụ cho tới khi nào nhận được mức các đơn thể (UNIT, Function,Procedure), khi đó chúng ta tiến hành cài đặt hệ thống.
Chúng ta sẽ làm rõ hơn từng mức của quá trình Top-Down thông qua bài toánsau:
Bài toán: Cho hai số nguyên có biểu diễn nhị phân là a=(a1, a2, , an), b =
(b1, b2, , bn); ai, bi =0, 1, i=1, 2, n Hãy xây dựng tập các thao tác trên hai sốnguyên đó
Mức tổng quan (level 0):Hình dung toàn bộ những thao tác trên hai
số nguyên a=(a1, a2, ., an),b=(b1,b2, ,bn) với đầy đủ những chức năngchính của nó Giả sử những thao tác đó baogồm:
F1- Chuyển đổi a, b thành các số nhị phân;
F2- Tính tổng hai số nguyên: a + b;
F3- Tính hiệu hai số nguyên: a - b;
F4 Tính tích hai số nguyên: a *b;
F5- Thương hai số nguyên : a/b;
F6- Phần dư hai số nguyên: a % b;
F7- Ước số chung lớn nhất của hai số nguyên
Mức 1 Mức các chức năng chính: mỗi chức năng cần mô tả đầy đủ thông tin
vào (Input), thông tin ra (Output), khuôn dạng (Format) và các hành động (Actions)
Chức năng F1: Chuyển đổi a, b thành các số ở hệ nhị phân
Trang 23Đối với các hệ thống lớn, quá trình còn được mô tả tiếp tục cho tới khi nhận được mức đơn vị chương trình Trong ví dụ đơn giản này, mức đơn vị chương trình xuất hiện ngay tại mức 1 nên chúng ta không cần phân rã tiếp nữa mà dừng lại để cài đặt hệ thống.
1.8 PHƯƠNG PHÁP BOTTOM-UP
Đi từ cái riêng tới cái chung, từ các đối tượng thành phần ở mức cao tới các đối tượng thành phần ở mức thấp, từ mức đơn vị chương trình tới mức tổng thể,
từ những đơn vị đã biết lắp đặt thành những đơn vị mới.
Nếu như phương pháp Top-Down là phương pháp phân rã vấn đề mộtcách có hệthống từ trên xuống, được ứng dụng chủ yếu cho quá trình phân tích vàthiết hệ thống, thìphương pháp Bottom- Up thường được sử dụng cho quá trình càiđặt hệ thống Trong ví dụ trên, chúng ta sẽ không thể xây dựng được chươngtrình một cách hoàn chỉnh nếu như tachưa xây dựng được các hàm Binary(a),
Trang 24Addition(a,b), Subtraction(a,b), Multial(a,b),Division(a,b), Modulation(a,b),USCLN(a,b) Chương trình sau thể hiện quá trình cài đặt theo phương phápBottom- Up
void Init(int *a, int *b){
printf("\n Nhap a=");scanf("%d", a);
printf("\n Nhap b=");scanf("%d", b);
}printf("\n");delay(500);
}int bit(int a, int k){
int j=1;
if (a & (j<<k))return(1);
return(0);
}int Addition(int a, int b){
int Addition(int a, int b){
Trang 26clrscr();printf("\n Tap thao tac voi so nguyen");
printf("\n 1- Nhap hai so a,b");
printf("\n 2- So nhi phan cua a, b");
printf("\n 3- Tong hai so a,b");
printf("\n 4- Hieu hai so a,b");
printf("\n 5- Tich hai so a,b");
printf("\n 6- Thuong hai so a,b");
printf("\n 7- Phan du hai so a,b");
printf("\n 8- USCLN hai so a,b");
printf("\n 0- Tro ve");
Trang 27if (control)printf("\n Tich a*b =%d", Multial(a,b));
break;
case '6':
if (control)printf("\n Chia nguyen a div b=%d",Division(a,b));
break;
case '7':
if (control)printf("\n Phan du a mod b =%d", Modul(a,b));
break;
case '8':
if (control)printf("\n Uoc so chung lon nhat:%d",USCLN(a,b));
Tóm tắt nội dung cần ghi nhớ:
Một ngôn ngữ lập trình bất kỳ đều dựa trên tập các cấu trúc lệnh điều khiển(tuầntự, tuyển chọn & lặp), các cấu trúc dữ liệu (dữ liệu kiểu cơ bản & dữ liệu cócấutrúc) cùng với các phép toán trên nó
Khi mới bắt đầu học lập trình, hãy lập trình từ tập tối thiểu các công cụ màngônngữ lập trình trang bị (Nguyên lý tối thiểu)
Khi nào dùng biến địa phương, khi nào dùng biến toàn cục là nội dungchính củanguyên lý địa phương Nắm vững nguyên lý này giúp cho ta sử dụngcách truyềntham biến và cách truyền tham trị cho hàm
Dữ liệu kiểu nào thì phép toán đó là nội dung chính của nguyên lý nhất quán
Mọi lỗi dù nhỏ nhất cũng phải lường trước ở mỗi mức cài đặt của chươngtrình
Cách phân rã một vấn đề lớn thành những vấn đề nhỏ hơn là nội dungchính của nguyên lý Top-Down
Cách cài đặt một vấn đề được thực hiện từ mức đơn vị chương trình (hàm,thủ tục) đến mức lắp ghép các đơn vị chương trình thành hệ thống hoàn thiện lànội dung chính của nguyên lý Botton-Up
Trang 28CHƯƠNG 2: NGUYÊN TẮC LẬP TRÌNH, GỠ RỐI VÀ CẢI TIẾN
HIỆU SUẤT CHƯƠNG TRÌNH2.1 Phong cách lập trình
Phong cách lập trình bao hàm một triết lý về lập trình, nhấn mạnh tới tính đơngiản, rõ ràng Viết một chương trình máy tính là viết một dãy các lệnh bằng mộtngôn ngữ lập trình cụ thể Cách thức của mỗi lệnh diễn tả trong một chừng mực nào
đó sẽ xác định tính dễ hiểu của toàn bộ chương trình
Mục đích của phong cách lập trình là làm cho chương trình dễ đọc hơn, mộtphong cách tốt quyết định đến việc lập trình tốt Những nguyên tắc của phong cáchlập trình dựa trên những cảm nhận chung bằng kinh nghiệm mà không dựa vàonhững quy luật hay những chỉ dẫn nào khác
Chương trình phải được viết rõ ràng, đơn giản, logic, diễn đạt một trình tự tựnhiên, dễ hiểu, tên hàm, tên biến phải có nghĩa, định dạng rõ ràng và phải có phầnchú thích Tính nhất quán là cần thiết giúp người khác có thể hiểu được chươngtrình nếu mọi người đều có phong cách giống nhau Do đó, người ta đưa ra cácnguyên tắc/quy ước trong lập trình để tạo nên phong cách tốt, đảm bảo chương trình
có mã nguồn dễ hiểu, dễ kiểm thử, dễ bảo trì và ít lỗi
Nếu ta bắt đầu bảo trì chương trình với bộ mã nguồn tồi, ta sẽ mất chi phí lớnhơn gấp 10 lần để phát triển và cố định lỗi so với chương trình có mã tốt
“ Lập trình không chỉ là viết một chương trình mà máy tính hiểu, mà còn
phải để mọi người hiểu”
Trang 29Là nguyên tắc quan trọng cần biết trong lập trình Viết chương trình để Máy tínhhiểu thì nó mới thực hiện đúng, để mọi người hiểu để bảo trì, sửa chữa, đánh giá.Đây là nguyên tắc kỹ nghệ phần mềm đầu tiên cần phải nghĩ đến Viết chương trình
mà người khác không thể đọc và hiểu là một kỹ nghệ tồi
Chú thích giúp người đọc hiểu chương trình:
- Chú thích giúp người đọc không phải bằng cách:
o Nhắc lại những điều mà mã nguồn đã nêu rõ
o Tuân theo các nguyên lý tạo chú thích sau:
Một chương trình tốt phải có các chú thích sau:
- Có một chú thích ở đầu chương trình, tên file giải thích công việc mà chương
trình làm
Ví dụ: Chiếu chương trình minh họa một chương trình tốt, một chương trình
tồi
- Chú thích trong chương trình Người lập trình cần lưu vết các mở rộng trong
chương trình, nó sẽ giúp cho việc gỡ rối và bảo trì chương trình:
o Chú thích cho mỗi phương thức: Giải thích những gì phương thứcthực hiện, các điều kiện trước, điều kiện sau
Thường 1->3 dòng là tốt nhất cho mỗi phương thức
Ví dụ: Minh họa một chương trình tốt, một chương trình tồi
(chỉ có mã, không có chú thích, không thụt cấp, không có ýniệm tiếp tục chương trình này)
o Chú thích cho các biến toàn cục
o Chú thích cho những thao tác phức tạp
o Chú thích cho các thuật toán phức tạp sử dụng trong chương trình:
Cho biết tài liệu tham khảo
Mô tả ngắn gọn thuật toán: Ý tưởng của thuật toán
Nguyên tắc 2
“ Cần tạo chú thích trong chương trình”
Trang 30 Các dữ liệu được sử dụng, điều kiện thực thi thuật toán
Lưu ý:
- Đừng chú thích cho những gì hiển nhiên
- Đừng chú thích phủ định mã nguồn: Các chú thích phải được cập nhật khi
mã nguồn thay đổi
- Chú thích phải rõ ràng, không gây nhầm lẫn: Chú thích được coi là giúp
người đọc hiểu những chỗ khó trong chương trình chứ không phải để gây thêm khó khăn cho họ.
o Mã nguồn tốt cần ít chú thích hơn và ngược lại
- Chú thích không được lấn áp mã nguồn
- Đừng chú thích cho những đoạn mã nguồn dở mà hãy viết lại nó.
Một vấn đề có thể là mức cao: Đó là bài toán đơn, mức cao và phân rã xuống Hầuhết các phương thức khoảng từ 1 -> 15 dòng mã
Nếu một phương thức rất ngắn, tên giải thích chính nó một cách chính xác, có cácđiều kiện trước, sau rõ ràng là một ý tưởng tốt Tên không nên đặt quá dài vì nótiềm ẩn lỗi
Có sự khác nhau giữa lập trình cấu trúc là lập trình hướng đối tượng:
- Trong lập trình có cấu trúc: Chương trình được tổ chức dưới dạng cây phân
cấp chức năng => Tên các chức năng là các động từ
- Trong lập trình hướng đối tượng: Chương trình được tổ chức dưới dạng cây
phân cấp lớp đối tượng => Tên các lớp đối tượng là các danh từ, dang từ cũngđược xem là phương thức (phương thức khởi tạo)
o Ví dụ: Một đĩa CD được xem là phương thức, nó cũng có các tham
biến của nó, và các câu lệnh của nó được ẩn đi Phương thức này cóthể được gọi khi tạo ra một chiếc đĩa CD
Nguyên tắc 5: Ẩn thông tin
“ Một phương thức nên che đi những gì diễn ra trong thân nó ”
Trang 31Người dùng phương thức chỉ cần biết:
- Tên phương thức: Có thể là động từ hoặc danh từ tùy theo kiểu giá trị trả về
của nó
- Phương thức dùng để làm gì: Làm thế nào thì không cần biết
- Các tham biến truyền vào nó là gì.
Ví dụ: Ta biết phương thức readInt(): Dùng để lấy thông của người dùng từ bànphím, giá trị (số nguyên) trả về thông qua tên hàm, không có tham biến truyềnvào mà không cần biết bên trong phương thức là gì
Có thể xem một phương thức như một hộp đen:
Hãy viết các biểu thức như cách bạn đọc Các biểu thức điều kiện phủ định luônluôn khó hiểu
Ví dụ:
If(!block_Id < actblks)|| !(block_Id>=unblocks))
Phương thức/hộp đen
Tham số 1 Tham số 2
Kết quả
Vấn đề i / Phương thức i
Bread/Bánh mì chưa nướng/input
Nhiệt độ
Toast/Bánh mì
nướng/output
Nguyên tắc 6: “ Các biểu thức điều kiện nên mang tính tự nhiên ”
Trang 32Ví dụ: Phát biểu sau đây sử dụng nguyên tắc 7
Các phép toán so sánh (<, <=, ==, !=, >=, >) có thứ tự ưu tiên lớn hơn các toán tử logic (&&, ||)
C C++ Java, có cú pháp và số lượng toán tử phong phú Khi lập trình rất dễ bịcuốn vào việc thu gọn chương trình bằng cách nhồi nhét tất cả chỉ vào trong mộtphát biểu
Ví dụ: Biểu thức sau rất ngắn gọn song đã đưa quá nhiều phép toán vào trong một
Nguyên tắc 7: “Dùng dấu ngoặc để tránh tối nghĩa”
Nguyên tắc 8: “Phân tích những biểu thức phức tạp thành những biểu thức
đơn giản hơn”
Nguyên tắc 9: Tính sáng sủa của biểu thức
“Diến đạt các biểu thức sao cho rõ nghĩa nhất, tránh mã nguồn bí hiểm”
Trang 33Năng lực sáng tạo vô tận của các lập trình viên đôi khi được dùng để viết mãnguồn ngắn nhất có thể, hoặc để tìm các cách thông minh hơn để đạt được mụcđích Tuy nhiên đôi khi những kỹ năng này được áp dụng sai chỗ; vì mục tiêu là viết
mã nguồn sao cho sáng sủa, dễ hiểu chứ không phải viết mã để thể hiện tài khéoléo
Ví dụ: xét xem phép tính sau thực hiện gì?
Subkey =Subkey>>(bitoff – ((bitoff>>3)<=3));
Ta thấy, rất mất thời gian giải đáp để hiểu biểu thức trên, do đó, nên thay biểu thứcnày bởi biểu thức tương đương:
Subkey = subkey >> (bitoff&0x7), hoặcSubkey >>=bitoff&0x7;
Biểu thức sẽ ngắn gọn và dễ hiểu hơn rất nhiều
Số tối nghĩa là các hằng số như kích thước mảng, vị trí ký tự, hệ số chuyểnđổi và các giá trị số khác xuất hiện trong chương trình Về nguyên tắc chung, tất cảcác giá trị số trừ 0 và 1 đều xem là tối nghĩa và nên đặt tên cho nó Một con số trong
mã nguồn sẽ không thể cho biết gì về tầm quan trọng cũng như xuất xứ của nó.Điều đó được xem là tối nghĩa và nó làm cho chương trình trở nên khó hiểu và khósửa đổi
Ví dụ: Chiếu chương trình để minh họa
- Cách đặt tên cho các số tối nghĩa để làm chương trình dễ hiểu
- Sự tiện lợi trong việc sửa chương trình khi đặt tên cho các số tối nghĩa
Dịch bitoff sang phải 3
bít
Kết quả thu được 3 bít cuối của bitoff (111=7)
Kết quả dịch sang trái 3 bít, thay 3 bít được dịch
Nguyên tắc 10:
“Đặt tên cho các số tối nghĩa”
Trang 34Nguyên tắc 11:
“Dùng các hằng số dạng ký tự, không dùng các dạng nguyên khi làm việc với
các biến ký tự”
Trang 35Ví dụ:
Xét một phép toán kiểm tra điều kiện sau:
If (c>=65 && c<=90)
Phép toán này hoàn toàn phụ thuộc vào một bẳng mã ký tự đặc thù nào đó Để tránh
sự phụ thuộc này, tốt nhất ta nên dùng phép toán sau thay thế:
If (c>=’A’&& c<=’Z’)
Tóm lại
Mã nguồn chương trình được viết tốt sẽ dễ đọc, dễ hiểu và hầu như chắc chắn là
ít lỗi hơn, và có khả năng kích thước sẽ nhỏ hơn so với mã nguồn được chắp vá, cẩuthả và không hề chau chuốt
Trong tình huống cấp bách cần hoàn thành chương trình cho kịp với một thờihạn nào đó, thường vấn đề phong cách bị gác sang một bên để giải quyết sau Quyếtđịnh này có thể sẽ phải trả giá đắt Mã nguồn tùy tiện cũng là mã nguồn dở Nókhông chỉ nguy hiểm và khó đọc mà còn có thể gây ra những hậu quả nghiêm trọngkhác
Quan điểm cơ bản là: Phong cách tốt phải là vấn đề thói quen Nếu ngay từ đầukhi viết mã nguồn bạn đã nghĩ đến vấn đề phong cách và nếu bạn dành thời gian đểduyệt lại và cải tiến chương trình, bạn sẽ tập cho mình một thói quen tốt Một khi đãtrở thành thói quen, tiềm thức của bạn sẽ giúp bạn trong nhiều chi tiết vụn vặt, vàngay cả khi bạn phải viết mã nguồn trong trạng thái chịu áp lực công việc cao, mãnguồn của bạn cũng sẽ tốt hơn
2.3 Các chuẩn trong lập trình
Các chuẩn có thể được kiểm thử bằng máy tính Một số chuẩn chung cho tất cả các ngôn ngữ lập trình:
Nguyên tắc 12: Viết chương trình theo cấu trúc
“Cấu trúc con lọt trong cấu trúc cha Điểm vào và điểm ra của một cấu
trúc phải trên cùng một cột”
Trang 36“Mọi modul” chỉ gồm 35 ->50 phát biểu Nếu ít hơn, hoặc lớn hơn cần hỏi ý kiến người quản lý.
“Các lệnh if không được lồng nhau quá 3 mức”
“Nên tránh sử dụng các phát biểu goto, goto có thể dược sử dụng để xử lý lỗi – nhưng nên tối thiểu dùng”
Mục đích của các chuẩn mã hóa là tăng tính dễ dùng cho hoạt động bảo trì, làm tăng chất lượng phần mềm, đây là một trong những mục đích chính của kỹ nghệ phần mềm
2.4 Gỡ rối chương trình
Ta hãy đơn giản hóa quá trình thiết kế và cài đặt một sản phẩm phần mềm theo một tiến trình gồm 5 giai đoạn đầu tiên (còn các giai đoạn khác như kiểm sửa, vận hành, bảo trì, tạm thời bỏ qua):
1 Phân tích các yêu cầu: thu thập, tìm hiểu các nhu cầu, hình thành bài toán
2 Thiết kế các chức năng: Xác định các chức năng cần có của sản phẩm+chia nhỏ các chức năng
và chuẩn mực thì nhóm thiết kế là nhóm giỏi Họ còn nhiều dịp để thi thố tài năng.Trong phát triển phần mềm, lỗi này liên quan đến việc lựa chọn sai quy trình pháttriển phần mềm
(~ quy trình thiết kế sai mục đích, hệ thông sai mục đích: dự án 122, )
36
Trang 372.4.2 Phân tích các yêu cầu không đầy đủ và lệnh lạc (xảy ra ở giai đoạn 1)
Cần lưu ý rằng, người đặt hàng vì không biết gì về tin học nên họ không thểphát biểu chính xác và đầy đủ các yêu cầu của họ Chúng ta cũng không đủ hiểu biết
về địa bàn và đối tượng mà ta định áp dụng tin học Do đó, ta không thể thu thậpđầy đủ và chính xác các thông tin của đối tượng
Đây là mâu thuẫn cơ bản của quá trình thiết kế, thường xảy ra ở giai đoạn 1
Ví dụ: Xét bài toán phân số, lỗi thuộc loại này là:
Người lập trình không thể diễn đạt chính xác khái niệm phân số, ví dụ về chiaphân số Bản thân ta cũng chưa bao giờ đụng chạm tới các phép toán về phần số
2.4.3 Hiểu sai về chức năng
Lỗi này thường xảy ra ở giai đoạn 2 Hiểu sai về chức năng dẫn đến đặc tảchức năng sai
Ví dụ: Trong bài toán phân số:
- Ta không định nghĩa chính xác được thế nào là phân số Ví dụ coi -2/-3, 2/-3
cũng là phân số Thực ra dấu – không đi với mẫu số vì sẽ gây nhiều rắc rốikhi thực hiện các phép toán
- Khi làm việc với phân số, bỏ qua toán tử tạo lập một phân số => vi phạm
nguyên lý đối tượng
- Không rút gọn phân số kết quả
- Quan niệm chia phân số giống nhân nghịch đảo mà quên không xét tử số = 0.
Điều đặc biệt là khi học ở phổ thông, ta có thể rất hiểu và thuộc các quy tắc thựchiện các phép toán trên phân số Nhưng khi đặt bút mô tả chúng, thì ta lại quênnhững điểm rất cơ bản Đó là nội dung của nguyên lý gián đoạn
Nguyên lý đối tượng
“Khi thao tác trên đối tượng nào, thì trước hết phải phát sinh ra nó”
Nguyên lý gián đoạn
“Người lập trình không có ý thức thường trực về việc dùng những tri thức
cơ bản của khoa học mà họ có sẵn”
Trang 382.4.4 Lỗi tại các đối tượng chịu tải
Thường xảy ra ở giai đoạn 3 Đây là các lỗi nằm ở các thủ tục/thao tác mứcthấp, có mặt trong bộ phận hợp thành của hầu hết các thủ tục khác Đây là những lỗinặng
Ví dụ 1: Tưởng tượng rằng, một seri các bóng bán dẫn sai quy cách do dây chuyền
sản xuất chính có trục trặc Các bóng bán dẫn này lại được dùng để lắp ráp thànhcác thiết bị điện tử đủ loại như đài thu thanh, máy ghi âm, Ti vi, Đồng hồ, =>Hậu quả của loại lỗi này rõ ràng là rất lớn
Ví dụ 2: Xét bài toán phân số
Cả 4 phép toán Cộng, trừ, nhân, chia đều gọi thủ tục rút gọn Rút gọn là thủ tụcchịu tải, nếu sai do đặc tả/cài đặt thì mọi phép toán trên phân số đều sai
Tiếp tục triển khai các phép toán BCNN, Rút gọn, 2 thủ tục này gọi thủ tụcUCLN, UCLN là thủ tục chụi tải mức thấp hơn, các lỗi của nó còn nặng hơn lỗi tạithủ tục rút gọn
UCLN lại gọi thủ tục lấy phần dư Mod Mod là thủ tục chịu tải mức thấp hơnnữa Thủ tục BCNN chỉ dùng cho hai phép toán Cộng, trừ thông qua thủ tục quyđồng mẫu số (qđms) => Mức chịu tải của BCNN trong thiết kế này là nhỏ hơn mứcchịu tải của thủ tục UCLN, Rút gọi và Mod
Càng đi xuống, mức chụi tải càng lớn, lỗi phát sinh ở mức dưới được xem là nặnghơn ở mức trên Chúng ta cần nắm vững nguyên lý mức độ lỗi
38
BCN N
Rút gọn
Trang 39Nguyên lý mức độ lỗi chỉ đạo chúng ta tập trung chú ý khi xây dựng mụcđích thiết kế & khi triển khai đặc tả và cài đặt các thao tác ở mức thấp.
2.4.5 Lỗi lây lan
Lỗi lây lan là những lỗi được truyền từ chương trình này sang chương trìnhkhác Lỗi này được định nghĩa như sau:
Giả sử ta có 2 chương trình A và B A chứa lỗi E, B không chứa lỗi E Nếu đếnmột thời điểm nào đó B cũng nhiễm E một cách trực tiếp hoặc gián tiếp từ A thì tanói E là lỗi nây lan E là đoạn chương trình do người ta cố lập ra và cài sẵn vàochương trình (vào A chẳng hạn), rồi tìm cách truyền cho những chương trình lànhkhác Từ cách nhìn này ta thấy, mỗi con visus (chương trình v) thường có hai thànhphần:
Nghiên cứu Visus máy tính là vấn đề khá hay Ta không đi sâu vào cơ chế visus
mà dành vấn đề này cho những nhà visus học
2.4.6 Lỗi cú pháp
Những lỗi đã phân tích ở trên (trừ lỗi do visus gây ra) đều là những lỗi thựchiện (Run Errors) Các chương trình đều được viết đúng về mặt cú pháp, tức là đúngvới những quy định về văn phạm và cấu trúc của ngôn ngữ Ngoài những loại lỗitrên, còn có một loại lỗi nữa đó là lỗi cú pháp Nói chung các lỗi cú pháp do chươngtrình dịch phát hiện và thông báo theo nguyên lý an toàn
Để tìm và sửa được các lỗi cú pháp trong chương trình của mình, lập trìnhviên phải đọc và hiểu được các thông báo lỗi (thường được viết bằng ngôn ngữTiếng Anh)
Cũng cần lưu ý: Do độ phức tạp của chương trình nên không phải lỗi nàocũng được chỉ ra một cách tường minh ở đúng chỗ nó xuất hiện Loại lỗi nàythường xảy ra ở giai đoạn 4
Nguyên lý an toàn
“Mọi lỗi, dù nhỏ, phải được phát hiện ở một bước nào đó của chương trình Việc phát hiện lỗi phải được thực hiện trước khi lỗi đó hoành hành”
Trang 402.4.7 Hiệu ứng phụ (Side Effect)
Đây là hiện tượng xảy ra khi một đơn vị chương trình/module làm thay đổigiá trị của một biến ngoài ý muốn của lập trình viên
Ví dụ: Viết một chương trình Pascal tính giá trị của biểu thức:
f = 2*(x+y*2-1);
Với x, y là một số nguyên nhập vào từ bàn phím
Chương trình sẽ gồm các bước sau:
Đáng lẽ phải in giá trị 4 của y như đã nạp, chườn trình lại in giá trị 9 Xem lại f
ta thấy lệnh y:=2*y+1 đã làm thay đổi giá trị của biến y từ 4 thành 9 Vì y là biếntoàn cục cho nên mọi điểm của chương trình nói chung và trong hàm f nói riêng đều
có thể truy cập và làm thay đổi giá trị của y
40