Tải bản đầy đủ (.pdf) (43 trang)

Giáo trình Lập trình căn bản (Nghề: Lập trình máy tính - Trình độ CĐ/TC): Phần 2 - Trường Cao đẳng Nghề An Giang

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (525.06 KB, 43 trang )

CHƢƠNG 5
HÀM
Giới thiệu
Một chƣơng trình viết trong ngơn ngữ C là một dãy các hàm. Hàm chia các bài
toán lớn thành các công việc nhỏ hơn, giúp thực hiện những cơng việc lặp lại nào đó
một cách nhanh chóng mà khơng phải viết lại đoạn chƣơng trình.
Bài này sẽ trình bày cách viết chƣơng trình theo cấu trúc hàm, cách truyền tham
số cho hàm trong lập trình, sử dụng đệ qui trong lập trình
Mục tiêu
- Viết chƣơng trình theo cấu trúc hàm
- Vận dụng các cách truyền tham số cho hàm trong lập trình
- Sử dụng đệ qui trong lập trình
Nội dung chính
I. KHÁI NIỆM
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 tốn lớn thành các công việc nhỏ hơn,
giúp thực hiện những công việc lặp lại nào đó một cách nhanh chóng mà khơng phải
viết lại đoạn chƣơng trình. 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ừ hàm main()
II. KHAI BÁO HÀM
type tên hàm (khai báo các đối số)
{
Khai báo các biến cục bộ
Các câu lệnh
[return[biểu thức];]
}
Dòng tiêu đề:
Trong dòng đầu tiên của hàm chứa các thông tin về: kiểu hàm, tên hàm, kiểu và
tên mỗi đối.
Ví dụ:
float max3s(float a, float b, float c)


khai báo các đối có dạng:
57


Kiểu đối 1 tên đối 1, kiểu đối 2 tên đối 2,..., kiểu đối n tên đối n
Thân hàm:
Sau dòng tiêu đề là thân hàm. Thân hàm là nội dung chính của hàm bắt đầu và
kết thúc bằng các dấu { }.
Trong thân hàm chứa các câu lệnh cần thiết để thực hiện một yêu cầu nào đó đã
đề ra cho hàm.
Thân hàm có thể sử dụng một câu lệnh return, có thể dùng nhiều câu lệnh return
ở các chỗ khác nhau, và cũng có thể khơng sử dụng câu lệnh này.
Dạng tổng quát của nó là:
return [biểu thức];
Giá trị của biểu thức trong câu lệnh return sẽ đƣợc gán cho hàm.
Ví dụ: Xét bài tốn: Tìm giá trị lớn nhất của ba số mà giá trị mà giá trị của
chúng đƣợc đƣa vào bàn phím.
Xây dựng chƣơng trình và tổ chức thành hai hàm: Hàm main() và hàm max3s.
Nhiệm vụ của hàm max3s là tính giá trị lớn nhất của ba số đọc vào, giả sử là a,b,c.
Nhiệm vụ của hàm main() là đọc ba giá trị vào từ bàn phím, rồi dùng hàm max3s để
tính nhƣ trên, rồi đƣa kết quả ra màn hình.
Chƣơng trình đƣợc viết nhƣ sau:
#include <stdio.h>
float max3s(float a,float b,float c); /* Nguyên mẫu hàm*/
void main()
{
float x,y,z;
printf("\n Vao ba so x,y,z:");
scanf("%f%f%f",&x&y&z);
printf("\n Max cua ba so x=%8.2f y=%8.2f z=%8.2f la: %8.2f",

x,y,z,max3s(x,y,z));
}

/* Kết thúc hàm main*/

float max3s(float a,float b,float c)
{
float max;
max=a;
58


if (maxif (maxreturn(max);
} /* Kết thúc hàm max3s*/
Quy tắc hoạt động của hàm:
Một cách tổng quát lời gọi hàm có dạng sau:
tên hàm ([Danh sách các tham số thực])
Số các tham số thực tế thay vào trong danh sách các đối phải bằng số tham số
hình thức và lần lƣợt chúng có kiểu tƣơng ứng với nhau.
Khi gặp một lời gọi hàm thì nó sẽ bắt đầu đƣợc thực hiện. Nói cách khác, khi
máy gặp lời gọi hàm ở một vị trí nào đó trong chƣơng trình, máy sẽ tạm dời chỗ đó và
chuyển đến hàm tƣơng ứng. Q trình đó diễn ra theo trình tự sau:
Cấp phát bộ nhớ cho 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 ứ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ẽ xoá các
đối, biến cục bộ và ra khỏi hàm.
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 các biểu thức chứa nó.
Các tham số thực, các đối và biến cục bộ:
Do đối và biến cục bộ đều có phạm vi hoạt động trong cùng một hàm nên đối
và biến cục bộ cần có tên khác nhau.
Đối và biến cục bộ đều là các biến tự động. Chúng đƣợc cấp phát bộ nhớ khi
hàm đƣợc xét đến và bị xố khi ra khỏi hàm nên ta khơng thể mang giá trị của đối ra
khỏi hàm.
Đối và biến cục bộ có thể trùng tên với các đại lƣợng ngồi hàm mà không gây
ra nhầm lẫn nào.
Khi một hàm đƣợc gọi tới, việc đầu tiên là giá trị của các tham số thực đƣợc
gán cho các đối (trong ví dụ trên hàm max3s, các tham số thực là x,y,z, các đối tƣơng
ứng là a,b,c). Nhƣ vậy các đối chính là các bản sao của các tham số thực. Hàm chỉ
làm việc trên các đối.
Các đối có thể bị biến đổi trong thân hàm, cịn các tham số thực thì khơng bị
thay đổi.
Chú ý:
59


Khi hàm khai báo khơng có kiểu ở trƣớc nó thì nó đƣợc mặc định là kiểu int.
Khơng nhất thiết phải khai báo nguyên mẫu hàm. Nhƣng nói chung nên có vì
nó cho phép chƣơng trình biên dịch phát hiện lỗi khi gọi hàm hay tự động việc
chuyển dạng.
Nguyên mẫu của hàm thực chất là dòng đầu tiên của hàm thêm vào dấu ;. Tuy
nhiên trong nguyên mẫu có thể bỏ tên các đối.
Hàm thƣờng có một vài đối. Ví dụ nhƣ hàm max3s có ba đối là a,b,c. cả ba đối
này đều có giá trị float. Tuy nhiên, cũng có hàm khơng đối nhƣ hàm main.
Hàm thƣờng cho ta một giá trị nào đó. Lẽ dĩ nhiên giá trị của hàm phụ thuộc
vào giá trị các đối.
III. KẾT QUẢ TRẢ VỀ CỦA HÀM

- Hàm có thể nhận một hoặc nhiều giá trị đầu vào và trả về một giá trị thuộc kiểu
dữ liệu nào đó. Kiểu dữ liệu đƣợc trả về của hàm đƣợc khai báo ở type.
- Hàm có thề có hoặc khơng có giá trị trả về. Nếu hàm khơng có nhận giá trị đầu
vào và khơng có giá trị trả về thì ở khai báo bằng từ khóa void.
- Các biến hoặc biểu thức cung cấp giá trị đầu vào cho hàm đƣợc gọi là đối số.
Hàm có thể thay đổi các đối số của nó.
IV. CÁCH TRUYỀN THAM SỐ CHO HÀM
Cho đến nay, trong tất cả các hàm chúng ta đã biết, tất cả các tham số truyền cho
hàm đều đƣợc truyền theo giá trị. Điều này có nghĩa là khi chúng ta gọi hàm với các
tham số, những gì chúng ta truyền cho hàm là các giá trị chứ không phải bản thân các
biến. Ví dụ, giả sử chúng ta gọi hàm addition nhƣ sau:
int x=5, y=3, z;
z = addition ( x , y );
Trong trƣờng hợp này khi chúng ta gọi hàm addition thì các giá trị 5 and 3 đƣợc
truyền cho hàm, khơng phải là bản thân các biến

Điều đáng nói ở đây là khi chúng ta thay đổi giá trị của các biến a hay b bên
trong hàm thì các biến x và y vẫn khơng thay đổi vì chúng đâu có đƣợc truyền cho
hàm chỉ có giá trị của chúng đƣợc truyền mà thôi.
60


Hãy xét trƣờng hợp cần thao tác với một biến ngồi ở bên trong một hàm. Vì
vậy ta sẽ phải truyền tham số dƣới dạng tham số biến nhƣ ở trong hàm duplicate
trong ví dụ dƣới đây:
// passing parameters by reference

x=2, y=6, z=14

#include <iostream.h>

void duplicate (int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2;
}
int main ()
{
int x=1, y=3, z=7;
duplicate (x, y, z);
cout << "x=" << x << ", y=" << y << ",
z=" << z;
return 0;
}

Điều đầu tiên làm ta chú ý là trong khai báo của duplicate theo sau tên kiểu của
mỗi tham số đều là dấu và (&), để báo hiệu rằng các tham số này đƣợc truyền theo
tham số biến chứ không phải tham số giá trị.
Khi truyền tham số dƣới dạng tham số biến chúng ta đang truyền bản thân biến
đó và bất kì sự thay đổi nào mà chúng ta thực hiện với tham số đó bên trong hàm sẽ
ảnh hƣởng trực tiếp đến biến đó.

61


Trong ví dụ trên, chúng ta đã liên kết a, b và c với các tham số khi gọi hàm (x, y
và z) và mọi sự thay đổi với a bên trong hàm sẽ ảnh hƣởng đến giá trị của x và hoàn
toàn tƣơng tự với b và y, c và z.
Kiểu khai báo tham số theo dạng tham số biến sử dụng dấu và (&) chỉ có trong
C++. Trong ngôn ngữ C chúng ta phải sử dụng con trỏ để làm việc tƣơng tự nhƣ thế.

Truyền tham số dƣới dạng tham số biến cho phép một hàm trả về nhiều hơn một
giá trị.
V. ĐỆ QUI
1. Giới thiệu
C không những cho phép từ hàm này gọi tới hàm khác, mà nó cịn cho phép từ
một điểm trong thân của một hàm gọi tới chính hàm đó. Hàm nhƣ vậy gọi là hàm đệ
qui.
Khi hàm gọi đệ qui đến chính nó, thì mỗi lần gọi máy sẽ tạo ra một tập các biến
cục bộ mới hoàn toàn độc lập với tập các biến cục bộ đã đƣợc tạo ra trong các lần gọi
trƣớc.
Để minh hoạ chi tiết những điều trên, ta xét một ví dụ về tính giai thừa của số
nguyên dƣơng n. Khi không dùng phƣơng pháp đệ qui hàm có thể đƣợc viết nhƣ sau:
long int gt(int n) /* Tính n! với n>=0*/
{
long int gtphu=1;
int i;
for (i=1;i<=n;++i)
gtphu*=i;
return s;
}
Ta nhận thấy rằng n! có thể tính theo cơng thức truy hồi sau:
n!=1

nếu n=0

n!=n*(n-1)!

nếu n>0

Hàm tính n! theo phƣơng pháp đệ qui có thể đƣợc viết nhƣ sau:

long int gtdq(int n)
{
if (n==0 || n==1)
return 1;
62


else
return(n*gtdq(n-1));
}
Giải thích hoạt động của hàm đệ qui khi sử dụng trong hàm main dƣới đây:
#include <stdio.h>
void main()
{
printf("\n 3!=%d",gtdq(3));
}
Lần gọi đầu tiên tới hàm gtdq đƣợc thực hiện từ hàm main(). Máy sẽ tạo ra một
tập các biến tự động của hàm gtdq. Tập này chỉ gồm các đối n. Ta gọi đối n đƣợc tạo
ra lần thứ nhất là n thứ nhất. Giá trị của tham số thực (số 3) đƣợc gán cho n thứ nhất.
Lúc này biến n trong thân hàm đƣợc xem là n thứ nhất. Do n thứ nhất có giá trị bằng 3
nên điều kiện trong tốn tử if là sai và do đó máy sẽ lựa chọn câu lệnh else. Theo câu
lệnh này, máy sẽ tính giá trị biểu thức:
n*gtdq(n-1) (*)
Để tính biểu thức trên, máy cần gọi chính hàm gtdq vì thế lần gọi thứ hai sẽ
thực hiện. Máy sẽ tạo ra đối n mới, ta gọi đó là n thứ hai. Giá trị của n-1 ở đây lại là
đối của hàm , đƣợc truyền cho hàm và hiểu là n thứ hai, do vậy n thứ hai có giá trị là
2. Bây giờ, do n thứ hai vẫn chƣa thoả mãn điều kiện if nên máy lại tiếp tục tính biểu
thức:
n*gtdq(n-1) (**)
Biểu thức trên lại gọi hàm gtdq lần thứ ba. Máy lại tạo ra đối n lần thứ ba và ở

đây n thứ ba có giá trị bằng 1. Đối n=1 thứ ba lại đƣợc truyền cho hàm, lúc này điều
kiện trong lệnh if đƣợc thoả mãn, máy đi thực hiện câu lệnh:
return 1=gtdq(1) (***)
Bắt đầu từ đây, máy sẽ thực hiện ba lần ra khỏi hàm gtdq. Lần ra khỏi hàm thứ
nhất ứng với lần vào thứ ba. Kết quả là đối n thứ ba đƣợc giải phóng, hàm gtdq(1) cho
giá trị là 1 và máy trở về xét giá trị biểu thức
n*gtdq(1) đây là kết quả của (**)
ở đây, n là n thứ hai và có giá trị bằng 2. Theo câu lệnh return, máy sẽ thực hiện lần ra
khỏi hàm lần thứ hai, đối n thứ hai sẽ đƣợc giải phóng, kết quả là biểu thức trong (**)
có giá trị là 2.1. Sau đó máy trở về biểu thức (*) lúc này là:
n*gtdq(2)=n*2*1
63


n lại hiểu là thứ nhất, nó có giá trị bằng 3, do vậy giá trị của biểu thức trong (*) là
3.2.1=6. Chính giá trị này đƣợc sử dụng trong câu lệnh printf của hàm main() nên kết
quả in ra trên màn hình là:
3!=6
Chú ý:
Hàm đệ qui so với hàm có thể dùng vịng lặp thì đơn giản hơn, tuy nhiên với
máy tính khi dùng hàm đệ qui sẽ dùng nhiều bộ nhớ trên ngăn xếp và có thể dẫn đến
tràn ngăn xếp. Vì vậy khi gặp một bài tốn mà có thể có cách giải lặp (khơng dùng đệ
qui) thì ta nên dùng cách lặp này. Song vẫn tồn tại những bài tốn chỉ có thể giải bằng
đệ qui.
2. Các bài tốn có thể dùng đệ qui
Phƣơng pháp đệ qui thƣờng áp dụng cho các bài toán phụ thuộc tham số có hai
đặc điểm sau:
Bài tốn dễ dàng giải quyết trong một số trƣờng hợp riêng ứng với các giá trị đặc
biệt của tham số. Ngƣời ta thƣờng gọi là trƣờng hợp suy biến.
Trong trƣờng hợp tổng quát, bài tốn có thể qui về một bài tốn cùng dạng

nhƣng giá trị tham số thì bị thay đổi. Sau một số hữu hạn bƣớc biến đổi dệ qui nó sẽ
dẫn tới trƣờng hợp suy biến.
Bài tốn tính n giai thừa nêu trên thể hiện rõ nét đặc điểu này.
3. Cách xây dựng hàm đệ qui:
Hàm đệ qui thƣờng đƣợc xây dựng theo thuật tốn sau:
if (trƣờng hợp suy biến)
{
Trình bày cách giải bài toán khi suy biến
}
else /* Trƣờng hợp tổng quát */
{
Gọi đệ qui tới hàm (đang viết) với các giá
trị khác của tham số
}
4. Các ví dụ về dùng hàm đệ qui:
Ví dụ 1: Bài tốn dùng đệ qui tìm USCLN của hai số nguyên dƣơng a và b.

64


Trong trƣờng hợp suy biến, khi a=b thì USCLN của a và b chính là giá trị của
chúng.
Trong trƣờng hợp chung:
uscln(a,b)=uscln(a-b,b) nếu a>b
uscln(a,b)=uscln(a,b-a) nếu aTa có thể viết chƣơng trình nhƣ sau:
#include <stdio.h>
int uscln(int a,int b); /* Nguyên mẫu hàm*/
void main()
{


int m,n;
printf("\n Nhap cac gia tri cua a va b:");
scanf("%d%d",&m,&n);
printf("\n USCLN cua a=%d va b=%d la:%d",m,m,uscln(m,n))

}
int uscln(int a,int b)
{
if (a==b)
return a;
else
if (a>b)
return uscln(a-b,b);
else
return uscln(a,b-a);
}
Ví dụ 2: Chƣơng trình đọc vào một số rồi in nó ra dƣới dạng các ký tự liên tiếp.
# include <stdio.h>
# include <conio.h>
void prind(int n);
void main()
65


{
int a;
clrscr();
printf("n=");
scanf("%d",&a);

prind(a);
getch();
}
void prind(int n)
{
int i;
if (n<0)
{
putchar('-');
n=-n;
}
if ((i=n/10)!=0)
prind(i);
putchar(n%10+'0');
}
BÀI TẬP
1. Sử dụng hàm viết lại các chƣơng trình đã thực hiện ở các chƣơng trƣớc
2. Sử dụng hàm viết chƣơng trình tính an (a: thực, n: nguyên dƣơng) sử dụng đệ
qui và khơng đệ qui
3. Tính phần tử thứn theo cơng thức truy hồi (đệ qui và không đệ qui)
Phần tử Fibonaci: F1=F2=1
Fn= Fn-1 + Fn-2 với n>2

66


CHƢƠNG 6
MẢNG
Giới thiệu
Bài này sẽ trình bày cách khai báo và khởi tạo mảng, sử dụng mảng trong lập

trình, sử dụng mảng làm tham số trong các bài toán
Mục tiêu
- Khai báo và khởi tạo mảng
- Sử dụng mảng trong lập trình
- Sử dụng mảng làm tham số trong các bài tốn
Nội dung chính
I. KHÁI NIỆM
Một mảng là một tập hợp các phần tử dữ liệu có cùng kiểu. Mỗi phần tử đƣợc
lƣu trữ ở các vị trí kế tiếp nhau trong bộ nhớ chính. Những phần tử này đƣợc gọi là
phần tử mảng.
Mỗi phần tử của mảng đƣợc định danh bằng một chỉ mục hoặc chỉ số gán cho
nó. Chiều của mảng đƣợc xác định bằng số chỉ số cần thiết để định danh duy nhất
mỗi phần tử. Một chỉ số là một số nguyên dƣơng đƣợc bao bằng dấu ngoặc vuông [ ]
đặt ngay sau tên mảng, không có khoảng trắng ở giữa. Một chỉ số chứa các giá trị
nguyên bắt đầu bằng 0. Vì vậy, một mảng player với 11 phần tử đƣợc biểu diễn nhƣ
sau:
player[0], player[1], player[2], ... , player[10].
II. KHAI BÁO MẢNG
Một mảng có một vài đặc tính riêng biệt và phải đƣợc khai báo khi sử dụng
chúng. Những đặc tính này bao gồm:
 Lớp lƣu trữ
 Kiểu dữ liệu của các phần tử mảng.
 Tên mảng – xác định vị trí phần tử đầu tiên của mảng.
 Kích thƣớc mảng - một hằng số có giá trị nguyên dƣơng.
Một mảng đƣợc khai báo giống nhƣ cách khai báo một biến, ngoại trừ tên mảng
đƣợc theo sau bởi một hoặc nhiều biểu thức, đƣợc đặt trong dấu ngoặc vuông [] xác
định chiều dài của mảng. Cú pháp tổng quát khai báo một mảng nhƣ sau:
lớp_lƣu_trữ kiểu_dữ_liệu tên_mảng[biểu_thức_kích_thƣớc]
67



Ở đây, biểu_thức_kích_thƣớc là một biểu thức xác định số phần tử trong
mảng và phải định ra một trị nguyên dƣơng. Lớp_lƣu_trữ là một tùy chọn. Mặc định
lớp automatic đƣợc dùng cho mảng khai báo bên trong một hàm hoặc một khối lệnh,
và lớp external đƣợc dùng cho mảng khai báo bên ngồi một hàm. Vì vậy mảng
player đƣợc khai báo nhƣ sau:
int player[11];
Nên nhớ rằng, trong khi khai báo mảng, kích thƣớc của mảng sẽ là 11, tuy
nhiên các chỉ số của từng phần tử bên trong mảng sẽ là từ 0 đến 10.
Các qui tắc đặt tên mảng là giống với qui tắc đặt tên biến. Một tên mảng và một tên
biến khơng đƣợc giống nhau, nó dẫn đến sự nhập nhằng. Nếu một sự khai báo nhƣ
vậy xuất hiện trong chƣơng trình, trình biên dịch sẽ hiển thị thông báo lỗi.
 Một vài qui tắc với mảng:
 Tất cả các phần tử của một mảng có cùng kiểu. Điều này có nghĩa là, nếu
một mảng đƣợc khai báo kiểu int, nó khơng thể chứa các phần tử có kiểu khác.
 Mỗi phần tử của mảng có thể đƣợc sử dụng bất cứ nơi nào mà một biến
đƣợc cho phép hay đƣợc yêu cầu.
 Một phần tử của mảng có thể đƣợc tham chiếu đến bằng cách sử dụng một
biến hoặc một biểu thức nguyên. Sau đây là các tham chiếu hợp lệ:
player[i]; /*Ở đó i là một biến, tuy nhiên cần phải chú ý rằng i nằm trong miền
giới hạn của chỉ số đã đƣợc khai báo cho mảng player*/
player[3] = player[2] + 5;
player[0] += 2;
player[i / 2 + 1];
 Kiểu dữ liệu của mảng có thể là int, char, float, hoặc double.
III. KHỞI TẠO MẢNG
Các mảng không đƣợc khởi tạo tự động, trừ khi mỗi phần tử mảng đƣợc gán một
giá trị riêng lẻ. Không nên dùng các mảng trƣớc khi có sự khởi tạo thích hợp. Điều
này là bởi vì khơng gian lƣu trữ của mảng khơng đƣợc khởi tạo tự động, do đó dễ gây
ra kết quả không lƣờng trƣớc. Mỗi khi các phần tử của một mảng chƣa khởi tạo đƣợc

sử dụng trong các biểu thức toán học, các giá trị đã tồn tại sẵn trong ô nhớ sẽ đƣợc sử
dụng, các giá trị này khơng đảm bảo rằng có cùng kiểu nhƣ khai báo của mảng, trừ
khi các phần tử của mảng đƣợc khởi tạo một cách rõ ràng. Điều này đúng khơng chỉ
cho các mảng mà cịn cho các biến thơng thƣờng.
Trong đoạn mã lệnh sau, các phần tử của mảng đƣợc gán giá trị bằng các dùng
vòng lặp for.
68


int ary[20], i;
for(i=0; i<20; i++)
ary[i] = 0;
Khởi tạo một mảng sử dụng vịng lặp for có thể đƣợc thực hiện với một hằng giá
trị, hoặc các giá trị đƣợc sinh ra từ một cấp số cộng.
Một vòng lặp for cũng có thể đƣợc sử dụng để khởi tạo một mảng các ký tự nhƣ
sau:
Ví dụ :
#include <stdio.h>
void main()
{
char alpha[26];
int i, j;
for(i = 65, j = 0; i < 91; i++, j++)
{
alpha[j] = i;
printf(“The character now assigned is %c\n”,
alpha[j]);
}
getchar();
}

Một phần kết quả của chƣơng trình trên nhƣ sau:
The character now assigned is A
The character now assigned is B
The character now assigned is C

.

.
.
Chƣơng trình trên gán các mã ký tự ASCII cho các phần tử của mảng alpha. Kết
quả là khi in với định dạng %c, một chuỗi các ký tự đƣợc xuất ra màn hình. Các
mảng cũng có thể đƣợc khởi tạo khi khai báo. Điều này đƣợc thực hiện bằng việc gán
tên mảng với một danh sách các giá trị phân cách nhau bằng dấu phẩy (,) đặt trong
cặp dấu ngoặc nhọn {}. Các giá trị trong cặp dấu ngoặc nhọn {} đƣợc gán cho các
phần tử trong mảng theo đúng thứ tự xuất hiện.
69


Ví dụ:
int deci[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
static float rates[4] = {0.0, -2.5, 13.75, 18.0};
char company[5] = {„A‟, „P‟, „P‟, „L‟, „E‟};
int marks[100] = {15, 13, 11, 9}
Các giá trị khởi tạo của mảng phải là các hằng, không thể là biến hoặc các biểu
thức. Một vài phần tử đầu tiên của mảng sẽ đƣợc khởi tạo nếu số lƣợng giá trị khởi
tạo là ít hơn số phần tử mảng đƣợc khai báo. Các phần tử còn lại sẽ đƣợc khởi tạo giá
trị 0. Ví dụ, trong mảng marks sau khi có sự khởi tạo nhƣ trên, bốn phần tử đầu tiên
(từ 0 đến 3) tƣơng ứng đƣợc khởi tạo là 15, 13, 11 và 9. Các phần tử cịn lại có giá trị
0. Không thể chỉ khởi tạo các phần tử từ 1 đến 4, hoặc từ 2 đến 4, hay từ 2 đến 5 khi
sự khởi tạo đƣợc thực hiện tại thời điểm khai báo. Trong C khơng có khả năng lặp lại

sự khởi tạo giá trị.
Trong trƣờng hợp sự khởi tạo là tƣờng minh, lớp extern hoặc static, các phần tử
của mảng đƣợc đảm bảo khởi tạo là 0 (không giống lớp auto).
Khơng cần thiết khai báo kích thƣớc của mảng đang đƣợc khởi tạo. Nếu kích
thƣớc của mảng đƣợc bỏ qua khi khai báo, trình biên dịch sẽ xác định kích thƣớc của
mảng bằng cách đếm các giá trị đang đƣợc khởi tạo. Ví dụ, sự khai báo mảng
external sau đây sẽ chỉ định kích thƣớc của mảng ary là 5 vì có 5 giá trị khởi tạo.
int ary[] = {1, 2, 3, 4, 5};
IV. DÙNG MẢNG LÀM THAM SỐ
Trong C, khi một mảng đƣợc truyền vào hàm nhƣ một tham số, thì chỉ có địa chỉ
của mảng đƣợc truyền vào. Tên mảng không kèm theo chỉ số là địa chỉ của mảng.
Đoạn mã dƣới đây mô tả cách truyền địa chỉ của mảng ary cho hàm fn_ary():
void main()
{

int ary[10];
.
fn_ary(ary);
.
.

}
Nếu tham số của hàm là mảng một chiều thì tham số có thể đƣợc khai báo theo
một trong các cách sau:
fn_ary (int ary [10]) /* sized array */
70


{
:

}
hoặc
fn_arry (int ary [])

/*unsized array */

{
:
}
Cả hai khai báo ở trên đều cho cùng kết quả. Kiểu thứ nhất sử dụng cách khai
báo mảng chuẩn, chỉ rõ ra kích thƣớc của mảng. Kiểu thứ hai chỉ ra rằng tham số là
một mảng kiểu int có kích thƣớc bất kỳ
Chƣơng trình sau đây nhận các số vào một mảng số nguyên. Sau đó mảng này sẽ
đƣợc truyền vào hàm sum_arr(). Hàm sẽ tính tốn và trả về tổng của các số nguyên
trong mảng.
Ví dụ 8:
#include <stdio.h>
void main()
{
int num[5], ctr, sum = 0;
int sum_arr(int num_arr[]); /* Function declaration */
clrscr();

for(ctr = 0; ctr < 5; ctr++) /*Accepts numbers into the array*/
{
printf("\nEnter number %d: ", ctr+1);
scanf("%d", &num[ctr]);
}
71



sum = sum_arr(num); /* Invokes the function */
printf("\nThe sum of the array is %d", sum);
getch();
}
int sum_arr(int num_arr[]) /* Function definition */
{
int i, total;
for(i = 0, total = 0; i < 5; i++) /* Calculates the sum */
total += num_arr[i];
return total; /* Returns the sum to main() */
}
Kết quả của chƣơng trình trên đƣợc minh họa nhƣ sau:
Enter number 1: 5
Enter number 2: 10
Enter number 3: 13
Enter number 4: 26
Enter number 5: 21

The sum of the array is 75

V. MẢNG NHIỀU CHIỀU
Chúng ta đã biết thế nào là mảng một chiều. Điều này có nghĩa là các mảng chỉ
có một chỉ số. Các mảng có thể có nhiều hơn một chiều. Các mảng đa chiều giúp dễ
dàng trình bày các đối tƣợng đa chiều, chẳng hạn một đồ thị với các dịng và cột hay
tọa độ màn hình của máy tính. Các mảng đa chiều đƣợc khai báo giống nhƣ các mảng
một chiều, ngoại trừ có thêm một cặp dấu ngoặc vuông [] trong trƣờng hợp mảng hai
chiều. Một mảng ba chiều sẽ cần ba cặp dấu ngoặc vuông,... Một cách tổng quát, một
mảng đa chiều có thể đƣợc biểu diễn nhƣ sau:
72



storage_class data_type ary[exp1][exp2]....[expN];
Ở đó, ary là một mảng có lớp là storage_class, kiểu dữ liệu là data_type, và
exp1, exp2,..... , expN là các biểu thức nguyên dƣơng xác định số phần tử của mảng
đƣợc kết hợp với mỗi chiều.
Dạng đơn giản nhất và thƣờng đƣợc sử dụng nhất của các mảng đa chiều là
mảng hai chiều. Một mảng hai chiều có thể xem nhƣ là một mảng của hai „mảng một
chiều‟. Một mảng hai chiều đặc trƣng nhƣ bảng lịch trình của máy bay, xe lửa. Để xác
định thơng tin, ta sẽ chỉ định dòng và cột cần thiết, và thơng tin đƣợc đọc ra từ vị trí
(dịng và cột) đƣợc tìm thấy. Tƣơng tự nhƣ vậy, một mảng hai chiều là một khung
lƣới chứa các dòng và cột trong đó mỗi phần tử đƣợc xác định duy nhất bằng toạ độ
dịng và cột của nó. Một mảng hai chiều tmp có kiểu int với 2 dịng và 3 cột có thể
đƣợc khai báo nhƣ sau:
int tmp[2][3];
Mảng này sẽ chứa 2 x 3 (6) phần tử, và chúng có thể đƣợc biểu diễn nhƣ sau:
Dịng

0

1

2

0

e1

e2


e3

1

e4

e5

e6

Cột

Ở đó e1 – e6 biểu diễn cho các phần tử của mảng. Cả dòng và cột đƣợc đánh số
từ 0. Phần tử e6 đƣợc xác định bằng dòng 1 và cột 2. Truy xuất đến phần tử này nhƣ
sau:
tmp[1][2];
Ví dụ :
/* Chƣơng trình nhập các số vào một mảng hai chiều. */
#include <stdio.h>
void main()
{
int arr[2][3];
int row, col;
for(row = 0; row < 2; row++)
73


{
for(col = 0; col < 3; col++)
{

printf(“\nEnter a Number at [%d][%d]: ”, row, col);
scanf(“%d”, &arr[row][col]);
}
}
for(row = 0; row < 2; row++)
{
for(col = 0; col < 3; col++)
{
printf(“\nThe Number at [%d][%d] is %d”,
row, col, arr[row][col]);
}
}
}
BÀI TẬP
1. Viết một chƣơng trình nhập mảng 1 chiều n phần tử (n đƣợc nhập từ bàn phím) sắp
xếp mảng tăng dần và xuất mảng đã sắp xếp.
2. Viết một chƣơng trình nhập mảng 1 chiều n phần tử (n đƣợc nhập từ bàn phím),
nhập m (m phần tử liên tiếp). Kiểm tra mảng có chuỗi con tăng hay khơng.
3. Viết một chƣơng trình nhập các số sau đây vào một mảng và đảo ngƣợc mảng
34 45 56 67 89
4. Viết chƣơng trình nhập mảng 1 chiều n phần tử (n đƣợc nhập từ bàn phím)
- Tính tổng các số dƣơng
- Tính tổng bình phƣơng các số âm
- Xóa các phần tử 0
- Xuất vị trí của phần tử a xuất hiện trong màng (a nhập từ bàn phím)
- Xuất các phần tử chẳn
- Xuất phần tử min, max.
5. Viết chƣơng trình nhập ma trận vng n phần tử (n đƣợc nhập từ bàn phím)
- Xuất 2 đƣờng chéo của ma trận.
74



-

Tính tổng các phần tử chẵn
Kiểm tra ma trận có đối xứng qua đƣờng chéo chính khơng.
Xuất các phần tử lẻ của ma trận
Xuất số lƣợng các phần tử a (a nhập từ bàn phím)

CHƢƠNG 7
CON TRỎ
Giới thiệu
Con trỏ là biến chứa địa chỉ của một biến khác. Con trỏ đƣợc sử dụng rất nhiều
trong C, một phần là do chúng đôi khi là cách duy nhất để biểu diễn tính tốn, và phần
nữa do chúng thƣờng làm cho chƣơng trình ngắn gọn và có hiệu quả hơn các cách
khác.
Bài này sẽ trình bày cách khai báo và sử dụng đƣợc con trỏ, các phép toán trên
con trỏ trong lập trình, phân biệt các kiểu con trỏ, cách sử dụng con trỏ trong lập trình
75


Mục tiêu
- Khai báo và sử dụng đƣợc con trỏ
- Vận dụng đƣợc các phép toán trên con trỏ trong lập trình
- Phân biệt đƣợc các kiểu con trỏ
- Sử dụng đƣợc con trỏ trong lập trình
Nội dung chính
I. KHÁI NIỆM
Con trỏ là biến chứa địa chỉ của một biến khác. Con trỏ đƣợc sử dụng rất nhiều
trong C, một phần là do chúng đôi khi là cách duy nhất để biểu diễn tính tốn, và phần

nữa do chúng thƣờng làm cho chƣơng trình ngắn gọn và có hiệu quả hơn các cách
khác.
Con trỏ có thể đƣợc sử dụng trong một số trƣờng hợp sau:
 Để trả về nhiều hơn một giá trị từ một hàm
 Thuận tiện hơn trong việc truyền các mảng và chuỗi từ một hàm đến một hàm
khác
 Sử dụng con trỏ để làm việc với các phần tử của mảng thay vì truy xuất trực
tiếp vào các phần tử này
 Để cấp phát bộ nhớ động và truy xuất vào vùng nhớ đƣợc cấp phát này
(dynamic memory allocation)
II. CON TRỎ VÀ ĐỊA CHỈ
Vì con trỏ chứa địa chỉ của đối tƣợng nên nó có thể xâm nhập vào đối tƣợng gián
tiếp qua con trỏ. Giả sử x là một biến kiểu int, và giả sử px là con trỏ đƣợc tạo ra theo
một cách nào đó.
Phép tốn một ngơi & sẽ cho địa chỉ của đối tƣợng, nên câu lệnh:
px=&x;
sẽ gán địa chỉ của biến x cho trỏ px, và px bây giờ đƣợc gọi là " trỏ tới biến x ".
Phép tốn một ngơi * coi là tốn hạng của nó là đại chỉ cần xét và thâm nhập tới
địa chỉ đó để lấy ra nội dung. Nếu biến y có kiểu int thì thì lệnh:
y=*px;
sẽ gán giá trị của biến mà trỏ px trỏ tới. Vậy dãy lệnh:
px=&x;
y=*px;
sẽ gán giá trị của x cho y nhƣ trong lệnh:
76


y=x;
Các khai báo cho các biến con trỏ có dạng:
tên kiểu *tên con trỏ

Ví dụ: Nhƣ trong ví dụ trên, ta khai báo con trỏ px kiểu int:
int *px;
Trong khai báo trên ta đã ngụ ý nói rằng đó là một cách tƣợng trƣng, rằng tổ hợp
*px có kiểu int, tức là nếu px xuất hiện trong ngữ cảnh *px thì nó cũng tƣơng đƣơng
với biến có kiểu int.
Con trỏ có thể xuất hiện trong các biểu thức. Chẳng hạn, nếu px trỏ tới số
ngun x thì *px có thể xuất hiện trong bất kỳ ngữ cảnh nào mà x có thể xuất hiện.
Ví dụ:
Lệnh y=*px+1;
sẽ đặt y lớn hơn x một đơn vị.
Lệnh printf("%d",*px);
sẽ in ra giá trị hiện tại của x
Lệnh:
d=sqrt((double) *px);
sẽ gán cho biến d căn bậc hai của x, giá trị này bị buộc phải chuyển sang
double trƣớc khi đƣợc chuyền cho sqrt (cách dùng hàm sqrt).
Trong các biểu thức kiểu nhƣ:
y=*px+1;
phép tốn một ngơi * và & có mức ƣu tiên cao hơn các phép toán số học, cho nên biểu
thức này lấy bất ký giá trị nào mà px trỏ tới, cộng với 1 rồi gán cho y.
Con trỏ cũng có thể xuất hiện bên vế trái của phép gán. Nếu px trỏ tới x thì sau
lệnh:
*px=0;
x sẽ có giá trị bằng 0. Cũng tƣơng tự các lệnh:
*px+=1;
(*px)++;
sẽ tăng giá trị của x lên 1 dơn vị.

77



Các dấu ngoặc đơn ở câu lệnh cuối là cần thiết , nếu khơng thì biểu thức sẽ tăng
px thay cho tăng ở biến mà nó trỏ tới vì phép tốn một ngơi nhƣ * và ++ đƣợc tính từ
phải sang trái.
Cuối cùng, vì con trỏ là biến nên ta có thao tác chúng nhƣ đối với các biến
khác. Nếu py cũng là con trỏ int thì lệnh:
py=px;
sẽ sao nội dung của px vào py, nghĩa là làm cho py trỏ tới nơi mà px trỏ
III. KHAI BÁO BIẾN KIỂU CON TRỎ
Nếu một biến đƣợc sử dụng nhƣ một con trỏ, nó phải đƣợc khai báo trƣớc. Câu
lệnh khai báo con trỏ bao gồm một kiểu dữ liệu cơ bản, một dấu *, và một tên biến.
Cú pháp tổng quát để khaibáo một biến con trỏ nhƣ sau:
type *name;
Ở đó type là một kiểu dữ liệu hợp lệ bất kỳ, và name là tên của biến con trỏ. Câu
lệnh khai báo trên nói với trình biên dịch là name đƣợc sử dụng để lƣu địa chỉ của
một biến có kiểu dữ liệu type. Trong câu lệnh khai báo, * xác định rằng một biến con
trỏ đang đƣợc khai báo.
Trong ví dụ của var1 và var2 ỏ trên, vì var2 là một con trỏ giữ địa chỉ của biến
var1 có kiểu int, nó sẽ đƣợc khai báo nhƣ sau:
int *var2;
Bây giờ, var2 có thể đƣợc sử dụng trong một chƣơng trình để trực tiếp truy xuất
giá trị của var1. Nhớ rằng, var2 khơng phải có kiểu dữ liệu int nhƣng nó là một con
trỏ trỏ đến một biến có kiểu dữ liệu int.
Kiểu dữ liệu cơ sở của con trỏ xác định kiểu của biến mà con trỏ trỏ đến. Về mặt
kỹ thuật, một con trỏ có kiểu bất kỳ có thể trỏ đến bất kỳ vị trí nào trong bộ nhớ. Tuy
nhiên, tất cả các phép toán số học trên con trỏ đều có liên quan đến kiểu cơ sở của nó,
vì vậy khai báo kiểu dữ liệu của con trỏ một cách rõ ràng là điều rất quan trọng.
IV. CÁC PHÉP TỐN
Có 4 phép tốn liên quan đến con trỏ và địa chỉ là:
Phép gán.

Phép tăng giảm địa chỉ.
Phép truy cập bộ nhớ.
Phép so sánh.
1. Phép gán:
78


Phép gán chỉ thực hiện với các con trỏ cùng kiểu. Muốn gán các con trỏ khác
kiểu phải dùng phép ép kiểu nhƣ ví dụ sau:
int x;
char *pc;
pc=(char*)(&x);
2. Phép tăng giảm địa chỉ:
Để minh hoạ chi tiết cho phép toán này, ta xét ví dụ sau:
Các câu lệnh:
float x[30],*px;
px=&x[10];
cho con trỏ px là con trỏ float trỏ tới phần tử x[10]. Kiểu địa chỉ float là kiểu địa chỉ 4
byte, nên các phép tăng giảm địa chỉ đƣợc thực hiện trên 4 byte. Vì thế:
px+i trỏ tới phần tử x[10+i]
px-i trỏ tới phần tử x[10-i]
Xét ví dụ khác:
Giả sử ta khai báo:
float b[40][50];
Khai báo trên cho ta một mảng b gồm các dòng 50 phần tử thực. Kiểu địa chỉ của b là
50*4=200 byte.
Do vậy:
b trỏ tới đầu dòng thứ nhất (phần tử b[0][0]).
b+1 trỏ tới đầu dòng thứ hai (phần tử b[1][0]).
..........

b+i trỏ tới đầu dòng thứ i (phần tử b[i][0]).
3. Phép truy cập bộ nhớ:
Con trỏ float truy nhập tới 4 byte, con trỏ int truy nhập 2 byte, con trỏ char truy
nhập 1 byte. Giả sử ta có cá khai báo:
float *pf;
int *pi;
char *pc;
Khi đó:
79


Nếu trỏ pi trỏ đến byte thứ 100 thì *pf biểu thị vùng nhớ 4 byte liên tiếp từ byte
100 đến 103.
Nếu trỏ pi trỏ đến byte thứ 100 thì *pi biểu thị vùng nhớ 2 byte liên tiếp từ byte
100 đến 101.
Nếu trỏ pc trỏ đến byte thứ 100 thì *pc biểu thị vùng nhớ 1 byte chính là byte
100.
4. Phép so sánh:
Cho phép so sánh các con trỏ cùng kiểu, ví dụ nếu p1 và p2 là các con trỏ cùng
kiểu thì nếu:
p1p1=p2 nếu địa chỉ p1 trỏ tới cũng là địa chỉ p2 trỏ tới.
p1>p2 nếu địa chỉ p1 trỏ tới cao hơn địa chỉ p2 trỏ tới.
Ví dụ:
Đoạn chƣơng trình tính tổng các số thực dùng phép so sánh con trỏ:
float a[100],*p,*pcuoi,tong=0.0;
int n;
pcuoi=a+n-1;

/* Địa chỉ cuối dãy*/


for (p=a;p<=pcuoi;++p)
s+=*p;
Ví dụ:
Dùng con trỏ char để tách các byte của một biến nguyên, ta làm nhƣ sau:
Giả sử ta có lệnh:
unsigned int n=0xABCD; /* Số nguyên hệ 16*/
char *pc;
pc=(char*)(&n);
Khi đó:
*pc=0xAB (byte thứ nhất của n)
*pc+1=0xCD (byte thứ hai của n)
V. CON TRỎ MẢNG
Mảng con trỏ là sự mở rộng khái niệm con trỏ. Mảng con trỏ là một mảng mà
mỗi phần tử của nó chứa đƣợc một địa chỉ nào đó. Cũng giống nhƣ con trỏ, mảng con
80


trỏ có nhiều kiểu: Mỗi phần tử của mảng con trỏ kiểu int sẽ chứa đƣợc các địa chỉ
kiểu int. Tƣơng tự cho các mảng con trỏ của các kiểu khác.
Mảng con trỏ đƣợc khai báo theo mẫu:
Kiểu *Tên_mảng_con_trỏ[N];
Trong đó Kiểu có thể là int, float, double, char ... cịn Tên_mảng_con_trỏ là tên
của mảng, N là một hằng số nguyên xác định độ lớn của mảng.
Khi gặp khai báo trên, máy sẽ cấp phát N khoảng nhớ liên tiếp cho N phần tử
của mảng Tên_mảng_con_trỏ.
Ví dụ:
Lệnh:
double *pa[100];
Khai báo một mảng con trỏ kiểu double gồm 100 phần tử. Mỗi phần tử pa[i] có thể

dùng để lƣu trữ một địa chỉ kiểu double.
Chú ý :
Bản thân các mảng con trỏ không dùng để lƣu trữ số liệu. Tuy nhiên mảng con
trỏ cho phép sử dụng các mảng khác để lƣu trữ số liệu một cách có hiệu quả hơn theo
cách: chia mảng thành các phần và ghi nhớ địa chỉ đầu của mỗi phần vào một phần tử
của mảng con trỏ.
Trƣớc khi sử dụng một mảng con trỏ ta cần gán cho mỗi phần tử của nó một giá
trị. Giá trị này phải là giá trị của một biến hoặc một phần tử mảng. Các phần tử của
mảng con trỏ kiểu char có thể đƣợc khởi đầu bằng các chuỗi ký tự.
Ví dụ:
Xét một tổ lao động có 10 ngƣời, mã của mỗi ngƣời chính là số thứ tự. Ta lập
một hàm để khi biết mã số của nhân viên thì xác định đƣợc họ tên của nhân viên đó.
#include <stdio.h>
#include <ctype.h>
void tim(int code);
void main()
{
int i;
tt:printf("\n Tim nguoi co so TT la:");
scanf("%d",&i);
tim(i);
81


×