Bài giảng Kỹ thuật lập trình C - Trường Đại học Hàng Hải

pdf 96 trang Gia Huy 17/05/2022 2100
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Kỹ thuật lập trình C - Trường Đại học Hàng Hải", để 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_ky_thuat_lap_trinh_c_truong_dai_hoc_hang_hai.pdf

Nội dung text: Bài giảng Kỹ thuật lập trình C - Trường Đại học Hàng Hải

  1. BỘ GIAO THƠNG VẬN TẢI TRƢỜNG ĐẠI HỌC HÀNG HẢI BỘ MƠN: KHOA HOC̣ 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. MỤC LỤC MỤC LỤC 2 CHƢƠNG 1. GIỚI THIỆU 6 1.1. Giới thiệu ngơn ngữ lập trình C. 6 1.2. Thuật tốn và sơ đồ khối 8 CHƢƠNG 2. CÁC KHÁI NIỆM CƠ BẢN VỀ NGƠN NGỮ LẬP TRÌNH C 9 2.1. Các phần tử cơ bản của ngơn ngữ lập trình C. 9 2.2. Cấu trúc chung của chƣơng trình C 10 2.3. Các bƣớc cơ bản khi lập chƣơng trình 11 2.4. Các kiểu dữ liệu cơ sở 12 2.5. Các khai báo trong chƣơng trình C 17 2.6. Biểu thức 21 2.7. Các hàm tốn học 22 CHƢƠNG 3. CÁC CÂU LỆNH CƠ BẢN 23 3.1. Lệnh gán giá trị, lệnh gộp 23 3.2. Hàm viết dữ liệu ra màn hình 25 3.3. Hàm nhập dữ liệu vào từ bàn phím 27 3.4. Câu lệnh điều kiện 31 3.5. Câu lệnh lựa chọn-lệnh switch 34 3.6. Câu lệnh lặp for 36 3.7. Câu lệnh while 37 3.8. Câu lệnh do while 37 3.9. Câu lệnh break 38 3.10. Lệnh continue 38 3.11. Tốn tử goto và nhãn ( label ) 38 CHƢƠNG 4. HÀM CHƢƠNG TRÌNH VÀ CẤU TRÚC CHƢƠNG TRÌNH. 39 4.1. Khái niệm về chƣơng trình con 39 4.2. Hàm trong C 39 4.3. Chuyển tham số cho hàm 41 4.4. Biến tồn cục và biến địa phƣơng 41 4.5.Tính đệ quy của hàm 42 4.6. Bộ tiền xử lý C 46 CHƢƠNG 5. MẢNG VÀ CÁC KIỂU DỮ LIỆU CĨ CẤU TRÚC 50 5.1.Dữ liệu kiểu mảng/con trỏ 50 5.3. Dữ liệu kiểu cấu trúc 61 CHƢƠNG 6. DỮ LIỆU KIỂU TỆP 67 6.1. Khái niệm về tệp tin 67 6.2. Cấu trúc và phân loại tệp 67 2
  3. 6.3. Tạo tệp mới để đọc/ghi dữ liệu 68 6.4. Một số hàm xử lý tệp của C 70 6.5. Bài tập áp dụng 77 CHƢƠNG 7. ĐỒ HOẠ 78 7.1. Giới thiệu chung 78 7.2. Các hàm đặt màu, vẽ điểm, tơ màu 80 7.3. Các hàm vẽ hình cơ bản 88 TÀI LIỆU THAM KHẢO 96 3
  4. 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 kiểu dữ liệu cơ sở 2.5. Các khai báo trong chƣơng trình C 2.6. Biểu thức 2.7. Các hàm tốn học Chƣơng 3. Các câu lệnh điều khiển của C 13 7 5 1 3.1. Lệnh gán giá trị, lệnh gộp 3.2. Hàm viết dữ liệu ra màn hình 3.3. Hàm nhập dữ liệu vào từ bàn phím 3.4. Câu lệnh điều kiện 3.5. Câu lệnh lựa chọn 3.6. Câu lệnh lặp for 3.7. Câu lệnh lặp while 3.8. Câu lệnh do while. 3.9. Câu lệnh break. 3.10. Lệnh continue 3.11. Tốn tử goto và nhãn (label) 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
  5. PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT 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 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 để đọc / ghi dữ liệu. 6.4. Một số hàm xử lý tệp của C. 6.5. Bài tập áp dụng 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 tham khảo: 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, thời gian làm bài 45 phút 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. Bài giảng này là tài liệu chính thức và thống nhất của Bộ mơn Khoa học máy tính, Khoa Cơng nghệ thơng tin và đƣợc dùng để giảng dạy cho sinh viên. Ngày phê duyệt: / /20 Trƣởng Bộ mơn: ThS. Nguyễn Hữu Tuân (ký và ghi rõ họ tên) 5
  6. 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 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 6
  7. 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 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ú 7
  8. 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 Khái niệm: thuật tốn hay giải thuật dùng để chỉ phƣơng pháp hay cách thức (method) để giải quyết vấn đề. Sơ dồ khối: sử dụng các khối để biểu diễn thuật tốn Bài tập: 1. Vẽ sơ đồ khối giải thuật giải phƣơng trình bậc nhất ax+b=0 2. Vẽ sơ đồ khối giải thuật giải bất phƣơng trình bậc nhất ax+b>0 3. Vẽ sơ đồ khối giải thuật giải phƣơng trình bậc hai ax2+bx+c=0 4. Vẽ sơ đồ khối giải thuật tìm ƣớc số chung lớn nhất của 2 số nguyên dƣơng 5. Vẽ sơ đồ khối giải thuật tìm số nhỏ nhất của 2 số 8
  9. 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 ý 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 9
  10. - 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ĩ 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() 10
  11. { 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 2 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 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. 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 11
  12. 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 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; 12
  13. 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 : 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 13
  14. 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: 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. 14
  15. 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 a>=b >= So sánh lớn hơn hoặc bằng 6>=2 cĩ giá trị 1 a<b < So sánh nhỏ hơn 6<=7 cĩ giá trị 1 a<=b <= So sánh nhỏ hơn hoặc bằng 8<=5 cĩ giá trị 0 a==b = = So sánh bằng nhau 6==6 cĩ giá trị 1 a!=b != So sánh khác nhau 9!=9 cĩ giá trị 0 Bốn phép tốn đầu cĩ cùng số ƣu tiên, hai phép sau cĩ cùng số thứ tự ƣu tiên nhƣng thấp hơn số thứ tự của bốn phép đầu. Các phép tốn quan hệ cĩ số thứ tự ƣu tiên thấp hơn so với các phép tốn số học, cho nên biểu thức: i<n-1 đƣợc hiểu là i<(n-1). Các phép tốn logic : Trong C sử dụng ba phép tốn logic : Phép phủ định một ngơi ! a !a 1 0 0 1 Phép và (AND) && Phép hoặc ( OR ) || A B A&&B A || B 1 1 1 1 1 0 0 1 0 1 0 1 15
  16. 0 0 0 0 Các phép quan hệ cĩ số ƣu tiên nhỏ hơn so với ! nhƣng lớn hơn so với && và ||, vì vậy biểu thức nhƣ: (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 : 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 16
  17. 13 ?: Phải qua trái 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. 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. 17
  18. 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 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 18
  19. 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" 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 19
  20. 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; 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 : 20
  21. 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 Đổ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ụ: 21
  22. (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. + 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. Các hàm tốn học 2.7.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.7.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. Bài tập: 1. Viết chƣơng trình sử dụng các hàm tốn học để tính tốn giá trị một số biểu thức 2. Tìm hiểu cách thức dịch một chƣơng trình bằng một trong các chƣơng trình dịch TC, C-Free, Dev C, 22
  23. CHƢƠNG 3. CÁC CÂU LỆNH CƠ BẢN 3.1. 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); } 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; 23
  24. 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; { 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. 24
  25. 3.2. Hàm viết dữ liệu ra màn hình 3.2.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.2.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.2.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. 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 25
  26. 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ĩ : 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ự chuyển dạng Ý nghĩa 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 ) 26
  27. x Đối đƣợc chuyển sang hệ mƣới sáu khơng dấu ( khơng cĩ 0x đứng 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ự khơng s 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 phân cĩ e 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 xác mặc định là f 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. g Dùng %e hoặc %f, tuỳ theo loại nào ngắn hơn, khơng in các số 0 vơ 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ụ : 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.3. Hàm nhập dữ liệu vào từ bàn phím 3.3.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(); 27
  28. 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.3.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.3.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. Đặ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ự 28
  29. 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ự ] 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 29
  30. 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 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= "); 30
  31. 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.4. Câu lệnh điều kiện 3.4.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="); 31
  32. scanf("%f",&a); printf("\n Cho b="); scanf("%f",&b); max=a; if (b>max) max=b; 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
  33. 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.4.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" main() { float a,b,c,d,x1,x2; 33
  34. 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.5. 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. 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. 34
  35. 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: 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); 35
  36. } printf("Tiep tuc 1, dung 0 :") scanf("%d",&diem); if (diem= =1) goto tt; getch(); return; } 3.6. 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 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. 36
  37. 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.7. 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 #include main () { 37
  38. 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.9. 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.10. 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 */ { 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. Bài tập: 1. Viết chƣơng trình giải pt bậc nhất ax+b=0 2. Viết chƣơng trình giải bất pt bậc nhất ax+b<0 3. Viết chƣơng trình giải pt bậc hai ax2+bx+c=0 38
  39. 4. Viết chƣơng trình tìm ƣớc số chung lớn nhất của 2 số 5. Viết chƣơng trình tìm số lớn nhất trong dãy các số nhập vào CHƢƠNG 4. HÀM CHƢƠNG TRÌNH VÀ CẤU TRƯC CHƢƠNG TRÌNH. 4.1. Khái niệm về chƣơng trình con 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.2. Hàm trong C 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. Dạng tổng quát của nĩ là : 39
  40. 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]) 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. 40
  41. 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ĩ. 4.3. Chuyển tham số cho hàm Các tham số hình thức: là các tham số đƣợc khai báo ở phần khai báo (định nghĩa hàm). Các tham số này khơng cĩ giá trị thực sự, chúng chỉ cĩ ý nghĩa về mặt hình thức. Các tham số thực: là các tham số đƣợc truyền vào khi hàm đƣợc gọi thực hiện. Chúng thay thế các tham số hình thức. 4.4. Biến tồn cục và biến địa phƣơng Biến tồn cục là biến nằm ngồi tất cả các hàm. Biến cục bộ là biến đƣợc khai báo bên trong một hàm nào đĩ. Tham số hình thức cĩ thể coi là 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. 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. 41
  42. 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. 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*/ { long int gtphu=1; int i; for (i=1;i<=n;++i) gtphu*=i; return s; } Ta nhận thấy rằng n! cĩ thể tính theo cơng thức truy hồi sau : n!=1 nếu n=0 42
  43. n!=n*(n-1)! nếu n>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à đố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 ( ) 43
  44. ở đâ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.5.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 : 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.5.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ố } 44
  45. 4.5.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) 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() 45
  46. { int a; clrscr(); printf("n="); scanf("%d",&a); prind(a); getch(); } void prind(int n) { int i; if (n<0) { putchar('-'); n=-n; } if ((i=n/10)!=0) prind(i); putchar(n%10+'0'); } 4.6. Bộ tiền xử 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. 46
  47. 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 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)); 47
  48. 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. 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)) main() 48
  49. 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 Bài tập 1. Viết hàm tìm ƣớc số chung lớn nhất của 2 số 2. Viết hàm tìm bội số chung nhỏ nhất của 2 số 3. Viết hàm tìm số lớn nhất của 3 số 4. Viết hàm in các số chẵn trong một dãy số nhập từ bàn phím 5. Viết hàm tính tổng các số từ 1 - n 49
  50. CHƢƠNG 5. MẢNG VÀ CÁC KIỂU DỮ LIỆU CĨ CẤU TRƯC 5.1.Dữ liệu kiểu mảng/con trỏ 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 và nhiều chiều a/ Khai báo mảng 1 chiều : [ ] 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 #define n 5 main ( ) { 50
  51. 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 ] { 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 51
  52. #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 ; /* nhập số liệu */ for ( i = 0 ; i < M ; i ++ ) for ( j = 0 ; j < N ; j ++ ) { printf ( " nhập a [ %d][%d] = " , i , j ); scanf ( " %f " , & tam ) ; a [i][j] = tam ;} /* tính tổng */ Tong = 0 ; Tich = 1; for ( i = 0 ; i < M ; i ++ ) for ( j = 0 ); j < N ; j ++ ) 52
  53. { Tong = Tong + a [ i ][j] ; Tich = Tich * a [i][j] ; } /* in kết quả */ printf ( " Tổng là tổng = %f, TONG ); printf ( " tích là TICH = %F, TICH ); getch ( ) ; } Truyền tham số mảng nhiều chiều cho hàm ( tham số thực là tên mảng nhiều chiều ) - giả sử a là mảng 2 chiều : float a[M][N] + Chƣơng trình gọi : { float a [M][N] Tong ( a ) ;// ( truyền địa chỉ của mảng cho hàm ) } + Chƣơng trình bị gọi ( chƣơng trình con ) : float tong ( float a[ ][N] ) /* khai báo đối để nhận địa chỉ của mảng */ { } Note : hàm tong chỉ dùng đƣợc đối với các mảng hai chiều cĩ N cột và số hàng khơng quan trọng, khơng khai báo ) : * Ví dụ : Viết chƣơng trình tính tổng của 2 ma trận cấp m x n theo cơng thức : C[i][j] = a[i][j] + b [i][j] #include #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 ) { 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 TongM ( int a [ ][N], int b [ ][N], int c [ ][N], int M , int N ) { int i, j ; for ( i = 0 ; i < M ; i ++ ) 53
  54. for ( j = 0 ; j &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 */ 54
  55. * p = a ; /* phép gán đúng */ scanf ( " %d " , &a ) ; tƣơng đƣơng scanf ( " %d , p ) ; b) Tính tốn trên biến con trỏ ( pointer ) + 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 */ + 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 thay bằng các lệnh : p = &a và scanf ( "%d" p ) ( đúng) c) Con trỏ mảng + 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]. 55
  56. 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 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] */ 56
  57. 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 [ ]. * 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 57
  58. - 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 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.2. Dữ liệu kiểu 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 "); + 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 : 58
  59. #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. + 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] 59
  60. 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 + Mối liên hệ giữa con trỏ và các khái niệm quan trọng: 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 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 < N ; i++) printf ("%4d",(list + i)); printf ("\n chứa các giá trị là : "); for ( i=0 ; i < N ; i++) printf("%4d", *(list + i)); 60
  61. printf("\n"); => 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 : - 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.3. Dữ liệu 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.3.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) - Ví dụ 2 : struct kieu HV 61