Ngôn ngữ lập trình C – Những khái niệm cơ bản

Nội dung

Ở bài viết này chúng ta sẽ tìm hiểu chi tiết hơn về tính năng, kỹ thuật và cú pháp của ngôn ngữ lập trình C. Nội dung chính bao gồm:

1. Giới thiệu về C

Tiêu chuẩn về C
C được chuẩn hóa theo tiêu chuẩn ISO/IEC 9899

  1. K&R C: chuẩn hóa đầu tiên dựa trên Brian Kernighan and Dennis Ritchie (K&R) theo cuốn sách “The C Programming Language” năm 1978
  2. C90 (ISO/IEC 9899:1990 “Programming Languages C”) – Còn được gọi là ANSI C 89
  3. C99 (ISO/IEC 9899:1999 “Programming Languages C”)
  4. C11 (ISO/IEC 9899:2011 “Programming Languages C”)

2. Cú pháp cơ bản trong C

2.1 Ví dụ:
Đây là chương trình C đơn giản mình họa các cấu trúc lập trình quan trọng (thực hiện tuần tự, while-loop, if-else) và input/output. Tham khảo bài viết Giới thiệu về lập trình C để hiểu rõ hơn về chương trình này.

2.2 Comments
Comments được sử dụng như tài liệu để chú giải cho các lệnh cũng như logic trong chương trình. Comments không phải là một câu lệnh lập trình và được trình biên dịch bỏ qua nhưng nó có ý nghĩa quan trọng để cung cấp thông tin và lời giải thích để mọi người có thể hiểu được chương trình của bạn (hay cho chính bạn khi đọc lại)

Có 2 loại Comments trong C:

  • Comments nhiều dòng: bắt đầu với /* và kết thúc */, nó có thể kéo dài nhiều dòng liên tiếp
  • Comments một dòng: bắt đầu với // và kéo dài cho tới hết dòng hiện tại

Bạn nên sử dụng comments để giải thích và ghi lại cho các lệnh đã được sử dụng. Trong quá trình phát triển chương trình, thay vì việc xóa đi một khối lệnh vĩnh viễn bạn có thể comment chúng lại vì rất có thể sau này bạn cần khôi phục lại nếu cần thiết.

2.3 Statements và Blocks (Lệnh và Khối lệnh)
Statement: Một Statement trong lập trình là đơn vị độc lập nhỏ nhất trong chương trình, nó thực hiện một hành động xác định. Một statement phải được kết thúc bởi dấu chẩm phẩy (;)

Block: Một block (khối lệnh) là một nhóm các lệnh được đặt trong dấu ngoặc {…}. Tất cả các statement được đặt trong block được coi là một đơn vị. Kết thúc một block không cần dấu chấm phẩy.

2.4 Khoảng trống (white-spaces) và Định dạng mã nguồn
Khoảng trống: dấu cách, tab, new-line được gọi chung là khoảng trống. C bỏ qua các khoảng trống, những khoảng trống liên tiếp nhau được coi như một khoảng trống duy nhất. Bạn cần sử dụng không gian trống để phân tách các từ khóa hoặc mã chương trình.

Định dạng mã nguồn: Như đã nói ở trên white-spaces được C bỏ qua và không có ý nghĩa trong việc thực thi chương trình. Tuy nhiên việc căn lề, phần chia các đoạn mã theo từng dòng và giới hạn độ dài có thể sẽ làm tăng khả năng đọc hiểu chương trình. Điều này cực kì quan trọng đối với người đọc để có thể hiểu được chương trình của bạn. Hầu hết các IDE hiện nay đều đã hộ trợ công cụ cho phép bạn định dạng mã nguồn một cách tự động.

2.5 Chỉ thị tiền xử lý (Preprocessor Directives)
Mã nguồn C được thực hiện quá trình tiền xử lý trước khi được biên dịch ra các file object.
Một chỉ thị tiền xử lý bắt đầu với dấu # (ví dụ: #include, #define). Nó thông báo với bộ xử lý thực hiện một hành động nhất định trước khi biên dịch. Các chỉ thỉ tiền xử lý không phải là câu lệnh và không phải kết thúc bởi dấu chấm phẩy (;)

3. Biến và Kiểu dữ liệu

3.1 Biến
Xem lại bài viết Giới thiệu về lập trình C để ôn lại kiến thức về Biến.
Biến là một vị trí lưu trữ có tên, lưu trữ giá trị của một kiểu dữ liệu cụ thể. Kiểu dữ liệu trong các ngôn ngữ lập trình được đưa ra để đơn giản hóa việc giải thích dữ liệu được tạo khi khởi tạo sử dụng dữ liệu. Kiểu dữ liệu xác định cấu trúc dữ liệu, phạm vi giá trị và tất cả các hoạt động có thể được áp dụng.

3.2 Định danh (Identifiers)
Một định danh được sử dụng cho việc đặt tên biến, tên phương thức, tên lớp… Trong C áp dụng các quy tắc về định danh như sau:

  • Định danh là một dãy các ký tự, có chiều dài giới hạn (phụ thuộc vào trình biên dịch, thường là 255 ký tự). Bao gồm chữ hoa, chữ thường (a – z, A – Z), chữ số (0 – 9) và dấu gạch dưới (_).
  • Không cho phép các khoảng trống (blank, tab, new-line) và các ký tự đặc biệt (ví dụ: +, -, *, /, @, & …).
  • Một định danh phải bắt đầu bằng một chữ cái hoặc dấu gạch dưới. Thông thường các định danh bắt đầu bằng dấu gạch dưới được dành riêng cho hệ thống sử dụng.
  • Một định danh không thể là một từ khóa (key word) (vd : if, else, while, for, …)
  • Định danh phân biệt giữa chữ hoa và chữ thường. Ví dụ: rose # Rose # ROSE

Quy ước đặt tên biến:
Một biến là một danh từ hoặc cụm danh từ gồm nhiều từ. Từ đầu tiên viết bằng chữ thường, các từ còn lại được viết hoa chữ đầu tiên.

Ví dụ: theFontSize, roomNumber, xMax, yMin.. Quy ước này được gọi là camel-case.
Ngoài quy ước trên thì việc đặt tên biến được mọi lập trình viên khuyến nghị đặt tên nhằm mô tả và phản ánh ý nghĩa của biến.

3.3 Khai báo biến
Xem lại bài viết Giới thiệu về lập trình C để xem lại lý thuyết về khai báo biến.
Lưu ý khi khai báo biến:

  • Khai báo trước khi sử dụng.
  • Biến thuộc kiểu dữ liệu nào chỉ có thể lưu giữ liệu của loại dữ liệu đó.
  • Mỗi biến chỉ khai báo 1 lần.
  • Có thể khai báo ở bất cứ nơi nào trong chương trình.
  • Không thể thay đổi kiểu của một biến trong chương trình.

Chú ý: Khi một biến được khai báo đồng nghĩa với việc cấp phát bộ nhớ và lưu trữ một giá trị rác cho đến khi bạn chỉ định một giá trị ban đầu. Điều quan trọng là trong C không có bất cứ cảnh báo hoặc báo lỗi nào nếu bạn sử dụng một biến trước khi khởi tạo giá trị cho nó, điều đó chắc chẵn dẫn đến một kết quả không mong muốn.

3.4 Hằng số (const)
Hằng số là các biến không thể điều chỉnh được (non-modifiable), khai báo bắt đầu với từ khóa const và không thể thay đổi giá trị trong quá trình thực hiện chương trình. Giá trị của hằng số phải được gán ngay khi khởi tạo.
Ví dụ:

const double PI = 3.1415926;

Quy ước đặt tên (Convention): Sử dụng các từ viết hoa và ngăn cách nhau bởi dấu gạch dưới. Ví dụ: MIN_VALUE, MAX_SIZE.

3.5 Biểu thức (Expressions)
Một biểu thức là sự kết hợp của các toán tử (+, – , * , /) và các toán hạng (biến hoặc giá trị xác định), có thể được tính toán và trả về một giá trị duy nhất của một kiểu dữ liệu nhất định.

3.6 Phép gán (=)
Một lệnh gán:

  • Gán một giá trị dạng văn bản (của vế phải) cho một biến số (của vế trái)
  • Đánh giá một biểu thức (của vế phải) và chỉ định giá trị kết quả cho một biến số (của vế trái)

Vế phải: là một giá trịVế trái: là một biến hoặc địa chỉ vùng nhớ
Ví dụ:

number = 88;
sum = sum + number;

3.7 Các thành phần cơ bản trong C
* Interger (Số Nguyên): C hỗ trợ các kiểu số nguyên: char, short, int, long, long long (C11). Các số nguyên (ngoại trừ kiểu char) có thể lưu trữ giá trị là số âm, số 0, số dương. Bạn có thể sử dụng từ khóa unsigned [char, short, in, long, long long] để khai báo một số nguyên unsigned (lưu trữ dữ liệu số không và số dương). Có tổng cộng 10 loại số nguyên signed|unsigned kết hợp với char, short, int, long, long long.

* Characters (Ký tự): Các ký tự (‘a’, ‘b’,’0’…) được mã hóa trong ASCII thành các số nguyên và lưu trữ trong kiểu char. Ví dụ: ký tự ‘0’ là 48(hệ thập phân) hay 30H (hệ thập lục phân). Lưu ý rằng kiểu dữ liệu char có thể được hiểu là ký tự trong mã ASCII hoặc là một số nguyên 8-bit.

* Floating-point Number (Số thực dấu phẩy động): Có 3 kiểu dữ liệu nổi bật: float, double, long double. Kiểu dữ liệu float và double được thể hiện theo chuẩn IEEE 754. Một kiểu dữ liệu sẽ đại diện cho một khoảng giá trị và mang tính chất xấp xỉ.

Ngoài ra, nhiều chức năng trong thư viện C sử dụng một kiểu dữ liệu được gọi là size_t, kiểu này tương đương(typedef) với unsigned int với ý nghĩa thể hiện đếm, kích thước, chiều dài với giá trị lưu trữ là 0 hoặc số nguyên dương

* Toán tử sizeof
Trong C cung cấp toán tử sizeof cho phép tính toán kích thước một toán hạng (theo byte)
Ví dụ: SizeofType.c
Kết quả này không giống nhau trên các nền tảng khác nhau.

* Tiêu đề <limits.h> và <float.h>
Tập tin tiêu đề <limits.h> chứa thông tin về giới hạn của các kiểu dữ liệu số nguyên(INT_MAX, INT_MIN, LLONG_MIN…). Các giá trị này cũng là không giống nhau trên các nền tảng khác nhau.
Tập tin tiêu đề <float.h> chứa giới hạn của số thực dấu phẩy động.

* Lựa chọn kiểu dữ liệu
Là một lập trình viên bạn phải quyết định kiểu dữ liệu của biến số bạn cần sử dụng trong chương trình của bạn. Dưới đây là quy tắc lựa chọn kiểu dữ liệu mà bạn có thể tham khảo:
Quy tắc Thumb:

  • Sử dụng int cho số nguyên và double cho số thực dấu phẩy động. Sử dụng char, short, long và float chỉ khi bạn có một lý do chính đáng để chọn với độ chính xác cụ thể.
  • Sử dụng int (or unsigned int) để counting (đếm) or indexing (chỉ mục). Không bao giờ lựa chọn kiểu số thực dấu phẩy động (float or double). Sử dụng kiểu số nguyên sẽ chính xác và hiệu quả hơn trọng hoạt động
  • Sử dụng một kiểu số nguyên nếu có thể, chỉ sử dụng kiểu dấu phẩy động khi có phân số.

* Lệnh typedef
Khi viết chương trình nếu thực hiện viết unsigned int nhiều lần sẽ cho bạn một cảm giác phiền nhiễu. Lệnh typedef có thể được sử dụng để tạo ra một tên mới đại diện cho một kiểu dữ liệu hiện có. Ví dụ bạn có thể định nghĩa một kiểu dữ liệu mới tên là uint đại diện cho kiểu unsigned int với cú pháp lệnh như sau:

typedef unsigned int uint;

Như một số trình biên dịch C xác định kiểu size_t là một typedef của unsigned int.

typedef unsigned int size_t;

Lưu ý: Bạn nên đặt typedef ngay sau #include. Sử dụng typedef cẩn thận vì nó có thể làm cho chương trình khó đọc, hiểu.

3.8 Phương thức printf()
C cung cấp phương thức printf() cho phép in dữ liệu ra màn hình console. Để sử dụng hàm printf() bạn cần include thư viện <stdio.h>.
Lưu ý: mặc định con trỏ được đặt ở cuối chuỗi in ra và không tiến đến dòng tiếp theo. bạn cần thêm ký tự new-line để con trỏ ở đầu dòng kế tiếp.
Định dạng đầu ra cho hàm printf()

printf(formattingString, variable1, variable2, ...)

formattingString là một chuỗi văn bản bình thường kết hợp với thành phần chuyển đổi. Các văn bản bình thường sẽ được in theo đúng nguyên mẫu. Các thành phần chuyển đổi sẽ được bắt đầu với ký tự “%” theo sau là một mã để xác định kiểu biến và định dạng đầu ra.

Bảng mã chuyển đổi:

Type
Type Conversion Code
Type & Format

Integers
%d (or %i)
(signed) int
%u
unsigned int
%o
int in octal

%x, %X
int in hexadecimal (%X uses uppercase A-F)

%hd, %hu
short, unsigned short
%ld, %lu
long, unsigned long
%lld, %llu
long long, unsigned long long

Floating-point
%f
float in fixed notation

%e, %E
float in scientific notation

%g, %G
float in fixed/scientific notation depending on its value

%f, %lf (printf), %lf (scanf)
double: Use %f or %lf in printf(), but %lf in scanf().

%Lf, %Le, %LE, %Lg, %LG
long double

Character
%c
char

String
%s
string

3.9 Phương thức scanf() Trong C hỗ trợ phương thức scanf() thuộc thư viện <stdio.h> cho phép đọc đầu vào từ bàn phím. scanf() sử dụng mã chuyển đổi tương tự như với printf(). Ví dụ: TestScanf.c
Lưu ý:

  • Cần thêm tiền tố & trước tên biến trong hàm scanf.
  • Đối với kiểu dữ liệu double phải sử dụng mã chuyển đổi là %lf.

Giá trị trả về của hàm scanf(): trả về một int cho biến số lượng các giá trị được đọc.

4. Toán tử

4.1 Toán tử toán học

4.2 Biểu thức toán học

4.3 Trộn kiểu dữ liệu
Xem lại bài Giới thiệu về lập trình C.

4.4 Overflow / Underflow Nghiên cứu đầu ra của chương trình sau đây: TestOverflow.c
Trong phép tính số học, các giá trị kết quả nên thuộc phạm vi giá trị mà kiểu dữ liệu nó lưu trữ. Nếu giá trị đó vượt quá phạm vi của nó (overflow – underflow). Trong C không có thông báo lỗi hay cảnh báo nào nhưng khi hoạt động sẽ có kết quả không chính xác. Việc kiểm tra overflow/underflow là trách nhiệm của lập trình viên.

4.5 Toán tử gán mở rộng
Ngoài toán tử gán thông thường “=” trong C cung cấp các toán tử gán được gọi là compound asignment operators.
Bảng các toàn tử:

Operator
Usage
Description
Example

=
var = expr
Assign the value of the LHS to the variable at the RHS
x = 5;

+=
var += expr
same as var = var + expr
x += 5; same as x = x + 5

-=
var -= expr
same as var = var – expr
x -= 5; same as x = x – 5

*=
var *= expr
same as var = var * expr
x *= 5; same as x = x * 5

/=
var /= expr
same as var = var / expr
x /= 5; same as x = x / 5

%=
var %= expr
same as var = var % expr
x %= 5; same as x = x % 5

4.6 Toán tử tăng/ giảm (increment/decrement)

Trong C hỗ trợ 2 toán tử số học: increment ‘++’ và decrement ‘–‘
Bảng giải thích:

Operator
Example
Result

++
x++; ++x
Increment by 1, same as x += 1


x–; –x
Decrement by 1, same as x -= 1

Các toàn tử này có thể được đặt ở trước hoặc sau toán hạng. Việc đặt trước các toán hạng (tiền tố) và đặt sau (hậu tố) có ý nghĩa khác nhau khi hoạt động.

Operator
Description
Example
Result

++var
Pre-Increment Increment var, then use the new value of var
y = ++x;
same as x=x+1; y=x;

var++
Post-Increment Use the old value of var, then increment var
y = x++;
same as oldX=x; x=x+1; y=oldX;

–var
Pre-Decrement
y = –x;
same as x=x-1; y=x;

var–
Post-Decrement
y = x–;
same as oldX=x; x=x-1; y=oldX;

Ví dụ:

x = 5;
printf("%d\n", x++);   
x = 5;
printf("%d\n", ++x);   
 

4.7 Chuyển đổi và ép kiểu dữ liệu
Chuyển đổi dữ liệu từ một kiểu dữ liệu này sang một kiểu dữ liệu khác được gọi là ép kiểu. Có 2 loại ép kiểu:

  • Chuyển đổi ngầm định được thực hiển bởi trình biên dịch một cách tự động
  • Ép kiểu tường mình thông qua toán tử type-casting đặt trước biến số

4.8 Toán tử quan hệ và logic
Bảng toán tử so sánh (toán tử quan hệ – comparison operator – relational operators)

Operator
Description
Usage
Example (x=5, y=8)

==
Equal to
expr1 == expr2
(x == y) → false

!=
Not Equal to
expr1 != expr2
(x != y) → true

>
Greater than
expr1 > expr2
(x > y) → false

>=
Greater than or equal to
expr1 >= expr2
(x >= 5) → true

<
Less than
expr1 < expr2
(y < 8) → false

<=
Less than or equal to
expr1 >= expr2
(y <= 8) → true

Bảng toán tử logic

Operator
Description
Usage

&&
Logical AND
expr1 && expr2

||
Logical OR
expr1 || expr2

!
Logical NOT
!expr

^
Logical XOR
expr1 ^ expr2

5. Cấu trúc điều khiển chương trình cơ bản (Flow Control)

Có 3 cấu trúc điều khiển chương trình cơ bản là : tuần tự, có điều kiện và vòng lặp như minh họa dưới đây:

5.1 Cấu trúc điều khiển tuần tự
Tuần tự là cấu trúc phổ biến nhất được sử dụng. Chương trình là một chuỗi các khối lệnh được thực hiện theo thứ tự mà chúng được viết từ trên xuống dưới một cách tuần tự

5.2 Cấu trúc điều khiển điều kiện
Có một vài loại điều kiện như: if-then, if-then-else, nested-if(if-elseif-elseif-…else), switch-case và conditional expression (biểu thức điều kiện).

“switch-case” là một cách thay thế cho “nested-if”. Khi sử dụng switch-case, lệnh break là cần thiết cho mỗi trường hợp. Nếu thiếu break, chương trình sẽ thực hiện các trường hợp phía sau đó.

Biểu thức điều kiện: Một biểu thức điều kiện được tạo thành bởi 3 toán hạng với cú pháp:

booleanExpr ? trueExpr : falseExpr

5.3 Cấu trúc vòng lặp
Có một vài kiểu vòng lặp trong C như: for-loop, while-do, do-while

5.4 Ngắt cấu trúc lặp với break và continue (Interrupting Loop Flow)
Lệnh break thông báo hủy bỏ vòng lặp và thoát ra khỏi vòng lặp hiện tại.
Lệnh continue thông báo hủy bỏ lượt thực hiện đang chạy và bắt đầu một lượt kế tiếp của vòng lặp.
Sử dụng break, continue là các cấu trúc không tốt, chỉ nên sử dụng khi thực sự cần thiết. Bạn luôn có thể viết một chương trình tương tự mà không sử dụng break or continue.

5.5 Kết thúc chương trình
Trong C bạn có thể kết thúc chương trình trước khi nó hoàn tất các tập lệnh.
Trong C bạn có thể sử dụng 2 lệnh exit() hoặc abort() của thư viện <stdlib.h> để gọi kết thúc chương trình.

6. Chương trình hoàn chỉnh

Điều quan trọng khi viết một chương trình là tạo ra một kết quả chính xác. Nó cũng quan trọng khi không chỉ bạn mà người khác đều có thể đọc và hiểu chương trình một các dễ dàng. Chương trình như vậy được coi là một chương trình tốt.

Dưới đây là một vài gợi ý dành cho lập trình viên khi viết chương trình:

  • Tuân thủ các quy ước (convention).
  • Định dạng bố cục mã nguồn với một quy định thích hợp (Có thể dùng định dạng tự động do IDE hỗ trợ).
  • Chọn những định danh tốt mà nó mô tả đúng ý nghĩa việc sử dụng. Tránh dùng các chữ đơn trong bảng chữ cái, ngoại trừ các tên cho trục tọa độ x, y, z hay i, j dùng cho biến đếm.
  • Comments để giải thích ý nghĩa chương trình đặc biệt ở các vị trí quan trọng.
  • Viết tài liệu hướng dẫn chương trình trong khi viết chương trình.
  • Tránh sử dụng break, continue vì đây là các cấu trúc không tốt.

6.1 Lỗi lập trình
Có 3 loại lỗi trong lập trình:

  1. Lỗi cú pháp (Compilation Error or Syntax Error): có thể được sửa chữa một cách dễ dàng.
  2. Lỗi trong khi chạy chương trình (Runtime Error): Chương trình dừng trước khi đưa ra một kết quả chính xác – có thể được sửa chữa một cách dễ dàng.
  3. Lỗi logic : Chương trình hoạt động xong trả về một kết quả không chính xác. Cũng có thể xác định dễ dàng lỗi nếu chương trình luôn tạo ra kết quả sai và khó khăn nếu chương trình tạo ra kết quả chính xác tạm thời nhưng đôi khi kết quả lại không chính xác. Đây là loại lỗi rất nghiêm trọng nếu nó không được loại bỏ

Viết chương trình tốt sẽ giúp bạn giảm thiểu và phát hiện các lỗi này. Một điều quan trọng nữa là việc chạy thử nghiệm chương trình là cần thiết trước khi bạn muốn phát hành một phần mềm. Software testing là một chủ đề mở rộng nằm ngoài bài hướng dẫn này.

6.2 Gỡ lỗi chương trình
Dưới đây là các kỹ thuật gỡ lỗi thường gặp:

  1. Nhìn chằm chằm vào màn hình, dò theo từng dòng code :D. Đây không phải là cách hữu hiệu, các lỗi sẽ không xuất hiện ngay và nó là một cách cực kì khó khăn.
  2. Nghiên cứu các thông báo lỗi! Không nên đóng các bảng điều khiển khi lỗi xảy ra, mọi thông tin là hữu ích để bạn có thể xác định lỗi.
  3. Chèn các lệnh in ra giá trị kết quả trung gian ở những vị trí phù hợp. Nó hoạt động cho các chương trình đơn giản, nhưng không hiệu quả cho các chương trình phức tạp.
  4. Sử dụng một trình gỡ lỗi có hỗ trợ đồ họa. Đây là phương tiện hiệu quả nhất. Theo dõi chương trình (Trace program) thực thi từng bước (step-by-step) và xem xét giá trị các biến và đầu ra.
  5. Sử dụng những công cụ nâng cao. (Cho phép kiểm tra rò rỉ bộ nhớ và sử dụng chức năng).
  6. Kiểm nghiệm thực tế chương trình để loại bỏ lỗi logic.

7. Mảng

7.1 Khai báo và sử dụng mảng
Khai báo:
Mảng là một danh sách các thành phần cùng một kiểu dữ liệu, xác định bởi một cặp dấu ngoặc vuông [ ]. Để sử dụng bạn cần khai báo các mảng với 3 yếu tố: tên mảng, loại dữ liệu và kích thước.
Cú pháp:

type arrayName[arraylength];

Ví dụ:

int marks[5];         
double numbers[10];  

 
#define SIZE 9
int numbers[SIZE];
 
 const int SIZE = 9;
float temps[SIZE];    
 
int size;
printf("Enter the length of the array: ");
scanf("%d", size);
float values[size];

Lưu ý: Trong C giá trị của các thành phần trong mảng không được xác định sau khi khai báo và bạn có thể khởi tạo giá trị cho mảng khi khai báo.
Ví dụ: TestArrayInit.c

Sử dụng mảng:
Bạn có thể truy vấn một phần tử trong mảng thông qua chỉ số kèm theo trong dấu ngoặc vuông. Chỉ số mảng trong C bắt đầu từ zero (0). Ví dụ: giả sử có mảng marks là một mảng int chứa 5 phần tử, như vậy 5 phần tử sẽ lần lượt là: marks[0], marks[1], marks[2], marks[3], marks[4].

Để khởi tạo một mảng bạn cần xác định kích thước (chiều dài) của mảng. Khi một mảng được tạo ra thì kích thước của nó là cố định và không thể thay đổi được. Đôi khi việc xác định kích thước mảng khá khó khăn (ví dụ: số lượng học sinh trong một lớp học). Tuy nhiên bạn cần phải ước tính kích thước phù hợp theo mục đích sử dụng. Đây có lẽ là nhược điểm lớn nhất của việc sử dụng mảng.

Trong lập trình bạn có thể xác định được kích thước của mảng (số lượng phần tử) bằng cách biểu thức sau: sizeof(arrayName)/sizeof(arrayName[0]). Trong đó: sizeof(arayName) là tổng bytes của mảng và sizeof(arrayName[0]) là số bytes của phần tử đầu tiên của mảng.

Chương trình trong C không thực hiện việc kiểm tra rằng buộc về chỉ số. Nói cách khác nếu chỉ số này vượt quá giới hạn về kích thước mảng thì sẽ không có một cảnh báo hay thông báo lỗi.
Đây là một đặc tính của C khác với các ngôn ngữ lập trình bậc cao như Java, C# (thực hiện kiểm tra chỉ số mảng khi sử dụng). Kiểm tra chỉ số sẽ giảm hiệu suất tính toán và quá trình thực hiện, tuy nhiên nó sẽ an toàn hơn khi lập trình.

7.2 Mảng và Lặp
Thông thường khi làm việc với Mảng lập trình viên sẽ sử dụng kết hợp với vòng lặp. Bạn có thể xử lý tất cả các phần tử của một mảng thông qua một vòng lặp.
Ví dụ: MeanStdArray.c

7.3 Mảng đa chiều
Ví dụ:

int[2][3] = { {11, 22, 33}, {44, 55, 66} };


Đối với mảng 2 chiều. Chỉ số đầu tiên là hàng, chỉ số thứ 2 là cột. Lập trình viên nên thao tác với các phần tử theo thứ tự hàng rồi tới cột.
Ví dụ: Test2DArray.c

8. Phương thức (hàm)

8.1 Tại sao cần phương thức?
Trong lập trình, đôi khi một phần nào đó của chương trình được sử dụng nhiều lần. Thay vì việc viết lại mã đó nhiều lần, cách tốt hơn là đặt chúng vào một chương trình con và sẽ được gọi mỗi khi bạn sử dụng. Phần chương trình còn này được gọi là method (trong Java) hay function (trong C/C++)
Lợi ích của việc sử dụng phương thức:

  1. Phân chia và quản lý: xây dựng một chương trình từ những thành phần nhỏ và đơn giản. Module hóa chương trình thành các khối thực hiện một nhiệm vụ nhất định.
  2. Tránh lặp mã: Sử dụng phương thức cho phép tối ưu hóa chương trình, tái sử dụng các đoạn mã.
  3. Tái sử dụng phần mềm: Tái sử dụng các phương thức trong các chương trình khác nhau bằng cách đóng gói chúng thành thư viện.

8.2 Sử dụng phương thức
Giả sử chúng ta cần tính diện tích của hình tròn nhiều lần với các bán kính khác nhau. Điều đó thực sự tốt khi chúng ta viết một phương thức getArea() và tái sử dụng khi cần thiết.

Chương trình minh họa: TestFunction.c
Trong ví dụ trên một phương thức với tên getArea() được định nghĩa. Phương thức này nhận một tham số đầu vào (kiểu double) sau đó thực hiện tính toán và trả về giá trị kết quả kiểu double. Ở ví dụ chức năng được gọi 3 lần với các đầu vào khác nhau.

Trong C, bạn cần khai báo một nguyên mẫu hàm (trước khi được sử dụng) và phải định nghĩa nguyên mẫu hàm đó.

* Định nghĩa phương thức:
Cú pháp:

returnValueType functionName ( parameterList ) {
   functionBody ;
}

Trong đó:
functionName: tên phương thức
ParameterList: danh sách các tham số đầu vào, được ngăn cách nhau bới dấu phẩy.
returnValueType: kiểu dữ liệu của kết quả trả về (int, double, …). Một kiểu giá trị trả về đặc biệt được gọi là void có thể được sử dụng để biểu thị rằng phương thức không có giá trị trả về. Trong C, một phương thức được phép trả về một giá trị hoặc không có giá trị(void). Nó không thể trả về nhiều giá trị. (C không cho phép trả về một mảng)

* Lệnh return
Bên trong thân hàm của một phương thức bạn có thể sử dụng lệnh return để trả về một giá trị.
Cú pháp:

return expression;    
return;              

Lưu ý rằng: khi gọi một phương thức (bới người gọi) đồng nghĩa với việc chuyển điều khiển về phương thức đó. Gọi lệnh return trong phương thức sẽ chuyển lại điều khiển lại cho người gọi.
Quy ước đặt tên phương thức:
Tên của một phương thức là một động từ hoặc cụm động từ bao gồm một hoặc nhiều từ ghép lại. Từ đầu tiên viết thường và các từ tiếp theo viết hoa chữ cái đầu tiên. Ví dụ: getArea(), setRadius(), moveDown(), isPrime(),, vv

* Prototype
Trong C, một phương thức cần được khai báo trước khi sử dụng. Có 2 cách để thực hiện điều đó, một là đặt định nghĩa hàm trước khi nó được sử dụng, hai là khai báo một nguyên mẫu hàm.
Một nguyên mẫu hàm thông báo với trình biên dịch về giao diện của phương thức như kiểu dữ liệu trả về, tên phương thức, đối số đầu vào. Phương thức sau đó có thể được định nghĩa ở bất cứ vị trí nào trong file mã nguồn.
Ví dụ:

 
double getArea(double);   
int max(int, int);

Lưu ý: trong nguyên mẫu hàm tên tham số là thông số tùy chọn bạn có thể viết hoặc không, trình biên dịch sẽ bỏ qua các tên tham số. Nó có vai trò như chỉ dẫn đầu vào của phương thức.
Ví dụ:

 
double getArea(double radius);   
int max(int number1, int number2);

Nguyên mẫu hàm thường được nhóm lại với nhau và đặt trong một tập tin header. Các tập tin header này sẽ được include trong nhiều chương trình.
Ví dụ: TestMaxFunction.c

* Phạm vi của biến và thống số trong phương thức
Tất cả các biến hay thông số bên trông một phương thức chỉ phục vụ riêng cho phương thức đó. Chúng được tạo ra khi phương thức được gọi và được giải phóng khi kết thúc phương thức. Các biến đó được gọi là biến cục bộ, được sử dụng bên trong phương thức và không có ý nghĩa bên ngoài phương thức. Một các gọi khác là các biến tự động (automatic variables) bởi vì nó được cấp phát và giải phòng một cách tự động mà lập trình viên không cần phải quan tâm.

8.3 Phương thức và Mảng
Mảng có thể làm đối số đầu vào cho một phương thức. Tuy nhiên, bạn cũng cần truyền kích thước mảng đi kèm. Điều này là cần thiết bởi không có cách nào để xác định kích thước mảng từ đối số mảng được truyền vào.
Ví dụ: SumArray.c

8.4 Pass-by-Value và Pass-by-Reference
Có 2 cách mà một tham số được truyền vào phương thức: pass by value và pass by reference.

* Pass-by-Value (Truyền vào giá trị)
Theo cách này, một bản sao của tham số được tạo ra và truyền vào phương thức. Phương thức hoạt động trên bản sao của tham số thực tế và không thể sửa đổi tham số gốc. Trong C, các kiểu dữ liệu cơ bản (int, double) được truyền theo cách này. Có nghĩa là không thể sửa đổi giá trị của tham số người gọi truyền vào bên trong phương thức.
Ví dụ: TestPassByValue.c

* Pass-by-Reference (Truyền vào tham chiếu)
Theo cách này, một tham chiếu của biến được được người gọi truyền vào phương thức. Nói cách khác phương thức sẽ hoạt động trên cùng một vùng dữ liệu (phù hợp trong một số trường hợp).
Trong C, mảng được truyền vào theo cách này. Nghĩa là bạn có thể sửa đổi giá trị của các phần tử trong mảng bên trong phương thức – điều này bạn cần phải lưu ý.
Ví dụ: IncrementArray.c

8.5 Tham số “const”
Pass-by-reference có rủi ro gây ra việc thay đổi dữ liệu gốc. Nếu bạn không có ý định thay đổi giá trị các thành phần của một mảng bên trong phương thức, bạn có thể dùng từ khóa const trước tham số đầu vào. Một const thông báo với phương thức tham số đó không được sửa đổi bên trong hàm.
Sử dụng const bất cứ khi nào tham số dạng tham chiếu vì điều này ngăn chặn trường hợp bạn vô tình sửa đổi tham số đầu vào và tránh gặp phải nhiều lỗi trong lập trình.
Ví dụ:
LinearSearch.c
BubbleSort.c
InsertionSort.c
SelectionSort

8.6 Phương thức toán học (Header <math.h>)
C cung cấp nhiều phương thức toán học thường được sử dụng trong thư viện <math.h>.
Ví dụ:

sin(x), cos(x), tan(x), asin(x), acos(x), atan(x):
   Take argument-type and return-type of float, double, long double.
atan2(y, x):
   Return arc-tan of y/x. Better than atan(x) for handling 90 degree.
sinh(x), cosh(x), tanh(x):
   hyper-trigonometric functions.
pow(x, y), sqrt(x):
   power and square root.
ceil(x), floor(x):
   returns the ceiling and floor integer of floating point number.
fabs(x), fmod(x, y):
   floating-point absolute and modulus.
exp(x), log(x), log10(x):
   exponent and logarithm functions.

9. Characters và Strings

Một String trong C là một mảng các ký tự và kết thúc bằng ký tự null, ký hiệu là ‘\0’ tương đương với mã ASCII 0. Ví dụ:

char message[] = {'H', 'e', 'l', 'l', 'o', '\0'};
char message[] = "Hello";      

Rõ ràng độ dài mảng bằng số ký tự + 1, để giải thích cho ký tự null kết thúc ở chuỗi.
Bạn có thể sử dụng scanf() để nhập hay lệnh printf() để in một chuỗi với phần chuyển đổi là %s.
Ví dụ: TestString.c

9.1 Character Type và Chuyển đổi dữ liệu với <ctype.h> Header
[TODO]
9.2 Chuyển đổi String/Number với <stdlib.h> Header

Function
Description

int atoi(const char * str);
double atof(const char * str);
long atol(const char * str);
long long atoll(const char * str);

String to int
String to double
String to long
String to long long
Convert the str to
int/double/long/long long.

double strtod(const char* str, char** endptr);
float strtof(const char* str, char** endptr);

String to double
String to float
Convert the str to double/float.
If endptr is not a null pointer,
it will be set to point to the first character after the number.

long strtol(const char* str, char** endptr, int base);
unsigned long strtoul(const char* str, char** endptr, int base);

String to long
String to unsigned long
Convert the str to long/unsigned long.

9.3 Thao tác String với <string.h> Header

Function
Description

char* strcpy(char* dest, const char* src);
char* strncpy(char* dest, const char* src, size_t n);

String copy
String copy at most n-chars
Copy src into dest.
Return dest.

char* strcat(char* dest, const char* src);
char* strncat(char* dest, const char* src, size_t n);

String concatenation
String concatenation at most n-char
Concatenate src into dest.
Return dest.

int strcmp(const char* s1, const char* s2);
int strncmp(const char* s1, const char* s2, size_t n);

String compare
String compare at most n-char
Comparing s1 and s2.
Return 0, less than 0, more than 0
if s1 is the same, less than, more than s2.

int strlen(const char* str);
String Length
Return the length of str
(excluding terminating null char)

char* strchr(const char* str, int c);
char* strrchr(const char* str, int c);

Search string for char
Search string for char reverse
Return a pointer to the first/last occurrence
of c in str
if present. Otherwise, return NULL.

char* strpbrk(const char* str, const char* pattern);
Search string for char in pattern
Locate the first occurrence in str
of any character in pattern.

char* strstr(const char* str, const char* substr);
Search string for sub-string
Return a pointer to the first occurrence
of substr in str
if present. Otherwise, return NULL.

char* strspn(const char* str, const char* substr);
char* strcspn(const char* str, const char* substr);

Search string for span of substr
Search string for complement span of substr

char* strtok(char* str, char *delimit);
Split string into tokens

void* memcpy(void *dest, const void *src, size_t n);
void* memmove(void *dest, const void *src, size_t n);
int memcmp(const void *p1, const void *p2, size_t n);
void* memchr(void *ptr, int value, size_t n);
void* memset(void *ptr, int value, size_t n);

Memory block copy
Memory block move
Memory block compare
Search memory block for char
Memory block set (fill)

9.4 Char/String IO với <stdio.h> Header

Function
Description

int getchar();
int putchar(int c);

Get character (from stdin)
Put character (to stdout)
Input/Output a character
from stdin/stdout.

int getc(FILE *stream);
int putc(int c, FILE *stream);
int ungetc(int c, FILE *stream);

Get character (from FILE stream)
Put character (to FILE stream)
Un-get character (to FILE stream)
Input/Output a character
from FILE stream.

char* gets(char *str);
int puts(const char* str);

Get string (from stdin)
Put string (to stdout)
Input/Output string
from stdin/stdout.

int sprintf(char *str, const char *format, ...);
int sscanf(char *str, const char *format, ....);

Formatted print (to string)
Formatted scan (from string)
Formatted string input/output.
Similar to printf() and scanf(),
except that the output/input comes from the str.

 

10. File Input/Output

File IO với Header
[TODO]
Open/Close File

Mode
Description

"r"
Read
Open file for reading. The file shall exist.

"w"
Write
Open file for writing. If the file does not exist, create a new file; otherwise, discard existing contents.

"a"
Append
Open file for writing. If the file does not exist, create a new file; otherwise, append to the existing file.

"r+"
Read/Write
Open file for reading/writing. The file shall exist.

"w+"
Read/Write
Open file for reading/writing. If the file does not exist, create a new file; otherwise, discard existing contents.

"a+"
Read/Append
Open file for reading/writing. If the file does not exist, create a new file; otherwise, append to the existing file.

"rb" "wb" "ab" "rb+" "wb+" "ab+"
For binary files.

11. Mở rộng

11.1 Các tập tin tiêu đề thư viện C

  • <stdio.h>

    : bao gồm các nguyên mẫu hàm cho việc nhập / xuất cơ bản như printf () và scanf ().

  • <stdlib.h>

    : chứa các nguyên mẫu hàm phục vụ chuyển đổi giữa dạng số và văn bản (ví dụ: atoi (), atof (), strtod ()); cấp phát bộ nhớ (malloc (), free()); tạo số ngẫu nhiên (rand (), srand ()); dịch vụ hệ thống (exit (), abort ()).

  • <math.h>

    : gồm các nguyên mẫu hàm toán học (ví dụ, pow (), sqrt () …).

  • <ctype.h>

    : (character type) để kiểm tra đặc tính của ký tự (isupper (), isalpha (), isspace () và chuyển đổi giữa các dạng (toupper (), tolower ()).

  • <limits.h>

    ,

    <float.h>

    : chứa các hàm về kích thước, giới hạn của các kiểu dữ liệu.

  • <string.h>

    : nguyên mẫu hàm cho việc xử lý chuỗi (ví dụ, strcpy (), strcat (), strcmp ()).

  • <time.h>

    : chứa các nguyên mẫu hàm cho các chức năng ngày và giờ (ví dụ: time()).

  • <assert.h>

    : để khẳng định (để hỗ trợ chẩn đoán).

  • <errno.h>

    : các nguyên mẫu hàm cho việc báo cáo lỗi.

  • <signal.h>

    : các phương thức cho việc xử lý tín hiệu.

  • <stdarg.h>

    : thư viện hỗ trợ xử lý các đối số.

  • <stddef.h>

    : chứa định nghĩa các kiểu dữ liệu phổ biến như size_t.

  • <locale.h>

    :

  • <setjmp.h>

    :

11.2 Keywords (Từ khóa)

ISO C90 (ANSI C89) có 32 từ khóa:

  • Type: int, double, long, char, float, short, unsigned, signed, typedef, sizeof (10).
  • Control: if, else, switch, case, break, default, for, do, while, continue, goto (11).
  • Function: return, void (2)
  • Data Structure: struct, enum, union (3)
  • Memory: auto, register, extern, const, volatile, static (6).

ISO C99 thêm 5 từ khóa, tổng 37 từ khóa:

  • _Bool, _Complex, _Imaginary, inline, restrict (5).

ISO C11 thêm 7 từ khóa, tổng 44 từ khóa:

  • _Alignas, _Alignof, _Atomic, _Generic, _Noreturn, _Static_assert, _Thread_local (7).

 

Share this:

Thích bài này:

Thích

Đang tải…