Tải bản đầy đủ (.pdf) (29 trang)

Nguyễn mẫu hàm con trỏ hàm C++ nâng cao

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (146.89 KB, 29 trang )


MỘT SỐ KIẾN THỨC NÂNG CAO TRONG
C/C++
I.TEMPLATE FUNCTION(KHUÔN MẪU HÀM)
Đặt vấn đề rằng ta có đoạn hàm để CT sau:
#include <iostream>
#include <conio.h>
using namespace std;
void HoanVi(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
}

void main()
{
int x = 1, y = 2;
HoanVi(x, y);
cout<<"x = "<<x<<", y = "<<y;
getch();
}
Đây là 1 đoạn CT hoàn toàn đúng.Nhưng giả sử chúng ta muốn bảo trì CT rằng
chúng ta muốn hoán vị
2 số thực kiễu float. Với hàm xây dựng sẵn như trên việc chúng ta truyền 2 biến
kiểu float vào cho hàm sẽ bị lỗi biên dịch. Cách khác là chúng ta tạo ra riêng 1 hàm
để hoán vị 2 kiểu khác chúng ta muốn nhưng cách này ko khả quan. Nếu 1 chương
trình muốn hoán vị tới 10 kiểu khác nhau không lẽ ta phải tạo 10 hàm HoanVi. =>
Code dài nhìn thiếu chuyên nghiệp, khó khăn trong việc bảo trì, nâng cấp.
Rất maymắn C++ cho ra đời template function(khuôn mẫu hàm) để ta có thể tham
số hóa kiễu dữ liệu được truyền vào 1 hàm.


Một hàm được tham số hóa là coi như là 1 hàm tổng quát xài chung cho mọi kiểu
dữ liệu từ cơ sơ cho đến kiễu dữ liệu định nghĩa.Chúng ta chỉ cần cài đặt hàm 1 lần
nhưng sử dụng được với nhiều kiểu dữ liệu .
CÁCH THỰC HIỆN.
-Đối với những hàm chúng ta xác định rằng hàm đó sẽ có thể dùng chung đối với
mọi kiễu dữ liệu thì chúng ta sẽ đặt dòng template<class T> hoặc
template<typename T>, chúng giống nhau hoàn trong hai trường hợp trên tuy
nhiên có một số chỗ chúng ta buộc phải dùng class hoặc buộc phải dùng
typename. Ở đây ta sẽ chỉ dùng class T (theo tôi thấy được sử dụng rộng rãi hơn
^^)
- Sau khi đã khai báo template cho hàm thì ta sẽ thay đổi kiễu dữ liệu trong hàm
mà ta cho rằng chúng sẽlà 1 kiễu dữ liệu không biết trước bởi vì đối với hàm có
xây dựng template thì trình compile sẽ thực sự những biến đó thuộc kiễu dữ liệu
nào chỉ khi ta truyền biến vào hàm.
VD ta sửa lại đoạn HoanVi trên cho nó có tính tham số hóa.
template<class T> // T là tên kiễu dữ liệu coi như là chưa biết trước. Các bạn có
//thể đặt tên khác tùy ý.
HoanVi(T &x, T &y) // Tôi thay kiểu int thành 1 kiểu T
{
T temp = x; // Thay int temp = T temp để có thể đồng nhất với kiểu của x,y
x = y; y = temp; }

Như vậy ở bất kỳ 1 hàm nào nếu chúng ta truyền 2 số float thì khi gọi hàm trình
compile sẽ "hiểu" rằng
T là float, nếu ta truyền vào 2 chuỗi string compile sẽ biến T thành string, thậm chí
cả 2 HocSinh, NhanVien,
Very good. ^_^
2 cách gọi hàm có dùng template .
C1: Gọi tường minh TenHam<kiểu dữ liệu truyền vào>(//Tham số nếu có)
C2: Gọi không tường mình thì như chúng ta gọi bình thường ^^ là như thế này

TenHam(//Tham số nếu có)
Theo kinh nghiệm của tôi thì trong 1 số trường hợp trình compile yêu cầu bạn phải
gọi tường minh đó ^^.
CHÚ Ý 1: Chỉ khi ta xài các phương thức nhập xuất(cout và cin) của C++ thì mới
nên dùng template function
vì C++ việc nhập xuất không cần quan tâm đến đặc tả % của dữ liệu.
VD đoạn dùng template function theo nhập xuất của thư viện <stdio.h> (standard
input output của C )
như sau vẫn bị lỗi vì "vướng" phải đặc tả %.
#include <stdio.h>
#include <conio.h>
template<class T>
void Sum(T x, T y)
{
printf("x + y = %d", x + y); // ? tại sao lại là %d khi kiểu dữ liệu T là kiểu chưa
//biết trước! }
void main()
{
double x = 1, y = 2;
Sum(x,y); // x + y = 0 . Một kết quả ko như mong muốn
getch();
}

VD đoạn dùng template function theo thư viện <iostream> (input output stream
của C++)
#include <iostream>
#include <conio.h>
using namespace std;
template<class T>
void Sum(T x, T y)

{
cout<<"x + y = "<<x+y; // OK. Hoàn hảo
}
void main()
{
double x = 1, y = 2;
Sum(x,y); // x + y = 3
getch();
}
- Chúng ta cũng có thể khai báo template với 2 kiểu dữ liệu không biết trước trình
compile sẽ xác định kiểu
dữ liệu theo thứ tự biến truyền vào hàm
VD:
#include <iostream>
#include <conio.h>
using namespace std;
template<class T, class Y> // 2 kiểu T và Y
void Nhap2Bien(T &x, Y &y)
{
cout<<"Nhap x:";
cin>>x;
cout<<"Nhap y:";
cin>>y;
}
void main()
{
int x;
char c;
Nhap2Bien(x, c); // T sẽ có kiểu int, Y có kiểu char
cout<<"x = "<<x<<endl;

cout<<"c = "<<c<<endl;
long a;
double b;
Nhap2Bien(a, b); // T sẽ có kiểu long, Y có kiểu double
getch();
}
CHÚ Ý 2: Chúng ta đã biết rằng hàm có cài đặt template function thí mọi kiễu dữ
liệu truyền vào sẽ được tham số
hóa nhưng ta cũng nên quản lý việc truyền kiểu dữ liệu 1 cách cẩn thận.
Ví dụ về việc sử dụng tham số hóa không hợp lý.
#include <iostream>
#include <conio.h>
#include <string> // Thư viện string của C++
using namespace std;
template<class T, class Y> // 2 kiểu T và Y
void Nhap2Bien(T &x, Y &y)
{
cout<<"Nhap x:";
cin>>x;
cout<<"Nhap y:";
cin>>y;
}
void main()
{
int x;
string str;
Nhap2Bien(x, str); // Y có kiểu string. Nhưng nhìn đoạn hàm trên.Chuỗi mà
//chúng ta lại dùng cin???
// Đáng lẽ phải fflush(stdin); rồi getline(cin, y); chứ :)
cout<<"x = "<<x<<endl;

cout<<"str = "<<str<<endl;
getch();
}

II. CLASS TEMPLATE( KHUÔN MẪU LỚP)
1.Tham số hóa cho lớp
- Tương tự như khuôn mẫu hàm, khuôn mẫu lớp có cú pháp sử dụng như sau.
VD1:
template<class T> // T hay tên gì khác tùy bạn đặt
class TenLop
{
// Nội dung của lớp
};

Khi định nghĩa các phương thức của lớp được tham số hóa kiễu dữ liệu như trên ta
sử dụng
cú pháp như sau:
template<class T>
KieuTraVe TenLop<T>::TenHam(//các tham số nếu có)
{
// Nội dung của phương thức
}
Khác với việc sử dụng khuôn mẫu hàm đối với khuôn mẫu lớp chúng ta phải nếu
tường minh kiễu dữ liệu cần sử dụng. Ví dụ trong trường hợp muốn sử dụng lớp
Mang1Chieu có kiểu int ta phải viết như sau:
Mang1Chieu<int> tendoituong;
2.Các tham số không kiểu
Chúng ta cũng có thể khai bao kiểu dữ liệu mặc định trong template
VD 1:
template<class T, int size)

class MyArray
{
private:
T a[size];
public:
// Các phương thức
};
void main()
{
MyArray<int,50> a; // Mảng a có kiểu dữ liệu int với tối đa 50 phần tử
MyArray<HOCSINH, 100> h; // Mảng h có kiểu dữ liệu HOCSINH ( Gia sử ta
//đã xây dựng class HOCSINH)// với tối đa 100 em
}
Đặc biệt hơn ta có thể khai báo vừa kiễu dữ liệu biết trước và cả giá trị mặc định
trong template như VD2 sau:
VD 2:
template<class T = int, int size = 50>
class MyArray
{
private:
T arr[size];
public:
// Các phương thức
};
Ở hàm main ta chỉ việc khai báo MyArray<> tendoituong; thì trình compile cũng
tự xác định rằng Mảng tendoituong có kiểu dữ liệu int với tối đa 50 phần tử.
3.Kế thừa các lớp tham số hóa
Khi 1 class dẫn xuất kế thừa 1 class cơ sở có sử dụng tham số hóa thì cú pháp như
sau:
template<class T>

class CoSo
{
//Nội dung của lớp cơ sở
public :
void NhapCoSo()
{
// Nội dung phương thức Nhap
}
};
template<class T>
class DanXuat:public CoSo<T>
{
// Nội dung của lớp dẫn xuất
void NhapDanXuat()
{
CoSo<T>::NhapCoSo(); // Gọi phương thức NhapCoSo của class CoSo
}
};
VD:
#include <iostream>
#include <conio.h>
#include <string> // Thư viện string của C++
using namespace std;
template<class T>
class CoSo
{
//Nội dung của lớp cơ sở
public :
void NhapCoSo()
{

cout<<"OK";
}
};
template<class T>
class DanXuat:public CoSo<T>
{
// Nội dung của lớp dẫn xuất
public:
void NhapDanXuat()
{
CoSo<T>::NhapCoSo(); // Gọi phương thức NhapCoSo của class CoSo
a = 69;
cout<<endl<<a;
}
T a;
};

void main()
{
DanXuat<int> x;
x.NhapDanXuat();
getch();
}
CHÚ Ý:Cẩn thận trong trường hợp hàm NhapCoSo là hàm ảo và có sử dụng tính
chất đa xạ.
Khi đó cách thức hoạt động của hàm có thể khác với mong muốn!
III.Con trỏ hàm
Biến đều có địa chỉ trong bộ nhớ.
Vậy hàm tồn tại tức nó sẽ có địa chỉ trong bộ nhớ.
=> Sẽ có 1 loại con trỏ trỏ đến các hàm. Ta gọi là con trỏ hàm

Cú pháp:
<Kiểu trả về của hàm> (*<Tên con trỏ hàm>)(<Danh sách tham số nếu có>)
CHÚ Ý: Phần danh sách tham số ta chỉ cần quan tâm đến kiễu dữ liệu của tham số,
ko cần thiết khai tên tham số(giống khai báo hàm).
TRỎ ĐẾN HÀM: Chỉ có thể trỏ đến các hàm nếu con trỏ hàm phù hợp kiểu trả về,
các ds tham số.
Cách 1:
<Tên con trỏ> = <Tên hàm>;
Cách 2:
<Tên con trỏ> = &<Tên hàm>;
Sau khi trỏ thay vì gọi hàm bằng cách thông thường ta có thể thay tên hàm bằng
tên con trỏ .
VD1:
void Nhap2SoNguyen(int &x, int &y)
{
printf("Nhap x,y:\n");
scanf("%d%d", &x, &y);}
int TinhTong(int x, int y)
{
return x + y;
}
int TinhHieu(int x, int y)
{
return x - y;
}
void main()
{
int x, y;
void (*q)(int &, int &); //Khai báo con trỏ hàm trỏ đến các hàm không có kiểu trả
//về là với 2

tham số kiểu int truyền theo kiểu tham chiếu ( tham biến).int (*p)(int , int ); // Khai
//báo con trỏ hàm trỏ đến các hàm có kiểu trả là int với 2 tham số kiểu
// int truyền theo kiểu tham trị.
q = TinhTong; // ERROR. Ko phù hợp để trỏ
q = Nhap2SoNguyen; // OK con trỏ hàm q đang trỏ đến hàm Nhap2SoNguyen
q = &Nhap2SoNguyen; // OK con trỏ hàm q đang trỏ đến hàm Nhap2SoNguyen
p = TinhTong; // OK Con trỏ p trỏ đến hàm TinhTong
q(x, y); <=>Nhap2SoNguyen(x,y);
printf("x + y = %d", p(x,y));
}

VD2 là sự kết hợp khuôn mẫu hàm và con trỏ hàm để tạo nên tình tùy biến mã
nguồn cao nhất.
template<class T>
void HoanVi(T* x, T* y)
{
T temp = *x;
*x = *y;
*y = temp;
}

void main()
{
// Khai báo con trỏ hàm để có thể trỏ tới hàm HoanVi
float x,y;
x = 1;
y = 2;
void (*Pointer)(float*, float*);
// Có 2 cách cho con trỏ trỏ tới hàm
Pointer = HoanVi; // C1

Pointer = &HoanVi; // C2
//HoanVi(&x, &y);
Pointer(&x, &y);
}

IV. MẢNG CON TRỎ HÀM
- Là mảng lưu giữ các phần tử là biến con trỏ hàm.
CÚ PHÁP: KieuTraVe(*TenConTroHam[SoLuongphantu])(//Ds tham số nếu có)
Chú ý rằng các biến con trỏ của mảng phải cùng kiểu tra ve, DS tham số.
VD:
int (*array[2])(int , int);
array[0] = TinhTong; // Con trỏ tại vị trí 0 trong mảng trỏ đến hàm TinhTong
array[1] = TinhHieu; // Con trỏ tại vị trí 1 trong mảng trỏ đến hàm TinhHieu
V. KHUÔN MẪU HÀM, CON TRỎ HÀM TRONG VIỆC XÂY
DỰNG CHƯƠNG TRÌNH CÓ TÍNH CHẤT TÙY BIẾN MÃ NGUỒN.
-Ta đã tìm hiểu về khuôn mẫu hàm, con trỏ hàm và các công dụng của chúng. Và
công dụng lớn nhất
của chúng theo đánh giá của tôi là sự kết hợp để tạo nên mã nguồn có tính tùy biến,
ngắn gọn, dễ bảo trì.
Ví dụ rằng ta có 2 hàm sắp xếp tăng dần và giảm dần mảng 1 chiều như sau:
void SapTang(int a[], int n)
{
for(int i = 0; i < n - 1; i++)
{
for(int j = i + 1; j < n; j++)
{
if(a[j] < a[i])
{
int temp = a[j];
a[j] = a[i];

a[i] = temp;
}
}
}
}
void SapGiam(int a[], int n)
{
for(int i = 0; i < n - 1; i++)
{
for(int j = i + 1; j < n; j++)
{
if(a[j] > a[i])
{
int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
}
}
Ta thấy rằng 2 hàm này nó gần tương đương nhau khác biết chỉ trong 1 câu lệnh if.
Vậy có cách nào đểxây dưng nên chỉ 1 hàm SapTuyChon và có thể tùy cách ta gọi
hàm mà nó có thể vừa làm công việc sắp
tăng, vừa làm công việc sắp giảm được ko?
Câu trả lời rằng hãy ứng dụng con trỏ hàm như các ví dụ sau:
VD1:
//Ta tạo ra 2 hàm sosanh
//Nhắc lại về kiễu dữ liệu bool của C++:bool là kiểu dữ liệu do C++ xây dựng nên
thuận tiện trong việc kiểm tra 1 điều kiện gì đó. Nó trả ra 2 giá trị true hoặc false
bool SoSanhNho(int x, int y)

{
if(x > y)
return true;
else
return false;
}
bool SoSanhLon(int x, int y)
{
if(x < y)
return true;
else
return false;
}

void SapTuyChon(int a[], int n, bool (*p)(int, int)) // Chúy y tham số thứ 3 là 1 con
//trỏ hàm phù hợp để trỏ đến
// 2 hàm SoSanhNho va SoSanhLon
{
for(int i = 0; i < n - 1; i++)
{
for(int j = i + 1; j < n; j++)
{
if(p(a[j], a[i]) == true)
{
int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
}

}
void main()
{
int a[5] = {1, 5, 3, 2, 0}; // Khởi tạo mảng a gồm 5 phần tử
SapTuyChon(a, 5, NhoHon); // Ta truyền tên hàm NhoHon vào tham số thứ 3.
//Lúc này con trỏhàm p trong hàm SapTuyChon sẽ trỏ vào hàm NhoHon. Như vậy
câu if trong hàm tương đuong
// if(SoSanhNho(a[j], a[i]) == true) => Hàm đang thực hiện sắp xếp tăng

SapTuyChon(a, 5, LonHon); // Hàm đang thực hiện sắp giảm
}
- Qua ví dụ trên ta đã thấy việc ứng dụng con trỏ hàm để tùy biến mã nguồn. Bây
giờ ta tăng thêm tính tùy biến khi bổ sung kết hợp cả khuôn mẫu hàm như sau để
có thể tùy sửa là mảng kiểu int hay kiểu khác thật nhanh gọn ở hàm main.
template<class T>
bool SoSanhNho(T x, T y)
{
if(x > y)
return true;
else
return false;
}
template<class T>
bool SoSanhLon(T x, T y)
{
if(x < y)
return true;
else
return false;
}

template<class T>
void SapTuyChon(T a[], int n, bool (*p)(T, T)) // Chúy y tham số thứ 3 là 1 con trỏ
//hàm phù hợp để trỏ đến 2 hàm SoSanhNho va SoSanhLon
{
for(int i = 0; i < n - 1; i++)
{
for(int j = i + 1; j < n; j++)
{
if(p(a[j], a[i]) == true)
{
int temp = a[j];
a[j] = a[i];
a[i] = temp;
}
}
}
}

void main()
{
int a[5] = {1, 5, 3, 2, 0}; // Khởi tạo mảng a gồm 5 phần tử
SapTuyChon(a, 5, NhoHon);
// Ta truyền tên hàm NhoHon vào tham số thứ 3. //Lúc này con trỏ hàm p trong
hàm SapTuyChon sẽ trỏ vào hàm NhoHon. Như vậy //câu if trong hàm tương
đuong
// if(SoSanhNho(a[j], a[i]) == true) => Hàm đang thực hiện sắp xếp tăng
SapTuyChon(a, 5, LonHon); // Hàm đang thực hiện sắp giảm
}
=>Bây giờ hàm đã thực sự có tính tùy biến rất cao. Điều quan trọng là biết cách
nhận ra điểm giống

nhau của các hàm để thêm 1 tham số là con trỏ hàm và tùy biến con trỏ hàm sẽ trỏ
vào hàm nào tùy cách ta
gọi hàm.
VD2: Thực hiện tìm max, min của mảng có kiểu dữ liệu bất kỳ
template<class T>
bool SoSanhNho(T x, T y)
{
if(x < y)
return true;
else
return false;
}
template<class T>
bool SoSanhLon(T x, T y)
{
if(x > y)
return true;
ele
return false;
}
template<class T>
T MaxorMin(T a[], int n, bool (*p)(T, T))
{
T x = a[0];
for(int i = 1; i < n; i++)
{
if(p(a[i], x) == true)
{
x = a[i];
}

}
return x;
}

void main()
{
float a[10] = {1, 5, 7, 69, -13.2 , 0 ,6, 8.4 , 11.5 , 1};
printf("Max = %f", MaxorMin(a,10, LonHon)); // Tìm max và trả về là 69
printf("Max = %f", MaxorMin(a, 10, NhoHon)); //Tìm min và trả về là -13.2
getch();
}
VD3:Thực hiện tính tổng các số nguyên tố, số chính phương, số hoàn thiện.
Thử tưởng tượng rằng nếu ta chưa biết đến các tùy biến mã nguồn thì mã nguồn
chúng ta sẽ như sau:
Ở đây tôi không dùng template function được lý do là vì số nguyên tố , số hoàn hảo
ngoài truyển kiểu
int ra thì truyền kiểu khác sao được ^_< .Điều hiển nhiên nhé.
bool KiemTraNguyenTo(int x) // Kiểm tra 1 số x có phải là số nguyên tố không.
Có nhiều thuật toán để làm bài này
{
if(x < 2)
return false;
else if(x > 2)
{
if(x % 2 == 0)
return false;
else
{
for(int i = 3; i <= (int)sqrt(float(x)); i += 2)
{

if(x % i == 0)
return false;
}
}
}
return true;
}
bool KiemTraChinhPhuong(int x) // Hàm kiểm tra 1 số có phải là số chính phương
không
{
if(sqrt(float(x)) * sqrt(float(x)) == x)
return true;
else
return false;
}
bool KiemTraHoanThien(int x) // Hàm kiểm tra 1 số có phải là số hoàn thiện
không
{
int tonguocso = 0;
for(int i = x - 1; i >= 1; i )
{
if(x % i == 0)
tonguocso += i;
if(tonguocso > x)
return false;
}
if(tonguocso == x)
return true;
}
// Gio ta phải tạo ra 3 hàm tính tổng

int TongNguyenTo(int a[],int n)
{
int tong = 0;
for(int i = 0; i < n; i++)
{
if(KiemTraNguyenTo(a[i]) == true)
tong += a[i];
}

×