STDIO
Tìm kiếm gần đây

    Nội dung

    Con Trỏ Hàm Trong C/C++

    18/08/2015
    03/03/2020
    Con Trỏ Hàm Trong C/C++
    Tương tự Windows Form, trong những game được lập trình bằng C++ cũng có những Button, và để tạo ra chúng thì không đơn giản. Vì ngoài việc xử lý hình ảnh, một Button còn phải có một chức năng chuyên biệt nào đó để thực thi khi ta ấn vào. Có thể là một, hoặc một vài công việc đã định trước thông qua các hàm ta đã viết. Bài viết này sẽ giới thiệu đến các bạn một khái niệm gọi là Funtion Pointer hay Pointer to Function.

    Giới thiệu

    Bài viết này sẽ giới thiệu đến các bạn một khái niệm gọi là Funtion Pointer hay Pointer to Function, dịch nôm na là Con trỏ hàm. Nếu bạn đã từng sử dụng Windows Form để lập trình GUI (C#) thì hẳn có biết đến cách mà Windows Form tạo ra một Button và định nghĩa thao tác xử lý của button đó.

    Tương tự Windows Form, trong những game được lập trình bằng C++ cũng có những Button, và để tạo ra chúng thì không đơn giản. Vì ngoài việc xử lý hình ảnh, một Button còn phải có một chức năng chuyên biệt nào đó để thực thi khi ta ấn vào. Có thể là một, hoặc một vài công việc đã định trước thông qua các hàm ta đã viết.

    Đó chỉ là một trong những công dụng của Con trỏ hàm, hy vọng sau khi đọc bài viết này, các bạn có thể đóng góp những cách sử dụng sáng tạo của bạn về con trỏ hàm.

    Tiền đề bài viết

    Bài viết xuất phát từ ý muốn chia sẻ kiến thức. Trước đây khi tôi phát triển tựa game đầu tiên của đời mình (thực ra chỉ là đồ án một môn học), nhóm chúng tôi đã vướng đến việc xử lý giao diện người dùng với C++ và DirectX. Do kiến thức của chúng tôi chưa rộng và không có sự hướng dẫn cụ thể, chúng tôi đã mất đến gần một tuần cho nội dung này. Rất nhiều giải pháp nhưng tất cả đều không giải quyết triệt để được vấn đề. Và rồi tôi vô tình đọc được một bài viết trên một diễn đàn về lập trình, từ đó tôi mới biết về con trỏ hàm. Với bài viết này, tôi mong có thể giới thiệu đến các bạn một khái niệm (có thể mới đối với bạn), mà có thể hữu dụng với bạn sau này.

    Đối tượng hướng đến

    • Những lập trình viên đang học tập và sử dụng ngôn ngữ C/C++.
    • Chưa biết hoặc chỉ mới nghe qua về con trỏ hàm.

    Con trỏ hàm là gì?

    Con trỏ hàm là một biến lưu trữ địa chỉ của một hàm, thông qua biến đó, ta có thể gọi hàm mà nó trỏ tới. Điều này rất thuận tiện khi bạn muốn định nghĩa các chức năng khác nhau cho một nhóm các đối tượng khá giống nhau. (Như ví dụ về Button ở trên).

    Sử dụng con trỏ hàm như thế nào?

    Một con trỏ hàm có thể khởi tạo theo mẫu sau:

    <kiểu trả về> (*<tên con trỏ>)(<danh sách đối số>);

    Ví dụ về con trỏ hàm nhận vào một biến kiểu int và trả về dữ liệu kiểu void.

    void (*func)(int);

    Nhưng đây mới chỉ là khai báo, cũng giống như mọi con trỏ khác, con trỏ hàm phải được định nghĩa giá trị trước khi sử dụng, nhưng chúng ta không thể dùng từ khóa new hay malloc để cấp phát vùng nhớ cho một con trỏ hàm, vì như thế thì chẳng cách nào định nghĩa được các lệnh của nó, hơn nữa vùng nhớ của lệnh và của biến cũng khác nhau vậy chỉ còn một cách là cho nó trỏ đến một vùng nhớ lưu trữ giá trị khai báo sẵn.
    Về bản chất, máy chỉ hiểu được các lệnh mã máy với 0 và 1, nên không chỉ có biến hay con trỏ, các câu lệnh cũng có địa chỉ của riêng nó. (Chi tiết về cách lưu trữ của một chương trình, bạn có thể tham khảo tại bài viết Memory Segment :: www.stdio.vn/articles/read/25-memory-segment của tác giả Hiếu Nguyễn :: www.stdio.vn/users/index/5/hieu-nguyen).

    Cũng nhưng những con trỏ thông thường, chúng ta có thể truyền địa chỉ của một hàm vào con trỏ với cách đơn giản như sau:

    void thefunc(int a)
    {
    	// DO SOMETHING
    }
    int main()
    {
    	// ...
    	void (*func)(int);
    	func = &thefunc;
    	// ...
    }

    Và để gọi một hàm đã lưu trữ trong con trỏ hàm, ta chỉ cần gọi con trỏ với một danh sách đối số phù hợp:

    func(1); 
    // OR
    (*func)(1);

    Lưu ý là con trỏ hàm và hàm được trỏ đến phải có cùng danh sách đối số và kiểu trả về.

    Truyền hàm vào hàm

    Do con trỏ hàm được khai báo dựa theo kiểu trả về và danh sách đối số của hàm sẽ được trỏ đến nên để thuận tiện hơn trong các thao tác xử lý, ta nên định kiểu cho mỗi kiểu con trỏ hàm để nó có một cái tên nhất định (thông qua từ khóa typedef). Chẳng hạn như:

    typedef int (*Func_int)();

    Vậy là ta đã có thể sử dụng kiểu dữ liệu Func_int như một kiểu dữ liệu định nghĩa các con trỏ hàm không đối số trả về một phần tử kiểu int.

    Theo đó, ta có thể yêu cầu một đối số func_int từ một hàm, như sau:

    void foo(func_int fun, int a)
    {
    	// DO SOMETHING
    	// ...
    }

    Và thao tác truyền hàm vào hàm được thực hiện như sau:

    typedef int (*func_int)();
    
    void foo(func_int fun, int a)
    {
    	// DO SOMETHING
    	// ...
    }
    
    int doSomething()
    {
    	// DO SOMETHING
    	// ...
    }
    
    int main()
    {
    	// ...
    	foo(&doSomething, 0);
    	// ...
    }

    Lập lịch thao tác với vector các con trỏ hàm

    Con trỏ hàm xét cho cùng cũng là một con trỏ, nên ta có thể sử dụng các kiểu tổ chức dữ liệu như mảng, cây, danh sách liên kết… để lưu trữ và xử lý các hàm như những phần tử đơn thuần. Ở đây sẽ nói về cách tổ chức các con trỏ hàm với vector.

    Chúng ta cùng xét ví dụ sau, giả sử một Button của chúng ta cần xử lý lần lượt 2 hàm thefunc1 và thefunc2 sau theo thứ tự.

    int thefunc1(int a)
    {
    	return a++;
    }
    
    
    int thefunc2(int a)
    {
    	return a + 3;
    }

    Vậy trong trường hợp này, ta có thể viết lớp Button và sử dụng như sau:

    class Button
    {
    public:
    	Button()
    	{
    		func1 = &thefunc1;
    		func2 = &thefunc2;
    	}
    	~Button(){};
    	int Action(int a)
    	{
    		return (*func1)(a) + (*func2)(a);
    	}
    private:
    	int (*func1)(int);
    	int (*func2)(int);
    };
    
    
    int main()
    {
    	Button b1;
    	b1.Action(3);
    	return 0;
    }

    Ta thấy button b1 được gán vào 2 hàm xử lý bên ngoài (thefunc1 và thefunc2). Tuy nhiên không phải button nào cũng có 2 hàm xử lý, cho nên ta phải tìm ra một phương pháp tổ chức các con trỏ hàm hợp lý hơn, tổng quát hơn cho những trường hợp Button có số hàm xử lý không xác định.

    Sau khi chỉnh sửa, ta có đoạn chương trình như sau:

    #include <vector>
    
    // ...
    
    class Button
    {
    public:
    	Button(){};
    	~Button(){};
    	void Add(func f)
    	{
    		functions.push_back(f);
    	}
    	int Action(int a)
    	{
    		int result;
    		for(int i = 0; i < functions.size(); ++i)
    			result += (*functions[i])(a);
    		return result;
    	}
    private:
    	std::vector<func> functions;
    };
    
    int main()
    {
    	Button b1;
    	b1.Add(&thefunc1);
    	b1.Add(&thefunc2);
    	b1.Action(3);
    }

    Vậy là với một lớp Button như thế này, ta có thể thêm vào danh sách thao tác của mỗi đối tượng kiểu Button một số lượng hàm tùy ý, tất nhiên, các hàm này phải có cùng kiểu trả về và danh sách đối số với con trỏ hàm.

    Con trỏ hàm trong lập trình hướng đối tượng

    Trong OOP, vấn đề khởi tạo và sử dụng một con trỏ hàm có phần phức tạp hơn lập trình hướng thủ tục do có vấn đề về tầm vực của hàm và các hàm phải được gọi thông qua đối tượng của lớp.

    Nói về phần này, tôi có một ví dụ như sau:

    #include <iostream>     
    using namespace std;
    
    class Default
    {
    public:
    	int Sum(int a,int b)
    	{
    		return a + b;
    	}
    };
    
    class Function
    {
    	typedef int (Default::*func)(int, int);
    public:
    	func p;
    	Function(func fun, Default* object)
    	{
    		this->p = fun;
    		Obj = object;
    	}
    	~Function(){}
    	int Action(int a, int b)
    	{
    		return (Obj->*p)(a, b);
    	}
    private:
    	Default* Obj;
    };
    
    int main()
    {
    	Default k;
    	Function* f;
    	f = new Function(&Default::Sum, &k);
    	cout << f->Action(5, 6);
    	getchar();
    	return 0;
    }

    Những điều các bạn cần chú ý ở đây là:

    • Con trỏ kiểu Default* trong hàm tạo của Function.
    • Hàm Action của Function.

    Chức năng của con trỏ kiểu Default trong lớp Function nhằm xác định đối tượng sẽ gọi đến hàm Sum của lớp Default, ta thấy rõ việc này nhất trong hàm Action của lớp Function. Không như thông thường, hàm Sum được gọi đến bằng cách như sau:

    (Obj->*p)(a,b);

    Với p là con trỏ trỏ đến hàm Sum, và Obj là con trỏ trỏ đến đối tượng k thuộc lớp Default. Cách gọi này về tư tưởng là tương đương với:

    k->Sum(a, b);

    Thảo luận

    In order to comment you must be a STDIO Insider. Please sign up or log in to continue.

    Đăng nhập

    Bài viết liên quan

    Con Trỏ trong C++ - Pointer

    Con Trỏ trong C++ - Pointer

    Tìm hiểu và hướng dẫn cách sử dụng con trỏ trong C/C++.

    Rye Nguyen

    28/07/2015

    Phân Biệt Tham Chiếu và Con Trỏ trong C++

    Phân Biệt Tham Chiếu và Con Trỏ trong C++

    Phân biệt tham chiếu và con trỏ trong C++ theo phương pháp đơn giản.

    La Kiến Vinh

    17/09/2014

    template Trong C++

    template Trong C++

    Template là từ khóa trong C++, chúng ta có thể hiểu rằng là nó một kiểu dữ liệu trừu tượng, đặc trưng cho các kiểu dữ liệu cơ bản. Khi học về lập trình hướng đối tượng ...

    Trung Nguyễn

    21/09/2014

    Con Trỏ Mảng Và Mảng Các Con Trỏ

    Con Trỏ Mảng Và Mảng Các Con Trỏ

    Con trỏ mảng và mảng các con trỏ là hai khái niệm quan trọng trong C++

    Nguyễn Minh Hiếu

    25/07/2015

    C++14 - Từ Khóa auto Và Khả Năng Tự Động Nhận Kiểu Khi Hiện Thực Một Hàm

    C++14 - Từ Khóa auto Và Khả Năng Tự Động Nhận Kiểu Khi Hiện Thực Một Hàm

    Với C++11 ta cũng biết khả năng nhận biết 1 kiểu dữ liệu với từ khóa auto, tuy nhiên với việc hiện thực 1 hàm thì thì C++11 chưa hỗ trợ khả năng nhận kiểu dữ liệu cho giá ...

    La Kiến Vinh

    15/09/2014

    Một Số Hàm Hữu Ích Trong Thư Viện Algorithm C++ (STL)

    Một Số Hàm Hữu Ích Trong Thư Viện Algorithm C++ (STL)

    Việc vận dụng những thư viện hỗ trợ trong C++ rất quan trọng. Đặc biệt là sử dụng thư viện algorithm để hỗ trợ giải quyết những vấn đề một cách nhanh chóng thay vì phải ...

    Phạm Tấn Phong

    02/03/2016

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++

    Bài viết hướng tối ưu hóa trong lập trình với C++, tối ưu hóa lập trình C++ với games, bài viết hướng games bởi vì games đòi hỏi hiệu năng rất cao, và các games lớn thông ...

    La Kiến Vinh

    18/09/2014

    9 Tính Năng Quan Trọng Trong C++11

    9 Tính Năng Quan Trọng Trong C++11

    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn gọi là C++), với những tính năng mới tối ưu hơn, dễ sử dụng hơn, dễ quản lý bộ nhớ hơn, và khắc phục ...

    Lê Minh Tài

    13/08/2015

    Virtual Destructor Trong C++

    Virtual Destructor Trong C++

    Phương pháp lập trình hướng đối tượng Object-Oriented Programming có tính chất: Tính trừu tượng hóa, tính đóng gói, tính kế thừa, tính đa hình. Trong đó tính đa hình được ...

    Nguyễn Nghĩa

    13/10/2015

    Vector Trong Unity

    Vector Trong Unity

    Trong môi trường game 3D, một vị trí hay vector được biểu diễn bằng 3 con số, đại diện cho 3 chiều không gian tương ứng. Trong Unity, Lớp Vector3 được sử dụng để biểu ...

    Rye Nguyen

    08/08/2015

    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020