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.. K
Trang 2 Viết chương trình tính S = a! + b! + c! với a, b, c
Nhập
Nhập NhậpNhập NhậpNhập TínhTính TínhTính TínhTính
Trang 3do {
cout<<“Nhap mot so nguyen duong: ”;
cin>> b ; } while ( b <= 0);
Trang 5 Giải pháp => Viết 1 lần và sử dụng nhiều lần
do {
cout<<“Nhap mot so nguyen duong: ”;
cin>> n ; } while ( n <= 0);
{ Tính s = n! = 1 * 2 * … * n }
Trang 6 Khái niệm
đầu ra.
chuyên biệt cho chương trình chính.
nhau.
• Tái sử dụng.
Trang 7 Các đặc trưng của Hàm
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)
Trang 9Có 2 loại hàm trong NNLT “C/C++”:
Hàm thư viện (library functions):
Do chương trình dịch “C/C++” cung cấp
Để 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>
Trang 10 Cú pháp
• <kiểu trả về> : kiểu bất kỳ của C ( char , int , long ,
float ,…) Nếu không trả về thì là void
• <tên hàm>: theo quy tắc đặt tên định danh.
• <danh sách tham số> : tham số hình thức đầu vào
giống khai báo biến, cách nhau bằng dấu ,
<kiểu trả về> <tên hàm> ( [danh sách tham số] )
{
<các câu lệnh>
[ return <giá trị>;]
}
Trang 11 Cần xác định các thông tin sau đây:
Trang 12 Ví dụ 1
Tên hàm: XuatTong
Công việc: tính và xuất tổng 2 số nguyên
Đầu vào: hai số nguyên x và y
Đầu ra: không có
void XuatTong(int x, int y)
{
int s;
s = x + y;
cout<<x<<“+”<<y<<“=“<<s;
Trang 13 Ví dụ 2
Tên hàm: TinhTong
Công việc: tính và trả về tổng 2 số nguyên
Đầu vào: hai số nguyên x và y
Đầu ra: một số nguyên có giá trị x + y
int TinhTong(int x, int y)
{
int s;
Trang 14 Ví dụ 3
Tên hàm: NhapXuatTong
Công việc: nhập và xuất tổng 2 số nguyên
Đầu vào: không có
Đầu ra: không có
Trang 15 Khái niệm
• Toàn cục: khai báo trong ngoài tất cả các hàm (kể
cả hàm main) và có tác dụng lên toàn bộ chương trình.
• Cục bộ: khai báo trong hàm hoặc khối { } và chỉ có tác dụng trong bản thân hàm hoặc khối đó (kể cả khối con nó) Biến cục bộ sẽ bị xóa khỏi bộ nhớ khi
Trang 16int a;
int Ham1() {
int a1;
}
int Ham2() {
int a2;
{
int a21;
} }
void main() {
Trang 17BB Hàm nguyên mẫu (function prototype)
Hàm nguyên mẫu:
Đượ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ị
Trang 18 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 19int min1 = min(a,b);
cout << “Min = “ << min1;}
int min(int a, int b){ if (a<b) return a;
Tổ chức một chương trình “C/C++”
Trang 20Có 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 21BB Các phương pháp truyền tham số
Trang 22Có 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 23 Truyền Giá trị (Call by Value)
hàm chỉ sẽ nhận giá trị
giá trị của tham số sau khi thực hiện hàm.
void TruyenGiaTri(int x)
{
Các phương pháp truyền tham số
Trang 24 Ví dụ: Khảo sát chương trình sau
cout << “Inside main function:” << endl;
cout << “a = “ << a << endl;
Trang 25BB Các phương pháp truyền tham số
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 26 Truyền Địa chỉ (Call by Address)
trỏ).
Không được truyền giá trị cho tham số này.
của tham số sau khi thực hiện hàm.
void TruyenDiaChi(int *x)
{
…
*x++;
Trang 27 Truyền Tham chiếu (Call by Reference) ( C++ )
Không được truyền giá trị cho tham số này.
của tham số sau khi thực hiện hàm.
void TruyenThamChieu(int &x)
{
Các phương pháp truyền tham số
Trang 28 Ví dụ: Khảo sát chương trình sau
cout << “Inside main function:” << endl;
cout << “a = “ << a << endl;
Trang 29BB Các phương pháp truyền tham số
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 30}
Trang 32 Cách thực hiện
(hằng, biến, biểu thức) cho các tham số theo đúng thứ tự đã được khai báo trong hàm.
<tên hàm> ( <đối số 1> , … , <đối số n> ) ;
Trang 33 Ví dụ
Trang 34 Ví dụ
Trang 35int gt;
if(n==1) return(1);
// goi de qui
gt = giaiThua(n-1)*n; return gt;
}
Trang 37Đặ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
Đệ quy
Trang 38Lớp các bài toán giải được bằng đệ qui
Giải quyết được dễ dàng trong các trường hợp riêng gọi là
trường hợp suy biến hay cơ sở, trong trường hợp này hàm được
tính bình thường mà không cần gọi lại chính nó,
Đối với trường hợp tổng quát, bài toán có thể giải được bằng bài
toán cùng dạng nhưng với tham đối khác có kích thước nhỏ hơn tham đối ban đầu Và sau một số bước hữu hạn biến đổi cùng dạng, bài toán đưa được về trường hợp suy biến
Trang 39 Cấu trúc chung của hàm đệ qui
if (trường hợp suy biến)
Trang 40 Các ví dụ
Ví dụ 1 : 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.
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);
}
Trang 42 Ví dụ 2 : Tháp Hà Nội
void chuyen(int n, int di, int den) // n: số tầng, di, den: vị trí đi,
đến{ if (n==1) cout << di << " → " << den << endl;
else { cout << di << "→" << 6-di-den << endl; // 1 tầng từ di qua trung gian
chuyen(n-1, di, den) ; // n-1 tầng từ di qua dencout << 6-di-den << "→" den << endl; // 1 tầng từ tg
về lại den }
Trang 43BB Nạp chồng hàm
Nạp chồng hàm là dùng chung một danh hiệu để đặt tên cho các
hàm khác nhau
Chỉ nạp chồng hàm đối với những hàm giống nhau về bản chất,
nhưng khác nhau ở số lượng, và kiểu dữ liệu của các tham số
Khả năng nạp chồng hàm kết hợp với hàm có tham số với giá trị
double x = 20.0;
int y = 10;
F(x, y); // mơ hồ! chương trình
Trang 44 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ớ (Memonic)
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,
Trang 45BB Một số gợi ý khi thiết kế hàm
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 46 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 47 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;
Phạm vi (scope) của các đối tượng
Trang 48 Ví dụ: 1: /* Scopes in nested block */
10: { /* the beginning of the inner block */
11: int i, j; /* block scope 2, int i hides the outer int i*/
12:
13: cout<<"Within the inner block:\n";
14: for (i=0, j=10; i<=10; i++, j )
15: cout<<"i=“<<i<<“ j= “<<j<<“\n”;
16: } /* the end of the inner block */
17: cout<<"Within the outer block: i=<< i<<endl;
i= 0, j=10 i= 1, j= 9 i= 2, j= 8 i= 3, j= 7 i= 4, j= 6 i= 5, j= 5 i= 6, j= 4 i= 7, j= 3 i= 8, j= 2 i= 9, j= 1 i=10, j= 0 Within the outer block:
Trang 49 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 */
start: /* A goto label has function scope */
goto start; /* the goto statement */
Phạm vi (scope) của các đối tượng
Trang 50 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 */
int main(){ int i; /* block scope */
Trang 51 Ví dụ:
1: /* Program scope vs block scope */
2: #include <stdio.h>
4: int x = 1234; /* program scope */
5: double y = 1.234567; /* program scope */
7: void function_1() 8: {
9: cout<<"From function_1: x=,”<<x<<“ y= “<< y<<endl;
10: } 12: main() 13: {
14: int x = 4321; /* block scope 1*/
16: function_1();
17: cout<<"Within the main block: x= “<<x<<“ y= “<<y;
18: /* a nested block */
19: { 20: double y = 7.654321; /* block scope 2 */
21: function_1();
Kết quả:
From function_1:
x=1234, y=1.234567 Within the main block:
x=4321, y=1.234567 From function_1:
x=1234, y=1.234567 Within the nested block:
x=4321, y=7.654321
Phạm vi (scope) của các đối tượng
Trang 52 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 */
int main(){ int i; /* block scope */
return 0;
}
Trang 53 Ví dụ:
#include <iostream.h>
int x=3; // biến x toàn cục
const int MAX = 10;
cout<<”Y = “<<y; // Sai vì biến y đã
bị hủy, không thể truy xuất
cout<<”X của hàm main() = “<<x; cout<<”X toàn cục = “<<::x;
} void fct(int x){
Trang 54Biế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 55BB 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 56 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 57 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.
Cấp lưu trữ của các đối tượng
Trang 58{ ham(); // Hàm chạy lần thứ 1, i = 2
ham(); // Hàm chạy lần thứ 2, i = 4 ham(); // Hàm chạy lần thứ 3, i = 6
Trang 59 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 đó
Ví dụ: int x = 0; /* a global variable */
extern int y; /* an allusion to a global variable y */
int main(){ extern int z; /* an allusion to a global variable z */
int i; /* a local variable */
Cấp lưu trữ của các đối tượng