Chương 12: Tìm Hiểu Về Con Trỏ Trong Lập Trình C

Chuyên khảo phân tích Chuong 12, đánh giá các khía cạnh quan trọng, đề xuất hướng nghiên cứu tiếp theo., phục vụ nghiên cứu và ứng dụng thực tiễn

Trường đại học

Trường Đại Học

Chuyên ngành

Lập Trình C

Người đăng

Ẩn danh

Thể loại

Bài Tập

2023

83
1
0

Phí lưu trữ

30 Point

Mục lục chi tiết

1. KHÁI NIỆM

2. THAO TÁC TRÊN POINTER

3. POINTER VÀ MẢNG

4. ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN

5. HÀM TRẢ VỀ POINTER VÀ MẢNG

6. CHUỖI KÝ TỰ

Tóm tắt

I. Khám Phá Con Trỏ Trong Lập Trình C Tổng Quan Về Khái Niệm

Con trỏ trong lập trình C là một khái niệm quan trọng, cho phép lập trình viên quản lý bộ nhớ một cách hiệu quả. Một biến có kiểu con trỏ có thể lưu trữ địa chỉ của một đối tượng, như biến, chuỗi hoặc hàm. Việc hiểu rõ về con trỏ giúp tối ưu hóa hiệu suất và khả năng mở rộng của chương trình.

1.1. Khái Niệm Về Con Trỏ Trong Lập Trình C

Con trỏ là một biến lưu trữ địa chỉ của một biến khác. Điều này cho phép truy cập và thao tác trực tiếp với dữ liệu trong bộ nhớ. Việc sử dụng con trỏ giúp tiết kiệm bộ nhớ và tăng tốc độ xử lý.

1.2. Tại Sao Con Trỏ Quan Trọng Trong Lập Trình C

Con trỏ cho phép quản lý bộ nhớ động, giúp lập trình viên có thể cấp phát và giải phóng bộ nhớ khi cần thiết. Điều này rất quan trọng trong các ứng dụng yêu cầu hiệu suất cao và quản lý tài nguyên hiệu quả.

II. 3 Thách Thức Chính Khi Sử Dụng Con Trỏ Trong Lập Trình C

Mặc dù con trỏ mang lại nhiều lợi ích, nhưng cũng có những thách thức mà lập trình viên cần phải đối mặt. Việc quản lý bộ nhớ không đúng cách có thể dẫn đến lỗi và sự cố trong chương trình.

2.1. Lỗi Con Trỏ Null Nguyên Nhân và Giải Pháp

Con trỏ null xảy ra khi một con trỏ không trỏ đến bất kỳ địa chỉ hợp lệ nào. Điều này có thể gây ra lỗi khi cố gắng truy cập dữ liệu thông qua con trỏ này. Cần kiểm tra con trỏ trước khi sử dụng để tránh lỗi.

2.2. Quản Lý Bộ Nhớ Động Thách Thức và Giải Pháp

Quản lý bộ nhớ động là một thách thức lớn khi sử dụng con trỏ. Nếu không giải phóng bộ nhớ sau khi sử dụng, sẽ dẫn đến rò rỉ bộ nhớ. Sử dụng các hàm như malloc() và free() một cách hợp lý là rất quan trọng.

III. Cách Sử Dụng Con Trỏ Để Tối Ưu Hóa Hiệu Suất Chương Trình

Sử dụng con trỏ một cách hiệu quả có thể giúp tối ưu hóa hiệu suất của chương trình. Việc truyền tham số qua con trỏ thay vì giá trị giúp tiết kiệm bộ nhớ và thời gian xử lý.

3.1. Truyền Tham Số Theo Kiểu Con Trỏ Lợi Ích và Cách Thực Hiện

Khi truyền tham số vào hàm, việc sử dụng con trỏ giúp tránh việc sao chép dữ liệu lớn, từ đó tiết kiệm bộ nhớ và tăng tốc độ thực thi. Cú pháp sử dụng con trỏ trong hàm rất đơn giản và dễ hiểu.

3.2. Sử Dụng Con Trỏ Để Quản Lý Mảng Phương Pháp Hiệu Quả

Con trỏ có thể được sử dụng để quản lý mảng một cách linh hoạt. Việc truy cập các phần tử trong mảng thông qua con trỏ giúp tối ưu hóa hiệu suất và giảm thiểu lỗi khi thao tác với mảng.

IV. Ứng Dụng Thực Tiễn Của Con Trỏ Trong Lập Trình C

Con trỏ không chỉ là một khái niệm lý thuyết mà còn có nhiều ứng dụng thực tiễn trong lập trình C. Từ việc quản lý bộ nhớ đến việc tối ưu hóa hiệu suất, con trỏ đóng vai trò quan trọng trong nhiều ứng dụng.

4.1. Ứng Dụng Con Trỏ Trong Quản Lý Bộ Nhớ Động

Con trỏ cho phép lập trình viên cấp phát và giải phóng bộ nhớ động một cách linh hoạt. Điều này rất quan trọng trong các ứng dụng yêu cầu quản lý tài nguyên hiệu quả.

4.2. Con Trỏ Trong Các Hàm Tối Ưu Hóa Thao Tác

Việc sử dụng con trỏ trong các hàm giúp tối ưu hóa thao tác và giảm thiểu việc sao chép dữ liệu. Điều này không chỉ tiết kiệm bộ nhớ mà còn tăng tốc độ thực thi của chương trình.

V. Kết Luận Tương Lai Của Con Trỏ Trong Lập Trình C

Con trỏ là một phần không thể thiếu trong lập trình C. Việc hiểu và sử dụng con trỏ một cách hiệu quả sẽ giúp lập trình viên phát triển các ứng dụng mạnh mẽ và hiệu quả hơn. Tương lai của lập trình C sẽ tiếp tục phát triển với sự hỗ trợ của các khái niệm như con trỏ.

5.1. Xu Hướng Phát Triển Con Trỏ Trong Các Ngôn Ngữ Lập Trình Mới

Nhiều ngôn ngữ lập trình mới đang học hỏi từ cách quản lý bộ nhớ của C. Việc sử dụng con trỏ sẽ tiếp tục là một chủ đề quan trọng trong các ngôn ngữ lập trình hiện đại.

5.2. Tầm Quan Trọng Của Con Trỏ Trong Lập Trình Hiện Đại

Con trỏ không chỉ giúp tối ưu hóa hiệu suất mà còn là công cụ mạnh mẽ để quản lý bộ nhớ. Việc nắm vững khái niệm này sẽ giúp lập trình viên phát triển kỹ năng lập trình của mình.

15/07/2025

Trích đoạn nội dung tài liệu

Company LOGO 1 Các nội dung:  Khái niệm  Thao tác trên POINTER  POINTER và mảng  Đối số của hàm là pointer - truyền đối số theo số dạng tham số biến  Hàm trả về pointer và mảng  Chuỗi ký tự  Pointer và việc định vị bộ nhớ động  Mảng các pointer © TS. Nguyễn Phúc Khải 2 Các nội dung:  Pointer của pointer  Đối số của hàm MAIN  Pointer trỏ đến hàm  Ứng dụng © TS. Nguyễn Phúc Khải 3 KHÁI NIỆM  Một biến có kiểu pointer có thể lưu được dữ liệu trong nó, là địa chỉ của một đối tượng đang khảo sát. Đối tượng đó có thể là một biến, một chuỗi hoặc một hàm.

Nguyễn Phúc Khải 4 KHÁI NIỆM  Ví dụ 13.1: Chương trình đổi trị #include<stdio.h> void Swap (int doi_1, int doi_2); main() { int a = 3, b = 4; printf (“Trước khi gọi hàm, a = %d, b = %d.\n”,a,b); Swap (a, b); // Gọi hàm đổi trị printf (“Sau khi gọi hàm, a = %d, b = %d.\n”,a,b);} void Swap (int doi_1, int doi_2) { int temp = doi_1; doi_1 = doi_2 ; doi_2 = temp ; } © TS. Nguyễn Phúc Khải 5 KHÁI NIỆM  Hình ảnh stack thực thi khi điều khiển chương trình đang ở dòng doi_1 = doi_2 ; © TS. Nguyễn Phúc Khải 6 KHÁI NIỆM  Hình ảnh stack thực thi khi điều khiển đến cuối chương trình © TS. Nguyễn Phúc Khải 7 THAO TÁC TRÊN POINTER  Cú pháp để khai báo biến pointer: kiểu *tên_biến_pointer  Với:  kiểu có thể là kiểu bất kỳ, xác định kiểu dữ liệu có thể được ghi vào đối tượng mà con trỏ đang trỏ đến.

 tên_biến_pointer là tên của biến con trỏ, một danh hiệu hợp lệ. Nguyễn Phúc Khải 8 THAO TÁC TRÊN POINTER  Biến hoặc đối tượng mà con trỏ đang trỏ đến có thể được truy xuất qua tên của biến con trỏ và dấu "*" đi ngay trước biến con trỏ, cú pháp cụ thể như sau: * tên_biến_con_trỏ © TS. Nguyễn Phúc Khải 9 THAO TÁC TRÊN POINTER  Khai báo biến pointer - pointer hằng:  Trong ngôn ngữ C, một toán tử lấy địa chỉ của một biến đang làm việc, toán tử này là một dấu & (ampersand), tạm gọi là toán tử lấy địa chỉ. Cú pháp như sau: & biến  với biến là một biến thuộc kiểu bất kỳ, nhưng không được là biến thanh ghi.

Nguyễn Phúc Khải 10 THAO TÁC TRÊN POINTER  Ví dụ:  Nếu có một biến đã được khai báo là: int he_so_a;  thì & he_so_a  sẽ là địa chỉ của biến he_so_a. Nguyễn Phúc Khải 11 THAO TÁC TRÊN POINTER  Ví dụ: Xét ví dụ sau: int object; int *pint; object = 5; pint = &object; printf(“%d”,*pint); © TS. Nguyễn Phúc Khải 12 THAO TÁC TRÊN POINTER  Ví dụ: void * pvoid; int a, * pint; double b, * pdouble; pvoid = (void *) &a; pint = (int *) pvoid; (*pint) ++; pvoid = (void *) &b; pdouble = (double *) pvoid; (*pdouble) -- ; © TS. Nguyễn Phúc Khải 13 THAO TÁC TRÊN POINTER  Các phép toán trên pointer:  Có thể cộng, trừ một pointer với một số nguyên (int, long,.

Kết quả là một pointer.  Ví dụ : int *pi1, *pi2, n; pi1 = &n; pi2 = pi1 + 3; © TS. Nguyễn Phúc Khải 14 THAO TÁC TRÊN POINTER  Ví dụ: Cho khai báo: int a[20]; int *p; p = &a[0]; p += 3; /* p lưu địa chỉ phần tử a[0 + 3], tức &a[3] */ © TS. Nguyễn Phúc Khải 15 THAO TÁC TRÊN POINTER  KHÔNG thể thực hiện các phép toán nhân, chia, hoặc lấy dư một pointer với một số, vì pointer lưu địa chỉ, nên nếu thực hiện được điều này cũng không có một ý nghĩa nào cả.

 Phép trừ giữa hai pointer vẫn là một phép toán hợp lệ, kết quả là một trị thuộc kiểu int biểu thị khoảng cách (số phần tử) giữa hai pointer đó. Nguyễn Phúc Khải 16 THAO TÁC TRÊN POINTER  Ví dụ: Xét chương trình ví dụ sau: #include <stdio. Nguyễn Phúc Khải 17 THAO TÁC TRÊN POINTER  Ví dụ: Cho các khai báo sau: int * a1; char * a2; a1 = 0;/* Chương trình dịch sẽ nhắc nhở lệnh này */ a2 = (char *)0; if( a1 != a2) /* Chương trình dịch sẽ nhắc nhở kiểu của đối tượng */ { a1 = (int *) a2; /* Hợp lệ vì đã ép kiểu */ } © TS. Nguyễn Phúc Khải 18 THAO TÁC TRÊN POINTER  Ví dụ: #include <stdio.

Nguyễn Phúc Khải 19 THAO TÁC TRÊN POINTER printf ("Tri cua bien pint la: %p\n", pint); printf ("Tri cua bien pchar la: %p\n", pchar); printf ("Doi tuong pint dang quan ly la %X \n", *pint); printf ("Doi tuong pchar dang quan ly la %X \n", *pchar); pchar++; printf ("Doi tuong pchar dang quan ly la %X \n", *pchar); getch(); } © TS. Nguyễn Phúc Khải 20 THAO TÁC TRÊN POINTER  C cho phép khai báo một biến pointer là hằng hoặc đối tượng của một pointer là hằng.  Ví dụ: Các khai báo sau đây là biến pointer hằng: 1. int a, b; int * const pint = &a; pint = &b;  C báo lỗi 2.

char * const ps = "ABCD"; © TS. Nguyễn Phúc Khải 21 THAO TÁC TRÊN POINTER  Các khai báo sau đây cho thấy đối tượng của một pointer là hằng: 1. int i; const int * pint; pint = &i; i = 1; (*pint) ++;  C báo lỗi i++; 2. const char * s = "ABCD" hoặc char const * s = "ABCD"; © TS.

Nguyễn Phúc Khải 22 THAO TÁC TRÊN POINTER  Phân biệt:  const trước * : pointer chỉ đến đối tượng hằng  * trước const : pointer hằng © TS. Nguyễn Phúc Khải 23 POINTER VÀ MẢNG  Trong C, tên mảng là một hằng pointer tới phần tử có kiểu là kiểu của biến thành phần dưới nó một bậc trong mảng,  VD: tên của mảng một chiều của các int là pointer chỉ tới int, tên của mảng hai chiều của các int là pointer chỉ tới mảng một chiều là hàng các int trong mảng.  Trong trường hợp mảng một chiều, tên mảng chính là địa chỉ của phần tử đầu tiên của mảng. Do đó, ta hoàn toàn có thể truy xuất mảng bằng một pointer © TS.

Nguyễn Phúc Khải 24 POINTER VÀ MẢNG  Ví dụ:  Ví dụ: Cho khai báo sau: int a[3]; int a[10]; int (*p)[3]; int *pa; p[0] = &a ; *(a + 0) chính là a[0], *(a + 1) chính là a[1],. Có thể gán: pa = a; hoặc pa =&a[0]; © TS. Nguyễn Phúc Khải 25 POINTER VÀ MẢNG Khi đó, (pa + 0) sẽ chỉ đến phần tử a[0], (pa + 1) sẽ chỉ đến phần tử a[1],. Như vậy, các đối tượng *(pa + 0) chính là *(a + 0), cũng là a[0] *(pa + 1) chính là *(a + 1), cũng là a[1].

Nguyễn Phúc Khải 26 POINTER VÀ MẢNG  Có thể truy xuất đến các đối tượng của mảng mà pointer đang trỏ đến như sau: pa[0] chính là phần tử a[0] pa[1] chính là phần tử a[1]. Nguyễn Phúc Khải 27 POINTER VÀ MẢNG  Phân biệt rõ ràng giữa mảng và pointer:  Một mảng, sau khi được khai báo và định nghĩa, đã được cấp một vùng nhớ trong bộ nhớ trong của máy tính và địa chỉ chính là tên mảng.  Một biến pointer, sau khi được khai báo, thì vùng nhớ được cấp chỉ là bản thân biến pointer, còn trị bên trong nó là một địa chỉ rác.  Ngoài ra, biến pointer có một địa chỉ trong bộ nhớ, còn không thể lấy địa chỉ của tên mảng.

Nguyễn Phúc Khải 28 POINTER VÀ MẢNG  Ví dụ: Sau khi khai báo int a[10];  thì trong bộ nhớ, a là một hằng pointer, hay một địa chỉ cố định  Vì tên mảng là một hằng pointer a++;  là không hợp lệ. Nguyễn Phúc Khải 29 POINTER VÀ MẢNG  Ví dụ: int *pa;  thì trong bộ nhớ  do đó lệnh gán  *pa = 2; là không có ý nghĩa, dù C không báo lỗi trong trường hợp này. Nguyễn Phúc Khải 30 POINTER VÀ MẢNG  Từ mảng hai chiều trở đi, việc dùng biến pointer để truy xuất mảng là khá phức tạp, chúng ta cần phải luôn nhớ là các thao tác trên pointer luôn diễn ra trên cùng một bậc quản lý đối tượng, nghĩa là chúng ta phải luôn biết pointer mà chúng ta sử dụng đang quản lý đối tượng kiểu nào. Nguyễn Phúc Khải 31 POINTER VÀ MẢNG  Ví du: #define MAX_ROW 20 #define MAX_COL 30 int array[MAX_ROW][MAX_COL]; int row, col; int (*parr) [MAX_ROW][MAX_COL]; /* biến con trỏ mảng hai chiều */ parr = &array; © TS.

Nguyễn Phúc Khải 32 POINTER VÀ MẢNG  Với khai báo trên, danh hiệu array là hằng pointer hai lần pointer chỉ tới phần tử đầu tiên của mảng array, tức ta có **array chính là array[0][0].  Với mảng một chiều, array[row] chính là *(array+row), tức array[row] ≡ *(array+row).  Với mảng hai chiều, ta có array[0] (= *array hay *(array + 0)) là con trỏ chỉ tới hàng 0 trong mảng (và dĩ nhiên array[row] là con trỏ chỉ tới hàng row trong mảng, …). Như vậy, ta có *array[0] chính là **array và cũng chính là array[0][0].

Nguyễn Phúc Khải 33 POINTER VÀ MẢNG  Khi đó, ta có: array[row][col] ≡ *(array[row] + col), tức array[row][col] ≡ *( int (*) array + row*MAX_COL + col) array[row][col] ≡ *(*(array + row) + col) array[row][col] ≡ *(*( (int (*)[MAX_COL]) parr + row) + col) © TS. Nguyễn Phúc Khải 34 ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN  Hàm Swap() ở đoạn chương trình trên có thể viết lại như sau: void Swap (int *doi_1, int *doi_2) /*pointer là đối số của hàm, để nhận địa chỉ của đối số thật*/ { int temp; temp = * doi_1; * doi_1 = * doi_2; * doi_2 = temp; } © TS. Nguyễn Phúc Khải 35 ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN #include <stdio.h> void Swap (int *doi_1, int *doi_2); main () { int a = 3, b = 4; clrscr(); printf ("Tri cua hai bien a va b la: %d %d\n", a, b); Swap (&a, &b); printf ("Sau khi goi ham Swap, tri cua a va b la: %d %d\n", a, b); getch(); } © TS. Nguyễn Phúc Khải 36 ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN © TS.

Nguyễn Phúc Khải 37 ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN © TS. Nguyễn Phúc Khải 38 ĐỐI SỐ CỦA HÀM LÀ POINTER - TRUYỀN ĐỐI SỐ THEO SỐ DẠNG THAM SỐ BIẾN  Trong thư viện chuẩn của C cũng có nhiều hàm nhận đối số vào theo địa chỉ, ví dụ hàm scanf(), nhập trị vào cho biến từ bàn phím. Nguyễn Phúc Khải 39 Bài tập  Viết hàm nhập 3 số từ bàn phím và hàm nhập mảng 1 chiều.

Nội dung được bảo vệ bản quyền — Tải xuống đầy đủ