Lập trình C: Bài 12 – Mối quan hệ giữa con trỏ và mảng, chuỗi ký tự

1. Con trỏ và mảng 1 chiều.

Như trong bài Mảng chúng ta đã biết ta có thể coi biến mảng như một con trỏ, vì vậy ta có thể sử dụng chính biến mảng đó để truy cập mảng theo cách của con trỏ.

//code by nguyenvanquan7826
#include <stdio.h>

void nhapMang(int a[], int n) 
{
    int i;
    for (i = 0; i < n; i++) 
    {
        printf("Nhap a[%d] = ", i);
        scanf("%d", &a[i]);
    }
}

void nhapContro(int a[], int n)
{
    int i;
    for (i = 0; i < n; i++) 
    {
        printf("Nhap a[%d] = ", i);
        scanf("%d", a + i);
    }
}

void xuatMang(int a[], int n) 
{
    int i;
    for (i = 0; i < n; i++) 
    {
        printf ("%d \t", a[i]);
    }
}

int main() 
{
    // khai bao mang a co n phan tu
    int n = 5;
    int a[n];
    nhapContro(a, n);
    xuatMang(a, n);

    return 0;
}

Ở hàm thứ nhất ta đã quen với cách nhập bình thường, mình sẽ không nói nhiều nữa.

Ở hàm thứ hai, chúng ta chỉ thay &a bằng a+i vì khi khai báo a[20] thì a coi như là một con trỏ và máy sẽ cấp phát cho ta các ô nhớ liên tiếp từ a đến a + 19. Và a + i là địa chỉ của a[i] (tức nó tương đương với &a[i]). a trỏ đến vị trí a[0].

Con trỏ và mảng trong c

Con trỏ và mảng trong C

Ngoài ra bạn có thể khai báo 1 mảng sau đó dùng 1 con trỏ trỏ tới đầu mảng thì con trỏ đó cũng trở thành mảng đó.

//code by nguyenvanquan7826
#include <stdio.h>

int main() 
{
    int n = 5, i;
    int a[n], *pa;
    pa = a; // con tro pa tro toi dau mang a

    for (i = 0; i < n; i++) 
    {
        printf("Nhap a[%d] = ", i);
        scanf("%d", pa + i);
    }

    for (i = 0; i < n; i++) 
    {
        printf ("%d \t", *(pa + i));
    }

    return 0;
}

Các bạn chú ý: tại sao ta không cần cấp phát ô nhớ cho con trỏ pa mà vẫn sử dụng được bình thường, bởi vì ta đã khai báo mảng a[20] nên máy đã cấp phát ô nhớ để lưu trữ mảng a, khi ta thực hiện trỏ con trỏ pa tới mảng a thì các ô nhớ này đã có rồi nên không cần cấp phát ô nhớ cho pa nữa. Ta xét ví dụ sau:

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    int n = 5, i;

    // cap phat bo nho cho pa
    int *pa = (int *) malloc(n * sizeof(int));

    for (i = 0; i < n; i++) 
    {
        printf("Nhap a[%d] = ", i);
        scanf("%d", pa + i);
    }

    for (i = 0; i < n; i++) 
    {
        printf ("%d \t", *(pa + i));
    }

    return 0;
}

Ở trong VD này ta không khai báo mảng a như VD trước, do đó cũng không thể trỏ pa tới vị trí nào, mà muốn thực hiện được ta cần cấp phát các ô nhớ cho pa như trên.

2. Nhập mảng trong hàm

Việc nhập mảng không phải lúc nào cũng thuận lợi vì như các ví dụ trước chúng ta cần phải có số lượng phần tử của mảng trước khi cấp phát hoặc nhập, vậy nếu chúng ta chưa biết trước số phần tử mà lại phải dùng hàm để nhập mảng thì sao. Các bạn xem ví dụ sau.

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

void nhapContro(int *(*a), int *n) 
{
    int i;

    printf("Nhap so phan tu cua mang: ");
    scanf("%d", n); // khong phai &n
    *a = (int *) malloc ((*n) * sizeof(int));
    // *a : lay dia chi cua mang a chu khong phai gia tri cua a

    for (i = 0; i < *n; i++) 
    {
        printf("Nhap a[%d] = ", i);
        scanf("%d", (*a + i));
    }
}

void xuatMang(int *a, int n) 
{
    int i;
    for (i = 0; i < n; i++) 
    {
        printf ("%d \t", a[i]);
    }
}

int main() 
{
    int *a, n;

    nhapContro(&a, &n); // lay dia chi cua a va n
    xuatMang(a, n);

    return 0;
}

Trong VD này ta thực hiện nhập và xuất mảng trong hàm, cấp phát bộ nhớ cũng trong hàm luôn. Các chú thích mình đã ghi rõ trong chương trình rồi.

Có 1 điểm chú ý là trong hàm nhập mảng a bằng con trỏ thì có 2 dấu *. 1 dấu là của mảng a (dấu thứ 2), còn dấu đầu tiên là dùng để truyền địa chỉ làm giá trị của mảng có thể giữ nguyên khi ra khỏi hàm, nó giống như là dấu * trong hàm HoanVi(int *a,int *b) vậy.

3.Con trỏ và xâu ký tự

Do xâu ký tự bản chất cũng là mảng các ký tự nên phần này nó cũng tương tự như mảng 1 chiều, mình sẽ nói qua 1 chút bằng 1 ví dụ đơn giản.

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    char *name;
    name = (char *) malloc (100*sizeof(char));

    printf("What your name? ");
    gets(name);

    printf("Oh, Hello %s\n", name);

    return 0;
}

4. Con trỏ và mảng 2 chiều, mảng các con trỏ – Con trỏ đa cấp

a. Con trỏ và mảng 2 chiều

Phần trên chúng ta đã tìm hiểu về con trỏ và mảng 1 chiều, và phần này con trỏ và mảng 2 chiều cũng tương tự như vậy.

Như ta đã biết thực chất trong máy tính thì bộ nhớ lưu mảng 2 chiều giống như mảng 1 chiều. Vì vậy ta hoàn toàn có thể biểu diễn mảng 2 chiều bằng con trỏ giống như mảng 1 chiều.

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    double a[10][10], *pa;
    int n, m, i;
    pa = (double *) a;
    printf("Nhap so hang va so cot:\n");
    scanf("%d %d", &n, &m);

    for (i = 0 ; i < n * m; i++) 
    {
        printf("Nhap a[%d][%d] = ", i / m, i % m);
        scanf("%lf", pa + i);
    }

    for (i = 0 ; i < n * m; i++) 
    {
        if (i % m == 0) printf("\n"); // xuong dong
        printf("%-5.2lf", *(pa + i));
    }

    return 0;
}

Kết quả:

Nhap so hang va so cot:
2
3
Nhap a[0][0] = 4.23
Nhap a[0][1] = 5.7
Nhap a[0][2] = 1.2
Nhap a[1][0] = 8.6
Nhap a[1][1] = 3.456
Nhap a[1][2] = 12

4.23 5.70 1.20
8.60 3.46 12.00

Ngoài ra, chúng ta có thể không cần dùng pa mà dùng ngay a là 1 con trỏ. Hoặc ta có thể nhập 1 cách tương tự như mảng 2 chiều bình thường như sau.

Các bạn lưu ý là tại sao ta lại có pa + i*10 + j. Đó là vì ở hàm main() ta khai báo là a[10][10] nên máy cấp phát cho chúng ta các ô nhớ của mảng 2 chiều với 10 hàng, 10 cột mà nếu ta ko dùng hết thì nó vẫn tồn tại.

Con trỏ và mảng trong c

Con trỏ và mảng 2 chiều trong C

Các bạn chú ý là ta có thể viết a[i][j] nhưng không thể viết pa[i][j] vì theo khai báo thì a là mảng 2 chiều nên ta có thể viết như vậy còn pa là 1 con trỏ và khi ta gán pa = a thì ta đã ngầm định coi a là mảng 1 chiều. Vì vậy nếu có thì chúng ta cũng chỉ được phép viết pa[i10+j] (lấy giá trị) và &pa[i10+j] (lấy địa chỉ).

b. Mảng các con trỏ

Mảng con trỏ là một mảng chứa tập hợp các con trỏ cùng một kiểu.
Float *a[10]; // khai báo một mảng con trỏ. Gồm 10 con trỏ: a[0], a[1], …a[9]; là 10 con trỏ.
Liên hệ với mảng 2 chiều thì ta có nhận xét sau: Mỗi hàng của mảng 2 chiều ta coi như 1 mảng 1 chiều. Mà mỗi con trỏ thì lại có mối quan hệ với 1 mảng 1 chiều, ta có hình vẽ:

Con trỏ và mảng trong c

Con trỏ và mảng 2 chiều trong C

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    double a[10][10], *pa[10];
    int n, m, i, j;

    printf("Nhap so hang va so cot: ");
    scanf("%d %d", &n, &m);

    for (i = 0 ; i < n; i++) 
    {
        pa[i] = a[i]; // con tro thu i tro den hang thu i
        for (j = 0 ; j < m; j++) 
        {
            printf("Nhap a[%d][%d] = ", i, j);
            scanf("%lf", &pa[i][j]);
        }
    }

    for (i = 0 ; i < n; i++) 
    {
        printf("\n"); // xuong dong
        for (j = 0 ; j < m; j++) 
        {
            printf("%-5.2lf", pa[i][j]);
        }
    }

    return 0;
}

Sở dĩ ở VD này ta viết được pa[i][j] đó là do mỗi pa là 1 con trỏ đến mảng một chiều. pa[i][j] tức là phần tử thứ j của con trỏ pa[i].

Các ví dụ ở trên chúng ta đều xét khi mà khai báo mảng a[][] nên không cần cấp phát bộ nhớ cho con trỏ, bây giờ muốn cấp phát bộ nhớ cho con trỏ với mảng 2 chiều giống như cấp phát ở mảng 1 chiều thì ta làm như sau:

//code by nguyenvanquan7826
#include <stdio.h>
#include <stdlib.h>

int main() 
{
    double **pa;
    int n, m, i, j;

    printf("Nhap so hang va so cot: ");
    scanf("%d %d", &n, &m);

    // cap phat n o nho cho n con tro (n hang)
    pa = (double**) malloc(n * sizeof(double));

    for (i = 0 ; i < n; i++) 
    {
        // cap phat m o nho cho moi con tro (moi hang)
        pa[i] = (double *) malloc(m * sizeof(double));
        for (j = 0 ; j < m; j++) 
        {
            printf("Nhap a[%d][%d] = ", i, j);
            scanf("%lf", &pa[i][j]);
        }
    }

    for (i = 0 ; i < n; i++) 
    {
        printf("\n"); // xuong dong
        for (j = 0 ; j < m; j++) 
        {
            printf("%-5.2lf", pa[i][j]);
        }
    }

    return 0;
}

Đây cũng có thể coi là mảng con trỏ, hoặc con trỏ trỏ đến con trỏ (con trỏ đa cấp).