Con trỏ trỏ tới một phần tử?

Chào mọi người
Mình đọc tài liệu thì họ có ghi như thế này:

float a[30], *p;
p = a;

4 cách viết sau có tác dụng như nhau:

a[i],   *(a+i),    *(p+i),    p[i]

Vậy sự khác nhau giữa những cách này là gì, mình không hiểu.

Ví dụ:
a[i] :

  • a là array
  • i là index của array
    => suy ra a[i] ý nghĩa là các phần tử của mảng

Vậy còn những cái còn lại, các bạn có thể giải thích rõ hơn cho mình được không ạ.
Xin cảm ơn

1 Like

biến a chính là địa chỉ bắt đầu của mảng, vì vậy giá trị tại địa chỉ a cũng là giá trị a[0]. Vì các phần tử trong mảng có địa chỉ liên tiếp nhau nên muốn lấy giá trị a[1] thì chỉ cần lấy giá trị tại địa chỉ a + 1, để truy xuất đến 1 phần tử thứ i thì cũng đồng nghĩa là lấy giá trị tại địa chỉ a + i, vì vậy mới có cú pháp *(a+i)
p = a; thực chất là gán địa chỉ của a[0] vào con trỏ p. Phần tử a[1] có địa chỉ nằm sau a[0] như vậy chỉ cần lấy gái trị địa chỉ trong con trỏ p + 1 sẽ có được địa chỉ của a[1], và muốn lấy giá trị đó ra thì dùng *(p+1). Tương tự với các phần tử tiếp theo vì vậy ta dùng cú pháp *(p+i) để lấy các giá trị tương tự như a[i] trong vòng lặp.

4 Likes

Cảm ơn bạn, nhưng mình thấy là bạn mới chỉ giải thích sự giống nhau giữa các cách viết về truy xuất dữ liệu trong mảng.

Vậy còn sự khác nhau giữa chúng? Ý nghĩa của chúng là lấy các phần tử trong mảng, vậy tại sao không xài luôn cái a[i] mà lại sinh ra nhiều cách như vậy ạ.

1 Like

Có thể áp dụng vào 1 số trick, ví dụ như mảng có index âm (giống Pascal):

int a[11] = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};
int *p = a+5; // hoặc có thể gán bằng &a[5];
int x = p[-2];

Trong 4 cách viết trên thì a[i] giống *(a+i) (do bạn khai báo a[] bản chất vẫn là khai báo *a với 1 số các phần tử đằng sau xếp “liền” a[0]), p[i] giống *(p+i). 4 cách viết này chỉ giống nhau khi p == a thôi.

Thêm nữa *a = *(a + 0) = a[0].

9 Likes

Đầu tiên là a[i], p[i] vs *(a+i), *(p+i): Thật ra thì hầu như chả có ai dùng cách sau cả, người ta chỉ dùng a[i] mà thôi. Chỉ có thành phần thích thể hiện, hoặc là không biết đang viết gì mới dùng tới cách *(a+i) thôi bạn.

Còn giờ tại sao lại có p = a rồi p[i] vs a[i]. Chỗ này thật ra là tùy nhu cầu, nhưng dùng kiểu alias như vầy thì hiếm. Kiểu như sau thì có vẻ hay gặp hơn:

   int a[10];
   // Khởi tạo cho a
   ...
   int * p = a;
   p++;

   for (int i=0; i<9; ++i)
      check(a[i], p[i]); // Xét 2 phần tử kề nhau trong mảng

Cuối cùng là, tại sao đã có a[i] rồi lại có *(a+i). Thật ra thì *(a+i) là có trước, đây là kiểu lập trình gần với phần cứng. Sau đó, nhìn thấy code *(a+i) “xấu”, nên người ta mới sinh ra a[i] nhìn “đẹp” hơn nhiều.

7 Likes

Còn có i[a], i[p] nữa. :smiley:

8 Likes
  int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  int* p;

  p = &arr[5];         // gán địa chỉ con trỏ tại phần tử thứ 5

  printf("*p = %d \n", *p);               // 5
  printf("*(p+1) = %d \n", *(p+1));  // 6
  printf("*(p-1) = %d", *(p-1));        // 4

Ví dụ bạn có 1 cái mảng, vì để giải quyết vấn đề gì đó nên bạn phải bắt đầu ở 1 vị trí không phải vị trí đầu tiên của mảng. Cách *(p+i) với i là số dương hoặc âm sẽ giúp cho bạn có thể tiến hoặc lùi trong mảng, bên cạnh đó, cách sử dụng p[i] kết hợp với vài phép tính vị trí vẫn giải quyết được bài toán. Lập trình mà người ta sinh ra nhiều cách để thuận tiện hơn thôi, ai thích cách nào thì dùng cách đó.

6 Likes

Mình hiểu rồi, cảm ơn mọi người đã giúp đỡ :yum:
Nhân tiện đoạn code này

Mình được biết là trong một mảng thì các phần tử được cấp phát 1 địa chỉ bộ nhớ liên tiếp nhau. Ví dụ
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005

Nhưng khi thay đổi cho hiển thị địa chỉ bộ nhớ thì mình thấy là nó cách nhau 4 byte. Vậy là trong bộ nhớ, với kiểu dữ liệu int thì các địa chỉ lưu cách nhau 4 byte như thế này phải không ạ.

0x0000, 0x0004, 0x0008, 0x0012, 0x0016, 0x0020

1 Like

khoảng cách địa chỉ của các phần tử trong mảng phụ thuộc vào kiểu dữ liệu của mảng, nếu mảng char thì các phần tử cách nhau 1 byte, mảng long thì cách nhau 8 byte.

4 Likes

Cộng 2 số hệ hexa 0x8 + 0x4 = 0xC = 12(10) nha bạn.

4 Likes

Cảm ơn mọi người :smiling_face_with_three_hearts:

1 Like

Đúng là thanh niên chưa trải sự đời

1 Like

Vậy, vãn bối xin cúi đầu thỉnh tiền bối khai sáng giúp những trường hợp nên dùng tới *(a+i) với ạ? :innocent:

2 Likes
// Binary Serialization
    uint8_t receivedData[225] = {20,0,0,0,12,0,0,0,10,0,0,0,22,0,0,0,23,0,0,0,...};
    int* length = (int*)(receivedData);
    std::cout << *length << std::endl;
    
    for (int i = 0; i < *length; i++) {
        A* a = (A*)(receivedData + sizeof(int) + i*sizeof(A)); //Đây nó đây
        std::cout << "A[" << i << "] = " << std::endl;
        std::cout << a->w << std::endl;
        std::cout << a->h << std::endl;
    }

A* a = (A*)(receivedData + sizeof(int) + i*sizeof(A)); //Đây nó đây

Có gì sai không.

3 Likes

Khác kiểu trên đều là int.
Ép khác kiểu con trỏ cũng có nhưng thường dùng khi bắt buộc phải ép nếu không sẽ không tương thích hoặc khó khăn trong tính toán.

Còn đang yên lành thì …

2 Likes

Tiền bối an minh thần võ, xin nhận của vãn bối một lạy.

Vãn bối nghe lỏm thế gian đồn đại là casting trực tiếp là một điều cấm kỵ trên giang hồ, nhưng lần này lại được chứng kiến ở đây, chắc là có huyền cơ gì đó vãn bối chưa rõ nên mạn phép mượn dùng skill casting này trước. Vãn bối ngu muội, xưa giờ đúng là chỉ biết dùng cách “chưa trải sự đời” như sau mà thôi, kính nhờ tiền bối chỉ điểm bến mê:

// Binary Serialization
    struct X {
         int length;
         A data[1];
    };
    uint8_t receivedData[225] = {20,0,0,0,12,0,0,0,10,0,0,0,22,0,0,0,23,0,0,0,...};
    X* x = receivedData;
    std::cout << x->length << std::endl;
    
    for (int i = 0; i < x->length; i++) {
        std::cout << "A[" << i << "] = " << std::endl;
        std::cout << x->data[i].w << std::endl;
        std::cout << x->data[i].h << std::endl;
    }

Ngoài ra, nếu mà tiền bối e ngại padding, vãn bối có skill “còn chưa bước chân ra khỏi cửa” như sau:

// Binary Serialization
    uint8_t receivedData[225] = {20,0,0,0,12,0,0,0,10,0,0,0,22,0,0,0,23,0,0,0,...};
    int* length = (int*)(receivedData);
    std::cout << *length << std::endl;
    
   A* a = receivedData + sizeof(int);

    for (int i = 0; i < *length; i++) {
        std::cout << "A[" << i << "] = " << std::endl;
        std::cout << a[i].w << std::endl;
        std::cout << a[i].h << std::endl;
    }
7 Likes

mặc dù không thích cái giọng mỉa mai lắm.

rf.read((char *) &rstu[i], sizeof(Student)); 
// tương tự như
rf.read((char *) (rstui + i), sizeof(Student)); 

Phần này thường là Binary Serialization, Data Transfer,

Ngoài ra *(a+i), *(p+i) còn dùng khi thực hiện các phép tính đa chiều phức tạp, người ta ưa nhìn hơn là [] của array.

// dimensions of given matrices.
void multiply(int m1, int m2, int mat1[][2],
              int n1, int n2, int mat2[][2])
{
    int x, i, j;
    int res[m1][n2];
    for (i = 0; i < m1; i++)
    {
        for (j = 0; j < n2; j++)
        {
            res[i][j] = 0;
            for (x = 0; x < m2; x++)
            {
                *(*(res + i) + j) += *(*(mat1 + i) + x) *
                                     *(*(mat2 + x) + j);
            }
        }
    }
}

Mình không phân biệt cách dùng, kiểu nào cũng dc, nhưng áp đặt suy nghĩ

Chỉ có thành phần thích thể hiện, hoặc là không biết đang viết gì mới dùng tới cách *(a+i) thôi bạn

thấy ngứa mắt nên comment thôi.

P.S thêm
có lẽ bạn chưa bao giờ gặp kiểu gán pointer trực tiếp vào địa chỉ vùng nhớ đâu

int *p = (int*) 0x00001234;
2 Likes

đừng lấy code của g4g lên mà khè, code trên trang đó toàn lởm =] được cái bài nào cũng có, đọc hiểu thuật toán rồi tự code chứ đừng xem code của nó, code siêu lởm =]

cái code trên dòng phía trên xài res[i][j] xuống dưới lại viết như thằng tâm thần, chắc tác giả cố tình để ai copy thì sẽ thấy chỗ vô lý ngay, biết là đồ copy chứ chả hiểu gì đó mà =]

// C++ C++ C++ C++ C++ program to multiply two 
// rectangular matrices 
...
void multiply(int m1, int m2, 
int res[m1][n2]; 
int res[m1][n2]; 
int res[m1][n2]; 
int res[m1][n2]; 
int res[m1][n2]; 

REEEEEEEEEEEEE =]]]]]]]]

3 Likes

Hahaha, vậy mình sẽ không dùng giọng kiếm hiệp nữa, bật nghiêm túc mode.

Nhìn PS thì mạo muội đoán bạn biết code nhúng đi. Nếu mà code C cho nhúng á, thì thay vì đọc g4g, bạn hãy đọc Misra C rồi quay lại đây bàn tiếp nha bạn.

Còn nếu mà code C++ á, thì bây giờ 99,99% là không còn raw pointer với plain array nữa đâu, đừng nói chi tới mấy cái + với * vớ vẫn này. Vậy nên, bạn đừng giả bộ mình nằm trong nhóm 0.01% nữa nha.

4 Likes

*(a + i) có thể xài ở trong thư viện người ta viết chứ code bình thường hiếm khi xài :V

https://en.cppreference.com/w/cpp/algorithm/sort


doc vẫn ghi kiểu *(a+i) kia chứng tỏ implementation có thể xài kiểu truy cập cho random_iterator theo kiểu *(first + i) đó thay vì first[i]. Có lẽ viết *(first + i) để thấy first ko phải là mảng, trong khi first[i] thì nghĩ ngay first là mảng.


ctrl + F *( thấy có rất nhiều.

gcc cũng thế:

chắc viết thư viện thì xài, còn lại chắc ko ai xài *(a+i) khi a là mảng

mặc dù https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator có viết

The following expressions must be valid and have their specified effects
i[n] === *(i + n)

nhưng implement thư viện chuẩn kia ko thấy ai xài :V :V :V

8 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?