Trong bài trước bạn đã biết thế nào là ngôn ngữ lập trình, và viết chương trình C đầu tiên (chương trình "Hello, World").Chắc chắn bạn có nhiều thắc mắc với chương trình đầu tiên đó, ví dụ: "tại sao cần có #include<stdio.h> ở đầu chương trình", hay "có phải dấu chấm phảy (;) cần thiết ở cuối mỗi lệnh", ... Tất cả những thắc mắc đó đều rất hay, và bạn sẽ có thể tự trả lời các câu hỏi đó trong bài này và các bài sắp tới. Thắc mắc, nghi vấn là điều hết sức cần thiết, nó tạo sự tò mò và sở thích khám phá cho bạn, hãy thắc mắc thật nhiều và tự mình khám phá ngôn ngữ lập trình C!.
1. Giải thích tạm thời về chỉ thị tiền xử lý #include:
Chỉ thị tiền xử lý (pre-processor directives) là các chỉ thị cho trình biên dịch C (gcc, ...) làm một việc gì đó trước khi biên dịch chương trình của bạn. Chúng được bắt đầu bởi dấu "#" (khác với các câu lệnh (statement), không được bắt đầu bởi dấu "#").
Chỉ thị tiền xử lý sẽ được đề cập kỹ hơn ở các bài sau. Ở đây, chúng ta chỉ tìm hiểu chỉ thị #include.
Chỉ thị
#include
là một chỉ thị tiền xử lý, nó báo cho trình biên dịch biết là cần phải gắn nội dung file được chỉ định ở chỉ thị #include vào chương trình hiện thời, tại điểm có chỉ thị #include. Nói cách khác, khi thấy chỉ thị này, trình biên dịch sẽ thay thế (replace) chỉ thị đó bằng nội dung file được định nghĩa trong chỉ thị, tức là câu lệnh chỉ thị sẽ bị xóa bỏ và thay vào đó là nội dung file. Ví dụ, chỉ thị #include<stdio.h> sẽ được thay thế bằng nội dung file stdio.h. File stdio.h là một file chuẩn của C, nó chứa các headers cho các lệnh vào ra (input / output) (viết tắt của standard input output chấm headers). Các header này thực ra là các lệnh khai báo hàm, biến cần thiết cho việc xuất nhập dữ liệu ra các thiết bị vào ra như màn hình, bàn phím, máy in, ... Trong chương trình "Hello, World", chúng ta đã hiển thị ra màn hình dòng chữ "Hello, World" (tức là output ra màn hình chữ "Hello, World") nên ta cần header file này. Bạn sẽ còn gặp nhiều file header khác trong các bài tới.
Ở phần ngay sau đây sẽ giải thích cụ thể về hàm, biến và bạn sẽ hiểu nội dung của các header file.
2.Biến số
Biến số (variable), như trong Toán học, là một ký hiệu đại diện cho một đại lượng biến thiên nào đó. Ví dụ, bạn vẫn quen thuộc với các biến số x, y, z trong các phương trình hàng ngày bạn thường viết.
Biến số trong ngôn ngữ lập trình cũng có ý nghĩa Toán học giống như vậy: chúng là đại diện cho một đại lượng biến thiên nào đó. Trong Toán học bạn thường ít chú ý đến kiểu của biến số (type of variables), nhưng thực ra chúng rất quan trọng. Kiểu của biến, xét về mặt Toán học, là tập hợp mà biến số ấy là thành viên. Ví dụ, biến x = 3 là thành viên của tập hợp số nguyên (Integer); biến y = 4.5 là thành viên của tập hợp số thực (Real). Tất nhiên bạn cũng có thể nói là biến x cũng là số thực, vì tập số thực bao hàm tập số nguyên. Một ví dụ mà bạn ít gặp hơn là biến kiểu chuỗi ký tự, ví dụ z = "Thủy điện Hòa Bình". Sau đây chúng ta sẽ tìm hiểu thế nào là kiểu của biến và tại sao kiểu của biến số lại quan trọng.
2.1. Kiểu của biến (type of variables):
Biến số (variable), như trong Toán học, là một ký hiệu đại diện cho một đại lượng biến thiên nào đó. Ví dụ, bạn vẫn quen thuộc với các biến số x, y, z trong các phương trình hàng ngày bạn thường viết.
Biến số trong ngôn ngữ lập trình cũng có ý nghĩa Toán học giống như vậy: chúng là đại diện cho một đại lượng biến thiên nào đó. Trong Toán học bạn thường ít chú ý đến kiểu của biến số (type of variables), nhưng thực ra chúng rất quan trọng. Kiểu của biến, xét về mặt Toán học, là tập hợp mà biến số ấy là thành viên. Ví dụ, biến x = 3 là thành viên của tập hợp số nguyên (Integer); biến y = 4.5 là thành viên của tập hợp số thực (Real). Tất nhiên bạn cũng có thể nói là biến x cũng là số thực, vì tập số thực bao hàm tập số nguyên. Một ví dụ mà bạn ít gặp hơn là biến kiểu chuỗi ký tự, ví dụ z = "Thủy điện Hòa Bình". Sau đây chúng ta sẽ tìm hiểu thế nào là kiểu của biến và tại sao kiểu của biến số lại quan trọng.
2.1. Kiểu của biến (type of variables):
Kiểu của biến số (nói tắt là kiểu biến, hay kiểu (type)) là tập xác định của biến đó. Kiểu của biến hết sức quan trọng trong việc kiểm tra lỗi của công thức.
Ví dụ, nếu bạn định nghĩa hàm số f(x) = x / 5, và bạn viết công thức f("Thủy điện Hòa Bình") thì trong đa số các trường hợp bạn sẽ làm cho người đọc khó hiểu vì họ không biết phải tính biểu thức "Thủy điện Hòa Bình" / 5 như thế nào. Nếu bạn viết f(15) thì người đọc bài Toán của bạn sẽ hiểu ngay là f(15) = 3.
Bạn hãy thử nghĩ xem tại sao lại như vậy?. Rất đơn giản, người đọc đã học Toán thông thường đều "ngầm hiểu" là phép tính chia (hay còn gọi là toán tử chia, division operator), chỉ được áp dụng đối với 2 số mà thôi. Vì thế, nếu bạn lấy biến x kiểu chuỗi (xâu) ký tự (string) thì người đọc sẽ không hiểu bạn định nói gì trong công thức đó, trừ khi bạn có định nghĩa rõ ràng, ví dụ "Toán tử chia cho số n ở đây là cắt chuỗi ký tự ra làm n phần bằng nhau và lấy phần đầu", khi đó người đọc sẽ hiểu f("Thủy điện Hòa Bình VN " ) = "Thủy".
Rất tiếc, ngôn ngữ lập trình C thường không cho phép "ngầm hiểu" như trên, nó yêu cầu bạn phải xác định rõ ràng kiểu của biến số của các phép tính mà bạn định thực hiện.
Các kiểu của biến số được định nghĩa sẵn trong ngôn ngữ lập trình C bao gồm: kiểu nguyên ("int"), kiểu số thực dấu phảy động (floating point number, "float"), kiểu số thực độ chính xác kép (double precision number, "double"), kiểu ký tự ("char"), ... và một số kiểu khác. Các kiểu được định nghĩa sẵn này gọi là primitive types.
Ngoài các kiểu được định nghĩa sẵn, ngôn ngữ C cho phép bạn tự định nghĩa các kiểu dữ liệu riêng của bạn, cũng như trong Toán học, bạn có thể tự định nghĩa tập xác định cho biến số của bạn.
2.2. Khai báo biến (variable declaration):
Ngôn ngữ C yêu cầu bạn phải khai báo biến trước khi dùng nó. Để khai báo biến, rất đơn giản, bạn chỉ cần khai báo kiểu của biến, ví dụ:
int x;
double y, z;
char my_character;
Hàm printf, như bạn đã dùng ở bài trước, là một hàm được khai báo trong file "stdio.h", để xuất dữ liệu ra màn hình. Dữ liệu có thể là chuỗi ký tự ("Hello, World"), biến kiểu nguyên (int), thực (float hay double), ... Để xuất biến kiểu nguyên, bạn cần chỉ cho hàm printf biết là bạn chuẩn bị xuất biến kiểu nguyên vào vị trí nào đó. Ví dụ:
int x;
double y;
float z;
x = 3;
y = 4.5;
z = 5.6f; // this is a float value, the value 5.6f means 5.6 in floating point
printf( "The value of x is: %d", x );printf( "The value of y is: %lf", y );printf( "The value of z is: %f", z );
Như bạn đã thấy, trong chuỗi ký tự mà bạn muốn hiển thị, có xuất hiện một số ký tự "kỳ dị", bắt đầu bằng dấu "%". Chúng cũng gần giống các pre-processor directives, chỉ có điều chúng chỉ thị cho hàm printf (chứ không phải chỉ thị cho trình biên dịch), là cần thay thế (replace) "%d" bằng biến kiểu nguyên (decimal) tương ứng ở phần sau. Tương tự, khi nhìn thấy "%f" thì cần thay thế bằng giá trị biến float tương ứng, "%lf" (long float) thì cần thay thế bởi giá trị của biến double tương ứng. Sự "tương ứng" ở đây được hiểu như sau: "dấu % thứ nhất tương ứng với biến đầu tiên ở ngay sau chuỗi, dấu % thứ 2 tương ứng với biến thứ 2, ...". Vì thế, câu lệnh sau đây sẽ hiển thị ra màn hình giá trị của cả 3 biến một lúc:
printf( "The values of x, y, z are: %d, %lf, %f", x, y, z );
Có vẻ bạn đã mệt mỏi với những quy tắc khai báo biến và quy tắc của hàm printf. Chúng ta hãy giải lao bằng một chương trình hoàn chỉnh sau (bạn có thể copy và paste vào editor, rồi compile và execute chương trình này, hãy để tên file là "printf_demo.c")
/*
File: printf_demo.c
The second program shows the declaration of variables and the usage of printf function
*/
#include<stdio.h> // stdio.h contains declaration of "printf", that's why we need to include it here!
int main(){
int x; double y; float z; x = 3; y = 4.5; z = 5.6f; // this is a float value, the value 5.6f means 5.6 in floating point printf( "The value of x is: %d", x ); printf( "The value of y is: %lf", y ); printf( "The value of z is: %f", z ); printf( "The values of x, y, z are: %d, %lf, %f", x, y, z ); return 0;}
Nếu bạn đã biên dịch và chạy thành công chương trình trên, bạn sẽ thấy các dòng bị in ra liền nhau. Đó là vì bạn chưa chỉ thị cho printf xuống dòng mỗi kết thúc một lần in. Muốn chỉ thị cho trình biên dịch C ký tự xuống dòng, bạn thêm ký hiệu đặc biệt "\n" (new line) vào chỗ cần xuống dòng:
printf( "The value of x is: %d\n", x );
Ký hiệu đặc biệt này (\n) chỉ là một trong số các ký hiệu đặc biệt, gọi là escape sequence, mà hàm ngôn ngữ C cho phép bạn chỉ thị. Các escape sequence đều bắt đầu bằng dấu back-slash (\). Khi gặp chuỗi "\n", trình biên dịch sẽ thay thế nó bằng ký tự xuống dòng.
Như vậy, trong hàm printf, bạn cần chú ý 1 loại ký tự đặc biệt là dấu %. Dấu % để chỉ thị cho hàm printf về in ra giá trị của các biến.
Bạn cũng cần chú ý 2 loại chỉ thị đặc biệt cho trình biên dịch C là dấu "#" và dấu "\". Dấu back-slash để chỉ thị về escape sequence, tức là bắt trình biên dịch thực hiện một số công việc đặc biệt, khác thường mà bạn không viết được trực tiếp trong chuỗi được (ví dụ, xuống dòng là 1 hành động đặc biệt, không viết vào chuỗi được (vì nếu viết xuống dòng, trình biên dịch sẽ tưởng là xuống dòng bình thường của chương trình), in ra chính bản thân dấu \ cũng là một hành động đặc biệt (vì bình thường dấu \ để chỉ hành động đặc biệt bắt đầu). Muốn in ra dấu \, bạn cần phải viết 2 dấu \ như sau: "\\", tức là printf( "The back-slash (\\) indicates an escape sequence" );, bạn sẽ có câu "The back-slash (\) indicates an escape sequence" hiển thị ra màn hình.
Sau đây là bảng tóm tắt các escape sequence:
\n Newline
\t Horizontal Tab
\v Vertical Tab
\r Carriage Return
\b Backspace
\f Form feed
\\ Backslash
\a Audible Alert (bell)
\? Question mark
\000 Oct - No one uses Octal unless they have an ICL background...
\' Single quote
\" Double quote
\xhh Hex number
\ Preprocessor line continuation, must be immediately followed
by a newline.
Sau đây là bảng tóm tắt các kiểu dữ liệu nguyên thủy (primitive types) của C:
Tên kiểu | Số byte bộ nhớ | Ký hiệu để in ở hàm printf | Max value | Min value | Ý nghĩa |
int | 4 | %d | 231 - 1 | -231 | Số nguyên |
short | 2 | %d | 215 - 1 | -215 | Số nguyên bé |
double | 8 | %lf | 1.7*10308 | 2.2*10-308 | Số thực độ chính xác kép |
float | 4 | %f | 3.4*1038 | 1.18*10-38 | Số thực độ chính xác đơn |
char | 1 | %c | 255 | 0 | Ký tự (số thứ tự của ký tự trong bảng ASCII) |
2.3. Ý nghĩa trừu tượng của biến:
Ở phần trên ạn đã biết một trong các ý nghĩa của biến số là "giống như ý nghĩa trong Toán học" (hãy xem lại xem nó là gì?), ở phần này chúng ta sẽ tìm hiểu ý nghĩa đầy đủ của biến số trong ngôn ngữ lập trình. Phần này áp dụng cho tất cả các ngôn ngữ lập trình, không chỉ là C.
Ý nghĩa trừu tượng của biến số: Biến số là sự trừu tượng hóa của bộ nhớ máy tính thành ký hiệu trong ngôn ngữ lập trình.
Ở đây, "sự trừu tượng hóa" có nghĩa là hành động làm cho một thực thể trở nên dễ hiểu hơn, dễ dùng hơn. Ví dụ, cái công tắc bật / tắt điện là sự trừu tượng hóa của điểm nối 2 đầu dây điện với nhau (bạn hoàn toàn có thể bật đèn bằng cách lấy kìm nối 2 đầu đây này với nhau, thay vì bật ON công tắc). Cũng như vậy, bạn có thể truy cập bộ nhớ máy tính và lưu vào vùng nhớ đó một giá trị nào đó (ví dụ số 2) thay vì đặt x = 2 trong chương trình.
Ngoài ý nghĩa làm cho thực thể trở nên dễ hiểu, dễ sử dụng hơn, trừu tượng hóa còn làm cho thực thể có các tính năng mới, các thuộc tính mới. Ví dụ, công tắc điện bảo vệ bạn khỏi bị điện giật (như khi dùng tay nối 2 đầu dây với nhau). Bạn hãy nghĩ xem, ví dụ tương ứng cho biến số mà tôi định nói ở đây là gì? (hãy xem lại phần ví dụ f("Thủy điện Hòa Bình ")).
Như vậy, biến số là khái niệm trung tâm của ngôn ngữ lập trình, vì nó là sự trừu tượng hóa của bộ nhớ máy tính. Vai trò của biến số đối với chương trình cũng như vai trò của bộ nhớ đối với máy tính vậy. Đến đây chắc chắn bạn sẽ đặt câu hỏi: máy tính có 2 thứ quan trọng là bộ nhớ (memory) và bộ vi xử lý (CPU), ngôn ngữ lập trình có biến số tương ứng với bộ nhớ, vậy khái niệm gì tương ứng với bộ vi xử lý của máy tính?. Câu hỏi đó sẽ phần nào được trả lời ở mục sau.
Hàm số, tất nhiên lại như trong Toán học (^_^), là sự biểu thị của sự ràng buộc giữa một đại lượng vào một hoặc nhiều các đại lượng khác. Ví dụ z = f(x, y) biểu diễn sự ràng buộc của biến số z vào 2 biến x và y. Hai biến x và y gọi là đối số (argument) của hàm.
Cũng giống như biến số, trong Toán học, bạn thường không để ý đến kiểu của hàm, nhưng thực ra nó cũng rất quan trọng. Ví dụ, khi bạn định nghĩa f(x) = "tên của nhà máy thủy điện lớn nhất của đất nước x", thì bạn sẽ gây khó hiểu khi viết f("Việt Nam") / 5, vì người đọc "ngầm hiểu" là toán tử chia chỉ áp dụng cho 2 số, trong khi giá trị trả về (return value) của hàm f có kiểu xâu ký tự ("Thủy điện Hòa Bình") (hay nói cách khác: kiểu trả về của hàm f là xâu ký tự, hay nói ngắn gọn hơn nữa: kiểu của hàm f là xâu ký tự). Sau đây chúng ta sẽ tìm hiểu cách khai báo hàm và kiểu trả về của hàm.
3.1. Cách khai báo hàm số (function declaration):
Trong ngôn ngữ lập trình C, bạn cần phải khai báo hàm trước khi sử dụng nó. Việc khai báo hàm rất đơn giản, bạn chỉ việc khai báo kiểu của hàm, tên hàm và danh sách đối số. Trong hầu hết các ngôn ngữ lập trình, thuật ngữ để gọi đối số không phải là "argument", mà là "parameter" (tham số). Vì thế, kể từ giờ, chúng ta cũng sẽ gọi đối số là tham số (thực ra ý nghĩa của đối số và tham số chỉ khác nhau ở tính biến thiên của giá trị biểu diễn bởi ký hiệu ấy, nhưng với hàm số, một khi bạn gọi hàm thì đối số đã được cố định nên cũng có ý nghĩa như tham số).
Sau đây là ví dụ khai báo một hàm số:
int add_two_numbers( int x, int y ){
int result;
result = x + y;
return result;
}
Bạn có thể dễ dàng nhận ra chúng ta đang khai báo hàm f(x, y) = x + y, trong đó x, y là 2 biến kiểu số nguyên. Như vậy chúng ta có nhận xét sau: vì 2 biến x và y là số nguyên, nên giá trị của hàm cũng sẽ là số nguyên (int). Vì thế, kiểu của hàm có thể đặt là số nguyên (int). Ta đặt tên dài cho hàm (add_two_numbers) để tránh nhầm lẫn và nêu bật ý nghĩa của hàm. Danh sách tham số được khai báo ngay sau tên hàm, và đặt trong ngoặc đơn:
add_two_numbers( int x, int y )
. Phần từ dấu mở ngoặc nhọn "{" trở đi gọi là phần thân hàm (body of function). Phép gán "add_two_numbers(x, y) = x + y" sẽ được thực hiện thông qua 2 bước. Ở bước 1, bạn tính giá trị của x + y và lưu vào biến số "result" (result = x + y;
), tất nhiên bạn phải khai báo biến số "result" trước khi dùng nó. Hơn nữa, bạn phải khai báo biến ở ngay đầu hàm, trước bất kỳ một câu lệnh nào (có một số trình biên dịch C cho phép khai báo biến ở bất cứ đâu, nhưng để an toàn bạn nên khai ở đầu hàm). Bước 2, và là bước rất quan trọng, đó là thực hiện câu lệnh "return result". Câu lệnh này có nghĩa là "gán cho hàm số "add_two_numbers" giá trị là giá trị của "result"" và ngay lập tức kết thúc hàm, không làm gì thêm nữa. Vì thế, nào bạn viết:int add_two_numbers( int x, int y ){
int result;
result = x + y;
return result;
printf( "Hello, World!\n" );
}
thì câu lệnh printf("Hello, World!\n") sẽ thừa, bởi nó không bao giờ được thực hiện, vì nó ở đằng sau lệnh return. Tóm lại, lệnh return có nghĩa là tuyên bố hàm "trả về giá trị" nào đó. Giá trị đó gọi là "return value" (giá trị trả về) của hàm. Tại sao lại là "trả về" (return), bạn sẽ hiểu ở phần tiếp theo.
3.2. Cách gọi hàm (function call):
Gọi hàm (function call) chính là việc bạn sử dụng hàm số mà bạn vừa định nghĩa. Ví dụ, trong Toán học, bạn định nghĩa hàm f(x) = x + 1, thì sau đó bạn có quyền viết y = f(5) và người đọc sẽ hiểu bạn muốn nói y = 6. Dòng y = f(5) thực ra là 2 lệnh riêng biệt: lệnh gọi hàm f ("f(5)") và lệnh gán giá trị trả về của hàm f cho biến số y ("y = 6").
Ngôn ngữ lập trình C cũng cho phép bạn làm như vậy, tức là khai báo hàm, sau đó gọi hàm.
Chương trình sau đây mô tả cách gọi hàm, hãy copy và paste vào editor để execute nó:
3.2. Cách gọi hàm (function call):
Gọi hàm (function call) chính là việc bạn sử dụng hàm số mà bạn vừa định nghĩa. Ví dụ, trong Toán học, bạn định nghĩa hàm f(x) = x + 1, thì sau đó bạn có quyền viết y = f(5) và người đọc sẽ hiểu bạn muốn nói y = 6. Dòng y = f(5) thực ra là 2 lệnh riêng biệt: lệnh gọi hàm f ("f(5)") và lệnh gán giá trị trả về của hàm f cho biến số y ("y = 6").
Ngôn ngữ lập trình C cũng cho phép bạn làm như vậy, tức là khai báo hàm, sau đó gọi hàm.
Chương trình sau đây mô tả cách gọi hàm, hãy copy và paste vào editor để execute nó:
/*
File: funcall_demo.c
The third program shows an example of function declaration and function calling
*/
#include<stdio.h> // stdio.h contains declaration of "printf", that's why we need to include it here!
int add_two_numbers( int x, int y ){
int result;
result = x + y;
return result;
}
int main(){
int retVal;
retVal = add_two_number( 5, 6 );
printf( "The value of retVal is: %d\n", retVal );
return 0;
}
Nếu mọi việc tốt đẹp, bạn sẽ thấy giá trị 11 được hiển thị ra màn hình. Thật tuyệt, bạn đã biết cách khai báo và sử hàm số rồi đấy!.
Đến đây bạn đã có thể hiểu câu lệnh "int main(){ ..." có nghĩa là gì. Quá đơn giản phải không, đó chẳng qua là một lời khai báo hàm số, tên hàm là "main" (một tên hàm đặc biệt được dùng trong C), và nó không có tham số nào cả (vì "main()" tức là danh sách tham số là 1 danh sách trống rỗng). Kiểu trả về của hàm main là kiểu số nguyên (int), vì thế ở cuối chương trình bạn thấy dòng "return 0;", tức là ta gán cho hàm main giá trị là 0.
3.3. Hàm số đặc biệt: main:
Trong C, có một tên hàm đặc biệt, đó là "main". Hàm main báo hiệu sự bắt đầu của chương trình của bạn. Có nghĩa là, các lệnh sẽ được thực hiện từ đầu hàm main trở đi. Vì thế, hàm main thường được gọi là entry point (điểm vào) của chương trình của bạn viết. Tôi phải nhấn mạnh từ "bạn viết" vì một lý do mà bạn không cần hiểu bây giờ (và có thể cả sau này, nếu bạn không nghiên môn cứu hệ điều hành hoặc viết trình biên dịch).
Như vậy, bố cục tổng quát của một chương trình C cho đến lúc này là:
Đến đây bạn đã có thể hiểu câu lệnh "int main(){ ..." có nghĩa là gì. Quá đơn giản phải không, đó chẳng qua là một lời khai báo hàm số, tên hàm là "main" (một tên hàm đặc biệt được dùng trong C), và nó không có tham số nào cả (vì "main()" tức là danh sách tham số là 1 danh sách trống rỗng). Kiểu trả về của hàm main là kiểu số nguyên (int), vì thế ở cuối chương trình bạn thấy dòng "return 0;", tức là ta gán cho hàm main giá trị là 0.
3.3. Hàm số đặc biệt: main:
Trong C, có một tên hàm đặc biệt, đó là "main". Hàm main báo hiệu sự bắt đầu của chương trình của bạn. Có nghĩa là, các lệnh sẽ được thực hiện từ đầu hàm main trở đi. Vì thế, hàm main thường được gọi là entry point (điểm vào) của chương trình của bạn viết. Tôi phải nhấn mạnh từ "bạn viết" vì một lý do mà bạn không cần hiểu bây giờ (và có thể cả sau này, nếu bạn không nghiên môn cứu hệ điều hành hoặc viết trình biên dịch).
Như vậy, bố cục tổng quát của một chương trình C cho đến lúc này là:
/*
The typical construct of a C program
Note: the text between /* and */ is comment (multiple-lines comment)
The text after // in a line is also comment! (single-line comment).
*/
// declare all include files here
#include<file1.h>
#include<file2.h>
// declare all functions, except the function "main" here
int add_two_numbers( int x, int y ){
int result;
result = x + y;
return result;
}
double my_function2( int my_param1 ){
...
}
void my_function2(){ // "void" is a special type, indicates that the function does NOT return any value
...
}
// and finally, declare the function "main" here!
int main(){
int retVal;
retVal = add_two_number( 5, 6 );
printf( "The value of retVal is: %d\n", retVal );
my_function2(); // a call to function my_function2, without any parameter, and does NOT return any value
return 0;
}
Có một kiểu hàm đặc biệt, gọi là kiểu "void" (không có gì, không kiểu) để chỉ những hàm không trả về giá trị gì. Ý nghĩa của việc gọi hàm đó nằm ở tác dụng phụ (side effect) của hàm, chẳng hạn hiển thị ra màn hình một câu "Hello, World" là một tác dụng phụ, nó làm trạng thái của màn hình bị thay đổi. Các hàm không kiểu ấy được gọi là "thủ tục" (procedure) ở một số ngôn ngữ lập trình khác (như Matlab hay Pascal).
3.4 Trình tự thực hiện lệnh của hàm:
Một hàm sẽ thực hiện các lệnh trong phần thân của hàm theo thứ tự từ trên xuống dưới (nếu không gặp phải các lệnh đặc biệt sẽ bàn đến ở bài sau). Khi gặp một lời gọi hàm, bộ xử lý sẽ chuyển quyền thực hiện đến đầu hàm được gọi. Sau khi hàm được gọi kết thúc (thực hiện lệnh return), bộ xử lý sẽ trả quyền điều khiển (quyền thực hiện lệnh) về cho hàm cũ (hàm đã gọi hàm kia). Đó chính là lý do tại sao câu lệnh gán giá trị cho hàm lại là "return" (trả về). Khi thực hiện đến hết hàm mà không gặp câu lệnh return (tức là gặp dấu } kết thúc hàm) thì bộ xử lý cũng sẽ chuyển quyền điều khiển về cho hàm cũ. Hàm cũ đó sẽ thực hiện lệnh tiếp theo lệnh gọi hàm vừa thực hiện xong. Mô hình này được mô tả trên hình vẽ sau:
Một hàm sẽ thực hiện các lệnh trong phần thân của hàm theo thứ tự từ trên xuống dưới (nếu không gặp phải các lệnh đặc biệt sẽ bàn đến ở bài sau). Khi gặp một lời gọi hàm, bộ xử lý sẽ chuyển quyền thực hiện đến đầu hàm được gọi. Sau khi hàm được gọi kết thúc (thực hiện lệnh return), bộ xử lý sẽ trả quyền điều khiển (quyền thực hiện lệnh) về cho hàm cũ (hàm đã gọi hàm kia). Đó chính là lý do tại sao câu lệnh gán giá trị cho hàm lại là "return" (trả về). Khi thực hiện đến hết hàm mà không gặp câu lệnh return (tức là gặp dấu } kết thúc hàm) thì bộ xử lý cũng sẽ chuyển quyền điều khiển về cho hàm cũ. Hàm cũ đó sẽ thực hiện lệnh tiếp theo lệnh gọi hàm vừa thực hiện xong. Mô hình này được mô tả trên hình vẽ sau:
3.5. Ý nghĩa trừu tượng của hàm số:
Đến đây chúng ta sẽ tìm hiểu ý nghĩa của hàm số. Chắc bạn đã đoán ra, trong ngôn ngữ lập trình, hàm số là sự trừu tượng hóa của tập hợp các lệnh mà bộ vi xử lý thực hiện. Nghĩa là, hàm số là sự trừu tượng hóa của một quá trình tính toán (computational process). Trong máy tính, bộ vi xử lý là nơi thực hiện quá trình tính toán đó, có nghĩa là nếu bạn muốn ra lệnh cho bộ vi xử lý thực hiện một quá trình tính toán, thì bạn khai báo quá trình tính toán đó trong phần thân hàm và khi gọi hàm, máy tính sẽ thực hiện quá trình tính toán của bạn.
Vậy, hàm số có thể nói là sự trừu tượng hóa của bộ vi xử lý trong ngôn ngữ lập trình.
Cùng với biến số, hàm số là một khái niệm cơ bản của ngôn ngữ lập trình. Ý nghĩa thực tiễn của nó là: giúp bạn chia nhỏ quá trình tính toán ra thành từng module, sau đó có thể dùng module đó nhiều lần. Ví dụ bạn có thể gọi hàm add_two_numbers bất kỳ khi nào bận cần cộng hai số nguyên. Vì thế, hàm số còn được gọi là sub-routine, sub-program.
Bạn nên phân chia các phần tính toán cần dùng nhiều lần ra thành hàm số, sau đó chỉ việc gọi hàm khi dùng nó.
Các toán tử (operator) thực chất cũng biểu diễn một quá trình tính toán (nho nhỏ), vì thế, các toán tử cũng có thể coi là hàm số. Khi đó, các toán hạng (operands) ứng với toán tử đó có thể coi là tham số của hàm toán tử. Rất tiếc, ngôn ngữ lập trình C không cho phép tự định nghĩa toán tử (như định nghĩa hàm), nhưng nhiều ngôn ngữ lập trình (như C++, C#, CBDL, ...) cho phép định nghĩa toán tử riêng của bạn (ví dụ, định nghĩa kiểu dữ liệu mới ComplexNumber và định nghĩa toán tử cộng (+) cho kiểu dữ liệu đó).
Đến đây chúng ta sẽ tìm hiểu ý nghĩa của hàm số. Chắc bạn đã đoán ra, trong ngôn ngữ lập trình, hàm số là sự trừu tượng hóa của tập hợp các lệnh mà bộ vi xử lý thực hiện. Nghĩa là, hàm số là sự trừu tượng hóa của một quá trình tính toán (computational process). Trong máy tính, bộ vi xử lý là nơi thực hiện quá trình tính toán đó, có nghĩa là nếu bạn muốn ra lệnh cho bộ vi xử lý thực hiện một quá trình tính toán, thì bạn khai báo quá trình tính toán đó trong phần thân hàm và khi gọi hàm, máy tính sẽ thực hiện quá trình tính toán của bạn.
Vậy, hàm số có thể nói là sự trừu tượng hóa của bộ vi xử lý trong ngôn ngữ lập trình.
Cùng với biến số, hàm số là một khái niệm cơ bản của ngôn ngữ lập trình. Ý nghĩa thực tiễn của nó là: giúp bạn chia nhỏ quá trình tính toán ra thành từng module, sau đó có thể dùng module đó nhiều lần. Ví dụ bạn có thể gọi hàm add_two_numbers bất kỳ khi nào bận cần cộng hai số nguyên. Vì thế, hàm số còn được gọi là sub-routine, sub-program.
Bạn nên phân chia các phần tính toán cần dùng nhiều lần ra thành hàm số, sau đó chỉ việc gọi hàm khi dùng nó.
Các toán tử (operator) thực chất cũng biểu diễn một quá trình tính toán (nho nhỏ), vì thế, các toán tử cũng có thể coi là hàm số. Khi đó, các toán hạng (operands) ứng với toán tử đó có thể coi là tham số của hàm toán tử. Rất tiếc, ngôn ngữ lập trình C không cho phép tự định nghĩa toán tử (như định nghĩa hàm), nhưng nhiều ngôn ngữ lập trình (như C++, C#, CBDL, ...) cho phép định nghĩa toán tử riêng của bạn (ví dụ, định nghĩa kiểu dữ liệu mới ComplexNumber và định nghĩa toán tử cộng (+) cho kiểu dữ liệu đó).
3.6. Thư viện (library):
Thư viện (library) là một tập hợp các hàm, các biến đã được khai báo sẵn, bạn chỉ việc dùng nó trong chương trình. Trước khi muốn dùng chúng, bạn cần include phần khai báo đó vào chương trình bằng chỉ thị tiền xử lý #include.
Thư viện mặc định cho nhiều hàm số của C là có tên là "libc" (library for C language). Ví dụ, hàm số printf mà bạn hay dùng là một hàm số được khai báo trong file "stdio.h", và có phần thân được implement trong thư viện libc.
Thư viện là tập hợp các hàm số nên nó cũng giúp ta tái sử dụng mã (đỡ phải viết lại) và giúp module hóa chương trình.
Thư viện (library) là một tập hợp các hàm, các biến đã được khai báo sẵn, bạn chỉ việc dùng nó trong chương trình. Trước khi muốn dùng chúng, bạn cần include phần khai báo đó vào chương trình bằng chỉ thị tiền xử lý #include.
Thư viện mặc định cho nhiều hàm số của C là có tên là "libc" (library for C language). Ví dụ, hàm số printf mà bạn hay dùng là một hàm số được khai báo trong file "stdio.h", và có phần thân được implement trong thư viện libc.
Thư viện là tập hợp các hàm số nên nó cũng giúp ta tái sử dụng mã (đỡ phải viết lại) và giúp module hóa chương trình.
(bài này còn tiếp: biểu thức, toán tử)
Câu hỏi ôn tập:
1. Thế nào là biến số. Ý nghĩa toán học và ý nghĩa trừu tượng của biến số?.
2. Có những kiểu biến nào?
3. Thế nào là chỉ thị tiền xử lý?. Khi bạn dùng chỉ thị tiền xử lý, bạn đang ra lệnh cho ai (trình biên dịch hay ra lệnh trực tiếp cho máy tính qua chương trình của bạn).
4. Hàm printf là hàm của thư viện nào, nó được khai báo ở đâu?.
5. Viết chương trình khai báo đủ các kiểu biến số đã học, gán cho chúng một số giá trị nào đó và dùng hàm printf để in chúng ra màn hình.
(gợi ý: giá trị kiểu char có thể biểu diễn bởi dấu nháy đơn: char ch; ch = 'A'; printf( "%c\n", ch ); sẽ in ra chữ 'A').
6. Thế nào là escape sequence?. Vì sao muốn viết dấu backslash lại phải dùng escape sequence?
7. Khi bạn ra lệnh cho hàm printf (PRINT Formatted), bạn dùng các dấu "%" để biểu diễn format của dữ liệu (tức kiểu và thứ tự của biến). Hơn nữa, nếu muốn xuống dòng bạn dùng "\n". Trả lời các câu hỏi sau đây:
a) Khi bạn viết dấu % trong hàm printf, bạn đang ra lệnh cho ai (cho hàm printf hay cho trình biên dịch?)
b) Khi bạn viết \n (hay một escape sequence nào đó), bạn đang ra lệnh cho ai (cho hàm printf hay cho trình biên dịch?)
c)(*) Nêu ý nghĩa của sự khác nhau này?.
(dấu (*) thể hiện câu hỏi khó, (**): rất khó, (***) cực kỳ khó, (****) research topic)
8. Hàm số là gì? Ý nghĩa trừu tượng của nó?
9. Ý nghĩa thực tiễn của hàm số là gì?
10. Hãy định nghĩa hàm số minus_two_numbers để trừ 2 số thực (chính xác kép) x và y và viết chương trình demo lời gọi hàm đó.
11. Trong nhiều chương trình, bạn cần tráo (exchange) giá trị của 2 biến. Ví dụ a = 5, b = 6, sau khi tráo thì a = 6 và b = 5.
Muốn thế, ta cần thêm 1 biến trung gian, ví dụ là c, và làm như sau: c = a; a = b; b = c;. Hãy viết chương trình khai báo 3 biến x, y, z kiểu double, sau đó đặt cho x = 5, y = 6. Hiển thị giá trị của chúng lên màn hình. Tiếp theo, hãy tráo 2 biến x và y nhờ biến trung gian z. Cuối cùng, lại hiển thị giá trị của 2 biến đó lên màn hình xem có đúng là chúng được tráo không?.
12. Hãy viết 2 hàm số: minus_two_int_numbers (kiểu int) và add_two_int_numbers (kiểu int) để trừ và cộng 2 số nguyên. Sau đó, hãy viết chương trình sau: khai báo 2 biến x và y kiểu int. Gán cho chúng giá trị là x = 5, y = 6. Hiển thị chúng ra màn hình. Sau đó thực hiện các lệnh sau:
x = add_two_int_numbers( x, y );
y = minus_two_int_number( x, y );
x = minus_two_int_number(x, y );
Cuối cùng, hiển thị chúng ra màn hình một lần nữa.
(*) Nhận xét về ý nghĩa của 3 dòng lệnh trên và bàn về ưu điểm, nhược điểm của chúng?
13. Trong Linux, lệnh "man" (gõ vào shell, terminal) có nghĩa là "manual", tức là nó sẽ hướng dẫn bạn khi bạn cần.
a) Hãy gõ thử "man 3 printf" để xem hướng dẫn hàm printf.
b) Hãy chỉ ra lời khai báo của hàm printf như thế nào?.
c) (*) Hãy thử giải thích lời khai báo hàm printf
d) Hãy tìm hiểu kỹ về lệnh man bằng cách xem hướng dẫn về chính nó (tức là gõ vào shell là "man man"). Hãy thử gõ "man printf" (không có số 3 như ở trên), giải thích ý nghĩa của số 3 ở lệnh trong câu (a).
1. Thế nào là biến số. Ý nghĩa toán học và ý nghĩa trừu tượng của biến số?.
2. Có những kiểu biến nào?
3. Thế nào là chỉ thị tiền xử lý?. Khi bạn dùng chỉ thị tiền xử lý, bạn đang ra lệnh cho ai (trình biên dịch hay ra lệnh trực tiếp cho máy tính qua chương trình của bạn).
4. Hàm printf là hàm của thư viện nào, nó được khai báo ở đâu?.
5. Viết chương trình khai báo đủ các kiểu biến số đã học, gán cho chúng một số giá trị nào đó và dùng hàm printf để in chúng ra màn hình.
(gợi ý: giá trị kiểu char có thể biểu diễn bởi dấu nháy đơn: char ch; ch = 'A'; printf( "%c\n", ch ); sẽ in ra chữ 'A').
6. Thế nào là escape sequence?. Vì sao muốn viết dấu backslash lại phải dùng escape sequence?
7. Khi bạn ra lệnh cho hàm printf (PRINT Formatted), bạn dùng các dấu "%" để biểu diễn format của dữ liệu (tức kiểu và thứ tự của biến). Hơn nữa, nếu muốn xuống dòng bạn dùng "\n". Trả lời các câu hỏi sau đây:
a) Khi bạn viết dấu % trong hàm printf, bạn đang ra lệnh cho ai (cho hàm printf hay cho trình biên dịch?)
b) Khi bạn viết \n (hay một escape sequence nào đó), bạn đang ra lệnh cho ai (cho hàm printf hay cho trình biên dịch?)
c)(*) Nêu ý nghĩa của sự khác nhau này?.
(dấu (*) thể hiện câu hỏi khó, (**): rất khó, (***) cực kỳ khó, (****) research topic)
8. Hàm số là gì? Ý nghĩa trừu tượng của nó?
9. Ý nghĩa thực tiễn của hàm số là gì?
10. Hãy định nghĩa hàm số minus_two_numbers để trừ 2 số thực (chính xác kép) x và y và viết chương trình demo lời gọi hàm đó.
11. Trong nhiều chương trình, bạn cần tráo (exchange) giá trị của 2 biến. Ví dụ a = 5, b = 6, sau khi tráo thì a = 6 và b = 5.
Muốn thế, ta cần thêm 1 biến trung gian, ví dụ là c, và làm như sau: c = a; a = b; b = c;. Hãy viết chương trình khai báo 3 biến x, y, z kiểu double, sau đó đặt cho x = 5, y = 6. Hiển thị giá trị của chúng lên màn hình. Tiếp theo, hãy tráo 2 biến x và y nhờ biến trung gian z. Cuối cùng, lại hiển thị giá trị của 2 biến đó lên màn hình xem có đúng là chúng được tráo không?.
12. Hãy viết 2 hàm số: minus_two_int_numbers (kiểu int) và add_two_int_numbers (kiểu int) để trừ và cộng 2 số nguyên. Sau đó, hãy viết chương trình sau: khai báo 2 biến x và y kiểu int. Gán cho chúng giá trị là x = 5, y = 6. Hiển thị chúng ra màn hình. Sau đó thực hiện các lệnh sau:
x = add_two_int_numbers( x, y );
y = minus_two_int_number( x, y );
x = minus_two_int_number(x, y );
Cuối cùng, hiển thị chúng ra màn hình một lần nữa.
(*) Nhận xét về ý nghĩa của 3 dòng lệnh trên và bàn về ưu điểm, nhược điểm của chúng?
13. Trong Linux, lệnh "man" (gõ vào shell, terminal) có nghĩa là "manual", tức là nó sẽ hướng dẫn bạn khi bạn cần.
a) Hãy gõ thử "man 3 printf" để xem hướng dẫn hàm printf.
b) Hãy chỉ ra lời khai báo của hàm printf như thế nào?.
c) (*) Hãy thử giải thích lời khai báo hàm printf
d) Hãy tìm hiểu kỹ về lệnh man bằng cách xem hướng dẫn về chính nó (tức là gõ vào shell là "man man"). Hãy thử gõ "man printf" (không có số 3 như ở trên), giải thích ý nghĩa của số 3 ở lệnh trong câu (a).
Nội Quy Khi Gửi Bình Luận: