Cú pháp ngôn ngữ (lập trình) C doc - Pdf 14

Cú pháp ngôn ngữ (lập trình) C
là tập hợp các qui tắc nhằm xác định cách thức để viết và dịch trong ngôn ngữ lập trình C.
Thí dụ:
// Dòng này sẽ được bỏ qua (không đọc) bởi trình dịch.
/* Các dòng này
cũng được bỏ qua
bởi trình dịch */
(Tiếp tục mã C)
Các hàm
Cú pháp
Một hàm C phải bao gồm một kiểu trả về (kiểu đó trả về void nếu không có giá trị trả về), một
tên xác định, một danh sách các tham số để trong ngoặc đơn (nếu danh sách này không có tham
số nào thì ghi là
void bên trong dấu ngoặc), sau đó là khối các câu lệnh (hay khối mã) và/hay các
câu lệnh
return. (Nếu kiểu trả về là void thì mệnh đề này không bắt buộc phải có. Ngược lại,
cũng không bắt buộc chỉ có một câu lệnh
return mà tùy theo kỹ thuật, người lập trình có thể dẫn
dòng mã sao cho mọi hướng chẻ nhánh đều được trả về đúng kiểu.)
<kiểu_trả_về> tên_hàm(<danh sách tham số>)
{
<các_câu_lệnh>

return <biến (hay giá trị) có kiểu là kiểu_trả_về>;
}
Trong đó, <danh sách tham số> của N biến thì được khai báo như là kiểu dữ liệu và tách rời
nhau bởi dấu phẩy
,:
<kiểu_dữ_liệu> var1, var2, , varN ;
Toàn bộ danh sách này được đặt trong ngoặc đơn ngay sau tên_hàm.


Chú ý: phần mã trên đã được thử thành công dùng trình dịch GNU (cho ANSI C và C99)

Hàm chủ
main có kiểu trả về là void nên không cần câu lệnh return.
Mô tả
Trong các câu lệnh tiền xử lý, ở cấp độ cao nhất, một chương trình ngôn ngữ C luôn có một
chuỗi các khai báo cho các
tập tin bao gồm.
Sau đó là các khai báo của phạm vi tập tin. Các khai báo này giới thiệu các hàm, các biến và các
kiểu biến. Các hàm trong C nhìn tương tự với các chương trình con của
Fortran hay các thủ tục
của
Pascal. Định nghĩa của hàm xuất hiện trong phần thân của nó (phần giữa bộ dấu ngoặc { và }
theo sau nguyên dạng của hàm).

Các chương trình trong C để tạo các ứng dụng trực tiếp đều cần phải có một hàm đặc biệt tên là
main, đây sẽ là hàm đầu tiên được gọi khi chương trình bắt đầu thực thi. Sau đây là một chương
trình đầy đủ mặc dù không có mấy ứng đụng thiết thực.
int main (void)
{
return 0;
}
Hàm code>main thường gọi các hàm khác để giúp nó hoàn tất công việc (tuỳ theo sự lập trình
của người dùng).
Trọng một số trường hợp C được dùng không phải để tạo ra các ứng dụng trực tiếp mà để dùng
với
hệ điều hành hay các nơi khác (như là phát triển các bộ điều vận, các phần sụn, hay các thư
viện ). Những trường hợp như vậy thì người lập trình hoàn toàn tự do trong việc giải quyết làm
sao để xử lý khởi động chương trình, đặc biệt nó sẽ không cần định nghĩa hàm
main.

chiếu cho con trỏ (tham chiếu này chỉ đến điạ chỉ của con trỏ):
void setInt(int **p, int n)
{
*p = (int *) malloc(sizeof(int)); // đăng kí một vùng nhớ
*p = n; // cài giá trị vào
}

void main(void)
{
int *p; //khai báo một con trỏ kiểu integer
setInt(&p, 42); // chuyển giá trị của 'p' vào.
}
int **p
sẽ định nghĩa một con trỏ chỉ đến con trỏ (thay vì chỉ đến các kiểu dữ liệu thông
thường) tức là chỉ đến địa chỉ của con trỏ
p.
Hàm
scanf làm việc theo cùng một cách thức:
int x;
scanf("%d", &x);
Các cấu trúc dòng điều khiển
Một cách cơ bản thì C là ngôn ngữ dạng tự do. Trong phần này, tất cả các chữ "mệnh đề" có
nghĩa tương đương với chữ "
câu lệnh".
Các mệnh đề phức hợp
Câu lệnh phức hợp được bọc trong dấu ngoặc { và } còn được gọi là khối mã. Các câu lệnh
phức hợp trong C có dạng.
{ <danh sách khai báo tùy chọn> <đanh sách câu lệnh tùy chọn> }
Khối mã được dùng như là phần thân của một hàm hay đưọc đặt bất kì ở vị trí nào mà một câu
lệnh đơn giản có thể đặt. Nghĩa là, về ý nghĩa văn phạm thì câu lệnh đơn giản và câu lệnh phức

{ và }.
Dạng dùng từ khóa
switch - case
Mệnh đề
switch sẽ gây ra việc chuyển dòng điều khiển sang một trong những mệnh đề con kế
tiếp tùy theo giá trị của một
biểu thức X (biểu thức này phải có kiểu nguyên). Các mệnh đề con
này thường là các mệnh đề phức hợp. Đứng trước mỗi mệnh đề con sẽ là một từ khóa
case, sau
đó là một biểu thức hằng H
i
, và dấu hai chấm : gắn liền tiếp theo đó là mệnh đề con M
i
.
Khi giá trị của X trùng với một giá trị H
i
được nêu ở đâu thì mệnh đề con đi gắn liền vói hằng tại
đó (tức là M
i
) sẽ được thực thi.
Nếu X không bằng với bất kì giá trị H
i
nào thì người lập trình có thể dùng thêm từ khóa
default, sau đó là dấu hai chấm : và tiếp theo là một mệnh đề con M
default
. Mệnh đề con này sẽ
được thực thi khi mà giá trị của X khác với mọi giá trị hằng H
i
.
Lưu ý:

> :
<mệnh đề M
1
>
case <hằng H
2
> :
<mệnh đề M
2
>
break;
case <hằng H
3
> :
<mệnh đề M
3
>
default :
<mệnh đề M
default
>
}

Các mệnh đề tái lặp (hay vòng lặp)
C có 3 dạng câu lệnh vòng lặp:
Vòng lặp
do
do
<mệnh đề>
while (<biểu thức>);

có thể làm cho vòng lặp biến thành vòng lặp vô hạn.
Thí dụ: vòng lặp for sau đây 3 biểu thức ở dạng phức hợp và ngăn cách nhau bởi dấu
chấm phẩy
;:
for (x=10,y=1;((x>4) && (y<8)); x ,y+=2)
printf("x = %d, y = %d \n", x,y);
Kết quả thực thi màn hình sẽ hiển thị như sau:
x = 10, y = 1
x = 9, y = 3
x = 8, y = 5
x = 7, y = 7
Vòng lặp kết thúc vì điều kiện trong biểu thức thứ nhì ((x>4) && (y<8)) không còn đúng nữa.
Các mệnh đề nhảy (hay bước nhảy)
Các lệnh nhảy sẽ thay đổi dòng điều khiển một cách vô điều kiện. có 4 kiểu mệnh đề nhảy trong
C là
goto, continue, break, và return.
goto
câu lệnh
goto sẽ có dạng:
goto <nhãn>;
nhãn phải có mặt trong hàm có chứa câu lệnh goto. Khi đọc đến lệnh này, dòng điều khiển sẽ
chuyển đến mệnh đề nhãn.
continue
Mệnh đề
continue chỉ có thể xuất hiện trong một vòng lặp và có tác dụng làm cho dòng điều
khiển chuyển sang chu kì mới của vòng lặp trong cùng nhất (có chứa câu lệnh). Các dạng sử
dụng bao gồm
while (expression) {
/* */
cont: ;

ngoặc đơn (nhóm)
phần chỉ số của mảng
sự lựa chọn phần tử, nhận dạng
sự lựa chọn phần tử, con trỏ
từ trái sang phải
++ và
+ và -
! và ~
(cast)
*
&
sizeof
tiền tố tăng/giảm
dấu dương/âm
phép toán Bool
NOT/phần bù 0 kiểu bit
đổi kiểu
tham chiếu ngược
tham chiếu
độ lớn
từ phải sang trái
*, /, và %
nhân/chia/mô dun
từ trái sang phải
+ và -
cộng/trừ
<< và >>
phép toán bit <ocde>left shift/right shift
<


shift
phép gán bit AND/XOR/OR
,
toán tử ,
từ trái sang phải
Nguồn: C Operator Precedence and Associativity
Khai báo dữ liệu
Các kiểu dữ liệu cơ bản
Nhiều ngôn ngữ lập trình kể cả C, biểu thị các số trong hai dạng: nguyên và thực (hay không
nguyên). Sự khác nhau này hình thành từ khía cạnh kỹ thuật của các cách thức xử lý và lưu trữ
các giá trị trong bộ nhớ.
Kiểu nguyên viết dưới dạng
int được dùng để biểu thị các số nguyên. Kiểu nguyên có trong
nhiều kích cỡ khác nhau tùy theo phân lượng bộ nhớ được dùng và độ lớn cao nhất
1
. Các từ
khóa, có tên là các định tính, được dùng thêm vào để điều chỉnh lại kích cỡ là:
short, long và
long long
2
. Kiểu kí tự mà từ khóa của nó là char, biểu thị đơn vị nhỏ nhất có thể địa chỉ hóa
được (bởi
kiến trúc máy tính) thường là một byte với 8 bit.
Dạng thực được dùng để biểu thị các số thập phân hay các bộ phận hữu tỉ. Mặc dù vậy chúng
không hoàn toàn chính xác mà chỉ là các biểu thị gần đúng.
Có 3 kiểu giá trị thực bao gồm: loại có độ chính xác đơn (có đặc tả là
float), loại có độ chính
xác kép (có đặc tả là
double), và loại có độ chính xác kép mở rộng (có đặc tả là long double).
Mỗi loại dùng để biểu thị các giá trị không nguyên trong một dạng khác nhau.

nhất
char
viết y hệt
CHAR_MIN
CHAR_MAX
signed char
viết y hệt
SCHAR_MIN
SCHAR_MAX
unsigned char
viết y hệt
0
UCHAR_MAX
short
signed short int
SHRT_MIN
SHRT_MAX
unsigned short
unsigned short int
0
USHRT_MAX
không viết gì hết,
signed
, hay
int
signed int INT_MIN INT_MAX
unsigned
unsigned int
0
UINT_MAX

1
— Định tính long long chỉ được hỗ trợ trong các trình dịch thỏa mãn chuẩn C99.
2
— Các hằng LLONG_MIN, LLONG_MAX, và ULLONG_MAX chỉ được định nghĩa trong limits.h nếu trình dịch
tương ứng thỏa mãn chuẩn C99.

Các giá trị biên điển hình
Sau đây là danh sách kích cỡ và các biên điển hình của các kiểu nguyên. Các giá trị này có thể
khác nhau tùy theo kiến trúc (máy và trình dịch).
ISO C cung cấp tiêu đề inttypes.h, trong đó, có
xác định các kiểu nguyên có dấu và không có dấu nhưng điều chắc chắn là kích cỡ đều nằm
trong khoảng 8 đến 64 bit.
Kích cỡ và biên điển hình của các kiểu nguyên
Đặc tả hiểu
ngầm
Đặc tả viết rõ ra
Số
bit
Số
byte
Giá trị nhỏ nhất Giá trị lớn nhất
char
viết y hệt
8
1
-128 or 0
127 hay 255
signed char
viết y hệt
8

1

signed long long
int
1

64 8
-9 223 372 036
854 775 808
9 223 372 036 854
775 807
unsigned long
long
1

unsigned long
long int
1

64 8 0
18 446 744 073
709 551 615
1
— Định tính long long chỉ được hỗ trợ trong các trình dịch thỏa mãn chuẩn C99.
Kích cỡ và giới hạn của kiểu cơ bản
int (mà không có các định tính short, long, hay long
long
) có thể thay đổi khác nhau (nhiều hơn các kiểu nguyên khác) tùy theo sư thiết lập (của trình
dịch hay của kiến trúc máy). Cho nên, một thao tác thông thường là định nghĩa nó như là một
đồng nghĩa của

nhớ của sự tham chiếu (hay tham chiếu ngược).
Sự tham chiếu ngược
Cùng một biểu hiện, giá trị có thể đọc về từ một giá trị tham chiếu. Trong thí dụ sau, biến nguyên
cơ bản
b sẽ được gán đến dữ liệu mà dữ liệu đó được tham chiếu bởi ptr:
int *ptr;
int a, b;

a = 10; //gán cho a giá trị là 10
ptr = &a; //phép gán địa chỉ của a (tức là &a) lên con trỏ 'ptr'
//để ptr bây giờ chỉ đến địa chỉ có nội dung là 10
b = *ptr //phép gán này cho b một giá trị nằm ở địa chỉ mà 'ptr'
//chỉ tới tức là giá trị của b sẽ là 10
Để hoàn tất được thao tác này, toán tử tham chiếu ngược (&) đã được dùng. Nó trả về dữ liệu cơ
bản. Dữ liệu này có giá trị là một tham chiếu chỉ tới (tức là một địa chỉ). Biểu thức
*ptr là một
cách viết khác của giá trị 10 (gán cho b).
Việc quá tải của kí tự * có hai biểu hiện liên hệ mà có thể gây ra sự nhầm lẫn. Hiểu được sự khác
nhau giữa việc dùng nó như là một tiền tố trong một khai báo (con trỏ) và việc xem nó là toán tử
tham chiếu trong một biểu thức là rất quan trọng.
Sự tham chiếu tương đương và các mệnh đề cơ bản
Bảng sau đây là danh sách các mệnh đề tương đương giữa kiểu cơ bản và kiểu tham chiếu (hay
tham chiếu ngược). Trong đó, biến cơ bản
d và biến tham chiếu ptr được hiểu ngầm.
Các mệnh đề cơ bản và tham chiếu tương đương

Đến một giá trị cơ bản
Đến một giá trị tham chiếu
Từ một giá trị cơ bản
d

trong mảng.
Bảng sau đây sẽ minh họa cách dùng của cả hai phương pháp:
Array Chỉ số và con trỏ số học
Phần tử vị trí
0
1
2
n
Kiểu dùng cơ bản
array[0]
array[1]
array[2]
array[n]
Dùng con trỏ
*array
*(array + 1)
*(array + 2)
*(array + n)
Các mảng động
C không cung cấp phương tiện để kiểm tra biên cho các mảng. Nghĩa là nó không thể bắt được
các lỗi khi gán cho một mảng chỉ số âm hay chỉ số vượt quá độ đài của mảng đó. Và hơn thế nữa
các chỉ số trong một mảng có thể vượt khỏi độ dài sẵn có của mảng đó.
Vì các mảng là thuần nhất, tức là nó chỉ chứa các dữ liệu có chung một kiểu nên hai thành phần
thông tin cần nhớ là địa chỉ của phần tử đầu tiên và kiểu của dữ liệu.
Nhắc lại về cú pháp để khai báo một mảng tĩnh, tức là tạo ra một biến tham chiếu nguyên và cấp
phát một vùng nhớ tương ứng cho nó:
int array[n];
Cách biểu hiện này có thể được tái lập với sự giúp đỡ của thư viện chuẩn C. Hàm calloc cung
cấp một cách đơn giản để cấp phát một vùng nhớ. Hai tham số dùng đến sẽ là số lượng các phần
tử và kích cỡ (độ lớn) của mỗi phần tử. Khi việc cấp phát bộ nhớ hoàn thành,

array2D (nếu số_hàng là 1) sẽ là một tham chiếu giá trị nguyên mà nó chỉ tới một mảng của
số_cột các phần tử.

Dãy kí tự
Dãy kí tự có thể được thay đổi nội dung của nó mà không cần đến thư viện chuẩn. Tuy nhiên, thư
viện này có nhiều hàm có thể dùng cho cả dãy kí tự có kết thúc 0 và mảng không có kí tự kết
thúc kiểu
char. Trong phần này từ "dãy" được để chỉ dãy kí tự.
Các hàm thường dùng là:
• strcat(dest, source) - nối một dãy kí tự source tiếp vào vị trí cuối của dãy kí tự
dest
• strchr(source, c) - tìm vị trí sự xuất hiện đầu tiên của c trong dãy kí tự source và trả
về con trỏ chỉ tới vị trí đó hay con trỏ trống nếu
c không tìm thấy trong source
• strcmp(a, b) - so sánh hai dãy kí tự a và b (theo thứ tự từ điển); trả về số âm nếu a nhỏ
hơn
b, 0 nếu chúng bằng nhau, dương nếu a lớn hơn
• strcpy(dest, source) - chép và thay các kí tự của dãy source vào dãy dest
• strlen(st) - trả về độ dài của st
• strncat(dest, source, n) - nối tối đa n kí tự từ dãy source tiếp vào vị trí cuối của
dãy
dest; các kí tự sau dấu kết thúc null sẽ không được chép vào
• strncmp(a, b, n) - so sánh từ kí tự đầu cho đến tối đa n kí tự từ hai dãy a và b (theo
thứ tự từ điển); hàm trả về số âm nếu phần so sánh của
a nhỏ hơn b, 0 nếu bằng nhau, và
dương nếu lớn hơn
• strncpy(dest, source, n) - chép từ đầu đến tối đa n kí tự từ dãy source vào dãy
dest
• strrchr(source, c) - tìm vị trí hiện lần cuối cùng của kí tự c trong dãy source và trả
về một con trỏ chỉ vào vị trí đó hay con trỏ trống nếu không tìm thấy

• stdout đầu ra chuẩn
• stderr lỗi chuẩn
Các dòng này được tự động mở và đóng lại bởi
môi trường của thời gian thi hành, chúng không
cần và không nên được mở một cách rõ ràng.
Thí dụ sau minh họa làm thế nào một
chương trình bộ lọc được cấu trúc một cách điển hình:

#include <stdio.h>

int main()
{
int c;

while ((c = getchar()) != EOF) {
/* do various things
to the characters */

if (anErrorOccurs) {
fputs("an error eee occurred\n", stderr);
break;
}

/* */
putchar(c);
/* */

}
return 0;
}

thì B sẽ được đánh giá bỏ qua biểu thức C. Nếu A sai thì B bị bỏ qua và chỉ có C được
đánh giá tiếp.

Lưu ý: Các biểu thức đứng trước trong một dãy điểm sẽ luôn luôn được đánh giá trước các biểu
thức theo sau. Trong trường hợp đánh giá của các ngắn mạch, biểu thức thứ hai có thể sẽ không
cần được đánh giá. Thí dụ, trong biểu thức
(a() || b()), nếu a() trả về giá trị đúng thì trình
dịch sẽ không cần đánh giá
b() nữa (vì lúc đó đã đủ để kết luận mệnh đề (a() || b()) là
đúng). Trong thực hành viết mã, nhiều lập trình viên thiếu kinh nghiệm rất dể bị lọt vào tình
huống rằng trình dịch không chịu tiến hành những gì họ muốn mà bỏ qua nhiều bước dẫn tới các
kết quả không chính xác mặc dù về lô gíc họ không hề sai sót. Trong trường hợp như vậy, cách
tốt nhất là hãy xem lại thật kỹ các mệnh đề Bool và đặc tính đánh giá này của trình dịch. Các lỗi
này thuộc loại rất khó tìm ra bởi vì nó hoàn toàn chính xác về mặt cú pháp, về mặt toán học và
ngay cả về mặt thuật toán xử lý và rất dể dẫn đến nhiều kết quả sai về mặt tính toán mà người lập
trình không ngờ. Đây cũng là điểm khác nhau giữa ngôn ngữ toán học thuần túy (dùng trong các
mã giả pseudo code ) và thực tế của ngôn ngữ lập trình.
Ứng xử không xác định
Một khía cạnh thú vị (mặc dù chắc không đơn nhất) của tiêu chuẩn C là ứng xử của một số dạng
mã chắc chắn dẫn tới tình trạng không xác định. Trong thực tế, điều này có nghĩa là chương trình
tạo ra từ mã này có thể làm bất kì gì từ việc thực thi đúng theo ý muốn cho đến việc hư hỏng mỗi
lần nó chạy.
Thí dụ: mã sau đây gây ra ứng xử không xác định, vì biến
b được dùng tới nhiều hơn một lần
(đồng thời lại có sự biến đổi của chính
b trong lúc tính toán) qua biểu thức a = b + b++;:
#include <stdio.h>

int main (void)
{


Nhờ tải bản gốc

Tài liệu, ebook tham khảo khác

Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status