1. Trang chủ
  2. » Công Nghệ Thông Tin

Hàm, mảng và con trỏ

35 408 2
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Hàm, mảng và con trỏ
Trường học Đại học Công nghệ Thông tin - Đại học Quốc gia Thành phố Hồ Chí Minh
Chuyên ngành Lập trình C
Thể loại Bài giảng
Năm xuất bản 2023
Thành phố Thành phố Hồ Chí Minh
Định dạng
Số trang 35
Dung lượng 267,24 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Quá trình đó diễn ra theo 4 bước sau: + Cấp phát bộ nhớ + Gán giá trị của các đối số cho các tham số tương ứng + Thực hiện các lệnh trong thân hàm + Khi gặp câu lệnh return hoặc } ở cuối

Trang 1

Ch−¬ng VI HÀM, MẢNG VÀ CON TRỎ

I.1 Khái niệm :

Khi viết chương trình nếu gặp các bài toán tương đối lớn thì việc tổ chức chương trình cũng như quản lý luồng dữ liệu trong chương trình đó gặp rất nhiều khó khăn Vì vậy trong kỹ thuật lập trình người ta thường tổ chức chương trình lớn thành những đơn vị chương trình nhỏ hơn và mỗi đơn vị chương trình như vậy có nhiệm vụ riêng nhất định “Chia để trị” (divide and conquer) : chia nhỏ vấn đề ra thành nhiều bài toán nhỏ hơn, mỗi bài toán nằm trong một hàm Khi sử dụng hàm, việc tìm lỗi sẽ dễ dàng hơn vì ta có thể khoanh vùng lỗi ở một hàm cụ thể nào đó, chứ không phải ở đâu đó trong chương trình

Mặt khác, trong chương trình, có khi ta gặp những đoạn chương trình lặp đi lặp lại nhiều lần Để tránh sự rườm rà, ta sử dụng những chương trình con để có thể gọi nhiều lần mà không phải viết lại đoạn chương trình đó

Như vậy khi sử dụng chương trình con, chương trình sẽ có đoạn mã ngắn hơn, hiệu quả hơn, thuận tiện trong việc gỡ rối, hiệu chỉnh, Ở chương trình chính, ta chỉ cần quan tâm đến kết quả làm việc của hàm mà không cần quan tâm đến việc hàm làm như thế nào

Khi xây dựng chương trình con ta phải chú ý đến mục đích sau :

+ Cho phép phân nhỏ bài toán thành nhiều đơn vị chương trình + Cho phép dễ dàng viết chương trình

+ Dễ dàng trong việc quản lý các biến nhớ + Dễ dàng kiểm soát các lỗi phát sinh trong chương trình Chương trình con có thể là thủ tục, hàm và macro Trong C chỉ có hàm và macro Các hàm thư viện (hàm chuẩn) được chứa trong các file header *.h Để sử dụng một hàm thư viện ta cần khai báo bằng chỉ thị tiền xử lí : #include <*.h> Đối với những hàm do người sử dụng tự lập, ta cần khai báo và định nghĩa trước khi sử dụng

Các hàm không được lồng vào nhau (không khai báo 1 hàm bên trong hàm khác) nhưng một hàm có thể gọi một hàm khác

Chương trình có thể có nhiều hàm Hàm main được gọi đầu tiên

Ví dụ : tính diện tích hình chữ nhật biết chiều dài và chiều rộng có sử dụng hàm :

#include <stdio.h>

#include <conio.h>

Trang 2

int tich(int a,int b); // khai báo nguyên mẫu hàm main()

{

printf(“\nNhap chieu dai:”);scanf(“%d”,&x); // hàm đọc từ bàn phím

printf(“\nNhap chieu rong:”);scanf(“%d”,&y);

printf(“\nDien tich hinh chu nhat: %d”,dientich); // hàm đưa ra màn hình

printf(“\nNhap chieu dai:”);scanf(“%d”,&x); // hàm đọc từ bàn phím

printf(“\nNhap chieu rong:”);scanf(“%d”,&y);

printf(“\nDien tich hinh chu nhat: %d”,dientich);

getch();

return 0;

}

Nếu ta định nghĩa hàm sau hàm main() thì trước hàm main() cần phải thực hiện việc khai báo nguyên mẫu hàm (function prototype)

kiểu_ giá_trị_trả_về tên_hàm(danh sách tham số và kiểu của chúng);

Nếu có giá trị trả về thì ta sử dụng câu lệnh return Nếu hàm không có giá trị trả về thì sử dụng từ khóa void để chỉ kiểu của giá trị trả về (khi đó hàm giống như thủ tục của Pascal) Nếu không dùng void thì kiểu ngầm định sẽ là int Từ khóa void còn được dùng để chỉ các hàm không có tham số (do đó khi gọi hàm sẽ không cần đối số)

Trang 3

Tên hàm được đặt theo các quy tắc đặt tên của biến

Trong phần khai báo nguyên mẫu hàm không cần phải đặt tên cho tham số Theo ví dụ trên ta có thể viết : int tich(int, int);

I.4 Lệnh return:

Lệnh return dùng để trả lại giá trị cho hàm Lệnh return có thể trả lại giá trị của biểu thức Trong 1 hàm có thể có nhiều lệnh return, tuy nhiên chỉ lệnh return được bắt gặp đầu tiên được thực hiện

Nếu kiểu trả về là void (không có giá trị trả về) thì dùng lệnh “return;” hoặc bỏ qua

I.5 Hoạt động của hàm :

Khi gặp một lời gọi hàm thì máy sẽ rời chỗ đó của chương trình và chuyển đến hàm tương ứng để thực hiện Quá trình đó diễn ra theo 4 bước sau:

+ Cấp phát bộ nhớ + Gán giá trị của các đối số cho các tham số tương ứng + Thực hiện các lệnh trong thân hàm

+ Khi gặp câu lệnh return hoặc } ở cuối của thân hàm thì máy sẽ giải phóng vùng nhớ vừa cấp phát và thoát khỏi hàm

I.6 Lời gọi hàm :

Hàm chỉ làm việc trên các bản sao mà thôi Nếu hàm có giá trị trả về thì ta có thể sử dụng hàm này như một biểu thức bình thường với các đối số thích hợp

Trang 4

I.7 Truyền đối số cho hàm :

Truyền đối số cho hàm tức là cung cấp các giá trị đầu vào thực sự cho hàm phù hợp với kiểu và trật tự của các tham số đã khai báo Đối số có thể là biến,hằng,biểu thức

Việc truyền đối số cho hàm được thực hiện theo một kiểu duy nhất : truyền theo giá trị: hàm không làm thay đổi giá trị của các biến dùng làm đối số vì hàm chỉ làm việc trên các bản sao của các đối số mà thôi

Truoc khi goi ham : x = 2 , y = 3

Truoc khi hoan vi : a = 2 , b = 3

Sau khi hoan vi : a = 3 , b = 2

Sau khi goi ham : x = 2 , y = 3

Như vậy hàm hoanvi đã hoán vị 2 số x và y, nhưng khi quay trở lại chương trình chính thì giá trị của x và y vẫn không thay đổi

Để tránh điều này ta có thể sử dụng cách truyền địa chỉ hoặc sử dụng biến toàn cục, nhưng sử dụng biến toàn cục gặp một số vấn đề như bộ nhớ, giá trị của nó có thể bị thay đổi ở mọi nơi trong chương trình

Chương trình sử dụng cách truyền địa chỉ :

#include <stdio.h>

#include <conio.h>

void hoanvi(int *px,int *py);

main()

Trang 5

I.8 Phạm vi biến:

Phạm vi biến là khả năng truy xuất biến đó ở các phần khác nhau trong chương trình Các biến chỉ được sử dụng kể từ vị trí biến được khai báo trở xuống

• Biến toàn cục : biến được khai báo ở ngoài các hàm (kể cả hàm main), được sử dụng ở mọi nơi trong chương trình Trình biên dịch sẽ tự động gán cho biến này giá trị 0 nếu

ta không khởi tạo giá trị cho nó

Thực ra đối với chương trình trải trên nhiều tập tin, để sử dụng các biến toàn cục trong các modul khác ta phải sử dụng từ khóa extern Khi đó trình biên dịch không cấp phát ô nhớ nào cho biến đó, lệnh này chỉ nhằm mục đích báo rằng biến đó được khai báo đâu đó trong chương trình (trong modul nào đó của chương trình)

• Biến địa phương : biến được khai báo bên trong hàm và chỉ sử dụng bên trong hàm trong thời gian hàm đó hoạt động (hàm được gọi) Nếu biến địa phương trùng tên với biến toàn cục thì trong phạm vi hàm đó, biến toàn cục không có tác dụng

Các biến toàn cục có thể được thay đổi trong bất kì hàm nào, đây cũng chính là nhược điểm của chúng: ta khó kiểm soát hơn, nhất là trong chương trình lớn Vì vậy cần hạn chế sử dụng biến toàn cục khi nó không cần thiết cho hầu hết các hàm Ta thường dùng biến toàn cục để giải quyết việc phải truyền các biến đó cho nhiều hàm khác nhau

Trang 6

 Các biến cục bộ trong khối lệnh:

Ta có thể khai báo các biến ở ngay đầu khối lệnh, các biến này là biến cục bộ

Khi máy bắt đầu làm việc với 1 khối lệnh thì các biến và các mảng khai báo bên trong khối lệnh đó mới được hình thành và được cấp phát bộ nhớ Các biến này chỉ tồn tại trong thời gian máy làm việc với khối lệnh và chúng sẽ được giải phóng khi kết thúc khối lệnh Vì vậy :

• Giá trị của biến hoặc mảng khai báo bên trong khối lệnh không thể đưa ra sử dụng bên ngoài khối lệnh đó

• Ở ngoài khối lệnh không thể can thiệp các biến và mảng khai báo trong khối lệnh

• Ở trong khối lệnh có thể sử dụng biến khai báo ngoài khối lệnh nếu biến đó không trùng tên với các biến khai báo trong khối lệnh

I.9 Cấp phát bộ nhớ :

Biến toàn cục được cấp phát bộ nhớ tĩnh nên gọi là biến tĩnh và cấp phát tĩnh

Biến địa phương có các loại :

• Cấp phát động : khi kết thúc hàm biến được giải phóng, không lưu kết quả cho lần sau

• Cấp phát tĩnh : khi kết thúc hàm không giải phóng biến, lưu kết quả cho lần sau ( dùng từ khóa static trước dòng khai báo)

Các biến địa phương được ngầm định là cấp phát động Nếu thích, ta có thể thêm vào từ khóa auto vào đầu dòng khai báo : auto int x; Các biến auto được cấp phát ở stack

Ví dụ : sự khác nhau giữa biến cấp phát động và biến cấp phát tĩnh

ham();

} getch();

Trang 7

Kết quả :

Lan goi thu 0 : x = 0 , y = 0 Lan goi thu 1 : x = 1 , y = 0 Lan goi thu 2 : x = 2 , y = 0 Lan goi thu 3 : x = 3 , y = 0 Câu lệnh static int x; chỉ được thực hiện khi biên dịch, khi ham() được gọi lần đầu tiên nó cũng không được thực hiện Câu lệnh int y=0 luôn được thực hiện mỗi khi gọi hàm

Và theo ví dụ trên ta thấy các biến địa phương tĩnh được khởi tạo giá trị đầu bằng 0 khi dùng lần đầu (nếu ta không khởi tạo)

Nếu chương trình được viết trên nhiều file và biến a được khai báo ở ngoài các hàm như sau: static int a; thì biến a chỉ được biết đến trong modul hiện tại, không được biết đến trong các modul khác

• Biến địa phương thanh ghi : register int x;

Dùng để yêu cầu trình biên dịch nếu có thể thì đặt biến đó vào thanh ghi thay vì đặt vào một ô nhớ thông thường Khi đó ta có thể truy xuất đến biến này rất nhanh chóng Ta thường sử dụng cách khai báo này với các biến đếm của vòng lặp Nếu trình biên dịch không thể cấp phát ở register thì biến đó là auto

Từ khóa register không được dùng với các biến tĩnh hay biến ngoài Ta không thể định nghĩa một con trỏ tới biến thanh ghi

I.10 Đệ qui :

Đệ qui là một hàm gọi lại chính nó

0 nnếunn

n

)!

1(

Trang 8

Ví dụ : Tính ước số chung lớn nhất của 2 số :

Ví dụ : ta định nghĩa lại hàm giaithua():

Mẫu : #define tên_macro dãy_văn_bản

Ví dụ : #define cube(y) ((y)*(y)*(y))

Không được viết #define cube (y) ((y)*(y)*(y))

Trang 9

z=cube(x); sẽ được thay thế bằng z=((x)*(x)*(x));

Ta phải viết mỗi toán hạng trong ngoặc đơn, nếu không khi thay thế sẽ sai:

Ví dụ : #define cube(y) (y*y*y)

z=cube(x+y); sẽ được thay thế bằng : z=(x+y*x+y*x+y); (kết quả không mong muốn) Ngay cả khi ta dùng #define cube(y) ((y)*(y)*(y))

Kết quả cũng sai khi : x=cube(++y) -> x=((++y)*(++y)*(++y))=(y+3)3

x=cube(y++) -> x=y3, y=y+3

Ví dụ : #define tong(x,y) ((x)+(y))

z=tong(x,y);

So với việc sử dụng hàm, chương trình sử dụng macro sẽ có đoạn mã dài hơn, chạy nhanh hơn do không mất thủ tục gọi hàm Tuy nhiên macro không có đệ quy, không có tính modul và macro có nhiều hiệu ứng xấu

#define Do(var,begin,end) for(var=begin;var<=end;var++)

Do(i,1,10) -> for(i=1;i<=10;i++) : đúng

Do(*ptr,1,10) -> for(*ptr=1;*ptr<=10;*ptr++) : sai vì

*ptr++ : ++ trước, * sau : không giống mong muốn của ta Macro lồng nhau:

#define ONE 1

#define TWO (ONE + ONE)

#define THREE (ONE + TWO)

result = TWO * THREE;

Ta sẽ có: result = (1 + 1) * (1 + (1 + 1));

I.12 Một số hàm và macro thông dụng trong stdlib.h và time.h:

Macro random(n) cho một số nguyên ngẫu nhiên trong khoảng từ 0 đến (n-1)

Hàm rand() cho số nguyên ngẫu nhiên trong khoảng từ 0 đến 32767

Macro randomize() khởi động bộ tạo số ngẫu nhiên cần được gọi đến khi muốn sử dụng random() và rand()

Trang 10

II.1 Khai báo mảng :

Mẫu : kiểu_dữ_liệu tên_mảng[kích_thước_của_mảng];

Ví dụ : int mang[10];

char ten[20];

float day[100];

Các phần tử được phân biệt nhờ chỉ số : từ 0 đến kích_thước_của_mảng - 1

Ví dụ : #define MAX 10

int mang[MAX];

tương đương với int mang[10];

Ví dụ sai : const int MAX=10;

int mang[MAX];

II.2 Khai báo xâu kí tự :

Trong C, một mảng kí tự được gọi là một xâu kí tự

Ví dụ : 3 ví dụ sau tương đương nhau :

char text[5]={‘a’,’b’,’c’,’d’,’\0’};

char text[5]=”abcd”;

char text[]=”abcd”;

Xâu kí tự trong C được kết thúc bởi kí tự NULL (\0)

II.3 Khởi tạo giá trị mảng :

Ta có thể khởi tạo mảng ngay sau khi khai báo :

Ví dụ : int x[3]={5,6,7};

Tức là : x[0]=5, x[1]=6, x[2]=7

Nếu ta không khởi tạo toàn bộ mảng thì phần còn lại của mảng sẽ tự động được đặt về

0 (đối với mảng nguyên và thực) và về NULL (đối với mảng kí tự)

Ví dụ : int a[10]={1,2,3};

Khi đó : a[0]=1, a[1]=2, a[2]=3, từ a[3] đến a[9] nhận giá trị 0

int d[]={2,5,9,7}; // d[0]=2, d[1]=5, d[2]=9, d[3]=7

Trang 11

Các phần tử của mảng được truy cập bình thường thông qua chỉ số của phần tử đó Để truy cập đến phần tử thứ i của mảng M ta dùng cú pháp như sau : M[i]

Chương trình không kiểm tra liệu ta có truy cập ra ngoài mảng hay không

Ví dụ : Nhập và hiển thị các giá trị của mảng từ bàn phím :

scanf(“%d”,&mang[i]);

} printf(“\nMang gom cac phan tu :\n”);

Lần lượt duyệt qua các phần tử của mảng đến khi tìm được giá trị mong muốn

Ví dụ : tìm xem trong xâu kí tự có bao nhiêu kí tự a :

int timkiem( char mang[], char a)

}

Ngoài ra còn có phương pháp tìm kiếm kiểu lính gác:

Ví dụ : Tìm xem trong mảng a gồm n phần tử nguyên có số nguyên d không

Ta khai báo mảng a gồm n+1 phần tử, đặt a[n]=d

i=0;

while (a[i] != d)

i++;

if(i==n)

printf(“\n Mảng a không có phần tử %d “,d);

else printf(“\n Phần tử %d ở vị trí %d“,d,i);

Trong trường hợp xấu nhất, phương pháp trước so sánh 2*n lần trong khi phương pháp sau so sánh nhiều nhất n+1 lần

Trang 12

Ví dụ : Sắp xếp các phần tử của mảng từ nhỏ đến lớn

scanf(“%d”,&mang[i]);

} printf(“\nMang chua sap xep:\n”);

mang[i]=mang[j];

mang[j]=tam;

} printf(“\nMang da sap xep:\n”);

for(i=0;i<n;i++)

printf(“ x[%d]=%3d”,i,mang[i]);

getch();

}

II.5 Mảng nhiều chiều :

Mảng 2 chiều là loại mảng quen thuộc nhất : ma trận

Mẫu : kiểu_mảng tên_mảng[số_hàng][số_cột];

Ví dụ : int x[5][7]; // khai báo này cho ta một mảng số nguyên 5 hàng 7 cột

Các phần tử cũng được đánh các chỉ số hàng và cột từ 0

Trang 13

Việc gán các giá trị vào các phần tử của mảng cũng như mảng một chiều:

x[3][4]=6;

Khởi tạo mảng 2 chiều :

int x[5][3]= { { 1,2,3 },

{ 2,3,4 },

};

Ta có thể tạo ra các mảng 3 chiều, 4 chiều, :

int x[5][3][7];

int y[5][6][8][3];

Để truy cập đến phần tử có chỉ số (i,j) của ma trận M ta dùng M[i][j]

Ví dụ : chương trình nhân hai ma trận Am*n và Bn*p :

{ for (j=0;j<n;j++)

printf("%4d",a[i][j]);

printf("\n");

}

Trang 14

return 0;

}

Trong chương trình trên, ta không sử dụng hàm vì đối với mảng nhiều chiều, ta không truyền như mảng một chiều được Để có thể sử dụng hàm, ta phải dùng con trỏ

III Con trỏ:

III.1 Khái niệm con trỏ :

Khi ta khai báo một biến trong C, trình biên dịch sẽ dành riêng 1 ô nhớ với địa chỉ duy nhất để lưu biến đó

Ví dụ : int b=5;

Con trỏ là biến dùng để chứa địa chỉ của 1 biến khác Nếu con trỏ p chứa địa chỉ của biến b thì ta nói : con trỏ p đang trỏ tới biến b

Có nhiều kiểu biến nên có nhiều kiểu con trỏ tương ứng

III.2 Con trỏ và biến đơn :

• Khai báo con trỏ : kiểu_dữ_liệu *tên_con_trỏ

Ví dụ : int *p,b; // p là con trỏ dùng để trỏ tới biến kiểu nguyên

// b là biến nguyên

Ta có thể viết khai báo int* p; Tuy nhiên, khi viết int* p,b; ta sẽ dễ nhầm lẫn khi cho rằng p và b là 2 biến con trỏ Thực ra, p là biến con trỏ, b là biến int thường

• Khởi tạo con trỏ : tên_con_trỏ=&tên_biến

trong đó & là toán tử lấy địa chỉ

Ví dụ : p=&b; // con trỏ p trỏ đến biến b

Trang 15

• Sử dụng con trỏ :

*p là biến mà p trỏ tới ( tức là biến b)

Vì vậy *p và b đều chỉ nội dung của biến b

p và &b đều chỉ địa chỉ của biến b

Ví dụ : printf(“%d”,b); tương đương với lệnh printf(“%d”,*p);

Ví dụ kiểm định các giá trị của con trỏ :

printf(“\nGia tri cua number : %d”,number);

printf(“\nDia chi cua number : %p”,&number);

printf(“\nGia tri cua con tro pointer : %p”,pointer);

printf(“\nGia tri duoc tro toi boi con tro pointer : %d”,*pointer);

printf(“\nDia chi cua con tro pointer : %p”,&pointer);

getch();

}

%p : dùng để in ra giá trị của con trỏ (pointer value)

Các ví dụ trên rõ ràng ta có thể sử dụng trực tiếp biến mà không cần đến con trỏ Tuy nhiên các ví dụ sau sẽ cho thấy lợi ích của con trỏ

Trang 16

Ví dụ : hàm printf dùng biến thường, hàm scanf dùng biến con trỏ

Tương tự như trên ta có thể viết hàm giải phương trình bậc 2, giá trị trả về tùy theo việc phương trình có nghiệm hay không, giá trị đầu ra là các nghiệm (nếu có)

Trang 17

printf("Nhap gia tri a,b,c:");

scanf("%f %f %f", &a, &b, &c);

Ngày đăng: 02/10/2013, 20:20

TỪ KHÓA LIÊN QUAN

w