2. 1 2 3 4 5 6 7 8
VC
BB
Nội dung
Sự tổng quát hóa trong lập trình
Vấn đề về các hàm cùng tên
Vấn đề về giá trị mặc định của tham số hàm
Vấn đề về các hàm mã nguồn y hệt nhau
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
Vấn đề về hàm có số lượng tham số không biết trước
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
Nhu cầu về gia tăng tính tái sử dụng của mã nguồn
#include <iostream>
using namespace std;
void main()
{
cout << “Hello World”;
cout << endl;
Một số vấn đề trong lập trình
2
3. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Một số ví dụ
BB
Hàm tìm số lớn nhất trong mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findMax(int a[], int n)
{
int i, iMax = 0;
for (i = 1; i < n; i++)
{
if (a[i] > a[iMax])
// a[i] “tốt” hơn?
{
iMax = i;
}
}
return a[iMax];
}
Một số vấn đề trong lập trình
3
4. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Một số ví dụ
BB
Hàm tìm số lớn nhất trong mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findMin(int a[], int n)
{
int i, iMin = 0;
for (i = 1; i < n; i++)
{
if (a[i] < a[iMin])
// a[i] “tốt” hơn?
{
iMin = i;
}
}
return a[iMin];
}
Một số vấn đề trong lập trình
4
5. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Một số ví dụ
BB
Hàm tìm số lớn nhất trong mảng
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findBest(int a[], int n, hàm bool isBetter(int, int))
{
int i, iBest = 0;
for (i = 1; i < n; i++)
{
if (isBetter(a[i], a[iBest]))
// a[i] “tốt” hơn?
{
iBest = i;
}
}
return a[iBest];
}
Một số vấn đề trong lập trình
5
6. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
BB
Con trỏ hàm
Khái niệm
Hàm cũng đuợc lưu trữ trong bộ nhớ tại một
địa chỉ xác định.
Con trỏ hàm là con trỏ trỏ đến vùng nhớ chứa
hàm và có thể gọi hàm thông qua con trỏ đó.
0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17
…
11 00 00 00
pfn
…
int add(int, int)
Một số vấn đề trong lập trình
6
7. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
BB
Khai báo con trỏ hàm
Khai báo tường minh
1
return-type (*var-name)(param-type-list);
Ví dụ
1
2
3
4
5
6
7
int (*pfn1)(int);
void(*pfn2)(int, int);
char(*pfn3)(char* []);
void(*pfn4)();
Một số vấn đề trong lập trình
7
8. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
BB
Khai báo con trỏ hàm
Khai báo không tường minh
1
2
typedef return-type (*func-ptr-type-name)(param-type-list);
func-ptr-type-name func-ptr-var-name;
Ví dụ
1
2
3
4
5
6
int (*pfn1)(int, int);
// Tường minh
// Định nghĩa kiểu con trỏ hàm mới
typedef int (*TPFnCalculate)(int, int);
TPFnCalculate pfn2, pfn3;
// Không tường minh
Một số vấn đề trong lập trình
8
9. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Gán giá trị cho con trỏ hàm
BB
Cú pháp
1
2
func-ptr-var-name = &func-name;
// Dạng chính quy
func-ptr-var-name = func-name;
// Dạng ngắn gọn
Ví dụ
1
2
3
4
5
6
int add(int nX, int nY) { return nX + nY; }
int subtract(int nX, int nY) { return nX - nY; }
…
int (*pfnCalculate)(int, int) = NULL;
pfnCalculate = &add;
// Trỏ đến hàm add()
pfnCalculate = subtract;
// Trỏ đến hàm subtract()
Một số vấn đề trong lập trình
9
10. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
So sánh con trỏ hàm
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
if (pfnCalculate != NULL)
{
if (pfnCalculate == &add)
// Dạng chính quy
printf(“Point to add()”);
else
if (pfnCalculate == subtract)
// Dạng ngắn gọn
printf(“Point to subtract()”);
else
printf(“Point to other function!”);
}
else
printf(“Point to none!”);
Một số vấn đề trong lập trình
10
11. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Gọi hàm sử dụng con trỏ hàm
BB
Cú pháp
1
2
(*func-ptr-var-name)(arg-list);
// Dạng chính quy
func-ptr-var-name(arg-list);
// Dạng ngắn gọn
Ví dụ
1
2
3
4
5
6
int add(int nX, int nY) { return nX + nY; }
int subtract(int nX, int nY) { return nX - nY; }
…
int (*pfnCalculate)(int, int) = &add;
int nResult1 = (*pfnCalculate)(3, 6);
// Dạng chính quy
int nResult2 = pfnCalculate(3, 6);
// Dạng ngắn gọn
Một số vấn đề trong lập trình
11
12. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Truyền đối số là con trỏ hàm
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
int add(int nX, int nY) { return nX + nY; }
int subtract(int nX, int nY) { return nX - nY; }
int doCalculation(int nX, int nY, int (*pfnCalculate)(int, int))
{
int nResult = (*pfnCalculate)(nX, nY);
// Gọi hàm
return nResult;
}
void main()
{
int (*pfnCalculate)(int, int) = &add;
int nResult1 = doCalculation(3, 6, pfnCalculate);
int nResult2 = doCalculation(3, 6, &subtract);
}
Một số vấn đề trong lập trình
12
13. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Trả về con trỏ hàm
BB
Ví dụ (khai báo tường minh)
1
2
3
4
5
6
7
8
9
10
11
12
13
int (*getCalculation(char cOperator))(int, int)
{
int (*pfnCalculate)(int, int) = NULL;
switch (cOperator)
{
case ‘+’: pfnCalculate = &add; break;
case ‘-’: pfnCalculate = &subtract; break;
}
return pfnCalculate;
}
…
int (*pfnCalculate)(int, int) = getCalculation(‘+’);
int nResult = pfnCalculate(3, 6);
Một số vấn đề trong lập trình
13
14. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Trả về con trỏ hàm
BB
Ví dụ (khai báo không tường minh)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef int (*TPFnCalculate)(int, int);
TPFnCalculate getCalculation (char cOperator)
{
TPFnCalculate pfnCalculate = NULL;
switch (cOperator)
{
case ‘+’: pfnCalculate = &add; break;
case ‘-’: pfnCalculate = &subtract; break;
}
return pfnCalculate;
}
…
TPFnCalculate pfnCalculate = getCalculation(‘+’);
int nResult = pfnCalculate(3, 6);
Một số vấn đề trong lập trình
14
15. 1 2 3 4 5 6 7 8 Sự tổng quát hóa trong lập trình
VC
Mảng con trỏ hàm
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef int (*TPFnCalculate)(int, int);
void main()
{
int (*apfnCalculate1[2])(int, int);
// Cách 1
TPFnPhepToan apfnCalculate2[2];
// Cách 2
apfnCalculate1[0] = apfnCalculate2[1] = &add;
apfnCalculate1[1] = apfnCalculate2[0] = &subtract;
int nResult1 = (*apfnCalculate1[0])(3, 6);
int nResult2 = apfnCalculate1[1](3, 6));
int nResult3 = apfnCalculate2[0](3, 6));
int nResult4 = apfnCalculate2[1](3, 6));
}
Một số vấn đề trong lập trình
15
16. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
BB
Hàm trùng tên
Nhu cầu
Thực hiện công việc bằng nhiều cách khác
nhau. Nếu các hàm khác tên sẽ khó nhớ.
Ví dụ
1
2
3
4
5
6
7
8
// Tính trị tuyệt đối trong C (math.h)
int abs(int);
long labs(long);
double fabs(double);
// Tính căn bậc 2 trong C (math.h)
float sqrtf(float);
double sqrt(double);
Một số vấn đề trong lập trình
16
17. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
BB
Khái niệm
Là các hàm cùng tên nhưng có tham số đầu vào
hoặc kiểu trả về khác nhau nhằm cho phép
người dùng chọn cách thuận lợi nhất để thực
hiện công việc.
Việc sử dụng các hàm trùng tên được gọi là
chồng hàm (function overloading).
1
2
3
4
5
void printNumbers(int n);
// 1, 2, …, n
void printNumbers(int x, int y);
// x, x + 1, …, y
void printNumbers(int x, int y, int a);
// x, x + a, …, y
Một số vấn đề trong lập trình
17
18. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
BB
Một số lưu ý
Nguyên mẫu hàm (prototype) sau khi bỏ tên
tham số phải khác nhau.
1
2
3
4
5
6
7
// Các hàm sau đây là như nhau vì cùng nguyên mẫu hàm
// Nguyên mẫu hàm chung: int add(int, int);
int add(int nA, int nB);
int add(int nB, int nA);
int add(int nX, int nY);
Một số vấn đề trong lập trình
18
19. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
Một số lưu ý
BB
Sử dụng con trỏ hàm để lấy địa chỉ của các hàm
cùng tên.
1
2
3
4
5
6
7
8
9
10
11
12
void printNumbers(int n);
// Xuất 1, 2, …, n
void printNumbers(int x, int y);
// Xuất x, x + 1, …, y
void main()
{
void (*pfn1)(int) = &printNumbers;
void (*pfn2)(int, int) = &printNumbers;
cout << pfn1 << endl; // Địa chỉ printNumbers(int)
cout << pfn2 << endl; // Địa chỉ printNumbers(int, int)
}
void printNumbers(int n) { /* … */ }
void printNumbers(int x, int y) { /* … */ }
Một số vấn đề trong lập trình
19
20. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
Sự nhập nhằng, mơ hồ
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
float f(float fX) { return fX / 2; }
double f(double dX) { return dX / 2; }
void main()
{
float fA = 29.12;
double dB = 17.06;
float fResult1 = f(fA);
// f(float)
double dResult2 = f(dB);
// f(double)
float fResult3 = f(369);
// ???
}
Một số vấn đề trong lập trình
20
21. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
Sự nhập nhằng, mơ hồ
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
void f(unsigned char cX) { printf(“%d”, cX); }
void f(char cX) { printf(“%c”, cX); }
void main()
{
char c = ‘A’;
f(c);
// f(char)
f(‘A’);
// f(char)
f(65);
// ???
f((char)65);
// f(char)
f((unsigned char)65);
// f(unsigned char)
}
Một số vấn đề trong lập trình
21
22. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
Sự nhập nhằng, mơ hồ
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
int f(int nX, int nY) { return nX + nY; }
int f(int nX, int& nY) { return nX + nY; }
void main()
{
int nA = 1, nB = 2;
int nResult1 = f(nA, 2);
// f(int, int)
int nResult2 = f(nA, nB);
// ???
}
Một số vấn đề trong lập trình
22
23. 1 2 3 4 5 6 7 8 Vấn đề về các hàm trùng tên
VC
Sự nhập nhằng, mơ hồ
BB
Ví dụ
1
2
3
4
5
6
7
8
int f(int nX) { return nX * nX; }
int f(int nX, int nY = 1) { return nX * nY; }
void main()
{
int nResult1 = f(2912, 1706);
// f(int, int)
int nResult2 = f(2912);
// ???
}
Một số vấn đề trong lập trình
23
24. 1 2 3 4 5 6 7 8 Vấn đề về giá trị mặc định của tham số hàm
VC
BB
Nhu cầu sử dụng?
Một số vấn đề trong lập trình
24
25. 1 2 3 4 5 6 7 8 Vấn đề về giá trị mặc định của tham số hàm
VC
BB
Hàm có tham số có đối số mặc định
Khái niệm
Là hàm có một hay nhiều tham số hình thức
được gán sẵn giá trị mặc định. Các tham số
này nhận giá trị mặc định đó nếu không có đối
số tương ứng được truyền vào.
Các tham số mặc định phải được dồn về tận
cùng bên phải.
Ví dụ
1
2
3
void printFraction(int nNum, int nDenom = 1);
float getFinalMark(float fTheoreticalMark, float fPracticalMark,
int nTheoreticalRatio = 1, int nPracticalRatio = 2);
Một số vấn đề trong lập trình
25
26. 1 2 3 4 5 6 7 8 Vấn đề về giá trị mặc định của tham số hàm
VC
Hàm có tham số có đối số mặc định
BB
Lưu ý
Muốn truyền đối số khác thay cho đối số mặc
định, phải truyền đối số thay cho các đối số
mặc định trước nó.
Ví dụ
1
2
3
4
5
6
float getFinalMark(float fTheoreticalMark, float fPracticalMark,
int nTheoreticalRatio = 1, int nPracticalRatio = 2);
…
// Điểm LT = 8, Điểm TH = 9, Hệ số LT = 2, Hệ số TH = 3
float fResult1 = getFinalMark(8, 9, 3);
// Sai!!!
float fResult2 = getFinalMark(8, 9, 2, 3);
// OK
Một số vấn đề trong lập trình
26
27. 1 2 3 4 5 6 7 8 Vấn đề về giá trị mặc định của tham số hàm
VC
BB
Hàm có tham số có đối số mặc định
Tình huống sử dụng
Nếu 𝑥 = 𝑎 thường xuyên xảy ra thì nên chuyển
𝑥 thành tham số có đối số mặc định là 𝑎.
Ví dụ, 𝐺𝑖𝑜𝑖𝑇𝑖𝑛ℎ = 0 (Nam), 𝑇𝑢𝑜𝑖 = 18.
Thứ tự của các tham số có đối số mặc định
Nếu 𝑥 = 𝑎 và 𝑦 = 𝑏 thường xuyên xảy ra
nhưng 𝑦 = 𝑏 thường xuyên hơn thì nên đặt
tham số mặc định 𝑦 sau 𝑥.
Ví dụ, 𝑇𝑢𝑜𝑖 = 18 xảy ra nhiều hơn 𝐺𝑖𝑜𝑖𝑇𝑖𝑛ℎ = 1
do đó nên đặt 𝑇𝑢𝑜𝑖 sau 𝐺𝑖𝑜𝑖𝑇𝑖𝑛ℎ.
Một số vấn đề trong lập trình
27
28. 1 2 3 4 5 6 7 8 Vấn đề về các hàm mã nguồn y hệt nhau
VC
Các hàm mã nguồn y hệt nhau
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
// Hàm tìm số nhỏ nhất trong 2 số nguyên kiểu int
int findMin(int x, int y)
{
return (x < y) ? x : y;
}
// Hàm tìm số nhỏ nhất trong 2 số thực kiểu float
float findMin(float x, float y)
{
return (x < y) ? x : y;
}
Một số vấn đề trong lập trình
28
29. 1 2 3 4 5 6 7 8 Vấn đề về các hàm mã nguồn y hệt nhau
VC
Khuôn mẫu hàm (function template)
BB
Cú pháp
1
template <template-type-list> function-definition
Ví dụ
1
2
3
4
5
6
7
8
9
10
// Hàm tìm số nhỏ nhất trong 2 số kiểu T bất kỳ
template <class T>
T findMin(T x, T y)
{
return (x < y) ? x : y;
}
void main()
{
int nMin = findMin<int>(2912, 1706);
}
Một số vấn đề trong lập trình
29
30. 1 2 3 4 5 6 7 8 Vấn đề về các hàm mã nguồn y hệt nhau
VC
BB
Khuôn mẫu hàm (function template)
Lợi ích của việc sử dụng khuôn mẫu hàm
Dễ viết do chỉ cần viết hàm tổng quát nhất.
Dễ hiểu do chỉ quan tâm đến kiểu tổng quát nhất.
Có kiểu an toàn do trình biên dịch kiểm tra
kiểu lúc biên dịch chương trình.
Khi phối hợp với quá tải hàm, quá tải toán tử
hoặc con trỏ hàm ta có thể viết được các
chương trình rất hay, ngắn gọn, linh động và
có tính tiến hóa cao.
Một số vấn đề trong lập trình
30
31. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
BB
Lệnh gộp – lệnh tắt (macro)
Trong C, ngoài việc sử dụng để định nghĩa các
hằng ký hiệu thì #define còn được dùng để
định nghĩa các lệnh gộp – lệnh tắt (macro).
Cú pháp
1
#define name(param-list) expression
Ở mọi chỗ xuất hiện của name với lượng
tham số param-list đưa vào phù hợp sẽ
được thay thế văn bản (textual substitution)
bởi expression (tham số được thay thế
tương ứng).
Một số vấn đề trong lập trình
31
32. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
Lệnh gộp – lệnh tắt (macro)
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#define SHOW_MESSAGE(szMessage) printf(szMessage)
#define EPSILON 0.0001
#define FLOAT_EQ(u, v) (((v-EPSILON)<x) && (x<(v+EPSILON)))
void main()
{
float a = 1.234f;
float b = 2.345f;
float c = a + b;
if (FLOAT_EQ(c, 3.579))
SHOW_MESSAGE(“Equaln”);
else
SHOW_MESSAGE(“Not equaln”);
}
Một số vấn đề trong lập trình
32
33. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
Hàm nội tuyến (inline function)
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define PI 3.14159f
float addPi(float s)
{
return s + PI;
}
void main()
{
float s1 = 0, s2 = 0;
for (int i = 1; i <= 100000; i++)
s1 = s1 + PI;
for (int j = 1; j <= 100000; j++)
s2 = addPI(s2);
// Cách 1
// ~0,7 giây
// Cách 2
// ~1,4 giây
}
Một số vấn đề trong lập trình
33
34. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
BB
Hàm nội tuyến (inline function)
Nhận xét
Sử dụng hàm giúp chương trình dễ hiểu
nhưng lại tốn chi phí cho lời gọi hàm.
Khắc phục
Trong C++, sử dụng hàm nội tuyến bằng cách
đặt từ khóa inline trước prototype của hàm.
1
inline float addPi(float s) { return s + PI; }
Trình biên dịch thực hiện tương tự như macro
bằng cách sao chép (triển khai nội tuyến)
thân hàm đến bất cứ nơi nào hàm được gọi.
Một số vấn đề trong lập trình
34
35. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
BB
Hàm nội tuyến (inline function)
Một số đặc điểm và lưu ý
Giảm thời gian thực hiện hàm (gọi và kết thúc).
Giảm không gian bộ nhớ do các hàm con
chiếm dụng khi hàm được gọi.
Không cho phép các hàm nội tuyến đệ quy.
Phần lớn không cho phép thực hiện nội tuyến
các hàm sử dụng vòng lặp while.
Chỉ inline các hàm nhỏ, inline các hàm lớn
sẽ gây phản tác dụng (bộ nhớ cho hàm
inline chiếm giữ sẽ lâu giải phóng hơn).
Một số vấn đề trong lập trình
35
36. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
BB
So sánh giữa macro và hàm nội tuyến
macro không kiểm tra kiểu dữ liệu và kiểm tra
các tham số có định dạng đúng hay không mà chỉ
thực hiện thay thế văn bản (textual substitution)
nên có thể dẫn đến các hiệu ứng phụ và sự không
hiệu quả nằm ngoài dự tính do việc đánh giá lại
các tham số và trật tự tính toán.
Lỗi biên dịch trong các macro thường rất khó hiểu
vì lỗi nằm trong phần mã đã khai triển, chứ không
phải phần mã do lập trình viên viết.
1
2
#define SQRT(X) X*X
// SQRT(1+2) 1+2*1+2 = 5!!!
#define SQRT(X) (X)*(X) // SQRT(1+2) (1+2)*(1+2) = 9
Một số vấn đề trong lập trình
36
37. 1 2 3 4 5 6 7 8 Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)
VC
BB
So sánh giữa macro và hàm nội tuyến
Khó biểu diễn các cấu trúc phức tạp bằng macro
trong khi đó hàm nội tuyến sử dụng cấu trúc
thông thường và có thể được chuyển hoặc
bỏ chế độ nội tuyến một cách dễ dàng.
macro không cho phép triển khai đệ qui trong khi
đó một số trình biên dịch cho phép triển khai đệ
qui đối với hàm nội tuyến.
Bjarne Stroustrup (cha đẻ của C++) nhấn mạnh
rằng nên hạn chế sử dụng macro mỗi khi có thể
tránh được và nên sử dụng các hàm nội tuyến.
Một số vấn đề trong lập trình
37
38. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Nhu cầu
BB
Trong một số trường hợp, các hàm hay các
phương thức không thể biết trước số lượng các
tham số ngay tại thời điểm biên dịch mà chỉ biết
vào lúc chạy chương trình (chẳng hạn các hàm
printf() và scanf() của C/C++).
Ví dụ
1
2
3
4
5
6
…
scanf(“%d”, &a);
// Nhận 2 đối số
scanf(“%d”, &b, &c);
// Nhận 3 đối số
printf(“%d + %d = ”, a, b);
// Nhận 3 đối số
printf(“%dn”, a + b);
// Nhận 2 đối số
…
Một số vấn đề trong lập trình
38
39. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Khai báo tham số …
BB
Cú pháp
1
2
3
4
return-type func-name(known-param-list, …)
{
// Các câu lệnh
}
Lưu ý
Hàm có số lượng tham số không biết trước và
thường cùng kiểu (không được là char,
unsigned char, float).
Phải có ít nhất 1 tham số biết trước.
Tham số … đặt ở cuối cùng.
Một số vấn đề trong lập trình
39
40. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Khai báo tham số …
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void printSum1(char* szMessage, int n, ...)
{
// Xuất n số nguyên kèm thông báo szMessage
}
void printSum2(char* szMessage, ...)
{
// Xuất các số nguyên kèm thông báo szMessage
}
int sum(int a, ...)
{
// Tính và trả về tổng các số nguyên bắt đầu là a
}
Một số vấn đề trong lập trình
40
41. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
BB
Truy xuất danh sách tham số …
Sử dụng kiểu và các macro sau (stdarg.h)
va_list: kiểu dữ liệu chứa các tham số có trong
…
va_start(va_list ap, lastfix): macro thiết
lập ap chỉ đến tham số đầu tiên trong … với
lastfix là tên tham số cố định cuối cùng.
type va_arg(va_list ap, type): macro trả về
tham số có kiểu type tiếp theo.
va_end(va_list ap): macro giúp cho hàm trả
về giá trị một cách “bình thường”.
Một số vấn đề trong lập trình
41
42. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Truy xuất danh sách tham số …
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdarg.h>
void printSum1(char* szMessage, int n, ...)
{
va_list ap;
va_start(ap, n);
// Đối số đã biết cuối cùng
int nValue, nSum = 0;
for (int i = 0; i < n; i++)
{
nValue = va_arg(ap, int);
nSum = nSum + nValue;
}
va_end(ap);
printf(“%s %d”, szMessage, nSum);
}
Một số vấn đề trong lập trình
42
43. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Truy xuất danh sách tham số …
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdarg.h>
void printSum2(char* szMessage, ...)
{
va_list ap;
va_start(ap, szMessage); // Đối số đã biết cuối cùng
int nValue, nSum = 0;
while ((nValue = va_arg(ap, int)) != 0)
{
nSum = nSum + nValue;
}
va_end(ap);
printf(“%s %d”, szMessage, nSum);
}
Một số vấn đề trong lập trình
43
44. 1 2 3 4 5 6 7 8 Vấn đề về hàm có số lượng tham số không biết trước
VC
Truy xuất danh sách tham số …
BB
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdarg.h>
int sum(int a, …)
{
va_list ap;
va_start(ap, a);
// Đối số đã biết cuối cùng
int nValue, nSum = a;
while ((nValue = va_arg(ap, int)) != 0)
{
nSum = nSum + nValue;
}
va_end(ap);
return nSum;
}
Một số vấn đề trong lập trình
44
45. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
BB
Ví dụ
Một số vấn đề trong lập trình
45
46. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Nạp chồng toán tử
BB
Cách thực hiện
Nạp chồng toán tử (operator overloading)
bằng cách viết thêm hàm toán tử mới.
Cú pháp
1
2
3
4
return-type operator#(param-list)
{
// Các thao tác cần thực hiện…
}
# là toán tử (trừ . :: * ?)
param-list (danh sách tham số) phụ thuộc
vào toán tử được nạp chồng.
Một số vấn đề trong lập trình
46
47. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
BB
Một số lưu ý
Toán tử 1 ngôi (chỉ có một toán hạng)
Bao gồm: tăng (++), giảm (--), đảo dấu (-)
Có thể thay đổi toán hạng.
Có thể trả kết quả về cho phép toán tiếp theo.
Toán tử 2 ngôi (gồm 2 toán hạng)
Bao gồm: gán (=), số học (+, -, *, /, %), quan
hệ (<, <=, >, >=, !=, ==), luận lý (&&, ||, !)
Có thể thay đổi toán hạng vế trái (toán tử gộp).
Có thể trả kết quả về cho phép toán tiếp theo.
Một số vấn đề trong lập trình
47
48. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
BB
Một số lưu ý
Ưu điểm
Cho phép thực hiện trên kiểu dữ liệu do
người lập trình tự định nghĩa.
Khuyết điểm
Không thể tạo toán tử mới.
Không thể định nghĩa lại toán tử trên kiểu
dữ liệu cơ sở.
Không thể thay đổi số ngôi của toán tử
(số lượng toán hạng tham gia) của toán tử.
Không thể thay đổi độ ưu tiên của toán tử.
Một số vấn đề trong lập trình
48
49. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử +
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct { int m_nNum, m_nDenom; } SFraction;
SFraction operator+(SFraction fracL, SFraction fracR)
{
SFraction fracResult;
fracResult.m_nNum = fracL.m_nNum * fracR.m_nDenom
+ fracR.m_nNum * fracL.m_nDenom;
fracResult.m_nDenom = fracL.m_nDenom * fracR.m_nDenom;
return fracResult;
}
…
SFraction frac1 = {1, 2}, frac2 = {3, 4}, frac3 = {5, 6};
SFraction frac4 = frac1 + frac2 + frac3;
// OK
Một số vấn đề trong lập trình
49
50. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử +
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct { int m_nNum, m_nDenom; } SFraction;
void operator+(SFraction& fracL, SFraction fracR)
{
SFraction fracResult;
fracResult.m_nNum = fracL.m_nNum * fracR.m_nDenom
+ fracR.m_nNum * fracL.m_nDenom;
fracResult.m_nDenom = fracL.m_nDenom * fracR.m_nDenom;
fracL = fracResult;
}
…
SFraction frac1 = {1, 2}, frac2 = {3, 4}, frac3 = {5, 6};
SFraction frac4 = frac1 + frac2 + frac3;
// Lỗi
Một số vấn đề trong lập trình
50
51. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử +
1
2
3
4
5
6
7
8
9
10
11
typedef struct { int m_nNum, m_nDenom; } SFraction;
SFraction operator+(SFraction frac, int n)
{
frac.m_nNum = frac.m_nNum + n * frac.m_nDenom;
return frac;
}
…
SFraction frac1 = {2912, 1706};
SFraction frac2 = frac1 + 369;
// OK
SFraction frac3 = 369 + frac1;
// Lỗi sai thứ tự toán hạng
Một số vấn đề trong lập trình
51
52. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử ==
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct { int m_nNum, m_nDenom; } SFraction;
int operator==(SFraction fracL, SFraction fracR)
{
return (fracL.m_nNum * fracR.m_nDenom
== fracR.m_nNum * fracL.m_nDenom);
}
…
SFraction frac1 = {1, 2}, frac2 = {2, 4};
if (frac1 == frac2)
cout << “Two fractions are equal.” << endl;
else
cout << “Two fractions are not equal.” << endl;
Một số vấn đề trong lập trình
52
53. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử ++ (trước)
1
2
3
4
5
6
7
8
9
10
11
typedef struct { int m_nNum, m_nDenom; } SFraction;
SFraction operator++(SFraction& frac)
{
frac.m_nNum = frac.m_nNum + frac.m_nDenom;
return frac;
}
…
SFraction frac1 = {1, 2}, frac2 = {1, 2};
SFraction frac3 = ++frac1;
SFraction frac4 = frac2++;
// OK nhưng có cảnh báo
Một số vấn đề trong lập trình
53
54. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử ++ (sau)
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct { int m_nNum, m_nDenom; } SFraction;
SFraction operator++(SFraction& frac, int nNotUsed)
{
SFraction fracResult = frac;
frac.m_nNum = frac.m_nNum + frac.m_nDenom;
return fracResult;
}
…
SFraction frac1 = {1, 2}, frac2 = {1, 2};
SFraction frac3 = ++frac1;
// Gọi toán tử ++ trước
SFraction frac4 = frac2++;
// Gọi toán tử ++ sau
Một số vấn đề trong lập trình
54
55. 1 2 3 4 5 6 7 8 Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới
VC
Ví dụ minh họa
BB
Nạp chồng toán tử - (đảo dấu)
1
2
3
4
5
6
7
8
9
10
typedef struct { int m_nNum, m_nDenom; } SFraction;
SFraction operator-(SFraction& frac)
{
frac.m_nNum = -frac.m_nNum;
return frac;
}
…
SFraction frac1 = {1, 2};
SFraction frac2 = -frac1;
Một số vấn đề trong lập trình
55
56. 1 2 3 4 5 6 7 8 Nhu cầu về gia tăng tính tái sử dụng của mã nguồn
VC
BB
Thảo luận
Một số vấn đề trong lập trình
56
57. 1 2 3 4 5 6
VC
BB
Một số thuật ngữ
ambiguous: tính nhập nhằng, mơ hồ.
default parameter: tham số mặc định.
duplicate code: trùng mã.
function overloading: nạp chồng hàm.
function pointer: con trỏ hàm.
function template: khuôn mẫu hàm.
operator overloading: nạp chồng toán tử.
Một số vấn đề trong lập trình
57
58. 1 2 3 4 5 6
VC
BB
Tài liệu tham khảo
ThS. Đặng Bình Phương, Bài giảng KTLT
Hàm nâng cao (phần 1 và phần 2)
[Primer] Chapter 7 – Functions: C++’s
Programming Modules
Pointers to Functions (trang 327-331)
[Primer] Chapter 8 – Adventures In Functions
Default Arguments (trang 362-365)
Function Overloading (trang 365-370)
Function Templates (trang 370-388)
Một số vấn đề trong lập trình
58
59. 1 2 3 4 5 6
VC
BB
Bài tập 1.1
Viết 3 phiên bản hàm sau:
Viết hàm tìm số nhỏ nhất trong một mảng a
gồm n số nguyên kiểu int cho trước.
Viết hàm tìm số nhỏ nhất trong một mảng a
gồm n phần tử có kiểu bất kỳ cho trước
(gợi ý sử dụng khuôn mẫu hàm – template).
Viết hàm tìm số “tốt nhất” (theo một tiêu chí
nào đó) trong một mảng a gồm n phần tử
có kiểu bất kỳ cho trước (gợi ý sử dụng khuôn
mẫu hàm kết hợp với con trỏ hàm).
Một số vấn đề trong lập trình
59
60. 1 2 3 4 5 6
VC
BB
Bài tập 1.2
Viết 3 phiên bản hàm sau:
Viết hàm sắp xếp tăng dần một mảng a
gồm n số nguyên kiểu int cho trước.
Viết hàm sắp xếp tăng dần một mảng a
gồm n phần tử có kiểu bất kỳ cho trước
(gợi ý sử dụng khuôn mẫu hàm – template).
Viết hàm sắp xếp mảng a gồm n phần tử có
kiểu bất kỳ cho trước theo một tiêu chí sắp
xếp được xác định lúc gọi hàm (gợi ý sử dụng
khuôn mẫu hàm kết hợp với con trỏ hàm).
Một số vấn đề trong lập trình
60
61. 1 2 3 4 5 6
VC
BB
Bài tập 1.3
Viết các hàm toán tử thao tác trên kiểu phân số
(SFraction):
Toán tử nhập xuất: >>, <<
Toán tử tăng giảm (trước và sau): ++, - Toán tử đảo dấu: Toán tử nghịch đảo: ~
Toán tử tính toán: +, -, *, /, +=, -=, *=, /=
Toán tử so sánh: >, >=, <, <=, ==, !=
(lưu ý cho phép tính toán/so sánh giữa 2
phân số và giữa phân số với số nguyên).
Một số vấn đề trong lập trình
61