Giáo trình Ngôn ngữ lập trình C - Bùi Thế Hồng
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Ngôn ngữ lập trình C - Bùi Thế Hồng", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
- giao_trinh_ngon_ngu_lap_trinh_c_bui_the_hong.pdf
Nội dung text: Giáo trình Ngôn ngữ lập trình C - Bùi Thế Hồng
- 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 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ó lớn hơn hoặc bằng toán x>=y hoặc bằng hạng 2 không?
- Nhỏ hơn 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 = 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 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 ý. ham1() main() { { gọi ham1() } . . . gọi ham2() ham2() . . . { gọi ham3() } . . . } ham3() { } 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à
- 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. main() Nhập Sửa Sắp xếp In Đọc Sửa Ghi 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
- 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 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 ++)
- Khởi đầu Gán 1 Tăng dem cho dem thêm 1 for(dem=1;dem<=20;dem++) printf(“\n%d”, dem); Đúng dem Thực hiện <=20? printf() Sai Kết thúc 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) ;
- 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 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 Khởi đầu while (điều kiện) các câu lệnh; Đúng đánh Thực hiện giá các câu lệnh điều kiện Sai Kết thúc 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:
- 11: dem = 1; 12: 13: while (dem 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òng lặp do while như sau: do câu_lệnh while (điều_kiện); trong đó, điều_kiện là một biểu thức C bất kỳ, còn câu_lệnh là một câu lệnh đơn hoặc phức hợp. Khi thực hiện chương trình gặp một câu lệnh do while, các sự kiện sau sẽ xẩy ra: 1. Các câu lệnh trong câu_lệnh được thực hiện. 2. điều_kiện được đánh giá. Nếu nó là đúng, thực hiện trở lại bước một. Nếu nó là sai, vòng lặp kết thúc. Sự hoạt động của do while được minh họa trong Hình 6.3.
- Khởi đầu Thực hiện các câu lệnh do các câu lệnh; while (điều kiện); đánh Đúng giá điều kiện Sai Kết thúc Hình 6.3. Hoạt động của vòng lặp do while. Các câu lệnh gắn với một vòng lặp do while luôn luôn được thực hiện ít nhất một lần. Đó là vì phép kiểm tra điều kiện được thực hiện ở cuối vòng lặp. Ngược lại, các vòng lặp for và while kiểm tra điều kiện ở ngay khi khởi đầu vòng lặp, và vì vậy các câu lệnh đi theo chúng sẽ không hề được thực hiện lần nào nếu phép kiểm tra ban đầu này có giá trị là sai. Vòng lặp do while được sử dụng ít hơn so với while và for. Nó chỉ thích hợp khi các câu lệnh đi theo vòng lặp này cần phải được thực hiện ít nhất một lần. Chương trình 6.5 chỉ ra một ví dụ về một vòng lặp do while.
- Chương trình 6.5. Ví dụ về một vòng lặp do while đơn giản. 1: */Minh họa cách dùng vòng lặp do while */ 2: 3: #include 4: 5: int thuc_don(void); 6: 7: main() 8: 9: int lua; 10: 11: lua = thuc_don(); 12: 13: printf("Bạn đã chọn lệnh:%d", lua); 14: 15: 16: int thuc_don(void) 17: 18: int chon = 0; 19: 20: do 21: 22: printf("\n"); 23: printf("\n1 - Thêm một record"); 24: printf("\n2 - Sửa đổi một record"); 25: printf("\n3 - Xóa một record"); 26: printf("\n4 - Ra!"); 27: printf("\n"); 28: printf("\nChọn một lệnh:"); 29: 30: scanf("%d", chon); 31: 32: while (chon 4); 33: 34: return chon; 35: Các vòng lặp lồng nhau Thuật ngữ các vòng lặp lồng nhau dùng để chỉ một vòng lặp được chứa trong một vòng lặp khác. C không đặt một hạn chế nào về độ lặp của các vòng trừ một qui định là, mọi vòng trong hơn phải nằm hoàn toàn trong vòng ngoài hơn; các vòng lặp không được phép gối lên nhau (overlapping). Vì vậy, viết như sau là không được phép: for (dem = 1; dem < 100; dem++) do
- /* vòng lặp do while */ /* kết thúc vòng lặp for */ while ( x != 0); Nhưng, nếu do while được đặt toàn bộ trong vòng for thì không có vấn đề gì. for (dem = 1; dem < 100; dem++) do /* vòng lặp do while */ while ( x != 0); /* kết thúc vòng lặp for */ Kết luận C có ba câu lệnh lặp dùng để điều khiển sự thực hiện của chương trình: for, while, và do while. Mỗi cấu trúc trong các loại này đảm bảo cho chương trình thực hiện một khối các câu lệnh không lần, một lần, hoặc nhiều lần dựa trên một điều kiện của các biến nào đó trong chương trình. Mặc dù cả ba cấu trúc này có thể được sử dụng để thực hiện cùng một nhiệm vụ, nhưng từng cái một có khác nhau. Câu lệnh for tiến hành việc khởi tạo, đánh giá, và tăng chỉ trong một lệnh. Câu lệnh while tiếp tục thao tác chừng nào điều kiện vẫn còn đúng. Câu lệnh do while luôn luôn thực hiện các câu lệnh của nó ít nhất một lần và tiếp tục thực hiện chúng cho đến khi điều kiện là sai. Lồng nhau là đặt một lệnh này vào trong một lệnh khác. C cho phép lồng ghép mọi lệnh của nó. Câu hỏi và trả lời 1. Làm thế nào để biết được nên dùng câu lệnh điều khiển chương trình for, while, hay do while? Nếu nhìn vào cấu trúc của ba câu lệnh này, thì có thể nói rằng cả ba đều có thể dùng để giải quyết một bài toán lặp. Tuy nhiên mỗi câu lệnh đều có nét riêng của nó. Câu lệnh for là tốt nhất nếu cần phải khởi tạo và tăng trong vòng lặp. Nếu chỉ có một điều kiện cần phải thỏa mãn và không quan tâm đến số lần thực hiện của vòng lặp thì while là một sự lựa chọn tốt. Còn nếu biết rằng một khối các câu lệnh cần phải thực hiện ít nhất một lần thì do while là cấu trúc tốt nhất. 2. Các vòng lặp có thể lồng nhau bao nhiêu lần? Có thể lồng bao nhiêu vòng lặp cũng được. Tuy nhiên, nếu chương trình cần từ ba vòng lặp trở lên thì nên thay bằng một hàm thì tốt hơn bởi vì dò theo tất cả các dấu ngoặc xem chừng khó hơn là đọc một hàm. 3. Có thể lồng các vòng lặp khác nhau không? Có thể lồng if, for, while, do while hoặc bất kỳ lệnh nào khác. Câu hỏi 1. Chỉ số của phần tử đầu tiên trong một mảng là mấy? 2. Sự khác nhau giữa một câu lệnh for và một câu lệnh while là gì?