Uploaded by Hiển Trần Minh

263 ngon ngu lap trinh c ebook 0998

advertisement
BOÄ GIAÙO DUÏC VAØ ÑAØO TAÏO
TRÖÔØNG ÑAÏI HOÏC SÖ PHAÏM KYÕ THUAÄT THAØNH PHOÁ HOÀ CHÍ MINH
NAÊM XAÂY DÖÏNG VAØ PHAÙT TRIEÅN
60
TRƯƠNG NGỌC SƠN - LÊ MINH
TRƯƠNG NGỌC HÀ - LÊ MINH THÀNH
GIÁO TRÌNH
NGÔN NGỮ LẬP TRÌNH C
(Ngành Kỹ thuật máy tính)
NHAØ XUAÁT BAÛN
ÑAÏI HOÏC QUOÁC GIA TP. HOÀ CHÍ MINH
BỘ GIÁO DỤC VÀ ĐÀO TẠO
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT
THÀNH PHỐ HỒ CHÍ MINH
*******************
TS. TRƯƠNG NGỌC SƠN
ThS. LÊ MINH
ThS. TRƯƠNG NGỌC HÀ
ThS. LÊ MINH THÀNH
GIÁO TRÌNH
NGÔN NGỮ LẬP TRÌNH C
(Ngành Kỹ thuật Máy tính)
NHÀ XUẤT BẢN ĐẠI HỌC QUỐC GIA
THÀNH PHỐ HỒ CHÍ MINH – NĂM 2020
GIÁO TRÌNH
NGÔN NGỮ LẬP TRÌNH C
(Ngành kỹ thuật máy tính)
TRƯƠNG NGỌC SƠN, LÊ MINH, TRƯƠNG NGỌC HÀ, LÊ MINH THÀNH
Chịu trách nhiệm xuất bản và nội dung
TS. ĐỖ VĂN BIÊN
Biên tập
LÊ THỊ THU THẢO
Sửa bản in
PHAN KHÔI
Trình bày bìa
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TP. HỒ CHÍ MINH
Website: http://hcmute.edu.vn
Đối tác liên kết – Tổ chức bản thảo và chịu trách nhiệm tác quyền
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TP. HỒ CHÍ MINH
Website: http://hcmute.edu.vn
NHÀ XUẤT BẢN ĐẠI HỌC QUỐC GIA THÀNH PHỐ HỒ CHÍ MINH
Phòng 501, Nhà Điều hành ĐHQG-HCM, phường Linh Trung, quận Thủ Đức, TP Hồ Chí Minh
ĐT: 028 6272 6361 - 028 6272 6390
E-mail: vnuhp@vnuhcm.edu.vn
Website: www.vnuhcmpress.edu.vn
VĂN PHÒNG NHÀ XUẤT BẢN
PHÒNG QUẢN LÝ DỰ ÁN VÀ PHÁT HÀNH
Tòa nhà K-Trường Đại học Khoa học Xã hội & Nhân văn, số 10-12 Đinh Tiên Hoàng, phường Bến Nghé,
quận 1, TP Hồ Chí Minh
ĐT: 028 66817058 - 028 62726390 - 028 62726351
Website: www.vnuhcmpress.edu.vn
Nhà xuất bản ĐHQG-HCM và tác giả/đối tác liên kết giữ bản quyền©
Copyright © by VNU-HCM Press and author/
co-partnership All rights reserved
ISBN: 978-604-73-7623-0
In số lượng 300 cuốn, khổ 16 x 24 cm, XNĐKXB số: 1296-2020/CXBIPH/8-33/ĐHQGTPHCM.
QĐXB số 38/QĐ-NXBĐHQGTPHCM cấp ngày 21/4/2020.
In tại: Công ty TNHH In và Bao bì Hưng Phú. Địa chỉ: 162A/1- KP1A – P.An Phú – TX Thuận An –
Bình Dương. Nộp lưu chiểu: Quý II/2020.
GIÁO TRÌNH
NGÔN NGỮ LẬP TRÌNH C
(Ngành kỹ thuật máy tính)
TRƯƠNG NGỌC SƠN,
LÊ MINH,
TRƯƠNG NGỌC HÀ,
LÊ MINH THÀNH
.
Bản tiếng Việt ©, TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TP. HCM, NXB ĐHQG-HCM và
TÁC GIẢ.
Bản quyền tác phẩm đã được bảo hộ bởi Luật Xuất bản và Luật Sở hữu trí tuệ Việt Nam. Nghiêm cấm
mọi hình thức xuất bản, sao chụp, phát tán nội dung khi chưa có sự đồng ý của Trường đại học Sư
phạm Kỹ thuật TP. HCM và Tác giả.
ĐỂ CÓ SÁCH HAY, CẦN CHUNG TAY BẢO VỆ TÁC QUYỀN!
2
LỜI NÓI ĐẦU
Trong ngành công nghiệp hiện đại, các hệ thống tự động trở nên
phổ biến, hiệu quả, và dần thay thế các hệ thống điều khiển bằng tay.
Trong các hệ thống tự động, thiết bị được lập trình hầu như đóng vai
trò chủ đạo. Đặc biệt trong kỷ nguyên công nghiệp và cuộc cách mạng
công nghiệp 4.0, tự động hoá, vạn vật kết nối (IoT), thông minh nhân
tạo (AI) thì các thiết bị lập trình, các hệ thống lập trình chiếm ưu thế và
là sức mạnh công nghệ. Ở góc nhìn kỹ thuật, ngôn ngữ lập trình là công
cụ cần thiết để tiếp cận công nghệ, đặc biệt là trong cuộc cách mạng
công nghiệp 4.0. Ngôn ngữ lập trình không những cung cấp các kiến
thức về lập trình, nó còn giúp rèn luyện khả năng tư duy, thông qua việc
giải quyết các bài toán, các giải thuật trong lập trình điều khiển. Hiện
nay có nhiều ngôn ngữ lập trình khác nhau, mỗi ngôn ngữ có thế mạnh,
cũng như phạm vi áp dụng riêng. Tuy nhiên, Ngôn ngữ lập trình C đã
được lựa chọn để dạy trong khối kỹ thuật. C là ngôn ngữ chuẩn và nền
tảng. Nhiều ứng dụng, trong đó có ứng dụng hệ thống, được xây dựng
từ ngôn ngữ C. Mặt khác, ngôn ngữ C được xem là ngôn ngữ nguồn gốc
và nền tảng của ngôn ngữ lập trình cấp cao (high-level programming
language). Theo quan sát và kinh nghiệm thực tế, các ngôn ngữ phát
triển sau đều dựa trên kiến trúc, hoặc có cấu trúc câu lệnh, vận hành của
câu lệnh giống như ngôn ngữ C. Do đó, việc học tốt, cũng như hiểu và
vận dụng tốt ngôn ngữ C sẽ giúp lập trình viên có thể dễ dàng tiếp cận
các ngôn ngữ lập trình hiện đại khác. Tài liệu Giáo trình Ngôn ngữ lập
trình C cung cấp cho sinh viên khối kỹ thuật các kiến thức cơ sở về
ngôn ngữ lập trình, ngôn ngữ lập trình C, hiểu các đối tượng trong ngôn
ngữ lập trình, hiểu cách vận hành của các câu lệnh, các cấu trúc điều
kiện, cấu trúc lập, hàm, mảng, con trỏ. Từ đó người học có thể vận dụng
vào trong các lĩnh vực khác như lập trình giao diện, lập trình game, lập
trình vi điều khiển, vi xử lý, lập trình cho các thiết bị lập trình được…
3
Kỹ thuật lập trình đòi hỏi nhiều kỹ năng tư duy, kinh nghiệm và cả kiến
thức. Trong đó, kỹ năng tư duy là cách mà người lập trình đưa ra các
giải thuật để giải quyết bài toán hiệu quả. Kinh nghiệm giúp người lập
trình chuyển đổi từ ý tưởng sang chương trình một cách tối ưu. Chính vì
thế, học lập trình đòi hỏi nhiều yếu tố hơn một số môn học khác. Cụ thể,
thực hành luôn được đòi hỏi kèm theo hoặc song song với quá trình học
lý thuyết. Quá trình thực hành giúp người học hình dung được, nắm được
cách vận hành của từng cấu trúc lệnh. Do đó, nhóm biên soạn đã cố gắng
trình bày cụ thể lý thuyết, giải thích chương trình và đưa ra một số bài ví dụ
mẫu nhằm giúp người học có thể tiếp cận kỹ thuật lập trình một cách dễ
nhất. Để đạt hiệu quả cao trong quá trình học tập, người học cần thực
hành lại các ý tưởng lập trình, các bài mẫu được trình bày trong tài liệu.
Giáo trình gồm những phần sau:
Chương 1: Giới thiệu
Chương 2: Lệnh rẽ nhánh có điều kiện
Chương 3: Lệnh vòng lặp
Chương 4: Mảng và chuỗi
Chương 5: Con trỏ
Chương 6: Hàm
Chương 7: Kiểu dữ liệu tự tạo
Chương 8: Tiền xử lý
Cuối cùng, tuy đã rất cố gắng biên soạn và chỉnh sửa nhưng chắc
hẳn không thể tránh khỏi những thiếu sót, rất mong nhận được những
đóng góp quý báu từ sinh viên và quý đồng nghiệp để tài liệu này được
hoàn thiện hơn trong những lần tái bản tiếp theo.
Mọi ý kiến phản hồi xin gửi về:
Bộ môn Kỹ thuật Máy tính – Viễn thông, Khoa Điện-Điện tử,
Trường Đại học Sư phạm Kỹ thuật TP HCM
Email: sontn@hcmute.edu.vn, leminh@hcmute.edu.vn, hatn@
hcmute.edu.vn, thanhlm@hcmute.edu.vn
Nhóm tác giả
4
MỤC LỤC
LỜI NÓI ĐẦU..................................................................................... 3
Chương 1 GIỚI THIỆU................................................................... 9
1.1. CHƯƠNG TRÌNH VÀ NGÔN NGỮ LẬP TRÌNH.............. 9
1.2. GIẢI THUẬT VÀ LƯU ĐỒ................................................. 13
1.3. NGÔN NGỮ LẬP TRÌNH C................................................ 19
1.4. MỘT CHƯƠNG TRÌNH C ĐƠN GIẢN.............................. 20
1.5. MỘT CHƯƠNG TRÌNH C KHÁC: CỘNG HAI SỐ
NGUYÊN............................................................................... 24
1.6. CÁC BƯỚC BIÊN DỊCH CHƯƠNG TRÌNH C.................. 27
1.7. TỪ KHÓA VÀ TÊN GỌI...................................................... 28
1.8. BIẾN...................................................................................... 30
1.9. HẰNG SỐ............................................................................. 34
1.10. CÁC PHÉP TOÁN TRONG C............................................ 35
1.11. XUẤT NHẬP DỮ LIỆU..................................................... 42
1.11.1. Hàm nhập dữ liệu...................................................... 42
1.11.2. Hàm xuất dữ liệu....................................................... 43
1.12. BÀI TẬP.............................................................................. 44
Chương 2 LỆNH RẼ NHÁNH CÓ ĐIỀU KIỆN.......................... 47
2.1. LỆNH ĐƠN VÀ LỆNH PHỨC............................................ 47
2.1.1 Lệnh đơn...................................................................... 47
2.1.2 Lệnh phức/ Khối lệnh................................................... 47
2.2. CÁC DẠNG CẤU TRÚC CHƯƠNG TRÌNH..................... 48
2.2.1 Cấu trúc tuần tự............................................................ 48
2.2.2 Cấu trúc rẽ nhánh......................................................... 50
2.3. LỆNH RẼ NHÁNH IF.......................................................... 51
2.3.1 Lệnh if thiếu................................................................. 51
2.3.2 Lệnh if đủ..................................................................... 55
5
2.3.3 Lệnh if … else if … else.............................................. 57
2.3.4 Cấu trúc if lồng nhau.................................................... 60
2.4. TOÁN TỬ ĐIỀU KIỆN BA NGÔI....................................... 61
2.5. LỆNH RẼ NHÁNH SWITCH... CASE................................ 62
2.5.1 Cú pháp ....................................................................... 62
2.5.2 Hoạt động .................................................................... 62
2.5.3 Giải thích . ................................................................... 63
2.5.4 Ví dụ minh họa............................................................. 64
2.6. BÀI TẬP................................................................................ 66
Chương 3 LỆNH VÒNG LẶP....................................................... 71
3.1. LỆNH for.............................................................................. 71
3.1.1. Cú pháp....................................................................... 71
3.1.2. Hoạt động.................................................................... 71
3.1.3. Ví dụ minh họa............................................................ 73
3.2. LỆNH WHILE...................................................................... 77
3.2.1. Cú pháp....................................................................... 77
3.2.2. Hoạt động.................................................................... 77
3.2.3. Ví dụ minh họa............................................................ 78
3.3. LỆNH DO .... WHILE.......................................................... 81
3.3.1. Cú pháp....................................................................... 81
3.3.2. Hoạt động.................................................................... 81
3.3.3. Ví dụ minh họa............................................................ 82
3.4. CÂU LỆNH BREAK............................................................ 84
3.5. CÂU LỆNH CONTINUE..................................................... 85
3.6. CÂU LỆNH GOTO VÀ NHÃN........................................... 86
3.7. BÀI TẬP................................................................................ 88
Chương 4 MẢNG VÀ CHUỖI....................................................... 93
4.1. MẢNG................................................................................... 93
4.1.1. Mảng 1 chiều............................................................... 93
4.1.2. Mảng 2 chiều............................................................. 103
4.2. CHUỖI VÀ MẢNG CHUỖI.............................................. 106
6
4.2.1. Chuỗi......................................................................... 106
4.2.2. Mảng chuỗi................................................................ 109
4.2.3. Một số hàm liên quan đến ký tự và chuỗi ký tự.........110
4.3. BÀI TẬP...............................................................................115
Chương 5 CON TRỎ.................................................................... 121
5.1. GIỚI THIỆU....................................................................... 121
5.2. KHAI BÁO VÀ SỬ DỤNG CON TRỎ............................. 122
5.3. CON TRỎ VÀ MẢNG....................................................... 124
5.4. CẤP PHÁT BỘ NHỚ ĐỘNG............................................. 127
5.4.1. Hàm malloc............................................................... 129
5.4.2. Hàm free()................................................................. 130
5.4.3. Hàm calloc và realloc................................................ 131
5.5. BÀI TẬP ............................................................................. 134
Chương 6 HÀM............................................................................. 136
6.1. GIỚI THIỆU....................................................................... 136
6.2. ĐỊNH NGHĨA HÀM........................................................... 137
6.3. PHÂN LOẠI HÀM THEO THAM SỐ VÀ GIÁ TRỊ
TRẢ VỀ................................................................................140
6.4. KHAI BÁO HÀM............................................................... 147
6.5. TRUYỀN THAM SỐ CHO HÀM...................................... 148
6.5.1. Truyền giá trị cho tham số hàm................................. 148
6.5.2. Truyền địa chỉ cho tham số hàm............................... 149
6.5.3.Truyền mảng cho hàm................................................ 151
6.6. ĐỆ QUY.............................................................................. 154
6.7. MỘT SỐ HÀM THƯ VIỆN CHUẨN................................ 156
6.8. BÀI TẬP.............................................................................. 156
Chương 7 KIỂU DỮ LIỆU TỰ TẠO.......................................... 159
7.1. KIỂU CẤU TRÚC.............................................................. 159
7.1.1. Giới thiệu kiểu cấu trúc............................................. 159
7.1.2. Định nghĩa một kiểu cấu trúc mới............................. 159
7.1.3. Khai báo biến kiểu cấu trúc....................................... 161
7
7.1.4 .Truy xuất tới các thành phần của biến cấu trúc......... 163
7.1.5. Mảng một chiều kiểu cấu trúc................................... 166
7.1.6. Con trỏ kiểu cấu trúc................................................. 170
7.1.7. Sử dụng kiểu cấu trúc với Hàm................................. 176
7.2. KIỂU UNION..................................................................... 182
7.2.1. Giới thiệu kiểu Union................................................ 182
7.2.2. Định nghĩa một kiểu Union mới............................... 183
7.2.3. Khai báo và sử dụng biến kiểu Union....................... 183
7.3. KIỂU LIỆT KÊ (ENUMERATION)................................... 185
7.3.1. Giới thiệu kiểu liệt kê................................................ 185
7.3.2. Định nghĩa một kiểu Enumeration mới..................... 186
7.3.3. Khai báo và sử dụng biến kiểu liệt kê....................... 186
7.4. BÀI TẬP.............................................................................. 188
Chương 8 TIỀN XỬ LÝ............................................................... 195
8.1. GIỚI THIỆU....................................................................... 195
8.2. CHỈ THỊ BAO HÀM TỆP (INCLUDE)............................. 196
8.3. CHỈ THỊ ĐỊNH NGHĨA #DEFINE................................... 203
8.4. CHỈ THỊ ĐIỀU KHIỂN TRÌNH BIÊN DỊCH.................... 204
8.5. BÀI TẬP.............................................................................. 206
TÀI LIỆU THAM KHẢO.............................................................. 207
8
CHƯƠNG 1
GIỚI THIỆU
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Phân biệt được các loại ngôn ngữ lập trình.
- Vẽ được lưu đồ giải thuật cho các yêu cầu lập trình.
- Sử dụng được các thành phần cơ bản trong ngôn ngữ C: câu
lệnh, câu chú thích, biến, hằng, phép toán.
- Sử dụng được các hàm xuất, nhập dữ liệu trong C.
- Viết được các chương trình C cơ bản theo đúng cấu trúc
chương trình.
1.1. CHƯƠNG TRÌNH VÀ NGÔN NGỮ LẬP TRÌNH
Chương trình máy tính (computer program) là một chuỗi các lệnh
được viết bởi một loại ngôn ngữ lập trình để thực hiện một hoặc một
số tác vụ nào đó. Trong đó ngôn ngữ lập trình là một tập quy ước
cụ thể được sử dụng để viết chương trình cho máy tính. Nói cách
khác, chương trình được xem như một loạt các lệnh mà máy tính
có thể đọc hiểu và thực thi theo đó. Khi nói đến chương trình máy
tính, người ta thường hình dung ra chương trình được chạy trên một
máy tính. Tuy nhiên, chương trình, nhìn ở góc độ kỹ thuật thì là một
chuỗi các mã lệnh dùng để điều khiển phần cứng, cụ thể là bộ vi xử
lý, bộ vi điều khiển. Có nhiều loại ngôn ngữ lập trình khác nhau.
Các ngôn ngữ lập trình được phân loại theo mức độ trừu tượng, trong
đó gồm có:
9
 Ngôn ngữ máy (machine language) hay còn được gọi là mã
máy (machine code).
 Ngôn ngữ lập trình cấp thấp (low-level programming
language).
 Ngôn ngữ lập trình cấp cao (high-level programming
language).
Hình 1.1. Phân loại ngôn ngữ lập trình
Ngôn ngữ cấp thấp cũng được chia ra 2 mức khác nhau, ngôn
ngữ máy hay mã máy và hợp ngữ (assembly language). Ngôn ngữ
máy (machine code) là các đoạn mã hệ 16 (hexadecimal code) hoặc
mã nhị phân (binary code) được sử dụng để lập trình cho các hệ
thống máy tính đầu tiên và được xem là thế hệ đầu tiên của ngôn ngữ
lập trình. Mã máy được viết dưới dạng nhị phân (0 và 1) giúp máy
tính (hay phần cứng hệ thống số) có thể đọc và thực thi trực tiếp các
tác vụ mà người lập trình thiết kế. Một lợi thế của ngôn ngữ máy là
máy tính có thể đọc trực tiếp và thực thi mà không cần tới một trình
biên dịch, hay chuyển đổi nào. Điều này giúp các chương trình được
10
viết dưới dạng ngôn ngữ máy có thể thực thi nhanh và chính xác.
Tuy nhiên, bên cạnh những ưu điểm, lập trình bằng ngôn ngữ máy
cũng có nhiều nhược điểm. Cụ thể là các mã nhị phân làm cho người
lập trình khó nhớ, khó hiểu, khó chỉnh sửa. Hơn nữa, việc sửa lỗi,
tìm lỗi chương trình cũng trở nên khó khăn thông qua việc dò, tham
chiếu các mã nhị phân với các lệnh.
Một ví dụ chương trình được viết bằng ngôn ngữ máy như sau:
Lệnh bằng mã nhị phân
Lệnh bằng mã Hex
0010 0001 0000 0100
2104
0001 0001 0000 0101
1105
0011 0001 0000 0110
3106
0111 0000 0000 0001
7001
0000 0000 0101 0011
0053
1111 1111 1111 1110
FFFE
Hợp ngữ (Assemly language) là ngôn ngữ thế hệ thứ 2 của ngôn
ngữ lập trình. Thay vì sử dụng mã nhị phân, 0 và 1, để thể hiện các lệnh,
hợp ngữ sử dụng các từ khóa, các ký hiệu bằng tiếng Anh để diễn tả các
câu lệnh. Hợp ngữ giúp cho người lập trình có thể hiểu được mỗi dòng
lệnh dễ dàng hơn, từ đó việc chỉnh sửa, thay đổi chương trình, cũng như
quản lý chương trình đơn giản hơn so với lập trình bằng ngôn ngữ máy.
Tuy nhiên, máy tính nói chung và các bộ xử lý nói riêng chỉ có thể hiểu
được ngôn ngữ máy hay các mã nhị phân, do đó, cần một trình chuyển
đổi hay biên dịch (compiler) để chuyển từ hợp ngữ sang mã máy, giúp
máy tính nói chung và các bộ vi xử lý nói riêng có thể đọc hiểu và thực
thi chương trình.
11
Ví dụ một chương trình được viết bằng hợp ngữ như sau:
section .text
global _start ;must be declared for linker (gcc)
_start:
mov
mov
mov
mov
int
edx,len
ecx,msg
ebx,1
eax,4
0x80
;tell linker entry point
;message length
;message to write
;file descriptor (stdout)
;system call number (sys_write)
;call kernel
mov
mov
mov
mov
int
edx,9
ecx,s2
ebx,1
eax,4
0x80
;message length
;message to write
;file descriptor (stdout)
;system call number (sys_write)
;call kernel
mov
int
eax,1
0x80
;system call number (sys_exit)
;call kernel
section .data
msg db ‘Displaying 9 stars’,0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db ‘*’
Ngôn ngữ cấp cao (high-level programming language) là ngôn
ngữ gần với ngôn ngữ con người và được sử dụng phổ biến hiện nay.
Chương trình được viết bằng ngôn ngữ cấp cao giúp người lập trình
dễ đọc, dễ hiểu, dễ chỉnh sửa. Với ngôn ngữ cấp cao, người lập trình
dễ dàng chuyển đổi ý tưởng, các giải thuật thành các đoạn chương
trình mà không cần quan tâm nhiều đến việc quản lý bộ nhớ, quản
lý các thanh ghi, các chi tiết phần cứng như đã làm trong lập trình
bằng ngôn ngữ cấp thấp. Cũng giống như hợp ngữ, ngôn ngữ cấp cao
cần được chuyển đổi sang mã máy (ngôn ngữ máy) trước khi được
thực thi. Có nhiều ngôn ngữ cấp cao được phát triển đến thời điểm
hiện tại. Trong đó một số ngôn ngữ hình thành lâu đời như C, Pascal,
Fortran, Basic, Java.
12
Ví dụ, một chương trình viết bằng ngôn ngữ cấp cao như sau:
#include <stdio.h>
int main()
{
printf(“Hello, World!\n”);
return 0;
}
1.2. GIẢI THUẬT VÀ LƯU ĐỒ
Một chương trình được xem như một sự kết hợp của giải thuật và
cách thể hiện của giải thuật thông qua ngôn ngữ lập trình. Giải thuật
(Algorithm) là các cách để giải quyết bài toán lập trình, hay phương
pháp cụ thể để thực hiện các tác vụ. Giải thuật được xem là cốt lõi để
giải quyết các vấn đề lập trình. Giải thuật còn thể hiện các ý tưởng của
người lập trình nhằm giúp chương trình đạt hiệu quả cao nhất. Chương
trình cuối cùng là tập hợp của các miêu tả giải thuật thông qua một ngôn
ngữ lập trình nào đó.
Lập trình là một quá trình tư duy logic giải quyết một vấn đề nào
đó. Do đó, giải thuật là một yếu tố quan trọng trong lập trình. Ví dụ,
để giải một phương trình bậc 2, người lập trình phải biết các bước cần
thiết để tự giải một phương trình. Nói cách khác là người lập trình có
thể tự giải được thông qua các bước, các bước đó chính là giải thuật.
Khi chúng ta không biết giải quyết vấn đề từ đâu, thì thật khó để viết
một chương trình máy tính để giải quyết vấn đề đó. Lưu đồ là thể hiện
của giải thuật bằng hình ảnh. Nó thể hiện các bước giải quyết một vấn
đề dựa trên một giải thuật nào đó. Các sinh viên mới học lập trình hoặc
đã biết lập trình ở mức cơ bản thường có thói quen bỏ qua bước vẽ lưu
đồ nhằm tiết kiệm thời gian. Điều này mang tính chất chủ quan vì các
bạn cho rằng mình đã quen với ngôn ngữ và các công việc lập trình.
Việc viết lưu đồ mang tính hình thức và mất thêm thời gian, điều này có
thể đúng với một số chương trình nhỏ, một vài ví dụ cơ bản mà các bạn
13
đã nắm trong đầu từng bước xử lý. Tuy nhiên, sẽ trở nên tai hại khi xây
dựng các chương trình xử lý ở mức độ phức tạp mà bỏ qua bước xây
dựng lưu đồ. Khi bắt đầu một chương trình, các ý tưởng cần được liệt
kê, phân tích một cách cụ thể. Tiếp theo đó, lưu đồ để thực hiện các ý
tưởng, các yêu cầu đó phải được viết ra một cách cẩn thận. Thực tế cho
thấy, nhiều sinh viên viết hoàn chỉnh chương trình trước rồi sau đó mới
viết lại lưu đồ giải thuật do yêu cầu phải trình bày trong báo cáo. Điều
này đi ngược với quá trình lập trình. Việc xây dựng và kiểm tra kỹ lưu
đồ cho phép người lập trình đánh giá, kiểm tra chương trình đã đáp ứng
được yêu cầu hay chưa; nó cho phép người lập trình tìm ra được các lỗi
mà chương trình khi chạy thực tế có thể mắc phải hoặc phát hiện một số
trường hợp bị bỏ sót. Khi lưu đồ đã hoàn chỉnh, việc lập trình chỉ là một
cách biên dịch từ các hình khối sang một ngôn ngữ nào đó. Vì thế, khi
viết lưu đồ có thể dùng ngôn ngữ mô tả sao cho đọc hiểu được, không
nhất thiết phải dùng một ngôn ngữ nhất định nào. Người lập trình dựa
vào các bộ nguyên tắc cụ thể của ngôn ngữ lập trình mà mình hướng
tới, xây dựng chương trình từ lưu đồ. Như vậy, tính chính xác, tính khoa
học, logic của một chương trình thể hiện ở chỗ xây dựng lưu đồ, giải
thuật có logic, chính xác hay không. Nếu lưu đồ giải thuật chính xác và
logic, mà chương trình chạy chưa chính xác thì giống như bạn viết một
bài văn mà sai chính tả, hoặc bạn dịch một bài từ tiếng Việt sang tiếng
Anh mà sai ngữ pháp, từ vựng. Vấn đề tác giả muốn nhấn mạnh ở đây là
các bạn mới học lập trình cần xây dựng tốt lưu đồ giải thuật, cũng như
học cách thể hiện ý tưởng thông qua lưu đồ, giải thuật.
Lưu đồ (Flowchart) là cách biểu diễn bằng hình ảnh của giải thuật
hay các bước xử lý và thực hiện của chương trình. Lưu đồ được viết
dưới dạng các khối được quy ước cụ thể nhằm giúp người lập trình có
thể đọc hiểu và chuyển các ý tưởng, giải thuật sang ngôn ngữ lập trình.
Trong quá trình vẽ lưu đồ giải thuật, các ký hiệu, từ khóa, ngôn ngữ thể
hiện có thể tùy chọn sao cho người đọc có thể hiểu được. Ngôn ngữ thể
hiện ở đây không nhất thiết là một ngôn ngữ lập trình cụ thể nào. Nó
có thể là một ngôn ngữ thông thường mà người không có kiến thức lập
14
trình cũng có thể đọc và hiểu được. Các ngôn ngữ dùng để biểu diễn
trong lưu đồ như vậy còn được gọi là code giả (Psuedo code). Một số
quy ước khi biểu diễn giải thuật dưới dạng lưu đồ như sau:
Bắt đầu hay kết thúc của một chương trình.
Nhập hay xuất dữ liệu
Các xử lý, tính toán
Rẽ nhánh hay các quyết định có điều kiện
Chương trình con, hàm, thủ tục
Các điểm đầu cuối dùng để nối lưu đồ khi sang trang
Chiều đi của quá trình xử lý
Ví dụ 1.1. Lưu đồ cho chương trình cộng 2 số được nhập từ bàn phím.
Start
Declare variables num1, num2, and sum
Read num1 and num2
sum ←num1+num2
Display sum
Stop
15
Trong lưu đồ trên, điểm đầu cuối cho quá trình bắt đầu và kết
thúc được phân biệt bởi từ khóa start và end trong hình oval. Các
từ khóa này có thể sử dụng begin và end, start và stop hoặc bằng
bất kỳ ngôn ngữ nào chỉ định việc bắt đầu và kết thúc. Để biểu diễn
tổng sum là kết quả của phép cộng 2 số a và b, có thể sử dụng mô tả
sum ← num1 + num2 hoặc đơn giản hơn có thể biểu diễn bởi
sum = num1 + num2.
Ví dụ 1.2. Lưu đồ cho bài toán in ra số lớn nhất trong 3 số được
nhập vào từ bàn phím.
Start
Declare variables a, b, c
Read a,b,c
a>b?
True
False
True
b>c?
False
Print c
Print b
Stop
16
False
a>c?
True
Print a
Ví dụ 1.3. Lưu đồ cho bài toàn giải phương trình bậc 2: ax2 + bx +
c=0
Start
Declare variables a, b, c, d
Declare x1 and x2
Read a, b, c
d = b*b-4*a*c
d<0?
True
False
x1 =
−b+ d
2*a
x2 =
−b− d
2*a
Print:
Non-real root
Print: x1, x2
Stop
17
Trong các khối quyết định có điều kiện (các khối hình thoi) luôn
có 2 ngõ ra, một cho điều kiện đúng và một cho điều kiện sai. Các từ
khóa biểu diễn cho cặp luận lý đúng sai có thể dùng là True/False, Yes/
No, T/F, Y/N, Đ/S hay một ngôn ngữ nào khác miễn là nó thỏa mãn ý
nghĩa biểu diễn luận lý đúng và sai. Chương trình máy tính là một quá
trình xử lý tuần tự, trong trường hợp xử lý song song sẽ dùng các kỹ
thuật khác và chưa được đề cập ở đây, chính vì điều đó, từ vị trí bắt đầu
chỉ có một đường duy nhất đi đến vị trí kết thúc.
Ví dụ 1.4. Hãy giải thích hoạt động của chương trình dựa vào lưu
đồ sau đây:
18
Ví dụ 1.5. Hãy phân tích hoạt động của chương trình dựa vào lưu
đồ sau đây:
1.3. NGÔN NGỮ LẬP TRÌNH C
Mặc dù hiện nay có nhiều ngôn ngữ được ứng dụng trong cả lập
trình hệ thống và lập trình ứng dụng, ngôn ngữ vẫn C được xem như
ngôn ngữ chuẩn và cơ bản để tiếp cận lập trình máy tính cũng như lập
trình các thiết bị lập trình như vi xử lý, vi điều khiển. Ngôn ngữ C được
phát triển bởi Dennis Ritchie tại phòng thí nghiệm Bell Laboratories
năm 1972. C trở thành ngôn ngữ được dùng để xây dựng hệ điều hành
UNIX và trở nên phổ biến tại thời điểm đó. Hiện nay, có nhiều ngôn
ngữ lập trình phổ biến cùng tồn tại và phát triển song song với ngôn
19
ngữ C. Các ngôn ngữ phổ biến hiện nay có thể kể đến là: Java, Python,
C++, C#, Swiff, Ruby, Perl, PHP, Visual Basic, Objective-C, R, Go,…
Trong số đó, nhiều ngôn ngữ phát triển từ ngôn ngữ C chuẩn như C++,
C#, Objective-C. Thực tế cho thấy, các ngôn ngữ khác nhau sử dụng
bộ từ khóa khác nhau dùng để phát biểu câu lệnh, tuy nhiên, cấu trúc,
cách vận hành các câu lệnh và các phát biểu có nhiều nét tương đồng
với nhau. Chính vì điều đó, hiểu và lập trình tốt ngôn ngữ C giúp lập
trình viên có thể tiếp cận các ngôn ngữ các một cách nhanh và đơn giản.
Tuy không phải là một ngôn ngữ hiện đại, C vẫn là một trong
các ngôn ngữ được sử dụng phổ biến hiện nay. Ngôn ngữ lập trình C
được ứng dụng trong một số lĩnh vực như lập trình hệ thống (system
programming), đặc biệt C là ngôn ngữ tiêu chuẩn cho việc lập trình
các hệ thống nhúng dựa trên hệ điều hành Linux (Embedded system
programming), lập trình điều khiển với các hệ thống điện tử dân dụng, công
nghiệp, sử dụng vi xử lý, vi điều khiển. Một số ngôn ngữ lập trình khác
có cấu trúc giống hoặc gần giống ngôn ngữ lập trình C được phát triển
như ngôn ngữ mô tả phần cứng Verilog (Verilog hardware description
language) được dùng trong thiết kế vi mạch số, Ngôn ngữ Python
được ứng dụng rộng rãi trong lĩnh vực trí tuệ nhân tạo, Swiff và
Objective-C được sử dụng để lập trình ứng dụng trên các thiết bị di
động chạy hệ điều hành iOS. Nắm vững ngôn ngữ lập trình C là nền
tảng để tiếp cận các ngôn ngữ lập trình khác nhanh hơn, hiệu quả hơn.
1.4. MỘT CHƯƠNG TRÌNH C ĐƠN GIẢN
Một chương trình C bao gồm các hàm, các biến và các câu lệnh
được đặt trong cùng một tập tin hoặc có thể trong các tập tin khác nhau.
Trong ví dụ đầu tiên tiếp cận với ngôn ngữ C, chúng ta sẽ tiến hành viết
một chương trình bằng ngôn ngữ C, biên dịch và chạy trên máy tính.
Một chương trình C thực thi việc in ra màn hình một dòng chữ
được viết như bên dưới:
20
SST dòng
lệnh
1
Nội dung chương trình
/* Vi du:
2
Chuong trinh C dau tien */
3
#include <stdio.h>
4
#include <conio.h>
5
//bat dau chuong trinh chinh
6
int main (void)
7
{
8
printf(“Chao mung den voi C! \n”);
9
getch();
10
return 0; /* cho biet chuong trinh da ket
11
thuc thanh cong */
12
13
} //ket thuc chuong trinh chinh
Sau khi thực thi, chương trình sẽ in ra màn hình dòng chữ Chao
mung den voi C!, như ở hình sau:
Chao mung den voi C!
Ở dòng lệnh số 1 và số 2, ta có hai câu:
/* Vi du:
Chuong trinh C dau tien */
được viết bắt đầu bằng ký hiệu /* và kết thúc bởi ký hiệu */ để thể hiện
rằng đây là hai câu chú thích – comment. Đây là cách viết chú thích
trên nhiều dòng lệnh. Câu chú thích được đặt trong chương trình để giải
thích hoặc làm rõ thêm cho chương trình mà không ảnh hưởng đến hoạt
động của chương trình khi chạy. Các câu chú thích sẽ được bỏ qua khi
biên dịch chương trình.
21
Ở dòng lệnh số 3, câu lệnh
#include<stdio.h>
là một lệnh tiền xử lý trong ngôn ngữ C. Các câu lệnh tiền xử lý
sẽ bắt đầu bằng dấu # và được xử lý trước khi chương trình được biên
dịch. Câu lệnh này là câu lệnh khai báo thư viện sẽ yêu cầu bộ tiền xử
lý bao gồm file thư viện stdio.h vào chương trình, đây là thư viện xuất/
nhập dữ liệu tiêu chuẩn trong C. Thư viện này sẽ chứa các thông tin
được sử dụng bởi trình biên dịch khi tiến hành biên dịch cho các hàm
xuất nhập dữ liệu cơ bản, như hàm printf hoặc scanf.
Ở dòng lệnh số 5, câu
//bat dau chuong trinh chinh
cũng là một câu chú thích. Đây là câu chú thích trên 1 dòng lệnh.
Để viết câu chú thích trên 1 dòng lệnh, ta sẽ bắt đầu bằng ký hiệu //. Câu
chú thích trên 1 dòng lệnh sẽ tự động kết thúc khi ta đưa con trỏ xuống
dòng mới.
Ở dòng lệnh số 6, câu
int main (void)
đánh dấu phần chương trình chính, phần bắt buộc phải có trong
mỗi chương trình C. Các thông số viết phía trước và phía sau từ main
cho biết main là một khối chương trình được gọi là hàm – function.
Một chương trình C có thể có một hoặc nhiều hàm nhưng bắt buộc phải
có một hàm main, hay còn gọi là chương trình chính. Phía bên trái từ
main là từ int thể hiện rằng hàm main sẽ trả về một giá trị số nguyên
Thông số (void) phía bên phải của main thể hiện rằng hàm main không
cần nhận bất kỳ thông tin đầu vào nào. Điều này sẽ được giải thích kỹ
hơn ở chương Hàm.
Dấu mở ngoặc { ở dòng lệnh số 7 thể hiện vị trí bắt đầu của hàm
main và tương ứng dấu đóng ngoặc } ở dòng 13 thể hiện vị trí kết thúc
22
của hàm main. Phần được viết giữa một cặp dấu ngoặc { } được gọi là
một khối lệnh.
Ở dòng lệnh số 8, câu
printf(“Chao mung den voi C! \n”);
là câu lệnh yêu cầu in ra màn hình một chuỗi các ký tự với nội
dung Chao mung den voi C! . Mỗi câu lệnh trong C sẽ được kết thúc
bằng một dấu chấm phẩy (;)
Ở dòng lệnh số 9, câu lệnh
getch();
là lệnh chờ nhập một ký tự bất kỳ từ bàn phím. Lệnh này được sử dụng
nhằm mục đích dừng chương trình để chờ người dùng nhập một ký tự
bất kỳ từ bàn phím. Để chương trình có thể hiểu và biên dịch được lệnh
getch(); chúng ta cần lệnh khai báo thư viện #include <conio.h> như ở
dòng lệnh số 4.
Ở dòng lệnh số 10, câu lệnh
return 0; /* cho biet chuong trinh da ket thuc thanh
cong */
là câu lệnh kết thúc hàm main và trả về số 0. return là một từ khóa
dùng để kết thúc một hàm bằng cách trả về một giá trị, và giá trị số 0
trong câu lệnh này cho biết chương trình chính (hàm main) đã kết thúc
thành công. Điều này sẽ được giải thích kỹ hơn ở chương 6.
Như vậy, tới đây, ta có thể thấy cấu trúc chung của một chương
trình C về cơ bản sẽ là:
#include <TênThưViện.h>
int main (void)
{
//Nội dung chương trình chính
return 0;
}
23
1.5. MỘT CHƯƠNG TRÌNH C KHÁC: CỘNG HAI SỐ NGUYÊN
Chúng ta sẽ viết một chương trình C thực hiện việc nhập hai số
nguyên từ bàn phím, sau đó thực hiện tính tổng hai số này và cuối
cùng in kết quả ra màn hình. Nội dung của chương trình được thể
hiện ở hình dưới.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a; //khai bao bien a, de luu so thu nhat
int b;
//khai bao bien b, de luu so thu hai
int tong;//khai bao bien tong, de luu ket qua
printf (“Nhap so thu nhat : \n”);
scanf(“%d”,&a); //nhap so nguyen tu ban phim
printf(“Nhap so thu hai: \n”);
scanf(“%d”,&b); //nhap so nguyen tu ban phim
tong = a + b;
//tinh tong
printf(“Tong la: %d \n”,tong); //in ket qua
getch(); //cho nhap 1 ky tu bat ky
return 0;
}
Kết quả sau khi thực thi chương trình như sau:
1
2
3
4
5
24
Nhap so thu nhat :
5
Nhap so thu hai:
7
Tong la: 12
Trong đó, giá trị số 5 và 7 ở dòng kết quả thứ 2 và dòng kế quả thứ
4 là giá trị dữ liệu do người dùng nhập vào từ bàn phím.
Trong chương trình này, ở dòng lệnh số 1 và số 2 là hai câu lệnh
khai báo hai thư viện của hệ thống : stdio.h và conio.h . Dòng lệnh số 4
đánh dấu chương trình chính, dấu mở ngoặc { ở dòng lệnh số 5 thể hiện
vị trí bắt đầu nội dung chương trình chính và dấu đóng ngoặc } ở dòng
lệnh số 21 dùng để kết thúc nội dung chương trình chính.
Ở dòng lệnh số 6, 7 và 8, các câu lệnh
int a;
//khai bao bien a, de luu so thu nhat
int b;
//khai bao bien b, de luu so thu hai
int tong;
//khai bao bien tong, de luu ket qua
được dùng để khai báo ra các vùng nhớ phục vụ cho việc lưu trữ
dữ liệu, được gọi là biến – variable. Một biến là một vùng nhớ trong
bộ nhớ RAM dùng để lưu trữ dữ liệu trong quá trình xử lý của chương
trình. Ba câu lệnh trên là ba câu lệnh khai bao biến để tạo ra 3 biến có
tên gọi lần lượt là a, b và tong có kiểu dữ liệu là int (kiểu số nguyên).
Điều này có nghĩa là các biến này có thể lưu trữ được các giá trị số
nguyên, ví dụ như: 9, -15, hoặc 12345. Tất cả các biến cần phải được
khai báo một lần trước khi sử dụng trong chương trình với một tên gọi
và một kiểu dữ liệu cố định.
Ở dòng lệnh số 10, câu lệnh
printf (“Nhap so thu nhat : \n”);
có tác dụng in ra màn hình câu thông báo Nhap so thu nhat :
Câu lệnh tiếp theo ở dòng số 11
scanf(“%d”,&a);
//nhap so nguyen tu ban phim
sử dụng hàm scanf để cho phép người dùng nhập một số nguyên
từ bàn phím và lưu vào biến a. Hàm scanf này có hai tham số: “%d” và
25
&a. Tham số đầu tiên “%d” được gọi là mã định dạng, cho biết kiểu dữ
liệu của dữ liệu mà người dùng sẽ nhập vào. Trong trường hợp này,
“%d” là mã định dạng cho kiểu số nguyên. Trong tham số thứ hai
(&a), dấu & được gọi là toán tử lấy địa chỉ. Theo sau toán tử lấy địa
chỉ là tên biến a dùng để xác định địa chỉ (vị trí) của biến a trong bộ
nhớ RAM.
Tương tự, câu lệnh ở dòng lệnh số 14
scanf(“%d”,&b);
//nhap so nguyen tu ban phim
cho phép người dùng nhập một số nguyên từ bàn phím và lưu
vào biến b.
Ở dòng lệnh số 16, câu lệnh
tong = a + b;
//tinh tong
thực hiện tính tổng của hai biến a và b bằng phép toán cộng +, sau
đó lưu kết quả vào vùng nhớ biến tong bằng phép toán gán = .
Câu lệnh ở dòng số 17
printf(“Tong la: %d \n”,tong);
//in ket qua
gọi hàm printf để in ra màn hình dòng chữ Tong la: đi kèm phía
sau là giá trị của biến tong. Hàm printf này có hai tham số, “Tong la:
%d \n” và tong . Tham số đầu tiên “Tong la: %d \n” là chuỗi định
dạng gồm chuỗi ký tự cần in ra màn hình Tong la: , mã định dạng
%d cho biết sẽ có một số nguyên được in ra và ký tự điều khiển \n
để đưa con trỏ xuống dòng. Tham số thứ hai, tong, sẽ xác định giá
trị dữ liệu cần in ra.
Câu lệnh ở dòng lệnh 19
getch(); //cho nhap 1 ky tu bat ky
dùng hàm getch() nhằm tác dụng chờ người dùng nhập một ký
tự bất kỳ từ bàn phím. Để sử dụng được hàm getch(), ta cần khai báo
26
thư viện conio.h như ở dòng lệnh số 2:
#include <conio.h>
1.6. CÁC BƯỚC BIÊN DỊCH CHƯƠNG TRÌNH C
Một chương trình trước khi được thực thi cần trải qua 4 bước như
hình dưới. Chương trình được viết và lưu dưới dạng các tập tin văn bản,
có phần mở rộng là .c cho các chương trình viết thuần túy bằng ngôn
ngữ C và .cpp cho các chương trình được viết bằng ngôn ngữ C++.
Các tập tin được lưu dưới dạng .h là các tập tin đầu (header), thông
thường các tập tin .h chứa các khai báo hàm hoặc có thể bao gồm các
định nghĩa (define).
 Preprocessor – Quá trình tiền xử lý là bước đầu tiên trong quá
trình biên dịch. Các tập tin chương trình được loại bỏ các phần chú
thích (comment). Đồng thời các chỉ thị được thể hiện sau dấu # được
xử lý. Các chỉ thị sau ký tự # bao gồm các khai báo thư viện, các định
nghĩa. Ví dụ:
#include <stdio.h>
#define PI
3.14
 Compilation – biên dịch: Chương trình được biên dịch sang
mã hợp ngữ (assembly code).
 Asembly – hợp dịch: Các mã hợp ngữ được biên dịch sang mã
máy (machine-code).
 Link- quá trình liên kết: trong trường hợp chương trình tham
chiếu đến các hàm thuộc các thư viện hoặc các hàm từ các tập tin mã
nguồn khác, trình liên kết sẽ tổng hợp các mã chương trình này lại để
tạo một tập tin mã nguồn có khả năng thực thi (executable machine
code). Tập tin cuối cùng này có phần mở rộng .exe. Đây là tập tin chứa
mã nhị phân để thực thi chương trình.
27
Hình 1.2. Các bước biên dịch chương trình C
1.7. TỪ KHÓA VÀ TÊN GỌI
Trong câu lệnh khai báo biến:
int a;
thì int được hiểu là từ khóa – keywork, trong trường hợp này
mang ý nghĩa là kiểu dữ liệu và a là tên gọi - identifier được đặt
cho biến.
 Từ khóa: là những từ được quy ước bởi ngôn ngữ C, mang
một ý nghĩa cố định mà người dùng không được phép thay đổi. Các từ
khóa trong C được liệt kê ở bảng dưới.
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
float
short
unsigned
continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
Bảng 1.1. Các từ khóa trong C
28
Tên gọi: là những từ được quy ước bởi người dùng, thường dùng
để gọi tên một số thành phần khi viết chương trình như: biến, hằng hoặc
hàm. Khi đặt một tên gọi trong C, cần tuân thủ một số quy ước như sau:
+ Tên gọi không được phép trùng với từ khóa.
+ Tên gọi chỉ bao gồm: ký số 0 - 9, chữ cái thường a – z, chữ cái
hoa A – Z, dấu gạch dưới _
+ Tên gọi không được bắt đầu bằng số.
+ Tên gọi có phân biệt chữ thường và chữ viết hoa.
Ví dụ một số tên gọi hợp lệ như:
a
Tên gọi hợp lệ
bien1
bien_1
thamSoThuNhat
Các tên gọi được đặt không tuân theo các quy ước trên thì sẽ là tên
gọi không hợp lệ, ví dụ như các tên gọi sau là không hợp lệ:
Tên gọi
không hợp lệ
2a
sai vì bắt đầu bằng số
if
sai vì trùng với từ khóa
bien@
sai vì chứa ký tự không hợp lệ @
tham so thu nhat
sai vì chứa khoảng trắng
Trong khi lập trình, các ngôn ngữ lập trình không cho phép đặt
các tên gọi trùng với tên từ khóa. Với ngôn ngữ lập trình C, hệ thống từ
khóa sử dụng toàn bộ ký tự thường. Do đó, để tránh việc trùng với các
từ khóa, các tên gọi có thể được đặt bằng các sử dụng một hoặc nhiều
ký tự in hoa. Ví dụ, đặt tên biến là “enum” là không hợp lệ vì trùng với
từ khóa. Tuy nhiên, tên biến là “Enum” là hoàn toàn hợp lệ vì nó không
trùng từ khóa.
29
1.8. BIẾN
Như đã đề cập ở phần trước, biến là một vùng nhớ trong bộ nhớ
RAM dùng để lưu trữ dữ liệu trong quá trình xử lý của chương trình.
Muốn tạo ra một biến, chúng ta cần tiến hành khai báo biến. Ví dụ,
trong chương trình trên, câu lệnh:
int a;
là câu lệnh khai báo biến có tên gọi là a với kiểu dữ liệu là kiểu
số nguyên int. Lúc này, chương trình sẽ tạo ra một vùng nhớ kiểu số
nguyên cho biến a và giá trị dữ liệu hiện tại đang chứa trong vùng nhớ
biến a là một giá trị ngẫu nhiên.
a
2019896881
Để quản lý giá trị ban đầu của biến sau khi khai báo ta có thể
khởi tạo giá trị ban đầu cho biến ngay tại thời điểm khai báo biến.
Ví dụ, câu lệnh
int a = 0;
sẽ tạo ra vùng nhớ cho biến a và khởi tạo giá trị ban đầu là số
0 cho vùng nhớ biến a.
0
a
Như vậy, cú pháp chung của câu lệnh khai báo biến trong C sẽ là:
TênKiểuDữLiệu
tênBiến;
hoặc:
TênKiểuDữLiệu
tênBiến = giá trị khởi tạo;
Ta cũng có thể khai báo một danh sách gồm nhiều biến có cùng
kiểu dữ liệu bằng một câu lệnh duy nhất. Ví dụ, có thể viết
int a, b, tong;
30
để khai báo nên 3 biến a, b, tong có kiểu dữ liệu là kiểu số nguyên,
thay cho 3 câu lệnh khai báo đã viết trước đó là:
int a;
int b;
int tong;
Tới đây, có thể thấy mỗi biến là một vùng nhớ độc lập có định
dạng dữ liệu nhất định được quy định bởi kiểu dữ liệu trong quá trình
khai báo biến. Bên cạnh việc quy định định dạng dữ liệu của vùng nhớ
một biến, kiểu dữ liệu còn quy định kích thước của vùng nhớ dành cho
biến đó. Một số kiểu dữ liệu cơ bản của C được liệt kê ở bảng dưới.
Tên kiểu
Kích thước
dữ liệu
vùng nhớ biến
Ví dụ
Định dạng dữ liệu
char
1 byte
unsigned char
1 byte
int
2 byte hoặc 4
byte
-3569
+137
unsigned int
2 byte hoặc 4
byte
23052
short
2 byte
Kiểu ký tự
Kiểu số nguyên
A
b
+423
-7
unsigned short 2 byte
153
long
4 byte
-2456783
+4539847
unsigned long
4 byte
345231
float
4 byte
5.6
double
8 byte
long double
10 byte
Kiểu số thực
245.78
3.1
Bảng 1.2. Các kiểu dữ liệu trong C
31
Trong một số ngôn ngữ lập trình hiện đại, biến chỉ cần được khai
báo trước khi sử dụng và tại bất kỳ vị trí nào trong chương trình. Một
số ngôn ngôn ngữ còn bỏ qua bước khai báo biến, tương đương với việc
biến sẽ được khởi tạo khi lần đầu tiên được sử dụng mà không cần phải
khai báo trước đó. Tuy nhiên, ngôn ngữ lập trình C bắt buộc biến phải
được khai báo trước khi sử dụng. Hơn nữa, biến phải được khai báo ở
đầu chương trình, trước vị trí các câu lệnh. Biến có thể được khai báo
bên trong chương trình chính hoặc chương trình con, hoặc bên ngoài
chương trình chính hoặc chương trình con. Trong 2 trường hợp này,
phạm vi hoạt động của biến khác nhau. Cụ thể, dựa vào phạm vi hoạt
động của biến, các biến được chia làm 2 loại cơ bản như sau:
 Biến chung hoặc biến toàn cục (global): là các biến được khai
báo bên ngoài các hàm và có thể được truy xuất trong bất kỳ hàm nào
cùng nằm trong một tệp chương trình. Ví dụ một chương trình được viết
có tên file1.c có nội dung như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
32
#include <stdio.h>
#include <conio.h>
int a,b,tong,hieu ;
void tinhhieu(){
hieu = a - b;
//tinh hieu
printf(“Hieu la: %d \n”,hieu);
}
int main (void)
{
printf (“Nhap so thu nhat : \n”);
scanf(“%d”,&a); //nhap so nguyen tu ban phim
printf(“Nhap so thu hai: \n”);
scanf(“%d”,&b); //nhap so nguyen tu ban phim
tong = a + b;
//tinh tong
printf(“Tong la: %d \n”,tong); //in ket qua
getch(); //cho nhap 1 ky tu bat ky
return 0;
}
Trong chương trình trên, biến a, b được khai báo bên ngoài có đặc
tính là biến chung, hàm main nhập giá trị cho 2 biến a, b. Trong hàm
con tinhhieu(), giá trị a và b được sử dụng với nội dung biến đã được
cập nhật trong hàm main. Nội dung của các biến cục bộ có thể được truy
xuất, thay đổi bởi các câu lệnh trong bất kỳ hàm nào. Trong ngôn ngữ
lập trình C, các biến không có thuộc tính truy cập nên quyền truy cập
các biến chung là như nhau.
 Biến cục bộ hay biến địa phương (local variable): các biến cục
bộ được khai báo bên trong các hàm, kể cả hàm main. Phạm vi hoạt
động của các biến cục bộ là trong các hàm mà nó được khai báo. Các
hàm không có khả năng truy xuất các biến cục bộ trong các hàm khác.
Ví dụ, biến cục bộ được khai báo và sử dụng như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <conio.h>
void tinhhieu(){
int a,b,hieu ;
hieu = a - b;
//tinh hieu
printf(“Hieu la: %d \n”,hieu);
}
int main (void)
{
int a,b,tong ;
printf (“Nhap so thu nhat : \n”);
scanf(“%d”,&a); //nhap so nguyen tu ban phim
printf(“Nhap so thu hai: \n”);
scanf(“%d”,&b); //nhap so nguyen tu ban phim
tong = a + b;
//tinh tong
printf(“Tong la: %d \n”,tong); //in ket qua
tinhhieu();
getch(); //cho nhap 1 ky tu bat ky
return 0;
}
33
Hàm main khai báo 2 biến cục bộ, a và b, và tiến hành nhập giá trị
cho 2 biến. Biến tổng được cập nhật giá trị là tổng của a và b tại dòng
15. Sau khi in tổng, hàm tinhhieu() được gọi. Trong hàm tinhhieu(), 2
biến a và b lại được khai báo. Các biến trong hàm main() có thể có cùng
tên với các biến trong hàm tinhhieu(). Trong hàm main() có 2 biến a và b,
trong hàm tinhhieu() cũng có 2 biến a và b. Biến a và b trong hàm main()
khác với biến a và b trong hàm tinhieu() mặc dù chúng được đặt cùng một
tên nhưng do chúng là các biến nội bộ. Như vậy, trong hàm tinhhieu(), giá
trị của a và b chưa được nhập nên chương trình sẽ lấy giá trị ngẫu nhiên cho
2 biến a và b trong hàm tinhhieu(). Để có thể cập nhật giá trị a và b đã được
nhập trong hàm main cho hàm giá trị nội bộ a và b trong hàm tinhhieu()
có thể sử dụng nhiều phương pháp sẽ đề cập trong các chương sau.
1.9. HẰNG SỐ
Tương tự như biến, hằng số là một vùng nhớ được phát sinh trong
bộ nhớ RAM để lưu trữ dữ liệu, tuy nhiên giá trị dữ liệu bên trong vùng
nhớ của hằng số chỉ được khởi tạo một lần và không được phép thay đổi
trong suốt chương trình.
Hằng số cần được khai báo và khởi tạo giá trị ban đầu trước khi
sử dụng. Câu lệnh khai báo sau:
const float PI = 3.14;
là một câu lệnh khai báo hằng số. Trong câu lệnh này, từ khóa const
là bắt buộc để bắt đầu một câu lệnh khai báo hằng; float là kiểu dữ liệu của
hằng, PI là tên của hằng và 3.14 là giá trị khởi tạo cho hằng số. Sau câu lệnh
này, chương trình sẽ tạo ra một ô nhớ kiểu float cho hằng PI và khởi tạo giá
trị dữ liệu lưu trong ô nhớ này là 3.14, như minh họa ở hình dưới:
PI
3.14
kích thước 4 bytes
34
Giá trị khởi tạo 3.14 của hằng PI là cố định và không được phép
thay đổi. Nếu người dùng yêu cầu thay đổi giá trị của hằng PI, chương
trình sẽ báo lỗi. Chẳng hạn câu lệnh sau:
PI = 4.5; //cau lenh gay ra loi
sẽ gây ra lỗi vì giá trị đã khởi tạo của hằng PI là cố định (3.14) và
không được phép thay đổi.
Sau khi khai báo, có thể sử dụng hằng số PI trong các câu lệnh
khác trong chương trình. Ví dụ, đoạn lệnh tính toán chu vi của một hình
tròn có đường kính là 5cm có thể viết như sau:
1
2
3
int d = 5;
float C;
C = d*PI;
Ngôn ngữ C sử dụng các hệ thống số trong lập trình máy tính bao
gồm các số thực, các số nguyên. Trong đó hệ thống số nguyên có thể
được biểu diễn trong các hệ khác nhau như hệ nhị phân, hệ bát phân,
hệ thập phân và hệ thập lục phân. Quy ước các hằng số được biểu diễn
dưới dạng các hệ thống số khác nhau như sau:
Hệ thống số
Cơ
số
Ký tự sử dụng biểu diễn số
Ví dụ: (giá trị 240
hệ thập phân)
Biểu diễn trong
ngôn ngữ C
Nhị phân
2
0,1
(11110000)2
int a =
0b11110000
Bát phân
8
0,1,2,3,4,5,6,7
(360)8
int a = 0360
Hệ thập phân
10
0,1,2,3,4,5,6,7,8,9
(240)10
int a = 240
Hệ thập lục phân
16
0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
(F0)16
int a = 0xF0
1.10. CÁC PHÉP TOÁN TRONG C
Ngôn ngữ C cung cấp nhiều phép toán khác nhau cho người dùng
để phục vụ cho các yêu cầu lập trình khác nhau. Các phép toán được sử
35
dụng trong ngôn ngữ C bao gồm các phép toán số học, các phép toán
quan hệ, các phép toán luận lý và các phép toán thao tác trên các bit nhị
phân (bit-wise).
 Các phép toán số học bao gồm các phép toán cộng, trừ, nhân,
chia,… được liệt kê trong bảng bên dưới.
Nhóm phép
Ký hiệu trong
toán
ngôn ngữ C
Ý nghĩa
=
Số học
Phép gán
Ví dụ
int a;
a = 5;
+
*
/
Cộng
Trừ
Nhân
Chia
a = 5/2;
Chia lấy phần
%
a = 5%2;
dư
Bảng 1.3. Các phép toán số học trong ngôn ngữ C
Ví dụ để thực hiện phép toán:
y=
a
x+c
b
thì câu lệnh được viết trong ngôn ngữ C tương ứng sẽ là:
y = (a/b)*x + c;
Lưu ý với các phép toán, thứ tự ưu tiên sẽ là:
1. Các phép toán trong cặp dấu ngoặc đơn ( ). Nếu có nhiều cặp
ngoặc đơn lồng vào nhau thì ưu tiên từ trong ra ngoài.
2. Phép nhân, phép chia và phép chia lấy phần dư. Nếu có nhiều
phép nhân, chia, chia lấy phần dư ngang hàng nhau thì sẽ thực thi từ trái
sang phải.
3. Phép cộng và phép trừ. Nếu có nhiều phép cộng và phép trừ
ngang hàng nhau thì sẽ thực thi từ trái sang phải.
36
4. Phép gán.
Với phép chia /, kết quả sẽ là số nguyên nếu cả số chia và số bị
chia đều có kiểu dữ liệu là số nguyên, kết quả sẽ là số thực nếu số chia
hoặc số bị chia có kiểu dữ liệu là số thực. Ví dụ, đoạn lệnh sau:
int a = 5, b = 2;
float c = a/b;
sẽ làm cho giá trị của biến c là 2.0 vì phép chia a/b sẽ cho kết quả
là số nguyên.
Nhưng khi thực thi các lệnh
int a = 5;
float b = 2;
float c = a/b;
thì giá trị của biến c là 2.5 vì phép chia a/b sẽ cho kết quả là số thực.
 Các phép toán quan hệ: được sử dụng để so sánh giá trị của các
toán hạng. Khác với các phép toán số học, trong đó toán tử bên trái được
cập nhật giá trị, các phép toán quan hệ trả về kết quả là giá trị luận lý: đúng
hoặc sai. Các phép toán quan hệ bao gồm các phép toán so sánh như sau:
Nhóm phép
toán
Ký hiệu trong
ngôn ngữ C
>
Ý nghĩa
Ví dụ
So sánh lớn hơn
a>b
So sánh lớn hơn
>=
a >= b
hoặc bằng
So sánh nhỏ
<
c<d
hơn
Quan hệ
So sánh nhỏ
<=
c <= d
hơn hoặc bằng
==
So sánh bằng
a == 5
So sánh không
!=
a != 2
bằng
Bảng 1.4. Các phép toán quan hệ trong ngôn ngữ C
37
Giá trị trả về của các phép toán quan hệ được sử dụng để thiết lập
các quyết định có điều kiện. Ví dụ, sử dụng các phép toán quan hệ trong
cú pháp sau:
int a = 3, b=2;
if (a>b) {…}
biểu thức (a>b) không trả về giá trị cho biến nào, tuy nhiên, biểu
thức (a>b) trả về kết quả luận lý là đúng (true), giúp cho phát biểu điều
kiện if có cơ sở thực hiện.
 Các phép toán luận lý: các phép toán luận lý cho phép kết nối
luận lý các phép toán quan hệ để tạo thành một biểu thức luận lý. Có 3
phép toán luận lý cơ bản như sau:
Nhóm phép
toán
Ký hiệu trong
Ý nghĩa
Ví dụ
ngôn ngữ C
!
not
!(a > b)
Luận lý
&&
and
(a > 3)&&(b < c)
||
or
(a < 0)||(a >10)
Bảng 1.5. Các phép toán luận lý trong ngôn ngữ C
Kết quả luận lý của các phép toán luận lý cũng trả về kết quả là
đúng (true) hay sai (false).
A
False
False
True
True
B
False
True
False
True
A&&B
False
False
False
True
A||B
False
True
True
True
!A
True
True
False
False
Ví dụ, xét kết quả trả về của phép toán luận lý sau:
int a = 3, b=2, c=4;
if ((a>b)&&(c==0)) {…}
Biểu thức luận lý ((a>b)&&(c==0)) là kết hợp luận lý của 2 biểu
38
thức quan hệ (a>b) và (c==0). Biểu thức (a>b) trả về kết quả là đúng
(true), tuy nhiên biểu thức (c==0) trả về kết quả là sai (false) nên biểu
thức luận lý ((a>b)&&(c==0)) trả về kết quả là sai (false).
 Các phép toán bit-wise: các phép toán bit-wise thao tác trên
từng bit riêng lẻ. Các toán tử bit-wise thực sự hữu ích cho các lập trình
viên khi lập trình điều khiển như lập trình vi xử lý, vi điều khiển, lập
trình điều khiển hệ thống nhúng.
Nhóm phép
toán
Bit-wise
Ký hiệu
trong ngôn
ngữ C
Ý nghĩa
&
AND
|
OR
^
XOR
~
NOT
<<
Dịch trái
>>
Dịch phải
Ví dụ
a = 0b00001111 ;
b= 0b11101000;
c= a&b;
→c = 00001000
c= a|b;
→c = 11101111
c= a^b;
→c = 11100111
c= ~a;
→c = 11110000
c= a<<1;
→c = 00011110
c= a>>2;
→c = 00000111
Bảng 1.6. Các phép toán bit-wise trong ngôn ngữ C
Các phép toán bit-wise hoạt động tương tự như các phép toán số
học. Toán hạng bên trái của biểu thức sẽ được cập nhật giá trị trong khi
các toán hạng bên phải không thay đổi. Ví dụ:
unsigned char a = 0b00001111,b=0x11101000 ,c;
c= a&b;
câu lệnh gán c = a&b, kết quả giá trị c sẽ là phép and từng bit
trong 2 thanh ghi, kết quả sẽ là c = 0b00001000.
39
Xét câu lệnh sau:
c= a>>2;
trong câu lệnh trên, giá trị của c là giá trị của a dịch đi bên phải
2 lần, giá trị của a vẫn không đổi. Kết quả là c được gán giá trị là
0b00000011. Khi dịch sang trái hoặc sang phải, nếu là các số nguyên
không dấu, số 0 sẽ được thêm vào tại vị trí bit bị thiếu.
 Các phép toán khác:
Nhóm phép
toán
Phép toán
khác
Ký hiệu
trong ngôn
ngữ C
Ý nghĩa
++
Tăng 1 đơn vị
––
Giảm 1 đơn vị
?
Toán tử điều
kiện
+= – = *=
Gán mở rộng
/= %=
Ví dụ
b = 3;
b++;
int c = ++b;
b = 5;
c = b – – + 2;
max= (a>b)?a:b;
int d = 1;
d += 2; // d = d + 2;
Bảng 1.7. Các phép toán khác trong ngôn ngữ C
Một số phép toán khác khá thông dụng và được sử dụng nhiều
trong quá trình lập trình như phép tăng 1 đơn vị (++) và phép giảm 1
đơn vị (– –). Các phép toán này có thể được kết hợp với các phép toán
khác nhằm tiết kiệm số dòng lệnh trong quá trình lập trình. Phép tăng,
giảm được đặt ở vị trí trước và sau biến sẽ có tác dụng khác nhau.
Ví dụ:
int a = 6, b = 7 ;
a++ ;
40
giá trị a được khởi tạo là 6, lệnh a++ sẽ cập nhật giá trị a bằng cách
tăng a lên 1 đơn vị. Lệnh a++ tương đương với lệnh a = a+1.
Một ví dụ khác:
int a = 6, b = 7, c ;
c = a++ +b ;
Phép tăng và giảm khi được sử dụng chung với các toán tử
khác như ví dụ trên cần chú ý phép tăng đặt trước hay đặt sau toán
hạng. Ví dụ, câu lệnh c = a++ +b tương đương với 2 câu lệnh c =
a+b và câu lệnh a = a+1. Có nghĩa là phép tăng sau khi thực hiện
toán tử chính là toán tử +. Nếu câu lệnh trên được thay đổi thành c =
++a + b thì kết quả tương đương với hai câu lệnh là a = a+1 và c =
a + b. Trong trường hợp này, phép tăng được thực hiện trước toán tử
chính là phép cộng. Rõ ràng trong 2 trường hợp, kết quả cuối cùng
là khác nhau.
Toán tử điều kiện (?): là toán tử được sử dụng phổ biến trong các
phát biểu điều kiện. Sử dụng toán tử điều kiện cho phép câu lệnh ngắn
gọn. Một toán tử điều kiện được phát biểu dưới cú pháp như sau:
Toán hạng = biểu thức ? giá trị 1: giá trị 2
Trong đó, nếu biểu thức trả về kết quả là đúng (true) thì giá trị 1
được gán cho toán hạng bên trái biểu thức, ngược lại thì giá trị 2 được
gán cho toán hạng bên trái biểu thức.
Ví dụ sau sẽ tìm và gán giá trị lớn nhất cho biến max giữa 2 biến
a và b:
int a = 6, b = 7, max ;
max = (a>b)?a:b ;
Toán tử điều kiện được xem là tương đương một phát biểu điều
kiện (if… else…).
Cuối cùng là các phép toán mở rộng. Đây chỉ là hình thức viết rút
41
gọn các toán tử khi có chung một toán hạng. Ví dụ trong câu lệnh a = a
+b; thì toán hạng a được cập nhật bằng cách cộng chính nó với b. Trong
câu lệnh này, có thể được lượt bỏ a bên phải phép gán = và di chuyển
phép + về trước, do đó sẽ có thể được rút gọn thành a += b ;
1.11. XUẤT NHẬP DỮ LIỆU
1.11.1 Hàm nhập dữ liệu
Như đã đề cập trong phần trước, trong chương trình cộng hai số
nguyên, ta có câu lệnh nhập dữ liệu từ bàn phím
scanf(“%d”,&a);
//nhap so nguyen tu ban phim
sử dụng hàm scanf để cho phép người dùng nhập một số nguyên
từ bàn phím và lưu vào biến a. Hàm scanf này có hai tham số: “%d” và
&a, với “%d” là mã định dạng kiểu số nguyên cho dữ liệu nhận vào và
& là toán tử xác định địa chỉ của biến để lưu dữ liệu.
Vậy cú pháp chung của hàm nhập dữ liệu sẽ là:
scanf(“mãĐịnhDạng”,&biếnLưuDữLiệu);
với mãĐịnhDạng là mã định dạng dữ liệu dùng để định dạng kiểu
dữ liệu cho dữ liệu nhập vào. Một số mã định dạng phổ biến như sau:
%d
mã định dạng kiểu số nguyên hệ thập phân
%f
mã định dạng kiểu số thực
%c
mã định dạng kiểu ký tự
%s
mã định dạng kiểu chuỗi nhiều ký tự
Ví dụ cho phép nhập 3 số liên tiếp và lưu vào 3 biến a, b, c:
scanf(“%d%d%d”,&a,&b,&c) ;
trong câu lệnh trên, các ký tự định dạng được viết liền nhau cho
phép chương trình dừng lại và chờ nhập 3 giá trị số nguyên. Các biến
được tham chiếu thông qua địa chỉ và đặt cách nhau bởi dấu “,”. Trường
hợp sau đây là không hợp lệ:
42
scanf(“%d,%d,%d”,&a,&b,&c) ;
trong trường hợp này, các dấu “,” được đặt trong chuỗi định dạng
là không hợp lệ. Các ký tự định dạng được liệt kê trong bảng trên và
luôn được bắt đầu bằng từ khóa “%”.
1.11.2 Hàm xuất dữ liệu
Cũng trong chương trình cộng hai số, ta có câu lệnh
printf(“Tong la: %d \n”,tong);
//in ket qua
là câu lệnh in dữ liệu ra màn hình sử dụng hàm printf. Hàm printf
ở lệnh này có hai tham số, “Tong la: %d \n” và tong . Tham số đầu
tiên “Tong la: %d \n” là chuỗi định dạng gồm chuỗi ký tự cần in ra
màn hình Tong la: , mã định dạng %d cho biết sẽ có một số nguyên
được in ra, ký tự điều khiển \n để đưa con trỏ xuống dòng. Tham số thứ
hai, tong, sẽ xác định giá trị dữ liệu cần in ra.
Vậy cú pháp chung của hàm in dữ liệu ra màn hình là:
printf (“chuỗiĐịnhDạng”, biếnCầnIn);
trong đó:
 biếnCầnIn là biến chứa dữ liệu cần in ra màn hình.
 chuỗiĐịnhDạng có thế là:
+ chuỗi ký tự thông thường.
+ mã định dạng (%d, %f, %c, %s,...) dùng để định dạng dữ liệu
cho biếnCầnIn.
+ ký tự điều khiển (\n
\t
…).
Lưu ý là để trình biên dịch có thể biên dịch được hàm scanf và
printf, ta cần khai báo thư viện stdio.h bằng câu lệnh
#include <stdio.h>
ở đầu chương trình.
Danh sách các mã định dạng dữ liệu cho biến được liệt kê trong
bảng sau:
43
Mã định dạng
%c
%d hoặc %i
Ý nghĩa
Kiểu ký tự
Kiểu số nguyên hệ thập phân
%e
Kiểu số mũ
%f
Kiểu số thực
%o
Kiểu số nguyên hệ bát phân
%u
Kiểu số nguyên không dấu hệ thập phân
%x
Kiểu số nguyên hệ thập lục phân không dấu
%s
Kiểu chuỗi
1.12. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu lệnh/đoạn lệnh sau:
a.
scanf(“Nhap gia tri: %d”,a);
b.
int a;
scanf(a);
c.
printf(“Gia tri bien a: %d”, &a);
d.
int a = 5;
printf(a);
e.
5 = a;
f.
float b = 3.5;
printf(“Gia tri b: %d\n”, b);
g.
#include <stdio.h>
int a;
printf(“Nhap a: “);
scanf(“%d”,&a);
printf(“So da nhap: %d\n”,a);
h.
#include <stdio.h>
int main(void)
printf(“Xin chao!”);
return 0;
44
2. Hãy phân tích chương trình sau và cho biết chương trình thực
hiện chức năng gì?
1
#include <stdio.h>
2
#include <conio.h>
3
4
int main (void)
5
{
6
int
a,b;
7
printf (“Nhap a:”);
8
scanf(“%d”, &a);
9
printf (“Nhap b:”);
10
scanf(“%d”, &b);
11
12
13
printf (“ket qua 1: %d , ket qua 2: %d”, a+b,
a - b);
14
getch();
15
return 0;
16
}
3. Cho biết kết quả khi thực thi chương trình sau:
1
#include <stdio.h>
2
int main(void)
3
{
4
int a = 10, b = 100;
5
float c = 10.5, d = 100.5;
6
printf(“++a = %d \n”, ++a); // Kết quả in:.......
7
printf(“--b = %d \n”, --b); // Kết quả in:.......
8
printf(“++c = %f \n”, ++c); // Kết quả in:.......
9
printf(“--d = %f \n”, --d); // Kết quả in:.......
10
return 0;
11
}
45
4. Cho biết kết quả khi thực thi chương trình sau:
#include <stdio.h>
int main(void)
{
int a = 5, c;
c = a;
printf(“c = %d\n”,
c += a;
printf(“c = %d\n”,
c -= a;
printf(“c = %d\n”,
c *= a;
printf(“c = %d\n”,
c /= a;
printf(“c = %d\n”,
c %= a;
printf(“c = %d\n”,
return 0;
}
c); // Kết quả in:......
c); // Kết quả in:......
c); // Kết quả in:......
c); // Kết quả in:......
c); // Kết quả in:......
c); // Kết quả in:......
5. Vẽ lưu đồ giải thuật và viết chương trình: nhập vào 2 số nguyên
từ bàn phím và in ra tổng, hiệu, tích, thương của 2 số đó.
6. Vẽ lưu đồ giải thuật cho bài toán: nhập vào 1 số nguyên và kiểm
tra xem số vừa nhập có phải là số nguyên tố hay không.
7. Vẽ lưu đồ giải thuật và viết chương trình cho bài toán nhập vào
4 số nguyên, in ra màn hình số lớn nhất, số bé nhất trong các số đã nhập.
46
CHƯƠNG 2
LỆNH RẼ NHÁNH CÓ ĐIỀU KIỆN
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Phân biệt được câu lệnh đơn và câu lệnh kép.
- Phân loại được các cú pháp lệnh rẽ nhánh khác nhau.
- Viết được các chương trình C liên quan đến các lệnh rẽ nhánh.
2.1. LỆNH ĐƠN VÀ LỆNH PHỨC
Phần đầu tiên ta sẽ tìm hiểu về lệnh đơn và lệnh phức (hay
khối lệnh).
2.1.1 Lệnh đơn
Lệnh đơn là một câu lệnh không chứa các câu lệnh khác bên trong
nó và kết thúc bằng một dấu chấm phẩy (;).
Ví dụ 2.1:
1
2
3
int x=5; //một câu lệnh đơn
x++; //một câu lệnh đơn
printf(“Giá trị x là: %d”,x); //một câu lệnh đơn
2.1.2 Lệnh phức/ Khối lệnh
Lệnh phức hay khối lệnh là một tập hợp nhiều lệnh đơn được đặt
trong cặp dấu ngoặc móc { và }.
Ví dụ 2.2:
1
2
3
4
5
6
{
char ten[30];
printf(“\n Nhap vao ten cua ban:”);
scanf(“%s”, ten);
printf(“\n Chao Ban %s”,ten);
}
47
Chú ý:
 Nếu một biến được khai báo bên ngoài khối lệnh và không
trùng tên với biến bên trong khối lệnh thì nó cũng được sử dụng bên
trong khối lệnh.
 Một khối lệnh bên trong có thể sử dụng các biến đã được khai
báo bên ngoài khối lệnh đó. Các lệnh bên ngoài của một khối lệnh
không thể sử dụng các biến đã được khai báo bên trong khối lệnh đó.
2.2. CÁC DẠNG CẤU TRÚC CHƯƠNG TRÌNH
Với một ngôn ngữ lập trình thì một chương trình là tập nhiều câu
lệnh, thông thường một cách trực quan, chúng ta hiểu một chương trình
sẽ thực hiện tuần tự các lệnh theo thứ tự từ trên xuống dưới, bắt đầu từ
lệnh thứ nhất trong hàm main và kết thúc sau lệnh cuối cùng của nó.
Nhưng thực tế, một chương trình có thể được thực thi theo kiểu phức
tạp hơn kiểu tuần tự nhiều, chẳng hạn như một câu lệnh (hay khối lệnh)
chỉ được thực hiện khi có một điều kiện nào đó đúng, còn ngược lại nó
sẽ bị bỏ qua, tức là xuất hiện khả năng lựa chọn một nhánh nào đó. Hay
một chức năng nào đó có thể phải lặp lại nhiều lần.
Như vậy với một ngôn ngữ lập trình có cấu trúc nói chung, phải có
các cấu trúc để điều khiển trình tự thực hiện các lệnh trong chương trình
(gọi là các cấu trúc, các toán tử điều khiển hay các lệnh điều khiển). Có
hai dạng cấu trúc: cấu trúc tuần tự và cấu trúc điều kiện.
2.2.1 Cấu trúc tuần tự
Đây là cấu trúc đơn giản nhất của các ngôn ngữ lập trình, điều
khiển thực hiện tuần tự các lệnh trong chương trình (bắt đầu từ các lệnh
trong thân hàm main) theo thứ tự từ trên xuống dưới (nếu không có điều
khiển nào khác).
Ví dụ 2.3: Chương trình nhập năm sinh của một người từ bàn
phím, sau đó in ra lời chào và tuổi của người đó.
48
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <conio.h>
int main(void)
{
int namsinh;
printf(“Nhap nam sinh cua ban : “);
scanf(“%d”, &namsinh);
printf(“\nChao ban! Tinh den nam 2020,tuoi ban là %4d
tuoi”,2020-namsinh);
getch();
return 0;
}
Ví dụ 2.4: Viết chương trình nhập ba số thực a, b, c từ bàn phím là
số đo 3 cạnh tam giác, sau đó tính và in chu vi và diện tích của tam giác.
Giải:
Dữ liệu vào : a, b, c kiểu float là 3 cạnh một tam giác.
Tính toán:
Chu vi p = (a+b+c), diện tích s = sqrt(q*(q-a)*(q-b)*(q-c)) với q
= p/2 và sqrt là hàm tính căn bậc 2.
Chúng ta có chương trình được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <conio.h>
#include <math.h>
int main(void)
{ float a,b,c, p,q,s;
printf(“Nhap so do 3 canh cua tam giac “);
printf(“\na = “);
scanf(“%f”, &a);
printf(“\nb = “);
scanf(“%f”, &b);
printf(“\nc = “);
scanf(“%f”, &c);
49
13
14
15
16
17
18
19
printf(“\nc = “);
scanf(“%f”, &c);
p = a+b+c; q = p/2;
s = sqrt(q*(q-a)*(q-b)*(q-c));
printf(“\nChu vi la %5.1f, dien tich la %5.2f “,p,s);
getch();
return 0;
}
Kết quả thực hiện chương trình:
1
2
3
4
5
Nhap so do 3 canh cua tam giac
a = 3 ↵
b = 4 ↵
c = 5 ↵
Chu vi la 12.0, dien tich la 6.00
Lưu ý:
 Trong chương trình ví dụ trên chúng ta sử dụng hàm tính căn
bậc 2: sqrt, hàm này được khai báo trong thư viện math.h.
 Chương trình trên chưa xử lý trường hợp a, b, c không hợp lệ
(ba số a, b, c có thể không thoả mãn là 3 cạnh một tam giác).
2.2.2 Cấu trúc rẽ nhánh
Chúng ta hãy xem lại chương trình trong ví dụ 2.4 trên, điều gì
xảy ra nếu dữ liệu không thoả mãn, tức là khi bạn nhập 3 số a, b, c từ
bàn phím nhưng chúng không thoả mãn là số đo 3 cạnh một tam giác
trong khi chương trình của chúng ta vẫn cứ tính và in diện tích.
Rõ ràng là có hai khả năng:
 Nếu a, b, c thoả mãn là 3 cạnh tam giác thì có thể tính chu vi,
diện tích và in kết quả.
 Ngược lại, phải thông báo dữ liệu không phù hợp. Như vậy
cần phải có một sự lựa chọn một trong hai nhánh xử lý, tuỳ vào điều
50
kiện a, b, c có phải là ba cạnh một tam giác hay không. Điều này gọi
là rẽ nhánh. Ngôn ngữ C cung cấp cho chúng ta hai cú pháp lệnh điều
khiển rẽ nhánh: rẽ nhánh if và rẽ nhánh switch… case.
2.3. LỆNH RẼ NHÁNH IF
Câu lệnh if cho phép lựa chọn một trong các nhánh tùy thuộc vào
giá trị của các biểu thức luận lý là đúng (true) hay sai (false).
2.3.1 Lệnh if thiếu
Lệnh if thiếu là lệnh mà ở đó khối lệnh sẽ được thực hiện nếu
biểu thức là đúng (true), hoặc không thực hiện khối lệnh nếu biểu
thức sai (false).
 Cú pháp
if (biểu thức)
{
Khối lệnh;
}
 Hoạt động
Hình 2.1. Lưu đồ giải thuật của lệnh if thiếu
51
 Giải thích hoạt động
Kết quả của biểu thức luận lý sẽ là đúng (khác 0) hoặc sai
(bằng 0).
Nếu biểu thức luận lý là đúng thì thực hiện khối lệnh và thoát khỏi
if, ngược lại thì không làm gì cả và thoát khỏi if.
 Lưu ý
Từ khóa if phải viết bằng chữ thường.
Không đặt dấu chấm phẩy sau câu lệnh if, ví dụ như if (biểu thức
luận lý);. Khi đó, trình biên dịch không báo lỗi nhưng khối lệnh không
được thực hiện cho dù biểu thức luận lý là đúng hay sai.
 Các ví dụ minh họa
Ví dụ 2.5: Cho chương trình sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(void)
{
int a = 5, b = 6, c = 7;
if (c > a)
{
a = a + b;
b = b + 1;
}
printf(“Gia tri cua a la %d cua b la %d”,a,b);
getch();
return 0;
}
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
Gia tri cua a la 11 cua b la 7
Giải thích hoạt động của chương trình:
Trong chương trình này, ở dòng lệnh số 1 là câu lệnh khai báo thư
viện của hệ thống: stdio.h. Dòng lệnh số 2 đánh dấu chương trình chính,
dấu mở ngoặc { ở dòng lệnh số 3 thể hiện vị trí bắt đầu nội dung chương
52
trình chính và dấu đóng ngoặc } ở dòng lệnh số 13 dùng để kết thúc nội
dung chương trình chính.
Ở dòng lệnh số 4, các biến a, b, c được khai báo kiểu int và gán
giá trị ban đầu.
Ở dòng lệnh số 5 là lệnh if (c > a), khi gặp dòng lệnh này, chương
trình sẽ xét biểu thức trong cặp dấu ( ) và xét kết quả luận lý của nó.
Như ở đây, trong cặp dấu ( ) là biểu thức c > a, với c = 7 và a = 5; như
vậy, biểu thức này đúng do đó khối lệnh trong cặp { } bắt đầu ở dòng 6
và kết thúc ở dòng 9 sẽ được thực hiện.
Như vậy các dòng 7 và 8 các giá trị a và b sẽ được thực hiện theo
phép toán.
Dòng 10 sẽ in giá trị a, b vừa tính dòng 7 và 8. Ở đây, kết quả a là
11 và b là 7, như vậy thực sự khối lệnh từ dòng 6 đến dòng 9 đã được
thực hiện.
Như vậy trong ví dụ này, biểu thức luận lý trong câu lệnh if là
đúng (khác 0) và khối lệnh được thực hiện.
Ta xét tiếp một ví dụ khác liên quan đến lệnh if sau đây:
Ví dụ 2.6: So sánh sự khác nhau của ví dụ 2.6 này và ví dụ 2.5 ở
phía trên:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main(void)
{
int a = 5, b = 6, c = 7;
if (c < a)
{
a = a + b;
b = b + 1;
}
printf(“Gia tri cua a la %d cua b la %d”,a,b);
getch();
return 0;
}
53
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
Gia tri cua a la 5 cua b la 6
Giải thích hoạt động của chương trình:
Chương trình ở ví dụ 2.6 này chỉ khác với ví dụ 2.5 ở dòng 5. Ở
đây, biểu thức luận lý trong câu lệnh if là c < a. Như vậy khi thực hiện,
biểu thức này sẽ cho kết quả là sai, tương đương với giá trị bằng 0.
Khi đó toàn bộ các lệnh từ dòng 6 đến dòng 9 sẽ không được thực thi.
Chương trình sẽ chuyển đến dòng 10 và in ra kết quả. Như vậy giá trị
của a và b sẽ không đổi so với lúc ban đầu.
Ví dụ 2.7: Cho chương trình sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main(void)
{
int a = 5, b = 6, c = 7;
if (a > c)
a = a + b;
b = b + 1;
if (a < b)
{
c = b - a;
b = c + 5;
}
printf(“Gia tri cua a la %d cua b la %d va c la
%d”,a,b,c);
getch();
return 0;
}
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
Gia tri cua a la 5 cua b la 7 va c la 2
Giải thích hoạt động của chương trình:
54
Trong ví dụ 2.7 này, ta lưu ý ở dòng số 6 và số 7, ở đây không có
cặp dấu { }. Do vậy dù biểu thức luận lý trong lệnh if ở dòng số 5 là
sai nhưng dòng 7 vẫn được thực thi (dòng 6 không thực thi). Do đó giá
trị b được tăng lên dẫn tới điều kiện if ở dòng 8 đúng nên khối lệnh từ
dòng 9 đến dòng 12 được thực hiện. Cuối cùng, giá trị các biến a, b, c
thu được như kết quả đã cho.
2.3.2 Lệnh if đủ
Không giống với lệnh if thiếu, lệnh if đủ sẽ lựa chọn và quyết định
thực hiện một trong hai khối lệnh cho trước tùy thuộc vào kết quả luận
lý của biểu thức.
 Cú pháp
if (biểu thức)
{
Khối lệnh 1;
}
else
{
Khối lệnh 2;
}
 Hoạt động
Hình 2.2. Lưu đồ giải thuật của lệnh if đủ
55
 Giải thích
Nếu biểu thức là đúng thì thực hiện khối lệnh 1 và thoát khỏi if,
ngược lại thì thực hiện khối lệnh 2 và thoát khỏi if.
 Lưu ý
Từ khóa if, else phải viết bằng chữ thường.
Kết quả của biểu thức là đúng (khác 0) hoặc sai (= 0).
Khi khối lệnh 1 hoặc khối lệnh 2 bao gồm từ 2 lệnh trở lên thì các
khối lệnh này phải được đặt trong các cặp dấu { }.
 Ví dụ minh họa
Ví dụ 2.8: Cho chương trình sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main(void)
{
int a = 4, b = 2, c = 5;
c -= 2;
if (a > c)
a = a + b;
b = b + c;
if (a < b)
{
c = b - a;
b = c + 5;
}
else
a = a + 1;
b = b +1;
printf(“Gia tri cua a la %d cua b la %d va c la
%d”,a,b,c);
18 getch();
19 return 0;
20 }
56
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
1
Gia tri cua a la 11 cua b la 7 va c la 3
Giải thích hoạt động của chương trình:
Ví dụ 2.8 này có cả hai cú pháp lệnh: if đầy đủ và if thiếu. Các
lệnh ở những dòng lệnh từ 1 đến 8 đã được trình bày ở ví dụ trước.
Điểm khác trong ví dụ này là từ dòng 9 đến dòng 16. Nếu biểu thức ở
dòng 9 (biểu thức a < b) là đúng thì chương trình sẽ thực hiện các dòng
lệnh từ dòng 10 đến dòng 13. Ngược lại, nếu biểu thức dòng 9 là sai thì
chương trình sẽ thực hiện ở khối lệnh của nhánh else, tức dòng lệnh 15,
dòng 16 đã nằm ngoài else. Như vậy, kết quả thực hiện của chương trình
bởi máy tính là phù hợp với những gì đã phân tích.
2.3.3 Lệnh if … else if … else
Với lệnh if … else if …else thì chương trình sẽ lựa chọn và quyết
định thực hiện 1 trong số n khối lệnh cho trước.
 Cú pháp
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;
}
57
else
{
Khối lệnh n;
}
 Hoạt động
Hình 2.3. Lưu đồ giải thuật của lệnh if...else if...else
 Giải thích
Nếu Biểu thức 1 là đúng thì thực hiện Khối lệnh 1 và kết thúc lệnh
if. Ngược lại, nếu Biểu thức 2 là đúng thì thực hiện Khối lệnh 2 và kết
58
thúc lệnh if. Ngược lại, nếu Biểu thức n-1 là đúng thì thực hiện Khối
lệnh n-1 và kết thúc lệnh if. Ngược lại thì thực hiện Khối lệnh n.
 Lưu ý
Không đặt dấu chấm phẩy ngay sau các phát biểu if, else if, else.
Nếu khối lệnh 1, 2…n bao gồm từ 2 lệnh trở lên thì phải đặt các
khối lệnh này trong các cặp dấu { }.
 Ví dụ minh họa
Ví dụ 2.9: Cho chương trình sau:
1
#include <stdio.h>
2
int main(void)
3
{
4
5
int a = 4, b = 2, c = 5;
if ((a > c)|| (a < b) && (b > c))
6
7
a = a - 2;
else if (a == b)
8
{
9
c = b - a;
10
b = c + 5;
11
}
12 else
13
a = a + 1;
14
b = b +1;
15 printf(“Gia tri cua a la %d cua b la %d va c la
%d”,a,b,c);
16
getch();
17 return 0;
18 }
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
1
Gia tri cua a la 11 cua b la 3 va c la 5
59
Giải thích hoạt động:
Trong chương trình này, các dòng từ 5 đến dòng 13 là khác với
các ví dụ trước. Ở dòng 5, biểu thức ((a > c)|| (a < b) && (b > c))
sẽ được xét, nếu kết quả là đúng thì câu lệnh ở dòng lệnh 6 sẽ được
thực hiện còn nếu sai thì bỏ qua câu lệnh ở dòng 6 và chuyển qua
câu lệnh ở dòng 7. Do kết quả của biểu thức ở dòng lệnh số 5 là
sai nên chương trình sẽ chuyển sang kiểm tra biểu thức điều kiện ở
dòng lệnh 7 (biểu thức a==b): nếu là đúng thì sẽ thực hiện các lệnh
từ dòng 8 đến hết dòng 11, nếu sai thì sẽ chuyển sang thực thi dòng
lệnh 12. Do biểu thức a == b ở dòng lệnh 7 cho kết quả là sai, nên
chương trình sẽ thực thi các dòng 12 và 13. Đoạn lệnh từ dòng 14
đến cuối chương trình luôn được thực hiện vì không liên quan tới
lệnh if ở trên.
2.3.4 Cấu trúc if lồng nhau
Với cấu trúc if lồng nhau, chương trình sẽ lựa chọn và quyết định
thực hiện 1 trong số n khối lệnh cho trước.
 Cú pháp
Cú pháp của cấu trúc này là một trong 3 dạng trên, nhưng các khối
lệnh bên trong sẽ chứa ít nhất một lệnh if khác nữa nên gọi là cấu trúc
if lồng nhau. Thông thường, khi cấu trúc if lồng nhau càng nhiều cấp
thì độ phức tạp càng cao, chương trình chạy càng chậm và trong lúc lập
trình càng dễ bị nhầm lẫn.
 Lưu ý
Trong lệnh if lồng nhau thì else sẽ luôn luôn kết hợp với if chưa
có else nào gần nó nhất. Vì vậy khi gặp những lệnh if thiếu, ta phải đặt
chúng trong những khối lệnh rõ ràng bằng cặp dấu {} để tránh bị hiểu
sai câu lệnh.
60
2.4. TOÁN TỬ ĐIỀU KIỆN BA NGÔI
C có một toán tử phổ biến và thích hợp để thay thế cho cú pháp
lệnh if đủ (if ...else...), đó là toán tử điều kiện 3 ngôi ( ? : ).
Cú pháp của toán tử ba ngôi là:
E1 ? E2 : E3
Trong đó E1, E2, E3 là các biểu thức.
Ý nghĩa: nếu E1 là Đúng thì thực thi E2, nếu E1 là Sai thì thực
thi E3.
Ví dụ 2.10a: Cho đoạn chương trình sau:
1
2
3
int a=2 , b = 3;
a == 0 ? b = 1 : b = 2;
printf(“b = %d”,b);
Khi chạy đoạn chương trình trên, kết quả in ra màn hình sẽ là:
1
b = 2
Vì biểu thức a == 0 ở dòng lệnh thứ 2 có kết quả là sai nên chương
trình thực thi biểu thức ở sau dấu : , tức biểu thức b = 2. Điều này làm
cho giá trị biến b sẽ được thay đổi thành 2.
Ví dụ 2.10b: Cho đoạn chương trình sau:
1
2
x=10;
y=x>9?100:200;
Khi thực hiện 2 dòng lệnh trên, biến y sẽ được gán giá trị 100 khi
biến x lớn hơn 9, ngược lại khi x <= 9 thì y sẽ được gán giá trị 200.
Đoạn mã ở ví dụ 2.10b tương đương với một lệnh if ...else... như sau:
1
2
3
4
x = 10;
if (x < 9)
y = 100;
else y = 200;
61
Với cả hai cách viết này giá trị của biến y đều bằng 100.
2.5. LỆNH RẼ NHÁNH SWITCH… CASE
Lệnh switch…case hoạt động tương tự như lệnh if ... else if ...
else.... Tuy nhiên, biểu thức được sử dụng để kiểm tra phải có kết quả
là một giá trị hằng số cụ thể. Một bài toán sử dụng lệnh switch thì cũng
có thể sử dụng được lệnh if nhưng việc ngược lại có được hay không là
còn tùy thuộc vào giải thuật của bài toán.
2.5.1 Cú pháp
switch (biểu thức nguyên)
{
case n1:
khối lệnh 1;
case n2:
break;
khối lệnh 2;
break;
……………………
case nk:
khối lệnh k;
break;
[default: khối lệnh k+1; ]
}
2.5.2
62
Hoạt động
Hình2.4. Lưu đồ giải thuật của lệnh switch...case
2.5.3 Giải thích
Từ khóa switch, case, break phải viết bằng chữ thường, biểu
thức phải là có kết quả là các giá trị hằng nguyên (char, int, long,...).
63
Khối lệnh 1, 2…n có thể gồm nhiều lệnh nhưng không cần đặt trong
cặp dấu { }. Không đặt dấu chấm phẩy ngay sau switch(biểu thức).
Với ni (n1, n2,… nk) là các số nguyên, hằng ký tự hoặc biểu thức
hằng và 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 lệnh switch. Thành phần default là một thành
phần không bắt buộc phải có trong thân của lệnh switch. Sự hoạt động
của lệnh 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ị của biểu thức không bằng bất kỳ giá
trị ni nào thì máy sẽ nhảy tới câu lệnh sau nhãn default : (nếu có thành
phần default) hoặc máy sẽ nhảy ra khỏi cấu trúc switch (nếu không có
thành phần default).
Lưu ý là máy sẽ nhảy ra khỏi lệnh switch khi nó gặp câu lệnh
break hoặc dấu ngoặc } đó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 lệnh switch để nhảy tới một câu lệnh
bất kỳ nào đó bên ngoài switch. Khi lệnh switch nằm trong nội dung
của một hàm nào đó thì ta cũng có thể sử dụng câu lệnh return trong
thân của lệnh switch để thoát ra khỏi lệnh switch và kết thúc hàm này
(lệnh return sẽ đề cập sau). Khi máy nhảy tới một vị trí câu lệnh nào
đó thì sự hoạt động tiếp theo của nó sẽ phụ thuộc vào nội dung của các
lệnh đứng sau vị trí này. Như vậy nếu máy nhảy tới vị trí có nhãn case
ni: thì máy sẽ thực hiện tất cả các câu lệnh sau vị trí đó 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ừ
khối lệnh thuộc case ni sang khối lệnh thuộc case thứ ni+1, nếu không
tồn tại lệnh break, goto hoặc return ở cuối mỗi khối lệnh. Nếu mỗi khối
lệnh được kết thúc bằng lệnh break thì switch sẽ thực hiện chỉ một trong
các khối lệnh này.
2.5.4 Ví dụ minh họa
Ví dụ 2.11: Cho chương trình sau:
64
1
#include <stdio.h>
2
int main(void)
3
{
4
int a = 15, b = 0;
5
switch (a%3)
6
{
7
case 0:
8
b = 0;
9
printf (“chia het”);
10 break;
11
case 1:
12 b = 1;
13 printf(“khong chia het”);
14 break;
15
default:
16 b = 2;
17 printf(“khong chia het”);
18
};
19
printf(“\n gia tri cua b la %d”, b);
20
getch();
21 return 0;
22 }
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
1
2
Chia het
Gia tri cua b la 0
Giải thích hoạt động của chương trình:
Ví dụ này khác với các ví dụ trước đó từ dòng lệnh 5. Ở dòng lệnh
5, biểu thức a%3 của lệnh switch sẽ được tính toán và kiểm tra, nếu kết
quả của biểu thức này là giá trị 0 thì chương trình sẽ nhảy đến vị trí dòng
lệnh 7 tương ứng với nhãn case 0:, nếu kết quả của biểu thức này là giá
65
trị 1 thì chương trình sẽ nhảy đến dòng 11 tương ứng với nhãn case 1:,
nếu là các giá trị khác thì chương trình sẽ nhảy đến dòng lệnh 15 tương
ứng với nhãn default:. Với trường hợp cụ thể của ví dụ này, kết quả của
biểu thức a%3 là 0, do đó chương trình sẽ nhảy đến nhãn case 0: ở dòng
7 để thực hiện tiếp cho đến khi gặp lệnh break ở dòng lệnh 10 thì kết
thúc lệnh switch. Sau đó, chương trình nhảy đến dòng lệnh 19 và thực
thi tiếp cho đến cuối chương trình chính.
Bây giờ, ta giả sử lệnh break; ở dòng lệnh 10 bị bỏ đi, thì điều gì
sẽ xảy ra? Kết quả của trường hợp này sẽ là:
1
2
Chia hetkhong chia het
Gia tri cua b la 1
Điều gì đã xảy ra? Sau khi nhảy tới nhãn case 0: và thực thi khối
lệnh ở dòng 8 và 9, vì không có lệnh break nên chương trình đã thực
hiện tiếp các lệnh ở dòng lệnh 11, 12, 13 cho đến khi gặp lệnh break tiếp
theo. Lúc này chương trình mới thoát ra khỏi lệnh switch và kết thúc
lệnh switch. Đây là vấn đề cần lưu ý để chương trình thực hiện đúng với
ý đồ của người lập trình.
2.6. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
a.
b.
c.
66
max = a;
If (a < b)
max = b;
scanf(“%d”,&a);
if a < 0
printf(“La so am\n”);
scanf(“%d”,&a);
if (a % 2 == 0)
printf(“La so chan\n”);
else (a % 2 != 0)
printf(“La so le\n”);
d.
e.
f.
g.
h.
if (a < b)
max = b;
printf(“%d\n”,max);
else
max = a;
printf(“%d\n”,max);
if (a < b);
max = b;
else
max = a;
printf(“%d\n”,max);
float diem;
scanf(“%f”,&diem);
if (diem > 0)
if (diem < 10)
printf(“Diem hop le.\n”);
else
printf(“Khong chap nhan diem am.\n”);
scanf(“%d”,&nam);
switch (nam%4)
{
case ==0:
printf(“Nam nhuan\n”);
break;
default:
printf(“Nam binh thuong\n”);
}
scanf(“%d”,&a);
switch (a % 2)
{
case 0:
printf(“La so chan\n”);
case 1:
printf(“La so le\n”);
}
67
2. Hãy đọc và phân tích chương trình sau:
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a,b,c,m;
printf (“Nhap a:”);
scanf(“%d”, &a);
printf (“Nhap b:”);
scanf(“%d”, &b);
printf (“Nhap c:”);
scanf(“%d”, &c);
if (a > b)
m = a;
else
m = b;
if (m < c)
m = c;
printf(“ket qua = %d”,m);
getch();
return 0;
}
Yêu cầu :
 Cho biết chương trình thực hiện chức năng gì?
 Vẽ lưu đồ hoạt động của chương trình.
 Viết lại chương trình khác thực hiện cùng chức năng với
chương trình trên.
68
3. Vẽ lưu đồ xử lý và viết chương trình nhập vào 4 số nguyên từ
bàn phím và in ra số lớn nhất trong 4 số này.
4. Vẽ lưu đồ xử lý và viết chương trình giải phương trình bậc 2 :
ax + bx + c = 0 (không xét đến trường hợp hệ số a = 0).
2
5. Vẽ lưu đồ xử lý và viết chương trình giải phương trình bậc 2:
ax2 + bx + c = 0 có xét đến trường hợp hệ số a = 0.
6. Vẽ lưu đồ xử lý và viết chương trình nhập vào: giờ : phút : giây
từ bàn phím; kiểm tra thời gian vừa nhập vào có hợp lệ hay không; nếu
hợp lệ cho biết trước đó 15s là thời gian bao nhiêu.
Ví dụ : Nhap gio:
25
Nhap phut: 19
Nhap giay: 43
Thoi gian nhap vao khong hop le!!!!
Ví dụ : Nhap gio:
0
Nhap phut: 0
Nhap giay: 0
Truoc do 15s la 23:59:45
7. Biết rằng một mã số sinh viên sẽ có định dạng như sau:
yyNNNsss. Trong đó:
yy: là năm nhập học
NNN: là mã ngành học
sss: là số thứ tự của sinh viên
Ví dụ nếu mã số sinh viên là 20141125, thì các thông tin tương
ứng sẽ là:
20: năm nhập học là 2020
69
141: là mã ngành học
125: là số thứ tự của sinh viên
Hãy vẽ lưu đồ xử lý và viết chương trình thực hiện yêu cầu sau:
 Nhập vào một mã số sinh viên theo định dạng trên.
 Kiểm tra và cho biết sinh viên đó học ngành nào trong các
ngành học sau:
mã ngành
tên ngành
141
Điện tử - Truyền thông
119
Kỹ thuật Máy tính
101
Điện - Điện tử
70
CHƯƠNG 3
LỆNH VÒNG LẶP
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Phân biệt được cấu trúc lặp trong C
- Viết được các chương trình C liên quan đến các lệnh vòng lặp.
- Sử dụng được lệnh break và lệnh continue trong vòng lặp.
3.1. LỆNH FOR
3.1.1. Cú pháp
for (biểu thức 1; biểu thức 2; biểu thức 3)
{
Khối lệnh;
}
Lệnh for gồm 3 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ừ khoá for. Bất kỳ biểu thức nào trong 3
biểu thức trên đều có thể vắng mặt nhưng phải giữ dấu ; ngăn cách giữa
các biểu thức. Thông thường, biểu thức 1 là toá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 vòng lặp, biểu thức 3 là một toán tử gán dùng để
thay đổi giá trị của biến điều khiển.
3.1.2. Hoạt động
Vòng lặp for hoạt động như trong Hình 3.1. Ta có thể thấy, lệnh
for hoạt động theo các bước sau :
71
 Bước 1: Thực thi Biểu thức 1.
 Bước 2: 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à máy lựa chọn một
trong hai nhánh: nếu Biểu thức 2 có giá trị là 0 (sai) thì máy sẽ kết thúc
lệnh for và chuyển tới câu lệnh tiếp theo sau for; nếu Biểu thức 2 có giá
trị khác 0 (đúng) thì máy sẽ thực hiện Khối lệnh trong thân for, sau đó
thực thi Biểu thức 3, rồi quay lại Bước 2 để bắt đầu một lần lặp mới của
vòng lặp.
Hình 3.1. Lưu đồ giải thuật của vòng lặp for
Chú ý nếu Biểu thức 2 vắng mặt thì kết quả của thao tác “xác định
Biểu thức 2” được xem là đúng. Trong trường hợp này, việc thoát khỏi
72
vòng lặp 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 vòng lặp. Trong dấu ngoặc tròn sau từ khoá for là
3 biểu thức phân cách nhau bởi hai dấu ; . Trong mỗi biểu thức, chúng
ta không những có thể viết một biểu thức mà còn có quyền viết một dãy
nhiều biểu thức phân cách nhau bởi các dấu phẩy. Khi đó các biểu thức
trong mỗi phần được xác định theo thứ tự từ trái sang phải. Tính đúng
sai của dãy biểu thức sẽ được quyết định bởi tính đúng sai của biểu thức
cuối cùng trong dãy này.
Trong thân của for, ta có thể dùng thêm các lệnh for khác, vì thế,
ta có thể xây dựng các lệnh for lồng nhau. Khi gặp câu lệnh break trong
thân for, máy ra sẽ ra khỏi lệnh for nào đang chứa lệnh break này. Trong
thân for cũng có thể sử dụng toán tử goto để nhảy đến một ví trí mong
muốn bất kỳ.
Trong thân for (hoặc while, do…while) cũng có thể có lệnh
continue: khi lệnh continue thi hành thì quyền điều khiển sẽ trao qua
cho biểu thức điều kiện của vòng lặp gần nhất. Điều này có nghĩa là
chương trình sẽ quay lại đầu vòng lặp gần nhất đang chứa lệnh continue
và bỏ qua các lệnh phía sau lệnh continue trong vòng. Ðối với lệnh for
máy sẽ tính lại biểu thức 3 và quay lại bước 2.
3.1.3. Ví dụ minh họa
Ví dụ 3.1: Xét đoạn chương trình sau:
1
2
3
int i, a = 3;
for (i = 0; i<5; i++)
a ++;
Ở dòng lệnh đầu tiên, các biến i và a được khai báo và gán giá trị.
Tiếp theo là đến vòng lặp for, biểu thức 1 ở đây giá trị biến i được
khởi tạo giá trị ban đầu: i = 0. Sau đó, chương trình sẽ thực hiện biểu thức
2: kiểm tra i < 5 là đúng hay sai. Trong trường hợp này, kết quả kiểm tra
sẽ là đúng (vì i = 0 sẽ nhỏ nhơn 5). Chương trình sẽ nhảy đến thực hiện
khối lệnh (ở đây là lệnh a++ hay a = a+1). Sau đó, chương trình sẽ thực
73
hiện biểu thức 3 (ở đây i++ hay i = i +1). Như vậy, sau lần lặp đầu tiên,
kết quả sẽ là a = 4 và i = 1. Tiếp đó, chương trình sẽ thực hiện lần lặp
tiếp theo: quay về xét điều kiện của biểu thức 2 xem còn đúng không.
Lúc này, kết quả của biểu thức 2 vẫn còn đúng (vì khi đó i = 1 là nhỏ
hơn 5), chương trình sẽ thực hiện khối lệnh (a++) làm cho a sẽ bằng 5.
Chương trình sẽ tiếp tục thực hiện biểu thức 3 (i++ hay i bằng 2) và tiếp
tục quay lại vòng lặp. Biểu thức 2 sẽ được xét, khối lệnh được thực hiện,
biểu thức 3 được thực hiện…Cứ như vậy cho đến khi điều kiện ở biểu thức
2 là sai thì chương trình sẽ thoát ra khỏi vòng lặp. Sau khi kết thúc vòng lặp
này, kết quả thu được là a = 8 và i = 5.
Để hiểu rõ hơn thứ tự các lệnh thực hiện, ta có thể xem xét tiếp ví
dụ 3.2 sau:
1
#include <stdio.h>
2
int main(void)
3
{
4
int i, a = 0;
5
for (i = 0; i<2; i++,printf(“gia tri i la %d, “,i))
6
{a ++;
7
printf(“\n gia tri a la %d, “,a);
8
}
9
printf(“\ngia tri sau vong lap cua i la %d va a la
10 %d”, i, a);
11 getch();
12 return 0;
}
Khi chạy chương trình, kết quả in ra màn hình sẽ là:
1
2
3
74
gia tri a la 1, gia tri i la 1,
gia tri a la 2, gia tri i la 2,
gia tri sau vong lap cua i la 2 va a la 2
Ta chú ý lệnh for dòng lệnh 5, ở đây biểu thức 3 là một khối lệnh
(đây là trường hợp khá đặc biệt: các lệnh không được đặt trong cặp dấu
ngoặc nhọn { } và các lệnh ngăn cách với nhau bằng dấu phẩy). Mục
đích của chương trình này là nhằm làm rõ: biểu thức 3 sẽ được thực hiện
trước hay khối lệnh trong vòng lặp for sẽ thực hiện trước. Và kết quả
chương trình đã cho ta câu trả lời chính xác như phần giải thích ở ví dụ
3.1: khối lệnh trong thân vòng lặp for sẽ được thực hiện trước, sau đó
biểu thức 3 mới được thực hiện.
Ví dụ 3.1 sẽ hoàn toàn tương đương với các đoạn lệnh sau:
Trường hợp 1:
1
2
3
int i=0, a = 3;
for (; i<5; i++)
a ++;
Trường hợp 2:
1
2
3
4
5
6
int i=0, a = 3;
for (; i<5;)
{
a ++;
i++;
}
Trường hợp 3:
1
2
3
4
5
6
7
8
int i=0, a = 3;
for (; ;)
{
a ++;
i++;
if (i>=5)
break;
}
75
Với 3 trường hợp trên ta thấy: lệnh for có thể vắng mặt từ 1 đến cả
3 biểu thức nhưng chương trình vẫn thực hiện đúng như yêu cầu. Cũng
cần lưu ý, các biểu thức có thể vắng mặt nhưng các dấu chấm phẩy (;)
thì không được bỏ.
Ví dụ 3.3: Viết chương trình nhập điểm số một môn học của một
sinh viên từ bàn phím. Biết rằng điểm phải lớn hơn 0 và nhỏ hơn 10.
Nếu vi phạm điều kiện này thì yêu cầu nhập lại.
1
#include <stdio.h>
2
int main(void)
3
{
4
float a;
5
printf(“Moi ban nhap 1 so: “);
6
scanf(“%f”,&a);
7
for (; ;)
8
{
9
if ((a<=10)&&(a>=0))
10
{
11
printf(“ban da nhap gia tri phu hop”);
12
break;
13
}
14
else
15
printf(“\ngia tri ban nhap khong phu hop”);
16
printf(“\n moi ban nhap gia tri khac: “);
17
scanf(“%f”,&a);
18
}
19
printf(“\n gia tri ban nhap phu hop la %f”, a);
20
return 0;
21
}
Với chương trình ở trên, các lệnh ở những dòng lệnh từ 9 đến 15
sẽ giúp kiểm tra điều kiện để quyết định vòng lặp có được tiếp tục hay
không. Như vậy, đây là ví dụ có dùng kết hợp giữa vòng lặp for và điều
76
kiện if cho một yêu cầu cụ thể. Cần lưu ý thêm, câu lệnh break ở dòng
lệnh 12 sẽ giúp chương trình thoát khỏi vòng lặp gần nhất chứa nó. Như
vậy, khi gặp lệnh break này, chương trình sẽ thoát khỏi câu lệnh for (vì
lệnh break nằm trong lệnh for).
3.2. LỆNH WHILE
3.2.1. Cú pháp
while (Biểu thức)
{
Khối lệnh;
}
3.2.2. Hoạt động
Hình 3.2. Lưu đồ giải thuật của vòng lặp while
Nguyên tắc thực hiện:
 Bước 1: Tính giá trị của Biểu thức.
77
 Bước 2: Nếu giá trị của Biểu thức là sai (= 0) thì chương trình
thoát khỏi khỏi vòng while.
 Bước 3: Nếu giá trị của Biểu thức là đúng (khác 0) thì thực hiện
Khối lệnh và quay lại bước 1.
Chú ý:
 Biểu thức ở đây được hiểu là một biểu thức hoặc nhiều biểu
thức con. Nếu là nhiều biểu thức con thì các biểu thức con sẽ được cách
nhau bởi các dấu phẩy (,) và tính đúng sai sẽ được quyết định bởi biểu
thức con cuối cùng.
 Trong thân while (khối lệnh) có thể chứa một hoặc nhiều cấu
trúc điều khiển khác.
 Trong thân while có thể sử dụng lệnh continue để chuyển đến
đầu vòng lặp để thực hiện lần lặp tiếp theo (bỏ qua các câu lệnh còn lại
trong thân).
 Muốn thoát khỏi vòng lặp while tùy ý, có thể dùng các lệnh
break, goto, return.
3.2.3. Ví dụ minh họa
Ví dụ 3.4: Xét đoạn chương trình sau:
1
2
3
4
5
6
int a = 4, i = 0;
while (i < 4)
{
i++;
}
a = a + i;
Đầu tiên, ở dòng lệnh số 1, các biến a và i được khai báo kiểu int
và gán giá trị.
Sau đó chương trình chạy tới vòng lặp while. Ở đây biểu thức
i<4 sẽ được kiểm tra xem có đúng hay không, nếu đúng thì khối lệnh
sẽ được thực hiện còn nếu sai thì sẽ thoát ra khỏi vòng lặp. Như vậy, ở
lần lặp đầu tiên, biểu thức này là đúng (vì 0 < 4) và khối lệnh sẽ được
thực hiện: i sẽ được tăng lên 1 giá trị, sau đó quay lại để thực hiện lần
78
lặp tiếp theo. Biểu thức điều kiện một lần nữa sẽ được kiểm tra xem còn
đúng không… tiếp tục như vậy cho đến khi kết quả của biểu thức i < 4
là sai. Trong ví dụ này, vòng lặp được thực hiện 4 lần (với i = 0, 1, 2, 3)
đến khi i = 4 thì biểu thức i < 4 bị sai và thoát ra khỏi vòng lặp. Chương
trình sẽ chuyển đến dòng 6 và tính giá trị của biến a = a + i = 4 + 4 = 8,
và kết thúc đoạn chương trình.
Như vậy, với đoạn chương trình trên, nếu vắng mặt biểu thức điều
kiện i < 4 của lệnh while ở dòng 4 thì vòng lặp sẽ lặp vô hạn vì thao tác
kiểm tra biểu thức sẽ cho kết quả luôn luôn đúng, đó là điều không
mong muốn. Do đó, người lập trình cần cẩn thận để tránh các vòng lặp
vô hạn này.
Ví dụ 3.5: Chương trình tính giai thừa của một số nhập từ bàn phím:
1
#include <stdio.h>
2
int main(void)
3
{
4
int
5
printf(“Moi nhap vao so can tim giai thua “);
6
scanf(“%d”,&N);
7
if(N < 0)printf(“So nay khong co giai thua”);
8
else if (N == 0)printf(“Giai thua cua 0 la 1”);
9
else
10
{
a = 1, N, i = 1;
11
while
12
{
(i <= N)
13
a = a*i;
14
i ++;
15
}
16
printf(“\n Giai thua cua %d la %d”, N,a);
17
}
18
return 0;
19
}
79
Trong ví dụ này, có sự kết hợp của câu lệnh if…else if…else... và lệnh
while để tìm điều kiện của một số để tính giai thừa của số này và in ra
kết quả. Các phần liên quan đến lệnh rẽ nhánh if đã được bày ở chương
trước. Trong phần này, ta chú ý đến chương trình trên trong câu lệnh
while, từ dòng 11 đến hết dòng 15, đây là đoạn chính để tính giai thừa,
giúp chương trình nhân liên tục các số từ 1 đến N. Dòng 16 sẽ in ra kết
quả đã tính được ở vòng lặp while.
Ví dụ 3.6: Xét đoạn chương trình yêu cầu người dùng nhập vào
một số bí mật (số 0) sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int a , b;
printf(“moi nhap vao so bi mat “);
scanf(“%d”, &a);
b = 1;
while (a!=0)
{
printf(“vui long nhap lai “);
scanf(“%d”,&a);
b++;
if(b>=3)
{
printf(“Ban bi khoa chuong trinh”);
while(1);
}
}
Trong đoạn chương trình trên ta thấy, lệnh while được sử dụng để
kiểm tra một số nguyên nhập vào có bằng với một giá trị đã cho trước
hay không (biểu thức kiểm tra a != 0: trong trường hợp này là kiểm tra
xem số nhập vào có bằng 0 hay không). Nếu số nhập vào là số 0 thì
chương trình sẽ thoát ra khỏi vòng lặp while, nếu sai thì chương trình sẽ
yêu cầu nhập lại và chỉ được phép nhập lại tối đa 3 lần (dòng lệnh 10).
Nếu cả 3 lần nhập lại đều sai thì chương trình sẽ rơi vào vòng lặp vô hạn
80
(dòng lệnh 13) và chỉ còn cách tắt đi và chạy lại. Chương trình này là
cơ sở dùng để kiểm tra thông tin bảo mật nào đó nếu áp dụng thêm các
phần học ở các chương tiếp theo.
3.3. LỆNH DO .... WHILE
3.3.1. Cú pháp
do
{
Khối lệnh ;
}
while (biểu thức);
3.3.2. Hoạt động
Hình 3.3. Lưu đồ giải thuật vòng lặp do... while
Nguyên tắc thực hiện:
 Bước 1: Máy thực hiện Khối lệnh.
 Bước 2: Sau đó tính giá trị của Biểu thức, nếu giá trị của Biểu
thức là sai thì chương trình thoát ra khỏi vòng lặp. Nếu giá trị của Biểu
thức là đúng thì quay lại Bước 1.
81
Chú ý: Với lệnh while thì Biểu thức điều kiện được kiểm tra trước,
nếu đúng mới thực hiện. Với lệnh do...while thì Khối lệnh được thực hiện
trước khi kiểm tra Biểu thức, đồng nghĩa với việc Khối lệnh sẽ được thực
hiện ít nhất là 1 lần. Biểu thức điều kiện có thể gồm nhiều biểu thức, tuy
nhiên tính đúng sai sẽ được căn cứ theo kết quả của biểu thức cuối cùng.
3.3.3. Ví dụ minh họa
Ví dụ 3.7: Cho đoạn chương trình sau:
1
2
3
4
5
6
int i = 0;
do
{
printf(“%d”,i);
}
while (i++ <4);
Kết quả sau khi chạy đoạn chương trình trên sẽ là:
01234
Giải thích hoạt động:
Lệnh do … while khác với while và for ở chỗ nó sẽ thực hiện khối
lệnh trước, sau đó mới kiểm tra biểu thức điều kiện. Như trong trường
hợp này, lệnh printf ở dòng lệnh số 4 sẽ được thực hiện ít nhất một lần
dù biểu thức điều kiện ở dòng 6 có đúng hay không. Ở đây, dòng lệnh
6 vừa có chức năng xét điều kiện vòng lặp vừa làm tăng biến điều kiện.
Như vậy, chương trình sẽ thực hiện như sau: đầu tiên lệnh printf được
thực hiện, sau đó biểu thức điều kiện được xét để kiểm tra xem biến i
có nhỏ hơn 4 không (và sau đó tăng giá trị biến i lên 1), nếu nhỏ hơn
thì chương trình quay lại lần lặp tiếp theo, cho đến khi i = 4 thì thoát ra
khỏi vòng lặp và kết thúc.
Nếu thay dòng lệnh 6 bằng câu lệnh while (++i<4); thì kết quả
sẽ là:
0123
82
Các bạn hãy giải thích xem tại sao? (Lưu ý đến việc toán tử ++ được
đặt trước biến i).
Nếu thay dòng lệnh 4 bằng lệnh printf(“%d\n”,i); thì điều gì sẽ
xảy ra?
Ví dụ 3.8: Chương trình nhập vào độ dài 3 cạnh của một tam giác
và kiểm tra xem các độ dài này có phù hợp không, nếu không phù hợp
yêu cầu nhập lại.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
int main(void)
{
int a,b,c,dk;
do
{
printf(“moi ban nhap vao canh 1: “);
scanf(“%d”,&a);
printf(“moi ban nhap vao canh 2: “);
scanf(“%d”,&b);
printf(“moi ban nhap vao canh 3: “);
scanf(“%d”,&c);
dk=0;
if((a>=b+c)||(b>=a+c)||(c>=a+b))
{
dk=1;
printf(“ban nhap khong dung\n”);
printf(“moi ban nhap lai 3 canh \n”);
}
else
printf(“ban da nhap dung 3 canh tam giac”);
}
while (dk);
return 0;
}
83
Giải thích hoạt động:
Ví dụ này cho ta thấy kết quả của biểu thức điều kiện của vòng
lặp do… while là do khối lệnh của nó tạo ra, tương ứng với dữ liệu nhập
vào. Dòng 14 là điều kiện để đảm bảo các giá trị đã nhập vào cho 3 biến
sẽ là độ dài 3 cạnh của tam giác: nếu điều này đúng thì thoát ra khỏi
vòng lặp, nếu sai thì lặp lại cho đến khi nào đúng thì mới thoát. Kết
hợp ví dụ này và ví dụ 2.4 ta sẽ có một bài toán tính chu vi và diện
tích của một tam giác và có xét đến các điều kiện của 3 cạnh của tam
giác đó.
3.4. CÂU LỆNH BREAK
Câu lệnh break; cho phép thoát khỏi for, while, do...while và
switch. Khi có nhiều vòng lặp lồng nhau, câu lệnh break; sẽ đưa máy ra
khỏi vòng lặp (hoặc switch) bên trong nhất đang chứa break. Như vậy,
lệnh break; cho ta khả năng thoát khỏi vòng lặp từ một điểm bất kỳ bên
trong vòng lặp mà không dùng đến điều kiện kết thúc vòng lặp ở biểu
thức điều kiện. Mọi câu lệnh break; có thể thay bằng lệnh goto với nhãn
ở vị trí thích hợp. Xét chương trình sau:
1
#include <stdio.h>
2
int main(void)
3
{
4
int a;
5
for(; ;)
6
{
7
8
scanf(“%d”,&a);
9
printf(“Nhap so duong: “);
if (a >= 0)
10 break;
11
}
12
return 0;}
13
84
Trong chương trình trên, lệnh for(; ;) ở dòng lệnh số 5 sẽ tạo ra một
vòng lặp vô tận. Bên trong thân vòng lặp, lệnh break; ở dòng lệnh số
10 sẽ giúp chương trình thoát khỏi vòng lặp vô tận trên khi xảy ra điều
kiện a >= 0. Điều này tương đương với việc vòng lặp sẽ tiếp tục lặp nếu
người dùng nhập vào một số a < 0 và chỉ kết thúc bởi lệnh break; khi
người dùng nhập một số a >= 0. Kết quả của chương trình là:
1
2
3
Nhap so duong: -4
Nhap so duong: -6
Nhap so duong: 8
3.5. CÂU LỆNH CONTINUE
Trái với câu lệnh break; (dùng để thoát khỏi vòng lặp), câu lệnh
continue; dùng để bắt đầu một lần lặp mới của vòng lặp bên trong nhất
đang chứa nó. Nói một cách chính xác hơn thì:
 Khi gặp câu lệnh continue; bên trong thân của một lệnh for, các
lệnh phía sau lệnh continue của sẽ được bỏ qua, chương trình sẽ tăng
biến điều khiển và kiểm tra điều kiện để thực hiện tiếp các lệnh trong
vòng lặp nếu điều kiện trả về True hoặc dừng lại nếu điều kiện trả về
False.
 Khi gặp lệnh continue bên trong thân của while hoặc do...
while, chương trình sẽ chuyển tới xác định giá trị biểu thức điều kiện
(viết sau từ khóa while) và sau đó tiến hành kiểm tra điều kiện kết thúc
vòng lặp.
Lưu ý là lệnh continue; chỉ áp dụng cho các vòng lặp chứ không
dùng cho lệnh switch.
Ví dụ 3.9: Ta xét lại ví dụ 3.1 ở trường hợp 3 và thêm vào câu lệnh
continue; như sau:
1
2
3
#include <stdio.h>
int main(void)
{
85
4
5
6
7
8
9
10
11
12
13
int i=0, a = 3;
for (; ;)
{ a ++;
i++;
if (i<5) continue;
break;
}
printf(“gia tri a la %d”,a);return 0;
return 0;
}
Kết quả sau khi chạy chương trình trên là:
gia tri a la 8
Ở dòng 8, nếu giá trị i thỏa mãn điều kiện i < 5 thì gặp câu lệnh
continue; nên vòng lặp được tiếp tục thực hiện (lúc này lệnh break; ở
dòng 9 sẽ không được thực hiện). Đến khi i = 5, điều kiện của lệnh if ở
dòng 8 sẽ có kết quả là sai, câu lệnh continue; không được thực hiện,
chương trình sẽ chuyển xuống dòng lệnh 9, khi đó lệnh break; được
thực hiện và thoát khỏi vòng lặp for và kết thúc chương trình.
3.6. CÂU LỆNH GOTO VÀ NHÃN
Lệnh goto có cú pháp:
goto nhãn;
nhãn:
Lệnh;
Nhãn có thể viết như tên biến và có thêm dấu hai chấm (:) đứng
sau. Nhãn có thể được gán cho bất kỳ câu lệnh nào trong chương trình.
Khi gặp lệnh này máy sẽ nhảy tới vị trí câu lệnh có nhãn tương
ứng với nhãn đã được viết sau từ khóa goto.
Khi dùng lệnh goto cần lưu ý các đặc điểm sau:
86
 Câu lệnh goto và nhãn cần nằm trong một khối lệnh. Điều này
nói lên rằng: lệnh goto chỉ cho phép nhảy từ vị trí này đến vị trí khác
trong thân của một hàm. Nó không thể dùng để nhảy từ hàm này sang
hàm khác.
 Không cho phép dùng toán tử goto để nhảy từ ngoài vào trong
một khối lệnh. Tuy nhiên việc nhảy từ trong ra ngoài khối lệnh lại hoàn
toàn hợp lệ.
Ví dụ 3.10:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main (void)
{
int a = 1;
nhan:do
{
if(a == 4)
{
a = a+2;
printf(“khong in a=4 va a=5!\n”);
goto nhan;
}
printf(“Gia tri cua a la: %d\n”, a);
a = a+1;
}while(a < 9);
return 0;
}
Kết quả chạy chương trình là:
1
2
3
4
5
6
7
Gia tri cua a la: 1
Gia tri cua a la: 2
Gia tri cua a la: 3
khong in a=4 va a=5!
Gia tri cua a la: 6
Gia tri cua a la: 7
Gia tri cua a la: 8
87
Đây là vòng lặp do … while có kết hợp với với lệnh goto… nhãn.
Dòng lệnh ở dòng 7 chỉ thực hiện khi a = 4 và lệnh goto ở dòng 11 sẽ
được thực hiện.
3.7. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
a.
b.
c.
d.
e.
88
int a, i;
scanf(“%d”,&a);
printf(“Nhap 5 so nguyen: \n”);
i = 0;
while (i < 5);
scanf(“%d”, &a);
i++;
float a = -1;
while (a <0 && a > 10)
{
printf(“Nhap so trong pham vi 0 - 10: \n”);
scanf(“%f”,&a);
}
int a = -1;
while (a <= 0)
do
{
printf(“Nhap so > 0: \n”);
scanf(“%d”,&a);
}
int b;
do
printf(“Nhap so nguyen duong: “);
scanf(“%d”,&b);
while (a < 0);
int kq = 1,i = 0;
int a;
scanf(“%d”,&a);
for (i < 5)
{
kq = a*kq; i++;
}
printf(“%d”,kq);
2. Hãy đọc và phân tích chương trình sau :
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a =0,i=0;
while (++i <10)
{
if (i%2==0)
a = a+i;
else
a = a+ 2*i;
if (i == 6)
break;
}
printf(“%d”, a);
getch();
return 0;
}
Yêu cầu:
 Vẽ lưu đồ hoạt động của chương trình..
 Cho biết kết quả in ra màn hình của chương trình.
3. Hãy đọc và phân tích chương trình sau :
#include <stdio.h>
#include <conio.h>
int main (void)
89
{
int a = 0,i, j = 0;
for (i = 3; i > 0; i--)
{
while (j++<10)
{
if (j == 7)
continue;
if (j%2==1)
printf(“%d”,j);
}
}
getch();
return 0;
}
Yêu cầu:
 Cho biết kết quả in ra màn hình của chương trình.
4. Hãy đọc và phân tích chương trình sau:
#include <stdio.h>
#include <conio.h>
int main (void)
{
int n, i, j;
printf(“Nhap vao so n :”);
scanf(“%d”,&n);
if (n == 0 || n ==1 || n == 2)
printf(“SNT”);
else
{
90
for (i=2; i<=n; i++)
if (n %i == 0)
break;
if (i==n)
printf(“SNT”);
else
printf(“Khong phai SNT”);
}
getch();
return 0;
}
Yêu cầu :
 Cho biết: Chương trình thực hiện chức năng gì?
 Hãy viết lại chương trình khác thực hiện cùng chức năng với
chương trình trên.
5. Viết chương trình nhập vào 2 số nguyên a, b từ bàn phím và in
ra tất cả các số nguyên tố trong đoạn [a b].
Ví dụ : Nhap a:
Nhap b:
4
15
Cac so nguyen to trong doan [4 15] la: 7 11 13
Ví dụ : Nhap a:
14
Nhap b:
16
Khong co so nguyen to trong doan [14 16].
6. Viết chương trình nhập vào 2 số nguyên a và b và chỉ chấp nhận
b > a > 0. Tìm và in ra ước số chung lớn nhất và bội số chung nhỏ nhất
của 2 số này.
7. Viết chương trình nhập vào số nguyên n, tính và in ra tổng sau:
91
S = 1+
1
1
1
1
+
+
+ ... +
1+ 2 1+ 2 + 3 1+ 2 + 3 + 4
1 + 2 + 3 + 4 + ... + n
8. Viết chương trình giải bài toán trăm trâu trăm cỏ.
 Có 100 con trâu và 100 bó cỏ.
 Trâu đứng ăn 5 (bó cỏ), trâu nằm ăn 3 (bó cỏ), trâu già 3 con 1
bó (cỏ).
 Hỏi có bao nhiêu trâu đứng, bao nhiêu trâu nằm và bao nhiêu
trâu già.
9. Viết chương trình xử lý các yêu cầu sau:
 Nhập vào một số nguyên n và chỉ chấp nhận giá trị n > 0, nếu
không thỏa mãn yêu cầu trên thì bắt buộc người dùng nhập lại cho tới
khi nào thỏa mãn yêu cầu.
 Tính và in ra tổng của tất cả các ước số của n trong đoạn [1 n].
92
CHƯƠNG 4
MẢNG VÀ CHUỖI
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Khai báo được mảng 1 chiều, mảng 2 chiều, chuỗi, mảng chuỗi.
- Ứng dụng được các thao tác xử lý trên mảng: nhập dữ liệu, xuất
dữ liệu, tìm số lớn nhất/nhỏ nhất, sắp xếp.
4.1. MẢNG
Mảng là tập hợp các phần tử có cùng kiểu dữ liệu và các phần tử
trong mảng được cấp các địa chỉ ô nhớ liền kề nhau.
4.1.1. Mảng 1 chiều
4.1.1.1. Khai báo mảng 1 chiều:
 Cú pháp 1: kiểu dữ liệu
Ví dụ 4.1a: int
tên mảng [số phần tử];
a[10];
Mỗi phần tử trong mảng có kiểu int
10 phần tử
Với cách khai báo như ví dụ 4.1a thì mảng a gồm 10 phần tử, các
phần tử trong mảng này có kiểu dữ liệu là int (có kích thước vùng nhớ
là 4 byte), vùng nhớ của các phần tử này liên tiếp nhau trong vùng nhớ
từ phần tử a[0] đến phần tử a[9].
Khi kháo báo như trên, các phần tử mảng được tham chiếu
như sau:
93
Ta xem kết quả của chương trình sau để hiểu rõ hơn.
Ví dụ 4.1b:
1
#include <stdio.h>
2
int main(void)
3
{
4
int
5
printf(“kich thuoc cua a[0] la: %d”,sizeof(a[0]));
6
printf(“\ndia chi cua a[0] la: %d”,&a[0]);
7
printf(“\ndia chi cua a[1] la: %d”,&a[1]);
8
printf(“\ndia chi cua a[2] la: %d”,&a[2]);
9
printf(“\ndia chi cua a[3] la: %d”,&a[3]);
10
printf(“\ndia chi cua a[4] la: %d”,&a[4]);
11
printf(“\ndia chi cua a[5] la: %d”,&a[5]);
12
printf(“\ndia chi cua a[6] la: %d”,&a[6]);
13
printf(“\ndia chi cua a[7] la: %d”,&a[7]);
14
printf(“\ndia chi cua a[8] la: %d”,&a[8]);
15
printf(“\ndia chi cua a[9] la: %d”,&a[9]);
16
return 0;
17
}
a[10];
Giải thích chương trình:
Ở dòng lệnh số 5 có câu lệnh sizeof(), ý nghĩa của câu lệnh này
là lấy kích thước tính theo byte của một biến bất kỳ. Dòng lệnh 6 có
câu lệnh &a[0]: ý nghĩa của câu lệnh này là lấy địa chỉ đầu tiên của
biến a[0]. Các câu lệnh ở các dòng phía dưới sẽ lấy và in ra địa chỉ
của các phần tử còn lại của mảng. Kết quả khi chạy chương trình trên
sẽ là:
94
1
2
3
4
5
6
7
8
9
10
11
kich thuoc cua a[0] la: 4
dia chi cua a[0] la: 2686664
dia chi cua a[1] la: 2686668
dia chi cua a[2] la: 2686672
dia chi cua a[3] la: 2686676
dia chi cua a[4] la: 2686680
dia chi cua a[5] la: 2686684
dia chi cua a[6] la: 2686688
dia chi cua a[7] la: 2686692
dia chi cua a[8] la: 2686696
dia chi cua a[9] la: 2686700
Với kết quả trên cho thấy địa chỉ trong bộ nhớ của các biến trong
mảng là liền kề nhau và tăng dần từ a[0] đến a[9], mỗi biến có kích
thước là 4 byte.
 Cú pháp 2: kiểu dữ liệu
tên mảng [số phần tử]= {giá trị};
Ví dụ 4.2: int a[10] = {5,7,9};
Với cách khai báo như trong ví dụ 4.2 thì các phần tử a[0], a[1],
a[2] là đã được gán giá trị ban đầu lần lượt là 5, 7 và 9, các phần tử còn
lại sẽ được khởi tạo bằng 0. Xem ví dụ này qua chương trình dưới đây:
1
#include <stdio.h>
2
int main(void)
3
{
4
int
5
printf(“gia tri phan tu a[0] la: %d”,a[0]);
6
printf(“\ngia tri phan tu a[1] la: %d”,a[1]);
7
printf(“\ngia tri phan tu a[2] la: %d”,a[2]);
a[10] = {5,7,9};
95
8
9
10
11
12
printf(“\ngia tri phan tu a[3] la: %d”,a[3]);
printf(“\ngia tri phan tu a[4] la: %d”,a[4]);
printf(“\ngia tri phan tu a[5] la: %d”,a[5]);
return 0;
}
Khi chạy chương trình trên, ta được kết quả sau:
1
2
3
4
5
6
gia
gia
gia
gia
gia
gia
tri
tri
tri
tri
tri
tri
phan
phan
phan
phan
phan
phan
tu
tu
tu
tu
tu
tu
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
Cú pháp 3: kiểu dữ liệu
la:
la:
la:
la:
la:
la:
5
7
9
0
0
0
tên mảng [ ] = {giá trị};
Ví dụ 4.3: int a[] = {5,7,9,3,4,6,8,1,2,5};
Với cách khai báo như ví dụ 4.3 thì máy sẽ tạo ra một mảng a có
số phần tử mảng đúng bằng số lượng các giá trị được khởi tạo trong cặp
dấu ngoặc nhọn { }. Và giá trị của từng phần tử cũng tương ứng với các
giá trị đã được khai báo trong cặp dấu ngoặc nhọn { } này. Chương trình
và kết quả chạy ở dưới đây thể hiện điều đó.
1
#include <stdio.h>
2
int main(void)
3
{
4
int
5
printf(“gia tri phan tu a[0] la: %d”,a[0]);
6
printf(“\ngia tri phan tu a[1] la: %d”,a[1]);
96
a[10] = {5,7,9,3,4,6,8,1,2,5};
7
8
9
10
11
12
13
14
15
16
printf(“\ngia
printf(“\ngia
printf(“\ngia
printf(“\ngia
printf(“\ngia
printf(“\ngia
printf(“\ngia
printf(“\ngia
return 0;
}
tri
tri
tri
tri
tri
tri
tri
tri
phan
phan
phan
phan
phan
phan
phan
phan
tu
tu
tu
tu
tu
tu
tu
tu
a[2]
a[3]
a[4]
a[5]
a[6]
a[7]
a[8]
a[9]
la:
la:
la:
la:
la:
la:
la:
la:
%d”,a[2]);
%d”,a[3]);
%d”,a[4]);
%d”,a[5]);
%d”,a[6]);
%d”,a[7]);
%d”,a[8]);
%d”,a[9]);
Kết quả chạy chương trình:
1
2
3
4
5
6
7
8
9
10
gia
gia
gia
gia
gia
gia
gia
gia
gia
gia
tri
tri
tri
tri
tri
tri
tri
tri
tri
tri
phan
phan
phan
phan
phan
phan
phan
phan
phan
phan
tu
tu
tu
tu
tu
tu
tu
tu
tu
tu
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
a[6]
a[7]
a[8]
a[9]
la:
la:
la:
la:
la:
la:
la:
la:
la:
la:
5
7
9
3
4
6
8
1
2
5
Phần trên đây đã trình bày 3 cú pháp khai báo của mảng một
chiều. Cũng cần lưu ý thêm, khi ta đã khai báo mảng một chiều và
gán giá trị ban đầu cho các phần tử thì những phần tử nếu chưa được
gán giá trị sẽ có giá trị bằng 0. Nếu khai báo 1 mảng là bao nhiêu
phần tử thì chương trình sẽ tạo ra một số lượng các ô nhớ tương ứng
cho dù ta có dùng hay không dùng đến, do vậy, ta cần khai báo một
mảng có kích thước phù hợp với dự định sẽ dùng để tránh lãng phí
bộ nhớ.
4.1.1.2. Các thao tác cơ bản trên mảng 1 chiều:
 Nhập giá trị cho các phần tử mảng:
97
Ví dụ 4.4: Nhập giá trị cho mảng từ bàn phím:
1
int a[10], i;
2
for (i=0; i<10; i++)
3
{
4
printf(“nhap phan tu thu %d: “, i);
5
scanf(“%d”, &a[i]);
6
}
Xuất giá trị các phần tử mảng:
Ví dụ 4.5: Xuất ra màn hình các giá trị của một mảng cho trước.
1
int a[5]={1,2,3};
2
for (i = 0; i<5; i++)
3
{
4
5
printf(“%d
“, a[i]);
}
Tìm giá trị lớn nhất trong các phần tử mảng:
Ví dụ 4.5a: Tìm giá trị lớn nhất trong một mảng cho trước và in
ra giá trị này:
1
int a[6]={1,7,9,6,8,2};
2
int max, i;
3
max = a[0];
4
for (i=1; i<6; i++)
5
{
6
if(max < a[i])
7
max = a[i];
8
}
9
printf(“gia tri lon nhat la %d”, max);
Kết quả thu được khi chạy ví dụ 4.5a là:
gia tri lon nhat la 9
Giải thích hoạt động:
Trong đoạn chương trình trên, biến max được dùng để so sánh giá
98
trị của các phần tử trong mảng. Đầu tiên, biến max được gán dữ liệu
là giá trị của phần tử a[0], sau đó bằng vòng lặp for (từ dòng 4 đến hết
dòng 8) max sẽ được so sánh với tất cả các phần tử còn lại trong mảng.
Trong quá trình so sánh, nếu giá trị nào lớn hơn max (biểu thức ở dòng
6) thì gán max bằng giá trị phần tử đó (dòng 7). Khi kết thúc vòng lặp
thì max là giá trị lớn nhất cần tìm.
Ví dụ 4.5b: Tìm giá trị và vị trí của phần tử lớn nhất trong mảng:
1
int a[6]={1,7,9,6,8,2};
2
int max, i, j;
3
max = a[0];
4
for (i=1; i<6; i++)
5
{
6
if(max < a[i])
7
{
8
max = a[i];
9
j=i;
10
}
11 }
12 printf(“phan tu lon nhat mang la a[%d] “, j);
13 printf(“\ngia tri cua a[%d] la %d”, j, max);
Và kết quả thu được khi chạy ví dụ 4.5b là:
1
phan tu lon nhat mang la a[2]
2
gia tri cua a[2] la 9
Giải thích hoạt động:
Ở ví dụ 4.5b này chỉ khác ví dụ 4.5a ở dòng lệnh 9. Dòng lệnh này
nhằm để gán cho biến j chỉ số mảng của phần tử có giá trị lớn nhất để
khi in ra giá trị đúng yêu cầu của đề bài.
Ví dụ 4.5c: Tìm giá trị nhỏ nhất trong mảng:
99
1
2
3
4
5
6
7
8
9
int
int
min
for
{
a[6]={1,7,9,6,8,2};
min, i;
= a[0];
(i=1; i<6; i++)
if(min > a[i])
min = a[i];
}
printf(“%d”, min);
Giải thích hoạt động:
Ví dụ 4.5c này khác với ví dụ 4.5a ở ý nghĩa tìm min so với tìm
max (ở dòng lệnh số 6). Lưu ý thêm là ta hoàn toàn có thể gộp các ví dụ
4.5a, 4.5b, 4.5c vào trong một chương trình nếu được yêu cầu.
 Sắp xếp mảng:
Ví dụ 4.6: Sắp xếp các giá trị của một mảng cho trước theo thứ tự
giảm dần:
1
int a[6]={1,7,9,6,8,2};
2
int
3
for (i=0; i<5; i++)
4
tam,i,j;
for (j = i+1; j<6; j++)
5
if (a [i]< a[j])
6
{
7
tam = a[i];
8
a[i]= a[j];
9
a[j]= tam;
10 }
11 for (i=0; i<6; i++)
12 {
13
14 }
100
printf (“%d”, a[i]);
Kết quả thu được khi chạy chương trình ở ví dụ 4.6 là:
987621
Giải thích hoạt động:
Đây là một bài toán khá điển hình về mảng nói chung và mảng
một chiều nói riêng. Để sắp xếp mảng bắt thì buộc phải dùng 2 vòng for
lồng vào nhau. Với vòng for bên trong, sau khi chạy hết vòng for này
ta sẽ được một giá trị lớn nhất (do dòng lệnh số 5 với điều kiện so sánh
(a[i]<a[j])) và được đặt ở ví trí đầu tiên trong mảng. Vòng for ngoài sẽ
thay đổi vị trí để tìm giá trị lớn tiếp theo và đặt ở vị trí kế tiếp với giá
trị đã tìm được trước đó. Cứ như vậy cho đến khi kết thúc 2 vòng for.
Cụ thể:
+ Với i = 0; j = 1, 2, 3, 4, 5: đầu tiên chương trình sẽ so sánh giá
trị của phần tử a[0] với giá trị của a[1], nếu a[1] lớn hơn a[0] thì hoán
đổi giá trị của a[0] và giá trị của a[1], lúc đó giá trị của a[0] sẽ lớn hơn
giá trị của a[1]. Tiếp tục so sánh a[0] với a[2]…. Và chương trình chạy
cho đến giá trị j = 5 (hết vòng for bên trong) thì ta thu được mảng a khi
đó là: a[]={9,1,7,6,8,2}.
+ i =1: máy sẽ bắt đầu so sánh từ phần tử a[1] (vì ta đã tìm được
giá trí a[0] là lớn nhất) và làm như đã giải thích ở phần trên, ta sẽ tìm
được a[]={9,8,1,6,7,2}.
+ i = 2: ta tìm tiếp được a[]={9,8,7,1,6,2}.
+ i = 3, ta tìm tiếp được a[]={9,8,7,6,1,2}.
+ i = 4, ta tìm tiếp được a[]={9,8,7,6,2,1}.
Các dòng lệnh từ 11 đến hết dòng 14 là vòng lặp for giúp chương
trình in ra các giá trị của mảng đã được sắp xếp.
Nếu với yêu cầu sắp xếp mảng và in ra giá trị từ lớn đến bé ta chỉ
cần thay đổi điều kiện so sánh ở dòng lệnh số 5 thành: if (a[i] >a[j]).Khi
101
đó, sau khi chạy xong chương trình, ta sẽ được mảng a[]={1,2,6,7,8,9}.
Ví dụ 4.7: Chương trình nhập giá trị của mảng 10 phần tử từ bàn
phím và sắp xếp và in ra mảng theo thứ tự tăng dần.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a[10], i, j, tam;
for (i=0; i<10; i++)
{
printf(“nhap phan tu thu %d: “, i) ;
scanf(“%d”,&a[i]);
}
for (i=0; i<9; i++)
for (j = i+1; j<10; j++)
if (a [i]> a[j])
{
tam = a[i];
a[i]= a[j];
a[j]= tam;
}
for (i=0; i<10; i++)
{
printf(“%d”, a[i]);
}
getch();
return 0;
}
Ví dụ 4.7 là sự mở rộng thêm của ví dụ 4.6, với các dòng lệnh từ
số 7 đến số 11 giúp ta có thể nhập giá trị của một mảng từ bàn phím. Và
102
điều kiện ở dòng lệnh 15 giúp cho việc sắp xếp các giá trị của mảng sẽ
theo thứ tự từ bé đến lớn.
4.1.2. Mảng 2 chiều
Mảng 2 chiều là mảng gồm chiều ngang và chiều dọc hay mảng
gồm hàng và cột (mảng một chiều có thể xem là một mảng 2 chiều
có số hàng là 1 và số cột là số lượng phần tử của mảng).
4.1.2.1. Khai báo:
Cú pháp : kiểu dữ liệu
Ví dụ 4.8:
int
tên mảng [số hàng][số cột];
a[5][10];
Mỗi phần tử trong mảng có kiểu int
Tham chiếu tới từng phần tử mảng:
Ví dụ 4.9a: Gán giá trị cho các phần tử trong mảng: gán đầy đủ các
giá trị cho các phần tử.
int b[ 2 ][ 2 ] = { { 1, 2 }, { 3, 4 } };
103
Như vậy ta sẽ được các giá trị của từng phần tử mảng 2 chiều
như sau:
1
2
3
4
b[0][0]=1
b[0][1]=2
b[1][0]=3
b[1][1]=4
Ví dụ 4.9b: Gán các giá trị cho các phần tử trong mảng bằng
cách khác:
1
int a1[ 2 ][ 3 ] = { 1, 2, 3, 4, 5 };
Khi đó, kết quả các phần tử của mảng này sẽ là:
1
2
3
4
5
6
a1[0][0]=1
a1[0][1]=2
a1[0][2]=3
a1[1][0]=4
a1[1][1]=5
a1[1][2]=0
Ví dụ 4.9c: Gán giá trị cho các phần tử trong mảng: gán không đầy
đủ các giá trị cho các phần tử.
int a2[ 2 ][ 3 ] = { { 1, 2 }, { 4 } };
Khi đó, các phần tử trong mảng này có giá trị là:
1
2
3
4
5
6
a2[0][0]=1
a2[0][1]=2
a2[0][2]=0
a2[1][0]=4
a2[1][1]=0
a2[1][2]=0
4.1.2.2. Các thao tác cơ bản trên mảng 2 chiều:
 Nhập giá trị cho mảng:
104
Ví dụ 4.10:
1
2
3
4
5
6
7
8
int a[5][10];
int i, j;
for (i=0; i<5; i++)
for(j=0; j<10; j++)
{
printf(“Nhap phan tu %d %d”,i, j);
scanf(“%d”, &a[i][j]) ;
}
Thứ tự nhập dữ liệu vào mảng 2 chiều:
 In giá trị các phần tử mảng ra màn hình:
Ví dụ 4.11:
1
2
3
4
5
6
7
8
9
10
int a[3][2]={{1,2},{2,3},{3,4}};
int i,j;
for (i=0;i<3;i++)
{
for(j=0;j<2;j++)
{
printf (“%d”, a[i][j]);
}
printf(“\n”);
}
105
Ví dụ 4.12: Chương trình sau sẽ in ra số lớn nhất trên từng hàng
của một mảng 2 chiều nhập vào từ bàn phím.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a[5][4], i,j,max;
for (i=0;i<5;i++)
for(j=0;j<4;j++)
{
printf(“Nhap phan tu %d %d: “,i, j);
scanf(“%d”, &a[i][j]) ;
}
for (i=0; i<5; i++)
{
max = a[i][0];
for(j=0;j<4;j++)
{
if (max < a[i][j])
max = a[i][j];
}
printf(“%d\n”, max);
}
getch();
return 0;
}
4.2. CHUỖI VÀ MẢNG CHUỖI
4.2.1. Chuỗi
4.2.1.1. Định nghĩa:
Chuỗi là mảng 1 chiều các phần tử kiểu ký tự, kết thúc bằng ký
tự NULL.
106
4.2.1.2. Khai báo chuỗi:
 Cú pháp 1: char tên chuỗi[số phần tử];
Ví dụ 4.13: char s[10];
Mỗi phần tử trong chuỗi có kiểu char
 Cú pháp 2: char
10 phần tử
tên chuỗi[]= “Nội dung”;
Ví dụ 4.14: char s[] = “HelloDTVT”;
Mỗi phần tử trong chuỗi có kiểu char
10 phần tử
4.2.1.3. Xuất, nhập chuỗi
Hàm nhập chuỗi:
gets (biến chuỗi);
Hàm xuất chuỗi:
puts (biến chuỗi);
Xét chương trình sau:
1
2
#include <stdio.h>
3
4
5
6
int main(void)
{
char s[40];
printf(“Hay nhap 1 chuoi: \n”);
107
7
8
9
10
11
12 }
gets(s);
printf(“Chuoi da nhap la: \n”);
puts(s);
return 0;
Trong chương trình này, lệnh gets(s); ở dòng lệnh số 7 sẽ chờ
người dùng nhập vào một chuỗi dữ liệu và lưu vào biến chuỗi s. Lệnh
puts(s); ở dòng lệnh số 10 sẽ in nội dung của dữ liệu trong biến s ra màn
hình. Kết quả thu được khi chạy chương trình trên sẽ là:
1
2
3
4
Hay nhap 1 chuoi:
Xin chao!
Chuoi da nhap la:
Xin chao!
với chuỗi Xin chao! ở dòng kết quả số 2 là dữ liệu người dùng
nhập vào từ bàn phím.
4.2.1.4. Các thao tác trên từng phần tử của chuỗi
Ta có thể thao tác trên từng phần tử của một chuỗi tương tự như
thao tác trên từng phần tử mảng 1 chiều.
Xét chương trình sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
108
#include <stdio.h>
int main(void)
{
char s[10] = “Hello”;
int i,dem;
dem = 0;
for(i = 0; i < 10; i++)
if(s[i] == ‘e’)
dem++;
printf(“Co %d ky tu e.”,dem);
return 0;
}
Trong chương trình này, từng phần tử s[i] của chuỗi s sẽ được
kiểm tra và so sánh với ký tự ‘e’ để thực hiện thao tác đếm số lượng
ký tự ‘e’ đang tồn tại trong chuỗi s. Kết quả thu được khi chạy chương
trình trên sẽ là:
Co 1 ky tu e.
4.2.2. Mảng chuỗi
4.2.2.1. Định nghĩa:
Là mảng 2 chiều các ký tự, mỗi hàng sẽ được kết thúc bằng một
ký tự NULL.
4.2.2.2. Khai báo mảng chuỗi:
 Cú pháp 1: char tên mảng chuỗi [số hàng][số cột];
Ví dụ 4.15: char s[5][10]:
Mỗi phần tử trong mảng chuỗi có kiểu char
 Cú pháp 2: char
dung”};
tên mảng chuỗi [số hàng][số cột]= {“nội
Ví dụ 4.16: char s[5][10]={“Mot”, “Hai”, “Ba”, “Bon”, “Nam”}:
Mỗi phần tử trong mảng chuỗi có kiểu char
109
4.2.2.3. Các thao tác trên mảng chuỗi
 Thao tác trên từng hàng: Mỗi hàng được xem làm một chuỗi,
việc thao tác trên từng hàng tương tự như thao tác trên từng chuỗi.
Ví dụ 4.17:
1
2
3
4
5
6
7
char s[5][10];
int i;
for (i=0;i<5;i++)
{
puts (“Nhap chuoi”);
gets (s[i]);
}
Thao tác trên từng phần tử: mỗi phần tử trong mảng chuỗi có
kiểu char, việc thao tác trên từng phần tử mảng chuỗi tương tự như thao
tác trên từng phần tử mảng 2 chiều.
Ví dụ 4.18:
1
2
3
4
5
char
s[5][10]={“Mot”, “Hai”, “Ba”, “Bon”, “Nam”};
int i,j;
for (i = 0; i < 5; i++)
for (j=0; j <10; j++)
printf (“%c”,s[i][j]);
4.2.3. Một số hàm liên quan đến ký tự và chuỗi ký tự
 Hàm atof
Cú pháp: double atof(const char *s); //Phải khai báo thư
viện math.h hoặc stdlib.h.
Hàm có chức năng chuyển đổi 1 chuỗi sang giá trị double.
Ví dụ 4.19:
1
float f;
2
3
4
5
char str[] = “12345.67”;
f= atof(str);
puts(str);
printf (“%.2f”, f);
110
Kết quả :
12345.67
12345.67
 Hàm atoi
Cú pháp: int atoi(const char *s); // Phải khai báo thư viện
stdlib.h.
Hàm có chức năng chuyển đổi 1 chuỗi sang giá trị kiểu int.
Ví dụ 4.20:
1
2
3
4
int i;
char str[] = “12345.67”;
i = atoi(str);
printf (“%d”, i);
Kết quả
12345
 Hàm itoa
Cú pháp: char *itoa(int value, char *string, int radix); //
Phải khai báo thư viện stdlib.h
Hàm có chức năng chuyển đổi số nguyên value sang chuỗi string
theo cơ số radix.
Ví dụ 4.21a:
1
2
3
4
int number = 12345;
char string[25];
itoa(number, string, 10);
puts(string);
Kết quả:
12345
Ví dụ 4.21b: chuyển đổi số sang chuỗi theo cơ số 2:
111
1
2
3
4
int number = 12345;
char string[25];
itoa(number, string, 2);
puts(string);
Kết quả:
11000000111001
 Hàm tolower
Cú pháp: int tolower(int ch);
// Phải khai báo thư viện ctype.h
Hàm có chức năng đổi chữ hoa sang chữ thường.
Ví dụ 4.22:
1
2
3
4
5
6
int len, i;
char string[] = “XIN CHAO”;
len = strlen(string);
for (i = 0; i < len; i++)
string[i] = tolower(string[i]);
puts(string);
Kết quả khi chạy chương trình:
xin chao
 Hàm toupper
Cú pháp: int toupper(int ch); // Phải khai báo thư viện ctype.h
Hàm có chức năng đổi chữ thường sang chữ hoa.
Ví dụ 4.23:
1
2
3
4
5
6
112
int len, i;
char string[] = “xin chao”;
len = strlen(string);
for (i = 0; i < len; i++)
string[i] = toupper(string[i]);
puts(string);
Kết quả khi chạy chương trình:
XIN CHAO
 Hàm strcat
Cú pháp: char *strcat(char *dest, const char *src); //
Phải khai báo thư viện string.h.
Hàm có chức năng thêm chuỗi src vào sau chuỗi dest.
Lưu ý: cú pháp trên có ký hiệu *, đây là ký hiệu của con trỏ sẽ
trình bày ở chương 5. Ở đây, ta hiểu đơn giản đây phải là tên của chuỗi
hay mảng.
1
2
3
4
5
6
char dich[25];
char s2[]=” “,s3[]=”chao”,s1[]=”Xin”;
strcat(dich, s1);
strcat(dich, s2);
strcat(dich, s3);
puts(dich);
Kết quả khi chạy chương trình:
Xin chao
 Hàm strcpy
Cú pháp: char *strcpy(char *dest, const char *src); //Phải khai
báo thư viện string.h.
Hàm có chức năng chép chuỗi src vào dest.
Ví dụ 4.24:
1
2
3
4
char dich[25];
char nguon[]=”Xin chao”;
strcpy(dich, nguon);
puts(dich);
Kết quả khi chạy chương trình:
113
Xin chao
 Hàm strcmp
Cú pháp: int *strcmp(const char *s1, const char *s2); // Phải khai
báo thư viện string.h.
Hàm có chức năng so sánh chuỗi s1 với chuỗi s2. Kết quả trả về:
• < 0 nếu s1 < s2
• = 0 nếu s1 = s2
• > 0 nếu s1 > s2
Ví dụ 4.25:
1
2
3
4
5
6
7
8
int a, b, c;
char s1[] =”aaa”,s2[]=”bbb”,s3[]= “aaa”;
a=strcmp(s1, s2); //ket qua tra ve - 1
printf(“%d”,a);
b=strcmp(s1, s3); //ket qua tra ve 0
printf(“\n %d”,b);
c=strcmp(s2, s3); //ket qua tra ve 1
printf(“\n %d”,c);
Kết quả khi chạy chương trình:
-1
0
1
 Hàm strlwr
Cú pháp: char *strlwr(char *s);
string.h
//Phải khai báo thư viện
Hàm có chức năng là chuyển chuỗi s sang chữ thường
Ví dụ 4.26:
1
2
114
char s[10] = “Xin Chao”;
puts(strlwr(s));
Kết quả khi chạy chương trình:
xin chao
 Hàm strupr
Cú pháp: char *strupr(char *s);
string.h
// Phải khai báo thư viện
Hàm có chức năng là chuyển chuỗi s sang chữ hoa
Ví dụ 4.27:
1
char s[10] = “Xin Chao”;
2
puts(strupr(s));
Kết quả khi chạy chương trình:
XIN CHAO
 Hàm strlen
Cú pháp: int strlen(const char *s);
viện string.h
//Phải khai báo thư
Hàm có chức năng là trả về độ dài chuỗi s.
Ví dụ 4.28:
1
char s[] = “Xin chao”;
2
int len_s;
3
len_s = strlen(s);
4
printf(“%d”,len_s);
Kết quả khi chạy chương trình:
8
4.3 BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
115
a.
int a[5] = {1};
printf(“%d”,a(2));
b.
int a[5], i;
for(i = 1; i <= 5; i++)
scanf(“%d”,&a[i]);
c.
int a[5], i;
printf(“Nhap mang 5 phan tu: \n”);
for(i = 0; i < 5; i++)
scanf(“%d”,&i);
printf(“Mang da nhap:\n “);
for(i = 0; i < 5; i++)
printf(“%d “,a[i]);
d.
min = a[0];
for(i = 1; i < 5; i++)
if(min > a[i])
a[i] = min;
printf(“So nho nhat: %d\n”,min);
e.
float a[2,3]= {{2,5},{7,4}};
2. Hãy đọc và phân tích chương trình sau :
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a[10],i, flag=0;
for (i=0; i<10; i++)
{
printf(“nhap phan tu thu %d: “, i);
116
scanf(“%d”,&a[i]);
}
for (i=0; i<10; i++)
{
if ((a[i]%2 == 1)&&(a[i]>=20))
{
flag = 1;
break;
}
}
if (flag == 1)
printf(“Co”);
else
printf(“Khong”);
getch();
return 0;
}
Yêu cầu:
 Cho biết chức năng của chương trình là gì?
3. Hãy đọc và phân tích chương trình sau :
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a[10],i, dem=0;
for (i=0; i<10; i++)
{
printf(“nhap phan tu thu %d: “, i);
scanf(“%d”,&a[i]);
}
117
for (i=0; i<10; i++)
{
if ((a[i]%5 == 0))
dem++;
}
printf (“co %d phan tu thoa yeu cau”, dem);
getch();
return 0;
}
Yêu cầu:
 Cho biết chức năng của chương trình là gì?
4. Hãy đọc và phân tích chương trình sau:
#include <stdio.h>
#include <conio.h>
int main (void)
{
int a[10],b[10],c[10],i, j=0,k=0;
for (i=0; i<10; i++)
{
printf(“nhap phan tu thu %d: “, i);
scanf(“%d”,&a[i]);
}
for (i=0; i<10; i++)
{
if ((a[i]%2 == 0))
b[j++]=a[i];
if ((a[i]%2 != 0))
c[k++]=a[i];
}
for (i=0; i<j; i++)
{
118
printf (“%d”, b[i]);
}
printf(“\n”);
for (i=0; i<k; i++)
{
printf (“%d”, c[i]);
}
getch();
return 0;
}
Yêu cầu :
 Cho biết chức năng của chương trình là gì?
5. Viết một chương trình thực hiện các yêu cầu sau:
a. Nhập vào điểm kết thúc môn C của một lớp học gồm 35
sinh viên.
b. Cho biết lớp có sinh viên đạt điểm 10.0 hay không.
c. Cho biết tỉ lệ % đậu và tỉ lệ % rớt của lớp.
d. Cho biết điểm rớt cao nhất của lớp là bao nhiêu.
6. Viết chương trình nhập vào một mảng gồm 10 số nguyên, tìm
và in ra các số nguyên tố có trong mảng; nếu không có in ra thông báo.
7. Viết chương trình nhập vào một mảng gồm 10 số nguyên, yêu
cầu: không cho phép nhập số âm. Sau đó tiến hành sắp xếp mảng theo
thứ tự số lẻ tăng dần và số chẵn giảm dần; in ra mảng đã sắp xếp.
8. Viết chương trình nhập vào một ma trận 3x4 các số nguyên, sắp
xếp từng hàng của ma trận theo thứ tự tăng dần và in ra.
9. Viết chương trình nhập vào một ma trận 4x4 các số nguyên,
không cho phép nhập số âm. Tìm và in ra phần tử lớn nhất trên đường
chéo chính của ma trận.
119
10. Viết một chương trình thực hiện các yêu cầu sau:
a. Nhập vào điểm giữa kỳ và cuối kỳ môn C của một lớp học gồm
25 sinh viên, lưu vào 1 mảng 2 chiều.
b. Cho biết tỉ lệ % đậu và tỉ lệ % rớt của lớp.
11. Viết chương trình nhập vào 1 danh sách họ và tên gồm 5 người.
a. In ra danh sách họ tên những người đã nhập.
b. In ra danh sách tên những người đã nhập.
120
CHƯƠNG 5
CON TRỎ
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Khai báo được biến con trỏ.
- Ứng dụng biến con trỏ trong các thao tác xử lý: trỏ tới vùng nhớ
khác, cấp phát bộ nhớ động cho con trỏ.
5.1. GIỚI THIỆU
Con trỏ (pointer) còn được một số tài liệu gọi là biến con trỏ.
Thực tế, con trỏ hay biến con trỏ đều như nhau, bởi con trỏ là biến
nhưng là một biến được sử dụng đặc biệt so với các biến thông thường
khác. Con trỏ là một biến mà nội dung của nó chứa địa chỉ của một biến
khác. Định nghĩa con trỏ nghe có vẻ hơi trừu tượng và người đọc khó có
thể hình dung được chức năng của con trỏ, so với các biến khác.
Chương này sẽ giới thiệu cách thức sử dụng biến con trỏ, cũng
như tác dụng của biến con trỏ trong việc truy xuất vùng nhớ. Con trỏ
được sử dụng khá nhiều trong ngôn ngữ C, C++, và đặc biệt trong lập
trình cho vi xử lý, vi điều khiển sử dụng ngôn ngữ C/C++. Sử dụng tốt
biến con trỏ cho phép người lập trình tối ưu hóa được vùng nhớ, nâng
cao hiệu suất của chương trình. Có thể hiểu, con trỏ là một đơn vị gián
tiếp, cho phép truy xuất đến bộ nhớ thông qua địa chỉ. Trong lập trình
cấp cao, các khái niệm về bộ nhớ, thanh ghi thông thường bị quên
lãngvì người viết chương trình sử dụng ngôn ngữ cấp cao và các biến
được đặt tên theo kiểu gợi nhớ. Để hiểu và sử dụng tốt con trỏ, chương
này sẽ giới thiệu sơ lược về biến, vùng nhớ, địa chỉ, nằm giúp người đọc
121
hiểu cách con trỏ được sử dụng trong việc truy xuất bộ nhớ. Ngoài việc
được sử dụng để truy xuất các biến riêng lẻ trong bộ nhớ, con trỏ chủ
yếu còn được sử dụng để truy xuất các vùng nhớ liên tục. Phần cuối
chương sẽ trình bày về ứng dụng của con trỏ trong việc cấp phát bộ
nhớ động.
5.2. KHAI BÁO VÀ SỬ DỤNG CON TRỎ
Giống như các biến khác, con trỏ phải được khai báo trước khi sử
dụng. Khai báo biến con trỏ cũng giống như khai báo các biến thông
thường khác được bắt đầu với kiểu dữ liệu. Tuy nhiên, kiểu dữ liệu khi
khai báo con trỏ không xác định độ dài ô nhớ mà xác định kiểu dữ liệu
con trỏ được dùng để trỏ tới. Kiểu dữ liệu này giúp cho con trỏ có thể
tăng địa chỉ thích hợp khi trỏ đến các phần tử liên tiếp nhau. Cú pháp
khai báo con trỏ như sau:
Tên kiểu dữ liệu
*tên con trỏ ;
Tên kiểu dữ liệu*
tên con trỏ ;
Ví dụ:
1
2
int a, b ;
int *p ;
Trong đoạn chương trình trên, ở dòng lệnh số 1 là khai báo 2
biến a và b, 2 biến a và b cùng có kiểu int và cùng có kích thước là
4 byte. Như vậy, chương trình dành ra 2 vùng nhớ, mỗi vùng nhớ
4 byte cho biến a và biến b. Dòng 2 của chương trình là câu lệnh
khai báo biến con trỏ với kiểu int. Trong trường hợp này, biến con
trỏ được sử dụng để truy xuất đến các biến khác có cùng kiểu int.
Chương trình sử dụng vùng nhớ để lưu biến con trỏ lúc này không
phải là 4 byte như biến a hoặc b mà là 8 byte để có thể chứa đủ địa
chỉ, tham chiếu được hết bộ nhớ. Như vậy, việc khai báo int *phoặc
char *p thì kích thước bộ nhớ sử dụng cho biến con trỏ p là như
nhau. Biến con trỏ p được khai báo và sử dụng để tham chiếu đến
một biến khác trong vùng nhớ. Nội dung dữ liệu của con trỏ p sẽ là
122
địa chỉ của biến mà nó tham chiếu tới. Giả sử đoạn chương trình khai
báo 3 biến với giá trị khởi tạo như sau:
1
2
int a = 3, b = 5 ;
int *p ;
Tên biến
a
b
p
Giá trị
3
5
--
Địa chỉ
4225568
4225572
4225588
Hình 5.1. Thuộc tính của các biến
Đoạn chương trình này có 3 biến, mỗi biến được khai báo với một
tên, các biến được truy xuất nội dung thông qua tên biến. Đồng thời,
mỗi biến có một địa chỉ trong bộ nhớ. Trong trường hợp này, 2 biến a
và b nằm ở hai vùng nhớ có địa chỉ bắt đầu là 4225568 và 4225572. Các
biến được khai báo có thể nằm ở các vùng nhớ nối tiếp nhau hoặc không
và không có cơ chế đảm bảo các biến nằm liên tiếp nhau trong bộ nhớ.
Mỗi biến được gán giá trị. Các giá trị này được xem như nội dung biến.
Trường hợp con trỏ p cũng tương tự, tên con trỏ là p, vùng nhớ của con
trỏ p được đặt ở vị trí bắt đầu là 4225588. Nội dung biến có thể được
cập nhật thông qua tên biến, ví dụ, lệnh a = 8; sẽ làm giá trị biến a được
cập nhật giá trị mới là 8. Nội dung biến con trỏ chưa được khởi tạo. Xét
chương trình ví dụ tiếp theo dưới đây:
1
a = 9 ;
2
b = 12 ;
3
p = &a ;
4
*p = 7 ;
5
p = &b ;
6
a = *p ;
123
Trong đoạn chương trình trên, các lệnh ở dòng 1 và dòng 2 cập
nhật giá trị mới cho a, b tương ứng là 9, 12. Con trỏ p được gán nội dung
là địa chỉ của biến a. Trong ngôn ngữ C, toán tử & đặt trước tên biến cho
phép chương trình trả về địa chỉ vùng nhớ đã cấp cho biến. Người lập
trình thao tác với các biến thông qua tên, vùng nhớ cấp cho biến có thể
thay đổi tùy từng lúc thực thi chương trình. Ở dòng 3 là lệnh gán địa chỉ
của biến a cho con trỏ p. Lúc này, con trỏ p sẽ tham chiếu đến vùng nhớ
của biến a. Ở dòng 4, lệnh *p = 7 sẽ gán giá trị 7 vào vùng nhớ mà con
trỏ p đang chứa địa chỉ, cụ thể ở đây là địa chỉ của biến a. Như vậy, sau
câu lệnh trên, biến a sẽ được cập nhật giá trị mới là 7. Như vậy, biến con
trỏ được sử dụng để tham chiếu đến vùng nhớ của biến a. Lệnh ở dòng
thứ 5 lại lấy địa chỉ biến b và gán cho con trỏ, nội dung con trỏ bây giờ
là địa chỉ của biến b, hay nói khác, lúc này con trỏ dùng để tham chiếu
đến biến b, thay vì biến a. Cuối cùng, giá trị biến a được cập nhật giá trị
mới là ô nhớ mà con trỏ p đang chứa địa chỉ, tức biến b. Sau câu lệnh,
biến a có giá trị là nội dung của biến b, tức a = 12.
5.3. CON TRỎ VÀ MẢNG
Con trỏ và mảng có mối liên hệ mật thiết với nhau. Trong quá
trình lập trình, có thể sử dụng qua lại giữa con trỏ và mảng. Khi khai
báo mảng 1 chiều, tên mảng cũng chính là địa chỉ bắt đầu của mảng,
chính vì thế, tên mảng một chiều cũng được sử dụng giống như con trỏ.
Xét câu lệnh sau:
1
int a[5]= {1,5,3,2,4} ;
Đây là câu lệnh khai báo mảng kiểu int với 5 phần tử và a là tên
mảng. Lệnh a[i] sẽ tham chiếu đến phần tử thứ i trong mảng, nếu khai
báo con trỏ và gán giá trị cho con trỏ như sau:
1
2
3
124
int *p,x;
p = &a[0];
x = *p ;
thì câu lệnh trên đã thiết lập con trỏ tham chiếu đến phần tử đầu
tiên của mảng a bằng cách gán địa chỉ phần tử đầu tiên của mảng a cho
con trỏ p.
p
a:
0
1
2
3
4
1
5
3
2
4
*(p)
*(p+1) *(p+2) *(p+3) *(p+4)
Hình 5.2. Mối liên hệ giữa mảng và con trỏ
Lệnh x = *p sẽ sao chép nội dung phần tử a[0] vào biến x.
Tương tự, *(p+i) sẽ tham chiếu đến phần tử thứ i trong mảng a, như
trình bày trong hình 5.2. Điều này luôn đúng bất kể kiểu dữ liệu
của mảng a. Khi p là con trỏ, nếu gặp lệnh p+1 thì chương trình sẽ
tự động cập nhật p sao cho có thể tham chiếu đến phần tử tiếp theo
trong mảng a.
Ngay khi tên mảng xuất hiện trong chương trình, nó được chuyển
sang kiểu con trỏ một cách tự động. Như vậy, 2 cách gán giá trị cho con
trỏ p sau đây là tương đương nhau:
1
2
pa= &a[0];
pa = a;
Như vậy, trong trường hợp này có thể xem a là con trỏ mà nội
dung của nó đã được thiết lập đến vùng nhớ cấp cho mảng a. Các phần
tử của mảng có thể được truy xuất thông qua tên mảng hoặc tên con trỏ
như sau:
125
Thứ tự phần tử
0
1
2
3
4
Giá trị mảng
1
5
3
2
4
Truy xuất phần
tử mảng
a[0]
a[1]
a[2]
a[3]
a[4]
Truy xuất phần tử
mảng dùng con trỏ
*(a)
*(a+1) *(a+2) *(a+3) *(a+4)
Hình 5.3. Mối liên hệ giữa mảng và con trỏ
Tuy nhiên, cần phân biệt sự khác nhau khi sử dụng mảng và con
trỏ trong một số trường hợp cụ thể như sau:
1
int a[] = {1,2,3,4,5,6} ;
2
int *p;
3
p = a;
4
printf(“ p(0)= %d, p(1) = %d
5
p++ ;
6
printf(“ p(0)= %d, p(1) = %d
\n “, *p,*(p+1)) ;
\n “, *p,*(p+1)) ;
Kết quả khi chạy chương trình
1
2
p(0)= 1, p(1) = 2
p(0)= 2, p(1) = 3
Trong ví dụ trên, chương trình đã khai báo mảng a và con trỏ p.
Sau đó, con trỏ p được gán nội dung là địa chỉ bắt đầu của mảng a. Như
đã giới thiệu ở trên, lệnh p = a sẽ tự động chuyển tên mảng a sang địa
chỉ bắt đầu của mảng và gán địa chỉ này cho con trỏ p. Câu lệnh p = a
tương đương với p = &a[0]; lấy địa chỉ của phần tử đầu tiên của mảng
gán cho con trỏ p. Như vậy, việc trỏ đến phần tử đầu tiên và phần tử tiếp
theo của con trỏ sẽ truy xuất tới phần tử a[0] và phần tử a[1] của mảng.
Lệnh p++ sẽ tự động tăng nội dung của biến con trỏ để truy xuất phần
tử tiếp theo. Khi sử dụng lệnh tăng hoặc cộng con trỏ, chương trình sẽ
126
tự động tăng nội dung biến con trỏ lên một giá trị tương ứng với kiểu
dữ liệu mà nó được định nghĩa để có thể tham chiếu đến phần tử tiếp
theo của vùng nhớ đang được truy xuất bởi con trỏ. Ví dụ, đối với
con trỏ kiểu int, lệnh p++ sẽ tăng địa chỉ lên 4 byte để tham chiếu
đến phần tử tiếp theo, thay vì chỉ tăng lên 1 đơn vị như tác dụng trên
biến thông thường. Sau khi tăng, con trỏ p đang tham chiếu đến phần tử
thứ 1 của mảng a.
Truy xuất từng phần tử mảng theo cách dùng tên mảng như là tên
con trỏ:
1
2
3
4
5
6
int a[] = {1,2,3,4,5,6}
int *p;
p = a;
printf(“ p(0)= %d, p(1)
a++ ; // lenh khong hop
printf(“ p(0)= %d, p(1)
;
= %d
le
= %d
\n “, *a,*(a+1)) ;
\n “, *a,*(a+1)) ;
Trong đoạn chương trình này, việc sử dụng cách tham chiếu như
con trỏ đế lấy nội dung của mảng a tại câu lệnh ở dòng thứ 4 và thứ 6
là hoàn toàn hợp lệ. Tuy nhiên lệnh a++; ở dòng lệnh 5 lại không hợp
lệ vì trường hợp này a được khai báo là mảng, không phải con trỏ. Như
vậy, người lập trình cần chú ý cách sử dụng giống và khác nhau giữa
mảng và con trỏ.
5.4. CẤP PHÁT BỘ NHỚ ĐỘNG
Thông thường, người lập trình ít quan tâm đến việc chương trình
tiêu tốn bộ nhớ bao nhiêu. Khi máy tính có bộ nhớ đủ lớn, việc sử dụng
bộ nhớ nhiều hơn hay ít hơn vài Mbyte có lẽ không là vấn đề gì lớn. Tuy
nhiên, thử hình dung chúng ta đang lập trình và thực thi trên các thiết
bị có bộ nhớ giới hạn thì việc sử dụng hiệu quả bộ nhớ lại trở nên cần
thiết. Ví dụ, khi viết chương trình cho phép người dùng nhập điểm cho
một lớp học, mà chúng ta không biết trước là sẽ có bao nhiêu sinh viên.
127
Mặc nhiên, người lập trình sẽ khởi tạo một mảng số thực với số phần
tử lớn nhất có thể để sử dụng cho việc lưu điểm. Trong trường hợp số
lượng điểm nhập vào thực sự nhỏ hơn nhiều lần so với số phần tử mảng
đã cấp phát thì sẽ dẫn đến lãng phí bộ nhớ.
Việc cấp phát bộ nhớ động sẽ giúp cho việc sử dụng bộ nhớ hiệu
quả hơn. Cấp phát bộ nhớ động (Dymanic memory allocation) là quá
trình cấp phát bộ nhớ trong lúc chương trình đang thực thi, thay vì cấp
phát lúc biên dịch chương trình. Các hàm, thư viện quản lý bộ nhớ cho
phép cấp phát cũng như giải phóng vùng nhớ trong lúc đang thực thi
chương trình. Các hàm quản lý bộ nhớ động được định nghĩa trong thư
viện stdlib.h.
Tiến trình cấp phát bộ nhớ cho chương trình được mô tả trong
hình dưới.
Biến cục bộ
Stack
Vùng nhớ trống
Heap
Biến toàn cục
Lệnh chương
trình
Biến tĩnh
Hình 5.4. Phân vùng bộ nhớ cho chương trình
Các biến toàn cục, các biến tĩnh và mã lệnh chương trình sử dụng
bộ nhớ cố định, trong khi các biến cục bộ được lưu trong vùng nhớ ngăn
xếp (Stack). Không gian bộ nhớ giữa vùng nhớ cố định và vùng nhớ
128
ngăn xếp là vùng nhớ Heap. Vùng nhớ Heap được sử dụng để cấp phát
động trong quá trình chương trình thực thi.
5.4.1. Hàm malloc
Hàm malloc được sử dụng để cấp phát bộ nhớ với kích thước theo
yêu cầu, hàm sẽ trả về con trỏ tham chiếu đến địa chỉ bắt đầu của vùng
nhớ được cấp phát. Sử dụng hàm malloc theo cú pháp sau:
void * malloc(size_t size)
Trong đó, size là kich thước tính theo đơn vị byte của vùng nhớ
cần cấp phát. Nếu quá trình cấp phát thành công, hàm sẽ trả về con trỏ
tham chiếu đến địa chỉ đầu tiên của vùng nhớ được cấp phát. Trường
hợp ngược lại, hàm sẽ trả về con trỏ rỗng (NULL).
Ví dụ: cấp phát bộ nhớ cho mảng 100 phần tử kiểu số nguyên
như sau:
int *p;
p = malloc(100*sizeof(int));
Chương trình sẽ cấp phát bộ nhớ có kích thước tổng cộng là 400
byte cho 100 phần tử kiểu số int. Hàm sizeof() sẽ trả về kích thước của
một kiểu dữ liệu. Nếu quá trình cấp phát thành công, p là con trỏ tham
chiếu đến vùng nhớ đã được cấp phát. Kết quả trả về của hàm malloc
tự động được chuyển sang kiểu (int *) và gán cho biến con trỏ p. Thông
thường hàm malloc được ghi ở hình thức rõ ràng về kiểu dữ liệu trả về
như sau:
int *p;
p = (int*)malloc(100*sizeof(int));
Ví dụ: viết chương trình nhập dữ liệu cho mảng gồm n phần
tử số nguyên với giá trị n được nhập trước, sau đó in ra phần tử lớn
nhất.
129
1
#include<stdio.h>
2
#include <stdlib.h>
3
void main(void)
4
{
5
int n, i,max;
6
int *p ;
7
do {
8
printf(“Nhap n: “);
9
scanf(“%d”,&n) ;
10 }while (n<=0);
11 p = (int *)malloc (n*sizeof(int));
12 if (p!=NULL)
13 {
14
for (i=0;i<n;i++)
15
{
16 printf(“\nnhap phan tu thu %d: “,i+1);
17 scanf(“%d”,(p+i));
18
}
19
max = *p ;
20
for (i=1;i<n;i++)
21
{
22 if(max<*(p+i))
23 max = *(p+i);
24
}
25
printf(“max = %d”,max) ;
26
}
27 free(p);
28 }
5.4.2. Hàm free()
Hàm free() được dùng để giải phóng bộ nhớ đã cấp phát cho biến
con trỏ trước đó bởi hàm malloc() và trả bộ nhớ về cho vùng nhớ Heap.
Cú pháp hàm free() như sau:
130
void
free(void *)
Ví dụ giải phóng vùng nhớ đã cấp phát trước đó cho con trỏ p:
free(p);
5.4.3. Hàm calloc và realloc
Ngoài hàm malloc, 2 hàm khác trong thư viện sdtlib.h cũng cho
phép cấp phát bộ nhớ động là hàm calloc() và hàm realloc().
Hàm calloc() có chức năng giống như hàm malloc. Trong khi hàm
malloc trả về vùng nhớ được cấp phát với giá trị dữ liệu ban đầu của
các ô nhớ là ngẫu nhiên thì hàm calloc trả về vùng nhớ được cấp phát
và khởi tạo giá trị ban đầu cho các phần tử là giá trị 0. Cú pháp hàm
calloc() có khác so với hàm malloc về tham số của hàm:
void * calloc(size_t n, size_t size)
Tham số đầu tiên n dùng để xác định số lượng phần tử cần cấp
phát, tham số thứ 2, size, xác định kích thước của mỗi phần tử. Để cấp
phát vùng nhớ cho một mảng 100 phần tử số nguyên, có thể sử dụng
hàm calloc như sau:
int *p;
p=(int*)calloc(100, sizeof(int));
Tương tự như hàm malloc(), hàm calloc() trả về giá trị rỗng
(NULL) nếu quá trình cấp phát không thành công.
Hàm cuối cùng quản lý việc cấp phát động bộ nhớ là hàm realloc().
Hàm realloc() được sử dụng để thay đổi kích thước của vùng nhớ đã
được cấp phát động trước đó bởi hàm malloc() hoặc hàm calloc(). Cú
pháp của hàm realloc() như sau:
void * realloc(void *p, size_t size)
Trong đó p là con trỏ tham chiếu đến vùng nhớ hiện hành đã được
cấp phát. Size là kích thước mới được yêu cầu cấp phát. Kết quả trả về
131
con trỏ tham chiếu đến vùng nhớ đã được thay đổi kích thước hoặc con
trỏ rỗng (NULL) nếu quá trình thay đổi kích thước không thành công.
Trong trường hợp tham số vào size cho hàm realloc() là 0 thì vùng nhớ
cấp cho con trỏ p sẽ được giải phóng và hàm realloc() sẽ trả về NULL.
Lúc này hàm realloc() được dùng tương tự như hàm free().
Ví dụ sử dụng hàm realloc() để tăng kích thước vùng nhớ đã cấp
cho con trỏ p từ 100 phần tử lên 200 phần tử như sau:
int *p;
p =(int*)malloc(100*sizeof(int)) ;
p = (int*)realloc(p, 200*sizeof(int));
Ví dụ: Viết chương trình nhập n số nguyên, in ra các số đã nhập
theo thứ tự tăng dần. Sau đó nhập thêm m số nguyên nữa, tìm và in ra
giá trị bé nhất trong số (n+m) số nguyên vừa nhập.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
132
#include<stdio.h>
#include <stdlib.h>
void main(void)
{
int n,m, i,j,min;
int *p ;
do
{
printf(“Nhap n: “);
scanf(“%d”,&n) ;
}while (n<=0);
p = (int *)calloc (n,sizeof(int));
if (p!=NULL)
{
for (i=0;i<n;i++)
{
printf(“\nnhap phan tu thu %d: “,i+1);
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
scanf(“%d”,(p+i));
}
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(*(p+i)>*(p+j))
{
*(p+i)=*(p+i)+*(p+j);
*(p+j)=*(p+i) - *(p+j);
*(p+i)=*(p+i) - *(p+j);
}
for (i=0;i<n;i++)
{
printf(“ %d , “,*(p+i));
}
do
{
printf(“\nNhap m: “);
scanf(“%d”,&m) ;
}while (m<=0);
p = (int*)realloc(p,(n+m)*sizeof(int));
if (p!=NULL)
{
for (i=0;i<m;i++)
{
printf(“\nnhap phan tu thu %d:
“,n+i+1);
scanf(“%d”,(p+n+i));
}
min = *p ;
for (i=1;i<n+m;i++)
{
if(min>*(p+i))
min = *(p+i);
}
printf(“min = %d”,min) ;
}
}
free(p);
}
133
5.5. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
a.
b.
c.
int a = 3;
int *b;
b = a;
*b = 5;
int a = 5;
float *p;
p = &a;
int n;
int *p;
p = (int *)malloc(n*sizeof(int))
printf(“nhap so luong phan tu: “);
scanf(“%d”,&n);
Các chương trình trong các bài tập dưới đều yêu cầu sử dụng con
trỏ và cấp phát bộ nhớ động.
2. Viết chương trình nhập vào một mảng gồm 10 số nguyên. Sau
đó copy và lưu các số lẻ (nếu có) trong mảng đã nhập ra một mảng thứ
hai. In ra mảng thứ hai vừa copy được.
3. Viết chương trình nhập vào n số nguyên, sau đó nhập vào một
số X bất kỳ, tìm xem số X này có tồn tại trong dãy số vừa nhập không.
4. Viết chương trình nhập vào một mảng 1 chiều gồm n số nguyên
và thực hiện các yêu cầu:
 Không được phép nhập số âm.
 Tìm và in ra số lẻ lớn nhất trong mảng, nếu mảng không có số
lẻ thì in ra thông báo.
5. Viết chương trình nhập mảng gồm n phần tử số nguyên, in các
phần tử vừa nhập theo thứ tự giảm dần. Nhập tiếp m phần tử số nguyên
vào mảng trên, in (n+m) phần tử vừa nhập theo thứ tự giảm dần.
134
6. Viết chương trình nhập vào 2 mảng 1 chiều kiểu số nguyên có
kích thước n phần tử. So sánh 2 mảng có bằng nhau không.
7. Viết chương trình nhập vào 2 mảng một chiều kiểu số nguyên,
mỗi mảng có n phần tử, cộng 2 mảng và xuất mảng tổng ra theo thứ tự
các phần tử tăng dần.
135
CHƯƠNG 6
HÀM
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Định nghĩa được hàm mới trong chương trình.
- Sử dụng được các hàm đã định nghĩa trước đó.
6.1. GIỚI THIỆU
Hàm được sử dụng để chia các công việc xử lý tính toán phức tạp
thành nhiều chương trình nhỏ hơn. Hàm cho phép người lập trình xây
dựng các ứng dụng từ các chương trình được viết sẵn, thay vì phải bắt
đầu viết lại từ đầu. Trong một số trường hợp, người lập trình xem các
hàm như là các hộp đen mà không cần thiết phải nắm rõ các chương
trình chi tiết trong hàm.
Có nhiều mục đích khác nhau khi sử dụng hàm. Trong quá trình
lập trình, thay vì viết một chương trình dài, người lập trình có thể chia
nhỏ các chương trình ra thành nhiều chương trình con, mỗi chương
trình con là một hàm. Mặt khác, khi đoạn chương trình lặp lại nhiều lần
thì việc sử dụng hàm thực thi đoạn chương trình đó sẽ tiết kiệm được
nhiều hơn.
Ngoài ra, ngôn ngữ C còn cung cấp các hàm xây dựng sẵn,
cho phép người lập trình sử dụng để tạo ra các ứng dụng nhanh hơn.
Khi sử dụng hàm, người lập trình cần quan tâm đến các thông số sẽ
được đưa vào khi gọi hàm và kết quả trả về của hàm sau khi hàm
kết thúc.
136
Hình 6.1 bên dưới mô tả cấu trúc của một hàm.
Tham số
truyền vào
Hàm
Thực hiện tính toán, xử lý...
Kết quả trả về
Hình 6.1. Hàm với tham số truyền vào, kết quả trả về của hàm.
Trong ngôn ngữ C cũng như trong một số ngôn ngữ lập trình
khác, dựa theo cách thức mà nó tồn tại, hàm có thể được chia làm
2 nhóm: hàm được xây dựng sẵn (built-in function) hoặc các hàm
do người lập trình định nghĩa (user-defined function). Trong các
chương trước, chúng ta sử dụng các hàm được xây dựng sẵn trong
các thư viện. Cụ thể như hàm printf, hàm scanf hoặc hàm malloc…
Chúng ta cũng không quan đến các đoạn lệnh chi tiết trong các hàm
trên. Tuy nhiên, khi sử dụng cần tuân thủ về tham số cũng như kết
quả trả về mà các hàm quy ước.
Chương này giúp cho người lập trình tự xây dựng và sử dụng các
hàm, cụ thể là các hàm do người dùng định nghĩa, đồng thời, nắm được
các phương pháp truyền tham số cho hàm, giúp cho quá trình lập trình
hiệu quả hơn, tiết kiệm được nhiều thời gian hơn.
6.2. ĐỊNH NGHĨA HÀM
Hàm được định nghĩa bao gồm tên hàm, danh sách và kiểu dữ
liệu của các tham số, kiểu dữ liệu trả về của hàm và quan trọng hơn hết
là các phát biểu thực thi chức năng của hàm. Bên trong hàm được xây
dựng bởi các phát biểu là các đoạn lệnh và bao gồm cả việc khai báo và
sử dụng các biến, hằng số.
Một cách tổng quát, hàm được định nghĩa như sau:
137
Kiểu_dữ_liệu
Tên_hàm (danh sách các tham số)
{
Khai báo biến cục bộ;
Câu lệnh;
...
return giá trị;
}
Trong đó:
 Tên hàm phải được đặt theo quy tắc đặt tên hàm, biến, đã được
giới thiệu trong chương trước.
 Danh sách tham số bao gồm kiểu dữ liệu và tên tham số. Các
hàm không giới hạn số lượng tham số. Tuy nhiên, khi xây dựng hàm với
số lượng tham số nhiều có thể sử dụng phương pháp truyền tham chiếu
hoặc truyền mảng.
 Các biến được khai báo bên trong hàm là các biến cục bộ. Các
biến cục bộ được tạo ra khi hàm được gọi và sẽ được giải phóng khi
hàm kết thúc.
 Từ khóa return sẽ trả về giá trị cho hàm với kiểu giá trị là
Kiểu_dữ_liệu đã được định nghĩa trước tên hàm.
Ví dụ hàm tìm max của 2 số nguyên được định nghĩa như sau:
1
2
3
4
5
6
7
int
{
timmax(int a, int b)
int max;
if (a>b) max = a;
else
max = b;
return
max;
}
Hàm tìm max được đặt tên là timmax, nhận vào 2 tham số kiểu int.
Biến max là biến cục bộ, biến max được khởi tạo ngay khi hàm được
138
gọi và sẽ được giải phóng khi hàm kết thúc. Sau khi so sánh, hàm sẽ trả
về giá trị max bằng câu lệnh return. Chương trình chính có thể gọi hàm
một hoặc nhiều lần. Trong quá trình gọi hàm, phải đảm bảo truyền 2
tham số cho hàm và nhận về giá trị từ giá trị trả về của hàm.
Ví dụ chương trình chính sử dụng hàm đã viết để tìm giá trị lớn
nhất từ 4 số nguyên a, b, c, d. Để tìm số lớn nhất trong 4 số, ta có thể xét
từng cặp và đi so sánh 2 kết quả với nhau. Thực tế, đây không phải là
phương pháp tối ưu khi tìm giá trị lớn nhất của 4 số nguyên. Tuy nhiên,
chương trình sử dụng phương pháp so sánh từng cặp này để minh họa
cho việc sử dụng hàm.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
timmax(int a, int b)
{
int max;
if (a>b) max = a;
else
return
max = b;
max;
}
void main (void)
{
int a = 5, b = 7, c=6, d= 9, max1, max2, max;
max1 = timmax (a,b);
max2 = timmax(c,d);
max = timmax(max1, max2);
printf(“ max = :%d”,max);
}
Trong ví dụ trên, hàm timmax được gọi 3 lần với 3 tham số khác
nhau. Trong một chương trình có nhiều hàm, chương trình sẽ tìm hàm
chính (hàm main) thực thi từ vị trí bắt đầu hàm cho đến khi kết thúc hàm.
Trong quá trình thực thi chương trình chính, hàm nào được gọi thì sẽ
được thực hiện, một hàm được định nghĩa trong chương trình nhưng nếu
không được gọi trong chương trình chính thì nó sẽ không được thực thi.
139
Để hiểu rõ cách thức chương trình hoạt động, có thể xem hình vẽ
mô tả như sau:
int
timmax(int a, int b)
{
int max;
if (a>b) max = a;
else
max = b;
return
max;
}
int
timmax(int a, int b)
{
int max;
if (a>b) max = a;
else
max = b;
return
max;
}
int
timmax(int a, int b)
{
int max;
if (a>b) max = a;
else
max = b;
return
max;
}
void main (void){
int a=5,b=7,c=6,d=9,max1,max2,max;
max1 = timmax (a,b);
max2 = timmax(c,d);
max = timmax(max1, max2);
printf( " max = :%d",max);
}
Hình 6.2. Mô tả hoạt động của chương trình khi gọi hàm
Hình 6.2 mô tả thứ tự hoạt động của chương trình. Trong đó,
khi gọi làm lần thứ nhất, giá trị 2 biến a, b, được sao chép vào giá
trị 2 biến nội bộ bên trong hàm, sau khi so sánh, hàm trả về kết quả
giá trị lớn nhất lưu lại trong biến max1. Khi gọi làm lần 2, hàm lại
thực thi với 2 giá trị mới là c và d, kết quả trả về cho biến max2.
Tương tự, khi gọi hàm lần thứ 3, kết quả trả về cho biến max. Như
vậy, thay vì thực hiện đoạn chương trình nhiều lần, ta đi định nghĩa
hàm 1 lần và cho phép hàm được gọi nhiều lần, như minh chứng ở
hình 6.2. Cần lưu ý là chương trình trên chỉ là một ví dụ minh họa
cách thức hàm hoạt động, không phải là một giải thuật tối ưu để tìm
số lớn nhất trong 4 số.
6.3. PHÂN LOẠI HÀM THEO THAM SỐ VÀ GIÁ TRỊ TRẢ VỀ
Trong mục 6.2, hàm được định nghĩa đầy đủ với kiểu giá trị trả
140
về và tham số. Trong một số trường hợp cụ thể, các thành phần trong
định nghĩa hàm có thể vắng mặt. Dựa vào tham số và giá trị trả về có
thể phân loại hàm như sau:
 Hàm có tham số và có giá trị trả về.
Hàm có tham số và có giá trị trả về được định nghĩa như mục 6.2.
Trong đó, hàm có một hoặc nhiều tham số và trả về kiểu giá trị cụ thể.
Khai báo và sử dụng loại hàm này sẽ tương tự như trong trường hợp
hàm tìm giá trị lớn nhất trong mục 6.2. Ví dụ định nghĩa và sử dụng hàm
tính giai thừa của một số nguyên n như sau:
1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include <stdlib.h>
int
giaithua(int n)
{
int i,S=1 ;
for (i=1;i<=n;i++)
S *=i ;
return
S;
}
void main (void)
{
12
13
14
15
16
17 }
int n,Sn;
printf(“Nhap n : “);
scanf(“%d”,&n);
Sn = giaithua(n);
printf(“ giai thua cua %d la %d”,n,Sn);
Hàm giaithua nhận vào một giá trị kiểu số nguyên và trả về một
giá trị kiểu số nguyên. Lệnh return cho phép trả về giá trị với kiểu giá trị
được khai báo phía trước tên hàm. Trong trường hợp thiếu lệnh return,
trình biên dịch vẫn không báo lỗi hoặc cảnh báo. Tuy nhiên, hàm sẽ
không trả về giá trị để gán cho biến khi gọi hàm trong chương trình
141
chính. Mặc khác, lệnh return không nhất thiết phải đặt ở cuối chương
trình của hàm. Trong một số trường hợp có thể đặt lệnh return ở giữa
hoặc bất kỳ nơi đâu trong thân hàm. Khi gặp lệnh return, hàm xem như
kết thúc và trả về giá trị cho chương trình gọi nó. Ví dụ:
1
#include<stdio.h>
2
#include <stdlib.h>
3
int
4
/*
5
Hàm trả về 1 nếu là số nguyên tố, ngược lại trả về 0
*/
{
int i ;
for (i=2;i<n;i++)
{
if (n%i==0) return 0;
}
return 1 ;
}
void main (void)
{
int n,Snt;
printf(“Nhap n : “);
scanf(“%d”,&n);
Snt = KiemtraSNT(n) ;
if (Snt)
printf(“ %d la So nguyen to” ,n);
else
printf(“ %d khong phai la So nguyen to”
,n);
}
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
KiemtraSNT(int n)
Trong ví dụ trên, hàm thực hiện chức năng kiểm tra xem một số
nguyên có phải là số nguyên tố hay không, nếu đúng thì trả về 1, ngược
142
lại thì trả về 0. Lệnh return được sử dụng 2 lần trong hàm. Không có
giới hạn cho số lần sử dụng lệnh return. Trong trường hợp này, người
lập trình có thể dùng một biến trung gian và chỉ sử dụng lệnh return 1
lần ở cuối hàm. Tuy nhiên, đặt 2 lệnh return trong hàm này vẫn đảm
bảo đúng logic. Trong trường hợp phát hiện không phải là số nguyên tố,
lệnh return sẽ kết thúc hàm tại vị trí đó và trả về kết quả là 0 mà không
cần phải chạy hết vòng lặp for. Trường hợp nếu như là số nguyên tố,
lệnh return trong vòng lặp sẽ không được thực hiện, chương trình thực
hiện lệnh return ở cuối hàm.
 Hàm có tham số nhưng không có giá trị trả về.
Trong nhiều trường hợp các hàm chỉ nhận tham số mà không trả
về giá trị. Trong một số ngôn ngữ lập trình như Pascal, các hàm không
trả về giá trị được gọi là các thủ tục. Tuy nhiên, ngôn ngữ C không phân
biệt hàm và thủ tục. Khai báo và sử dụng hàm không có giá trị trả về
như sau:
void Tên_hàm (danh sách các tham số)
{
Khai báo biến cục bộ;
Câu lệnh;
...
}
Kiểu dữ liệu trả về trước Tên_hàm được thay thế bằng từ khóa
void để thể hiện thông tin: hàm không có giá trị trả về và dĩ nhiên, trong
hàm cũng không cần lệnh return.
Ví dụ viết hàm kiểm tra một số có phải là số Armstrong hay không:
hàm không trả về kết quả là 0 hay 1 như hàm tìm số nguyên tố mà hàm
kiểm tra số Armstrong sẽ tự in ra kết quả sau khi kiểm tra trong hàm. Số
Armstrong được định nghĩa là số có giá trị bằng tổng lập phương của
các chữ số trong số đó. Ví dụ 153, 371
143
1
#include<stdio.h>
2
#include <stdlib.h>
3
void Armstrong (int n)
4
{
5
int s1, s, n1;
6
s = 0;
7
n1=n;
8
while(n1!=0)
9
{
10
s1 = n1 % 10;
11
s += s1 * s1 * s1;
12
n1 = n1/10;
13
}
14
if (n == s) printf(“ %d la So armstrong” ,n);
15
else
16
printf(“ %d khong phai la So armstrong” ,n);
17 }
18 void main (void)
19 {
20
int n ;
21
printf(“Nhap n : “);
22
scanf(“%d”,&n);
23
Armstrong(n) ;
24 }
Hàm thực hiện in kết quả trong hàm nên không trả về giá trị cho
chương trình chính. Tại dòng lệnh 23, hàm được gọi bằng tên hàm cùng
với tham số của hàm.
 Hàm không có tham số nhưng có giá trị trả về.
Trong một số trường hợp hàm không nhận bất kỳ tham số nào
nhưng có trả về giá trị cho chương trình gọi hàm. Hàm không có tham
số nhưng có giá trị trả về được khai báo như sau:
144
int Tên_hàm (void)
{
Khai báo biến cục bộ;
Câu lệnh;
...
}
Ví dụ về hàm không có tham số nhưng có giá trị trả về:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int GetEven(void)
{ int n;
do{
printf(“Nhap mot so chan
scanf(“%d”,&n);
}
while(n%2!=0) ;
return n ;
}
void main()
{
int a;
a= GetEven();
printf(“a = %d “, a) ;
:”);
15 }
Hàm GetEven() không nhận tham số. Khi gọi hàm GetEven(),
biến n được khởi tạo và được nhập giá trị từ bàn phím. Nếu giá
trị nhập là số chẵn, vòng lặp kết thúc và giá trị n được trả về cho
chương trình chính.
 Hàm không có tham số và không có giá trị trả về.
Cũng giống như hàm không có giá trị trả về, trong một số trường
hợp, hàm không cần nhận tham số từ chương trình chính. Với hàm
không không tham số và không giá trị trả về thì thông số tại vị trí tham
số và kiểu dữ liệu trả về sẽ được thay thế bằng từ khóa void.
145
void Tên_hàm (void)
{
Khai báo biến cục bộ;
Câu lệnh;
...
}
Hàm không có tham số và giá trị trả về có chức năng giống như
một đoạn chương trình được đưa vào hàm thay vì viết trực tiếp trên
chương trình chính. Khi gọi các hàm này thì người dùng chỉ cần gọi tên
hàm. Ví dụ:
1
2
3
4
5
6
7
8
9
10
void
{
inchu(void)
int i;
for (i = 0; i< 5; i++)
puts(“Hello”);
}
void main (void)
{
inchu( );
}
Trong chương trình trên, hàm inchu là một một chương trình con,
hay thủ tục có chức năng in 5 từ “Hello” liên tục. Trong chương trình
chính, hàm được gọi mà không có tham số hay giá trị trả về.
Hàm main là hàm chính trong các chương trình viết bằng ngôn
ngữ C. Ta thường hay thấy hàm main được viết với thể thức void
main(void), có nghĩa là hàm main không có tham số và cũng không có
giá trị trả về. Khi xây dựng hàm main, theo thói quen chúng ta viết như
trên và ngầm hiểu hàm main không có tham số và giá trị trả về vì là hàm
chính, không được gọi từ hàm khác. Suy luận này là chưa chính xác vì
thực tế hàm main cũng có tham số và giá trị trả về. Tuy nhiên, trong một
146
số ứng dụng, người ta viết hàm main không tham số, không giá trị trả
về. Ví dụ hàm main được viết như sau:
1
#include <stdio.h>
2
int main(int argc, char *argv[])
3
{
4
…
5
return 0;
6
}
Trong ví dụ trên, hàm main nhận 2 tham số, thực tế 2 tham số này
được truyền cho hàm main khi chương trình được gọi từ dòng lệnh.
Lệnh return trong hàm main trả về kết quả cho hệ thống, cho biết hàm
main thực thi thành công (có giá trị là 0) hoặc không thành công (trả về
giá trị khác 0, thường là mã lỗi).
6.4. KHAI BÁO HÀM
Hàm phải được khai báo trước khi sử dụng. Điều này đồng nghĩa
với việc định nghĩa hàm hoặc là khai báo hàm phải tồn tại ở trước vị
trí nó được gọi. Trong các ví dụ trên, hàm được định nghĩa ở trên hàm
main và nó được gọi trong hàm main. Để đảm bảo việc hàm có thể được
gọi từ một hàm khác mà hàm này có thể được viết trên hoặc dưới hàm
được gọi thì các hàm cần phải được khai báo ở vị trí đầu chương trình.
Việc khai báo hàm chỉ đơn thuần là cung cấp cho chương trình các
thông tin về hàm như tên hàm, kiểu dữ liệu trả về, tham số. Khai báo
hàm được thực hiện theo cú pháp sau
Kiểu_dữ_liệu
Tên_hàm (danh sách tham số);
Các hàm được khai báo trong cùng một chương trình hoặc có
thể được đặt trong một tệp tiêu đề (header file). Ví dụ về khai báo
hàm như sau:
147
1
#include<stdio.h>
2
#include <stdlib.h>
3
int
4
void main (void)
5
{
timmax(int a, int b);// khai báo hàm
6
int a = 5, b = 7, c=6, d= 9, max1, max2, max;
7
max1 = timmax (a,b);
8
max2 = timmax(c,d);
9
max = timmax(max1, max2);
10
printf( “ max = :%d”,max);
11 }
12 int
timmax(int a, int b)
13 {
14
int max;
15
if (a>b) max = a;
16
else
17
return
max = b;
max;
18 }
Hàm timmax có thể được đặt ở bất kỳ vị trí nào trong chương trình
cùng với hàm main và các hàm khác. Khi nó đã được khai báo ở vị trí
đầu chương trình, bất kỳ hàm nào trong cùng một tập tin chương trình
đều có thể gọi hàm timmax.
6.5. TRUYỀN THAM SỐ CHO HÀM
6.5.1. Truyền giá trị cho tham số hàm
Truyền giá trị cho tham số hàm, hay gọi ngắn gọn là truyền tham
số kiểu tham trị, là phương pháp phổ biến khi gọi hàm. Các giá trị tại
vị trí tham số trong lúc gọi hàm được sao chép vào các biến cục bộ của
hàm, sau khi hàm kết thúc, các biến này tự mất đi.
Ví dụ về phương pháp truyền giá trị cho tham số hàm như sau:
148
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <conio.h>
void hoanvi(int a, int b) ;
void main (void)
{
int a = 5, b =6;
hoanvi(a , b);
printf (“a = %d , a= %d”, a,b);
}
void hoanvi(int a, int b)
{
int tam;
tam = a;
a = b;
b = tam;
}
Kết quả sau khi chạy chương trình sẽ là:
a = 5, b= 6
Trong chương trình trên, 2 biến a và b được gán giá trị lần lượt là
5, 6 trong hàm main. Đây là 2 biến cục bộ của hàm main. Khi gọi hàm
hoanvi(a,b); ở dòng lệnh số 7, 2 giá trị của a và b của hàm main được
sao chép vào 2 tham số a và b của hàm hoanvi. Trong hàm hoanvi, giá trị
của a và b bị hoán đổi. Tuy nhiên, cần chú ý 2 biến này là 2 biến cục bộ
của hàm hoanvi và được đặt tên ngẫu nhiên trùng với 2 biến bên trong hàm
main nhưng không liên quan với nhau. Sau khi hàm hoanvi kết thúc, 2 biến
nội bộ của hàm mất đi, không ảnh hưởng gì tới 2 biến của hàm main.
Kết quả là nội dung 2 biến của hàm main vẫn không thay đổi.
6.5.2. Truyền địa chỉ cho tham số hàm
Truyền địa chỉ cho tham số hàm còn được gọi là phương pháp
149
truyền tham chiếu. Trong trường hợp này, tham số không nhận giá trị trực
tiếp mà nhận địa chỉ của biến bên ngoài. Kết hợp với các phép toán con
trỏ, cho phép các câu lệnh bên trong hàm truy xuất đến các biến bên ngoài.
Trong ví dụ sau, để người đọc không nhầm lẫn, 2 biến bên trong hàm hoanvi
được đổi tên là x và y. Hàm hoán đổi giá trị 2 biến được viết lại như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
void hoanvi(int *x, int *y) ;
void main (void)
{
int a = 5, b =6;
hoanvi(&a , &b);
printf (“a = %d , a= %d”, a,b);
}
void hoanvi(int *x, int *y)
{
int tam;
tam = *x;
*x = *y;
*y = tam;
}
Kết quả sau khi chạy chương trình sẽ là:
a = 6, b= 5
Trong ví dụ trên, giá trị của 2 biến a và b của hàm main đã bị
thay đổi bởi các đoạn chương trình trong hàm hoanvi. Hàm hoanvi
được định nghĩa với 2 tham số kiểu con trỏ. Như chương trước đã
giới thiệu, con trỏ được sử dụng để truy xuất biến khác thông qua địa
chỉ. Trong hàm main, hàm hoanvi được gọi với 2 giá trị cho tham số
là địa chỉ của 2 biến a và b. Lúc này, con trỏ x trong hàm hoán vị sẽ
trỏ tới biến a, con trỏ y sẽ trỏ đến biến b, dẫn đến các phép toán trên
2 con trỏ này ảnh hưởng trực tiếp đến 2 vùng nhớ cho biến a và biến
b của chương trình chính.
150
6.5.3. Truyền mảng cho hàm
Mảng sẽ được truyền vào cho tham số hàm thông qua tham chiếu
đến phần tử đầu tiên của mảng. Khi xây dựng các hàm có tham số là
mảng, có thể thực hiện một trong 2 cách sau đây: sử dụng khai báo kiểu
con trỏ (*), hoặc sử dụng kiểu khai báo mảng ([ ]). Chẳng hạn như:
1
2
3
int func(int *a, int n);
//hoặc
int func(int a[], int n);
Và khi gọi hàm, người dùng sẽ truyền tham số cho hàm thông qua
địa chỉ. Địa chỉ có thể được lấy là địa chỉ phần tử đầu tiên của mảng
hoặc là dùng tên mảng để chương trình tự động chuyển sang địa chỉ.
Với phương pháp truyền tham chiếu, các chương trình bên trong hàm
sẽ ảnh hưởng trực tiếp đến vùng nhớ lưu mảng, hay nói cách khác, các
lệnh bên trong hàm có thể thay đổi giá trị của mảng được truyền vào.
Ví dụ hàm tìm giá trị phần tử lớn nhất của mảng được định nghĩa
và sử dụng như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <conio.h>
int Timmax (int *a,int n) ;
void main (void)
{
int arr[] = {6,13,2,3,9,7,5,18,4,12};
int max ;
max = Timmax(arr,10);
printf (“Max = %d “,max);
}
int Timmax (int *a, int n)
{
int i,max ;
max = a[0] ;
for (i=1;i<n;i++)
max = (max<a[i])?a[i]:max ;
return max ;
}
151
Hàm tìm số lớn nhất trong mảng sử dụng tham số con trỏ. Trong
thân hàm, biến tham số được sử dụng như một mảng số nguyên, việc
truy xuất các vùng nhớ đã được cấp phát sử dụng mảng hoặc con trỏ đã
được giới thiệu ở chương 5. Trong chương trình trên, tại vị trí gọi hàm
ở câu lệnh 8, người dùng truyền địa chỉ cho tham tham số của hàm bằng
tên mảng, chương trình sẽ tự động chuyển sang kiểu con trỏ. Cũng có
thể truyền địa chỉ phần tử đầu tiên cho tham số. Hơn nữa, cũng có thể
khai báo hàm sử dụng mảng, thay cho con trỏ, hoặc có thể dùng con trỏ
thay cho mảng bên trong hàm.
Chương trình trên có thể được viết lại như sau mà không làm thay
đổi kết quả cũng như chức năng của hàm.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <conio.h>
int Timmax (int *a,int n);
void main (void)
{
int arr[] = {6,13,2,3,9,7,5,18,4,12};
int max ;
max = Timmax(&arr[0],10);
printf (“Max = %d “,max);
}
int Timmax (int a[],int n)
{
int i,max ;
max = *a ;
for (i=1;i<n;i++)
max = (max<*(a+i))? *(a+i):max ;
return max ;
}
Mảng được truyền cho hàm thông qua tham chiếu địa chỉ. Hay nói
cách khác, tham số hàm là một biến con trỏ được tham chiếu đến mảng
khi hàm thực thi nên mọi thao tác trên mảng bên trong hàm sẽ là thao
tác trên mảng mà địa chỉ của nó được truyền cho tham số hàm.
152
Ví dụ, hàm sắp xếp mảng theo thứ tự tăng dần được định nghĩa và
sử dụng như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <conio.h>
#include<stdio.h>
#include <stdlib.h>
void Sapxepmang(int a[],int n);
void Inmang(int a[],int n);
void main (void)
{
int arr[] = {6,13,2,3,9,7,5,18,4,12};
printf(“mang truoc khi sap xep :\n”);
Inmang(arr,10);
Sapxepmang(arr,10);
printf(“\nmang sau khi sap xep :\n”);
Inmang(arr,10);
}
void Sapxepmang(int a[],int n)
{
int i,j,tam ;
for (i=0;i<n-1 ; i++)
for(j=i+1; j<n;j++)
{
if (a[i]>a[j])
{
tam = a[i];
a[i]= a[j];
a[j]=tam;
}
}
}
void Inmang(int a[],int n)
{
int i;
for (i=0;i<n ; i++)
printf(“%d, “,a[i]);
}
153
Kết quả sau khi thực thi chương trình:
mang truoc khi sap xep :
6, 13, 2, 3, 9, 7, 5, 18, 4, 12,
mang sau khi sap xep :
2, 3, 4, 5, 6, 7, 9, 12, 13, 18,
6.6. ĐỆ QUY
Trong ngôn ngữ C, hàm có thể gọi các hàm khác và cũng có thể
gọi chính nó. Một hàm được gọi bởi chính nó được gọi là hàm đệ quy.
Các hàm đệ quy hữu ích trong các chương trình cần lặp lại các quá trình
tính toán hoặc xử lý. Cấu trúc hàm đệ quy được minh họa như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
void recurse()
{
... .. ...
recurse();
... .. ...
}
int main()
{
... .. ...
recurse();
... .. ...
}
Trong hàm main chúng ta gọi hàm recurse(). Trong hàm recurse()
lại gọi chính hàm recurse(). Hàm recurse() trong ví dụ trên được gọi là hàm
đệ quy. Có thể thấy hàm recurse() gọi chính nó và quá trình cứ thế lặp lại
không thoát. Do đó, khi xây dựng các hàm đệ quy cần chú ý điều kiện dừng
của hàm để tránh việc các hàm được lặp lại vô tận.
Một ví dụ của hàm đệ quy là thực hiện phép tính giai thừa. Giai thừa
của n có thể được tính bằng cách lặp lại các phép nhân. Chúng ta có thể viết
hàm tính giai thừa bằng phương pháp sử dụng vòng lặp trong đó các biến
có thể xuất phát từ 1 và tăng dần đến n hoặc đi theo chiều ngược lại từ n về
1. Hàm tính giai thừa sử dụng vòng lặp được minh họa như sau:
154
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int giaithua(int n)
{
int i,gt = n;
for (i=n-1;i>0;i--)
gt=gt*i ;
return gt ;
}
void main()
{
int n;
do{
printf(“Nhap n :”);
scanf(“%d”,&n);
} while(n<1);
printf(“Giai thua cua %d la %d”, n, giaithua(n));
}
Trong chương trình trên chúng ta tính giai thừa bằng cách lặp lại
phép nhân với biến điều khiển trong khi biến điều khiển giảm dần từ n
về 1. Chúng ta có thể xây dựng hàm đệ quy để tính giai thừa như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int giaithua(int n)
{
if (n==1)
return 1;
return n*giaithua(n-1) ;
}
void main()
{
int n;
do{
printf(“Nhap n :”);
scanf(“%d”,&n);
} while(n<1);
printf(“Giai thua cua %d la %d”, n, giaithua(n));
}
155
Trong ví dụ trên, chúng ta lặp phép nhân trong hàm giai thừa bằng
cách gọi chính nó và giảm giá trị tham số đi 1 đơn vị. Điều kiện dừng
được thiết lập là n=1.
6.7. MỘT SỐ HÀM THƯ VIỆN CHUẨN
Thư viện chuẩn cung cấp nhiều hàm thông dụng bao gồm các
hàm xử lý, tính toán. Các hàm chuẩn này tồn tại trên hầu hết các hệ
thống chuẩn. Phần này giới thiệu sơ lược một số hàm, người đọc có
thể tự tìm hiểu thêm chức năng hàm, tham số, kiểu giá trị trả về của
hàm khi sử dụng.
 Các hàm xử lý toán học: sqrt, pow, sin, cos, tan.
 Các hàm thao tác trên ký tự: isdigit, isalpha, isspace, toupper,
tolower.
 Các hàm thao tác trên chuỗi: strlen, strcpy, strcmp, strcat,
strstr, strtok.
 Các hàm thao tác trên thiết bị xuất nhập: printf, scanf,
sprintf, sscanf.
 Các hàm thao tác trên tập tin: fopen, fclose, fgets, fprints.
 Các hàm thao tác trên dữ liệu thời gian: clock, time, difftime.
 Các hàm hỗ trợ sắp xếp và tìm kiếm: qsort, bsearch.
6.8. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
a.
b.
156
void Ham1(int a, int b)
{
return (a + b);
}
#include<stdio.h>
int Ham2(void)
{
int a, b, max;
max = a;
if(max < b)
max = b;
return max;
}
c.
d.
int main(void)
{
int x,y;
scanf(“%d%d”,&x,&y);
printf(“%d”,Ham2(x,y));
return 0;
}
#include<stdio.h>
void Ham3(void)
{
printf(“Xin chao!”);
}
int main(void)
{
Ham3(void);
return 0;
}
#include<stdio.h>
#include <malloc.h>
//chuong trinh nhap mang 5 so nguyen va in ra mang
void Ham(int n)
{
int *x;
int i;
x = (int*)malloc(n*sizeof(int));
for(i = 0; i < n; i++)
scanf(“%d”,(a+i));
}
int main(void)
{
int k = 5, i;
printf(“Nhap mang 5 phan tu:\n”);
Ham(k);
printf(“Mang da nhap:\n”);
for(i = 0; i < k; i++)
printf(“%d “,*(x+i));
return 0;
}
157
Viết hàm tính và trả về giá trị ab, với b là số nguyên dương.
Viết chương trình nhập vào số nguyên k > 0, tính và in ra giá trị
10 , sử dụng hàm đã định nghĩa ở trên.
k
3. Viết hàm tìm và trả về số chẵn lớn nhất trong một mảng một
chiều gồm n số nguyên, nếu trong mảng không có số chẵn thì hàm trả
về giá trị -1. Ứng dụng: viết chương trình nhập vào một mảng gồm n số
nguyên, tìm và in ra số chẵn lớn nhất, nếu mảng không có số chẵn thì
in ra thông báo
4. Viết chương trình nhập vào một mảng gồm n số nguyên, cho
biết giá trị và vị trí của số lớn nhất trong mảng. Trong đó xây dựng và
sử dụng các hàm sau:
a. Hàm nhập mảng
b. Hàm tìm và trả về phần tử lớn nhất trong mảng
c. Hàm tìm và trả về vị trí phần tử lớn nhất trong mảng
5. Viết chương trình nhập vào một mảng gồm n số nguyên, sắp
xếp và in ra mảng theo thứ tự tăng dần. Trong đó xây dựng và sử dụng
các hàm sau:
a. Hàm nhập mảng
b. Hàm in mảng
c. Hàm sắp xếp mảng theo thứ tự tăng dần
6. Viết hàm thực hiện phép cộng 2 mảng 1 chiều có cùng kích
thước. Viết chương trình nhập vào 2 mảng một chiều, in ra mảng là kết
quả của phép cộng 2 mảng vừa nhập. Sử dụng hàm nhập mảng đã viết
ở bài tập trước.
7. Viết hàm đệ quy thực hiện phép cộng các số lẻ từ 1 đến n, với
n được nhập từ bàn phím.
8. Viết hàm đệ quy tạo ra một dãy Fibonacci cho một số cho trước.
158
CHƯƠNG 7
KIỂU DỮ LIỆU TỰ TẠO
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Định nghĩa kiểu cấu trúc và áp dụng kiểu cấu trúc trên các biến
cấu trúc, mảng một chiều kiểu cấu trúc và con trỏ cấu trúc.
- Định nghĩa và sử dụng kiểu dữ liệu Union.
- Định nghĩa và sử dụng kiểu dữ liệu liệt kê.
7.1. KIỂU CẤU TRÚC
7.1.1. Giới thiệu kiểu cấu trúc
Kiểu cấu trúc – structure – là một tập hợp các biến khác kiểu dữ liệu
với nhau dưới cùng một tên gọi. Một biến kiểu cấu trúc có thể gồm nhiều
biến thành phần có kiểu dữ liệu khác nhau. Điều này khác với mảng một
chiều vì mảng một chiều bao gồm nhiều phần tử mảng có cùng kiểu dữ
liệu với nhau. Kiểu cấu trúc thường được sử dụng cho mục đích lưu trữ các
thông tin dữ liệu với nhiều loại định dạng dữ liệu khác nhau.
7.1.2. Định nghĩa một kiểu cấu trúc mới
Các kiểu cấu trúc là kiểu dữ liệu tự tạo, được xây dựng dựa trên
các đối tượng của các kiểu dữ liệu khác. Xét một kiểu cấu trúc được
định nghĩa như sau:
1
2
3
4
5
6
7
8
struct SinhVien
{
char hoTen[40];
char MSSV[12];
float diemGiuaKy;
float diemCuoiKy;
float diemTongKet;
};
159
Ở dòng lệnh số 1, từ khóa struct là bắt buộc để bắt đầu cho phần
định nghĩa một kiểu cấu trúc. Tên gọi SinhVien là tên được đặt cho kiểu
cấu trúc đang định nghĩa. Các biến được khai báo bên trong cặp dấu
ngoặc { }; của phần định nghĩa ở trên được gọi là các biến thành phần
của kiểu cấu trúc. Các biến thành phần trong cùng một kiểu cấu trúc
phải có tên gọi khác nhau, tuy nhiên các biến thành phần của các kiểu
cấu trúc khác nhau có thể có tên gọi trùng nhau. Việc định nghĩa một
kiểu cấu trúc phải được kết thúc bằng một dấu chấm phẩy ;
Các biến thành phần của một kiểu cấu trúc có thể là các biến thuộc
các kiểu dữ liệu cơ bản (char, int, float,...) hoặc là biến con trỏ, biến
mảng, hoặc là biến thuộc một kiểu cấu trúc khác. Xét một kiểu cấu trúc
khác được định nghĩa như sau:
1
2
3
4
5
6
7
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
Kiểu cấu trúc ThietBi này được định nghĩa gồm 4 biến thành phần,
biến thành phần tenTB là một mảng gồm 20 phần tử kiểu ký tự dùng để
lưu tên của thiết bị, biến thành phần namSanXuat là một biến kiểu int
dùng để lưu năm sản xuất của thiết bị, biến thành phần giaTien là một
biến kiểu float dùng để lưu giá tiền của thiết bị, và biến thành phần
trangThai có kiểu ký tự sẽ dùng để lưu ký tự: có thể là ‘H’ hoặc ‘T’ cho
trạng thái ‘hỏng’ hoặc ‘tốt’ của thiết bị.
Như vậy, cú pháp chung của thao tác định nghĩa để tạo nên một
kiểu cấu trúc mới sẽ là:
struct
160
TênKiểuCấuTrúc
{
};
Khai báo các biến thành phần;
7.1.3. Khai báo biến kiểu cấu trúc
Các thao tác định nghĩa kiểu cấu trúc ở trên không tạo ra bất kỳ
vùng nhớ dữ liệu nào trong bộ nhớ chương trình, mà thay vào đó là tạo
ra các kiểu dữ liệu mới để dùng cho việc khai báo biến. Các biến kiểu
cấu trúc cũng được khai báo tương tự như các biến kiểu dữ liệu khác.
Xét câu lệnh khai báo biến sau:
struct SinhVien
a,b;
Câu lệnh này đã khai báo ra biến a thuộc kiểu cấu trúc SinhVien
và biến b thuộc kiểu cấu trúc SinhVien . Trong câu lệnh này, từ khóa
struct là bắt buộc, SinhVien là tên của kiểu cấu trúc đã được định
nghĩa trước đó, và a,b là tên của các biến được khai báo. Vì thuộc kiểu
cấu trúc SinhVien nên mỗi biến a và b sẽ có 5 biến thành phần, mang tên
gọi lần lượt là hoTen, MSSV, diemGiuaKy, diemCuoiKy, diemTongKet.
Vùng nhớ của các biến a và b sẽ được phát sinh như hình sau:
Tổng kích thước vùng nhớ của biến a là 64 bytes
Tổng kích thước vùng nhớ của biến b là 64 bytes
161
Lúc này, kích thước của vùng nhớ mỗi biến a, b sẽ bằng tổng
kích thước của các biến thành phần (hoTen, MSSV, diemGiuaKy,
diemCuoiKy, diemTongKet). Lưu ý với các biến thành phần của mỗi
biến cấu trúc, máy tính sẽ cấp vùng nhớ theo đơn vị word (thường là 4
byte). Chẳng hạn, biến thành phần hoTen của biến a và biến b ở trên, đã
được định nghĩa trong kiểu cấu trúc SinhVien là một mảng một chiều
gồm 40 ký tự, thì máy tính sẽ cấp vùng nhớ cho thành phần này là 10
words (tương đương 40 bytes). Nếu thành phần hoTen được định nghĩa
là mảng 38 ký tự:
1
2
3
4
5
6
7
8
struct SinhVien
{
char hoTen[38];
char MSSV[12];
float diemGiuaKy;
float diemCuoiKy;
float diemTongKet;
};
thì máy tính cũng sẽ cấp vùng nhớ cho thành phần này hoTen của
biến a hoặc biến b là 10 words (tức 40 bytes, dư 2 bytes).
Khi khai báo một biến cấu trúc, có thể tiến hành khởi tạo giá trị
ban đầu cho các biến thành phần của biến cấu trúc này. Ví dụ câu lệnh
khai báo biến sau:
struct ThietBi
c = {“Quat”, 2019, 225.5, ‘T’};
sẽ phát sinh vùng nhớ cho biến c thuộc kiểu cấu trúc ThietBi và
khởi tạo giá trị ban đầu cho các biến thành phần của biến c như sau:
Tổng kích thước vùng nhớ của biến c là 32 bytes
162
Như vậy, cú pháp chung để khai báo các biến thuộc kiểu cấu trúc
sẽ là:
struct
hoặc
struct
TênKiểuCấuTrúc
tênBiến;
TênKiểuCấuTrúc
tênBiến = {các giá trị khởi tạo};
Với kiểu cấu trúc, các phép toán có thể sử dụng được trên các biến
cấu trúc là: phép gán = giữa các biến cấu trúc cùng kiểu, phép toán lấy
địa chỉ & của một biến cấu trúc, phép truy xuất . tới các biến thành phần
của một biến cấu trúc. Các phép so sánh (ví dụ: ==, !=, ...) sẽ không thể
thực hiện được trên các biến cấu trúc. Chẳng hạn ở đoạn lệnh sau:
1
2
3
4
5
6
7
struct ThietBi c = {“Quat”, 2019, 225.5, ‘T’};
struct ThietBi d;
d = c; //gan du lieu cua c vao d
//phep toan hop le
if (d != c)
//phep toan khong hop le
printf(“Du lieu khac nhau”);
thì trình biên dịch sẽ báo lỗi ở dòng lệnh 6 if (d != c) vì
phép so sánh khác nhau != không thể thực hiện được trên hai biến
cấu trúc d và c.
7.1.4 .Truy xuất tới các thành phần của biến cấu trúc
Có hai toán tử có thể được sử dụng để truy xuất tới các thành phần
của một vùng nhớ kiểu cấu trúc: toán tử dấu chấm (.) và toán tử mũi
tên (->).
Toán tử dấu chấm (.) hay còn gọi là toán tử thành phần cấu trúc
dùng để truy xuất tới các biến thành phần của một biến cấu trúc thông
qua tên gọi của biến cấu trúc đó. Ví dụ, với biến c thuộc kiểu cấu trúc
ThietBi đã khai báo ở trên:
struct ThietBi
c = {“Quat”, 2019, 225.5, ‘T’};
163
thì ta có thể thực hiện in thông tin của các biến thành phần của
biến c bằng câu lệnh:
printf(“%s, %d, %.1f, %c\n”,c.tenTB,c.namSanXuat,c.giaTien,c.trangThai);
và thu được kết quả in ra màn hình sẽ là:
Quat, 2019, 225.5, T
Như vậy, các truy xuất : c.tenTB, c.namSanXuat, c.giaTien,
c.trangThai sẽ truy xuất vào các thành phần tenTB, namSanXuat,
giaTien, trangThai của biến c như hình sau:
Toán tử mũi tên, hay còn gọi là toán tử con trỏ cấu trúc, bao gồm
dấu trừ (-) và dấu so sánh lớn hơn (>) dùng để truy xuất tới các biến
thành phần của một vùng nhớ kiểu cấu trúc thông qua một con trỏ cấu
trúc. Điều này sẽ được minh họa rõ hơn ở phần sau.
Xét một chương trình được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
164
#include <stdio.h>
#include <conio.h>
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
int main (void)
{
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
char tam[2];
struct ThietBi
c = {“NA”, 0, 0, ‘T’};
printf(“Nhap thong tin cua 1 thiet bi:\n”);
printf(“Nhap ten: “);
gets(c.tenTB);
printf(“Nhap nam san xuat: “);
scanf(“%d”,&c.namSanXuat);
printf(“Nhap gia tien: “);
scanf(“%f”,&c.giaTien);
gets(tam);
printf(“Nhap trang thai: Tot (T) hoac Hong
(H): “);
scanf(“%c”,&c.trangThai);
printf(“\nThong tin thiet bi da nhap:\n”);
printf(“%s, %d, %.1f, %c\n”,c.tenTB,c.namSanXuat,c.giaTien,c.trangThai);
getch();
return 0;
}
Trong chương trình này, kiểu cấu trúc ThietBi đã được định nghĩa
ở trước hàm main ở đoạn từ dòng lệnh số 4 đến dòng lệnh số 10. Việc
định nghĩa kiểu cấu trúc ThietBi trước hàm main nhằm đảm bảo rằng
kiểu ThietBi được tạo ra có thể được sử dụng trong toàn chương trình
(hàm main và các hàm khác – nếu có). Trong chương trình chính – hàm
main – biến c thuộc kiểu cấu trúc ThietBi được khai báo và khởi tạo
giá trị ban đầu ở dòng lệnh số 15. Vùng nhớ dành cho biến c được cấp
phát và khởi tạo giá trị ban đầu như hình sau:
165
Các câu lệnh ở đoạn lệnh từ dòng lệnh số 17 đến dòng lệnh số 31
có tác dụng yêu cầu người dùng nhập các thông tin: tên thiết bị, năm sản
xuất, giá tiền, và trạng thái thiết bị, sau đó lưu vào vùng nhớ của biến c.
Câu lệnh gets(tam); ở dòng lệnh số 28 có tác dụng xóa bỏ ký tự Enter
còn xót lại của lệnh scanf dữ liệu trước đó scanf(“%f”,&c.giaTien);
ở dòng lệnh số 26, giúp cho việc nhập ký tự sau đó scanf(“%c”,&c.
trangThai); ở dòng lệnh số 31 không bị trôi.
Các câu lệnh trong đoạn lệnh từ dòng lệnh số 33 đến dòng lệnh số
35 có tác dụng in ra màn hình các thông tin đã lưu trong biến c. Kết quả
sau khi chạy chương trình trên là:
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua 1 thiet bi:
ten: may lanh
nam san xuat: 2019
gia tien: 4.5
trang thái: Tot (T) hoac Hong (H): T
Thong tin thiet bi da nhap:
may lanh, 2019, 4.5, T
trong đó, các thông tin: may lanh, 2019, 4.5, T là thông tin
người dùng đã nhập vào từ bàn phím.
7.1.5. Mảng một chiều kiểu cấu trúc
Khi cần phát sinh nhiều vùng nhớ cấu trúc cùng chung một kiểu
cấu trúc, ta có thể khai báo một mảng một chiều các phần tử kiểu cấu trúc.
Mảng một chiều các phần tử kiểu cấu trúc sẽ được khai báo tương tự như
các mảng một chiều khác. Câu lệnh khai báo một mảng một chiều sau:
166
struct ThietBi
d[5];
sẽ tạo ra một mảng một chiều d gồm 5 phần tử kiểu ThietBi.
Trong câu lệnh này, từ khóa struct là bắt buộc, ThietBi là tên của kiểu
cấu trúc đã định nghĩa trước đó, d là tên mảng, và 5 là số lượng phần tử
mảng. Lúc này, chương trình sẽ phát sinh vùng nhớ cho mảng d gồm 5
phần tử như sau:
chỉ số
mảng
d
0
1
2
d[0]
3
4
d[4]
Trong mảng d, các phần tử mảng lần lượt có tên gọi là d[0], d[1],
d[2], d[3], và d[4]; mỗi phần tử mảng sẽ mang định dạng dữ liệu của
kiểu cấu trúc ThietBi. Hay nói cách khác, mỗi ô nhớ d[i] (i = 0
– 4) đều có các biến thành phần là: tenTB, namSanXuat, giaTien,
trangThai. Chẳng hạn, ô nhớ d[0] sẽ có vùng nhớ chi tiết như sau:
d[0].tenTB
d[0].namSanXuat
d[0].giaTien [0].trangThai
d[0]
Các phần tử mảng còn lại (d[1], d[2], d[3], d[4]) sẽ có cấu trúc
vùng nhớ gồm 4 biến thành phần (tenTB, namSanXuat, giaTien,
trangThai) tương tự như phần tử mảng d[0].
Để truy xuất vào các biến thành phần của một phần tử mảng cấu
trúc, ta sẽ dùng toán tử dấu chấm (.) như đã đề cập ở phần trước. Ví dụ
câu lệnh:
d[0].namSanXuat = 2019;
sẽ lưu giá trị số 2019 vào biến thành phần namSanXuat của ô nhớ
mảng d[0] như minh họa ở hình sau.
167
d[0].tenTB
d[0].namSanXuat d[0].giaTien d[0].trangThai
d[0]
2019
Xét một chương trình khác được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
168
#include <stdio.h>
#include <conio.h>
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
int main (void)
{
char tam[2];
struct ThietBi d[5];
int i, n = 5;
for (i = 0; i < n; i++)
{
printf(“\nNhap thong tin cua thiet bi
thu %d:\n”, i);
printf(“Nhap ten: “);
gets(d[i].tenTB);
printf(“Nhap nam san xuat: “);
scanf(“%d”,&d[i].namSanXuat);
printf(“Nhap gia tien: “);
scanf(“%f”,&d[i].giaTien);
gets(tam);
printf(“Nhap trang thai: Tot (T) hoac
Hong (H): “);
scanf(“%c”,&d[i].trangThai);
gets(tam);
}
printf(“\nThong tin cac thiet bi da
39
nhap:\n”);
40
for ( i = 0; i < n; i++)
41 printf(“%s, %d, %.1f, %c\n”,d[i].tenT42 B,d[i].namSanXuat,d[i].giaTien,d[i].trangThai);
43
getch();
44
return 0;
45 }
Trong chương trình này, mảng một chiều d gồm 5 phần tử kiểu
cấu trúc ThietBi được khai báo trong hàm main ở dòng lệnh số 15. Mục
đích của việc khai báo mảng d là tạo ra vùng nhớ gồm 5 ô nhớ để lưu
trữ thông tin của 5 thiết bị. Mỗi thiết bị sẽ có bốn thành phần thông tin
cần lưu trữ là: tenTB, namSanXuat, giaTien, trangThai. Tiếp theo,
các thông tin của 5 thiết bị sẽ được người dùng nhập vào từ bàn phím, ở
đoạn lệnh từ dòng lệnh số 17 đến dòng lệnh số 36, bằng cách dùng một
vòng lặp. Cuối cùng, thông tin của tất cả các thiết bị sẽ được in ra màn
hình, ở đoạn lệnh từ dòng lệnh số 39 đến dòng lệnh số 42. Kết quả sau
khi chạy chương trình trên là:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 0:
ten: quat
nam san xuat: 2017
gia tien: 0.25
trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 1:
ten: den
nam san xuat: 2017
gia tien: 0.75
trang thai: Tot (T) hoac Hong (H): H
Nhap thong tin cua thiet bi thu 2:
Nhap ten: may lanh
Nhap nam san xuat: 2018
169
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Nhap gia tien: 4.75
Nhap trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 3:
ten: Tivi
nam san xuat: 2017
gia tien: 12.5
trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 4:
ten: loa
nam san xuat: 2017
gia tien: 2.3
trang thai: Tot (T) hoac Hong (H): T
Thong tin cac thiet bi da nhap:
quat, 2017, 0.3, T
den, 2017, 0.8, H
may lanh, 2018, 4.8, T
Tivi, 2017, 12.5, T
loa, 2017, 2.3, T
trong đó, các thông tin ở các dòng kết quả từ dòng số 2 đến dòng
số 30 là thông tin do người dùng nhập vào từ bàn phím.
7.1.6. Con trỏ kiểu cấu trúc
Tương tự như biến con trỏ của các kiểu dữ liệu khác, một biến con
trỏ kiểu cấu trúc có thể được dùng để truy xuất tới một vùng nhớ cùng
kiểu cấu trúc đó bằng cách sử dụng thông tin địa chỉ của vùng nhớ cần
truy xuất. Con trỏ kiểu cấu trúc cũng cần phải được khai báo trước khi
sử dụng. Xét các câu lệnh khai báo sau:
1
2
struct ThietBi c = {“Quat”, 2019, 225.5, ‘T’};
struct ThietBi *p;
Câu lệnh ở dòng lệnh số 1 là câu lệnh khai báo biến cấu trúc c
thuộc kiểu cấu trúc ThietBi như đã đề cập ở các phần trước. Sau câu
170
lệnh này, chương trình sẽ cấp phát vùng nhớ dữ liệu cho biến c gồm 4
biến thành phần như sau:
c.tenTB
c
c.namSanXuat
Quat
c.giaTien c.trangThai
2019
225.5
T
Câu lệnh ở dòng lệnh số 2 là câu lệnh khai báo biến p là một biến
con trỏ cấu trúc thuộc kiểu cấu trúc ThietBi. Trong câu lệnh này, từ
khóa struct là bắt buộc, ThietBi là tên của kiểu cấu trúc, * là toán tử
bắt buộc đặt trước tên biến p để thể hiện p là biến con trỏ. Chương trình
sẽ cấp phát một vùng nhớ cho con trỏ p dùng để lưu trữ địa chỉ của vùng
nhớ mà con trỏ p muốn trỏ tới. Vùng nhớ của biến con trỏ p lúc này có
thể có kích thước là 4 byte (hoặc 8 byte, tùy thuộc vào phần cứng) và
được khởi tạo một giá trị ngẫu nhiên như hình dưới, biến p không có
các biến thành phần (tenTB, namSanXuat, giaTien, trangThai) của
kiểu ThietBi như biến c.
p
1824868116
Như vậy, cú pháp chung để khai báo một biến con trỏ kiểu cấu
trúc sẽ là:
struct TênKiểuCấuTrúc
*TênBiếnConTrỏ;
Sau khi khai báo, có thể sử dụng con trỏ cấu trúc để trỏ tới một
vùng nhớ cấu trúc cùng kiểu cấu trúc với con trỏ. Ví dụ, đoạn lệnh sau:
1
2
p = &c;
p->namSanXuat = 2015; //tuc: c.namSanXuat = 2015;
sẽ lưu trữ giá trị 2015 vào vùng nhớ c.namSanXuat bằng cách
dùng con trỏ p. Ở đây, câu lệnh p = &c; ở dòng lệnh số 1 sẽ lưu địa chỉ
của biến c vào con trỏ p. Ở câu lệnh ở dòng lệnh số 2, toán tử mũi tên
(->) được sử dụng để yêu cầu con trỏ p truy xuất tới biến thành phần
namSanXuat của vùng nhớ mà p đang lưu địa chỉ, trong trường hợp này
171
là biến thành phần namSanXuat của biến c. Kết quả ta có giá trị 2015
được lưu trong vùng nhớ c.namSanXuat như minh họa ở hình dưới.
địa chỉ biến c (ví dụ) là: 2685540
Sau khi khai báo con trỏ cấu trúc, ta cũng có thể cấp phát bộ nhớ
động cho con trỏ để dùng cho việc lưu trữ dữ liệu. Việc cấp phát bộ nhớ
động cho con trỏ cấu trúc được thực hiện tương tự như thao tác cấp phát
bộ nhớ cho con trỏ của các kiểu dữ liệu khác. Xét câu lệnh cấp phát bộ
nhớ cho con trỏ cấu trúc ThietBi p đã khai báo ở trên như sau:
p = (ThietBi*)malloc(sizeof(ThietBi));
trong câu lệnh này, hàm malloc được sử dụng để cấp phát cho con
trỏ p một ô nhớ dữ liệu kiểu ThietBi, hàm sizeof() được sử dụng để
giúp cung cấp thông tin kích thước của một ô nhớ kiểu ThietBi cho
hàm malloc. Sau câu lệnh cấp phát này, nếu chương trình cấp phát
thành công vùng nhớ cho con trỏ p thì p sẽ lưu địa chỉ của ô nhớ đã cấp
phát, nếu không thành công thì p sẽ lưu giá trị NULL.
Ô nhớ cấp phát cho p, (có thể) có địa chỉ hiện tại là 1514208
Sau khi cấp phát, ta có thể truy xuất tới các biến thành phần của
ô nhớ đã cấp phát cho con trỏ p bằng cách sử dụng toán tử mũi tên (->)
với con trỏ p. Chẳng hạn câu lệnh:
172
p->giaTien = 5.6;
sẽ thực hiện lưu giá trị 5.6 vào biến thành phần giaTien của ô nhớ
đã cấp phát.
Nếu không còn nhu cầu sử dụng vùng nhớ đã xin cấp phát trước
đó, có thể yêu cầu giải phóng bộ nhớ đã cấp phát cho con trỏ bằng cách
dùng hàm free().Ví dụ câu lệnh:
free(p);
sẽ giải phóng vùng nhớ đã cấp cho con trỏ p.
Xét một chương trình được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
int main (void)
{
char tam[2];
int i, n;
printf(“Nhap so n: “);
173
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
scanf(“%d”,&n);
struct ThietBi *p;
p = (ThietBi*)malloc(n*sizeof(ThietBi));
if (p == NULL)
printf(“Cap phat bo nho khong thanh cong!”);
else
{
for (i = 0; i < n; i++)
{
printf(“\nNhap thong tin cua thiet bi thu
%d:\n”, i);
gets(tam);
printf(“Nhap ten: “);
gets((p+i)->tenTB);
printf(“Nhap nam san xuat: “);
scanf(“%d”,&(p+i)->namSanXuat);
printf(“Nhap gia tien: “);
scanf(“%f”,&(p+i)->giaTien);
gets(tam);
printf(“Nhap trang thai: Tot (T) hoac
Hong (H): “);
scanf(“%c”,&(p+i)->trangThai);
}
printf(“\nThong tin cac thiet bi da nhap:\n”);
for ( i = 0; i < n; i++)
printf(“%s, %d, %.1f, %c\n”,
(p+i)->tenTB,(p+i)->namSanXuat,(p+i)->giaTien,
(p+i)->trangThai);
}
free(p);
getch();
return 0;
}
Trong chương trình này, con trỏ p kiểu cấu trúc ThietBi được khai
báo ở câu lệnh tại dòng lệnh số 20. Sau đó, chương trình sẽ cấp phát động
174
cho con trỏ p một vùng nhớ gồm n ô nhớ kiểu ThietBi bởi câu lệnh ở dòng
21, với số n là giá trị người dùng đã nhập vào từ bàn phím trước đó ở câu
lệnh tại dòng 18. Ở đây, việc sử dùng hàm cấp phát bộ nhớ động malloc()
đòi hỏi phải khai báo thư viện malloc.h như ở dòng lệnh số 3. Sau đó,
chương trình sẽ kiểm tra xem việc cấp phát bộ nhớ có diễn ra thành công hay
không. Tiếp tục, chương trình sẽ yêu cầu người dùng nhập dữ liệu cho n thiết
bị và lưu vào n ô nhớ đã cấp cho con trỏ p, ở đoạn lệnh từ dòng 27 đến dòng
46. Kế tiếp, chương trình sẽ in ra lại dữ liệu đã lưu trước đó của n thiết bị,
ở đoạn lệnh từ dòng 49 tới dòng 52. Cuối cùng, chương trình sẽ giải phóng
vùng nhớ đã cấp phát cho con trỏ p bởi lệnh free() ở dòng lệnh số 53.
Kết quả sau khi chạy chương trình trên là:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Nhap so n: 3
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 0:
ten: may phat song
nam san xuat: 2018
gia tien: 35.5
trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 1:
ten: VOM
nam san xuat: 2017
gia tien: 0.5
trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 2:
ten: dao dong ky
nam san xuat: 2019
gia tien: 35.6
trang thai: Tot (T) hoac Hong (H): T
Thong tin cac thiet bi da nhap:
may phat song, 2018, 35.5, T
VOM, 2017, 0.5, T
dao dong ky, 2019, 35.6, T
175
trong đó, các thông tin ở các dòng kết quả từ dòng số 2 đến dòng
số 20 là thông tin do người dùng nhập vào từ bàn phím.
7.1.7. Sử dụng kiểu cấu trúc với Hàm
Một hàm có thể có các tham số đầu vào kiểu cấu trúc. Khi truyền
các biến cấu trúc vào cho hàm, có thể truyền toàn bộ biến cấu trúc
đó, hoặc chỉ truyền một vài biến thành phần của biến cấu trúc đó. Khi
truyền, có thể truyền giá trị (truyền tham trị) hoặc truyền địa chỉ (truyền
tham chiếu) của biến cấu trúc vào cho hàm.
Xét một chương trình được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <conio.h>
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
void NhapDuLieu(struct ThietBi *x, int n)
{
char tam[2];
int i;
for (i = 0; i < n; i++)
{
printf(“\nNhap thong tin cua thiet bi thu
%d:\n”, i);
21 printf(“Nhap ten: “);
22 gets((x+i)->tenTB);
23
176
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
printf(“Nhap nam san xuat: “);
scanf(“%d”,&(x+i)->namSanXuat);
printf(“Nhap gia tien: “);
scanf(“%f”,&(x+i)->giaTien);
gets(tam);
printf(“Nhap trang thai: Tot (T) hoac
Hong (H): “);
scanf(“%c”,&(x+i)->trangThai);
gets(tam);
}
}
void InMotThietBi(struct ThietBi a)
{
printf(“%s, %d, %.1f, %c\n”,a.tenTB,a.namSanXuat,a.giaTien,a.trangThai);
}
int main (void)
{
int i, n = 2;
struct ThietBi
d[2];
NhapDuLieu(&d[0],n);
printf(“\nThong tin cac thiet bi da nhap:\n”);
for ( i = 0; i < n; i++)
InMotThietBi(d[i]);
getch();
return 0;
}
177
Trong chương trình này, hàm NhapDuLieu được định nghĩa trong
đoạn lệnh từ dòng 13 đến dòng 37. Hàm này có một tham số đầu vào
là một con trỏ x kiểu cấu trúc ThietBi, tham số đầu vào này cần được
truyền vào địa chỉ của một ô nhớ kiểu ThietBi khi hàm được gọi. Hàm
InMotThietBi được định nghĩa trong đoạn lệnh từ dòng 39 đến dòng
43. Hàm này có một tham số đầu vào là một biến dữ liệu a kiểu cấu trúc
ThietBi, tham số đầu vào này cần được truyền vào giá trị của một ô
nhớ kiểu ThietBi khi hàm được gọi. Trong hàm main, sau khi khai báo
mảng 2 phần tử kiểu cấu trúc ThietBi bằng câu lệnh struct ThietBi
d[2]; ở dòng lệnh số 48, chương trình sẽ gọi hàm NhapDuLieu bằng câu
lệnh NhapDuLieu(&d[0],n); ở dòng lệnh 50, thông số &d[0] sẽ truyền
địa chỉ của ô nhớ d[0] vào cho con trỏ đầu vào x của hàm NhapDuLieu.
Khi hàm NhapDuLieu chạy, con trỏ x sẽ truy xuất tới các ô nhớ của
mảng d và lưu dữ liệu vào mảng d. Do đó, dữ liệu khi người dùng nhập
vào sẽ được lưu vào vùng nhớ của mảng d. Sau khi hàm NhapDuLieu đã
thực hiện xong, hàm main tiếp tục thực hiện vòng lặp for ở dòng lệnh số
53. Vòng lặp này sẽ thực hiện việc in thông tin của từng thiết bị đã nhập.
Ở mỗi lần in thông tin, câu lệnh InMotThietBi(d[i]); ở dòng lệnh số
54 sẽ gọi hàm InMotThietBi, thông số d[i] sẽ lấy toàn bộ giá trị của ô
nhớ thứ i trong vùng nhớ kiểu cấu trúc ThietBi của mảng d truyền vào
cho biến đầu vào a của hàm InMotThietBi. Lúc này, tham số a sẽ mang
thông tin dữ liệu tương tự như ô nhớ d[i], và hàm InMotThietBi sẽ in
các nội dung này ra màn hình.
Kết quả sau khi chạy chương trình trên là:
1
2
3
4
5
6
178
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 0:
ten: may vi tinh
nam san xuat: 2018
gia tien: 15.5
trang thai: Tot (T) hoac Hong (H): T
7
8
9
10
11
12
13
14
15
16
17
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 1:
ten: Tivi
nam san xuat: 2019
gia tien: 12.4
trang thai: Tot (T) hoac Hong (H): T
Thong tin cac thiet bi da nhap:
may vi tinh, 2018, 15.5, T
Tivi, 2019, 12.4, T
trong đó, các thông tin ở các dòng kết quả từ dòng số 1 đến dòng
số 12 là thông tin do người dùng nhập vào từ bàn phím.
Xét một chương trình khác được viết như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
struct ThietBi
{
char tenTB[20];
int namSanXuat;
float giaTien;
char trangThai;
};
void NhapDuLieu(struct ThietBi *x, int n)
{
char tam[2];
int i;
for (i = 0; i < n; i++)
{
printf(“\nNhap thong tin cua thiet bi thu
%d:\n”, i);
179
20
21
22
23
24
25
26
27
28
29
30
31
32
gets(tam);
printf(“Nhap ten: “);
gets((x+i)->tenTB);
printf(“Nhap nam san xuat: “);
scanf(“%d”,&(x+i)->namSanXuat);
printf(“Nhap gia tien: “);
scanf(“%f”,&(x+i)->giaTien);
gets(tam);
printf(“Nhap trang thai: Tot (T) hoac Hong
(H): “);
33 scanf(“%c”,&(x+i)->trangThai);
34
}
35 }
36
37 void InMotThietBi(struct ThietBi a)
38 {
39
printf(“%s, %d, %.1f, %c\n”,a.tenTB,a.namSanXuat,
a.giaTien,a.trangThai);
40 }
41
42 int main (void)
43 {
44
45
int i, n;
46
printf(“Nhap so n: “);
47
scanf(“%d”,&n);
48
49
struct ThietBi *p;
50
p = (ThietBi*)malloc(n*sizeof(ThietBi));
51
if (p == NULL)
52 printf(“Cap phat bo nho khong thanh
cong!”);
53
else
180
54
else
55
{
56 NhapDuLieu(p,n);
57
printf(“\nThong tin cac thiet bi da
58 nhap:\n”);
59 for ( i = 0; i < n; i++)
60 InMotThietBi(*(p+i));
61
}
62
free(p);
63
getch();
64
return 0;
}
Trong chương trình này, hàm NhapDuLieu và hàm InMotThietBi
được định nghĩa tương tự như ở chương trình trước. Trong hàm
main, một vùng nhớ mảng một chiều gồm n phần tử kiểu cấu
trúc ThietBi được cấp phát động cho con trỏ p. Tiếp đó, câu lệnh
NhapDuLieu(p,n); ở dòng lệnh 55 sẽ gọi hàm NhapDuLieu, và truyền
địa chỉ bắt đầu của vùng nhớ mà p đang quản lý (địa chỉ này đang
lưu trong biến p) vào cho con trỏ đầu vào x của hàm NhapDuLieu.
Khi hàm NhapDuLieu chạy, con trỏ x sẽ truy xuất tới vùng nhớ của
p và lưu dữ liệu người dùng nhập từ bàn phím vào vùng nhớ này.
Sau đó, hàm main thực hiện vòng lặp for ở dòng lệnh số 63. Vòng
lặp này sẽ thực hiện việc in thông tin của từng thiết bị đã nhập. Ở
mỗi lần in thông tin, câu lệnh InMotThietBi(*(p+i)); ở dòng lệnh
số 59 sẽ gọi hàm InMotThietBi, thông số *(p+i) sẽ lấy toàn bộ giá
trị của ô nhớ thứ i trong vùng nhớ kiểu cấu trúc ThietBi của con trỏ
p truyền vào cho biến đầu vào a của hàm InMotThietBi. Lúc này,
tham số a sẽ mang thông tin dữ liệu tương tự như ô nhớ *(p+i), và
hàm InMotThietBi sẽ in các nội dung này ra màn hình. Kết quả sau
khi chạy chương trình trên là:
181
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Nhap so n: 2
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 0:
ten: may vi tinh
nam san xuat: 2018
gia tien: 15.5
trang thai: Tot (T) hoac Hong (H): T
Nhap
Nhap
Nhap
Nhap
Nhap
thong tin cua thiet bi thu 1:
ten: Tivi
nam san xuat: 2019
gia tien: 12.4
trang thai: Tot (T) hoac Hong (H): T
Thong tin cac thiet bi da nhap:
may vi tinh, 2018, 15.5, T
Tivi, 2019, 12.4, T
trong đó, các thông tin ở các dòng kết quả từ dòng số 1 đến dòng
số 13 là thông tin do người dùng nhập vào từ bàn phím.
Như vậy, khi hàm có tham số đầu vào là kiểu cấu trúc thì lúc
gọi hàm, tùy thuộc vào loại tham số đầu vào của hàm, ta có thể thực
hiện truyền địa chỉ hoặc truyền giá trị của vùng nhớ kiểu cấu trúc vào
cho hàm.
7.2. KIỂU UNION
7.2.1. Giới thiệu kiểu Union
Kiểu Union (hay còn gọi là kiểu hợp nhất) là kiểu dữ liệu tương tự
như kiểu cấu trúc nhưng có các biến thành phần cùng chia sẻ chung một
vùng nhớ. Trong một số trường hợp, việc dùng chung một vùng nhớ cho
các biến thành phần của biến kiểu Union sẽ giúp tiết kiệm bộ nhớ hơn
so với biến kiểu cấu trúc. Các biến thành phần của một kiểu Union có
182
thể thuộc các kiểu dữ liệu khác nhau, và kích thước của vùng nhớ dùng
chung phải bảo đảm lưu trữ được dữ liệu của biến thành phần có kích
thước lớn nhất. Tại một thời điểm, chỉ có một biến thành phần có thể sử
dụng vùng nhớ chung này.
7.2.2. Định nghĩa một kiểu Union mới
Kiểu Union được định nghĩa tương tự như kiểu cấu trúc. Xét đoạn
lệnh định nghĩa một kiểu Union như sau:
1
union KieuSo
2
{
3
int x;
4
double y;
5
};
trong đoạn lệnh này, union là từ khóa bắt buộc, KieuSo là tên
của kiểu dữ liệu, các biến thành phần được khai báo trong cặp dấu
ngoặc { }; ở đây là biến thành phần x kiểu int và biến thành phần y
kiểu double. Lúc này, chương trình sẽ tạo ra một kiểu union mới có tên
là KieuSo, kiểu này có 2 biến thành phần là biến x kiểu int và biến y
kiểu double.
Như vậy, cú pháp chung của thao tác định nghĩa để tạo nên một
kiểu cấu trúc mới sẽ là:
union
TênKiểuUnion
{
Khai báo các biến thành phần;
};
7.2.3. Khai báo và sử dụng biến kiểu Union
Các biến union cần được khai báo trước khi sử dụng, tương tự như
biến cấu trúc và các biến bình thường khác. Xét chương trình sau:
183
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <conio.h>
union KieuSo
{
int x;
double y;
};
int main (void)
{
union KieuSo a;
printf(“Kich thuoc bien a: %d\n”,sizeof(a));
a.x = 5;
printf(“%d\n”,a.x);
a.y = 7.85;
printf(“%f\n”,a.y);
getch();
return 0;
}
Trong chương trình này, ở dòng lệnh số 14, câu lệnh union KieuSo
a; là câu lệnh khai báo biến a thuộc kiểu dữ liệu KieuSo là kiểu union.
Sau câu lệnh này, chương trình sẽ phát sinh cho biến a một vùng nhớ,
vùng nhớ này sẽ là vùng nhớ dùng chung cho cả hai biến thành phần
a.x và a.y; kích thước vùng nhớ sẽ là 8 bytes (bằng với kích thước vùng
nhớ của biến thành phần lớn nhất – biến y kiểu double) như minh họa
ở hình sau:
184
a.x
a
a.y
Vùng nhớ chung, kích thước 8 bytes
Câu lệnh printf(“Kich thuoc bien a: %d\n”,sizeof(a)); ở
dòng lệnh số 15 sẽ thực hiện in ra màn hình thông số kích thước vùng
nhớ của biến a, ta sẽ có kết quả là:
Kich thuoc bien a: 8
Tiếp tục, ở dòng lệnh số 17, câu lệnh a.x = 5; sẽ tiến hành lưu giá
trị số nguyên 5 vào vùng nhớ dùng chung. Lúc này, muốn truy xuất
đúng tới dữ liệu số nguyên đã lưu vào ô nhớ này, ta phải dùng tên gọi
a.x, như trong câu lệnh printf(“%d\n”,a.x); ở dòng lệnh số 18. Lệnh
in này sẽ in ra màn hình số 5 như hình dưới:
1
2
Kich thuoc bien a: 8
5
Ở dòng lệnh thứ 20, câu lệnh a.y = 7.85; sẽ thực hiện lưu số 7.85
vào vùng nhớ dữ liệu của biến a. Lúc này, dữ liệu mới sẽ thay thế dữ
liệu cũ trong vùng nhớ, vùng nhớ dùng chung này của a sẽ có định dạng
số thực và ta phải dùng tên gọi a.y để truy xuất tới dữ liệu số thực. Câu
lệnh printf(“%f\n”,a.y); ở dòng lệnh số 21 thực hiện việc in dữ liệu
số thực trong vùng nhớ biến a và ta có kết quả như bên dưới:
1
2
3
Kich thuoc bien a: 8
5
7.850000
7.3. KIỂU LIỆT KÊ (ENUMERATION)
7.3.1. Giới thiệu kiểu liệt kê
Kiểu liệt kê, enumeration – gọi tắt là kiểu enum, là một tập hợp
185
các hằng số kiểu số nguyên được biểu diễn dưới các tên gọi khác nhau.
Các giá trị hằng số nguyên bên trong một kiểu liệt kê được mặc định
bắt đầu bằng số 0, và sẽ tăng lên 1 đơn vị sau mỗi giá trị được liệt kê.
7.3.2. Định nghĩa một kiểu Enumeration mới
Tương tự như kiểu cấu trúc hoặc union, kiểu liệt kê là kiểu dữ liệu
do người dùng tự tạo và cần phải được định nghĩa trước khi sử dụng.
Đoạn lệnh định nghĩa một kiểu liệt kê sau:
1
2
3
4
enum MauSac
{
Den, Nau, Do, Cam, Vang, Luc, Lam, Tim, Xam, Trang
};
sẽ tạo ra một kiểu liệt kê mới có tên gọi là MauSac, các tên định
danh bên trong: Den, Nau, Do, Cam, Vang, Luc, Lam, Tim, Xam, Trang
sẽ tương ứng với các số nguyên lần lượt là: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Đơn
giản hơn, có thể hiểu các giá trị 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 bên trong kiểu
MauSac sẽ có tên định danh tương ứng là Den, Nau, Do, Cam, Vang,
Luc, Lam, Tim, Xam, Trang. Khác với kiểu cấu trúc hoặc kiểu union,
kiểu liệt kê không tồn tại các biến thành phần.
Như vậy, cú pháp để định nghĩa một kiểu liệt kê mới sẽ là:
enum
TênKiểuLiệtKê
{
Danh sách tên định danh;
};
7.3.3. Khai báo và sử dụng biến kiểu liệt kê
Thao tác định nghĩa kiểu liệt kê ở phần trước hoàn toàn chưa tạo
ra vùng nhớ dữ liệu nào cho người dùng. Muốn có vùng nhớ dữ liệu,
người dùng cần tiến hành khai báo các biến kiểu liệt kê. Một biến kiểu
liệt kê sẽ được khai báo tương tự như các biến bình thường khác. Hãy
xem xét chương trình sau:
186
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <conio.h>
23
}
enum MauSac
{
Den, Nau, Do, Cam, Vang, Luc, Lam, Tim, Xam, Trang
};
int main (void)
{
enum MauSac R;
printf(“Kich thuoc R: %d\n”,sizeof(R));
printf(“Gia tri R: %d\n”,R);
R = Den;
printf(“Gia tri R: %d\n”, R);
R = Cam;
printf(“Gia tri R: %d\n”, R);
getch();
return 0;
Ở dòng lệnh số 11, câu lệnh enum MauSac R; chính là câu lệnh
khai báo một biến kiểu MauSac - là kiểu liệt kê đã định nghĩa trước đó.
Câu lệnh này sẽ khai báo ra một biến có tên là R mang kiểu dữ liệu
MauSac. Sau câu lệnh này, chương trình sẽ cấp cho biến R một ô nhớ
có kích thước 4 bytes, và mang một giá trị ngẫu nhiên, như minh họa ở
hình dưới.
R
1824868116
Kích thước 4 bytes
187
Hai câu lệnh ở dòng lệnh số 12 và 13 sẽ in ra kích thước và giá trị
hiện tại của biến R, kết quả thu được sẽ là:
Kich thuoc R: 4
Gia tri R: 1824868116
Tiếp tục, ở dòng lệnh 15, câu lệnh R = Den; sẽ thực hiện gán một
giá trị dữ liệu mới vào R. Lúc này, tên định danh Den là một trong số
các tên định danh đã được liệt kê ở phần định nghĩa của kiểu MauSac
và tương ứng với số 0. Do đó, câu lệnh R = Den; sẽ làm cho giá trị biến
R bằng 0. Kết quả in ra màn hình của lệnh in printf(“Gia tri R:
%d\n”, R); ở dòng lệnh 16 sẽ như hình dưới.
Kich thuoc R: 4
Gia tri R: 1824868116
Gia tri R: 0
Tương tự, câu lệnh R = Cam; ở dòng lệnh 18 sẽ làm cho giá trị
biến R bằng 3 vì tên định danh Cam tương đương với số 3 như đã liệt kê
ở phần định nghĩa của kiểu MauSac. Kết quả của lệnh in printf(“Gia
tri R: %d\n”, R); ở dòng lệnh 19 sẽ như hình dưới.
Kich thuoc
Gia tri R:
Gia tri R:
Gia tri R:
R: 4
1824868116
0
3
7.4. BÀI TẬP
1. Hãy tìm và sửa lỗi trong các câu/đoạn lệnh sau:
a.
188
struct
{
int ma;
int namSX;
} thietBi;
b.
#include<stdio.h>
struct HinhChuNhat
{
float dai,rong;
};
int main(void)
{
printf(“Nhap chieu dai, rong:\n”);
scanf(“%f%f”,&HinhChuNhat.dai, &HinhChuNhat.
rong)
float dienTich = HinhChuNhat.dai * HinhChuNhat.
rong;
printf(“%f”,dienTich);
return 0;
c.
}
#include<stdio.h>
struct Diem
{
float giuaKy;
float cuoiKy;
};
int main(void)
{
struct Diem a;
scanf(“%f”,&giuaKy.a);
scanf(“%f”,&cuoiKy.a);
return 0;
}
189
d.
#include<stdio.h>
struct DuLieu a[3]
{
float nhietDo;
float doAm;
};
int main(void)
{
printf(“Nhap du lieu buoi sang, trua,
chieu:\n”);
int i;
for(i = 0; i < 3; i++)
{
scanf(“%f”,&a[i].nhietDo);
scanf(“%f”,&a[i].doAm);
}
return 0;
}
Cho chương trình dưới. Hãy phân tích và cho biết chương trình
thực hiện chức năng gì?
1
2
3
4
5
6
7
8
9
10
11
12
190
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
struct SinhVien
{
char hoTen[30];
float
diemToan;
float
diemLy;
float
diemHoa;
float diemTB;
};
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void
{
NhapDs( struct SinhVien *sv, int n)
int i; char tam[2];
puts (“Nhap du lieu cho danh sach Sinh
vien”);
for (i = 0; i < n; i++)
{
printf ( “Sinh vien thu : %d\n”,i);
puts ( “Ho ten:”);
gets ((sv+i)->hoTen);
puts (“Diem toan:”);
scanf (“%f”, &(sv+i)->diemToan);
puts (“Diem ly:”);
scanf (“%f”, &(sv+i)->diemLy);
puts (“Diem hoa:”);
scanf (“%f”, &(sv+i)->diemHoa);
gets(tam);
(sv+i)->diemTB = ((sv+i)->diemToan +
(sv+i)->diemLy + (sv+i)->diemHoa)/3;
}
}
void
{
InDs(struct SinhVien *sv, int n)
int i;
puts(“Danh sach sinh vien da nhap:”);
for (i = 0; i < n; i++)
{
puts ( “ho ten: “);
puts ((sv+i)->hoTen);
printf (“Diem tb: %f\n”,
(sv+i)->diemTB);
191
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
192
}
}
int main (void)
{
int n, i;
char tam[2];
float max;
struct SinhVien *a;
puts (“Nhap so luong sinh vien: “);
scanf (“%d”,&n);
gets(tam);
a = (SinhVien*) malloc (n*sizeof (SinhVien));
if (a == NULL)
puts (“Khong the cap phat bo nho.”);
else
{
NhapDs (a,n);
InDs (a, n);
max = a->diemTB;
for (i= 0; i <n; i++)
if( max < (a +i)->diemTB)
max = (a +i)->diemTB;
puts(“sinh vien co diem trung binh lon
nhat:”);
for (i= 0; i <n; i++)
if( max == (a +i)->diemTB)
{
puts( (a + i)->hoTen);
printf ( “Diem tb: %f\n”,
(a + i)->diemTB);
}
}
free (a);
getch();
return 0;
}
Tạo cấu trúc để quản lý điểm của sinh viên với các thông tin sau:
 Họ và tên
 Mã số sinh viên
 Điểm giữa kỳ
 Điểm cuối kỳ
 Điểm tổng kết, trong đó điểm giữa kỳ chiếm 50% và điểm cuối
kỳ chiếm 50%.
Viết chương trình nhập dữ liệu cho 10 sinh viên và thực hiện các
yêu cầu sau:
a. In ra danh sách sinh viên đậu, sinh viên đậu là sinh viên có điểm
tổng kết >= 5 và điểm giữa kỳ khác 0.
b. In ra sinh viên có điểm tổng kết thấp nhất.
c. Sắp xếp và in ra danh sách sinh viên đã nhập theo thứ tự tăng
dần của điểm tổng kết.
4. Viết chương trình như ở bài tập trên nhưng xử lý cho n sinh
viên, yêu cầu cấp phát động bộ nhớ cho mảng cấu trúc.
5. Tạo một kiểu cấu trúc để quản lý thiết bị, với các thông tin của
mỗi thiết bị cần quản lý là:
 Mã thiết bị (kiểu int)
 Tên thiết bị (chuỗi)
 Năm sản xuất (kiểu int)
 Trạng thái hiện tại (Tắt hay Mở)
Viết chương trình thực hiện các công việc sau:
a. Nhập thông tin của 10 thiết bị.
b. In ra danh sách các thiết bị đang Mở. Nếu tất cả các thiết bị đều
193
đang Tắt thì in thông báo “Tat ca thiet bi da Tat”.
c. Tìm và in ra danh sách các thiết bị được sản xuất từ năm 2018
trở về trước.
d. Cho phép người dùng tìm kiếm thông tin của một thiết bị nào
đó bằng cách nhập vào mã thiết bị để tìm kiếm. Nếu tìm thấy thì in
thông tin của thiết bị đã tìm thầy, nếu không tìm thấy thì in thông báo
“Khong tim thay”.
6. Viết chương trình như ở bài tập trên nhưng xử lý cho n thiết bị,
yêu cầu cấp phát động bộ nhớ cho mảng cấu trúc.
7. Tạo cấu trúc để quản lý danh bạ điện thoại với các nội dung:
 Họ tên
 Số điện thoại
 Địa chỉ
Viết chương trình quản lý danh bạ điện thoại với các chức năng sau:
a. In ra menu lựa chọn với các tủy chọn:
Nhập số 1: Thêm tên vào danh bạ
Nhập số 2: Tìm theo số điện thoại nhập vào
Nhập số 3: Thoát chương trình
b. Chương trình sẽ xử lý theo lựa chọn của người dùng sau khi
nhập số tương ứng với menu chức năng, trong đó khi xử lý chức năng
Thêm tên, danh bạ lưu được tối đa 40 số.
194
CHƯƠNG 8
TIỀN XỬ LÝ
Mục tiêu:
Sau khi kết thúc chương này, người đọc có thể:
- Khai báo được các thư viện bằng chỉ thị bao hàm tệp.
- Khai báo được các đối tượng thay thế bằng các chỉ thị định nghĩa.
- Sử dụng các chỉ thị điều khiển trình biên dịch để biên dịch hoặc
không biên dịch một đoạn chương trình.
8.1. GIỚI THIỆU
Tiền xử lý (preprocessor) là các xử lý đơn giản có chức năng xử
lý tập tin mã nguồn trước khi trình biên dịch đọc và biên dịch chúng.
Các bộ tiền xử lý trong ngôn ngữ C được bắt đầu với một số từ khóa
đặc biệt, bắt đầu các tiền xử lý là ký tự #. Bộ tiền xử lý thay thế các lệnh
tiền xử lý bằng các đoạn chương trình, hoặc các đoạn lệnh tương ứng và
đặt trong tập tin mã nguồn. Chương này giới thiệu đến người đọc một
số tiền xử lý thông dụng và cách sử dụng chúng để tạo ra các chương
trình tối ưu hơn. Các tiền xử lý không theo nguyên tắc giống như các
lệnh trong chương trình C.
Quá trình tiền xử lý được thực hiện trước khi quá trình biên dịch
diễn ra, được biểu diễn như hình 8.1.
Tệp mã nguồn
Tiền xử lý
Tệp mã nguồn
đã được tiền xử lý
Trình biên dịch
Hình 8.1. Sơ đồ khối quá trình tệp mã nguồn được tiền xử lý trước
khi đưa đến trình biên dịch
195
8.2. CHỈ THỊ BAO HÀM TỆP (INCLUDE)
Chỉ thị bao hàm tệp (include) xuất hiện trong hầu hết các chương
trình C. Chỉ thị bao hàm tệp được sử dụng để khai báo thư viện, đồng
thời được sử dụng để khai báo các tệp tiêu đề (header file). Trong các
ví dụ ở các chương trước đó, chúng ta thường bắt gặp chỉ thị bao hàm
tệp sau:
1
2
3
#include <stdio.h>
#include <math.h>
#include “myfile.h”
Khai báo trên cho phép bộ tiền xử lý đính kèm các mã nguồn của
các hàm trong thư viện stdio.h hoặc trong thư viện math.h khi hàm được
gọi. Khi sử dụng cặp ký hiệu < >, bộ tiền xử lý sẽ tự hiểu rằng các tệp
thư viện này được lưu trong thư mục mà trình biên dịch được cài đặt
trong hệ thống. Mặt khác, khi sử dụng cặp ký hiệu “ “, bộ tiền xử lý sẽ
tìm tệp có tên tương ứng trong cùng một thư mục với tệp mã nguồn, nơi
khai báo bao hàm tệp.
Ngoài việc khai báo các thư viện chuẩn, chỉ thị bao hàm tệp còn
được sử dụng rộng rãi khi khai báo tập tiêu đề. Trong các ví dụ trong tài
liệu, các chương trình khá đơn giản và được đặt trong cùng một tệp mã
nguồn. Thực tế các chương trình hệ thống phức tạp hơn, số lượng hàm
nhiều hơn và được chia ra nhiều tệp mã nguồn khác nhau. Chương trình
có thể gọi một hàm mà hàm đó được định nghĩa ở một tệp mã nguồn
khác. Lúc này, hàm cần được khai báo trong tệp tiêu đề.
Ví dụ: Viết chương trình thực hiện các yêu cầu sau: nhập mảng n
phần tử, in các phần tử mảng, tìm giá trị lớn nhất, nhỏ nhất trong các
phần tử mảng. Tính giá trị trung bình phần tử lớn nhất và phần tử nhỏ
nhất mảng. Hoán đổi phần tử lớn đầu tiên và phần tử cuối cùng của
mảng. In mảng theo thứ tự tăng dần. Mỗi chức năng đều được viết dưới
dạng 1 hàm.
196
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*chapter08.c*/
#include <stdio.h>
#include <stdlib.h>
void Sapxepmang(int a[],int n);
void Inmang(int a[],int n);
void Nhapmang(int a[],int n);
int Timmax (int *a,int n);
int Timmin (int *a,int n);
void Hoanvi(int *x, int *y);
float Tinhtrungbinh(int a, int b);
void main (void)
{
int *arr;
int n ;
int max, min;
float tb;
do{
printf(“ Nhap n “);
scanf(“%d”,&n);
}while(n<=0);
arr = (int*) malloc (n*sizeof(int));
Nhapmang(arr,n);
printf(“cac phan tu mang :\n”);
Inmang(arr,n);
max = Timmax(arr,n);
min = Timmin(arr,n);
printf(“\nmax = %d, min = %d”,max,min);
tb=Tinhtrungbinh(max,min);
printf(“\ntrung binh = %.2f “,tb);
Hoanvi(&arr[0],&arr[n-1]);
printf(“\ncap nhat mang :\n”);
Inmang(arr,n);
Sapxepmang(arr,n);
printf(“\nmang sau khi sap xep :\n”);
Inmang(arr,n);
}
void Sapxepmang(int a[],int n)
197
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
198
{
int i,j,tam ;
for (i=0;i<n-1 ; i++)
for(j=i+1; j<n;j++)
{
if (a[i]>a[j])
{
tam = a[i];
a[i]= a[j];
a[j]=tam;
}
}
}
void Inmang(int a[],int n)
{
int i;
for (i=0;i<n ; i++)
printf(“%d, “,a[i]);
}
void Nhapmang(int a[],int n)
{
int i;
for (i=0;i<n ; i++)
{
printf(“ \na[%d] = “,i);
scanf(“%d”,&a[i]);
}
}
int Timmax (int *a,int n)
{
int i,max ;
max = a[0] ;
for (i=1;i<n;i++)
max = (max<a[i])?a[i]:max ;
return max ;
}
int Timmin (int *a,int n)
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
{
}
int i,min ;
min = a[0] ;
for (i=1;i<n;i++)
min = (min>a[i])?a[i]:min ;
return min ;
void Hoanvi(int *x, int *y)
{
int tam;
tam = *x;
*x = *y;
*y = tam;
}
float Tinhtrungbinh(int a, int b)
{
return (float)(a+b)/2 ;
}
Kết quả khi chạy chương trình với n được nhập n = 6, các phần tử
mảng được nhập và kết quả tương ứng như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Nhap n 6
a[0] = 9
a[1] = 4
a[2] = 2
a[3] = 7
a[4] = 5
a[5] = 6
cac phan tu mang :
9, 4, 2, 7, 5, 6,
max = 9, min = 2
trung binh = 5.50
cap nhat mang :
6, 4, 2, 7, 5, 9,
mang sau khi sap xep :
2, 4, 5, 6, 7, 9,
199
Trong ví dụ trên, chương trình chính được viết đầu tiên. Có 7 hàm
do người lập trình định nghĩa. Các hàm được khai báo từ dòng thứ 4 đến
dòng thứ 10, và được định nghĩa tương ứng bên dưới.
Khi số lượng hàm cũng như số lượng dòng lệnh chương trình
tăng lên, người ta thường chia ra thành nhiều tệp mã nguồn. Điều này
hữu ích cho quá trình kiểm thử và phát triển chương trình. Khi các hàm
được viết trong một tệp, các hàm từ các tệp khác cũng có thể sử dụng
các hàm này. Như vậy, việc tách riêng các hàm giúp cho việc chia sẻ
các hàm thuận lợi hơn.
Ví dụ trên sẽ được viết lại với 3 tệp, tệp chứa hàm main sẽ gọi
các hàm trong 2 tệp còn lại. Các tệp đều nằm trong cùng một thư mục.
Chương trình được sắp xếp lại như sau bao gồm một tệp chứa chương
trình chính.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
200
/*chapter08.c*/
#include <stdio.h>
#include <stdlib.h>
#include “thaotacmang.h”
#include “tinhtoan.h”
void main (void)
{
int *arr;
int n ;
int max, min;
float tb;
do{
printf(“ Nhap n “);
scanf(“%d”,&n);
}while(n<=0);
arr = (int*) malloc (n*sizeof(int));
Nhapmang(arr,n);
printf(“cac phan tu mang :\n”);
Inmang(arr,n);
max = Timmax(arr,n);
min = Timmin(arr,n);
22
23
24
25
26
27
28
29
30
31
printf(“\nmax = %d, min = %d”,max,min);
tb=Tinhtrungbinh(max,min);
printf(“\ntrung binh = %.2f “,tb);
Hoanvi(&arr[0],&arr[n-1]);
printf(“\ncap nhat mang :\n”);
Inmang(arr,n);
Sapxepmang(arr,n);
printf(“\nmang sau khi sap xep :\n”);
Inmang(arr,n);
}
Trong chương trình trên, các hàm thao tác trên mảng được đặt
trong thư tệp thaotacmang.h . Tệp này có nội dung như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*thaotacmang.h*/
void Sapxepmang(int a[],int n)
{
int i,j,tam ;
for (i=0;i<n-1 ; i++)
for(j=i+1; j<n;j++)
{
if (a[i]>a[j])
{
tam = a[i];
a[i]= a[j];
a[j]=tam;
}
}
}
void Inmang(int a[],int n)
{
int i;
for (i=0;i<n ; i++)
printf(“%d, “,a[i]);
}
void Nhapmang(int a[],int n)
{
int i;
201
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
}
for (i=0;i<n ; i++){
printf(“ \na[%d] = “,i);
scanf(“%d”,&a[i]);
}
int
{
}
Timmax (int *a,int n)
int
{
}
Timmin (int *a,int n)
int i,max ;
max = a[0] ;
for (i=1;i<n;i++)
max = (max<a[i])?a[i]:max ;
return max ;
int i,min ;
min = a[0] ;
for (i=1;i<n;i++)
min = (min>a[i])?a[i]:min ;
return min ;
Tương tự, các hàm tính toán được tách riêng sang một tệp được
đặt tên tinhtoan.h . Tệp này có nội dung như sau:
1
2
3
4
5
6
7
8
9
10
11
12
202
/*tinhtoan*/
void Hoanvi(int *x, int *y)
{
int tam;
tam = *x;
*x = *y;
*y = tam;
}
float Tinhtrungbinh(int a, int b)
{
return (float)(a+b)/2 ;
}
Trong ví dụ trên, các hàm được đặt trong các tệp khác nhau, và
trong hàm chương trình chính sử dụng chỉ thị bao hàm tệp #include để
khai báo cho quá trình tiền xử lý. Trong thực tế, các hàm được định
nghĩa trong các tệp mã nguồn (.c), đồng thời được khai báo trong tệp
tiêu đề (.h). Trong trường hợp này thì tập tin điều khiển trình biên dịch
(makefile) phải được thay đổi để tạo liên kết các tệp.
8.3. CHỈ THỊ ĐỊNH NGHĨA #define
Chỉ thị định nghĩa #define được sử dụng để định nghĩa một đối tượng
thay thế. Sau khi định nghĩa, đối tượng được dùng trong toàn bộ các hàm
cùng trong một tệp mà đối tượng được định nghĩa. Chỉ thị #define được
dùng với 2 mục đích khác nhau; định nghĩa một thay thế hoặc định nghĩa
một macro. Macro được hiểu là một đoạn code. Macro chỉ là một đoạn
code được định nghĩa ngắn gọn với một chuỗi. Macro không phải làm hàm.
Cú pháp sử dụng chỉ thị #define định nghĩa một chuỗi tương
đương như sau:
#define
tên
giáTrịThayThế
Ví dụ sử dụng chỉ thị #define định nghĩa hằng số và chuỗi như sau:
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#define PI 3.14
#define hello “xin chao \n”
void main (void)
{
float r = 2 ;
printf(hello) ;
printf (“Dien tich hinh tron la %.2f”,r*r*PI);
}
Dòng thứ 2 là thao tác định nghĩa một hằng số PI. Trong chương
trình, khi sử dụng chuỗi có tên PI, trình biên dịch tự động thay thế bằng giá
trị 3.14 như đã được định nghĩa. Tương tự, dòng 3 định nghĩa một chuỗi
hello tương đương với một chuỗi “xin chao \n”. Khi gặp chuỗi hello tại
203
dòng 7, chương trình tự động thay thế hello bằng chuỗi “xin chao \n”.
Kết quả sau khi chạy chương trình trên là:
1
2
xin chao
Dien tich hinh tron la 12.56
Chỉ thị #define còn được sử dụng để định nghĩa một đoạn chương
trình, được xem như một macro. Ví dụ, định nghĩa macro timmax như sau:
1
2
3
4
5
6
7
8
#include <stdio.h>
#define timmax(x,y) ((x)>(y) ? (x) : (y))
void main (void)
{
int a = 5, b = 9 ;
int max = timmax(a,b);
printf (“max = %d”,max);
}
Macro này sẽ được sử dụng tương tự như hàm với chức năng tìm
max trong 2 số, tuy nhiên về cấu trúc và cách thức hoạt động macro
không giống hàm, mặc dù ngôn ngữ C cho phép định nghĩa Macro với
tham số. Macro được xử lý ở bước tiền xử lý. Bộ tiền xử lý thay thế
macro với đoạn code được định nghĩa.
So sánh macro trên với hàm sau đây:
1
2
3
4
int timmax1 (int a, int b)
{
return a>b?a:b ;
}
Trong hàm timmax1, kiểu dữ liệu cho tham số và giá trị trả về được
định nghĩa là kiểu số nguyên. Như vậy, khi gọi hàm timmax1, tham số
đưa vào cũng phải là kiểu số nguyên. Còn đối với macro timmax, đoạn
code thay để có thể nhận bất kỳ kiểu dữ liệu nào.
8.4. CHỈ THỊ ĐIỀU KHIỂN TRÌNH BIÊN DỊCH
Bộ tiền xử lý của ngôn ngữ C cung cấp các chỉ thị điều kiện điều
204
khiển quá trình biên dịch bao gồm: #if, #elif, #else, #ifdef,
#ifndef, và #endif. Các chỉ thị này điều khiển trình biên dịch có thể
biên dịch hoặc không biên dịch một đoạn chương trình.
Xét ví dụ sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#define INTERGER
#ifdef INTERGER
int timmax(int a, int b)
{
return a>b ? a:b ;
}
#else
float timmax(float a, float b)
{
return a>b ? a:b ;
}
#endif
void main (void)
{
int a = 5, b = 9 ;
int max = timmax(a,b);
printf (“max = %d”,max);
}
Chương trình trên xây dựng hàm tìm giá trị lớn nhất. Tuy nhiên,
2 hàm cùng tên và khác kiểu giá trị cho tham số và giá trị trả về. Chỉ
một trong 2 hàm được biên dịch. Lúc này bộ tiền xử lý sẽ kiểm tra
xem đoạn chương trình nào được biên dịch. Chương trình bắt gặp
chuỗi INTERGER được định nghĩa ở đầu chương trình ở dòng 2, vậy
nên đoạn chương trình từ dòng thứ 4 đến dòng thứ 7 được biên dịch,
ngược lại nếu chuỗi INTERGER không được định nghĩa ở dòng 2, đoạn
chương trình từ dòng 9 đến dòng thứ 12 sẽ được biên dịch. Các chỉ thị
205
điều khiển trình biên dịch #ifdef, #else, #endif sẽ kiểm tra một đối
tượng đã được định nghĩa bằng chỉ thị #define hay chưa để quyết định
đoạn chương trình nào sẽ được biên dịch.
Tương tự, cũng có thể dùng các chỉ thị điều khiển quá trình biên
dịch với #if, #else, và #endif. Ví dụ:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#define INTERGER 1
#if INTERGER == 1
int timmax(int a, int b)
{
return a>b ? a:b ;
}
#else
float timmax(float a, float b)
{
return a>b ? a:b ;
}
#endif
void main (void)
{
int a = 5, b = 9 ;
int max = timmax(a,b);
printf (“max = %d”,max);
}
Trong chương trình này, bộ tiền xử lý sẽ kiểm tra phát biểu sau
#if trả về giá trị đúng hay sai để quyết định đoạn chương nào được biên
dịch, đoạn chương trình nào bị bỏ qua.
8.5. BÀI TẬP
1. Định nghĩa macro thực thi hoán vị 2 số, viết chương trình ứng
dụng và so sánh macro đã viết với hàm hoán vị đã viết ở chương 6.
2. Định nghĩa macro in ra các số lẻ từ 0 đến n, ứng dụng viết
chương trình nhập vào n, in ra các số lẻ từ 0 đến n.
206
TÀI LIỆU THAM KHẢO
1. Paul Deitel and Harvey Deitel, C How to program, 6th Edition,
Prentice Hall, 2010.
2. Brian W. Kernighan and Dennis M. Ritchie, The C Programming
Language, Prentice Hall, 1988.
3. Peter D. Hipson, Advanced C, SAMS Publishing, 1992.
4. Dan Gookin, C for Dimmies, Wiley Publishing, InC., 2004.
5. John W. Perry, Advanced C Programming by Example, Pws
Pub Co, 1998
6. Richard Reese, Understanding and Using C Pointers, O’Reilly,
2013.
7. K. N. King, C Programming – A modern Approach, W. W.
Norton & Company, 2008.
8. Zed A. Shaw, Lear C the Hard Way, Addison-Wesley, 2015.
207
CHÍNH SÁCH CHẤT LƯỢNG
Không ngừng nâng cao chất lượng dạy, học, nghiên cứu khoa học
và phục vụ cộng đồng nhằm mang đến cho người học những điều kiện tốt nhất
để phát triển toàn diện các năng lực đáp ứng nhu cầu phát triển và hội nhập quốc tế.
Download