Giáo trình Lý thuyết ngôn ngữ lập trình - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Lý thuyết ngôn ngữ lập trình - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
- giao_trinh_ly_thuyet_ngon_ngu_lap_trinh_nghe_lap_trinh_may_t.pdf
Nội dung text: Giáo trình Lý thuyết ngôn ngữ lập trình - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề
- BỘ LAO ĐỘNG - THƯƠNG BINH VÀ XÃ HỘI TỔNG CỤC DẠY NGHỀ Dự án giáo dục kỹ thuật và dạy nghề (VTEP) GIÁO TRÌNH Mô đun: LÝ THUYẾT NGÔN NGỮ LẬP TRÌNH Mã số: ITPRG06 NGHỀ: LẬP TRÌNH MÁY TÍNH Trình độ : Cao đẳng nghề NĂM 2012
- Tuyên bố bản quyền : Tài liệu này thuộc loại sách giáo trình Cho nên các nguồn thông tin có thể được phép dùng nguyên bản hoặc trích dùng cho các mục đích về đào tạo và tham khảo . Mọi mục đích khác có ý đồ lệch lạc hoặc sử dụng với mục đích kinh doanh thiếu lành mạnh sẽ bị nghiêm cấm. Tổng Cục Dạy nghề sẽ làm mọi cách để bảo vệ bản quyền của mình. Tổng Cục Dạy Nghề cám ơn và hoan nghênh các thông tin giúp cho việc tu sửa và hoàn thiện tốt hơn tàI liệu này. Địa chỉ liên hệ: Dự án giáo dục kỹ thuật và nghề nghiệp Tiểu Ban Phát triển Chương trình Học liệu 2
- LỜI TỰA Đây là tài liệu được xây dựng theo chương trình của dự án giáo dục kỹ thuật và dạy nghề, để có đươc giáo trình này dự án đã tiến hành theo hai giai đoạn. Giai đoạn 1 : Xây dựng chương trình theo phương pháp DACUM, kết quả của gian đoạn này là bộ khung chương trình gồm 230 trang cấp độ 2 và 170 trang cấp độ 3. Giai đoạn 2 : 29 giáo trình và 29 tài liệu hướng dẫn giáo viên cho nghề lập trình máy tính 2 cấp độ. Để có được khung chương trình chúng tôi đã mời các giáo viên, các chuyên gia đang làm việc trong lĩnh vực công nghệ thông tin cùng xây dựng chương trình. Trong giai đoạn viết giáo trình chúng tôi cũng đã có những sự điều chỉnh để giáo trình có tính thiết thực và phù hợp hơn với sự phát triển của lĩnh vực công nghệ thông tin. Trong quá trình biên soạn, mặc dù đã cố gắng tham khảo nhiều tài liệu và giáo trình khác nhưng tác giả không khỏi tránh được những thiếu sót và hạn chế. Tác giả chân thành mong đợi những nhận xét, đánh giá và góp ý để cuốn giáo trình ngày một hoàn thiện hơn. Tài liệu này được thiết kế theo từng mô đun/ môn học thuộc hệ thống mô đun/môn học của một chương trình, để đào tạo hoàn chỉnh nghề Lập trình máy tính ở cấp trình độ lành nghề và được dùng làm Giáo trình cho học viên trong các khoá đào tạo, cũng có thể được sử dụng cho đào tạo ngắn hạn hoặc cho các công nhân kỹ thuật, các nhà quản lý và người sử dụng nhân lực tham khảo. Đây là tài liệu thử nghiệm sẽ được hoàn chỉnh để trở thành giáo trình chính thức trong hệ thống dạy nghề. 3
- MỤC LỤC ĐỀ MỤC TRANG 01. LỜI TỰA 3 02. MỤC LỤC 4 03. GIỚI THIỆU VỀ MÔN HỌC 5 04. SƠ ĐỒ MỐI LIÊN HỆ GIỮA CÁC MÔ ĐUN VÀ MÔN HỌC TRONG CHƯƠNG TRÌNH 7 05: YÊU CẦU ĐÁNH GIÁ HOÀN THÀNH MÔN HỌC 8 06: BÀI 01: GIỚI THIỆU TỔNG QUAN VỀ NGÔN NGỮ LẬP TRÌNH 9 07: BÀI 02: CÁC LOẠI DỮ LIỆU CUẤ TRÚC 24 08: BÀI 03: HÀM THỦ TỤC 61 09: BÀI 04: ĐẶC TRƯNG CÚ PHÁP VÀ NGỮ NGHĨA TRƯƠNG TRÌNH 74 10: BÀI 05: ĐẶC TRƯNG LẬP TRÌNH CÂU LỆNH(LẬP TRÌNH THỦ TỤC) 85 11: BÀI 06: LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG 95 12: BÀI 07: LẬP TRÌNH LOGIC VÀ LẬP TRÌNH HÀM 103 13: BÀI 08: LẬP TRÌNH SONG SONG 118 14: BÀI 09: CÁC PHƯƠNG PHÁP NGÔN NGỮ LẬP TRÌNH KHÁC 120 15: BÀI 10: PHÂN TÍCH CÚ PHÁP VÀ TRƯƠNG TRÌNH DỊCH 125 16.THUẬT NGỮ CHUYÊN NGÀNH 137 17.TÀI LIỆU THAM KHẢO 140 4
- GIỚI THIỆU VỀ MÔN HỌC Vị trí, ý nghĩa, vai trò môn học Trong lĩnh vực công nghệ thông tin, không thể không nói đến các ngụn ngữ lập trỡnh. Bởi vỡ, chỳng là cụng cụ cần thiết giỳp cho chỳng ta làm việc và giao tiếp với mỏy tớnh điện tử. Vỡ vậy, việc nắm được các khái niệm cơ bản của các ngôn ngữ lập trỡnh là rất cần thiết đối với những ai làm việc trong lĩnh vực cụng nghệ thụng tin hoặc cỏc lĩnh vực cú ứng dụng cụng nghệ thụng tin. Mục tiêu của môn học Sau khi học xong môn học này học viên có khả năng: Nắm được các khái niệm cơ bản của ngôn ngữ lập trỡnh chung, hiểu được các thành phần của một ngôn ngữ lập trỡnh, biết phõn biệt cỏc đặc trưng khác nhau của các ngôn ngữ lập trỡnh, nắm rừ cấu trỳc, quy trỡnh thực hiện của một ngụn ngữ lập trỡnh, nắm được xu hướng phát triển của các ngôn ngữ lập trỡnh hiện đại và biết cách lựa chọn ngôn ngữ thích hợp để viết chương trỡnh ứng dụng cụ thể. Mục tiêu thực hiện của môn học Học xong môn học này học viên có khả năng: Phân tích một bài toán thành các thành phần cấu trúc chương trình Phân tích chương trình nguồn thành các ứng dụng của kỹ thuật lập trình để giải quyết các bài toán ứng dụng Đánh giá chất lượng của một chương trình. Lựa chọn ngôn ngữ thích hợp cho lập trình ứng dụng Mô tả cấu trúc dữ liệu cho bài toán Xây dựng các thủ tục, hàm thực hiện các chức năng trên các ngôn ngữ lập trình Hiểu rõ các nguyên nhân sai sót khi biên tập chương trình, dịch và thực hiện chương trình. Nội dung chính của môn học Lịch sử của ngôn ngữ lập trình Giới thiệu tổng quan về Ngôn ngữ lập trình Các loại dữ liệu cấu trúc Hàm và thủ tục Đặc trưng cú pháp chương trình Đặc trưng ngữ nghĩa chương trình Đặc trưng lập trình câu lệnh Đặc trưng lập trình hướng đối tượng Ngôn ngữ lập trình hướng đối tượng Đặc trưng lập trình logic Đặc trưng lập trình Hàm 5
- Đặc trưng lập trình song song Logic có ký hiệu hàm Các phương pháp lập trình khác Vấn đề dịch ngôn ngữ Dịch ngôn ngữ cấp cao Lập trình di động (Mobile Code) 6
- SƠ ĐỒ MỐI LIÊN HỆ GIỮA CÁC MÔ ĐUN VÀ MÔN HỌC TRONG CHƯƠNG TRÌNH Học kỳ I Học kỳ II Học kỳ III Học kỳ IV Hệ thống Giao diện Lập trình Lập trình máy tính người máy nâng cao Web Lập trình Phân tích thiết căn bản Lập trình hướng kế hệ thống đối tượng Mạng căn bản Thiết kế hướng đối tượng Kỹ năng tin học Cấu trúc dữ liệu và thuật giải văn phòng Kỹ năng Ứng dụng CNTT trong doanh nghiệp Giao tiếp Cơ sở dữ liệu Kỹ năng Công nghệ Internet & WWW phần mềm Cơ sở toán học Thiết kế Web Công nghệ Đa Quản lý dự án phương tiện phần mềm Lập trình Visual Basic Hệ cơ sở dữ Hướng dẫn đồ liệu Môi trường PT án tốt nghiệp Phần mềm Anh văn Phần cứng An toàn Thi cho tin học máy tính lao động tốt nghiệp Trang 7
- YÊU CẦU VỀ ĐÁNH GIÁ HOÀN THÀNH MÔN HỌC Kỹ năng thực hành: - Sử dụng thành thạo phần mềm hỗ trợ thiết kế, hiểu được các ngôn ngữ lập trình - Lập tài liệu phân tích thiết kế - Hiểu được các kiểu dữ liệu và các loại ngôn ngữ lập trình Thái độ học viên: - Cẩn thận lắng nghe ý kiến và thảo luận trong nhóm thiết kế - Học viên cần tuân thủ các bài tập thực hành theo thứ tự các chương, từ dễ đến khó. Đánh giá thông qua kiểm tra trắc nghiệm: - Dùng phần mềm thi trắc nghiệm. - Kiểm tra trắc nghiệm có thể trên giấy hoặc trên máy tính. - Xây dựng ngân hàng câu hỏi, học viên sẽ nhận được một bộ để phát sinh ngẫu nhiên và chất lượng các đề như nhau (trung bình, khá, giỏi, xuất sắc). - Thời gian làm bài tuỳ theo số lượng các câu trong đề. - Thang điểm 10 chia đều cho các câu. - Kết quả đánh giá dựa vào bài làm theo điểm đạt được. Thực hành: Đánh giá thông qua khả năng giải hoàn thành chương trình (đề kiểm tra) đề ra. Thang điểm: (đánh giá câu hỏi trắc nghiệm) 0-49 : Không đạt 50-69 : Đạt trung bình 70-85 : Đạt khá 86-100 : Đạt Giỏi 8
- Bài 1 TÊN BÀI:GIỚI THIỆU TỔNG QUAN VỀ NGÔN NGỮ LẬP TRÌNH Mã bài : ITPRG3-06.1 Giới thiệu Trong lĩnh vực công nghệ thông tin, không thể không nói đến các ngôn ngữ lập trình. Bởi vì, chúng là công cụ cần thiết giúp cho chúng ta làm việc và giao tiếp với máy tính điện tử. Vì vậy, việc nắm được các khái niệm cơ bản của các ngôn ngữ lập trình là rất cần thiết đối với những ai làm việc trong lĩnh vực công nghệ thông tin hoặc các lĩnh vực có ứng dụng công nghệ thông tin. Mục tiêu thực hiện - Nắm được các khái niệm cơ bản về ngôn ngữ lập trình - Hiểu được lịch sử phát triển của ngôn ngữ lập trình - Đánh giá sơ bộ về một ngôn ngữ lập trình - Xác định lĩnh vực ứng dụng của ngôn ngữ lập trình Nội dung chính Giới thiệu lịch sử phát triến của ngôn ngữ lập trình và các khái niệm của chúng. Chỉ ra các yếu tố ảnh hưởng đến sự phát triển của ngôn ngữ lập trình và các lĩnh vực ứng dụng của ngôn ngữ lập trình. I. Lịch sử phát triển của ngôn ngữ lập trình Những ngôn ngữ lập trình (programming language) đầu tiên trên máy tính điện tử là ngôn ngữ máy (machine language), tổ hợp của các con số hệ nhị phân, hay các bit (binary digit) 0 và 1. Ngôn ngữ máy phụ thuộc vào hoàn toàn kiến trúc phần cứng của máy tính và các quy ước khắt khe của nhà chế tạo. Để giải các bài toán, những người lậ trình phải sử dụng một tập hợp các lệnh điều khiển rất sơ cấp mà mỗi lệnh là tổ hợp các bit nhị phân nên gặp rất nhiều khó khăn, mệt nhọc, rất dễ gặp phải sai sót, nhưng rất khó sửa lỗi. Từ những năm 1950, để giảm nhẹ việc lập trình, người ta đưa vào kỹ thuật chương trình con (sub-program hay sub-routine) và xây dựng các thư viện chương trình (library) đẻ khi cần thì gọi đến hoặc dùng lại các đoạn chương trình đã viết. Như thế, chúng ta nhận thấy ở vào giai đoạn sơ khai ban đầu của máy tính điện tử, việc sử dụng máy tính là rất khó khăn, vì ngôn ngữ lập trình là phương tiện giao tiếp lại quá phức tạp đối với người sử dụng. Người sử dụng máy tính vào giai đoạn này chỉ là các chuyên gia về tin học. Như thế, ứng dụng của máy tính điện tử vẫn còn rất hạn chế. II. Sự ra đời và thúc đẩy của ngôn ngữ lập trình Cũng từ những năm 1950, ngôn ngữ hợp dịch, hay hợp ngữ (assembly) ra đời. Trong hợp ngữ, các mã lệnh và địa chỉ các toán hạng được thay thế bằng các từ gợi nhớ, như ADD, SUB, JUMP, tương ứng với các phép toán số học +, -, lệnh chuyển điều khiển, 9
- Do máy tính chỉ hiểu ngôn ngữ máy, các chương trình viết bằng hợp ngữ không thể chạy ngay được mà phải qua giai đoạn hợp dịch (assembler) thành ngôn ngữ máy. Tuy nhiên, hợp ngữ vẫn còn phụ thuộc nhiều vào phần cứng và xa lạ với ngôn ngữ tự nhiên, người lập trình vẫn gặp nhiều khó khăn khi giải các bài toán trên máy tính, đặc biệt là các bài toán tương đối lớn. Năm 1957, hãng IBM đưa ra ngôn ngữ FORTRAN (FORmula TRANslator). Đây là ngôn ngữ lập trình đầu tiên gần gũi với ngôn ngữ tự nhiên với cách diễn đạt toán học. FORTRAN cho phép giải quyết nhiều loại bài toán khoa học, kỹ thuật và sau đó được nhanh chóng ứng dụng rất rộng rãi cho đến ngày nay với kho tàng thư viện thuật toán rất đồ sộ và tiện dụng. Tiếp theo là sự ra đời của các ngôn ngữ ALGOL 60 (ALGOrithmic Language) vào năm 1960, COBOL (Common Business Oriented Language) vào năm 1964, Simula vào năm 1964, Theo sự phát triển của máy tính điện tử, các ngôn ngữ lập trình không ngừng được cải tiến và hoàn thiện đẻ ngày càng đáp ứng nhu cầu của người sử dụng và làm giảm nhẹ công việc lập trình. Sự phát triển của ngôn ngữ lập trình đã làm xích gần lại “khoảng cách” giữa người sử dụng và máy tính, nghĩa là máy tính không còn chỉ được sử dụng bởi các chuyên gia tin học mà có thể được sử dụng bởi rất nhiều người trong nhiều lĩnh vực khác nhau. Rất nhiều ngôn ngữ lập trình đã được ra đời trên nền tảng lý thuyết tính toán và hình thành nên hai loại ngôn ngữ lập trình: ngôn ngữ bậc thấp và ngôn ngữ bậc cao. Các ngôn ngữ bậc thấp (low-level language) gồm hợp ngữ và ngôn ngữ máy, thường được dùng để viết các chương trình điều khiển và kiểm tra thiết bị, Các ngôn ngữ bậc cao (high-level language) là phương tiện giúp người làm tin học giải quyết các vấn đề thực tế đồng thời cũng là nơi mà những thành tựu mới nhất của khoa học máy tính được đưa vào. Lĩnh vực nghiên cứu các ngôn ngữ lập trình vừa có tính truyền thống vừa có tính hiện đại. Ngày nay, với các tiến bộ của khoa học công nghệ, người ta đã có thể sử dụng các công cụ hình thức cho phép giảm nhẹ công việc xây dựng các hệ thống chương trình từ phân tích, thiết kế cho đến sử dụng ngôn ngữ lập trình. Chúng ta có thể có cài nhìn toàn cảnh hơn về lích sử của các ngôn ngữ lập trình qua hình vẽ dưới đây: 10
- Hình 1.Ngôn ngữ lập trình qua hình vẽ III. Phân loại các ngôn ngữ lập trình Cho đến nay có hàng trăm ngôn ngữ lập trình được đề xuất nhưng trên thực tế chỉ có một số ít ngôn ngữ được sử dụng rộng rãi. Ngoài cách phân loại theo bậc như đã nói ở trên, người ta còn phân loại ngôn ngữ lập trình theo phương thức (paradgm), theo mức độ quan trọng, theo thế hệ, Cách phân loại theo mức hay bậc là dựa trên mức độ trừu tượng so với các yếu tố phần cứng, chẳng hạn như lệnh (instruction) và cấp phát bộ nhớ (memory allocation) dưới đây: Mức Lệnh Sử dụng bộ nhớ Ví dụ Thấp Lệnh máy đơn giản Truy cập và cấp phát trực tiếp Hợp ngữ Cao Biểu thức và điều Truy cập và cấp phát nhờ các C, Pascal, Ada kiện tương minh phép gán Rất cao Máy trừu tượng Truy cập ẩn và tự động cấp Prolog, Miranda phát 11
- Những năm gần đây, ngôn ngữ lập trình phát triển theo phương thức lập trình (còn được gọi là phong cách hay kiểu lập trình). Một phương thức lập trình có thể được hiểu là một tập hợp các tính năng trừu tượng đặc trưng cho một lớp ngôn ngữ mà có nhiều người lập trình thường xuyên sử dụng chúng. Sau đây là sơ đồ minh họa sự phân cấp các phương thức lập trình: Phương thức lập trình Mệnh lệnh Khai báo Thủ tục Hướng Xử lý Logic Hàm Cơ sở dữ đối tượng song liệu song Sau đây là một sốngôn ngữ lập trình quen thuộc liệt kê theo phương thức: Các ngôn ngữ thủ tục (procedural language) có Fortran (1957), Cobol (1959), Basic (1965), Pascal (1971), C (1969), Ada (1975), Các ngôn ngữ hướng đối tượng (object-oriented language) có Smalltalk (1971), C++ (1985), Eiffel (1990), Java (1995), C# (2000), Các ngôn ngữ hàm (functional language) có Lisp (1958), ML (1973), Scheme (1975), Caml (1987), Miranda (1982), Các ngôn ngữ dựa logic (logic-based language) chủ yếu là ngôn ngữ Prolog (1972). Các ngôn ngữ thao tác cơ sở dữ liệu SQL (1980), Các ngôn ngữ xử lý song song như Ada, Occam (1982), C-Linda, Ngoài ra còn có một số phương thức lập trình đang được phát triển ứng dụng như: Lập trình phân bổ (distributed programming). Lập trình ràng buộc (constraint programming). Lập trình hướng truy cập (access-oriented programming). Lập trình theo luồng dữ liệu (dataflow programming). Việc phân loại các ngôn ngữ lập trình theo mức độ quan trọng là dựa trên cái gì (what) sẽ thao tác được, hay tính được, so với cách thao tác như thế nào (how). Một ngôn ngữ thể hiện cái gì sẽ thao tác được mà không chỉ ra cách thao tác như thế nào được gọi là ngôn ngữ định nghĩa (definitional) hay ngôn ngữ khai báo (declarative). Một ngôn ngữ thể hiện cách thao tác như thế nào mà không chỉ ra cái gì sẽ thao tác được gọi là ngôn ngữ thao tác (operational) hay không khai báo (non-declarative), đó là các ngôn ngữ mệnh lệnh. Ngoài ra, các ngôn ngữ lập trình cũng được phân chia theo thế hệ như sau: Thế hệ 1: ngôn ngữ máy Thế hệ 2: hợp ngữ Thế hệ 3: ngôn ngữ thủ tục 12
- Thế hệ 4: ngôn ngữ áp dụng hay hàm Thế hệ 5: ngôn ngữ suy diễn hay dựa logic Thế hệ 6: mạng nơ-ron (neural networks) IV. Vai trò và ảnh hưởng của phần cứng đối với ngôn ngữ lập trình Ngôn ngữ lập trình liên quan chặt chẽ đến kiến trúc máy tính (phần cứng) mà nó chạy trên đó. Chương trình viết bằng một ngôn ngữ, dù là ngôn ngữ đó thế nào đi nữa thì cũng phải chuyển sang mã máy để thực thi. Ngôn ngữ máy gắn chặt vào kiến trúc máy tính, vì vậy hiểu quả thực thi của ngôn ngữ cũng phụ thuộc rất nhiều vào kiến trúc máy tính. Một kiến trúc máy tính xử lý tuần tự không thể thực hiện song song thật sự nhiều phát biểu trong chương trình, dù cho dạng đặc tả trong ngôn ngữ là xử lý song song. Một kiến trúc máy tính chỉ cho phép đọc/ghi mỗi lần một từ nhớ không thể bảo đảm việc xử lý đồng thời cả một đối tượng dữ liệu, dù rằng dạng đặc tả của hầu như tấct cả các ngôn ngữ lập trình hiện nay là thao tác trên cả một đối tượng dữ liệu. Kiến trúc may tính cũng ràng buộc quá trình thiết kế ngôn ngữ chạy trên máy tính đó. Ngôn ngữ lập trình càng xa ngôn ngữ máy bao nhiêu thì càng khó thực hiện bấy nhiêu. Trong trường hợp đó chương trình dich sẽ phức tạp và đòi hỏi mô phỏng nhiều. Quá trình dịch cũng sẽ sinh ra những đaọn mã máy thừa, và do đó khó đạt được hiệu quả cao trong việc sử dụng phần cứng máy tính. Kiến trúc của hầu hết máy tính hiện nay là kiến trúc Von Neumann. Kiến trúc Von Neumann có hai đặc điểm đáng chú ý sau: Các lệnh được xử lý tuần tựtheo thứ tự sắp xếp trong bộ nhớ. Đặc điểm này thể hiện rõ ở các ngôn ngữ lập trình thủ tục, trong đó các phát biểu được thực hiện theo thứ tự sắp xếp trong chương trình. Các quá trình mang bản chất xử lý song song không thể được thực hiện một cách hiệu quả trên kiến trúc Von Neumann. Chỉ cho phép đọc/ghi mỗi lần một từ nhớ. Kết quả là đối tượng dữ liệu phải bị phân nhỏ ra trước khi đọc/ghi đối tượng dữ liệu đó. Quá trình xử lý dữ liệu do vậy bị chậm đi gây nên sự tắc nghẽn trong ”giao thông” các đối tượng dữ liệu. Giả sử s là một biến chuỗi, phép gán s:= ‘abc’ không làm thay đổi tức thời đối tượng mà thông qua việc cắt từng kí tự của chuỗi ở vế phải vào s. Các đối tượng dữ liệu kiểu bản ghi, như trong ngôn ngữ Pascal, không được xuất hiện như sự xuất hiện của nó với tư cách là một thực thể hoàn chỉnh trong chương trình. Chẳng hạn, phép gán x := y, với x và y là hai biến cùng một kiểu bản ghi, được thực hiện bằng cách đọc từng thành phần của y và chi vào thành phần tương ứng của x. Bản thân mỗi thành phần x và y lại tiếp tục bị phân rã trong quá trình đọc/ghi. Ngay cả việc đọc/ghi đối tượng dữ liệu đơn giản như số nguyên dài hai từ nhớ (longint) cũng phải được thực hiện hai lần. Các phương thức lập trình mới như lập trình hàm, lập trình hướng đối tượng, lập trình logic đang từ bỏ dần dần kiến trúc Von Neumann. Lisp là ngôn ngữ điển hình của họ lập trình hàm, tuy nhiên các đặc tính hàm ban đàu của Lisp cũng đã bị sửa đổi để có thể cài đặt hiệu quả trên kiến trúc máy tính cổ điển. Prolog đại diện cho họ ngôn ngữ lập trình logic, là một công cụ đặc tả vấn đề tốt, nhưng hiệu quả của nó chưa được như mong muốn do cách 13
- giải quyêt vấn đề trên đặc tả của ngôn ngữ bị hạn chế bởi cơ chế điều khiển của kiến trúc máy tính hiện thời. Tóm lại, vì sự ảnh hưởng đáng kể của phần cứng như vừa trình bày đối với ngôn ngữ lập trình, bước nhảy vọt thật sự trong ngôn ngữ lập trình, với một ngôn ngữ mới thay đổi tận gốc, chỉ có khi có một kiến trúc máy tính mới bảo đảm cho sự cài đặt hiệu quả của ngônngữ mới trên đó. V. Các thuộc tính của ngôn ngữ lập trình tốt Một ngôn ngữ lập trình tốt cần phải thỏa mãn một số yêu cầu cơ bản sau đây: dễ viết, dễ đọc và tin cậy. A. Tính dễ viết (writability) Với tư cách là công cụ để viết chương trình, ngôn ngữ lập trình phải dễ sử dụng, tức là dễ viết. Ngôn ngữ phải có tính diễn đạt cao. Trường hợp lý tưởng là có ánh xạ từ những suy nghĩ của người lập trình về vấn đề cần giải quyết và chiến lược giải quyết vấn đề vào ngôn ngữ. Nó liên quan đến khả năng trừu tượng hóa dữ liệu và điều khiển của ngôn ngữ. Chẳng hạn, các khiếm khuyết của ngôn ngữ Pascal như không có phát biểu nhập xuất cho kiểu liệt kê hay hàm không thể trả về kết quả kiểu dãy hoặc kiểu bản ghi cũng gây trở ngại cho người lập trình trong một số trường hợp. Một ví dụ khác là khai niệm đệ quy. Đệ quy không chỉ là một kỹ thuật để lập trình mà còn là công cụ để tư duy. Có những vấn đề mà người lập trình không nghĩ ra một cách giải quyết nào khác ngoài cách sử dụng đệ quy. Vì vậy, ngôn ngữ cần cho phép gọi đệ quy các chương trình con. Tuy nhiên, các ngôn ngữ bậc cao đầu tiên như Fortran, Cobol không cho phép gọi đệ quy. Muốn thực hiện lời giải đệ quy trên các ngôn ngữ này phải gỡ đệ quy, tức là chuyển lời giải đệ quy thành lời giải lặp. Ngôn ngữ phải đơn giản, để dễ học, dễ nhớ và dễ nắm vững. Ngôn ngữ không nên đưa ra quá nhiều khái niệm làm cho người sử dụng thấy rối rắm. Chẳng hạn, như ngôn ngữ C có quá nhiều toán tử. Nếu như trong Pascal các toán tử Not, And và Ỏ được dùng chung cho kiểu dữ liệu logic và kiểu dữ liệu số học thì trong C lại có sự phân biệt: các toán tử !, &&, || dùng cho kiểu logic còn các toán tử ~, & và | dùng cho kiểu dữ liệu số học. Số lượng toán tử nhiều, các kí hiệu lại gần giống nhau và không gợi nhớ đã làm tăng tính phức tạp của ngôn ngữ C. Ngôn ngữ phải linh hoạt, không nên quá gò bó vào một nguyên tắc nào đó. Chẳng hạn một trong những nguyên tắc của ngôn ngữ Pascal là: mỗi cấu trúc điều khiển chỉ có một ngõ vào và một ngõ ra. Nó ngăn cấm sự kết thúc bất thường từ bên trong vòng lặp For và các vòng lặp có điều kiện như While và Repeat, cũng như không cho nhiều điểm trở về trong chương trình con. Nguyên tắc này nhằm đảm bảo tính cấu trúc của chương trình nhưng đôi khi cũng gây trở ngại cho người lập trình. Ngôn ngữ C linh hoạt hơn với các phát biểu Break và Return. Ví dụ, trong trường hợp sau, phát biểu Return của C tránh được các phát biểu if lồng nhau: Bằng C: p () 14
- { ; if ( ) return; ; if ( ) return; ; return; } Bằng Pascal: Procedure p; Begin ; if not( ) then begin ; if not( ) then begin ; end; end; End; B. Tính dễ đọc (readability) Yêu cầu về tính dễ đọc của ngôn ngữ xuất phát từ yêu cầu về tính dễ đọc của chương trình. Chương trình cần phải dễ đọc vì đọc chương trình là công việc thường xuyên trong khi viết, bảo trì và phát triển chương trình. Tính dễ đọc càng có ý nghĩa khi người đọc không phải là tác giả của chương trình. Nói một cách lý tưởng, chương trình được gọi là dễ đọc khi một người bình thường không biết gì về ngôn ngữ dùng để viết chương trình, đọc mà vẫn hiểu được chương trình làm gì. Ngôn ngữ cần đảm bảo cho cấu trúc chương trình phản ánh được cấu trúc của vấn đề cần giải quyết. Chương trình cũng như các bộ phận của chương trình cần thể hiện được chúng làm những gì, trước khi người đọc cần biết những điều đó được thực hiện như thế nào. Các cấu trúc điều khiển của ngôn ngữ chỉ nên có một ngõ vào và một ngõ ra để người đọc có thể theo dõi dễ dàng từng bộ phận của chương trình từ trên xuống dưới. Cũng do vây ngôn ngữ cần có các cấu trúc điều khiển để thay thế phát biểu goto. Phát biểu goto làm chương trình trở nên khó đọc, vì người đọc không thể đọc chương trình một mạch từ trên xuống, mà luôn phải dò theo các đích của goto. Việc cho phép dấu gạch ngang dưới trong danh hiệu sẽ làm cho danh hiệu dễ đọc hơn. Các từ khóa của ngôn ngữ cũng cần phải gợi nhớ. 15
- Ngoài ra, các chú thích trong chương trình là cách thức hữu hiệu để nâng cao tính dễ đọc của chương trình. Với các ngôn ngữ lập trình hiện nay, tính dễ đọc của chương trình còn tùy thuộc vào việc người đọc có quen thuộc với ngôn ngữ dùng để viét chương trình hay không, cũng như phong cách và phương pháp lập trình của người viết chương trình. C. Tính tin cậy (reliability) Tính tin cậy của ngôn ngữ lập trình được đánh giá trên khả năng mà ngôn ngữ có thể bảo đảm được cho tính tin cậy của chương trình viết bằng ngôn ngữ đó. Không có một định nghĩa rõ ràng cho tính tin cậy của chương trình. Một cách không hình thức, chương trình được xem là tin cậy nếu xác xuất chạy đúng của nó cao trong quá trình sử dụng. Nói chúng để chương trình có độ tin cậy cao, ngôn ngữ cần hạn chế sự xuất hiện của các lỗi không thể ngờ được. Lỗi đó có thể sinh ra do một cấu trúc ngữ pháp nhiều ngữ nghĩa. Ví dụ phát biểu sau đây trong Fortran: Sum (i, j) = i + j Có hai nghĩa: (1) đó là định nghĩa hàm tổng của hai đối số nguyên, (2) hoặc đó là phép gán một biểu thức vào dãy biến. Nếu người lập trình quên khai báo biến dãy Sum thì phát biểu trên sẽ được hiểu theo nghĩa thư nhất, chứ không phải là nghĩa thứ hai như mong muốn (việc quên khai báo biến trong Fortran là bình thường vì Fortran cho phép sử dụng biến mà không cần khai báo). Hiệu ứng lề cũng là nguồn gây ra lỗi. Hiệu ứng lề, theo nghĩa rộng là hiệu ứng phụ xuất hiện thường ngoài ý muốn, khi sử dụng một cấu trúc ngôn ngữ nào đó của ngôn ngữ. Ví dụ, trong Pascal hiệu ứng lề nảy sinh do sự thay đổi trị của biến không cục bộ trong các chương trình con mà biến có ý nghĩa. Ngôn ngữ cần kiểm tra chặt chẽ sự tương hợp kiểu của các biến trong biểu thức và phép gán, sự tương hợp của danh sách thông số của chương trình con ở nơi gọi và nơi định nghĩa. Lấy ví dụ đơn giản là phép gán một giá trị thực vào biến nguyên. Nếu không có sự kiểm tra kiểu và báo lỗi, mà tự động đổi kiểu thì kết quả sau đó sẽ bị sai lệch đi vì giá trị thực đã bị cắt hoặc làm tròn phần lẽ để thành giá trị nguyên. Các bộ phận của chương trình cần có tính độc lập đối với nhau cao, cho phép kiểm tra riêng rẽ tính đúng đắn từng bộ phận của chương trình. Tính dễ sửa đổi cũng liên quan với tính tin cậy, vì trong quá trình sửa đổi tính tin cậy vần phải được duy trì. Sự độc lập của các bộ phận của chương trính sẽ làm cho việc sửa đổi dễ dàng hơn; việc sửa đổi ở một bộ phận sẽ không ảnh hưởng đến các bộ phận còn lại cảu chương trình. Tính tin cậy đôi khi lại có thể bị ảnh hưởng bởi tính dễ viết của chương trình. Bởi vì ngôn ngữ quá linh hoạt dễ viết thì có thể khó làm chủ bởi người viết chương trình, có nghĩa là chương trình dễ xảy ra lỗi hơn. Cuối cùng, tính tin cậy của ngôn ngữ còn phụ thuộc vào sự cài đặt ngôn ngữ ấy, tức là phụ thuộc vào chất lượng của chương trình dịch. Các lĩnh vực ứng dụng của ngôn ngữ lập trình Vào những năm 1950, khi máy tính điện tử mới ra đời, ngôn ngữ lập trình chỉ là ngôn ngữ máy hoặc cao hơn là hợp ngữ. Cho nên, việc xây dựng các ứng dụng tin học là rất 16
- phức tạp và khó khăn. Vì thế, vào giai đoạn này ứng dụng của ngôn ngữ lập trình là rất hạn chế. Đi đôi với sự phát triển của phần cứng và sự ra đời của rất nhiều ngôn ngữ lập trình khác nhau, khả năng và lĩnh vực ứng dụng của ngôn ngữ lập trình trở nên rất phong phú. Dưới đây là một số ngôn ngữ lập trình và lĩnh vực ứng dụng của chúng: Xây dựng các hệ thống thông tin quản lý trong các lĩnh vực sản xuất, xã hội, kinh tế, : Delphi, Visual Basic, Access, SQL, Xây dựng các hệ thống phân tán: Java, Corba, C++, Xử lý ảnh và mô hình hóa hình học: C++, Matlab, Xây dựng các hệ chuyên gia: Prolog Giải quyết các bài toán trong lĩnh vực trí tuệ nhân tạo: Lisp, Scheme, Prolog, Xây dựng các hệ thống thời gian thực: C, Ada Xây dựng các hệ thống nhúng, điều khiển thiết bị: C, Assembly, VI. Các lĩnh vực ứng dụng của ngôn ngữ lập trình Hiện nay, số người quen với máy tính, với việc lập trình ngày một nhiều, PC đã trở nên phổ biến. Nhu cầu được giao tiếp với thế giới bên trong máy tính không chỉ là một sở thích, hay công việc riêng tư của những người làm tin học nữa. Chỉ với vốn tiếng Anh tương đối, một chút trợ giúp là bạn đã có thể trở thành một nhà lập trình rồi, thế nhưng đó chỉ là các điều kiện cần mà chưa đủ. Số lượng trình biên dịch, chủng loại, tính năng ngày một phong phú, để chọn cho mình một ngôn ngữ lập trình, một trình biên dịch phù hợp với công việc chuyên môn cũng như nhu cầu học tập, nghiên cứu, bạn không thể không khỏi có những đắn đo. Những gì sau đây có thể giúp ích cho bạn ? Để có cho mình một công cụ lập trình phù hợp về cả trình độ lẫn nhu cầu, bạn cần xác định xem bạn sẽ dùng nó để làm gì; tìm hiểu thế giới bên trong máy tính, chỉ để học thêm một ngôn ngữ lập trình mới nhằm phục vụ cho quá trình học tập, hay đó là một lựa chọn cho một hướng phát triển phần mềm chuyên nghiệp ? Hơn thế nữa bạn còn cần phải định hướng rõ ràng; môi trường thực hiện sẽ là môi trường phân tán hay môi trường cục bộ ? Có thể là hơi rắc rối nhưng những suy tính ban đầu này sẽ có ảnh hưởng rất nhiều tới các bước đi sau này. Những người có ham muốn tìm hiểu sâu thế giới bên trong máy tính thường lấy hợp ngữ (Assembly) làm công cụ, có thể nói đây là thứ ngôn ngữ đầu tiên tương đối độc lập đối với các quá trình thực xảy ra trong các bộ vi xử lý. Qua một tập hữu hạn các lệnh được nhận biết nhờ các từ gợi nhớ sơ đẳng, người lập trình có thể trực tiếp can thiệp vào quá trình di chuyển dữ liệu, sửa đổi dữ liệu, điều khiển thiết bị Công việc còn lại của trình dịch Assembler rất ít, phần lớn nhiệm vụ của nó là ánh xạ các lệnh gợi nhớ trong chương trình nguồn tới một tập cố định các lệnh của bộ vi xử lý, một số thao tác xử lý macro. Để có được một chương trình hoàn chỉnh, người lập trình sẽ phải tìm hiểu thấu đáo tập lệnh, vì số lệnh, các chi tiết kỹ thuật cho tập lệnh có thể rất khác nhau giữa các bộ vi xử lý; định hình rõ ràng trình tự các thao tác; khả năng mà trình dịch có thể làm được; và nhất là xác định mức độ cần thiết của các thủ thuật lập trình. Chẳng hạn, trong khi các bộ vi xử lý 17
- dòng Intel (x86 phổ dụng trong các máy PC) thường có khoảng 8 thanh ghi đa năng, 6 thanh ghi đoạn, một thanh ghi con trỏ lệnh, cờ thì các bộ vi xử lý dòng Motorola (MC680x0 phổ dụng trong các máy MacIntosh, các máy trạm của Sun, trong các hệ thống máy tính nhiều bộ vi xử lý, và trong rất nhiều máy PC) thì lại có tới khoảng 8 thanh ghi dữ liệu 80 bit, khoảng ngần ấy số thanh ghi địa chỉ cùng hàng tá thanh ghi với rất nhiều công dụng khác nhau, chế độ làm việc khác nhau. Chính vì tính chỉ định phần cứng cao như vậy mà hiệu quả làm việc của một người thông qua hợp ngữ phụ thuộc rất nhiều vào kinh nghiệm làm việc, theo đó các chương trình này rất khó bảo trì, khó kiểm soát khi số thao tác của chương trình tăng, và đôi khi còn khó hiểu đối với chính người viết ra nó nếu không có các văn bản bảo trì được ghi chép cẩn thận. Nhưng bù lại, các chương trình thực hiện bằng hợp ngữ nói chung thường có kích thước rất khiêm tốn, chạy nhanh nhất tính trên cùng một trình tự thao tác cụ thể so với các ngôn ngữ khác. Basic vốn là một ngôn ngữ phi cấu trúc, nó được phát triển để giúp người lập trình đỡ phần vất vả khi làm việc trên các bộ vi xử lý khác nhau. Với nó, người lập trình không phải lo lắng nhiều về sự khác nhau trong chi tiết kỹ thuật của từng bộ vi xử lý cụ thể, họ chỉ cần bận tâm tới việc cấu trúc sao cho chương trình của họ được tối ưu. Để có được tính khả chuyển trên nhiều loại vi xử lý, các chương trình Basic cần có một chương trình thông dịch để kích hoạt, trình thông dịch này có nhiệm vụ ánh xạ mã đầu ra của trình dịch Basic vào tập lệnh cụ thể của bộ vi xử lý khi chạy chương trình. Người ta đã từng đưa trình thông dịch này vào trong phần cứng, lưu trữ lâu dài trong các bộ nhớ chỉ đọc (ROM), và cung cấp các khả năng tương tác tương đối thuận tiện, giúp người lập trình thiết kế và gỡ rối nhanh chóng các chương trình Basic. Ngày nay, Basic đã được cải tiến nhiều, về cả trình dịch lẫn bản thân ngôn ngữ, các ứng dụng của Microsoft thường dùng Basic như một công cụ để người sử dụng tuỳ biến chúng theo nhu cầu. Vì chạy thông dịch cho nên các ứng dụng viết bằng Basic chạy không nhanh, nhưng vì tính phổ cập, rất nhiều nhà phát triển công cụ vẫn hỗ trợ nó. Sản phẩm hỗ trợ Basic ở mức cao được nói đến ở đây là Visual Basic của Microsoft. Đây là một công cụ phát triển được Công ty này rất ưu ái, hiện nó đang được ưu chuộng trong lĩnh vực phát triển ứng dụng trên Windows. Visual Basic được hỗ trợ rất nhiều khả năng về cơ sở dữ liệu, các kỹ thuật phát triển phần mềm mới như OLE, COM, DCOM Nếu như bạn chưa có ý định trở thành nhà phát triển phần mềm ứng dụng thì cũng nên biết tới Basic, bởi vì hầu hết các ứng dụng lớn ngày này như Notes, bao gồm cả các phần mềm xử lý bảng tính, văn bản của Mirosoft đều có sử dụng macro lệnh được thiết kế dựa trên Basic, cho phép người sử dụng sửa đổi, bổ sung các tính năng mới theo nhu cầu. Ngoài ra còn phải nói tới Pascal, có thể nói đây là thứ ngôn ngữ vỡ lòng cho hầu hết những người bắt đầu tiếp xúc với máy tính. Nó được biết tới không chỉ vì là một trong số các ngôn ngữ cấu trúc ra đời đầu tiên trên thế giới, mà còn là vì tính dễ đọc, dễ tiếp cận của nó. Nếu bạn biết tiếng Anh, không nhất thiết phải biết về tin học, khi đọc một chương trình viết trong ngôn ngữ này bạn sẽ thấy ngay về cơ bản nó đang nói về một quá trình làm việc nào đó. 18
- Với thứ ngôn ngữ này, người lập trình khỏi phải đau đầu vì phải tổ chức lấy chương trình, thay vào đó họ sẽ dùng các câu lệnh tiếng Anh rất dễ nhớ, dễ sử dụng. Việc xây dựng một chương trình rất giống với việc mô phỏng một quá trình hoạt động, có đầu ra đầu vào, mã nguồn của một chương trình như thế rất dễ đọc, dễ sửa đổi. Tất nhiên, trình dịch sẽ phải làm việc vất vả hơn bởi nó phải phân giải cả một dãy lệnh vốn chỉ dễ hiểu đối với con người nhưng lại không thể hiểu nổi đối với các bộ vi xử lý. Hầu hết các ngôn ngữ lập trình cấu trúc (tất nhiên trong đó có Pascal) đều lấy việc dịch sang hợp ngữ làm một bước trung gian, theo đó các cấu trúc lệnh if then, case of, v.v. được chuyển thành các khối mã nguồn Assembly. Tóm lại, việc cấu trúc chi tiết cho một chương trình cụ thể được thực hiện tự động bởi trình dịch, lúc này các thủ thuật lập trình Assembly của người lập trình không còn có thể áp dụng vào đây, đôi khi nó còn máy móc làm phình to mã cho dù đã sử dụng tới cả chục thuật toán tối ưu. Hầu hết các công cụ phát triển có hỗ trợ Pascal ngày nay đều đưa ra các khả năng kết nối mới cho nó, mã trình có thể được viết riêng rẽ trên nhiều tệp rồi kết nối, hoặc được nạp từ thư viện động nhưng nói chung, đây là ngôn ngữ chỉ phù hợp với các ứng dụng nhỏ và trung bình, phổ dụng trong lĩnh vực đào tạo. Nếu bạn là người mới tiếp xúc với máy tính, muốn tìm hiểu cách hoạt động của một chương trình thì bạn hãy chọn ngôn ngữ này. Delphi của Borland chỉ là một công cụ phát triển ứng dụng, nó được xây dựng bằng lõi Pascal. Với công cụ này, sau một vài tiếng đồng hồ đọc help, nhất là có ai đó hướng dẫn đôi chút, bạn hoàn toàn có thể tự viết cho mình các ứng dụng đơn giản như trình xem tệp .AVI, nghe nhạc, các thao tác tính toán, lưu trữ đơn giản Nó tỏ ra rất thích hợp với những bạn thích khám phá nhưng không muốn tốn quá nhiều thời gian nghiền ngẫm. Ngôn ngữ C là ngôn ngữ lập trình cấu trúc như Pascal và là thứ công cụ mạnh đã từng được sử dụng để thiết kế hầu hết các hệ điều hành trên thế giới. Các hệ điều hành như UNIX, AMOEBA đều thực thi bằng C, và nói chung đây là thứ ngôn ngữ có tính khả chuyển tương đối cao cho nên các hệ điều hành này có thể chạy trên rất nhiều phần cứng khác nhau, ngay cả với WINDOWS cũng vậy, rất nhiều module của nó cũng được xây dựng bằng C. C++ là một bước phát triển tiếp theo của C trong xu thế 'đối tượng hoá' ngôn ngữ, nói như vậy là bởi hầu hết các trình dịch C++ đều lấy C làm nền cho tất cả các định hướng nhằm tận dụng các ưu thế mà mô hình thiết kế hướng đối tượng mang lại. Vốn dĩ C vẫn chưa được chuẩn hoá mặc cho rất nhiều cố gắng đã được đưa ra, các trình dịch C++ lại càng khó tìm được tiếng nói chung. Các nhà cung cấp trình dịch C đều muốn rằng sản phẩm của họ được các nhà phát triển công cụ ưa dùng, thế nhưng các nhà cung cấp công cụ phát triển lại muốn các trình dịch hướng theo mô hình thiết kế vốn muôn hình muôn vẻ mà họ đưa ra. Cứ như thế, C++ phát triển trong sự thiếu nhất quán, hệ thống từ khoá không được hỗ trợ đầy đủ, đôi khi không thống nhất, cách cấu trúc chương trình cũng không giống nhau mặc dù chúng giống nhau về mô hình. Ngày nay, hầu hết các công cụ phát triển hệ thống mạnh như Visual C++, C++Builder, Visual Age đều hỗ trợ song song cả C lẫn C++. Nói chung đây là các công cụ mạnh, thể hiện được ưu thế của chúng trong từng môi trường phát triển cụ thể; ví dụ Visual C++ thích 19
- hợp với những người muốn phát triển các ứng dụng nhất là các ứng dụng gắn với Windows, C++Builder thân thiện ngay cả với những người không nhiều kinh nghiệm trong lĩnh vực lập trình, Để tìm cho mình một trình dịch C++ phù hợp hãy lựa chọn; chẳng hạn, nếu bạn cần hướng theo việc xây dựng các ứng dụng phục vụ, có liên quan tới các dịch vụ chuẩn của Windows, không nhất thiết phải có màn hình giao tiếp phức tạp, hoặc cần có các ứng dụng can thiệp sâu vào hệ thống bạn hãy lựa chọn Visual C++. Công cụ này đưa ra khá nhiều mẫu (wizard), theo khung định sẵn đó bạn chỉ cần thực thi các chi tiết là đã có một ứng dụng hoàn chỉnh rồi. Còn nếu bạn không đủ thời gian cần thiết để nghiền ngẫm cả đống các văn bản công bố từ Microsoft, mà lại muốn có các ứng dụng mang tính bề mặt, nhanh, đầy tính tương tác, bạn hãy sử dụng C++Builder hay một số sản phẩm tương tự từ IBM, Symantec Java là ngôn ngữ thế hệ mới, thế hệ năm, nó kế thừa hầu hết những 'tư chất' tốt đẹp của các bậc tiền bối, hướng đối tượng từ mô hình thiết kế tới mô hình thực thi, hỗ trợ đa luồng một cách rất tinh tế, độ tin cậy cao, tính khả chuyển tuyệt vời Java nay không còn là một cơn sốt bình thường, nó là một xu thế song song tồn tại với các mô hình lập trình hiện có, ngày càng nhiều lĩnh vực mà nó có mặt. Ban đầu, mục tiêu của các nhà thiết kế của ngôn ngữ này là "Web đi tới đâu, Java đi tới đó", nay thì sao, nó đang len lỏi vào cả các hệ thống đầy tính thương mại như các hệ quản trị dữ liệu của ORACLE, rồi cả các hệ thống phục vụ cực lớn Với phiên bản 2, từ tên ấn bản JDK được đổi thành SDK, Sun dần lộ rõ những ham muốn rất lớn lao trong việc đưa Java vào đời sống tin học của mọi người trên thế giới. Java là ngôn ngữ mạnh, về cả mô hình thiết kế lẫn tính năng. Nếu bạn muốn thiết kế các trang Web sống động, bạn hãy chọn nó, một khối mã .CLASS vài KB có thể làm được nhiều điều hơn cả 100KB ảnh, nó là giải pháp cho một đường truyền tốc độ thấp. Nếu bạn muốn thiết kế các chương trình phân tán, Java là một lựa chọn tốt, nó có một lượng thư viện mạng được tổ chức hợp lý, thân thiện với người lập trình. Với nó bạn có thể tự thiết kế lấy các giao thức (ngay cả các giao thức lạ lẫm chưa từng được nhắc tới trong RFC), các ứng dụng phục vụ, và các ứng dụng sử dụng dịch vụ mà không đòi hỏi mất quá nhiều thời gian tìm hiểu hệ thống, tìm kiếm các công bố kỹ thuật. Nếu bạn cần viết các ứng dụng mà mã của chúng có thể được sử dụng lại một cách linh hoạt, trên nhiều loại phần cứng, tốn ít thời gian bảo trì và hợp 'thời' nhất, bạn cũng nên chọn Java. Với các ngôn ngữ khác, việc sử dụng lại mã rất khó, ví dụ bạn đã có một tệp .dll, cùng với hàng tá chi tiết kỹ thuật kèm theo bạn cũng rất khó sử dụng lượng thư viện có trong đó, đấy là chưa tính tới việc mã thư viện động này chỉ có thể sử dụng được trên các hệ thống Windows. Với Java thì lại khác, mô hình thiết kế của nó cho phép mã của mỗi lớp được gói trong một tệp .CLASS riêng, được kiểm soát trong không gian tên bởi hệ thống chạy Java, và được nạp một cách tường minh mỗi khi chương trình cần tới các hành vi của chúng. Có thể xem môi trường chạy Java lúc này là một cái giỏ táo, mỗi quả táo là một đối tượng, vết kích hoạt của một chương trình Java rất giống như lối của các con sâu, đục xuyên từ quả này sang quả khác ứng với một con sâu, chương trình có một luồng kích hoạt, nhiều con sâu ứng với một chương trình Java đa luồng (multithread). 20
- Môi trường kích hoạt Java có xu hướng phân tán, các đối tượng kích hoạt có thể không cùng nằm trên một máy duy nhất, theo đó nó có thể nằm rảI rác đâu đó trên mạng, chúng 'liên kết' với nhau để hình thành một chương trình thông qua mạng Thế nhưng, khi các ưu thế trên không có trong định hướng của bạn về một công cụ lập trình, bạn đừng nên sử dụng nó. Thứ nhất, Java chạy thông dịch, tốc độ chậm dù đã được cải thiện nhờ cơ chế dịch JIT (một cơ chế nhận biết để ánh xạ một cách thông minh khối mã đầu vào cần thông dịch và khối mã đầu ra cần kích hoạt nhằm tiết kiệm thời gian dịch), và dù có mong đợi thế nào thì Java vẫn sẽ chạy thông dịch. Thứ hai, bản thân ngôn ngữ này đang trong thời gian hoàn thiện; hoàn thiện về hệ thống từ khoá, hoàn thiện về cách tổ chức máy ảo, hoàn thiện về thư viện Có rất nhiều loại ngôn ngữ lập trình :Java,C#,VB.Net,PHP,ASP Java thì lập trình cho các thiết bị di động nhiều hơn. Lập trình web thì có thể dùng Java hoặc PHP Bạn có thể thiết kế web bằng ASP.NET, viết bằng ngôn ngữ C#, hoặc VB.NET Ngoài ra còn có công nghệ .NET của Microsoft, hỗ trợ rất nhiều ngôn ngữ Về lập trình phần mềm thì có rất nhiều ngôn ngữ thông dụng tùy theo loại phần mềm nào : - Phần mềm quản lý chạy trên windows : Visual Basic 6.0, Visual Basic.NET , Visual C# - Web : ASP, ASP.NET, PHP, JSP, CGI, Perl - Phần mềm mạng : Java, Visual C++ - Lập trình hệ thống : C/C++ - Trí tuệ nhân tạo : Prolog ngoài ra còn nhiều ngôn ngữ khác nữa phục vụ cho nhiều mục đích khác nhau VII. Chuẩn hoá ngôn ngữ lập trình Là việc chọn lọc một ngôn ngữ theo một chuẩn ngôn ngữ nhất định nào đó phù hợp để có thể phát triển và ứng dụng rộng rãi trong các công việc Khi việc chuẩn hoá ngôn ngữ hoàn thành thì nó sẽ trở thành một ngôn ngữ tiêu chuẩn trên toàn thế giới với các mức khác nhau của tiêu chuẩn VIII. Các vấn đề nảy sinh từ sử dụng ngôn ngữ lập trình Mỗi ngôn ngữ, do hạn chế của môi trường và bản thân ngôn ngữ cũng như do mục tiêu sử dụng, có thể có một số luật cấm mà người lập trình không thể vi phạm. Những luật cấm này có thể có những cách xử lý khác nhau như là: Nhiều ngôn ngữ cho phép dùng các câu lệnh đặc biệt để lập trình viên có toàn quyền xử lý lỗi và thường được gọi là ngoại lệ (hay exception). Những ngoại lệ này nếu không xử lý đúng mức sẽ có thể gây ra những sai sót trong thời gian thi hành hay ngay cả trong thời gian dịch. Dĩ nhiên, người viết mã có thể tùy theo tình huống mà viết các câu lệnh rẽ nhánh tránh không để cho mã vi phạm các lỗi. Hay là dùng các câu lệnh xử lý các ngoại lệ này. Một số ngôn ngữ không cung cấp khả năng xử lý ngoại lệ thì người viết mã buộc phải tự mình phán đoán hết các tình huống có thể vi phạm lỗi và dùng câu lệnh điều kiện để loại trừ. 21
- Các loại lỗi về ngôn ngữ khi lập trình thường xảy ra là : Lỗi cú pháp Vi phạm khi đặt hay gọi tên biến và hàm: Lỗi loại này thường rất dễ tìm ra trong lúc phát triển mã. Thường người ta có thể đọc lại các bảng tham chiếu về ngôn ngữ để tránh sai cú pháp mẫu (prototype) của hàm hay tránh dùng các ký tự đặc biệt bị cấm không cho dùng trong khi đặt tên. Trong không ít trường hợp người lập trình có thể đã định nghĩa cùng một tên cho nhiều hơn một đối tượng khác nhau và lại có giá trị toàn cục. Trong nhiều trường hợp chúng tạo thành lỗi ý nghĩa. Lỗi chính tả: người viết mã có thể viết hay gọi sai tên hàm, tên biến. Trong nhiều ngôn ngữ có kiểu tĩnh thì các lỗi này sẽ rất dễ bị phát hiện. Còn đối với ngôn ngữ có kiểu động hay có kiểu yếu thì nó có thể dẫn đến sai sót nghiêm trọng vì bản thân phần mềm dịch không hề phát hiện ra. Vượt quá khả năng tính toán: Bản thân máy tính và hệ điều hành cũng có rất nhiều giới hạn về phần cứng, phần mềm và các đặc diểm chuyên biệt. Khi người lập trình yêu cầu máy làm quá khả năng sẽ gây ra các lỗi mà đôi khi không xác định được như Lỗi thời gian (timing error) thường thấy trong các hệ thống đa luồng hay đa nhiệm. Lỗi chia cho 0: Bản thân phần cứng máy tính sẽ ở trạng thái bất định khi thực hiện phép chia cho 0; trong nhiều trường hợp, mã sau khi dịch mới phát hiện ra trong lúc thi hành và được đặt tên là lỗi division by 0. Dùng hay gọi tới các địa chỉ hay các thiết bị mà bản thân máy hay hệ điều hành đang thực thi lại không có hay không thể đạt tới. Đây là trường hợp rất khó lường. Bởi vì thường ngưòi lập trình có thể viết mã trên một máy nhưng lại cho thi hành trong các máy khác và các máy này lại không thỏa mãn các yêu cầu. Để giảm trừ các lỗi loại này thường người lập trình nên xác định trước các điều kiện mà phần mềm làm ra sẽ hỗ trợ. Thí dụ: trong nhiều phần mềm ngày nay ở trong vỏ hộp đều được ghi rõ các yêu cầu về vận tốc, bộ nhớ tối thiểu, và quan trọng là hệ điều hành nào mà phần mềm đó hỗ trợ. Gán sai dữ liệu: Tức là dùng một dữ liệu có kiểu khác với kiểu của biến để gán cho biến đó một cách không chủ ý. Đối với các ngôn ngữ tĩnh hay có kiểu mạnh thì lỗi này dể tìm thấy hơn. Còn những ngôn ngữ động hay ngôn ngữ có kiểu yếu thì lỗi tạo ra sẽ có thể khó phát hiện và thường xảy ra lúc thi hành. Các lỗi biên: Lỗi biên thường xảy ra khi người viết mã không chú ý đến các giá trị ở biên của các biến, các hàm. Những lỗi để thấy có thể là: Gán giá trị của một số (hay một chuỗi) lên một biến mà nó vượt ngoài sự cho phép của định nghĩa. Thí dụ: Gán một giá trị lớn hơn 255 cho một biến có kiểu là short trong ngôn ngữ C Tạo nên các lỗi khi biến chạy trong vòng lặp đạt giá trị ở biên. Thí dụ: đoạn mã C/C++ sau đây sẽ gây ra lỗi biên Chia cho 0 for (m=10; m >= 0, m ) { x= 8+ 2/m; } 22
- Lỗi ý nghĩa Lỗi về quản lý bộ nhớ. Trong nhiều loại ngôn ngữ người lập trình có thể xin đăng ký một lượng nào đó của bộ nhớ để dùng làm chỗ chứa giá trị cho một biến (một hàm hay một đối tượng). Thường thì sau khi dùng xong người viết mã phải có phần lệnh trả về các phần bộ nhớ mà nó đã đăng ký dùng. Nếu không, sự trả về này chỉ xảy ra ở giai đoạn kết thúc việc thi hành. Trong nhiều trường hợp, số lượng bộ nhớ xin đăng ký quá nhiều và không được dùng đúng chỗ có thể làm cho máy kiệt quệ về mặt tài nguyên bộ nhớ và gây ra treo máy. Điển hình nhất là việc xin đăng ký các phần của bộ nhớ trong các vòng lặp lớn để gán cho các đối tượng bên trong vòng lặp nhưng không trả về sau khi xử dụng. Người ta thường gọi lỗi kiểu này là lỗi rò rỉ bộ nhớ (memory leaking). Sai sót trong thuật toán: Trước khi viết một chương trình, để giảm thiểu sai sót về mặt lập luận thì người ta có nhiều biện pháp để làm giảm lỗi trong đó có các phương pháp vẽ lưu đồ, vẽ sơ đồ khối, hay viết mã giả. Những biện pháp này nhằm tạo nên các thuật toán để giải quyết vấn đề. Tuy nhiên, một thuật toán không chặt chẽ, xử lý không rốt ráo mọi trường hợp có thể xảy ra, không dự đoán được sự thay đổi trong lúc thi hành thì có thể tạo nên các lỗi và các lỗi này thường khó thấy bởi vì nó chỉ xảy ra ở những chỗ, những thời điểm mà người lập trình không ngờ trước. Một trong những phương pháp đơn giản làm giảm thiểu lỗi thuật toán là phải chú ý xử lý mọi tình huống khi dùng câu lệnh điều kiện (hay chẻ nhánh) mặc dù có thể có các trường hợp tưởng như hiển nhiên. Lỗi về lập luận: Đây có thể xem là trường hợp đặc biệt của sai sót trong thuật toán. Trong các biểu thức tính giá trị, đôi khi không quen dùng đại số Bool (nhất là khi dùng luật De Morgan để phủ định một biểu thức phức tạp) nên người lập trình có thể tính toán sai, hay định nghĩa sai các phép toán. Do đó, giá trị trả về của các biểu thức logic hay biểu thức nhị phân sẽ bị sai trong một vài trường hợp hay toàn bộ biểu thức. Trong những tình huống như vậy phần mềm dịch sẽ không thể nào phát hiện ra cho đến khi chương trình được thi hành và lọt vào tình huống tính sai của người lập trình. 23
- Bài 2 TÊN BÀI:CÁC LOẠI DỮ LIỆU CẤU TRÚC MÃ BÀI: ITPRG3_06.2 Giới thiệu : Trong khi lập trình tạo ra sản phẩm, người lập trình cần phải hiểu được các kiến thức liên quan đến các kiểu dữ liệu có cấu trúc đơn giản của công cụ (ngôn ngữ lập trình) đang sử dụng để phát triển chương trình. Mặt khác, tất cả các kiễu dữ liệu có cấu trúc phức tạp đều được hình thành bởi các thành phần chứa các cấu trúc cơ bản. Để đi sâu hơn về các vấn đề được đề cập đến trong bài học này cũng nhưng trong những bài học tiếp theo, chúng ta cần phải hiểu ngôn ngữ lập trình C/C++ đồng thời cần phải nghiên cứu lại các bài học đã học trong chương trình Lập trình căn bản. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Hiểu và nắm được các kiểu dữ liệu cơ bản: kiểu số nguyên, số thực, ký tự, chuỗi, mảng. - Nắm được cách thức tổ chức bộ nhớ để lưu trữ các kiểu dữ liệu cơ bản - Áp dụng giải một số bài toán. Nội dung chính: 2.1. Các kiểu dữ liệu cơ sở Các kiểu dữ liệu cơ sở là các kiểu dữ liệu do ngôn ngữ lập trình dựng sẵn, ở đây chúng ta xét ngôn ngữ lập trình C++ với công cụ lập trình Borland C++ 3.0. Kiểu nguyên Kiểu số nguyên được khai báo với từ khoá int (short) có kích thước 2 byte, với miền giá trị -32768 32767. Tên kiểu Kích thước Miền giá trị Chú thích int 02 byte -32738 đến 32767 unsign int 02 byte 0 đến 65335 Có thể gọi tắt là unsign long 04 byte -232 đến 231 -1 unsign long 04 byte 0 đến 232-1 Các phép toán được trang bị để hỗ trợ cho số nguyên: Phép cộng, trừ: Cộng, trừ hai số nguyên cho kết quả một số nguyên. Phép chia: Chia hai số nguyên cho kết quả một số nguyên là phần nguyên của phép chia. Phép lấy dư (%): Phép toán này lấy phần dư của phép chia hai số nguyên. Kiểu thực C/C++ có hai từ khoá để khai báo số thực: float , double; được mô tả trong bảng sau: Tên kiểu Kích thước Giá trị Độ chính xác float 4 byte 3.4 10-38 3.4 1038 7 chữ số sau dấu thập phân double 8 byte 1.7 10-308 1.7 10308 15 chữ số sau dấu thập phân 24
- long double 10 byte 3.4E-4932 1.1E4932 Số thực được hỗ trợ các phép toán: cộng, trừ, nhân, chia nhưng không tồn tại phép toán lấy dư (%). Kiểu ký tự Kiểu ký tự được hỗ trợ từ khoá char dùng để lưu 256 ký tự trong bảng mã ASCII mở rộng. Một biến dùng kiểu ký tự để lưu trữ thực chất chỉ lưu trữ được một số nguyên theo mã số trong bảng mã ASCII, do đó các phép toán của số nguyên đều dùng được với cùng một quy cách với ký tự. Để gán giá trị cho một biến ký tự người dùng có thể gán giá trị số nguyên (mã ASCII) của ký tự đó hoặc gán tên ký tự được đóng bởi cặp dấu nháy đơn (Ví dụ: a=’B’). Trong quá trình lập trình, chúng ta cần phải chú ý đến các tình huống sau: - Người lập trình có thể kết hợp các từ khoá: unsigned, long để chỉ định kiểu dữ liệu có dấu hoặc để tăng giá trị có thể lưu trữ được cho một biến. Ví dụ: Biến a có kiểu long int hoặc long có thể lưu trữ được giá trị số nguyên có dấu giá trị khoảng 2 tỷ; biến b có kiểu unsigned int có thể lưu trữ được giá trị số nguyên có độ lớn từ 0 65535; biến c có kiểu unsigned long int có thể lưu trữ được số nguyên không dấu giá trị khoảng 4,2 tỷ. Kiểu logic Borland C++ 3.0 trở lên có cung cấp một kiểu dữ liệu logic mang tên kiểu bool. Kiểu dữ liệu này chứa một trong hai giá trị TRUE (đúng), FALSE (sai). Tuy nhiên, trong C/C++, khi sử dụng giá trị bằng 0 được hiểu là FALSE, sử dụng giá trị khác 0 được hiểu là FALSE. - Từ khoá long không dùng được với kiểu char. - Ngoài các các phép toán chuẩn, người sử dụng phải còn có thể sử dụng các hàm dựng sẵn được cung cấp bởi các thư viện trong C/C++ để xử lý đối với từng kiểu dữ liệu được yêu cầu của từng hàm. - Kiểu trả về của một biểu thức số có kiểu dữ liệu là kiểu dữ liệu cao nhất trong các phần tử trong biểu thức. Ví dụ: Biểu thức 1/n với n là số nguyên sẽ trả về một giá trị nguyên. Chẳng hạn, 1/5 sẽ trả về giá trị 0. - Trong trường hợp muốn biểu thức cho ra một kết quả theo kiểu dữ liệu nào đó, chúng ta phải thực hiện phép toán ép kiểu. Ví dụ 1: Viết chương trình thực hiện nhập vào một số nguyên dương n, sau đó in kết quả của biểu thức #include #include void main() { float s; int n; int i; 25
- clrscr(); cout >n; s=0; for (i=1;i #include #include long giaithua(long n) { if (n == 0) return(1); else return(n * giaithua(n-1)); } void main() { int n; char c; clrscr(); printf("Chuong trinh tinh giai thua.\n"); do { printf("\nNhap n: "); scanf("%d", &n); printf("Giai thua cua %d la %ld\n", n, giaithua(n)); printf("\nTiep tuc khong? (c/k): "); c = getche(); } while(c == 'c' || c == 'C'); } Chương trình trên chỉ tính chính xác 12!, 13! trở lên chương trình trên tính không chính xác nữa. Vì lý do, số nguyên long chỉ chứa được giá trị của 12! mà không chứa được giá trị của 13! hay lớn hơn. Để khắc phục hiện tượng này, chúng ta sử dụng giá trị long double thay cho long. Chương trình được viết lại như sau: Ví dụ 3: Tính n! sử dụng biến lưu trữ là số thực long double. 26
- #include #include #include long double giaithua(long double n) { return(n == 0.0 ? 1.0 : n * giaithua(n-1)); } void main() { long double n; char c; clrscr(); printf("Chuong trinh tinh giai thua.\n"); do { cout >n; cout tênbiến[ ] Ví dụ 4: Để khai báo một biến a là một mảng nguyên một chiều có 100 phần tử, chúng ta phải khai báo như sau: int a[100]; Để truy xuất đến các phần tử,chúng ta phải chỉ định tên và chỉ số phần tử cần truy xuất. Chẳng hạn: a[0] - Truy xuất đến phần tử đầu tiên. a[11] – Truy xuất đến phần tử thứ 12 (có chỉ số 11) a[99] – Truy xuất đến phần tử thứ 100 (có chỉ số 99), là phần tử cuối cùng ở mảng trên. Chúng ta cũng có thể vừa khai báo vừa gán giá trị cho một mảng như sau: int a [5] = { 16, 2, 77, 40, 12071 }; Tương tự, chúng ta có thể khai báo một mảng 2 hay nhiều chiều theo cú pháp sau: tênbiến[ ][ ][ ]; Chẳng hạn, chúng ta khai báo khai báo mảng nguyên 2 chiều như sau: 27
- int a[100][20]; Ví dụ 1: Nhập một mảng 1 chiều có tối đa 100 phần tử, số phần tử được nhập từ bàn phím. Hãy in tổng các phần tử không chia hết cho 2 và các phần tử chia hết cho 2. #include #include void main() { int a[100]; //toi da 100 phan tu int n,i; long tongle,tongchan; clrscr(); cout >n; for (i=0;i >a[i]; } tongle=0;tongchan=0; for (i=0;i #include void main() { int a[100][100],b[100][100]; int c[100][100]; int n,m,i,j; clrscr(); cout >m; cout >n; cout<<"Nhap cac phan tu cho ma tran A"<<endl; for (i=0;i<m;i++) 28
- for (j=0;j >a[i][j]; } cout >b[i][j]; } //tinh ma tran C for (i=0;i<m;i++) for (j=0;j<n;j++) c[i][j]=a[i][j]+b[i][j]; cout<<"cac phan tu trong ma tran C:"<<endl; for (i=0;i<m;i++) for(j=0;j<n;j++) cout<<"c["<<i<<"]["<<j<<"]="<<c[i][j]<<endl; getch(); } Mảng được xử lý rất uyển chuyển nhờ phép toán truy cập trực tiếp dữ liệu thông qua chỉ số phần tử, do đó, mảng thường được dùng để lưu trữ các dữ liệu trong những chương trình mà khả năng lưu trữ dữ liệu không lớn. Đồng thời, mảng vẫn là cấu trúc dữ liệu hay được dùng trong các giải pháp xử lý đệ quy-quay lui, hay xử lý tham lam, Chúng ta tiến hành nghiên cứu việc ứng dụng mảng trong một số phương pháp. Ví dụ 6: Bài toán tám quân hậu: Tìm cách đặt các quân hậu lên bàn cờ vua sao cho không quân nào có thể ăn được quân nào. Quy luật khống chế ô cờ của quân hậu được thể hiện theo hướng mũi tên sau: Hậu Hình 2.1 Quy luật khống chế ô cờ của quân Hậu 29
- Thuật toán xây dựng dựa trên việc đặt quân hậu lần lượt trên từng dòng. Chúng ta phải tìm cột thích hợp để đặt quân hậu lên dòng thứ i, tại cột j nào? - Giá trị cột đã bị quân hậu khống chế rất dễ dàng xác định bằng cách xem có quân hậu nào nằm trên cột đó hay không? - Các đường chéo xuôi được xác định dễ dàng vì lấy chỉ số cột trừ chỉ số dòng trên một đường chéo luôn luôn là một hằng số. - Cac đường chéo ngược được xác định dễ dàng vì lấy chỉ số dòng cộng chỉ số cột trên một đường chéo luôn luôn là một hằng số. Bảng chỉ số i+j của đường chéo xuôi. I+j01234567891011121314 Hinh 2.2 Bảng chỉ số i+j của đường chéo xuôi. Bài toán này chúng ta xử lý bằng cách dùng một mảng để lưu lời giải (mảng loigiai). Hai mảng dùng để lưu đường chéo của bàn cờ (cheoxuoitrong, cheonguoctrong), một mảng lưu trữ các cột trống #include #include #include #include #define KICHTHUOC 8 // Kich thuoc cua ban co #define SODUONGCHEO (2*KICHTHUOC-1) // So duong cheo cua ban co 30
- #define SOGIA (KICHTHUOC-1) // so gia #define TRUE 1 #define FALSE 0 // prototypes void hoanghau(int); void inloigiai(int loigiai[]); int cottrong[KICHTHUOC]; // mang cac cot co the dat hoang hau int cheoxuoitrong[SODUONGCHEO]; // mang cac duong cheo xuoi co the dat hhau int cheonguoctrong[SODUONGCHEO]; // mang cac duong cheo nguoc co the dat hhau int loigiai[KICHTHUOC]; /* mang loigiai cho biet cot dat cac hoang hau tren ban co. Vi du cac phan tu cua mang la: 7 3 0 2 5 1 6 4 cho biet hoanghau0 dat o cot 7, hoanghau1 dat o cot 3, , hoanghau7 o cot 4 */ int SoLoiGiai = 0; void main(void) { int i; /* Khoi dong tat ca cac cot duong cheo xuoi, duong cheo nguoc deu co the dat hoang hau */ for(i = 0; i < KICHTHUOC; i++) cottrong[i] = TRUE; for(i = 0; i < SODUONGCHEO; i++) { cheoxuoitrong[i] = TRUE; cheonguoctrong[i] = TRUE; } // Goi ham de qui de bat dau dat HoangHau0 (hoang hau o hang 0) hoanghau(0); } // Ham hoanghau giup dat hoang hau i (i tu 0 den KICHTHUOC-1) tren hang i void hoanghau(int i) { int j; for(j = 0; j < KICHTHUOC; j++) if(cottrong[j] && cheoxuoitrong[i-j+SOGIA] && cheonguoctrong[i+j]) { // Dat hoang hau vao o (i, j) tren ban co loigiai[i] = j; cottrong[j] = FALSE; cheoxuoitrong[i-j+SOGIA] = FALSE; 31
- cheonguoctrong[i+j] = FALSE; if(i == KICHTHUOC-1) // Dkien dung, dat duoc con hoang hau cuoi inloigiai(loigiai); else // Buoc de qui, goi dat hoang hau i+1 hoanghau(i + 1); // lan nguoc cottrong[j] = TRUE; cheoxuoitrong[i-j+SOGIA] = TRUE; cheonguoctrong[i+j] = TRUE; } } void inloigiai(int loigiai[]) { int j; SoLoiGiai++; cout #include #include #define KICHTHUOC 5 // Kich thuoc cua ban co void nuocdi(int, int, int); void innuocdi(); // To chuc ban co la mang hai chieu int BanCo[KICHTHUOC][KICHTHUOC]; // 8 cach di cua con ma int a[8] = {2, 2, 1, -1, -2, -2, -1, 1}; int b[8] = {1, -1, -2, -2, -1, 1, 2, 2}; int SoLoiGiai = 0; int main(void) { 32
- int i, j, m, n; clrscr(); for(i = 0; i = 0 && x+a[i] = 0 && y+b[i] < KICHTHUOC &&BanCo[x+a[i]] [y+b[i]] == 0) { // Di nuoc thu n BanCo[x+a[i]][y+b[i]] = n; if(n == KICHTHUOC*KICHTHUOC) // Dkien dung, di duoc nuoc cuoi { innuocdi(); } else // Buoc de qui, goi di nuoc n+1 nuocdi(n+1, x+a[i], y+b[i]); // lan nguoc BanCo[x+a[i]][y+b[i]] = 0; } } } void inloigiai() 33
- { int i,j; cout<<”Loi giai”; for (i=0;i<8;i++) { for (j=0;j<8;j++) printf(“%3d”,BanCo[i][j]); } 2.3.Xâu kí tự Trong C++ không có kiểu dữ liệu cơ bản để lưu các xâu kí tự. Để có thể thỏa mãn nhu cầu này, người chúng ta sử dụng mảng có kiểu char. Hãy nhớ rằng kiểu dữ liệu này (char) chỉ có thể lưu trữ một kí tự đơn, bởi vậy nó được dùng để tạo ra xâu của các kí tự đơn. Ví dụ, mảng sau (hay là xâu kí tự): char alpha [20]; có thể lưu một xâu kí tự với độ dài cực đại là 20 kí tự. Bạn có thể tưởng tượng tương tự như sau: alpha Kích thước cực đại này không cần phải luôn luôn dùng đến. Chẳng hạn, alpha có thể lưu xâu "Hello" hay "Merry christmas". Vì các mảng kí tự có thể lưu các xâu kí tự ngắn hơn độ dài của nó, trong C++ đã có một quy ước để kết thúc một nội dung của một xâu kí tự bằng một kí tự null, có thể được viết là '\0'. Chúng ta có thể biểu diễn alpha (một mảng có 20 phần tử kiểu char) khi lưu trữ xâu kí tự "Hello" và "Happy New Year" theo cách sau: alpha Helllo\0 HappyNewYear\0 Hình 2.3 Biểu diễn alpha xâu kí tự HELLO và HAPPY NEW YEAR Chú ý rằng sau nội dung của xâu, một kí tự null ('\0') được dùng để báo hiệu kết thúc xâu. Những ô màu xám biểu diễn những giá trị không xác định. Khởi tạo các xâu kí tự. Vì những xâu kí tự là những mảng bình thường nên chúng cũng như các mảng khác. Chẳng hạn, nếu chúng ta muốn khởi tạo một xâu kí tự với những giá trị xác định chúng ta có thể làm điều đó tương tự như với các mảng khác: char mystring[] = { 'H', 'e', 'l', 'l', 'o', '\0' }; 34
- Tuy nhiên, chúng ta có thể khởi tạo giá trị cho một xâu kí tự bằng cách khác: sử dụng các hằng xâu kí tự. Trong các biểu thức chúng ta đã sử dụng trong các ví dụ trong các chương trước các hằng xâu kí tự để xuất hiện vài lần. Chúng được biểu diễn trong cặp ngoặc kép ("), ví dụ: "the result is: " là một hằng xâu kí tự chúng ta sử dụng ở một số chỗ. Không giống như dấu nháy đơn (') cho phép biểu diễn hằng kí tự, cặp ngoặc kép (") là hằng biểu diễn một chuỗi kí tự liên tiếp, và ở cuối chuỗi một kí tự null ('\0') luôn được tự động thêm vào. Chúng ta có thể khởi tạo xâu mystring theo một trong hai cách sau đây: char mystring [] = { 'H', 'e', 'l', 'l', 'o', '\0' }; char mystring [] = "Hello"; Trong cả hai trường hợp mảng (hay xâu kí tự) mystring được khai báo với kích thước 6 kí tự: 5 kí tự biểu diễn Hello cộng với một kí tự null. Trước khi tiếp tục, tôi cần phải nhắc nhở bạn rằng việc gán nhiều hằng như việc sử dụng dấu ngoặc kép (") chỉ hợp lệ khi khởi tạo mảng, tức là lúc khai báo mảng. Các biểu thức trong chương trình như: mystring = "Hello"; mystring[] = "Hello"; là không hợp lệ, cả câu lệnh dưới đây cũng vậy: mystring = { 'H', 'e', 'l', 'l', 'o', '\0' }; Vậy hãy nhớ: Chúng ta chỉ có thể "gán" nhiều hằng cho một mảng vào lúc khởi tạo nó. Nguyên nhân là một thao tác gán (=) không thể nhận vế trái là cả một mảng mà chỉ có thể nhận một trong những phần tử của nó. Vào thời điểm khởi tạo mảng là một trường hợp đặc biệt, vì nó không thực sự là một lệnh gán mặc dù nó sử dụng dấu bằng (=). Gán giá trị cho xâu kí tự Vế trái của một lệnh gán chỉ có thể là một phần tử của mảng chứ không thể là cả mảng, chúng ta có thể gán một xâu kí tự cho một mảng kiểu char sử dụng một phương pháp như sau: mystring[0] = 'H'; mystring[1] = 'e'; mystring[2] = 'l'; mystring[3] = 'l'; mystring[4] = 'o'; mystring[5] = '\0'; Nhưng rõ ràng đây không phải là một phương pháp thực tế. Để gán giá trị cho một xâu kí tự, chúng ta có thể sử dụng loạt hàm kiểu strcpy (string copy), hàm này được định nghĩa trong string.h và có thể được gọi như sau: strcpy (string1, string2); Lệnh này copy nội dung của string2 sang string1. string2 có thể là một mảng, con trỏ hay một hằng xâu kí tự, bởi vậy lệnh sau đây là một cách đúng để gán xâu hằng "Hello" cho mystring: strcpy (mystring, "Hello"); 35
- Ví dụ 8: #include J. Soulie #include int main () { char szMyName [20]; strcpy (szMyName,"J. Soulie"); cout để có thể sử dụng hàm strcpy. Mặc dù chúng ta luôn có thể viết một hàm đơn giản như hàm setstring dưới đây để thực hiện một thao tác giống như strcpy: Ví dụ 9: // setting value to string J. Soulie #include void setstring (char szOut [], char szIn []) { int n=0; do { szOut[n] = szIn[n]; n++; } while (szIn[n] != 0); } int main () { char szMyName [20]; setstring (szMyName,"J. Soulie"); cout << szMyName; return 0; } Một phương thức thường dùng khác để gán giá trị cho một mảng là sử dụng trực tiếp dòng nhập dữ liệu (cin). Trong trường hợp này giá trị của xâu kí tự được gán bởi người dùng trong quá trình chương trình thực hiện. Khi cin được sử dụng với các xâu kí tự nó thường được dùng với phương thức getline của nó, phương thức này có thể được gọi như sau: 36
- cin.getline ( char buffer[], int length, char delimiter = ' \n'); trong đó buffer (bộ đệm) là địa chỉ nơi sẽ lưu trữ dữ liệu vào (như là một mảng chẳng hạn), length là độ dài cực đại của bộ đệm (kích thước của mảng) và delimiter là kí tự được dùng để kết thúc việc nhập, mặc định - nếu chúng ta không dùng tham số này - sẽ là kí tự xuống dòng ('\n'). Ví dụ sau đây lặp lại tất cả những gì bạn gõ trên bàn phím. Nó rất đơn giản nhưng là một ví dụ cho thấy bạn có thể sử dụng cin.getline với các xâu kí tự như thế nào: Ví dụ 10: #include What's your name? Juan Hello Juan. int main () Which is your favourite team? Inter Milan { I like Inter Milan too. char mybuffer [100]; cout > để nhận dữ liệu trực tiếp từ đầu vào chuẩn. Phương thức này có thể được dùng với các xâu kí tự thay cho cin.getline. Ví dụ, trong chươn trình của chúng ta, khi chúng ta muốn nhận dữ liệu từ người dùng chúng ta có thể viết: cin >> mybuffer; lệnh này sẽ làm việc như nó có những hạn chế sau mà cin.getline không có: Nó chỉ có thể nhận những từ đơn (không nhận được cả câu) vì phương thức này sử dụng kí tự trống(bao gồm cả dấu cách, dấu tab và dấu xuống dòng) làm dấu hiệu kết thúc Nó không cho phép chỉ định kích thước cho bộ đệm. Chương trình của bạn có thể chạy không ổn định nếu dữ liệu vào lớn hơn kích cỡ của mảng chứa nó. Vì những nguyên nhân trên, khi muốn nhập vào các xâu kí tự bạn nên sử dụng cin.getline thay vì cin >>. Chuyển đổi xâu kí tự sang các kiểu khác. Vì một xâu kí tự có thể biểu diễn nhiều kiểu dữ liệu khác như dạng số nên việc chuyển đổi nội dung như vậy sang dạng số là rất hữu ích. Ví dụ, một xâu có thể mang giá trị "1977"nhưng đó là một chuỗi gồm 5 kí tự (kể cả kí tự null) và không dễ gì chuyển thành một số nguyên. Vì vậy thư viện cstdlib (stdlib.h) đã cung cấp 3 macro/hàm hữu ích sau: 37
- atoi: chuyển xâu thành kiểu int. atol: chuyển xâu thành kiểu long. atof: chuyển xâu thành kiểu float. Tất cả các hàm này nhận một tham số và trả về giá trị số (int, long hoặc float). Các hàm này khi kết hợp với phương thức getline của cin là một cách đáng tin cậy hơn phương thức cin>> cổ điển khi yêu cầu người sử dụng nhập vào một số. Ví dụ 11: #include Enter price: 2.75 #include Enter quantity: 21 Total price: 57.75 int main () { char mybuffer [100]; float price; int quantity; cout void main() { char qcao[81], i=0, len; 38
- textattr(0x1e); printf("\nNhap vao dong quang cao : "); gets(qcao); len = strlen(qcao); while (!kbhit()) { movetext(2, 1, 80, 1, 1, 1); gotoxy(80, 1); cprintf("%c", qcao[i++]); delay(100); i %= len; } getch(); } Ví dụ 13: Chương trình sau dùng các hàm dựng sẵn của C++ để chuyển sang các dạng ký tự khác nhau. #include"stdio.h" #include"alloc.h" #include"string.h" #include"conio.h" char *thaythe(unsigned char kytu,size_t lan) { char *inkytu; inkytu=calloc(lan,sizeof(char)+1); memset(inkytu,kytu,lan); return(inkytu); } main() { char *s, *t; s=calloc(60,sizeof(char)); t=calloc(60,sizeof(char)); clrscr(); textcolor(YELLOW); cprintf(" - Nhap chuoi thu nhat (chu thuong)= "); gets(s); textcolor(LIGHTRED); cprintf(" - Nhap chuoi thu hai (chu thuong) = "); gets(t); cprintf("\n %s",thaythe(205,60)); textcolor(LIGHTCYAN); 39
- cprintf("\n\r %10c DOI RA CHU HOA",' '); cprintf("\n\n\r"); puts(strupr(s)); cprintf("\r"); puts(strupr(t)); cprintf("\n %s",thaythe(205,60)); textcolor(LIGHTMAGENTA); cprintf("\n\r %10c DOI TRO LAI CHU THUONG",' '); cprintf("\n\n\r"); puts(strlwr(s)); cprintf("\r"); puts(strlwr(t)); cprintf("\n %s",thaythe(205,60)); textcolor(LIGHTGREEN); cprintf("\n\r %10c DAO NGUOC CHU",' '); cprintf("\n\n\r"); puts(strrev(s)); cprintf("\r"); puts(strrev(t)); cprintf("\n %s",thaythe(205,60)); getch(); } Ví dụ 14:Chương trình sau thực hiện xóa một số ký tự #include"conio.h" #include"alloc.h" void xoa(char *nguon, char vitri, char so) { char k; for (k=vitri-1;nguon[k+so];k++) nguon[k]=nguon[k+so]; nguon[k]='\0'; } void main() { int m,n; char *s; s=calloc(100,sizeof(char)); clrscr(); textcolor(YELLOW); cprintf("\n - Nhap vao mot chuoi :"); gets(s); 40
- textcolor(LIGHTRED); cprintf("\r - Muon xoa tu vi tri nao : "); scanf("%d",&m); textcolor(LIGHTMAGENTA); cprintf("\r- Muon xoa bao nhieu ky tu : "); scanf("%d",&n); textcolor(LIGHTRED); cprintf("\n\r "); textcolor(YELLOW); cprintf("\n\n\r + Chuoi nguon la :"); textcolor(LIGHTCYAN); cprintf("\n\r %s",s); textcolor(LIGHTGREEN); cprintf("\n\r + Chuoi nguon dai :%d ky tu",strlen(s)); textcolor(LIGHTRED); cprintf("\n\r + Sau khi xoa con lai chuoi : "); xoa(s,m,n); textcolor(LIGHTMAGENTA); cprintf("\n\r %s",s); textcolor(LIGHTGREEN); cprintf("\n\r + Chieu dai la : %d ky tu",strlen(s)); textcolor(LIGHTCYAN); cprintf("\n\n\r * Bam phim bat ky de ket thuc"); getch(); } Ví dụ 15: Chương trình sau thực hiện đảo chuỗi ký tự. #include"conio.h" #include"stdio.h" #include"string.h" #include"alloc.h" void main() { char *chuoi; chuoi=calloc(80,sizeof(char)); clrscr(); cprintf("\n- Nhap vao 1 chuoi : "); gets(chuoi); cprintf("\n\r- Chuoi nay co : %d ky tu ke ca ky tu trong \n",strlen(chuoi)); cprintf("\n\r- Dao nguoc chuoi nay thanh \n "); cprintf("\n\r %s ",strrev(chuoi)); cprintf("\n\n\r * Bam phim bat ky de ket thuc"); 41
- getch(); } Ví dụ 16: Chương trình sau minh họa hàm cộng chuỗi. #include"string.h" #include"stdio.h" void main() { char mot[100]="Que huong la chum khe ngot"; char hai[100]=" Cho con treo hai moi ngay"; char ba[100]="Que huong la duong di hoc"; char bon[100]=" Con ve rop buom vang bay"; clrscr(); printf("\nCau \" %s\". Co chieu dai :%d ky tu ",mot,strlen(mot)); printf("\nCau \" %s\". Co chieu dai :%d ky tu ",hai,strlen(hai)); printf("\nCau \" %s\". Co chieu dai :%d ky tu ",ba,strlen(ba)); printf("\nCau \" %s\". Co chieu dai :%d ky tu ",bon,strlen(bon)); strcat(mot,hai); strcat(ba,bon); printf("\n"); printf("\n PHEP CONG CHUOI KY TU \n"); printf("\n + Dem cau 1 + cau 2"); printf("\nHai cau %s",mot); printf("\n - Co chieu dai: %d ky tu :",strlen(mot)); printf("\n"); printf("\n + Dem cau 3 + cau 4"); printf("\nHai cau %s",ba); printf("\n - Co chieu dai : %d ky tu",strlen(ba)); getch(); } 2.4 Kiểu cấu trúc Cấu trúc là một kiểu dữ liệu được người dùng tự định nghĩa, kiểu dữ liệu này chứa trong nó các phần tử mà mỗi phần tử có thể có kiểu dữ liệu khác nhau. Kiểu cấu trúc cho phép mô tả các đối tượng có cấu trúc phức tạp. Khai báo tổng quát của kiểu cấu trúc (struct) như sau: struct { ; ; ; 42
- }; Ví dụ 17: Để mô tả các thông tin về một con người, chúng ta có thể khai báo một kiểu dữ liệu và khai báo một cấu trúc như sau: struct nguoi { char hoten[35]; int namsinh; char noisinh[60]; int gioitinh; {0- nu;1:nam} char diachi[79]; } Kiểu cấu trúc được tổ chức trong bộ nhớ với một cấu trúc liên tục, không chồng lấp lên nhau: 35 bytes 2 bytes 60 bytes 2 bytes 79bytes Hoten namsinh Noisinh gioitinh diachi Kiểu cấu trúc bổ sung những hạn chế của kiểu mảng, giúp chúng ta có khả năng thể hiện các đối tượng đa dạng của thế giới thực vào trong máy tính một cách dễ dàng và chính xác hơn. Khi khai báo biến có kiểu dữ liệu cấu trúc, chúng ta truy xuất đến các thành phần con bên trong bằng dấu chấm (.). Ví dụ 18: Nhập danh sách các học sinh có kiểu dữ liệu nguoi như trên. In ra màn hình thông tin của các học sinh có năm sinh bé hơn 1985. #include #include #include void main() { struct nguoi { char hoten[35]; int namsinh; char noisinh[60]; int gioitinh; char diachi[79]; }; nguoi ds[100]; int n,i; clrscr(); cout >n; cout<<"Nhap thong tin cho tung hoc sinh:"<<endl; 43
- for (i=0;i >ds[i].namsinh; cout >ds[i].gioitinh; cout #include #include void main() { struct nv { char hoten[35]; int tuoi; }; nv nhanvien[100]; float tuoitrungbinh=0; int n,i; clrscr(); cout >n; for (i=0;i<=n-1;i++) 44
- { cout >nhanvien[i].tuoi; } for (i=0;i tuoitrungbinh) { cout #include #include #define MAXLIST 100 #define TRUE 1 #define FALSE 0 struct sinhvien { int mssv; char hoten[20]; }; struct list { int numnodes; sinhvien nodes[MAXLIST]; }; list L; void initialize(list &L) { L.numnodes = 0; //khởi động danh sách } 45
- // Xác định số phần tử int listsize(list &L) { return(L.numnodes); } //Kiểm tra xem danh sách rỗng hay không int empty(list L) { return((L.numnodes == 0) ? TRUE : FALSE); } // Kiểm tra xem danh sách có đầy không? int full(list L) { return((L.numnodes == MAXLIST) ? TRUE : FALSE); } //chèn một phần tử vào vị trí pos void insert(list &L, int pos, sinhvien x) { int i; if(pos listsize(L)) { printf("Vi tri chen khong phu hop."); return; } else if(full(L)) { printf("Danh sach bi day."); return; } else { for(i = listsize(L)-1; i >= pos; i ) L.nodes[i+1] = L.nodes[i]; L.nodes[pos] = x; L.numnodes++; } } //xoá một nút tại pos sinhvien remove(list &L, int pos) { 46
- int i; sinhvien x; if(pos = listsize(L)) printf("Vi tri xoa khong phu hop."); else if(empty(L)) printf("Danh sach khong co sinh vien."); else { x = L.nodes[pos]; for(i = pos; i = listsize(L)) { printf("Vi tri hieu chinh khong phu hop."); return; } else if(empty(L)) { printf("Danh sach khong co sinh vien."); return; } else L.nodes[pos] = x; } //duyệt danh sách void traverse(list L) { 47
- int i; if(L.numnodes == 0) { printf("\n Danh sach khong co sinh vien"); return; } for(i = 0; i L.nodes[j].mssv) { svmin = L.nodes[j]; vitrimin = j; } // hoan doi L.nodes[vitrimin] = L.nodes[i]; L.nodes[i] = svmin; } } //tìm kiếm một sinh viên int linearsearch(list &L, int mssv) { int vitri = 0; while(L.nodes[vitri].mssv != mssv && vitri < L.numnodes) vitri++; if(vitri == L.numnodes) // khong tim thay return(-1); return(vitri); // tim thay } void main() { 48
- sinhvien sv; int chucnang, vitri; char c; clrscr(); initialize(L); do { // menu chinh cua chuong trinh printf("\n\nCHUONG TRINH QUAN LY DANH SACH SINH VIEN:\n"); printf("Cac chuc nang cua chuong trinh:\n"); printf(" 1: Xem danh sach sinh vien\n"); printf(" 2: Them mot sinh vien vao danh sach\n"); printf(" 3: Xoa mot sinh vien trong danh sach\n"); printf(" 4: Hieu chinh sinh vien\n"); printf(" 5: Sap xep danh sach theo MSSV\n"); printf(" 6: Tim kiem sinh vien theo MSSV\n"); printf(" 7: Xoa toan bo danh sach\n"); printf(" 0: Ket thuc chuong trinh\n"); printf("Chuc nang ban chon: "); scanf("%d", &chucnang); switch(chucnang) { case 1: { printf("\nDanh sach sinh vien: "); printf("\n STT MSSV HO TEN"); traverse(L); break; } case 2: { printf("\nVi tri them (0, 1, 2, ): "); scanf("%d", &vitri); printf("Ma so sinh vien: "); scanf("%d", &sv.mssv); printf("Ho ten sinh vien: "); scanf("%s", &sv.hoten); insert(L, vitri, sv); break; } case 3: 49
- { printf("\nVi tri xoa (0, 1, 2, ): "); scanf("%d", &vitri); remove(L, vitri); break; } case 4: { printf("\nVi tri hieu chinh (0, 1, 2, ): "); scanf("%d", &vitri); printf("\nSTT:%d MSSV:%d HOTEN:%s", vitri, ds.nodes[vitri].mssv, ds.nodes[vitri].hoten); printf("\nMa so sv moi: "); scanf("%d", &sv.mssv); printf("Ho ten sv moi: "); scanf("%s", &sv.hoten); replace(L, vitri, sv); break; } case 5: { printf("\nBan co chac khong (c/k): "); c = getche(); if(c == 'c' || c == 'C') selectionsort(L); break; } case 6: { printf("\nMa so sinh vien can tim: "); scanf("%d", &sv.mssv); vitri = linearsearch(L, sv.mssv); if(vitri == -1) printf("Khong co sinh vien co MSSV %d trong danh sach", sv.mssv); else printf("Tim thay o vi tri %d trong danh sach", vitri); break; } case 7: { 50
- printf("\nBan co chac khong (c/k): "); c = getche(); if(c == 'c' || c == 'C') clearlist(L); break; } } } while(chucnang != 0); } Ví dụ 21: Viết chương trình tạo một mảng các điểm ngẫu nhiên (dùng cấu trúc điểm). Kiểm tra xem các điểm phát sinh này có thỏa mãn 2x+4y>20. Nhập r là bán kính của một đường tròn tâm 0. Kiểm tra xem các điểm phát sinh ở trên có nằm trên đường tròn này hay không? In kết quả lên màn hình. #include"stdio.h" #include"stdlib.h" #define Max 100 typedef struct { int x,y; }toa_do; toa_do M[Max]; unsigned char k; int r; char tim_thay; void khoi_tao() { randomize(); for (k=0; k 20 :"); for (k=0; k 20) { tim_thay = 1; 51
- printf("( %2d\, %2d )",M[k].x, M[k].y); } if (tim_thay) puts("\n la cac diem thoa x + 2y > 3"); else puts("\nKhong co diem nao nhu vay"); } void tim_kiem2() { tim_thay=0; printf("-Nhap ban kinh R= "); scanf("%d",&r); printf("Cac diem tren vong tron tam O ban kinh %d :\n",r); for (k=0; k<Max; k++); if (M[k].x * M[k].x + M[k].y * M[k].y == r*r) { tim_thay=1; printf("( %2d\, %2d )",M[k].x, M[k].y); } if (tim_thay) printf("\n la cac diem tren vong tron tam O ban kinh %d",r); else puts("\nKhong co diem nao nhu vay"); } void main() { khoi_tao(); tim_kiem1(); tim_kiem2(); printf("\n\t Bam phim bat ky de ket thuc"); getch(); } 2.5. Kiểu hợp Kiểu hợp khá giống kiểu cấu trúc. Tuy nhiên, tất cả các thành phần được lưu trữ trên cùng một địa chỉ bộ nhớ. Ví dụ: union viduhop { long lonnhat; int thap; }; 52
- Biến a có kiểu dữ liệu viduhop và thực hiện phép gán: a.lonnhat bểu diễn trong bộ nhớ như sau: a.lonnhat=502010416 2bytes 2bytes 11101111011000001001000110000 a.thap Một trong những công dụng của union là dùng để kết hợp một kiểu dữ liêu cơ bản với một mảng hay các cấu trúc gồm các phần tử nhỏ hơn. Ví dụ: union mix_t{ long l; struct { short hi; short lo; } s; char c[4]; } mix; định nghĩa ba phần tử cho phép chúng ta truy xuất đến cùng một nhóm 4 byte: mix.l, mix.s và mix.c mà chúng ta có thể sử dụng tuỳ theo việc chúng ta muốn truy xuất đến nhóm 4 byte này như thế nào. Tôi dùng nhiều kiểu dữ liệu khác nhau, mảng và cấu trúc trong union để bạn có thể thấy các cách khác nhau mà chúng ta có thể truy xuất dữ liệu. Kiểu hợp khá thuận tiện trong việc xử lý các chương trình tính theo byte trong một luồng dữ liệu nhiều byte nhận được, chẳng hạn, xử lý các dữ liệu từ các cổng điều khiển. Ngoài ra, kiểu hợp còn rất thuận lợi trong việc xử lý số liệu đến bit, byte hay xử lý các giá trị ở mức độ hệ thống. Ví dụ 22: Chương trình sau hiển thị giá trị byte thấp và byte cao của một số nguyên int. #include #include union kieu_hop { int i; char ch[2]; }; kieu_hop uni; void main() { uni.i=0X2040; 53
- hien_thi(uni); } void hien_thi(kieuhop so) { clrscr(); cout<<"\n\n CHUONG TRINH MINH HOA SU DUNG UNION\n"; printf("-Hien thi tri 16 (thap luc) cua Byte thap : %X\n",so.ch[0]); printf("-Hien thi tri 16 (thap luc) cua Byte cao : %X\n",so.ch[1]); printf("\n Bam phim bat ky de ket thuc"); getch(); } Ví dụ 23: Hiển thị bảng mã ASCII theo hai kiểu mã: thập phân và nhị phân. #include"stdio.h" typedef struct { unsigned a: 1; unsigned b: 1; unsigned c: 1; unsigned d: 1; unsigned e: 1; unsigned f: 1; unsigned g: 1; unsigned h: 1; }kytu; union { char ch; kytu b; }ma; void hien_thi(char c) { ma u; u.ch = c; printf("\n %c %3d %u %u %u %u %u %u %u %u ", u.ch,u.ch,u.b.h,u.b.g,u.b.f,u.b.e,u.b.d,u.b.c,u.b.b,u.b.a); } void main() { char c; clrscr(); printf("\n B A N G M A A S C I I"); 54
- cout } fclose (fp) ; /* đóng file */ 55
- } + Đóng tất cả các tập đang mở : int fcloseall(void) ; nếu thành công trả về số nguyên bằng tổng số các file đóng được, ngược lại trả về EOF. + Hàm xóa tập : remove (const + char*ten tập ) ; nếu thành công cho giá trị 0, ngược lại EOF. + Hàm kiểm tra cuối tập : int feof(FILE*fp) : !=0 : nếu cuối tập= 0 : chưa cuối tập. + Hàm int putc ( int ch, FILE*fp); Hàm int fputc( int ch, FILE*fp); Công dụng của hai hàm này :ghi một ký tự lên tập fp theo khuôn dạng được xác định trong chuỗi điều khiển dk. Chuỗi dk và danh sách đối tương tự hàm printf( ). + Hàm int fscanf ( FILE *fp, const char *dk, ); Công dụng : đọc dữ liệu từ tập tin fp theo khuôn dạng ( đặc tả) làm việc giống scanf( ). Ví dụ 25: Giả sử có file c:\data.txt lưu 10 số nguyên 1 5 7 9 8 0 4 3 15 20 . Hãy đọc các số nguyên thêm vào một mãng sau đó sắp xếp tăng dần rồi ghi vào file datasx.txt. #include #include #include #define n 10 void main ( ) { FILE *fp ; int i, j, t, a[n]; clrscr ( ) ; fp = fopen (" c :\\data.txt ", "rt" ); /* mở file để đọc vào mãng */ if (fp = NULL) { printf ("không mở được file "); exit (1); } while (1) { fscanf (fp,"%d",&a[i] ; i++; if (foef(fp) ) break ; /* Sắp xếp mảng */ for ( i=0 ; i<n-1 ; i++) for (j=i+1; j<n ; j++) if (a[i]<a[j] ) { t = a[i] ; a[i]=a[j] ; a[j] = t ; } fclose (fp); 56
- /* mở file datasx.txt để ghi sau khi sắp xếp */ fp = fopen ("c:\\datasx.txt ", "wt"); for ( i=0 ; i 0) Công dụng : đọc n(1) mẫu tin kích thước sizebyte (size of(tam)) từ tập tin fp chứa vào vùng nhớ p(&tam). Hàm trả về một giá trị bằng số mẫu tin thực sự đọc được. Ví dụ 26 : Nhập vào danh sách lớp gồm n học viên ("nhập vào). Thông tin về mỗi học viên gồm Họ tên, phái , điểm, kết quả. Xét kết quả theo điều kiện sau : nếu Ðiểm>= 5 ( đậu ), điểm #include #include #include 57
- struct { char ten[20] ; char phai[4] ; int diem ; char kq[4] ; } KieuHV; KieuHV lop[100] , tam; /* Hàm nhập danh sách n học viên */ void nhapds ( int n, KieuHV lop[ ] ) { int i , diem ; for ( i=0; i 5) strcpy (lop[i].kq,"Ðậu"); else strcpy (lop[i].kq, "rớt " ) ; } /* Hàm sắp xếp */ void sapxep ( int n , KieuHV lop[ ] ) { int i , j ; KieuHV tam; for ( i=0 ; i<n-1; i++) for ( j=i+1 ; j<0; j++) if (lop[i].diem< lop[j]diem ) { tam = lop[i] ; lop[i] = lop[j] ; lop [i] = tam ;} } /* Hàm in danh sách */ void inds ( int n, KieuHS lop[ ] ) { int i ; for ( i=0 ; i<n ; i++) printf ("\n %20s %5s%5d%5s, lop[i].ten, lop[i].phai, lop[i].diem, LOP[I].kq ); void main ( ) { int i , j, n, t, diem ; FILE *fp, *fr ; printf ("\n nhập sĩ số : ") ; scanf("%d%*c",&n); lop = (KieuHV*) malloc (n*size of (KieuHV)); nhapds(n, lop) ; sapxep ( n, lop ); inds( n, lop); getch( ); fp = fopen ( "c :\\lop.txt ", "wb"); for ( i = 0; i<n ; i++) fwrite (&lop[ i], size of (KieuHV), 1, fp); fclose(fp); 58
- printf ("\n ghi dữ liệu xong "); printf("\n in file sau khi sắp xếp và xét kết quả lại "); fr = fopen ("c:\\ketqua.txt", "wb"); while ( fread (&tam, size of ( KieuHV), 1, fp ) > 0) { printf ("\n %s %s%d%s", tam.ten, tam.phai, tam.diem, tam.kq); if (tam.diem = = 4 &&strcmp(tam.phai,"nữ")= =0 ) strcpy(&tam.kq, "đậu"); fwrite(&tam,size of(tam),1, fr); } fclose (fp); fclose(fr); printf ("\n in file ketqua.txt sau khi xét lại kết qủa "); fp = fopen ("c:\\ketqua.txt", "rb"); while (fread(&tam, size of (KieuHV) , 1, fp) > 0) printf("\n %s%s%d%s",tam.ten,tam.phai, tam.diem,tam.kq); fclose (fp); getch( ); } Các hàm xuất nhập ngẫu nhiên - Khi mở tệp tin để đọc hay ghi, con trỏ chỉ vị luôn luôn ở đầu tập tin (byte 0) nếu mở mode "a" (append) chuyển con trỏ chỉ vị trí cuối tập tin. + Hàm void rewind (FILE*fp) : chuyển con trỏ chỉ vị của tập fp về đầu tập tin. + Hàm int fseek (FILE*fp, long số byte, int xp) fp : là con trỏ tập tin; số byte : là số byte cần di chuyển. xp " cho biết vị trí xuất phát mà việc dịch chuyển được bắt đầu từ đó. xp = SEEK - SET hay 0 xuất phát từ đầu tập. xp = SEEK - CUR hay 1 : xuất phát từ vị trí hiện tại của con trỏ. xp= SEEK - END HAY 2 : xuất phát từ vị trí cuối tập của con trỏ. + Công dụng : hàm di chuyển con trỏ chỉ vị của tập fp từ vị trí xác định bởi xp qua một số byte bằng giá trị tuyệt đối của số byte. Nếu số byte > 0 : chuyển về hướng cuối tập ngược lại chuyển về hướng đầu tập. Nếu thành công trả về trị 0. Nếu có lỗi trả khác 0. + Chú ý : không nên dùng fseep trên kiểu văn bản, vì sự chuyển đổi ký tự( mã 10) sẽ làm cho việc định vị thiếu chính xác. + Hàm long ftell(FILE*fp) ; : cho biết vị trí hiện tại của con trỏ chỉ vị (byte thứ mấy trên tập fp) nếu không thành công trả về trị -1L. Ví dụ 27: giả sử chúng ta có tập tin c:\lop.txt chứa danh sách các học viên. Hãy đọc danh sách và sắp xếp giảm dần theo điểm sau đó ghi lại file c:\lop.txt ( nối điểm) #include #include #include #define N 100 59
- struct { char ten[20] ; int tuoi; float diem ; } KieuHV ; void main( ) { KieuHV hv[N] ; t; FILE*fp ; int i, , n ; fp = fopen ("c:\\lop.txt ", "rat"); if (fp = =NULL) { printf ("không mở được file "); exit(1); } n = 0 ; i = 0 ; while (!feof (fp)) { fread (&hv[i], size of (KieuHV), 1,fp); i++; n++ ; /* sắp xếp giảm dần theo điểm */ for (i=0, i void main(int argc, char *argv[]) { FILE *fp; char c; if (argc "); else if ((fp = fopen(argv[1], "r")) == NULL) printf("\nKhong the mo tap tin %s", argv[1]); else { while ((c = fgetc(fp)) != EOF) putc(c, stdout); } 60
- getch(); } Phiên bản 2: Dùng mảng ký tự Tên tập tin: typex.cpp #include #include void main(int argc, char *argv[]) { FILE *fp; char s[255]; if (argc "); else if ((fp = fopen(argv[1], "r")) == NULL) printf("\nKhong the mo tap tin %s", argv[1]); else while (fgets(s, 255, fp)) { s[strlen(s) - 1] = 0; if (strlen(s)) puts(s); } getch(); } Ví dụ 29: Chương trình sau đọc hai tập tin và nối 2 tập tin thành tập thứ 3. #include void main() { FILE *fp1, *fp2, *fpout; char sf1[50], sf2[50], sfout[50]; int c; printf("\nNhap ten tap tin thu nhat : "); scanf("%s", &sf1); printf("\nNhap ten tap tin thu hai : "); scanf("%s", &sf2); printf("\nNhap ten tap tin ket qua : "); scanf("%s", &sfout); if ((fp1 = fopen(sf1, "r")) == NULL) fprintf(stderr, "Khong the mo tap tin %s\n", sf1); if ((fp2 = fopen(sf2, "r")) == NULL) fprintf(stderr, "Khong the mo tap tin %s\n", sf2); 61
- if ((fpout = fopen(sfout, "w")) == NULL) fprintf(stderr, "Khong the mo tap tin %s\n", sfout); while ((c = getc(fp1)) != EOF) putc(c, fpout); while ((c = getc(fp2)) != EOF) putc(c, fpout); fclose(fp1); fclose(fp2); fclose(fpout); printf("\nHoan tat! Nhan phim bat ky de ket thuc."); getch(); } Ví dụ 30: Chương trình sẽ mã hóa từng ký tự trong tập tin với một ký tự nhập vào theo phép toán XOR. #include void main() { char c, filein[50], fileout[50], key; FILE *fpin, *fpout; printf("\nCho biet ten tap tin nguon : "); gets(filein); printf("\nCho biet ten tap tin dich : "); gets(fileout); printf("\nCho biet khoa : "); scanf("%c", &key); if ((fpin = fopen(filein, "r")) == NULL) printf("Khong tim thay tap tin %s", filein); else if ((fpout = fopen(fileout, "w+")) == NULL) { printf("Khong the tao tap tin %s", fileout); fclose(fpin); } else { do { c = fgetc(fpin); if (c != EOF) fputc(c ^ key, fpout); //XOR } while (c != EOF); fclose(fpin); 62
- fclose(fpout); printf("\nĐã mã hóa xong"); } getch(); } 2.7. Các kiểu dữ liệu khác Kiểu tự định nghĩa typedef C++ cho phép chúng ta định nghĩa các kiểu dữ liệu của riêng mình dựa trên các kiểu dữ liệu đã có. Để có thể làm việc đó chúng ta sẽ sử dụng từ khoá typedef, dạng thức như sau: typedef kiểu_dữ_liệu_đã_có kiểu_dữ_liệu_mới; trong đó kiểu_dữ_liệu_đã_có là một kiểu dữ liệu cơ bản hay bất kì một kiểu dữ liệu đã định nghĩa và kiểu_dữ_liệu_mới là tên của kiểu dữ liệu mới. Ví dụ typedef char C; typedef unsigned int WORD; typedef char * string_t; typedef char field [50]; Trong trường hợp này chúng ta đã định nghĩa bốn kiểu dữ liệu mới: C, WORD, string_t và field kiểu char, unsigned int, char* kiểu char[50], chúng ta hoàn toàn có thể sử dụng chúng như là các kiểu dữ liệu hợp lệ: achar, anotherchar, *ptchar1; WORD myword; string_t ptchar2; field name; typedef có thể hữu dụng khi bạn muốn định nghĩa một kiểu dữ liệu được dùng lặp đi lặp lại trong chương trình hoặc kiểu dữ liệu bạn muốn dùng có tên quá dài và bạn muốn nó có tên ngắn hơn. Kiểu liệt kê (enum) Kiểu dữ liệu liệt kê dùng để tạo ra các kiểu dữ liệu chứa một cái gì đó hơi đặc biệt một chút, không phải kiểu số hay kiểu kí tự hoặc các hằng true và false. Dạng thức của nó như sau: enum model_name { value1, value2, value3, . . } object_name; Chẳng hạn, chúng ta có thể tạo ra một kiểu dữ liệu mới có tên color để lưu trữ các màu với phần khai báo như sau: enum colors_t {black, blue, green, cyan, red, purple, yellow, white}; 63
- Chú ý rằng chúng ta không sử dụng bất kì một kiểu dữ liệu cơ bản nào trong phần khai báo. Chúng ta đã tạo ra một kiểu dữ liệu mới mà không dựa trên bất kì kiểu dữ liệu nào có sẵn: kiểu color_t, những giá trị có thể của kiểu color_t được viết trong cặp ngoặc nhọn {}. Ví dụ, sau khi khai báo kiểu liệt kê, biểu thức sau sẽ là hợp lệ: colors_t mycolor; mycolor = blue; if (mycolor == green) mycolor = red; Trên thực tế kiểu dữ liệu liệt kê được dịch là một số nguyên và các giá trị của nó là các hằng số nguyên được chỉ định. Nếu điều này không đựoc chỉ định, giá trị nguyên tương đương với phần tử đầu tiên là 0 và các giá trị tiếp theo cứ thế tăng lên 1, Vì vậy, trong kiểu dữ liệu colors_t mà chúng ta định nghĩa ở trên, white tương đương với 0, blue tương đương với 1, green tương đương với 2 và cứ tiếp tục như thế. Nếu chúng ta chỉ định một giá trị nguyên cho một giá trị nào đó của kiểu dữ liệu liệt kê (trong ví dụ này là phần tử đầu tiên) các giá trị tiếp theo sẽ là các giá trị nguyên tiếp theo, chẳng hạn: enum months_t { january=1, february, march, april, may, june, july, august, september, october, november, december} y2k; trong trường hợp này, biến y2k có kiểu dữ liệu liệt kê months_t có thể chứa một trong 12 giá trị từ january đến december và tương đương với các giá trị nguyên từ 1 đến 12, không phải 0 đến 11 vì chúng ta đã đặt january bằng 1. Ví dụ 31: Chương trình sau kết hợp giữa kiểu liệt kê và kiểu cấu trúc để tính lương cho một tuần làm việc. Chủ nhật được tính giờ gấp đôi, thứ năm được tính 1,25 giờ cho một giờ làm. Giá tiền trả cho một giờ lương là 1,1 USD. #include"stdio.h" enum luong_tuan {Thu_Hai,Thu_Ba,Thu_tu,Thu_Nam,Thu_Sau,Thu_Bay,Chu_Nhat}; char *thu[]={"Thu Hai","Thu Ba","Thu Tu","Thu Nam","Thu Sau","Thu Bay","Chu Nhat"}; void main() { enum luong_tuan ngay; char ten[8]; float luong=0; int gio; clrscr(); printf("\nCho biet ten : "); scanf("%s",&ten); for (ngay=Thu_Hai; ngay <=Chu_Nhat;ngay++) { printf("\n- Gio lam viec trong ngay: %s la= ",thu[(int)ngay]); scanf("%d",&gio); switch (ngay) 64
- { case Thu_Nam: luong+=1.25*gio; break; case Chu_Nhat: luong+=2*gio; break; default : luong+=1.1*gio; break; } } printf("\n Ong ( Ba ) : %s ",ten); printf("\n-Se nhan duoc tien luong trong tuan la = "); printf("%d dollars va %0.2f cents",(int)luong,luong-(int)luong); getch(); } 2.7. Bài tập tự nghiên cứu ở nhà. Bài tập 1: Hãy viết chương trình nhập vào một dãy các số nguyên. Lưu dãy số nguyên này vào file. Bài tập 2: Tìm kiếm một số nguyên trong dãy các số nguyên có trong file ở bài tập 1. Bài tập 3: Viết chương trình giải hệ phương trình dùng phương pháp Gauss-Jordan. Bài tập 4: Viết chương trình quản lý sách trong thư viện. Bài tập 5: Viết chương trình đọc hai ma trận vuông từ hai tập tin. Thực hiện các phép toán: a) Nhân hai ma trận. b) Cộng hai ma trận. c) Tính đa thức ma trận. d) Trừ hai ma trận e) Tính AT Bài tập 6: Vẽ sơ đồ các kiểu dữ liệu đã học trong bài. Bài tập 7: Viết chương trình với hai tập tin, một tập tin dùng để mã hóa, một tập tin chứa dữ liệu. Thực hiện, đọc tập tin dữ liệu và đọc tập tin mã hóa, sau đó dùng phép toán XOR để mã hóa từng ký tự đọc được lưu lại vào tập tin thứ 3. 65