Tài liệu Giáo trình: Lập trình cơ bản (Phần 2): Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
52
CHƯƠNG 4: HÀM
1. Khái niệm hàm
1.1Khái niệm và phân loại
Một chương trình viết trong ngôn ngữ C là một dãy các hàm, trong đó có một
hàm chính(hàm main). Hàm chia các bài toán lớn thành các công việc nhỏ, có thể có
những đoạn chương trình viết lặp đi lặp lại nhiều lần, để tránh rườm rà và mất thời
gian khi viết chương trình; người ta thường phân chia chương trình thành nhiều
module, mỗi module giải quyết một công việc vào đó. Thứ tự các hàm trong chương
trình là bất kỳ, song chương trình bao giờ cũng đi thực hiện từ main().
Trong C, chương trình con được gọi là hàm. Hàm trong C có thể trả về kết quả
thông quan tên hàm hay có thể không trả về kết quả.
Hàm có hai loại: Hàm chuẩn và hàm tự định nghĩa. Trong chương này ta chú trọng
đến cách định nghĩa hàm và cách sử dụng các hàm đó.
Hàm thư viện
Hàm thư viện là những hàm đã được định nghĩa sẵn trong một thư viện nào đó,
muốn sử dụng các hàm thư...
53 trang |
Chia sẻ: honghanh66 | Lượt xem: 903 | Lượt tải: 0
Bạn đang xem trước 20 trang mẫu tài liệu Giáo trình: Lập trình cơ bản (Phần 2), để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
52
CHƯƠNG 4: HÀM
1. Khái niệm hàm
1.1Khái niệm và phân loại
Một chương trình viết trong ngôn ngữ C là một dãy các hàm, trong đó có một
hàm chính(hàm main). Hàm chia các bài toán lớn thành các công việc nhỏ, có thể có
những đoạn chương trình viết lặp đi lặp lại nhiều lần, để tránh rườm rà và mất thời
gian khi viết chương trình; người ta thường phân chia chương trình thành nhiều
module, mỗi module giải quyết một công việc vào đó. Thứ tự các hàm trong chương
trình là bất kỳ, song chương trình bao giờ cũng đi thực hiện từ main().
Trong C, chương trình con được gọi là hàm. Hàm trong C có thể trả về kết quả
thông quan tên hàm hay có thể không trả về kết quả.
Hàm có hai loại: Hàm chuẩn và hàm tự định nghĩa. Trong chương này ta chú trọng
đến cách định nghĩa hàm và cách sử dụng các hàm đó.
Hàm thư viện
Hàm thư viện là những hàm đã được định nghĩa sẵn trong một thư viện nào đó,
muốn sử dụng các hàm thư viện thì phải khai báo thư viện trước khi sử dụng bằng
lệnh #inlcude
Hàm người dùng
Hàm người dùng là những hàm do người lập trình tự tạo ra nhằm đáp ứng nhu
cầu xử lý của mình.
Một hàm khi được định nghĩa thì có thể sử dụng bất cứ đâu trong chương trình.
Trong C, một chương trình bắt đầu thực thi bằng hàm main.
Chương trình con được dùng để tôí ưu hóa việc tổ chức chương trình, chia một
chương trình lớn thành nhiều công việc độc lập nhỏ. Dùng chương trình con thực
hiện các công việc nhỏ, tạo thành mô-đun. Khi đó nhiệm vụ chương trình chính chỉ là
cung cấp dữ liệu đầu vào cho các mô-đun để hoàn thành công việc của mình. Một
chương trình viết theo cách này gọi là chương trình cấu trúc.
Có thể minh họa chương trình con như hình sau :
Chương trình
con thực hiện
công việc A
Dữ liệu đưa vào để thực
hiện công việc A
Dữ liệu kết quả
của công việc A
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
53
Hình 4.1.1 : Hình ảnh minh họa nhiệm vụ của chương trình con
Để nhận các dữ liệu đưa vào cho chương trình con và nếu có thể chứa dữ liệu
kết quả ra chúng ta phải sử dụng tham số (parameters) của chương trình con.
Tham số tồn tại dưới hai hình thức đó là tham số thực và tham số hình thức.
Tham số hình thức là tham số để khai báo và xây dựng trương trình con, còn tham số
thực để xác định dữ liệu đưa vào khi gọi chương trình con.
Trong một số ngôn ngữ lập trình cung cấp hai loại chương trình con riêng biệt
đó là hàm(function) và thủ tục (procedures) nhưng trong C chỉ cung cấp một loại đó
là hàm
Để tạo một hàm chúng ta cần xác định
- Hàm tạo ra sẽ thực hiện công việc gì ?
- Sau khi thưc hiện song có cần trả về một dữ liệu hay nhiều dữ liệu không ?
- Để thực hiện được công việc đó ta cần những dữ liệu nào ?
1.2 Quy tắc hoạt động của hàm
Khi gặp một lời gọi hàm, thì hàm sẽ được thực hiện theo trình tự sau:
Cấp phát bộ nhớ cho các đối và các biến cục bộ.
Gán giá trị của các tham số thực cho các đối tượng tương ứng.
Thực hiện các câu lệnh trong thân hàm.
Khi gặp câu lệnh return hoặc dấu } cuối cùng của thân hàm thì máy sẽ xóa các
đối, các biến cục bộ và thoát khỏi hàm.
Chú ý:
Số tham số thực sự phải bằng số tham số hình thức (đối của hàm) và kiểu của
tham số thực phải cùng kiểu với tham số hình thức tương ứng.
Nếu trở về từ một câu lệnh return có chứa biểu thức thì giá trị của biểu thức
được gán cho hàm. Giá trị của hàm sẽ được sử dụng trong biểu thức chứa nó.
2. Xây dựng hàm
2.1 Định nghĩa hàm
Khai báo hàm
Khai báo hàm được thực hiện ở phần đầu chương trình, khai báo để chỉ cho
máy biết các thông tin về hàm bao gồm : kiểu dữ liệu trả về có hay không, tên hàm,
các tham số bao gồm kiểu và tên của từng tham số.
Cú pháp khai báo như sau :
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
54
Tên_kiểu_trả_về tên_hàm(kiểu1 tham_số1, kiểu2 tham_số2 ..... ) ;
Trong đó :
- Tên kiểu trả về : là một tên kiểu dữ liệu quy định kết quả trả về là khiểu gì.
- Tên hàm : Tự đặt theo quy định đặt tên của ngôn ngữ C.
- Kiểu1 tham_số1 : xác định tên của tham số thứ nhất và kiểu của tham số đó,...
Nếu có nhiều tham số, mỗi tham số phân cách nhau bởi dấu phẩy (,)
Ví dụ:
int sum(int a , int b) ;
float max(float x, float y);
Nếu trong hàm không có dữ liệu trả về thì viết kiểu trả về là void nếu không có
tham số thì bỏ trống và phải có cặp dấu đóng mở ngoăc () sau tên hàm.
Ví dụ:
void hien();
Xây dựng hàm
Sau khi khai báo xong hàm chúng ta có thể viết lệnh thực hiện công việc đặt ra cho
chương trình con.
Cú pháp:
Tên_kiểu_trả_về tên_hàm(kiểu1 tham_số1,kiểu2 tham_số2 ..... )
{
Các câu lệnh thực hiện công việc đặt ra cho hàm.
}
Khi cần kết thúc thực hiện hàm và trả về dữ liệu nào đây, chúng ta viết lệnh return
vào vị trí cần thiết nhất trong hàm.
Cú pháp:
return giá-trị-dữ-liệu-trả-về;
Ví dụ:
int sum(int a, int b)
{
return a+b;
}
Hoặc
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
55
float max(float x, float y)
{
if(x>y)
return x;
else
return y;
}
Đối với hàm có kiểu void có thể không cần lệnh return
Ví dụ:
void hien()
{
printf(“ho va ten: Nguyen Van Nam”);
printf(“Ngay sinh :12\08\2014”);
printf(“que quan : Yen Bai”);
}
Chú ý:
Thông thường với những chương trình đơn giản có ít chương trình con thì người ta
viết lệnh cho hàm ngay tại nơi khai báo.
2.2 Sử dụng hàm
Một hàm khi định nghĩa thì chúng vẫn chưa được thực thi trừ khi ta có một lời gọi
đến hàm đó. Khi thực hiện chương trình máy sẽ thực hiện các lệnh trong chương
trình chính(hàm main), do đó để yêu cầu máy thực hiện công việc của chương trình
con ta phải viết lệnh gọi chương trình con với cú pháp sau.
Cú pháp:
([Danh sách các tham số])
Lời gọi này có thể là một câu lệnh độc lập hoặc đặt trong biểu thức, nếu đặt
trong biểu thức thì hàm đó phải có giá trị trả về để thực hiện tính toán biểu thức đó.
Ví dụ:
printf(“Tong hai so 5 va 6 la: %d”,tong(5,6));
Hoặc:
float x ;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
56
x = max(a,b) ;
Hoăc: hien();
Chú ý:
Thông thường lời gọi hàm được viết trong chương trình chính, tuy nhiên có thể
viết trong chương trình con khác. Các chương trình con có thể gọi lẫn nhau.
3. Các tham số của hàm
3.1 Phân biệt các loại tham số
Trong lập trình, tham số là biến được thu nhận bởi một chương trình con. Tại
thời gian chạy, chương trình con sử dụng các giá trị được gán cho các tham số để
thay đổi cách ứng xử của mình. Hầu hết các ngôn ngữ lập trình có thể định nghĩa
các chương trình con không có tham số hoặc chấp nhận một vài tham số.
Tham số hình thức:
Là biến được liệt kê trong danh sách tham số (thường nằm tại phần đầu của
định nghĩa chương trình con).
Tham số thực sự : Là giá trị cụ thể của biến đó tại thời gian chạy.
Để phân biệt rõ hai khái niệm trên, xét ví dụ dưới đây:
int sum(int gt1, int gt2)
{
return (gt1+gt2);
}
Hàm sum nhận hai tham số hình thức: gt1và gt2. Nó lấy tổng của các giá trị
được truyền vào các tham số này và trả về kết quả cho nơi gọi hàm (bằng cách sử
dụng một kỹ thuật được cung cấp tự động bởi trình biên dịch C). Mã gọi hàm sum có
thể trông như dưới đây:
int a=40;
int b=2;
int c = sum(a,b);
Các biến a và b được khởi tạo với các giá trị 40 và 2. Các biến này không
phải tham số hình thức hay tham số thực sự. Tại thời gian chạy, giá trị đã được gán
cho các biến này được truyền vào cho hàm sum . Trong hàm sum, các tham số hình
thức gt1 và gt2 được tính giá trị và lần lượt cho kết quả là hai tham số thực
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
57
sự 40 và 2. Giá trị của các tham số thực sự được cộng lại, kết quả được trả về cho nơi
gọi hàm - nơi nó được gán cho biến c
Tham số hình thức thường được gọi tắt là tham số. Tham số thực sự còn được
gọi là tham số thực, tham đối hoặc đối số.
3.2 Cách truyền tham số
Có hai cách để truyền tham số cho hàm: Truyền theo tham trị và truyền theo
tham chiếu.
Truyền bằng tham trị
Trong cơ chế truyền tham số bằng giá trị (gọi là truyền tham trị), khi chương
trình con được chạy, một bản sao của tham số thực sự được gán cho tham số hình
thức (sao chép giá trị). Nghĩa là mọi sửa đổi của chương trình con đối với tham số
hình thức không gây ảnh hưởng tới biến được truyền vào chương trình con theo kiểu
truyền tham trị.
Các tham số được truyền bằng giá trị được gọi là tham trị. Do chỉ có giá trị
được truyền vào chương trình con, tham số thực sự không nhất thiết phải là một
biến thông thường mà có thể là hằng giá trị, hằng biến, biểu thức trả về giá trị...
Truyền bằng biến
Trong cơ chế truyền tham số bằng biến (gọi là truyền tham biến), khi chương
trình con được chạy, tham số hình thức trở thành một tham chiếu tham số thực sự.
Nghĩa là mọi sửa đổi của chương trình con đối với tham số hình thức sẽ có tác dụng
với tham số thực sự. Đây được gọi là hiệu ứng phụ của chương trình con.
Các tham số được truyền bằng biến được gọi là tham biến. Ngược lại với cơ
chế truyền bằng giá trị, cơ chế truyền bằng biến đòi hỏi tham số thực sự phải là một
biến.
Các đối số mặc định
Một số ngôn ngữ lập trình cho phép một đối số mặc định (default argument)
được cho trước một cách tường mình hay ngầm định trong phần khai báo của một
chương trình con. Điều này cho phép nơi gọi bỏ qua đối số này khi gọi chương trình
con đó. Nếu đối số mặc định được cho một cách tường mình, thì giá trị đó được sử
dụng mỗi khi nó không được cung cấp bởi nơi gọi. Nếu đối số mặc định là ngầm định
(mà đôi khi được khai báo bởi từ khóa như là "Optional" (không bắt buộc)) thì ngôn
ngữ sẽ cung ứng một giá trị "ban đầu" thông dụng (như là null, tập rỗng, giá trị 0, xâu
kí tự rỗng,...) nếu giá trị (của tham số) không được nơi gọi cung cấp.
Chiều dài biến đổi được của danh sách tham số
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
58
Một số ngôn ngữ cho phép các chương trình con được định nghĩa với số các
đối số thay đổi được. Đối với các ngôn ngữ như vậy, các chương trình con phải duyệt
qua danh mục của các đối số
Các tham số danh định
Một số ngôn ngữ lập trình cho phép các chương trình con có các tham số danh
định (named parameter). Điều này cho phép mã nơi gọi chương trình con có tính tự
mô tả (self-documenting) cao hơn. Nó cũng cung cấp cho nơi gọi khả năng uyển
chuyển cao hơn, thường cho phép thay đổi thứ tự của các đối số hay bỏ qua một số
đối số nếu cần thiết.
3.3. Biến toàn cục và biến cục bộ
Biến toàn cục là biến nhớ được khai báo ở ngoài mọi hàm(thường được khai báo
ở trên cùng sau khai báo thư viện), có tác dụng đến toàn bộ chương trình cả
chương trình chính và chương trình con.
Biến cục bộ là biến nhớ được khai báo bên trong một chương trình con, chỉ có
tác dụng ở trong chương trình con đó. Bên ngoài chương trình con đó thì biến đó
không được sử dụng nữa.
Ví du:
#include
#include
int a,b;
int sum()
{
int c = a + b;
return c;
}
int tich()
{
int c ;
c =a*b;
return c;
}
void main()
{
clrscr();
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
59
printf(“nhap vao so a = ”); scanf(“%d”,&a);
printf(“nhap vao so b = ”);scanf(“%d”,&b);
printf(“\n tong hai so: %d”,sum());
printf(“\n tich hai so la: %d”,tich());
getch();
}
Trong ví dụ trên hai biến a và b là biến toàn cục được sử dụng trong cả 3 hàm
sum, max và main. Trong hàm sum và max đều có biến cục bộ là c nhưng biến c
trong hàm sum sẽ độc lập và riêng biệt so với c trong hàm max.
4. Hàm đệ quy
4.1 Khái niệm đệ quy
Một hàm được gọi là đệ quy nếu bên trong thân hàm có lệnh gọi đến chính nó
Ví dụ: Người ta định nghĩa giai thừa của một số nguyên dương n như sau:
N!= 1*2*3*.*(n-1)*n=(n-1)! *n (với 0!=1)
Như vậy, để tính n! ta thấy nếu n=0 thì n!=1 ngược lại thì n!=n*(n-1)! Với định
nghĩa trên thì hàm đệ quy tính n! được viết:
#include
#include
/*hàm tính n! bằng đệ quy*/
long giaithua_ dequy(int n)
{
if(n==0)
Return 1;
else
return n*giaithua_dequy(n-1);
}
4.2 Các bài toán dùng đệ quy
Phương pháp đệ quy thường dùng phổ biến trong ứng dụng mà cách giải
quyết có thể được thể hiện bằng việc áp dụng liên tiếp cùng giải pháp cho những
tập hợp con của bài toán.
Phương pháp đệ quy không phải bao giờ cũng là giải pháp hữu hiệu nhất. Giải
pháp vòng lặp có hiệu quả về mặt thời gian và vùng nhớ. Còn với đệ quy mỗi lần
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
60
gọi đệ quy máy phải dành một số vùng nhớ để trữ các trị, thông số và biến cục bộ.
Do đó, đệ quy tốn nhiều vùng nhớ, thời gian truyền đối mục, thiết lập vùng nhớ
trung gian và trả kết quảNhưng sử dụng phương pháp đệ quy trông chương trình
đẹp mắt hơn vòng lặp và tính thuyết phục của nó. Điều cốt lõi khi thiết đặt chương
trình phải làm thế nào hàm đệ quy có thể chấm dứt thông qua điều kiện cơ bản
Chính vì vậy, trong lập trình người ta cố tránh sử dụng thủ tục đệ quy nếu thấy
không cần thiết.
4.3 Cách xây dựng hàm đệ quy
Hàm đệ quy phải có 2 phần :
+ Phần dừng hay phải có trường hợp nguyên tố.Trong ví dụ ở trên thì trường
hợp n=0 là trường hợp nguyên tố.
+ Phần đệ quy : Là phần có gọi lại hàm được định nghĩa. Trong ví dụ trên thì
phần đệ quy là n>0 thì n !=n*(n-1) !
Sử dụng hàm đề quy trong chương trình sẽ làm chương trình dễ đọc, dễ hiểu và
vấn đề được nêu bật rõ ràng hơn.Tuy nhiên trong đa số trường hợp thì hàm đệ
quy tốn bộ nhớ nhiều hơn và tốc độ thực hiện chương trình chậm hơn không
đệ quy.
Tùy từng bài cụ thể mà người lập trình quyết định nên dùng đệ quy hay
không(có trường hợp không dùng đệ quy thì không giải quyết được bài toán).
4.4 Các ví dụ về hàm đệ quy
Ví dụ 1: Tính n!
N!= 1*2*3**(n-2)*(n-1)*n với n>=1 và 0!=1
long giaithua(int n)
{
if(n==0)
Return 1;
else
return n*giaithua(n-1);
}
Giải thích hoạt động của hàm đệ quy giaithua
Ví dụ giá trị truyền vào hàm giaithua qua biến n = 5
Thứ tự gọi thực hiện hàm giai thừa
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
61
giaithua(n) return(n * giaithua(n-1))
5
4
3
2
1
5*giaithua(4) = 5 *?
4*giaithua(3) = 4 *?
3*giaithua(2) =3 *?
2*giaithua(1) = 2*?
1*giaithua(0) = 1 *?
Khi tham số n =0 thì return về giá trị (giá trị 1 kiểu long). Lúc này các giá trị? Bắt đầu
định trị theo thứ tự ngược lại
Giaithua(in) Return(in * giaithua(in-1))
1
2
3
4
5
1*giaithua(0) = 1 *1 = 1
2*giaithua(1) = 2 *1=2
3*giaithua(2) =3 *2=6
4*giaithua(3) = 4*6=24
5*giaithua(4) = 5 *24=120
Kết quả sau cùng 5!= 120
Minh họa bằng hình ảnh.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
62
Hình 4.4.4: Hình ảnh minh họa tính 5! bằng giải thuật đệ quy
Ví dụ 2: dãy số Fibonaci
0,1,1,2,3,5,8,13,21,34.. Bắt đầu bằng 0 và 1, các số tiếp theo bằng tổng hai
số đi trước
Dãy số fibonaci được khai báo đề quy như sau:
fibonaci(0)=0
fibonaci(1)=1
fibonaci(n)= fibonaci(n-1) + fibonaci(n-2)
/* tinh so fibonaci thu n*/
# include
# include
void main(void)
{
long n;
long fibonaci(long);
printf(“nhap vao so n:”);
scanf(%ld”,&n);
print(“fibonaci(%ld)=%ld\n”,n,fibonaci(n));
getch();
}
long fibonaci(long in)
{
if (n==0|| n==1)
Return n;
else
return fibonaci(n -1 )+ fibonaci(n – 2);
}
Ket qua in ra man hinh
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
63
Nhap vao so n:10
Fibonaci(10)=55
Ví dụ 3 :
Tìm UCLN của 2 số a, b. Bài toán có thể được định nghĩa dưới dạng đệ qui như
sau:
− nếu a = b thì UCLN = a
− nếu a > b thì UCLN(a, b) = UCLN(a-b, b)
− nếu a < b thì UCLN(a, b) = UCLN(a, b-a)
Từ đó ta có chương trình đệ qui để tính UCLN của a và b như sau.
int UCLN(int a, int b) // qui uoc a, b > 0
{
if (a < b) UCLN(a, b-a);
if (a == b) return a;
if (a > b) UCLN(a-b, b);
}
BÀI TẬP VẬN DỤNG
Bài tập 1:
Viết chương trình con tìm Max, Min 3 sô nguyên, chương trình chính nhập vào
3 số nguyên, hiện số lớn nhất và nhỏ nhất ra màn hình.
#include
#include
int max(int a,int b,int c)
{ int max=a;
if(max<b)max=b;
if(max<c)max=c;
return max;
}
int min(int a, int b, int c)
{
int min=a;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
64
if(min>b)min=b;
if(min>c)min=c;
return min;
}
void main()
{
int a,b,c;
clrscr();
printf("\nnhap vao 3 so nguyen:");
printf("\nso thu nhat: ");scanf("%d",&a);
printf("\nso thu hai: ");scanf("%d",&b);
printf("\nso thu ba : ");scanf("%d",&c);
printf("\n so lon nhat la: %d",max(a,b,c));
printf("\n so nho nhat la: %d",min(a,b,c));
getch() ;
}
Bài tập 2:
Viết chương trình kiểm tra một số nguyên có phải là số nguyên tố hay không.
#include
#include
#include
int SNT(int n)
{
int i,kt=1;
for(i=2; i<=sqrt(n);i++)
{
if(n%i==0)
{
kt=0;
break;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
65
}
}
return kt;
}
void main()
{
int n;
printf("Nhap So Can Kiem Tra: ");
scanf("%d", &n);
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");
}
Bài tập 3
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ố.
//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);
int dem = Dem(n);
printf(".:|Chu so cua so n la: %d", dem);
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
66
printf("\n");
}
Bài tập 4
Viết chương trình đảo ngược của 1 số: ví dụ nhập 123 -> trả về 321.
int DaoSo(int n)
{
int kq=0;
while(n!=0)
{
kq=kq*10 + n%10;
n=n/10;
}
return kq;
}
void main()
{
int n;
printf("Nhap n: ");
scanf("%d", &n);
printf("\nSo Dao Nguoc La: %d", DaoSo(n));
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
67
Chương 5: MẢNG
1. Khái niệm mảng
Mảng là cấu trúc dữ liệu cho phép quản lý một danh sách hữu hạn các dữ liệu
cùng kiểu.
Ví dụ: Để chứa 5 dữ liệu số nguyên sau 10, 12, 25, 30, 40
Chúng ta có thể sử dụng 5 biến nhớ khác nhau
a = 10, b = 12, c = 25, d = 30, e = 40
Tuy nhiên nếu số lượng dữ liệu tăng thì cách này không phù hợp, và chúng ta
sẽ sử dụng cấu trúc mảng, minh hoạ như sau:
10 12 25 30 40 ....
Các dữ liệu trong mảng phải cùng kiểu (số nguyên, số thực, ký tự ... ).
Mảng để chứa dữ liệu như trên là mảng 1 chiều, chúng ta có thể sử dụng
mảng 2 chiều hoặc mảng nhiều chiều.
Ví dụ về mảng 2 chiều:
10 2 5 ....
45 23 56 ....
8 5 20 ....
.... .... .... ....
Mảng hai chiều là một bảng các dữ liệu được xếp theo hàng và cột, mỗi dữ liệu
sẽ chứa trong ô được xác định bằng cách chỉ ra ở hàng nào và cột nào.
2. Khai báo mảng
Mảng một chiều
Mảng một chiều giống như một vector. Mỗi phần tử của mảng một chiều có giá
trị không phải là một mảng khác.
Khai báo với số phần tử xác định
Cú pháp:
Trong đó:
+ Tên_mảng: Đây là tên mảng đạt theo đúng quy tắc đặt tên của danh biểu. Tên
này cũng mang ý nghĩa là tên biến mảng.
+ Số_phần_tử : Là một hằng số nguyên, cho biết số lượng phàn tử tối đa trong
mảng là bao nhiêu (hay nói khác đi kích thước của mảng là gì).
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
68
+ Kiểu : Mỗi phần tử của mảng có dữ liệu thuộc kiểu gì.
Ở đây, ta khai báo một biến mảng gồm có Số_phần_tử phần tử, phần tử thứ nhất
là tên_mảng [0], phần tử cuối cùng là tên_mảng[Số_phần_tử -1]
VD:
int a[10]; /*Khai báo biến mảng tên a, phần tử thứ nhất là a[0], phần tử cuối cùng
là a[9].*/
Ta có thể coi mảng a là một dãy liên tiếp các phần tử trong bộ nhớ như sau:
Vị trí 0 1 2 3 4 5 6 7 8 9
Tên phần
tử
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
Khai báo với số phần tử không xác định (khai báo không tường minh)
Cú pháp:
Khi khai báo, không cho biết rõ số phần tử của mảng, kiểu khai báo này thường
được áp dụng trong các trường hợp: vừa khai báo vừa gán giá trị, khai báo mảng là
tham số hình thức của hàm.
Vừa khai báo vừa gán giá trị
Cú pháp:
[] = {Các giá trị cách nhau bởi dấu phẩy}
Nếu vừa khai báo vừa gán giá trị thì mặc nhiên C sẽ hiểu số phần tử của mảng là
số giá trị mà chúng ta gán cho mảng trong cặp dấu {}.
Ví dụ:
int S[] = {3,5,6};
Mảng nhiều chiều
Mảng nhiều chiều là mảng có từ hai chiều trở lên. Điều đó có nghĩa là mỗi phần tử
của mảng là một mảng khác.
Người ta thường sử dụng mảng nhiều chiều để lưu các ma trận, các tọa độ 2
chiều, 3 chiều.
Phần dưới đây là các vấn đề liên quan đến mảng 2 chiều; các mang 3, 4 chiều thì
tương tự (chỉ cần tổng quát hóa lên).
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
69
Khai báo mảng hai chiều tường minh
Cú pháp:
VD: Người ta cần lưu trữ thông tin của một ma trận gồm các số thực. Lúc này ta
có thể khai báo một mảng 2 chiều như sau:
float m[8][9];
/*Khai báo mảng hai chiều có 8*9 phần tử là số thực*/
Trong trường hợp này, ta đã khai báo cho một ma trận có tối đa là 8 dòng, mỗi
dòng có tối đa là 9 cột.
0 1 2 3 4 5 6 7 8
0 m[0][0] m[0][1] m[0][2] m[0][3] m[0][4] m[0][5] m[0][6] m[0][7] m[0][8]
1 m[1][0] m[1][1] m[1][2] m[1][3] m[1][4] m[1][5] m[1][6] m[1][7] m[1][8]
2 m[2][0] m[2][1] m[2][2] m[2][3] m[2][4] m[2][5] m[2][6] m[2][7] m[2][8]
3 m[3][0] m[3][1] m[3][2] m[3][3] m[3][4] m[3][5] m[3][6] m[3][7] m[3][8]
4 m[4][0] m[4][1] m[4][2] m[4][3] m[4][4] m[4][5] m[4][6] m[4][7] m[4][8]
5 m[5][0] m[5][1] m[5][2] m[5][3] m[5][4] m[5][5] m[5][6] m[5][7] m[5][8]
6 m[6][0] m[6][1] m[6][2] m[6][3] m[6][4] m[6][5] m[6][6] m[6][7] m[6][8]
7 m[7][0] m[7][1] m[7][2] m[7][3] m[7][4] m[7][5] m[7][6] m[7][7] m[7][8]
Khai báo mảng 2 chiều không tường minh
Để khai báo mảng 2 chiều không tường minh, ta vẫn phải chỉ ra số phần tử của
chiều thứ hai (chiều cuối cùng).
Cú pháp:
Cách khai báo này cũng được áp dụng trong trường hợp vừa khai báo, vừa gán trị
hay đặt mảng 2 chiều là tham số hình thức của hàm.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
70
3. Truy xuất mảng
3.1 Truy xuất mảng 1 chiều
Đối với mảng một chiều các phần tử chứa dữ liệu được sắp xếp liên tục trong
bộ nhớ máy, và mỗi phần tử sẽ có một chỉ số (là số thứ tự) của phần tử đó trong
mảng.
Phần tử đầu tiên có chỉ số là 0, tiếp theo là 1 và tiếp tục cho đến hết, vậy sẽ
dừng lại ở chỉ số bằng số lượng phần tử -1.
Minh hoạ bằng hình sau:
10 25 15 30 .....
0 1 2 3 ....
Kích thước của mảng (tính bằng byte) sẽ là: số lượng phần tử của mảng nhân
với kích thước của kiểu dữ liệu của mảng đó.
Ví dụ:
int a[20];
Thì mảng sẽ chiếm kích thước bộ nhớ là: 20 x 2 = 40 byte
Để truy nhập đến các phần tử ta sử dụng tên và các chỉ số của phần tử đó theo
cú pháp sau:
Tên_mảng[chỉ_số_phần_tử_cần_truy_nhập];
Ví dụ:
a[0]=10;
Truy xuất đến phần tử đầu tiên của mảng a rồi gán giá trị cho phần tử đó bằng
10.
3.2 Truy xuất mảng 2 chiều
Để truy nhập đến các phần tử của mảng hai chiều ta sử dụng tên mảng và chỉ số
hàng và chỉ số cột theo cú pháp sau:
Tên_mảng_hai_chiều[chỉ_số_hàng] [chỉ_số_cột];
Ví dụ:
a[1][2] = 5;
Chú ý:
Số lượng dữ liệu lưu trong mảng có thể ít hơn số lượng phần tử đã khai báo,
nhưng không vượt quá số lượng phần tử đã khai báo đó. Thông thường sử dụng
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
71
mảng sẽ có một số nguyên n để lưu số lượng dữ liệu được lưu trong mảng tương
ứng.
Danh sách các dữ liệu lưu trong mảng phải liên tục nhau để quản lý và sử lý dễ
dàng.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
72
BÀI TẬP THỰC HÀNH
Bài tập 1:
Viết chương trình nhập vào từ bàn phím một mảng gồm n số nguyên. Hiện
mảng vừa nhập lên màn hình.
#include
#include
void nhap(int n,int C[])
{
for(int i=0;i<n;i++)
{
printf("\n Nhap so thu %d ",i+1);
scanf("%d",&C[i]);
}
}
void hien(int n,int C[])
{
for(int i=0;i<n;i++)
{
printf(" %d",C[i]);
}
}
void main()
{
clrscr();
int A[100],n;
printf("\nnhap so luong phan tu cua mang: n= ");
scanf("%d",&n);
nhap(n, A);
printf("\n Mang vua nhap : ");
hien(n, A);
getch();
}
Bài tập 2:
Lập trình đọc vào từ bàn phím hai dãy số nguyên. Dãy A có n số, dãy B có m số.
Tính tổng của mỗi dãy, so sánh xem dãy nào có tổng lớn hơn. Đưa kết quả ra màn
hình bao gồm: dãy A, dãy B, tổng dãy A,B, so sánh.
#include
#include
void nhap(int n,int C[])
{
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
73
for(int i=0;i<n;i++)
{
printf("\n Nhap so thu %d ",i+1);
scanf("%d",&C[i]);
}
}
void hien(int n,int C[])
{
for(int i=0;i<n;i++)
{
printf(" %d",C[i]);
}
}
int tong(int n,int C[])
{
int sum=0;
for(int i=0;i<n;i++)
sum=sum+C[i];
return sum;
}
void main()
{
clrscr();
int A[100],B[100],m,n;
printf("\nnhap so luong phan tu day A: n= ");
scanf("%d",&n);
printf("\nnhap so luong phan tu day B: m= ");
scanf("%d",&m);
printf("\n Nhap gia tri cho day A:");
nhap(n,A);
printf("\n Nhap gia tri cho day B");
nhap(m,B);
printf("\n Day A vua nhap la:");
hien(n,A);
printf("\n Day B vua nhap la:");
hien(m,B);
printf("\n tong day A la: %d",tong(n,A));
printf("\n tong day B la: %d",tong(m,B));
printf("\n Day lon hon la: ");
if(tong(n,A)>tong(m,B)) printf("A");
else printf("B");
getch() ;
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
74
Bài tập 3: Nhập ma trận A có m hàng n cột đưa ra màn hình ma trận A vừa nhập để
kiểm tra.
#include
#include
void main()
{
int n,m,i,j,A[100][100];
clrscr();
printf("nhap so hang cua ma tran: n= ");
scanf("%d",&n);
printf("nhap so cot cua ma tran: m= ");
scanf("%d",&m);
//nhap gia tri cho ma tran
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
printf("A[%d][%d]= ",i+1,j+1);
scanf("%d",&A[i][j]);
}
}
// hien ma tran vua nhap
printf("\nMa tran ban vua nhap la:");
for(i=0;i<n;i++)
{
printf("\n");
for(j=0;j<m;j++)
{
printf("%d ",A[i][j]);
}
}
getch();
}
Bài tập 4
Nhập vào từ bàn phím một dãy số nguyên. Tìm số lớn nhất, nhỏ nhất và vị trí
của số đó trong mảng.
#include
#include
void main()
{
int i,n,A[100],max=0,min=0;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
75
clrscr();
printf("nhap so luong phan tu cua day: ");
scanf("%d",&n);
//nhap gia tri cho day
for(i=0;i<n;i++)
{
printf("\n Nhap A[%d] = ",i+1);
scanf("%d",&A[i]);
}
//tim Max, Min
for(i=1;i<=n;i++)
{
if(A[max]<A[i]) max=i;
if(A[min]>A[i]) min=i;
}
printf("\nSo lon nhat trong day la: %d",A[max]);
printf("\t Vi tri so do la: %d",max+1);
printf("\nSo nho nhat trong day la: %d",A[min]);
printf("\t Vi tri so do la: %d",min+1);
getch();
}
Bài tập 5
Nhập và từ bàn phím một dãy số nguyên, xắp xếp dãy đó theo chiều tăng dần
và giảm dần. Hiện lên màn hình: dãy số vừa nhập, dãy đã sắp xếp.
#include
#include
void nhap(int n,int C[])
{
for(int i=0;i<n;i++)
{
printf("\n Nhap so thu %d ",i+1);
scanf("%d",&C[i]);
}
}
void hien(int n,int C[])
{
for(int i=0;i<n;i++)
{
printf(" %d",C[i]);
}
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
76
void tang(int n, int C[])
{
int tg;
for(int i=0;i<n;i++)
for(int j=0;j<n-1;j++)
if(C[j] >C[j+1])
{
tg=C[j];
C[j]=C[j+1];
C[j+1]=tg;
}
}
void giam(int n, int C[])
{
int tg;
for(int i=0;i<n;i++)
for(int j=0;j<n-1;j++)
if(C[j] <C[j+1])
{
tg=C[j];
C[j]=C[j+1];
C[j+1]=tg;
}
}
void main()
{
clrscr();
int A[100],n;
printf("\nnhap so luong phan tu day : n= ");
scanf("%d",&n);
printf("\n Nhap gia tri cho day :");
nhap(n,A);
printf("\n Day vua nhap la:");
hien(n,A);
//hien day da xap xep tang dan
printf("\n Day da xap xep tang dan la:");
tang(n,A);
hien(n,A);
//hien day da xap xep giam dan
printf("\n Day da xap xep giam dan:");
giam(n,A);
hien(n,A);
getch() ;}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
77
CHƯƠNG 6: CON TRỎ
1. Khái niệm về con trỏ và địa chỉ
Một con trỏ (pointer) là một biến mà nội dung của nó là địa chỉ của một đối
tượng khác. Đối tượng ở đây có thể là một biến hoặc một hàm và không phải là một
hằng. Việc sử dụng các biến con trỏ sẽ giúp chúng ta khai thác đến 1 biến thông qua
địa chỉ của nó. Khả năng đó cộng với khả năng tính toán linh hoạt trên các con trỏ
của C đã khiến con trỏ trở thành một công cụ mạnh để thực hiện nhiều thao tác mà
thiếu nó thì không thể làm được hoặc rất khó khăn.
Bộ nhớ máy tính bao gồm một bảng các ô nhớ liên tiếp có thể đánh địa chỉ
được. Chúng ta có thể thao tác trên từng ô nhớ riêng rẽ hoặc trên các ô nhớ liên tiếp.
Khi chúng ta khai báo một biến, máy sẽ cấp phát cho biến đó một số ô nhớ liên tiếp
đủ để chứa nội dung của biến, ví dụ một biến ký tự được cấp phát một byte, một
biến nguyên được cấp phát 2 byte, một biến thực được cấp phát 4 byteĐịa chi của
biến được tính là số thứ tự của byte đầu tiên trong dãy các byte được cấp phát cho
biến. Vì vậy người ta phân biệt các con trỏ theo kiểu địa chỉ chứa trong các con trỏ.
Con trỏ được minh họa bằng hình sau.
Hình 6.1: Hình ảnh mô tả giá trị của biến con trỏ
Có hai kiểu biến trỏ: Biến trỏ có kiểu và biến trỏ không kiểu
Biến trỏ có kiểu : loại biến trỏ này chỉ chứa địa chỉ vùng nhớ của một kiểu dữ
liệu nào đó, kiểu dữ liệu này được xác định khi khai báo biến trỏ. Và kiểu của biến trỏ
xác định kích thước của vùng nhớ mà biến trỏ đến
Ví dụ biến trỏ kiểu int sẽ trỏ đến vùng nhớ có kích thước là 2 bytes, kiểu floast sẽ trỏ
đến vùng nhớ có kích thước là 4 bytes.
Địa chỉ vùng nhớ
của biến a (0100)
Biến a
Địa chỉ vùng
nhớ: 0100
Biến b Biến
trỏ
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
78
Biến trỏ không kiểu: loại biến trỏ này có thể chứa địa chỉ vùng nhớ có kích
thước bất kỳ.
Vùng nhớ động: Là vùng nhớ được cấp phát khi cần và giải phóng khỏi bộ nhớ khi sử
dụng song. Để truy cập đến vùng nhớ động chúng ta phải dùng biến trỏ.
2. Khai báo về sử dụng biến con trỏ
2.1 Khai báo biến con trỏ
biến trỏ có kiểu
Kiểu_dữ_liệu *tên_con_trỏ;
Ví dụ: *pn /*Khai báo con trỏ pn chứa địa chỉ của biến nguyên*/
- biến trỏ không kiểu:
void *tên_biến_trỏ
ví dụ: void *i;
Với câu lệnh khai báo trên, con trỏ chưa trỏ đến địa chỉ nào cả và nó có giá trị
bằng NULL. NULL là một hằng số biểu thị giá trị của con trỏ khi chưa được gán địa chỉ
nào.
2.2. Các thao tác trên con trỏ
2.2.1. Gán địa chỉ của biến cho con trỏ
Toán tử & dùng để định vị con trỏ đến địa chỉ của một biến đang làm việc.
Cú pháp : =&
Giải thích:
Ta gán địa chỉ của biến a cho con trỏ pa, Ta gán địa chỉ của biến b cho con trỏ pb
pa=&a ; pb=&b ;
Lúc này hình ảnh của các biến trong bộ nhớ được mô tả :
Lưu ý :
Khi gán địa chỉ của biến tĩnh cho con trỏ cần phải lưu ý kiểu dữ liệu của chúng. Ví
dụ sau đây không đúng do không tương thích kiểu :
int biennguyen ;
float *controthuc ;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
79
.
controthuc=&biennguyen ;
Phép gán ở đây là sai vì controthuc là một con trỏ kiểu float(nó chỉ có thể chứa
được địa chỉ của biến kiểu float) ; trong khi đó biến nguyên kiểu int.
2.2.2. Nội dung của ô nhớ con trỏ chỉ tới
Để truy cập đến nọi dung của ô nhớ mà con trỏ chỉ tới, ta sử dụng cú pháp :
*
Với cách truy cập này thì * có thể coi là một biến có kiểu được mô
tả trong phần khai báo biến con trỏ.
Ví dụ : Cho phép khai báo, gán địa chỉ cũng như lấy nội dung vùng nhớ của biến con
trỏ :
Int x= 100 ;
int *ptr ;
ptr = &x ;
int y= *ptr ;
Lưu ý :
Khi gán địa chỉ của một biến cho biến con trỏ, mọi sự thay đổi trên nội dung ô
nhớ con trỏ chỉ tới sẽ làm giá trị của biến thay đổi theo( thực chất nội dung ô nhớ và
biến là một)
Ví dụ : Đoạn chương trình sau thấy rõ sự thay đổi
#include
#include
int main ()
{
int a,b, *pa, *pb ;
a=2 ;
b=3 ;
clrscr() ;
printf (‘\n gia tri cua bien a=%d \n gia tri cua bien
b=%d’, a,b) ;
pa=&a ;
pb=&b ;
printf (‘\n noi dung cua o nho con tro pa tro toi=%d’,
*pa) ;
printf (‘\n noi dung cua o nho con tro pa tro toi=%d’,
*pb) ;
*pa=20 ;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
80
*pb=20 ;
printf (‘\n gia tri moi cua bien a=%d \n gia tri moi
cua bien b=%d’, a,b) ;
getch() ;
return 0;
}
Kết quả thực hiện chương trình
2.2.3. Cấp phát và giải phóng vùng nhớ
Trước khi sử dụng biến con trỏ, ta nên cấp phát vùng nhớ cho biến con trỏ này
quản lý địa chỉ. Việc cấp phát được thực hiện nhờ các hàm malloc(), calloc() trong
thư viện alloc.h
Cú pháp các hàm :
Void *malloc(size_t size) : Cấp phát vùng nhớ có kích thước là size.
Void *calloc(size_t nitems,size_t size) : Cấp phát vùng nhớ có kích thước là
nitems*size.
Ví dụ: Giả sử ta có khai báo
int a, *pa, *pb;
pa=(int*)malloc(sizeof(int));/*cấp phát vùng nhớ có kích thước bằng với kích
thước của một số nguyên */
pb=(int*)calloc(10,sizeof(int));/*cấp phát vùng nhớ có thể chứa được 10 số
nguyên */
Lúc này hình ảnh trong bộ nhớ như sau:
Lưu ý:
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
81
Khi sử dụng hàm malloc() hay calloc(), ta phải ép kiểu vì nguyên mẫu các hàm
này trả về con trỏ kiểu void.
Trong quá trình thao tác trên biến con trỏ, nếu ta cần cấp phát thêm vùng nhớ
có kích thước lớn hon vùng nhớ đã cấp phát, ta sử dụng hàm realloc().
Cú pháp: void *realloc(void *block,size_t size)
Ý nghĩa:
Cấp phát lại 1 vùng nhớ cho con trỏ block quản lý, vùng nhớ này có kích thước
mới là size; khi cấp phát lại thì nội dung vùng nhớ trước đó vẫn tồn tại.
Kết quả trả về của hàm là địa chỉ đầu tiên của vùng nhớ mới. Địa chỉ này có thể
khác với địa chỉ được chỉ ra khi cấp phát ban đầu.
Ví dụ: Trong ví dụ trên ta có thể cấp phát lại vùng nhớ do con trỏ pa quả lý như
sau:
int a, *pa;
pa=(int *)malloc(sizeof(int)) ;/*cấp phát vùng nhớ có kích thước 2 byte*/
pa=realloc(pa,6) ;/*cấp phát lại vùng nhớ có kích thước 6byte*/
Một vùng nhớ đã cấp phát cho biến con trỏ, khi không còn sử dụng nữa, ta sẽ thu
hồi lại vùng nhớ này nhờ hàm free()
Cú pháp: void free(void *block)
Ý nghĩa: Giải phóng vùng nhớ được quản lý bởi con trỏ block.
Ví dụ: Ở ví dụ trên, sau khi thực hiện xong, ta giải phóng vùng nhớ cho 2 biến con trỏ
pa & pb:
free(pa);
free(pb);
2.2.4. Một số phép toán
Phép gán con trỏ:
Hai con trỏ cùng kiểu có thể gán cho nhau:
Ví dụ:
int a, *p, *a; float *f;
A=5; p =&a; q=p; /*đúng*/
f=p;/* sai do khác kiểu*/
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
82
Ta cũng có thể ép kiểu con trỏ theo cú pháp:
(*)
Chẳng hạn, ví dụ trên được viết lại:
int a, *p, *q; float *f;
a=5; p =&a; q=p; /*đúng*/
f=(float*)p;/* đúng nhờ ép kiểu*/
Cộng, trừ con trỏ với một số nguyên
Ta có thể cộng (+), trừ (-) 1 con trỏ với 1 số nguyên N nào đó; kết quả trả về là 1
con trỏ. Con trỏ này chỉ đến vùng nhớ cách vùng nhớ cảu con trỏ hiện tại N phần tử.
Ví dụ: Cho đoạn chương trình sau:
int *pa;
/*cấp phát vùng nhớ 20byte = 10 số nguyên*/
pa =(int*)malloc(20);
int *pb, *pc;
pb = pa +7;
pc=pb -3 ;
Lúc này hình ảnh của pa, pb, pc như sau :
Con trỏ Null :
Là con trỏ không chứa địa chỉ nào cả. Ta có thể gán giá trị NULL cho 1 con trỏ có
kiểu bất kỳ.
Lưu ý :
- Ta không thể cộng 2 con trỏ với nhau
- Phép trừ 2 con trỏ cùng kiểu sẽ trả về 1 giá trị nguyên (int). Đây chính là khoảng
cách(số phần tử) giữa 2 con trỏ đó.
Chẳng hạn, trong ví dụ trên pc – pa = 4
3. Con trỏ và mảng 1 chiều
Giữa mảng và con trỏ có một sự liên hệ rất chặt chẽ. Những phần tử của mảng
có thể được xác định bằng chỉ số trong mảng, bên cạnh đó chúng cũng có thể được
xác lập qua biến con trỏ.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
83
3.1 Phép toán lấy địa chỉ
Ta có các quy tắc sau:
&[0] tương đương với
&[] tương đương với+
[] tương đương với *(+)
Ví dụ :
Cho 1 mảng 1 chiều các số nguyên a có 5 phần tử, truy cập các phần tử theo
kiểu mảng và theo kiểu con trỏ.
#include
#include
/*nhap mang binh thuong*/
void nhapmang(int a[], int n)
{
int i;
for ( i=0; i<n; i++)
{
printf (“phan tu thu %d:”, i);
scanf(“%d”, & a[i]);
}
}
/* nhap mang theo dang con tro*/
void nhapcontro(int a[], int n)
{
int i;
for (i=0; i<n;i++)
{
printf(“phan tu thu %d:”,i);
scanf(“%d”, a+i);
}
}
int main()
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
84
{
int a[20], n, i;
clrscr();
printf(“so phan tu n=”);
scanf(“%d”,&n);
nhapmang(a,n);
printf(“truy cap theo kieu mang:”);
for (i=0;i<n;i++)
printf(“%d”,a[i]);
printf(“truy cap theo kieu con tro:”);
for (i=0;i<n;i++)
Printf(“%d”,*(a+i));
Getch();
Return 0;
}
Kết quả thực thi chương trình
3.2 Tên mảng là 1 hằng địa chỉ
[] tương đương với *(+)
&[] tương đương với (+)
Trong đó là biến con trỏ, là 1 biểu thức số nguyên.
Ví dụ: giả sử có khai báo:
#include
#include
#include
int main()
{
int *a;
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
85
int i;
clrscr();
a=(int*)malloc(sizeof(int)*10);
for (i=0;i<10;i++)
a[i] = 2*i;
printf(“truy cap theo kieu mang:”);
for (i=0;i<10;i++)
printf(“%d”,a[i]);
printf(“truy cap theo kieu con tro:”);
for (i=0;i<10;i++)
printf(“%d”,a + i);;
getch();
retrun 0;
}
Với khai báo ở trên, hình ảnh của con trỏ a trong bộ nhớ
3.3 Con trỏ trỏ tới các phần tử của mảng 1 chiều
Giả sử con trỏ ptr chỉ đến phần tử a[i] nào đó của mảng a thì:
ptr + j chỉ đến phần tử đứng sau a[i], tức là a[i + j]
ptr - j chỉ đến phần tử đứng trước a[i], tức là a[i - j]
Ví dụ:
Giả sử có 1 mảng mang_int, cho con trỏ contro_int chỉ đến phần tử thứ 5 trong
mảng. In ra các phần tử của contro_int & mang_int.
#include
#include
#include
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
86
int main()
{
int i, mangint[10];
int *controint;
clrscr()
for(i=0;i<=9;i++)
mangint[i]=i*2;
controint=&mangint[5];
printf(“\n noi dung cua mang int ban dau=”);
for (i=0;i<=9;i++)
printf(“%d”, mangint[i]);
printf(“\n noi dung cua contro int ban dau=”);
for (i=0;i<=5;i++)
printf(“%d”, controint[i]);
for i=0;i<5;i++)
controint [i]++;
printf(“\n -----------------------------------------
“);
printf(“\n noi dung cua mang int sau khi tang 1=”);
for (i=0;i<=9;i++)
printf(“%d”, mangint[i]);
printf(“\n noi dung cua contro int sau khi tang
1=”);
for (i=0;i<5;i++)
printf(“%d”, controint[i]);
for i=0;i<5;i++)
if(controint !=null)
free(controint);
getch();
return 0;
}
Kết quả của chương trình
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
87
4. Con trỏ và hàm
Khi tham số hình thức của hàm là một con trỏ thì theo nguyên tắc gọi hàm ta
dùng tham số thực tế là 1 con trỏ có kiểu giống với kiểu của tham số hình thức. Nếu
lúc thực thi hàm ta có sự thay đổi trên nội dung vùng nhớ được chỉ bởi con trỏ tham
số hình thức thì lúc đó nội dung vùng nhớ được chỉ bởi tham số thực tế cũng sẽ bị
thay đổi theo.
Ví dụ: xét hàm hoán vị được viết như sau:
#include
#include
void hoanvi(int *a, int *b)
{
int c=*a;
*a=*b;
*b=c;
}
int main()
{
int m=20, n=30;
clrscr();
printf(“truoc khi goi ham m=%d, n=%d\n”,m,n);
hoanvi(&m,&n);
printf(“sau khi goi ham m=%d, n=%d”,m,n);
getch();
return 0;
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
88
Kết quả thực thi chương trình
Truớc khi gọi hàm Khi gọi hàm Sau khi gọi hàm
m=20 , n=30
&m, &n
a=&m; b=&n;
Lúc này
*a=m; *b=n;
m=20 , n=30
Đổi chỗ ta được
a=30, b= 20
m = 30 , n= 20
&m, &n
Con trỏ a,b bị giải phóng
m,n đã thay đổi:
m=30, n=20
&m, &n
BÀI TẬP THỰC HÀNH
Bài tập 1
Khai báo 1 biến nguyên a và biến trỏ kiểu nguyen p, cho p trỏ đến a. in ra màn
hình địa chỉ của a, nội dung trong p, địa chỉ của biến trỏ p.
#include
#include
#include
void main()
{
int a, *p;
clrscr();
p=&a;
printf("\nDia chi cua bien nho a= %x",&a);
printf("\nNoi dung cua bien tro p= %x",p);
printf("\n dia chi cu bien tro p= %x",&p);
getch();
}
Bài tập 2
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
89
Viết chương trình nhập vào từ bàn phím n số nguyên, cấp phát một vùng nhớ
động để lưu trữ n số nguyên đó, hiện các số vừa nhập, tính tổng, trung bình cộng,
max, min của dãy số đó.
#include
#include
#include
void main()
{
clrscr();
int *a,n ,i;
printf("\n nhap so luong phan tu n= ");
scanf("%d",&n);
// cap phat bo nho
a=(int*)malloc(n*sizeof(int));
//nhap du lieu
for(i=0;i<n;i++)
{
printf("\n Nhap so thu %d ",i+1);
scanf("%d",a+i);
}
// hien du lieu
printf("\n day so vua nhap la: ");
for(i=0; i<n; i++)
{
printf(" %d",*(a+i));
}
//tinh tong
int tong=0;
for(i=0; i<n; i++)
{
tong=tong + *(a+i);
}
printf("\n tong cua day so do la: %d",tong);
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
90
//tinh tb cong
printf("\n Trung binh cong cua day do la:
%f",tong/(n*1.0));
// tim max
int max= *a;
for(i=0; i<n; i++)
{
if(max< *(a+i))
max= *(a+i);
}
printf("\n so lon nhat la: %d",max);
//tim min
int min = *a;
for(i=0; i<n; i++)
{
if(min >*(a+i))
min = *(a+i);
}
printf("\n so nho nhat la: %d", min);
getch();
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
91
Chương 7 : CHUỖI KÝ TỰ
1. Khái niệm
Trong C không có kiểu xâu, do vậy ngôn ngữ C sử dụng mảng để lưu trữ chuỗi ký
tự, mỗi phần tử của mảng sẽ là một kiểu dữ liệu kiểu char
Chuỗi ký tự là một dãy các ký tự hoặc một mảng các ký tự. Các ký tự được lưu
theo thứ tự từ trái sang phải của chuỗi và bắt đầu bằng phần tử có chỉ số 0 cho đến
hết chuỗi. Kết thúc chuỗi là ký tự ‘\0’ (còn được gọi là ký tự NULL trong bảng mã
ASCII).
Các hằng chuỗi ký tự được đặt trong cặp dấu nháy kép ””.
2. Khai báo
2.1 Khai báo theo mảng
Cú pháp: char [Chiều dài tối đa]
VD: Trong chương trình, ta có khai báo:
char Ten[12];
Trong khai báo trên, bộ nhớ sẽ cung cấp 12+1 bytes để lưu trữ nội dung của chuỗi
ký tự Ten; byte cuối cùng để lưu ký tự ‘\0’ để chấm dứt chuỗi.
Ghi chú:
Chiều dài tối đa của biến chuỗi là một hằng nguyên nằm trong khoẳng từ 1 đến
255 bytes.
Chiều dài tối đa không nên khai báo thừa để tránh lẵng phí bộ nhớ, nhưng cũng
không nên khai báo thiếu.
2.2 Khai báo theo con trỏ
Cú pháp: char *;
VD: Trong chương trình, ta có khai báo:
char *Ten;
Trong khai báo trên, bộ nhớ sẽ dành 2 bytes để lưu trữ địa chỉ của biến con trỏ
Ten đang chỉ đến, chưa cung cấp nơi để lưu trữ dữ liệu. Muốn có chỗ để lưu trữ dữ
liệu, ta phải gọi đến hàm malloc() hoặc calloc có trong alloc.h, sau đó mới gán dữ liệu
cho biến.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
92
3. Các thao tác trên chuỗi
3.1 Nhập chuỗi từ bàn phím
Để nhập một chuỗi ký tự từ nàn phím, ta sử dụng hàm gets() với cú pháp như
sau:
gets()
VD: char Ten[20];
gets(Ten);
Ta cũng có thể sử dụng hàm scanf() để nhập dữ liệu cho biến chuỗi, tuy nhiên
lúc này ta chỉ có thể nhập được một chuỗi không có dấu khoẳng trắng.
Ngoài ra, hàm cgets() (trong conio.h) cũng được sử dụng để nhập chuỗi.
Chú ý:
Nếu trước lệnh nhập chuỗi có lệnh nhập số thì phải xóa vùng đệm bàn phím
trước khi nhập chuỗi bằng lệnh sau:
fflush(stdin);
Ví dụ
int n;
char s[50];
printf(“nhap so n =”);
scanf(“%d”,&n);
printf(“nhap chuoi s = ”);
fflush(stdin); gets(s);
3.2 Xuất chuỗi ra màn hình
Để xuất chuỗi (biểu thức chuỗi) lên màn hình, ta sử dụng hàm puts(), với cú pháp
như sau:
puts()
VD: Nhập vào một chuỗi và hiển thị trên màn hình chuỗi vừa nhập.
#include
#include
#include
int main()
{
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
93
char Ten[12];
printf(“Nhap chuoi:”); gets(Ten);
printf(“Chuoi vua nhap”);puts(Ten);
getch();
return 0;
}
Ngoài ra, ta có thể sử dụng hàm printf(), cputs() (trong conio.h) để hiển thị
chuỗi lên màn hình.
3.3 Một số hàm xử lý chuỗi
Hàm cho biết độ dài thực sự (số ký tự) của chuỗi ký tự lưu trong mảng
int strlen(s);
ví dụ:
char s[100] = “Ha Noi Viet Nam”;
int a = strlen(s);
sẽ trả về độ dài thực của sâu s gán vào biến nhớ a, cụ thể là a = 15
Lệnh gán chuỗi từ sâu này vào sâu khác
strcpy(s1, s2 );
sẽ thưc hiện chép chuỗi ký tự trong sâu s2 vào s1.
Chú ý:
Trong ngôn ngữ C không thể sử dung phép gán ( = ) để gán hai chuỗi do đó phải
sử dụng lệnh strcpy này.
Lệnh nối hai sâu lại với nhau
strcat( s1,s2);
sẽ thưc hiện nối sâu S2 vào sâu S1
ví dụ:
char s1[100] = “Ha Noi”;
char s2[100] =”Viet Nam”;
strcat (s1,s2);
kết quả xâu s1 sẽ thay đổi và đó là:”Ha NoiViet Nam”
Lệnh chuyển sâu thành chữ hoa
strupr(s);
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
94
Lệnh chuyển sâu thành chữ thường.
strlwr(s);
3.4 Một số thao tác cơ bản trên chuỗi
Đêm ký tự, đếm từ
Cho một chuỗi s và đếm xem trong chuỗi có bao nhiêu chữ thỏa mãn một điều
kiện nào đó:
int dem = 0
for( i =0; i<strlen(s);i++)
if(s[i] thỏa mãn điều kiện) dem ++;
printf(“so ky tu dem duoc la: %d”,dem) ;
Thuật toán đếm số từ co trong chuỗi s theo tiêu chuẩn nhận biết đầu từ( là
cặp ký tự thỏa mãn ký tự trước là dấu cách, ký tự sau khác dấu cách).
int dem;
if(s[0] = = ‘ ’ ) dem = 0 ;
else dem = 1;
for( i = 1;i < strlen(s);i++ )
{
if(s[i] = =’ ’&& s[i+1]!=’ ’)
dem ++;
}
printf(“so tu dem duoc là: %d”,dem) ;
Chuẩn hóa chuỗi ký tự
Một chuỗi ký tự ở dạng chuẩn nếu không có dấu cách ở đầu, không có dấu cách
ở cuối và giữa hai từ có duy nhất một dấu cách.
Thuật toán chuẩn hóa được chia làm 3 bước như sau
Bước 1: Xóa dấu cách ở đầu
int i = 0 ;
while (s[i] = =’ ’&&i < strlen(s))
{
i++ ;
strcpy(&s[0],&s[i]);
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
95
}
Bước 2: Xóa dấu cách thừa giữu hai từ
int i = 0;
while(i<strlen(s)-1)
{
if(s[i] == ‘ ‘&& s[i+1] == ‘ ‘ )
strcpy(&s[i],&s[i+1]);
i++;
}
Bước 3: xóa dấu cách ở cuối chuỗi
while(s[strlen(s)-1] ==’ ’ ) s[strlen(s)-1]= ‘\0’;
Tách từ
+ Tách từ đầu tiên
i=0;
while(s[i] = =' ')i++;
for(j=0;(i<strlen(s))&&(s[i]!=' ');i++,j++)
tudau[j] = s[i];
tudau[j]='\0';
Tách từ cuối cùng
i = strlen(s)-1;
while(s[i] = =' ')i--;
j=i;
while(s[i]!=' ')i--;
i++;
for(int x = 0;i<=j;i++)
{
tucuoi[x] = s[i];
x++;
}
tucuoi[x] = '\0';
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
96
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
97
BÀI TẬP THỰC HÀNH
Bài tập 1
Lập trình đọc vào một sâu. Đọc vào từ bàn phím một kí tự bất kì, rồi đếm xem
số lần xuất hiện ký tự này trong sâu. Báo kết quả ra màn hình.
#include
#include
#include
int dem(char s[],char a)
{
int d=0;//so ki tu
int l=strlen(s);
for(int i=0;i<l;i++)
{
if(s[i] == a)
d++;
}
return d;
}
void main()
{
int i;
char s[100],ch;
clrscr();
printf("\n nhap vao mot cau: ");
fflush(stdin);gets(s);
printf("\n Nhap vao mot ki tu bat ki:");
ch = getche();
//dem so lan xuat hien cua ki tu
printf("\nki tu %c xuat hien %d lan trong xau
",ch,dem(s,ch));
getch();
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
98
Bài tập 2
Viết chương trình đọc vào một xâu. Đếm số từ của một sâu. Từ được hiểu là
xâu khác rỗng và không có dấu cách.
#include
#include
#include
int sotu(char S[])
{
int dem=0,n;
n=strlen(S);
if(S[0]!=' ')dem=1;
for(int i=1;i<n;i++)
{
if(S[i]= =' '&& S[i+1]!=' ')dem++;
}
return dem;
}
void main()
{
int i;
char s[100];
clrscr();
printf("\n nhap vao mot sau ki tu: ");
fflush(stdin);gets(s);
//dem so tu cua mot sau
printf("sau ban vua nhap co: %d tu",sotu(s));
getch();
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
99
Bài tập 3
Lập trình đọc vào một câu từ bàn phím rồi đưa ra màn hình dưới dạng một cột.
thí dụ đọc vào TIÊN HỌC LỄ HẬU HỌC VĂN kết quả đưa ra thành
TIÊN
HỌC
LỄ
HẬU
HỌC
VĂN
#include
#include
#include
void hien(char s[])
{
int l=strlen(s),n=0;
while(s[n]= =' ')n++;
for(int i = n ; i<l ; i++)
{ if(s[i]!=' ')
printf("% c",s[i]);
else
printf("\n");
}
}
void main()
{ int i;
char s[100];
clrscr();
printf("\n nhap vao mot cau: ");
fflush(stdin);gets(s);
printf("hien cau moi tu tren mot dong la:\n");
hien(s);
getch();
}
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
100
Bài tập 4
Nhập vào từ bàn phím họ tên của một người. Hiện tên và họ của người đó lên
màn hình.
#include
#include
#include
void main()
{
char s[100],ten[10],ho[10];
int i=0,j=0;
clrscr();
printf("\n nhap vao ten cua mot nguoi: ");
fflush(stdin);gets(s);
// tach ho
while(s[i]= =' ')i++;
do{
ho[j]=s[i];
i++; j++;
}while(s[i]!=' ');
ho[j]='\0';
printf("\n ho cua nguoi nay la: %s",ho);
// tach ten
j=0;
i=strlen(s);
while(s[i]= =' ')i--;
int i1=i;
while(s[i1]!=' ')i1--;
do{
ten[j]=s[i1];
i1++; j++;
}while(i1<=i);
ten[j]='\0';
printf("\n ten cua nguoi nay la: %s",ten);
getch(); }
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
101
PHỤ LỤC
HƯỚNG DẪN DEBUG TRÊN MÔI TRƯỜNG BORLAND C
Mặc dù chương trình không còn lỗi nhưng khi chạy chương trình vẫn ra kết quả
sai, những lỗi đó có thể là:
• Dùng chấm phẩy sau: if, else, for, while, mà chưa thực hiện lệnh.
• Định dạng nhập xuất sai hay khai báo sai kiểu dữ liệu.
• Chia cho 0.
• Không có điều kiện dừng (điều kiện dừng sai).
• Phân tích thuật toán thiếu (chưa vét hết các trường hợp) hoặc sai.
Các thao tác debug:
Nhấn F7 hoặc F8 để chạy từng bước (nếu không có lỗi khi biên dịch)
- F7: Đi từng lệnh của hàm con nếu có gọi hàm.
- F8: không vào chi tiết từng lệnh khi gọi đến hàm con (chỉ đưa ra kết quả của hàm
con).
Quan sát vệt sáng để biết chương trình đang thực hiện đến vị trí lệnh nào.
- Nhấn Ctrl+F7 (hoặc nhấn phím Insert nếu đã có cửa sổ Watch): Nhập vào biến
cần theo dõi giá trị các biến khi thực hiện xong lệnh hay hàm nào đó.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
102
- Có thể xóa biến trên cửa sổ Watch bằng cách chọn biến trên cửa sổ Watch và
nhấn phím Delete.
- Nếu không thấy cửa sổ hiển thị giá trị biến (Watch) nhấn Alt+W+W hoặc vào
menu Window chọn Watch.
Nếu muốn bỏ qua một đoạn nào đó (tức không cần kiểm tra đọan đó) thì
nhấn F4 để chương trình thực thi tới vị trí dòng của dấu nháy rồi dừng lại đó (dấu
nháy phải tại vị trí những dòng phía sau của vệt sáng, nhấn F6 để chuyển qua lại các
cửa sổ).
- Muốn thay đổi giá trị của biến ta dùng phím Ctrl+F4 để hiển thị cửa sổ.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
103
Nhập vào tên biến ở ô Expression, chọn nút Evaluate (hoặ nhấn Enter), ô Result
sẽ hiển thị kết quả tại thời điểm đó, sau đó nhập giá trị mới cho biến tại ô New Value
Enter (dùng phím tab để di chuyển vị trí chọn).
Ngoài ra có thể đánh dấu để chương trình thực thi đến vị trí đánh dấu (khi chưa chạy
từng bước) dùng phím F8 để đánh dấu ngay vị trí dấu nháy. Vị trí đánh dấu sẽ có vệt
sáng màu đỏ.
Có thể đánh dấu nhiều vị trí khác nhau. Nhấn Ctrl+F9 để chương trình thực thi
đến vị trí đánh dấu theo thứ tự từ trên xuống dưới, đồng thời cũng có thể dùng phím
F7 hoặc F8 giống như trên để chạy từng bước.
Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái
104
Ngoài ra, có thể dùng phím ALT+F5 để xem kết quả xuất trong quá trình debug
(để kiểm tra nhập xuất).
Trong quá trình chạy từng bước có thể kết thúc bằng cách nhấn Ctrl+F2.
Các thao tác liên quan đến cửa sổ Watch
- Di chuyển cửa sổ Watch: Chọn cửa sổ Watch, nhấn Ctrl+F5. Sau đó dùng phím
mũi tên để di chuyển cửa sổ tới vị trí mới. Nhấn phím Enter.
- Thay đổi kích thứơc cửa sổ Watch (khi đang chọn bằng Ctrl+F5 trên cửa sổ
Watch) nhấn Shift + phím mũi tên rồi nhấn phím Enter.
TÀI LIỆU THAM KHẢO
PHẠM VĂN ẤT: “Kỹ thuật lập trình C: cơ sở và nâng cao”. Nhà Xuất BảnKhoa Học
Kỹ Thuật – 1996.
Lê Mạnh Thạnh, Giáo trình môn lập trình C, NXB Giáo dục, 2000;
Nguyễn Linh Giang, Nguyễn Xuân Thực, Lê Văn Thái, Giáo trình kỹ thuật lập trình
C, NXB Giáo dục, 2005;
Ngô Trung việt, Giáo trình ngôn ngữ lập trình C và C++ , NXB Giao thông vận tải,
1995;
B. Kernighan and D. Ritchie, The C programming language, Prentice Hall, 1990.
Các file đính kèm theo tài liệu này:
- lap_tr_nh_co_ban_p2_5647.pdf