1. Ngôn ngữ C là gì?
Ngôn ngữ C là một ngôn ngữ lập trình máy tính được phát triển bởi Dennis Ritchie vào những năm 1970 tại Bell Labs. C là một ngôn ngữ lập trình mạnh mẽ, có hiệu suất cao, và được sử dụng rộng rãi trong phát triển phần mềm hệ thống, phần mềm nhúng, ứng dụng máy tính cá nhân, và nhiều lĩnh vực công nghiệp khác.
C được chú trọng vào việc quản lý bộ nhớ và cung cấp nhiều tính năng gần gũi với cấu trúc máy tính, giúp lập trình viên có kiểm soát chi tiết hơn về cách dữ liệu và mã máy được xử lý. Nó cũng có một cú pháp đơn giản và mạnh mẽ, giúp trong việc phát triển phần mềm hiệu quả và dễ bảo trì.
Ngôn ngữ C đã tạo nền tảng cho nhiều ngôn ngữ lập trình khác, bao gồm C++, C#, và nhiều ngôn ngữ khác. Ngoài ra, nó cũng là ngôn ngữ phổ biến cho việc viết hệ điều hành và phần mềm nhúng do khả năng kiểm soát phần cứng của nó.
2. Ứng dụng của ngôn ngữ C
Ngôn ngữ C có nhiều ứng dụng khác nhau trong nhiều lĩnh vực khác nhau do tính linh hoạt, hiệu suất cao và khả năng tương tác gần gũi với cấu trúc máy tính. Dưới đây là một số ứng dụng phổ biến của ngôn ngữ lập trình C:
- Hệ điều hành: C được sử dụng rộng rãi trong việc phát triển hệ điều hành. Hầu hết những hệ điều hành phổ biến hiện nay đều được viết bằng C như UNIX, Linux, Windows hay MacOS.
- Phát triển phần mềm hệ thống: C là một ngôn ngữ lý tưởng để phát triển phần mềm hệ thống, chẳng hạn như trình quản lý cơ sở dữ liệu (SQLite, MySQL, Berkeley DB), trình quản lý tập tin (ext4 trên Linux và NTFS trên Windows), và các thành phần quan trọng của hệ thống máy tính.
- Phát triển ứng dụng nhúng: C thường được sử dụng trong việc phát triển phần mềm nhúng, tức là phần mềm chạy trên các thiết bị như điện thoại di động, thiết bị y tế, thiết bị điều khiển công nghiệp, hệ điều khiển động cơ, trò chơi điện tử và nhiều ứng dụng khác. Ví dụ các hệ máy chơi game như PlayStation và Xbox, hay hệ điều hành Android / iOS, đều được viết bằng C.
- Phát triển ứng dụng máy tính cá nhân: C vẫn được sử dụng để phát triển ứng dụng máy tính cá nhân, đặc biệt là trong các lĩnh vực đòi hỏi hiệu suất cao như các trò chơi máy tính và phần mềm đồ họa. Ví dụ như các tựa game trên steam được viết bằng Unity và Unreal Engine, phần lõi của các phần mềm này đều được viết bằng C.
- Phân tích số liệu và tính toán khoa học: C thường được sử dụng trong các ứng dụng liên quan đến tính toán khoa học và phân tích số liệu. Ví dụ các thư viện sử dụng trong lĩnh vực khoa học dữ liệu và máy học (machine learning) như OpenCV và TensorFlow cung cấp API cho C/C++ để phát triển các ứng dụng trong lĩnh vực này.
- Viết thư viện và framework: C thường được sử dụng để viết thư viện và framework mà các lập trình viên có thể sử dụng để phát triển ứng dụng cho nhiều mục đích khác nhau. Một số thư viện được viết bằng C như OpenGL, OpenSSL, GTK+ (GIMP Toolkit),...
3. Tại sao cần phải học C?
- Nền tảng kiến thức: C là một trong những ngôn ngữ lập trình cơ bản và cổ điển nhất. Hiểu rõ về C sẽ giúp bạn nắm vững các khái niệm cơ bản trong lập trình, cú pháp, và quản lý bộ nhớ, điều này sẽ giúp bạn dễ dàng học và làm việc với các ngôn ngữ khác. Vì C là nền tảng của mọi ngôn ngữ, các ngôn ngữ lập trình khác đều được xây dựng dựa trên C.
- Hiệu suất cao: C là một ngôn ngữ hiệu suất cao, cho phép bạn kiểm soát trực tiếp tài nguyên máy tính. Điều này rất quan trọng khi bạn cần phát triển ứng dụng yêu cầu xử lý nhanh hoặc đòi hỏi quản lý tài nguyên hiệu quả, chẳng hạn như hệ điều hành, phần mềm nhúng, hoặc ứng dụng đòi hỏi hiệu năng cao.
- Học cú pháp cấu trúc: Ngôn ngữ C dạy bạn cách sử dụng cú pháp cấu trúc, một kỹ thuật lập trình quan trọng. Điều này giúp bạn viết mã sạch sẽ, dễ đọc và bảo trì.
- Phát triển hệ điều hành và phần mềm hệ thống: C là ngôn ngữ phù hợp để phát triển hệ điều hành và các phần mềm hệ thống quan trọng. Nếu bạn quan tâm đến lĩnh vực này, học C là cần thiết.
- Hiểu cách hoạt động của máy tính: C cho phép bạn hiểu rõ hơn về cách máy tính hoạt động. Điều này sẽ giúp bạn hiểu rõ luồng hoạt động của mọi dòng code bạn viết ra và từ đấy, trở thành một lập trình viên giỏi hơn. Các lập trình viên nằm ở TOP đều phải nắm rõ cách mọi thứ vận hành.
- Giải quyết vấn đề: Học C giúp bạn phát triển kỹ năng tư duy lập trình và giải quyết vấn đề. Giải quyết nhiều bài toán khác nhau với C giúp bạn tiến tới giải được những bài toán thực tế có độ khó lớn hơn rất nhiều.
4. Ngôn ngữ C hoạt động như thế nào?
Quá trình biên dịch trong C chuyển đổi mã mà con người có thể đọc thành định dạng mà máy có thể đọc được. Đối với C, việc này xảy ra trước khi chương trình bắt đầu thực thi để kiểm tra cú pháp và ngữ nghĩa của mã. Quá trình biên dịch trong C bao gồm 4 bước: tiền xử lý (pre-processing), biên dịch (compiling), tập hợp (assembling) và liên kết (linking), sau đó chúng ta chạy file thực thi thu được để xuất ra màn hình kết quả.
4.1 Biên dịch là gì?
Giả sử có 2 người đang nói chuyện với nhau, anh A đến từ Úc nói tiếng Anh còn anh B đến từ Việt Nam. Để 2 anh này có thể hiểu nhau nói gì thì một trong hai người phải biết ngôn ngữ của người kia, ví dụ anh A sử dụng Google Dịch để dịch tiếng Anh sang tiếng Việt, quá trình dịch này có thể hiểu là quá trình biên dịch.
Đối với máy tính cũng vậy, bản thân máy tính chỉ hiểu các kí tự 01010101000, hay còn gọi là mã máy (Machine Code). Chính vì thế cần có trình biên dịch đứng ở giữa giúp chuyển đổi những dòng code ta viết ra ở dạng con người hiểu (ABC) sang dạng nhị phân (000 0000) để máy tính hiểu, các bạn có thể tìm hiểu thêm về ASCII để hiểu rõ hơn.
4.2 Quá trình biên dịch trong C
Quá trình biên dịch trong C diễn ra qua 4 bước: tiền xử lý (pre-processing), biên dịch (compiling), tập hợp (assembling) và liên kết (linking). Giờ chúng ta sẽ cùng nhau phân tích từng bước thông qua những ví dụ cụ thể nhé.
4.2.1 Pre-processing (tiền xử lý)
Tiền xử lý trong C là bước đầu tiên của quá trình biên dịch để thực hiện các thay đổi hoặc xử lý trước khi mã nguồn được biên dịch thành mã máy. Tiền xử lý trong C đầu tiên sẽ loại bỏ các dòng comment, sau đó sử dụng các hằng số (#define), macro (#define), và các chỉ thị (#include) để thực hiện các nhiệm vụ như định nghĩa hằng số, tạo các phiên bản mã, và nhập các tệp mã nguồn khác vào tệp mã nguồn hiện tại.
Comments Removal (Loại bỏ comment)
Các dòng comment trong chương trình C sẽ được loại bỏ trong bước tiền xử lý, do nó chỉ có chức năng là giải thích đoạn code chứ không được sử dụng khi biên dịch.
Macros Expansion (Định nghĩa hằng số)
Việc định nghĩa sẵn các hằng số ở đầu chương trình giúp tạo ra một danh mục dễ đọc về các giá trị hằng số được sử dụng trong chương trình. Điều này làm cho mã nguồn dễ hiểu hơn, giúp lập trình viên và đồng nghiệp khác dễ dàng theo dõi và hiểu mã nguồn của bạn.
#define PI 3.14159265
#define MAX_SIZE 100
Ở ví dụ này, #define
được sử dụng để định nghĩa hằng số PI và MAX_SIZE. Khi tiền xử lý thực hiện, mọi sự xuất hiện của PI và MAX_SIZE trong mã nguồn sẽ được thay thế bằng giá trị tương ứng.
File inclusion (Nhập mã nguồn từ thư viện)
Để sử dụng được các hàm như printf
hay scanf
, ta cần sử dụng mã nguồn từ các thư viện bên ngoài. Thư viện điển hình mà chúng ta hay sử dụng trong C là thư viện stdio.h
. Để sử dụng thư viện này trong mã nguồn của chúng ta, ta viết như sau:
#include <stdio.h>
4.2.2 Compiling (Biên dịch)
Giai đoạn Compilation (Biên dịch) trong quá trình biên dịch mã nguồn C là bước quan trọng để chuyển mã nguồn C của bạn thành mã máy hoặc mã nguồn trung gian. Trong giai đoạn này, trình biên dịch (compiler) sẽ phân tích mã nguồn C và tạo ra mã máy hoặc mã nguồn trung gian. Dưới đây là một ví dụ cụ thể:
Giả sử bạn có một tệp mã nguồn C đơn giản như sau (ví dụ "hello.c"):
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
Bây giờ, để biên dịch tệp này, bạn sử dụng trình biên dịch C, chẳng hạn như GCC trên hệ thống Unix/Linux. Sử dụng lệnh sau:
gcc -o hello hello.c
Trong lệnh trên:
gcc
là trình biên dịch C.-o hello
chỉ định tên của tệp thực thi đầu ra sẽ là "hello".hello.c
là tệp mã nguồn C bạn muốn biên dịch.
Khi bạn chạy lệnh trên, trình biên dịch sẽ thực hiện giai đoạn Compilation. Trong giai đoạn này, nó thực hiện các nhiệm vụ sau:
- Phân tích cú pháp (Parsing): Trình biên dịch sẽ đọc mã nguồn C từ tệp "hello.c" và phân tích cú pháp của mã nguồn để hiểu cấu trúc và ngữ pháp của chương trình.
- Kiểm tra lỗi (Error Checking): Trình biên dịch kiểm tra chương trình để tìm lỗi cú pháp và lỗi logic. Nếu có lỗi, trình biên dịch sẽ tạo thông báo lỗi.
- Tạo mã nguồn trung gian (Intermediate Code): Trình biên dịch tạo mã trung gian (intermediate code) dựa trên mã nguồn C của bạn. Mã trung gian thể hiện các hướng dẫn trung gian cho máy tính về cách thực hiện chương trình.
- Tối ưu hóa (Optimization): Trong giai đoạn này, trình biên dịch có thể thực hiện tối ưu hóa để cải thiện hiệu suất của chương trình. Các tối ưu hóa bao gồm loại bỏ mã không sử dụng, tối ưu hóa động và tĩnh, và nhiều tối ưu hóa khác.
- Tạo tệp đối tượng (Object File): Cuối cùng, trình biên dịch tạo ra một tệp đối tượng (object file) hoặc tệp mã máy dựa trên mã trung gian. Tệp đối tượng chứa mã máy hoặc mã nguồn trung gian đã tối ưu hóa.
Sau giai đoạn Compilation, bạn có thể có một tệp đối tượng (với đuôi .o
) hoặc tệp thực thi (với tên "hello" trong ví dụ này) tùy thuộc vào cách bạn đã thiết lập trình biên dịch.
Lưu ý rằng một số trình biên dịch có thể tạo mã nguồn trung gian (intermediate code) thay vì mã máy, và mã nguồn trung gian này có thể được thực thi trong một máy ảo hoặc môi trường chạy giả lập (sandbox).
4.2.3 Assembly (Sử dụng mã hoá)
Assembly (Sử dụng mã hóa) là bước quan trọng sau khi mã nguồn C đã được biên dịch thành mã nguồn trung gian hoặc mã nguồn tương tự C. Trong giai đoạn này, mã nguồn trung gian hoặc mã nguồn tương tự C sẽ được chuyển đổi thành mã hợp ngữ (assembly code). Mã hợp ngữ là một dạng gần gũi với mã máy nhưng được viết bằng ngôn ngữ hợp ngữ, dễ đọc hơn và dễ hiểu hơn cho con người.
Giả sử bạn đã biên dịch tệp mã nguồn C "hello.c" thành tệp đối tượng "hello.o" sử dụng trình biên dịch GCC như sau:
gcc -o hello hello.c
Sau đó, bạn có thể sử dụng trình liên kết để tạo tệp thực thi "hello" bằng cách sử dụng lệnh:
gcc -o hello hello.o
Tại giai đoạn liên kết, mã hợp ngữ được tạo ra bởi trình biên dịch. Để xem mã hợp ngữ, bạn có thể sử dụng các tùy chọn đối với trình biên dịch hoặc trình liên kết để in ra mã hợp ngữ. Ví dụ, bạn có thể sử dụng tùy chọn -S
với GCC để tạo tệp mã hợp ngữ:
gcc -S -o hello hello.c
Sau đó, một tệp mã hợp ngữ "hello.s" sẽ được tạo ra, và bạn có thể xem nội dung của tệp này bằng trình soạn thảo hoặc trình biên tập mã nguồn.
Ví dụ nội dung tệp mã hợp ngữ "hello.s" có thể trông giống như sau:
.section .data
hello:
.string "Hello, World!\n"
.section .text
.globl _start
_start:
movl $4, %eax
movl $1, %ebx
movl $hello, %ecx
movl $13, %edx
int $0x80
movl $1, %eax
int $0x80
Mã hợp ngữ trên thể hiện mã máy tương ứng cho chương trình "Hello, World!" đơn giản. Nó sử dụng các chỉ thị gán giá trị, di chuyển dữ liệu và gọi hàm hệ thống để in ra thông báo "Hello, World!" trên màn hình.
Sau giai đoạn Assembly, mã hợp ngữ này sẽ được chuyển đổi thành mã máy thực thi trên máy tính của bạn.
4.2.4 Linking (Liên kết)
Linking (Liên kết) là bước quan trọng sau khi mã nguồn đã được biên dịch và có thể thực hiện riêng lẻ. Trong giai đoạn này, các tệp đối tượng và thư viện được kết hợp để tạo một tệp thực thi (executable file) hoàn chỉnh. Giai đoạn này cũng giải quyết các tham chiếu đến các hàm và biến từ các thư viện hoặc tệp đối tượng khác.
Giả sử bạn đã biên dịch hai tệp mã nguồn C thành hai tệp đối tượng "file1.o" và "file2.o" như sau:
gcc -c -o file1.o file1.c
gcc -c -o file2.o file2.c
Sau đó, bạn muốn liên kết hai tệp đối tượng này để tạo một tệp thực thi "myprogram". Bạn có thể sử dụng lệnh sau:
gcc -o myprogram file1.o file2.o
Trong ví dụ này:
gcc
là trình liên kết (linker).-o myprogram
chỉ định tên của tệp thực thi đầu ra sẽ là "myprogram".file1.o
vàfile2.o
là các tệp đối tượng cần được liên kết lại với nhau.
Khi bạn chạy lệnh trên, trình liên kết sẽ thực hiện các công việc sau:
- Giải quyết các tham chiếu: Trình liên kết kiểm tra xem các tham chiếu đến hàm và biến trong
file1.o
vàfile2.o
có thể được giải quyết bằng cách sử dụng mã máy từ các thư viện hoặc tệp đối tượng khác. Nếu có tham chiếu không được giải quyết, trình liên kết sẽ tạo lỗi liên kết. - Kết hợp mã máy: Trình liên kết sẽ kết hợp mã máy từ
file1.o
vàfile2.o
để tạo một tệp thực thi hoàn chỉnh. Điều này bao gồm gắn các đoạn mã máy từ các tệp đối tượng vào một chương trình duy nhất. - Xác định điểm bắt đầu: Trình liên kết cũng xác định điểm bắt đầu của chương trình (entry point). Thông thường, điểm bắt đầu là hàm
main
.
Kết quả cuối cùng là một tệp thực thi "myprogram" mà bạn có thể chạy để thực hiện chương trình. Tệp thực thi này bao gồm mã máy đã được kết hợp từ các tệp đối tượng và thư viện, và nó thực hiện toàn bộ ứng dụng C của bạn.
Xem biểu đồ bên dưới để thấy trình tự diễn ra của quy trình biên dịch này
5. Các khái niệm quan trọng trong ngôn ngữ C
5.1 Biến (Variable)
Biến trong ngôn ngữ lập trình C là một định danh được sử dụng để lưu trữ giá trị dữ liệu. Mỗi biến có một kiểu dữ liệu và một tên riêng biệt để xác định nó. Dưới đây là một số khái niệm quan trọng về biến trong C:
5.1.1 Khai báo biến (Variable Declaration)
Để tạo một biến trong C, bạn phải khai báo nó. Khai báo biến bao gồm kiểu dữ liệu và tên biến. Ví dụ:
int age; // Khai báo một biến kiểu int có tên là "age"
Vậy điều gì xảy ra ở dưới khi chúng ta khai báo một biến trong C?
- Không gian bộ nhớ được cấp phát: Khi bạn khai báo một biến, hệ thống cấp phát một phần của bộ nhớ cho biến đó. Kích thước của phần này phụ thuộc vào kiểu dữ liệu của biến. Ví dụ, một biến kiểu
int
thường cấp phát 4 byte (32 bit) trong bộ nhớ. - Địa chỉ bộ nhớ được gán cho biến: Mỗi biến sẽ có một địa chỉ bộ nhớ cụ thể, và bạn sử dụng tên biến để tham chiếu đến địa chỉ này trong mã nguồn của bạn.
5.1.2 Khởi tạo biến (Variable Initialization)
Bạn có thể khai báo và khởi tạo biến cùng một lúc. Ví dụ:
int count = 0; // Khai báo và khởi tạo biến "count" với giá trị ban đầu là 0
Việc khởi tạo biến trong C là không bắt buộc, tuy nhiên nó cũng có một số lợi ích. Dưới đây là một số lý do tại sao bạn nên khởi tạo biến:
- Tránh giá trị rác: Khi bạn khởi tạo một biến, bạn cung cấp một giá trị ban đầu cho nó. Nếu bạn không khởi tạo biến, giá trị của biến có thể chứa giá trị rác từ vùng bộ nhớ trước đó, và điều này có thể dẫn đến lỗi không xác định hoặc hành vi không mong muốn.
- Tạo mã dễ đọc và dễ bảo trì: Khởi tạo biến giúp làm cho mã nguồn dễ đọc hơn. Khi bạn đọc mã, bạn biết giá trị ban đầu của biến là gì, điều này giúp bạn hiểu mã nguồn nhanh hơn.
- Bảo trì tính nhất quán: Khởi tạo biến giúp đảm bảo tính nhất quán trong mã nguồn. Điều này làm cho mã dễ bảo trì hơn bởi vì bạn biết rằng biến luôn có một giá trị ban đầu đã định.
- Tránh lỗi logic: Khởi tạo biến có thể giúp bạn phát hiện và tránh lỗi logic trong mã nguồn. Khi bạn sử dụng một biến mà bạn đã quên khởi tạo, bạn có thể gặp lỗi không mong muốn khi sử dụng giá trị của nó.
5.1.3 Tên biến (Variable Name)
Tên biến là một định danh duy nhất được sử dụng để xác định biến. Tên biến phải tuân theo các quy tắc về cú pháp và không được trùng với từ khóa trong ngôn ngữ C.
Có một số quy tắc cơ bản trong việc đặt tên biến trong C:
- Chỉ sử dụng chữ cái, số và dấu gạch dưới: Tên biến chỉ có thể bắt đầu bằng một chữ cái (a-z hoặc A-Z), một số (0-9) hoặc một dấu gạch dưới (_). Những ký tự sau có thể bao gồm chữ cái, số và dấu gạch dưới.
- Không sử dụng dấu cách: Tên biến không được chứa khoảng trắng hoặc dấu cách. Nếu bạn muốn kết hợp từ hoặc cụm từ, bạn có thể sử dụng dấu gạch dưới hoặc chữ hoa để phân biệt, ví dụ:
my_variable
,myVariable
. - Không sử dụng từ khóa của ngôn ngữ: Tên biến không được trùng với từ khóa hoặc các từ được định nghĩa trước trong ngôn ngữ C, chẳng hạn như
int
,if
,while
,return
, v.v. - Phân biệt chữ hoa và chữ thường: Các biến trong C phân biệt chữ hoa và chữ thường. Ví dụ,
myVariable
vàmyvariable
là hai biến khác nhau. - Chọn tên biến có ý nghĩa: Tên biến nên phản ánh mục đích hoặc nhiệm vụ của biến. Điều này làm cho mã nguồn dễ đọc và dễ bảo trì hơn. Ví dụ, thay vì sử dụng
x
hoặca
, bạn nên sử dụng tên biến mô tả giá trị của nó, chẳng hạnage
,count
,total
, v.v. - Sử dụng kiểu Camel Case hoặc Snake Case: Có hai phong cách phổ biến để đặt tên biến: Camel Case và Snake Case. Trong Camel Case, từ đầu tiên bắt đầu bằng chữ thường, sau đó mỗi từ tiếp theo bắt đầu bằng chữ in hoa, ví dụ:
myVariableName
. Trong Snake Case, các từ được phân tách bằng dấu gạch dưới, ví dụ:my_variable_name
. Cả hai phong cách đều được chấp nhận, nhưng quan trọng là duy nhất trong mã nguồn của bạn. - Giới hạn độ dài tên biến: Tên biến nên ngắn gọn và dễ đọc. Tránh đặt tên biến quá dài hoặc quá ngắn.
5.1.4 Gán giá trị (Assignment)
Để gán một giá trị mới cho biến, bạn sử dụng toán tử gán (=). Ví dụ:
age = 25; // Gán giá trị 25 cho biến "age"
Khi bạn gán giá trị cho một biến trong ngôn ngữ lập trình C, bạn đang thực hiện thao tác gán, tức là bạn đang đặt giá trị mới cho biến. Dưới đây là một số điểm quan trọng về quá trình gán giá trị này:
- Gán giá trị mới: Gán giá trị cho biến có nghĩa là bạn thay đổi giá trị của biến bằng giá trị mới.
int x = 10; // Khai báo và khởi tạo biến x với giá trị ban đầu là 10
x = 20; // Gán giá trị mới cho biến x, giờ x = 20
- Thay đổi giá trị biến: Gán giá trị mới cho biến thay đổi giá trị của nó và làm cho giá trị trước đó bị mất đi. Giá trị mới được lưu trong biến.
- Kiểu dữ liệu phải phù hợp: Giá trị bạn gán cho biến phải phù hợp với kiểu dữ liệu của biến. Ví dụ, nếu biến là kiểu
int
, bạn chỉ có thể gán giá trị nguyên cho nó. - Gán có thể kết hợp với các biểu thức: Bạn có thể kết hợp gán với các biểu thức, ví dụ:
int x = 10;
x = x + 5; // Gán giá trị mới cho biến x bằng cách sử dụng biểu thức
- Thứ tự trong biểu thức gán: Các ngôn ngữ lập trình xác định một thứ tự cụ thể cho các biểu thức gán. Điều này có thể ảnh hưởng đến kết quả của biểu thức, đặc biệt trong trường hợp của biểu thức phức tạp. Ví dụ:
int x = 5;
int y = 10;
x = y = 15; // Điều này gán giá trị 15 cho cả x và y
5.1.5 Phạm vi biến (Variable Scope)
Phạm vi của một biến xác định nơi mà biến có thể được truy cập. Biến cục bộ (local variables) chỉ có thể truy cập trong phạm vi của hàm hoặc khối mã trong đó chúng được khai báo, trong khi biến toàn cục (global variables) có thể truy cập từ mọi nơi trong chương trình.
Biến Cục Bộ (Local Variable):
#include <stdio.h>
int main() {
int x = 10; // Đây là một biến cục bộ trong hàm main
if (x > 5) {
int y = 20; // Đây là một biến cục bộ trong khối mã if
printf("x = %d, y = %d\n", x, y);
}
// Biến y không thể truy cập ở đây vì nó đã vượt ra khỏi phạm vi của khối mã if
printf("x = %d\n", x);
return 0;
}
Trong ví dụ trên, biến x
và y
là biến cục bộ. Biến x
được khai báo trong hàm main
, và biến y
được khai báo trong khối mã của câu lệnh if
. Biến x
có phạm vi trong toàn bộ hàm main
, trong khi biến y
chỉ có phạm vi trong khối mã của câu lệnh if
. Vì vậy, sau khi chương trình rời khỏi khối mã if
, biến y
không còn tồn tại.
Biến Toàn Cục (Global Variable):
#include <stdio.h>
int globalVar = 100; // Đây là biến toàn cục
int main() {
int x = 10; // Đây là biến cục bộ trong hàm main
printf("x = %d, globalVar = %d\n", x, globalVar);
return 0;
}
Trong ví dụ này, biến globalVar
là biến toàn cục, có thể truy cập từ bất kỳ hàm nào trong chương trình. Biến x
là biến cục bộ, chỉ có phạm vi trong hàm main
. Khi bạn muốn sử dụng biến toàn cục, bạn có thể thực hiện điều đó từ bất kỳ hàm nào trong chương trình, như trong ví dụ trên.
5.2 Kiểu dữ liệu (Data Type)
C có một loạt các kiểu dữ liệu mà bạn có thể sử dụng để định nghĩa và lưu trữ dữ liệu. Dưới đây là một số kiểu dữ liệu thường gặp trong C:
- int: Kiểu dữ liệu nguyên (integer) được sử dụng để lưu trữ các số nguyên. Ví dụ:
int x = 10;
- float: Kiểu dữ liệu số thực đơn (floating-point) được sử dụng để lưu trữ các số thập phân. Ví dụ:
float y = 3.14;
- double: Kiểu dữ liệu số thực kép (double-precision floating-point) tương tự như
float
, nhưng có độ chính xác cao hơn. Ví dụ:double z = 3.14159265359;
- char: Kiểu dữ liệu ký tự (character) được sử dụng để lưu trữ một ký tự. Ví dụ:
char ch = 'A';
- long int: Kiểu dữ liệu số nguyên dài (long integer) có kích thước lớn hơn so với
int
. Ví dụ:long int b = 1000L;
- unsigned int: Kiểu dữ liệu số nguyên không dấu (unsigned integer) để lưu trữ các số nguyên không âm (tức là không có dấu). Ví dụ:
unsigned int c = 20;
- unsigned char: Kiểu dữ liệu ký tự không dấu (unsigned character) để lưu trữ ký tự không dấu. Ví dụ:
unsigned char uc = 'B';
5.3 In và đọc dữ liệu
C cung cấp sẵn cho chúng ta 2 hàm là printf
và scanf
dùng để in và đọc dữ liệu.
5.3.1 printf
Được sử dụng để in dữ liệu ra màn hình theo một định dạng cụ thể.'
- Cú pháp
printf("Định dạng chuỗi", tham số1, tham số2, ...);
- Ví dụ:
int age = 30;
float salary = 1000.50;
printf("Tuổi: %d\n", age);
printf("Lương: %.2f\n", salary);
5.3.2 scanf
Được sử dụng để đọc dữ liệu từ bàn phím vào biến theo một định dạng cụ thể.
- Cú pháp
scanf("Định dạng chuỗi", &biến1, &biến2, ...);
- Ví dụ:
int age;
float salary;
printf("Nhập tuổi: ");
scanf("%d", &age);
printf("Nhập lương: ");
scanf("%f", &salary);
Lưu ý rằng trong scanf
, bạn phải sử dụng dấu &
trước tên biến để truyền địa chỉ của biến đó cho scanf
. Điều này là cần thiết để scanf
có thể ghi dữ liệu vào biến được chỉ định.
5.3.3 Định dạng dữ liệu
Các kí hiệu như %d
hay %s
được sử dụng trong printf
với scanf
được gọi là định dạng của dữ liệu. Để định dạng cách in hay đọc dữ liệu, C đã cung cấp sẵn cho chúng ta các loại định dạng sau:
- %c : Ký tự đơn
- %s : Chuỗi
- %d : Số nguyên hệ 10 có dấu
- %f : Số chấm động (VD 7.44 khi in sẽ ra 7.440000)
- %e : Số chấm động (ký hiệu có số mũ)
- %g : Số chấm động (VD 7.44 khi in sẽ in ra 7.44)
- %x : Số nguyên hex không dấu (hệ 16)
- %o : Số nguyên bát phân không dấu (hệ 8)
- %p : Địa chỉ con trỏ
5.4 Hàm (Function)
Trong ngôn ngữ lập trình C, hàm (function) là một khối mã thực hiện một tác vụ cụ thể. Hàm là một phần quan trọng của chương trình C, vì chúng giúp bạn chia nhỏ chương trình thành các phần nhỏ dễ quản lý và sử dụng lại. Dưới đây là cách định nghĩa và sử dụng hàm trong C:
5.4.1 Cú pháp của hàm
Một hàm trong C được định nghĩa bằng cú pháp sau:
kiểu_dữ_liệu tên_hàm(tham_số) {
// Khối mã của hàm
// Thực hiện các công việc ở đây
return giá_trị_trả_về; // Optional
}
kiểu_dữ_liệu
: Đây là kiểu dữ liệu của giá trị mà hàm sẽ trả về. Nếu hàm không trả về giá trị, bạn sử dụngvoid
.tên_hàm
: Đây là tên của hàm, bạn tự đặt tên cho hàm. Tên hàm phải tuân theo quy tắc đặt tên biến.tham_số
: Đây là các tham số (có thể là không có tham số) mà hàm có thể nhận vào. Tham số là các giá trị mà bạn truyền cho hàm để thực hiện các công việc cụ thể.khối mã của hàm
: Đây là nơi bạn đặt mã nguồn của hàm, thực hiện các công việc cụ thể.giá_trị_trả_về
: Nếu hàm trả về một giá trị, bạn sử dụng lệnhreturn
để trả giá trị đó. Nếu hàm không trả về giá trị hoặc chỉ có mục tiêu thực hiện công việc mà không cần trả về giá trị, bạn sử dụngreturn
vớivoid
.
5.4.2 Gọi hàm
Để sử dụng hàm, bạn gọi hàm bằng tên của nó cùng với các đối số (nếu có). Ví dụ:
int sum(int a, int b) {
return a + b;
}
int main() {
int x = 5;
int y = 3;
int result = sum(x, y); // Gọi hàm sum và lưu kết quả vào result
return 0;
}
5.4.3 Hàm có hoặc không có giá trị trả về
Hàm có thể có giá trị trả về hoặc không. Ví dụ:
int multiply(int a, int b) {
return a * b; // Hàm trả về một giá trị kiểu int
}
void greet() {
printf("Hello, World!\n"); // Hàm không trả về giá trị (void)
}
5.4.4 Hàm nguyên mẫu (Function Prototype)
Trước khi sử dụng hàm trong một tệp mã nguồn khác, bạn nên định nghĩa hàm nguyên mẫu (function prototype) ở đầu tệp mã hoặc bao gồm tệp tiêu đề chứa định nghĩa hàm. Hàm nguyên mẫu chỉ ra kiểu dữ liệu trả về của hàm và kiểu dữ liệu của các tham số nó có. Điều này giúp trình biên dịch biết cách gọi hàm và kiểm tra sự phù hợp của đối số và kiểu trả về.
// Hàm nguyên mẫu cho hàm sum
int sum(int a, int b);
int main() {
int x = 5;
int y = 3;
int result = sum(x, y); // Gọi hàm sum và lưu kết quả vào result
return 0;
}
// Định nghĩa hàm sum sau main hoặc trong một tệp mã nguồn khác
int sum(int a, int b) {
return a + b;
}
5.5 Chuỗi (String)
Trong C, chuỗi (string) thường được biểu diễn bằng một mảng các ký tự (char
). Mảng này chứa các ký tự liên tiếp kết thúc bằng một ký tự null ('\0'
) để xác định kết thúc của chuỗi. Dưới đây là cách bạn có thể định nghĩa, khai báo và làm việc với chuỗi trong C:
5.5.1 Định nghĩa chuỗi
Bạn có thể khai báo một mảng ký tự cố định để định nghĩa chuỗi. Độ dài của mảng cố định phải đủ lớn để chứa chuỗi và ký tự null cuối cùng. Ví dụ:
char myString[20] = "Hello, World!";
5.5.2 Làm việc với chuỗi
Có nhiều cách để thao tác và làm việc với chuỗi, dưới đây là một số cách:
- In chuỗi: Để hiển thị một chuỗi trong C lên màn hình chúng ta sẽ dùng kí hiệu
%s
char result = "Hello"
printf("Ket qua la %s", result);
- Sao chép chuỗi: Bạn có thể sử dụng hàm
strcpy
để sao chép một chuỗi vào một chuỗi khác.
char source[] = "Hello";
char destination[20];
strcpy(destination, source); // Sao chép chuỗi source vào chuỗi destination
- Nối chuỗi: Để nối một chuỗi vào cuối chuỗi khác, bạn có thể sử dụng hàm
strcat
.
char str1[] = "Hello";
char str2[] = ", World!";
strcat(str1, str2); // Kết quả: "Hello, World!"
- So sánh chuỗi: Hàm
strcmp
được sử dụng để so sánh hai chuỗi.
char str1[] = "apple";
char str2[] = "banana";
int result = strcmp(str1, str2);
// Kết quả: result < 0 (str1 đứng trước str2 trong thứ tự từ điển)
- Tính độ dài chuỗi: Để tính độ dài của chuỗi, bạn có thể sử dụng hàm
strlen
.
char str[] = "Hello, World!";
int length = strlen(str); // Kết quả: 13
Lưu ý rằng chuỗi kết thúc bằng ký tự null '\0'
, nên khi bạn làm việc với chuỗi, hãy đảm bảo rằng bạn duyệt đến ký tự null để xác định kết thúc của chuỗi.
5.6 Mảng một chiều (Array)
Mảng (array) trong C là một tập hợp các phần tử có cùng kiểu dữ liệu được sắp xếp theo thứ tự. Mảng cho phép bạn lưu trữ nhiều giá trị cùng loại trong một biến duy nhất.
5.6.1 Định nghĩa và Khai báo Mảng
Để định nghĩa một mảng trong C, bạn cần chỉ định kiểu dữ liệu của các phần tử và kích thước của mảng (số lượng phần tử). Cú pháp tổng quan như sau:
kiểu_dữ_liệu tên_mảng[kích_thước];
kiểu_dữ_liệu
: Đây là kiểu dữ liệu của các phần tử trong mảng, ví dụint
,float
,char
, ...tên_mảng
: Đây là tên của mảng.kích_thước
: Số lượng phần tử trong mảng.
Ví dụ:
int numbers[5]; // Định nghĩa mảng numbers với 5 phần tử kiểu int
char letters[10]; // Định nghĩa mảng letters với 10 phần tử kiểu char
5.6.2 Truy cập phần tử trong Mảng
Bạn có thể truy cập các phần tử trong mảng bằng cách sử dụng chỉ mục (index) của phần tử trong dấu ngoặc vuông []
. Chỉ mục bắt đầu từ 0. Ví dụ:
int numbers[5] = {10, 20, 30, 40, 50};
int firstNumber = numbers[0]; // firstNumber = 10
int thirdNumber = numbers[2]; // thirdNumber = 30
5.7 Mảng hai chiều (2D Array)
Mảng hai chiều (2D array) trong ngôn ngữ lập trình C là một cấu trúc dữ liệu cho phép bạn lưu trữ dữ liệu dưới dạng ma trận hoặc lưới có hai chiều (hàng và cột).
5.7.1 Định nghĩa và Khai báo Mảng hai chiều
kiểu_dữ_liệu tên_mảng[số_hàng][số_cột];
kiểu_dữ_liệu
: Đây là kiểu dữ liệu của các phần tử trong mảng 2D, ví dụint
,float
,char
, ...tên_mảng
: Đây là tên của mảng.số_hàng
: Số lượng hàng trong mảng.số_cột
: Số lượng cột trong mảng.
Ví dụ:
int matrix[3][3]; // Định nghĩa mảng 2D với 3 hàng và 3 cột
float data[2][4]; // Định nghĩa mảng 2D với 2 hàng và 4 cột
5.7.2 Truy cập phần tử trong Mảng hai chiều
Bạn có thể truy cập các phần tử trong mảng 2D bằng cách sử dụng chỉ mục của hàng và cột trong dấu ngoặc vuông [][]
. Chỉ mục hàng và cột bắt đầu từ 0. Ví dụ:
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int element = matrix[1][2]; // element = 6
Mảng hai chiều thường được sử dụng để biểu diễn ma trận, hình ảnh, dữ liệu lưới và nhiều loại dữ liệu có cấu trúc tương tự.
5.8 Câu lệnh điều kiện (Conditional Statement)
5.8.1 Câu lệnh if...else
Câu lệnh if...else
cho phép bạn thực hiện một khối mã nguồn nếu điều kiện kiểm tra là đúng (true), và một khối mã nguồn khác nếu điều kiện là sai (false). Cú pháp tổng quan như sau:
if (điều_kiện) {
// Khối mã thực hiện nếu điều_kiện là true
} else {
// Khối mã thực hiện nếu điều_kiện là false
}
Ví dụ:
int x = 3;
printf("x lớn hơn 5.\n");
} else {
printf("x không lớn hơn 5.\n");
}
5.8.2 Câu lệnh if... elseif ...else
Câu lệnh if...else if...else
cho phép bạn kiểm tra nhiều điều kiện liên tiếp và thực hiện các khối mã nguồn tương ứng với điều kiện đúng đầu tiên. Cú pháp tổng quan như sau:
if (điều_kiện_1) {
// Khối mã thực hiện nếu điều_kiện_1 là true
} else if (điều_kiện_2) {
// Khối mã thực hiện nếu điều_kiện_2 là true
} else {
// Khối mã thực hiện nếu không có điều kiện nào là true
}
Ví dụ:
int x = 7;
if (x < 5) {
printf("x nhỏ hơn 5.\n");
} else if (x == 5) {
printf("x bằng 5.\n");
} else {
printf("x lớn hơn 5.\n");
}
5.8.3 Câu lệnh switch
Câu lệnh switch
cho phép bạn kiểm tra một biểu thức hoặc giá trị và thực hiện các tác vụ tương ứng với từng giá trị. Cú pháp tổng quan như sau:
switch (biểu_thức) {
case giá_trị_1:
// Khối mã thực hiện nếu biểu_thức bằng giá_trị_1
break;
case giá_trị_2:
// Khối mã thực hiện nếu biểu_thức bằng giá_trị_2
break;
// Các trường hợp khác
default:
// Khối mã thực hiện nếu không có trường hợp nào khớp
}
Ví dụ:
int choice = 2;
switch (choice) {
case 1:
printf("Bạn đã chọn tùy chọn 1.\n");
break;
case 2:
printf("Bạn đã chọn tùy chọn 2.\n");
break;
default:
printf("Tùy chọn không hợp lệ.\n");
}
5.9 Vòng lặp (Loop)
Trong ngôn ngữ lập trình C, có ba loại vòng lặp chính được sử dụng để lặp qua mã nguồn hoặc thực hiện các tác vụ lặp đi lặp lại. Các loại vòng lặp này là:
5.9.1 Vòng lặp for
Vòng lặp for được sử dụng để lặp qua một khối mã nguồn một số lần xác định. Cú pháp tổng quan như sau:
for (khởi_tạo; điều_kiện; cập_nhật) {
// Khối mã thực hiện trong mỗi vòng lặp
}
Ví dụ:
for (int i = 0; i < 5; i++) {
printf("Giá trị của i: %d\n", i);
}
5.9.2 Vòng lặp while
Vòng lặp while
được sử dụng để lặp qua một khối mã nguồn trong khi một điều kiện là đúng (true). Cú pháp tổng quan như sau:
while (điều_kiện) {
// Khối mã thực hiện trong mỗi vòng lặp
}
Ví dụ:
int x = 0;
while (x < 5) {
printf("Giá trị của x: %d\n", x);
x++;
}
5.9.3 Vòng lặp do...while
:
Vòng lặp do...while
tương tự với while
, nhưng khối mã trong vòng lặp được thực hiện ít nhất một lần trước khi kiểm tra điều kiện. Cú pháp tổng quan như sau:
do {
// Khối mã thực hiện ít nhất một lần
} while (điều_kiện);
Ví dụ:
int y = 0;
do {
printf("Giá trị của y: %d\n", y);
y++;
} while (y < 5);
5.9.4 Sự khác nhau giữa 3 loại vòng lặp
Sự khác nhau chính giữa ba vòng lặp chính trong ngôn ngữ lập trình C (for, while, và do...while) là cách chúng kiểm tra và điều khiển việc lặp. Dưới đây là sự khác nhau giữa chúng và trường hợp nào nên sử dụng mỗi loại vòng lặp:
Vòng lặp for | Vòng lặp while | Vòng lặp do...while | |
---|---|---|---|
Cú pháp | sử dụng một biểu thức khởi tạo, một biểu thức điều kiện, và một biểu thức cập nhật | sử dụng một biểu thức điều kiện | sử dụng một biểu thức điều kiện |
Thứ tự kiểm tra điều kiện | Đầu tiên kiểm tra điều kiện, sau đó thực hiện khối mã nếu điều kiện là true. Sau khi thực hiện khối mã, biểu thức cập nhật được thực hiện và sau đó kiểm tra lại điều kiện. | Đầu tiên kiểm tra điều kiện, sau đó thực hiện khối mã nếu điều kiện là true. Không thực hiện nếu điều kiện là false. | Thực hiện khối mã ít nhất một lần, sau đó kiểm tra điều kiện. Nếu điều kiện là true, thực hiện thêm vòng lặp. |
Thích hợp cho | Sử dụng khi bạn biết trước số lần lặp cụ thể hoặc cần thực hiện một số lần lặp cố định. | Sử dụng khi bạn không biết trước số lần lặp và muốn thực hiện lặp dựa trên điều kiện nào đó. | Sử dụng khi bạn muốn đảm bảo rằng khối mã ít nhất được thực hiện một lần, sau đó kiểm tra điều kiện để quyết định liệu cần tiếp tục lặp hay không. |
5.10 Struct (Cấu trúc)
Struct (cấu trúc) trong ngôn ngữ lập trình C là một cấu trúc dữ liệu cho phép bạn tổng hợp nhiều biến có kiểu dữ liệu khác nhau vào một đối tượng duy nhất. Cấu trúc giúp bạn tạo ra kiểu dữ liệu tùy chỉnh để biểu diễn các thực thể có tính chất phức tạp hơn. Dưới đây là cách bạn định nghĩa và làm việc với cấu trúc trong C:
5.10.1 Định nghĩa cấu trúc
Để định nghĩa cấu trúc trong C, bạn sử dụng từ khóa struct
theo sau là tên cấu trúc và danh sách các biến thành viên (members) trong cấu trúc. Cú pháp tổng quan như sau:
struct TenCauTruc {
kieu_du_lieu1 thanh_vien1;
kieu_du_lieu2 thanh_vien2;
// ...
};
Ví dụ:
struct Person {
char name[50];
int age;
float height;
};
5.10.2 Khai báo biến kiểu struct trong C
Việc khai báo biến với struct cũng giống như cách khai báo biến thông thường, trong đó kiểu dữ liệu là kiểu struct trong C mà bạn vừa định nghĩa.
struct Person
{
int id;
char firstName[20];
char lastName[20];
int age;
char address[100];
};
int main(){
struct Person ps1, ps2;
// Khai báo mảng
struct Person ps[100];
}
5.10.3 Truy xuất các thuộc tính của struct
C cung cấp cho chúng ta 2 toán tử để truy xuất các thuộc tính của struct:
- Sử dụng
.
=> Truy xuất tới thuộc tính khi khai báo biến bình thường. - Sử dụng
->
=> Truy xuất tới thuộc tính khi biến là con trỏ.
Ví dụ chúng ta muốn truy xuất đến thuộc tính firstName
của Person ta làm như sau:
Person ps;
printf("First name: %s", ps.firstName);
5.11 Enum (Liệt kê)
Enum (liệt kê) trong ngôn ngữ lập trình C là một cấu trúc dữ liệu cho phép bạn xác định một tập hợp các giá trị nguyên có tên. Thông thường, liệt kê được sử dụng để đặt tên cho các hằng số hoặc để tạo các biểu thức nguyên với ý nghĩa dễ đọc. Dưới đây là cách bạn định nghĩa và làm việc với liệt kê trong C:
5.11.1 Định nghĩa enum
Để định nghĩa enum trong C, bạn sử dụng từ khóa enum
sau đó là tên liệt kê và danh sách các giá trị liệt kê trong dấu ngoặc nhọn {}
. Ví dụ:
enum Season {
Spring,
Summer,
Autumn,
Winter
};
Trong ví dụ này, Season
là tên của liệt kê, và Spring
, Summer
, Autumn
, và Winter
là các giá trị liệt kê. Mặc định, giá trị của các trạng thái trong enum là các số nguyên liên tiếp, bắt đầu từ 0. Spring
có giá trị là 0, Summer
là 1, và tiếp tục như vậy.
5.11.2 Gán giá trị tùy chỉnh cho các trạng thái
Bạn có thể gán giá trị tùy chỉnh cho các trạng thái của enum bằng cách chỉ định giá trị sau mỗi trạng thái. Ví dụ:
enum Day {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
Trong ví dụ này, Monday
có giá trị là 1, và các giá trị tiếp theo sẽ được tự động tăng thêm một đơn vị (2, 3, 4,...).
5.11.3 Sử dụng enum
Sau khi bạn đã định nghĩa enum, bạn có thể sử dụng nó để khai báo biến hoặc trong các biểu thức. Ví dụ:
enum Season currentSeason = Spring;
Ngoài ra bạn có thể sử dụng enum làm tham số cho hàm để biểu thị trạng thái hoặc tùy chọn.
void printDay(enum Day day) {
switch (day) {
case Monday:
printf("Monday");
break;
case Tuesday:
printf("Tuesday");
break;
// ...
}
}
Enum giúp làm cho mã nguồn dễ đọc hơn và giúp tránh sử dụng các số nguyên cứng.
5.12 Thư viện (Library)
Trong ngôn ngữ lập trình C, thư viện (library) là một tập hợp các tệp và hàm được biên dịch sẵn (compiled) mà bạn có thể sử dụng trong các chương trình của mình. Thư viện giúp tái sử dụng mã nguồn, giảm độ phức tạp của chương trình, và tăng hiệu suất trong quá trình phát triển phần mềm. Dưới đây là một số điểm quan trọng về thư viện trong ngôn ngữ C:
- Thư viện tiêu chuẩn (Standard Library): C có một thư viện tiêu chuẩn chứa các hàm và tệp thư viện đã được định nghĩa trước để thực hiện các nhiệm vụ phổ biến. Ví dụ, thư viện
stdio.h
chứa hàm liên quan đến đầu vào và đầu ra, nhưprintf
,scanf
, vàfgets
. Thư viện tiêu chuẩn được cung cấp bởi ngôn ngữ C và có sẵn cho mọi trình biên dịch C. - Thư viện bên ngoài (External Libraries): Bên cạnh thư viện tiêu chuẩn, có nhiều thư viện bên ngoài được tạo ra bởi cộng đồng lập trình C và các nhà sản xuất trình biên dịch. Những thư viện này cung cấp các tính năng đặc biệt và phức tạp hơn cho việc phát triển ứng dụng. Để sử dụng thư viện bên ngoài, bạn cần bao gồm tiêu đề (header) thư viện trong mã nguồn của mình và liên kết (link) với tệp thư viện khi biên dịch.
- Tiêu đề (Header Files): Tiêu đề (header) là tệp văn bản có phần mở rộng
.h
chứa các khai báo hàm, cấu trúc dữ liệu và hằng số mà bạn cần sử dụng từ một thư viện. Để sử dụng thư viện, bạn cần đưa tiêu đề của nó vào mã nguồn của mình bằng cách sử dụng#include
. Ví dụ:#include
. - Liên kết (Linking): Sau khi bạn đã sử dụng các hàm từ thư viện trong mã nguồn của mình, bạn cần liên kết mã nguồn với thư viện khi biên dịch. Điều này đảm bảo rằng trình biên dịch biết nơi tìm các định nghĩa hàm cụ thể. Thông thường, việc liên kết được thực hiện bằng cách sử dụng câu lệnh liên kết hoặc trình biên dịch.
- Tạo thư viện tùy chỉnh (Custom Libraries): Bạn cũng có thể tạo thư viện tùy chỉnh để đóng gói và tái sử dụng mã nguồn của mình. Điều này đặc biệt hữu ích khi bạn muốn chia sẻ mã nguồn với các dự án khác hoặc đảm bảo tính riêng tư và bảo mật của mã nguồn.
Thư viện đóng một vai trò quan trọng trong việc phát triển ứng dụng C và giúp tối ưu hóa quá trình phát triển phần mềm bằng cách sử dụng lại mã nguồn đã có sẵn.
5.13 Con trỏ (Pointer)
Con trỏ (Pointer) trong ngôn ngữ lập trình C là một biến đặc biệt được sử dụng để lưu trữ địa chỉ bộ nhớ của một biến khác. Con trỏ cho phép bạn truy cập và thao tác trực tiếp với bộ nhớ và dữ liệu, và nó là một trong những tính năng quan trọng của C. Dưới đây là các khái niệm và cách sử dụng con trỏ trong C:
Khai báo con trỏ
Để khai báo một con trỏ, bạn sử dụng dấu *
sau kiểu dữ liệu. Ví dụ:
int *ptr; // Con trỏ kiểu int
double *ptr2; // Con trỏ kiểu double
Gán giá trị cho con trỏ
Con trỏ có thể được gán bất kỳ địa chỉ của biến nào cùng kiểu dữ liệu với nó. Điều này được thực hiện bằng cách sử dụng toán tử &
để lấy địa chỉ của biến. Ví dụ:
int x = 10;
int *ptr = &x; // Gán địa chỉ của biến x cho con trỏ ptr
Truy cập giá trị của con trỏ
Để truy cập giá trị mà con trỏ đang trỏ đến, bạn sử dụng toán tử *
. Ví dụ:
int y = *ptr; // Lấy giá trị mà con trỏ ptr đang trỏ đến (giá trị của biến x)
Sử dụng con trỏ để thay đổi giá trị biến
Con trỏ cho phép bạn thay đổi giá trị của biến mà nó đang trỏ đến. Ví dụ:
*ptr = 20; // Thay đổi giá trị của biến x thành 20
Thực hiện các phép tính với con trỏ
Bạn có thể thực hiện các phép tính số học và logic với con trỏ để di chuyển giữa các địa chỉ bộ nhớ và thực hiện các phép tính trên dữ liệu. Ví dụ:
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // Gán địa chỉ của mảng cho con trỏ
// Di chuyển con trỏ qua các phần tử mảng và in giá trị
for (int i = 0; i < 5; i++) {
printf("Gia tri cua phan tu thu %d: %d\n", i, *ptr);
ptr++; // Di chuyen con tro toi phan tu tiep theo
}
Con trỏ và mảng
Mảng và con trỏ trong C có mối quan hệ mật thiết. Tên mảng một cách tường minh là một con trỏ tới phần tử đầu tiên trong mảng, và bạn có thể sử dụng con trỏ để truy cập các phần tử của mảng.
Dưới đây là một ví dụ về cách sử dụng con trỏ và mảng trong ngôn ngữ lập trình C. Trong ví dụ này, chúng ta sẽ tạo một mảng các số nguyên và sử dụng một con trỏ để truy cập các phần tử của mảng.
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// Khai báo một con trỏ kiểu int và gán địa chỉ của mảng cho nó
int *ptr = arr;
// Sử dụng con trỏ để truy cập và in các phần tử của mảng
for (int i = 0; i < 5; i++) {
printf("Gia tri cua phan tu thu %d: %d\n", i, *ptr);
ptr++; // Di chuyển con trỏ tới phần tử tiếp theo trong mảng
}
return 0;
}
- Chúng ta đã khai báo một mảng
arr
có 5 phần tử và khởi tạo nó với các giá trị từ 10 đến 50. - Sau đó, chúng ta đã khai báo một con trỏ kiểu
int
có tênptr
. - Chúng ta gán địa chỉ của mảng
arr
cho con trỏptr
bằng cách sử dụng tên mảng làm con trỏ vào phần tử đầu tiên của mảng. - Trong vòng lặp
for
, chúng ta sử dụng con trỏ để truy cập và in giá trị của từng phần tử của mảng. Sau đó, chúng ta di chuyển con trỏ tới phần tử tiếp theo trong mảng bằng cách sử dụngptr++
.
Kết quả của chương trình sẽ in ra giá trị của các phần tử trong mảng:
Gia tri cua phan tu thu 0: 10
Gia tri cua phan tu thu 1: 20
Gia tri cua phan tu thu 2: 30
Gia tri cua phan tu thu 3: 40
Gia tri cua phan tu thu 4: 50
Con trỏ và chuỗi
Chuỗi trong C thường được biểu diễn bằng một mảng các ký tự và kết thúc bằng ký tự null ('\0'
). Con trỏ thường được sử dụng để xử lý chuỗi, vì chúng cho phép bạn truy cập và thay đổi từng ký tự trong chuỗi một cách dễ dàng.
Dưới đây là một ví dụ về cách sử dụng con trỏ để làm việc với chuỗi (string) trong ngôn ngữ lập trình C:
#include <stdio.h>
int main() {
char greeting[] = "Hello, World!"; // Một mảng ký tự (chuỗi)
char *ptr = greeting; // Con trỏ kiểu char trỏ tới chuỗi
// Sử dụng con trỏ để truy cập và in từng ký tự của chuỗi
while (*ptr != '\0') {
printf("%c", *ptr);
ptr++; // Di chuyển con trỏ tới ký tự tiếp theo
}
return 0;
}
- Chúng ta đã khai báo một mảng ký tự có tên
greeting
và khởi tạo nó với chuỗi "Hello, World!". - Sau đó, chúng ta đã khai báo một con trỏ kiểu
char
có tênptr
. - Chúng ta gán địa chỉ của mảng
greeting
cho con trỏptr
, vì mảng là một chuỗi ký tự và tên mảng chính là con trỏ tới phần tử đầu tiên của chuỗi. - Sử dụng vòng lặp
while
, chúng ta duyệt qua chuỗi bằng cách sử dụng con trỏ. Chúng ta kiểm tra ký tự mỗi lần và in nó ra màn hình, sau đó di chuyển con trỏ tới ký tự tiếp theo trong chuỗi bằng cách sử dụngptr++
. - Chuỗi kết thúc bằng ký tự null
'\0'
, nên chúng ta dừng khi gặp ký tự null.
Kết quả của chương trình sẽ in ra chuỗi "Hello, World!" lên màn hình.
Con trỏ là một tính năng mạnh mẽ trong ngôn ngữ C, nhưng nó cũng đòi hỏi kiểm soát cẩn thận và làm việc chính xác với bộ nhớ để tránh lỗi segmentation fault và rò rỉ bộ nhớ.
5.14 File I/O (Input/Output)
File I/O (Input/Output) trong ngôn ngữ lập trình C là quá trình đọc và ghi dữ liệu từ và vào các tệp (file). Các tệp được sử dụng để lưu trữ và truyền dữ liệu giữa chương trình và hệ thống tệp. Dưới đây là một số ví dụ về cách thực hiện File I/O trong C:
Ghi dữ liệu vào tệp
Để ghi dữ liệu vào một tệp, bạn cần mở tệp để ghi bằng cách sử dụng hàm fopen()
. Sau đó, bạn có thể sử dụng các hàm như fprintf()
hoặc fputc()
để ghi dữ liệu vào tệp. Ví dụ:
#include <stdio.h>
int main() {
FILE *file;
file = fopen("example.txt", "w"); // Mở tệp để ghi
if (file != NULL) {
fprintf(file, "Hello, World!\n");
fclose(file);
} else {
printf("Khong the mo tệp.\n");
}
return 0;
}
Trong ví dụ này, chúng ta mở tệp "example.txt" để ghi (mode "w" - viết tắt của write). Sau đó, chúng ta sử dụng fprintf()
để ghi chuỗi "Hello, World!" vào tệp và đóng tệp sau khi đã hoàn thành.
Đọc dữ liệu từ tệp
Để đọc dữ liệu từ một tệp, bạn cũng cần mở tệp nhưng trong mode đọc bằng fopen()
. Sau đó, bạn có thể sử dụng các hàm như fscanf()
hoặc fgetc()
để đọc dữ liệu từ tệp. Ví dụ:
#include <stdio.h>
int main() {
FILE *file;
char data[100]; // Mảng để lưu dữ liệu từ tệp
file = fopen("example.txt", "r"); // Mở tệp để đọc
if (file != NULL) {
fscanf(file, "%s", data); // Đọc dữ liệu từ tệp vào mảng
printf("Noi dung trong tệp: %s\n", data);
fclose(file);
} else {
printf("Khong the mo tệp.\n");
}
return 0;
}
Trong ví dụ này, chúng ta mở tệp "example.txt" để đọc (mode "r" - viết tắt của read). Sau đó, chúng ta sử dụng fscanf()
để đọc dữ liệu từ tệp vào mảng data
và in ra nội dung đã đọc.
File I/O trong C là một phần quan trọng của việc làm việc với tệp và dữ liệu trên hệ thống tệp. Nó cho phép bạn lưu trữ, đọc, và xử lý dữ liệu từ và vào các tệp theo nhiều cách khác nhau.
6. Tài liệu học ngôn ngữ C
Có rất nhiều tài liệu giúp bạn học ngôn ngữ C, tuy nhiên lựa chọn đúng tài liệu phù hợp với bản thân cũng như thật sự chất lượng thì không phải chuyện dễ. Dưới đây là một số tài liệu được 200Lab chọn lọc để giúp bạn nắm thật chắc ngôn ngữ C:
- Sách K&R C: "The C Programming Language" của Brian Kernighan và Dennis Ritchie (K&R C) là một trong những tài liệu cơ bản nhất cho ngôn ngữ C. Cuốn sách này giúp bạn nắm vững cú pháp cơ bản và cách sử dụng C một cách hiệu quả.
- Sách C Programming Absolute Beginner's Guide: Cuốn sách này của Perry và Miller cung cấp một cách tiếp cận dễ hiểu cho người mới học lập trình C. Nó bao gồm nhiều ví dụ thực tế và dự án để thực hành.
- Learn-C.org: Trang web này cung cấp một khóa học trực tuyến miễn phí về ngôn ngữ C. Nó bao gồm các bài giảng và bài tập để bạn thử nghiệm kiến thức.
- GeeksforGeeks C Programming: GeeksforGeeks cung cấp nhiều bài hướng dẫn, ví dụ và bài tập về lập trình C. Đây là nguồn tài liệu tốt để rèn luyện kỹ năng lập trình C.
7. Cách học ngôn ngữ C hiệu quả
Ngôn ngữ C tính đến thời điểm hiện tại mặc dù đã cũ nhưng vẫn được sử dụng để giảng dạy ở rất nhiều trường Đại học, đặc biệt là ở Việt Nam. Chính vì thế nếu bạn đang là sinh viên, bạn cần nắm thật chắc ngôn ngữ này, vì nó sẽ giúp bạn rất nhiều ở chặng đường phía trước.
Dưới đây là các bước thực hiện để học ngôn ngữ C một cách hiệu quả được đúc rút từ kinh nghiệm cá nhân:
Bước 1: Tìm hiểu qua các khái niệm cơ bản
Bước đầu tiên là các bạn cần đọc qua các khái niệm cơ bản trong C, hiểu được chúng dùng để làm gì sau đó nhớ cú pháp của chúng, ở bước này quan trọng nhất là cần nắm được cách sử dụng chúng.
Bước 2: Tiến hành làm bài tập
Hãy bắt đầu với những bài tập theo từng chủ đề trước, từ dễ đến khó. Các bạn có thể tìm trên Google 500 bài code thiếu nhi hoặc làm các bài tập mà thầy cô giao trên trường. Ở bước này điều quan trọng nhất là cố gắng tìm ra lời giải đúng của mỗi bài, từ cách làm đấy đem đi làm những bài tương tự nhưng ở mức độ khó hơn một chút.
Bước 3: Quay lại tìm hiểu về khái niệm cơ bản
Trong lúc làm bài tập chắc chắn sẽ có lúc bạn bị quên một số khái niệm, đừng ngần ngại mà hãy quay lại để đọc lại chúng. Lúc này bạn sẽ được thấy những khái niệm này hoạt động như thế nào trong thực tế sử dụng, giúp bạn nhớ nó lâu hơn.
Bước 4: Luyện tập, luyện tập, và luyện tập
Đến bước này thì việc còn lại của bạn chỉ là lặp đi lặp lại 3 bước trên, hãy cố gắng làm thật nhiều bài tập, xử lý thật nhiều vấn đề khác nhau. Từ đấy sẽ giúp bạn nắm rõ các khái niệm và nâng cao được tư duy.
Bước 5: Không bỏ cuộc
Bước cuối cùng và cũng là bước quan trọng nhất đấy là không được bỏ cuộc. Hãy cố gắng để tìm ra lời giải bằng mọi giá dù nó có tốn của bạn rất nhiều thời gian. Khi tìm ra được lời giải bạn sẽ thấy thời gian bạn bỏ ra là xứng đáng.
8. Kết luận
C là ngôn ngữ cơ bản nhất và là nền tảng cho rất nhiều ngôn ngữ khác về sau. Chính vì thế hiểu về C là rất quan trọng và là cần thiết đối với tất cả lập trình viên ngày nay.
Bài viết liên quan
C++ là gì? Giới thiệu ngôn ngữ lập trình C++
Oct 07, 2023 • 35 min read
Java Core là gì? So sánh Java Core và Java
Oct 02, 2023 • 12 min read
Clean code là gì ? Nguyên tắc viết clean code trong Lập Trình
Sep 28, 2023 • 27 min read
Bài toán tháp Hà Nội và cách giải sử dụng Đệ Quy
Sep 28, 2023 • 13 min read
Prompt Engineering là gì? Tìm hiểu về Prompt Engineer
Sep 25, 2023 • 22 min read
Đệ Quy là gì? Có bao nhiêu loại Đệ Quy cơ bản?
Sep 21, 2023 • 19 min read