You are on page 1of 211

0 0

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

0 0
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.

0 0
TRƯƠNG NGàC SƠN,
GIÁO TRÌNH
LÊ MINH,
NGÔN NGĀ L¾P TRÌNH C TRƯƠNG NGàC HÀ,
(Ngành kÿ thu¿t máy tính)
.
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!

0 0
2

0 0
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…

0 0
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ả

0 0
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

0 0
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

0 0
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

0 0
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

0 0
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ó:

0 0
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

0 0
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

0 0
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: ;tell linker entry point


mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;昀椀le descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel

mov edx,9 ;message length


mov ecx,s2 ;message to write
mov ebx,1 ;昀椀le descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel

mov eax,1 ;system call number (sys_exit)


int 0x80 ;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

0 0
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

0 0
đã 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

0 0
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

0 0
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

True
a>b?
False

False
True b>c? a>c?

False True

Print c Print b Print a

Stop

16

0 0
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
− b+ d
x1 =
2*a

−b − d Print:
x2 = Non-real root
2 *a

Print: x1, x2

Stop

17

0 0
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
0 0
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

0 0
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
0 kiểu
0 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

0 0
CHƯƠNG 1
GIỚI
0 THIỆU
0
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ó:

0 0
0 0
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
0 dưới
0 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

0 0
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 0 0 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

0 0
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: ;tell linker entry point


mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;昀椀le descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel

mov edx,9 ;message length


mov ecx,s2 ;message to write
0 0
mov ebx,1 ;昀椀le descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel

mov eax,1 ;system call number (sys_exit)


int 0x80 ;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

0 0
Ví dụ, một chương trình viết bằng ngôn ngữ cấp cao như sau:
#include <stdio.h> 0 0

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

0 0
0 0
đã 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ó
0 0
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

0 0
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
0 trình
0 cộng 2 số được nhập từ bàn phím.

Start
Start

Declare variables num1, num2, and sum

Read num1 and num2

sum ←num1+num2

Display sum

Stop

15

0 0
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. 0 0
Start

Declare variables a, b, c

Read a,b,c

True
a>b?
False

False
True b>c? a>c?

False True

Print c Print b Print a

Stop

16

0 0
Ví dụ 1.3. Lưu đồ cho bài 0toàn 0giả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
− b+ d
x1 =
2*a

−b − d Print:
x2 = Non-real root
2 *a

Print: x1, x2

Stop

17

0 0
0 0
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:

0 0
18

0 0
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:

0 0
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

0 0
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.
0 0
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

0 0
0 0
SST dòng
Nội dung chương trình
lệnh
1 /* 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
0 0
0 0
Ở 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 昀椀le thư viện stdio.h vào chương trình, đây là thư viện xu
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ừ
0 0
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

0 0
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
0 0
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

0 0
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 #include <stdio.h>
2 #include <conio.h>0 0
3
4 int main (void)
5 {
6 int a; //khai bao bien a, de luu so thu nhat
7 int b; //khai bao bien b, de luu so thu hai
8 int tong;//khai bao bien tong, de luu ket qua
9
10 printf (“Nhap so thu nhat : \n”);
11 scanf(“%d”,&a); //nhap so nguyen tu ban phim
12
13 printf(“Nhap so thu hai: \n”);
14 scanf(“%d”,&b); //nhap so nguyen tu ban phim
15
16 tong = a + b; //tinh tong
17 printf(“Tong la: %d \n”,tong); //in ket qua
18 getch(); //cho nhap 1 ky tu bat ky
19 return 0;
20 }
21

Kết quả sau khi thực thi chương trình như sau:
1 Nhap so thu nhat :
2 5
3 Nhap so thu hai:
4 7
5 Tong la: 12

24

0 0
0 0

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à

0 0 25
0 0
&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
0 0
để đư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

0 0
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 (de昀椀ne).
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
0 gồm
0 các khai báo thư viện, các định
nghĩa. Ví dụ:
#include <stdio.h>

#de昀椀ne 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

0 0
0 0
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 昀氀oat 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

0 0
0 0
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
bien1
Tên gọi hợp lệ
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ệ:

2a sai vì bắt đầu bằng số


Tên gọi if sai vì trùng với từ khóa
không hợp lệ 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.
0 0
29
0 0
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.

a 0

Như vậy, cú pháp chung


0 của
0 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

0 0
để 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
Định dạng dữ liệu Ví dụ
dữ liệu vùng nhớ biến
char 1 byte A
Kiểu ký tự
unsigned char 1 byte 0 0 b
2 byte hoặc 4 -3569
int
byte +137
2 byte hoặc 4
unsigned int 23052
byte
+423
short 2 byte Kiểu số nguyên
-7
unsigned short 2 byte 153
-2456783
long 4 byte
+4539847
unsigned long 4 byte 345231
昀氀oat 4 byte 5.6
double 8 byte Kiểu số thực 245.78
long double 10 byte 3.1
Bảng 1.2. Các kiểu dữ liệu trong C

31

0 0
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 ở
0 0
đầ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 昀椀le1.c có nội dung như sau:
1 #include <stdio.h>
2 #include <conio.h>
3 int a,b,tong,hieu ;
4 void tinhhieu(){
5 hieu = a - b; //tinh hieu
6 printf(“Hieu la: %d \n”,hieu);
7 }
8 int main (void)
9 {
10 printf (“Nhap so thu nhat : \n”);
11 scanf(“%d”,&a); //nhap so nguyen tu ban phim
12 printf(“Nhap so thu hai: \n”);
13 scanf(“%d”,&b); //nhap so nguyen tu ban phim
14 tong = a + b; //tinh tong
15 printf(“Tong la: %d \n”,tong); //in ket qua
16 getch(); //cho nhap 1 ky tu bat ky
17 return 0;
18 }

32

0 0
0 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 #include <stdio.h>
2 #include <conio.h>
3 void tinhhieu(){
4 int a,b,hieu ;
5 hieu = a - b; //tinh hieu
6 printf(“Hieu la: %d \n”,hieu);
7 }
8 int main (void)
9 {
10 int a,b,tong ;
11 printf (“Nhap so thu nhat : \n”);
12 scanf(“%d”,&a); //nhap so nguyen tu ban phim
13 printf(“Nhap so thu hai: \n”);
14 scanf(“%d”,&b); //nhap so nguyen tu ban phim
15 tong = a + b; //tinh tong
16 printf(“Tong la: %d \n”,tong); //in ket qua
17 tinhhieu();
18 getch(); //cho nhap 1 ky tu bat ky
19 return 0;
20 }
0 0
33

0 0
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 昀氀oat PI = 3.14;

là một câu lệnh khai báo


0 hằng
0 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; 昀氀oat là kiểu dữ liệu c
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 昀氀oat cho hằng PI và khởi tạo g
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

0 0
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 int d = 5;
2 昀氀oat C;
3 C = d*PI;
0 thống
Ngôn ngữ C sử dụng các hệ 0 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ơ Ký tự sử dụng biểu diễn số Ví dụ: (giá trị 240 Biểu diễn trong
số hệ thập phân) 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

0 0
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.
0 0
Nhóm phép Ký hiệu trong
Ý nghĩa Ví dụ
toán ngôn ngữ C
int a;
= Phép gán
a = 5;
+ Cộng
- Trừ
Số học
* Nhân
/ Chia a = 5/2;
Chia lấy phần
% a = 5%2;

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:
a
y= 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

0 0
0 0
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;
昀氀oat 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;
昀氀oat b = 2;
昀氀oat 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 Ký hiệu trong
Ý nghĩa Ví dụ
toán ngôn ngữ C
> 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
0 0
Bảng 1.4. Các phép toán quan hệ trong ngôn ngữ C
37

0 0
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 Ký hiệu trong
Ý nghĩa Ví dụ
toán 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 B 0 A&&B
0 A||B !A
False False False False True
False True False True True
True False False True False
True True True True 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

0 0
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.
Ký hiệu Ví dụ
Nhóm phép
trong ngôn Ý nghĩa a = 0b00001111 ;
toán
ngữ C b= 0b11101000;
c= a&b;
& AND
→c = 00001000
0 0 c= a|b;
| OR
→c = 11101111
c= a^b;
^ XOR
→c = 11100111
Bit-wise
c= ~a;
~ NOT
→c = 11110000
c= a<<1;
<< Dịch trái
→c = 00011110
c= a>>2;
>> Dịch phải
→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

0 0
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à
0 0
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:
Ký hiệu
Nhóm phép
trong ngôn Ý nghĩa Ví dụ
toán
ngữ C
b = 3;
++ Tăng 1 đơn vị b++;
int c = ++b;
b = 5;
Phép toán –– Giảm 1 đơn vị
c = b – – + 2;
khác
Toán tử điều
? max= (a>b)?a:b;
kiện
+= – = *= int d = 1;
Gán mở rộng
/= %= 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

0 0
0 0
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…). 0 0

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

0 0
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
0 0
%f mã định dạng kiểu số thực
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 printf(“%d “, a[i]);
5 }
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

0 0
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

0 0
1 int a[6]={1,7,9,6,8,2};
2 int min, i;
3 min = a[0];
4 for (i=1; i<6; i++)
5 {
6 if(min > a[i])
7 min = a[i];
8 }
9 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 tam,i,j;
3 for (i=0; i<5; i++)
4 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 printf (“%d”, a[i]);
14 }

100

0 0
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

0 0
đó, 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 #include <stdio.h>
2 #include <conio.h>
3
4 int main (void)
5 {
6 int a[10], i, j, tam;
7 for (i=0; i<10; i++)
8 {
9 printf(“nhap phan tu thu %d: “, i) ;
10 scanf(“%d”,&a[i]);
11 }
12
13 for (i=0; i<9; i++)
14 for (j = i+1; j<10; j++)
15 if (a [i]> a[j])
16 {
17 tam = a[i];
18 a[i]= a[j];
19 a[j]= tam;
20 }
21 for (i=0; i<10; i++)
22 {
23 printf(“%d”, a[i]);
24 }
25 getch();
26 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

0 0
đ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 tên mảng [số hàng][số c฀฀t];
Ví dụ 4.8: int 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

0 0
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 b[0][0]=1
2 b[0][1]=2
3 b[1][0]=3
4 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 a1[0][0]=1
2 a1[0][1]=2
3 a1[0][2]=3
4 a1[1][0]=4
5 a1[1][1]=5
6 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 a2[0][0]=1
2 a2[0][1]=2
3 a2[0][2]=0
4 a2[1][0]=4
5 a2[1][1]=0
6 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

0 0
Ví dụ 4.10:
1 int a[5][10];
2 int i, j;
3 for (i=0; i<5; i++)
4 for(j=0; j<10; j++)
5 {
6 printf(“Nhap phan tu %d %d”,i, j);
7 scanf(“%d”, &a[i][j]) ;
8 }

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 int a[3][2]={{1,2},{2,3},{3,4}};
2 int i,j;
3 for (i=0;i<3;i++)
4 {
5 for(j=0;j<2;j++)
6 {
7 printf (“%d”, a[i][j]);
8 }
9 printf(“\n”);
10 }

105

0 0
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 #include <stdio.h>
2 #include <conio.h>
3 int main (void)
4 {
5 int a[5][4], i,j,max;
6 for (i=0;i<5;i++)
7 for(j=0;j<4;j++)
8 {
9 printf(“Nhap phan tu %d %d: “,i, j);
10 scanf(“%d”, &a[i][j]) ;
11 }
12
13 for (i=0; i<5; i++)
14 {
15 max = a[i][0];
16 for(j=0;j<4;j++)
17 {
18 if (max < a[i][j])
19 max = a[i][j];
20 }
21 printf(“%d\n”, max);
22 }
23 getch();
24 return 0;
25 }
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

0 0
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

10 phần tử
Cú pháp 2: char
 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 #include <stdio.h>
2
3 int main(void)
4 {
5 char s[40];
6 printf(“Hay nhap 1 chuoi: \n”);

107

0 0
7 gets(s);
8
9 printf(“Chuoi da nhap la: \n”);
10 puts(s);
11 return 0;
12 }

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 Hay nhap 1 chuoi:
2 Xin chao!
3 Chuoi da nhap la:
4 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 #include <stdio.h>
2
3 int main(void)
4 {
5 char s[10] = “Hello”;
6 int i,dem;
7 dem = 0;
8 for(i = 0; i < 10; i++)
9 if(s[i] == ‘e’)
10 dem++;
11 printf(“Co %d ky tu e.”,dem);
12 return 0;
13 }

108

0 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 tên mảng chui [số hàng][số c฀฀t]= {“n฀฀i


dung”};
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

0 0
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 char s[5][10];
2 int i;
3 for (i=0;i<5;i++)
4 {
5 puts (“Nhap chuoi”);
6 gets (s[i]);
7 }
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 char s[5][10]={“Mot”, “Hai”, “Ba”, “Bon”, “Nam”};
2 int i,j;
3 for (i = 0; i < 5; i++)
4 for (j=0; j <10; j++)
5 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 昀氀oat f;
2 char str[] = “12345.67”;
3 f= atof(str);
4 puts(str);
5 printf (“%.2f”, f);

110

0 0
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 int i;
2 char str[] = “12345.67”;
3 i = atoi(str);
4 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 int number = 12345;
2 char string[25];
3 itoa(number, string, 10);
4 puts(string);

Kết quả:
12345

Ví dụ 4.21b: chuyển đổi số sang chuỗi theo cơ số 2:

111

0 0
1 int number = 12345;
2 char string[25];
3 itoa(number, string, 2);
4 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 int len, i;
2 char string[] = “XIN CHAO”;
3 len = strlen(string);
4 for (i = 0; i < len; i++)
5 string[i] = tolower(string[i]);
6 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 int len, i;
2 char string[] = “xin chao”;
3 len = strlen(string);
4 for (i = 0; i < len; i++)
5 string[i] = toupper(string[i]);
6 puts(string);

112

0 0
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 char dich[25];
2 char s2[]=” “,s3[]=”chao”,s1[]=”Xin”;
3 strcat(dich, s1);
4 strcat(dich, s2);
5 strcat(dich, s3);
6 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 char dich[25];
2 char nguon[]=”Xin chao”;
3 strcpy(dich, nguon);
4 puts(dich);

Kết quả khi chạy chương trình:

113

0 0
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 int a, b, c;
2 char s1[] =”aaa”,s2[]=”bbb”,s3[]= “aaa”;
3 a=strcmp(s1, s2); //ket qua tra ve - 1
4 printf(“%d”,a);
5 b=strcmp(s1, s3); //ket qua tra ve 0
6 printf(“\n %d”,b);
7 c=strcmp(s2, s3); //ket qua tra ve 1
8 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); //Phải khai báo thư viện
string.h
Hàm có chức năng là chuyển chuỗi s sang chữ thường
Ví dụ 4.26:
1 char s[10] = “Xin Chao”;
2 puts(strlwr(s));

114

0 0
Kết quả khi chạy chương trình:
xin chao

Hàm strupr
Cú pháp: char *strupr(char *s); // Phải khai báo thư viện
string.h
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); //Phải khai báo thư
viện string.h

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

0 0
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. 昀氀oat 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, 昀氀ag=0;
for (i=0; i<10; i++)
{
printf(“nhap phan tu thu %d: “, i);

116

0 0
scanf(“%d”,&a[i]);
}
for (i=0; i<10; i++)
{
if ((a[i]%2 == 1)&&(a[i]>=20))
{
昀氀ag = 1;
break;
}
}
if (昀氀ag == 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

0 0
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

0 0
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). 0 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
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 #include <stdio.h>
2 int main(void)
3 {
4 int a = 5, b = 6, c = 7;
5 if (c > a)
6 {
7 a = a + b;
8 b = b + 1;
9 }
10 printf(“Gia tri cua a la %d cua b la %d”,a,b);
11 getch();
12 return 0;
13 }

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

0 0
0 0
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 #include <stdio.h>
2 int main(void)
3 {
4 int a = 5, b = 6, c = 7;
5 if (c < a)
6 {
7 a = a + b;
8 b = b + 1;
9 }
0 0
10 printf(“Gia tri cua a la %d cua b la %d”,a,b);
11 getch();
12 return 0;
13 }

53

0 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 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 #include <stdio.h>
2 int main(void)
3 {
4 int a = 5, b = 6, c = 7;
5 if (a > c)
6 a = a + b;
7 b = b + 1;
8 if (a < b)
0 0
9 {
10 c = b - a;
11 b = c + 5;
12 }
13 printf(“Gia tri cua a la %d cua b la %d va c la
14 %d”,a,b,c);
15 getch();
16 return 0;
17 }

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

0 0
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 0 0
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

0 0
Giải thích

Nếu biểu thức là đúng0 thì thực
0 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 #include <stdio.h>
2 int main(void)
3 {
4 int a = 4, b = 2, c = 5;
5 c -= 2;
6 if (a > c)
7 a = a + b;
8 b = b + c;
9 if (a < b)
10 {
11 c = b - a;
12 b = c + 5;
13 }
14 else
15 a = a + 1;
16 b = b +1;
17 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

0 0
0 0
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;
}
…………………
0 0
else if (biểu thức n-1)
{
Khối lệnh n-1;
}

57

0 0
else
{
Khối lệnh n;
}

Ho愃฀t đ฀฀ng


0 0
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

0 0
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
0
sau:
0

1 #include <stdio.h>
2 int main(void)
3 {
4 int a = 4, b = 2, c = 5;
5 if ((a > c)|| (a < b) && (b > c))
6 a = a - 2;
7 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

0 0
Giải thích hoạt động:
0 0
Trong chương trình này, các dòng từ 5 đến dòng 13 là khác với
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 int timmax(int a, int b)
2 {
3 int max;
4 if (a>b) max = a;
5 else max = b;
6 return max;
7 }

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

0 0
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 int timmax(int a, int b)
2 {
3 int max;
4 if (a>b) max = a;
5 else max = b;
6 return max;
7 }
8 void main (void)
9 {
10 int a = 5, b = 7, c=6, d= 9, max1, max2, max;
11 max1 = timmax (a,b);
12 max2 = timmax(c,d);
13 max = timmax(max1, max2);
14 printf(“ max = :%d”,max);
15 }

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

0 0
Để 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;
void main (void){ else max = b;
return max;
int a=5,b=7,c=6,d=9,max1,max2,max; }

max1 = timmax (a,b);


int timmax(int a, int b)
max2 = timmax(c,d); {
int max;
max = timmax(max1, max2); if (a>b) max = a;
else max = b;
printf( " max = :%d",max); return max;
}
}
int timmax(int a, int b)
{
int max;
if (a>b) max = a;
else max = b;
return 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

0 0
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 #include<stdio.h>
2 #include <stdlib.h>
3 int giaithua(int n)
4 {
5 int i,S=1 ;
6 for (i=1;i<=n;i++)
7 S *=i ;
8 return S;
9 }
10 void main (void)
11 {
12 int n,Sn;
13 printf(“Nhap n : “);
14 scanf(“%d”,&n);
15 Sn = giaithua(n);
16 printf(“ giai thua cua %d la %d”,n,Sn);
17 }

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

0 0
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 KiemtraSNT(int n)
4 /*
5 Hàm trả về 1 nếu là số nguyên tố, ngược lại trả về 0
6 */
7 {
8 int i ;
9 for (i=2;i<n;i++)
10 {
11 if (n%i==0) return 0;
12 }
13 return 1 ;
14 }
15 void main (void)
16 {
17 int n,Snt;
18 printf(“Nhap n : “);
19 scanf(“%d”,&n);
20 Snt = KiemtraSNT(n) ;
21 if (Snt)
22 printf(“ %d la So nguyen to” ,n);
23 else
24 printf(“ %d khong phai la So nguyen to”
25 ,n);
26 }

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

0 0
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

0 0
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

0 0
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 int GetEven(void)
2 { int n;
3 do{
4 printf(“Nhap mot so chan :”);
5 scanf(“%d”,&n);
6 }
7 while(n%2!=0) ;
8 return n ;
9 }
10 void main()
11 {
12 int a;
13 a= GetEven();
14 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

0 0
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 void inchu(void)
2 {
3 int i;
4 for (i = 0; i< 5; i++)
5 puts(“Hello”);
6 }
7 void main (void)
8 {
9 inchu( );
10 }

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

0 0
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 昀椀le). Ví dụ về khai báo
hàm như sau:

147

0 0
1 #include<stdio.h>
2 #include <stdlib.h>
3 int timmax(int a, int b);// khai b愃Āo hàm
4 void main (void)
5 {
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 max = b;
17 return 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

0 0
1 #include <stdio.h>
2 #include <conio.h>
3 void hoanvi(int a, int b) ;
4 void main (void)
5 {
6 int a = 5, b =6;
7 hoanvi(a , b);
8 printf (“a = %d , a= %d”, a,b);
9 }
10
11 void hoanvi(int a, int b)
12 {
13 int tam;
14 tam = a;
15 a = b;
16 b = tam;
17 }

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

0 0
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 #include <stdio.h>
2 void hoanvi(int *x, int *y) ;
3 void main (void)
4 {
5 int a = 5, b =6;
6 hoanvi(&a , &b);
7 printf (“a = %d , a= %d”, a,b);
8 }
9
10 void hoanvi(int *x, int *y)
11 {
12 int tam;
13 tam = *x;
14 *x = *y;
15 *y = tam;
16 }

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

0 0
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 int func(int *a, int n);
2 //hoặc
3 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 #include <stdio.h>
2 #include <conio.h>
3 int Timmax (int *a,int n) ;
4 void main (void)
5 {
6 int arr[] = {6,13,2,3,9,7,5,18,4,12};
7 int max ;
8 max = Timmax(arr,10);
9 printf (“Max = %d “,max);
10 }
11 int Timmax (int *a, int n)
12 {
13 int i,max ;
14 max = a[0] ;
15 for (i=1;i<n;i++)
16 max = (max<a[i])?a[i]:max ;
17 return max ;
18 }

151

0 0
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 #include <stdio.h>
2 #include <conio.h>
3 int Timmax (int *a,int n);
4 void main (void)
5 {
6 int arr[] = {6,13,2,3,9,7,5,18,4,12};
7 int max ;
8 max = Timmax(&arr[0],10);
9 printf (“Max = %d “,max);
10 }
11 int Timmax (int a[],int n)
12 {
13 int i,max ;
14 max = *a ;
15 for (i=1;i<n;i++)
16 max = (max<*(a+i))? *(a+i):max ;
17 return max ;
18 }

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

0 0
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 #include <stdio.h>
2 #include <conio.h>
3 #include<stdio.h>
4 #include <stdlib.h>
5 void Sapxepmang(int a[],int n);
6 void Inmang(int a[],int n);
7
8 void main (void)
9 {
10 int arr[] = {6,13,2,3,9,7,5,18,4,12};
11 printf(“mang truoc khi sap xep :\n”);
12 Inmang(arr,10);
13 Sapxepmang(arr,10);
14 printf(“\nmang sau khi sap xep :\n”);
15 Inmang(arr,10);
16 }
17 void Sapxepmang(int a[],int n)
18 {
19 int i,j,tam ;
20 for (i=0;i<n-1 ; i++)
21 for(j=i+1; j<n;j++)
22 {
23 if (a[i]>a[j])
24 {
25 tam = a[i];
26 a[i]= a[j];
27 a[j]=tam;
28 }
29 }
30 }
31 void Inmang(int a[],int n)
32 {
33 int i;
34 for (i=0;i<n ; i++)
35 printf(“%d, “,a[i]);
36 }
37

153

0 0
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 void recurse()
2 {
3 ... .. ...
4 recurse();
5 ... .. ...
6 }
7
8 int main()
9 {
10 ... .. ...
11 recurse();
12 ... .. ...
13 }

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

0 0
1 int giaithua(int n)
2 {
3 int i,gt = n;
4 for (i=n-1;i>0;i--)
5 gt=gt*i ;
6 return gt ;
7 }
8
9 void main()
10 {
11 int n;
12 do{
13 printf(“Nhap n :”);
14 scanf(“%d”,&n);
15 } while(n<1);
16 printf(“Giai thua cua %d la %d”, n, giaithua(n));
17 }

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 int giaithua(int n)
2 {
3 if (n==1)
4 return 1;
5 return n*giaithua(n-1) ;
6 }
7 void main()
8 {
9 int n;
10 do{
11 printf(“Nhap n :”);
12 scanf(“%d”,&n);
13 } while(n<1);
14 printf(“Giai thua cua %d la %d”, n, giaithua(n));
15 }

155

0 0
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. void Ham1(int a, int b)
{
return (a + b);
}
b. #include<stdio.h>
int Ham2(void)
{
int a, b, max;
max = a;
if(max < b)
max = b;
return max;

156

0 0
}

int main(void)
{
int x,y;
scanf(“%d%d”,&x,&y);
printf(“%d”,Ham2(x,y));
return 0;
}
c. #include<stdio.h>
void Ham3(void)
{
printf(“Xin chao!”);
}
int main(void)
{
Ham3(void);
return 0;
}
d. #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

0 0
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

0 0
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 struct SinhVien
2 {
3 char hoTen[40];
4 char MSSV[12];
5 昀氀oat diemGiuaKy;
6 昀氀oat diemCuoiKy;
7 昀氀oat diemTongKet;
8 };

159

0 0
Ở 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, 昀氀oat,...) 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 struct ThietBi
2 {
3 char tenTB[20];
4 int namSanXuat;
5 昀氀oat giaTien;
6 char trangThai;
7 };

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 昀氀oat 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 TênKiểuCấuTr甃Āc

160

0 0
{
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

0 0
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 struct SinhVien
2 {
3 char hoTen[38];
4 char MSSV[12];
5 昀氀oat diemGiuaKy;
6 昀氀oat diemCuoiKy;
7 昀氀oat diemTongKet;
8 };
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

0 0
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 TênKiểuCấuTr甃Āc tênBiến;
hoặc
struct 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 struct ThietBi c = {“Quat”, 2019, 225.5, ‘T’};
2 struct ThietBi d;
3 d = c; //gan du lieu cua c vao d
4 //phep toan hop le
5
6 if (d != c) //phep toan khong hop le
7 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

0 0
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.gia-
Tien,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 #include <stdio.h>
2 #include <conio.h>
3
4 struct ThietBi
5 {
6 char tenTB[20];
7 int namSanXuat;
8 昀氀oat giaTien;
9 char trangThai;
10 };
11
12 int main (void)
13 {

164

0 0
14 char tam[2];
15 struct ThietBi c = {“NA”, 0, 0, ‘T’};
16
17 printf(“Nhap thong tin cua 1 thiet bi:\n”);
18
19 printf(“Nhap ten: “);
20 gets(c.tenTB);
21
22 printf(“Nhap nam san xuat: “);
23 scanf(“%d”,&c.namSanXuat);
24
25 printf(“Nhap gia tien: “);
26 scanf(“%f”,&c.giaTien);
27
28 gets(tam);
29 printf(“Nhap trang thai: Tot (T) hoac Hong
30 (H): “);
31 scanf(“%c”,&c.trangThai);
32
33 printf(“\nThong tin thiet bi da nhap:\n”);
34 printf(“%s, %d, %.1f, %c\n”,c.tenTB,c.nam-
35 SanXuat,c.giaTien,c.trangThai);
36 getch();
37 return 0;
38 }

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

0 0
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 thong tin cua 1 thiet bi:
Nhap ten: may lanh
Nhap nam san xuat: 2019
Nhap gia tien: 4.5
Nhap 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

0 0
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ố
0 1 2 3 4
mảng
d

d[0] 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

0 0
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 #include <stdio.h>
2 #include <conio.h>
3
4 struct ThietBi
5 {
6 char tenTB[20];
7 int namSanXuat;
8 float giaTien;
9 char trangThai;
10 };
11
12 int main (void)
13 {
14 char tam[2];
15 struct ThietBi d[5];
16 int i, n = 5;
17 for (i = 0; i < n; i++)
18 {
19 printf(“\nNhap thong tin cua thiet bi
20 thu %d:\n”, i);
21
22 printf(“Nhap ten: “);
23 gets(d[i].tenTB);
24
25 printf(“Nhap nam san xuat: “);
26 scanf(“%d”,&d[i].namSanXuat);
27
28 printf(“Nhap gia tien: “);
29 scanf(“%f”,&d[i].giaTien);
30
31 gets(tam);
32 printf(“Nhap trang thai: Tot (T) hoac
33 Hong (H): “);
34 scanf(“%c”,&d[i].trangThai);
35 gets(tam);
36 }
37
38 printf(“\nThong tin cac thiet bi da

168

0 0
39 nhap:\n”);
40 for ( i = 0; i < n; i++)
41 printf(“%s, %d, %.1f, %c\n”,d[i].tenT-
42 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 Nhap thong tin cua thiet bi thu 0:
3 Nhap ten: quat
4 Nhap nam san xuat: 2017
5 Nhap gia tien: 0.25
6 Nhap trang thai: Tot (T) hoac Hong (H): T
7
8 Nhap thong tin cua thiet bi thu 1:
9 Nhap ten: den
10 Nhap nam san xuat: 2017
11 Nhap gia tien: 0.75
12 Nhap trang thai: Tot (T) hoac Hong (H): H
13
14 Nhap thong tin cua thiet bi thu 2:
15 Nhap ten: may lanh
16 Nhap nam san xuat: 2018

169

0 0
17 Nhap gia tien: 4.75
18 Nhap trang thai: Tot (T) hoac Hong (H): T
19
20 Nhap thong tin cua thiet bi thu 3:
21 Nhap ten: Tivi
22 Nhap nam san xuat: 2017
23 Nhap gia tien: 12.5
24 Nhap trang thai: Tot (T) hoac Hong (H): T
25
26 Nhap thong tin cua thiet bi thu 4:
27 Nhap ten: loa
28 Nhap nam san xuat: 2017
29 Nhap gia tien: 2.3
30 Nhap trang thai: Tot (T) hoac Hong (H): T
31
32 Thong tin cac thiet bi da nhap:
33 quat, 2017, 0.3, T
34 den, 2017, 0.8, H
35 may lanh, 2018, 4.8, T
36 Tivi, 2017, 12.5, T
37 loa, 2017, 2.3, T
38

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 struct ThietBi c = {“Quat”, 2019, 225.5, ‘T’};
2 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

0 0
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.namSanXuat c.giaTien c.trangThai

c Quat 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 p = &c;
2 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

0 0
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

0 0
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 #include <stdio.h>
2 #include <conio.h>
3 #include <malloc.h>
4
5 struct ThietBi
6 {
7 char tenTB[20];
8 int namSanXuat;
9 昀氀oat giaTien;
10 char trangThai;
11 };
12
13 int main (void)
14 {
15 char tam[2];
16 int i, n;
17 printf(“Nhap so n: “);

173

0 0
18 scanf(“%d”,&n);
19
20 struct ThietBi *p;
21 p = (ThietBi*)malloc(n*sizeof(ThietBi));
22 if (p == NULL)
23 printf(“Cap phat bo nho khong thanh cong!”);
else
24 {
25 for (i = 0; i < n; i++)
26 {
27 printf(“\nNhap thong tin cua thiet bi thu
28 %d:\n”, i);
29
30 gets(tam);
31 printf(“Nhap ten: “);
32 gets((p+i)->tenTB);
33
34 printf(“Nhap nam san xuat: “);
35 scanf(“%d”,&(p+i)->namSanXuat);
36
37 printf(“Nhap gia tien: “);
38 scanf(“%f”,&(p+i)->giaTien);
39
40 gets(tam);
41 printf(“Nhap trang thai: Tot (T) hoac
42 Hong (H): “);
43 scanf(“%c”,&(p+i)->trangThai);
44 }
45
46 printf(“\nThong tin cac thiet bi da nhap:\n”);
47 for ( i = 0; i < n; i++)
48 printf(“%s, %d, %.1f, %c\n”,
49 (p+i)->tenTB,(p+i)->namSanXuat,(p+i)->giaTien,
50 (p+i)->trangThai);
51 }
52 free(p);
53 getch();
54 return 0;
55 }
56

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

0 0
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 Nhap so n: 3
3
4 Nhap thong tin cua thiet bi thu 0:
5 Nhap ten: may phat song
6 Nhap nam san xuat: 2018
7 Nhap gia tien: 35.5
8 Nhap trang thai: Tot (T) hoac Hong (H): T
9
10 Nhap thong tin cua thiet bi thu 1:
11 Nhap ten: VOM
12 Nhap nam san xuat: 2017
13 Nhap gia tien: 0.5
14 Nhap trang thai: Tot (T) hoac Hong (H): T
15
16 Nhap thong tin cua thiet bi thu 2:
17 Nhap ten: dao dong ky
18 Nhap nam san xuat: 2019
19 Nhap gia tien: 35.6
20 Nhap trang thai: Tot (T) hoac Hong (H): T
21
22 Thong tin cac thiet bi da nhap:
23 may phat song, 2018, 35.5, T
24 VOM, 2017, 0.5, T
25 dao dong ky, 2019, 35.6, T
26

175

0 0
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 #include <stdio.h>
2 #include <conio.h>
3
4 struct ThietBi
5 {
6 char tenTB[20];
7 int namSanXuat;
8 昀氀oat giaTien;
9 char trangThai;
10 };
11
12
13 void NhapDuLieu(struct ThietBi *x, int n)
14 {
15 char tam[2];
16 int i;
17 for (i = 0; i < n; i++)
18 {
19 printf(“\nNhap thong tin cua thiet bi thu
20 %d:\n”, i);

21 printf(“Nhap ten: “);


22 gets((x+i)->tenTB);
23

176

0 0
24 printf(“Nhap nam san xuat: “);
25 scanf(“%d”,&(x+i)->namSanXuat);
26
27 printf(“Nhap gia tien: “);
28 scanf(“%f”,&(x+i)->giaTien);
29
30 gets(tam);
31 printf(“Nhap trang thai: Tot (T) hoac
32 Hong (H): “);
33 scanf(“%c”,&(x+i)->trangThai);
34 gets(tam);
35 }
36 }
37
38 void InMotThietBi(struct ThietBi a)
39 {
40 printf(“%s, %d, %.1f, %c\n”,a.tenTB,a.namSanXu-
41 at,a.giaTien,a.trangThai);
42 }
43
44 int main (void)
45 {
46 int i, n = 2;
47 struct ThietBi d[2];
48
49 NhapDuLieu(&d[0],n);
50
51 printf(“\nThong tin cac thiet bi da nhap:\n”);
52 for ( i = 0; i < n; i++)
53 InMotThietBi(d[i]);
54
55 getch();
56 return 0;
57 }
58

177

0 0
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 Nhap thong tin cua thiet bi thu 0:
3 Nhap ten: may vi tinh
4 Nhap nam san xuat: 2018
5 Nhap gia tien: 15.5
6 Nhap trang thai: Tot (T) hoac Hong (H): T

178

0 0
7
8 Nhap thong tin cua thiet bi thu 1:
9 Nhap ten: Tivi
10 Nhap nam san xuat: 2019
11 Nhap gia tien: 12.4
12 Nhap trang thai: Tot (T) hoac Hong (H): T
13
14 Thong tin cac thiet bi da nhap:
15 may vi tinh, 2018, 15.5, T
16 Tivi, 2019, 12.4, T
17

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 #include <stdio.h>
2 #include <conio.h>
3 #include <malloc.h>
4
5 struct ThietBi
6 {
7 char tenTB[20];
8 int namSanXuat;
9 昀氀oat giaTien;
10 char trangThai;
11 };
12
13 void NhapDuLieu(struct ThietBi *x, int n)
14 {
15 char tam[2];
16 int i;
17 for (i = 0; i < n; i++)
18 {
19 printf(“\nNhap thong tin cua thiet bi thu
%d:\n”, i);

179

0 0
20
21 gets(tam);
22 printf(“Nhap ten: “);
23 gets((x+i)->tenTB);
24
25 printf(“Nhap nam san xuat: “);
26 scanf(“%d”,&(x+i)->namSanXuat);
27
28 printf(“Nhap gia tien: “);
29 scanf(“%f”,&(x+i)->giaTien);
30
31 gets(tam);
32 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

0 0
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

0 0
1 Nhap so n: 2
2
3 Nhap thong tin cua thiet bi thu 0:
4 Nhap ten: may vi tinh
5 Nhap nam san xuat: 2018
6 Nhap gia tien: 15.5
7 Nhap trang thai: Tot (T) hoac Hong (H): T
8
9 Nhap thong tin cua thiet bi thu 1:
10 Nhap ten: Tivi
11 Nhap nam san xuat: 2019
12 Nhap gia tien: 12.4
13 Nhap trang thai: Tot (T) hoac Hong (H): T
14
15 Thong tin cac thiet bi da nhap:
16 may vi tinh, 2018, 15.5, T
17 Tivi, 2019, 12.4, T
18

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

0 0
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

0 0
1 #include <stdio.h>
2 #include <conio.h>
3
4
5 union KieuSo
6 {
7 int x;
8 double y;
9 };
10
11 int main (void)
12 {
13
14 union KieuSo a;
15 printf(“Kich thuoc bien a: %d\n”,sizeof(a));
16
17 a.x = 5;
18 printf(“%d\n”,a.x);
19
20 a.y = 7.85;
21 printf(“%f\n”,a.y);
22
23 getch();
24 return 0;
25 }

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

0 0
a.x

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 Kich thuoc bien a: 8
2 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 Kich thuoc bien a: 8
2 5
3 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

0 0
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 enum MauSac
2 {
3 Den, Nau, Do, Cam, Vang, Luc, Lam, Tim, Xam, Trang
4 };

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

0 0
1 #include <stdio.h>
2 #include <conio.h>
3
4 enum MauSac
5 {
6 Den, Nau, Do, Cam, Vang, Luc, Lam, Tim, Xam, Trang
7 };
8
9 int main (void)
10 {
11 enum MauSac R;
12 printf(“Kich thuoc R: %d\n”,sizeof(R));
13 printf(“Gia tri R: %d\n”,R);
14
15 R = Den;
16 printf(“Gia tri R: %d\n”, R);
17
18 R = Cam;
19 printf(“Gia tri R: %d\n”, R);
20
21 getch();
22 return 0;
23 }

Ở 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

0 0
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 R: 4
Gia tri R: 1824868116
Gia tri R: 0
Gia tri R: 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. struct
{
int ma;
int namSX;
} thietBi;

188

0 0
b. #include<stdio.h>
struct HinhChuNhat
{
昀氀oat dai,rong;
};

int main(void)
{
printf(“Nhap chieu dai, rong:\n”);
scanf(“%f%f”,&HinhChuNhat.dai, &HinhChuNhat.
rong)

昀氀oat dienTich = HinhChuNhat.dai * HinhChuNhat.


rong;
printf(“%f”,dienTich);
return 0;
}
c. #include<stdio.h>
struct Diem
{
昀氀oat giuaKy;
昀氀oat cuoiKy;
};

int main(void)
{
struct Diem a;
scanf(“%f”,&giuaKy.a);
scanf(“%f”,&cuoiKy.a);
return 0;
}

189

0 0
d. #include<stdio.h>
struct DuLieu a[3]
{
昀氀oat nhietDo;
昀氀oat 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 #include <stdio.h>
2 #include <conio.h>
3 #include <malloc.h>
4
5 struct SinhVien
6 {
7 char hoTen[30];
8 昀氀oat diemToan;
9 昀氀oat diemLy;
10 昀氀oat diemHoa;
11 昀氀oat diemTB;
12 };

190

0 0
13
14 void NhapDs( struct SinhVien *sv, int n)
15 {
16 int i; char tam[2];
17 puts (“Nhap du lieu cho danh sach Sinh
18 vien”);
19 for (i = 0; i < n; i++)
20 {
21 printf ( “Sinh vien thu : %d\n”,i);
22 puts ( “Ho ten:”);
23 gets ((sv+i)->hoTen);
24
25 puts (“Diem toan:”);
26 scanf (“%f”, &(sv+i)->diemToan);
27
28 puts (“Diem ly:”);
29 scanf (“%f”, &(sv+i)->diemLy);
30
31 puts (“Diem hoa:”);
32 scanf (“%f”, &(sv+i)->diemHoa);
33 gets(tam);
34
35 (sv+i)->diemTB = ((sv+i)->diemToan +
36 (sv+i)->diemLy + (sv+i)->diemHoa)/3;
37 }
38 }
39
40 void InDs(struct SinhVien *sv, int n)
41 {
42 int i;
43 puts(“Danh sach sinh vien da nhap:”);
44 for (i = 0; i < n; i++)
45 {
46 puts ( “ho ten: “);
47 puts ((sv+i)->hoTen);
48 printf (“Diem tb: %f\n”,
49 (sv+i)->diemTB);

191

0 0
50 }
51 }
52
53 int main (void)
54 {
55 int n, i;
56 char tam[2];
57 昀氀oat max;
58 struct SinhVien *a;
59 puts (“Nhap so luong sinh vien: “);
60 scanf (“%d”,&n);
61 gets(tam);
62
63 a = (SinhVien*) malloc (n*sizeof (SinhVien));
64 if (a == NULL)
65 puts (“Khong the cap phat bo nho.”);
66 else
67 {
68 NhapDs (a,n);
69 InDs (a, n);
70
71 max = a->diemTB;
72 for (i= 0; i <n; i++)
73 if( max < (a +i)->diemTB)
74 max = (a +i)->diemTB;
75
76 puts(“sinh vien co diem trung binh lon
77 nhat:”);
78 for (i= 0; i <n; i++)
79 if( max == (a +i)->diemTB)
80 {
81 puts( (a + i)->hoTen);
82 printf ( “Diem tb: %f\n”,
83 (a + i)->diemTB);
84 }
85 }
86 free (a);
87 getch();
return 0;
}

192

0 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

0 0
đ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

0 0
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
đã được tiền xử lý
Tệp mã nguồn 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

0 0
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 昀椀le). 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 #include <stdio.h>
2 #include <math.h>
3 #include “my昀椀le.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

0 0
1 /*chapter08.c*/
2 #include <stdio.h>
3 #include <stdlib.h>
4 void Sapxepmang(int a[],int n);
5 void Inmang(int a[],int n);
6 void Nhapmang(int a[],int n);
7 int Timmax (int *a,int n);
8 int Timmin (int *a,int n);
9 void Hoanvi(int *x, int *y);
10 昀氀oat Tinhtrungbinh(int a, int b);
11
12 void main (void)
13 {
14 int *arr;
15 int n ;
16 int max, min;
17 昀氀oat tb;
18 do{
19 printf(“ Nhap n “);
20 scanf(“%d”,&n);
21 }while(n<=0);
22 arr = (int*) malloc (n*sizeof(int));
23 Nhapmang(arr,n);
24 printf(“cac phan tu mang :\n”);
25 Inmang(arr,n);
26 max = Timmax(arr,n);
27 min = Timmin(arr,n);
28 printf(“\nmax = %d, min = %d”,max,min);
29 tb=Tinhtrungbinh(max,min);
30 printf(“\ntrung binh = %.2f “,tb);
31 Hoanvi(&arr[0],&arr[n-1]);
32 printf(“\ncap nhat mang :\n”);
33 Inmang(arr,n);
34 Sapxepmang(arr,n);
35 printf(“\nmang sau khi sap xep :\n”);
36 Inmang(arr,n);
37 }
38
39 void Sapxepmang(int a[],int n)

197

0 0
 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.
 0
Không cho phép dùng toán 0tử 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 #include <stdio.h>
2 int main (void)
3 {
4 int a = 1;
5 nhan:do
6 {
7 if(a == 4)
8 {
9 a = a+2;
10 printf(“khong in a=4 va a=5!\n”);
11 goto nhan;
12 }
13 printf(“Gia tri cua a la: %d\n”, a);
14 a = a+1;
15 }while(a < 9);
16 return 0;
17 }

Kết quả chạy chương trình là:


1 Gia tri cua a la: 1
2 Gia tri cua a la: 2
3 Gia tri cua a la: 3
4 khong in a=4 va a=5!
5 Gia tri cua a la: 6
6 Gia tri cua a la: 7
7 Gia tri cua a la: 8

87

0 0
0 0
Đâ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. int a, i;
scanf(“%d”,&a);
printf(“Nhap 5 so nguyen: \n”);
i = 0;
while (i < 5);
scanf(“%d”, &a);
i++;
b. 昀氀oat a = -1;
while (a <0 && a > 10)
{
printf(“Nhap so trong pham vi 0 - 10: \n”);
scanf(“%f”,&a);
}
c. int a = -1;
while (a <= 0)
do
{
printf(“Nhap so > 0: \n”);
scanf(“%d”,&a);
}
d. int b;
do
printf(“Nhap so nguyen duong: “);
scanf(“%d”,&b);
while (a < 0);
e. int kq = 1,i = 0;
int a;
scanf(“%d”,&a);
for (i < 5)
{
kq = a*kq; i++;
0 0
}
printf(“%d”,kq);
88

0 0
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;
0 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

0 0
{
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);
}
} 0 0
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

0 0
for (i=2; i<=n; i++)
if (n %i == 0)
break;
if (i==n) 0 0
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: 4
Nhap b: 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

0 0

You might also like