1. Trang chủ
  2. » Công Nghệ Thông Tin

Win32 Programming Tutorial  Tham khảo toàn diện về Con trỏ trong C/C++ ppt

21 580 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 21
Dung lượng 584,11 KB

Nội dung

Trang 1 Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Tham khảo toàn diện về “Con trỏ” trong C/C++ Reverse Engineering Association Win32ProgrammingTutorial  For more updated info, please check http://nhatphuongle.spaces.live.com Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net MỤC LỤC I. Con trỏ 3 1. Một số khái niệm 3 2. Biến 3 a) Biến 3 b) Ví dụ 4 3. Con trỏ 5 a) Khái niệm 6 b) Tại sao phải dùng con trỏ 7 c) Một số thao tác cơ bản với con trỏ 9 II. Con trỏ hàm 11 1. Một số khái niệm 11 2. Con trỏ hàm 11 a) Khái niệm 11 b) Cú pháp 12 c) Định nghĩa một con trỏ hàm 12 3. Kiểu quy ước gọi của hàm 13 4. Gán một hàm (địa chỉ hàm) vào/cho con trỏ hàm 13 5. Gọi hàm sử dụng con trỏ hàm 15 6. Tại sao sử dụng con trỏ hàm 16 7. Định nghĩa con trỏ hàm bằng từ khóa typedef 20 III. Tài liệu tham khảo 21 Trang 2 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net I. Con trỏ 1. Một số khái niệm Con trỏ (pointer) đơn giản là địa chỉ của một đối tượng trong bộ nhớ. Thông thường, các đối tượng có thể được truy xuất bằng một trong 2 cách: trực tiếp bằng đại diện hoặc gián tiếp bằng con trỏ. Các biến con trỏ được định nghĩa trỏ tới các đối tượng có một kiểu dữ liệu cụ thể sao cho khi con trỏ bị hủy đi thì vùng nhớ mà đối tượng đuợc cấp phát sẽ đư ợc giải phóng hoặc thu hồi. Các con trỏ thường được dùng để tạo ra các đối tượng động trong lúc thực thi chương trình. Không giống như các đối tượng bình thường (biến cục bộ và toàn cục) được cấp phát và lưu trữ trong ngăn xếp (stack), một đối tượng động được cấp phát vùng nhớ từ vùng lưu trữ khác được gọi là heap. Các đối tượng không t uân theo quy luật thông thường. Phạm vi của chúng được điều khiển bởi các lập trình viên. Tham chiếu (reference) cung cấp một tên tượng trưng cho đối tượng, gọi là alias (tên đại diện). Truy xuất một đối tượng thông qua một tham chiếu giống như truy xuất thông qua tên của đối tượng. Tham chiếu nâng cao tính hữu dụng của con trỏ và sự tiện lợi của việc truy xuất trực tiếp các đối tượng. Chúng được sử dụng để hổ trợ gọi thông qua tham chiếu của các tham số hàm đặc biệt khi các đối tượng lớn được truyền tới hàm. 2. Biến Khái niệm con trỏ trong ngôn ngữ C/C++ là một phần quan trọng và khá phức tạp, mang lại rất nhiều tiện lợi cũng như rắc rối. Nếu như muốn giải thích cặn kẽ về vai trò, chức năng, hoạt động cũng như những khái niệm khác liên quan tới con trỏ thì tôi e rằng cần khoảng vài chương sách mới đủ. Tôi tạm mượn câu nói “Ngôn ngữ C/C++ không phải chỉ có con trỏ, nhưng nếu chưa hiểu về con trỏ thì kể như chưa học C/C++” để nói lên vài trò của con trỏ. Bởi vì nhiều khái niệm khác trong C liên quan mật thiết tới con trỏ như: chuỗi ký tự, mảng, hàm CallBack, các kiểu dữ liệu trừu tượng như danh sách liên kết, cấu trúc cây, và mở rộng ra trong C++ như lớp, các hàm thành viên, V-Table, a) Biến Để hiểu được con trỏ thì trước tiên bạn phải hiểu về biến. Tr ong C/C++, biến là một vùng trong bộ nhớ, do đó mỗi biến đều có 1 địa chỉ. Khi bạn khai báo một biến, bạn phải cung cấp tên biến, kiểu dữ liệu của biến đó. Khi bạn muốn thao tác với một biến thì bạn sử dụng tên gọi của nó, còn kiểu dữ liệu giúp cho trình biên dịch biết là biến đó cần bao nhiêu byte trong bộ nhớ và nó sẽ cấp phát vùng nhớ tương ứng cho biến đó. Ví dụ, bạn khai báo 1 biến kiểu integer: int var; Trang 3 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Khi biên dịch tới đây, trình biên dịch sẽ cấp phát bộ nhớ cho biến var này là 2 byte hoặc 4 bytes (tùy thuộc vào kiến trúc máy tính). Giả sử kiểu int cần 2 bytes, và trình biên dịch sẽ gán cho biến var hai byte trong bộ nhớ bắt đầu tại địa chỉ xxx. Sau đó, trình biên dịch sẽ điền tên và địa chỉ của biến var này vào trong một bảng (chỉ sử dụng khi biên dịch). Bạn có thể hiểu nôm na bảng này gồm 2 cột: một cột là tên biến và một cột là địa chỉ của biến. Mỗi biến trong chuơng trì nh sẽ chiếm một dòng trong bảng đó, khi biên dịch gặp dòng lệnh gán: var = 25; Đầu tiên, nó sẽ tìm xem trong bảng có biến nào tên là var hay không, nếu nó tìm không thấy thì nó sẽ khai báo lỗi là bạn dùng một biến chưa khai báo: error Cxxx: 'i' : undeclared identifier Nếu tìm thấy biến này thì nó sẽ điền giá trị là 25 vào 2 byte tại địa chỉ xxx. b) Ví dụ Về cơ bản bộ nhớ máy tính là một dã y các bytes. Mỗi byte được đánh địa chỉ cụ thể. Hình dưới đây tượng trưng một dãy bytes, từ địa chỉ 924 tới địa chỉ 940, trong bộ nhớ máy tính: Bạn có chương trình như sau: Mã lệnh C Mã lệnh C++ 1: #include <stdio.h> 1: #include <iostream> 2: int main () 2: int main () 3: { 3: { 4: float fl=3.14; 4: float fl=3.14; 5: printf (“%.2f\n”,fl) 5: std::count << fl << std::endl 6: return 0; 6: return 0; 7: } 7: } Khi biên dịch tới dòng (4) của chương trình, thì trình biên dịch sẽ cấp vùng nhớ cho biến fl. Trong ví dụ này, giả sử rằng biến float cần 4 bytes: Trang 4 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Khi biến fl được sử dụng ở dòng (5), trình biên dịch sẽ làm 2 bước như sau: + Chương trình sẽ tìm và lấy địa chỉ dành cho biến fl (trong ví dụ này là 924) + Lấy nội dung lưu tại địa chỉ 924  Tổng quát lên, khi bất kỳ một biến nào đó được sử dụng, thì trình biên dịch cũng làm 2 bước trên để lấy nội dung của biến. Minh họa giá trị của biến được khởi tạo lưu trữ trong bộ nhớ máy tính có thể bị hiểu nhầm. Hãy nhìn hình, giá trị 3 đựơc lưu tại địa chỉ 924, dấm chấm được lưu tại địa chỉ 925, giá trị 1 được lưu tại đại chỉ 926, và cuối cùng giá trị 4 đựơc lưu tại địa chỉ 927. Hãy luôn nhớ rằng, trong thực tế máy tính sẽ sử dụng một thuật toán để chuyển đổi số kiểu chấm động (float point number) 3.14 thành dãy các bit 0 và 1. Mỗi byte có 8 bits 0 và 1, vì thế 4 bytes float sẽ lưu trữ 32 bits 0 và 1. Bất kể con số này là 3.14 hay -273.15, thì nó luôn luôn được lưu trữ trong 4 bytes bộ nhớ. 3. Con trỏ Ví dụ trên minh họa cho chúng ta cách khai báo, sử dụng cũng như biểu diễn chúng trên bộ nhớ như thế nào. Đây là các biến có kích thước và kiểu dữ liệu xác định. Người ta gọi các biến kiểu này là biến tĩnh. Khi khai báo biến tĩnh, việc cấp phát vùng nhớ cho các biến này luôn luôn được thực hiện mà không cần biết trong quá trình thực thi chương trình có sử dụng hết vùng nhớ đã được cấp hay không? Mặt khác, các biến tĩnh dạng này sẽ tồn tại t rong suốt thời gian thực thi chương trình mặc dù có những biến chỉ được sử dụng trong suốt chương trình. Một số hạn chế có thể gặp phải khi sử dụng các biến tĩnh:  Cấp phát vùng nhớ thừa  lãng phí vùng nhớ  Cấp phát vùng nhớ thiếu, chương trình thực thi bị lỗi. Để tránh những hạn chế trên, ngôn ngữ C cung cấp c ho ta một loại biến đặc biệt gọi là biến động với các đặc điểm sau:  Chỉ phát sinh trong quá trình thực hiện chương trình chứ không phát sinh lúc bắt đầu chương trình.  Khi chạy chương trình, kích thước của biến, vùng nhớ và địa chỉ vùng nhớ được cấp phát cho biến có thể thay đổi.  Sau khi sử dụng xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ. Tuy nhiên các biến động không có địa chỉ nhất định nên ta không thể truy cập đến chúng được. Vì thế, ngôn ngữ C lại cung cấp cho ta một loại biến đặc biệt nữa để khắc phục tình trạng này, đó là biến con trỏ (pointer). Trang 5 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net a) Khái niệm Con trỏ là một biến lưu trữ địa chỉ của một biến khác có kiểu dữ liệu cụ thể. Một con trỏ có tên gọi giống như bất kỳ biến khác và cũng có một kiểu dữ liệu quy định loại biến nào mà nội dung của nó tham chiếu đến. Con trỏ là một biến đặc biệt trong C, giá trị mà nó lưu trữ luôn được hiểu là địa chỉ trong bộ nhớ. Giả sử bạn có 2 biến, một biến kiểu nguyên ( integer), một biến là kiểu con trỏ và giả sử cả 2 biến đều có giá trị là 1000. Khi đó, giá trị mà biến kiểu int lưu trữ đụơc hiểu một con số nguyên có giá trị là 1000, còn giá trị mà con trỏ lưu trữ được coi là địa chỉ thứ 1000 trong bộ nhớ. Con trỏ được dùng để lưu trữ địa chỉ của những biến khác trong chương trình, vì t hế khi khai báo ngoài dấu *, bạn cần phải khai báo rõ thêm là con trỏ sẽ “trỏ” đến một biến kiểu gì (kiểu int, long, hay kiểu char, ). Ví dụ, bạn muốn lưu trữ địa chỉ của biến kiểu int, thì bạn khai báo như sau: int *pInt; Ở đây, pInt là tên con trỏ, dấu * cho trình biên dịch biết đó là con trỏ - một biến đặc biệt chứ không phải những biến bình thường khác và từ khóa int đứng đầu báo cho trình biên dịch biết là con trỏ pInt dùng để lưu trữ địa chỉ của những biến kiểu int. Bây giờ, chúng ta muốn gán địa chỉ của biến integer đã đựơc khai báo từ trước cho con trỏ pInt thì làm thế nào?  Để gán địa chỉ của một biến cho c on trỏ  dùng toán tử lấy địa chỉ của biến (&). Ví dụ: pInt = & var; Giả sử biến var đang được lưu trữ tại địa chỉ 1776 và nếu bạn viết như sau: var = 25; int temp = var; pInt = & var; Kết quả sẽ giống như sơ đồ dưới đây: Trang 6 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Chúng ta đã gán cho biến temp nội dung của biến var, nhưng với biến pInt chúng ta sẽ gán địa chỉ mà hệ điều hành lưu giá trị của biến var, chúng ta đã giả sử địa chỉ đó là 1776. Khi gặp dòng lệnh pInt = & var thì trình dịch cũng thực hiện bình thường như những biến khác: đầu tiên nó tìm trong bảng xem có biến nào tên là pInt hay không, sau đó nó dịch đến dấu = và nó hiểu là phải gán một giá trị cho biến pInt. Tiếp đến là toán tử lấy giá trị (&), khi gặp toán tử đó, trình dịch sẽ hiểu là cần phải gán địa chỉ của biến var cho pInt (tức là 1776), chứ không phải là giá trị của bản thân biến var (tức là 25). Vậy là đến đây chúng ta có như sau: giá trị của var là 25, giá trị của pInt là 1776 (địa chỉ của biến var). Vì con trỏ cũng là biến cho nên nó cũng phải có địa chỉ trong bộ nhớ, còn số byte cần thiết cho con trỏ có thể là 2 hoặc 4 byte phụ thuộc hệ điều hành (DOS hay Win32). Giả sử địa chỉ của pInt là 2000, khi đó chúng ta có sơ đồ sau: Bên trên là tên biến, những số bên trên (1776 và 2000) là địa chỉ của 2 biến, còn những số bên trong (25 và 1776) là giá trị của 2 biến đó. b) Tại sao phải dùng con trỏ Nếu chỉ dùng c on trỏ để lưu giá trị là những địa chỉ trong bộ nhớ, thì chẳng việc gì người ta lại phải nghĩ ra kiểu con trỏ làm gì cho nó phức tạp, bởi vì thực ra địa chỉ trong bộ nhớ cũng là một số nguyên, và tất nhiên là dùng một biến kiểu số nguyên là đủ. Sở dĩ người ta phải nghĩ ra kiểu con trỏ là bởi vì có những lúc bạn không thể thao tác trực tiếp với các biến thông qua tên gọi, mà bạn phải thao tác với chúng một cách gián tiếp, thông qua địa chỉ của nó, tức là dùng con trỏ. Ví dụ, để thay đổi giá trị của biến var từ 25 thành 30, thông thường bạn viết là: var = 30; Trang 7 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Nhưng nếu bạn muốn gán giá trị 30 cho biến var tại một điểm nào đó trong chương trình, mà tại điểm đó biến var bị "che" mất (tức là tại điểm đó không "tồn tại" biến nào tên là var), bạn chỉ biết địa chỉ của biến đó thôi, thì bạn làm thế nào?  Câu trả lời là dùng con trỏ. Ví dụ: *pInt = 30; Ở đây, dấu * có ý nghĩa khác với khi khai báo con trỏ pInt. Nó báo cho trình dịch biết là bạn muốn gán giá trị 30 cho biến mà địa chỉ của nó đang do pInt lưu trữ. Vì trình dịch biết giá trị của pInt (hiện là 1776) nên kết quả là số 30 sẽ được viết vào 2 byte (do pInt trỏ tới kiểu int có 2 byte) bắt đầu tại địa chỉ 1776. Đến đây thì chúng ta có sơ đồ sau: Như bạn thấy, mặc dù bạn không gán số 30 cho biến var trực tiếp thông qua tên gọi, mà bạn chỉ dùng con trỏ, nhưng kết quả là bây giờ biến var sẽ có giá trị mới là 30, chứ không phải là 25 như trước nữa. Kết luận: chúng ta có thể thao tác với 1 biến bằng 2 cách: dùng tên biến hoặc dùng con trỏ, trỏ đến biến đó. Tức là: var = 30 hoặc *pInt = 30; là tương đương nhau. Tương tư, nếu như integer là một biến kiểu int thì: var = integer ; và *pInt = integer ; cũng tương đương nhau. Trang 8 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net c) Một số thao tác cơ bản với con trỏ i. Toán tử lấy địa chỉ (&) Vào thời điểm mà chúng ta khai báo một biến thì nó phải được lưu trữ trong một địa chỉ cụ thể trong bộ nhớ. Và chúng ta không biết địa chỉ của biến đó - thật may mắn rằng điều đó đã được làm tự động bởi trình biên dịch và hệ điều hành, nhưng một khi hệ điều hành đã gán một địa chỉ cho biến thì chúng ta có thể biết biến đó đư ợc lưu trữ ở đâu. Điều này có thể được thực hiện bằng cách đặt trước tên biến một dấu và (&), có nghĩa là "địa chỉ của". Ví dụ: int *pInt; int var;  pInt = &var; pInt = &var sẽ gán cho biến pInt địa chỉ của biến var, vì khi đặt trước tên biến var dấu và (&) chúng ta không còn nói đến nội dung của biến đó mà chỉ nói đến địa chỉ của nó trong bộ nhớ. Giả sử rằng biến var được đặt ở ô nhớ có địa chỉ 1776 và chúng ta viết như sau: var = 25; int temp = var; pInt = &var; Kết quả sẽ giống như trong sơ đồ dưới đây: Chúng ta đã gán cho temp nội dung của biến andy như chúng ta đã làm rất lần nhiều khác trong những phần trước nhưng với biến pInt chúng ta đã gán địa chỉ mà hệ điều hành lưu giá trị của biến var, chúng ta vừa giả sử nó là 1776. Trang 9 Reverse Engineering Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net ii. Toán tử lấy tham chiếu (*) Bằng cách sử dụng con trỏ chúng ta có thể truy xuất trực tiếp đến giá trị được lưu trữ trong biến được trỏ bởi nó bằng cách đặt trước tên biến con trỏ một dấu sao (*) - ở đây có thể được dịch là "giá trị được trỏ bởi". Vì vậy, nếu chúng ta viết: int temp = *pInt; (chúng ta có thể đọc nó là: "temp bằng giá trị được trỏ bởi pInt" temp sẽ mang giá trị 25, vì pInt bằng 1776 và giá trị trỏ bởi 1776 là 25. Bạn phải phân biệt được rằng pInt có giá trị 1776, nhưng *pInt (với một dấu sao đằng trước) trỏ tới giá trị được lưu trữ trong địa chỉ 1776, đó là 25. Hãy chú ý sự khác biệt giữa việc có hay không có dấu sao tham chiếu. temp = pInt; // temp bằng pInt( 1776 ) temp = *pInt; // temp bằng giá trị được trỏ bởi(25) Toán tử lấy địa chỉ (&) Nó được dùng như là một tiền tố của biến và có thể được dịch là "địa chỉ của", vì vậy &variable có thể được đọc là "địa chỉ của variable". Toán tử tham chiếu (*) Nó chỉ ra rằng cái cần được tính toán là nội dung được trỏ bởi biểu thức được coi như là một địa chỉ. Nó có thể được dịch là "giá trị được trỏ bởi" *mypointer được đọc là "giá trị được trỏ bởi mypointer". Vào lúc này, với những ví dụ đã viết ở trên var = 25; pInt = &var; bạn có thể dễ dàng nhận ra tất cả các biểu thức sau là đúng: var = 25 &var = 1776 pInt = 1776 *pInt = 25 Trang 10 Reverse Engineering Association [...]... goo() cho con trỏ pFoo, không phải là giá trị trả về từ hàm goo() Do đó, bạn không cần dấu ngoặc đơn pFoo = goo; NhatPhuongLe www.reaonline.net Reverse Engineering Association Trang 14 Tham khảo toàn diện về Con trỏ trong C/C++ Chú ý: Một con trỏ hàm luôn trỏ đến một hàm đặc biệt nên tất cả những hàm mà chúng ta muốn sử dụng với cùng một con trỏ hàm thì phải có cùng tham số và giá trị trả về Nói một.. .Tham khảo toàn diện về Con trỏ trong C/C++ II Con trỏ hàm 1 Một số khái niệm Pointer/Pointee: một con trỏ “pointer” sẽ lưu một reference đến một biến khác được biết như là pointee của nó Con trỏ có thể được thiết lập giá trị NULL có nghĩa là nó refer đến một pointee nào (Trong C và C++, giá trị NULL có thể được sử dụng như là giá trị boolean false) Dereference: toán tử dereference trên con trỏ. .. giống như một lời gọi hàm thông thường Hãy nhớ rằng, đối với một hàm bình thường thì nó là một con trỏ luôn trỏ tới tên của chính nó NhatPhuongLe www.reaonline.net Reverse Engineering Association Trang 15 Tham khảo toàn diện về Con trỏ trong C/C++ 6 Tại sao sử dụng con trỏ hàm Trong nhiều trường hợp con trỏ hàm có thể hữu dụng, ví dụ như bạn cần viết một hàm sắp xếp mảng một chiều, nhưng đồng thời... www.reaonline.net Reverse Engineering Association Trang 11 Tham khảo toàn diện về Con trỏ trong C/C++ Bây giờ, bạn có một câu lệnh khai báo một hàm không có tham số đầu vào và kiểu trả của hàm là kiểu số nguyên: int foo(); Nếu bạn đoán rằng foo là một con trỏ hằng trỏ tới một hàm, thì bạn đã chính xác Khi một hàm được gọi thông qua toán tử (), thì con trỏ hàm được truy xuất, và thực thi hàm b) Cú pháp Dựa... www.reaonline.net Reverse Engineering Association Trang 17 Tham khảo toàn diện về Con trỏ trong C/C++ Như vậy, chúng ta đã để cho caller truyền vào một con trỏ hàm để thực hiện việc so sánh như một tham số đầu vào của hàm, và sau đó chúng ta sẽ sử dụng nó để so sánh Đây là mã lệnh đầy đủ của thuật toán Selection Sort, sử dụng con trỏ hàm như là một tham số cho phép người dùng chỉ định việc sắp xếp theo... Tham khảo toàn diện về Con trỏ trong C/C++ Chương trình cho ra kết quả như sau: Tới đây, bạn có thể “chế” ra những hàm so sánh khác để phục vụ cho thuật toán sắp xếp Ví dụ như là sắp xếp mảng sao cho các phần tử chẳn ở đầu mảng, phần tử lẻ ở cuối mảng: Chương trình cho ra kết quả như sau: 2 4 6 8 1 3 5 7 9 NhatPhuongLe www.reaonline.net Reverse Engineering Association Trang 19 Tham khảo toàn diện về. .. Association Trang 12 Tham khảo toàn diện về Con trỏ trong C/C++ 3 Kiểu quy ước gọi của hàm Thông thường chúng ta không phải quan tâm về kiểu quy ước gọi (calling convention) của một hàm Trình biên dịch giả định rằng cdecl là kiểu quy ước mặc định nếu chúng ta không sử dụng một kiểu quy ước khác Kiểu quy ước gọi hàm nói cho trình biên dịch biết cách truyền tham số và cách tạo ra một hàm Ví dụ về những kiểu... Engineering Association Trang 13 Tham khảo toàn diện về Con trỏ trong C/C++ // C++ class TMyClass { public: int DoIt(float a, char b, char c) { cout . Trang 1 Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net Tham khảo toàn diện về Con trỏ trong C/C++ . Association Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net I. Con trỏ 1. Một số khái niệm Con trỏ (pointer) đơn giản là địa chỉ của một đối tượng trong bộ nhớ http://nhatphuongle.spaces.live.com Tham khảo toàn diện về Con trỏ trong C/C++ NhatPhuongLe www.reaonline.net MỤC LỤC I. Con trỏ 3 1. Một số khái niệm 3 2. Biến 3 a) Biến 3 b) Ví dụ 4 3. Con trỏ 5 a) Khái

Ngày đăng: 29/06/2014, 08:20

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w