Tại sao nên sử dụng con trỏ? [đóng cửa]


356

Tôi biết đây là một câu hỏi thực sự cơ bản, nhưng tôi mới bắt đầu với một số chương trình C ++ cơ bản sau khi mã hóa một vài dự án với các ngôn ngữ cấp cao.

Về cơ bản tôi có ba câu hỏi:

  1. Tại sao sử dụng con trỏ trên các biến bình thường?
  2. Tôi nên sử dụng con trỏ khi nào và ở đâu?
  3. Làm thế nào để bạn sử dụng con trỏ với mảng?

5
Thảo luận trước tại câu hỏi này Hy vọng rằng sẽ giúp!
Doug T.

4
Để biết danh sách các cuốn sách, hãy xem stackoverflow.com/questions/388242/ . Sau Java, tôi thấy Tăng tốc C ++ rất hữu ích.
Jabba

163
Chúng tôi sử dụng con trỏ bởi vì việc cung cấp cho ai đó một địa chỉ đến nhà bạn dễ dàng hơn là đưa một bản sao nhà của bạn cho mọi người.
Rishi Dua

14
@RishiDua Đó là lời giải thích tốt nhất về một con trỏ tôi từng gặp. Cảm ơn bạn vì điều đó, nó đã tăng sự hiểu biết của tôi :)
Josh

2
Con trỏ cũng có thể được sử dụng khi bạn muốn trả về nhiều hơn một giá trị / đối tượng.
Gaurav

Câu trả lời:


172
  • Tại sao sử dụng con trỏ trên các biến bình thường?

Câu trả lời ngắn gọn là: Đừng. ;-) Con trỏ sẽ được sử dụng khi bạn không thể sử dụng bất cứ thứ gì khác. Đó là do thiếu chức năng phù hợp, thiếu các loại dữ liệu hoặc cho sự hoàn hảo. Dưới đây ...

  • Tôi nên sử dụng con trỏ khi nào và ở đâu?

Câu trả lời ngắn ở đây là: Nơi bạn không thể sử dụng bất cứ thứ gì khác. Trong C, bạn không có bất kỳ sự hỗ trợ nào cho các kiểu dữ liệu phức tạp, chẳng hạn như một chuỗi. Cũng không có cách nào để chuyển một biến "bằng cách tham chiếu" cho một hàm. Đó là nơi bạn phải sử dụng con trỏ. Ngoài ra, bạn có thể yêu cầu họ chỉ vào hầu hết mọi thứ, danh sách được liên kết, các thành viên của cấu trúc, v.v. Nhưng chúng ta đừng đi sâu vào vấn đề đó ở đây.

  • Làm thế nào để bạn sử dụng con trỏ với mảng?

Với ít nỗ lực và nhiều nhầm lẫn. ;-) Nếu chúng ta nói về các kiểu dữ liệu đơn giản như int và char thì có rất ít sự khác biệt giữa một mảng và một con trỏ. Các khai báo này rất giống nhau (nhưng không giống nhau - ví dụ: sizeofsẽ trả về các giá trị khác nhau):

char* a = "Hello";
char a[] = "Hello";

Bạn có thể tiếp cận bất kỳ phần tử nào trong mảng như thế này

printf("Second char is: %c", a[1]);

Chỉ số 1 vì mảng bắt đầu bằng phần tử 0. :-)

Hoặc bạn cũng có thể làm điều này

printf("Second char is: %c", *(a+1));

Toán tử con trỏ (*) là cần thiết vì chúng ta đang nói với printf rằng chúng ta muốn in một ký tự. Nếu không có dấu *, đại diện ký tự của chính địa chỉ bộ nhớ sẽ được in. Bây giờ chúng tôi đang sử dụng chính nhân vật. Nếu chúng tôi đã sử dụng% s thay vì% c, chúng tôi sẽ yêu cầu printf in nội dung của địa chỉ bộ nhớ được chỉ ra bởi 'a' cộng với một (trong ví dụ này ở trên) và chúng tôi sẽ không phải đặt * ở phía trước:

printf("Second char is: %s", (a+1)); /* WRONG */

Nhưng điều này sẽ không chỉ in ký tự thứ hai, mà thay vào đó, tất cả các ký tự trong địa chỉ bộ nhớ tiếp theo, cho đến khi tìm thấy ký tự null (\ 0). Và đây là nơi mọi thứ bắt đầu trở nên nguy hiểm. Điều gì xảy ra nếu bạn vô tình thử và in một biến của số nguyên loại thay vì một con trỏ char với định dạng% s?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

Điều này sẽ in bất cứ thứ gì được tìm thấy trên địa chỉ bộ nhớ 120 và tiếp tục in cho đến khi tìm thấy một ký tự null. Thật sai lầm và bất hợp pháp khi thực hiện câu lệnh printf này, nhưng dù sao nó cũng có thể hoạt động, vì một con trỏ thực sự thuộc kiểu int trong nhiều môi trường. Hãy tưởng tượng các vấn đề bạn có thể gây ra nếu bạn sử dụng sprintf () thay vào đó và gán cách này quá dài "mảng char" cho một biến khác, chỉ có một không gian giới hạn nhất định được phân bổ. Rất có thể bạn sẽ kết thúc việc viết lên một thứ khác trong bộ nhớ và khiến chương trình của bạn bị sập (nếu bạn may mắn).

Ồ, và nếu bạn không gán giá trị chuỗi cho mảng / con trỏ char khi bạn khai báo, bạn PHẢI phân bổ đủ lượng bộ nhớ cho nó trước khi đưa ra giá trị. Sử dụng malloc, calloc hoặc tương tự. Điều này vì bạn chỉ khai báo một phần tử trong mảng / một địa chỉ bộ nhớ duy nhất để trỏ đến. Vì vậy, đây là một vài ví dụ:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

Xin lưu ý rằng bạn vẫn có thể sử dụng biến x sau khi bạn đã thực hiện miễn phí () bộ nhớ được phân bổ, nhưng bạn không biết có gì trong đó. Cũng lưu ý rằng hai printf () có thể cung cấp cho bạn các địa chỉ khác nhau, vì không có gì đảm bảo rằng việc phân bổ bộ nhớ thứ hai được thực hiện trong cùng một không gian như địa chỉ thứ nhất.


8
Ví dụ của bạn với 120 trong đó là sai. Đó là sử dụng% c chứ không phải% s nên không có lỗi; nó chỉ đơn thuần là in chữ thường x. Hơn nữa, tuyên bố tiếp theo của bạn rằng một con trỏ là kiểu int là sai và lời khuyên rất tệ để cung cấp cho một lập trình viên C thiếu kinh nghiệm với các con trỏ.
R .. GitHub DỪNG GIÚP ICE

13
-1 Bạn đã bắt đầu tốt nhưng ví dụ đầu tiên là sai. Không, nó không giống nhau. Trong trường hợp đầu tiên alà một con trỏ và trong trường hợp thứ hai alà một mảng. Tôi đã đề cập đến nó chưa? Nó không giống nhau! Tự kiểm tra: So sánh sizeof (a), thử gán địa chỉ mới cho một mảng. Nó sẽ không hoạt động.
sellibitze

42
char* a = "Hello";char a[] = "Hello";không giống nhau, chúng khá khác nhau. Một tuyên bố một con trỏ khác một mảng. Hãy thử một sizeofvà bạn thấy sự khác biệt.
Patrick Schlüter

12
"Không sai hoặc bất hợp pháp khi thực hiện tuyên bố printf này". Điều này hoàn toàn sai. Đó là tuyên bố printf có hành vi không xác định. Trên nhiều triển khai, nó sẽ gây ra vi phạm truy cập. Con trỏ không thực sự thuộc kiểu int, chúng thực sự là con trỏ (và không có gì khác).
Mankude

19
-1, Bạn đã nhận được rất nhiều sự thật sai ở đây và tôi sẽ nói thẳng rằng bạn không xứng đáng với số lượng upvote hoặc trạng thái câu trả lời được chấp nhận.
asveikau

50

Một lý do để sử dụng các con trỏ là để một biến hoặc một đối tượng có thể được sửa đổi trong một hàm được gọi.

Trong C ++, cách sử dụng tài liệu tham khảo tốt hơn so với con trỏ. Mặc dù các tài liệu tham khảo về cơ bản là con trỏ, C ++ trong một chừng mực nào đó che giấu sự thật và làm cho nó có vẻ như bạn đang vượt qua giá trị. Điều này giúp dễ dàng thay đổi cách hàm gọi nhận giá trị mà không phải sửa đổi ngữ nghĩa của việc truyền nó.

Hãy xem xét các ví dụ sau:

Sử dụng tài liệu tham khảo:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

Sử dụng con trỏ:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

16
Có lẽ một ý tưởng tốt để đề cập rằng các tài liệu tham khảo là an toàn hơn, trong đó bạn không thể vượt qua một tài liệu tham khảo null.
SpoonMeiser

26
Vâng, đó có lẽ là lợi thế lớn nhất của việc sử dụng tài liệu tham khảo. Cảm ơn đã chỉ ra điều đó. Không có ý định chơi chữ :)
trshiv

2
Bạn chắc chắn có thể vượt qua tham chiếu null. Nó chỉ không dễ dàng như vượt qua con trỏ null.
n0rd

1
đồng tình với @ n0rd. Nếu bạn nghĩ rằng bạn không thể vượt qua một tài liệu tham khảo null, bạn đã sai. Dễ dàng vượt qua một tham chiếu lơ lửng hơn tham chiếu null, nhưng cuối cùng bạn có thể làm một cách dễ dàng. Tài liệu tham khảo không có viên đạn bạc nào bảo vệ một kỹ sư tự bắn vào chân mình. Xem nó trực tiếp .
WhozCraig

2
@ n0rd: Làm điều đó rõ ràng là hành vi không xác định.
Daniel Kamil Kozar

42
  1. Con trỏ cho phép bạn tham chiếu đến cùng một không gian trong bộ nhớ từ nhiều vị trí. Điều này có nghĩa là bạn có thể cập nhật bộ nhớ ở một vị trí và có thể thấy sự thay đổi từ một vị trí khác trong chương trình của bạn. Bạn cũng sẽ tiết kiệm không gian bằng cách có thể chia sẻ các thành phần trong cấu trúc dữ liệu của bạn.
  2. Bạn nên sử dụng con trỏ bất kỳ nơi nào bạn cần lấy và chuyển xung quanh địa chỉ đến một vị trí cụ thể trong bộ nhớ. Bạn cũng có thể sử dụng các con trỏ để điều hướng các mảng:
  3. Mảng là một khối bộ nhớ liền kề đã được phân bổ với một loại cụ thể. Tên của mảng chứa giá trị của điểm bắt đầu của mảng. Khi bạn thêm 1, điều đó sẽ đưa bạn đến vị trí thứ hai. Điều này cho phép bạn viết các vòng lặp làm tăng một con trỏ trượt xuống mảng mà không có bộ đếm rõ ràng để sử dụng trong việc truy cập mảng.

Đây là một ví dụ trong C:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

in

ifmmp

bởi vì nó lấy giá trị cho mỗi ký tự và tăng nó thêm một.


because it takes the value for each character and increments it by one. Là trong đại diện ascii hoặc làm thế nào?
Eduardo S.

28

Con trỏ là một cách để có được một tham chiếu gián tiếp đến một biến khác. Thay vì giữ giá trị của một biến, họ cho bạn biết địa chỉ của nó . Điều này đặc biệt hữu ích khi xử lý các mảng, vì sử dụng một con trỏ tới phần tử đầu tiên trong một mảng (địa chỉ của nó), bạn có thể nhanh chóng tìm thấy phần tử tiếp theo bằng cách tăng con trỏ (đến vị trí địa chỉ tiếp theo).

Lời giải thích tốt nhất của con trỏ và con trỏ số học mà tôi đã đọc là trong K & R của C Programming Language . Một cuốn sách hay để bắt đầu học C ++ là C ++ Primer .


1
cảm ơn bạn! cuối cùng, một lời giải thích thực tế về lợi ích của việc sử dụng stack! một con trỏ đến vị trí trong một mảng cũng cải thiện hiệu suất truy cập các giá trị @ và liên quan đến con trỏ?

Đây có lẽ là lời giải thích tốt nhất mà tôi đã đọc. mọi người có cách "làm quá phức tạp" mọi thứ :)
Detilium

23

Hãy để tôi thử và trả lời điều này quá.

Con trỏ tương tự như tài liệu tham khảo. Nói cách khác, chúng không phải là bản sao, mà là một cách để chỉ giá trị ban đầu.

Trước bất cứ điều gì khác, một nơi mà bạn thường sẽ phải sử dụng con trỏ rất nhiều là khi bạn đang xử lý phần cứng nhúng . Có lẽ bạn cần chuyển trạng thái của chân IO kỹ thuật số. Có thể bạn đang xử lý một ngắt và cần lưu trữ một giá trị tại một vị trí cụ thể. Bạn nhận được hình ảnh. Tuy nhiên, nếu bạn không trực tiếp xử lý phần cứng và chỉ đang phân vân không biết nên sử dụng loại nào, hãy đọc tiếp.

Tại sao sử dụng con trỏ trái ngược với các biến thông thường? Câu trả lời trở nên rõ ràng hơn khi bạn xử lý các loại phức tạp, như các lớp, cấu trúc và mảng. Nếu bạn sử dụng một biến thông thường, cuối cùng bạn có thể tạo một bản sao (trình biên dịch đủ thông minh để ngăn chặn điều này trong một số trường hợp và C ++ 11 cũng giúp, nhưng chúng ta sẽ tránh xa cuộc thảo luận đó ngay bây giờ).

Bây giờ điều gì xảy ra nếu bạn muốn sửa đổi giá trị ban đầu? Bạn có thể sử dụng một cái gì đó như thế này:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

Điều đó sẽ hoạt động tốt và nếu bạn không biết chính xác lý do tại sao bạn sử dụng con trỏ, bạn không nên sử dụng chúng. Coi chừng lý do "có lẽ họ nhanh hơn". Chạy thử nghiệm của riêng bạn và nếu chúng thực sự nhanh hơn, sau đó sử dụng chúng.

Tuy nhiên, giả sử bạn đang giải quyết vấn đề cần phân bổ bộ nhớ. Khi bạn phân bổ bộ nhớ, bạn cần phải phân bổ nó. Việc cấp phát bộ nhớ có thể hoặc không thể thành công. Đây là nơi con trỏ trở nên hữu ích - chúng cho phép bạn kiểm tra sự tồn tại của đối tượng bạn đã phân bổ và chúng cho phép bạn truy cập vào đối tượng mà bộ nhớ được cấp phát bằng cách hủy tham chiếu con trỏ.

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

Đây là chìa khóa lý do tại sao bạn sẽ sử dụng con trỏ - các tham chiếu giả định rằng phần tử bạn tham chiếu đã tồn tại . Một con trỏ không.

Lý do khác khiến bạn sử dụng con trỏ (hoặc ít nhất là cuối cùng phải xử lý chúng) là vì chúng là loại dữ liệu tồn tại trước khi tham chiếu. Do đó, nếu bạn kết thúc việc sử dụng các thư viện để làm những việc mà bạn biết họ giỏi hơn, bạn sẽ thấy rằng rất nhiều thư viện này sử dụng con trỏ ở khắp mọi nơi, đơn giản là vì họ đã ở đây bao lâu (rất nhiều trong số họ đã được viết trước C ++).

Nếu bạn không sử dụng bất kỳ thư viện nào, bạn có thể thiết kế mã theo cách mà bạn có thể tránh xa các con trỏ, nhưng cho rằng các con trỏ là một trong những loại ngôn ngữ cơ bản, bạn càng cảm thấy thoải mái khi sử dụng chúng, thì càng nhanh kỹ năng C ++ của bạn sẽ được.

Từ quan điểm duy trì, tôi cũng nên đề cập rằng khi bạn sử dụng con trỏ, bạn phải kiểm tra tính hợp lệ của chúng và xử lý trường hợp khi chúng không hợp lệ, hoặc, giả sử chúng hợp lệ và chấp nhận sự thật rằng chương trình sẽ sụp đổ hoặc tệ hơn KHI giả định đó bị hỏng. Nói cách khác, lựa chọn của bạn với con trỏ là giới thiệu độ phức tạp của mã hoặc nỗ lực bảo trì nhiều hơn khi có lỗi xảy ra và bạn đang cố gắng theo dõi một lỗi thuộc về cả một loại lỗi mà con trỏ đưa ra, như hỏng bộ nhớ.

Vì vậy, nếu bạn kiểm soát tất cả các mã của mình, hãy tránh xa các con trỏ và thay vào đó sử dụng các tham chiếu, giữ chúng là const khi bạn có thể. Điều này sẽ buộc bạn phải suy nghĩ về thời gian sống của các đối tượng của bạn và cuối cùng sẽ giữ cho mã của bạn dễ hiểu hơn.

Chỉ cần nhớ sự khác biệt này: Một tham chiếu về cơ bản là một con trỏ hợp lệ. Một con trỏ không phải lúc nào cũng hợp lệ.

Vì vậy, tôi có nói rằng không thể tạo ra một tài liệu tham khảo không hợp lệ? Không. Hoàn toàn có thể, bởi vì C ++ cho phép bạn làm hầu hết mọi thứ. Việc làm vô tình chỉ khó hơn và bạn sẽ ngạc nhiên khi có nhiều lỗi vô ý :)


Bạn có thể viết các lớp trình bao bọc đẹp cho IO được ánh xạ bộ nhớ mà về cơ bản bạn có thể tránh sử dụng các con trỏ.
einpoklum

13

Đây là một chút khác biệt, nhưng sâu sắc về lý do tại sao nhiều tính năng của C có ý nghĩa: http://steve.yegge.googlepages.com/tour-de-babel#C

Về cơ bản, kiến ​​trúc CPU tiêu chuẩn là kiến ​​trúc Von Neumann và thật hữu ích khi có thể tham khảo vị trí của một mục dữ liệu trong bộ nhớ và thực hiện số học với nó, trên một máy như vậy. Nếu bạn biết bất kỳ biến thể nào của ngôn ngữ lắp ráp, bạn sẽ nhanh chóng thấy mức độ quan trọng của nó ở mức độ thấp.

C ++ làm cho con trỏ hơi khó hiểu, vì đôi khi nó quản lý chúng cho bạn và che giấu hiệu ứng của chúng dưới dạng "tài liệu tham khảo". Nếu bạn sử dụng C thẳng, nhu cầu về con trỏ rõ ràng hơn nhiều: không có cách nào khác để gọi theo tham chiếu, đó là cách tốt nhất để lưu trữ một chuỗi, đó là cách tốt nhất để lặp qua một mảng, v.v.


12

Một cách sử dụng con trỏ (tôi sẽ không đề cập đến những điều đã được đề cập trong bài đăng của người khác) là truy cập vào bộ nhớ mà bạn chưa phân bổ. Điều này không hữu ích nhiều cho lập trình PC, nhưng nó được sử dụng trong lập trình nhúng để truy cập các thiết bị phần cứng được ánh xạ bộ nhớ.

Quay lại thời kỳ cũ của DOS, bạn đã từng có thể truy cập trực tiếp vào bộ nhớ video của thẻ video bằng cách khai báo một con trỏ tới:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

Nhiều thiết bị nhúng vẫn sử dụng kỹ thuật này.


Không phải là một lý do để sử dụng một con trỏ - một cấu trúc giống như nhịp - cũng giữ độ dài - phù hợp hơn nhiều. Những ngày này gsl::span, và nó sẽ sớm std::span.
einpoklum

10

Phần lớn, con trỏ là mảng (trong C / C ++) - chúng là địa chỉ trong bộ nhớ và có thể được truy cập như một mảng nếu muốn (trong trường hợp "bình thường").

Vì chúng là địa chỉ của một mặt hàng, chúng nhỏ: chúng chỉ chiếm không gian của một địa chỉ. Vì chúng nhỏ, gửi chúng đến một chức năng là rẻ. Và sau đó họ cho phép chức năng đó hoạt động trên vật phẩm thực tế chứ không phải là một bản sao.

Nếu bạn muốn thực hiện phân bổ lưu trữ động (chẳng hạn như cho danh sách được liên kết), bạn phải sử dụng các con trỏ, bởi vì chúng là cách duy nhất để lấy bộ nhớ từ heap.


8
Tôi nghĩ thật sai lầm khi nói con trỏ là mảng. Thực sự, tên mảng là con trỏ const cho phần tử đầu tiên của mảng. Chỉ vì bạn có thể truy cập một điểm tùy ý như thể một mảng không có nghĩa là ... bạn có thể bị vi phạm quyền truy cập :)
rmeador

2
Con trỏ không phải là mảng. Đọc phần 6 của FAQ comp.lang.c .
Keith Thompson

Con trỏ thực sự không phải là mảng. Ngoài ra, cũng không có nhiều sử dụng trong mảng thô - chắc chắn không phải sau C ++ 11 và std::array.
einpoklum

@einpoklum - Con trỏ đủ gần để các mảng tương đương trong các hoạt động tham chiếu và lặp qua các mảng :) .... cũng vậy - C ++ 11 đã được phát hành 3 năm khi câu trả lời này được viết vào năm 2008
warren

@warren: Con trỏ không phải là mảng cũng không gần với mảng, chúng chỉ phân rã thành mảng. Thật không phù hợp về mặt sư phạm để nói với mọi người rằng họ giống nhau. Ngoài ra, bạn chắc chắn có thể có các tham chiếu đến các mảng, không cùng loại với con trỏ và duy trì thông tin kích thước; xem tại đây
einpoklum

9

Con trỏ rất quan trọng trong nhiều cấu trúc dữ liệu mà thiết kế của nó yêu cầu khả năng liên kết hoặc xâu chuỗi một "nút" với một nút khác một cách hiệu quả. Bạn sẽ không "chọn" một con trỏ nói kiểu dữ liệu bình thường như float, đơn giản là chúng có các mục đích khác nhau.

Con trỏ rất hữu ích khi bạn yêu cầu hiệu năng cao và / hoặc dung lượng bộ nhớ nhỏ gọn.

Địa chỉ của phần tử đầu tiên trong mảng của bạn có thể được gán cho một con trỏ. Điều này sau đó cho phép bạn truy cập trực tiếp các byte được phân bổ. Toàn bộ điểm của một mảng là để tránh bạn cần phải làm điều này mặc dù.


9

Một cách để sử dụng con trỏ trên các biến là loại bỏ bộ nhớ trùng lặp cần thiết. Ví dụ: nếu bạn có một số đối tượng phức tạp lớn, bạn có thể sử dụng một con trỏ để trỏ đến biến đó cho mỗi tham chiếu bạn thực hiện. Với một biến, bạn cần nhân đôi bộ nhớ cho mỗi bản sao.


Đó là một lý do để sử dụng tài liệu tham khảo (hoặc nhiều nhất - trình bao bọc tham chiếu), chứ không phải con trỏ.
einpoklum

6

Trong C ++, nếu bạn muốn sử dụng đa hình phụ , bạn phải sử dụng các con trỏ. Xem bài này: Đa hình C ++ không có con trỏ .

Thực sự, khi bạn nghĩ về nó, điều này có ý nghĩa. Cuối cùng, khi bạn sử dụng đa hình loại phụ, bạn sẽ không biết trước việc triển khai phương thức của lớp hoặc lớp con sẽ được gọi vì bạn không biết lớp thực sự là gì.

Ý tưởng về việc có một biến chứa một đối tượng của một lớp không xác định không tương thích với chế độ lưu trữ mặc định (không phải con trỏ) của C ++ trên ngăn xếp, trong đó lượng không gian được phân bổ trực tiếp tương ứng với lớp. Lưu ý: nếu một lớp có 5 trường đối tượng so với 3, sẽ cần phân bổ nhiều không gian hơn.


Lưu ý rằng nếu bạn đang sử dụng '&' để truyền các đối số bằng tham chiếu, thì chỉ định (nghĩa là con trỏ) vẫn có liên quan đến hậu trường. '&' Chỉ là đường cú pháp giúp (1) tiết kiệm cho bạn những rắc rối khi sử dụng cú pháp con trỏ và (2) cho phép trình biên dịch chặt chẽ hơn (chẳng hạn như cấm con trỏ null).


Không, bạn không phải sử dụng con trỏ - bạn có thể sử dụng tài liệu tham khảo. Và khi bạn viết "con trỏ có liên quan đến hậu trường" - điều đó thật vô nghĩa. gotohướng dẫn cũng được sử dụng phía sau hậu trường - trong hướng dẫn của máy đích. Chúng tôi vẫn không yêu cầu sử dụng chúng.
einpoklum

@ einpoklum-rebstateMonica Nếu bạn có một tập hợp các đối tượng mà bạn muốn hành động, lần lượt gán từng phần tử cho một biến tạm thời và gọi một phương thức đa hình trên biến đó, thì bạn CẦN một con trỏ bởi vì bạn không thể đặt lại một tham chiếu.
Sildoreth

Nếu bạn có một tham chiếu lớp cơ sở đến một lớp dẫn xuất và gọi một phương thức ảo trên tham chiếu đó, thì ghi đè của lớp dẫn xuất sẽ được gọi. Tôi có lầm không?
einpoklum

@ einpoklum-rebstateMonica Đúng vậy. Nhưng bạn không thể thay đổi đối tượng được tham chiếu. Vì vậy, nếu bạn đang lặp qua một danh sách / bộ / mảng của các đối tượng đó, một biến tham chiếu sẽ không hoạt động.
Sildoreth

5

Bởi vì sao chép các đối tượng lớn ở khắp mọi nơi làm lãng phí thời gian và bộ nhớ.


4
Và con trỏ giúp với điều đó như thế nào? Tôi nghĩ rằng một người đến từ Java hoặc .Net không biết sự khác biệt giữa stack và heap, vì vậy câu trả lời này khá vô dụng ..
Mats Fredriksson

Bạn có thể vượt qua bằng cách tham khảo. Điều đó sẽ ngăn chặn việc sao chép.
Martin York

1
@MatsFredriksson - Thay vì chuyển (sao chép) cấu trúc dữ liệu lớn và sao chép lại kết quả, bạn chỉ cần trỏ đến vị trí của nó trong RAM và sau đó sửa đổi trực tiếp.
John U

Vì vậy, đừng sao chép các đối tượng lớn. Không ai nói bạn cần con trỏ cho điều đó.
einpoklum

5

Đây là anwser của tôi, và tôi sẽ không trở thành một chuyên gia, nhưng tôi đã tìm thấy những gợi ý tuyệt vời trong một trong những thư viện của tôi mà tôi đang cố gắng viết. Trong thư viện này (Đó là API đồ họa với OpenGL :-)) bạn có thể tạo một hình tam giác với các đối tượng đỉnh được truyền vào chúng. Phương thức vẽ lấy các đối tượng tam giác này, và .. vẽ chúng dựa trên các đối tượng đỉnh tôi đã tạo. Vâng, nó ổn

Nhưng, nếu tôi thay đổi tọa độ đỉnh thì sao? Di chuyển nó hoặc một cái gì đó với moveX () trong lớp đỉnh? Chà, ok, bây giờ tôi phải cập nhật tam giác, thêm nhiều phương thức và hiệu suất đang bị lãng phí vì tôi phải cập nhật tam giác mỗi khi một đỉnh di chuyển. Vẫn không phải là một vấn đề lớn, nhưng nó không phải là tuyệt vời.

Bây giờ, điều gì sẽ xảy ra nếu tôi có một lưới với hàng tấn đỉnh và tấn hình tam giác, và lưới đang xoay, và di chuyển, và như vậy. Tôi sẽ phải cập nhật mọi tam giác sử dụng các đỉnh này, và có thể là mọi tam giác trong cảnh bởi vì tôi không biết cái nào sử dụng đỉnh nào. Đó là máy tính cực kỳ chuyên sâu, và nếu tôi có một vài mắt lưới, thì trời ơi! Tôi đang gặp rắc rối, vì tôi đang cập nhật mọi tam giác gần như mọi khung hình vì các đỉnh này đang thay đổi theo thời gian!

Với con trỏ, bạn không phải cập nhật hình tam giác.

Nếu tôi có ba * đối tượng Vertex cho mỗi lớp tam giác, thì tôi không chỉ tiết kiệm phòng vì một trăm triệu tam giác không có ba đối tượng đỉnh lớn, mà cả những con trỏ này sẽ luôn hướng đến các Vertice mà chúng có ý nghĩa, bất kể làm thế nào thường xuyên các đỉnh thay đổi. Vì các con trỏ vẫn trỏ đến cùng một đỉnh, nên các tam giác không thay đổi và quá trình cập nhật dễ xử lý hơn. Nếu tôi làm bạn bối rối, tôi sẽ không nghi ngờ gì, tôi không giả vờ là một chuyên gia, chỉ ném hai xu của tôi vào cuộc thảo luận.


4

Sự cần thiết cho con trỏ trong ngôn ngữ C được mô tả ở đây

Ý tưởng cơ bản là nhiều hạn chế trong ngôn ngữ (như sử dụng mảng, chuỗi và sửa đổi nhiều biến trong các hàm) có thể được loại bỏ bằng cách thao tác với các vị trí bộ nhớ của dữ liệu. Để khắc phục những hạn chế này, con trỏ được giới thiệu trong C.

Hơn nữa, người ta cũng thấy rằng bằng cách sử dụng các con trỏ, bạn có thể chạy mã của mình nhanh hơn và tiết kiệm bộ nhớ trong trường hợp bạn truyền các loại dữ liệu lớn (như cấu trúc có nhiều trường) cho một hàm. Tạo một bản sao của các loại dữ liệu đó trước khi truyền sẽ mất thời gian và sẽ tiêu tốn bộ nhớ. Đây là một lý do tại sao các lập trình viên thích con trỏ cho các loại dữ liệu lớn.

PS: Vui lòng tham khảo liên kết được cung cấp để giải thích chi tiết với mã mẫu.


Câu hỏi là về C ++, câu trả lời này là về C.
einpoklum

Không, câu hỏi được gắn thẻ c VÀ c ++. Có thể thẻ c không liên quan, nhưng nó ở đây từ đầu.
Jean-François Fabre

3

Trong java và C # tất cả các tham chiếu đối tượng là con trỏ, điều với c ++ là bạn có nhiều quyền kiểm soát hơn về nơi bạn trỏ con trỏ. Hãy nhớ với sức mạnh to lớn đến trách nhiệm lớn.


1

Về câu hỏi thứ hai của bạn, nói chung bạn không cần sử dụng con trỏ trong khi lập trình, tuy nhiên có một ngoại lệ cho vấn đề này và đó là khi bạn tạo API công khai.

Vấn đề với các cấu trúc C ++ mà mọi người thường sử dụng để thay thế các con trỏ phụ thuộc rất nhiều vào bộ công cụ mà bạn sử dụng, điều này tốt khi bạn có tất cả quyền kiểm soát bạn cần đối với mã nguồn, tuy nhiên nếu bạn biên dịch một thư viện tĩnh với visual studio 2008 chẳng hạn và thử sử dụng nó trong một studio trực quan 2010, bạn sẽ nhận được rất nhiều lỗi liên kết vì dự án mới được liên kết với phiên bản STL mới hơn không tương thích ngược. Mọi thứ thậm chí còn khó khăn hơn nếu bạn biên dịch một DLL và cung cấp một thư viện nhập khẩu mà mọi người sử dụng trong một bộ công cụ khác vì trong trường hợp đó, chương trình của bạn sẽ bị sập sớm hay muộn mà không có lý do rõ ràng.

Vì vậy, với mục đích di chuyển các tập dữ liệu lớn từ thư viện này sang thư viện khác, bạn có thể xem xét đưa một con trỏ tới một mảng thành hàm được cho là sao chép dữ liệu nếu bạn không muốn buộc người khác sử dụng cùng các công cụ mà bạn sử dụng . Điểm hay của việc này là nó thậm chí không phải là mảng kiểu C, bạn có thể sử dụng std :: vector và đưa ra con trỏ bằng cách cho địa chỉ của phần tử đầu tiên & vectơ [0] và sử dụng std :: vector để quản lý mảng bên trong.

Một lý do chính đáng khác để sử dụng con trỏ trong C ++ một lần nữa liên quan đến các thư viện, xem xét việc có một dll không thể tải khi chương trình của bạn chạy, vì vậy nếu bạn sử dụng thư viện nhập thì sự phụ thuộc không được thỏa mãn và chương trình gặp sự cố. Đây là trường hợp khi bạn đưa ra một api công khai trong một dll bên cạnh ứng dụng của bạn và bạn muốn truy cập nó từ các ứng dụng khác. Trong trường hợp này để sử dụng API, bạn cần tải dll từ vị trí của nó (thường là trong khóa đăng ký) và sau đó bạn cần sử dụng một con trỏ hàm để có thể gọi các hàm bên trong DLL. Đôi khi, những người tạo ra API đủ tốt để cung cấp cho bạn tệp .h có chứa các hàm trợ giúp để tự động hóa quy trình này và cung cấp cho bạn tất cả các con trỏ hàm mà bạn cần,


1
  • Trong một số trường hợp, các con trỏ hàm được yêu cầu sử dụng các hàm trong thư viện dùng chung (.DLL hoặc .so). Điều này bao gồm thực hiện các công cụ trên các ngôn ngữ, trong đó thường là giao diện DLL được cung cấp.
  • Làm trình biên dịch
  • Làm máy tính khoa học, nơi bạn có một mảng hoặc vectơ hoặc sơ đồ chuỗi của con trỏ hàm?
  • Cố gắng sửa đổi bộ nhớ video trực tiếp - tạo gói đồ họa của riêng bạn
  • Tạo một API!
  • Cấu trúc dữ liệu - con trỏ liên kết nút cho các cây đặc biệt bạn đang tạo

Có rất nhiều lý do cho con trỏ. Việc xáo trộn tên C đặc biệt quan trọng trong DLL nếu bạn muốn duy trì khả năng tương thích giữa các ngôn ngữ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.