Giáo trình Kỹ thuật lập trình

pdf 211 trang Gia Huy 17/05/2022 2570
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Kỹ thuật lập trình", để 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:

  • pdfgiao_trinh_ky_thuat_lap_trinh.pdf

Nội dung text: Giáo trình Kỹ thuật lập trình

  1. MỤC LỤC Chương 1 5 CƠ BẢN VỀ NGÔN NGỮ LẬP TRÌNH 5 1.1. MỘT SỐ KHÁI NIỆM 5 1.1.1. Bộ chữ viết 5 1.1.2. Từ khóa 5 1.1.3. Tên gọi 6 1.1.4. Câu lệnh 6 1.1.5. Chú thích 7 1.2. CÁC KIỂU DỮ LIỆU CƠ BẢN 8 1.2.1. Dữ liệu và kiểu dữ liệu 8 1.2.2. Kiểu ký tự 8 1.2.3. Kiểu số nguyên 10 1.2.4. Kiểu số thực 10 1.3. BIẾN, HẰNG VÀ BIỂU THỨC 10 1.3.1. Biến 10 1.3.2. Hằng 12 1.3.3. Biểu thức 14 1.4. CẤU TRÚC MỘT CHƯƠNG TRÌNH ĐƠN GIẢN 18 1.4.1. Cấu trúc chung 18 1.4.2. Nhập/Xuất dữ liệu 20 1.4.3. Ví dụ 24 1.5. ĐIỀU KHIỂN TRÌNH TỰ THỰC HIỆN CÁC LỆNH 25 1.5.1. Tuần tự 25 1.5.2. Điều khiển chọn 26 1.5.3. Điều khiển lặp 34 1.6. BÀI TẬP 43 Chương 2 46 MỘT SỐ CẤU TRÚC DỮ LIỆU CƠ BẢN 46 2.1. MẢNG 46 2.1.1. Mảng một chiều 46 2.1.2. Mảng nhiều chiều 50 2.1.3. Một số ví dụ 54 2.2. XÂU KÝ TỰ 63 2.2.1. Khai báo và nhập xuất dữ liệu 63 1
  2. 2.2.2. Các hàm xử lý dữ liệu xâu 65 2.2.3. Một số ví dụ 70 2.3. CON TRỎ 74 2.3.1. Khái báo, truy xuất con trỏ 74 2.3.2. Con trỏ và mảng 75 2.3.3. Cấp phát bộ nhớ 76 2.3.4. Ví dụ 78 2.4. BẢN GHI 80 2.4.1. Khai báo, truy xuất dữ liệu 80 2.4.2. Ví dụ 83 2.5. BÀI TẬP 84 Chương 3 87 HÀM VÀ CẤU TRÚC CHƯƠNG TRÌNH 87 3.1. TỔ CHỨC CHƯƠNG TRÌNH 87 3.1.1. Ví dụ 87 3.1.2. Cấu trúc chương trình 88 3.1.3. Hàm được xây dựng sẵn 93 3.2. HÀM 93 3.2.1. Khai báo và định nghĩa hàm 93 3.2.2. Lời gọi và sử dụng hàm 96 3.2.3. Hàm với đối mặc định 97 3.2.4. Khai báo hàm trùng tên 98 3.2.5. Biến, đối tham chiếu 100 3.2.6. Cách truyền tham số 101 3.2.7. Hàm và mảng 105 3.3. CON TRỎ HÀM 115 3.3.1. Khai báo 115 3.3.2. Sử dụng con trỏ hàm 115 3.3.3. Mảng con trỏ hàm 117 3.4. HÀM ĐỆ QUY 118 3.4.1. Khái niệm 118 3.4.2. Lớp các bài toán giải được bằng đệ qui 119 3.4.3. Các ví dụ 120 3.5. BÀI TẬP 122 Chương 4 126 2
  3. KỸ THUẬT LẬP TRÌNH CẤU TRÚC 126 4.1. NGUYÊN TẮC ÍT NHẤT 127 4.2. TÍNH ĐỊA PHƯƠNG 130 4.3. TÍNH NHẤT QUÁN 131 4.4. NGUYÊN TẮC AN TOÀN 132 4.5. PHƯƠNG PHÁP TOP-DOWN 134 4.6. KỸ THUẬT BOTTOM-UP 139 4.7. BÀI TẬP 143 Chương 5 146 CƠ BẢN VỀ THUẬT TOÁN 146 5.1. MỘT SỐ VẤN ĐỀ CƠ SỞ 146 5.1.1. Thuật toán 146 5.1.2. Biểu diễn thuật toán 149 5.1.3. Các bước giải bài toán trên máy tính 152 5.2. MỘT SỐ THUẬT TOÁN SẮP XẾP CƠ BẢN 153 5.2.1. Sắp xếp chọn 153 5.2.2. Sắp xếp nổi bọt 154 5.2.3. Sắp xếp chèn 157 5.3. THUẬT TOÁN TÌM KIẾM 158 5.4. GIẢI THUẬT ĐỆ QUY 161 5.5. KỸ THUẬT QUAY LUI 166 5.6. BÀI TẬP 178 Chương 6 180 MỘT SỐ CẤU TRÚC PHỨC HỢP 180 6.1. BẢN GHI TỰ TRỎ 180 6.2. DỮ LIỆU KIỂU DANH SÁCH ĐỘNG 181 6.2.1. Danh sách kiểu LIFO 182 6.2.2. Danh sách kiểu FIFO 186 6.2.3. Danh sách phi tuyến 191 6.3. BÀI TẬP 195 Chương 7 196 THAO TÁC VỚI TỆP DỮ LIỆU 196 7.1. MỘT SỐ KHÁI NIỆM 196 7.1.1. Thư mục 196 7.1.2. Tệp tin 197 3
  4. 7.1.3. Dữ liệu dạng tệp tin 197 7.2 CÁC THAO TÁC VỚI TỆP TIN 198 7.2.1. Khai báo biến tệp tin 198 7.2.2. Mở tệp tin 198 7.2.3. Đóng tập tin 199 7.2.4. Kiểm tra xem đã đến cuối tệp tin 200 7.2.5. Di chuyển con trỏ tệp tin về đầu 200 7.2.6. Lấy vị trí con trỏ 200 7.3. TRUY CẬP TỆP TIN VĂN BẢN 200 7.3.1. Ghi dữ liệu lên tập tin văn bản 200 7.3.2. Đọc dữ liệu từ tập tin văn bản 202 7.3.3. Ví dụ 203 7.4. TRUY CẬP TỆP TIN NHỊ PHÂN 204 7.4.1. Ghi dữ liệu lên tập tin nhị phân 204 7.4.2. Đọc dữ liệu từ tập tin nhị phân 204 7.4.3. Ví dụ 205 7.5. BÀI TẬP 208 4
  5. Chương 1 CƠ BẢN VỀ NGÔN NGỮ LẬP TRÌNH 1.1. MỘT SỐ KHÁI NIỆM 1.1.1. Bộ chữ viết Cũng như mọi ngôn ngữ giao tiếp thông thường, các ngôn ngữ lập trình được xây dựng trên bộ ký tự và sau đó là tập hợp các từ vựng cùng với các quy tắc cho phép ghép các từ thành các câu để diễn tả sự việc, hành động và yêu cầu. Nói cách khác, khi sử dụng một ngôn ngữ lập trình thì phải tuân thủ cú pháp của ngôn ngữ lập trình đó. Ngôn ngữ lập trình C (gọi tắt là C) sử dụng bộ các ký tự bao gồm: - Các chữ cái Latin: A, B, C, . . ., Z và a, b, c, , z; - Các chữ số thập phân: 0,1,2,3, . . ,9; - Các kí hiệu toán học: +,-, *, /, =, , %, ^; - Các ký hiệu đặc biệt: :. , ; " ' _ @ # $ ! ^ [ ] { } ( ) ; - Kí tự gạch nối _ (chú ý phân biệt với dấu trừ -); - Dấu cách (khoảng trống - space) để phân biệt giữa các từ. Lưu ý rằng trong C dạng viết thường và hoa của cùng một chữ cái được hiểu là hai chữ cái khác nhau. 1.1.2. Từ khóa Trong các ngôn ngữ lập trình người ta quy định một số từ dùng vào mục đích nhất định nào đó, các từ này được gọi là từ khóa (keyword). Mỗi từ khóa có một ý nghĩa xác định và thường không cho phép dùng vào mục đích khác. Về cơ bản bộ từ khóa của các ngôn ngữ lập trình khác nhau là khác nhau. Trong nhiều ngôn ngữ lập trình, ví dụ như trong C, hoặc Pascal, từ khóa là một từ được qui định trước về hình thức và mang một ý nghĩa xác định và không được dùng với bất kỳ mục đích nào khác. Trong một số ngôn ngữ, ví dụ như PostScript, khái niệm từ khóa được mở rộng hơn sơ với các ngôn ngữ thường dùng như C, trong đó cho phép người sử dụng định nghĩa lại từ khóa với một mục đích khác. Hoặc trong một số ngôn ngữ, ví dụ như trong Common Lisp, thì từ khóa được hiểu là dạng viết tắt của các ký hiệu hoặc định danh, nhưng không giống như ký hiệu thường để biểu diễn cho biến hoặc hàm, từ khóa là được tự trích dẫn (self-quoting) và đánh giá; từ khóa thường được sử dụng để gán nhãn các tham số cho hàm, và để biểu diễn các giá trị tượng trưng khác. Trong giới hạn của tài liệu này chúng ta sử dụng định nghĩa sau về từ khóa. Từ khóa là một từ được qui định trước về hình thức, mang một ý nghĩa xác định và người sử dụng không được dùng từ khóa cho một mục đích khác. Dưới đây là một số từ khóa thường dùng trong C, chi tiết về cách dùng và ý nghĩa các từ khóa này sẽ được trình bày trong các nội dung sau. break, case, char, continue, default, do, double, else, externe, float, for, goto, if, int, long, 5
  6. register, return, short, sizeof, static, struct, switch, typedef, unsigned, while Một đặc trưng của C là các từ khoá luôn luôn được viết bằng chữ thường. 1.1.3. Tên gọi Trong chương trình, để phân biệt các đối tượng (biến, hằng, hàm v.v.), ngôn ngữ lập trình cho phép người lập trình đặt tên cho các đối tượng cần sử dụng thông qua một cách thức khai báo nào đó. Hầu hết mọi thành phần được viết ra trong chương trình đều thuộc một trong hai dạng: 1) các thành phần được xây dựng sẵn trong ngôn ngữ (và công cụ lập trình) như từ khóa, tên các hàm chuẩn và 2) những thành phần do người lập trình tạo ra, những thành phần này là có thể là biến, hằng, hàm. Để mô tả các thành phần do người lập trình tạo ra thì việc đầu tiên cần làm đó là đặt tên cho đối tượng đó. Trong C (và hầu hết với mọi ngôn ngữ lập trình) việc đặt tên phải tuân theo một số qui tắc sau: - Không chứa khoảng trống (space) ở giữa tên; - Không được trùng với từ khóa; - Bắt đầu bằng một chữ cái hoặc dấu gạch dưới; - Độ dài tối đa của tên là giới hạn (tùy thuộc vào ngôn ngữ và biên dịch); Ví dụ về một số tình huống đặt tên như sau: - Các tên gọi sau đây là đúng: i, i1, j, tinhoc, tin_hoc, luu_luong; - Các tên gọi sau đây là sai: 1i, tin hoc, luu-luong-nuoc; - Các tên gọi sau đây là khác nhau: ha_noi, Ha_noi, HA_Noi, HA_NOI; Chú ý: Trong C người lập trình được phép đặt tên trùng với tên của một số đối tượng đã có trong một thư viện chuẩn nào đó, tuy nhiên khi đó ý nghĩa của tên chuẩn không còn giá trị trong một phạm vi nào đó. 1.1.4. Câu lệnh Câu lệnh là đơn vị cơ bản của một ngôn ngữ lập trình. Trong trường hợp đặc biệt, nó cũng có thể trở thành một đơn vị thao tác của máy tính điện tử hay còn gọi là một chỉ thị. Trong C, các câu lệnh được ngăn cách với nhau bởi dấu chấm phẩy (;). Có thể phân câu lệnh thành hai loại: lệnh đơn và các lệnh điều khiển. Lệnh đơn là một thao tác nhằm thực hiện một công việc nào đó, ví dụ như: lệnh gán, các câu lệnh nhập/xuất dữ liệu. Sau đây là ví dụ về ba lệnh đơn trong C: delta = b*b - 4*a*c; printf("Hello World!"); scanf("%d %d",&a,&b); Lệnh điều khiển là các lệnh được dùng để điều khiển trình tự thực hiện các thao tác trong chương trình. Lệnh điều khiển thường bao gồm: điều khiển chọn - 6
  7. để rẽ một trong hai hoặc nhiều nhánh, điều khiển lặp - để thực hiện lặp đi lặp lại theo điều kiện nào đó. Ví dụ, sau đây là lệnh điều khiển chọn, lệnh if, trong C: if (delta 0 */ { x1= (-b+sqrt(delta))/(2*a); x2= (-b-sqrt(delta))/(2*a); } Chú ý: - Chú thích dùng các cặp ký tự /* */ không được phép sử dụng lồng 7
  8. nhau; - Nên viết chú thích để không mất thời gian đọc lại chương trình khi cần thiết. Các chú thích sẽ được bỏ qua khi biên dịch; 1.2. CÁC KIỂU DỮ LIỆU CƠ BẢN 1.2.1. Dữ liệu và kiểu dữ liệu Máy tính ngày nay cho phép làm việc với hầu hết tất cả các loại dữ liệu mà con người phải xử lý hàng ngày, ví dụ một số loại dữ liệu có thể kể đến như: số thực, số nguyên, ký tự, xâu ký tự, logic, ngày tháng. Ngôn ngữ lập trình thường cho phép làm việc với một số (hoặc tất cả) các loại dữ liệu kể trên; và mỗi loại dữ liệu được thể hiện bằng từ khóa mô tả kiểu dữ liệu (data type) đó, các kiểu dữ liệu này được gọi là các kiểu dữ liệu cơ bản hay nguyên thủy của ngôn ngữ. Bảng 1-1 Các kiểu cơ bản trong C Kiểu dữ Tên kiểu Số Miền giá trị liệu (từ khóa) byte Từ Đến Ký tự char 1 -128 128 unsigned 1 0 255 char Số int 2 -32767 32768 nguyên unsigned int 2 0 65535 short 2 -32767 32768 long 4 -215 215-1 Số thực float 4 ±10-37 ±1038 double 8 ±10-307 ±10+308 Trong C, các kiểu dữ liệu cơ bản bao gồm: kiểu ký tự, kiểu số nguyên, kiểu số thực; nội dung cơ bản về các kiểu dữ liệu này được thể hiện như Bảng 1-1. Trong ngôn ngữ lập trình, mỗi kiểu dữ liệu được quan tâm ở các khía cạnh: tên gọi - để sử dụng khi khai báo; số byte trong bộ nhớ cần dùng cho mỗi biến - để sử dụng hợp lý bộ nhớ của máy tính; miền giá trị - để xem xét về sự phù hợp của miền giá trị với đối tượng mà nó mô tả; và các phép toán (operator) có thể thực hiện đối với kiểu dữ liệu đó. Ngoài các kiểu dữ liệu cơ bản thì hầu hết các ngôn ngữ đều định nghĩa sẵn hoặc cho phép người lập trình tự định nghĩa các kiểu dữ liệu phức tạp hơn từ những kiểu cơ bản; các kiểu này được gọi chung là kiểu dữ liệu có cấu trúc. Các kiểu dữ liệu có cấu trúc sẽ được trình bày trong các phần sau của tài liệu. 1.2.2. Kiểu ký tự Bảng mã ASCII là một bảng quy ước về mã cho các chữ cái La tinh, những ký tự, kí hiệu thường dùng khác. Ký tự, kí hiệu, qui ước, chữ cái trong bảng mã ASCII được gọi chung là ký tự (Character). Người ta đưa ra bảng mã ASCII để thống nhất về cách mã hóa đối với các ký tự được sử dụng trên máy tính, và như mô tả trong tên bảng mã, ASCII - American Standard Code for Information 8
  9. Interchange, thì nó được hiểu là mã tiêu chuẩn dùng trong việc trao đổi thông tin của Mỹ. Bảng mã ASCII chuẩn có 128 ký tự bao gồm các ký tự điều khiển (không hiển thị), và các ký tự hiển thị được. Bảng mã ASCII mở rộng có 256 ký tự bao gồm 128 ký tự của bảng mã ASCII chuẩn và 128 ký tự mở rộng gồm nhiều ký tự có dấu, ký tự trang trí khác. Bảng 95 ký tự hiển thị được của bảng mã ASCII được cho như Bảng 1-2. Trong C kiểu ký tự được hiểu theo hai cách, một là ký tự trong bảng mã ASCII và hai là mã ASCII của ký tự đó. Ví dụ, nếu ch là một biến kiểu ký tự thì lệnh gán ch = ’A’ hoặc ch = 65 có ý nghĩa như nhau vì mã ASCII của ký tự ‘A’ là 65 (xem Bảng 1-2). Theo Bảng 1-1 ta thấy có hai loại dữ liệu kiểu kí tự là char với miền giá trị từ -128 đến 127 và unsigned char (kí tự không dấu) với miền giá trị từ 0 đến 255. Trường hợp một biến được gán giá trị vượt ra ngoài miền giá trị của kiểu thì giá trị của biến sẽ được tính theo mã bù - (256 - c). Ví dụ nếu gán cho char ch giá trị 179 (vượt khỏi miền giá trị đã được qui định của char) thì giá trị thực sự được lưu trong máy sẽ là - (256 - 179) = -77. Bảng 1-2 Các kí tự hiển thị được trong bảng mã ASCII Mã Ký tự Mã Ký tự Mã Ký tự Mã Ký tự Mã Ký tự 32 ␠ 51 3 70 F 89 Y 108 l 33 ! 52 4 71 G 90 Z 109 m 34 " 53 5 72 H 91 [ 110 n 35 # 54 6 73 I 92 \ 111 o 36 $ 55 7 74 J 93 ] 112 p 37 % 56 8 75 K 94 ^ 113 q 38 & 57 9 76 L 95 _ 114 r 39 ' 58 : 77 M 96 ` 115 s 40 ( 59 ; 78 N 97 a 116 t 41 ) 60 81 Q 100 d 119 w 44 , 63 ? 82 R 101 e 120 x 45 - 64 @ 83 S 102 f 121 y 46 . 65 A 84 T 103 g 122 z 47 / 66 B 85 U 104 h 123 { 48 0 67 C 86 V 105 i 124 | 49 1 68 D 87 W 106 j 125 } 50 2 69 E 88 X 107 K 126 ~ Một ví dụ về việc sử dụng kiểu char và unsigned char trong C như sau: char c, d; // c, d duoc phep gan gia tri tu -128 đen 127 unsigned char e,f; // e đuoc phep gan gia tri tu 0 đen 255 9
  10. c = 65 ; d = 179; // d có giá tri ngoài mien cho phép e = 179; f = 330; // f có giá tri ngoài mien cho phép printf("%c,%d",c,c); // in ra chu cái 'A' và giá tri so 65 printf("%c,%d",d,d); // in ra là kí tu '|' và giá tri so -77 printf("%c,%d",e,e); // in ra là kí tu '|' và giá tri so 179 printf("%c,%d",f,f); // in ra là kí tu 'J' và giá tri so 74 Từ ví dụ trên ta thấy một biến nếu được gán giá trị ngoài miền giá trị cho phép sẽ dẫn đến kết quả không theo suy nghĩ thông thường. Do vậy nên ước lượng chính xác nhu cầu và miền giá trị của kiểu dữ liệu khi sử dụng; ví dụ nếu muốn sử dụng biến có giá trị từ 128 255 thì nên khai báo biến dưới dạng kí tự không dấu (unsigned char). Với nhiều ngôn ngữ thì ký tự được hiểu là những ký tự của bảng mã ASCII. 1.2.3. Kiểu số nguyên Trong C, các số nguyên được phân chia thành bốn kiểu khác nhau với các miền giá trị tương ứng được cho trong Bảng 1-1. Các kiểu số nguyên gồm có số nguyên ngắn (short), số nguyên (int), số nguyên không dấu (unsigned int) sử dụng 2 byte và số nguyên dài (long) sử dụng 4 byte. Kiểu số nguyên còn được chia thành hai loại có dấu và không dấu với cùng số byte phải dùng nhưng có miền giá trị khác nhau; mục đích của việc này là nhằm tạo sự mền dẻo, thuận tiện cho người sử dụng có thể tiết kiện được tài nguyên máy tính mà vẫn giải quyết được vấn đề đặt ra. Các phép toán trong C có thể thực hiện trên số nguyên được đề cập trong các nội dung sau của tài liệu. 1.2.4. Kiểu số thực Để sử dụng số thực ta cần khai báo kiểu float hoặc double, miền giá trị của các biến kiểu này được mô tả trong Bảng 1-1. Các giá trị số kiểu double được gọi là số thực với độ chính xác gấp đôi vì với kiểu dữ liệu này máy tính có cách biểu diễn khác so với kiểu float để đảm bảo số số lẻ sau một số thực có thể tăng lên nhằm thể hiện sự chính xác cao hơn so với số kiểu float. Tuy nhiên, trong các bài toán thông dụng thường ngày độ chính xác của số kiểu float là đủ dùng. 1.3. BIẾN, HẰNG VÀ BIỂU THỨC 1.3.1. Biến Biến là đối tượng được tạo ra trong chương trình dùng để lưu trữ giá trị thuộc một kiểu nào đó cần thiết cho quá trình xử lý và tính toán. Giá trị của biến có thể thay đổi trong quá trình tính toán. Có thể khẳng định biến là đối tượng quan trọng nhất trong chương trình, không chỉ đơn thuần dùng để lưu trữ dữ liệu mà việc tổ chức các biến còn quyết định đến tính hiệu quả của thuật toán và chương trình. Điều này được khẳng định qua tiêu đề cuốn sách “Giải thuật + Cấu trúc dữ liệu = Chương trình” của giáo sư Niklaus Emil Wirth, câu nói được hiểu như một định lý trong lĩnh vực lập trình. Trong C, biến phải được khai báo trước khi sử dụng. Việc khai báo nhằm cung cấp các thuộc tính của một biến như: tên gọi (tên biến), kiểu của biến, và vị 10
  11. trí khai báo biến đó cũng có ý nghĩa nhất định. Thông thường với nhiều ngôn ngữ lập trình tất cả các biến phải được khai báo ngay từ đầu chương trình, tuy nhiên để thuận tiện C cho phép khai báo biến ngay bên trong chương trình. Với C, khi cần, người sử dụng có thể khai báo một biến tại bất kỳ vị trí nào trong chương trình để sử dụng. 1. Khai báo Trong C, khai báo biến được viết theo cú pháp mô tả sau đây. Cú pháp: Kiểu Tên_biến_1 [,Tên_biến_2, Tên_biến_3 ]; Trong đó: - Kiểu: Là từ khóa qui định cho kiểu dữ liệu cần dùng. Từ khóa tương ứng với các kiểu dữ liệu cơ bản được đề cập trong Bảng 1-1; - Tên_biến_1: Tên biến do người sử dụng tự đặt. Tên biến phải được viết theo quy tắc viết tên đã mô tả trong phần 1.1.3 - Tên gọi. Quy tắc cơ bản là không được chứa khoảng trống, không bắt đầu bằng chữ số, và không (nên) dùng chữ cái Tiếng Việt có dấu. - Khi cần khai báo nhiều biến có cùng kiểu thì có thể liệt kê tên các biến đó, Tên_biến_2, Tên biến_3, , sau Tên_biến_1 và mỗi biến cách nhau bởi một dấu phảy (,). - Trong C dấu chấm phảy (;) cuối lệnh trên là bắt buộc. Ví dụ xét hai dòng lệnh sau: int a, b, c; float delta; Hai lệnh trên khai báo các biến a, b, c có kiểu là số nguyên và delta có kiểu là số thực. Các khai báo trong ví dụ trên không xác định giá trị ban đầu cho tất cả các biến, khi được tạo ra chúng nhận một giá trị (ngẫu nhiên) nào đó. Trong C cho phép khai báo biến kết hợp với việc xác định giá trị ban đầu cho biến đó theo cú pháp mô tả dau đây. Cú pháp: Kiểu Tên_biến_1 = giá_trị_1 [,Tên_biến_2 = giá_trị_2, ]; Trong đó: - Kiểu, Tên_biến_1, Tên_biến_2 được hiểu như trên; - giá_trị_1, giá_trị_2 là các biểu thức cho kết quả thuộc miền giá trị của Tên_biến_1, Tên_biến_2 Chú ý: Khi đặt tên biến, ngoài việc phải tuân theo quy tắc đặt tên của ngôn ngữ, nên đặt sao cho tên đó có tính gợi nhớ để thuận tiện cho sử dụng về sau. Tuy vậy thì không nên dùng tên biến quá dài, bởi nó dễ làm khồng kềnh các biểu thức về sau. Biến delta trong ví dụ trên được đặt tên như vậy với ý định dùng nó để lưu trữ giá trị delta theo cách tính nghiệm của phương trình bậc hai. 2. Phạm vi tác động của biến Như đã đề cập ở trên, mỗi biến khi khai báo ngoài những thuộc tính đã biết như tên, kiểu thì vị trí trong chương trình của biến đó cũng có ý nghĩa nhất định. 11
  12. Ý nghĩa về vị trí khai báo của biến chính là phạm vi tác động của biến. Một biến xuất hiện trong chương trình có thể được sử dụng bởi hàm này nhưng không được bởi hàm khác hoặc bởi cả hai, điều này phụ thuộc chặt chẽ vào vị trí nơi biến được khai báo. Một nguyên tắc đầu tiên là biến sẽ có tác dụng kể từ vị trí nó được khai báo cho đến hết khối lệnh chứa nó; khối lệnh ở đây được hiểu là một lệnh hợp thành, một hàm, một chương trình. Nội dung chi tiết về vấn đề này sẽ được trình bày chi tiết trong Chương 4 - Hàm và Cấu trúc chương trình. 1.3.2. Hằng Hằng, cũng giống như biến, là những đối tượng được tạo ra trong chương trình dùng để lưu trữ giá trị thuộc một kiểu nào đó cần thiết cho quá trình tính toán và xử lý. Tuy nhiên khác với biến là giá trị của hằng được xác định khi khai báo và không thể thay đổi được trong chương trình. Tương ứng với mỗi kiểu dữ liệu ngôn ngữ lập trình qui định cách biểu diễn hằng của kiểu đó. Các hằng thường dùng trong C gồm có: hàng ký tự, hàng số nguyên, hằng số thực, hằng xâu ký tự. 1. Hằng số nguyên Ngôn ngữ C cho phép viết hằng số nguyên ở dạng thập phân, bát phân và thập lục phân. - Dạng thập phân: Dùng 10 chữ số 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 để biểu diễn như chúng ta vẫn sử dụng hàng ngày; ví dụ số 2012, 365, -328, 40000. Với các hằng nguyên kể trên, số 40000 vượt quá giới hạn số nguyên int (lớn nhất 32767) khi đó C tự động dùng kiểu long để biểu diễn. Trường hợp muốn chủ động biểu diễn số nguyên dạng long thì thêm vào chữ cái l hay L sau cách viết thông thường; ví dụ 2012l, 365L. - Dạng bát phân: Dùng các chữ số 0, 1, 2, 3, 4, 5, 6, 7 để biểu diễn. Không giống cách biểu diễn thông thường của số thập phân, C quy ước việc biểu diễn số bát phân như sau: [dấu]0 Trong đó dấu: để chỉ ra số âm (dấu -) hay dương (dấu + hoặc không dấu); 0: là ký hiệu bắt buộc; chữ số: các chữ số 0 đến 7. Ví dụ: 0345 là số 345 trong hệ bát phân (= 3*82 + 4*81 + 5*80 = 229 trong hệ thập phân). - Dạng thập lục phân: Sử dụng 10 ký số từ 0 đến 9 và 6 ký tự A, B, C, D, E ,F để biểu diễn. C quy ước việc biểu diễn số thập lục phân như sau: [dấu]0x Trong đó: dấu: để chỉ ra số âm (dấu -) hay dương (dấu + hoặc không dấu); 0x: là ký hiệu bắt buộc; Ký tự: gồm các chữ số 0 đến 9 và 6 chữ cái A, B, C, D, E, F. 12
  13. Ví dụ: 0x34F là số 34F trong hệ thập lục phân (= 3*162 + 4*161 + 15*160 = 847 trong hệ thập phân). Chú ý: Ngoài những điều trên cần lưu ý: - Hằng số nguyên 4 byte (long): Số long (số nguyên dài) được biểu diễn như số int trong hệ thập phân và kèm theo ký tự l hoặc L. Một số nguyên nằm ngoài miền giá trị của số int ( 2 bytes) là số long ( 4 bytes) ; ví dụ: 45345L hay 45345l hay 45345. - Các hằng số còn lại: Viết như cách viết thông thường (không có dấu phân cách giữa 3 số) . 2. Hằng số thực Số thực bao gồm các giá trị kiểu float, double, long double được thể hiện theo hai cách sau: - Cách 1: Sử dụng cách viết thông thường mà chúng ta đã sử dụng trong các môn Toán, Lý, Điều cần lưu ý là sử dụng dấu thập phân là dấu chấm (.); ví dụ: 123.34 -223.333 3.00 -56.0. - Cách 2: Sử dụng cách viết theo số mũ hay số khoa học. Một số thực được tách làm 2 phần, cách nhau bằng ký tự e hay E có dạng men hay mEn, trong đó: m là phần định trị là một số nguyên hay số thực được viết theo cách 1; n là phần mũ 10 là một số nguyên. Khi đó giá trị của số thực là: m*10n, ví dụ: 1234.56e-3 = 1.23456 (là số 1234.56 * 10-3), hoặc - 123.45E4 = -1234500 ( là -123.45 *104). 3. Hằng ký tự Hằng ký tự là một ký tự riêng biệt được viết trong cặp dấu nháy đơn (‘’). Mỗi một ký tự tương ứng với một giá trị trong bảng mã ASCII. Hằng ký tự cũng được xem như trị số nguyên. Ví dụ: ‘a’, ‘A’, ‘0’, ‘9’. Có thể thực hiện các phép toán số học trên 2 ký tự (thực chất là thực hiện phép toán trên giá trị ASCII của chúng). 4. Hằng chuỗi ký tự Hằng chuỗi ký tự là một chuỗi hay một xâu ký tự được đặt trong cặp dấu nháy kép ("). Ví dụ: "Ngon ngu lap trinh C", "Cong hoa xa hoi chi nghia Viet Nam". Chú ý: - Một chuỗi không có nội dung "" được gọi là chuỗi rỗng; - Khi lưu trữ trong bộ nhớ, một chuỗi được kết thúc bằng ký tự NULL (‘\0’: mã ASCII là 0); - Để biểu diễn ký tự đặc biệt bên trong chuỗi ta phải thêm dấu \ phía trước. Ví dụ: "I’m a student" phải viết "I\’m a student"; "Day la ky tu "dac biet"" phải viết "Day la ky tu \"dac biet\"". 5. Khai báo Hằng tham gia vào các biểu thức tính toán theo hai cách: 1) là một giá trị cụ thể trong biểu thức, 2) được xác định thông qua tên hằng. Khi hằng không xuất hiện nhiều lần thì nên sử dụng chúng theo cách 1, và ngược lại, khi hằng dùng 13
  14. đến nhiều lần thì nên sử dụng theo cách 2. Ví dụ, xét đoạn chương trình sau: delta = b*b - 4*a*c; x1= (-b+sqrt(delta))/(2*a); x2= (-b-sqrt(delta))/(2*a); Trong ví dụ trên, số 4 trong dòng lệnh đầu, số 2 trong hai dòng lệnh dưới là các hằng số được dùng trực tiếp trong các biểu thức. Để sử dụng theo cách 2 cần khai báo hằng theo cú pháp sau: Cú pháp: #define Tên_hằng giá_trị; Hoặc: const Kiểu Tên_hằng = giá_trị; Trong đó: - Kiểu: Là từ khóa qui định cho kiểu dữ liệu cần dùng; như phần trước đã nêu các kiểu dữ liệu cơ bản có thể là char, unsigned char, int, float, long, double ; - Tên hằng: Do người sử dụng tự định nghĩa, lưu ý rằng tên hằng phải tuân theo qui tắc đặt tên qui định bởi ngôn ngữ lập trình. - giá_trị: Giá trị thuộc miền giá trị của Kiểu đối với khai báo dạng const. Ví dụ có thể khai báo các hằng để sử dụng như sau: #define so_Pi 3.1415 const int Max = 1001; const char* monhoc = "Ky thuat lap trinh"; 1.3.3. Biểu thức Biểu thức là dãy kí hiệu kết hợp giữa các toán hạng (operand), toán tử (operator) và cặp dấu () theo một qui tắc nhất định. Các toán hạng là hằng, biến, hàm; toán tử là các phép toán số học, phép so sánh, phép logic, các phép toán trên tập ký tự và xâu ký tự. Ví dụ biểu thức tính delta: b*b - 4*a*c, biểu thức tính nghiệm của phương trình bậc hai (-b+sqrt(delta))/(2*a). Trong các ngôn ngữ lập trình phép toán trên các kiểu dữ liệu giống nhau nói chung là giống nhau, chúng thường khác nhau ở một số khía cạnh nhỏ như có hay không một toán tử nào đó mà ngôn ngữ khác không có. Đối với kiểu dữ liệu số, C cung cấp một cách phong phú nhất các phép toán như các toán tử tăng, giảm, toán tử bitwise so với ngôn ngữ khác do vậy phần nội dung các phép toán sau đây là định nghĩa bởi ngôn ngữ lập trình C. 1. Các phép toán số học - Các phép toán + (cộng), - (trừ), * (nhân), / (chia): được hiểu theo nghĩa thông thường trong số học. Kết quả trả về của các phép chia, a/b, phụ thuộc vào kiểu của các toán hạng a và b; nếu cả a và b là nguyên thì kết 14
  15. quả phép chia là lấy phần nguyên, nếu một trong hai toán hạng là thực thì kết quả phép chia trả về giá trị thực. Ví dụ 16/3 = 5, nhưng 16.0/3 = 16/3.0 = 16.0/3.0 = 5.3. - Phép toán % (lấy phần dư): a % b trả về kết quả là phần dư của phép chia a cho b. Ví dụ 16 % 3 =1, 6 % 2 =0. - Toán tử ++ (tăng) và (giảm): Đây là các phép toán một ngôi, toán tử ++ thêm 1 vào toán hạng của nó và trừ bớt 1; nói cách khác: ++ x giống như x = x + 1 và x giống như x = x - 1. Cả hai toán tử tăng và giảm đều có thể đặt trước hoặc sau toán hạng là một biến. Ví dụ: x = x + 1 có thể viết x ++ (hay ++ x). Tuy nhiên giữa việc đặt trước hoặc đặt sau có một khác biệt nhỏ; khi một toán tử tăng hay giảm đứng trước toán hạng của nó, thì giá trị của toán hạng sẽ được tăng hay giảm trước khi tham gia vào tính giá trị của biểu thức; nếu toán tử đi sau toán hạng, thì giá trị của toán hạng được tăng hay giảm sau khi nó tham gia vào tính giá trị của biểu thức. Ví dụ, nếu x = 10 thì lệnh y = ++ x sẽ cho y giá trị bằng 11 và sau đó x được tăng lên 1 giá trị; nhưng nếu thực hiện lệnh y = x ++ thì y sẽ nhận giá trị bằng 10, sau đó x sẽ được tăng lên 1 đơn vị. 2. Các phép so sánh và logic Đây là các phép toán mà giá trị trả lại là đúng hoặc sai. Nếu giá trị của biểu thức là đúng thì nó nhận giá trị 1, ngược lại là sai thì biểu thức nhận giá trị 0. Nói cách khác 1 và 0 là giá trị cụ thể của hai khái niệm “đúng”, “sai” ở trong C; mở rộng hơn C++ quan niệm một giá trị bất kỳ khác 0 là “đúng” và giá trị 0 là “sai”, đây được hiểu là hai hằng logic trong C. - Các phép so sánh là các phép toán hai ngôi và ký hiệu của chúng như sau: == (bằng nhau), != (khác nhau), > (lớn hơn), = (lớn hơn hoặc bằng), <= (nhỏ hơn hoặc bằng). Ví dụ, a == b là phép so sánh bằng, nó trả về kết quả đúng (true) nếu giá trị a bằng giá trị của b, trả về kết quả sai (false) trong tình huống ngược lại. Lưu ý trong phép so sánh thì hai toán hạng phải có cùng kiểu. - Các phép logic bao gồm các phép toán: && (AND), || (OR), và ! (NOT), trong đó && và || là các phép toán hai ngôi và ! là phép toán một ngôi. Bảng giá trị các phép toán logic được cho như Bảng 1.3. Bảng 1-3 Bảng giá trị các phép toán logic a b a&&b a||b !a 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 Ví dụ có thể viết các biểu thức logic sử dụng phép so sánh và logic như sau: 15
  16. 3 && (4 > 5) // = 0 vì có hạng thức (4>5) sai (3 >= 1) && (7) // = 1 vì cả hai hạng thức cùng đúng !1 // = 0 ! (4 + 3 = 6) // = 1 vì có một hạng thức (5) đúng (5 = 6) // = 0 vì cả hai hạng thức đều sai 3. Các toán tử bitwise Trong ngôn ngữ máy tính, các toán tử bitwise thực hiện tính toán (theo từng bit) trên một hoặc hai chuỗi bit, hoặc trên các số nhị phân. Với nhiều loại máy tính, việc thực thi các phép toán thao tác bit thường nhanh hơn so với khi thực thi các phép toán cộng, trừ, nhân, hoặc chia. Trong C/C++ các phép toán bitwise gồm có: & (AND), | (OR), ^ (XOR), ~ (NOT), >> (dịch phải), << (dịch trái). Các toán hạng trong phép toán bitwise phải là dạng số nguyên. - Toán tử & là toán tử hai ngôi dạng a & b, thực hiện phép AND từng cặp bit của a và b từ trái qua phải. Phép AND bit có kết quả như sau: 0 & 0 = 0 0 & 1 = 0 1 & 0 = 0 1 & 1 = 1 Ví dụ cho a = 5, biểu diễn bit của nó là 0101, b = 3 và biểu diễn bit của nó là 0011 khi đó kết quả phép a & b được thực hiện như sau: a 0101 = b 0011 = a & b 0001 = - Toán tử | là toán tử hai ngôi dạng a | b, thực hiện phép OR từng cặp bit của a và b từ trái qua phải. Phép OR bit có kết quả như sau: 0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1 Ví dụ cho a = 5, biểu diễn bit của nó là 0101, b = 3 và biểu diễn bit của nó là 0011 khi đó kết quả phép a | b được thực hiện như sau: a = 0101 b = 0011 a | b = 0111 - Toán tử ! là toán tử một ngôi dạng !a, thực hiện phép NOT từng bit của a. Phép NOT bit có kết quả như sau: ! 0 = 1 ! 1 = 0 16
  17. Ví dụ cho a = 5, biểu diễn bit của nó là 0101 khi đó kết quả phép !a được thực hiện như sau: a = 0101 !a = 1010 - Toán tử ^ là toán tử hai ngôi dạng a^b, thực hiện phép XOR từng cặp bit của a và b từ trái qua phải. Phép XOR bit có kết quả như sau: 0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0 Ví dụ cho a=5, biểu diễn bit của nó là 0101, b=8 và biểu diễn bit của nó là 1000 khi đó kết quả phép a^b được thực hiện như sau: a = 0101 b = 1000 a ^ b = 1101 - Toán tử >> là toán tử hai ngôi dạng a>>b, trong đó b là số bước dịch chuyển qua phải các biểu diễn bit của a.Ví dụ cho a=23 (char), biểu diễn bit của nó là 00010111 khi đó kết quả phép a>>1 được thực hiện như sau: Vị trí bit 7 6 5 4 3 2 1 0 a = 0 0 0 1 0 1 1 1 a >> 1 -> 0 0 0 0 1 0 1 1 1 -> a = 0 0 0 0 1 0 1 1 - Toán tử 9 ? 100 : 200; Sẽ gán cho y giá trị 100, và đoạn mã trên có thể thay bằng lệnh if như sau: x = 10 17
  18. if (x > >= & ^ | && || ?: = += -= *= /= , Thấp nhất 1.4. CẤU TRÚC MỘT CHƯƠNG TRÌNH ĐƠN GIẢN 1.4.1. Cấu trúc chung Với hầu hết các ngôn ngữ lập trình, chương trình đều được định nghĩa theo một cấu trúc chung nào đó. Một chương trình C có thể được đặt trong một hoặc 18
  19. 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. Trong C, 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à người lập trình 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 người lập trình định nghĩa và được dùng chung trong toàn bộ chương trình; - Danh sách các hàm của chương trình (do người lập trình 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 các nội dung sau. Phần cấu trúc chương trình hoàn chỉnh trong C sẽ được trình bày trong các chương sau. Ví dụ 1.1: Dưới đây là một đoạn chương trình đơn giản chỉ gồm một hàm chính là hàm main(). Nội dung của chương trình là viết ra màn hình dòng chữ: Hello Wolrd! #include #include int main() { printf("Hello World! "); getch(); return 0; } Trong ví dụ trên, các lệnh #include và #include cho phép khai báo sử dụng hai thư việc nguyên mẫu là stdio.h và conio.h với mục đích là sử dụng các hàm printf("Hello World!") để viết dòng chữ "Hello World!" ra màn hình và hàm getch() để dừng chương trình đợi người sử dụng nhấn phím bất kỳ trước khi kết thúc chương trình. Đây là tình huống đơn giản nhất của một chương trình, thực tế thì ngoài việc đưa kết quả ra màn hình thì mọi chương trình đều cần và yêu cầu người sử dụng nhập dữ liệu vào theo một cách nào đó; và chức năng tổng quát, hoàn chỉnh của một chương trình là cho phép người sử dụng mô tả dữ liệu vào, biến đổi dữ liệu đó theo yêu cầu và biểu diễn kết quả ở đầu ra; một cách đơn giản nhất thì kết quả ở đầu ra thường được biểu 19
  20. diễn trên màn hình, dữ liệu đầu vào được nhập qua bàn phím. Sau đây chúng ta sẽ xem xét khả năng xuất/nhập dữ liệu trên C. 1.4.2. Nhập/Xuất dữ liệu Trong bất kỳ ngôn ngữ lập trình nào, việc nhập giá trị cho các biến và in chúng ra sau khi xử lý có thể được làm theo hai cách: 1) Thông qua phương tiện nhập/xuất chuẩn (I/O - Input/Output), 2) Thông qua những tập tin. Nội dung chương này chỉ đề cập đến việc nhập/xuất dữ liệu thông qua thiết bị chuẩn, việc nhập xuất qua tập tin sẽ được trình bày trong các chương sau. Nhập và xuất (I/O) luôn là các thành phần quan trọng của bất kỳ chương trình nào, một chương trình nói chung, từ đơn giản đến phức tạp đều cần có khả năng nhập dữ liệu vào và hiển thị lại những kết quả của nó. Trong C, thư viện chuẩn cung cấp những thủ tục cho việc nhập và xuất, những hàm này quản lý các thao tác nhập/xuất cũng như các thao tác trên ký tự và chuỗi. Thiết bị nhập chuẩn thông thường là bàn phím. Thiết bị xuất chuẩn thông thường là màn hình (console). Hai trong số các hàm nhập xuất của C là: printf() - Hàm xuất có định dạng; scanf() - Hàm nhập có định dạng. 1. Hàm printf() Hàm printf() được dùng để hiển thị dữ liệu trên thiết bị xuất chuẩn - console (màn hình) - một cách có định dạng. Cú pháp: printf("Chuỗi_điều_khiển", Danh_sách_tham_số); Trong đó: - Chuỗi_điều_khiển: Chuỗi điều khiển; - Danh_sách_tham_số: Danh sách tham số bao gồm các hằng, biến, biểu thức hay hàm và được phân cách bởi dấu phẩy. Bảng 1-4 Một số mã định dạng sử dụng trong printf() Ðịnh dạng printf() scanf() Ký tự đơn (Single Character) %c %c Chuỗi (String) %s %s Số nguyên có dấu (Signed decimal integer) %d %d Số thập phân có dấu chấm động (Floating point) %f %f hoặc %e Số thập phân có dấu chấm động - Biểu diễn phần thập phân %lf %lf Số thập phân có dấu chấm động - Biểu diễn dạng số mũ %e %f hoặc %e Số thập phân có dấu chấm động (%f hoặc %e, con số nào ít %g hơn) 20
  21. Số nguyên không dấu (Unsigned decimal integer) %u %u Số thập lục phân không dấu (Dùng “ABCDEF”) %x %x (Unsigned hexadecimal integer) Số bát phân không dấu (Unsigned octal integer) %o %o Mỗi tham số trong Danh_sách_tham_số phải có một lệnh định dạng nằm trong Chuỗi_điều_khiển, những lệnh định dạng phải tương ứng với danh sách các tham số về số lượng, phù hợp về kiểu dữ liệu và đúng thứ tự. Chuỗi_điều_khiển phải luôn được đặt bên trong cặp dấu nháy kép "", và chứa một hay nhiều hơn ba thành phần dưới đây: 1. Ký tự văn bản (Text characters): Bao gồm các ký tự có thể in ra được và sẽ được in giống như ta nhìn thấy. Các khoảng trắng thường được dùng trong việc phân chia các trường (field) được xuất ra; 2. Lệnh định dạng: Định nghĩa cách thức các mục dữ liệu trong danh sách tham số sẽ được hiển thị. Một lệnh định dạng bắt đầu với một ký hiệu % và theo sau là mã định dạng cho mục dữ liệu. Dấu % được dùng trong hàm printf() để chỉ ra các đặc tả chuyển đổi. Các lệnh định dạng và các mục dữ liệu tương thích nhau theo thứ tự và kiểu từ trái sang phải. Bảng 1-4 liệt kê các mã định dạng khác nhau được hỗ trợ bởi câu lệnh printf(). 3. Các ký tự không in được: Bao gồm phím tab, dấu khoảng trắng và dấu xuống dòng. Ví dụ 1.2: Chương trình mô tả việc sử dụng hàm printf(). #include #include int main() { int a = 10; float b = 3.141592; char ch = ‘A’; printf("\nSo nguyen = %d", a); printf("\nSo thuc = %f", b); printf("\nKy tu = %c", ch); printf("\nXau ky tu"); printf("%s", "\nKy thuat lap trinh"); getch(); return 0; } Trên đây là một chương trình đơn giản dùng minh họa cho một chuỗi có thể được in theo lệnh in có định dạng printf(). Chương trình này cũng hiển thị một ký tự đơn, số nguyên và số thực. Bổ từ (Modifier) cho các lệnh định dạng trong printf(): Các lệnh định dạng có thể có bổ từ (modifier), để thay đổi các đặc tả chuyển đổi gốc, trong lệnh định dạng bổ từ được đặt sau dấu %. Sau đây là các bổ từ được chấp nhận trong câu 21
  22. lệnh printf(). Nếu có nhiều bổ từ được dùng thì chúng tuân theo trình tự sau: - Bổ tử “-”: Dữ liệu sẽ được canh trái bên trong không gian dành cho nó, chúng sẽ được in bắt đầu từ vị trí ngoài cùng bên trái. - Bổ từ xác định độ rộng: Chúng có thể được dùng với kiểu: float, double hay char array. Bổ từ xác định độ rộng là một số nguyên xác định độ rộng nhỏ nhất của trường dữ liệu. Các dữ liệu có độ rộng nhỏ hơn sẽ cho kết quả canh phải trong trường dữ liệu. Các dữ liệu có kích thước lớn hơn sẽ được in bằng cách dùng thêm những vị trí cho đủ yêu cầu.Ví dụ, %10f là lệnh định dạng cho các mục dữ liệu kiểu số thực với độ rộng trường dữ liệu thấp nhất là 10. - Bổ từ xác định độ chính xác: Chúng có thể được dùng với kiểu float, double hay mảng ký tự (char array, string). Bổ từ xác định độ rộng chính xác được viết dưới dạng .m với m là một số nguyên. Nếu sử dụng với kiểu float và double, chuỗi số chỉ ra số con số tối đa có thể được in ra phía bên phải dấu chấm thập phân. Nếu phần phân số của các mục dữ liệu kiểu float hay double vượt quá độ rộng con số chỉ trong bổ từ, thì số đó sẽ được làm tròn. Nếu chiều dài chuỗi vượt quá chiều dài chỉ định thì chuỗi sẽ được cắt bỏ phần dư ra ở phía cuối. Một vài số không (0) sẽ được thêm vào nếu số con số thực sự trong một mục dữ liệu ít hơn được chỉ định trong bổ từ. Tương tự, các khoảng trắng sẽ được thêm vào cho chuỗi ký tự. Ví dụ, %10.3f là lệnh định dạng cho mục dữ liệu kiểu float, với độ rộng tối thiểu cho trường dữ liệu là 10 và 3 vị trí sau phần thập phân. - Bổ từ ‘0’: Theo mặc định, việc thêm vào một trường được thực hiện với các khoảng trắng. Nếu người dùng muốn thêm vào trường với số không (0), bổ từ này phải được dùng. - Bổ từ ‘l’: Bổ từ này có thể được dùng để hiển thị số nguyên như: long int hay một tham số kiểu double. Mã định dạng tương ứng cho nó là %ld. - Bổ từ ‘h’: Bổ từ này được dùng để hiện thị kiểu short integer. Mã định dạng tương ứng cho nó là %hd. - Bổ từ ‘*’: Bổ từ này được dùng khi người dùng không muốn chỉ trước độ rộng của trường mà muốn chương trình xác định nó. Nhưng khi đi với bổ từ này, một tham số được yêu cầu phải chỉ ra độ rộng trường cụ thể. Ví dụ 1.3: Cách dùng bổ từ với số nguyên. #include #include int main() { printf("So 555 duoc in ra o ca dang khac nhau:\n"); printf("Khong su dung bo tu: \n"); printf("[%d]\n", 555); printf("Voi bo tu -:\n"); printf(" [%-d]\n", 555); 22
  23. printf("Voi bo tu 10:\n"); printf(" [%10d]\n", 555); printf("Voi bo tu 0: \n"); printf(" [%0d]\n", 555); printf("Voi bo tu 0,10:\n"); printf(" [%010d]\n", 555); printf("Voi bo tu -, 0 va 10:\n"); printf(" [%-010d]\n", 555); getch(); return 0; } Khi thực hiện ví dụ trên chúng ta có kết quả như Hình 1.1. Hình 1.1. Kết quả thực hiện ví dụ 1.3 2. Hàm scanf() Hàm scanf() được sử dụng để nhập dữ liệu từ thiết bị nhập. Cú pháp: scanf("Chuỗi_điều_khiển", Danh_sách_tham_số); Trong đó: - Chuỗi_điều_khiển: Chuỗi điều khiển; - Danh_sách_tham_số: Danh sách tham số bao gồm các hằng, biến, biểu thức hay hàm và được phân cách bởi dấu phẩy. Việc dùng chuỗi điều khiển và danh sách các tham số trong scanf() và printf() là tương đối giống nhau. Những điểm khác nhau giữa hai hàm này sẽ được xem xét trong phần sau. Sự khác nhau trong danh sách tham số giữa printf() và scanf(): Hàm printf() dùng các tên biến, hằng số, hằng chuỗi và các biểu thức, nhưng scanf() sử dụng những con trỏ tới các biến. Một con trỏ tới một biến là một mục dữ liệu chứa đựng địa chỉ của nơi mà biến được cất giữ trong bộ nhớ. Những con trỏ sẽ được bàn luận chi tiết ở chương sau. Khi sử dụng scanf() cần tuân theo những quy tắc cho danh sách tham số: - Nếu ta muốn nhập giá trị cho một biến có kiểu dữ liệu cơ bản, gõ vào tên biến cùng với ký hiệu & trước nó; - Khi nhập giá trị cho một biến thuộc kiểu dữ liệu dẫn xuất (không phải 23
  24. thuộc bốn kiểu cơ bản char, int, float, double), không sử dụng & trước tên biến. Sự khác nhau trong lệnh định dạng giữa printf() và scanf(): - Không có tùy chọn %g; - Mã định dạng %f và %e có cùng hiệu quả tác động. Cả hai nhận một ký hiệu tùy chọn, một chuỗi các con số có hay không có dấu chấm thập phân và một trường số mũ tùy chọn; Cách thức hoạt động của scanf(): Hàm scanf() sử dụng những ký tự không được in như ký tự khoảng trắng, ký tự phân cách (tab), ký tự xuống dòng để quyết định khi nào một trường nhập kết thúc và bắt đầu. Có sự tương ứng giữa lệnh định dạng với những trường trong danh sách tham số theo một thứ tự xác định, bỏ qua những ký tự khoảng trắng bên trong. Do đó, đầu vào có thể được trải ra hơn một dòng, miễn là chúng ta có ít nhất một ký tự phân cách, khoảng trắng hay hàng mới giữa các trường nhập vào. Nó bỏ qua những khoảng trắng và ranh giới hàng để thu được dữ liệu. Ví dụ 1.4: Chương trình sau mô tả việc dùng hàm scanf(). #include #include int main() { int a; float d; char ch; char ten[40]; printf("Hay nhap vao so nguyen a, so thuc d, ky tu ch va xau Ten: \n"); scanf("%d %f %c %s", &a, &d, &ch, ten); printf("\nCac gia tri nhan duoc la: %d, %f, %c, %s",a,d,ch,ten); getch(); return 0; } Trong chương trình trên, dòng lệnh scanf("%d %f %c %s", &a, &d, &ch, ten) có chuỗi điều khiển là "%d %f %c %s" và danh sách tham số là &a, &d, &ch, ten, như vậy dòng lệnh yêu cầu nhập vào bốn giá trị cho các biến a, b, ch và ten, với kiểu dữ liệu tương ứng là nguyên, thực, ký tự và xâu ký tự. 1.4.3. Ví dụ Để kết thúc phần trình bày về cấu trúc chung của một chương trình trong C/C++, phần này sẽ giới thiệu một chương trình đơn giản nhưng mang đầy đủ các đặc trưng của một chương trình nói chung là: cho phép nhập dữ liệu, xử lý và in kết quả ra. Ví dụ 1.5: Viết chương trình cho phép nhập vào hai số a và b, sau đó trình bày kết quả và phương pháp cộng hai số đó theo hình thức sau (với a = 876 và b = 7655): 876 24
  25. + 7655 8531 #include #include #include int main() { int a,b,tong; printf("Nhap vao a va b: "); scanf("%d %d",&a,&b); printf("Ket qua theo phuong phap cong\n\n"); tong=a+b; printf("%20d\n",a); printf("%10s\n","+"); printf("%20d\n",b); printf("%20s\n"," "); printf("%20d\n\n",tong); printf("Nhan phim bat ky de ket thuc!"); getch(); return 0; } Chương trình trên là một ví dụ hoàn chỉnh về một chương trình máy tính bao gồm các khối chức năng chính: nhập dữ liệu vào, xử lý dữ liệu, và kết xuất kết quả. Trong ví dụ trên, lệnh system("cls") cho phép xóa sạch màn hình, đưa con trỏ về cuối; lệnh printf("Nhap vao a va b:") và scanf("%d %d",&a,&b) cho phép hiển thị dòng nhắc “Nhap vao a va b” và yêu cầu người sử dụng nhập vào hai số nguyên, hai số đó lần lượt được gán vào biến a và b. Lệnh tong=a+b có thể hiểu như là việc xử lý dữ liệu, tính tổng hai số nhập vào và gán vào biến tong. Phần cuối của ví dụ trên là việc trình bày kết quả như yêu cầu đặt ra. 1.5. ĐIỀU KHIỂN TRÌNH TỰ THỰC HIỆN CÁC LỆNH Một chương trình máy tính, dù đơn giản hay phức tạp, đều có thể khái quát là một tập hữu hạn các lệnh được thực hiện bắt đầu ở một vị trí xác định và theo một trình tự nào đó dựa trên ba dạng điều khiển cơ bản là: 1) tuần tự, 2) điều khiển chọn và 3) điều khiển lặp. 1.5.1. Tuần tự Trong tất cả các ngôn ngữ lập trình, các lệnh trong chương trình được thực hiện theo thứ tự từ trên xuống dưới ngoại trừ khi thực hiện các lệnh điều khiển (điều khiển chọn, điều khiển lặp - được giới thiệu ở phần sau). Ví dụ, xét đoạn chương trình sau: 25
  26. (1) printf("Nhap vao a va b:"); (2) scanf("%d %d",&a,&b); (3) printf("Ket qua theo phuong phap cong\n\n"); (4) tong=a+b; (5) printf("%20d\n\n",tong); Trong đoạn chương trình trên, các lệnh được thực hiện theo thứ tự từ trên xuống dưới: - Đầu tiên lệnh số (1) - printf("Nhap vao a va b:"); được thực hiện và kết quả hiển thị trên màn hình là: Nhap vao a va b: - Tiếp đến lệnh số (2) - scanf("%d %d",&a,&b); được thực hiện, lệnh này yêu cầu người sử dụng nhập vào hai số nguyên a và b; giả sử người sử dụng nhập vào 5 và 6 khi đó trên màn hình kết quả hiển thị: Nhap vao a va b: 5 6 - Tiếp theo dòng lệnh số (3) - printf("Ket qua theo phuong phap cong\n\n"); được thực hiện và khi đó kết quả hiển thị trên màn hình là: Nhap vao a va b: 5 6 Ket qua theo phuong phap cong - Tiếp theo dòng lệnh số (4) - tong = a+b; được thực hiện, kết quả hiển thị ra màn hình ở bước này là không thay đổi. - Tiếp theo dòng lệnh số (5) - printf("%20d\n\n",tong); được thực hiện và khi đó kết quả hiển thị trên màn hình là: Nhap vao a va b: 5 6 Ket qua theo phuong phap cong 11 Mặc dù các lệnh điều khiển chọn, điều khiển lặp là không thể thiếu trong mọi ngôn ngữ lập trình thì khi nhìn chương trình ở mức độ tổng quát ta thấy trình tự thực hiện chính của chương trình là tuần tự từ đầu đến cuối, có điểm bắt đầu, có điểm kết thúc và điểm kết thúc luôn diễn ra sau bước khởi tạo ban đầu và quá trình xử lý trung gian. 1.5.2. Điều khiển chọn Nói một cách tổng thể việc thực hiện chương trình là hoạt động tuần tự như đã đề cập ở trên. Tuy nhiên, khi thực hiện các công việc một cách chi tiết thì hầu hết mọi vấn đề đều cần có các điều khiển dạng lựa chọn một trong nhiều khả năng hoặc lặp đi lặp lại nhiều lần một công việc nào đó. Phần này sẽ đề cập đến các lệnh điều khiển lựa chọn trong C/C++ là lệnh if và switch. 1. Lệnh if Lệnh if cho phép chương trình có thể thực hiện công việc này hay công việc khác tùy thuộc vào điều kiện nào đó của dữ liệu là đúng hay sai. Nói cách khác câu lệnh if cho phép người lập trình lựa chọn một trong hai công việc cần làm tùy 26
  27. thuộc vào điều kiện logic nào đó. Cú pháp (dạng 1): if (Biểu_thức_logic) {Các lệnh cho công việc 1} else {Các lệnh cho công việc 2} Hoặc (dạng 2): if (Biểu_thức_logic) {Các lệnh cho công việc 1} Trong đó: - Biểu_thức_logic: Biểu thức logic, biểu thức này sẽ trả về một trong hai giá trị là đúng (true) hoặc (false); - Các lệnh cho công việc 1: Các lệnh nhằm thực hiện công việc thứ nhất khi Biểu_thức_logic trả về kết quả là đúng; - Các lệnh cho công việc 2: Các lệnh nhằm thực hiện công việc thứ hai khi Biểu_thức_logic trả về kết quả là sai (nếu lệnh if được viết theo dạng 1). Cách thực hiện: 1. Đầu tiên chương trình sẽ tính giá trị của Biểu_thức_logic, nếu kết quả là đúng thì Các lệnh cho công việc 1 sẽ được thực hiện; 2. Nếu Biểu_thức_logic kết quả trả về là sai và câu lệnh if viết theo dạng 1 thì Các lệnh cho công việc 2 sẽ được thực hiện. 3. Kết thúc lệnh if. Với cú pháp dạng 2 thì khi Biểu_thức_logic trả về kết quả là sai thì chương trình cũng không thực hiện bất kỳ công việc gì. Ví dụ 1.6: Viết chương trình cho phép giải phương trình bậc nhất a*x + b = 0. #include #include int main () { int a, b; // bieu dien cac he so float x; // bieu dien nghiem cua phuong trinh printf("Nhap vao cac he so a,b:"); scanf("%d %d",&a,&b); if (a==0) { printf("Phuong trinh khong co nghiem"); } else { x=(float)(-b)/a; 27
  28. printf("Phuong trinh co nghiem x = %0.5f",x); } getch(); return 0; } Trong ví dụ trên, lệnh if (a==0) cho phép kiểm tra xem nếu a = 0 thì chương trình sẽ in ra dòng thông báo "Phuong trinh khong co nghiem" và ngược lại (else) thì lệnh x=(float)(-b)/a tính nghiệm và lệnh printf("Phuong trinh co nghiem x = %0.5f",x) in ra kết quả đó. Ví dụ 1.7: Viết chương trình cho phép nhập vào một số nguyên dương là tháng trong năm và in ra số ngày của tháng đó, biết rằng tháng 1, 3, 5, 7, 8, 10, 12 có 31 ngày; tháng 4, 6, 9, 10 có 30 ngày; và tháng 2 có 28 hoặc 29 ngày. #include #include int main () { int thg; printf("Nhap vao thang trong nam !"); scanf("%d",&thg); if(thg==1||thg==3||thg==5||thg==7||thg==8||thg==10||thg==12) { printf("\n Thang %d co 31 ngay",thg); } else { if (thg==4||thg==6||thg==9||thg==11) printf("\n Thang %d co 30 ngay",thg); else if (thg==2) printf("\n Thang %d co 28 hoac 29 ngay",thg); else printf("Khong co thang %d",thg); } getch(); return 0; } Ví dụ trên minh họa việc sử dụng câu lệnh if else , các lệnh: { if (thg==4||thg==6||thg==9||thg==11) printf("\n Thang %d co 30 ngay",thg); else if (thg==2) 28
  29. printf("\n Thang %d co 28 hoac 29 ngay",thg); else printf("Khong co thang %d",thg); } sau từ khóa else thứ nhất chỉ được thực hiện nếu tháng nhập vào không phải là một trong các giá trị 1, 3, 5, 7, 8, 10, 12. Lệnh printf("\n Thang %d co 30 ngay",thg) chỉ được thực hiện khi tháng nhập vào không phải là một trong các giá trị 1, 3, 5, 7, 8, 10, 12 mà thuộc vào một trong các giá trị 4, 6, 9, 11. Lệnh printf("Khong co thang %d",thg) được thực hiện khi tháng nhập vào không phải là giá trị nằm trong khoảng từ 1 đến 12. Chú ý: - Khi biểu diễn Biểu_thức_logic, nên nhớ rằng phép so sánh bằng trong C/C++ là dấu ==, trong khi dấu = là phép gán. Thêm nữa, khi người lập trình sử dụng nhầm phép so sánh bằng với phép gán trong Biểu_thức_logic thì nói chung trình biên dịch không báo lỗi. Ví dụ trong đoạn lệnh sau: else if (thg==2) printf("\n Thang %d co 28 hoac 29 ngay",thg); else printf("Khong co thang %d",thg); nếu thay (thg==2) bởi (thg=2) thì chương trình xét về cú pháp là không lỗi, tuy nhiên về ý nghĩa là hoàn toàn sai. - Khi viết lệnh if, một số người do sơ xuất hoặc hiểu nhầm nên đặt dấu chấm phảy (;) ngay sau Biểu_thức_logic, trong một số tình huống cách viết này không xảy ra lỗi cú pháp nhưng về ý nghĩa cũng hoàn toàn sai. Ví dụ xét đoạn chương tình sau: printf("nhap vao mot so nguyen: "); scanf("%d", &a); if (a%7==0); printf("So %d chia het cho 7",a); getch(); Đoạn chương trình trên khi biên dịch sẽ không báo sai lỗi cú pháp, tuy nhiên dòng lệnh printf("So %d chia het cho 7",a); luôn được thực hiện với bất kỳ giá trị nào của a. 2. Lệnh switch Nếu lệnh if chỉ cho phép lựa chọn một trong nhiều nhất là hai công việc để thực hiện thì lệnh switch cho phép chương trình lựa chọn một trong nhiều công việc để thực hiện. Cú pháp: switch (Biểu_thức_điều_kiện) 29
  30. { case Giá_trị_1: Các lệnh cho công việc 1 [break;] case Giá_trị_2: Các lệnh cho công việc 2 [break;] case Giá_trị_n: Các lệnh cho công việc n [break;] [default: Các lệnh cho công việc n+1] } Trong đó: - Biểu_thức_điều_kiện: Biểu thức điều kiện để xác định công việc cần làm, biểu thức này phải trả về giá trị nguyên hoặc ký tự; - Giá_trị_1, Giá_trị_2, , Giá_trị_n: là các hằng nguyên hoặc ký tự; - Các lệnh cho công việc 1: Các lệnh nhằm thực hiện công việc thứ 1 khi giá trị của biểu thức điều kiện bằng Giá_trị_1; - Các lệnh cho công việc n: Các lệnh nhằm thực hiện công việc thứ n khi giá trị của biểu thức điều kiện bằng Giá_trị_n; Cách thực hiện: 1. Tính giá trị của biểu thức Biểu_thức_điều_kiện; 2. So sánh kết quả của biểu thức điều kiện lần lượt với các giá trị Giá_trị_1, Giá_trị_2, , Giá_trị_n, nếu giá trị của biểu thức điều kiện bằng giá trị của nhánh (case) thứ i là Giá_trị_i thì chương trình sẽ thực hiện bắt đầu từ dãy Các lệnh cho công việc i cho đến khi gặp lệnh break, hoặc nếu không gặp lệnh break nào thì sẽ thực hiện cho đến hết lệnh switch. 3. Nếu quá trình so sánh không gặp trường hợp Giá_trị_i nào bằng với giá trị của Biểu_thức_điều_kiện thì chương trình thực hiện dãy các Các lệnh cho công việc n+1 trong nhánh default nếu có. Trường hợp câu lệnh switch không có nhánh default và Biểu_thức_điều_kiện không khớp với bất cứ nhánh case nào thì lệnh switch đó không thực hiện bất kỳ công việc nào. Ví dụ 1.8: Giả sử thời khóa biểu tuần của một sinh viên như sau: thứ 2 học Giải tích, thứ 3 học Đại số tuyến tính, thứ 4 học Anh văn, thứ 5 học Kỹ thuật lập trình, thứ 6 học Vật lý đại cương, thứ 7 học Hóa học đại cương và chủ nhật là ngày nghỉ. Hãy viết chương trình cho phép nhập vào một ngày trong tuần và in ra công việc cần làm của sinh viên trong ngày đó. #include 30
  31. #include int main() { int thu; printf("Nhap vao thu (2-8, 8 la CN):"); scanf("%d",&thu); switch(thu) { case 2:printf("Giai tich"); break; case 3:printf("Dai so tuyen tinh"); break; case 4:printf("Anh van"); break; case 5:printf("Ky thuat lap trinh"); break; case 6:printf("Vat ly dai cuong"); break; case 7:printf("Hoa hoc dai cuong"); break; case 8:printf("Nghi hoc"); break; default:printf("Nhap sai ngay!"); } getch(); return 0; } Ví dụ này sử dụng biểu thức điều kiện của lệnh switch và các giá trị hằng trong mỗi nhánh case là số nguyên. Ví dụ 1.9: Nhập vào 2 số nguyên và 1 ký tự biểu diễn phép toán. Nếu phép toán là ‘+’, ‘-‘, ‘*’ thì in ra kết qua là tổng, hiệu, tích của 2 số; nếu phép toán là ‘/’ thì kiểm tra xem nếu số thứ 2 khác không thì in ra thương của chúng, ngược lại thì in ra thông báo “khong chia cho 0”. #include #include int main () { int so1, so2; float thuong; char pheptoan; printf("\n Nhap vao 2 so nguyen "); scanf("%d%d",&so1,&so2); 31
  32. fflush(stdin);//Xoa ky tu enter trong vung dem truoc khi nhap phep toan printf("\n Nhap vao phep toan "); scanf("%c",&pheptoan); switch(pheptoan) { case '+': printf("\n %d + %d =%d",so1, so2, so1+so2); break; case '-': printf("\n %d - %d =%d",so1, so2, so1-so2); break; case '*': printf("\n %d * %d =%d",so1, so2, so1*so2); break; case '/': if (so2!=0) { thuong=float(so1)/float(so2); printf("\n %d / %d =%f", so1, so2, thuong); } else printf("Khong chia duoc cho 0"); break; default : printf("\n Chua ho tro phep toan %c", pheptoan); break; } getch(); return 0; } Ví dụ này sử dụng biểu thức điều kiện của lệnh switch và các giá trị hằng trong mỗi nhánh case là ký tự. Ví dụ 1.10: Viết chương trình cho phép nhập vào một số nguyên dương là tháng trong năm và in ra số ngày của tháng đó, biết rằng tháng 1, 3, 5, 7, 8, 10, 12 có 31 ngày; tháng 4, 6, 9, 10 có 30 ngày; tháng 2 có 28 hoặc 29 ngày. #include #include int main () { int thang; printf("\n Nhap vao thangs trong nam "); 32
  33. scanf("%d",&thang); switch(thang) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: printf("\n Thang %d co 31 ngay ",thang); break; case 4: case 6: case 9: case 11: printf("\n Thang %d co 30 ngay ",thang); break; case 2: printf ("\ Thang 2 co 28 hoac 29 ngay"); break; default : printf("\n Khong co thang %d", thang); break; } getch(); return 0; } Ví dụ trên minh họa cách sử dụng lệnh break để điều khiển việc kết thúc lệnh switch, cách viết: case 1: case 3: case 5: case 7: case 8: case 10: case 12: printf("\n Thang %d co 31 ngay ",thang); break; cho phép thực hiện tất cả các nhánh này với cùng một dòng in ra kết quả số tháng là 31 và kết thúc bằng break. Tương tự như vậy cho các trường hợp tháng nhập vào là 4, 6, 9 và 11. Các lệnh: 33
  34. default : printf("\n Khong co thang %d", thang); break; được thực hiện khi giá trị nhập vào không nằm trong khoảng từ 1 đến 12. Chú ý: - Để lệnh switch chỉ thực hiện duy nhất các lệnh cho công việc thứ i (khi Biểu_thức_điều_kiện=Giá_trị_i) thì cuối dãy lệnh thứ i thêm vào lệnh break để kết thúc lệnh switch, xem ví dụ 1.8 và 1.9; - Lệnh break trong phần default của lệnh switch là không cần thiết. 1.5.3. Điều khiển lặp Các lệnh điều khiển lặp đóng một vai trò quan trọng trong ngôn ngữ lập trình, cùng với các dạng điều khiển tuần tự và điều khiển chọn lựa tạo nên một văn phạm tương đối đầy đủ của ngôn ngữ lập trình trong việc viết chương trình phần mềm. Các ngôn ngữ lập trình bậc cao thường cung cấp ba lệnh lặp tương đối giống nhau về ý nghĩa, cách thức thực hiện và thường là khác nhau về hình thức thể hiện. Phần này đề cập đến ba lệnh lặp của C/C++ là lệnh lặp for, while và do while. 1. Lệnh for Cho phép thực hiện công việc nào đó lặp đi lặp lại một số lần. Cú pháp: for ( [Khởi_tạo]; [Kiểm_tra]; [Biến_đổi]) {Các lệnh} Trong đó: - Khởi_tạo: Là một biểu thức hoặc một số câu lệnh đơn. Phần này thường được dùng để khởi tạo giá trị ban đầu cho một biến đếm dùng để kiểm soát số bước lặp; - Kiểm_tra: Là một biểu thức hoặc một số câu lệnh đơn. Phần này thường được dùng để kiểm tra điều kiện kết thúc của vòng lặp bằng một biểu thức logic; - Biến_đổi: Là một biểu thức hoặc một số câu lệnh đơn. Phần này thường được dùng để thay đổi giá trị biến đếm. Cách thực hiện: 1. Trước tiên Khởi_tạo được thực hiện nhằm khởi tạo giá trị ban đầu cho các biến điều khiển của vòng lặp; 2. Tiếp đến là phần Kiểm_tra được tính toán, nếu nó trả về giá trị là đúng (1) thì {Các_lệnh} sẽ được thực hiện, ngược lại thì vòng lặp for sẽ chuyển đến bước kết thúc (bước 4); 3. Sau khi thực hiện được một vòng lặp thi Biến_đổi được thực hiện nhằm làm thay đổi giá trị của biến điều khiển, sau đó điều khiển được chuyển về bước 2; và vòng lặp sẽ tiếp tục mãi cho đến khi Kiểm_tra có giá trị bằng sai (0). 4. Kết thúc vòng lặp. Ví dụ 1.11: Viết chương trình để in các số từ 1 đến 10 ra màn hình. 34
  35. #include #include int main () { printf("Day so tu 1 den 10 :\n"); for (int i=1; i #include int main () { unsigned int n,i,tong; printf("\n Nhap vao so nguyen duong n:"); scanf("%d",&n); tong=0; for (i=1; i #include 35
  36. int main() { float m,T,lai; int n, K; printf("Lai suat : "); scanf("%f",&m); m=m/100; printf("So thang de lai vao goc: "); scanf("%d",&n); printf("So tien gui : "); scanf("%f",&T); printf("So thang gui : "); scanf("%d",&K); lai= 0; for (int i=1; i int main() { int i, j; for ( i = 5, j = 10 ; i + j < 20; i++, j++ ) printf("\n i + j = %d", (i + j) ); 36
  37. } Phần khởi tạo thực hiện hai lệnh gán i = 5 và j = 10, và phần biến đổi thực hiện hai lệnh i++ và j++. - Các thành phần Khởi_tạo, Kiểm_tra và Biến_đổi trong lệnh for là tùy chọn, nghĩa là có thể có hoặc không. Khi bị bỏ qua thì phần Kiểm_tra luôn được nhận giá trị là đúng. Ví dụ chương trình sau đây sẽ thực hiện lặp vô tận: #include int main() { int i, j; for ( ; ; ) printf( "\n i + j = %d", (i + j) ); } - Mục dù các thành phần Khởi_tạo, Kiểm_tra và Biến_đổi trong lệnh for được thiết kế với mục đích là: khởi tạo, kiểm tra và biến đổi biến điều khiển của vòng lặp thì người lập trình vẫn có thể sử dụng chúng vào các mục đích khác. Ví dụ có thể sử dụng lệnh printf ở phần Kiểm_tra như sau: #include int main() { int i; for( i = 0; i #include 37
  38. int main () { int i; printf("Day so tu 1 den 10 :\n"); i=1; while (i 10, lưu ý rằng trong thân vòng lặp phải làm biến đổi Biểu_thức_điều_kiện, trong trường hợp này là biến i. Để đảm bảo tính đúng đắn của chương trình thì giá trị khởi tạo của biến i trước khi thực hiện vòng lặp i=1 là rất quan trọng. Ví dụ 1.15: Cho hai số nguyên dương a và b, viết chương trình tính và in ra ước số chung lớn nhất (USCLN) của hai số đó. #include #include int main () { int a, b, m, n, tg; printf("Nhap vao cac he so a,b:"); scanf("%d %d",&a,&b); m=a, n=b; while (n>0) { tg = m % n; m = n; n = tg; } printf("USCLN cua %d va %d la: %d",a,b,m); getch(); return 0; } Giải thuật trên được xây dựng theo thuật toán Euler, phần chính của thuật toán này là vòng lặp khi số “nhỏ” hơn còn lớn hơn không. Trong chương trình này, các giá trị nhập vào được lưu vào hai biến a, b; sau đó dùng các biến m, n để lưu các giá trị này và dùng để tính toán. Vòng lặp trong ví dụ trên kết thúc khi 38
  39. n #include int main () { char ch; ch = ’~’ ; while (ch!=13) { ch=getch(); printf("%c - %d \n",ch, ch); } return 0; } Theo bảng mã ASCII (Bảng 1-2) Enter có mã là 13. Hàm getch() cho phép đọc một ký tự từ bàn phím và gán vào biến ch. Trong chương trình trên lệnh ch=’~’; có vẻ là không cần thiết, tuy nhiên trước khi thực hiện biểu thức ch!=13 thì ch chưa xác định giá trị nên lệnh gán trước đó là cần thiết. Đây là tình huống không hay khi sử dụng lệnh while và chúng ta sẽ viết lại đẹp đẽ hơn khi sử dụng lệnh do while sau đây. 3. Lệnh do while Cho phép thực hiện công việc nào đó lặp đi lặp lại một số lần. Cú pháp: do {Các lệnh} while (Biểu_thức_điều_kiện) Trong đó: - Biểu_thức_điều_kiện là biểu thức điều kiện dùng để điều khiển vòng lặp. Cách thực hiện: 1. Thực hiện các lệnh {Các lệnh}; 2. Tính giá trị Biểu_thức_điều_kiện, nếu giá trị nhận được khác không thì trở lại bước 1, nếu bằng không thì chuyển đến bước 3; 3. Kết thúc. Ví dụ 1.17: Viết chương trình để in các số từ 1 đến 10 ra màn hình sử dụng vòng lặp do while. #include #include 39
  40. int main () { int i; printf("Day so tu 1 den 10 :\n"); i=1; do { printf("%d \n",i); i++; } while (i #include int main () { int a, b, m, n, tg; printf("Nhap vao cac he so a,b:"); scanf("%d %d",&a,&b); m=a, n=b; do { tg = m % n; m=n; n=tg; } while (n>0); printf("USCLN cua %d va %d la: %d",a,b,m); getch(); return 0; } Về cơ bản chương trình trên là giống với chương trình trong ví dụ 1.15. Điểm không hay của chương trình này so với ví dụ trước đó là phép chia m%n lần đầu không đảm bảo rằng n<>0. Ví dụ này cho thấy để giải bài toán tìm USCLN của 2 số thì sử dụng lệnh while sẽ thuận lợi hơn. Ví dụ 1.19: Viết lại ví dụ 1.16 sử dụng lệnh do while. 40
  41. #include #include int main () { char ch; do { ch=getch(); printf("%c - %d \n",ch, ch); } while (ch!=13) return 0; } Chương trình này rõ ràng là “đẹp” hơn chương trình tương tự trong ví dụ 1.16. 4. Một số nhận xét về for, while và do while Như đã thấy trong các phần trên, về cơ bản 3 lệnh lặp for, while và do while đều cho phép thực hiện một số công việc với lặp đi lặp lại một số lần. Tuy nhiên chúng tồn tại một số khác biệt như sau: - Lệnh for cho phép thực hiện lặp với số vòng lặp có thể tính được trước, lệnh này phù hợp cho việc lặp/duyệt trên một danh sách, trên một mảng. - Lệnh while và do while cho phép thực hiện các công việc lặp mà việc ước lượng số bước lặp tại thời điểm viết chương trình là không thuận lợi. Các lệnh này phù hợp khi làm việc trên các bài toán biến đổi theo thời gian. Ví dụ như thực hiện một công việc nào đó cho đến khi người sử dụng nhấn phím ESC. - Trong một số tình huống, ví dụ như trong các ví dụ 1.11, 1.14 và 1.17, thì việc sử dụng một trong ba lệnh lặp là không có sự khác biệt. - Trong một số tình huống, như trong ví dụ 1.15 và 1.18, thì sử dụng lệnh while sẽ thuận lợi hơn khi sử dụng lệnh do while. - Trong một số tình huống, như trong ví dụ 1.16 và 1.19, thì sử dụng lệnh do while sẽ thuận lợi hơn khi sử dụng lệnh while. 5. Lệnh break và continue Trong phần trình bày về các câu lệnh lặp ở trên ta thấy việc kết thúc lặp chỉ diễn ra khi điều kiện lặp không còn thỏa mãn. Vấn đề là nếu muốn thoát ra khỏi vòng lặp sớm hơn thì có được không? Trong một số ngôn ngữ lập trình điều này là được phép, và trong C/C++ đó là lệnh break hoặc continue. Lệnh break: Dùng để kết thúc sự thực hiện của một trong các lệnh do, for, switch hoặc while chứa nó, sau đó điều khiển được chuyển đến câu lệnh kế tiếp. Cú pháp: break; Lệnh break thường được dùng để kết thúc việc xử lý một nhánh nào đó của lệnh switch, và nếu thiếu các lệnh break trong lệnh này thường dẫn đến lỗi 41
  42. chương trình. Trong các lệnh lặp, lệnh break chỉ kết thúc duy nhất một câu lệnh do, for, hoặc while trực tiếp chứa nó. Ví dụ 1.20: Sử dụng lệnh break. #include int main() { char c; for(;;) { printf( "\nNhan phim bat ky, Nhan Q de thoat: " ); scanf("%c", &c); if (c == 'Q') break; } } Như đã biết, vòng lặp for(;;) sẽ thực hiện vô tận nếu như không có tác động nào. Trong ví dụ trên, trong thân vòng lặp lệnh scanf("%c", &c) cho phép đọc vào ký tự mà người sử dụng vừa nhập vào, lệnh if (c == 'Q') tiếp theo sẽ kiểm tra xem ký tự vừa nhập vào có phải là Q hay không, nếu đúng thì lệnh break sẽ kết thúc vòng lặp. Trường hợp người sử dụng nhập vào là ký tự q (thường) thì chương trình có kết thúc không? Để nhập vào Q hoặc q chương trình đều thoát thì cần sửa chương trình như thế nào? Bạn đọc tự giải quyết vấn đề này như là bài tập. Lệnh continue: Lệnh này dùng để quay lại đầu vòng lặp mà không thực hiện các lệnh trong khối lệnh lặp (dạng for, do hay while) kể từ sau lệnh continue. Cú pháp: continue; Vòng lặp tiếp theo được xác định như sau: - Trong vòng lặp do và while, vòng lặp tiếp theo được bắt đầu bằng cách tính lại biểu thức điều khiển của câu lệnh. - Với vòng lặp for (dạng for(i; c; e)) thì lệnh continue sẽ cho phép thực hiện công việc của e, sau đó tính lại c và tùy thuộc vào kết quả đúng hay sai mà vòng lặp được tiếp tục hay không. Ví dụ 1.21: Sử dụng lệnh continue. #include int main() { int i = 0; do { i++; 42
  43. printf("before the continue\n"); continue; printf("after the continue, should never print\n"); } while (i < 3); printf("after the do loop\n"); } Kết quả nhận được trên màn hình là: before the continue before the continue before the continue after the do loop Và như giải thích ở trên thì lệnh printf("after the continue, should never print\n"); không bao giờ được thực hiện vì nó luôn đứng sau continue trong ví dụ này trong mọi tình huống. 1.6. BÀI TẬP 1. Viết chương trình cho phép nhập vào các hệ số a, b và c và giải phương trình bậc hai a*x2+b*x+c = 0. 2. Viết chương trình cho phép nhập vào các hệ số a1, b1, c1, a2, b2, c2 và giải hệ bậc nhất sau: a1* x + b1* y = c1 a2* x + b2* y = c2 3. Viết chương trình cho phép nhập vào ba số thực a, b, c và kiểm tra xem chúng có phải là số đo các cạnh của: 1) một tam giác; 2) một tam giác vuông; 3) một tam giác cân; 4) một tam giác đều hay không? In kết quả ra màn hình. 4. Viết chương trình trong đó gán sẵn toạ độ tâm O và bán kính r của một hình tròn, nhập vào toạ độ của điểm M bất kì từ bàn phím, và hãy cho biết vị trí tương đối của M so với đường tròn: ở trong, trên hay ngoài đường tròn? 5. Viết chương trình cho phép nhập vào hai số a, b và một ký tự k, nếu k là một trong bốn ký tự biểu diễn phép toán ‘+’, ‘-‘, ‘*’, ‘/’ thì thực hiện phép cộng, trừ, nhân, chia của a và b và in kết quả ra màn hình. 6. Tên của năm âm lịch được cấu tạo từ hai thành phần là can và chi, ví dụ năm 2010 tương ứng với năm âm lịch là Canh Dần trong đó Canh là can và Dần là chi. Có tất cả 10 can là Giáp, Ất, Bính, Đinh, Mậu, Kỷ, Canh, Tân, Nhâm, Quý và 12 chi là Tí, Sửu, Dần, Mão, Thìn, Tỵ, Ngọ, Mùi, Thân, Dậu, Tuất, Hợi. Viết chương trình cho phép nhập vào một năm dương lịch (ví dụ 2015) và hãy đưa ra tên âm lịch tương ứng của năm đó biết rằng phần can và chi được lấy lần lượt xoay vòng (hết cuối chuyển về đầu) theo thứ tự kể trên. 7. Giả sử biết ngày đầu của một tháng (nào đó) là ngày thứ mấy trong tuần, hãy viết chương trình cho phép nhập vào ngày bất kỳ của tháng đó và cho biết đó là ngày thứ mấy trong tuần. Ví dụ nếu ngày 1 (của tháng nào đó) là Thứ 2 thì ngày 5 (của tháng đó) là Thứ 6. 43
  44. 8. Viết chương trình cho phép nhập vào tháng/năm, hãy cho biết số ngày của tháng/năm đó trong năm. Biết rằng các tháng 1, 3, 5, 7, 8, 10, 12 có 31 ngày; các tháng 4, 6, 9, 11 có 30 ngày; tháng 2 năm thường có 28 ngày, năm nhuận có 29 ngày. Những năm không chia hết cho 4 hoặc những năm chẵn thế kỷ nhưng không chia hết cho 400 là năm thường, ví dụ các năm 1996, 2000 là năm nhuận; các năm 1900 hay 2002 không nhuận. 9. (*) Giả sử biết ngày đầu tiên của một năm (nào đó) là ngày thứ mấy trong tuần, hãy viết chương trình cho phép nhập vào ngày, tháng, năm (của năm đó) và cho biết đó là ngày thứ mấy trong tuần. Ví dụ, ngày 1/1/2014 là ngày Thứ 4 thì ngày 3/6/2014 là ngày Thứ 3. 10. (*) Viết chương trình cho phép nhập vào ngày/tháng/năm bắt đầu và ngày/tháng/năm kết thúc, tính và in ra số ngày tính từ ngày/tháng/năm bắt đầu đến ngày/tháng/năm kết thúc. Ví dụ nếu ngày bắt đầu là 1/1/1970 và ngày kết thúc là 15/6/2014 thì số ngày tính được là 16236. 11. (*) Viết chương trình cho phép nhập vào ngày/tháng/năm bắt đầu và một số nguyên n, tính và in ra màn hình ngày/tháng/năm mới là ngày sau ngày bắt đầu n ngày. Ví dụ ngày bắt đầu là 1/1/1970 và n = 16236 thì ngày mới là 15/6/2014. 12. (*) Viết chương trình cho phép nhập vào ngày của tháng (nào đó), hãy chuyển ngày đó thành dạng chữ và in kết quả ra màn hình. Ví dụ nếu nhập vào ngày = 6 thì dạng chữ là “ngày sáu”, nếu nhập vào ngày = 31 thì dạng chữ là “ngày ba mươi mốt”. 13. (*) Viết chương trình cho phép nhập vào tháng của năm (nào đó), hãy chuyển tháng đó thành dạng chữ và in kết quả ra màn hình. Ví dụ nếu nhập vào tháng = 3 thì dạng chữ là “tháng ba”, nếu nhập vào tháng = 11 thì dạng chữ là “tháng mười một”. 14. (*) Viết chương trình cho phép nhập vào năm (nào đó) nhỏ hơn 2100, hãy chuyển năm đó thành dạng chữ và in kết quả ra màn hình. Ví dụ nếu nhập vào năm = 1989 thì dạng chữ là “năm một nghìn chín trăm tám mươi chín”. 15. ( ) Viết chương trình cho phép nhập vào các giá trị ngày, tháng, năm dạng số; hãy thực hiện việc chuyển các giá trị ngày, tháng, năm đó thành dạng chữ. Ví dụ nếu nhập vào là ngày = 24, tháng = 6, năm = 2014 thì dạng chữ của các giá trị đó là “Ngày hai mươi tư tháng sáu năm hai nghìn không trăm mười bốn”. 16. (*) Viết chương trình cho phép chuyển một số tiền nguyên (dạng số) về dạng chữ của số tiền đó và in kết quả ra màn hình, ví dụ với số tiền là 125050 thì dạng chữ của nó là “Một trăm hai mươi lăm nghìn không trăm năm mươi đồng”. 17. ( ) Viết chương trình cho phép chuyển một số tiền (dạng số) có độ chính xác sau dấu phảy 2 chữ số về dạng chữ của số tiền đó và in kết quả ra màn hình, ví dụ với số tiền là 125050,35 thì dạng chữ của nó là “Một trăm hai mươi lăm nghìn không trăm năm mươi phảy ba mươi lăm đồng”. 44
  45. 18. Viết chương trình cho phép tìm in ra màn hình các nghiệm nguyên dương của hệ phương trình sau: X + Y + Z = 100 5X + 3Y + Z/3 = 100 19. Viết chương trình cho phép nhập vào số nguyên dương n, hãy tìm tất cả các bộ 3 số nguyên dương a, b, c sao cho a2+b2 = c2 với a ≤ b ≤ c ≤ n và in các kết quả đó ra màn hình. 20. Viết chương trình cho phép in ra màn hình n số Fibonacci với n được nhập từ bàn phím. Số Fibonacci thứ k, kí hiệu Fk, được định nghĩa như sau: Fk = Fk-1 + Fk-2, với F0 = 0, F1 = 1. 21. Viết chương trình cho phép in ra màn hình tất cả các số nguyên tố nhỏ hơn n, với n là một số nguyên dương được nhập từ bàn phím. 22. Giả sử tiền gửi tiết kiệm được tính với lãi suất là m% mỗi tháng, sau n tháng thì tiền lãi được cộng vào gốc. Viết chương trình cho phép tính và in ra màn hình số tiền lãi có được sau K tháng gửi tiết kiệm với số tiền gốc ban đầu là T. Các giá trị m, n, K, T được nhập từ bàn phím. 23. Viết chương trình cho phép nhập vào hai số nguyên dương a và b, tính và in ra ước số chung lớn nhất (USCLN) và bội số chung nhỏ nhất (BSCNN) của hai số đó. 24. Gọi TongN (tổng N) của một số nguyên dương là tổng các chữ số của số nguyên đó, ví dụ TongN(3205) = 3+2+0+5 = 10. Viết chương trình cho phép nhập vào một số nguyên, tính là in ra TongN của số nguyên đó. 25. (*) Nếu giá trị TongN (bài tập 24) của một số nguyên dương có nhiều hơn một chữ số thì người ta tiếp tục tính TongN của giá trị đó và lặp lại cho đến khi giá trị tính được cuối cùng chỉ còn một chữ số, giá trị cuối cùng đó gọi là tổng triệt để của số nguyên. Ví dụ với số nguyên 3205 ta có TongN(3205)=3+2+0+5 = 10, vì 10 có 2 chữ số nên tính tiếp TongN(10) = 1+0 = 1, như vậy tổng triệt để của 3205 là 1. Viết chương trình cho phép nhập vào một số nguyên, tính và in ra tổng triệt để của số nguyên đó. 45
  46. Chương 2 MỘT SỐ CẤU TRÚC DỮ LIỆU CƠ BẢN Các kiểu Dữ liệu cơ bản trong lập trình cơ bản giải quyết được các phép tính toán đơn giản, nhưng trong những bài toán phức tạp cần lưu trữ dữ liệu có số lượng biến lớn, thể hiện cấu trúc thông tin phức tạp, thì chưa đáp ứng được. Ví dụ để lưu trữ 100 giá trị kiểu int thành một danh sách cần phải có kiểu dữ liệu lưu trữ được nhiều phần tử thành danh sách thay cho 100 biến, cần lưu trữ dữ liệu là một chuỗi văn bản, không thể sử dụng các biến kiểu char để lưu trữ, thông tin sinh viên gồm họ và tên, ngày sinh, điểm thi sẽ rất khó khăn trong xử lý nếu thông tin này lưu thành nhiều biến riêng biệt. Để giải quyết vấn đề này các ngôn ngữ lập trình đưa ra một số mô hình cấu trúc dữ liệu hỗ trợ quá trình lưu trữ và xử lý dữ liệu. Chương này đề cập đến một số loại cấu trúc dữ liệu cơ bản: mảng, chuỗi ký tự, kiểu dữ liệu con trỏ, và cấu trúc dữ liệu bản ghi. 2.1. MẢNG Cấu trúc dữ liệu kiểu mảng thể hiện tập hợp các phần tử có cùng kiểu được khai báo thông qua một tên và truy xuất đến từng phần tử trong mảng thông qua chỉ số của phần tử. Dữ liệu kiểu mảng có thể là mảng một chiều hoặc mảng nhiều chiều được khai báo và sử dụng theo nhu cầu thiết kế bài toán. Sử dụng mảng giúp cho quá trình khai báo giảm bớt số lượng biến phải khai báo. Sử dụng mảng còn giúp cho câu lệnh xử lý trên các phần tử có thể được đưa vào các vòng lặp thay vì viết các lệnh cho các biến khác nhau. Mảng là cấu trúc dữ liệu cơ bản và thông dụng trong lưu trữ và xử lý. 2.1.1. Mảng một chiều Mảng một chiều là tập hợp các phần tử cùng kiểu biến lưu trữ liên tiếp nhau trong bộ nhớ. Truy xuất một phần tử trong danh sách thông qua tên biến và một chỉ số thứ tự của phần tử. 1. Khai báo Cú pháp: Kiểu Tên_biến_1[Số_phần_tử_1], ,Tên_biến_n[Số_phần_tử_n]; Trong đó: - Kiểu: Là từ khóa qui định cho kiểu dữ liệu cần dùng. Ví dụ một số kiểu dữ liệu thông dụng như char, unsigned char, int, float, long, double. - Tên_biến_1, , Tên_biến_n: Tên biến do người sử dụng tự định nghĩa, lưu ý rằng tên biến phải tuân theo qui tắc đặt tên qui định bởi ngôn ngữ lập trình. - Số_phần_tử_1, , Số_phần_tử_n: Hằng số nguyên dương xác định số phần tử của mảng. Tùy theo hỗ trợ của hệ điều hành và trình biên dịch mà số lượng phần tử của mảng có giới hạn khác nhau. Ví dụ 2.1: Khai mảng kiểu nguyên int x[10]; Khai báo biến x có 10 phần tử, mỗi phần tử là một ô nhớ kiểu int, chỉ số để truy xuất đến các phần tử trong biến x là từ 0 đến 9 - chỉ số của phần tử mảng 46
  47. trong ngôn ngữ lập trình C mặc định được đánh số từ 0. char s[100]; Khai báo biến s có 100 phần tử, mỗi phần tử là một ô nhớ kiểu char, chỉ số để truy xuất đến các phần tử của biến s là từ 0 đến 99. Chú ý: Khi đặt tên biến, ngoài việc phải tuân theo qui định của việc đặt tên thì nên đặt tên biến sao cho gợi nhớ để dễ dàng sử dụng về sau; tuy vậy thì không nên dùng tên biến quá dài, bởi nó dễ làm khồng kềnh các biểu thức về sau. Thông thường những biến mảng sẽ được đặt thêm tiền tố là arr để phân biệt với các biến khác. Việc khai báo biến như trên sẽ không xác định giá trị ban đầu cho các phần tử của biến, chúng nhận một giá trị ngẫu nhiên tùy thuộc vào tình trạng bộ nhớ. Khi cần xác định giá trị định trước cho phần tử của mảng có thể khai báo theo cú pháp sau. Cú pháp: Kiểu Tên_biến_1[ Số_phần_tử_1 ] = {danh_sách_giá_trị_các_phần_tử_1}, Tên_biến_n[ Số_phần_tử_n ] = {danh_sách_giá_trị_các_phần_tử_n}; Trong đó: - Kiểu, Tên_biến_1, , Tên_biến_n, Số_phần_tử_1, , Số_phần_tử_n được hiểu như trên; - danh_sách_giá_trị_các_phần_tử_1, ,danh_sách_giá_trị_các_phần _tử_n là danh sách các biểu thức cho kết quả thuộc miền giá trị của Kiểu. Trong trường hợp khai báo có khởi tạo các giá trị, số phần tử của mảng có thể được để trống, lúc đó hệ thống sẽ xác định số phần tử giá trị khởi tạo sẽ là số phần tử của mảng. Ví dụ 2.2: Khai báo mảng có khởi tạo giá trị int arrData1[5]={1,2,3,4,5}, arrData2[]={5,4,3,2,1, 0}; Khai báo trên sẽ tạo ra mảng arrData1 bao gồm 5 phần tử kiểu int, giá trị tương ứng với phần tử thứ tự 0 đến phần tử thứ tự 4 là 1, 2, 3, 4, 5. Mảng arrData2 gồm có 6 phần tử kiểu int, giá trị tương ứng với phần tử thứ tự 0 đến phần tử thứ tự 5 là 5, 4, 3, 2, 1, 0. 2. Truy xuất phần tử mảng Các phần tử mảng là một biến với kiểu đã được định nghĩa, vì thế khi truy xuất vào phần tử mảng có thể sử dụng các phép toán như với một biến có cùng kiểu. Cú pháp: Tên_biến_mảng[Chỉ_số_phần_tử]; Trong đó: - Tên_biến_mảng: Là một tên biến kiểu mảng đã được khai báo. - Chỉ_số_phần_tử: Chỉ số phần tử mảng là thứ tự phần tử trong mảng cần truy xuất. Mảng trong ngôn ngữ lập trình C mặc định đánh chỉ số thứ tự 47
  48. từ giá trị 0, vì thế chỉ_số_phần_tử của mảng sẽ có giá trị từ 0 đến số phần tử đã được khai báo trừ đi 1. Ví dụ nếu mảng có 5 phần tử thì chỉ số mảng sẽ từ 0 đến 4. Ví dụ 2.3: Truy xuất phần tử của mảng: - Lệnh arrData1[0]=10; sẽ gán giá trị 10 cho phần tử thứ 0 của mảng arrData1; - Lệnh arrData1[1]=arrData1[0]+5; sẽ gán cho phần tử thứ 1 của mảng arrData1 giá trị bằng giá trị được lưu trữ trong phần tử thứ 0 của mảng arrData1 cộng với 5. 3. Nhập, xuất dữ liệu mảng Mảng là một cấu trúc dữ liệu đặc biệt vì thế không được hỗ trợ các toán tử làm việc với các biến kiểu mảng. Các trình biên dịch chỉ hỗ trợ các toán tử làm việc với phần tử của mảng vì vậy để làm việc với mảng phải làm việc trực tiếp với phần tử của mảng. Ví dụ 2.4: Nhập, xuất dữ liệu mảng #include int main() { int arrData[5]; int i; printf("Nhap gia tri mang arrData, 5 phan tu:\n"); for(i=0;i<5;i++) { printf("arrData[%d]:",i); scanf("%d",&arrData[i]); } printf("\ngia tri mang arrData:\n"); for(i=0;i<5;i++) { printf("%d\n",arrData[i]); } return 0; } 4. Ví dụ a) Tính tổng các phần tử của mảng N giá trị nguyên Bài toán: Cho một mảng N phần tử kiểu nguyên cho trước, tìm tổng các giá trị của mảng. Ý tưởng: Khởi tạo giá trị ban đầu bằng không. Duyệt các phần tử cộng giá trị vào biến lưu trữ. Thuật toán: Dữ liệu vào: arrData[N] phần tử Dữ liệu ra: cSum tổng các giá trị của mảng 48
  49. Mô tả: cSum=0; for(i=1 -> N) cSum=cSum+arrData[i]; Cài đặt chương trình: Theo thuật toán phần tử đầu tiên của mảng là 1, và duyệt từ 1 đến N, nhưng trong cài đặt ngôn ngữ C do chỉ số phần tử của mảng được xét từ 0 đến N-1 nên trong chương trình vòng lặp sẽ tiến hành từ giá trị 1 đến giá trị N-1; #include #define N 5 int main() { int arrData[N]={3,5,1,2,4}; int i, cSum; cSum=0; for(i=0;i 0) hay không. Ý tưởng: Giả sử ban đầu dãy chứa các phần tử dương. Duyệt các phần tử của dãy, nếu phát hiện phần tử không thỏa mãn điều kiện dương, thay đổi trạng thái và kết thúc kiểm tra. Thuật toán: Dữ liệu vào: arrData[N] phần tử Dữ liệu ra: Kết luận về tính dương của dãy iPosi=1; for(i=1 -> N) if(arrData[i]<=0) iPosi=0; if(iPosi==1) Kết luận dãy dương else Kết luận dãy không dương Cài đặt chương trình: Tương tự như giải thích ở trên, trong cài đặt ở đây chỉ số mảng là 0 đến N-1, vì vậy vòng lặp sẽ thực hiện từ 0 đến N-1; 49
  50. #include #define N 5 int main() { int iPosi; int arrData[N]={3,5,1,2,4}; int i; iPosi=1; for(i=0;i<=N-1;i++) { if(arrData[i]<=0) { iPosi=0; break; } } if(iPosi==1) { printf("Day toan duong"); } else { printf("Day khong thoa man toan duong"); } getch(); return 0; } 2.1.2. Mảng nhiều chiều Trong trường hợp mô hình dữ liệu lưu trữ không chỉ là một dãy các phần tử mà theo mô hình không gian hai chiều, hoặc nhiều chiều việc xử lý trên mảng một chiều sẽ gặp bất lợi trong xác định vị trí phần tử. Để giải quyết vấn đề này trong ngôn ngữ lập trình C đưa ra khái niệm ma trận nhiều chiều. Mỗi ô nhớ sẽ được truy xuất thông qua các chỉ số tương ứng với số chiều của mảng, điều này giúp cho việc mô tả dữ liệu trên không gian nhiều chiều đơn giản hơn. 1. Khai báo Cú pháp: Kiểu Tên_biến_1[Số_phần_tử_11] [ Số_phần_tử_1n], , Tên_biến_n[Số_phần_tử_k1] [ Số_phần_tử_km]; Trong đó: - Kiểu: Là từ khóa qui định cho kiểu dữ liệu cần dùng. Ví dụ một số kiểu dữ liệu thông dụng như char, unsigned char, int, float, long, double. - Tên_biến_1, , Tên_biến_n: Tên biến do người sử dụng tự định nghĩa, 50
  51. lưu ý rằng tên biến phải tuân theo qui tắc đặt tên qui định bởi ngôn ngữ lập trình. - Số_phần_tử_11, , Số_phần_tử_1n: tương ứng là số phần tử chiều thứ 1, , n của mảng thứ nhất. Tương tự cho Số_phần_tử_k1, , Số_phần_tử_km là số phần tử của chiều thứ 1, , m của mảng thứ k. Ví dụ 2.5: Khai báo mảng nguyên hai chiều: - Khai báo int mang[10][20]; tạo biến mang có 10x20 = 200 phần tử, mỗi phần tử là một ô nhớ kiểu int, chỉ số để truy xuất đến các phần tử trong biến x là từ 0 đến 9 trong chiều thứ nhất, và 0 đến 19 trong chiều thứ hai. - Khai báo char s[5][100]; tạo ra biến s có 5x100=500 phần tử, mỗi phần tử là một ô nhớ kiểu char, chỉ số để truy xuất đến các phần tử của biến s là từ 0 đến 4 trong chiều thứ nhất và 0 đến 99 trong chiều thứ hai. Chú ý: Trong trường hợp khai báo ở trên có thể quan điểm là khai báo mảng 10 phần tử, mỗi phần tử mảng là mảng 20 phần tử kiểu int. Tương tự ta có mảng s có 5 phần tử mỗi phần tử là mảng 100 phần tử kiểu char. Khai báo biến với các giá trị được xác định theo cú pháp sau. Cú pháp: Kiểu Tên_biến_1[ Số_phần_tử_1 ] [ Số_phần_tử_n ] = {danh_sách_giá_trị_các_phần_tử_1}, ; Trong đó: - Kiểu, Tên_biến_1 hiểu như trên; - Số_phần_tử_1, , [ Số_phần_tử_n ] là số biến của các chiều tương ứng; - danh_sách_giá_trị_các_phần_tử_1 là danh sách các biểu thức cho kết quả thuộc miền giá trị của Kiểu. Trong trường hợp khai báo có khởi tạo các giá trị, số phần tử chiều thứ nhất của mảng có thể được để trống, lúc đó hệ thống sẽ xác định số phần tử giá trị khởi tạo sẽ là số phần tử của mảng. Ví dụ 2.6: Khai báo mảng có khởi tạo giá trị int arrData1[2][3]={1,2,3,4,5,6}, arrData2[][3]={5,4,3,2,1,0}; Khai báo mảng arrData1 bao gồm 6 phần tử kiểu int trong mảng hai chiều 2x3 giá trị tương ứng với phần tử {1,2,3}, {4,5,6}. Mảng arrData2 gồm có 6 phần tử kiểu int, giá trị tương ứng {5, 4, 3}, {2, 1, 0}. 2. Truy xuất phần tử mảng Các phần tử mảng là một biến với kiểu đã được định nghĩa, vì thế khi truy xuất vào phần tử mảng có thể sử dụng các phép toán như với một biến có cùng kiểu. Cú pháp: Tên_biến_kiểu_mảng[Chỉ_số_chiều 1] [ Chỉ_số_chiều n] Trong đó: - Tên_biến_kiểu_mảng: Là một tên biến kiểu mảng đã được khai báo. - Chỉ_số_chiều 1, , Chỉ_số_chiều n: Chỉ số của các chiều tương ứng. 51
  52. Mảng trong ngôn ngữ lập trình C mặc định đánh chỉ số thứ tự từ giá trị 0, vì thế chỉ_số_phần_tử của mảng sẽ có giá trị từ 0 đến số phần tử đã được khai báo trừ đi 1. Ví dụ mảng có 2 x 3 thì chỉ số tương ứng chiều thứ nhất là 0, 1 chiều thứ hai là 0, 1, 2. Ví dụ 2.7: Truy xuất phần tử của mảng: - Khai báo arrData1[0][1]=10; gán phần tử tại chỉ số thứ 0 chiều thứ nhất và chỉ số 1 của chiều thứ hai của mảng arrData1 giá trị 10; - Khai báo arrData1[0][1]=arrData1[0][0]+5; gán phần tử tại chỉ số thứ 0 chiều thứ nhất và 1 chiều thứ hai của mảng arrData1 bằng giá trị của phần tử thứ 0 chiều thứ nhất và 0 chiều thứ 2 cộng với 5. 3. Nhập, xuất dữ liệu mảng Tương tự mảng một chiều, mảng nhiều chiều cũng không được hỗ trợ các hàm, toán tử trực tiếp trên toàn bộ mảng. Để tương tác với mảng cần tương tác với các phần tử mảng. Ví dụ 2.8: Nhập, xuất dữ liệu mảng #include #include int main() { int arrData[2][3]; int i,j; printf("Nhap gia tri mang arrData, 2 hang 3 cot:\n"); for(i=0;i<2;i++) { for(j=0;j<3;j++) { printf("arrData[%d][%d]:",i,j); scanf("%d",&arrData[i][j]); } } printf("\ngia tri mang arrData:\n"); for(i=0;i<2;i++) { for(j=0;j<3;j++) { printf("%4d",arrData[i][j]); } printf("\n"); } getch(); return 0; } 52
  53. 4. Ví dụ a) Xác định sự xuất hiện của phần tử k trên cột c của ma trận hai chiều. Bài toán: Cho một mảng NxM, phần tử k và cột c phần tử kiểu nguyên cho trước. Xác định sự xuất hiện của phần tử k trên cột c. Ý tưởng: Ban đầu trạng thái chưa tìm thấy. Duyệt trên cột c của ma trận, nếu xuất hiện chuyển trạng thái tìm thấy. Thuật toán: Dữ liệu vào: arrData[N][M] phần tử, giá trị k, và cột c. Dữ liệu ra: Xuất hiện của k trên c. found=0; for(i=1 -> N) if(arData[i][c]==k) found=1; Cài đặt chương trình: #include #include #define N 5 #define M 3 int main() { int arrData[N][M]={{3,5,1},{2,4,5},{1,4,3},{3,2,5},{2,3,1}}; int c=1; int k=3; int i, found=0; for(i=0;i<=N-1;i++) { if(arrData[i][c]==k) { found=1; } } if(found==1) { printf("Tim thay gia tri %d tren cot %d cua ma tran",k,c); } else { printf("Khong tim thay %d tren cot %d cua ma tran", k, c); } getch(); return 0; } 53
  54. b) Tính tích các phần tử trên đường chéo chính của ma trận vuông Bài toán: Cho ma trận hai chiều vuông NxN. Tính tích của các phần tử trên đường chéo chính. Ý tưởng: Giá trị tích ban đầu bằng 1. Duyệt các giá trị trên đường chéo chính nhân với giá trị tích hiện tại. Thuật toán: Dữ liệu vào: arrData[N][N] phần tử Dữ liệu ra: iProduct tích các phần tử iProduct=1; for(i=1 -> N) iProduct=iProduct * arrData[i][i]; Cài đặt chương trình: #include #include #define N 4 int main() { int iProduct; int arrData[N][N]={ {3,5,1,2} ,{2,1,4,2} ,{1,3,0,2} ,{5,4,1,2}}; int i; iProduct=1; for(i=0;i<=N-1;i++) { iProduct*=arrData[i][i]; } printf("Tich %d",iProduct); getch(); return 0; } 2.1.3. Một số ví dụ 1. Bài toán trên mảng một chiều a) Kiểm tra tính tăng, giảm của dãy Bài toán: Cho một dãy N số nguyên, kiểm tra các phần tử trong dãy đảm bảo tính tăng (phần tử đứng sau sẽ lớn hơn phần tử đứng trước). Ý tưởng: Giả sử dãy ban đầu là dãy tăng. Duyệt từ phần tử thứ 1 đến phần tử thứ N-1, nếu phần tử i không lớn hơn phần tử i-1 thì chuyển trạng thái sang trạng thái không thỏa mãn. Thuật toán: Dữ liệu vào: Cho một mảng N phần tử nguyên 54
  55. Dữ liệu ra: Tính tăng của dãy bIn=1; For i=a -> N-1 If(arrData[i] #include #define N 6 int main() { int bIn=1; int arrData[N]={5,4,1,2,3,7}; int i; for(i=1;i<=N-1;i++) { if(arrData[i]<=arrData[i-1]) { bIn=0; break; } } if(bIn==1) { printf("Day tang"); } else { printf("Day khong tang"); } getch(); return 0; } b) Tìm kiếm đoạn tăng dài nhất trên dãy hiện tại Bài toán: Cho một dãy N số nguyên, xác định đoạn có tạo thành dãy tăng dài nhất. Ý tưởng: Dãy tăng dài nhất ban đầu là phần tử đầu tiên. Từ phần tử thứ 2 ta kiểm tra dãy vẫn tăng tiếp tục tiến hành đếm số phần tử tăng của dãy hiện tại, khi 55
  56. có vi phạm dãy tăng tiến hành kiểm tra dãy tăng hiện tại và dãy tăng dài nhất đang lưu trữ. Thuật toán: Dữ liệu vào: Cho một mảng N phần tử nguyên Dữ liệu ra: Dãy tăng dài nhất li=1; sl=0; cli=1; csl=0; for(i=1->N-1) { if(arrData[i]>arrDate[i-1]) cli++; else { if(cli>li) { li=cli; sl=csl; } csl=i; cli=1; } if(cli>li) { li=cli; sl=csl; } } Cài đặt chương trình: #include #include #define N 6 int main() { int li=1, sl=0; int cli=1, csl=0; int arrData[N]={5,9,1,2,3,7}; int i; for(i=1;i arrData[i-1]) 56
  57. { cli++; } else { if(cli>li) { li=cli; sl=csl; } cli=1; csl=i; } } if(cli>li) { li=cli; sl=csl; } printf("Day dai nhat bat dau %d co do dai %d",sl,li); getch(); return 0; } c) Đếm số dương trong mảng Bài toán: Cho một dãy N số nguyên, đếm số phần tử lớn hơn không của dãy. Ý tưởng: Số phần tử lớn hơn không ban đầu bằng không. Duyệt từ phần tử đầu tiên đến phần tử thứ N-1, nếu phần tử lớn hơn không công thêm một. Thuật toán: Dữ liệu vào: Cho một mảng N phần tử nguyên Dữ liệu ra: Số phần tử lớn hơn không cPositive=0; for(i=0 -> N-1) if(arrData[i]>0) cPositive++; Số phần tử dương là cPositive Cài đặt chương trình: #include #include #define N 6 int main() { int cPositive=0; 57
  58. int arrData[N]={5,-19,1,-12,-3,7}; int i; for(i=0;i 0) { cPositive++; } } printf("So phan tu duong %d",cPositive); getch(); return 0; } d) Tính giá trị của đa thức Bài toán: Cho một đa thức được lưu trữ trong mảng một chiều. an*xn + + a0 các phần tử an, , a0 được lưu trong phần tử thứ a[n], , a[0] của mảng. Cho giá trị x tính giá trị của đa thức. Ý tưởng: Khai triển biểu thức an*xn + + a0 thành dạng ((an*x+an-1)*x + )*x + a0. Biến S ban đầu bằng an lặp từ n-1 đến 0, S=S*x + a[i]; Thuật toán: Dữ liệu vào: Giá trị x, mảng chứa hệ số Dữ liệu ra: Giá trị của đa thức S=arrData[n]; For(i=n-1 -> 0) S=S*x+arrData[i]; Giá trị đa thức là S Cài đặt chương trình: #include #include #define N 6 int main() { float S; float arrData[N+1]={5,-19,1,-12,-3,7,2}; float x=-1; int i; S=arrData[N]; for(i=N-1;i>=0;i ) { S=S*x+arrData[i]; } printf("Gia tri bieu thuc %f",S); 58
  59. getch(); return 0; } 2. Bài toán trên mảng nhiều chiều a) Cộng hai ma trận Bài toán: Cho hai ma trận A, và B có độ lớn NxM, tính ma trận tổng của hai ma trận trên. Ý tưởng: Duyệt các phần tử của ma trận C (ma trận kết quả), cij=aij + bij; Thuật toán: Dữ liệu vào: Ma trận A, B kích thước NxM Dữ liệu ra: Ma trận kết quả C = A + B For(i=0 -> N-1) For(j=0 -> M-1) cij=aij + bij; Cài đặt chương trình: #include #include #define N 3 #define M 2 int main() { int A[N][M]={{1,2},{1,5},{3,4}}; int B[N][M]={{-2,4},{2,9},{2,4}}; int C[N][M], i, j; for(i=0;i<N;i++) { for(j=0;j<M;j++) { C[i][j]=A[i][j]+B[i][j]; } } printf("Ma tran tong\n"); for(i=0;i<N;i++) { for(j=0;j<M;j++) { printf("%4d",C[i][j]); } printf("\n"); } getch(); return 0; 59
  60. } b) Nhân hai ma trận Bài toán: Cho hai ma trận A kích thước NxM, và B có kích thước MxN, tính ma tích của hai ma trận trên. Ý tưởng: Ma trận tích C có độ lớn NxN. Duyệt các phần tử của ma trận C (ma trận kết quả), cij=∑ aik * bkj; k=1 M. Thuật toán: Dữ liệu vào: Ma trận A, B kích thước NxM, và MxN Dữ liệu ra: Ma trận kết quả C = A x B For(i=0 -> N-1) For(j=0 -> N-1) { C[i][j]=0; For(k=0 -> M) C[i][j]=C[i][j] + A[i][k] * B[k][j]; } Cài đặt chương trình: #include #include #define N 3 #define M 2 int main() { int A[N][M]={{1,2},{1,5},{3,4}}; int B[M][N]={{-2,4,2},{9,2,4}}; int C[N][N], i, j, k; for(i=0;i<N;i++) { for(j=0;j<N;j++) { C[i][j]=0; for(k=0;k<M;k++) { C[i][j]+=A[i][k]*B[k][j]; } } } printf("Ma tran tich\n"); for(i=0;i<N;i++) { for(j=0;j<N;j++) { 60
  61. printf("%4d",C[i][j]); } printf("\n"); } getch(); return 0; } c) Tìm định thức của ma trận Bài toán: Cho ma trận A kích thước NxN. Tìm định thức của ma trận A. Ý tưởng: Sử dụng phương pháp Gauss để tính định thức. Thuật toán: Dữ liệu vào: Ma trận A kích thước NxN Dữ liệu ra: Định thức ma trận A det=1; for (i=0 -> N) { for(j=i -> N) if (A[j][i] !=0) break; if(A[j][i]==0) { det=0; break; } if(i!=j) { det*=-1; for(k=0->N) { tmp=A[i][k]; A[i][k]=A[j][k]; A[j][k]=tmp } } for(j=i+1 -> N) { hs=-A[j][i]/A[i][i]; for(k=0 - > N) A[j][k]=A[j][k] + A[i][k]*hs; } } 61
  62. if(det!=0) for(i=0 -> N) det*=A[i][i]; Cài đặt chương trình: #include #include #define N 3 #define M 2 int main() { float A[N][N]={ {1,2,3} ,{1,5,-1} ,{2,4,7}}; float det=1, hs, tmp ; int i, j, k; for(i=0;i<N;i++) { for(j=i;j<N;j++) { if(A[j][i]!=0) { break; } } if(!(A[j][i]!=0)) { det=0; break; } if(j!=i) { for(k=0;k<N;k++) { tmp=A[j][k]; A[j][k]=A[i][k]; A[i][k]=tmp; } det*=-1; } for(j=i+1;j<N;j++) { hs=-A[j][i]/A[i][i]; 62
  63. for(k=0;k<N;k++) { A[j][k]=A[j][k]+A[i][k]*hs; } } } if(det==1) { for(i=0;i<N;i++) { det*=A[i][i]; } } printf("Det A = %.2f\n",det); getch(); return 0; } 2.2. XÂU KÝ TỰ Trong thực tiễn việc xử lý chuỗi các ký tự xảy ra thường xuyên trên máy tính, vì thế nhu cầu định nghĩa kiểu dữ liệu đặc biệt cho chuỗi ký tự được đặt ra cho các ngôn ngữ lập trình. Trong ngôn ngữ lập trình C, chuỗi ký từ là mảng các ký tự với ký tự đặc biệt đánh dấu kết thúc – mã là 0. Do đặc trưng riêng và nhu cầu xử lý thường xuyên nên các ngôn ngữ lập trình xây dựng thư viện các hàm xử lý chuỗi ký tự. 2.2.1. Khai báo và nhập xuất dữ liệu 1. Khai báo Cú pháp: char Tên_biến_1[Số_phần_tử_1], ,Tên_biến_n[Số_phần_tử_n]; Trong đó: - char: là từ khóa kiểu dữ liệu cho chuỗi. - Tên_biến_1, , Tên_biến_n: Tên biến do người sử dụng tự định nghĩa, lưu ý rằng tên biến phải tuân theo qui tắc đặt tên qui định bởi ngôn ngữ lập trình. - Số_phần_tử_1, , Số_phần_tử_n: Hằng số nguyên dương xác định số phần tử của của chuỗi. Tùy theo hỗ trợ của hệ điều hành và trình biên dịch mà số lượng phần tử của chuỗi là khác nhau. Ví dụ 2.9: Khai báo chuỗi char x[10]; Khai báo chuỗi x có nhiều nhất là 10 ký tự, chỉ số để truy xuất đến các ký tự trong chuỗi x là từ 0 đến 9. Chú ý: Bản chất chuỗi là một mảng các ký tự với ký tự kết thúc là ‘\0’ vì thế các quan niệm trên mảng vẫn đúng với chuỗi ký tự. 63
  64. Khai báo chuỗi với giá trị khởi tạo theo cú pháp sau. Cú pháp: char Tên_biến_1[ Số_phần_tử_1 ] = {chuỗi_khởi_tạo_1}, Tên_biến_n[ Số_phần_tử_n ] = {chuỗi_khởi_tạo_n}; Trong đó: - Tên_biến_1, , Tên_biến_n, Số_phần_tử_1, , Số_phần_tử_n được hiểu như trên; - chuỗi_khởi_tạo_1, , chuỗi_khởi_tạo_n là chuỗi khởi tạo cho các giá trị biến. Trong trường hợp khai báo có khởi tạo các giá trị, độ dài của chuỗi có thể được để trống, lúc đó hệ thống sẽ xác định độ dài của chuỗi theo độ dài chuỗi khởi tạo. Ví dụ 2.10: Khai báo mảng có khởi tạo giá trị char s[100]= "Xin chao den voi ky thua lap trinh"; char a[]="Ky thuat lap trinh co ban"; Khai báo chuỗi s có độ dài tối đa là 100 ký tự, được khởi tạo chuỗi ban đầu là “Xin chao den voi ky thua lap trinh”. Chuỗi a được khởi tạo là “Ky thuat lap trinh co ban” với độ dài tối đa của chuỗi chính là độ dài của chuỗi khởi tạo. Chú ý: Cách khai báo chuỗi ký tự trên hoàn toàn khác với khai báo char *b="Con tro den hang so chuoi"; trường hợp này khai báo một con trỏ kiểu ký tự và khởi tạo của con trỏ được trỏ đến địa chỉ chứa hằng số là chuỗi trong chương trình. Vì là hằng số chuỗi nên mọi thay đổi trên dữ liệu trỏ đến bởi con trỏ b sẽ nảy sinh lỗi. 2. Truy xuất phần tử Truy xuất phần tử của chuỗi thông qua cặp ngoặc [, ] như truy xuất phần tử của mảng. Mỗi phần tử của chuỗi là một ký tự. Ví dụ 2.11: Ví dụ truy xuất phần tử của chuỗi char s[100]= "Xin chao den voi ky thua lap trinh"; s[0]='x'; 3. Nhập xuất dữ liệu xâu Chuỗi là kiểu mảng đặc biệt được xem như là kiểu dữ liệu nên trong ngôn ngữ lập trình C hỗ trợ các hàm nhập xuất xâu. a) Nhập xâu - Sử dụng hàm scanf() với tham số %s Ví dụ 2.12: Nhập xâu bằng hàm scanf() char s[100]; scanf("%s",s); - Sử dụng hàm gets() Cú pháp: char *gets(char *s); Tham số đầu vào s là con trỏ của xâu. Giá trị trả về: con trỏ đến xâu đã được nhập vào. 64