1. Giới thiệu về biến con trỏ:
    - Biến con trỏ không chứa dữ liệu mà chỉ chứa địa chỉ của dữ liệu hay chứa địa chỉ của  ô nhớ dữ liệu.
    - Kích thước của biến con trỏ không phụ thuộc vào kiểu dữ liệu, luôn luôn bằng 4byte (đối với hệ thống window 32 bit).

2. Khai báo biến con trỏ:
    Khai báo biến con trỏ:
    Cú pháp: <Kiểu dữ liệu> * <tên con trỏ>;
 
    Ý nghĩa: Khai báo một biến có tên con trỏ dùng để chứa địa chỉ của các biến có kiểu <Kiểu>.

    Ví dụ:
      int a = 10;// (1)
      int *p;    // (2)
      p = &a;    // (3)

   Biểu diễn biến a và biến con trỏ p trong bộ nhớ:

   Chương trình sẽ thực thi như sau:
  (1)Cấp phát cho biến tĩnh a một vùng nhớ (trên stack) và gán giá trị ô nhớ bằng 10.
  (2)Cấp phát cho biến con trỏ p một vùng nhớ (trên stack).
  (3)Gán giá trị ô nhớ của biến con trỏ p bằng địa chỉ của biến a.

    Ghi chú: Nếu chưa muốn khai báo kiểu dữ liệu mà con trỏ ptr trỏ đến, ta sử dụng:
                   void * ptr;//con trỏ ptr có thể trỏ đến bất kỳ vùng nhớ có kiểu dữ liệu gì.

3. Thao tác với biến con trỏ: 
    - Toán tử & khi đặt trước một biến nào đó tức là lấy địa chỉ của biến đó.
       Ví dụ:
                  . &a sẽ có giá trị 0x12345678 (xem hình trên)
                  . &p sẽ có giá trị 0x87654321 (xem hình trên)
    - Để truy cập đến nội dung của ô nhớ mà con trỏ chỉ tới ta thêm dấu * vào trước biến con trỏ:
                   * <tên con trỏ>
       Ví dụ:
                  . *p sẽ có giá trị bằng 10, tức là nội dung của ô nhớ mà con trỏ p trỏ tới (xem hình trên)         - Còn p là nội dung ô nhớ của con trỏ p bằng 0x12345678 (địa chỉ của biến a).

4. Cấp phát và giải phóng vùng nhớ cho biến con trỏ quản lý: 
    Cấp phát vùng nhớ động:
    Có 2 vùng nhớ:
     - Vùng nhớ stack là vùng nhớ tĩnh, vùng nhớ có giới hạn được cấp phát để lưu các biến: local variable, tham số truyền vào một phương thức, hoặc các biến khai báo bên trong một biên thức, biến tham chiếu và gọi hàm.
     - Vùng nhớ heap gọi là vùng nhớ động. Các vùng nhớ khi cấp phát trên vùng nhớ heap có thể giải phóng để thu hồi vùng nhớ khi không sử dụng. Các vùng nhớ được cấp phát động thông qua các hàm như:
. void* maloc(size_t size);
. void* calloc (size_t num, size_t size);
. Toán tử New

     - Ví dụ: cấp phát vùng nhớ động
     int *p = new int[n];    // (1)
         Vùng nhớ của biến con trỏ p được lưu trữ trên vùng nhớ stack, còn vùng nhớ mà con trỏ p trỏ đến nằm ở vùng nhớ heap.
Giải phóng vùng nhớ con trỏ p trỏ đến:

    - Có 2 bước: 
         1. Xóa vùng nhớ mà con trỏ p trỏ đến: 
             delete []p;//thêm dấu [] nếu p trỏ đến mảng vùng nhớ int *p = new int[n];//n ô nhớ
             delete p;  // nếu p trỏ đến 1 ô vùng nhớ int *p = new int
        2. Sau khi xóa vùng nhớ p trỏ đến để thì gán nội dung con trỏ p = 0;//hoặc NULL để tránh p trỏ tới vùng nhớ không còn tồn tại
    





5. Các phép toán trên con trỏ: 
    Phép gán con trỏ: hai con trỏ cùng kiểu có thể gán cho nhau
        Ví dụ:
      int a = 10, *p, *q ;
      float *f;
      p = &a ;
      q = p ; // đúng
      f = p ; // sai do khác kiểu

    
Ta có thể ép kiểu con trỏ theo cú pháp: (<kiểu kết quả> *) <tên con trỏ>;
        Ví dụ: tiếp theo ví dụ trên
      f = (float*)p ; // đúng
    Cộng, trừ con trỏ với một số nguyên: 
     <Kiểu dữ liệu x> * p = new <Kiểu dữ liệu x>[n];//Tạo mảng vùng nhớ    
     Có thể cộng trừ con trỏ với một số nguyên n nào đó (p +  n) và kết quả trả về là một con trỏ, con trỏ này trỏ đến vùng nhớ cách vùng nhớ của con trỏ hiện tại n * sizeof(kiểu dữ liệu x của con trỏ).
    Lưu ý: 
    - Con trỏ có giá trị bằng NULL là con trỏ không trỏ đến địa chỉ nào cả.

    - Không thể cộng hai con trỏ với nhau.
    - Phép trừ hai con trỏ cùng kiểu sẽ trả về 1 giá trị chính là khoản cách (số phần tử) giữa 2 con trỏ đó.

6. Con trỏ và mảng: 
    Mảng một chiều:
 
   Con trỏ và mảng: 
    <kiểu dữ liệu x> a[n] = {a1, a2, ..., an};//a  một mảng n phần tử  kiểu dữ liệu x.
    <kiểu dữ liệu x> *p;
                                   p = a;   Lúc này: *(p + i) chính bằng a[i]

    Với khai báo như trên thì ta có một số tính toán như sau:
(1) Đối với mảng a: 
        1. *a        :  = a[0] (vì a là địa chỉ của mảng a nên khi * sẽ lấy nội dung)
        2. &a       : cho địa chỉ a (cũng bằng a)
        3. &a + 1 : sẽ cộng thêm số byte bằng kích cỡ vùng nhớ của mảng a, cộng thêm n * sizeof(x)
        4. a + 1    : a  địa chỉ của a, cộng thêm sizeof(x)
        5. a++      : lỗi (câu lệnh này tương đương a = a + 1 do a một hằng nên không thể gán lại giá trị khác)

(2) p  một con trỏ kiểu x trỏ vào mảng a. (tức  p = a;)
                 1. *p     : giá trị ô nhớ p trỏ tới
                 2. &p    : địa chỉ của p
                 3. &p+1: sẽ cộng thêm số byte bằng kích cỡ của con trỏ p, tức = 4 byte (windows 32 bits)
                 4. p+1   : nội dung của con trỏ p + cộng thêm sizeof(kiểu x của con trỏ p)
                 5. p++   : lênh này tương đương gán lại p = p + 1 nên kết quả như trên, cộng thêm sizeof( kiểu x của con trỏ p)

  Ví dụ về con trỏ vs với mảng:
      char a[6] = {10, 20, 30, 40, 50, 60};
      char *p = a;
Tính các kết quả sau:
 a = ?                            p = ?
&a = ?                           &p = ?
*a = ?                           *p = ?

&a + 1 = ?                       &p + 1 = ?;
                                  p + 1 = ?  
                               (*p) + 1 = ?       
                               *(p + 1) = ?
a++; a = ?                       p++; p = ?

Đáp án:
 a = ?                            p = ?
&a = ?                           &p = ?
*a = ?                           *p = ?

&a + 1 = ?                       &p + 1 = ?;
                                  p + 1 = ?  
                               (*p) + 1 = ?       
                               *(p + 1) = ?
a++; a = ?                       p++; p = ?



7. Con trỏ hàm:
     (1) Ta có một hàm như sau:
       <KiểuDữLiệuHàm> Tên_Hàm_A(<kiểuthamsố_1> [*][&]ThamSo_1,..,
                                                                  <kiểuthamsố_n> [*][&]ThamSo_n){
            //code here ...
        }
     (2) Khai báo một con trỏ hàm trỏ đến hàm trên, cú pháp như sau:
<KiểuDữLiệuHàm> (*Tên_ConTrỏ_Hàm)(<kiểuthamsố_1> [*][&],..,<kiểuthamsố_n> [*][&])
       
(3) Gán giá trị (địa chỉ ) cho con trỏ hàm:
        Tên_ConTrỏ_Hàm = Tên_Hàm_A;//hoặc &Tên_Hàm_A
(4) Cách gọi hàm:
           Tên_ConTrỏ_Hàm(ThamSo_1, …,ThamSo_n);

      Ví dụ về con trỏ hàm:

#include <iostream>

void Foo(int &n)
{
    n++;
}
void main()
{
  int x = 10;
  void (*ptrFoo)(int &);//Khai báo con trỏ hàm
  ptrFoo = Foo;         //Gán địa chỉ cho con trỏ hàm
  ptrFoo(x);            //Gọi con trỏ hàm
  printf("x = %d\n", x);
  getchar();
}

      Ví dụ về con trỏ hàm: đối số truyền vào là con trỏ hàm
#include <stdio.h>

int Add(int a, int b) {
      return a + b;
}
int Sub(int a, int b) {
      return a - b;
}
//pFunction là tham số con trỏ hàm cho một hàm khác
int Calc(int a, int b, int (*pFunction)(int, int)) {
      return (pFunction)(a, b);
}
void main()
{
      int a = 11, b = 10;
      printf("Result = %d\n", Calc(a, b, Add));//Truyền con trỏ hàm
      printf("Result = %d\n", Calc(a, b, Sub));//Truyền con trỏ hàm
      getchar();
} 

8. Bài tập về con trỏ: 
    Bài 1:

    Bài 2:

    Bài 3:

    Bài 4:

    Bài 5: