Khai báo và định nghĩa hàm tt Hàm nguyên mẫu function prototype : Được dùng để cung cấp thông tin cho chương trình dịch về tên hàm, kiểu giá trị trả về, số lượng, thứ tự và kiểu của c
Trang 1Môn: PHƯƠNG PHÁP LẬP TRÌNH
Chương 6: Hàm và con trỏ
Trang 26.1.4 Các phương pháp truyền tham số
6.1.5 Phạm vi của các đối tượng
6.1.6 Cấp lưu trữ của các đối tượng
6.1.7 Đệ qui
6.1.8 Nạp chồng hàm
6.1.9 Tổ chức chương trình
6.1.10 Các chỉ thị tiền xử lý
Trang 3Chương 6: Hàm và con trỏ 3
6.1.1 Khái niệm Hàm
Một hàm là một khối lệnh được đặt tên và có tính chất là nó có thể được
thực thi tại nhiều điểm khác nhau trong chương trình khi được gọi Khối lệnh này còn được gọi là unit hay module
Hàm có thể được sử dụng trong chương trình này mà cũng có thể được sử
dụng trong chương trình khác, dễ cho việc kiểm tra và bảo trì chương trình
Hàm có một số đặc trưng:
Nằm trong hoặc ngoài văn bản có chương trình gọi đến hàm Một
văn bản có thể chứa nhiều hàm
Được gọi từ chương trình chính (main), từ hàm khác hoặc từ
chính nó (đệ quy)
Không lồng nhau.
Có 3 cách truyền giá trị: Truyền theo tham trị, tham biến và tham
trỏ
Các biến cục bộ trong hàm được tạo ra khi hàm được gọi và biến
mất khi hàm thực thi xong
Trang 46.1.1 Khái niệm Hàm (tt)
Trang 5 Để sử dụng các hàm này trong chương trình, đầu chương trình
phải chứa các khai báo và định nghĩa hằng, biến, hàm nguyên mẫu, bằng các chỉ thị tiền xử lý #include <tên tập tin>
Ví dụ: #include <iostream.h>
#include <conio.h>
Hàm tự tạo:
Do người sử dụng định nghĩa thêm các hàm khác phục vụ cho
nhu cầu lập trình của mình
Trang 66.1.2 Khai báo và định nghĩa hàm
.//Trị trả về
[Return <Biểu thức trả về>;]
}
Trang 7Chương 6: Hàm và con trỏ 7
6.1.2 Khai báo và định nghĩa hàm (tt)
Hàm nguyên mẫu (function prototype) :
Được dùng để cung cấp thông tin cho chương trình dịch về tên
hàm, kiểu giá trị trả về, số lượng, thứ tự và kiểu của các tham số của hàm
Chương trình dịch căn cứ vào các thông tin này để kiểm tra các
lời gọi hàm trong chương trình
Hàm nguyên mẫu được đặt sau phần khai báo toàn cục và ngay
trước hàm main() hoặc có thể đặt trong tập tin khác
Khai báo:
[<kiểu giá trị trả về>] <tên hàm>([<danh sách các tham số>]) ;
Ví dụ: Khai báo hàm nguyên mẫu có chức năng xác định trị min giữa 2
số nguyên
int Min(int, int) ;
int Min(int a, int b) ; // nên dùng cách khai báo này
Trang 86.1.2 Khai báo và định nghĩa hàm (tt)
Tổ chức một chương trình “C/C++”
Cách 1: chương trình gồm 3 phần
PHẦN KHAI BÁO TOÀN CỤC
PHẦN KHAI BÁO VÀ ĐỊNH NGHĨA HÀM
HÀM main()
Cách 2: chương trình gồm 4 phần (nên dùng cách này)
PHẦN KHAI BÁO TOÀN CỤC
PHẦN KHAI BÁO HÀM NGUYÊN MẪU
HÀM main()
PHẦN ĐỊNH NGHĨA HÀM
Trang 9Chương 6: Hàm và con trỏ 96.1.2 Khai báo và định nghĩa hàm (tt)
Ví dụ:
Trang 106.1.3 Các dạng hàm
1 Hàm không có giá trị trả về
Ví dụ: Hàm xoá màn hình 100 lần
Trang 11Chương 6: Hàm và con trỏ 11
6.1.3 Các dạng hàm (tt)
1 Hàm không có giá trị trả về
Ví dụ:
Trang 126.1.3 Các dạng hàm (tt)
2 Hàm có giá trị trả về (Lệnh return)
Ví dụ: hàm tính luỹ thừa n (với n nguyên) của một số thực bất kỳ
Trang 13Chương 6: Hàm và con trỏ 13
6.1.3 Các dạng hàm (tt)
2 Hàm có giá trị trả về (tt)
Ví dụ: Tính tổng các phần tử có trong mảng
Trang 146.1.3 Các dạng hàm (tt)
3 Hàm gọi đệ qui: một lệnh trong thân hàm gọi đến chính nó
Ví dụ: Chương trình tính giai thừa của n
if(n==0) return 1;
Trang 15Chương 6: Hàm và con trỏ 15
6.1.3 Các dạng hàm (tt)
4 Hàm không có tham số
Ví dụ: Hàm in dòng chữ “HELLO C”
Trang 166.1.3 Các dạng hàm (tt)
5 Hàm có tham số
Ví dụ: hàm in số lớn nhất
Trang 17Chương 6: Hàm và con trỏ 17
6.1.4 Các phương pháp truyền tham số
Có hai loại tham số:
Tham số thực (actual parameter) là tham số trong lời gọi hàm.
Tham số hình thức (formal parameter) là tham số trong phần khai
báo và định nghĩa Tham số hình thức chỉ là tên đại diện cho tham số thực tương ứng Kiểu của tham số hình thức sẽ qui định kiểu của tham số thực
Trang 186.1.4 Các phương pháp truyền tham số (tt)
Trang 19Chương 6: Hàm và con trỏ 19
6.1.4 Các phương pháp truyền tham số (tt)
Có hai cách truyền tham số:
1 Truyền tham trị (call by value):
Chương trình dịch cấp phát vùng nhớ riêng cho từng tham số hình
thức, sau đó sao chép giá trị của tham số thực tương ứng vào các tham số hình thức
Khi kết thúc thực hiện hàm, chương trình dịch sẽ thu hồi các vùng
nhớ đã cấp phát cho các tham số hình thức, và các biến cục bộ khai báo bên trong hàm
Như vậy, mọi sự thay đổi trị của các tham số hình thức đều không
ảnh hưởng đến các tham số thực bên ngoài hàm
Trang 206.1.4 Các phương pháp truyền tham số (tt)
Ví dụ: Khảo sát chương trình
Trang 21Chương 6: Hàm và con trỏ 21
6.1.4 Các phương pháp truyền tham số (tt)
2 Truyền tham chiếu(call by reference):
Chương trình dịch sẽ truyền địa chỉ của các tham số thực tương ứng
cho các tham số hình thức
Nghĩa là ta có thể xem tham số hình thức cũng chính là tham số thực,
hay nói cách khác tham số hình thức là tên gọi khác của tham số thực
Mọi sự thay đổi trị của tham số hình thức bên trong hàm chính là
thay đổi trị của tham số thực bên ngoài hàm
Trang 226.1.4 Các phương pháp truyền tham số (tt)
Ví dụ: Khảo sát chương trình sau
Trang 23Chương 6: Hàm và con trỏ 23
6.1.4 Các phương pháp truyền tham số (tt)
Chú ý:
Trong cách truyền tham chiếu, tham số thực tương ứng phải là một
biến Còn trong cách truyền trị, tham số thực tương ứng có thể là biến, hằng, lời gọi hàm, hoặc một biểu thức cùng kiểu với tham số hình thức
Các tham số hình thức trong cách truyền bằng giá trị được gọi là
tham trị Còn các tham số hình thức trong cách truyền bằng tham chiếu được gọi là tham biến
Trang 246.1.4 Các phương pháp truyền tham số (tt)
Truyền mảng vào hàm:
Truyền mảng vào hàm mặc định là truyền tham chiếu Những thay
đổi đến giá trị của các phần tử mảng trong thân hàm sẽ ảnh hưởng đến mảng gôc
Ví dụ: Viết chương trình thay đổi giá trị các phân tử mảng theo yêu cầu:
nếu giá trị >=0 thì thay bằng 1, ngược lại thay bằng 0
Trang 266.1.5 Phạm vi của các đối tượng
Phạm vi là vùng chương trình mà đối tượng được nhận biết và có thể
được sử dụng
Phạm vi của một đối tượng trải dài từ nơi nó được khai báo đến cuối
khối, hàm, hay tập tin chứa đối tượng đó Có các loại phạm vi sau:
Phạm vi cục bộ (local scope)
− Phạm vi khối (Block scope)
− Phạm vi hàm (Function scope)
Phạm vi toàn cục (global scope)
− Phạm vi tập tin (File scope)
− Phạm vi chương trình (Program scope)
Trang 27Chương 6: Hàm và con trỏ 27
6.1.5 Phạm vi của các đối tượng (tt)
Phạm vi khối: Trong C, một khối được giới hạn bởi ngoặc {} Biến
khai báo trong khối đó có phạm vi khối, nghĩa là nó chỉ hoạt động
trong khối đó mà thôi Phạm vi này còn gọi là cục bộ, và biến đưọc gọi là biến cục bộ.
Ví dụ:
int main(){
int i; /* block scope */
return 0;
}
Trang 286.1.5 Phạm vi của các đối tượng (tt)
Ví dụ:
Trang 29Chương 6: Hàm và con trỏ 29
6.1.5 Phạm vi của các đối tượng (tt)
Phạm vi hàm: chỉ định một biến có phạm vi hoạt động từ đầu đến
cuối một hàm (không nhầm lẫn với biến có phạm vi khối) Trong C, chỉ có nhãn (label) đối với lệnh goto là có phạm vi hàm
Ví dụ:
int main(){ int i; /* block scope */
}
Trang 306.1.5 Phạm vi của các đối tượng (tt)
Phạm vi chương trình: Biến có phạm vi chương trình khi nó được
khai báo bên ngoài các hàm
Ví dụ:
int x = 0; /* program scope */
float y = 0.0; /* program scope */
Trang 31Chương 6: Hàm và con trỏ 316.1.5 Phạm vi của các đối tượng (tt)
Ví dụ:
Trang 326.1.5 Phạm vi của các đối tượng (tt)
Phạm vi tập tin: Trong C, biến được khai báo là toàn cục và static
được gọi là có phạm vi tập tin
int x = 0; /* program scope */
static int y = 0; /* file scope */
static float z = 0.0; /* file scope */
Trang 33Chương 6: Hàm và con trỏ 336.1.5 Phạm vi của các đối tượng (tt)
Ví dụ:
Trang 346.1.5 Phạm vi của các đối tượng (tt)
Biến toàn cục (local variable) Biến cục bộ (Global variable)
Biến cục bộ là biến khai báo bên
trong khối hay hàm của khối Biến toàn cục là biến khai báo bên ngoài mọi hàm
Chỉ có thể được truy xuất bên trong
phạm vi khối hay hàm đó mà thôi Có thể được truy xuất ở mọi nơi trong chương trình
Các biến cục bộ có thời gian tồn tại
tương đối ngắn Chúng sẽ bị hủy
mỗi khi ra khỏi khối hay kết thúc
thực hiện hàm chứa nó
Các biến toàn cục có thời gian tồn tại là thời gian của chương trình
Trang 35Chương 6: Hàm và con trỏ 35
6.1.6 Cấp lưu trữ của các đối tượng
Cấp lưu trữ (storage class) là cách thức NNLT cấp phát vùng nhớ và
lưu trữ biến Cấp lưu trữ của một biến được xác định bằng các từ
khóa sau: auto, register, static, extern.
Biến auto (còn gọi là biến tự động, biến cục bộ):
Mọi biến khai báo bên trong một khối hay hàm mặc nhiên có tính
chất auto, trừ khi xác định rõ cấp lưu trữ khác
Ví dụ, khai báo int x; tương đương với khai báo auto int x;.
Biến auto có phạm vi cục bộ bên trong hàm hay khối và có thời
gian tồn tại ngắn, do được cấp phát trong vùng nhớ STACK
Trang 366.1.6 Cấp lưu trữ của các đối tượng (tt)
Biến register (ít dùng)
Để tăng tốc độ truy xuất biến, chương trình dịch lưu trữ biến trong thanh ghi Chương trình dịch có thể bỏ qua không đáp ứng lời yêu cầu này nếu có quá nhiều lời đề nghị loại này hoặc nếu không còn đủ thanh ghi để cấp phát
Ví dụ: int main()
{ /* block scope with the register specifier */
register int i;
for (i=0; i<MAX_NUM; i++){ /* some statements */}
return 0;
Trang 37Chương 6: Hàm và con trỏ 37
6.1.6 Cấp lưu trữ của các đối tượng (tt)
Biến static (còn gọi là biến tĩnh)
Là biến được cấp phát trong vùng nhớ DATA do đó có tính chất
cố định, lâu dài
Biến static khai báo bên trong một khối, hay hàm sẽ không bị hủy
khi ra khỏi khối hay hàm đó, và vẫn lưu giữ giá trị cũ của lần gọi hàm trước
Biến static phải được khởi tạo giá trị khi khai báo Chương trình
dịch sẽ chỉ khởi tạo giá trị cho biến static duy nhất một lần trong lần gọi hàm đầu tiên
Trang 386.1.6 Cấp lưu trữ của các đối tượng (tt)
Ví dụ: Biến static
Trang 39Chương 6: Hàm và con trỏ 39
6.1.6 Cấp lưu trữ của các đối tượng (tt)
Biến extern
Phạm vi của một biến extern trong chương trình có thể được trải
dài trên nhiều tập tin
Chương trình dịch sẽ không cấp phát thêm vùng nhớ cho biến có
khai báo extern mà sử dụng chung vùng nhớ đã cấp phát trước đó
Trang 406.1.6 Cấp lưu trữ của các đối tượng (tt)
Ví dụ: Biến extern
Giả thiết 2 chương trình trên nằm trong 2 tệp khác nhau Để liên kết
(link) biến i giữa 2 chương trình cần định nghĩa tổng thể i trong một
và khai báo extern trong chương trình kia
Trang 41Chương 6: Hàm và con trỏ 416.1.6 Cấp lưu trữ của các đối tượng (tt)
Cấp lưu trữ biến (Storage class)
Trang 43Chương 6: Hàm và con trỏ 43
6.1.7 Đệ qui (tt)
Đặc điểm của hàm đệ qui
Chương trình viết rất gọn,
Việc thực hiện gọi đi gọi lại hàm rất nhiều lần phụ thuộc vào độ lớn
của đầu vào Do đó chương trình sẽ mất thời gian để lưu giữ các thông tin của hàm gọi trước khi chuyển điều khiển đến thực hiện hàm được gọi Mặt khác các thông tin này được lưu trữ nhiều lần trong ngăn xếp sẽ dẫn đến tràn ngăn xếp nếu n lớn
Tuy nhiên, đệ qui là cách viết rất gọn, dễ viết và đọc chương trình,
mặt khác có nhiều bài toán hầu như tìm một thuật toán lặp cho nó là rất khó trong khi viết theo thuật toán đệ qui thì lại rất dễ dàng
Trang 446.1.7 Đệ qui (tt)
Cấu trúc chung của hàm đệ qui
if (trường hợp suy biến)
Trang 45Chương 6: Hàm và con trỏ 45
6.1.7 Đệ qui (tt)
Đệ quy - Các ví dụ
Ví dụ: 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)
Chương trình đệ qui để tính UCLN của a và b như sau.
Trang 466.1.7 Đệ qui (tt)
Ví dụ: Tính số hạng thứ n của dãy Fibonaci là dãy f(n) được định nghĩa:
− f(0) = f(1) = 1
− f(n) = f(n-1) + f(n-2) với n ≥ 2.∀
Trang 47Chương 6: Hàm và con trỏ 47
6.1.9 Một số gợi ý khi thiết kế hàm
Xác định rõ chức năng, nhiệm vụ của hàm.
Chỉ nên thiết kế hàm theo phương châm “mỗi hàm chỉ thực hiện
một nhiệm vụ duy nhất”, và nên thiết kế sao cho có thể sử dụng lại hàm để hổ trợ cho các việc khác (reusable)
Đặt tên hàm sao cho có tính gợi nhớ (Mnemonic)
Nên đặt chú thích, ghi rõ các thông tin về hàm như chức năng, điều
kiện dữ liệu vào, xác định dữ liệu ra của hàm,
Xác định trị trả về: hàm có cần trả về giá trị? Nếu có, xác định rỏ
kiểu trả về Đối với các hàm có chức năng nhập/xuất dữ liệu, trị trả
về thường là void Còn đối với loại hàm kiểm tra một tính chất P nào
đó, ta thường trả về giá trị 0 hoặc 1, i.e trả về trị của một biểu thức logic
Trang 486.1.9 Một số gợi ý khi thiết kế hàm (tt)
Xác định số lượng tham số và kiểu của từng tham số: hàm có nhận
tham số hay không? Bao nhiêu tham số? Kiểu của từng tham số?
Xác định rõ phương pháp truyền tham số: nếu không có nhu cầu
làm thay đổi trị của tham số thực truyền vào cho hàm thì áp dụng phương pháp truyền bằng giá trị Còn ngược lại thì áp dụng cách truyền bằng tham chiếu
Trang 49Chương 6: Hàm và con trỏ 49
6.1.10 Các chỉ thị tiền xử lý
a Chỉ thị bao hàm tệp #include
Cho phép ghép nội dung các tệp đã có khác vào chương trình trước
khi dịch Các tệp cần ghép thêm vào chương trình thường là các tệp chứa khai báo nguyên mẫu của các hằng, biến, hàm … có sẵn trong
C hoặc các hàm do lập trình viên tự viết
Có hai dạng viết chỉ thị này.
Trang 506.1.10 Các chỉ thị tiền xử lý (tt)
b Chỉ thị macro #define
Cú pháp: #define tên_macro xaukitu
Trước khi dịch bộ tiền xử lý sẽ tìm trong chương trình và thay thế bất
kỳ vị trí xuất hiện nào của tên_macro bởi xâu kí tự Ta thường sử
dụng macro để định nghĩa các hằng hoặc thay cụm từ này bằng cụm
từ khác dễ nhớ hơn
Ví dụ:
#define then // thay then bằng dấu cách
#define begin { // thay begin bằng dấu {
#define end } // thay end bằng dấu }
#define MAX 100 // thay MAX bằng 100
#define TRUE 1 // thay TRUE bằng 1
if (i < MAX) then begin
Ok = TRUE;
cout << i ; end
if (i < 100 then {
Ok = 1;
Viết
CT dịch
Trang 51Chương 6: Hàm và con trỏ 51
6.1.10 Các chỉ thị tiền xử lý (tt)
c Các chỉ thị biên dịch có điều kiện #if, #ifdef, #ifndef
Chỉ thị: #if dãy lệnh … #endif
#if dãy lệnh … #else dãy lệnh … #endif,
Báo cho chương trình dịch biết đoạn lệnh giữa #if (điều kiện) và
#endif chỉ được dịch nếu điều kiện đúng
#endif }
Trang 526.1.10 Các chỉ thị tiền xử lý (tt)
d Chỉ thị #ifdef và #ifndef
Chỉ thị này báo cho chương trình dịch biết đoạn lệnh có được dịch
hay không khi một tên gọi đã được định nghĩa hay chưa
Để định nghĩa một tên gọi ta dùng chỉ thị #define tên.
Chỉ thị này đặc biệt có ích khi chèn các tệp thư viện vào để sử dụng
Một tệp thư viện có thể được chèn nhiều lần trong văn bản do vậy nó
có thể sẽ được dịch nhiều lần, điều này sẽ gây ra lỗi vì các biến được khai báo nhiều lần
Trang 53Chương 6: Hàm và con trỏ 53
6.1.10 Các chỉ thị tiền xử lý (tt)
d Chỉ thị #ifdef và #ifndef
Thư viện 1 tên tệp: MYLIB.H
int max(int a, int b)
Trang 546.1.10 Các chỉ thị tiền xử lý (tt)
Khắc phục
// tệp mylib.h
#ifndef _MYLIB_ // nếu chưa định nghĩa tên gọi _MYLIB_
#define _MYLIB_ // thì định nghĩa nó
int max(int a, int b) // và các hàm khác
{
return (a>b? a: b);
}
#endif