Download - Pplthdt c01 mot_sovandetronglaptrinh_v13.09a
trueclass
cout
C++operator
catch
virtual throw
try
friend bool
cinnew
inline private OOP
deleteusing falseSTL
public
1
T h S . Đ ặ n g B ì n h P h ư ơ n gd b p h u o n g @ f i t . h c m u s . e d u . v n
VCBB© 13.09a
this
Bộ môn Công nghệ phần mềm
Khoa Công nghệ thông tin
Trường Đại học Khoa học Tự nhiên
PP LT HƯỚNG ĐỐI TƯỢNG
MỘT SỐ VẤN ĐỀ
TRONG LẬP TRÌNH
2
VC
BB
2 3 4 5 6 7 81
Nội dung
Một số vấn đề trong lập trình
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;
3
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Một số ví dụ
Hàm tìm số lớn nhất trong mảng
Một số vấn đề trong lập trình
1234567891011121314
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];
}
4
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Một số ví dụ
Hàm tìm số lớn nhất trong mảng
Một số vấn đề trong lập trình
1234567891011121314
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];
}
5
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Một số ví dụ
Hàm tìm số lớn nhất trong mảng
Một số vấn đề trong lập trình
1234567891011121314
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];
}
6
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
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ỏ đó.
Một số vấn đề trong lập trình
……
0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17
pfn int add(int, int)
11 00 00 00
7
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Khai báo con trỏ hàm
Khai báo tường minh
Ví dụ
Một số vấn đề trong lập trình
int (*pfn1)(int);
void(*pfn2)(int, int);
char(*pfn3)(char* []);
void(*pfn4)();
1234567
return-type (*var-name)(param-type-list);1
8
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Khai báo con trỏ hàm
Khai báo không tường minh
Ví dụ
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
123456
12
typedef return-type (*func-ptr-type-name)(param-type-list);
func-ptr-type-name func-ptr-var-name;
Một số vấn đề trong lập trình
9
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Gán giá trị cho con trỏ hàm
Cú pháp
Ví dụ
Một số vấn đề trong lập trình
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()
123456
12
func-ptr-var-name = &func-name; // Dạng chính quy
func-ptr-var-name = func-name; // Dạng ngắn gọn
10
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
So sánh con trỏ hàm
Ví dụ
Một số vấn đề trong lập trình
123456789101112
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!”);
11
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Gọi hàm sử dụng con trỏ hàm
Cú pháp
Ví dụ
Một số vấn đề trong lập trình
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
123456
12
(*func-ptr-var-name)(arg-list); // Dạng chính quy
func-ptr-var-name(arg-list); // Dạng ngắn gọn
12
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Truyền đối số là con trỏ hàm
Ví dụ
Một số vấn đề trong lập trình
12345678910111213
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);
}
13
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Trả về con trỏ hàm
Ví dụ (khai báo tường minh)
Một số vấn đề trong lập trình
12345678910111213
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);
14
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Trả về con trỏ hàm
Ví dụ (khai báo không tường minh)
Một số vấn đề trong lập trình
1234567891011121314
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);
15
VC
BB
Sự tổng quát hóa trong lập trình2 3 4 5 6 7 81
Mảng con trỏ hàm
Ví dụ
Một số vấn đề trong lập trình
1234567891011121314
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));
}
16
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
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ụ
Một số vấn đề trong lập trình
12345678
// 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);
17
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
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).
Một số vấn đề trong lập trình
12345
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
18
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Một số lưu ý
Nguyên mẫu hàm (prototype) sau khi bỏ tên
tham số phải khác nhau.
Một số vấn đề trong lập trình
1234567
// 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);
19
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Một số lưu ý
Sử dụng con trỏ hàm để lấy địa chỉ của các hàm
cùng tên.
Một số vấn đề trong lập trình
123456789101112
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) { /* … */ }
20
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Sự nhập nhằng, mơ hồ
Ví dụ
Một số vấn đề trong lập trình
123456789101112
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); // ???
}
21
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Sự nhập nhằng, mơ hồ
Ví dụ
Một số vấn đề trong lập trình
12345678910111213
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)
}
22
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Sự nhập nhằng, mơ hồ
Ví dụ
Một số vấn đề trong lập trình
12345678910
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); // ???
}
23
VC
BB
Vấn đề về các hàm trùng tên2 3 4 5 6 7 81
Sự nhập nhằng, mơ hồ
Ví dụ
Một số vấn đề trong lập trình
12345678
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); // ???
}
24
VC
BB
Vấn đề về giá trị mặc định của tham số hàm2 3 4 5 6 7 81
Nhu cầu sử dụng?
Một số vấn đề trong lập trình
25
VC
BB
Vấn đề về giá trị mặc định của tham số hàm2 3 4 5 6 7 81
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ụ
Một số vấn đề trong lập trình
123
void printFraction(int nNum, int nDenom = 1);
float getFinalMark(float fTheoreticalMark, float fPracticalMark,
int nTheoreticalRatio = 1, int nPracticalRatio = 2);
26
VC
BB
Vấn đề về giá trị mặc định của tham số hàm2 3 4 5 6 7 81
Hàm có tham số có đối số mặc định
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ụ
Một số vấn đề trong lập trình
123456
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
27
VC
BB
Vấn đề về giá trị mặc định của tham số hàm2 3 4 5 6 7 81
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𝐺𝑖𝑜𝑖𝑇𝑖𝑛ℎ = 1do đó nên đặt 𝑇𝑢𝑜𝑖 sau 𝐺𝑖𝑜𝑖𝑇𝑖𝑛ℎ.
Một số vấn đề trong lập trình
28
VC
BB
Vấn đề về các hàm mã nguồn y hệt nhau2 3 4 5 6 7 81
Các hàm mã nguồn y hệt nhau
Ví dụ
Một số vấn đề trong lập trình
1234567891011
// 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;
}
29
VC
BB
Vấn đề về các hàm mã nguồn y hệt nhau2 3 4 5 6 7 81
Khuôn mẫu hàm (function template)
Cú pháp
Ví dụ
Một số vấn đề trong lập trình
1 template <template-type-list> function-definition
12345678910
// 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);
}
30
VC
BB
Vấn đề về các hàm mã nguồn y hệt nhau2 3 4 5 6 7 81
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
31
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
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
Ở 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
1 #define name(param-list) expression
32
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
Lệnh gộp – lệnh tắt (macro)
Ví dụ
Một số vấn đề trong lập trình
1234567891011121314
#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(“Equal\n”);
else
SHOW_MESSAGE(“Not equal\n”);
}
33
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
Hàm nội tuyến (inline function)
Ví dụ
Một số vấn đề trong lập trình
1234567891011121314
#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++) // Cách 1
s1 = s1 + PI; // ~0,7 giây
for (int j = 1; j <= 100000; j++) // Cách 2
s2 = addPI(s2); // ~1,4 giây
}
34
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
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.
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
1 inline float addPi(float s) { return s + PI; }
35
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
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
36
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
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.
Một số vấn đề trong lập trình
12
#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
37
VC
BB
Vấn đề về lệnh gộp (macro) và hàm nội tuyến (inline function)2 3 4 5 6 7 81
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
38
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Nhu cầu
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ụ
Một số vấn đề trong lập trình
123456
…
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(“%d\n”, a + b); // Nhận 2 đối số
…
39
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Khai báo tham số …
Cú pháp
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
1234
return-type func-name(known-param-list, …)
{
// Các câu lệnh
}
40
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Khai báo tham số …
Ví dụ
Một số vấn đề trong lập trình
1234567891011121314
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
}
41
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
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
42
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Truy xuất danh sách tham số …
Ví dụ
Một số vấn đề trong lập trình
1234567891011121314
#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);
}
43
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Truy xuất danh sách tham số …
Ví dụ
Một số vấn đề trong lập trình
12345678910111213
#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);
}
44
VC
BB
Vấn đề về hàm có số lượng tham số không biết trước2 3 4 5 6 7 81
Truy xuất danh sách tham số …
Ví dụ
Một số vấn đề trong lập trình
12345678910111213
#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;
}
45
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ
Một số vấn đề trong lập trình
46
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Nạp chồng toán tử
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
# 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
1234
return-type operator#(param-list)
{
// Các thao tác cần thực hiện…
}
47
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
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
48
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
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
49
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử +
Một số vấn đề trong lập trình
12345678910111213
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
50
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử +
Một số vấn đề trong lập trình
12345678910111213
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
51
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử +
Một số vấn đề trong lập trình
1234567891011
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
52
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử ==
Một số vấn đề trong lập trình
12345678910111213
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;
53
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử ++ (trước)
Một số vấn đề trong lập trình
1234567891011
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
54
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử ++ (sau)
Một số vấn đề trong lập trình
123456789101112
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
55
VC
BB
Nhu cầu về kí hiệu phép toán cho kiểu dữ liệu mới2 3 4 5 6 7 81
Ví dụ minh họa
Nạp chồng toán tử - (đảo dấu)
Một số vấn đề trong lập trình
12345678910
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;
56
VC
BB
Nhu cầu về gia tăng tính tái sử dụng của mã nguồn2 3 4 5 6 7 81
Thảo luận
Một số vấn đề trong lập trình
57
VC
BB
1 2 3 4 5 6
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
58
VC
BB
1 2 3 4 5 6
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
59
VC
BB
1 2 3 4 5 6
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 agồ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 agồ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
60
VC
BB
1 2 3 4 5 6
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 agồ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 agồ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
61
VC
BB
1 2 3 4 5 6
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