Tổng hợp bài tập lập trình C có lời giải Đầu tiên là bài truyền kiếp của lập trình là tìm ước chung lớn nhất và bội chung
nhỏ nhất của hai số:
PHP Code:
int UCLN(int a, int b)
{
while(a != b)
{
if(a > b)
a = a - b;
else
b = b - a;
}
return a;
}
int BCNN(int a, int b)
{
return a*b/UCLN(a,b);
}
void main()
{
int a, b;
printf("\nNhap a: ");
scanf("%d", &a);
printf("\nNhap b: ");
scanf("%d", &b);
int uc = UCLN(a,b);
int kq;
kq = SNT(n);
if(kq==1)
printf("So: %d là so nguyen to.", n);
else
printf("So: %d khong là so nguyen to.", n);
printf("\n");
}
Viết chương trình đếm số chữ số của một số nào đó, Ví dụ: 21432 trả về: 5 vì có
5 chữ số.
PHP Code:
//dem so chu so cua n vd: 12342 -> tra ve: 5
int Dem(int n)
{
int dem=0;
while(n != 0)
{
n=n/10;
dem ++;
}
return dem;
}
void main()
{
int n;
printf("Nhap n: ");
scanf("%d", &n);
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.
Thí dụ
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.
Các hàm có thể được viết ra bởi người lập trình hay được cung cấp sẵn bởi
các thư viện. Các thư viện cần được khai báo (sử dụng) bằng cách nêu tên
các tập tin tiêu dề trong câu lệnh dạng #include tập tin tiêu đề. Một số hàm
thư viện như là printf đã được định nghĩa bởi chuẩn C, chúng được tham
chiếu như là các hàm thư viện chuẩn.
int 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.
return 0;
}
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[sửa]
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 hợp là tương đương nhau.
Các mệnh đề biểu thức
Một câu lệnh (hay một mệnh đề) của C có dạng:
<biểu thức tùy chọn>;
là một mệnh đề biểu thức. Nếu biểu thức này không có nội dung (mà chỉ còn
lại dấu ; thì biểu thức được gọi là mệnh đề null (hay mệnh dề rỗng). (Theo
ngôn ngữ máy Assembler thì mệnh đề null sẽ tương đương với câu lệnh
NOP; chiếm 1 byte chỉ làm nhiệm vụ tăng địa chỉ của chồng (stack) lên 1 đơn
vị.)
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 ý:
Trong câu lệnh switch thì không cho phép có hai giá trị hằng bằng nhau.
Nghĩa là khi X được đánh giá thì chỉ có tối đa một mệnh đề con được
thực thi.
Các câu lệnh switch có thể được dùng trong dạng lồng vào nhau (nest),
một từ khóa case hay default sẽ thuộc vào câu lệnh switch bên trong
nhất (hay nhỏ nhất) chứa nó.
Một khi dòng điều khiển hoàn tất câu lệnh con M
i
thì nó sẽ tiếp tục thi
hành các câu lệnh con M
i+1
theo sau cho đến khi nó bị yêu cầu ngưng bởi
câu lệnh nhảy (mà thường dược dùng nhiều nhất là câu lệnh break)
> :
<mệnh đề M
3
>
default :
<mệnh đề M
default
>
}
Các mệnh đề tái lặp (hay vòng lặp)[sửa]
C có 3 dạng câu lệnh vòng lặp:
Vòng lặp do[sửa]
do
<mệnh đề>
while (<biểu thức>);
Trong mệnh đề này thì mệnh đề được thực thi lặp lại cho tới khi nào <biểu
thức> được đánh giá (hay có giá trị) là true. Một khi <biểu thức> không còn
có giá trị true nữa thì vòng lặp sẽ bị kết thúc.
Vòng lặp while
while (<biểu thức>)
<mệnh đề>
<mệnh đề> chỉ được thực thi hay thực thi lặp lại khi <biểu thức> có giá trị
là true. Nếu <biểu thức> có giá trị false thì câu lệnh sẽ bị kết thúc ngay lập
tức.
Vòng lặp for[sửa]
Dạng C89 của vòng lặp for là:
for (<biểu thức 1> ; <biểu thức 2> ; <biểu thức 3>)
<câu lệnh>
Nó đã được tổng quát hóa trong C99 thành:
for (<khai báo> <biểu thức 1> ; <biểu thức 2>)
này, dòng điều khiển sẽ chuyển đến mệnh đề nhãn.
continue[sửa]
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: ;
}
do {
/* */
cont: ;
} while (expression);
for (optional-expr; optexp2; optexp3) {
/* */
cont: ;
}
break[sửa]
Mệnh đề break dùng để kết thúc một câu lệnh vòng lặp hay câu
lệnh switch ngay lập tức và chuyển tiếp đến câu lệnh tiếp theo sau của
vòng lặp đó.
return[sửa]
Một hàm trả dòng điều khiển về nơi gọi nó bằng câu lệnh return. Khi
lệnh return được theo sau bởi một biểu thức thì biểu thức đó sẽ được
đánh giá và giá trị này sẽ được trả về cho nơi đã gọi hàm. Khi return
được gọi mà không có biểu thức đi kèm thì giá trị trả về là không xác
định.
Các phép toán[sửa]
Xem thêm bài chính Phép toán trong C và C++
Để tham khảo, sau đây là bảng thứ tự ưu tiên của các phép toán
*, /, 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
< và <=
> và >=
quan hệ nhỏ hơn/nhỏ hơn hay bằng
quan hệ lớn hơn/lớn hơn hay bằng
== và !=
bằng với/khác với
&
phép toán bit AND
^
phép toán bít XOR
|
phép toán bit OR
&&
phép toán bool AND
||
phép toán bool OR
?:
điều kiện tam phân
từ phải sang trái
=
+= và -=
*=, /=,
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.
Các kiểu nguyên có thể hoặc là có dấu ( signed) hay không dấu
(unsigned). Nếu không chỉ rõ khi khai báo thì mặc định (hiểu ngầm) sẽ là
loại có dấu. Một ngoại lệ là các kiểu char, signed char và unsigned
char đều khác nhau, và kiểu char có thể là loại có dấu hay không có
dấu.
Đối với loại có dấu, thì bit có nghĩa cao nhất được dùng để biểu thị dấu
(dương hay âm). Thí dụ một số nguyên 16-bit loại có dấu thì chỉ dùng 15
bit để biểu thị giá trị (tuyệt đối) của nó còn bit có nghĩa cao nhất lại được
dùng để cho biết đó là số dương hay âm. Đối với loại không dấu thì bit
này lại được dùng thêm vào để biểu thị giá trị.
1
= Trong các kiểu nguyên, độ lớn cao nhất biểu thị giá trị lớn nhất (tùy
theo có dấu hay không) mà nó biểu thị. (Xem thêm bản dưới đây)
2
= Từ khóa long long được đưa thêm vào trong chuẩn C99.
Các hằng số xác định các giá trị biên[sửa]
Tập tin tiêu đề chuẩn limits.h sẽ xác định các giá trị nhỏ nhất và lớn nhất
của các kiểu nguyên cơ bản cũng như là xác định các giới hạn khác. Tập
tin tiêu đề chuẩn float.h sẽ xác định các giá trị nhỏ nhất và lớn nhất của
các kiểu float, double, và long double. Nó cũng xác định các giới hạn
khác liên quan tới việc xử lý các giá trị của dấu chấm động (floating-point)
có độ chính xác đơn hay có độ chính xác kép như là chúng được định
nghĩa trong chuẩn IEEE 754.
hết, signed, hay int
signed int
INT_MIN
INT_MAX
unsigned
unsigned int
0
UINT_MAX
long
signed long int
LONG_MIN
LONG_MAX
unsigned long
unsigned long
int
0
ULONG_MAX
long long
1
signed long
long int
1
LLONG_MIN
2
LLONG_MAX
2
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
-128
127
unsigned
viết y hệt
8
1
0
255
char
short
signed short
int
16
2
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.
Trong trường hợp tổng quát, cụm từ giá trị tham chiếu được dùng để
nói về địa chỉ trong bộ nhớ của sự tham chiếu (hay tham chiếu ngược).
Sự tham chiếu ngược[sửa]
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[sửa]
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
nhau.
Chỉ số bắt đầu của một mảng là 0. Như vậy, chỉ số lớn nhất của một
mảng bằng tổng số các phần tử trong mảng trừ đi 1. Thí dụ mảng A có
10 phần tử thì giá trị của phần tử đầu tiên của A là A[0] và của phần tử
cuối dùng là A[9].
Một cách truy cập khác là dùng con trỏ số học để tham chiếu đến giá
trị của các phần tử 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[sửa]
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
C có hỗ trợ việc dùng mảng đa chiều. Việc định nghĩa chúng giống như là
tạo ra mảng của các mảng , mặc dù vậy trong thực tế nó không hoàn
toàn đúng. Cú pháp sau:
int array2D[số_hàng][số_cột];
sẽ định nghĩa một mảng hai chiều; chiều thứ nhất có số_hàng phần tử.
Chiều thứ hai sẽ có số_hàng * số_cột các phần tử—một tập hợp
của số_cột các phần tử mà mỗi phần tử là một chiều thứ nhất.
Các mảng đa chiều hoàn toàn có thể được xem như là dãy của các con
trỏ. Trong thí dụ trên, 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ự[sửa]
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
fclose
Tiêu chuẩn I/O[sửa]
Ba tiêu chuẩn dòng I/O được định nghĩa sẵn là:
stdin đầu vào chuẩn
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);
/* */
Các toán tử ngắn mạch: gồm phép và (&&) và phép hoặc (|| ).
Toán tử điều kiện (A?B:C): Giá trị của biểu thức A được đánh giá
trước. Nếu A là đúng 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[sửa]
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++;: