Chúng tôi cũng sử dụng các dâu ngoặc nhọn xung quanh câu lệnh đơn tạo nên phần thân của do-while, mặc dù chúng không cần thiết, đề người đọc vội vàng sẽ không nhằm phần while với phần đầ
Trang 1
TRƯỜNG ĐẠI HỌC LAO ĐỘNG - XÃ HỘI (CƠ SỞ II) KHOA GIÁO DỤC ĐẠI CƯƠNG
i BAO CAO KET THUC HOC PHAN
Nhóm sinh viên thực hiện:
NGUYEN HOANG KIM ANH - 223404050146 HUYNH THI LE HUYEN - 223404050168 NGUYEN THỊ HỎNG NHƯ - 223404050183
LÊ MAI ANH THY - 223404050198 PHAN THỊ TRÀ — 223404050201 Ngành: Hệ thống thông tin quan ly
TP Hé Chi Minh, thang 01 năm 2024
Trang 3
3.1 Câu lệnh và khối - + t1 E211 E121 1 1 1 111 ng HH rà Hàn ryk 2
CHƯƠNG 4: HÀM VÀ CẤU TRÚC CHƯƠNG TRÌNH - 5° 5° 5 52 19 4.1 Cơ bản về hàm 2 2 SE E21 1127102 1 E1 H1 1 trau 20 4.3 Biến bên ngoài à 1c TT HH ng 1n 112gr yo 28
Trang 4Bài tập 3-5: 0 c1 ng ng reo 59 Bài tập 3-6: 22 2 2221 n2 ng 22g reo 59 Bài tập 4-1: 222 2c 222211 n2 n1 22 re rreaee 60 Bài tập 4-2: 0c 221 n1 nung rreaee 60 Bài tập 4-3: 00 n1 ng n2 ren rreaee 62 Bài tập 4-4: 22 c2 n2 n1 22 ng greree 62
Bài tập 4-14: 22222 2211 tnn2221E 22tr ng eeegere 65
BANG PHAN CONG CONG YVIỆC 2° se csExsExsevkEEseEteeerkerererrersrerrsrrke 67
Trang 5Trong thế giới rộng lớn của ngôn ngữ lập trình, ít ngôn ngữ nào có thể sánh bằng sức mạnh, tính linh hoạt và ý nghĩa lịch sử của C Ngôn ngữ này đã vượt qua thử thách thời gian và tiếp tục được sử dụng rộng rãi trong rất nhiều ứng dụng, từ lập trình hệ thống
và thiết bị nhúng đến phát triển game và tính toán cao cấp Nếu bạn là người mới muốn
bắt đầu hành trình khám phá thê giới lập trình, việc học C có thể là một điểm khởi đầu
tuyệt vời
Trong bài viết này, chúng tôi sẽ khám phá các khía cạnh cơ bản của C và cung cấp cho bạn kiến thức và tài nguyên cần thiết dé bước đầu học những ngôn ngữ này Bài viết này sẽ nghiên cứu tập trung hai chủ đề: luồng điều khiển (Control Flow), hàm và cầu trúc chương trinh (Functions And Program Structure); tir đó đi sâu hơn vào cách sử dụng và những lưu ý khi sử dụng Với luồng điều khiến, lập trình viên có thê tạo ra các chương trình thích ứng với các tình huỗng khác nhau, xử lý các điều kiện khác nhau và thực hiện các hành động cụ thê dựa trên những điều kiện đó Hàm cho phép lập trình viên hoàn
thành các nhiệm vụ cụ thê và cải thiện tổng thê cầu trúc chương trình Cầu trúc chương
trình bao gồm các tiêu đề, khai báo, định nghĩa hàm và các câu lệnh Bằng việc tuân đúng theo cấu trúc chương trình, chúng ta có thể đảm bảo tính rõ ràng của code, khả nang bao trì và dễ dàng sửa lỗi
Do đó, đề tài Luông điều khiển (Control Flow), Hàm và cấu trúc chương trình (Functions And Program Structure) được chúng tôi lựa chọn đề nghiên cứu Chúng tôi sẽ tìm hiểu, giải thích cú pháp cơ bản của chúng và giới thiệu cho bạn các công cụ và tải liệu cần thiết để xây dựng kỹ năng lập trình của bạn Bài viết này sẽ cung cấp những thông tin vô cùng hữu ích và là nền tảng đề các lập trình viên tập sự dựa vào và phát triển khả năng lập trình của mình
Trang 6CHUONG 3: LUONG DIEU KHIEN
Luong điều khiển câu lệnh của một ngôn ngữ chỉ rõ trình tự thực hiện của bài toán
chúng ta đã được tiếp cận đến cấu trúc của luồng điều khiển phổ biến nhất trong ví dụ trước; trong chương này chúng ta sẽ hoàn thiện chúng và đi sâu hơn vào những gì đã
được bàn luận ở trước
3.1 Câu lệnh và khối
Một biểu thức như x=0 hay ¡++ hay printf ( ) trở thành một câu lệnh khi theo sau
nó là một dầu chấm phầy, như bên dưới
câu lệnh đơn Các đấu ngoặc nhọn bao quanh các câu lệnh của hàm là một ví dụ rõ ràng:
dau ngoặc nhọn xung quanh nhiều câu lệnh sau ïf, else, while hoặc for là một câu lệnh khác (Các biến có thể được khai báo bên trong bất kỳ khối nào; chủng ta sẽ nói về điều này trong Chương 4.) Không đặt dấu chấm phây sau dâu ngoặc nhọn bên phải đề kết thúc
một khối
3.2 If-else
Mệnh đề if-else được sử dụng đề thể hiện các quyết định Về mặt hình thức, cú
pháp của nó là
Trang 7statement2
trong đó phần else là tùy chọn Biều thức được kiểm tra; nêu nó đúng (nghĩa là biêu
thức if co gia trị khác 0), câu lệnh 1 sẽ được thực thi Nếu nó sai (biêu thức bằng 0) và
nếu có phần khác thi câu lệnh 2 sẽ được thực thi thay thé
Vi if chi kiém tra gia tri số của một biểu thức nên có thể sử dụng một 36 phim tat
mã hóa nhất định Rõ ràng nhất là viết
if (expression)
thay vì viết
if (expression != 9)
Đôi khi điều này hiển nhiên và rõ ràng: đôi khi nó có thê khó hiéu
Boi vi phan else cua if-else là tùy chọn nên sẽ có sự mơ hồ khi phần else bi bỏ qua khỏi chuỗi ¡f lồng nhau Điều này được giải quyết bằng cach lién két else véi else-less if gân nhất trước đó Ví dụ, trong
phan else đi cùng với ¡f bên trong, như chúng ta đã chỉ ra bằng cách thụt lề Nếu đó
không phải là điều bạn muốn, thì phải sử dụng dấu ngoặc nhọn đề buộc có sự liên kết
phù hợp:
Trang 8printf ("error n is negative\n");
Việc thụt lề hiển thị rõ ràng những gì bạn muốn, nhưng trình biên dịch không nhận
được thông báo và sẽ liên kết else với ¡f bên trong Loại lỗi này có thê khó tìm; bạn nên
sử dụng dau ngoặc nhọn khi có các 1f long nhau
Nhân tiện, hãy chú ý rằng có dấu chấm phẩy sau z = a trong
if (a > b)
z=a;
else
z=b;
Điều này là do về mặt ngữ pháp, một câu lệnh tuân theo 1Ý và một câu lệnh biểu
thức như "z = a;" luôn được kết thúc bằng dâu chấm phay
3.3 Else-if
Cấu trúc dưới đây
Trang 9xay ra thuong xuyén đến mức cần có một cuộc thảo luận ngăn về nó Chuỗi câu
lệnh ¡f này là cách tổng quát nhất đề viết một quyết định đa chiều Các biểu thức được kiêm tra theo thứ tự; nêu bất kỳ biểu thức nào là đúng thì câu lệnh liên quan đến nó sẽ được thực thi và điều này sẽ chấm dứt toàn bộ chuỗi Như mọi khi, code cho mỗi câu
lệnh là một câu lệnh đơn hoặc một nhóm trong dấu ngoặc nhọn
Lệnh else cuối cùng xử lý trường hợp "không có trường hợp nào ở trên" hoặc trường hợp default, khi mà không có điều kiện nào khác được thỏa mãn Đôi khi không
có hành động rõ ràng nào cho trường hợp mặc định, khi đó
else
statement
có thể được bỏ qua hoặc có thé duoc str dung dé kiém tra 16i nham nam bat diéu
kiện "không thê"
Để minh họa quyết định ba chiều, đây là hàm tìm kiếm nhị phân quyết định xem
một giá trị x cụ thê có xuất hiện trong mảng v được sắp xép hay không Các phần tử của
v phải theo thứ tự tăng dần Hàm trả về vị trí (một số nằm giữa 0 và n-l) nêu x xuất hiện trong v, và -[ nêu không
Trước tiên, tìm kiêm nhị phân so sánh giá trị đầu vào x voi phân tử ở giữa của máng
v Nêu x nhỏ hơn giá trị ở giữa, việc tìm kiêm sẽ tập trung vào nửa dưới của bảng, nêu không thì ở nửa trên Trong cả hai trường hợp, bước tiếp theo là so sánh x với phần tử ở
Trang 10Quyết định cơ bản là liệu x nhỏ hơn, lớn hơn hay bằng phần tử ở giữa v[mid] ở mỗi bước; đây là điều đương nhiên đối với else-if
Bai tap 3-1 Tim kiém nhị phân của chúng tôi thực hiện hai thử nghiệm bên trong
vòng lặp, khi đó chỉ cần một thử nghiệm là đủ (bỏ hết các thử nghiệm bên ngoài) Viết một phiên bản chỉ có một bài test bên trong vòng lặp và đo lường sự khác biệt về thời gian chạy
3.4 Switch
Cau lénh switch la mot quyét định nhiều chiều nhằm kiểm tra xem một biểu thức có
khớp với một trong số các giá trị nguyên không đôi và các nhánh tương ứng hay không
switch (expression)
case const-expr: statements
case const-expr: statements
Trang 11đó Tất cả các biểu thức case phải khác nhau Default được thực thĩ nêu không có case nào khác được thỏa mãn Giá trị default là tùy chọn; nếu nó không có ở đó và nêu không
có case nào khớp thì sẽ không có hành động nào diễn ra Các case và default có thê xảy
ra theo bất kỳ thứ tự nào
Trong Chương 1, chủng ta đã viết một chương trình đếm số lần xuất hiện của mỗi chữ số, khoảng trắng và tất cả các ký tự khác, sử dụng một chuỗi if else ¡f else Đây
là chương trình tương tự được viết bằng switch:
Câu lệnh break sé giúp thoát ra ngay lập tức khỏi switch Vi các case chỉ đóng vai
trò là nhãn nên sau khi code cho một case được thực hiện xong, việc thực thi sẽ chuyển
Trang 12trong chương này
Các case chạy liên tục không có khoảng nghỉ là một điều vui buồn lẫn lộn Về mặt tích cực, nó cho phép gắn nhiều case vào một hành động duy nhất, như với các chữ số trong ví dụ này Nhưng nó cũng hàm ý rằng thông thường mỗi case phải kết thúc bằng một khoảng nghỉ đề tránh chuyến sang case tiếp theo Việc chuyên từ case này sang case
khác khiến chương trình bị lỏng léo khi được sửa đôi Ngoại trừ nhiều nhãn cho một lần tính toán, các thông số dự phòng nên được sử dụng một cách tiết kiệm và được chủ thích
Đề đảm bảo hình thức, hãy đặt dấu ngắt sau case cuối cùng (default) mặc dù về mặt logic thì điều đó là không cần thiết Một ngày nào đó khi một case khác được thêm vào cuối, phần lập trình phòng thủ này sẽ cứu bạn
Bài tập 3-2 Viết hàm thoát ( s, t) để chuyên đôi các ký tự thành dòng mới và tab
thành các chuỗi thoát hiền thị như \n và \ t khi nó sao chép chuỗi t thành s Sử dụng lệnh switch Viết một hàm cho hướng khác, chuyên đôi chuỗi thoát thành kí tự
3.5 Vong lap While va For
Chúng ta đã gặp các vòng lặp while và for Trong
biểu thức được kiểm tra Nếu nó khác 0, câu lệnh sẽ được thực thi và biểu thức
được kiểm tra lại Chu kỳ này tiếp tục cho đến khi biêu thức trở thành 0, tại thời điểm đó
việc thực thị sẽ tiếp tục sau câu lệnh
Trang 13ngoại trừ hành vĩ tiếp tục, được mô tả trong Phần 3.7
Về mặt ngữ pháp, ba thành phần của vòng lặp for là các biêu thức Thông thường nhất, exprl và expr3 là các phép gán hoặc lệnh gọi hàm và expr2 là một biểu thức quan
hệ Bất kỳ phần nào trong ba phần đều có thê được bỏ qua, mặc dù vẫn phải giữ lại dấu cham phẩy Nếu exprl hoặc expr3 bị bỏ qua, nó sẽ bị loại khỏi bản mở rộng Nếu phép thử expr2 không xuất hiện thì nó được coi là đúng vĩnh viễn, vì vậy
là một vòng lặp "vô hạn”, có lẽ bị phá vỡ bằng các cách khác, chăng hạn như break
hoặc return
Việc sử dụng while hay for phân lớn là một vấn đề về sở thích cá nhân Ví dụ, trong
không có khởi tạo hoặc khởi tạo lại nên while chạy tự nhiên nhất
For được ưu tiên hơn khi có sự khởi tạo và tăng dân đơn giản, vì nó giữ cho các câu lệnh điều khiển vòng lặp gần nhau và hiển thị ở đầu vòng lặp Điều này thể hiện rõ nhất ở
Trang 14đó là thành ngữ trong C đề xử lý n phần tử đầu tiên của một mảng, tương tự như vòng lặp Fortran DO hoặc For của Pascal Tuy nhiên, sự tương tự này không hoàn hảo vì chỉ số và giới hạn của vòng lặp C for có thể được thay đổi từ bên trong vòng lặp và biến ¡ vấn giữ nguyên giá trị của nó khi vòng lặp kết thúc vì bất kỳ lý do gì Bởi vì các thành phân của for là các biêu thức tùy ý, nên các vòng lặp for không bị giới hạn ở cấp số cộng Tuy nhiên, sẽ không phù hợp nếu buộc các phép tính không liên quan vào việc khởi tạo
và tăng dần for, vốn tốt hơn nên dành cho các hoạt động điều khiển vòng lặp
Một ví dụ lớn hơn, đây là một phiên bản khác của atoi để chuyên đổi một chuỗi thành số tương đương Phần này tông quát hơn một chút so với phần ở Chương 2; nó xử
lý khoảng trăng ở đầu tùy chọn và dấu + hoặc - tùy chọn (Chương 4 trình bày atof, thực hiện chuyền đổi tương tự cho các số dấu phây động.)
Cấu trúc của chương trình phan anh dang dau vao:
bỏ qua khoảng trăng, nếu có
lay dấu, nêu có
lây phán nguyên và chuyên đổi nỗ
Mỗi bước thực hiện phan việc của minh va dé moi thứ ở trang thai chin chu cho
bước tiếp theo Toàn bộ quá trình kết thúc ở ký tự đầu tiên không thề là một phần của sô
Trang 15Thư viện chuẩn cung cấp hàm strtol phức tap hon dé chuyền đôi chuỗi thành số
nguyên đài; xem Phần 5 của Phụ lục B
Ưu điểm của việc duy trì điều khiển vòng lặp tập trung thậm chí còn rõ ràng hơn khi có nhiều vòng lặp lồng nhau Hàm sau đây là sắp xếp Shell đề sắp xếp một mảng các
số nguyên Ý tưởng cơ bản của thuật toán sắp xếp này được phát minh vào năm 1959 bởi
D L Shell, đó là trong giai đoạn đầu, các phần tử ở xa nhau được so sánh, thay vì các phân tử liền kề như trong các loại trao đối đơn giản hơn Điều này có xu hướng loại bỏ một lượng lớn rồi loạn một cách nhanh chóng, đo đó các giai đoạn sau có ít việc phải làm hơn Khoảng cách giữa các phần tử được so sánh giảm dần xuống còn một, tại thời điểm
đó, việc sắp xếp trở thành một phương pháp trao đôi liền kề một cách hiệu quả
Trang 16Có ba vòng lặp lồng nhau Phần ngoài cùng kiểm soát khoảng cách giữa các phần
tử được so sánh, thu nhỏ nó từ n/2 theo hệ số hai mỗi lần đi qua cho đến khi nó trở thành
0 Vòng lặp giữa bước dọc theo các phần tử Vòng lặp trong cùng so sánh từng cặp phần
tử được phân tách bằng khoảng cách và đảo ngược bất kỳ phần tử nào không theo thứ tự
Vì khoảng cách cuối cùng được giảm xuống còn một, nên tất cả các phần tử thậm chí còn được sắp xếp chính xác Lưu ý tính tổng quát của for làm cho vòng lặp bên ngoài có đạng giống như các vòng lặp khác, mặc dù nó không phải là một cấp số cộng
Toán tử C cuối cùng là dấu phẩy "„ ", thường được sử dụng nhiều nhất trong câu lệnh for Một cặp biêu thức được phân tách bằng dau phây được kiểm tra từ trái sang phải, loại và giá trị của kết quả là loại và giá trị của toán hạng bên phải Do đó, trong câu lệnh for, có thể đặt nhiều biêu thức vào các phần khác nhau, ví dụ như xử lý song song hai chỉ số Điều này được minh họa trong hàm đảo ngược ( s), đảo ngược chuỗi s tại chỗ
Dau phây phân tách các đối số hàm, biến trong khai báo, v.v., không phải là toán tử dâu phầy và không đảm bảo việc kiêm tra từ trái sang phải
Toán tử đấu phẩy nên được sử dụng một cách tiết kiệm Cách sử dụng phù hợp nhất
là cho các cấu trúc có liên quan chặt chế với nhau, như trong vòng lặp for ngược và trong macro trong đó tính toán nhiều bước phải là một biểu thức duy nhất Biểu thức dấu phây
Trang 17cũng có thê thích hợp cho việc trao đôi các phần tử ngược lại, trong đó việc trao đổi có
thể được coi là một thao tác đơn lẻ:
Bài tập 3-3 Viết hàm mở rộng ( s I, s2) để mở rộng các ký hiệu tốc ký như a-z trong chuỗi sI thành danh sách đầy đủ tương đương abc xyz trong s2 Cho phép các chữ cái có cả chữ hoa và chữ số, đồng thời chuẩn bị xử lý các trường hợp như a-b-c và a- z0-9 và -a-z Sắp xếp sao cho đầu hoặc cuối - được hiệu theo nghĩa đen
3.6 Vong lap Do-while
Nhu chung ta da thao ludn 6 Chuong 1, vong lap while va for kiém tra điều kiện kết thúc ở trên cùng Ngược lại, vòng lặp thứ ba trong C, vòng lặp do-while, kiểm tra ở phía
dưới sau khi thực hiện mỗi lần đi qua thân vòng lặp; phan thân luôn được thực thi ít nhất
một lân
Cú pháp của do là
Câu lệnh được thực thi, sau đó biểu thức được kiểm tra Nếu nó đúng, câu lệnh sẽ được kiểm tra lại, v.v Khi biểu thức sai, vòng lặp sẽ kết thúc Ngoại trừ ý nghĩa của việc
kiêm tra, do-while tương đương với câu lệnh repeat-until của Pascal
Kinh nghiệm cho thấy do-while ít được sử dụng hơn while và for Tuy nhiên, đôi khi nó vẫn có giá trị, như trong hàm itoa sau đây, hàm này chuyên đôi một số thành một chuỗi ký tự (nghịch đáo của atoi) Công việc phức tạp hơn một chút so với những gi
Trang 18người ta nghĩ lúc đầu, bởi vì các phương pháp tạo chữ số dễ dàng sẽ tạo ra chúng theo
thứ tự sai Chúng tôi đã chọn tạo chuỗi ngược, sau đó đảo ngược chuỗi đó
Việc do-while là cần thiết, hoặc ít nhất là thuận tiện, vì ít nhất một ký tự phải được
cài đặt trong mảng s, ngay cả khi n bằng 0 Chúng tôi cũng sử dụng các dâu ngoặc nhọn xung quanh câu lệnh đơn tạo nên phần thân của do-while, mặc dù chúng không cần thiết,
đề người đọc vội vàng sẽ không nhằm phần while với phần đầu của vòng lặp while Bài tập 3-4 Trong cách biều điễn số bù 2, phiên bản itoa của chúng tôi không xử lý
số âm lớn nhất, tức là giá trị của n bang -(2wordsize-1) Giải thích tại sao lai không Sửa
déi no dé in giá trị đó một cách chính xác bat ké may chay trén do
Bài tập 3-5 Viết hàm itob ( n, s, b) dé chuyén sé nguyén n thanh biéu dién ky ty co
số b trong chuỗi s Cụ thể, itob (n,s, 16) định dạng n dưới dạng sỐ nguyên thập lục phân trong s
Bài tập 3-6 Viết một phiên bản của itoa chấp nhận ba đối số thay vì hai Đối số thứ
ba là độ rộng trường tối thiêu; số được chuyền đôi phải được đệm bằng khoảng trống ở bên trái nêu cân thiệt dé lam cho nó đủ rộng
Trang 193.7 Break va Continue
Đôi khi thật thuận tiện đề có thể thoát khỏi vòng lặp ngoài việc kiểm tra ở đầu hoặc
cuối Câu lệnh break cung cấp một lối thoát sớm khỏi for, while va do, giống như switch Việc dùng break làm cho vòng lặp bao quanh bên trong hoặc lệnh switch bi thoat ra ngay lập tức
Hàm sau đây - trim - giúp loại bỏ các khoảng trống, tab và dòng mới khỏi cuối chuỗi, sử dụng break để thoát khỏi vòng lặp khi tìm thấy dòng không trông, không tab, không phải dòng mới ngoài cùng bên phải
strlen trả về độ dài của chuỗi Vòng lặp for bắt đầu ở cuối và quét ngược lại dé tìm
ký tự đầu tiên không phái là ký tự trống, tab hoặc dòng mới Vòng lặp thoát ra khi tìm thấy một chuỗi hoặc khi n trở thành số âm (nghĩa là khi toàn bộ chuỗi đã được quét) Bạn nên xác minh rằng hành vi này đúng ngay cả khi chuỗi có giá trị trồng hoặc chỉ chứa các
ký tự khoảng trắng
Câu lệnh continue có liên quan đến break nhưng ít được sử đụng hơn; nó làm cho lần lặp tiếp theo của vòng lặp for, while hoặc do bắt đầu Trong while và do, điều này nghĩa là phần kiểm tra được thực thi ngay lập tức; trong for, điều khiển chuyển sang bước tăng dần Câu lệnh continue chỉ áp dụng cho vòng lặp, không áp dụng cho switch Việc continue bên trong một switch nằm trong một vòng lặp sẽ gây ra sự lặp lại vòng lặp tiếp theo
Trang 20Ví dụ, đoạn này chỉ xử lý các phần tử không 4m trong mang a, gia tri âm được bỏ qua
Câu lệnh continue thường được sử dụng khi phần vòng lặp tiếp theo phức tạp hơn,
do đó việc đảo ngược phép kiểm tra và thụt lề sang một cấp độ khác sẽ lồng chương trình quá sâu
3.8 Cau lénh Goto va Label
Ngôn ngữ C cung cấp câu lệnh goto có khả năng sử dụng vô hạn và gắn nhãn cho các nhánh tới Về mặt hình thức, goto không bao giờ là cần thiết, và trong thực tế, việc viết code mà không cần đến nó hầu như luôn dễ dàng Chúng tôi chưa sử dụng gotfo trong cuôn sách này
Tuy nhiên, có một số trường hợp goto có thê tìm được chỗ đứng Phô biến nhất là
từ bỏ việc xử lý trong một số câu trúc lồng nhau sâu, chăng hạn như thoát ra khỏi hai hoặc nhiều vòng lặp cùng một lúc Câu lệnh break không thể được sử dụng trực tiếp vì
nó chỉ thoát ra từ vòng lặp trong cùng Như vậy:
Trang 21Cách tô chức nay rất hữu ích nêu code xử lý lỗi không tầm thường và nếu lỗi có thê
xảy ra ở một sô nơi
Một label có dạng giống như tên biến và được theo sau bởi dầu hai chấm Nó co thé được gắn vào bất kỳ câu lệnh nào có cùng chức năng với goto Phạm vi của label là toàn
bộ chức năng
Một ví dụ khác, hãy xem xét bài toán xác định xem hai mảng a và b có phần tử chung hay không Một khả năng là
Code liên quan đến goto luôn có thê được viết mà không cần đến goto, mặc đù có
thé bd qua một số thử nghiệm lặp lại hoặc một biến bổ sung Ví dụ, mảng tìm kiếm trở
thành
Với một vài trường hợp ngoại lệ như được trích dẫn ở đây, code dựa trên câu lệnh goto thường khó hiểu và khó duy trì hơn code không có goto Mặc đù chúng tôi không giáo điều về vần đề này, nhưng có vẻ như việc sử dung goto la kha hiém hoi
Trang 22CHUONG 4: HAM VA CAU TRUC CHUONG TRINH
Ham phân chia những nhiệm vụ lớn trên máy tính ra từng phần nhỏ hơn, và cho
phép chúng ta dựng lên từ những gì đã có sẵn thay vì phải bắt đầu lại từ con số 0 Những hàm thích hợp sẽ ân đi những chỉ tiết trong cách thức hoạt động của một chương trình mà
chúng ta không cần thiết phải biết đến, từ đó khiến moi thir dé hiéu hon, để thay đôi hơn
Ngôn ngữ C được thiết kế để giúp các hàm trở nên hiệu quả và đễ sử dụng hơn; nhìn chung, chương trình C bao gồm nhiều hàm nhỏ thay vì các hàm lớn Một chương
trình có thê nằm trong một hoặc nhiều tập tin nguồn Cac tap tin nguon có thê được biên
dịch một cách riêng lẻ và tải đồng thời, cùng với những hàm đã được biên dịch trước đó
từ những thư viện Tuy nhiên, chúng ta sẽ không đi sâu vào quá trình đó ở chương này,
vì các chỉ tiết vô cùng đa dạng qua từng hệ thông
Những khai báo và định nghĩa về hàm là nơi mà tiêu chuẩn ANSI đã có những thay đôi rõ rệt với ngôn ngữ C Như chúng ta đã thấy ở chương l, giờ đây việc khai báo các
loại đối số khi một hàm được khai báo là khả thi Cu pháp của định nghĩa hàm cũng được thay đôi, để các khai báo và định nghĩa khớp nhau Điều này giúp bộ biên dịch có thê
phát hiện ra nhiều lỗi hơn so với trước đây Hơn thế nữa, khi một đối số được khai báo
đúng cách, các coercion dugc ty động thực hiện
Tiêu chuẩn này giải thích những quy luật của các tên gọi; trên thực tế, nó yêu cầu chỉ một định nghĩa duy nhất cho mỗi đối tượng bên ngoài Việc khởi tạo trở nên tổng quát hơn, các mảng và cầu trúc tự động bây giờ có thê được khởi tạo
Bộ xử lý trước của C cũng được nâng cao Các cơ sở xử lý mới bao gồm một bộ chỉ
thị biên dich có điều kiện hoàn chỉnh hơn, một cách đề tạo các chuỗi trích dẫn từ các đối
so macro và kiêm soát tôt hơn quá trình mở rộng macro
Trang 234.1 Co ban vé ham
Dé bat dau, chung ta hãy thiết kế và viết một chương trình in từng dòng đầu vào chứa một mẫu hoặc chuỗi ký tự cụ thê (Đây là trường hợp đặc biệt của UNIX grep) Ví
du: tim kiém cum “ould” trong các dòng sau
To grasp this sorry Scheme of Things entire, Would not we shatter it to bits and then
Re-mould it nearer to the Heart's Desire!
sé cho dau ra
Would not we shatter it to bits and then
Re-mould it nearer to the Heart's Desire!
Công việc được chia thành ba phân:
Mặc dù chắc chắn có thể đặt code cho tất cả những điều này trong mai, nhưng cách tốt hơn là sử dụng cấu trúc đề tận dụng lợi thế bằng cách biến phần bộ đệm thành
một hàm riêng biệt Những phần nhỏ sẽ dễ xử lý hơn một phân lớn vì các chi tiết không
liên quan có thê bị ân trong các chức năng và khả năng xảy ra các tương tác không mong muốn được giảm thiểu Và các phần thậm chí có thê hữu ích trong các chương trình khác
Trang 24“While there's another line” là getline, mot ham ma chung ta da viét 6 Chuong | va
“print it” la printf, thir ma ai do da cung cap cho chung ta Diéu nay co nghia la chung ta chỉ cần viết một thủ tục đề quyết định xem dòng đó có chứa sự xuất hiện của mẫu hay không
Chúng ta có thê giải quyết vấn đề đó bằng cách viết hàm strindex( s.f) trả về vị trí hoặc chỉ mục trong chuỗi s nơi chuỗi t bắt đầu hoặc -I nêu s không chứa t Bởi vì mảng
C bat dau 6 vi trí 0, nên các chỉ mục sẽ bằng 0 hoặc dương và do đó, giá trị âm như -Ì sẽ
thuận tiện cho việc báo hiệu lỗi Sau này khi chúng ta cần so khớp mẫu phức tạp hơn, chúng ta chỉ phải thay thế strindex; phần còn lại của mã có thể giữ nguyên (Thư viện
chuẩn cung cấp ham sfrstr tương tự như strnndex, ngoại trừ việc nó trả về một con trỏ thay vì một chỉ mục.)
Với nhiều thiết kế như vậy, việc điền thông tin chỉ tiết của chương trình rất đơn giản Đây là toàn bộ, vì vậy bạn có thê thấy các mảnh ghép khớp với nhau như thế nào Hiện tại, mẫu cần tìm kiếm là một chuỗi ký tự, không phải là cơ chế tổng quát nhất Chúng ta sẽ sớm quay lại phần thảo luận về cách khởi tạo mảng ký tự và trong Chương 5
sẽ chỉ ra cách biến mẫu thành một tham số được thiết lập khi chương trình được chạy
Ngoài ra còn có một phiên bản hơi khác một chút của getline; bạn có thê thấy hữu ích khi
so sánh nó với cái trong Chương 1
Trang 25Mỗi định nghĩa hàm có dạng
Trang 26Nó không làm gì và không trả về gì Chức năng không làm gì như thế này đôi khi hữu ích với vai trò giữ chỗ trong quá trình phát triển chương trình Nếu kiểu trả về bị bỏ qua, int duoc giả định
Một chương trình chỉ là một tập hợp các định nghĩa về các biến và hàm Giao tiếp giữa các hàm được thực hiện bằng các đối số và giá trị được hàm trả về và thông qua các biến bên ngoài Các hàm có thê xuất hiện theo bất kỳ thứ tự nào trong tệp nguồn và chương trình nguồn có thê được chia thành nhiều tệp, miễn là không có hàm nảo bị phân chia
Câu lệnh return là cơ chế trả về một giá trị từ hàm được gọi cho hàm gọi nó Bat ky
biểu thức nào cũng có thé theo sau return:
Biểu thức này sẽ được chuyên đôi thành kiêu trả về của hàm nêu cân thiết Dấu ngoặc đơn thường được sử dụng xung quanh biêu thức, nhưng không bắt buộc Hàm khi gọi có thể bỏ qua giá trị trả về Hơn nữa, không cần có biểu thức nào sau return; trong trường hợp đó, không có giá trị nào được trả lại cho người gọi Điều khiển cũng trả về cho người gọi mà không có giá trị khi việc thực thi "rơi ra khỏi phần cuối"
Trang 27của hàm bằng cách chạm đến dấu ngoặc đóng bên phải Điều này không phải là bất hợp pháp, nhưng có thể là đầu hiệu của sự cố nếu một hàm trả về một giá trị ở nơi này và không có giá trị ở nơi khác Trong mọi trường hợp, nêu một hàm không trả về một giá trị thì "giá trị" của nó chắc chăn là rác
Chương trình tìm kiếm mẫu trả về trạng thái từ main, số lượng kết quả khớp được tìm thấy Giá trị này có sẵn để sử dụng bởi môi trường được gọi là chương trình
Cơ chề biên địch và tải một chương trình C nằm trên nhiều tệp nguồn khác nhau tùy theo hệ thống Ví dụ, trên hệ thống UNIX, lệnh cc được đề cập ở Chương I sẽ thực hiện công việc này Giả sử rằng ba hàm này được lưu trữ trong ba tệp co tén main c, getline c
và strindex c Sau đó lệnh
biên dịch ba tệp, đặt code của đối tượng kết quả vào các tệp main.o, getiine.o và
strindex,.o, sau do tai tat ca chúng vào một tệp thực thi có tên là a.out Nếu có lỗi, chăng hạn như trong main.c, tệp đó có thể được tự biên dịch lại và kết quả được tải cùng với
các tệp đối tượng trước đó bằng lệnh
Lệnh cc sử đụng quy ước đặt tên " c" so với " o" để phân biệt tệp nguồn với tệp
đối tượng
Bài tập 4-1 Viết hàm strrindex ( s, t), trả về vị trí xuất hiện ngoài cùng bên phải của
t trong s hoac -1 nêu không có
4.2 Hàm trả về số không nguyên
Trang 28Cho đến nay các ví dụ về hàm của chúng ta đều không trả về giá trị (void) hoặc int Điều gì xảy ra nếu một hàm phải trả về một số kiểu khác? Nhiều hàm số như sqrf, sin va cos return double; các hàm chuyên biệt khác trả về các kiêu khác Để minh họa cách giải quyết vấn đề này, chúng ta hãy viết và sử đụng hàm atof{s), hàm này sẽ chuyên đôi chuỗi
s thành chuỗi dâu phây động có độ chính xác kép tương đương atof là một phần mở rộng của atoi, mà chúng tôi đã trình bày các phiên bản trong Chương 2 và 3 Nó xử lý một dâu hiệu và dấu thập phân tùy chọn cũng như sự hiện diện hay vắng mặt của phần nguyên hoặc phần phân số Phiên bản của chúng tôi không phải là một quy trình chuyển đôi đầu vào chất lượng cao; điều đó sẽ chiếm nhiều không gian hơn mức chúng ta muốn sử dụng Thư viện tiêu chuẩn bao gồm atof: tiêu đề <stdlib.h> khai báo nó
Đầu tiên, bản thân atof phải khai báo kiểu giá trị mà nó trả về, vì nó không phải là
Int Tên loại đứng trước tên hàm:
Thứ hai, và cũng quan trọng không kém, quy trình gọi phải biết rằng atof trả về giá trị không phải int Một cách để đảm bảo điều này là khai báo atof một cách rõ ràng trong
Trang 29quy trình gọi Khai báo được hiển thị trong máy tính nguyên thủy này (hầu như không đủ
để cân bằng số séc), đọc một số trên mỗi dòng, tùy ý đặt trước một dấu hiệu và cộng chúng lại, m tông hiện có sau môi đâu vảo:
nÓI rằng sum là một biến kép và atof là một hàm nhận một đối số char[] va tra về
một giá trị gấp đôi
Hàm atof phải được khai báo và xác định một cách nhất quán Nếu chính atof và
lệnh gọi nó trong main có kiều không nhất quán trong cùng một tệp nguôn, thì lỗi sẽ
được trình biên dịch phát hiện Nhưng nếu (rất có thẻ) atof được biên dịch riêng biệt thi
sự không khớp sẽ không được phát hiện, atof sẽ trả về một giá trị gấp đôi mà main sẽ coi
là immt và sẽ dân đến các câu trả lời vô nghĩa
Dựa trên những gì chúng tôi đã nói về cách các khai báo phải khớp với các định nghĩa, điều này có vẻ đáng ngạc nhiên Lý do có thê xảy ra sự không khớp là nêu không
có nguyên mẫu hàm, thì hàm sẽ được khai bảo ngầm bằng lần xuất hiện đầu tiên trong một biểu thức, chăng hạn như
Trang 30Nếu một tên chưa được khai báo trước đó xuất hiện trong một biểu thức và được
theo sau bởi đầu ngoặc đơn bên trái, thì tên đó được khai báo theo ngữ cảnh là tên hàm, hàm đó được giả định trả về một int và không có gì được giả định về các đối số của nó Hơn nữa, nêu một khai báo hàm không bao gôm các đôi sô, như trong
điều này cũng có nghĩa là không có gì phải giả định về các lập luận của atof; tất cả
việc kiểm tra tham số đều bị tắt Ý nghĩa đặc biệt này của danh sách đối số trong nham
mục đích cho phép các chương trình C cũ hơn biên dịch với các trình biên dịch mới Nhưng sẽ là một ý tưởng tồi nếu sử dụng nó với các chương trình mới Nếu hàm này mat đôi số, khai báo chúng: nều không có đôi sô, hãy sử dụng void
Cho atof, được khai báo đúng, chúng ta có thê viết atoi (chuyên một chuỗi thành int) theo no:
Lưu ý câu trúc của các khai báo va cau lénh return Gia tri cua biéu thire trong
duoc chuyén đổi thành loại hàm trước khi trả về Do đó, giá trị của atof, một
double, được tự động chuyển đôi thành int khi nó xuất hiện trong return nay, vi hàm atoi
trả về một int Tuy nhién, thao tac nay co khả năng loại bỏ thông tin nên một số trình biên dich sẽ cảnh báo về nó
Trang 31Bài tập 4-2 Mở rộng atof để xử lý ký hiệu khoa học dạng 123.45e-6 trong đó số dâu phẩy động có thê được theo sau bởi e hoặc E và số mũ có dấu tùy ý
4.3 Biến bên ngoài
Một chương trình C bao gồm một tập hợp các đôi tượng bên ngoài, là các biến hoặc
Ar
ham Tinh từ "bên ngoài" được sử dụng trái ngược với "nội bộ”, mô tả các đối số và biến được xác định bên trong các hàm Các biến bên ngoài được xác định bên ngoài bat ky ham nao và vì thế có khả năng sử dụng được cho nhiều hàm Bản thân các hàm luôn ở
bên ngoài, vì C không cho phép các hàm được xác định bên trong các hàm khác Theo mặc định, các biến và hàm bên ngoài có đặc tính là tất cả các tham chiếu đến chúng có cùng tên, ngay cả từ các hàm được biên dịch riêng biệt, đều là tham chiếu đến cùng một thứ (Tiêu chuẩn gọi thuộc tinh nay là liên kết ngoài.) Theo nghĩa này, các biến bên ngoài tương tự như các khối Fortran COMMON hoặc các biến ở khối ngoài cùng trong Pascal Sau này chúng ta sẽ xem cách xác định các biến và hàm bên ngoài chỉ hiển thị trong một tệp nguồn duy nhất
Bởi vì các biến bên ngoài có thể truy cập được trên toàn cầu nên chúng cung cấp một giải pháp thay thê cho các đối số của hàm và trả về các giá trị đề truyền đữ liệu giữa các hàm Bất kỳ hàm nào cũng có thê truy cập một biến ngoài bằng cách tham chiếu đến
nó theo tên, nều tên đó đã được khai báo bằng cách nào đó
Nếu một số lượng lớn các biến cần phải được chia sẻ giữa các hàm, các biến bên ngoài thuận tiện và hiệu quả hơn danh sách đối số dài Tuy nhiên, điều đã được chỉ ra ở
Chương I, lý do này nên được áp dụng một cách thận trọng, vì nó có thể có tác động xâu
đến cầu trúc chương trình và dẫn đến việc các chương trình có quá nhiều kết nói đữ liệu giữa các hàm Các biến bên ngoài cũng hữu ích vì phạm vi và thời gian tồn tại lớn hơn của chúng Các biến tự động là yếu tố bên trong của một hàm; chúng tổn tại khi chức
Trang 32năng được nhập và biến mắt khi chức năng được bỏ lại Mặt khác, các biến bên ngoài là
vĩnh viễn, vì vậy chúng giữ lại các giá trị từ lần gọi hàm này sang lần gọi hàm tiếp theo
Do đó, nếu hai hàm phải chia sẻ một số dữ liệu, nhưng không hàm nào gọi hàm kia, thì sẽ thuận tiện nhất nếu đữ liệu dùng chung được giữ trong các biến ngoài thay vì được truyền vào và ra thông qua các đối số Chúng ta hãy xem xét vấn đề này sâu hơn bằng một ví dụ lớn hơn Vấn đề là viết một chương trình máy tính cung cấp các toán tử +, -, *
và / Vì đễ thực hiện hơn nên máy tính sẽ sử đụng ký hiệu Ba Lan ngược thay vì trung tố (Tiếng Ba Lan ngược được một số máy tính bỏ túi sử đụng và trong các ngôn ngữ như Forth va Postscript.)
Trong ký hiệu Ba Lan ngược, mỗi toán tử tuân theo toán hạng của nó; một biểu thức trung tố như
(1-2) * (4+5)
được nhập dưới dạng
12-45 +*
Dấu ngoặc đơn là không cần thiết; ký hiệu này rõ ràng miễn là chúng ta biết mỗi
toán tử mong đợi có bao nhiều toán hạng
Việc thực hiện rất đơn giản Mỗi toán hạng được đây vào một ngăn xếp; khi một toán tử xuất hiện, số lượng toán hạng thích hợp (hai toán hạng cho toán tử nhị phân) sẽ được đưa ra, toán tử được áp dụng cho chứng và kết quả được đây trở lại ngăn xếp Ví
dụ, trong ví dụ trên, I và 2 được đây lên, sau đó được thay thế bằng hiệu của chúng, -l Tiếp theo, 4 và 5 được đây lên và thay thế bằng tổng của chúng, 9 Tích của -1 và 9, bằng -9, thay thế chúng trên stack Giá trị trên cùng của stack được bật lên và in ra khi gặp phân cuôi của dòng đâu vào
Trang 33Do do, cau tric cha chương trình là một vòng lặp thực hiện thao tác thích hợp trên mỗi toán tử và toán hạng khi nó xuât hiện:
Quá trình đây và bật một stack là bình thường, nhưng do thời gian phát hiện và phục hỏi lỗi được thêm vào, chúng đủ đài để tốt hơn nên đặt từng cái vào một hàm riêng biệt hơn là lặp lại code trong toàn bộ chương trình Và cần có một hàm riêng dé tim nap
toán tử hoặc toán hạng tiếp theo
Quyết định thiết kế chính vẫn chưa được thảo luận là stack nằm ở đâu, tức là các
thủ tục nào truy cập trực tiếp vào nó Một khả năng là giữ nó trong main và chuyền stack cũng như vị trí stack hiện tại cho các quy trình đây và bật Nhưng main không cân biết về
các biến điều khiến stack; nó chỉ thực hiện các thao tác đây và bật Vì vậy, chúng tôi đã
quyết định lưu trữ stack và thông tin liên quan của nó trong các biến bên ngoài có thể truy cập được bằng các hàm push và pop nhưng không thể truy cập được vào hàm main Việc dịch dàn ý này thành code là đủ để đàng Nếu bây giờ chúng ta coi chương trình đã ton tại trong một tệp nguồn thì nó sẽ trông như thế này:
Trang 34Sau này chúng ta sẽ thảo luận về cách chia tệp này thành hai hoặc nhiều tệp nguồn Hàm mam là một vòng lặp chứa swItch thuộc loại toán tử hoặc toán hạng: đây là cách sử dụng switch điển hình hơn cách sử dụng được trình bày trong Phần 3.4.