Tài liệu Giáo trình ngôn ngữ lập trình C: Lý thuyết và thực hành: BÙI THẾ HỒNG
GI ÁO TR ÌNH
Ngôn ngữ lập trình C:
L Ý THUYẾT VÀ THỰC HÀNH
NHÀ XUẤT BẢN KHOA HỌC KỸ THUẬT
LỜI NÓI ĐẦU
Phần I: Các khái niệm cơ bản
Bài 1 Mở đầu
Tóm tắt lịch sử phát triển của ngôn ngữ C
Ngôn ngữ lập trình C do Dennis Ritchie tạo lập vào năm 1972 tại Bell Telephone
Laboratories. Mục đích ban đầu của ngôn ngữ này là để thiết kế hệ điều hành UNIX.
C là một ngôn ngữ mạnh và mềm dẻo nên nó đã được rất nhiều người sử dụng. Khắp nơi
người ta đã bắt đầu dùng nó để viết mọi loại chương trình. Tuy nhiên, các tổ chức khác
nhau đã bắt đầu sử dụng các version khác nhau của C, và đã phát sinh nhiều sự khác nhau
trong các thao tác làm cho người lập trình phải đau đầu. Để khắc phục vấn đề này, năm
1983, Viện Tiêu chuẩn Quốc gia của Mỹ (ANSI) đã thành lập một ủy ban để đưa ra một
định nghĩa chuẩn cho ngôn ngữ C, được gọi là ANSI Standard C.
Có lẽ phải nói thêm một chút về tên của ngôn ngữ. Ngôn ngữ này có tên là C vì trước nó đã
có một ngôn ngữ được gọ...
195 trang |
Chia sẻ: putihuynh11 | Lượt xem: 1887 | Lượt tải: 0
Bạn đang xem trước 20 trang mẫu tài liệu Giáo trình ngôn ngữ lập trình C: Lý thuyết và thực hành, để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
BÙI THẾ HỒNG
GI ÁO TR ÌNH
Ngôn ngữ lập trình C:
L Ý THUYẾT VÀ THỰC HÀNH
NHÀ XUẤT BẢN KHOA HỌC KỸ THUẬT
LỜI NÓI ĐẦU
Phần I: Các khái niệm cơ bản
Bài 1 Mở đầu
Tóm tắt lịch sử phát triển của ngôn ngữ C
Ngôn ngữ lập trình C do Dennis Ritchie tạo lập vào năm 1972 tại Bell Telephone
Laboratories. Mục đích ban đầu của ngôn ngữ này là để thiết kế hệ điều hành UNIX.
C là một ngôn ngữ mạnh và mềm dẻo nên nó đã được rất nhiều người sử dụng. Khắp nơi
người ta đã bắt đầu dùng nó để viết mọi loại chương trình. Tuy nhiên, các tổ chức khác
nhau đã bắt đầu sử dụng các version khác nhau của C, và đã phát sinh nhiều sự khác nhau
trong các thao tác làm cho người lập trình phải đau đầu. Để khắc phục vấn đề này, năm
1983, Viện Tiêu chuẩn Quốc gia của Mỹ (ANSI) đã thành lập một ủy ban để đưa ra một
định nghĩa chuẩn cho ngôn ngữ C, được gọi là ANSI Standard C.
Có lẽ phải nói thêm một chút về tên của ngôn ngữ. Ngôn ngữ này có tên là C vì trước nó đã
có một ngôn ngữ được gọi là B. Ngôn ngữ B do Ken Thompson phát triển cũng tại Bell
Labs. Có thể, B là để chỉ tên của phòng thí nghiệm, còn C chắc là do đứng sau B.
Tại sao lại dùng C?
Trong thế giới lập trình ngày nay, có rất nhiều ngôn ngữ lập trình bậc cao để lựa chọn, chẳng
hạn như C, Pascal, BASIC, Modula, v.v. Tất cả đều là những ngôn ngữ tuyệt vời phù hợp
cho hầu hết các công việc lập trình. Nhưng tuy vậy, vẫn có một vài lý do để nhiều nhà lập
trình chuyên nghiệp cảm thấy C là ngôn ngữ đứng đầu, vì:
C là một ngôn ngữ mạnh và mềm dẻo. Hạn chế duy nhất của C chính là sự hạn chế
trong tư duy trừu tượng của chính người lập trình mà thôi. C được sử dụng cho nhiều
mục đích khác nhau, như thiết kế các hệ điều hành, các bộ soạn thảo văn bản, đồ hoạ,
trang tính, và thậm chí làm các chương trình dịch cho các ngôn ngữ khác.
C là một ngôn ngữ bình dân được nhiều người lập trình chuyên nghiệp ưa dùng. Bằng
chứng là đã có rất nhiều chương trình dịch khác nhau và nhiều tiện ích kèm theo.
C là một ngôn ngữ chuyển đổi được tức là một chương trình C được viết cho một hệ
máy vi tính (ví dụ IBM PC) có thể được dịch và chạy trên một hệ thống khác (ví dụ
DEC VAX) mà chỉ cần thay đổi chút ít hoặc không cần thay đổi gì cả.
C là một ngôn ngữ ngắn gọn, nó chỉ bao gồm một số các từ được gọi là từ khóa
(keyword) làm cơ sở để tạo ra các câu lệnh của ngôn ngữ.
Với các đặc điểm trên, C là sự lựa chọn tuyệt vời đối với một ngôn ngữ lập trình. Nhưng có
lẽ người ta còn nghe nói về C++ và một kỹ thuật lập trình mới được gọi là lập trình hướng
đối tượng. Cũng không có gì đáng hoang mang cho lắm vì thực ra C++ chỉ là một ngôn ngữ
siêu C với tất cả những gì mà C có cộng thêm với kỹ thuật lập trình hướng đối tượng. Nếu ai
đó bắt đầu ngay bằng việc học C++, thì tất cả những gì đã được dạy khi học C vẫn còn
được áp dụng cho C++. Trong khi học C, không những chỉ học một ngôn ngữ lập trình phổ
thông và mạnh nhất hôm nay mà còn tự chuẩn bị cho mình một kỹ thuật lập trình hướng đối
tượng cho ngày mai.
Chuẩn bị cho việc lập trình
Khi giải một bài toán cần phải tiến hành một số bước nhất định. Đầu tiên phải hình thành hay
còn gọi là định nghĩa bài toán. Nếu không biết bài toán cần phải giải quyết là gì thì không thể
nào tìm ra kết qủa được. Một khi bài toán đã được định rõ, thì có thể lập ra một kế hoạch
để giải quyết nó. Sau khi đã có một kế hoạch thì việc thực hiện bài toán này sẽ trở nên dễ
dàng. Cuối cùng, một khi kế hoạch đã được thực thi, thì các kết qủa phải được thử lại để
kiểm chứng xem liệu bài toán đã được giải quyết đúng và trọn vẹn chưa. Một logic như trên
có thể được áp dụng cho nhiều lĩnh vực khác nhau bao gồm cả việc lập trình trên máy tính.
Khi tạo lập một chương trình trong C, chúng ta nên tiến hành theo các bước sau đây:
1. Xác định các mục tiêu của chương trình.
2. Xác định các phương pháp mà ta muốn sử dụng trong khi viết chương trình.
3. Tạo lập chương trình để giải quyết bài toán.
4. Chạy chương trình để xem xét các kết qủa.
Chu trình phát triển chương trình
Chu trình phát triển chương trình có các bước riêng của nó. Trong bước thứ nhất, một bộ
soạn thảo sẽ được sử dụng để tạo ra một tệp đĩa chứa các mã gốc (source code). Tại bước
thứ hai, các mã gốc sẽ được dịch để tạo ra một tệp đích (object file). Tại bước thứ ba, các
mã đã được dịch sẽ được kết nối lại để tạo ra một tệp chương trình có thể chạy được
(executable file). Cuối cùng, bước bốn là để chạy chương trình và kiểm tra xem nó có làm
việc như đã dự kiến không.
Tạo lập mã gốc
Các mã gốc là một loạt các câu lệnh, hoặc lệnh được sử dụng để chỉ thị cho máy tính thực
hiện các công việc mà người lập trình mong muốn. Ví dụ, dưới đây là một dòng của mã gốc:
printf ("Chào các bạn");
Lệnh này chỉ thị cho máy tính hiện trên màn hình thông điệp Chào các bạn.
Hầu hết các chương trình dịch đều có một bộ sạon thảo để soạn ra các tệp mã gốc. Tuy
nhiên, ta cũng có thể sử dụng bất kỳ một bộ soạn thảo văn bản nào để viết các chương trình
C. Chỉ cần lưu ý là khi cất giữ tệp thì phải chọn kiểu tệp là văn bản và có phần mở rộng là
.C.
Dịch các mã gốc
Các máy tính chỉ hiểu được một ngôn ngữ, đó là ngôn ngữ máy (machine language). Bởi
vậy, trước khi một chương trình C có thể chạy được trên một máy tính, nó phải được dịch
từ các mã gốc sang mã máy bằng một chương trình được gọi là chương trình dịch
(compiler). Chương trình dịch lấy các mã gốc làm input và cho ra một tệp đĩa chứa các câu
lệnh mã máy tương ứng với các câu lệnh mã gốc. Các câu lệnh mã máy được tạo ra bởi
chương trình dịch được gọi là các mã đích và tệp đĩa chứa chúng được gọi là tệp đích. Tệp
đích có tên trùng với tên tệp gốc và luôn có đuôi là OBJ. Đuôi OBJ để chỉ rằng tệp này là
tệp đích và sẽ được sử dụng bởi chương trình kết nối (linker).
Kết nối để tạo ra một tệp có thể chạy được
Cần phải thực hiện thêm một bước nữa thì chương trình mới có thể chạy được. Trong C có
một bộ phận được gọi là thư viện các hàm, nó chứa các mã đích của các hàm đã được lập
sẵn. Ví dụ, hàm printf được sử dụng trong ví dụ trước là một hàm thư viện. Các hàm thư
viện thực hiện các công việc thường hay phải dùng đến, chẳng hạn như hiện các thông tin lên
màn hình, đọc số liệu từ các tệp đĩa. Nếu một chương trình có sử dụng các hàm thư viện thì
tệp đích của nó cần phải được kết nối với mã đích của các hàm này để tạo ra một chương
trình có thể chạy được. Tệp này có tên trùng với tên tệp gốc và có đuôi là .EXE.
Hoàn thiện chu trình phát triển
Sau khi dịch và kết nối cần phải chạy thử chương trình. Nếu kết qủa nhận được không như
mong muốn thì cần phải tìm ra nguyên nhân dẫn đến các sai sót và sửa lại chương trình cho
đúng. Khi có bất kỳ một thay đổi nào trong mã gốc, chương trình phải được dịch và kết nối
lại. Chu trình trên sẽ phải lặp đi lặp lại cho đến khi nào nhận được kết qủa như mong muốn.
Một điểm lưu ý cuối cùng về việc dịch và kết nối là, mặc dù việc dịch và kết nối như đã trình
bầy trên đây là hai giai đoạn tách biệt nhưng nhiều chương trình dịch, chẳng hạn các chương
trình dịch chạy dưới DOS lại gộp hai bước trên thành một. Cho dù như vậy nhưng vẫn cứ
phải hiểu rằng đây là hai qúa trình riêng rẽ ngay cả khi chúng chỉ được thực hiện bằng một
lệnh.
Chu trình phát triển một chương trình C
Bước 1: Sử dụng một bộ soạn thảo để viết các mã gốc.
Thông thường các tệp gốc của C có đuôi là .C
Bước 2: Dùng một chương trình để dịch chương trình gốc.
Nếu chương trình dịch không phát hiện một sai sót nào trong chương trình gốc,
nó sẽ sinh ra một tệp đích. Tệp này có cùng tên với tệp chương trình gốc với
đuôi là .OBJ. Nếu chương trình dịch tìm thấy một lỗi nào đó, nó sẽ thông báo
lên màn hình và ta phải trở lại từ bước một để sửa các lỗi đã được phát hiện
trong chương trình gốc.
Bước 3: Kết nối chương trình bằng bộ kết nối.
Nếu không có lỗi, bộ kết nối sẽ sinh ra một tệp đĩa có tên trùng với tên chương
trình đích với đuôi .EXE, chứa một chương trình có thể chạy được.
Bước 4: Chạy chương trình.
Cần phải chạy thử chương trình sau khi kết thúc bước 3 để xem nó đã hoạt
động đúng như mong muốn chưa. Nếu chưa thì phải quay lại từ bước 1 và tiến
hành các thay đổi cần thiết trong mã gốc.
Kết luận
C là một công cụ lập trình mạnh, thông dụng và chuyển đổi được. Bài này giải thích các
bước khác nhau trong qúa trình viết một chương trình C. Đó là chu trình soạn
thảo/dịch/kết nối/kiểm tra.
Việc mắc lỗi trong khi phát triển chương trình là một điều không thể tránh khỏi. Chương
trình dịch của C sẽ phát hiện các lỗi trong mã gốc và hiện một thông báo lỗi về kiểu lỗi và vị
trí của lỗi. Các thông tin này giúp người lập trình sửa lại mã gốc một cách dễ dàng. Tuy
nhiên, chương trình dịch không phải lúc nào cũng thông báo được chính xác kiểu của lỗi
cũng như vị trí của chúng. Đôi khi người lập trình phải vận dụng các kiến thức về C để phát
hiện các sai sót đã gây ra thông báo lỗi.
Câu hỏi và trả lời
1. Nếu muốn cho một người nào đó một chương trình C thì phải cho tệp nào?
C là một ngôn ngữ có chương trình dịch. Điều đó có nghĩa là, sau khi mã gốc đã được
dịch sẽ phát sinh một chương trình có thể chạy được. Chương trình này có thể tự chạy
một mình. Nếu muốn chuyển CHAO đến những người nào đó, điều duy nhất cần làm là
hãy chuyển cho họ tệp CHAO.EXE. Họ không cần tệp gốc CHAO.C, hoặc tệp đích
CHAO.OBJ và thậm chí ngay cả chương trình dịch C nữa.
2. Sau khi đã tạo được tệp có thể chạy được, thì có cần phải giữ tệp gốc hoặc tệp
đích nữa không?
Nếu tệp gốc bị mất thì sau này không còn cách nào khác để có thể thay đổi được
chương trình đã dịch và kết nối. Do vậy nên giữ lại các tệp gốc. Nhưng đối với các tệp
đích thì lại khác. Có một vài lý do cần phải giữ lại các tệp đích, tuy nhiên nếu nói ngay
bây giờ thì còn hơi sớm. Do vậy, có thể xóa tệp đích một khi đã có tệp có thể chạy
được. Nếu cần tệp đích, có thể dịch lại từ tệp gốc.
3. Liệu có thể bỏ qua các thông báo nhắc nhở không?
Một số thông báo nhắc nhở không làm ảnh hưởng đến việc chạy của chương trình. Một
số khác lại có. Nếu chương trình dịch cho ra một thông báo nhắc nhở, thì đó là một dấu
hiệu báo rằng có một cái gì đó không hoàn toàn đúng. Hầu hết các chương trình dịch
đều cho phép đặt mức nhắc nhở. Bằng cách dó thì chỉ nhận được các nhắc nhở từ mức
này trở đi. Trong chương trình, cần phải xem từng nhắc nhở một và quyết định nên khắc
phục nó hay là không. Tốt nhất là nên sửa lại mã gốc sao cho không còn một thông báo
lỗi và nhắc nhở nào.
Luyện lập
Mục Luyện tập sẽ cho các câu hỏi để giúp người đọc củng cố lại kiến thức của mình và các
bài tập nhằm cung cấp các kinh nghiệm khi sử dụng những điều đã học.
Câu hỏi
1. Hãy nêu ba lý do tại sao C là một lựa chọn tốt nhất về ngôn ngữ lập trình.
2. Chương trình dịch làm công việc gì?
3. Các bước trong chu trình phát triển chương trình là gì?
4. Cần phải gõ lệnh nào để dịch một chương trình có tên PROG1.C bằng chương trình
dịch C ?
5. Chương trình dịch C kết nối và dịch bằng một lệnh hay bằng hai lệnh riêng rẽ?
6. Nên dùng đuôi nào cho các tệp gốc của C?
7. FILENAME.TXT có hợp lệ cho một tệp gốc của C không?
8. Nếu một chương trình đã được dịch và nó không làm việc như mong muốn, thì nên làm
như thế nào ?
9. Mã máy là gì?
10. Bộ kết nối làm công việc gì?
Bài tập
1. Hãy nhập và dịch chương trình sau đây. Chương trình này làm việc gì? (không viết các
số hiệu dòng khi soạn chương trình)
1: include
2: int bankinh, dientich;
3: main()
4:
5: printf( "Nhập bán kính: " );
6: scanf( "%d", &bankinh );
7: dientich = 3.14159 * bankinh * bankinh;
8: printf( "\n\nDiện tích = %d", dientich );
9: return 0;
10:
2. Nhập và dịch chương trình sau. Chương trình này làm việc gì?
1: #include
2: int x,y;
3: main()
4:
5: for ( x = 0; x < 10; x++, printf( "\n" ) )
6: for ( y = 0; y < 10; y++ )
7: printf( "X" );
8: return 0;
9:
3. Chương trình sau đây có một lỗi. Hãy nhập và dịch nó. Các dòng nào sinh ra
thông báo lỗi?
1: #include
2: main();
3:
4: printf( "Hãy nhìn xem!" )
5: printf( "Bạn sẽ tìm thấy nó!" );
6: return 0;
7:
4. Chương trình sau đây cũng có lỗi. Hãy nhập và dịch nó. Các dòng nào sinh
ra thông báo lỗi?
1: #include
2: main()
3:
4: printf( "Đây là một chương trình" )
5: do_it( "có lỗi!" );
6: return 0;
7:
5. Thay lệnh số 7 trong chương trình ở bài tập 2 như dưới đây. Dịch và chạy lại
chương trình. Bây giờ chương trình này làm gì?
7: printf( "%c", 1 );
Bài 2. Các thành phần của một chương trình
C
Mọi chương trình C đều chứa một số các thành phần được liên kết với nhau theo một cách
nhất định. Để có một bức tranh tổng thể, trước hết, ta bắt đầu xét một chương trình C ngắn
nhưng hoàn chỉnh với tất cả các thành phần của nó.
Một chương trình C ngắn
Chương trình 2.1 là mã gốc của một chương trình có tên là NHAN.C. Đây là một chương
trình rất đơn giản: nó chỉ bao gồm việc nhập hai số từ bàn phím và tính tích của chúng. Ở
đây, chúng ta chưa cần phải hiểu tất cả các lệnh của chương trình, mà chủ yếu là làm quen
với các thành phần của một chương trình C mà thôi.
Trước khi xét chi tiết chương trình này, cần phải hiểu rõ khái niệm về hàm (function), vì hàm
là một yếu tố cơ bản trong việc lập trình C. Một hàm là một bộ phận độc lập của chương
trình, được đặt tên và thực hiện một công việc nào đó. Bằng việc gọi tên một hàm, chương
trình chính có thể thực hiện các lệnh trong hàm này. Chương trình chính có thể gửi các thông
tin, được gọi là đối số (argument) cho hàm, và hàm có thể đưa trở lại thông tin cho chương
trình. Có hai kiểu hàm: các hàm thư viện - một bộ phận của chương trình dịch của C; và
các hàm do người sử dụng tự định nghĩa.
Chương trình 2.1. NHAN.C.
1: /* Chương trình tính tích của hai số. */
2: #include
3: int a,b,c;
4: int tich(int x, int y);
5: main()
6:
7: /* Nhập số thứ nhất */
8: printf("Nhập một số nằm giữa 1 và 100: ");
9: scanf("%d", &a);
10
11: /* Nhập số thứ hai */
12: printf("Nhập một số khác nằm giữa 1 và 100: ");
13: scanf("%d", &b);
14:
15: /* Tính và hiện kết qủa */
16: c = tich(a,b);
17: printf( "\n%d nhân %d = %d", a, b, c);
18:
19:
20: /* Hàm tính tích của hai đối số */
21: int tich(int x, int y)
22:
23: return ( x * y );
24:
Chương trình này sẽ hiện trên màn hình các dòng kết qủa sau:
Nhập một số nằm giữa 1 và 100: 35
Nhập một số khác nằm giữa 1 và 100: 23
35 nhân 23 = 805
Các thành phần của chương trình
Sau đây là mô tả của các thành phần khác nhau của chương trình mẫu nói trên. Các số hiệu
dòng không phải là thành phần của chương trình mà chỉ được đưa vào để tiện việc theo dõi
và phân tích.
Hàm main() (Các dòng 5-18)
Bộ phận duy nhất cần phải có trong mọi chương trình C là hàm main(). Dạng đơn giản
nhất của nó là: main theo sau là một cặp dấu ngoặc tròn ( ) và một cặp dấu ngoặc nhọn .
Nằm giữa các dấu ngoặc nhọn là các lệnh tạo nên thân chính của chương trình. Dưới điều
kiện bình thường, chương trình sẽ bắt đầu thực hiện từ lệnh thứ nhất trong main() và kết
thúc bằng lệnh cuối cùng trong main().
Chỉ thị #include (Dòng 2)
Chỉ thị #include báo cho chương trình dịch của C biết phải thêm vào chương trình một tệp
trong khi dịch. Một tệp được thêm vào là một tệp đĩa riêng biệt chứa các thông tin cần thiết
cho chương trình dịch. Một số tệp trong các tệp này (đôi khi còn gọi là các tệp tiêu đề -
header files) được cung cấp cùng với chương trình dịch. Tất cả các tệp này đều có đuôi là
.H.
Chỉ thị #include trong ví dụ này có nghĩa là "Cộng thêm nội dung của tệp stdio.h". Hầu hết
các chương trình C đều yêu cầu một hoặc nhiều tệp cộng thêm.
Định nghĩa biến
Một biến là một tên gọi cho một đại lượng hoặc một đối tượng nào đó. Mỗi biến đều được
phân bổ một địa chỉ trong bộ nhớ. Chương trình C sử dụng các biến để lưu giữ các loại dữ
liệu khác nhau trong quá trình thực hiện chương trình. Trong C, một biến chỉ có thể được sử
dụng sau khi đã được định nghĩa. Một định nghĩa biến cho chương trình dịch biết tên của
biến và kiểu của dữ liệu mà nó lưu giữ. Trong ví dụ mẫu, định nghĩa ở dòng 3, int
a,b,c, định nghĩa ba biến có tên là a, b, và c và mỗi biến sẽ chứa một giá trị nguyên
(integer). Trong Bài 3 sẽ nói rõ hơn về các biến và các định nghĩa biến.
Khai báo hàm (Dòng 4)
Một khai báo hàm cho chương trình dịch biết tên và đối số của hàm sẽ được sử dụng trong
chương trình. Khai báo này phải xuất hiện trước khi hàm được sử dụng. Khai báo hàm khác
với định nghĩa hàm. Một định nghĩa hàm chứa các lệnh tạo ra hàm đó.
Các lệnh chương trình (Các dòng 8, 9, 12, 13, 16, 17, 23)
Công việc thực sự của một chương trình C được thực hiện bởi các câu lệnh của nó. Các
câu lệnh của C là: hiện các thông tin trên màn hình, đọc các phím được gõ vào từ bàn phím,
thực hiện các phép số học, gọi các hàm, đọc các tệp đĩa, và tất cả các thao tác khác mà
một chương trình cần phải thực hiện. Từ bài này trở đi, mỗi một lệnh C được viết riêng trên
một dòng và luôn luôn được kết thúc bằng một dấu chấm phẩy (;). Sau đây là các giải thích
ngắn gọn cho các câu lệnh của chương trình NHAN.C.
printf()
Lệnh printf() (các dòng 8, 12, và 17) là một hàm thư viện dùng để hiện thông tin lên màn
hình. Lệnh này có thể dùng để hiện một thông báo văn bản đơn giản (như ở dòng 8 và 12)
hoặc một thông báo và một hoặc nhiều biến chương trình (như ở dòng 17).
scanf()
Lệnh scanf() (các dòng 9 và 13) cũng là một hàm thư viện dùng để đọc dữ liệu từ bàn
phím và gán dữ liệu này cho một hoặc nhiều biến chương trình.
c = tich (a, b);
Lệnh này gọi một hàm có tên là tich().Nghĩa là, nó thực hiện các lệnh chương trình chứa
trong hàm tich(). Nó còn gửi các đối số a và b cho hàm. Sau khi các lệnh trong hàm
tich() được hoàn tất, tich() còn đưa ra một giá trị cho chương trình. Giá trị này được
gán và lưu giữ trong biến có tên là c.
return (x * y);
Lệnh này là một bộ phận của hàm tich(). Nó tính tích của các biến x và y rồi đưa kết
qủa trở lại cho chương trình đã gọi tich().
Định nghĩa hàm (Các dòng 21-24)
Một hàm là một đoạn mã gốc độc lập thực hiện một nhiệm vụ nhất định. Mỗi hàm đều có
một tên, và các mã gốc của hàm này được thực hiện mỗi khi tên hàm được gọi trong một
chương trình.
Hàm có tên tich() ở các dòng 21-24 là một hàm do người sử dụng tự định nghĩa và tự
viết trong quá trình phát triển chương trình. Hàm này rất đơn giản: nó chỉ làm mỗi một việc là
nhân hai giá trị với nhau và đưa trở lại kết qủa cho chương trình đã gọi nó.
C còn có các hàm thư viện được cung cấp cùng với chương trình dịch. Các hàm thư viện
thực hiện hầu hết các công việc cơ bản nhất (như các thao tác trên màn hình, bàn phím,
đọc/ghi đĩa) mà một chương trình cần đến. Trong ví dụ này, printf() và scanf() là
các hàm thư viện.
Các chú thích trong chương trình (Các dòng 1, 7, 11, 15, 20)
Bất kỳ phần nào mà khởi đầu bằng /* và kết thúc bằng */ đều được gọi là một chú thích.
Chương trình dịch bỏ qua tất cả các chú thích và như vậy thì chúng tuyệt đối không ảnh
hưởng đến sự hoạt động của một chương trình. Có thể viết bất cứ thứ gì trong một chú thích
và nó sẽ không ảnh hưởng đến hoạt động của chương trình. Một chú thích có thể chỉ chiếm
một phần nào đó của một dòng, toàn bộ một dòng, hoặc cũng có thể nhiều dòng. Tuy nhiên,
không nên lồng chú thích này trong một chú thích khác. Hầu hết các chương trình dịch đều
không chấp nhận loại hình này.
Nhiều người mới bắt đầu học lập trình cho rằng không cần đến chú thích và làm như vậy là
mất thì giờ. Thực ra thì không hẳn là như vậy. Các thao tác trong chương trình có thể rất rõ
ràng và dễ theo dõi trong khi đang viết. Nhưng khi chương trình trở nên dài hơn và phức tạp
dần lên, hoặc khi cần phải sửa đổi một chương trình đã viết từ nhiều tháng trước thì các chú
thích thật là có giá trị. Rõ ràng là cần phải tạo ra một thói quen dùng các chú thích để làm
sáng tỏ tất cả các cấu trúc và các hoạt động trong chương trình.
Các dấu ngoặc nhọn (Các dòng 6, 18, 22, 24)
Các dấu ngoặc nhọn () dùng để nhóm các dòng lệnh thành một khối. Các lệnh của một
hàm, kể cả hàm main(), phải được để trong các dấu ngoặc này.
Chạy chương trình
1. Chuyển vào thư mục chứa chương trình dịch C.
2. Khởi động bộ soạn thảo.
3. Nhập mã gốc của NHAN.C (lưu ý, không nhập các số hiệu dòng).
4. Cất giữ tệp chương trình.
5. Dịch và kết nối chương trình bằng cách ra các lệnh thích hợp với chương trình dịch hiện
có. Nếu không có sai sót gì trong qúa trình dịch cũng như kết nối, chạy chương trình
bằng lệnh run, hoặc bằng cách gõ nhan tại dấu nhắc của DOS.
6. Nếu xuất hiện một hoặc nhiều thông báo lỗi, quay lại từ bước hai và sửa các lỗi đã mắc
phải.
Kết luận
Bài này tuy ngắn nhưng rất quan trọng. Nó cho biết cấu trúc và các thành phần của một
chương trình C. Bộ phận duy nhất mà một chương trình C yêu cầu là hàm main(). Công
việc thực sự của chương trình được thực hiện bởi các lệnh chương trình. Các lệnh này
hướng dẫn cho máy tính thực hiện các hành động mà chương trình mong muốn. Bài này còn
nói về các biến và các định nghĩa biến, và chỉ ra lợi ích của việc sử dụng các chú thích và
cách dùng chúng.
Ngoài hàm main() ra, một chương trình C còn có thể sử dụng hai kiểu hàm khác: các hàm
thư viện được cung cấp cùng với bộ chương trình dịch và các hàm do người sử dụng tự định
nghĩa.
Câu hỏi và trả lời
1. Các chú thích có tác động gì trên một chương trình?
Các chú thích là để cho người lập trình. Khi chương trình dịch chuyển các mã gốc thành
các mã đích, nó sẽ vứt bỏ các chú thích và các vùng trắng. Điều đó có nghĩa là chúng
không hề có một tác động nào lên chương trình có thể chạy được. Các chú thích có làm
cho chương trình gốc dài hơn, nhưng điều đó cũng không đáng kể lắm. Về lâu dài, nên
sử dụng các chú thích và các khoảng trống để chương trình gốc dễ đọc và dễ hiểu hơn.
2. Sự khác nhau giữa một lệnh và một khối là gì?
Một khối là một nhóm các lệnh được đặt giữa các dấu ngoặc nhọn. Một khối có thể
được sử dụng ở hầu hết các vị trí mà tại đó có thể sử dụng một lệnh.
3. Làm thế nào để tìm thấy danh sách các hàm thư viện có thể sử dụng?
Nhiều chương trình dịch có kèm theo tài liệu mô tả các hàm thư viện. Chúng thường
được xếp theo thứ tự từ điển.
Luyện tập
Câu hỏi
1. Thuật ngữ nào dùng để chỉ một nhóm của một hoặc nhiều lệnh C được để giữa các dấu
ngoặc nhọn?
2. Thành phần nào phải luôn luôn có mặt trong một chương trình C?
3. Làm thế nào để thêm vào các chú thích chương trình và tại sao phải dùng chúng?
4. Một hàm là gì?
5. Trong C có hai kiểu hàm. Các hiểu đó là kiểu gì và chúng khác nhau như thế nào?
6. Chỉ thị #include dùng để làm gì?
Bài tập
I. Hãy viết một chương trình C ngắn nhất.
II. Dùng chương trình sau đây để trả lời cho các câu hỏi sau:
A. Các dòng nào chứa các lệnh?
B. Các dòng nào chứa các định nghĩa biến?
C. Các dòng nào chứa các khai báo hàm?
D. Các dòng nào chứa các định nghĩa hàm?
E. Các dòng nào chứa các chú thích?
1: /* BT2-2.C */
2: #include
3: void in_dong(void);
4: main()
5:
6: in_dong();
7: printf("\n Tự dạy chương trình C trong 21 ngày!\n";
8: in_dong();
9: return 0
10: /* In một dòng dấu sao */
11: void in_dong(void)
12:
13: int dem;
14: for(dem = 0; dem = 21; dem ++)
15: printf("*");
16:
17: /* kết thúc chương trình */
III. Chương trình sau đây làm việc gì?
1: /*BT2-3.C */
2: #include
3: main()
4:
5: int ctr;
6: for ( ctr=65; ctr,91; ctr++ );
7: printf( "%c", ctr );
8: return 0;
9:
10: /* kết thúc chương trình */
IV. Chương trình sau đây làm việc gì?
1: /* BT2-4.C */
2: #include
3: main()
4:
5: char buffer(256);
6: printf( "Hãy viết tên của bạn và ấn :\n";
7: gets(buffer);
8: printf( "\nTên của bạn có %d chữ cái và dấu cấch!",
strlen( bufferr);
9: return 0;
10:
Bài 3. Các biến và các hằng
Các chương trình máy tính thường làm việc với các kiểu dữ liệu khác nhau và cần một cách
nào đó để lưu giữ các giá trị sẽ phải sử dụng. Các giá trị này có thể là các số hoặc các ký
tự. C có hai cách để lưu giữ các giá trị số - biến (variable) và hằng (contstant) - với nhiều
tuỳ chọn khác nhau cho mỗi cách. Một biến là một tên gọi và được phân bổ một địa chỉ lưu
trữ. Địa chỉ này được dùng để lưu giữ giá trị của biến. Giá trị này có thể thay đổi trong khi
chương trình thực hiện. Ngược lại, một hằng có một giá trị cố định không được thay đổi.
Trước khi đi vào các biến, cần biết qua một chút về sự hoạt động của bộ nhớ máy tính.
Bộ nhớ của máy tính
Máy tính dùng một bộ nhớ truy nhập ngẫu nhiên (random access memory - RAM) để lưu
giữ thông tin trong khi đang hoạt động. RAM được đặt trong các mạch tích hợp, hay còn
gọi là chip, nằm bên trong máy tính. Các thông tin lưu trong RAM thường xuyên có thể bị
xóa và được thay thế bằng các thông tin mới. Ngoài ra RAM chỉ nhớ thông tin khi máy đang
chạy và các thông tin sẽ bị mất khi tắt máy.
Một byte là đơn vị cơ bản của bộ nhớ dữ liệu của máy tính. Các kiểu dữ liệu khác nhau
được lưu giữ bằng các số lượng byte khác nhau. Bảng 3.1. cho thấy sự khác nhau này.
Bảng 3.1. Số lượng bytes cần thiết để lưu giữ dữ liệu
Dữ liệu Số bytes cần thiết
Chữ cái x 1
Số 100 2
Số 120.145 4
Cụm từ Tự học ngôn ngữ C 17
Một trang đánh máy 3000 (gần đúng)
RAM trong máy tính được tổ chức theo kiểu tuần tự, byte này tiếp byte kia. Mỗi byte có
một địa chỉ duy nhất để định danh cho nó và phân biệt nó với tất cả các byte khác trong bộ
nhớ. Các địa chỉ được gán cho các vị trí bộ nhớ một cách tuần tự, khởi đầu từ 0 cho đến
hết bộ nhớ của máy.
Các biến
Một biến là một tên gọi và được phân bổ một địa chỉ lưu trữ. Địa chỉ này được dùng để lưu
giữ giá trị của biến. Giá trị này có thể thay đổi trong khi chương trình thực hiện.
Các tên biến
Trong C, các tên biến phải tuân theo các qui tắc sau:
Tên biến có thể chứa các chữ, các chữ số và dấu gạch dưới (_).
Ký tự đầu tiên của tên phải là một chữ. Có thể dùng dấu gạch dưới như là ký tự đầu
tiên, nhưng không nên làm như vậy vì xem ra không được hợp lý cho lắm.
Chữ hoa và chữ thường là khác nhau. Tức là, Count và count là hai biến khác nhau.
Các từ giành riêng (keyword) của C không được sử dụng làm tên biến. Một keyword là
một từ mà C sử dụng như là một thành phần của nó.
Sau đây là một số ví dụ về các tên biến hợp lệ và không hợp lệ trong C:
phantram /* hợp lệ */
y2x5_fg7h /* hợp lệ */
loinhuan_hangnam /* hợp lệ */
_1995_thue /* hợp lệ, nhưng không nên dùng */
Taikhoan#tk /* không hợp lệ, vì ký tự # không hợp lệ */
double /* không hợp lệ, vì là keyword của C */
9thang /* không hợp lệ, vì ký tự đầu tiên là số */
Vì C phân biệt chữ hoa với chữ thường, nên phantram, Phantram, và PHANTRAM là
tên của ba biến khác nhau. Dù rằng không bắt buộc, nhưng các tên biến trong C thường
được viết bằng chữ thường, còn tên của các hằng thì được viết bằng chữ hoa.
Đối với nhiều chương trình dịch, một tên biến của C có thể dài tới 31 ký tự. Với độ dài như
vậy, có thể tạo ra các tên biến nói lên được ý nghĩa của dữ liệu được lưu giữ trong chúng.
Các tên có bao hàm ý nghĩa thường gồm nhiều từ. Có thể dùng dấu gạch dưới để phân cách
các từ hoặc các cụm từ với nhau cho dễ hiểu. Còn một kiểu khác gọi là bướu lạc đà. Thay
cho dấu cách, người ta viết hoa chữ đầu tiên của mỗi từ. Ví dụ, TyLeLaiSuat, hoặc
TyleLaisuat. Kiểu này có vẻ thuận tiện hơn vì không phải gõ thêm dấu gạch dưới.
Các kiểu biến số
C cung cấp một số kiểu biến số khác nhau. Tại sao lại phải cần một số kiểu biến khác nhau?
Đó là vì, các giá trị số khác nhau yêu cầu số bytes để lưu giữ khác nhau và các phép toán
số học có thể thực hiện trên chúng cũng khác nhau. Các số nguyên nhỏ (ví dụ, 1, 19, -8)
yêu cầu ít byte để lưu giữ, và các phép toán số học (cộng, nhân, v.v.) trên các số này có thể
được thực hiện rất nhanh bằng máy tính. Ngược lại, những số nguyên lớn và các số dấu
chấm động (ví dụ, 123000000 hoặc 0.000000345213) yêu cầu nhiều bytes hơn và thời gian
tính toán với chúng cũng lâu hơn. Bằng cách dùng các kiểu biến số thích hợp, chương trình
sẽ chạy một cách hiệu qủa hơn.
Các biến số của C rơi vào hai nhóm chính sau đây:
Các biến nguyên (interger) dùng để lưu giữ các giá trị không có phần thập phân. Các
biến nguyên lại phân thành hai lớp: biến nguyên có dấu để ghi các giá trị nguyên dương
và nguyên âm; biến nguyên không dấu chỉ để ghi các giá trị không âm (bao gồm cả số
0).
Các biến dấu chấm động (Floating-point) giữ các giá trị có phần thập phân (số thực).
Lưu ý một điểm là, trước đây ta vẫn quen dùng dấu phẩy để ngăn cách phần nguyên và
phần phân của một số, nên mới có tên gọi dấu phẩy động, nay theo chuẩn của ANSI
thì người ta dùng dấu chấm thập phân, bởi vậy mới có tên gọi dấu chấm động. Từ nay
trở đi sẽ dùng dấu chấm thập phân thay cho dấu phẩy thập phân. Còn dấu phẩy được
dùng làm dấu ngăn cách hàng nghìn, hàng triệu, hàng tỷ, v.v.
Bảng 3.2. Các kiểu dữ liệu số của C
Kiểu biến Keyword Số bytes Miền giá trị
ký tự char 1 -128 đến 127
nguyên int 2 -32768 đến 32767
nguyên ngắn short 2 -32768 đến 32767
nguyên dài long 4 -2147483648 đến 2147483647
ký tự không dấu unsigned char 1 0 đến 255
nguyên không dấu unsigned int 2 0 đến 65535
nguyên ngắn kh. dấu unsigned short 2 0 đến 65535
nguyên dài kh. dấu unsigned long 4 0 đến 4294967295
dấu chấm động float 4 1.2E-38 đến 3.4E38*
độ chính xác đơn
dấu chấm động float 8 2.2E-308 đến 1.8E308**
độ chính xác gấp đôi
*
Miền gần đúng; độ chính xác= 7 chữ số.
**
Miền gần đúng; độ chính xác= 19 chữ số.
Miền gần đúng trong bảng này có nghĩa là các giá trị cao nhất và thấp nhất mà một biến đã
cho có thể nhận. Độ chính xác nghĩa là đúng đến bao nhiêu chữ số của giá trị được ghi trong
biến đã cho. (Ví dụ, nếu tính 1/3, thì kết qủa đúng là 0.333333.... với các số 3 cứ tiếp tục
đến vô hạn. Một biến với độ chính xác đơn chỉ ghi được 7 con 3.)
Trong bảng này, các kiểu int và short int là giống nhau. Nhưng tại sao lại phải có hai
kiểu khác nhau như vậy? Các kiểu biến int và short int là giống hệt nhau trên các hệ
thống tương thích với các máy tính IBM-PC 16-bit, nhưng chúng có thể sẽ khác nhau trên
các phần cứng kiểu khác. Trên một hệ VAX, một biến short và một biến int không
cùng cỡ với nhau. Một biến short chiếm 2 bytes, nhưng một biến int lại chiếm những 4
bytes. Do vậy, đối với các máy PC, hai kiểu này là như nhau.
Các khai báo biến
Mọi biến cần phải được khai báo trước khi được đem ra sử dụng. Một khai báo biến cho
chương trình dịch biết tên và kiểu của một biến và có thể khởi tạo cho biến một giá trị nào
đó. Một khai báo biến có dạng sau:
tênkiểu tênbiến
Tênkiểu xác định kiểu của biến và phải là một trong các keyword đã cho trong Bảng 3.2.
Tênbiến là tên của biến được đặt theo các qui tắc đã nói ở phần trước. Có thể khai báo
nhiều biến cùng một kiểu trên một dòng, mỗi biến cách nhau bằng một dấu phẩy. Ví dụ:
int tuoi, luong, con; /* ba biến nguyên */
float phantram, tong; /* hai biên động */
Keyword typedef
Từ khóa typedef được sử dụng để tạo lập một tên mới cho một kiểu dữ liệu đã có. Nói một
cách chính xác, typedef tạo ra một kiểu đồng nghĩa. Ví dụ, lệnh
typedef int integer;
tạo ra kiểu integer từ kiểu int. Sau đó, có thể dùng integer để định nghĩa các biến
kiểu int.
Khởi tạo các biến số
Khi một biến được khai báo, chương trình dịch sẽ dành ra một vùng nhớ cho biến này. Tuy
nhiên, giá trị được nhớ tại vùng này chưa được xác định. Nó có thể là 0, hoặc có thể là một
giá trị ngẫu nhiên nào đó còn lại sau khi vùng này được giải phóng. Bởi vậy, trước khi một
biến được sử dụng, nó phải được khởi tạo bằng một giá trị xác định nào đó. Việc này có thể
thực hiện hoàn toàn độc lập với việc khai báo biến bằng cách dùng một lệnh gán. Ví dụ:
int count; /* Dành một vùng nhớ cho biến count */
count = 0; /* Ghi 0 vào count */
Có thể khởi tạo một biến ngay khi khai báo bằng cách viết dấu bằng và giá trị khởi tạo sau
tên biến. Ví dụ
int count = 0;
double phantram = 0.01, tyle_thue = 20.5;
Các hằng
Giống như một biến, một hằng cũng là một địa chỉ trong bộ nhớ được sử dụng bởi một
chương trình. Nhưng không giống như một biến, giá trị được ghi trong một hằng không được
thay đổi trong quá trình thực hiện của chương trình. C có hai kiểu hằng, mỗi kiểu lại có các
cách sử dụng khác nhau.
Các hằng thực sự (literal constants)
Một hằng literal là một giá trị được gõ trực tiếp vào chương trình gốc bất cứ khi nào nó
được cần đến. Đây là hai thí dụ:
int count = 20;
float tyle_thue = 0.20;
Các giá trị 20 và .20 là các hằng literal. Các lệnh trên lưu giữ các giá trị này vào các biến
count và tyle_thue.
Một hằng literal với dấu chấm thập phân là một hằng dấu chấm động và được chương trình
dịch C xem như một số có độ chính xác gấp đôi.
Một hằng không có dấu chấm thập phân được chương trình dịch biểu diễn như một số
nguyên. Các hằng nguyên có thể được viết theo ba dạng dưới đây:
Một hằng khởi đầu bằng một chữ số khác 0 được xem là số nguyên cơ số 10
(decimal). Một hằng nguyên hệ 10 có thể chứa các số 0-9 và một dấu trừ hoặc + ở
đằng trước.
Một hằng khởi đầu bằng chữ số 0 được xem là một số nguyên cơ số 8 (octal). Các
hằng hệ 8 có thể chứa các chữ số 0-7 và phía trước có thể có thêm dấu trừ hoặc cộng.
Một hằng khởi đầu bằng 0x hoặc 0X được xem là một hằng hệ cơ số 16
(hexadecimal). Các hằng hệ 16 có thể chứa các chữ số 0-9 và các chữ A, B, C, D và
F, và một dấu trừ hoặc cộng ở đằng trước.
Các hằng ký hiệu (symbolic constants)
Một hằng ký hiệu là một hằng được ký hiệu bởi một tên gọi (symbolic) nào đó. Giống như
một hằng literal, giá trị của một hằng symbolic không thể thay đổi. Giá trị thực sự của hằng
symbolic chỉ được nhập vào khi nó được định nghĩa. Việc sử dụng một hằng kiểu này y hệt
như sử dụng một biến.
Hằng ký hiệu có hai ưu điểm nổi bật so với hằng thực sự. Ví dụ, để tính chu vi và diện tích
của một hình tròn khi biết bán kính, có thể viết như sau:
chu_vi = 3.14 * (2 * ban_kinh);
dien_tich = 3.14 * ban_kinh * ban_kinh;
Tuy nhiên, nếu định nghĩa một hằng ký hiệu với tên là PI và giá trị là 3.14, thì đoạn chương
trình trên có thể thay bằng:
chu_vi = PI * (2 * ban_kinh);
dien_tich = PI * ban_kinh * ban_kinh;
Hai lệnh này trở nên sáng sủa và dễ hiểu hơn. Thay vì phải băn khoăn về giá trị 3.14 có
nghĩa là gì, ở đây có thể hiểu ngay được PI là gì.
Ưu điểm thứ hai của hằng ký hiệu là ở chỗ dễ thay đổi. Tiếp tục ví dụ trên, để cho kết qủa
của chương trình chính xác hơn cần phải thay giá trị của PI là 3.14 bằng một giá trị khác với
độ chính xác cao hơn, chẳng hạn 3.14159. Nếu đã chót dùng một hằng thực sự cho số PI,
thì bây giờ phải duyệt lại tất cả chương trình để thay các giá trị 3.14 bằng 3.14159. Với một
hằng ký hiệu, chỉ cần thay ở đúng một chỗ, nơi hằng này được định nghĩa.
C có hai phương pháp dùng để định nghĩa một hằng ký hiệu: chỉ thị #define và từ khóa
const. Chỉ thị #define là một trong những chỉ thị tiền xử lý sẽ được trình bầy trong các
bài sau. Chỉ thị này được sử dụng như sau:
#define TENHANG giatri
Dòng này tạo ra một hằng có tên là TENHANG với giá trị là giatri. Trong đó
giatri chính là một hằng số, còn TENHANG tuân theo các qui luật giống như của tên
biến. Để cho dễ phân biệt giữa một hằng và một biến, ta nên viết các tên hằng bằng chữ
hoa, còn tên biến viết bằng chữ thường. Trong ví dụ trước, để định nghĩa hằng ký hiệu PI,
có thể viết như sau:
#define PI 3.14159
Chú ý là, chỉ thị #define không kết thúc bằng dấu chấm phẩy (;).#define có thể được
đặt ở bất cứ chỗ nào trong chương trình gốc, nhưng chỉ có hiệu lực cho phần mã gốc đứng
sau nó mà thôi. Nói chung, người ta hay gom tất cả các #define lại với nhau và đặt
chúng ở đầu tệp ngay trước hàm main().
Cách thứ hai để định nghĩa một hằng ký hiệu là dùng từ khóa const. Một biến được khai
báo là const thì không thể thay đổi giá trị trong khi chương trình thực hiện. Đây là một vài
ví dụ:
const int count = 100;
const float pi= 3.14159;
const long no= 12000000, float tyle_thue = 0.21;
const tác động lên tất cả các biến trên dòng khai báo. Trong dòng thứ ba, no và
tyle_thue là các hằng ký hiệu. Nếu trong chương trình có một lệnh nào đó định thay đổi
giá trị của một hằng, thì chương trình dịch sẽ sinh ra một thông báo lỗi. Ví dụ,
const int count = 100;
count = 200; /* Sai! Không thể gán lại hoặc thay đổi
giá trị của một hằng */
Các hằng ký hiệu được định nghĩa bằng chỉ thị #define và các hằng được định nghĩa bằng
const có sự khác nhau. Sự khác nhau này có liên quan đến các con trỏ và phạm vi của
các biến. Con trỏ và phạm vi của các biến là hai khái niệm quan trọng sẽ được trình bầy
trong các bài sau.
Kết luận
Bài này đã trình bầy kỹ về các biến số dùng để lưu giữ các dữ liệu trong qúa trình thực hiện
chương trình. Có hai lớp lớn các biến số: nguyên và dấu chấm động. Trong mỗi lớp lại có
một số kiểu. Việc sử dụng kiểu nào - int, long, float, hoặc double - cho một ứng
dụng là tuỳ thuộc vào bản chất của các dữ liệu được ghi trong biến. Một biến cần phải được
khai báo trước khi được sử dụng. Một khai báo biến cho chương trình dịch biết tên và kiểu
của biến.
Bài này còn nói về hai kiểu hằng của C, hằng thực sự, và hằng ký hiệu. Không giống như
các biến, giá trị của một hằng không được thay đổi trong quá trình thực hiện chương trình.
Các hằng thực sự là những giá trị được gõ trực tiếp vào chương trình gốc bất cứ khi nào giá
trị này được cần đến. Các hằng ký hiệu được đặt tên và tên này được sử dụng bất cứ khi
nào giá trị của hằng được cần đến. Các hằng ký hiệu có thể được định nghĩa và khởi tạo
bằng chỉ thị #define hoặc từ khóa const.
Câu hỏi và trả lời
1. Các biến long int chứa được các số nguyên lớn hơn, vậy tại sao không dùng
chúng thay cho các biến int ?
Một biến long int chiếm nhiều RAM hơn một biến int. Trong các chương trình nhỏ,
điều này không ảnh hưởng lắm. Tuy nhiên, khi chương trình càng ngày càng lớn hơn, thì
nên nghĩ đến việc sử dụng thật tiết kiệm bộ nhớ.
2. Điều gì sẽ xảy ra, nếu gán một số thập phân cho một biến nguyên?
Có thể gán một số thập phân cho một biến nguyên. Nếu biến nguyên này là một biến
hằng thì chương trình dịch sẽ cho ra một thông báo nhắc nhở (warning). Giá trị được gán
sẽ bị chặt đi phần thập phân. Ví dụ, nếu gán giá trị 3.14 cho một biến nguyên tên gọi là
pi, thì pi sẽ chỉ chứa 3. Phần phân .14 bị bỏ đi.
3. Điều gì sẽ xẩy ra, nếu gán một số vào một kiểu không đủ lớn để chứa nó?
Nhiều chương trình dịch cho phép làm điều này mà không thông báo gì cả. Tuy nhiên, số
này sẽ bị biến đổi để vừa với kiểu đã cho và đôi khi còn làm thay đổi hẳn giá trị của nó.
Ví dụ, nếu gán 32768 cho một biến kiểu nguyên có dấu hai bytes, thì biến này sẽ nhận
giá trị nguyên là -1. Nếu gán giá trị 65535 cho biến nguyên này, thì nó sẽ chứa giá trị
-32768. Nguyên tắc biến đổi này là, lấy giá trị lớn nhất mà kiểu được gán có thể chứa
được trừ đi giá trị được gán và chứa kết qủa vào biến này.
4. Điều gì sẽ xảy ra, nếu gán một số âm cho một biến kiểu không dấu?
Cũng giống như câu hỏi trước, chương trình dịch có thể sẽ không bảo gì cả nhưng kết
qủa lại không đúng như mong muốn. Ví dụ, nếu gán -1 cho một biến nguyên không dấu
hai bytes, thì chương trình dịch sẽ đặt vào biến này số cao nhất có thể (65535).
Luyện lập
Câu hỏi
1. Có gì khác nhau giữa một biến nguyên và một biến dấu chấm động?
2. Hãy cho biết hai lý do tại sao lại phải dùng một biến dấu chấm động độ chính xác gấp
đôi (kiểu double) thay cho một biến dấu chấm động độ chính xác đơn (kiểu float.)
3. Việc dùng một hằng ký hiệu thay cho một hằng thực sự có những ưu điểm nào?
4. Hãy nêu hai phương pháp để định nghĩa một hằng ký hiệu có tên là MAXIMUM và có giá
trị là 100.
5. Các ký tự nào có thể được dùng để đặt tên các biến C?
6. Sự khác nhau giữa một hằng ký hiệu và một hằng thực sự là gì?
Bài tập
I. Nên dùng kiểu biến nào để lưu giữ một cách tốt nhất cho các giá trị sau đây:
A. Tuổi của một người trong những năm sắp tới.
B. Trọng lượng của một người bằng kilogram.
C. Bán kính của một đường tròn.
D. Lương tháng của công chức.
E. Giá của một mặt hàng nào đó.
F. Nhiệt độ.
G. Khoảng cách bằng kilomet từ trái đất đến một ngôi sao nào đó.
II. Hãy tạo lập các tên biến thích hợp cho các giá trị trong bài tập một.
III. Hãy viết các khai báo cho các biến trong bài tập hai.
IV. Các biến nào là hợp lệ và không hợp lệ trong số các biến sau? Tại sao không
hợp lệ?
A. 123abc
B. x
C. tong_so
D. trong_luong_#s
E. mot
F. tyle-phantram
G. BAN_KINH
H. Ban_kinh
I. ban_kinh
J. day_la_mot_bien_de_chua_chieu_rong_hop
Bài 4. Câu lệnh, Biểu thức và Phép toán
Các chương trình C chứa các câu lệnh (statement), và hầu hết các câu lệnh đều chứa các
biểu thức (expression) và các phép toán (operator). Bài này bao gồm các mục sau:
Một câu lệnh là gì?
Một biểu thức là gì?
Các phép toán toán học, quan hệ, và logic.
Tiền toán tử là gì?
Câu lệnh if.
Các câu lệnh
Một câu lệnh là một phương hướng hành động hướng dẫn cho máy tính thực hiện một nhiệm
vụ nào đó. Trong C, các câu lệnh thường được viết trên một dòng, nhưng cũng có những
câu lệnh phải viết trên nhiều dòng. Mọi câu lệnh của C đều kết thúc bằng một dấu chấm
phẩy (;) (trừ các chỉ thị, chẳng hạn như #include và #define).
Ví dụ,
x = 2+3;
là một câu lệnh gán. Nó chỉ ra cho máy tính lấy số 2 cộng với số 3, rồi gán kết qủa vào biến
x.
Các câu lệnh và các khoảng trắng
Thuật ngữ khoảng trắng (whitespace) là để chỉ các dấu cách, tabs và các dòng rỗng trong
chương trình gốc. Chương trình dịch C không xét đến các khoảng trắng. Khi chương trình
dịch đọc một câu lệnh trong một chương trình gốc, nó sẽ đọc tất cả các ký tự trong câu lệnh
này cho đến khi gặp dấu chấm phẩy nhưng bỏ qua tất cả các khoảng trắng. Vì vậy, câu lệnh
x=2+3; trên đây là hoàn toàn tương đương với
x = 2 + 3;
và với
x =
2
+
3 ;
Mỗi câu lệnh nên viết trên một dòng và nên có một dấu cách đứng trước và một dấu cách
đứng sau mỗi tên biến. Trước và sau mỗi khối nên có một dòng trống để dễ nhận biết từng
khối một. Ngoài ra, có thể dùng tabs để đẩy một khối thụt vào so với lề trái nhằm tạo ra một
cấu trúc có tính phân cấp trong chương trình.
Tuy nhiên, trong một xâu các ký tự (string constant) thì các dấu cách và các dấu tabs lại
không bị bỏ qua, chúng được coi là một bộ phận của xâu ký tự này. Một xâu là một dãy
các ký tự. Các hằng ký tự thực sự là các xâu được đặt trong các dấu nháy và được chương
trình dịch xử lý từng ký tự một.
Nếu chỉ có đúng một dấu chấm phẩy trên một dòng nào đó, thì dòng này được gọi là câu
lệnh rỗng. Câu lệnh rỗng không thực hiện bất kỳ hành động nào.
Các câu lệnh phức hợp
Một câu lệnh phức hợp (compound statement), hay còn gọi là một khối (block), là một
nhóm gồm hai hay nhiều câu lệnh của C được đặt trong các dấu ngoặc nhọn. Đây là một ví
dụ về một khối:
printf("Xin chào, ");
printf(các bạn!");
Trong C, một câu lệnh phức hợp có thể được sử dụng ở bất cứ nơi nào mà một câu lệnh
đơn có thể được sử dụng. Chú ý rằng, có thể có một vài cách để viết các dấu ngoặc. Câu
lệnh phức hợp sau đây là tương đương với câu lệnh trên:
printf("Xin chào, ");
printf(các bạn!");
Tuy nhiên, việc để các dấu ngoặc trên hai dòng riêng biệt làm cho dễ nhận ra nơi bắt đầu và
nơi kết thúc của một khối hơn.
Các biểu thức
Trong C, một biểu thức là một mệnh đề dùng để tính ra một giá trị nào đó. Các biểu thức C
có thể có độ phức tạp tùy ý.
Các biểu thức đơn giản
Biểu thức đơn giản nhất của C là biểu thức chỉ có một biến, một hằng thực sự, hoặc một
hằng ký hiệu. Đây là mấy ví dụ:
PI /* một hằng ký hiệu (đã định nghĩa trong chương trình )*/
20 /* một hằng thực sự */
ty_le /* một biến */
-1.25 /* một hằng thực sự khác */
Các biểu thức phức tạp
Các biểu thức phức tạp hơn bao gồm các biểu thức đơn giản hơn được liên kết với nhau
bằng các phép toán.
Ví dụ,
2 + 10
là một biểu thức gồm các biểu thức con 2 và 10, và phép toán +. Biểu thức 2+10 cho ra
kết qủa là 12. Có thể viết các biểu thức phức tạp hơn:
1.25 / 8 + 5 * ty_le + ty_le * ty_le / gia
Khi một biểu thức chứa nhiều phép toán, thì việc tính toán biểu thức này phụ thuộc vào thứ
tự thực hiện và độ ưu tiên của phép toán.
Các biểu thức của C còn có rất nhiều điều thú vị. Hãy xét biểu thức sau:
x = a + 10;
Câu lệnh này tính biểu thức a + 10 và gán kết qủa cho x. Ngoài ra, toàn bộ câu lệnh này
đến lượt nó cũng là một biểu thức tính ra giá trị của biến số nằm bên trái dấu bằng. Vì vậy
có thể viết câu lệnh sau:
y = x = a + 10;
Câu lệnh này gán giá trị của biểu thức a + 10 cho cả hai biến x và y. Cũng có thể viết
x = 6 + (y = 4 + 5);
Kết qủa của câu lệnh này là y nhận giá trị 9 và x nhận giá trị 15. Hãy chú ý đến các dấu
ngoặc đơn, chúng rất cần cho việc dịch lệnh này.
Các phép toán
Một phép toán là một ký hiệu báo cho C biết phải thực hiện một hoạt động, hoặc một thao
tác nào đó trên một hoặc nhiều toán hạng (operand). Một toán hạng là một biểu thức. Các
phép toán của C được chia thành một số nhóm.
Phép gán
Phép gán được ký hiệu là dấu bằng (=). Cách dùng của nó trong C có khác một chút so với
cách dùng của nó trong toán học thông thường. Nếu viết
x = y;
trong một chương trình C, thì không có nghĩa là "x bằng y.", mà là "gán giá trị của y cho
x." Trong một câu lệnh gán, vế phải có thể là một biểu thức bất kỳ còn vế trái phải là một
tên biến. Vì vậy, dạng của câu lệnh gán là:
tenbien = bieuthuc;
Khi được thực hiện, bieuthuc được ước lượng và giá trị của kết qủa được gán cho
tenbien.
Các phép toán học
C có hai phép toán học một toán hạng (đơn nguyên) và năm phép toán học hai toán hạng
(nhị nguyên).
Các phép toán học đơn nguyên
Bảng 4.1. Các phép toán học đơn nguyên
Phép toán Ký hiệu Hành động Ví dụ
Tăng ++ Tăng giá trị của toán hạng lên một ++x, x++
Giảm -- Giảm giá trị của toán hạng đi một --x, x--
Các phép tăng và giảm chỉ được sử dụng với các biến, không được sử dụng với các hằng.
++x; là tương đương với x = x + 1;
--y; là tương đương với y = y - 1;
Các phép toán đơn nguyên có hai kiểu: kiểu trước và kiểu sau. Hai kiểu này là không
tương đương. Chúng khác nhau ở chỗ khi nào việc tăng hoặc giảm được thực hiện.
Khi được sử dụng ở kiểu trước, phép tăng và giảm sẽ thay đổi giá trị của toán
hạng trước khi toán hạng được sử dụng.
Khi được sử dụng ở kiểu sau, phép tăng và giảm sẽ thay đổi giá trị của toán
hạng sau khi toán hạng được sử dụng.
Hãy xét hai câu lệnh sau đây thì sẽ thấy rõ điều đó:
x = 10;
y = x++;
Sau khi các lệnh này được thực hiện, x có giá trị là 11, còn y có giá trị là 10 vì giá trị của x
được gán cho y, và sau đó x mới được tăng thêm một. Ngược lại, các câu lệnh
x = 10;
y = ++x;
cho x và y cùng một giá trị là 11 vì x được tăng thêm một, sau đó giá trị của nó mới được
gán cho y.
Chương trình sau minh họa sự khác nhau giữa kiểu trước và kiểu sau.
Chương trình 4.1. MOT.C
1: /*Minh hoạ kiểu trước và kiểu sau của phép toán một toán hạng */
2:
3: #include
4:
5: int a , b;
6:
7: main()
8:
9: /* Đặt cả a và b bằng 5 */
10:
11: a = b =5;
12:
13: /* Sau đó in chúng và giảm đi một */
14: /* dùng kiểu trước cho b và kiểu sau cho a */
15:
16: printf("\n%d %d", a--, --b);
17: printf("\n%d %d", a--, --b);
18: printf("\n%d %d", a--, --b);
19: printf("\n%d %d", a--, --b);
20: printf("\n%d %d", a--, --b);
21:
22: return 0;
23:
Kết qủa của chương trình là
5 4
4 3
3 2
2 1
1 0
Các phép toán học nhị nguyên
Bảng 4.2. là danh sách các phép toán học nhị nguyên. Bốn phép đầu rất quen thuộc. Phép
thứ năm, modulus (lấy phần dư của phép chia), có thể là còn mới. Modulus cho ra phần dư
của phép chia toán hạng thứ nhất cho toán hạng thứ hai. Ví dụ, 11 modulus 4 bằng 3 (tức là,
11 chia cho 4 được 2 dư 3), 10 modulus 5 bằng 0 (10 chia cho 5 được 2 dư 0, hay nói
cách khác, 10 chia hết cho 5).
Bảng 4.2. Các phép toán học nhị nguyên
Phép toán Ký hiệu Hành động Ví dụ
Cộng + Cộng hai toán hạng x + y
Trừ - Toán hạng thứ nhất trừ toán hạng thứ hai x - y
Nhân * Nhân hai toán hạng của nó x * y
Chia / Toán hạng thứ nhất chia toán hạng thứ hai x / y
Modulus % Lấy phần dư của phép chia x % y
Thứ tự thực hiện các phép toán và các dấu ngoặc
Trong một biểu thức có chứa từ hai phép toán trở lên thì thứ tự để thực hiện chúng như thế
nào? Tầm quan trọng của câu hỏi này có thể được minh họa bằng câu lệnh gán sau đây:
x = 4 + 5 * 3;
Nếu phép cộng được thực hiện trước, thì
x = 9 *3;
và x được gán giá trị 27. Ngược lại, nếu phép nhân được thực hiện trước, thì lại khác:
x = 4 + 15;
và x được gán giá trị 19. Như vậy là cần phải có các luật nào đó về thứ tự thực hiện của
các phép toán trong một biểu thức. Thứ tự này được gọi là quyền ưu tiên của các phép
toán. Mỗi phép toán đều có một quyền ưu tiên xác định. Khi một biểu thức được tính toán,
các phép toán có quyền ưu tiên cao hơn sẽ được thực hiện trước.
Thứ tự thực hiện của các phép toán là:
Tăng và giảm đơn nguyên.
Nhân, chia, và modulus.
Cộng và trừ
Nếu một biểu thức chứa từ hai phép toán trở lên có cùng một hạng ưu tiên, thì chúng được
thực hiện theo thứ tự từ trái sang phải.
Trở lại ví dụ trước, câu lệnh x = 4 + 5 *3; gán giá trị 19 cho x bởi vì phép nhân được
thực hiện trước phép cộng.
Vẫn ví dụ này, nếu muốn cộng 4 với 5 sau đó mới nhân với 3 thì phải làm như thế nào? C
dùng dấu ngoặc để điều chỉnh lại thứ tự thực hiện phép toán. Một biểu thức con được đóng
trong các dấu ngoặc được tính trước cho dù nó chứa phép toán nào. Vì vậy, để thực hiện ý
định trên, có thể viết:
x = (4 + 5) * 3;
Biểu thức con 4 + 5 trong các dấu ngoặc đơn được tính trước và vì thế giá trị được gán
cho x là 27.
Có thể dùng nhiều cặp dấu ngoặc lồng nhau trong một biểu thức. Khi các dấu ngoặc lồng
nhau, việc tính toán được thực hiện từ biểu thức nằm trong cùng, sau đó tiến dần ra ngoài.
Hãy xét biểu thức:
x = 25 - (2 * (10 + (8 / 2))
Biểu thức này được xử lý theo các bước sau:
1. Biểu thức trong nhất, 8 / 2, được tính trước nhất và cho ra giá trị 4.
25 - (2 * (10 + 4))
2. Chuyển ra ngoài, biểu thức tiếp theo, 10 + 4, được tính và cho ra giá trị 14.
25 - (2 * 14)
3. Biểu thức ngoài cùng, 2 * 14, được tính và cho giá trị 28.
25 - 28
4. Biểu thức cuối cùng, 25 - 28, được tính và gán giá trị -3 cho biến x.
x = -3
Thứ tự tính toán các biểu thức con
Như đã nói ở trên, nếu một biểu thức C chứa nhiều hơn một phép toán có cùng hạng ưu tiên
thì chúng được thực hiện theo thứ tự từ trái qua phải. Ví dụ, trong biểu thức
w * x / y * z
w được nhân với x trước, chia kết qủa thu được cho y, và cuối cùng mới nhân kết qủa của
phép chia này với z.
Tuy nhiên với các mức ưu tiên khác nhau thì không có gì để đảm bảo thứ tự từ_trái_qua_
phải. Xét biểu thức sau:
w * x / y + z / y
Theo thứ tự ưu tiên, phép nhân và chia được thực hiện trước phép cộng. Tuy nhiên C không
xác định biểu thức con w * x / y được tính trước hay tính sau biểu thức con z / y. Hãy xem
một ví dụ khác:
w * x / ++y + z / y
Nếu biểu thức con thứ nhất được tính trước, thì y được tăng thêm 1 khi biểu thức con thứ
hai được tính. Nếu biểu thức con thứ hai được tính trước, thì y chưa được tăng và kết qủa
sẽ khác nhau. Do vậy, nên tránh viết các biểu thức kiểu này trong chương trình.
Các phép quan hệ
Các phép quan hệ của C được sử dụng để so sánh các biểu thức, chúng đặt ra các câu hỏi,
ví như, "x có lớn hơn 100 không?", hoặc "y có bằng 0 không ?". Một biểu thức chứa một
phép quan hệ chỉ có một trong hai giá trị, đúng - true (1) hoặc sai - false (0). Sáu phép quan
hệ của C được cho trong Bảng 4.4.
Bảng 4.4. Các phép quan hệ của C
Phép toán Ký hiệu Câu hỏi được đặt ra Ví dụ
Bằng == Toán hạng 1 có bằng toán hạng 2 không? x == y
Lớn hơn > Toán hạng 1 có lớn hơn toán hạng 2 không? x > y
Nhỏ hơn < Toán hạng 1 có nhỏ hơn toán hạng 2 không? x < y
Lớn hơn >= Toán hạng 1 có lớn hơn hoặc bằng toán x>=y
hoặc bằng hạng 2 không?
Nhỏ hơn <= Toán hạng 1 có nhỏ hơn hoặc bằng toán hạng x<=y
hoặc bằng 2 không?
Không bằng != Toán hạng 1 không bằng toán hạng 2 x != y
phải không?
Lưu ý: Khi làm việc với các phép quan hệ, đúng (true) là 1 còn sai (false) là 0. Đừng nhầm
lẫn phép quan hệ == với phép gán =. Đây là một trong những lỗi mà người lập trình C
hay mắc phải.
Câu lệnh if
Các phép quan hệ được sử dụng chủ yếu để xây dựng các biểu thức quan hệ dùng trong
các câu lệnh if và while. Các câu lệnh trong một chương trình C thường được thực
hiện từ trên xuống dưới theo đúng thứ tự xuất hiện của chúng trong chương trình gốc. Một
câu lệnh điều khiển có thể sẽ làm thay đổi thứ tự thực hiện này. Các câu lệnh điều khiển
chương trình có thể làm cho các câu lệnh khác thực hiện nhiều lần hoặc không thực hiện lần
nào phụ thuộc vào từng hoàn cảnh cụ thể. Câu lệnh if là một trong các câu lệnh điều khiển
chương trình của C. Ngoài ra còn có các câu lệnh khác, như for, do ... while, và
while.
Ở dạng thông thường, câu lệnh if ước lượng một biểu thức và thay đổi sự thực hiện
chương trình theo kết qủa của việc ước lượng này. Dạng của if như sau:
if (biểu thức)
câu_lệnh;
Nếu biểu thức được đánh giá là đúng thì câu_lệnh được thực hiện. Nếu biểu thức nhận
giá trị sai, thì câu_lệnh không được thực hiện và điều khiển chuyển đến câu lệnh đứng
ngay sau câu lệnh if.
Chú ý là cả hai dòng if (biểu thức) và câu_lệnh; mới hợp thành một câu lệnh
if hoàn chỉnh và chúng không phải là hai câu lệnh tách rời. Do vậy, không được viết dấu
chấm phẩy ở cuối dòng if.
Một câu lệnh if có thể điều khiển việc thực hiện của nhiều câu lệnh khác bằng cách dùng
một khối. Vì vậy, có thể viết một câu lệnh if như sau:
if (biểu_thức)
câu_lệnh1;
câu_lệnh2;
/* có thể viết thêm nhiều câu lệnh nữa tại đây */
câu_lệnhn;
Trong khi lập trình, câu lệnh if rất hay được sử dụng cùng với các biểu thức quan hệ, hay
nói một cách khác, "hãy thực hiện (các) câu lệnh sau đây chỉ khi điều kiện như thế này
là đúng". Ví dụ,
if ( x > y )
y = x;
Câu lệnh này gán giá trị của x cho y chỉ khi x lớn hơn y. Nếu x không lớn hơn y, thì không có
phép gán nào xẩy ra cả.
Một câu lệnh if có thể chứa một mệnh đề else (ngược lại). Mệnh đề này được đưa vào if
như sau:
if (biểu_thức)
câu_lệnh1;
else
câu_lệnh2;
Nếu biểu_thức là đúng, câu_lệnh1 được thực hiện. Nếu biểu_thức là sai,
câu_lệnh2 được thực hiện. Cả câu_lệnh1 và câu_lệnh2 đều có thể là một khối.
Cú pháp của câu lệnh if
Dạng 1
if ( biểu_thức )
câu _lệnh1
câu_lệnh_tiếp_theo
Đây là dạng đơn giản nhất của câu lệnh if. Nếu biểu_thức là đúng, thì câu_lệnh1
được thực hiện. Nếu biểu_thức là không đúng, thì câu_lệnh1 bị bỏ qua.
Dạng 2
if ( biểu_thức )
câu _lệnh1
else
câu _lệnh2
câu_lệnh_tiếp_theo
Đây là dạng chung nhất của câu lệnh if. Nếu biểu_thức mà đúng thì câu_lệnh1 được
thực hiện, ngược lại câu_lệnh2 được thực hiện.
Dạng 3
if (biểu_thức1)
câu _lệnh1
else if(biểu_thức2)
câu _lệnh2
else
câu_lệnh3
câu_lệnh_tiếp_theo
Đây là dạng if lồng nhau. Nếu biểu_thức1 là đúng, thì câu_lệnh1 được thực hiện,
ngược lại, biểu_thức2 được kiểm tra. Nếu biểu_thức1 là không đúng, và
biểu_thức2 là đúng, thì câu_lệnh2 được thực hiện. Nếu cả hai cùng không đúng,
câu_lệnh3 được thực hiện. Chỉ có một trong ba câu lệnh này được thực hiện mà thôi.
Ví dụ 1
if ( luong > 10,000,000 )
thue = .50;
else
thue = .25;
Ví dụ 2
if ( tuoi <15 )
printf( "Thiếu niên");
else if( tuoi < 65 )
printf("Người lớn");
else
printf("Người già");
Tính giá trị các biểu thức quan hệ
Các biểu thức quan hệ cho ra một giá tri hoặc sai (0) hoặc đúng (1). Dù rằng các biểu thức
quan hệ được dùng phổ biến nhất trong các câu lệnh if và các cấu trúc điều kiện khác,
chúng còn được sử dụng như các giá trị số thuần tuý. Chương trình 4.5 sau đây minh họa
cho cách dùng này.
Chương trình 4.5. Trình diễn cách tính các biểu thức quan hệ
1: /* Minh họa cách tính các biểu thức quan hệ */
2:
3: #include
4:
5: int a;
6:
7: main()
8:
9: a = (5 == 5); /* cho ra giá trị 1 */
10: printf("\na = (5 == 5)\na = %d", a);
11:
12: a = (5 != 5); /* cho ra giá trị 0 */
13: printf("\na = (5 != 5)\na = %d", a);
14:
15: a = (12 == 12) + (5 != 1); /* cho ra 1 + 1 */
16: printf("\na = (12 == 12) + (5 != 1)\na = %d", a);
17: return 0;
18:
Kết qủa của chương trình này là:
a = (5 == 5)
a = 1
a = (5 != 5)
a = 0
a = (12 == 12) + (5 != 1)
a = 2
Xem ra kết qủa này mới đầu có vẻ hơi lạ. Khi dùng các biểu thức quan hệ, có một lỗi mà
người ta hay mắc phải là chỉ dùng một dấu = cho phép quan hệ bằng. Biểu thức
x = 5
cho giá trị 5 đồng thời gán 5 cho x. Thế nhưng, biểu thức
x == 5
thì lại cho ra 0 hoặc 1 (phụ thuộc vào giá trị của x có bằng 5 hay không) và không làm thay
đổi giá trị của x. Nếu do sơ xuất mà viết là
if (x = 5)
printf("x bằng 5");
thì thông báo này luôn luôn được in ra vì biểu thức được kiểm tra ở câu lệnh if bao giờ
cũng nhận giá trị đúng bất kể x có bằng 5 hay không.
Cuối cùng, cần phải nhắc lại một lần nữa là, các phép quan hệ được dùng để tạo lập các
mối quan hệ giữa các biểu thức. Một biểu thức quan hệ cho ra một giá trị số, bằng 0 (nếu
biểu thức có nghĩa là sai) hoặc bằng 1 (nếu biểu thức có nghĩa là đúng).
Thứ tự thực hiện của các phép quan hệ
Giống như các phép toán học đã nói trong bài trước, các phép quan hệ cũng có các quyền
ưu tiên riêng, quyết định thứ tự mà chúng được thực hiện trong một biểu thức có nhiều phép
toán. Ngoài ra, cũng có thể dùng các dấu ngoặc để thay đổi thứ tự thực hiện trong một biểu
thức có dùng các phép quan hệ.
Trước hết, tất cả các phép quan hệ đều có quyền ưu tiên thấp hơn các phép toán học. Vì
vậy, nếu viết
if ( x + 2 > y)
thì 2 được cộng với x trước, sau đó, kết qủa này mới được đem so sánh với y. Mệnh đề trên
tương đương với
if ( (x + 2 ) > y)
trong đó, dấu ngoặc được sử dụng để làm rõ thêm thứ tự thực hiện của các phép trong
biểu thức.
Thứ tự ưu tiên của các phép quan hệ:
Phép toán Quyền ưu tiên tương đối
>= 1
!= == 2
Vì vậy,
x == y > z; là tương đương với x == ( y > z );
bởi vì, đầu tiên C tính biểu thức y > z để cho ra một giá trị hoặc là 0 hoặc là 1. Tiếp theo,
C xác định xem x có bằng giá trị thu được từ bước một hay không.
Không nên dùng câu lệnh gán trong các câu lệnh if. Điều đó có thể làm cho người đọc
chương trình dễ nhầm lẫn. Họ có thể hiểu nhầm là chương trình viết không đúng và sửa lại
phép gán thành phép bằng logic. Đồng thời cũng không nên dùng phép "không bằng" (!=)
trong một câu lệnh if có chứa một mệnh đề else. Tốt hơn cả là nên dùng phép "bằng"
(==) cùng với else. Ví dụ, câu lệnh
if (x != y )
câu_lệnh1;
else
câu_lệnh2;
nên thay bằng câu lệnh sau thì dễ hiểu hơn
if ( x == y )
câu_lệnh2;
else
câu_lệnh1;
Các phép logic
Đôi khi, cần phải đặt ra nhiều câu hỏi về các mối quan hệ của các biểu thức nào đó cùng
một lúc. Các phép logic của C đảm bảo việc kết nối hai hay nhiều biểu thức quan hệ thành
một biểu thức để tính ra được một giá trị hoặc là đúng hoặc là sai. Ba phép logic của C
được cho trong Bảng 4.7.
Bảng 4.7. Các phép logic của C
Phép toán Ký hiệu Ví dụ
và && biểu_thức1 && biểu_thức2
hoặc Ư Ư biểu_thức1 Ư Ư biểu_thức2
không ! ! biểu_thức1
Phương thức làm việc của các phép logic được chỉ ra trong Bảng 4.8.
Bảng 4.8. Phương thức làm việc của các phép logic
Biểu thức Cho ra giá trị
(biểu_thức1 && biểu_thức2) True (1) chỉ khi cả biểu_thức1 và
biểu_thức2 đều true; ngược lại false (0).
(biểu_thức1 Ư Ư biểu_thức2) True (1) nếu biểu_thức1 hoặc
biểu_thức2 là true; false (0) chỉ khi cả
hai đều false.
(! biểu_thức1) False (0) nếu biểu_thức1 là true;
true (1) nếu biểu_thức1 là false.
Nói thêm về các giá trị True/False (Đúng/Sai)
Các biểu thức quan hệ của C cho ra giá trị 0 để biểu hiện là sai và cho ra giá trị 1 để biểu
hiện là đúng. Tuy nhiên, bất kỳ một giá trị số nào cũng có thể được biểu hiện như là đúng
hoặc như là sai khi nó được sử dụng trong một biểu thức hoặc một câu lệnh đang chờ một
giá trị logic (tức là, một giá trị đúng hoặc sai). Các qui tắc áp dụng cho các trường hợp này
là:
Một giá trị bằng không được coi là sai.
Một giá trị khác không được coi là đúng.
Các ví dụ sau đây minh họa cho các qui tắc này:
x = 125;
if (x)
printf ("%d", x);
Trong trường hợp này, giá trị của x được in ra, vì x có giá trị khác không nên biểu thức (x)
được câu lệnh if cho là đúng. Bởi vậy, đối với mọi biểu thức C thì cách viết
(biểu_thức) là tương đương với cách viết (biểu_thức != 0)
Cả hai đều cho giá trị đúng nếu biểu_thức khác không, và cả hai đều cho giá trị sai nếu
biểu_thức bằng không. Bằng phép toán không (!), ta cũng có thể viết
(! biểu_thức) hoặc tương đương là (biểu_thức == 0)
Thứ tự thực hiện các phép logic
Các phép logic cũng có một thứ tự ưu tiên ngay trong chúng và cả với các phép khác. Phép
! (not) có cùng độ ưu tiên với các phép toán học một ngôi (đơn nguyên) ++ và -- Vì vậy,
phép ! có thứ tự ưu tiên cao hơn tất cả các phép quan hệ và tất cả các phép toán học hai
ngôi (nhị nguyên).
Ngược lại, các phép && (and) và Ư Ư (or) có thứ tự ưu tiên thấp hơn nhiều, thấp hơn tất
cả các phép toán học và các phép quan hệ. && có thứ tự ưu tiên cao hơn Ư Ư . Cũng như
đối với các phép khác, dấu ngoặc có thể được sử dụng để thay đổi thứ tự thực hiện khi
dùng các phép logic.
Các phép gán phức hợp
Các phép gán phức hợp (compound assignment) của C cung cấp một giải pháp ngắn gọn
kết nối một phép toán học nhị nguyên với một phép gán. Ví dụ, giả sử muốn tăng giá trị của
x lên 5, hay nói cách khác cộng 5 với x và lại gán kết qủa cho x thì có thể viết như sau:
x = x + 5;
Dùng một phép gán phức hợp, gần như một cách viết tắt của phép gán, có thể viết ngắn gọn
như sau:
x += 5;
Tổng quát, phép gán phức hợp có dạng cú pháp như sau (trong đó op là một phép nhị
nguyên):
biểu_thức1 op= biểu_thức2
lệnh này tương đương với
biểu_thức1 = biểu_thức1 op biểu_thức2;
Phép điều kiện
Phép điều kiện là phép tam nguyên duy nhất của C, tức là nó có ba toán hạng. Cú pháp của
nó là:
Biểu_thức1 ? biểu_thức2 : biểu_thức3
Nếu biểu_thức1 cho ra true (khác 0), thì biểu thức tổng thể này có giá trị của biểu_thức2.
Nếu biểu_thức1 cho ra false (bằng 0), thì biểu thức tổng thể có giá trị của biểu_thức3. Ví
dụ, câu lệnh
x = y ? 1 : 100;
gán 1 cho x, nếu y là true, hoặc gán 100 cho x, nếu y là false. Tương tự như vậy, để z lấy
giá trị lớn hơn trong hai giá trị x và y, có thể viết
z = (x > y) ? x : y;
Phép điều kiện hơi giống với câu lệnh if. Câu lệnh trên có thể viết như sau:
if ( x > y)
z = x;
else
z = y;
Phép dấu phẩy
Dấu phẩy được sử dụng thường xuyên trong C để làm dấu ngăn cách, tách biệt các khai
báo biến, các đối số của hàm, v.v. Trong một số trường hợp nhất định, dấu phẩy lại hành
động như một phép toán chứ không còn là một dấu phân cách nữa. Có thể tạo ra một biểu
thức bằng cách ghép hai biểu thức con bằng dấu phẩy. Kết qủa như sau:
Cả hai biểu thức được tính nhưng biểu thức bên trái được tính trước.
Biểu thức tổng thể nhận giá trị của biểu thức bên phải.
Ví dụ, câu lệnh
x = (a++ , b ++ );
tăng a, rồi tăng b, sau đó gán giá trị của b (chưa tăng) cho x. Vì phép ++ ở đây là kiểu sau,
nên giá trị của b - trước khi được tăng - được gán cho x. Dùng các dấu ngoặc là cần thiết
bởi vì phép dấu phẩy có thứ tự ưu tiên thấp, thậm chí thấp hơn cả phép gán.
Kết luận
Trong bài này, nhiều khái niệm quan trọng của C đã được trình bày: một câu lệnh C là gì,
các khoảng trắng không có ý nghĩa đối với chương trình dịch của C, và các câu lệnh của C
đều kết thúc bằng một dấu chấm phảy. Ngoài ra, chúng ta cũng đã học về một câu lệnh
phức hợp hay còn gọi là một khối. Một khối chứa từ hai câu lệnh trở lên và được đóng
khung trong các dấu ngoặc nhọn, có thể được sử dụng ở bất kỳ chỗ nào mà một câu lệnh
đơn có thể được sử dụng.
Nhiều câu lệnh được tạo thành từ một số biểu thức và các phép toán. Một biểu thức là một
mệnh đề nào đó dùng để tính ra được một giá trị số. Các biểu thức phức hợp có thể chứa
nhiều biểu thức đơn giản hơn (biểu thức con).
Các phép toán là các ký hiệu của C, dùng để hướng dẫn máy tính thực hiện một thao tác
trên một hoặc nhiều biểu thức. Một số phép toán là đơn nguyên, có nghĩa là chúng thao tác
trên một toán hạng đơn. Tuy nhiên, hầu hết các phép của C đều là nhị nguyên, thao tác trên
hai toán hạng. Có một phép tam nguyên, đó là phép điều kiện. Các phép toán của C có một
quyền ưu tiên phân cấp quyết định thứ tự mà theo đó các phép toán sẽ được thực hiện
trong một biểu thức có chứa nhiều phép toán.
Các phép toán của C trong bài này được chia làm ba kiểu:
Các phép toán học thực hiện các phép số học trên các toán hạng của chúng.
Các phép quan hệ thực hiện việc so sánh giữa các toán hạng của chúng.
Các phép logic thao tác trên các biểu thức true/false. 0 để biểu diễn cho false, 1 để biểu
diễn cho true.
Câu hỏi và trả lời
1. Các dấu cách và các dòng trắng có làm ảnh hưởng đến sự thực hiện của một
chương trình không?
Các khoảng trắng (dấu cách, dòng trắng, tabs) thường được dùng để làm chương trình
dễ đọc hơn. Khi chương trình được dịch, các khoảng trắng bị bỏ qua, và vì thế chúng
không có hiệu lực gì đối với chương trình đã dịch và kết nối.
2. Sự khác nhau giữa các phép đơn nguyên và nhị nguyên là gì?
Các phép đơn nguyên thao tác trên một biến còn phép nhị nguyên làm việc với hai biến.
3. Phép trừ (-) là đơn nguyên hay nhị nguyên?
Là cả hai. Chương trình dịch đủ thông minh để biết phải làm gì với phép này. Nó biết
phải dùng kiểu nào căn cứ vào số biến trong biểu thức được sử dụng. Trong câu lệnh
sau, nó là đơn nguyên
x = -y;
ngược lại, trong trường hợp sau là nhị nguyên
x = a - b;
4. Một số âm được coi là đúng(true) hay sai (false)?
0 là false, và mọi giá trị khác là true. Vì vậy số âm là true.
Luyện tập
Câu hỏi
1. Câu lệnh sau đây được gọi là gì, và ý nghĩa của nó ra sao?
x = 5 + 8;
2. Một biểu thức là gì?
3. Trong một biểu thức có chứa nhiều phép toán, cái gì quyết định thứ tự thực hiện của
chúng?
4. Nếu một biến x có giá trị là 10, giá trị của x và a là bao nhiêu sau mỗi lần các câu lệnh
sau đây được thực hiện tách biệt?
a = x++;
a = ++x;
5. Biểu thức 10 % 3 cho giá trị bao nhiêu?
6. Còn biểu thức 5 + 3 * 8 / 2 + 2 ?
7. Hãy viết thêm các dấu ngoặc vào biểu thức trong câu 6 để biểu thức mới có giá trị bằng
16.
8. Nếu một biểu thức được đánh giá là false thì biểu thức này có giá trị là mấy?
9. Phép toán nào có thứ tự ưu tiên cao hơn?
a. == hay <
b. * hay +
c. != hay ==
d. >= hay >
10. Phép gán phức hợp là gì và nó có ích lợi như thế nào?
Bài tập
1. Chương trình sau viết không được hay lắm. Hãy nhập và dịch xem nó có chạy không.
#include
int x, y; main() printf(
―\nNhập hai số‖); scanf(
―%d %d, &x,&y);printf(
―\n\n%d lớn hơn‖,(x>y)?x:y);return 0;
2. Hãy viết lại chương trình trên cho dễ đọc hơn.
3. Hãy viết lại chương trình 4.1 để đếm theo chiều tăng dần thay cho giảm dần.
4. Viết một câu lệnh if để gán giá trị của x cho biến y chỉ khi x nằm giữa 1 và 20. Giữ
nguyên y không đổi nếu x không nằm trong miền này.
5. Dùng phép điều kiện để thực hiện nhiệm vụ của câu 4.
6. Viết lại các câu lệnh if lồng nhau sau đây bằng một câu lệnh if đơn và các phép logic.
if (x < 1)
if ( x >10)
câu_lệnh;
7. Các biểu thức sau cho ra giá trị nào ?
a. (1 + 2 * 3)
b. 10 % 3 * 3 - (1 + 2)
c. ((1 + 2) * 3)
d. (5 == 5)
e. (x = 5)
8. Nếu x = 4, y = 6, và z = 2, hãy xem các mệnh đề sau đây là true hay false
a. if (x == 4)
b. if (x != y - z)
c. if (z = 1)
d. if (y)
9. Viết một câu lệnh if để khẳng định một người là người lớn (21 tuổi) nhưng chưa già (65
tuổi).
10. Hãy sửa cho chương trình sau đây chạy đúng.
/* một chương trình có lỗi ... */
#include
int x = 1;
main()
if (x == 1);
printf ( ― x bằng 1‖);
otherwise
printf (― x không bằng 1‖);
return
Bài 5. Các khái niệm về hàm
Các hàm là bộ phận trung tâm của ngôn ngữ lập trình C và là cơ sở triết lý trong thiết kế
chương trình C. Trong bài này, sẽ trình bầy những vấn đề sau đây:
Một hàm là gì và bao gồm những thành phần nào.
Ưu điểm của lập trình có cấu trúc bằng các hàm.
Tạo lập một hàm như thế nào.
Khai báo các biến cục bộ trong một hàm.
Đưa trở lại một giá trị từ một hàm về chương trình như thế nào.
Truyền các đối số cho một hàm như thế nào.
Định nghĩa một hàm
Một hàm là một đoạn chương trình gốc độc lập, có tên gọi, thực hiện một nhiệm vụ
nhất định và có thể đưa trở lại một giá trị cho chương trình gọi nó.
Một hàm có tên gọi: Mỗi hàm có một tên duy nhất. Bằng cách viết tên hàm vào một phần
khác của chương trình là có thể thực hiện các câu lệnh chứa trong hàm này. Người ta gọi
việc đó là gọi hàm. Một hàm có thể được gọi bởi một hàm khác.
Một hàm là độc lập: Một hàm có thể thực hiện nhiệm vụ của nó một cách độc lập với các
phần khác của chương trình. Mỗi hàm thực hiện một nhiệm vụ nhất định. Một nhiệm vụ là
một công việc rời rạc mà chương trình phải thực hiện như là một công đoạn trong toàn bộ
thao tác của nó, chẳng hạn như gửi một dòng văn bản ra máy in, sắp xếp một mảng theo thứ
tự tăng dần, hoặc tính căn bậc hai của một số.
Một hàm có thể đưa trở lại một giá trị cho chương trình gọi nó: Khi chương trình gọi
một hàm, các câu lệnh của hàm này được thực hiện. Các câu lệnh này, nếu muốn, có thể
truyền thông tin trở lại cho chương trình đã gọi nó.
Ví dụ về hàm
Chương trình 5.1. Chương trình sử dụng một hàm tính lập phương của một số
1: /* Minh họa một hàm đơn giản */
2: #include
3:
4: long lap_phuong(long x);
5:
6: long so, ket_qua;
7:
8: main()
9:
10: printf(―Nhập một số nguyên: ―);
11: scanf(―%d", &so);
12: ket_qua = lap_phuong(so);
13: /* Chú ý: %ld là format dùng cho các số nguyên */
14: /* kiểu long */
15: printf(―\n\nLập phương của %ld bằng %ld.‖, so,ket_qua);
16:
17:
18: long lap_phuong(long x)
19:
20: long x_lap_phuong;
21:
22: x_lap_phuong = x * x * x;
23: return x_lap_phuong;
24:
Dòng 4 chứa nguyên mẫu của hàm (function prototype), nó cho chương trình dịch biết
trước rằng sẽ có một hàm có tên như vậy được viết trong chương trình này. Một nguyên
mẫu hàm gồm tên của hàm, một danh sách các biến cần phải được truyền cho hàm, và kiểu
của biến mà hàm sẽ đưa trở lại cho chương trình, nếu có. Tên của hàm là lap_phuong, nó
cần một biến có kiểu long, và nó sẽ đưa trở lại cho chương trình một giá trị có kiểu long.
Danh sách các biến cần được truyền cho hàm được gọi là các đối số (argument), chúng
được đặt trong hai dấu ngoặc và đứng ngay sau tên hàm. Trong ví dụ này, đối số của hàm là
long x. Từ giành riêng đứng trước tên hàm dùng để chỉ kiểu của biến mà hàm sẽ đưa trở
lại. Trong ví dụ này, một giá trị của một biến kiểu long sẽ được đưa trở lại cho chương
trình gọi hàm.
Dòng 12 gọi hàm lap_phuong và truyền biến số làm đối số cho hàm. Giá trị quay trở lại
của hàm này được gán cho biến ket_qua. Chú ý là, cả hai biến so và ket_qua đều
được khai báo tại dòng 6 với kiểu long.
Bản thân hàm thì được gọi là định nghĩa hàm. Trong ví dụ, hàm này được đặt tên là
lap_phuong và được viết tại các dòng 18-24. Giống như nguyên mẫu, định nghĩa hàm
cũng có một vài bộ phận. Hàm bắt đầu bằng một đầu hàm trên dòng 18, nó cho tên của
hàm (lap_phuong), kiểu của biến quay trở lại của hàm và khai báo các đối số. Chú ý là,
đầu hàm trùng với nguyên mẫu hàm, ngoại trừ dấu chấm phẩy.
Thân hàm, các dòng 19-24, được đóng trong hai dấu ngoặc nhọn. Các câu lệnh chứa trong
thân hàm sẽ được thực hiện khi hàm được gọi. Dòng 20 là một khai báo biến giống y như
các khai báo biến khác trong chương trình, nhưng có một điều khác là nó chỉ có tính cục bộ
(local). Các biến cục bộ là những biến được khai báo trong thân một hàm. Cuối cùng, hàm
kết thúc bằng một câu lệnh return trên dòng 23. Câu lệnh này báo hiệu đã hết hàm và có
thể truyền trở lại một giá trị cho chương trình gọi nó. Trong ví dụ, giá trị của biến
x_lap_phuong được truyền trở lại.
Cấu trúc của hàm lap_phuong cũng giống như của hàm main(). Các hàm khác được sử
dụng trong chương trình là printf()và scanf(). Mặc dù các hàm này là hàm thư viện
nhưng chúng cũng hoạt động y như một hàm do người sử dụng tạo lập, tức là chúng có thể
lấy các đối số và đưa trở lại các giá trị cho chương trình gọi chúng.
Cách thức làm việc của một hàm
Một chương trình C không thực hiện các câu lệnh trong một hàm cho đến khi hàm này được
gọi bởi một câu lệnh khác của chương trình. Khi một hàm được gọi thì chương trình có thể
truyền cho hàm các thông tin dưới dạng một hoặc một vài đối số. Một đối số là một dữ liệu
nào đó mà hàm cần có để thực hiện nhiệm vụ của nó. Sau đó, các câu lệnh của hàm được
chạy để thực hiện những công việc đã định. Khi các câu lệnh này kết thúc, điều khiển được
chuyển trở lại đúng vị trí mà tại đó đã gọi hàm. Các hàm có thể truyền thông tin trở lại cho
chương trình dưới dạng một giá trị trở lại.
Hình 5.1 chỉ ra một chương trình với ba hàm, mỗi hàm được gọi một lần. Mỗi khi một hàm
được gọi, điều khiển chuyển vào hàm đó. Khi hàm kết thúc, điều khiển lại chuyển trở lại vị
trí mà từ đó hàm được gọi. Một hàm có thể được gọi nhiều lần và theo một thứ tự tùy ý.
Hình 5.1. Khi một chương trình gọi một hàm, điều khiển chuyển vào hàm và sau đó
quay trở lại chương trình gọi.
Cú pháp của hàm
Nguyên mẫu hàm
kiểu_quay_lại tên_hàm(kiểu tên_1,..., kiểu tên_n);
Định nghĩa hàm
kiểu_quay_lại tên_hàm(kiểu tên_1,..., kiểu tên_n)
các câu lệnh;
Nguyên mẫu hàm cung cấp cho chương trình dịch mô tả của một hàm sẽ được định nghĩa
sau trong chương trình. Trong nguyên mẫu hàm có thể có kiểu của biến mà hàm sẽ đưa trở
lại giá trị khi điều khiển quay trở lại chương trình. Tiếp theo là tên hàm. Nên đặt tên hàm sao
cho tự bản thân tên này có thể mô tả được chức năng của nó. Sau tên hàm là một cặp dấu
ngoặc đơn. Trong đó chứa kiểu của các tham số và có thể cả bản thân các tham số sẽ được
truyền cho hàm (nếu hàm có tham số). Một nguyên mẫu hàm bao giờ cũng kết thúc bằng
một dấu chấm phẩy.
Một định nghĩa hàm chính là bản thân hàm đó. Nó chứa các câu lệnh sẽ được thực hiện.
Dòng đầu tiên của một định nghĩa hàm được gọi là đầu hàm. Đầu hàm nên trùng khớp với
nguyên mẫu nhưng không có dấu chấm phẩy ở cuối. Dù rằng tên các biến tham số có thể
không xuất hiện trong nguyên mẫu, nhưng chúng phải có mặt trong đầu hàm. Sau đầu hàm là
main()
{
gọi ham1()
. . .
gọi ham2()
. . .
gọi ham3()
. . .
}
ham1()
{
...
}
ham2()
{
...
}
ham3()
{
...
}
thân hàm chứa các câu lệnh mà hàm sẽ thực hiện. Thân hàm phải mở đầu bằng một dấu
ngoặc nhọn mở và kết thúc bằng một dấu ngoặc nhọn đóng. Nếu kiểu của biến quay trở lại
khác void (kiểu void tức là hàm không đưa trở lại một giá trị nào cả) thì phải có một câu
lệnh return để đưa trở lại một giá trị có kiểu của kiểu biến quay trở lại đã được khai báo
trong nguyên mẫu và trong đầu hàm.
Các hàm và lập trình có cấu trúc
Sử dụng các hàm trong một chương trình chính là sử dụng kỹ thuật lập trình có cấu trúc,
trong đó các nhiệm vụ riêng biệt của chương trình được thực hiện bởi từng bộ phận độc lập.
Các hàm và lập trình có cấu trúc có quan hệ mật thiết với nhau.
Những ưu điểm của lập trình có cấu trúc
Một chương trình có cấu trúc dễ viết hơn bởi vì các vấn đề của một chương trình
phức tạp có thể được chia ra thành các nhiệm vụ nhỏ hơn, đơn giản hơn. Mỗi nhiệm
vụ được thực hiện bởi một hàm, trong đó các câu lệnh và các biến được cô lập đối
với phần còn lại của chương trình.
Dễ dàng sửa chữa một chương trình có cấu trúc. Nếu chương trình có lỗi, thì với
một thiết kế có cấu trúc, có thể dễ dàng tìm ra lỗi và cô lập bộ phận nào của
chương trình có thể sinh lỗi.
Tiết kiệm thời gian lập trình. Nếu viết một hàm để thực hiện một công việc nhất định
nào đó trong một chương trình, thì có thể nhanh chóng và dễ dàng sử dụng nó trong
một chương trình khác mà cũng cần phải thực hiện một công việc đúng như vậy.
Ngay cả khi nếu trong chương trình mới, công việc này cần phải sửa đôi chút thì việc
sửa lại một hàm đã được tạo lập vẫn còn nhanh hơn rất nhiều so với việc viết mới.
Thiết kế một chương trình có cấu trúc
Cách tiếp cận trên xuống (Top-Down)
Bằng kỹ thuật lập trình có cấu trúc, người lập trình đã sử dụng cách tiếp cận từ trên xuống.
Hình 5.2 mô tả cấu trúc của chương trình như một hình cây dựng ngược. Mỗi hàm là một
nhánh con của cây.
Hình 5.2. Một chương trình có cấu trúc được tổ chức theo dạng phân cấp.
Nhiều chương trình C chỉ có rất ít câu lệnh tại hàm main(). Các công việc chính của
chương trình phần lớn nằm trong các hàm. Trong main(), thường chỉ có một vài câu lệnh
điều khiển việc thực hiện của các hàm. Thông thường, một thực đơn được hiện ra để người
sử dụng chọn lựa một nhánh nào đó của chương trình.
Viết một hàm
main()
Sửa Nhập Sắp xếp In
Sửa Ghi Đọc
Bước đầu tiên trong việc viết một hàm là phải xác định được hàm này làm gì. Một khi đã
biết điều đó, thì việc viết hàm không còn khó khăn gì mấy nữa.
Đầu hàm
Dòng đầu tiên của một hàm là đầu hàm, với ba thành phần sau:
kiểu_biến_quay_lại tên_hàm(tham_số_1, tham_số_2,. . .)
Kiểu biến quay lại của hàm
Kiểu biến quay lại của hàm xác định kiểu của dữ liệu mà hàm sẽ đưa trở lại cho chương trình
gọi nó. Kiểu ở đây có thể là bất kỳ kiểu dữ liệu nào của C: char, int, long,
float, hoặc double. Cũng có thể định nghĩa một hàm không đưa trở lại một giá trị nào
cả, trong trường hợp này kiểu quay trở lại là void.
Tên hàm
Tên hàm có thể đặt là gì cũng được, nhưng phải tuân theo các qui tắc như đối với tên biến.
Tên hàm phải duy nhất (tức là không được trùng với một hàm hoặc một biến khác). Ngoài
ra, tên hàm nên đặt sao cho có thể nói lên được nhiệm vụ của nó.
Danh sách tham số
Có rất nhiều hàm sử dụng các đối số. Các đối số là các giá trị được truyền cho hàm khi nó
được gọi. Một hàm cần phải biết loại đối số nào mà nó cần. Ta có thể truyền cho một hàm
bất kỳ kiểu dữ liệu nào của C. Kiểu của đối số được cho trong danh sách tham số của đầu
hàm.
Trong danh sách tham số phải có mặt mọi đối số mà chương trình sẽ phải truyền cho hàm.
Đối với mỗi đối số đều có hai thành phần: kiểu dữ liệu và tên của tham số. Nếu có nhiều đối
số thì các đối số được đặt cách nhau bằng một dấu phẩy. Ví dụ, đầu hàm
void ham1(int x, float y, char z)
định nghĩa một hàm với ba đối số: một kiểu nguyên có tên là x, một kiểu float có tên y,
và một kiểu char có tên z. Một số hàm không có đối số, trong trường hợp này danh sách
tham số được đọc là void:
void ham2(void)
Lưu ý, sau đầu hàm không được viết dấu chấm phẩy.
Đôi khi có người nhầm lẫn giữa một tham số và một đối số. Một tham số tham gia trong
thân hàm như là “người giữ chỗ” cho một đối số. Các tham số của một hàm là cố định,
chúng không hề thay đổi trong quá trình thực hiện của hàm. Một đối số là một giá trị thực sự
được truyền cho hàm bởi chương trình gọi hàm. Mỗi lần hàm được gọi, nó có thể được
truyền các đối số khác nhau. Số đối số được truyền phải bằng đúng số tham số. Đồng thời
kiểu của đối số phải trùng với kiểu của tham số tương ứng. Trong hàm, các đối số được
thâm nhập thông qua tên của tham số tương ứng.
Thân hàm
Thân hàm được đóng trong hai dấu ngoặc nhọn và đứng ngay sau đầu hàm. Tại đây các
công việc của hàm được thực hiện. Khi một hàm được gọi, điều khiển bắt đầu tại câu lệnh
đầu tiên của thân hàm và kết thúc (quay trở lại chương trình gọi nó) khi gặp một câu lệnh
return hoặc khi đã đến dấu ngoặc đóng.
Các biến cục bộ (local)
Có thể khai báo các biến trong thân của một hàm. Các biến được khai báo trong thân hàm
gọi là các biến cục bộ. Thuật ngữ cục bộ có nghĩa là các biến này là sở hữu cá nhân của
riêng hàm này và khác biệt với các biến khác có cùng tên nhưng được khai báo tại những
nơi khác trong chương trình. Ngược lại với cục bộ là tổng thể. Một biến được gọi là tổng
thể nếu nó được khai báo trong chương trình nhưng không nằm trong bất cứ một thân hàm
nào.
Một biến cục bộ cũng được khai báo y như các biến khác. Nó cũng có thể được khởi tạo
ngay khi khai báo. Trong một hàm, có thể khai báo mọi kiểu biến của C.
Khi khai báo và sử dụng một biến trong một hàm nào đó thì có nghĩa là biến này hoàn toàn
độc lập và khác biệt với mọi biến khác được khai báo ở đâu đó trong chương trình.
Chương trình 5.2. Minh hoạ các biến cục bộ
1: /* Minh hoạ các biến cục bộ */
2:
3: #include
4:
5: int x = 1, y = 2;
6:
7: void demo(void);
8:
9: main()
10: {
11: printf(―\nTrước khi gọi demo(), x=%d và y=%d.‖, x,y);
12: demo();
13: printf(―\nSau khi gọi demo(), x=%d và y=%d.‖, x,y);
14: }
15:
16: void demo(void)
17: {
18: /* Khai báo và khởi tạo hai biến cục bộ.*/
19:
20: int x=88, y=99;
21:
22: /* Hiện các giá trị của chúng. */
23:
24: printf(―\nTrong hàm demo(), x=%d và y=%d.‖, x, y);
25: }
Có ba quy tắc chi phối cách sử dụng các biến trong một hàm
Để dùng một biến trong một hàm, phải khai báo nó trong đầu hàm hoặc trong thân
hàm (trừ những biến tổng thể ).
Một hàm muốn thu nhận một giá trị từ chương trình gọi nó thì giá trị này phải được
truyền cho hàm như là một đối số.
Để một chương trình gọi hàm có thể thu nhận được một giá trị từ một hàm, thì giá trị
này phải được cho trở lại từ hàm.
Giữ cho các biến của hàm tách biệt với các biến khác của chương trình là một trong những
cách tốt nhất để cho hàm có tính độc lập. Một hàm có thể thực hiện mọi thao tác dữ liệu
trên tập các biến cục bộ của nó mà không hề có một ảnh hưởng nào đến các phần khác của
chương trình.
Các câu lệnh của hàm
Về nguyên tắc, không hề có một hạn chế nào về các câu lệnh có thể viết trong một hàm.
Điều duy nhất không được phép là trong một hàm không được định nghĩa một hàm khác.
Trong một hàm, có thể gọi các hàm thư viện và các hàm tự định nghĩa khác.
Về độ dài của hàm thì sao? C không hạn chế về độ dài của các hàm, nhưng theo kinh
nghiệm thực tế thì nên viết các hàm có độ dài vừa phải thôi. Trong lập trình có cấu trúc, mỗi
hàm thường được thiết kế để thực hiện một nhiệm vụ tương đối đơn giản.
Cho trở lại một giá trị
Để cho trở lại một giá trị từ một hàm, phải dùng từ khóa return và theo sau là một biểu
thức C. Khi thực hiện đến câu lệnh return, biểu thức này được đánh giá, sau đó giá trị
này và điều khiển được truyền trở lại cho chương trình gọi hàm. Giá trị quay lại của hàm
chính là giá trị của biểu thức đứng sau return.
Một hàm có thể chứa nhiều câu lệnh return, nhưng chỉ có một câu lệnh được thực hiện
bởi vì sau câu lệnh này là hàm kết thúc và sự thực hiện được chuyển trở lại cho chương
trình. Dùng nhiều câu lệnh return trong một hàm là một cách hữu hiệu để cho trở lại các giá
trị khác nhau từ một hàm.
Chương trình 5.3. Minh hoạ cách dùng nhiều câu lệnh return trong một hàm
1: /* Minh hoa cach dung nhiuu lenh return trong 1 ham */
2:
3: #ịnclude
4:
5: int x, y, z;
6:
7: int lager(int a, int b);
8:
9: main()
10: {
11: puts(―Nhap hai so nguyen khac nhau: ―);
12: scanf(―%d%d‖, &x, &y);
13:
14: z = lager(x,y);
15:
16: printf(―\nSo lon hon là %d.‖, z);
17: }
18:
19: int lager(int a, int b)
20: {
21: if (a > b)
22: return (a);
23: else
24: return (b);
25: }
Nguyên mẫu hàm
Một chương trình phải chứa một nguyên mẫu cho mọi hàm mà nó sử dụng. Nguyên mẫu là
gì và tại sao lại phải cần đến nó?
Nguyên mẫu của một hàm giống hệt như đầu hàm cộng thêm dấu chấm phẩy ở cuối. Giống
như đầu hàm, nguyên mẫu hàm chứa thông tin về kiểu biến được cho trở lại, tên hàm và các
tham số.
Nói một cách chặt chẽ thì một nguyên mẫu hàm không cần thiết phải đồng nhất với đầu hàm.
Tên của các tham số có thể khác nhau miễn sao chúng có cùng kiểu, cùng một số lượng, và
theo cùng một thứ tự. Nhưng không có một lý do nào mà nguyên mẫu lại không thể viết
giống y như đầu hàm; khi chúng như nhau thì dễ viết chương trình hơn và việc đọc chương
trình cũng sẽ dễ hiểu hơn.
Truyền các tham số cho một hàm
Để truyền các đối số cho một hàm, chỉ cần viết các đối số này vào sau tên hàm và đóng
chúng bằng các dấu ngoặc tròn. Số đối số, kiểu của từng đối số phải trùng khớp với các
tham số trong đầu hàm (cũng như trong nguyên mẫu hàm). Nếu hàm có nhiều đối số, thì các
đối số đã được cho trong lời gọi hàm được gán cho các tham số của hàm theo thứ tự: đối số
thứ nhất ứng với tham số thứ nhất, đối số thứ hai ứng với tham số thứ hai, và cứ thế tiếp tục.
Mỗi đối số có thể là một biểu thức C bất kỳ: một hằng, một biến, một biểu thức toán học
hoặc logic, hoặc thậm chí là một hàm khác (một hàm có cho trở lại một giá trị).
Gọi các hàm
Có hai cách gọi một hàm:
Viết tên hàm và danh sách đối số trong một câu lệnh riêng biệt. Nếu hàm này cho trở lại
một giá trị thì giá trị này không được sử dụng. Ví dụ
doi(15);
Viết tên hàm và danh sách đối số trong một biểu thức nào đó. Cách này chỉ được sử
dụng cho những hàm có cho trở lại một giá trị. Vì các hàm loại này có cho trở lại một giá
trị nên chúng là các biểu thức hợp lệ của C và do vậy có thể được sử dụng ở bất cứ chỗ
nào mà một biểu thức có thể được sử dụng. Ví dụ:
printf(―Một nửa của %d là %d.‖, x, mot_nua_cua(x));
Trong đó, mot_nua_cua() là một lời gọi hàm và được sử dụng như một đối số của
hàm printf(). Hoặc
y = mot_nua_cua(x) + mot_nua_cua(z);
Trong đó, hàm mot_nua_cua() được gọi hai lần với các đối số khác nhau.
Đệ qui
Thuật ngữ đệ qui dùng để chỉ một tình huống trong đó một hàm lại gọi trực tiếp hoặc gián
tiếp chính nó. Đệ qui gián tiếp xuất hiện khi một hàm gọi một hàm khác mà hàm này lại gọi
hàm thứ nhất. C cho phép sử dụng các hàm đệ qui, và trong một số trường hợp chúng rất
hữu ích.
Ví dụ, đệ qui có thể được dùng để tính giai thừa của một số. Giai thừa của x được viết là x!
và được tính theo công thức:
x! = x * (x-1) * (x-2) * . . . * (2) * 1
Tuy nhiên, cũng có thể tính x! như sau:
x! = x * (x-1)!
Đi tiếp một bước nữa, lại có thể tính (x-1)! theo cách tương tự:
(x-1)! = (x-1) * (x-2)!
Cứ tiếp tục phép tính đệ qui này cho đến khi số cần tính giai thừa giảm xuống đến 1, đó là
lúc kết thúc. Chương trình 5.4 dùng một hàm đệ qui để tính giai thừa. Vì giai thừa của 9 đã
lớn hơn giá trị lớn nhất có thể chứa được của kiểu nguyên không dấu nên giá trị tối đa của
đối số chỉ là 8.
Chương trình 5.3. Dùng hàm đệ qui để tính các giai thừa.
1: /* Minh hoạ cách dùng hàm đệ qui để */
2: /* tính giai thừa của một số */
3:
4: #include
5:
6: unsigned int f, x;
7: unsigned int giai_thua(unsigned int a);
8:
9: main()
10:
11: puts(―Nhập một số nguyên nằm giữa 1 và 8: ―);
12: scanf(―%d‖, &x);
13:
14: if( x > 8 || x < 1)
15: {
16: printf(―Chỉ các giá trị từ 1 đến 8 được chấp nhận!‖);
17:
18: else
19:
20: f = giai_thua(x);
21: printf(―%u giai thừa bằng %u‖, x, f);
22:
23:
24:
25: unsigned int giai_thua(unsigned int a)
26:
27: if (a==1)
28: return 1;
29: else
30:
31: a *= giai_thua(a-1);
32: return a;
33:
34:
Kết luận
Bài này giới thiệu về các hàm, một bộ phận quan trọng của lập trình C. Các hàm là các phần
độc lập thực hiện các công việc nhất định. Khi chương trình cần thực hiện một công việc
nào đó, nó sẽ gọi hàm thực hiện công việc này. Việc sử dụng các hàm chính là việc lập trình
có cấu trúc - một phương pháp thiết kế chương trình theo kiểu trên xuống. Lập trình có cấu
trúc tạo ra các chương trình có hiệu qủa hơn và cũng đơn giản hơn khi lập trình.
Một định nghĩa hàm chứa một đầu hàm và một thân hàm. Đầu hàm chứa các thông tin về
kiểu của giá trị quay lại, tên hàm, và các tham số. Thân hàm chứa các khai báo biến cục bộ
và các câu lệnh C sẽ được thực hiện khi hàm được gọi.
Câu hỏi
1. Dòng đầu tiên của một định nghĩa hàm phải là gì, và nó chứa những thông tin nào?
2. Một hàm có thể cho quay lại bao nhiêu giá trị?
3. Nếu một hàm không cho quay lại một giá trị, thì phải khai báo nó như thế nào?
4. Sự khác nhau giữa một định nghĩa hàm và một nguyên mẫu là gì?
5. Một biến cục bộ là gì?
6. Các biến cục bộ đặc biệt như thế nào?
Bài tập
1. Trong chương trình sau đây có gì sai không?
#include
void in_thong_bao(void);
main()
in_thong_bao( ―Đây là một thông báo cần in ra‖);
void in_thong_bao(void)
puts(―Đây là một thông báo cần in ra‖);
return 0;
2. Có gì sai trong định nghĩa hàm sau đây?
int hai_lan (int y );
return ( 2 * y);
3. Hãy viết lại Chương trình 5.3 với chỉ một câu lệnh return.
4. Hãy viết một hàm nhận hai số làm đối số và cho quay trở lại tích của chúng.
5. Viết một hàm nhận hai số làm đối số. Hàm này chia số thứ nhất cho số thứ hai. Không
chia nếu số thứ hai bằng 0.
6. Viết một hàm gọi các hàm trong các bài tập bốn và năm.
7. Viết một chương trình có sử dụng một hàm tìm giá trị trung bình của năm giá trị kiểu float
được nhập vào từ bàn phím.
8. Viết một hàm đệ qui tính 3 lũy thừa lên một số lần bất kỳ.
Bài 6. Điều khiển chương trình
Trong Bài 4 đã nói đến câu lệnh if. Câu lệnh này cho phép điều khiển quá trình thực hiện
của chương trình. Tuy nhiên, khả năng tạo ra các điều kiện đúng sai chưa đáp ứng được đầy
đủ các nhu cầu về việc điều khiển chương trình. Bài này sẽ giới thiệu ba cách mới để điều
khiển luồng thực hiện của chương trình:
Sử dụng các mảng đơn như thế nào.
Các chu trình for, while, và do ... while để thực hiện nhiều lần một nhóm
các câu lệnh.
Các câu lệnh điều khiển lồng nhau.
Cơ sở về các mảng
Trước khi học câu lệnh for, cần biết qua về khái niệm mảng (array). Câu lệnh for và các
mảng có liên quan chặt chẽ với nhau trong C, vì vậy rất khó định nghĩa khái niệm này mà
không giải thích gì về khái niệm kia.
Một mảng là một tập hợp các vị trí lưu trữ dữ liệu mang cùng một tên và phân biệt với nhau
qua các chỉ số. Giống như các biến khác của C, các mảng phải được khai báo trước khi
đem ra sử dụng. Một khai báo mảng bao gồm kiểu dữ liệu, tên mảng và cỡ của mảng. Ví
dụ, câu lệnh
int data1000;
khai báo một mảng có tên là data, kiểu int và có 1000 phần tử. Các phần tử riêng biệt
của mảng được đánh số từ data0 đến data999. Chú ý rằng phần tử đầu tiên là
data0 chứ không phải là data1 như các ngôn ngữ khác.
Mỗi phần tử của mảng này tương đương với một biến nguyên thông thường và có thể được
sử dụng như nhau. Chỉ số của một mảng có thể là một biểu thức C, như trong ví dụ sau:
int data1000;
int a;
a = 100;
dataa = 10; /* tương đương data100=10; */
Điều khiển sự thực hiện của chương trình
Thứ tự thực hiện trong một chương trình C được ngầm định là từ trên xuống. Nó khởi đầu
từ câu lệnh đầu tiên của hàm main(), rồi đến câu lệnh tiếp theo và cứ thế tiếp tục cho đến
câu lệnh cuối cùng của main(). Tuy nhiên, thứ tự này hiếm khi xảy ra trong một chương
trình C thực sự. Ngôn ngữ C có nhiều câu lệnh điều khiển chương trình cho phép điều khiển
thứ tự thực hiện của các câu lệnh. Trong Bài 4 đã trình bầy về câu lệnh if, bài này sẽ nói
về một số câu lệnh điều khiển khác.
Câu lệnh for
Câu lệnh for là một cấu trúc của C, cho phép thực hiện một khối các câu lệnh một số lần
nhất định. Đôi khi còn được gọi là vòng lặp for.
Một câu lệnh for có cấu trúc như sau:
for (khởi_tạo; điều_kiện; tăng)
câu_lệnh
Trong đó, khởi_tạo, điều_kiện, tăng đều là các biểu thức C và câu_lệnh là một câu lệnh
đơn hoặc phức hợp. Khi gặp một câu lệnh for thì các sự kiện sau sẽ xẩy ra:
1. Biểu thức khởi_tạo được tính. Thông thường, khởi_tạo là một biểu thức gán, nó gán
một giá trị nào đó cho một biến.
2. Biểu thức điều_kiện được đánh giá. Thông thường, điều_kiện là một biểu thức quan
hệ.
3. Nếu biểu thức điều_kiện được đánh giá là false, thì câu lệnh for kết thúc và việc thực
hiện sẽ chuyển đến câu lệnh đầu tiên đứng sau câu_lệnh.
4. Nếu biểu thức điều_kiện được đánh giá là true, các câu lệnh trong câu_lệnh được
thực hiện.
5. Biểu thức tăng được tính, và quay trở lại bước hai.
Sau đây là một ví dụ đơn giản. Chương trình 6.1 dùng một câu lệnh for để in các số từ 1
đến 20.
Chương trình 6.1. Minh họa một câu lệnh for đơn giản
1: /* Minh họa một câu lệnh for đơn giản */
2:
3: #include
4:
5: int dem;
6:
7: main()
8:
9: /* In các số từ 1 đến 20 */
10:
11: for(dem = 1; dem <=20; dem++)
12: printf("\n%d‖, dem);
13:
Hình 6.1 là sơ đồ khối minh họa hoạt động của vòng lặp for trong Chương trình 6.1.
Câu lệnh for thường được sử dụng để tăng giá trị của một con đếm, hay còn gọi là đếm
tiến. Còn có thể sử dụng nó để đếm lùi. Ví dụ,
for (dem =100; dem >0; dem--)
Ngoài ra, mỗi lần đếm không chỉ tăng 1 mà còn có thể tăng thêm một giá trị tuỳ ý, ví dụ:
for (dem = 0; dem < 1000; dem +=5)
Câu lệnh for rất mềm dẻo, ví dụ, có thể bỏ qua biểu thức khởi tạo nếu biến kiểm tra đã
được khởi tạo trước đó trong chương trình. Nhưng vẫn phải dùng dấu chấm phẩy để ngăn
cách:
dem = 1;
for ( ; dem < 1000; dem ++)
Hình 6.1. Cách thức hoạt động của vòng lặp for trong chương trình 6.1.
Cũng có thể bỏ qua biểu thức tăng, nếu như biểu thức điều kiện được cập nhật trong thân
của câu lệnh for. Tất nhiên, vẫn phải có dấu chấm phẩy để làm dấu ngăn cách. Ví dụ, để in
các số từ 0 đến 99, có thể viết như sau:
for (dem = 0; dem <100; )
printf ("%d ", dem ++);
Biểu thức điều kiện dùng để điều khiển sự kết thúc của vòng lặp có thể là một biểu thức C
bất kỳ. Chừng nào nó còn được đánh giá là true (khác 0), câu lệnh for vẫn tiếp tục thực
hiện. Có thể dùng các phép logic của C để kiến tạo các biểu thức điều kiện phức tạp. Ví dụ
sau đây dùng để in các phần tử của một mảng có tên mang(), và sẽ kết thúc khi tất cả các
phần tử của mảng đã được in ra hoặc khi gặp phải một phần tử nào đó của mảng có giá trị
bằng 0.
for (dem = 0; dem <1000 && mangdem != 0; dem ++)
printf ("%d", mangdem);
Có thể viết sau câu lệnh for một câu lệnh rỗng để đảm bảo rằng tất cả công việc đã được
thực hiện trong bản thân câu lệnh for. Một lệnh rỗng là một dấu chấm phẩy duy nhất trên
một dòng. Ví dụ, để khởi tạo cho tất cả các phần tử của một mảng gồm 1000 phần tử một
giá trị chung là 30, có thể viết như sau:
for (dem = 0; dem < 1000; mangdem ++ = 30)
;
Đúng
Khởi
đầu
Gán 1
cho dem
dem
<=20?
Tăng dem
thêm 1
Thực hiện
printf()
Kết thúc
for(dem=1;dem<=20;dem++)
printf(“\n%d”, dem);
Sai
Trong Bài 4, đã nói đến phép dấu phẩy. Bằng phép này có thể tạo lập một biểu thức bằng
cách ghép hai biểu thức con liền nhau và phân cách chúng bằng một dấu phẩy. Hai biểu thức
con này được tính theo thứ tự từ_trái_sang_phải, và biểu thức tổng thể được gán giá trị
của biểu thức con bên phải dấu phẩy. Bằng cách dùng phép dấu phẩy, có thể thực hiện
nhiều lần từng bộ phận của một câu lệnh for.
Giả sử, có hai mảng 1000 phần tử, a và b, và cần copy nội dung của a sang b theo
thứ tự ngược, sao cho sau thao tác copy, b0 = a999, b1 = a998, và cứ thế tiếp
tục. Câu lệnh for sau đây sẽ làm việc này:
for (i = 0, j =999; i <1000 ; i++ , j--)
bj = a i;
Các câu lệnh for lồng nhau
Một câu lệnh for có thể được thực hiện trong một câu lệnh for khác. Các câu lệnh này
được gọi là lồng nhau. Bằng cách lồng các câu lệnh for lại với nhau, rất nhiều kỹ thuật lập
trình có thể giải quyết được. Chương trình 6.2 tuy không phải là phức tạp, nhưng nó minh
họa khá tốt cách sử dụng hai lệnh for lồng nhau.
Chương trình 6.2. Minh họa các câu lệnh for lồng nhau.
1: /* Minh hoạ hai câu lệnh for lồng nhau */
2:
3: #include
4:
5: void ve_hop(int dong, int cot);
6:
7: main()
8:
9: ve_hop(8, 35);
10:
11:
12: void ve_hop(int dong, int cot)
13:
14: int cot1;
15: for (;dong > 0; dong--)
16:
17: for(cot1 = cot; cot1 > 0; cot1--)
18: printf("X");
19:
20: printf("\n";
21:
22:
Câu lệnh while
Câu lệnh while, hay còn gọi là vòng lặp while, thực hiện một khối các câu lệnh chừng
nào một điều kiện đã xác định vẫn còn đúng. Nó có dạng sau:
while (điều_kiện)
câu_lệnh
điều_kiện ở đây là một biểu thức C, còn câu_lệnh là một câu lệnh đơn hoặc phức hợp. Khi
sự thực hiện chương trình gặp một câu lệnh while, các sự kiện sau sẽ sảy ra:
1. Biểu thức điều_kiện được đánh giá.
2. Nếu điều_kiện có giá trị false (nghĩa là bằng không), câu lệnh while kết thúc, và
thực hiện chuyển đến câu lệnh đầu tiên sau câu_lệnh.
3. Nếu điều_kiện có giá trị true (nghĩa là khác không), câu_lệnh được thực hiện.
4. Thực hiện chuyển lên bước 1.
Hoạt động của while được minh họa trong Hình 6.2
Hình 6.2. Sơ đồ khối minh họa họat động của câu lệnh while.
Chương trình 6.3 là một ví dụ đơn giản về cách sử dụng một câu lệnh while để in các số
từ 1 đến 20.
Chương trình 6.3. Minh họa cách dùnh câu lệnh while
1: /* Minh họa cách dùng câu lệnh while */
2:
3: #include
4:
5: int dem;
6:
7: main()
8:
9: /* In các số từ 1 đến 20 */
10:
Đúng
Khởi
đầu
đánh
giá
điều
kiện
Thực hiện
các câu lệnh
Kết thúc
while (điều kiện)
các câu lệnh;
Sai
11: dem = 1;
12:
13: while (dem <= 20)
14:
15: printf(―\n%d, dem);
16: dem++;
17:
18:
Câu lệnh while về cơ bản tương đối giống với một câu lệnh for không có các thành phần
khởi tạo và tăng. Nghĩa là,
for (; điều_kiện ; )
tương đương với
while (điều_kiện)
Vì sự tương đương này mà bất cứ điều gì có thể làm được bằng một câu lệnh for thì cũng
có thể làm được bằng một câu lệnh while. Khi sử dụng một câu lệnh while, trước hết
cần phải tiến hành việc khởi tạo trong một câu lệnh riêng, và việc cập nhật cũng phải được
thực hiện bởi một câu lệnh trong thành phần của vòng lặp while.
Khi mà việc khởi tạo và cập nhật là cần thiết, thì hầu hết những người lập trình có kinh
nghiệm đều chọn câu lệnh for vì các biểu thức khởi tạo, kiểm tra, và tăng đều được viết
cùng với nhau ngay sau từ for, điều đó giúp cho việc tìm, đọc, và sửa chữa dễ dàng hơn.
Với câu lệnh while, các biểu thức khởi tạo và cập nhật được viết riêng tại những chỗ khác
và như vậy dễ bị bỏ quên và khó theo dõi.
Các câu lệnh while lồng nhau
Giống như for và if, các câu lệnh while cũng có thể được lồng nhau. Chương trình 6.4
chỉ ra một ví dụ về kiểu này.
Chương trình 6.4. Minh họa các câu lệnh while lồng nhau.
1: /* Minh họa các câu lệnh while lồng nhau */
2:
3: #include
4:
5: int mang5;
6:
7: main()
8:
9: int ctr = 0,
10: nbr = 0;
11:
12: printf(―Chương trình này nhắc bạn nhập 5 số\n‖);
13: printf(―Mỗi số đều nằm giữa 1 và 10\n‖);
14:
15: while (ctr < 5)
16:
17: nbr = 0;
18: while (nbr 10)
19:
20: printf(―\nNhập số thứ %d trong 5 số: ―,ctr+1);
21: scanf( ―%d‖, &nbr);
22:
23:
24: mangctr = nbr;
25: ctr++;
26:
27:
28: for( ctr=0; ctr<5; ctr++ )
29: printf( ―\nGiá trị thứ %d là %d‖, ctr+1,mangctr);
30:
Vòng lặp do ... while
Cấu trúc lặp thứ ba của C là vòng lặp do...while. Vòng lặp này thực hiện một khối các câu
lệnh khi mà một điều kiện đã cho vẫn còn đúng. Nó thử điều kiện này ở cuối vòng lặp chứ
không làm ở đầu như các vòng lặp for và while.
Cấu trúc của vòn
Các file đính kèm theo tài liệu này:
- 01200051_0378_1983580.pdf