Một hàm viết dưới dạng đối tham chiếu sẽ đơn giản hơn rất nhiều so với đối con trỏ và giống với cách viết bình thường (truyền theo tham trị), trong đó chỉ có một khác biệt đó là các đối[r]
(1)CHƯƠNG HÀM VÀ CON TRỎ HÀM 5.1 HÀM
Hàm nhận (hoặc không) đối số trả lại (hoặc không) giá trị cho chương trình gọi Trong trường hợp khơng trả lại giá trị, hàm hoạt động thủ tục ngơn ngữ lập trình khác Một chương trình tập hàm, có hàm với tên gọi main(), chạy chương trình, hàm main() chạy gọi đến hàm khác Kết thúc hàm main() kết thúc chương trình
Hàm giúp cho việc phân đoạn chương trình thành môđun riêng rẽ, hoạt động độc lập với ngữ nghĩa chương trình lớn, có nghĩa hàm sử dụng chương trình mà sử dụng chương trình khác, dễ cho việc kiểm tra bảo trì chương trình Hàm có số đặc trưng:
- Nằm ngồi văn có chương trình gọi đến hàm Trong văn chứa nhiều hàm;
- Được gọi từ chương trình (main()), từ hàm khác từ (đệ quy); - Khơng lồng nhau;
- Có cách truyền giá trị: Truyền theo tham trị, tham biến tham trỏ 5.1.1 Khai báo định nghĩa hàm
1 Khai báo
Một hàm thường làm chức năng: tính toán tham đối cho lại giá trị kết quả, đơn thực chức đó, khơng trả lại kết tính tốn Thông thường kiểu giá trị trả lại gọi kiểu hàm Các hàm thường khai báo đầu chương trình Các hàm viết sẵn khai báo file nguyên mẫu *.h Do đó, để sử dụng hàm này, cần có thị #include <*.h> đầu chương trình, *.h tên file cụ thể có chứa khai báo hàm sử dụng (ví dụ để sử dụng hàm toán học ta cần khai báo file nguyên mẫu math.h) Đối với hàm người lập trình tự viết, cần phải khai báo Khai báo hàm sau:
<kiểu giá trị trả về> <tên hàm>(d/s kiểu đối) ;
trong đó, kiểu giá trị trả lại cịn gọi kiểu hàm nhận kiểu chuẩn C++ kiểu NSD tự tạo Đặc biệt hàm không trả lại giá trị kiểu giá trị trả lại khai báo void Nếu kiểu giá trị trả lại bỏ qua chương trình ngầm định hàm có kiểu int (phân biệt với void!) Ví dụ sau vài khai báo hàm:
int bp(int); // Khai báo hàm bp, có đối kiểu int kiểu hàm int int rand100(); // Không đối, kiểu hàm (giá trị trả lại) int
void alltrim(char[ ]); // đối xâu kí tự, hàm không trả lại giá trị (không kiểu) cong(int, int); // Hai đối kiểu int, kiểu hàm int (ngầm định)
(2)dụ khai báo cong(int, int); nên khai báo rõ kiểu hàm (trong trường hợp kiểu hàm ngầm định int) sau : int cong(int, int);
2 Định nghĩa hàm
Cấu trúc hàm bố trí giống hàm main() phần trước Hàm có trả giá trị
Cú pháp:
<kiểu hàm> <tên hàm>(danh sách tham đối hình thức) {
khai báo cục hàm ; // dùng riêng cho hàm dãy lệnh hàm ;
return (biểu thức trả về); // nằm dãy lệnh }
Trong đó:
- Danh sách tham đối hình thức cịn gọi ngắn gọn danh sách đối gồm dãy đối cách dấu phẩy, đối biến thường, biến tham chiếu biến trỏ, hai loại biến sau ta trình bày phần tới Mỗi đối khai báo giống khai báo biến, tức cặp gồm <kiểu đối> <tên đối>
- Với hàm có trả lại giá trị cần có câu lệnh return kèm theo sau biểu thức Kiểu giá trị biểu thức kiểu hàm khai báo phần tên hàm Câu lệnh return nằm vị trí phần câu lệnh, tuỳ thuộc mục đích hàm Khi gặp câu lệnh return chương trình tức khắc khỏi hàm trả lại giá trị biểu thức sau return giá trị hàm
Ví dụ 5.1: Viết hàm tính luỹ thừa n (với n nguyên) số thực double luythua(float x, int n)
{
int i ; // biến số
double kq = ; // để lưu kết for (i=1; i<=n; i++) kq *= x ;
return kq; }
Có nhiều cách giải cho yêu cầu ví dụ này, nhiên nêu cách giải gần với định nghĩa lũy thừa Lệnh kq *= x tương đương với kq=kq*x nên kết thúc vòng for (i=1; i<=n; i++) giá trị kq giá trị cần tính xn
Hàm không trả giá trị Cú pháp:
(3){
khai báo cục hàm ; // dùng riêng cho hàm dãy lệnh hàm ;
[return;] }
Nếu hàm không trả lại giá trị (tức kiểu hàm void), có khơng có câu lệnh return, có đằng sau return khơng có biểu thức giá trị trả lại
Ví dụ 5.2: Viết hàm cho hiển thị lên hình 10 lần dịng chữ "Ky thuat lap trinh" void hienthi()
{
int i;
for (i=1; i<=10; i++)
printf("Ky thuat lap trinh"); return;
}
Hàm thực việc in lên hình dịng chữ "Ky thuat lap trinh" 10 lần, không trả giá trị nên hàm khai báo có kiểu void, hàm khơng trả giá trị nên lệnh trả return khơng có tham số
Hàm main()
Trong hầu hết trình biên dịch hàm main() cho phép có khơng có giá trị trả chương trình chạy xong, trình biên dịch DevC++ cho phép làm điều khai báo hàm int main() void main() câu lệnh cuối hàm return (return 0) hoặc return Trường hợp bỏ qua từ khố void thân hàm khơng có câu lệnh return chương trình ngầm hiểu hàm main() trả lại giá trị ngun khơng có nên biên dịch chương trình ta gặp lời cảnh báo “Cần có giá trị trả lại cho hàm” (một lời cảnh báo khơng phải lỗi, chương trình chạy bình thường) Để tránh bị quấy rầy lời cảnh báo “khơng mời” đặt thêm câu lệnh return 0; (nếu không khai báo void main()) khai báo kiểu hàm void main() và đặt câu lệnh return vào cuối hàm
Chú ý:
- Danh sách đối khai báo hàm chứa khơng chứa tên đối, thông thường ta khai báo kiểu đối không cần khai báo tên đối, dòng định nghĩa hàm phải có tên đối đầy đủ;
- Cuối khai báo hàm phải có dấu chấm phẩy (;), cuối dòng định nghĩa hàm khơng có dấu chấm phẩy;
- Hàm khơng có đối (danh sách đối rỗng), nhiên cặp dấu ngoặc sau tên hàm phải viết Ví dụ lamtho(), vietgiaotrinh(), … ;
(4)gọi đến Ví dụ viết hàm main() trước (trong văn chương trình), sau viết đến hàm “con” Do hàm main() chắn gọi đến hàm nên danh sách chúng phải khai báo trước hàm main(). Trường hợp ngược lại hàm viết (định nghĩa) trước khơng cần phải khai báo chúng (vì định nghĩa hàm ý khai báo) Nguyên tắc áp dụng cho hai hàm A, B không riêng cho hàm main(), nghĩa B gọi đến A trước A phải định nghĩa có dịng khai báo A
5.1.2 Lời gọi sử dụng hàm
Lời gọi hàm phép xuất biểu thức, câu lệnh hàm khác … Nếu lời gọi hàm lại nằm thân hàm ta gọi đệ quy Để gọi hàm ta cần viết tên hàm danh sách giá trị cụ thể truyền cho đối đặt cặp dấu ngoặc tròn ()
Cú pháp:
Tên_hàm(danh sách tham đối thực sự) ; Trong đó:
- Danh sách tham đối thực gọi danh sách giá trị gồm giá trị cụ thể để gán cho đối hình thức hàm Khi hàm gọi thực tất vị trí xuất đối hình thức gán cho giá trị cụ thể đối thực tương ứng danh sách, sau hàm tiến hành thực câu lệnh hàm (để tính kết quả);
- Danh sách tham đối thực truyền cho tham đối hình thức có số lượng với số lượng đối hàm truyền cho đối theo thứ tự tương ứng Các tham đối thực hằng, biến biểu thức Biến giá trị trùng với tên đối Ví dụ ta có hàm in n lần kí tự c với tên hàm inkitu(int n, char c); lời gọi hàm inkitu(12, 'A'); thì n c đối hình thức, 12 'A' đối thực giá trị Các đối hình thức n c gán giá trị tương ứng 12 'A' trước tiến hành câu lệnh phần thân hàm Giả sử hàm in kí tự khai báo lại thành inkitu(char c, int n); lời gọi hàm phải thay lại thành inkitu('A', 12);
- Các giá trị tương ứng truyền cho đối phải có kiểu với kiểu đối (hoặc C++ tự động chuyển kiểu kiểu đối);
- Khi hàm gọi, nơi gọi tạm thời chuyển điều khiển đến thực dòng lệnh hàm gọi Sau kết thúc thực hàm, điều khiển lại trả thực tiếp câu lệnh sau lệnh gọi hàm nơi gọi
Ví dụ 5.3: Viết chương trình tính giá trị biểu thức 2x3 - 5x2 - 4x + 1 cách sử dụng hàm luythua() để tính thành phần x3 x2
#include <iostream.h> #include <iomanip.h>
double luythua(float x, int n) // trả lại giá trị xn
{
(5)for (i=1; i<=n; i++) kq *= x ; return kq;
}
main() // tính giá trị 2x3- 5x2- 4x + {
float x ; // tên biến trùng với đối hàm double f ; // để lưu kết
printf("x = "); scanf("%d" ,&x ) ;
f = 2*luythua(x,3) - 5*luythua(x,2) - 4*x + 1; printf("%d",f) ;
getch(); return 0; }
Ví dụ cho thấy lợi ích lập trình cấu trúc, chương trình trở nên gọn hơn, chẳng hạn hàm luythua() viết lần sử dụng nhiều lần (2 lần ví dụ này) câu lệnh gọi đơn giản cho lần sử dụng thay phải viết lại nhiều lần đoạn lệnh tính luỹ thừa
5.1.3 Hàm với đối mặc định
Mục mục sau bàn đến vài mở rộng thiết thực C/C++ C có liên quan đến hàm, hàm với đối mặc định cách tạo, sử dụng hàm có chung tên gọi Một mở rộng quan trọng khác cách truyền đối theo tham chiếu bàn chung mục truyền tham đối thực cho hàm
Trong phần trước khẳng định số lượng tham đối thực phải số lượng tham đối hình thức gọi hàm Tuy nhiên, thực tế nhiều lần hàm gọi với giá trị số tham đối hình thức lặp lặp lại Trong trường hợp lúc phải viết danh sách dài tham đối thực giống cho lần gọi công việc khơng thú vị Từ thực tế C++ đưa cú pháp hàm cho danh sách tham đối thực lời gọi không thiết phải viết đầy đủ số chúng có sẵn giá trị định trước
Cú pháp:
<kiểu hàm> <tên hàm>(đ1, …, đn, đmđ1 = gt1, …, đmđm = gtm) Trong đó:
- Các đối đ1, …, đn đối mặc định đmđ1, …, đmđm khai báo bình thường, nghĩa gồm có kiểu đối tên đối;
(6)các đ1, …, đm có khơng tham đối thực ứng với đối mặc định đmđ1, …, đmđm Nếu tham đối khơng có tham đối thực tự động gán giá trị mặc định khai báo
Ví dụ, khai hàm ví dụ 3.2 viết lại hienthi(int n = 10), n mặc định 10 Khi gọi hienthi(9) chương trình hiển thị dịng "Kỹ thuật lập trình" lần, cịn gọi hienthi(10) gọn hienthi() chương trình hiển thị 10 lần
Tương tự, khai báo hàm ví dụ 3.3 viết lại int luythua(float x, int n = 2), tham số n khai báo với giá trị mặc định 2, lời gọi hàm bỏ qua số mũ chương trình hiểu tính bình phương x (n = 2), ví dụ lời gọi luythua(4, 3) hiểu 43, luythua(2) hiểu 22
Một ví dụ khác, giả sử viết hàm tính tổng số nguyên: int tong(int m, int n, int i = 0; int j = 0) tính tổng 5, 2, 3, lời gọi hàm tong(5,2,3,7) hoặc tính tổng số 4, 2, lời gọi tong(4,2,1) hoặc gọi tong(6,4) để tính tổng số
Chú ý: Các đối ngầm định phải khai báo liên tục xuất cuối danh sách đối Ví dụ:
int tong(int x, int y=2, int z, int t=1); // sai đối mặc định không liên tục
void xoa(int x=0, int y) // sai đối mặc định không cuối 5.1.4 Khai báo hàm trùng tên
Hàm trùng tên hay gọi hàm chồng (đè) Đây kỹ thuật cho phép sử dụng tên gọi cho hàm “giống nhau” (cùng mục đích) xử lý kiểu liệu khác số lượng liệu khác Ví dụ hàm sau tìm số lớn số nguyên:
int max(int a, int b) { return (a > b) ? a: b; }
Nếu đặt c = max(3, 5) ta có c = 5 Tuy nhiên tương tự đặt c = max(3.0, 5.0) chương trình bị lỗi giá trị có kiểu float khơng phù hợp kiểu int của đối hàm max() Trong trường hợp phải viết hàm để tính max() của số thực Mục đích, cách làm việc hàm hoàn toàn giống hàm trước, nhiên C ngơn ngữ lập trình khác buộc phải sử dụng tên cho hàm “mới” Ví dụ:
float fmax(float a, float b) { return (a > b) ? a: b ; } tương tự để tuận tiện ta viết thêm hàm:
char cmax(char a, char b) { return (a > b) ? a: b ; } long lmax(long a, long b) { return (a > b) ? a: b ; }
double dmax(double a, double b) { return (a > b) ? a: b ; }
Tóm lại ta có hàm: max(), cmax(), fmax(), lmax(), dmax(), việc sử dụng tên gây bất lợi cần gọi hàm C++ cho phép ta khai báo định nghĩa hàm với tên gọi, ví dụ max Khi ta có hàm:
(7)4: long max(long a, long b) { return (a > b) ? a: b ; }
5: double max(double a, double b) { return (a > b) ? a: b ; }
Và lời gọi hàm dạng max(3,5), max(3.0,5), max('O', 'K') đáp ứng Chúng ta đặt vấn đề: với hàm tên vậy, chương trình gọi đến hàm Vấn đề giải dễ dàng chương trình dựa vào kiểu đối gọi để định chạy hàm Ví dụ lời gọi max(3,5) có đối kiểu nguyên nên chương trình gọi hàm 1, lời gọi max(3.0,5) hướng đến hàm số tương tự chương trình chạy hàm số gặp lời gọi max('O','K') Như đặc điểm hàm trùng tên danh sách đối chúng phải có cặp đối khác kiểu Một đặc trưng khác để phân biệt thông qua đối số lượng đối hàm phải khác (nếu kiểu chúng giống nhau)
Ví dụ việc vẽ hình: thẳng, tam giác, vng, chữ nhật hình giống nhau, chúng phụ thuộc vào số lượng điểm nối toạ độ chúng Do ta khai báo định nghĩa hàm vẽ nói với chung tên gọi Chẳng hạn:
void ve(Diem A, Diem B) ; // vẽ đường thẳng AB
void ve(Diem A, Diem B, Diem C) ; // vẽ tam giác ABC void ve(Diem A, Diem B, Diem C, Diem D) ; // vẽ tứ giác ABCD
Trong ví dụ ta giả thiết Diem kiểu liệu lưu toạ độ điểm hình Hàm ve(Diem A, Diem B, Diem C, Diem D) vẽ hình vng, chữ nhật, thoi, bình hành hay hình thang phụ thuộc vào toạ độ điểm ABCD, nói chung sử dụng để vẽ tứ giác
Tóm lại nhiều hàm định nghĩa chồng (với tên gọi giống nhau) chúng thoả điều kiện sau:
- Số lượng tham đối hàm khác nhau, - Kiểu tham đối hàm khác
5.1.5 Biến, đối tham chiếu
Một biến gán cho bí danh mới, chỗ xuất biến tương đương dùng bí danh ngược lại, bí danh gọi biến tham chiếu Ý nghĩa thực tế cho phép “tham chiếu” tới biến khác kiểu nó, tức sử dụng biến khác tên biến tham chiếu
Giống khai báo biến bình thường, nhiên trước tên biến ta thêm dấu (&) Có thể tạm phân biến thành loại: biến thường với tên thường, biến trỏ với dấu * trước tên biến tham chiếu với dấu &
<kiểu biến> &<tên biến tham chiếu> = <tên biến tham chiếu>;
Cú pháp khai báo cho phép ta tạo biến tham chiếu cho tham chiếu đến biến tham chiếu (cùng kiểu phải khai báo từ trước) Khi biến tham chiếu cịn gọi bí danh biến tham chiếu Chú ý khơng có cú pháp khai báo tên biến tham chiếu mà không kèm theo khởi tạo Ví dụ:
int hung, dung ; // khai báo biến nguyên hung, dung
(8)int &teo = dung; // dung ti, teo bí danh hung, dung
Từ vị trí trở việc sử dụng tên hung, ti dung, teo Ví dụ: = ;
ti ++; // tương đương ++;
printf("%d, %d",hung, ti); // 3
teo = ti + ; // tương đương dung = +
dung ++ ; // tương đương teo ++
printf("%d, %d",dung,teo); // 7
Vậy sử dụng thêm biến tham chiếu để làm gì?
Cách tổ chức bên biến tham chiếu khác với biến thường chỗ nội dung địa biến mà đại diện (giống biến trỏ), ví dụ câu lệnh:
printf("%d", teo) ; //
in giá trị thực chất nội dung biến teo, nội dung teo địa dung, cần in teo, chương trình tham chiếu đến dung in nội dung dung (7) Các hoạt động khác teo (ví dụ teo++), thực chất tăng đơn vị nội dung dung (chứ teo) Từ cách tổ chức biến tham chiếu ta thấy chúng giống trỏ thuận lợi chỗ truy cập đên giá trị biến tham chiếu (dung) ta cần ghi tên biến tham chiếu (teo) khơng cần thêm tốn tử (*) trước trường hợp dùng trỏ Điểm khác biệt có ích sử dụng để truyền đối cho hàm với mục đích làm thay đổi nội dung biến
Chú ý:
- Biến tham chiếu phải khởi tạo khai báo
- Tuy giống trỏ không dùng phép tốn trỏ cho biến tham chiếu Nói chung nên dùng truyền đối cho hàm
5.1.6 Cách truyền tham số
Có cách truyền tham đối thực cho tham đối hình thức lời gọi hàm Trong cách ta dùng thời điểm gọi truyền theo tham trị, tức đối hình thức nhận giá trị cụ thể từ lời gọi hàm tiến hành tính tốn trả lại giá trị Để dễ hiểu cách truyền đối xem qua cách thức chương trình thực với đối thực hàm
5.1.6.1 Truyền theo tham trị
Ta xét lại ví dụ hàm luythua(float x, int n) tính xn Giả sử chương trình ta có biến a, b, f đang chứa giá trị a = 2, b = 3, f chưa có giá trị Để tính abvà gán giá trị tính cho f, ta gọi f = luythua(a,b) Khi gặp lời gọi này, chương trình tổ chức sau:
- Tạo biến (tức nhớ nhớ) có tên x n Gán nội dung ô nhớ giá trị lời gọi, tức gán (a) cho x và (b) cho n;
(9)- Tiến hành tính tốn (gán lại kết cho kq);
- Cuối lấy kết kq gán cho nhớ f (là nhớ có sẵn khai báo trước, nằm bên hàm);
- Kết thúc hàm quay chương trình gọi Do hàm luythua() hồn thành xong việc tính tốn nên ô nhớ tạo thực hàm để lưu trữ x, n, kq, i xố khỏi nhớ Kết tính tốn lưu giữ nhớ f (khơng bị xố khơng liên quan đến hàm)
Trên truyền đối theo cách thông thường Vấn đề đặt giả sử ngồi việc tính f, ta cịn muốn thay đối giá trị ô nhớ a, b (khi truyền cho hàm) thực khơng? Để giải tốn ta cần theo kỹ thuật khác, nhờ vào vai trò biến trỏ tham chiếu
5.1.6.2 Truyền trỏ
Xét ví dụ tráo đổi giá trị biến Đây yêu cầu nhỏ gặp nhiều lần chương trình, ví dụ để xếp danh sách Do cần viết hàm để thực yêu cầu Hàm không trả kết Do biến cần trao đổi chưa biết trước thời điểm viết hàm, nên ta phải đưa chúng vào hàm tham đối, tức hàm có hai tham đối x, y đại diện cho biến thay đổi giá trị sau
Từ vài nhận xét trên, theo thông thường hàm tráo đổi viết sau: void doi_cho(int x, int y)
{ int t ; t = x ; x = y ; y = t ; }
Giả sử chương trình ta có biến x, y chứa giá trị 2, Ta cần đổi nội dung biến cho x = 5 y = bằng cách gọi đến hàm doi_cho(x, y)
int main() {
int x = 2; int y = 5; doi_cho(x, y) ;
printf("%d, %d",x, y); // 2, (x, y không đổi) getch();
(10)Thực sau chạy xong chương trình ta thấy giá trị x y vẫn khơng thay đổi !? Như giải thích mục (gọi hàm luythua()), việc chương trình thực hàm tạo biến (các ô nhớ mới, độc lập với ô nhớ x, y đã có sẵn) tương ứng với tham đối, trường hợp có tên x, y gán nội dung x, y (ngoài hàm) cho x, y (mới) Và việc cuối chương trình sau thực xong hàm xố biến Do nội dung biến thực có thay đổi, khơng ảnh hưởng đến biến x, y cũ Hình vẽ minh hoạ cách làm việc hàm doi_cho(), trước, sau gọi hàm
Hình 3.3 Sự thay đổi giá trị tham số thực
Như hàm doi_cho() cần viết lại cho việc thay đối giá trị không thực biến tạm mà phải thực thực biến ngồi Muốn thay truyền giá trị biến cho đối, ta truyền địa cho đối, thay đổi phải thực nội dung địa Đó lý ta phải sử dụng trỏ để làm tham đối thay cho biến thường Cụ thể hàm swap viết lại sau:
void doi_cho(int *p, int *q) {
int t; // khai báo biến tạm t
t = *p ; // đặt giá trị t nội dung nơi p trỏ tới *p = *q ; // thay nội dung nơi p trỏ nội dung nơi q trỏ
*q = t ; // thay nội dung nơi q trỏ tới nội dung t }
Với cách tổ chức hàm rõ ràng ta cho p trỏ tới biến x và q trỏ tới biến y thì hàm doi_cho() sẽ thực làm thay đổi nội dung x, y chứ khơng phải p, q Từ lời gọi hàm doi_cho(&x, &y) (tức truyền địa x cho p, p trỏ tới x và tương tự q trỏ tới y)
Như tóm tắt đặc trưng để viết hàm làm thay đổi giá trị biến sau: - Đối hàm phải trỏ (ví dụ int *p);
(11)- Lời gọi hàm phải chuyển địa cho p (ví dụ &x)
Ngồi hàm doi_cho() đã trình bày, ta đưa thêm ví dụ để thấy cần thiết phải có hàm cho phép thay đổi biến ngồi Ví dụ hàm giải phương trình bậc 2, tức cho trước số a, b, c hệ số phương trình, cần tìm nghiệm x1, x2 Khơng thể lấy giá trị trả lại hàm để làm nghiệm giá trị trả lại có ta cần đến nghiệm Do ta cần khai báo biến “ngồi” chương trình để chứa nghiệm, hàm phải làm thay đổi biến (tức chứa giá trị nghiệm giải được) Như hàm viết cần phải có đối, đối a, b, c đại diện cho hệ số, không thay đổi biến x1, x2 đại diện cho nghiệm, đối phải khai báo dạng trỏ Ngồi ra, phương trình vơ nghiệm, nghiệm nghiệm hàm trả lại giá trị số nghiệm phương trình, trường hợp nghiệm (nghiệm kép), giá trị nghiệm cho vào x1
Ví dụ 5.4: Viết hàm cho phép giải phương trình bậc int gptb2(float a, float b, float c, float *p, float *q) {
float d ; // để chứa delta d = (b*b) - 4*a*c ;
if (d < 0) return ; else
if (d == 0)
{ *p = -b/(2*a) ; return ; } else
{
*p = (-b + sqrt(d))/(2*a) ; *q = (-b - sqrt(d))/(2*a) ; return ;
} }
Ví dụ sau thực lời gọi hàm gptb2() dùng kết trả qua biến trỏ main()
{
float a, b, c ; // hệ số
float x1, x2 ; // nghiệm printf("Nhập hệ số: ");
(12){
case 0:
printf("Phương trình vơ nghiệm"); break;
case 1:
printf("Phương trình có nghiệm kép x = %f", x1); break;
case 2:
printf("Phương trình có nghiệm phân biệt:\n"); printf("x1 = %f , x2 = %f", x1, x2);
break; }
}
Trên trình bày cách xây dựng hàm cho phép thay đổi giá trị biến Một đặc trưng dễ nhận thấy cách viết hàm tương đối phức tạp Do C++ phát triển cách viết khác dựa đối tham chiếu việc truyền đối cho hàm gọi truyền theo tham chiếu
5.1.6.3 Truyền theo tham chiếu
Một hàm viết dạng đối tham chiếu đơn giản nhiều so với đối trỏ giống với cách viết bình thường (truyền theo tham trị), có khác biệt đối khai báo dạng tham chiếu
Để so sánh cách sử dụng ta nhắc lại điểm viết hàm theo trỏ phải ý đến, là:
- Đối hàm phải trỏ (ví dụ int *p);
- Các thao tác liên quan đến đối thân hàm phải thực nơi trỏ đến (ví dụ *p = …);
- Lời gọi hàm phải chuyển địa cho p (ví dụ &x) Hãy so sánh với đối tham chiếu, cụ thể:
- Đối hàm phải tham chiếu (ví dụ int &p);
- Các thao tác liên quan đến đối phải thực nơi trỏ đến, tức địa cần thao tác Vì thao tác biến tham chiếu thực chất thao tác biến tham chiếu nên hàm cần viết p thao tác (thay *p trỏ);
- Lời gọi hàm phải chuyển địa cho p Vì thân p tham chiếu đến biến chứa địa biến đó, lời gọi hàm cần ghi tên biến, ví dụ x (thay &x như dẫn trỏ)
(13)nơi khác viết đơn giản cách viết truyền theo tham trị Ví dụ 5.5: Hãy viết hàm cho phép đổi giá trị hai biến void doi_cho(int &x, int &y)
{
int t = x; x = y; y = t; }
và lời gọi hàm thực sau: int a = 5, b = 3;
doi_cho(a, b);
printf("%d, %d",a,b);
Bảng minh hoạ tóm tắt cách viết hàm thơng qua ví dụ đổi biến Bảng 5-1So sánh cách truyền tham số
Tham trị Tham chiếu Dẫn trỏ
Khai báo void doi_cho(int x,int
y) void doi_cho(int &x,int &y) void doi_cho(int *x,int *y) Câu lệnh t=x; x=y; y=t t=x; x=y; y=t t=*x; *x=*y; *y=t Lời gọi doi_cho(a,b) doi_cho(a,b) doi_cho(&a,&b) Tác dụng a,b khơng thay đổi a,b có thay đổi a,b có thay đổi 5.1.7 Hàm mảng
5.1.7.1 Truyền mảng chiều cho hàm
Thông thường hay xây dựng hàm làm việc mảng vectơ hay ma trận phần tử Khi tham đối thực hàm mảng liệu Trong trường hợp ta có cách khai báo đối Cách thứ đối khai báo bình thường khai báo biến mảng khơng cần có số phần tử kèm theo, ví dụ:
int x[ ]; float x[ ];
cách thứ hai khai báo đối trỏ kiểu phần tử mảng, ví dụ: int *p;
float *p
(14)tử mảng a, nên hàm gọi địa gán cho trỏ p Vì giá trị phần tử thứ i a có thể truy cập x[i] (theo khai báo 1) *(p+i) (theo khai báo 2) thay đổi thực (do cách truyền theo dẫn trỏ) Sau ví dụ đơn giản, nhập in vectơ, minh hoạ cho kiểu khai báo đối
Ví dụ 5.6: Viết hàm cho phép nhập hàm cho phép in giá trị thành phần vectơ
#include <stdio.h> #include <conio.h>
void nhap(int x[ ], int n) // n: số phần tử {
int i;
for (i=0; i<n; i++)
scanf("%d",&x[i]); }
void in(int *p, int n) {
int i;
for (i=0; i<n; i++)
printf("%d", *(p+i)); }
Và lời gọi hàm viết sau: int main()
{
int a[10] ; // mảng a chứa tối đa 10 phần tử nhap(a,7); // vào phần tử cho a in(a,7); // phần tử a getch();
return 0; }
Như tổ chức chương trình hàm nhap() hàm in() thiết kế với chức thực cơng việc khơng cần trả giá trị nào, hàm khai báo dạng void Hai lời gọi hàm chương trình nhap(a,7), in(a,7) cho biết mảng cần nhập a số phần tử 7
5.1.7.2 Truyền mảng chiều cho hàm
(15)Ta có hai cách khai báo đối sau:
- Khai báo theo chất mảng chiều float x[m][n] C/C++ qui định, x mảng chiều m phần tử, phần tử có kiểu float[n] Từ đó, đối khai báo mảng hình thức chiều (không cần số phần tử - số dịng) kiểu float[n] Tức khai báo sau:
float x[ ][n] ; // mảng với số phần tử không định trước, phần tử n số float (*x)[n] ; // trỏ, có kiểu mảng n số (float[n])
Để truy nhập đến đến phần tử thứ i, j ta sử dụng cú pháp x[i][j] Tên mảng a viết bình thường lời gọi hàm Nói chung theo cách khai báo việc truy nhập đơn giản phương pháp có hạn chế số cột mảng truyền cho hàm phải cố định n
- Xem mảng float x[m][n] thực mảng chiều float x[m*n] sử dụng cách khai báo mảng chiều, sử dụng trỏ float *p để truy cập đến phần tử mảng Cách có hạn chế lời gọi: địa truyền cho hàm mảng a mà cần phải ép kiểu (float*) (để phù hợp với p) Với cách gọi k thứ tự phần tử a[i][j] mảng chiều (m*n), ta có quan hệ k, i, j như sau: k = *(p + i*n + j), i = k/n và j = k%n n là số cột mảng truyền cho hàm Điều có nghĩa để truy cập đến a[i][j] ta viết *(p+i*n+j), ngược lại biết số k tính dòng i, cột j phần tử Ưu điểm cách khai báo ta truyền mảng với kích thước (số cột khơng cần định trước) cho hàm
Sau ví dụ minh hoạ cho cách khai báo
Ví dụ 5.7: Viết chương trình tính tổng số hạng ma trận #include <stdio.h>
#include <conio.h>
float tong(float x[][10], int m, int n) {
float t = 0; int i, j ;
for (i=0; i<m; i++)
for (j=0; j<n; j++) t += x[i][j] ; return t;
}
int main() {
float a[8][10], b[8][10] ; int i, j, ma, na, mb, nb;
(16)scanf("%d%d",&ma,&na); for (i=0; i<ma; i++)
for (j=0; j<na; j++) {
printf("a[%d,%d]= ",i,j); scanf("%f",&a[i][j]); }
printf("Nhap so dong, so cot ma tran b: "); scanf("%d%d",&mb,&nb);
for (i=0; i<mb; i++) for (j=0; j<nb; j++) {
printf("b[%d,%d]= ",i,j); scanf("%f",&b[i][j]); }
printf("%f\n",tong(a, ma, na)); printf("%f\n",tong(b, mb, nb)); getch();
return 0; }
Trong ví dụ này, hàm float tong(float x[][10], int m, int n) sử dụng khai báo ma trận x mảng hai chiều với số chiều thứ 10 Lời gọi hàm tong(a, ma, na) tong(b, mb, nb) cho phép tính tổng tất phần tử mảng a b
Ví dụ 5.8: Viết chương trình cho phép tìm phần tử bé ma trận #include <stdio.h>
#include <conio.h>
void minmt(float *x, int m, int n) {
float = *x; int k, kmin;
for (k=1; k<m*n; k++) if (min > x[k])
(17)min = x[k]; kmin = k; }
printf("Gia tri la: %f , tai dong %d, cot %d\n",min,kmin/n,kmin%n); }
int main() {
float a[3][3]; int i, j ;
for (i=0; i<3; i++) for (j=0; j<3; j++) {
printf("a[%d,%d]",i,j); scanf("%f",&a[i][j]); }
minmt((float*)(a), 3, 3) ; getch();
return 0; }
Trong ví dụ khai báo void minmt(float *x, int m, int n) coi ma trận mảng chiều, lời gọi hàm minmt((float*)(a), 3, 3) tham số thực truyền vào trỏ float đến mảng a
Ví dụ 5.9: Viết chương trình cho phép cộng hai ma trận in ma trận kết #include <stdio.h>
#include <conio.h>
void inmt(float *x, int m, int n) {
int i, j;
for (i=0; i<m; i++) {
for (j=0; j<n; j++)
printf("%f-",*(x+i*n+j)); printf("\n");
(18)}
void cong(float *x, float *y, int m, int n) {
float *t = new float[m*n]; int k, i, j ;
for (k = 0; k < m*n; k++) *(t+k) = *(x+k) + *(y+k); inmt((float*)t, m, n);
}
int main() {
float a[3][3], b[3][3] ; int i, j, m, n;
m=3; n=3;
for (i=0; i<m; i++) for (j=0; j<n; j++) {
printf("a[%d,%d]",i,j); scanf("%f",&a[i][j]); printf("b[%d,%d]",i,j); scanf("%f",&b[i][j]); }
cong((float*)a, (float*)b, m, n); // Cong va In ket qua getch();
return 0; }
Chương trình giải yêu cầu đặt cách tạo hai hàm void inmt(float *x, int m, int n) void cong(float *x, float *y, int m, int n) để in cộng ma trận Trong hàm void cong(float *x, float *y, int m, int n) thực lời gọi inmt(float *x, int m, int n) để in ma trận kết hình
5.1.7.3 Giá trị trả lại hàm mảng
(19)được trả lại vào tham đối hàm (giống nghiệm phương trình bậc trả lại vào tham đối) Ở xét cách làm việc
Giá trị trả lại trỏ trỏ đến mảng kết Trước hết xét ví dụ sau đây:
Ví dụ 3.10: Sử dụng hàm để tạo dãy số #include <stdio.h>
#include <conio.h> #include <stdlib.h> int* day1()
{
int kq[3] = { 7, 5, }; return kq;
}
int* day2() {
int *kq;
kq=(int*)malloc(3*sizeof(int)); *kq = *(kq+1) = *(kq+2) = ; return kq ;
}
int main() {
int *a, i; a = day1();
for (i=0; i<3; i++)
printf("%d\n",*(a+i)); a = day2();
for (i=0; i<3; i++)
printf("%d\n",*(a+i)); getch();
return 0; }
(20)Hình 3.4 Kết thực ví dụ 3.10
Từ kết ta thấy, dãy a lấy kết trả từ hàm day1() cho kết không đúng, dãy a lấy kết từ hàm day2() cho kết Tại có vấn đề này? Xét mảng kq khai báo khởi tạo day1(), mảng cục (được tạo bên hàm) sau thấy, loại biến “tạm thời” (và tham đối) tồn trình hàm hoạt động Khi hàm kết thúc biến Do hàm trả lại địa kq trước kết thúc, sau hàm thực xong, toàn kq xố khỏi nhớ trỏ kết hàm trỏ đến vùng nhớ không cịn giá trị kq có Từ điều việc sử dụng hàm trả lại trỏ phải cẩn thận Muốn trả lại trỏ cho hàm trỏ phải trỏ đến dãy liệu cho khơng sau hàm kết thúc, hay nói khác phải dãy liệu khởi tạo bên hàm sử dụng theo phương pháp hàm day2() Trong day2() mảng kết số tạo cách xin cấp phát vùng nhớ Vùng nhớ cấp phát cịn tồn sau hàm kết thúc (nó bị xoá sử dụng toán tử delete) Do hoạt động day2() cho kết mong muốn
Mảng cần trả lại khai báo tham đối danh sách đối hàm
Tham đối trỏ nên hiển nhiên truyền mảng khai báo sẵn (để chứa kết quả) từ ngồi vào cho hàm mảng thực nhận nội dung kết (tức có thay đổi trước sau gọi hàm xem mục truyền tham đối thực theo dẫn trỏ) Để nắm vấn đề xét ví dụ sau
Ví dụ 5.11: Viết chương trình cho phép nhập vào vectơ, tính in vector tổng hai vector
#include <stdio.h> #include <conio.h> #include <stdlib.h>
void congvt(int *x, int *y, int *z, int n) {
for (int i=0; i<n; i++) z[i] = x[i] + y[i]; }
main() {
int i, n, a[10], b[10], c[10] ; printf("Nhap vao so phan tu: "); scanf("%d",&n);