Bài giảng môn Kỹ thuật lập trình C

pdf 102 trang vanle 2020
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng môn Kỹ thuật lập trình C", để 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:

  • pdfbai_giang_mon_ky_thuat_lap_trinh_c.pdf

Nội dung text: Bài giảng môn Kỹ thuật lập trình C

  1. BỘ GIAO THƠNG VẬN TẢI TRƢỜNG ĐẠI HỌC HÀNG HẢI BỘ MƠN: KHOA HỌ C MÁ Y TÍ NH KHOA: CƠNG NGHỆ THƠNG TIN BÀI GIẢNG KỸ THUẬT LẬP TRÌNH C TÊN HỌC PHẦN : KỸ THUẬT LẬP TRÌNH C MÃ HỌC PHẦN : 17206 TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUY DÙNG CHO SV NGÀNH : CƠNG NGHỆ THƠNG TIN HẢI PHÕNG - 2008
  2. 11.6. Tên học phần: Kỹ thuật lập trình (C) Loại học phần: 2 Bộ mơn phụ trách giảng dạy: Khoa học Máy tính Khoa phụ trách: CNTT Mã học phần: 17206 Tổng số TC: 4 TS tiết Lý thuyết Thực hành/Xemina Tự học Bài tập lớn Đồ án mơn học 75 45 30 0 0 0 Điều kiện tiên quyết: Sinh viên phải học xong các học phần sau mới được đăng ký học phần này: Tin đại cương, Tốn rời rạc, Đại số, Giải tích 1. Mục tiêu của học phần: Cung cấp cho sinh viên kiến thức và rèn luyện kỹ năng lập trình dựa trên ngơn ngữ lập trình C Nội dung chủ yếu - Những vấn đề cơ bản về ngơn ngữ lập trình C. - Cách thức xây dựng một chương trình dựa trên ngơn ngữ lập trình C. - Các vấn đề về con trỏ, file và đồ họa trong C Nội dung chi tiết của học phần: PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT Chƣơng 1: Giới thiệu 2 2 0 1.1. Giới thiệu ngơn ngữ lập trình C. 1.1.1. Xuất xứ của ngơn ngữ lập trình C. 1.1.2. Trình biên dịch C và cách sử dụng. 1.2. Thuật tốn và sơ đồ khối Chƣơng 2. Các khái niệm cơ bản về ngơn ngữ C 8 4 4 2.1. Các phần tử cơ bản của ngơn ngữ lập trình C. 2.2. Cấu trúc chung của chương trình C 2.3. Các bước cơ bản khi lập chương trình 2.4. Các hàm nhập xuất cơ bản 2.5. Biến và các kiểu dữ liệu cơ sở Chƣơng 3. Các câu lệnh điều khiển của C 13 7 5 1 3.1. Hàm viết dữ liệu ra màn hình 3.2. Hàm nhập dữ liệu vào từ bàn phím 3.3. Câu lệnh điều kiện 3.4. Câu lệnh lựa chọn 3.5. Câu lệnh lặp xác định 3.6. Câu lệnh lặp khơng xác định 3.6.1. Câu lệnh while. 3.6.2. Câu lệnh do. Chƣơng 4. Hàm 14 8 6 4.1. Khái niệm về chương trình con 4.2. Hàm trong C 4.3. Chuyển tham số cho hàm 4.4. Biến tồn cục và biến địa phương 4.5. Tính đệ quy của hàm 4.6. Đối dịng lệnh của hàm i
  3. PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT 4.7. Một số hàm đặc biệt Chƣơng 5. Mảng và kiểu dữ liệu cĩ cấu trúc 21 12 8 1 5.1. Dữ liệu kiểu mảng/con trỏ 5.1.1. Mảng 1 chiều và nhiều chiều 5.1.2. Con trỏ và địa chỉ 5.1.3. Liên hệ giữa mảng và con trỏ 5.1.4. Con trỏ và hàm 5.2. Dữ liệu kiểu xâu ký tự. Liên hệ giữa con trỏ và xâu ký tự 5.3. Dữ liệu kiểu bản ghi 5.4. Một số ví dụ tổng hợp Chƣơng 6. File 10 5 4 1 6.1. Khái niệm. 6.2. Cấu trúc và phân loại tệp. 6.3. Tạo tệp mới để ghi dữ liệu. 6.4. Mở một tệp dữ liệu đã cĩ để đọc dữ liệu. 6.5. Các hàm và hàm xử lý tệp của Turbo C. 6.6. Tệp văn bản. 6.7. Tệp nhị phân 6.8. Truy cập tệp ngẫu nhiên: hàm fread, fwrite Chƣơng 7. Đồ hoạ trong C 7 4 3 7.1. Giới thiệu chung 7.2. Các hàm đặt màu, vẽ điểm, tơ màu 7.3. Các hàm vẽ hình cơ bản Nhiệm vụ của sinh viên : Tham dự các buổi thuyết trình của giáo viên, tự học, tự làm bài tập do giáo viên giao, tham dự các bài kiểm tra định kỳ và cuối kỳ. Tài liệu học tập : 1. Phạm Văn Ất, Kỹ thuật lập trình C - Cơ sở và nâng cao, NXB KHKT, 1998. 2. Quách Tuấn Ngọc, Ngơn ngữ lập trình C, NXB GD, 1998. 3. Một số website liên quan: Hình thức và tiêu chuẩn đánh giá sinh viên: - Hình thức thi cuối kỳ : Thi vấn đáp trên máy tính - Sinh viên phải đảm bảo các điều kiện theo Quy chế của Nhà trường và của Bộ Thang điểm: Thang điểm chữ A, B, C, D, F Điểm đánh giá học phần: Z = 0,3X + 0,7Y. ii
  4. CHƢƠNG 1. GIỚI THIỆU 1.1. Giới thiệu ngơn ngữ lập trình C. 1.1.1. Xuất xứ của ngơn ngữ lập trình C. Khoảng cuối những năm 1960 đầu 1970 xuất hiện nhu cầu cần cĩ các ngơn ngữ bậc cao để hỗ trợ cho những nhà tin học trong việc xây dựng các phần mềm hệ thống, hệ điều hành. Ngơn ngữ C ra đời từ đĩ, nĩ đã được phát triển tại phịng thí nghiệm Bell. Đến năm 1978, giáo trình " Ngơn ngữ lập trình C " do chính các tác giả của ngơn ngữ là Dennish Ritchie và B.W. Kernighan viết, đã được xuất bản và phổ biến rộng rãi. C là ngơn ngữ lập trình vạn năng. Ngồi việc C được dùng để viết hệ điều hành UNIX, người ta nhanh chĩng nhận ra sức mạnh của C trong việc xử lý cho các vấn đề hiện đại của tin học. C khơng gắn với bất kỳ một hệ điều hành hay máy nào, và mặc dầu nĩ đã được gọi là " ngơn ngữ lập trình hệ thống" vì nĩ được dùng cho việc viết hệ điều hành, nĩ cũng tiện lợi cho cả việc viết các chương trình xử lý số, xử lý văn bản và cơ sở dữ liệu. 1.1.2. Trình biên dịch C và cách sử dụng A. Turbo C (TC) 1. Giới thiệu chung TC Khởi động C cũng như mọi chương trình khác bằng cách nhấp đúp chuột lên biểu tượng của chương trình. Khi chương trình được khởi động sẽ hiện ra giao diện gồm cĩ menu cơng việc và một khung cửa sổ bên dưới phục vụ cho soạn thảo. Một con trỏ nhấp nháy trong khung cửa sổ và chúng ta bắt đầu nhập nội dung (văn bản) chương trình vào trong khung cửa sổ soạn thảo này. Mục đích của giáo trình này là trang bị những kiến thức cơ bản của lập trình thơng qua NNLT C cho các sinh viên mới bắt đầu nên chúng tơi vẫn chọn trình bày giao diện của các trình biên dịch quen thuộc là Turbo C hoặc Borland C. Về các trình biên dịch khác độc giả cĩ thể tự tham khảo trong các tài liệu liên quan. Để kết thúc làm việc với C (soạn thảo, chạy chương trình ) và quay về mơi trường Windows chúng ta ấn Alt-X. 2. Giao diện và cửa sổ soạn thảo của TC a. Mơ tả chung Khi gọi chạy C trên màn hình sẽ xuất hiện một menu xổ xuống và một cửa sổ soạn thảo. Trên menu gồm cĩ các nhĩm chức năng: File, Edit, Search, Run, Compile, Debug, Project, Options, Window, Help. Để kích hoạt các nhĩm chức năng, cĩ thể ấn Alt+chữ cái biểu thị cho menu của chức năng đĩ (là chữ cái cĩ gạch dưới). Ví dụ để mở nhĩm chức năng File ấn Alt+F, sau đĩ dịch chuyển hộp sáng đến mục cần chọn rồi ấn Enter. Để thuận tiện cho NSD, một số các chức năng hay dùng cịn được gắn với một tổ hợp các phím cho phép người dùng cĩ thể chọn nhanh chức năng này mà khơng cần thơng qua việc mở menu như đã mơ tả ở trên. Một số tổ hợp phím cụ thể đĩ sẽ được trình bày vào cuối phần này. Các bộ chương trình dịch hỗ trợ người lập trình một mơi trường tích hợp tức ngồi chức năng soạn thảo, nĩ cịn cung cấp nhiều chức năng, tiện ích khác giúp người lập trình vừa cĩ thể soạn thảo văn bản chương trình vừa gọi chạy chương trình vừa gỡ lỗi Các chức năng liên quan đến soạn thảo phần lớn 3
  5. giống với các bộ soạn thảo khác (như WinWord) do vậy chúng tơi chỉ trình bày tĩm tắt mà khơng trình bày chi tiết ở đây. b. Các chức năng soạn thảo Giống hầu hết các bộ soạn thảo văn bản, bộ soạn thảo của Turbo C hoặc Borland C cũng sử dụng các phím sau cho quá trình soạn thảo: − Dịch chuyển con trỏ: các phím mũi tên cho phép dịch chuyển con trỏ sang trái, phải một kí tự hoặc lên trên, xuống dưới 1 dịng. Để dịch chuyển nhanh cĩ các phím như Home (về đầu dịng), End (về cuối dịng), PgUp, PgDn (lên, xuống một trang màn hình). Để dịch chuyển xa hơn cĩ thể kết hợp các phím này cùng phím Control (Ctrl, ^) như ^PgUp: về đầu tệp, ^PgDn: về cuối tệp. − Chèn, xố, sửa: Phím Insert cho phép chuyển chế độ soạn thảo giữa chèn và đè. Các phím Delete, Backspace cho phép xố một kí tự tại vị trí con trỏ và trước vị trí con trỏ (xố lùi). − Các thao tác với khối dịng: Để đánh dấu khối dịng (thực chất là khối kí tự liền nhau bất kỳ) ta đưa con trỏ đến vị trí đầu ấn Ctrl-KB và Ctrl-KK tại vị trí cuối. Cũng cĩ thể thao tác nhanh hơn bằng cách giữ phím Shift và dùng cácphím dịch chuyển con trỏ quét từ vị trí đầu đến vị trí cuối, khi đĩ khối kí tự đuợc đánh dấu sẽ chuyển mầu nền. Một khối được đánh dấu cĩ thể dùng để cắt, dán vào một nơi khác trong văn bản hoặc xố khỏi văn bản. Để thực hiện thao tác cắt dán, đầu tiên phải đưa khối đã đánh dấu vào bộ nhớ đệm bằng nhĩm phím Shift-Delete (cắt), sau đĩ dịch chuyển con trỏ đến vị trí mới cần hiện nội dung vừa cắt và ấn tổ hợp phím Shift-Insert. Một đoạn văn bản được ghi vào bộ nhớ đệm cĩ thể được dán nhiều lần vào nhiều vị trí khác nhau bằngcách lặp lại tổ hợp phím Shift-Insert tại các vị trí khác nhau trong văn bản. Để xố một khối dịng đã đánh dấu mà khơng ghi vào bộ nhớ đệm, dùng tổ hợp phím Ctrl- Delete. Khi một nội dung mới ghi vào bộ nhớ đệm thì nĩ sẽ xố (ghi đè) nội dung cũ đã cĩ, do vậy cần cân nhắc để sử dụng phím Ctrl-Delete (xố và khơng lưu lại nội dung vừa xố vào bộ đệm) và Shift-Delete (xố và lưu lại nội dung vừa xố) một cách phù hợp. − Tổ hợp phím Ctrl-A rất thuận lợi khi cần đánh dấu nhanh tồn bộ văn bản. c. Chức năng tìm kiếm và thay thế Chức năng này dùng để dịch chuyển nhanh con trỏ văn bản đến từ cần tìm. Để thực hiện tìm kiếm bấm Ctrl-QF, tìm kiếm và thay thế bấm Ctrl-QA. Vào từ hoặc nhĩm từ cần tìm vào cửa sổ Find, nhĩm thay thế (nếu dùng Ctrl-QA) vào cửa sổ Replace và đánh dấu vào các tuỳ chọn trong cửa sổ bên dưới sau đĩ ấn Enter. Các tuỳ chọn gồm: khơng phân biệt chữ hoa/thường, tìm từ độc lập hay đứng trong từ khác, tìm trong tồn văn bản hay chỉ trong phần được đánh dấu, chiều tìm đi đến cuối hay ngược về đầu văn bản, thay thế cĩ hỏi lại hay khơng hỏi lại Để dịch chuyển con trỏ đến các vùng khác nhau trong một menu hay cửa sổ chứa các tuỳ chọn ta sử dụng phím Tab. d. Các chức năng liên quan đến tệp − Ghi tệp lên đĩa: Chọn menu File\Save hoặc phím F2. Nếu tên tệp chưa cĩ (cịn mang tên Noname.cpp) máy sẽ yêu cầu cho tên tệp. Phần mở rộng của tên tệp được mặc định là CPP. − Soạn thảo tệp mới: Chọn menu File\New. Hiện ra cửa sổ soạn thảo trắng và tên file tạm thời lấy là Noname.cpp. − Soạn thảo tệp cũ: Chọn menu File\Open hoặc ấn phím F3, nhập tên tệp hoặc dịch chuyển con trỏ trong vùng danh sách tệp bên dưới đến tên tệp cần soạn rồi ấn Enter. Cũng cĩ thể áp dụng cách này để soạn tệp mới khi khơng nhập vào tên tệp cụ thể. − Ghi tệp đang soạn thảo lên đĩa với tên mới: Chọn menu File\Save As và nhập tên tệp 4
  6. mới vào rồi ấn Enter. e. Chức năng dịch và chạy chương trình − Ctrl-F9: Khởi động chức năng dịch và chạy tồn bộ chương trình. − F4: Chạy chương trình từ đầu đến dịng lệnh hiện tại (đang chứa con trỏ) − F7: Chạy từng lệnh một của hàm main(), kể cả các lệnh con trong hàm. − F8: Chạy từng lệnh một của hàm main(). Khi đĩ mỗi lời gọi hàm được xem là một lệnh (khơng chạy từng lệnh trong các hàm được gọi). Các chức năng liên quan đến dịch chương trình cĩ thể được chọn thơng qua menuCompile (Alt-C). f. Tĩm tắt một số phím nĩng hay dùng − Các phím kích hoạt menu: Alt+chữ cái đại diện cho nhĩm menu đĩ. Ví dụ Alt-F mở menu File để chọn các chức năng cụ thể trong nĩ như Open (mở file), Save (ghi file lên đĩa), Print (in nội dung văn bản chương trình ra máy in), Alt-C mở menu Compile để chọn các chức năng dịch chương trình. − Các phím dịch chuyển con trỏ khi soạn thảo. − F1: mở cửa sổ trợ giúp. Đây là chức năng quan trọng giúp người lập trình nhớ tên lệnh, cú pháp và cách sử dụng. − F2: ghi tệp lên đĩa. − F3: mở tệp cũ ra sửa chữa hoặc soạn thảo tệp mới. − F4: chạy chương trình đến vị trí con trỏ. − F5: Thu hẹp/mở rộng cửa sổ soạn thảo. − F6: Chuyển đổi giữa các cửa sổ soạn thảo. − F7: Chạy chương trình theo từng lệnh, kể cả các lệnh trong hàm con. − F8: Chạy chương trình theo từng lệnh trong hàm chính. − F9: Dịch và liên kết chương trình. Thường dùng chức năng này để tìm lỗi cú pháp của chương trình nguồn trước khi chạy. − Alt-F7: Chuyển con trỏ về nơi gây lỗi trước đĩ. − Alt-F8: Chuyển con trỏ đến lỗi tiếp theo. − Ctrl-F9: Chạy chương trình. − Ctrl-Insert: Lưu khối văn bản được đánh dấu vào bộ nhớ đệm. − Shift-Insert: Dán khối văn bản trong bộ nhớ đệm vào văn bản tại vị trí con trỏ. − Shift-Delete: Xố khối văn bản được đánh dấu, lưu nĩ vào bộ nhớ đệm. − Ctrl-Delete: Xố khối văn bản được đánh dấu (khơng lưu vào bộ nhớ đệm). − Alt-F5: Chuyển sang cửa sổ xem kết quả của chương trình vừa chạy xong. − Alt-X: thốt C về lại Windows. B. Dev C C. Visual C++ 1.2. Thuật tốn và sơ đồ khối 5
  7. CHƢƠNG 2. CÁC KHÁI NIỆM CƠ BẢN VỀ NGƠN NGỮ LẬP TRÌNH C 2.1. Các phần tử cơ bản của ngơn ngữ lập trình C. 2.1.1. Tập ký tự dùng trong ngơn ngữ C Mọi ngơn ngữ lập trình đều được xây dựng từ một bộ ký tự nào đĩ. Các ký tự được nhĩm lại theo nhiều cách khác nhau để tạo nên các từ. Các từ lại được liên kết với nhau theo một qui tắc nào đĩ để tạo nên các câu lệnh. Một chương trình bao gồm nhiều câu lệnh và thể hiện một thuật tốn để giải một bài tốn nào đĩ. Ngơn ngữ C được xây dựng trên bộ ký tự sau : - 26 chữ cái hoa : A B C Z - 26 chữ cái thường : a b c z - 10 chữ số : 0 1 2 9 - Các ký hiệu tốn học : + - * / = ( ) - Ký tự gạch nối : _ - Các ký tự khác : . , : ; [ ] {} ! \ & % # $ Dấu cách (space) dùng để tách các từ. Ví dụ chữ VIET NAM cĩ 8 ký tự, cịn VIETNAM chỉ cĩ 7 ký tự. Chú ý : Khi viết chương trình, ta khơng được sử dụng bất kỳ ký tự nào khác ngồi các ký tự trên. Ví dụ như khi lập chương trình giải phương trình bậc hai ax2 +bx+c=0 , ta cần tính biệt thức Delta = b2 - 4ac, trong ngơn ngữ C khơng cho phép dùng ký tự , vì vậy ta phải dùng ký hiệu khác để thay thế. 2.1.2. Từ khố: Từ khố là những từ được sử dụng để khai báo các kiểu dữ liệu, để viết các tốn tử và các câu lệnh. Bảng dưới đây liệt kê các từ khố của TURBO C : asm break case cdecl char const continue default do double else enum extern far float for goto huge if int interrupt long near pascal register return short signed sizeof static struct switch tipedef union unsigned void volatile while 6
  8. ý nghĩa và cách sử dụng của mỗi từ khố sẽ được đề cập sau này, ở đây ta cần chú ý : - Khơng được dùng các từ khố để đặt tên cho các hằng, biến, mảng, hàm - Từ khố phải được viết bằng chữ thường, ví dụ : viết từ khố khai báo kiểu nguyên là int chứ khơng phải là INT. 2.1.3. Tên: Tên là một khái niệm rất quan trọng, nĩ dùng để xác định các đại lượng khác nhau trong một chương trình. Chúng ta cĩ tên hằng, tên biến, tên mảng, tên hàm, tên con trỏ, tên tệp, tên cấu trúc, tên nhãn, Tên được đặt theo qui tắc sau: Tên là một dãy các ký tự bao gồm chữ cái, số và gạch nối. Ký tự đầu tiên của tên phải là chữ hoặc gạch nối. Tên khơng được trùng với khố. Độ dài cực đại của tên theo mặc định là 32 và cĩ thể được đặt lại là một trong các giá trị từ 1 tới 32 nhờ chức năng : Option-Compiler-Source-Identifier length khi dùng TURBO C. Ví dụ : Các tên đúng : a_1 delta x1 _step GAMA Các tên sai: 3MN Ký tự đầu tiên là số m#2 Sử dụng ký tự # f(x) Sử dụng các dấu ( ) do Trùng với từ khố te ta Sử dụng dấu trắng Y-3 Sử dụng dấu - Chú ý: Trong TURBO C, tên bằng chữ thường và chữ hoa là khác nhau ví dụ tên AB khác với ab. Trong C, ta thường dùng chữ hoa để đặt tên cho các hằng và dùng chữ thường để đặt tên cho hầu hết cho các đại lượng khác như biến, biến mảng, hàm, cấu trúc. Tuy nhiên đây khơng phải là điều bắt buộc. 2.2. Cấu trúc chung của chƣơng trình C Một chương trình C cĩ thể được đặt trong một hoặc nhiều file văn bản khác nhau. Mỗi file văn bản chứa một số phần nào đĩ của chương trình. Với những chương trình đơn giản và ngắn thường chỉ cần đặt chúng trên một file. Một chương trình gồm nhiều hàm, mỗi hàm phụ trách một cơng việc khác nhau của chương trình. Đặc biệt trong các hàm này cĩ một hàm duy nhất cĩ tên hàm là main(). Khi chạy chương trình, các câu lệnh trong hàm main() sẽ được thực hiện đầu tiên. Trong hàm main() cĩ thể cĩ các câu lệnh gọi đến các hàm khác khi cần thiết, và các hàm này khi chạy lại cĩ thể gọi đến các hàm khác nữa đã được viết trong chương trình (trừ việc gọi quay lại hàm main()). Sau khi chạy đến lệnh cuối cùng của hàm main() chương trình sẽ kết thúc. Cụ thể, thơng thường một chương trình gồm cĩ các nội dung sau: − Phần khai báo các tệp nguyên mẫu: khai báo tên các tệp chứa những thành phần cĩ 7
  9. sẵn (như các hằng chuẩn, kiểu chuẩn và các hàm chuẩn) mà NSD sẽ dùng trong chương trình. − Phần khai báo các kiểu dữ liệu, các biến, hằng do NSD định nghĩa và được dùng chung trong tồn bộ chương trình. − Danh sách các hàm của chương trình (do NSD viết, bao gồm cả hàm main()). Cấu trúc chi tiết của mỗi hàm sẽ được đề cập đến trong chương 4. Dưới đây là một đoạn chương trình đơn giản chỉ gồm 1 hàm chính là hàm main(). Nội dung của chương trình dùng in ra màn hình dịng chữ: Chào các bạn, bây giờ là 2giờ. #include // khai báo tệp nguyên mẫu để được sử dụng hàm printf, scanf void main() { int h = 2; // Khai báo và khởi tạo biến h = 2 printf( “Chào các bạn, bây giờ là %d giờ”,h) ;// in ra màn hình } Dịng đầu tiên của chương trình là khai báo tệp nguyên mẫu stdio.h. Đây là khai báo bắt buộc vì trong chương trình cĩ sử dụng hàm chuẩn printf() (in ra màn hình), hàm này được khai báo và định nghĩa sẵn trong stdio.h. Khơng riêng hàm main(), mọi hàm khác đều phải bắt đầu tập hợp các câu lệnh của mình bởi dấu { và kết thúc bởi dấu }. Tập các lệnh bất kỳ bên trong cặp dấu này được gọi là khối lệnh. Khối lệnh là một cú pháp cần thiết trong các câu lệnh cĩ cấu trúc như ta sẽ thấy trong các chương tiếp theo. Vậy nĩi tĩm lại cấu trúc cơ bản của chương trình như sau : Các #include Các #define Khai báo các đối tượng dữ liệu ngồi ( biến, mảng, cấu trúc vv ). Khai báo nguyên mẫu các hàm. Hàm main(). Định nghĩa các hàm ( hàm main cĩ thể đặt sau hoặc xen vào giữa các hàm khác ). 2.3. Các bƣớc cơ bản khi lập chƣơng trình 2.3.1. Qui trình viết và thực hiện chƣơng trình Trước khi viết và chạy một chương trình thơng thường chúng ta cần: 1. Xác định yêu cầu của chương trình. Nghĩa là xác định dữ liệu đầu vào (input) cung cấp cho chương trình và tập các dữ liệu cần đạt được tức đầu ra (output).Các tập hợp dữ liệu này ngồi các tên gọi cịn cần xác định kiểu của nĩ.Ví dụ để giải một phương 2 trình bậc 2 dạng: ax + bx + c = 0, cần báo cho chương trình biết dữ liệu đầu vào là a, b, c và đầu ra là nghiệm x1 và x2 của phương trình. Kiểu của a, b, c, x1, x2 là các số thực. 2. Xác định thuật tốn giải. 3. Cụ thể hố các khai báo kiểu và thuật tốn thành dãy các lệnh, tức viết thành chương trình thơng thường là trên giấy, sau đĩ bắt đầu soạn thảo vào trong máy. Quá trình này được gọi là soạn thảo chương trình nguồn. 8
  10. 4. Dịch chương trình nguồn để tìm và sửa các lỗi gọi là lỗi cú pháp. 5. Chạy chương trình, kiểm tra kết quả in ra trên màn hình. Nếu sai, sửa lại chương trình, dịch và chạy lại để kiểm tra. Quá trình này được thực hiện lặp đi lặp lại cho đến khi chương trình chạy tốt theo yêu cầu đề ra của NSD. 2.3.2. Soạn thảo tệp chƣơng trình nguồn Soạn thảo chương trình nguồn là một cơng việc đơn giản: gõ nội dung của chương trình (đã viết ra giấy) vào trong máy và lưu lại nĩ lên đĩa. Thơng thường khi đã lưu lại chương trình lên đĩa lần sau sẽ khơng cần phải gõ lại. Cĩ thể soạn chương trình nguồn trên các bộ soạn thảo (editor) khác nhưng phải chạy trong mơi trường tích hợp C++ (Borland C, Turbo C). Mục đích của soạn thảo là tạo ra một văn bản chương trình và đưa vào bộ nhớ của máy. Văn bản chương trình cần được trình bày sáng sủa, rõ ràng. Các câu lệnh cần giĩng thẳng cột theo cấu trúc của lệnh (các lệnh chứa trong một lệnh cấu trúc được trình bày thụt vào trong so với điểm bắt đầu của lệnh). Các chú thích nên ghi ngắn gọn, rõ nghĩa và phù hợp. 2.3.3. Dịch chƣơng trình Sau khi đã soạn thảo xong chương trình nguồn, bước tiếp theo thường là dịch (ấn tổ hợp phím Alt-F9) để tìm và sửa các lỗi gọi là lỗi cú pháp. Trong khi dịch C++ sẽ đặt con trỏ vào nơi gây lỗi (viết sai cú pháp) trong văn bản. Sau khi sửa xong một lỗi NSD cĩ thể dùng Alt-F8 để chuyển con trỏ đến lỗi tiếp theo hoặc dịch lại. Để chuyển con trỏ về ngược lại lỗi trước đĩ cĩ thể dùng Alt-F7. Quá trình sửa lỗi − dịch được lặp lại cho đến khi văn bản đã được sửa hết lỗi cú pháp. Sản phẩm sau khi dịch là một tệp mới gọi là chương trình đích cĩ đuơi EXE tức là tệp mã máy để thực hiện.Tệp này cĩ thể lưu tạm thời trong bộ nhớ phục vụ cho quá trình chạy chương trình hoặc lưu lại trên đĩa tuỳ theo tuỳ chọn khi dịch của NSD. Trong và sau khi dịch, C++ sẽ hiện một cửa sổ chứa thơng báo về các lỗi (nếu cĩ), hoặc thơng báo chương trình đã được dịch thành cơng (khơng cịn lỗi). Các lỗi này được gọi là lỗi cú pháp. Để dịch chương trình ta chọn menu \Compile\Compile hoặc \Compile\Make hoặc nhanh chĩng hơn bằng cách ấn tổ hợp phím Alt-F6. 2.3.4. Chạy chƣơng trình Ấn Ctrl-F9 để chạy chương trình, nếu chương trình chưa dịch sang mã máy, máy sẽ tự động dịch lại trước khi chạy. Kết quả của chương trình sẽ hiện ra trong một cửa sổ kết quả để NSD kiểm tra. Nếu kết quả chưa được như mong muốn, quay lại văn bản để sửa và lại chạy lại chương trình. Quá trình này được lặp lại cho đến khi chương trình chạy đúng như yêu cầu đã đề ra. Khi chương trình chạy, cửa sổ kết quả sẽ hiện ra tạm thời che khuất cửa sổ soạn thảo. Sau khi kết thúc chạy chương trình cửa sổ soạn thảo sẽ tự động hiện ra trở lại và che khuất cửa sổ kết quả. Để xem lại kết quả đã hiện ấn Alt-F5 (hoặc thêm lệnh getch() vào cuối hàm main()). Sau khi xem xong để quay lại cửa sổ soạn thảo ấn phím bất kỳ. 2.4. Các kiểu dữ liệu cơ sở Trong C sử dụng các các kiểu dữ liệu cơ sở sau : 2.4.1. Kiểu ký tự (char): Một giá trị kiểu char chiếm 1 byte ( 8 bit ) và biểu diễn được một ký tự thơng qua bảng mã ASCII. Ví dụ: Ký tự Mã ASCII 9
  11. 0 048 1 049 2 050 A 065 B 066 a 097 b 098 Cĩ hai kiểu dữ liệu char : kiểu signed char và unsigned char. Kiểu Phạm vi biểu diễn Số ký tự Kích thƣớc char (signed char ) -128 đến 127 256 1 byte unsigned char 0 đến 255 256 1 byte Ví dụ sau minh hoạ sự khác nhau giữa hai kiểu dữ liệu trên. Xét đoạn chương trình sau: char ch1; unsigned char ch2; ch1=200; ch2=200; Khi đĩ thực chất : ch1=-56; ch2=200; Nhưng cả ch1 và ch2 đều biểu diễn cùng một ký tự cĩ mã 200. Phân loại ký tự : Cĩ thể chia 256 ký tự làm ba nhĩm : Nhĩm 1: Nhĩm các ký tự điều khiển cĩ mã từ 0 đến 31. Chẳng hạn ký tự mã 13 dùng để chuyển con trỏ về đầu dịng, ký tự 10 chuyển con trỏ xuống dịng dưới ( trên cùng một cột ). Các ký tự nhĩm này nĩi chung khơng hiển thị ra màn hình. Nhĩm 2 : Nhĩm các ký tự văn bản cĩ mã từ 32 đến 126. Các ký tự này cĩ thể được đưa ra màn hình hoặc máy in. Nhĩm 3 : Nhĩm các ký tự đồ hoạ cĩ mã số từ 127 đến 255. Các ký tự này cĩ thể đưa ra màn hình nhưng khơng in ra được ( bằng các lệnh DOS ). 2.4.2. Kiểu số nguyên : 10
  12. Trong C cho phép sử dụng số nguyên kiểu int, số nguyên dài kiểu long và số nguyên khơng dấu kiểu unsigned. Kích cỡ và phạm vi biểu diễn của chúng được chỉ ra trong bảng dưới đây : Kiểu Phạm vi biểu diễn Kích thƣớc int -32768 đến 32767 2 byte unsigned int 0 đến 65535 2 byte long -2147483648 đến 2147483647 4 byte unsigned long 0 đến 4294967295 4 byte Chú ý: Kiểu ký tự cũng cĩ thể xem là một dạng của kiểu nguyên. 2.4.3. Kiểu dấu phảy động (số thực): Trong C cho phép sử dụng ba loại dữ liệu dấu phảy động, đĩ là float, double và long double. Kích cỡ và phạm vi biểu diễn của chúng được chỉ ra trong bảng dưới đây : Số chữ số Kích Kiểu Phạm vi biểu diễn cĩ nghĩa thƣớc float 3.4E-38 đến 3.4E+38 7 đến 8 4 byte double 1.7E-308 đến 1.7E+308 15 đến 16 8 byte long double 3.4E-4932 đến 1.1E4932 17 đến 18 10 byte Giải thích: Máy tính cĩ thể lưu trữ được các số kiểu float cĩ giá trị tuyệt đối từ 3.4E-38 đến 3.4E+38. Các số cĩ giá trị tuyệt đối nhỏ hơn3.4E-38 được xem bằng 0. Phạm vi biểu diễn của số double được hiểu theo nghĩa tương tự. Chú ý: Trong C khơng cĩ kiểu logic Boolean (thể hiện giá trị True, False). C sử dụng kiểu số nguyên để xây dựng kiểu logic, 0 ứng với False, ≠ 0 ứng với trị True. Ví dụ: biểu thức 6>8 nhận giá trị 0, 6>3 nhận giá trị 1. 2.4.4. Định nghĩa kiểu bằng typedef : Cơng dụng: Từ khố typedef dùng để đặt tên cho một kiểu dữ liệu. Tên kiểu sẽ được dùng để khai báo dữ liệu sau này. Nên chọ n tên kiểu ngắn và gọn để dễ nhớ. Chỉ cần thêm từ khố typedef vào trước một khai báo ta sẽ nhận được một tên kiểu dữ liệu và cĩ thể dùng tên này để khai báo các biến, mảng, cấu trúc, vv Cách viết: Viết từ khố typedef, sau đĩ kiểu dữ liệu ( một trong c ác kiểu trên ), rồi đến tên của kiểu. Ví dụ câu lệnh: 11
  13. typedef int nguyen; sẽ đặt tên một kiểu int là nguyen. Sau này ta cĩ thể dùng kiểu nguyen để khai báo các biến, các mảng int như ví dụ sau ; nguyen x, y; 2.4.5. Các phép tốn số học, quan hệ và logic Các phép tốn số học. Các phép tốn hai ngơi số học là Phép tốn Ý nghiã Ví dụ + Phép cộng a+b - Phép trừ a-b * Phép nhân a*b a/b / Phép chia ( Chia số nguyên sẽ chặt phần thập phân ) a%b % Phép lấy phần dư ( Cho phần dư của phép chia a cho b ) Cĩ phép tốn một ngơi - ví du -(a+b) sẽ đảo giá trị của phép cộng (a+b). Ví dụ :11/3=3 11%3=2 -(2+6)=-8 Các phép tốn + và - cĩ cùng thứ tự ưu tiên, cĩ thứ tự ưu tiên nhỏ hơn các phép * , / , % và cả ba phép này lại cĩ thứ tự ưu tiên nhỏ hơn phép trừ một ngơi. Các phép tốn số học được thực hiện từ trái sang phải. Số ưu tiên và khả năng kết hợp của phép tốn được chỉ ra trong một mục sau này Các phép tốn quan hệ và logic : Phép tốn quan hệ và logic cho ta giá trị đúng ( 1 ) hoặc giá trị sai ( 0 ). Nĩi cách khác, khi các điều kiện nêu ra là đúng thì ta nhận được giá trị 1, trái lại ta nhận giá trị 0. Các phép tốn quan hệ là : Phép tốn Ý nghĩa Ví dụ a>b > So sánh lớn hơn 4>5 cĩ giá trị 0 12
  14. a>=b >= So sánh lớn hơn hoặc bằng 6>=2 cĩ giá trị 1 a d) cĩ thể viết lại thành: a d Chú ý: Cả a và b cĩ thể là nguyên hoặc thực. Phép tốn tăng giảm : 13
  15. C đưa ra hai phép tốn một ngơi để tăng và giảm các biến ( nguyên và thực ). Tốn tử tăng là ++ sẽ cộng 1 vào tốn hạng của nĩ, tốn tử giảm thì sẽ trừ tốn hạng đi 1. Ví dụ: n=5 ++n Cho ta n=6 n Cho ta n=4 Ta cĩ thể viết phép tốn ++ và trước hoặc sau tốn hạng như sau : ++n, n++, n, n . Sự khác nhau của ++n và n++ ở chỗ: trong phép n++ thì tăng sau khi giá trị của nĩ đã được sử dụng, cịn trong phép ++n thì n được tăng trước khi sử dụng. Sự khác nhau giữa n và n cũng như vậy. Ví dụ: n=5 x=++n Cho ta x=6 và n=6 x=n++ Cho ta x=5 và n=6 Thứ tự ƣu tiên các phép tốn : Các phép tốn cĩ độ ưu tiên khác nhau, điều này cĩ ý nghĩa trong cùng một biểu thức sẽ cĩ một số phép tốn này được thực hiện trước một số phép tốn khác. Thứ tự ưu tiên của các phép tốn được trình bày trong bảng sau : TT Phép tốn Trình tự kết hợp 1 () [] -> Trái qua phải 2 ! ~ & * - ++ (type ) sizeof Phải qua trái 3 * ( phép nhân ) / % Trái qua phải 4 + - Trái qua phải 5 > Trái qua phải 6 >= Trái qua phải 7 == != Trái qua phải 8 & Trái qua phải 9 ^ Trái qua phải 10 | Trái qua phải 11 && Trái qua phải 12 || Trái qua phải 13 ?: Phải qua trái 14
  16. 14 = += -= *= /= %= >= &= ^= |= Phải qua trái 15 , Trái qua phải Chú thích: Các phép tốn tên một dịng cĩ cùng thứ tự ưu tiên, các phép tốn ở hàng trên cĩ số ưu tiên cao hơn các số ở hàng dưới. Đối với các phép tốn cùng mức ưu tiên thì trình tự tính tốn cĩ thể từ trái qua phải hay ngược lại được chỉ ra trong cột trình tự kết hợp. Ví dụ: * px=*( px) ( Phải qua trái ) 8/4*6=(8/4)*6 ( Trái qua phải ) Nên dùng các dấu ngoặc trịn để viết biểu thức một cách chính xác. Các phép tốn lạ : Dịng 1 [ ] Dùng để biểu diễn phần tử mảng, ví dụ : a[i][j] . Dùng để biểu diễn thành phần cấu trúc, ví dụ : ht.ten -> Dùng để biểu diễn thành phần cấu trúc thơng qua con trỏ Dịng 2 * Dùng để khai báo con trỏ, ví dụ : int *a & Phép tốn lấy địa chỉ, ví dụ : &x ( type) là phép chuyển đổi kiểu, ví dụ : (float)(x+y) Dịng 15 Tốn tử , thường dùng để viết một dãy biểu thức trong tốn tử for. 2.5. Các khai báo trong chƣơng trình C 2.5.1. Hằng: Hằng là các đại lượng mà giá trị của nĩ khơng thay đổi trong quá trình tính tốn. Tên hằng: Nguyên tắc đặt tên hằng ta đã xem xét trong mục đặt tên ở phần trước. Để đặt tên một hằng, ta dùng dịng lệnh sau: Để khai báo hằng ta dùng các câu khai báo sau: #define tên_hằng giá_trị_hằng hoặc: const tên_hằng = giá_trị_hằng ; Ví dụ: #define sosv 50 #define MAX 100 const sosv = 50 ; Lúc này, tất cả các tên MAX trong chương trình xuất hiện sau này đều được thay bằng 100. Vì vậy, ta thường gọi MAX là tên hằng, nĩ biểu diễn số 100. 15
  17. Một ví dụ khác : #define pi 3.141593 Đặt tên cho một hằng float là pi cĩ giá trị là 3.141593. Các loại hằng : Hằng int: Hằng int là số nguyên cĩ giá trị trong khoảng từ -32768 đến 32767. Ví dụ : #define number1 -50 Định nghiã hằng int number1 cĩ giá trị là -50 #define sodem 2732 Định nghiã hằng int sodem cĩ giá trị là 2732 Chú ý: Cần phân biệt hai hằng 5056 và 5056.0 : ở đây 5056 là số nguyên cịn 5056.0 là hằng thực. Hằng long: Hằng long là số nguyên cĩ giá trị trong khoảng từ -2147483648 đến 2147483647. Hằng long được viết theo cách : 1234L hoặc 1234l ( thêm L hoặc l vào đuơi ) Một số nguyên vượt ra ngồi miền xác định của int cũng được xem là long. Ví dụ : #define sl 8865056L Định nghiã hằng long sl cĩ giá trị là 8865056 #define sl 8865056 Định nghiã hằng long sl cĩ giá trị là 8865056 Hằng int hệ 8: Hằng int hệ 8 được viết theo cách 0c1c2c3 ở đây ci là một số nguyên dương trong khoảng từ 1 đến 7. Hằng int hệ 8 luơn luơn nhận giá trị dương. Ví dụ: #define h8 0345 Định nghiã hằng int hệ 8 cĩ giá trị là 3*8*8+4*8+5=229 Hằng int hệ 16: Trong hệ này ta sử dụng 16 ký tự : 0,1 ,9,A,B,C,D,E,F. Cách viết Giá trị a hoặc A 10 b hoặc B 11 c hoặc C 12 d hoặc D 13 e hoặc E 14 f hoặc F 15 Hằng số hệ 16 cĩ dạng 0xc1c2c3 hặc 0Xc1c2c3 ở đây ci là một số trong hệ 16. Ví dụ : #define h16 0xa5 #define h16 0xA5 #define h16 0Xa5 #define h16 0XA5 16
  18. Cho ta các hắng số h16 trong hệ 16 cĩ giá trị như nhau. Giá trị của chúng trong hệ 10 là: 10*16+5=165. Hằng ký tự: Hằng ký tự là một ký tự riêng biệt được viết trong hai dấu nháy đơn, ví dụ 'a'. Giá trị của 'a' chính là mã ASCII của chữ a. Như vậy giá trị của 'a' là 97. Hằng ký tự cĩ thể tham gia vào các phép tốn như mọi số nguyên khác. Ví dụ : '9'-'0'=57-48=9 Ví dụ : #define kt 'a' Định nghiã hằng ký tự kt cĩ giá trị là 97 Hằng ký tự cịn cĩ thể được viết theo cách sau: ' \c1c2c3' .Trong đĩ c1c2c3 là một số hệ 8 mà giá trị của nĩ bằng mã ASCII của ký tự cần biểu diễn. Ví dụ : chữ a cĩ mã hệ 10 là 97, đổi ra hệ 8 là 0141. Vậy hằng ký tự 'a' cĩ thể viết dưới dạng '\141'. Đối với một vài hằng ký tự đặc biệt ta cần sử dụng cách viết sau ( thêm dấu \ ) : Cách viết Ký tự '\'' ' '\"' " '\\' \ '\n' \n (chuyển dịng ) '\0' \0 ( null ) '\t' Tab '\b' Backspace '\r' CR ( về đầu dịng ) '\f' LF ( sang trang ) Chú ý: Cần phân biệt hằng ký tự '0' và '\0'. Hằng '0' ứng với chữ số 0 cĩ mã ASCII là 48, cịn hằng '\0' ứng với kýtự \0 ( thường gọi là ký tự null ) cĩ mã ASCII là 0. Hằng ký tự thực sự là một số nguyên, vì vậy cĩ thể dùng các số nguyên hệ 10 để biểu diễn các ký tự, ví dụ lệnh printf("%c%c",65,66) sẽ in ra AB. Hằng xâu ký tự: Hằng xâu ký tự là một dãy ký tự bất kỳ đặt trong hai dấu nháy kép. Ví dụ : #define xau1 "Ha noi" #define xau2 "My name is Giang" 17
  19. Xâu ký tự được lưu trữ trong máy dưới dạng một bảng cĩ các phần tử là các ký tự riêng biệt. Trình biên dịch tự động thêm ký tự null \0 vào cuối mỗi xâu ( ký tự \0 được xem là dấu hiệu kết thúc của một xâu ký tự ). Chú ý: Cần phân biệt hai hằng 'a' và "a". 'a' là hằng ký tự được lưu trữ trong 1 byte, cịn "a" là hằng xâu ký tự được lưu trữ trong 1 mảng hai phần tử : phần tử thứ nhất chứa chữ a cịn phần tử thứ hai chứa \0. 2.5.2. Biến. Là đại lượng mà giá trị cĩ thể thay đổi được trong chương trình. Mỗi biến cần phải được khai báo trước khi đưa vào sử dụng, giá trị của biến cĩ thể thay đổi được trong chương trình. Việc khai báo biến được thực hiện theo mẫu sau: Kiểu_dữ_liệu_của_biến tên biến ; Ví dụ : int a,b,c; Khai báo ba biến int là a,b,c long dai,mn; Khai báo hai biến long là dai và mn char kt1,kt2; Khai báo hai biến ký tự là kt1 và kt2 float x,y Khai báo hai biến float là x và y double canh1, canh2; Khai báo hai biến double là canh1 và canh2 Biến kiểu int chỉ nhận được các giá trị kiểu int. Các biến khác cũng cĩ ý nghĩa tương tự. Các biến kiểu char chỉ chứa được một ký tự. Để lưu trữ được một xâu ký tự cần sử dụng một mảng kiểu char. Vị trí của khai báo biến: Các khai báo cần phải được đặt ngay sau dấu { đầu tiên của thân hàm và cần đứng trước mọi câu lệnh khác. Sau đây là một ví dụ về khai báo biến sai : ( Khái niệm về hàm và cấu trúc chương trình sẽ nghiên cứu sau này) main() { int a,b,c; a=2; int d; /* Vị trí của khai báo sai */ } Khởi đầu cho biến: Nếu trong khai báo ngay sau tên biến ta đặt dấu = và một giá trị nào đĩ thì đây chính là cách vừa khai báo vừa khởi đầu cho biến. Ví dụ : int a, b=20, c, d=40; 18
  20. float e=-55.2, x=27.23, y, z, t=18.98; Việc khởi đầu và việc khai báo biến rồi gán giá trị cho nĩ sau này là hồn tồn tương đương. Lấy địa chỉ của biến: Mỗi biến được cấp phát một vùng nhớ gồm một số byte liên tiếp. Số hiệu của byte đầu chính là địa chỉ của biến. Địa chỉ của biến sẽ được sử dụng trong một số hàm ta sẽ nghiên cứu sau này ( ví dụ như hàm scanf ). Để lấy địa chỉ của một biến ta sử dụng phép tốn: &tên_biến 2.5.3. Chuyển đổi kiểu giá trị: Việc chuyển đổi kiểu giá trị thường diễn ra một cách tự động trong hai trường hợp sau : + Khi gán biểu thức gồm các tốn hạng khác kiểu. + Khi gán một giá trị kiểu này cho một biến ( hoặc phần tử mảng ) kiểu khác. Điều này xảy ra trong tốn tử gán, trong việc truyền giá trị các tham số thực sự cho các đối. Ngồi ra, ta cĩ thể chuyển từ một kiểu giá trị sang một kiểu bất kỳ mà ta muốn bằng phép chuyển sau: ( type ) biểu thức Ví dụ : (float) (a+b) Chuyển đổi kiểu trong biểu thức : Khi hai tốn hạng trong một phép tốn cĩ kiểu khác nhau thì kiểu thấp hơn sẽ được nâng thành kiểu cao hơn trước khi thực hiện phép tốn. Kết quả thu được là một giá trị kiểu cao hơn. Chẳng hạn : Giữa int và long thì int chuyển thành long. Giữa int và float thì int chuyển thành float. Giữa float và double thì float chuyển thành double. Ví dụ: 1.5*(11/3)=4.5 1.5*11/3=5.5 (11/3)*1.5=4.5 Chuyển đổi kiểu thơng qua phép gán : Giá trị của vế phải được chuyển sang kiểu vế trái đĩ là kiểu của kết quả. Kiểu int cĩ thể được được chuyển thành float. Kiểu float cĩ thể chuyển thành int do chặt đi phần thập phân. Kiểu double chuyển thành float bằng cách làm trịn. Kiểu long được chuyển thành int bằng cách cắt bỏ một vài chữ số. Ví dụ :int n; n=15.6 giá trị của n là 15 19
  21. Đổi kiểu dạng (type)biểu thức : Theo cách này, kiểu của biểu thức được đổi thành kiểu type theo nguyên tắc trên. Ví dụ : Phép tốn : (int)a cho một giá trị kiểu int. Nếu a là float thì ở đây cĩ sự chuyển đổi từ float sang int. Chú ý rằng bản thân kiểu của a vẫn khơng bị thay đổi. Nĩi cách khác, a vẫn cĩ kiểu float nhưng (int)a cĩ kiểu int. Đối với hàm tốn học của thư viện chuẩn, thì giá trị của đối và giá trị của hàm đều cĩ kiểu double, vì vậy để tính căn bậc hai của một biến nguyên n ta phải dùng phép ép kiểu để chuyển kiểu int sang double như sau : sqrt((double)n) Phép ép kiểu cĩ cùng số ưu tiên như các tốn tử một ngơi. Chú ý:Muốn cĩ giá trị chính xác trong phép chia hai số nguyên cần dùng phép ép kiểu : (float)a/b Để đổi giá trị thực r sang nguyên, ta dùng: (int)(r+0.5) Chú ý thứ tự ưu tiên: (int)1.4*10=1*10=10 (int)(1.4*10)=(int)14.0=14 2.6. Biểu thức Biểu thức là dãy kí hiệu kết hợp giữa các tốn hạng, phép tốn và cặp dấu () theo một qui tắc nhất định. Các tốn hạng là hằng, biến, hàm. Biểu thức cung cấp một cách thức để tính giá trị mới dựa trên các tốn hạng và tốn tử trong biểu thức. Như vậy hằng, biến, phần tử mảng và hàm cũng được xem là biểu thức. Ví dụ: (x + y) * 2 - 4 ; 3 - x + sqrt(y) ; (-b + sqrt(delta)) / (2*a) ; Trong C, ta cĩ hai khái niệm về biểu thức : Biểu thức gán. Biểu thức điều kiện. Biểu thức được phân loại theo kiểu giá trị: nguyên và thực. Trong các mệnh đề logic, biểu thức được phân thành đúng ( giá trị khác 0 ) và sai ( giá trị bằng 0, chúng ta thường quy ước là 1 ). Biểu thức thường được dùng trong: + Vế phải của câu lệnh gán. + Làm tham số thực sự của hàm. 20
  22. + Làm chỉ số. + Trong các tốn tử của các cấu trúc điều khiển. Tới đây, ta đã cĩ hai khái niệm chính tạo nên biểu thức đĩ là tốn hạng và phép tốn. Tốn hạng gồm: hằng, biến, phần tử mảng và hàm trước đây ta đã xét. Dưới đây ta sẽ nĩi đến các phép tốn. Hàm sẽ được đề cập trong chương sau. 2.7. Lệnh gán giá trị, lệnh gộp Lệnh gán giá trị: Biểu thức gán (lệnh gán) là biểu thức cĩ dạng: v = e Trong đĩ v là một biến ( hay phần tử mảng ), e là một biểu thức. Giá trị của biểu thức gán là giá trị của e, kiểu của nĩ là kiểu của v. Nếu đặt dấu ; vào sau biểu thức gán ta sẽ thu được phép tốn gán cĩ dạng: v = e; Biểu thức gán cĩ thể sử dụng trong các phép tốn và các câu lệnh như các biểu thức khác. Ví dụ như khi ta viết a=b=5; thì điều đĩ cĩ nghĩa là gán giá trị của biểu thức b=5 cho biến a. Kết qủa là b=5 và a=5. Hồn tồn tương tự như: a=b=c=d=6; gán 6 cho cả a, b, c và d Ví dụ: z=(y=2)*(x=6); { ở đây * là phép tốn nhân } gán 2 cho y, 6 cho x và nhân hai biểu thức lại cho ta z=12. Lệnh gộp (khối lệnh): Một câu lệnh trong C được thiết lập từ các từ khố và các biểu thức và luơn luơn được kết thúc bằng dấu chấm phẩy. Các ví dụ vào/ra hoặc các phép gán tạo thành những câu lệnh đơn giản như: x = 3 + x ; y = (x = sqrt(x)) + 1 ; printf(“x = %4d, y=%4.2f”, x, y ); Các câu lệnh được phép viết trên cùng một hoặc nhiều dịng. Một số câu lệnh được gọi là lệnh cĩ cấu trúc, tức bên trong nĩ lại chứa dãy lệnh khác. Dãy lệnh này phải được bao giữa cặp dấu ngoặc {} và được gọi là khối lệnh. Ví dụ tất cả các lệnh trong một hàm (như hàm main()) luơn luơn là một khối lệnh. Một đặc điểm của khối lệnh là các biến được khai báo trong khối lệnh nào thì chỉ cĩ tác dụng trong khối lệnh đĩ. Một dãy các câu lệnh được bao bởi các dấu { } gọi là một khối lệnh. Ví dụ : { a=2; b=3; printf("\n%6d%6d",a,b); } 21
  23. TURBO C xem khối lệnh cũng như một câu lệnh riêng lẻ. Nĩi cách khác, chỗ nào viết được một câu lệnh thì ở đĩ cũng cĩ quyền đặt một khối lệnh. Khai báo ở đầu khối lệnh : Các khai báo biến và mảng chẳng những cĩ thể đặt ở đầu của một hàm mà cịn cĩ thể viết ở đầu khối lệnh : { int a, b; float x, y, z; a=b=3; x=5.5; y=a*x; z=b*x; printf("\n y= %8.2f\n z=%8.2f",y,z); } Sự lồng nhau của các khối lệnh và phạm vi hoạt động của các biến và mảng : Bên trong một khối lệnh lại cĩ thể viết lồng khối lệnh khác. Sự lồng nhau theo cách như vậy là khơng hạn chế. Khi máy bắt đầu làm việc với một khối lệnh thì các biến và mảng khai báo bên trong nĩ mới được hình thành và được hình thành và được cấp phát bộ nhớ. Các biến này chỉ tồn tại trong thời gian máy làm việc bên trong khối lệnh và chúng lập tức biến mất ngay sau khi máy ra khỏi khối lệnh. Vậy: Giá trị của một biến hay một mảng khai báo bên trong một khối lệnh khơng thể đưa ra sử dụng ở bất kỳ chỗ nào bên ngồi khối lệnh đĩ. Ở bất kỳ chỗ nào bên ngồi một khối lệnh ta khơng thể can thiệp đến các biến và các mảng được khai báo bên trong khối lệnh. Nếu bên trong một khối ta dùng một biến hay một mảng cĩ tên là a thì điều này khơng làm thay đổi giá trị của một biến khác cũng cĩ tên là a ( nếu cĩ ) được dùng ở đâu đĩ bên ngồi khối lệnh này. Nếu cĩ một biến đã được khai báo ở ngồi một khối lệnh và khơng trùng tên với các biến khai báo bên trong khối lệnh này thì biến đĩ cũng cĩ thể sử dụng cả bên trong cũng như bên ngồi khối lệnh. Ví dụ : Xét đoạn chương trình sau : { int a=5,b=2; { 22
  24. int a=4; b=a+b; printf("\n a trong =%3d b=%3d",a,b); } printf("\n a ngoai =%3d b=%3d",a,b); } Khi đĩ đoạn chương trình sẽ in kết quả như sau : a trong =4 b=6 a ngồi =5 b=6 Do tính chất biến a trong và ngồi khối lệnh. 2.8. Các hàm tốn học 2.8.1. Các hàm số học • abs(x), labs(x), fabs(x) : trả lại giá trị tuyệt đối của một số nguyên, số nguyên dài và số thực. y • pow(x, y) : hàm mũ, trả lại giá trị x lũy thừa y (x ). x • exp(x) : hàm mũ, trả lại giá trị e mũ x (e ). • log(x), log10(x) : trả lại lơgarit cơ số e và lơgarit thập phân của x (lnx, logx) . • sqrt(x) : trả lại căn bậc 2 của x. • atof(s_number) : trả lại số thực ứng với số viết dưới dạng xâu kí tự s_number. 2.8.2. Các hàm lƣợng giác • sin(x), cos(x), tan(x) : trả lại các giá trị sinx, cosx, tgx. 23
  25. CHƢƠNG 3. CÁC CÂU LỆNH CƠ BẢN 3.1. Hàm viết dữ liệu ra màn hình 3.1.1. Hàm putchar (): Để đưa một ký tự ra thiết bị ra chuẩn, nĩi chung là màn hình, ta sử dụng hàm putchar() Cách dùng: Dùng câu lệnh sau: putchar(ch); Cơng dụng: Đưa ký tự ch lên màn hình tại vị trí hiện tại của con trỏ. Ký tự sẽ được hiển thị với màu trắng. Ví dụ: int c; c = getchar(); putchar(c); 3.1.2. Hàm putch(): Cách dùng: Dùng câu lệnh sau: putch(ch); Cơng dụng: Đưa ký tự ch lên màn hình tại vị trí hiện tại của con trỏ. Ký tự sẽ được hiển thị theo màu xác định trong hàm textcolor. Hàm cũng trả về ký tự được hiển thị. 3.1.3. Đƣa kết quả lên màn hình - hàm printf : Cách dùng: prinf(điều khiển, đối số 1, đối số 2, ); Hàm printf chuyển, tạo khuơn dạng và in các đối của nĩ ra thiết bị ra chuẩn dưới sự điều khiển của xâu điều khiển. Xâu điều khiển chứa hai kiểu đối tượng : các ký tự thơng thường, chúng sẽ được đưa ra trực tiếp thiết bị ra, và các đặc tả chuyển dạng, mỗi đặc tả sẽ tạo ra việc đổi dạng và in đối tiếp sau của printf. Chuỗi điều khiển cĩ thể cĩ các ký tự điều khiển: \n sang dịng mới; \f sang trang mới; \b lùi lại một bước; \t dấu tab Dạng tổng quát của đặc tả : %[-][n][.m] ký_tự_chuyển_dạng Mỗi đặc tả chuyển dạng đều được đưa vào bằng ký tự % và kết thúc bởi một ký_tự_chuyển_dạng. Giữa % và ký_tự_chuyển_dạng cĩ thể cĩ: Dấu trừ: Khi khơng cĩ dấu trừ thì kết quả ra được dồn về bên phải nếu độ dài thực tế của kết quả ra nhỏ hơn độ rộng tối thiểu n dành cho nĩ. Các vị trí dư thừa sẽ được lấp đầy bằng các khoảng trống. Riêng đối với các trường số, nếu dãy số n bắt đầu bằng số 0 thì các vị trí dư thừa bên trái sẽ được lấp đầy bằng các số 0. Khi cĩ dấu trừ thì kết quả được dồn về bên trái và các vị trí dư thừa về bên phải (nếu cĩ) luơn được lấp đầy bằng các khoảng trống. 24
  26. n : Khi n lớn hơn độ dài thực tế của kết quả ra thì các vị trí dư thừa sẽ được lấp đầy bởi các khoảng trống hoặc số 0 và nội dung của kết quả ra sẽ được đẩy về bên phải hoặc bên trái. Khi khơng cĩ n hoặc n nhỏ hơn hay bằng độ dài thực tế của kết quả ra thì độ rộng trên thiết bị ra dành cho kết quả sẽ bằng chính độ dài của nĩ. Tại vị trí của n ta cĩ thể đặt dấu *, khi đĩ n được xác định bởi giá trị nguyên của đối tương ứng. Ví dụ : Kết quả ra n Dấu - Kết quả đƣa ra -2503 8 cĩ -2503 -2503 08 cĩ -2503 -2503 8 khơng -2503 -2503 08 khơng 000-2503 "abcdef" 8 khơng abcdef "abcdef" 08 cĩ abcdef "abcdef" 08 khơng abcdef m: Tham số m chỉ được sử dụng khi đối tương ứng là một xâu ký tự hoặc một giá trị kiểu float hay double. Trong trường hợp đối tương ứng cĩ giá trị kiểu float hay double thì m là độ chính xác của trường ra. Nĩi một cách cụ thể hơn giá trị in ra sẽ cĩ pp chữ số sau số thập phân. Khi vắng mặt pp thì độ chính xác sẽ được xem là 6. Khi đối là xâu ký tự: Nếu m nhỏ hơn độ dài của xâu thì chỉ pp ký tự đầu tiên của xâu được in ra. Nếu khơng cĩ n hoặc nếu m lớn hơn hay bằng độ dài của xâu thì cả xâu ký tự sẽ được in ra. Ví dụ : Kết quả ra n m Dấu - Kết quả đƣa ra Độ dài trƣờng ra -435.645 10 2 cĩ -435.65 7 -435.645 10 0 cĩ -436 4 -435.645 8 vắng cĩ -435.645000 11 "alphabeta" 8 3 vắng alp 3 "alphabeta" vắng vắng vắng alphabeta 9 "alpha" 8 6 cĩ alpha 5 Các ký tự chuyển dạng và ý nghĩa của nĩ : 25
  27. Ký tự chuyển dạng là một hoặc một dãy ký hiệu xác định quy tắc chuyển dạng và dạng in ra của đối tương ứng. Như vậy sẽ cĩ tình trạng cùng một số sẽ được in ra theo các dạng khác nhau. Cần phải sử dụng các ký tự chuyển dạng theo đúng qui tắc định sẵn. Bảng sau cho các thơng tin về các ký tự chuyển dạng. Ký tự Ý nghĩa chuyển dạng d Đối được chuyển sang số nguyên hệ thập phân o Đối được chuyển sang hệ tám khơng dấu ( khơng cĩ số 0 đứng trước ) Đối được chuyển sang hệ mưới sáu khơng dấu ( khơng cĩ 0x đứng x trước ) u Đối được chuyển sang hệ thập phân khơng dấu c Đối được coi là một ký tự riêng biệt Đối là xâu ký tự, các ký tự trong xâu được in cho tới khi gặp ký tự s khơng hoặc cho tới khi đủ số lượng ký tự được xác định bởi các đặc tả về độ chính xác m. Đối được xem là float hoặc double và được chuyển sang dạng thập e phân cĩ dạng [-]m.n nE[+ hoặc -] với độ dài của xâu chứa n là pp. Đối được xem là float hoặc double và được chuyển sang dạng thập phân cĩ dạng [-]m m.n n với độ dài của xâu chứa n là pp. Độ chính f xác mặc định là 6. Lưu ý rằng độ chính xác khơng xác định ra số các chữ số cĩ nghĩa phải in theo khuơn dạng f. Dùng %e hoặc %f, tuỳ theo loại nào ngắn hơn, khơng in các số 0 vơ g nghĩa. Chú ý: Mọi dãy ký tự khơng bắt đầu bằng % hoặc khơng kết thúc bằng ký tự chuyển dạng đều được xem là ký tự hiển thị. Để hiển thị các ký tự đặc biệt : Cách viết Hiển thị \' ' \" " \\ \ Các ví dụ : 26
  28. 1 printf("\" Nang suat tang : %d % \" \n\\d"",30,-50); "Nang suat tang ; 30 %" \d=-50 2 n=8; 25.500000 float x=25.5, y=-47.335 -47.34 printf("\n%f\n%*.2f",x,n,y); Lệnh này tương đương với printf("\n%f\n%8.2f",x,n,y); Vì n=8 tương ứng với vị trí * 3.2. Hàm nhập dữ liệu vào từ bàn phím 3.2.1. Hàm getchar (): Cơ chế vào đơn giản nhất là đọc từng ký tự từ thiết bị vào chuẩn, nĩi chung là bàn phím và màn hình của người sử dụng, bằng hàm getchar(). Cách dùng: Dùng câu lệnh sau: biến = getchar(); Cơng dụng: Nhận một ký tự vào từ bàn phím và khơng đưa ra màn hình. Hàm sẽ trả về ký tự nhận được và lưu vào biến. Ví dụ: int c; c = getchar(); 3.2.2. Hàm getch(): Hàm nhận một ký tự từ bộ đệm bàn phím, khơng cho hiện lên màn hình. Cách dùng: Dùng câu lệnh sau: getch(); Cơng dụng : Nếu cĩ sẵn ký tự trong bộ đệm bàn phím thì hàm sẽ nhận một ký tự trong đĩ. Nếu bộ đệm rỗng, máy sẽ tạm dừng. Khi gõ một ký tự thì hàm nhận ngay ký tự đĩ ( khơng cần bấm thêm phím Enter như trong các hàm nhập khác ). Ký tự vừa gõ khơng hiện lên màn hình. Nếu dùng: biến=getch(); Thì biến sẽ chứa ký tự đọc vào. Ví dụ: c = getch(); 3.2.3. Vào số liệu từ bàn phím - hàm scanf : Hàm scanf là hàm đọc thơng tin từ thiết bị vào chuẩn ( bàn phím ), chuyển dịch chúng ( thành số nguyên, số thực, ký tự vv ) rồi lưu trữ nĩ vào bộ nhớ theo các địa chỉ xác định. Cách dùng: scanf(điều khiển,đối 1, đối 2, ); Xâu điều khiển chứa các đặc tả chuyển dạng, mỗi đặc tả sẽ tạo ra việc đổi dạng biến tiếp sau của scanf. 27
  29. Đặc tả cĩ thể viết một cách tổng quát nhƣ sau : %[*][d d]ký tự chuyển dạng Việc cĩ mặt của dấu * nĩi lên rằng trường vào vẫn được dị đọc bình thường, nhưng giá trị của nĩ bị bỏ qua ( khơng được lưu vào bộ nhớ ). Như vậy đặc tả chứa dấu * sẽ khơng cĩ đối tương ứng. d d: là một dãy số xác định chiều dài cực đại của trường vào, ý nghĩa của nĩ được giải thích như sau: Nếu tham số d d vắng mặt hoặc nếu giá trị của nĩ lớn hơn hay bằng độ dài của trường vào tương ứng thì tồn bộ trường vào sẽ được đọc, nội dung của nĩ được dịch và được gán cho địa chỉ tương ứng ( nếu khơng cĩ dấu * ). Nếu giá trị của d d nhỏ hơn độ dài của trường vào thì chỉ phần đầu của trường cĩ kích cỡ bằng d d được đọc và gán cho địa chỉ của biến tương ứng. Phần cịn lại của trường sẽ được xem xét bởi các đặc tả và đối tương ứng tiếp theo. Ví dụ : int a; float x, y; char ch[6],ct[6] ;//khai báo xâu ký tự scanf("%f%5f%3d%3s%s",&x&y&a&ch&ct0; Với dịng vào : 54.32e-1 25 12452348a Kết quả là lệnh scanf sẽ gán 5.432 cho x 25.0 cho y 124 cho a xâu "523" và dấu kết thúc \0 cho ch xâu "48a" và dấu kết thúc \0 cho ct Ký tự chuyển dạng: Ký tự chuyển dạng xác định cách thức dị đọc các ký tự trên dịng vào cũng như cách chuyển dịch thơng tin đọc đựợc trước khi gán nĩ cho các địa chỉ tương ứng. Cách dị đọc thứ nhất là đọc theo trường vào, khi đĩ các khoảng trắng bị bỏ qua. Cách này áp dụng cho hầu hết các trường hợp. Cách dị đọc thứ hai là đọc theo ký tự, khi đĩ các khoảng trắng cũng được xem xét bình đẳng như các ký tự khác. Phương pháp này chỉ xảy ra khi ta sử dụng một trong ba ký tự chuyển dạng sau : C, [ dãy ký tự ], [^ dãy ký tự ] 28
  30. Các ký tự chuyển dạng và ý nghĩa của nĩ : c Vào một ký tự, đối tương ứng là con trỏ ký tự. Cĩ xét ký tự khoảng trắng d Vào một giá trị kiểu int, đối tương ứng là con trỏ kiểu int. Trường phải vào là số nguyên ld Vào một giá trị kiểu long, đối tương ứng là con trỏ kiểu long. Trường phải vào là số nguyên o Vào một giá trị kiểu int hệ 8, đối tương ứng là con trỏ kiểu int. Trường phải vào là số nguyên hệ 8 lo Vào một giá trị kiểu long hệ 8, đối tương ứng là con trỏ kiểu long. Trường phải vào là số nguyên hệ 8 x Vào một giá trị kiểu int hệ 16, đối tương ứng là con trỏ kiểu int. Trường phải vào là số nguyên hệ 16 lx Vào một giá trị kiểu long hệ 16, đối tương ứng là con trỏ kiểu long. Trường phải vào là số nguyên hệ 16 f hay e Vào một giá trị kiểu float, đối tương ứng là con trỏ float, trường vào phải là số dấu phảy động lf hay le Vào một giá trị kiểu double, đối tương ứng là con trỏ double, trường vào phải là số dấu phảy động s Vào một giá trị kiểu double, đối tương ứng là con trỏ kiểu char, trường vào phải là dãy ký tự bất kỳ khơng chứa các dấu cách và các dấu xuống dịng [ Dãy ký tự ], [ ^Dãy ký tự ] Các ký tự trên dịng vào sẽ lần lượt được đọc cho đến khi nào gặp một ký tự khơng thuộc tập các ký tự đặt trong[]. Đối tương ứng là con trỏ kiểu char. Trường vào là dãy ký tự bất kỳ ( khoảng trắng được xem như một ký tự ). Ví dụ : int a,b; char ch[10], ck[10]; scanf("%d%[0123456789]%[^0123456789]%3d",&a,ch,ck,&b); Với dịng vào: 35 13145 xyz 584235 Sẽ gán: 35 cho a xâu "13145" cho ch xâu "xyz' cho ck 29
  31. 584 cho b Chú ý: Xét đoạn chương trình dùng để nhập ( từ bàn phím ) ba giá trị nguyên rồi gán cho ba biến a,b,c như sau: int a,b,c; scanf("%d%d%d”,&a,&b,&c); Để vào số liệu ta cĩ thể thao tác theo nhiều cách khác nhau: Cách 1: Đưa ba số vào cùng một dịng, các số phân cách nhau bằng dấu cách hoặc dấu tab. Cách 2: Đưa ba số vào ba dịng khác nhau. Cách 3: Hai số đầu cùng một dịng ( cách nahu bởi dấu cách hoặ tab ), số thứ ba trên dịng tiếp theo. Cách 4: Số thứ nhất trên một dịng, hai số sau cùng một dịng tiếp theo ( cách nhau bởi dấu cách hoặc tab), số thứ ba trên dịng tiếp theo. Khi vào sai sẽ báo lỗi và nhảy về chương trình chứa lời gọi nĩ. Ví dụ minh họa sử dụng hàm printf, scanf nhập vào hai số a, b kiểu nguyên, tính tốn và đưa kết quả biểu thức ab, a lên màn hình. #include #include #include void main() { int a, b; float kq; printf("\nNhap so thu nhat a= "); scanf("%d",&a); printf("\nNhap so thu hai b= "); scanf("%d",&b); kq=sqrt(a); printf("\nKet qua %d ^ %4d = %6.2f",a,b, pow(a,b)); printf("\nKet qua can bac 2 cua %d = %4.2f",a, kq); getch(); } 3.3. Câu lệnh điều kiện 30
  32. 3.3.1. Lệnh if-else: Tốn tử if cho phép lựa chọn chạy theo một trong hai nhánh tuỳ thuộc vào sự bằng khơng và khác khơng của biểu thức. Nĩ cĩ hai cách viết sau : if ( biểu thức ) if ( biểu thức ) khối lệnh 1; khối lệnh 1; else /* Dạng một */ khối lệnh 2 ; /* Dạng hai */ Hoạt động của biểu thức dạng 1 : Máy tính giá trị của biểu thức. Nếu biểu thức đúng ( biểu thức cĩ giá trị khác 0 ) máy sẽ thực hiện khối lệnh 1 và sau đĩ sẽ thực hiện các lệnh tiếp sau lệnh if trong chương trình. Nếu biểu thức sai ( biểu thức cĩ giá trị bằng 0 ) thì máy bỏ qua khối lệnh 1 mà thực hiện ngay các lệnh tiếp sau lệnh if trong chương trình. Hoạt động của biểu thức dạng 2 : Máy tính giá trị của biểu thức. Nếu biểu thức đúng ( biểu thức cĩ giá trị khác 0 ) máy sẽ thực hiện khối lệnh 1 và sau đĩ sẽ thực hiện các lệnh tiếp sau khối lệnh 2 trong chương trình. Nếu biểu thức sai ( biểu thức cĩ giá trị bằng 0 ) thì máy bỏ qua khối lệnh 1 mà thực hiện khối lệnh 2 sau đĩ thực hiện tiếp các lệnh tiếp sau khối lệnh 2 trong chương trình. Ví dụ : Chương trình nhập vào hai số a và b, tìm max của hai số rồi in kết quả lên màn hình. Chương trình cĩ thể viết bằng cả hai cách trên như sau : #include "stdio.h" main() { float a,b,max; printf("\n Cho a="); scanf("%f",&a); printf("\n Cho b="); scanf("%f",&b); max=a; if (b>max) max=b; 31
  33. printf(" \n Max cua hai so a=%8.2f va b=%8.2f la Max=%8.2f",a,b,max); } #include "stdio.h" main() { float a,b,max; printf("\n Cho a="); scanf("%f",&a); printf("\n Cho b="); scanf("%f",&b); if (a>b) max=a; else max=b; printf(" \n Max cua hai so a=%8.2f va b=%8.2f la Max=%8.2f",a,b,max); } Sự lồng nhau của các tốn tử if : C cho phép sử dụng các tốn tử if lồng nhau cĩ nghĩa là trong các khối lệnh ( 1 và 2 ) ở trên cĩ thể chứa các tốn tử if - else khác. Trong trường hợp này, nếu khơng sử dụng các dấu đĩng mở ngoặc cho các khối thì sẽ cĩ thể nhầm lẫn giữa các if-else. Chú ý là máy sẽ gắn tốn tử else với tốn tử if khơng cĩ else gần nhất. Chẳng hạn như đoạn chương trình ví dụ sau : if ( n>0 ) /* if thứ nhất*/ if ( a>b ) /* if thứ hai*/ z=a; else z=b; thì else ở đây sẽ đi với if thứ hai. Đoạn chương trình trên tương đương với : if ( n>0 ) /* if thứ nhất*/ { if ( a>b ) /* if thứ hai*/ 32
  34. z=a; else z=b; } Trường hợp ta muốn else đi với if thứ nhất ta viết như sau : if ( n>0 ) /* if thứ nhất*/ { if ( a>b ) /* if thứ hai*/ z=a; } else z=b; 3.3.2. Lệnh else-if : Khi muốn thực hiện một trong n quyết định ta cĩ thể sử dụng cấu trúc sau : if ( biểu thức 1 ) khối lệnh 1; else if ( biểu thức 2 ) khối lệnh 2; else if ( biểu thức n-1 ) khối lệnh n-1; else khối lệnh n; Trong cấu trúc này, máy sẽ đi kiểm tra từ biểu thức 1 trở đi đến khi gặp biểu thức nào cĩ giá trị khác 0. Nếu biểu thức thứ i (1,2, n-1) cĩ giá trị khác 0, máy sẽ thực hiện khối lệnh i, rồi sau đĩ đi thực hiện lệnh nằm tiếp theo khối lệnh n trong chương trình. Nếu trong cả n-1 biểu thức khơng cĩ biểu thức nào khác 0, thì máy sẽ thực hiện khối lệnh n rồi sau đĩ đi thực hiện lệnh nằm tiếp theo khối lệnh n trong chương trình. Ví dụ : Chương trình giải phương trình bậc hai. #include "stdio.h" 33
  35. main() { float a,b,c,d,x1,x2; printf("\n Nhap a, b, c:"); scanf("%f%f%f”,&a&b&c); d=b*b-4*a*c; if (d<0.0) printf("\n Phuong trinh vo nghiem "); else if (d= =0.0) printf("\n Phuong trinh co nghiem kep x1,2=%8.2f",-b/(2*a)); else { printf("\n Phuong trinh co hai nghiem "); printf("\n x1=%8.2f",(-b+sqrt(d))/(2*a)); printf("\n x2=%8.2f",(-b-sqrt(d))/(2*a)); } 3.4. Câu lệnh lựa chọn-lệnh switch Là cấu trúc tạo nhiều nhánh đặc biệt. Nĩ căn cứ vào giá trị một biểu thức nguyên để để chọn một trong nhiều cách nhảy. Cấu trúc tổng quát của nĩ là : switch ( biểu thức nguyên ) { case n1: khối lệnh 1 case n2: khối lệnh 2 case nk: khối lệnh k [ default: khối lệnh k+1 ] } Với ni là các số nguyên, hằng ký tự hoặc biểu thức hằng. Các ni cần cĩ giá trị khác nhau. Đoạn chương trình nằm giữa các dấu { } gọi là thân của tốn tử switch. default là một thành phần khơng bắt buộc phải cĩ trong thân của switch. 34
  36. Sự hoạt động của tốn tử switch phụ thuộc vào giá trị của biểu thức viết trong dấu ngoặc ( ) như sau : Khi giá trị của biểu thức này bằng ni, máy sẽ nhảy tới các câu lệnh cĩ nhãn là case ni. Khi giá trị biểu thức khác tất cả các ni thì cách làm việc của máy lại phụ thuộc vào sự cĩ mặt hay khơng của lệnh default như sau : Khi cĩ default máy sẽ nhảy tới câu lệnh sau nhãn default. Khi khơng cĩ default máy sẽ nhảy ra khỏi cấu trúc switch. Chú ý : Máy sẽ nhảy ra khỏi tốn tử switch khi nĩ gặp câu lệnh break hoặc dấu ngoặc nhọn đĩng cuối cùng của thân switch. Ta cũng cĩ thể dùng câu lệnh goto trong thân của tốn tử switch để nhảy tới một câu lệnh bất kỳ bên ngồi switch. Khi tốn tử switch nằm trong thân một hàm nào đĩ thì ta cĩ thể sử dụng câu lệnh return trong thân của switch để ra khỏi hàm này ( lệnh return sẽ đề cập sau ). Khi máy nhảy tới một câu lệnh nào đĩ thì sự hoạt động tiếp theo của nĩ sẽ phụ thuộc vào các câu lệnh đứng sau câu lệnh này. Như vậy nếu máy nhảy tới câu lệnh cĩ nhãn case ni thì nĩ cĩ thể thực hiện tất cả các câu lệnh sau đĩ cho tới khi nào gặp câu lệnh break, goto hoặc return. Nĩi cách khác, máy cĩ thể đi từ nhĩm lệnh thuộc case ni sang nhĩm lệnh thuộc case thứ ni+1. Nếu mỗi nhĩm lệnh được kết thúc bằng break thì tốn tử switch sẽ thực hiện chỉ một trong các nhĩm lệnh này. Ví dụ: Lập chương trình phân loại học sinh theo điểm sử dụng cấu trúc switch : #include "stdio.h" main() { int diem; tt: printf("\nVao du lieu :"); printf("\n Diem ="); scanf("%d",&diem); switch (diem) { case 0: case 1: 35
  37. case 2: case 3:printf("Kem\n");break; case 4:printf("Yeu\n");break; case 5: case 6:printf("Trung binh\n");break; case 7: case 8:printf("Kha\n");break; case 9: case 10:printf("Gioi\n");break; default:printf(Vao sai\n); } printf("Tiep tuc 1, dung 0 :") scanf("%d",&diem); if (diem= =1) goto tt; getch(); return; } 3.5. Câu lệnh lặp for Tốn tử for dùng để xây dựng cấu trúc lặp cĩ dạng sau : for ( biểu thức 1; biểu thức 2; biểu thức 3) Lệnh hoặc khối lệnh ; Tốn tử for gồm ba biểu thức và thân for. Thân for là một câu lệnh hoặc một khối lệnh viết sau từ khố for. Bất kỳ biểu thức nào trong ba biểu thức trên cĩ thể vắng mặt nhưng phải giữ dấu ;. Thơng thường biểu thức 1 là tốn tử gán để tạo giá trị ban đầu cho biến điều khiển, biểu thức 2 là một quan hệ logic biểu thị điều kiện để tiếp tục chu trình, biểu thức ba là một tốn tử gán dùng để thay đổi giá trị biến điều khiển. Hoạt động của tốn tử for : Tốn tử for hoạt động theo các bước sau : B1: Xác định biểu thức 1 B2: Xác định biểu thức 2 36
  38. Tuỳ thuộc vào tính đúng sai của biểu thức 2 để máy lựa chọn một trong hai nhánh: Nếu biểu thức hai cĩ giá trị 0 ( sai ), máy sẽ ra khỏi for và chuyển tới câu lệnh sau thân for. Nếu biểu thức hai cĩ giá trị khác 0 ( đúng ), máy sẽ thực hiện các câu lệnh trong thân for. Tính biểu thức 3, sau đĩ quay lại bước 2 để bắt đầu một vịng mới của chu trình. Chú ý : Nếu biểu thức 2 vắng mặt thì nĩ luơn được xem là đúng. Trong trường hợp này việc ra khỏi chu trình for cần phải được thực hiện nhờ các lệnh break, goto hoặc return viết trong thân chu trình. Trong dấu ngoặc trịn sau từ khố for gồm ba biểu thức phân cách nhau bởi dấu ;. Trong mỗi biểu thức khơng những cĩ thể viết một biểu thức mà cĩ quyền viết một dãy biểu thức phân cách nhau bởi dấu phảy. Khi đĩ các biểu thức trong mỗi phần được xác định từ trái sang phải. Tính đúng sai của dãy biểu thức được tính là tính đúng sai của biểu thức cuối cùng trong dãy này. Trong thân của for ta cĩ thể dùng thêm các tốn tử for khác, vì thế ta cĩ thể xây dựng các tốn tử for lồng nhau. Khi gặp câu lệnh break trong thân for, máy ra sẽ ra khỏi tốn tử for sâu nhất chứa câu lệnh này. Trong thân for cũng cĩ thể sử dụng tốn tử goto để nhảy đến một ví trí mong muốn bất kỳ. 3.6. Câu lệnh while : - Cú pháp : while ( biểu thức 1) lệnh 1 ; - Nguyên tắc thực hiện : +b1. Tính giá trị của biểu thức 1. +b2. Nếu giá trị của biểu thức 1 sai ( = 0 ) thì chương trình ra khỏi vịng while +b3. Nếu giá trị của biểu thức đúng thì thực hiện lệnh 1 và quay lại bước 1(b1). - Chú ý : Biểu thức 1 cĩ thể gồm nhiều biểu thức nhưng tính đúng sai phụ thuộc vào biểu thức cuối cùng. Ví dụ : Nhập 1 dãy số nguyên từ bàn phím #include #include main () { Int dayso [ 10 ] ; int i = 0 ; While ( i < 10) { printf ( "\n Số thu %d : ", i ); scanf ( " %d", & dayso [i]); i ++ ; 37
  39. } 3.7 Câu lệnh do while ( làm trƣớc hỏi sau ) - Cú pháp : do lệnh 1 ; while ( biểu thức 1 ) ; - Nguyên tắc thực hiện : +b1. Máy thực hiện câu lệnh 1 ; +b2. Sau đĩ tính giá trị của biểu thức 1, nếu giá trị của biểu thức 1 sai thì chương trình thốt ra khỏi vịng lặp. Nếu giá trị của biểu thức 1 đúng thì quay lại bước 1. Chú ý : - while : Ðiều kiện được kiểm tra trước, nếu đúng mới thực hiện. - do while : câu lệnh được thực hiện trước khi kiểm tra. Câu lệnh thực hiện bao giờ ít nhất là 1 lần. ( do while ngược với Repeat until của Pascal : lệnh Do while sai thì dừng, cịn lệnh repeat until đúng thì dừng ). -Biểu thức 1 cĩ thể gồm nhiều biểu thức, tuy nhiên tính đúng sai căn cứ theo biểu thức cuối cùng. * Ví dụ : tính pi với sai số eps = 1E - 4 , pi = 4 - 4/3 + 4/5 - 4/7 + eps #include #include main () { float pi, dau, i , eps, saiso ; i=1.0; dau = -1; saiso = 1e -4 ; pi = 4.0; printf ( "\n đang xử lý vui lịng đợi !"); do { eps = 4.0 / ( 2.0 * i + 1.0 ); pi + = dau * eps ; dau = dau * - 1.0 ; i + = 1.0; } while ( eps > saiso ); printf ("\n số pi là : " % f ", pi ) ; getch (); } 3.8. Câu lệnh Break : - Cú pháp : Dùng để thốt khỏi vịng lặp. Khi gặp câu lệnh này trong vịng lặp, máy ra khỏi và chỉ đến câu lệnh sau các lệnh trên. Nếu nhiều vịng lặp > break sẽ thốt ra khỏi vịng lặp gần nhất. 3.6. Lệnh continue : - Cú pháp continue; : khi gặp lệnh này trong các vịng lặp, máy sẽ bỏ qua phần cịn lại trong vịng lặp và tiếp tục thực hiện vịng lặp tiếp theo. - Ðối với lệnh For máy sẽ tính lại biểu thức 3 (bt3) và quay lại bước 2. - Ðối với lệnh while, do while máy sẽ tính lại giá trị của biểu thức 1 và quay lại bước 1. * Ví dụ : Nhập 1 chuỗi ký tự kể cả ký tự trống và bỏ qua các ký tự khơng hợp lệ và kết thúc khi ấn ESC hoặc số ký tự vượt quá kích thước mãng. char xau [MAXL], kytu ; int i = 0 ; while (1) /* luơn luơn đúng vịng lặp vĩnh cửu */ 38
  40. { kytu = getch ( ) ; if ( kytu = = 27 ) break ; if ( i >= MAXL ) break ; if ( kytu > 122 || kytu tiep tuc là nhãn của lệnh st = a [ i ]; - Lệnh goto nhãn => nhảy đến câu lệnh đứng sau nhãn. - CHÚ Ý : PHẠM VI NHÃN TRONG CÙNG 1 HÀM. 39
  41. CHƢƠNG 4 : HÀM CHƢƠNG TRÌNH VÀ CẤU TRƯC CHƢƠNG TRÌNH. Một chương trình viết trong ngơn ngữ C là một dãy các hàm, trong đĩ cĩ một hàm chính ( hàm main() ). Hàm chia các bài tốn lớn thành các cơng việc nhỏ hơn, giúp thực hiện những cơng việc lặp lại nào đĩ một cách nhanh chĩng mà khơng phải viết lại đoạn chương trình. Thứ tự các hàm trong chương trình là bất kỳ, song chương trình bao giờ cũng đi thực hiện từ hàm main(). 4.1. Cơ sở : Hàm cĩ thể xem là một đơn vị độc lập của chương trình. Các hàm cĩ vai trị ngang nhau, vì vậy khơng cĩ phép xây dựng một hàm bên trong các hàm khác. Xây dựng một hàm bao gồm: khai báo kiểu hàm, đặt tên hàm, khai báo các đối và đưa ra câu lệnh cần thiết để thực hiện yêu cầu đề ra cho hàm. Một hàm được viết theo mẫu sau : type tên hàm ( khai báo các đối ) { Khai báo các biến cục bộ Các câu lệnh [return[biểu thức];] } Dịng tiêu đề : Trong dịng đầu tiên của hàm chứa các thơng tin về : kiểu hàm, tên hàm, kiểu và tên mỗi đối. Ví dụ : float max3s(float a, float b, float c) khai báo các đối cĩ dạng : Kiểu đối 1 tên đối 1, kiểu đối 2 tên đối 2, , kiểu đối n tên đối n Thân hàm : Sau dịng tiêu đề là thân hàm. Thân hàm là nội dung chính của hàm bắt đầu và kết thúc bằng các dấu { }. Trong thân hàm chứa các câu lệnh cần thiết để thực hiện một yêu cầu nào đĩ đã đề ra cho hàm. Thân hàm cĩ thể sử dụng một câu lệnh return, cĩ thể dùng nhiều câu lệnh return ở các chỗ khác nhau, và cũng cĩ thể khơng sử dụng câu lệnh này. 40
  42. Dạng tổng quát của nĩ là : return [biểu thức]; Giá trị của biểu thức trong câu lệnh return sẽ được gán cho hàm. Ví dụ : Xét bài tốn : Tìm giá trị lớn nhất của ba số mà giá trị mà giá trị của chúng được đưa vào bàn phím. Xây dựng chương trình và tổ chức thành hai hàm : Hàm main() và hàm max3s. Nhiệm vụ của hàm max3s là tính giá trị lớn nhất của ba số đọc vào, giả sử là a,b,c. Nhiệm vụ của hàm main() là đọc ba giá trị vào từ bàn phím, rồi dùng hàm max3s để tính như trên, rồi đưa kết quả ra màn hình. Chương trình được viết như sau : #include "stdio.h" float max3s(float a,float b,float c ); /* Nguyên mẫu hàm*/ main() { float x,y,z; printf("\n Vao ba so x,y,z:"); scanf("%f%f%f",&x&y&z); printf("\n Max cua ba so x=%8.2f y=%8.2f z=%8.2f la : %8.2f", x,y,z,max3s(x,y,z)); } /* Kết thúc hàm main*/ float max3s(float a,float b,float c) { float max; max=a; if (max<b) max=b; if (max<c) max=c; return(max); } /* Kết thúc hàm max3s*/ Quy tắc hoạt động của hàm : Một cách tổng quát lời gọi hàm cĩ dạng sau : tên hàm ([Danh sách các tham số thực]) 41
  43. Số các tham số thực tế thay vào trong danh sách các đối phải bằng số tham số hình thức và lần lượt chúng cĩ kiểu tương ứng với nhau. Khi gặp một lời gọi hàm thì nĩ sẽ bắt đầu được thực hiện. Nĩi cách khác, khi máy gặp lời gọi hàm ở một vị trí nào đĩ trong chương trình, máy sẽ tạm dời chỗ đĩ và chuyển đến hàm tương ứng. Quá trình đĩ diễn ra theo trình tự sau : Cấp phát bộ nhớ cho các biến cục bộ. Gán giá trị của các tham số thực cho các đối tương ứng. Thực hiện các câu lệnh trong thân hàm. Khi gặp câu lệnh return hoặc dấu } cuối cùng của thân hàm thì máy sẽ xố các đối, biến cục bộ và ra khỏi hàm. Nếu trở về từ một câu lệnh return cĩ chứa biểu thức thì giá trị của biểu thức được gán cho hàm. Giá trị của hàm sẽ được sử dụng trong các biểu thức chứa nĩ. Các tham số thực, các đối và biến cục bộ : Do đối và biến cục bộ đều cĩ phạm vi hoạt động trong cùng một hàm nên đối và biến cục bộ cần cĩ tên khác nhau. Đối và biến cục bộ đều là các biến tự động. Chúng được cấp phát bộ nhớ khi hàm được xét đến và bị xố khi ra khỏi hàm nên ta khơng thể mang giá trị của đối ra khỏi hàm. Đối và biến cục bộ cĩ thể trùng tên với các đại lượng ngồi hàm mà khơng gây ra nhầm lẫn nào. Khi một hàm được gọi tới, việc đầu tiên là giá trị của các tham số thực được gán cho các đối ( trong ví dụ trên hàm max3s, các tham số thực là x,y,z, các đối tương ứng là a,b,c ). Như vậy các đối chính là các bản sao của các tham số thực. Hàm chỉ làm việc trên các đối. Các đối cĩ thể bị biến đổi trong thân hàm, cịn các tham số thực thì khơng bị thay đổi. Chú ý : Khi hàm khai báo khơng cĩ kiểu ở trước nĩ thì nĩ được mặc định là kiểu int. Khơng nhất thiết phải khai báo nguyên mẫu hàm. Nhưng nĩi chung nên cĩ vì nĩ cho phép chương trình biên dịch phát hiện lỗi khi gọi hàm hay tự động việc chuyển dạng. 42
  44. Nguyên mẫu của hàm thực chất là dịng đầu tiên của hàm thêm vào dấu ;. Tuy nhiên trong nguyên mẫu cĩ thể bỏ tên các đối. Hàm thường cĩ một vài đối. Ví dụ như hàm max3s cĩ ba đối là a,b,c. cả ba đối này đều cĩ giá trị float. Tuy nhiên, cũng cĩ hàm khơng đối như hàm main. Hàm thường cho ta một giá trị nào đĩ. Lẽ dĩ nhiên giá trị của hàm phụ thuộc vào giá trị các đối. 4.2. Hàm khơng cho các giá trị : Các hàm khơng cho giá trị giống như thủ tục ( procedure ) trong ngơn ngữ lập trình PASCAL. Trong trường hợp này, kiểu của nĩ là void. Ví dụ hàm tìm giá trị max trong ba số là max3s ở trên cĩ thể được viết thành thủ tục hiển thị số cực đại trong ba số như sau : void htmax3s(float a, float b, float c) { float max; max=a; if (max =0*/ { 43
  45. long int gtphu=1; int i; for (i=1;i 0 Hàm tính n! theo phương pháp đệ qui cĩ thể được viết như sau : long int gtdq(int n) { if (n==0 || n==1) return 1; else return(n*gtdq(n-1)); } Ta đi giải thích hoạt động của hàm đệ qui khi sử dụng trong hàm main dưới đây : #include "stdio.h" main() { printf("\n 3!=%d",gtdq(3)); } Lần gọi đầu tiên tới hàm gtdq được thực hiện từ hàm main(). Máy sẽ tạo ra một tập các biến tự động của hàm gtdq. Tập này chỉ gồm các đối n. Ta gọi đối n được tạo ra lần thứ nhất là n thứ nhất. Giá trị của tham số thực ( số 3 ) được gán cho n thứ nhất. Lúc này biến n trong thân hàm được xem là n thứ nhất. Do n thứ nhất cĩ giá trị bằng 3 nên điều kiện trong tốn tử if là sai và do đĩ máy sẽ lựa chọn câu lệnh else. Theo câu lệnh này, máy sẽ tính giá trị biểu thức : n*gtdq(n-1) (*) Để tính biểu thức trên, máy cần gọi chính hàm gtdq vì thế lần gọi thứ hai sẽ thực hiện. Máy sẽ tạo ra đối n mới, ta gọi đĩ là n thứ hai. Giá trị của n-1 ở đây lại là 44
  46. đối của hàm , được truyền cho hàm và hiểu là n thứ hai, do vậy n thứ hai cĩ giá trị là 2. Bây giờ, do n thứ hai vẫn chưa thoả mãn điều kiện if nên máy lại tiếp tục tính biểu thức : n*gtdq(n-1) ( ) Biểu thức trên lại gọi hàm gtdq lần thứ ba. Máy lại tạo ra đối n lần thứ ba và ở đây n thứ ba cĩ giá trị bằng 1. Đối n=1 thứ ba lại được truyền cho hàm, lúc này điều kiện trong lệnh if được thoả mãn, máy đi thực hiện câu lệnh : return 1=gtdq(1) ( ) Bắt đầu từ đây, máy sẽ thực hiện ba lần ra khỏi hàm gtdq. Lần ra khỏi hàm thứ nhất ứng với lần vào thứ ba. Kết quả là đối n thứ ba được giải phĩng, hàm gtdq(1) cho giá trị là 1 và máy trở về xét giá trị biểu thức n*gtdq(1) đây là kết quả của ( ) ở đây, n là n thứ hai và cĩ giá trị bằng 2. Theo câu lệnh return, máy sẽ thực hiện lần ra khỏi hàm lần thứ hai, đối n thứ hai sẽ được giải phĩng, kết quả là biểu thức trong ( ) cĩ giá trị là 2.1. Sau đĩ máy trở về biểu thức (*) lúc này là : n*gtdq(2)=n*2*1 n lại hiểu là thứ nhất, nĩ cĩ giá trị bằng 3, do vậy giá trị của biểu thức trong (*) là 3.2.1=6. Chính giá trị này được sử dụng trong câu lệnh printf của hàm main() nên kết quả in ra trên màn hình là : 3!=6 Chú ý : Hàm đệ qui so với hàm cĩ thể dùng vịng lặp thì đơn giản hơn, tuy nhiên với máy tính khi dùng hàm đệ qui sẽ dùng nhiều bộ nhớ trên ngăn xếp và cĩ thể dẫn đến tràn ngăn xếp. Vì vậy khi gặp một bài tốn mà cĩ thể cĩ cách giải lặp ( khơng dùng đệ qui ) thì ta nên dùng cách lặp này. Song vẫn tồn tại những bài tốn chỉ cĩ thể giải bằng đệ qui. 4.3.2. Các bài tốn cĩ thể dùng đệ qui : Phương pháp đệ qui thường áp dụng cho các bài tốn phụ thuộc tham số cĩ hai đặc điểm sau : 45
  47. Bài tốn dễ dàng giải quyết trong một số trường hợp riêng ứng với các giá trị đặc biệt của tham số. Người ta thường gọi là trường hợp suy biến. Trong trường hợp tổng quát, bài tốn cĩ thể qui về một bài tốn cùng dạng nhưng giá trị tham số thì bị thay đổi. Sau một số hữu hạn bước biến đổi dệ qui nĩ sẽ dẫn tới trường hợp suy biến. Bài tốn tính n giai thừa nêu trên thể hiện rõ nét đặc điểu này. 4.3.3. Cách xây dựng hàm đệ qui : Hàm đệ qui thường được xây dựng theo thuật tốn sau : if ( trường hợp suy biến) { Trình bày cách giải bài tốn khi suy biến } else /* Trường hợp tổng quát */ { Gọi đệ qui tới hàm ( đang viết ) với các giá trị khác của tham số } 4.3.4. Các ví dụ về dùng hàm đệ qui : Ví dụ 1 : Bài tốn dùng đệ qui tìm USCLN của hai số nguyên dương a và b. Trong trường hợp suy biến, khi a=b thì USCLN của a và b chính là giá trị của chúng. Trong trường hợp chung : uscln(a,b)=uscln(a-b,b) nếu a>b uscln(a,b)=uscln(a,b-a) nếu a<b Ta cĩ thể viết chương trình như sau : #include "stdio.h" int uscln(int a,int b ); /* Nguyên mẫu hàm*/ main() { int m,n; 46
  48. printf("\n Nhap cac gia tri cua a va b :"); scanf("%d%d",&m,&n); printf("\n USCLN cua a=%d va b=%d la :%d",m,m,uscln(m,n)) } int uscln(int a,int b) { if (a==b) return a; else if (a>b) return uscln(a-b,b); else return uscln(a,b-a); } Ví dụ 2 : Chương trình đọc vào một số rồi in nĩ ra dưới dạng các ký tự liên tiếp. # include "stdio.h" # include "conio.h" void prind(int n); main() { int a; clrscr(); printf("n="); scanf("%d",&a); prind(a); getch(); } void prind(int n) { 47
  49. int i; if (n<0) { putchar('-'); n=-n; } if ((i=n/10)!=0) prind(i); putchar(n%10+'0'); } 4.4. Bộ tiền sử lý C : C đưa ra một số cách mở rộng ngơn ngữ bằng các bộ tiền sử lý macro đơn giản. Cĩ hai cách mở rộng chính là #define mà ta đã học và khả năng bao hàm nội dung của các file khác vào file đang được dịch. Bao hàm file : Để dễ dàng xử lý một tập các #define và khai báo ( trong các đối tượng khác ), C đưa ra cách bao hàm các file khác vào file đang dịch cĩ dạng : #include "tên file" Dịng khai báo trên sẽ được thay thế bởi nội dung của file cĩ tên là tên file. Thơng thường cĩ vài dịng như vậy xuất hiện tại đầu mỗi file gốc để gọi vào các câu lệnh #define chung và các khai báo cho các biến ngồi. Các #include được phép lồng nhau. Thường thì các #include được dùng nhiều trong các chương trình lớn, nĩ đảm bảo rằng mọi file gốc đều được cung cấp cùng các định nghĩa và khai báo biến, do vậy tránh được các lỗi khĩ chịu do việc thiếu các khai báo định nghĩa. Tất nhiên khi thay đổi file được bao hàm vào thì mọi file phụ thuộc vào nĩ đều phải dịch lại. Phép thế MACRO : Định nghĩa cĩ dạng : #define biểu thức 1 [ biểu thức 2 ] sẽ gọi tới một macro để thay thế biểu thức 2 (nếu cĩ) cho biểu thức 1. Ví dụ : #define YES 1 48
  50. Macro thay biến YES bởi giá trị 1 cĩ nghĩa là hễ cĩ chỗ nào trong chương trình cĩ xuất hiện biến YES thì nĩ sẽ được thay bởi giá trị 1. Phạm vi cho tên được định nghĩa bởi #define là từ điểm định nghĩa đến cuối file gốc. Cĩ thể định nghĩa lại tên và một định nghĩa cĩ thể sử dụng các định nghĩa khác trước đĩ. Phép thế khơng thực hiện cho các xâu dấu nháy, ví dụ như YES là tên được định nghĩa thì khơng cĩ việc thay thế nào được thực hiện trong đoạn lệnh cĩ "YES". Vì việc thiết lập #define là một bước chuẩn bị chứ khơng phải là một phần của chương trình biên dịch nên cĩ rất ít hạn chế về văn phạm về việc phải định nghĩa cái gì. Chẳng hạn như những người lập trình ưa thích PASCAL cĩ thể định nghĩa : #define then #define begin { #define end; } sau đĩ viết đoạn chương trình : if (i>0) then begin a=i; end; Ta cũng cĩ thể định nghĩa các macro cĩ đối, do vậy văn bản thay thế sẽ phụ thuộc vào cách gọi tới macro. Ví dụ : Định nghĩa macro gọi max như sau : #define max(a,b) ((a)>(b) ?(a):(b)) Việc sử dụng : x=max(p+q,r+s); tương đương với : x=((p+q)>(r+s) ? (p+q):(r+s)); Như vậy ta cĩ thể cĩ hàm tính cực đại viết trên một dịng. Chừng nào các đối cịn giữ được tính nhất quán thì macro này vẫn cĩ giá trị với mọi kiểu dữ liệu, khơng cần phải cĩ các loại hàm max khác cho các kiểu dữ liệu khác nhưng vẫn phải cĩ đối cho các hàm. 49
  51. Tất nhiên nếu ta kiểm tra lại việc mở rộng của hàm max trên, ta sẽ thấy rằng nĩ cĩ thể gây ra số bẫy. Biểu thức đã được tính lại hai lần và điều này là khơng tốt nếu nĩ gây ra hiệu quả phụ kiểu như các lời gọi hàm và tốn tử tăng. Cần phải thận trọng dùng thêm dấu ngoặc để đảm bảo trật tự tính tốn. Tuy vậy, macro vẫn rất cĩ giá trị. Chú ý : Khơng được viết dấu cách giữa tên macro với dấu mở ngoặc bao quanh danh sách đối. Ví dụ : Xét chương trình sau : main() { int x,y,z; x=5; y=10*5; z=x+y; z=x+y+6; z=5*x+y; z=5*(x+y); z=5*((x)+(y)); printf("Z=%d",z); getch(); return; } Chương trình sử dụng MACRO sẽ như sau : #define BEGIN { #define END } #define INTEGER int #define NB 10 #define LIMIT NB*5 #define SUMXY x+y #define SUM1 (x+y) #define SUM2 ((x)+(y)) 50
  52. main() BEGIN INTEGER x,y,z; x=5; y=LIMIT; z=SUMXY; z=5*SUMXY; z=5*SUM1; z=5*SUM2; printf("\n Z=%d",z); getch(); return; END 51
  53. CHƢƠNG 5. MẢNG VÀ CÁC KIỂU DỮ LIỆU CĨ CẤU TRƯC 5.1/ Mảng : là tập hợp của các biến cùng kiểu được xếp liên tiếp nhau trong bộ nhớ trong. 5.1.1/ Mảng 1 chiều : a/ Khái niệm : [ ] Ví dụ : int a [5 ] ; => a [0] a[1] a[2] a [3] a [4] ( chỉ số chạy từ 0 đến n - 1 ). Char S [20] ; => 'A' 'B' 'X ' S[0]S[1] S[19] b/ Cách nhập số liệu cho mảng từ bàn phím ( cĩ thể dùng hàm Random C). + Mảng số nguyên : Ví dụ : Nhập vào mảng số nguyên 5 phần tử #include #include #define n 5 main () { int a [ n ] ; int i ; for ( i = 0 ; i #include #define n 5 ; main () { float a [ n ] , tam ; scanf ( " % f " , &tam) ; /*nhập qua biến trung gian tạm */ a [ i ] = tam ; c/Khởi tạo mảng : a [ 5 ] = { 1,2,3,5,4 }a[0]=1 a[2]=2 a[4]=4 d/ Mảng ký tự : - là chuỗi ký tự kết thúc bằng ký tự NULL cĩ mã ASCII là 0 . - Ví dụ : char S [3] = { 'L', '0', 'P'] : chuỗi này khơng đúng do thiếu chỗ cho ký tự kết thúc là NULL. - Ta cĩ thể gán : char S [ 4 ] = " Lop "; Ngơn ngữ C sẽ tự động ghi ký tự kết thúc là NULL, tức là ' \0 '. char S[ ] = " Lop " ; Khơng cần khai báo số phần tử mãng. * Ví dụ 1 : Nhập vàị một mảng số nguyên sau đĩ sắp xếp theo thứ tự tăng dần : #include 52
  54. #define n 5 main ( ) { int a [ n ] ; int i , j, t ; for ( i = 0 ; i > n ; i ++ ); { printf ( " nhập a [ % d] = " , i ); scanf ( " %d", & a [i ]); } /* Sắp xếp tăng dần */ for ( i = 0 ; i #include #define N 5 void sapxep ( int a [ ] , int n ); void main ( ) { int a [ N ] ; int i ; /* nhập 1 số liệu cho mãng */ for ( i = 0 ; i n - 1 ; i ++) for ( j = i + 1 ; j a [ j ] { 53
  55. t = a [ i ] ; a [ i ] = a [ j ] ; a [j ] = t ; } * Ví dụ 3 : chuyển đổi 1 chuỗi ký tự thường thành Hoa. Chú ý : + Hàm tolower ( ch ) : đổi 1 ký tự ch thành thường. + Hàm toupper ( ch ) : đổi ký tự ch thành Hoa. + Cả 2 hàm trên đều năm trong thư viện : Giải : #include # include #define n 20 main ( ) { char s [ n ] ; int i ; for ( i = 0 ; i [ ] [ ] *Ví dụ 1 : int a [ 3 ] [ 2 ] ; float b [ 3 ] [ 4 ] ; char c [5 ] [6 ] ; => a [ 0 ] [0 ] a [ 0 ] [ 1 ] a [ 1 ] [ 0 ] a [ 1 ] [ 1] a [ 2 ] [ 0 ] a [ 2 ] [ 1 ] Ví dụ 2 : #define Hang 5 # define Cot 6 int a [ Hang ] [ Cot ] ; => ta cĩ các biến chạy i ( chỉ số chạy từ 0 đến ( Dong - 1)). ta cĩ các biến chạy j ( chỉ số chạy từ 0 đến ( Cot - 1 )) . a [0] [0] a [0][1] a [ 0 ][Cot - 1] a [1] [0] a [1][1] a [a][Cot - 1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . a[Dong-1][0] . . . . . . . . a[Dong-1][Cot-1] *Ví dụ : Viết chương trình tính tổng, tích các số trong mãng số thực a[3][2] ; #include #define N 3 #define N 2 main ( ) { int i , j ; float a [M][N] ; float tong, tich, tam ; 54
  56. /* nhập số liệu */ for ( i = 0 ; i #define m 3 #define n 4 /* các prototype ( khai báo hàm )*/ void nhap ( int a[ ][N] , int M, int N ); void TongMT ( int a[ ][N], int b[ ][N] , int c [ ][N], int M , int N ); void InMT ( int c [ ][N], int M, int N ); /* chương trình chính */ { int a [M][N], b[M][N], c[M][N] ; /* gọi các hàm */ Nhap ( a, M ,N ) ; nhap ( b, M,N); TONGMT ( a, b, c , M, N ); InMT ( c, M, N ); Getch ( ) ; } /* Hàm nhập số liệu cho mãng 2 chiều m x n phần tử */ void Nhap ( int a [ ][N] , int M , int N ) { 55
  57. int i , j ; for ( i= 0 ; i < M ; i ++ ) for ( j = 0 ; j < N ; j++ ) { printf ( " a[%d][5d] = " , i , j ) ; scanf ( " %d " , &a [i][j]) ; } return ; } Void TongMT ( int a [ ][N], int b [ ][N], int c [ ][N], int M , int N ) { int i, j ; for ( i = 0 ; i < M ; i ++ ) for ( j = 0 ; j < N ; j ++ ) c [i][j] = a [i][j] + b [i][j] ; return ; } /* in kết quả */ void inMT ( int c[ ][N], int M, int N ) { int i, j ; for ( i = o ; i < M ; i ++ ) { for ( j = 0 ; j < N ; j ++ ) printf ( " % 3d", a[i][j] ); printf ( " \n " ) ; /* xuống dịng */ } return ; } Bài tập mảng : 1/ cho mãng 2 chiều A, là ma trận vuơng cấp n x n , lập chương trình : a/ tính tổng tất cả các phần tử dương của mãng. b/ tính tổng các phần tử A[i][j] mà i + j chia hết cho 5 . c/ In ra các số nguyên tố theo từng hàng. d/ Sắp xếp theo hàng. e/ Sắp xếp theo cột . f/ Tính tổng các phần tử trên đường chéo ( i = j ) , đường biên. g/ Tìm max ; min theo từng hàng, cột và tồn bộ ma trận. 2/ Một chuỗi gọi là palindrone nếu nĩ khơng thay đổi khi ta đảo ngược thứ tự của các ký tự trong nĩ ( ví dụ " 12321 " ) . Lập chương trình đọc một chuỗi ( xâu ) ký tự và xác định xem cĩ tính palondrone khơng. 5.3/ Biến con trỏ : 5.3.1/ Khái niệm con trỏ ( pointer ) và địa chỉ : - Mỗi biến trong ngơn ngữ C đều cĩ 1 tên và tương ứng với nĩ là một vùng nhớ dùng để chứa giá trị của nĩ. Tuỳ theo biến mà vùng nhớ dành cho biến cĩ độ dài khác nhau. Ðịa chỉ của biến là sơ thứ tự của byte đầu tiên tương ứng với biến đĩ. Ðịa chỉ của biến cĩ kiểu khác nhau là khác nhau. Ðịa chỉ và biển kiểu int liên tiếp cách nhau 2 byte , biến kiểu float là 4 byte. - Con trỏ là biến dùng để chứa địa chỉ của biến khác hoặc cĩ thể là một hàm. Do cĩ 56
  58. nhiều loại địa chỉ nên cũng cĩ nhiều loại biến con trỏ. Con trỏ kiểu int dùng để chứa địa chỉ của kiểu int. Con trỏ kiểu float dùng để chứa địa chỉ kiểu float. - Muốn sử dụng được pointer, trước tiên phải cĩ được địa chỉ của biến mà ta cần quan tâm bằng phép tốn lấy địa chỉ & . Kết quả của phép lấy địa chỉ & sẽ là 1 phần tử hằng. * Ví dụ : int num ; => &num là địa chỉ của num. int pnum ; /* pnum là 1 pointer chỉ đến một int */ pnum = & num ; /* pnum chứa địa chỉ biến int num*/ giả sử : num = 5 ; => * pnum = 5 /* do * là tốn tử nội dung */ Hai câu lệnh sau đây là tương đương Num = 100 ; ( * pnum ) = 100 ; - Quy tắc khai báo biến con trỏ : * *Ví dụ 2 : int a, *p ; a = 5 ; /* giả sử địa chỉ của a là */ p = & a ; /* p = */ p = a ; /* phép gán sai */ * p = a ; /* phép gán đúng */ scanf ( " %d " , &a ) ; tương đương scanf ( " %d , p ) ; 5.3.2/ tính tốn trên biến con trỏ ( pointer ) a/ Hai biến con trỏ cùng kiểu cĩ thể gán cho nhau : Ví dụ 1 : int a, * p, *a ; float * f; a = 5 ; p = &a ; q = p ; /* đúng */ f = p ; /* sai do khác kiểu */ f = ( float * )p ; /* đúng nhờ ép kiểu con trỏ nguyên về kiểu float */ Ví dụ 2 : int a ; char *c ; c = &a ; /* sai vì khác kiểu */ c = ( char*) /* đúng */ b/ Một biến pointer cĩ thể được cộng, trừ với một số nguyên ( int , long ) để cho kết quả là một pointer. * Ví dụ : int a , *p , * p10 ; a = 5 ; p = &a ; p10 = p + 10 ; Ví dụ : int V[10] ;/* mãng 10 phần tử */ int *p ; p = & V[0]; for ( i = 0 ; i < 10 ; i ++ ) { *p = i ; /* gán giá trị i cho phần tử mà p đang trỏ đến */ p ++ /* p được tăng lên 1 để chỉ đến phần tử kế tiếp */ } /* kết quả V[0] = 0 , V [ 1] = 1 V[9] = 9 * / c/ Phép trừ 2 pointer cho kết quả là một số int biểu thị khoảng cách ( số phần tử ) giữa 2 pointer đĩ. d/ Phép cộng 2 pointer là khơng hợp lệ, pointer khơng được nhân chia với 1 số nguyên hoặc nhân chia vơi nhau. e/ p = NULL : là con trỏ p khơng trỏ đến đâu cả. 57
  59. Chú ý : khơng được sử dụng biến con trỏ khi chưa được khởi gán . Ví dụ : int a , *p ; Scanf ( "%d", p ) ( sai ) => thay bằng các lệnh : p = &a và scanf ( "%d" p ) ( đúng) 5.4/ Con trỏ mảng : 5.4.1/ Mãng 1 chiều và con trỏ : - Trong ngơn ngữ C : giữa mãng và con trỏ cĩ mối quan hệ chặt chẽ. Các phần tử của mãng cĩ thể xác định nhờ chỉ số hoặc thơng qua con trỏ. - Ví dụ : int A[5] ; * p ; P = A ; + mãng bố trí 5 ơ nhớ liên tiếp ( mỗi ơ chiếm 2 byte ). + Tên mãng là 1 hằng địa chỉ ( khơng thay đổi được ), chính là địa chỉ của phần tử đầu tiên. => A tương đương với &A[0] (A + i ) tương đương với &A[i] *(A + i ) tương đương với A[i] p = A => p = &A[0] ( p trỏ tới phần tử A[0]) *(p + i ) tương đương với A[i]. =>bốn cách viết như sau là tương đương : A[i], * ( a + i ), * ( p + i ), p[i]. Ví dụ 2 : int a [5] ; *p ; p = a ; for ( i = 0; i #define n 5 main ( ) { int a [n], t , *p, i , j, ; int s ; p = a ; for ( i = 0; i * ( a + j ) { t = * ( a + i ) ; *(a + i ) = * ( a + j) ; *(a + j ) = t ; } s= 0 ; for ( j=0 ; i < n , ++i ) s + = a[ i]; printf ("\n Tong = %5d ", s ); printf ( "\n số lớn nhất là %d ", a [4] ); printf ( " số nhỏ nhất là %d \n ", a [d] ); getch ( ); 58
  60. } 5.4.2 / Con trỏ và mãng nhiều chiều : - Phép tốn lấy địa chỉ & chỉ áp dụng được với mãng 2 chiều kiểu nguyên. Các kiểu khác khơng được. * Ví dụ 1 : int a[2][3] { scanf ( "%d", & a[1][1]) } ( đúng ) * Ví dụ 2 : float a[2][3] Scanf (" %f", &a[1][1]); ( sai ). - Mãng 2 chiều a[2][3] => gồm 2 x 3 = 6 phần tử cĩ 6 địa chỉ liên tiếp theo thứ tự sau : Phần tử : a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] ( * ) Ðịa chỉ : 0 1 2 3 4 5 - Ngơn ngữ C quan niệm mãng 2 chiều là mãng một chiều của mãng a[2][3] tương đương khơng phần tử mà mỗi phần tử của nĩ gồm 3 số nguyên nên : a trỏ tới hàng thứ nhất ( a [0][0] ) a+1 trỏ tới hàng thứ hai ( a[1][0] ) - Do đĩ để duyệt các phần tử của mãng a[2][3] ta dùng con trỏ theo cách sau : + ( theo * ) => ta cĩ cơng thức a[i][j] = ( int*) a + i * n + j trong đĩ : int* : con trỏ a ( địa chỉ a ). n : số cột. - float a[2][3] , *p ; p = ( float*)a ; /* chú ý lệnh này */ khi đĩ : p trỏ tới a[0][0] /* p = & a[0][0] */ p + 1 trỏ tới a[0][1] /* *(p+1) = a[0][1] */ P + 2 trỏ tới a[0][2] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . p + 5 trỏ tới a[1][2] /* *(p+5) = a[1][2] */ * Tổng quát : a[i][j] = * ( p + i* N + 5 ); trong đĩ N : số cột ) Kết luận : Mãng 2 chiều cĩ thể chuyển thành mãng 1 chiều nhờ con trỏ. * Ví dụ : để nhập một số liệu vào mãng 2 chiều kiểu float a[2][3] ta cĩ thể dùng các cách sau: + Cách 1 : #include " stdio.h " main ( ) { float a[2][3] , *p ; int i ; p = (float*)a ; /* lưu ý lệnh này */ for ( i = 0 ; i < 2*3 ; ++i) scanf ( "%f", (p+i)) ; /* (p_+ i ) là địa chỉ */ ( X ) } + Cách 2 : Sửa lệnh ( X ) như sau : scanf ( "%f", (float*)a + 1 ) ; + Cách 3 : #include " stdio.h " #define m 2 #define n 3 main ( ) { float a[m][n] ; int i , j ; float *p ; p = ( float* )a ; for ( i=0 ; i<m ; i++ ) for ( j=0 ; j<n ; j++ ) scanf ( "%f" , ( p +i*n + j ) hoặc lệnh scanf ( " %f" , ( float *)a + i * N + j )); } 59
  61. + Cách 4 : sử dụng biến trung gian : #include " stdio.h" #define dong 2 #define cot 3 main ( ) { float a[dong][cot] , tam ; int i , j ; for ( i = 0 ; i [ ]. * Ví dụ : int *a[5] ; - trong đĩ : a là mãng gồm 5 ơ nhớ liên tiếp, mỗi ơ nhớ là 1 biến con trỏ trỏ đến kiểu int ; bản thân a khơng thể dùng để lưu trữ số liệu. - Giả sử : a a[0] a[1] a[2] a[3] a[4] a[5] Ðịa chỉ 7 8 9 10 11 1 2 3 4 5 6 12 13 - a= &a[0] => a = ( địa chỉ 100 ). - a[0] = ( địa chỉ bằng 30 : tại địa chỉ 30 con trỏ a[0] trỏ đến địa chỉ và giả sử tại địa chỉ cĩ giá trị là 6 ). => *a[0] = * ( > = 6 . a[1] = => *a[1] = 1 a [2] = => *a[2] = 7 . Chú ý 1: Xem a là con trỏ 2 lần ( con trỏ của con trỏ ) : - a = => *a = ( do a = &a[0] ) => a = 6 ( do *( )). - *(*(a + 1) + 2 ) *(102) * ( + 2 ) => * = 3 Chú ý 2 : - int a[5] => a là con trỏ hằng khơng thay dổi địa chỉ của nĩ được ( nên a++ sai) - int *a[5] ; => a laf con trỏ động nên thay đổi giá trị được ( a++ đúng ). Ví dụ : int *a[5] For ( i = 0 ; i < 5 ; i++ ) { printf ("%d", *a[0] ); a[0]++ ; } * Chú ý 3 : mãng 2 chiều chẳng qua là 1 con trỏ 2 lần ( con trỏ của con trỏ ). 60
  62. Lý do : a[i][k] ; trong đĩ đặt b = a[i] => b[k] = a[i][k] ; + Cơng thức : ( a[i] = *(a+i)) => ( b[i] = *(b+i)). b[k] = *(b+k)). b[k] = *(a[i] + k ) = * ( *(a+i) + j). => a[i][k] = *(*(a+i) + k) ; trong đĩ *(*(a+i) là con trỏ 2 lần. 5.4.4/ Con trỏ và xâu ký tự : - Xâu ký tự : là dãy ký tự đặt trong ngoặc kép . Ví dụ : " Lớp học ". Xâu này được chứa trong 1 mãng kiểu char. L O P H O C \0 Ðịa chỉ : NULL : kết thúc chuỗi => char *lop ; lop = " Lop Hoc " ; Ðúng : gán địa chỉ của chuỗi cho con trỏ lớp. + puts (" Lop Hoc ") ; và puts (lop ) đểu hiển thị dịng chữ Lop Hoc. Ví dụ : char Tenlop[10] ; Printf ("\n Tenlop : " ) ; gets( Tenlop ) ; => ( Nhập vào chuỗi " lớp học " ) Cịn nếu chúng ta khai báo như sau là sai : Char *lop , tenlop [10] ; Tenlop = " lớp học " ; sai vì Tenlop và chuỗi là 2 con trỏ hằng , khơng được gán cho nhau . Muốn gán ta dùng hàm strcpy (Tenlop , "lớp học "); 5.4.5/ Con trỏ và việc định vị bộ nhớ động : - Ví dụ 1 : #define N=10 ; main ( ) { int a[N] ; int m : printf ( " nhập số phần tử m = "); scanf("%d", &m) ; for ( i= 0 ; i N ( tức là m > 10 ) : thì chương trình sẽ chạy sai vì ta khơng đ ủ biến mãng. => Do đĩ ta phải khắc phục bằng cách : định vị bộ nhớ động. ( Bằng hàm malloc và calloc). * Ví dụ 2 : #include #include hoặc #include main ( ) { int m , *a ; printf (" Nhập số phần tử m = " ); scanf ( "%d", &m ); /* Cấp phát và định vị bộ nhớ động */ a = ( int*) malloc ( m* size of ( int ) ); (1) if ( a!= NULL ) /* cấp phát thành cơng */ for ( i=0 ; i . Hàm này cung cấp số lượng byte liên tiếp từ phần bộ nhớ cịn chưa sử dụng trên máy tính. 61
  63. + Ví dụ : malloc (num) = num byte và trả về con trỏ kiểu void trỏ đến địa chỉ bắt đầu của ơ nhớ. - Size of ( int ) : là số byte mà một biến kiểu int yêu cầu ( giá trị = 2 ) - ( int*) : ép kiểu ( type - casing) : coi địa chỉ bắt đầu là int ( do malloc trỏ về con trỏ kiểu void , đặc biệt khơng cĩ kiểu ) , cĩ thể nhận bất kỳ địa chỉ kiểu nào ( nhờ ép kiểu ). - Muốn sử dụng hàm calloc thay cho hàm malloc => khai báo : a = (int*) calloc ( n, size of (int)); * Chú ý : Luơn gán một địa chỉ cho một con trỏ trước khi sử dụng tới nĩ. Nếu khơng biến con trỏ sẽ mang một giá trị ngẫu nhiên cĩ thể phá huỷ chương trình. * Cấp phát bộ nhớ động cho mãng 2 chiều m x n phần tử, m , n nhập từ bàn phím: + Ví dụ : #include #include void main ( ) { int a , m, n, OK ; printf ( " nhập m = " ); scanf ("%d", &m); printf (nhập m = n) ; scanf ( "%d", &n ); a = ( int ) malloc ( m*seze of (int *)); if (a!=NULL ) /*Cấp phát thành cơng */ { OK = 1 ; for ( i=0 ; i < m ; i++ ) } /* giá trị ban đầu cho biến con trỏ*/ a[i] = (int*) break ; for ( i=0 ; i <m ; i ++ ) { if !(OK) break ; a[i] = (int*) malloc ( n * size of (int)); if ( a[i] = NULL ) OK = 0 ; } if(OK) { sử dụng a[0][0] , a[0][1] , a[i][j] , a[m][n] } /* giải phĩng vùng nhớ cấp phát */ if ( a!=NULL ) { for ( i = 0 ; i < m ; i++) if ( a[i] ! = NULL , free ( a[i]); free (a); } } * Chú ý : ta xem mãng 2 chiều là mãng 1 chiều nên cĩ thể khai báo : a = (int*) malloc ( m*n * size of ( int )); VÀ A[I][J] = A[ I*N + J] Bài tập : 1/ Làm lại các bài tập phần mãng nhưng dùng con trỏ . 2/ Dùng hàm malloc hay calloc nhập mãng n phần tử , sau đĩ tính tổng các phần tử và sắp xếp mãng giảm dần. 3/ Dùng hàm malloc hay calloc nhập ma trận m x n , sau đĩ tính tổng và sắp xếp theo tăng dần 5.4.6/ Mối liên hệ giữa con trỏ và các khái niệm quan trọng trong : a/ Con trỏ và hàm : - Chú ý 1 : bản thân tham số truyền cho hàm khơng bao giờ bị thay đổi. Nhưng nếu 62
  64. tham số là con trỏ thì giá trị của nĩ khơng thay đổi nhưng nội dung được chứa ở địa chỉ đĩ lại cĩ thể thay đổi. - Chú ý 2 : Truyền cho hàm một tham số hình thức được khai báo là con trỏ, và khi gọi hàm truyền cho nĩ một giá trị địa chỉ của biến muốn thay đổi. - Ví dụ :giả sử tân xây dựng một hàm dùng để hốn vị biến thực, ta viết như sau : Cách 1 : #include void swap (float x , float y ) /* cách 1 sai */ { float temp ; temp = x ; s hàm viết theo cách 1 khơng đạt yêu cầu => yêu cầu viết lại theo cách 2. * Cách 2 : void swap (float *x , float *y) /* viết đúng*/ { float temp ; temp = *x ; *x = *y ; * y = temp ; } main ( ) b/ Số học con trỏ ( cĩ thể thao tác số học trên nội dung con trỏ ) * Ví dụ : #include #include main ( ) { #define N 3 int *list , i ; list = int*) calloc ( N, size of(int)); *list = 15 ; * (list + 1) = 20 ; *(list + 2 ) = 30 ; printf ( " các địa chỉ là : "); for ( i=o ; i list trỏ tới một dãi bộ nhớ dài 6 byte ( 3*2) cĩ các giá trị là 5,20, 30 . giá trị địa chỉ đầu là 06A => kết quả các địa chỉ là : 06A 06AC 06AE chứa các giá trị là : 5 20 30 c/ Con trỏ và mãng : 63
  65. - Ví dụ 2 : #include main ( ) { #define N 3 int list [N] , i ; list [0] = 5 ; list [1] = 20 ; list[2]=30; printf ( " Các địa chỉ là : "); for ( i = 0 ; i { list + i) = = &(list[i]) và *(list + i) = = list[i]} d/ Con trỏ và cấu trúc : - Ta cĩ thể khai báo con trỏ như một biến cấu trúc, cũng như con trỏ của bấu kỳ kiểu dữ liệu nào khác. Ðiều này cho phép tạo một danh sách mĩc nối các phần tử ( sẽ trình bày chương sau ). e/ Con trỏ tới hàm : dùng để chứa địa chỉ của hàm. Nên kiểu của hàm và con trỏ phải giống nhau. Ví dụ : #include Double fmax ( double x, double y ) /* hàm tính max của 2 số */ { return ( x>y ? x:y ) ; } /* khai báo và gán tên hàm cho con trỏ hàm */ double (*pf) (double , double ) = fmax ; main ( ) { printf ( " In max = % f " , pf(15.5, 20.5 )); } 5.5. Kiểu cấu trúc - Khái niệm : Cấu trúc là một kiểu dữ liệu kiểu bản ghi(record) , cho phép nhiều loại dữ liệu được nhĩm lại với nhau. ( Khái niệm cấu trúc trong C tương tự như pascal hay Foxpro). 5.5.1/ Khai báo kiểu cấu trúc : a/ struct tên _ kiểu cấu trúc { khai báo các thành phần của nĩ ( các field và kiểu dữ liệu của field) } ; - Ví dụ 1 : struct kieu HV ị-> tên kiểu cấu trúc. { char Ten[30] ; int namsinh ;float diemTB ; } HV ; ( biến HV) 64
  66. - Ví dụ 2 : struct kieu HV { các thành phần } struct kieu HV HV ; /* khai báo biến theo cách 2 */ b/ Dùng tốn tử typedef để khai báo kiểu cấu trúc ( định nghĩa kiểu mới) ; - Ví dụ 3 : typedef struct { char Ten[30] int namsinh ; float diemTB ; } kieu HV ; kieu HV Hoc vien ; kieu HV DSLop[20]; kieu HV Lop[ ] = { { "nguyễn văn Ðơng", 1980, 10.0}, { " Trần văn Tây", 1982, 5.5}, { " Phạm văn Nam ", 1979, 6.5} }; - Ví dụ 4 : struct ngay{ int ngay ; char Thang[10]; int nam ; } ; type struct { char Ten[30] ; ngay namsinh ; /* thành phần cấu trúc cĩ kiểu cấu trúc*/ float diemTB; } kieu HV ; kieu HV HV; * Chú ý : - Khai báo struct phải nằm ở vị trí tồn cục của chương trình, thường sau các #include. - Cấu trúc thường dùng để xây dựng một bảng các cấu trúc. + Ví dụ : kieu HV DSLop[30] ; struct kieu HV person[50]; - Cĩ thể truyền cấu trúc như một tham số hình thức, nhưng với những cấu trúc kích thước lớn sẽ khơng tối ưu về thời gian lẫn độ nhớ. Khi khơng nên sử dụng con trỏ cấu trúc. + Ví dụ : struc kieu HV *HV ; 5.5.2/ Truy cập đến các thành phần của kiểu cấu trúc : Tên cấu trúc. Tên thành phần Hoặc Tên cấu trúc. Tên cấu trúc con. Tên thành phần. - Ví dụ : + nhập vào tên, năm sinh, điểm cho biến cấu trúc học viên ( ví dụ 3). gets(hoc vien.ten) /* nhập " Phạm thị Bắc" và Enter */ scanf("%d ", & hoc vien.namsinh ); scanf("%f", &tam); hoc vien.diem = tam; (*) + Nhập năm sinh cho biến học viên ở ví dụ 4 : scanf("%d",&hv.ngay.namsinh); * Chú ý : Nếu các thành phần khơng phải là nguyên(int) => nhập qua trung gian như (*). puts(hoc vien.ten); => " Phạm thị Bắc" printf("%d%f", hoc vien.namsinh, hoc vien.diemTB); 65
  67. * Lệnh gán : + Ta cĩ thể gán 2 biến cấu trúc cĩ cùng kiểu cho nhau : Ví dụ : hv2=hv1; + Gán giá trị đầu cho biến cấu trúc và khai báo một mãng cấu TRÚC( XEM VÍ DỤ 3) Bài tập : viết chương trình nhập danh sách học viên gồm các trường họ tên, tuổi, điểm, và tìm kiếm trong dánhách cĩ ai tên " Phạm Tèo " khơng. Tên Tuổi điểm HV [ 0] Nguyễn A 20 5.5 HV [1] Trần B 22 6.5 HV [2] Phạm Tèo 25 8.5 HV [3] Lê C 21 7.5 #include #define n 10 typedef struct { char Ten[30]; int tuoi ; float diem ; } kieu HV ; kieu HV HV[11] void main( ) { int i ; float tam ; kieu HV HV; /* nhập dữ liệu cách 1*/ for ( i = 0 ; i = 5 thì kết quả đậu. + Nếu điểm trung bình <5 thì kết qua rớt. 66