Con trỏ

Con trỏ là gì?

Ví dụ 1: Nhập tên và in ra màn hình tên bạn vừa nhập

#include "stdio.h"
#include "conio.h"

void main()
{
    char s[30];
    printf("\nNhap ten: ");
    gets(s);
    printf("\nTen cua ban: %s", s);
    getch();
}

Ở đây, chương trình này có một số hạn chế như sau:

  • Mảng kí tự s chứa tối đa được 29 kí tự
  • Nếu bạn nhập tên có độ dài nhỏ hơn 29 gây lãng phí bộ nhớ
  • Nếu bạn nhập tên có độ dài lớn hơn 29 gây lỗi chương trình

Để khắc phục những hạn chế trên đây, ngôn ngữ C đưa ra khái niệm biến con trỏ và cấp phát động

Vậy biến con trỏ là gì? Cấp phát động là gì?

Khái niệm: Biến con trỏ (Pointer variable) là biến chứa địa chỉ của biến hoặc chứa địa chỉ của ô nhớ.

Khai báo

<Kiểu dữ liệu> * <Tên biến con trỏ>

Kiểu dữ liệu: int, char, float, long, struct, … Kiểu dữ liệu nào thì sẽ lưu địa chỉ của biến có kiểu dữ liệu tương ứng

Tên biến con trỏ: đặt tên theo quy tắc đặt tên biến

Ví dụ 2:

#include "stdio.h"
#include "conio.h"

void main()
{
    int *x;
    int a = 6;
    x = &a;
    printf("\na = %d", a);
    printf("\n&a = 0x%x", &a);
    printf("\nDia chi cua bien a: 0x%x", x);
    printf("\nGia tri cua bien a: %d", *x);
    printf("\nDia chi cua bien x: 0x%x", &x);
    getch();
}

Kết quả:

Con trỏ trong ngôn ngữ C
Con trỏ trong ngôn ngữ C

Đoạn chương trình trên khai báo biến con trỏ x kiểu nguyên. Lệnh x = &a, con trỏ x chứa địa chỉ của biến a và *x = a = 6. Minh họa như hình sau:

Con trỏ ngôn ngữ C
Con trỏ ngôn ngữ C

Ví dụ 3: 

#include "stdio.h"
#include "conio.h"

void main()
{
    int *x;
    float a = 1.6;
    x = &a;
    getch();
}

Kết quả:

Con trỏ ngôn ngữ lập trình C
Con trỏ ngôn ngữ lập trình C

Chương trình biên dịch báo lỗi vì con trỏ kiểu số nguyên không thể trỏ tới biến kiểu số thực.

Chú ý: Con trỏ chỉ có thể chứa địa chỉ của biến có cùng kiểu dữ liệu với con trỏ.

 

Phép toán thao tác con trỏ

Có 4 nhóm phép toán liên quan đến con trỏ và địa chỉ là: phép gán, phép tăng giảm địa chỉ, phép truy cập bộ nhớ và so sánh.

Phép gán

Chỉ nên thực hiện phép gán giữa các con trỏ cùng kiểu dữ liệu. Nếu muốn thực hiện phép gán giữa các con trỏ có kiểu dữ liệu khác nhau cần phải ép kiểu. Tuy nhiên, để thực hiện ép kiểu bạn phải hiểu rất rõ cách lưu địa chỉ của con trỏ.

Ví dụ 4:

#include "stdio.h"
#include "conio.h"

void main()
{
    float *x, *y;
    float a = 1.6;
    x = &a;
    y = x;
    printf("\na = %f", a);
    printf("\n*x = %f", *x);
    printf("\n*y = %f", *y);

    printf("\n&a = 0x%x", &a);
    printf("\nx = 0x%x", x);
    printf("\ny = 0x%x", y);

    printf("\n&x = 0x%x", &x);
    printf("\n&y = 0x%x", &y);

    getch();
}

Kết quả:

Phép gán con trỏ ngôn ngữ lập trình C
Phép gán con trỏ ngôn ngữ lập trình C

Câu lệnh y = x tương đương y = x = &a. Lúc này, biến con trỏ y cũng chứa địa chỉ của biến a hay ta nói con trỏ y trỏ tới biến a. *x và *y chính là giá trị của biến a (*x = *y = a = 1.6). Hình sau mô tả địa chỉ và giá trị của biến a, biến con trỏ x, y.

Phép gán con trỏ ngôn ngữ lập trình C
Phép gán con trỏ ngôn ngữ lập trình C

Chú ý: Giá trị địa chỉ của các biến là số nguyên (kích thước 4 byte (Windows 32bit, Visual Studio 2008 Professional SP1)).

Phép tăng giảm địa chỉ

Dùng toán tử ++, —

Ví dụ 5:

#include "stdio.h"
#include "conio.h"

void main()
{
    float A[]= {1.2, 2.5, 3.6, 4.8};
    float *p;
    p = A;
    printf("\nGia tri *(p+0) = %f", *p);
    p++;
    printf("\nGia tri *(p+1) = %f", *p);
    getch();
}

Kết quả:

Phép tăng giảm con trỏ ngôn ngữ lập trình C
Phép tăng giảm con trỏ ngôn ngữ lập trình C

Khi khai báo 1 mảng thì tên mảng A chính là địa chỉ của phần tử đầu tiên của mảng; các phần tử của mảng int sẽ được lưu tại các ô nhớ liền kề nhau có kích thước 4 byte.

Câu lệnh gán p = A, con trỏ p trỏ tới phần tử đầu tiên của mảng và *p = A[0] = 1.2

Câu lệnh p++ tương đương với câu lệnh p = p+1 di chuyển con trỏ p tới 1×4(byte) (phần tử thứ 2 của mảng A) và *p = A[1] = 2.5.

Chú ý: Nếu p = p + k (k > 3), chương trình sẽ gây lỗi vì con trỏ p truy cập tới phần tử không xác định.

Hình sau minh họa hoạt động tăng giảm của biến con trỏ p. Việc thay đổi giá trị p chỉ làm thay đổi địa chỉ vùng nhớ mà con trỏ p trỏ tới mà không thay đổi địa chỉ của biến con trỏ p.

Mảng và con trỏ
Mảng và con trỏ

Ví dụ 6:

#include "stdio.h"
#include "conio.h"

void main()
{
    char s[]= "vncoding";
    char *p;
    p = s;
    printf("\n*p = %c", *p);
    printf("\np = 0x%x", p);
    printf("\n&p = 0x%x", &p);

    p = p + 4;
    printf("\n\n*p = %c", *p);
    printf("\np = 0x%x", p);
    printf("\n&p = 0x%x", &p);

    getch();
}

Kết quả:

Phép tăng giảm con trỏ
Phép tăng giảm con trỏ

Như bạn đã biết, khi khai báo 1 mảng thì tên mảng s chính là địa chỉ của phần tử đầu tiên của mảng; các phần tử của mảng char sẽ được lưu tại các ô nhớ liền kề nhau có kích thước 1 byte.

Câu lệnh gán p = s, con trỏ p trỏ tới phần tử đầu tiên của mảng và *p = s[0] = ‘v’

Câu lệnh p = p+4, di chuyển con trỏ p tới 4×1(byte) tiếp theo (phần tử thứ 5 tính từ phần tử ‘v’) và *p = s[4] = ‘d’

Phép tăng giảm con trỏ ngôn ngữ C
Phép tăng giảm con trỏ ngôn ngữ C

Nguyên tắc truy cập vùng nhớ

Con trỏ kiểu float truy cập 4 byte, kiểu int truy cập 4 byte, kiểu char truy cập 1 byte,..

float *a;
int *b;
char *c;

Giả sử cả 3 con trỏ a,b,c đang trỏ đến vùng nhớ 1500 thì:

*a sẽ biểu thị vùng nhớ 4 byte liên tiếp từ 1500 – 1503

*b sẽ biểu thị vùng nhớ 4 byte liên tiếp từ 1500 – 1503

*c sẽ biểu thì vùng nhớ 1 byte từ 1500 – 1501

Ví dụ 8:

#include "stdio.h"
#include "conio.h"

void main()
{
    unsigned int n = 0xABCDEF;// hexa
    unsigned char *c;
    c = (unsigned char*)(&n);
    printf("\n&n = %d", &n);
    printf("\nc = %d", c);
    printf("\nc = %x", *c);
    c++;
    printf("\nc = %d", c);
    printf("\nc = %x", *c);
    c++;
    printf("\nc = %d", c);
    printf("\nc = %x", *c);

    getch();
}

Kết quả:

Nguyên tắc truy cập bộ nhớ dùng con trỏ ngôn ngữ C
Nguyên tắc truy cập bộ nhớ dùng con trỏ ngôn ngữ C

Giá trị n được lưu trên vùng nhớ như sau:

Nguyên tắc truy cập vùng nhớ dùng con trỏ C
Nguyên tắc truy cập vùng nhớ dùng con trỏ C

Kiểu số nguyên không dấu có kích thước 4 byte (windows 32bit). Vì con trỏ c là kiểu unsigned char, nên c truy cập 1 byte đầu tiên của n nên *c = 0xEF và *(c++) = 0xCD.

Phép so sánh 2 con trỏ

Cho phép so sánh 2 con trỏ p1,p2 cùng kiểu dữ liệu

p1 < p2 nếu địa chỉ p1 trỏ tới thấp hơn địa chỉ p2 trỏ tới

p1 = p2 nếu địa chỉ p1 trỏ tới bằng địa chỉ p2 trỏ tới

p1 > p2 nếu địa chỉ p1 trỏ tới cao hơn địa chỉ p2 trỏ tới

Mối liên hệ giữa con trỏ và mảng 1 chiều

Ví dụ 9:

#include "stdio.h"
#include "conio.h"

void main()
{
    int A[] = {1, 2, 3, 4, 5};
    int i; //duyet mang
    for(i = 0; i < 5; i++)
        printf("\nA[%d] = %d", i, *(A+i));
    getch();
}

Kết quả:

Mối liên hệ giữa mảng 1 chiều và con trỏ
Mối liên hệ giữa mảng 1 chiều và con trỏ

Ghi nhớ: A là địa chỉ của phần tử đầu tiên của mảng A, A+1 : địa chỉ của phần tử thứ 2,…

Giá trị của các phần tử của mảng:

*(A+0) = A[0] = 1

*(A+1) = A[1] = 2

*(A+4) = A[4] = 5

Vậy : Muốn lấy giá trị của phần tử trong mảng 1 chiều ta có thể dùng: *(A+i) , i là chỉ số mảng.

 

Mối liên hệ giữa con trỏ và mảng 2 chiều

Ví dụ 10:


#include "stdio.h"
#include "conio.h"
#define N 4
#define M 3

void main()
{
    int A[N][M] = {1, 2, 3,
                   4, 0, 5,
                   8, 9, 2,
                   3, 9, 3};
    int i, j;
    for(i = 0; i < N; i++)
        for(j = 0; j < M; j++)
        {
            printf("\nA[%d][%d] = %d", i, j, *(*(A+i)+j));
            printf("\nDia chi phan tu %d = %d", i+j, *(A+i)+j);
        }
    getch();
}

Kết quả:

Mối liên hệ giữa con trỏ và mảng 2 chiều
Mối liên hệ giữa con trỏ và mảng 2 chiều

Để lấy giá trị của các phần tử mảng 2 chiều ta dùng biểu thức sau: *(*(A+i)+j) và để lấy địa chỉ của phần tử mảng 2 chiều ta dùng biểu thức sau: *(A+i)+j. (trong đó i,j là chỉ số hàng và cột)

 

1 Comment on Con trỏ

Leave a Reply