Thứ Tư, 30 tháng 12, 2015

Một ví dụ thường thấy là hàm HoanVi() nhằm hoán chuyển nội dung của 2 biến a, b khi gửi địa chỉ của chúng đến khi gọi hàm với lời gọi là HoanVi(&a, &b).
- Sau khi gọi hàm, ta muốn a nhận giá trị của b, còn b nhận giá trị cũ của a, trong đó các tham số x và y là địa chỉ của các biến cần hoán chuyển nội dung, còn *x và *y chính là các biến mà địa chỉ của chúng lưu tương ứng trong x và y.
- Đối với lời gọi hàm thì địa chỉ của biến a được chuyến vào x (tức là lúc đó x = &a), còn địa chỉ của biến b được chuyển vào y(tương đương y = &b).
-> Do vậy *x nghĩa là *(&a) chính là a, còn *y chính là *(&b) hay là b
void HoanVi(int* x, int* y) // int* x = &a;  int* y = &b;
{
int temp = *x;
*x = *y;
*y = temp;
}

void main()
{
int a = 1;
int b = 2;
HoanVi(&a, &b);
cout << "a = " << a <<" ,b = " << b;
}
Một hàm có thể nhận tham số là biến con trỏ hay cũng có thể trả về địa chỉ vùng nhớ hay địa chỉ biến
void InHoa(char* pCh)
{
if(*pCh >= 'a' && *pCh <= 'z')
*pCh = (*pCh) - 32;
}
int main()
{
char ch;
printf("\nNhap 1 ky tu: ");
scanf("%c", &ch);
InHoa(&ch);
printf("\nKy tu sau khi in hoa: %c", ch);
Mỗi khi gọi hàm thì *pCh sẽ liên kết đồng nhất với biến mà được gửi địa chỉ đến hàm. Lời gọi hàm InHoa(&ch) truyền địa chỉ của biến ch (tức là &ch) đến hàm, lúc đó *pCh và ch là như nhau nên các dòng mã trong hàm InHoa làm thay đổi biến ch.
Địa chỉ của biến là một con số. Ta có thể tạo biến khác để lưu địa chỉ của biến này. Ở đây ta dùng Con trỏ. Giống như mọi biến khác, biến con trỏ muốn sử dụng cũng cần phải được khai báo. Con trỏ NULL là con trỏ không trỏ vào đâu cả. Khác với con trỏ chưa được khởi tạo.

int n;
int *p1 = &n; // khai báo và đặt địa chỉ của biến vào con trỏ
int *p2; // unreferenced local variable
int *p3 = NULL;
Khi mới khai báo, biến con trỏ được đặt ở địa chỉ nào đó (không biết trước). chứa giá trị không xác định trỏ đến vùng nhớ không biết trước. Đặt địa chỉ của biến vào con trỏ sử dụng (toán tử &). Con trỏ chứa một số nguyên chỉ địa chỉ. Để truy xuất đến ô nhớ mà con trỏ trỏ đến, sử dụng (toán tử *). Ví dụ: Trong C:

int a = 5, *pa = &a;
printf("\nGia tri bien pa: %d", pa);
printf("\nDia chi cua bien a: %d", &a);
// -> pa và &a đều chỉ địa chỉ của biến a

printf("\nGia tri vung nho pa tro den: %d", *pa);
printf("\nGia tri cua bien a: %d", a);
// -> *pa và a đều chỉ nội dung của biến a

printf("\nDia chi bien pa: %d", &pa);
// -> địa chỉ biến pa,

// có 2 cái địa chỉ
// 1 là địa chỉ của biến con trỏ pa
// 2 là địa chỉ mà con trỏ pa trỏ đến
// Toán tử * để lấy ra giá trị mà con trỏ đang trỏ đến
// Ko để toán tử gì cả thì lấy ra địa chỉ mà con trỏ đang trỏ đến
// Nếu để cả dấu & trước tên con trỏ thì ta đang lấy ra "địa chỉ của riêng" con trỏ.

Thứ Năm, 24 tháng 12, 2015

Chúng ta cùng tìm hiểu một cấu trúc dữ liệu cũng khá hữu ích là Danh sách liên kết vòng (Circular Linked List). Nó biểu diễn một cách tự nhiên các cấu trúc dạng tròn như các góc của đa giác, v.v... DSLK vòng có hai dạng thường thấy là dạng vòng đơnvà vòng kép.

Dạng vòng đơn thực chất là một danh sách liên kết đơn có phần tử cuối trỏ về phần tử đầu tiên. Nó cũng có nhược điểm là chỉ duyệt từ một chiều. Dạng vòng kép cũng là một danh sách liên kết kép có phần tử cuối trỏ về đầu và đầu trỏ ngược về cuối.

Với DSLK vòng ta cần biết một vài thao tác cơ bản đủ dùng và các thao tác này sẽ được minh họa bằng C++. Bài này chỉ nói về danh sách liên kết vòng kép và bạn cũng nên sử dụng vòng kép để việc code lại đơn giản hơn.

Tổ chức dữ liệu
Một danh sách gồm có các phần tử gọi là node, mỗi node gồm 1 biến chứa dữ liệu và một hoặc nhiều biến con trỏ để liên kết với các node khác. Dưới đây là khai báo cấu trúc node:

1
2
3
4
5
struct DoublyNode
{
   <datatype> info;
   DoublyNode* prev, *next;
};

Do cấu trúc này ở dạng vòng nên một danh sách ta chỉ cần chọn một phần tử đầu thôi.
1
2
3
4
struct DoublyList
{
   DoublyNode* head;
};


Các thao tác cơ bản
Tạo danh sách rỗng
Do đặc điểm của cách cài đặt hướng cấu trúc và dùng con trỏ trong C++ nên cần thiết phải tạo danh sách rỗng bằng cách gán NULL cho phần tử đầu.
1
2
3
4
void CreateList(DoublyList &list)
{
   list.head = NULL;
}

Đưa dữ liệu vào node
Đơn giản là đưa dữ liệu của bạn vào một node để có thể thêm vào danh sách.
1
2
3
4
5
6
7
8
9
10
DoublyNode* CreateNode(<datatype> data)
{
   DoublyNode* node = new DoublyNode;
   if (node)
   {
      node->info = data;
      node->next = node->prev = NULL;
   }
   return node;
}

Thêm node vào danh sách
Ở đây ta chỉ cần 2 trường hợp: thêm trước 1 node và thêm sau 1 node (mà thực ra cũng chỉ cần thêm trước 1 node là đủ dùng), nhưng mình cũng code thêm hàm thêm vào cuối (có thể hiểu như thêm trước phần tử đầu).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void AddTail(DoublyList &list, DoublyNode* node)
{
   if (!list.head)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = list.head->prev;
      node->next = list.head;
      list.head->prev->next = node;
      list.head->prev = node;
   }
}
void AddBefore(DoublyList &list, DoublyNode* node, DoublyNode* before)
{
   if (!before)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = before->prev;
      node->next = before;
      before->prev->next = node;
      before->prev = node;
   }
}
void AddAfter(DoublyList &list, DoublyNode* node, DoublyNode* after)
{
   if (!after)
   {
      list.head = node;
      node->next = node->prev = list.head;
   }
   else
   {
      node->prev = after;
      node->next = after->next;
      after->next->prev = node;
      after->next = node;
   }
}

Duyệt danh sách
Duyệt là đến từng phần tử để thực hiện thao tác nào đó. Trong DSLK vòng phải có một điều kiện dừng nào đó để dừng duyệt (nếu không nó cứ đi lòng vòng). Cái này mình không nói cụ thể ở đây mà tùy trường hợp cụ thể điều kiện dừng sẽ khác nhau.
1
2
3
4
5
6
7
void Browse(DoublyList list)
{
   for (DoublyNode* i = list.head; <condition>; i=i->next)
   {
      ///
   }
}

Xóa phần tử và danh sách
Ở đây ta chỉ quan tâm việc xóa một phần tử cụ thể và xóa danh sách.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void RemoveKey(DoublyList &list, int key)
{
   DoublyNode *i = list.head;
   do
   {
      if (i->info == key)
      {
         i->prev = i->next;
         i->next = i->prev;
         if (i == list.head)
            list.head = NULL;
         delete i;
         break;
      }
      i = i->next;
   } while (i != list.head);
}
void RemoveList(DoublyList &list)
{
   DoublyNode *i = NULL;
   do
   {
      i = list.head;
      list.head->prev->next = list.head->next;
      list.head->next->prev = list.head->next;
      list.head = list.head->next;
      delete i;
      if (!i) list.head = NULL;
   } while (!list.head)
}

Các thao tác cơ bản này đã đủ dùng với danh sách liên kết vòng. Tuy nhiên các cài đặt trên chỉ là cài đặt mẫu, bạn cần phải cài đặt linh động hơn trong một số thao tác để giải quyết bài toán nhanh hơn.

Categories

Sample Text

Được tạo bởi Blogger.

Must Read

Biểu mẫu liên hệ

Tên

Email *

Thông báo *

Popular Posts

Video

Popular Posts

Our Facebook Page