เมื่อใดควรใช้การเปลี่ยนทิศทางแบบคู่ใน C มีใครอธิบายได้บ้างไหม?
สิ่งที่ฉันรู้คือว่าการเบี่ยงเบนสองครั้งเป็นตัวชี้ไปยังตัวชี้ ทำไมฉันต้องใช้ตัวชี้ไปยังตัวชี้?
เมื่อใดควรใช้การเปลี่ยนทิศทางแบบคู่ใน C มีใครอธิบายได้บ้างไหม?
สิ่งที่ฉันรู้คือว่าการเบี่ยงเบนสองครั้งเป็นตัวชี้ไปยังตัวชี้ ทำไมฉันต้องใช้ตัวชี้ไปยังตัวชี้?
คำตอบ:
หากคุณต้องการมีรายชื่อตัวละคร (คำ) คุณสามารถใช้ char *word
หากคุณต้องการรายการคำ (ประโยค) คุณสามารถใช้ char **sentence
หากคุณต้องการรายการของประโยค (คำพูดคนเดียว) คุณสามารถใช้ char ***monologue
หากคุณต้องการรายการสะสม (ชีวประวัติ) คุณสามารถใช้ char ****biography
หากคุณต้องการรายชื่อชีวประวัติ (ห้องสมุดชีวภาพ) คุณสามารถใช้ char *****biolibrary
หากคุณต้องการรายชื่อห้องสมุดชีวภาพ (a ?? lol) คุณสามารถใช้ char ******lol
... ...
ใช่ฉันรู้ว่าสิ่งเหล่านี้อาจไม่ใช่โครงสร้างข้อมูลที่ดีที่สุด
ตัวอย่างการใช้งานกับlol ที่น่าเบื่อมากๆ
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int wordsinsentence(char **x) {
int w = 0;
while (*x) {
w += 1;
x++;
}
return w;
}
int wordsinmono(char ***x) {
int w = 0;
while (*x) {
w += wordsinsentence(*x);
x++;
}
return w;
}
int wordsinbio(char ****x) {
int w = 0;
while (*x) {
w += wordsinmono(*x);
x++;
}
return w;
}
int wordsinlib(char *****x) {
int w = 0;
while (*x) {
w += wordsinbio(*x);
x++;
}
return w;
}
int wordsinlol(char ******x) {
int w = 0;
while (*x) {
w += wordsinlib(*x);
x++;
}
return w;
}
int main(void) {
char *word;
char **sentence;
char ***monologue;
char ****biography;
char *****biolibrary;
char ******lol;
//fill data structure
word = malloc(4 * sizeof *word); // assume it worked
strcpy(word, "foo");
sentence = malloc(4 * sizeof *sentence); // assume it worked
sentence[0] = word;
sentence[1] = word;
sentence[2] = word;
sentence[3] = NULL;
monologue = malloc(4 * sizeof *monologue); // assume it worked
monologue[0] = sentence;
monologue[1] = sentence;
monologue[2] = sentence;
monologue[3] = NULL;
biography = malloc(4 * sizeof *biography); // assume it worked
biography[0] = monologue;
biography[1] = monologue;
biography[2] = monologue;
biography[3] = NULL;
biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
biolibrary[0] = biography;
biolibrary[1] = biography;
biolibrary[2] = biography;
biolibrary[3] = NULL;
lol = malloc(4 * sizeof *lol); // assume it worked
lol[0] = biolibrary;
lol[1] = biolibrary;
lol[2] = biolibrary;
lol[3] = NULL;
printf("total words in my lol: %d\n", wordsinlol(lol));
free(lol);
free(biolibrary);
free(biography);
free(monologue);
free(sentence);
free(word);
}
เอาท์พุท:
คำทั้งหมดใน lol ของฉัน: 243
arr[a][b][c]
***arr
ตัวชี้ของพอยน์เตอร์ใช้การอ้างอิงของการอ้างอิงในขณะที่arr[a][b][c]
ถูกเก็บไว้เป็นอาร์เรย์ปกติในลำดับหลักของแถว
เหตุผลหนึ่งคือคุณต้องการเปลี่ยนค่าของตัวชี้ที่ส่งไปยังฟังก์ชันเป็นอาร์กิวเมนต์ของฟังก์ชันเพื่อทำสิ่งนี้คุณต้องใช้ตัวชี้เป็นตัวชี้
ในคำง่าย ๆใช้**
เมื่อคุณต้องการรักษา (หรือคงการเปลี่ยนแปลง) การจัดสรรหน่วยความจำหรือการมอบหมายแม้อยู่นอกการเรียกใช้ฟังก์ชัน (ดังนั้นผ่านฟังก์ชั่นดังกล่าวด้วย ARG ตัวชี้คู่)
นี่อาจไม่ใช่ตัวอย่างที่ดีมาก แต่จะแสดงให้คุณเห็นถึงการใช้งานพื้นฐาน:
void allocate(int** p)
{
*p = (int*)malloc(sizeof(int));
}
int main()
{
int* p = NULL;
allocate(&p);
*p = 42;
free(p);
}
void allocate(int *p)
และคุณเรียกมันว่าallocate(p)
อะไร?
pointer1 = pointer2
คุณให้ตัวชี้ 1 ที่อยู่ของตัวชี้ 2แต่! หากคุณทำสิ่งนั้นภายในฟังก์ชั่นและคุณต้องการให้ผลลัพธ์คงอยู่หลังจากที่ฟังก์ชันเสร็จสิ้นคุณต้องทำงานพิเศษบางอย่าง คุณต้องการตัวชี้ใหม่ 3 เพียงชี้ไปที่ตัวชี้ 1 pass pointer3 ไปยังฟังก์ชั่น
นี่คือตัวอย่าง ดูผลลัพธ์ด้านล่างก่อนเพื่อทำความเข้าใจ
#include <stdio.h>
int main()
{
int c = 1;
int d = 2;
int e = 3;
int * a = &c;
int * b = &d;
int * f = &e;
int ** pp = &a; // pointer to pointer 'a'
printf("\n a's value: %x \n", a);
printf("\n b's value: %x \n", b);
printf("\n f's value: %x \n", f);
printf("\n can we change a?, lets see \n");
printf("\n a = b \n");
a = b;
printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
printf("\n cant_change(a, f); \n");
cant_change(a, f);
printf("\n a's value is now: %x, Doh! same as 'b'... that function tricked us. \n", a);
printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
printf("\n change(pp, f); \n");
change(pp, f);
printf("\n a's value is now: %x, YEAH! same as 'f'... that function ROCKS!!!. \n", a);
return 0;
}
void cant_change(int * x, int * z){
x = z;
printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}
void change(int ** x, int * z){
*x = z;
printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}
นี่คือผลลัพธ์: ( อ่านก่อนนี้ )
a's value: bf94c204
b's value: bf94c208
f's value: bf94c20c
can we change a?, lets see
a = b
a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see...
cant_change(a, f);
----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see
a's value is now: bf94c208, Doh! same as 'b'... that function tricked us.
NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a'
change(pp, f);
----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see
a's value is now: bf94c20c, YEAH! same as 'f'... that function ROCKS!!!.
การเพิ่มการตอบสนองของ Ashaถ้าคุณใช้ตัวชี้เดียวกับตัวอย่างร้อง (เช่น alloc1 ()) คุณจะสูญเสียการอ้างอิงไปยังหน่วยความจำที่จัดสรรภายในฟังก์ชัน
void alloc2(int** p) {
*p = (int*)malloc(sizeof(int));
**p = 10;
}
void alloc1(int* p) {
p = (int*)malloc(sizeof(int));
*p = 10;
}
int main(){
int *p = NULL;
alloc1(p);
//printf("%d ",*p);//undefined
alloc2(&p);
printf("%d ",*p);//will print 10
free(p);
return 0;
}
เหตุผลที่มันเกิดขึ้นเช่นนี้คือในalloc1
ตัวชี้จะถูกส่งผ่านโดยค่า ดังนั้นเมื่อมีการกำหนดใหม่ให้กับผลลัพธ์ของการmalloc
โทรภายในalloc1
การเปลี่ยนแปลงจะไม่เกี่ยวข้องกับโค้ดในขอบเขตอื่น
free(p)
ยังไม่พอคุณต้องทำif(p) free(*p)
เช่นนั้นด้วย
*p
ให้คะแนนเพื่อint
เก็บค่า 10 ผ่านสิ่งนี้ int
เพื่อฟรี () `เป็นความคิดที่ไม่ดี
alloc1()
แนะนำหน่วยความจำรั่ว ค่าตัวชี้ที่จะส่งผ่านฟรีจะหายไปโดยกลับมาจากฟังก์ชัน
วันนี้ฉันเห็นตัวอย่างที่ดีมากจากโพสต์บล็อกนี้เนื่องจากฉันสรุปไว้ด้านล่าง
ลองนึกภาพคุณมีโครงสร้างสำหรับโหนดในรายการที่เชื่อมโยงซึ่งอาจเป็น
typedef struct node
{
struct node * next;
....
} node;
ตอนนี้คุณต้องการที่จะใช้remove_if
ฟังก์ชั่นที่ยอมรับเกณฑ์การลบrm
เป็นหนึ่งในข้อโต้แย้งและสำรวจรายการที่เชื่อมโยง: ถ้ารายการเป็นไปตามเกณฑ์ (เช่นrm(entry)==true
) โหนดของมันจะถูกลบออกจากรายการ ในท้ายที่สุดremove_if
ส่งกลับหัว (ซึ่งอาจแตกต่างจากหัวเดิม) ของรายการที่เชื่อมโยง
คุณอาจจะเขียน
for (node * prev = NULL, * curr = head; curr != NULL; )
{
node * const next = curr->next;
if (rm(curr))
{
if (prev) // the node to be removed is not the head
prev->next = next;
else // remove the head
head = next;
free(curr);
}
else
prev = curr;
curr = next;
}
เป็นfor
วงของคุณ ข้อความคือหากไม่มีพอยน์เตอร์คู่คุณต้องเก็บรักษาprev
ตัวแปรเพื่อจัดระเบียบพอยน์เตอร์ใหม่และจัดการกับทั้งสองกรณีที่แตกต่างกัน
แต่ด้วยพอยน์เตอร์สองตัวคุณสามารถเขียนได้จริง
// now head is a double pointer
for (node** curr = head; *curr; )
{
node * entry = *curr;
if (rm(entry))
{
*curr = entry->next;
free(entry);
}
else
curr = &entry->next;
}
คุณไม่ต้องการprev
ตอนนี้เพราะคุณสามารถแก้ไขสิ่งที่prev->next
ชี้ไปโดยตรง
เพื่อให้สิ่งต่าง ๆ ชัดเจนขึ้นลองทำตามโค้ดสักหน่อย ระหว่างการกำจัด:
entry == *head
: มันจะเป็น*head (==*curr) = *head->next
- head
ตอนนี้ชี้ไปที่ตัวชี้ของโหนดส่วนหัวใหม่ คุณทำได้โดยการเปลี่ยนhead
เนื้อหาของตัวชี้เป็นตัวชี้ใหม่โดยตรงentry != *head
: ในทำนองเดียวกัน*curr
คือสิ่งที่ชี้ไปและตอนนี้ชี้ไปที่prev->next
entry->next
ไม่ว่าในกรณีใดคุณสามารถจัดระเบียบพอยน์เตอร์อีกครั้งด้วยวิธีการรวมกันด้วยพอยน์เตอร์คู่
1. แนวคิดพื้นฐาน -
เมื่อคุณประกาศดังต่อไปนี้: -
1. char * ch - (เรียกว่าตัวชี้อักขระ)
- ch มีที่อยู่ของอักขระเดียว
- (* ch) จะพิจารณาค่าตามตัวอักษร ..
2. char ** ch -
'ch' มีที่อยู่ของ Array ของพอยน์เตอร์พอยน์เตอร์ (ดังใน 1)
'* ch' มีที่อยู่ของตัวละครเดียว (โปรดทราบว่ามันแตกต่างจาก 1 เนื่องจากความแตกต่างในการประกาศ)
(** ch) จะพิจารณาถึงค่าที่แท้จริงของตัวละคร ..
การเพิ่มพอยน์เตอร์ให้มากขึ้นจะขยายขนาดของประเภทข้อมูลจากอักขระไปยังสตริงไปยังอาร์เรย์ของสตริงและอื่น ๆ ... คุณสามารถเชื่อมโยงมิติข้อมูลกับเมทริกซ์ 1d, 2d, 3d
ดังนั้นการใช้งานตัวชี้จึงขึ้นอยู่กับว่าคุณประกาศอย่างไร
นี่คือรหัสง่ายๆ ..
int main()
{
char **p;
p = (char **)malloc(100);
p[0] = (char *)"Apple"; // or write *p, points to location of 'A'
p[1] = (char *)"Banana"; // or write *(p+1), points to location of 'B'
cout << *p << endl; //Prints the first pointer location until it finds '\0'
cout << **p << endl; //Prints the exact character which is being pointed
*p++; //Increments for the next string
cout << *p;
}
2. แอปพลิเคชั่นตัวชี้สองตัวอื่น -
(ซึ่งจะครอบคลุมถึงการอ้างอิงผ่าน)
สมมติว่าคุณต้องการอัปเดตอักขระจากฟังก์ชัน หากคุณลองทำสิ่งต่อไปนี้: -
void func(char ch)
{
ch = 'B';
}
int main()
{
char ptr;
ptr = 'A';
printf("%c", ptr);
func(ptr);
printf("%c\n", ptr);
}
ผลลัพธ์จะเป็น AA วิธีนี้ใช้ไม่ได้เนื่องจากคุณมี "ส่งผ่านมูลค่า" ไปยังฟังก์ชัน
วิธีที่ถูกต้องในการทำเช่นนั้นคือ -
void func( char *ptr) //Passed by Reference
{
*ptr = 'B';
}
int main()
{
char *ptr;
ptr = (char *)malloc(sizeof(char) * 1);
*ptr = 'A';
printf("%c\n", *ptr);
func(ptr);
printf("%c\n", *ptr);
}
ตอนนี้ขยายข้อกำหนดนี้สำหรับการอัพเดตสตริงแทนอักขระ
สำหรับสิ่งนี้คุณจะต้องได้รับพารามิเตอร์ในฟังก์ชั่นเป็นตัวชี้คู่
void func(char **str)
{
strcpy(str, "Second");
}
int main()
{
char **str;
// printf("%d\n", sizeof(char));
*str = (char **)malloc(sizeof(char) * 10); //Can hold 10 character pointers
int i = 0;
for(i=0;i<10;i++)
{
str = (char *)malloc(sizeof(char) * 1); //Each pointer can point to a memory of 1 character.
}
strcpy(str, "First");
printf("%s\n", str);
func(str);
printf("%s\n", str);
}
ในตัวอย่างนี้เมธอดต้องการตัวชี้ดับเบิลเป็นพารามิเตอร์เพื่ออัพเดตค่าของสตริง
#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; }
แต่คุณสามารถทำได้โดยไม่ต้องใช้ตัวชี้คู่
char
อยน์เตอร์ ตัวชี้ไปยังอาร์เรย์ของchar*
จะได้รับการพิมพ์ตัวอย่างเช่นนี้char(*(*p)[42])
กำหนดp
เป็นตัวชี้ไปยังอาร์เรย์ของ 42 char
ชี้ไปยัง
*str = ...
str
คือพฤติกรรมที่ไม่ได้กำหนดที่กล่าวอ้าง
malloc(sizeof(char) * 10);
ไม่ได้จัดสรรห้องสำหรับตัวชี้ 10 ตัวchar
แต่สำหรับ 10 char
เท่านั้น ..
for(i=0;i<10;i++) { str = ...
i
พอยน์เตอร์ไปยังพอยน์เตอร์ยังมีประโยชน์ในฐานะ "จับ" ไปยังหน่วยความจำที่คุณต้องการส่งผ่าน "จับ" ระหว่างฟังก์ชั่นไปยังหน่วยความจำที่ตั้งใหม่ได้ นั่นหมายความว่าฟังก์ชั่นสามารถเปลี่ยนหน่วยความจำที่ถูกชี้ไปที่ตัวชี้ภายในตัวแปรจับและทุกฟังก์ชั่นหรือวัตถุที่ใช้จับจะถูกชี้ไปที่หน่วยความจำ (หรือจัดสรร) ย้ายใหม่ ไลบรารี่ชอบทำสิ่งนี้ด้วยข้อมูลแบบ "ทึบ" ซึ่งเป็นประเภทข้อมูลที่คุณไม่ต้องกังวลเกี่ยวกับสิ่งที่พวกเขากำลังทำกับหน่วยความจำที่ถูกชี้คุณแค่ผ่าน "มือจับ" ระหว่าง ฟังก์ชั่นของห้องสมุดเพื่อดำเนินการบางอย่างกับหน่วยความจำ ...
ตัวอย่างเช่น
#include <stdlib.h>
typedef unsigned char** handle_type;
//some data_structure that the library functions would work with
typedef struct
{
int data_a;
int data_b;
int data_c;
} LIB_OBJECT;
handle_type lib_create_handle()
{
//initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
handle_type handle = malloc(sizeof(handle_type));
*handle = malloc(sizeof(LIB_OBJECT) * 10);
return handle;
}
void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }
void lib_func_b(handle_type handle)
{
//does something that takes input LIB_OBJECTs and makes more of them, so has to
//reallocate memory for the new objects that will be created
//first re-allocate the memory somewhere else with more slots, but don't destroy the
//currently allocated slots
*handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);
//...do some operation on the new memory and return
}
void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }
void lib_free_handle(handle_type handle)
{
free(*handle);
free(handle);
}
int main()
{
//create a "handle" to some memory that the library functions can use
handle_type my_handle = lib_create_handle();
//do something with that memory
lib_func_a(my_handle);
//do something else with the handle that will make it point somewhere else
//but that's invisible to us from the standpoint of the calling the function and
//working with the handle
lib_func_b(my_handle);
//do something with new memory chunk, but you don't have to think about the fact
//that the memory has moved under the hood ... it's still pointed to by the "handle"
lib_func_c(my_handle);
//deallocate the handle
lib_free_handle(my_handle);
return 0;
}
หวังว่าจะช่วยได้
เจสัน
unsigned char
มีการใช้โดยเฉพาะเพราะเรากำลังเก็บตัวชี้ไปยังข้อมูลไบนารีที่จะแสดงเป็นไบต์ดิบ การใช้void
จะต้องมีการโยนในบางจุดและโดยทั่วไปจะไม่สามารถอ่านได้ตามเจตนาของสิ่งที่กำลังทำ
int main(int argc, char **argv)
ในพารามิเตอร์ที่สองคุณมีมัน: ตัวชี้ไปยังตัวชี้เพื่อถ่าน
โปรดทราบว่าสัญกรณ์พอยน์เตอร์ ( char* c
) และสัญกรณ์อาร์เรย์ ( char c[]
) สามารถใช้แทนกันในอาร์กิวเมนต์ของฟังก์ชัน char *argv[]
ดังนั้นคุณยังสามารถเขียน กล่าวอีกนัยหนึ่งchar *argv[]
และchar **argv
สามารถใช้แทนกันได้
สิ่งที่แสดงข้างต้นคืออาร์เรย์ของลำดับอักขระ (อาร์กิวเมนต์บรรทัดคำสั่งที่กำหนดให้กับโปรแกรมเมื่อเริ่มต้น)
ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับลายเซ็นฟังก์ชั่นด้านบน
char* c
) และสัญกรณ์อาร์เรย์ ( char c[]
) สามารถใช้แทนกัน" (และมีความหมายที่แน่นอนเดียวกัน) ในการขัดแย้งฟังก์ชั่น พวกเขาจะแตกต่างกันอย่างไรก็ตามข้อโต้แย้งฟังก์ชั่นนอก
สตริงเป็นตัวอย่างที่ดีของการใช้พอยน์เตอร์พอยน์เตอร์ สตริงนั้นเป็นตัวชี้ดังนั้นเมื่อใดก็ตามที่คุณต้องการชี้ไปที่สตริงคุณจะต้องมีตัวชี้คู่
ตัวอย่างเช่นคุณอาจต้องการตรวจสอบให้แน่ใจว่าเมื่อคุณเพิ่มหน่วยความจำของสิ่งที่คุณตั้งค่าตัวชี้เป็นโมฆะในภายหลัง
void safeFree(void** memory) {
if (*memory) {
free(*memory);
*memory = NULL;
}
}
เมื่อคุณเรียกใช้ฟังก์ชั่นนี้คุณจะเรียกมันด้วยที่อยู่ของตัวชี้
void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);
ตอนนี้myMemory
ถูกตั้งค่าเป็น NULL และความพยายามใด ๆ ที่จะนำมาใช้ซ้ำจะผิดอย่างเห็นได้ชัด
if(*memory)
และfree(*memory);
ตัวอย่างเช่นหากคุณต้องการเข้าถึงข้อมูลที่ไม่ต่อเนื่องแบบสุ่ม
p -> [p0, p1, p2, ...]
p0 -> data1
p1 -> data2
- ใน C
T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));
คุณเก็บตัวชี้p
ที่ชี้ไปยังอาร์เรย์ของพอยน์เตอร์ ตัวชี้แต่ละตัวชี้ไปยังส่วนของข้อมูล
หากsizeof(T)
มีขนาดใหญ่อาจไม่สามารถจัดสรรบล็อกที่ต่อเนื่องกัน (เช่นใช้ malloc) เป็นsizeof(T) * n
ไบต์
สิ่งหนึ่งที่ฉันใช้พวกมันตลอดเวลาคือเมื่อฉันมีอาร์เรย์ของวัตถุและฉันต้องทำการค้นหา (การค้นหาแบบไบนารี) กับพวกเขาโดยสาขาที่แตกต่างกัน
ฉันเก็บอาร์เรย์เดิม ...
int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);
จากนั้นสร้างอาร์เรย์ของพอยน์เตอร์ที่เรียงลำดับกับวัตถุ
int compare_object_by_name( const void *v1, const void *v2 ) {
OBJECT *o1 = *(OBJECT **)v1;
OBJECT *o2 = *(OBJECT **)v2;
return (strcmp(o1->name, o2->name);
}
OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
int i = 0;
for( ; i<num_objects; i++)
object_ptrs_by_name[i] = original_array+i;
qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);
คุณสามารถสร้างอาร์เรย์ของตัวชี้ที่เรียงลำดับได้มากเท่าที่คุณต้องการจากนั้นใช้การค้นหาแบบไบนารี่บนอาเรย์ตัวชี้ที่เรียงเพื่อเข้าถึงวัตถุที่คุณต้องการโดยข้อมูลที่คุณมี อาเรย์ของออบเจกต์ดั้งเดิมยังคงไม่เรียงลำดับ แต่อาเรย์ตัวชี้แต่ละอันจะถูกจัดเรียงตามฟิลด์ที่ระบุ
ทำไมต้องพอยน์เตอร์คู่
วัตถุประสงค์คือเพื่อเปลี่ยนสิ่งที่ studentA ชี้ไปโดยใช้ฟังก์ชัน
#include <stdio.h>
#include <stdlib.h>
typedef struct Person{
char * name;
} Person;
/**
* we need a ponter to a pointer, example: &studentA
*/
void change(Person ** x, Person * y){
*x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}
void dontChange(Person * x, Person * y){
x = y;
}
int main()
{
Person * studentA = (Person *)malloc(sizeof(Person));
studentA->name = "brian";
Person * studentB = (Person *)malloc(sizeof(Person));
studentB->name = "erich";
/**
* we could have done the job as simple as this!
* but we need more work if we want to use a function to do the job!
*/
// studentA = studentB;
printf("1. studentA = %s (not changed)\n", studentA->name);
dontChange(studentA, studentB);
printf("2. studentA = %s (not changed)\n", studentA->name);
change(&studentA, studentB);
printf("3. studentA = %s (changed!)\n", studentA->name);
return 0;
}
/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
ต่อไปนี้เป็นเรื่องง่ายมากที่ C ++ ตัวอย่างที่แสดงให้เห็นว่าถ้าคุณต้องการที่จะใช้ฟังก์ชั่นที่จะตั้งตัวชี้ไปยังจุดที่จะวัตถุที่คุณต้องชี้ไปชี้ มิฉะนั้นตัวชี้จะเปลี่ยนเป็นโมฆะต่อไป
(คำตอบ C ++ แต่ฉันเชื่อว่ามันเหมือนกันใน C. )
(นอกจากนี้สำหรับการอ้างอิง: Google ("pass by value c ++") = "โดยค่าเริ่มต้นอาร์กิวเมนต์ใน C ++ จะถูกส่งผ่านโดยค่าเมื่ออาร์กิวเมนต์ถูกส่งผ่านตามค่าค่าของอาร์กิวเมนต์จะถูกคัดลอกไปยังพารามิเตอร์ของฟังก์ชัน")
ดังนั้นเราจึงต้องการที่จะตั้งตัวชี้เท่ากับสตริงb
a
#include <iostream>
#include <string>
void Function_1(std::string* a, std::string* b) {
b = a;
std::cout << (b == nullptr); // False
}
void Function_2(std::string* a, std::string** b) {
*b = a;
std::cout << (b == nullptr); // False
}
int main() {
std::string a("Hello!");
std::string* b(nullptr);
std::cout << (b == nullptr); // True
Function_1(&a, b);
std::cout << (b == nullptr); // True
Function_2(&a, &b);
std::cout << (b == nullptr); // False
}
// Output: 10100
สิ่งที่เกิดขึ้นที่เส้นFunction_1(&a, b);
?
"มูลค่า" ของ&main::a
(ที่อยู่) std::string* Function_1::a
จะถูกคัดลอกลงในพารามิเตอร์ ดังนั้นจึงFunction_1::a
เป็นตัวชี้ไป (เช่นที่อยู่ของหน่วยความจำ) main::a
สตริง
"มูลค่า" ของmain::b
(ที่อยู่ในความทรงจำ) std::string* Function_1::b
จะถูกคัดลอกลงในพารามิเตอร์ ดังนั้นขณะนี้มี 2 ที่อยู่เหล่านี้ในหน่วยความจำทั้งตัวชี้โมฆะ ที่บรรทัดb = a;
นั้นตัวแปรโลคัลFunction_1::b
จะถูกเปลี่ยนเป็นเท่ากับFunction_1::a
(= &main::a
) แต่ตัวแปรmain::b
ไม่เปลี่ยนแปลง หลังจากที่การเรียกร้องให้Function_1
, main::b
ก็ยังคงเป็นตัวชี้โมฆะ
สิ่งที่เกิดขึ้นที่เส้นFunction_2(&a, &b);
?
การรักษาของa
ตัวแปรเดียวกัน: ภายในฟังก์ชั่นที่เป็นที่อยู่ของสตริงFunction_2::a
main::a
แต่b
ตอนนี้ตัวแปรกำลังถูกส่งผ่านเป็นตัวชี้ไปยังตัวชี้ "มูลค่า" ของ&main::b
(คนที่อยู่ของตัวชี้ main::b
) std::string** Function_2::b
ถูกคัดลอกลง ดังนั้นภายใน Function_2, dereferencing นี้เช่นการเข้าถึงและจะปรับเปลี่ยน*Function_2::b
main::b
ดังนั้นบรรทัด*b = a;
จึงตั้งค่าmain::b
(ที่อยู่) เท่ากับFunction_2::a
(= ที่อยู่ของmain::a
) ซึ่งเป็นสิ่งที่เราต้องการ
หากคุณต้องการใช้ฟังก์ชั่นในการปรับเปลี่ยนสิ่งต่าง ๆ ไม่ว่าจะเป็นวัตถุหรือที่อยู่ (ตัวชี้) คุณจะต้องผ่านตัวชี้ไปยังสิ่งนั้น สิ่งที่คุณจริงผ่านไม่สามารถแก้ไขได้ (ในขอบเขตโทร) เพราะสำเนาทำ
(ข้อยกเว้นคือถ้าพารามิเตอร์เป็นการอ้างอิงเช่นstd::string& a
แต่โดยปกติแล้วจะเป็นconst
โดยทั่วไปถ้าคุณเรียกใช้f(x)
ถ้าx
เป็นวัตถุคุณควรจะสามารถสมมติว่าf
จะไม่แก้ไขx
แต่ถ้าx
เป็นตัวชี้คุณควร สมมติว่าf
อาจแก้ไขวัตถุที่ชี้ไปตามx
)
สายไปงานเลี้ยงเล็ก ๆ น้อย ๆ แต่หวังว่ามันจะช่วยให้ใครบางคน
ในอาร์เรย์ C จะจัดสรรหน่วยความจำบนสแต็กเสมอดังนั้นฟังก์ชั่นจะไม่สามารถคืนค่าอาร์เรย์ (ไม่คงที่) ได้เนื่องจากหน่วยความจำที่จัดสรรในสแต็กจะได้รับการปลดปล่อยโดยอัตโนมัติเมื่อการดำเนินการถึงจุดสิ้นสุดของบล็อกปัจจุบัน มันน่ารำคาญจริงๆเมื่อคุณต้องการจัดการกับอาร์เรย์สองมิติ (เช่นเมทริกซ์) และใช้ฟังก์ชั่นบางอย่างที่สามารถแก้ไขและคืนเมทริกซ์ได้ เพื่อให้บรรลุเป้าหมายนี้คุณสามารถใช้ตัวชี้ไปยังตัวชี้เพื่อนำเมทริกซ์ไปใช้กับหน่วยความจำที่จัดสรรแบบไดนามิก:
/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
// Allocate memory for num_rows float-pointers
double** A = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(A == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols floats
for(int i = 0; i < num_rows; i++){
A[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(A[i] == NULL){
for(int j = 0; j < i; j++){
free(A[j]);
}
free(A);
return NULL;
}
}
return A;
}
นี่คือภาพประกอบ:
double** double* double
------------- ---------------------------------------------------------
A ------> | A[0] | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
| --------- | ---------------------------------------------------------
| A[1] | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[i] | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
------------- ---------------------------------------------------------
double-pointer-to-double-pointer A ชี้ไปที่องค์ประกอบแรก [0] ของบล็อกหน่วยความจำที่มีองค์ประกอบเป็นตัวชี้ดับเบิล คุณสามารถนึกพอยน์เตอร์พอยน์เตอร์เหล่านี้เป็นแถวของเมทริกซ์ นั่นคือเหตุผลที่ทุก ๆ ตัวชี้ดับเบิลจัดสรรหน่วยความจำสำหรับองค์ประกอบ num_cols ของประเภทคู่ นอกจากนี้ A [i] ชี้ไปยังแถวที่ i นั่นคือ A [i] ชี้ไปที่ A [i] [0] และนั่นเป็นเพียงองค์ประกอบสองครั้งแรกของบล็อกหน่วยความจำสำหรับแถวที่ i สุดท้ายคุณสามารถเข้าถึงองค์ประกอบในแถวที่ i และคอลัมน์ j-th ได้อย่างง่ายดายด้วย A [i] [j]
นี่คือตัวอย่างที่สมบูรณ์ที่แสดงให้เห็นถึงการใช้งาน:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
// Allocate memory for num_rows double-pointers
double** matrix = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(matrix == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols
// doubles
for(int i = 0; i < num_rows; i++){
matrix[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(matrix[i] == NULL){
for(int j = 0; j < i; j++){
free(matrix[j]);
}
free(matrix);
return NULL;
}
}
return matrix;
}
/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
for (int i = 0; i < rows; ++i){
for (int j = 0; j < cols; ++j){
matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
}
}
}
/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
free(matrix[i]);
}
free(matrix);
}
/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
printf(" %- f ", matrix[i][j]);
}
printf("\n");
}
}
int main(){
srand(time(NULL));
int m = 3, n = 3;
double** A = init_matrix(m, n);
randn_fill_matrix(A, m, n);
print_matrix(A, m, n);
free_matrix(A, m, n);
return 0;
}
ฉันใช้พอยน์เตอร์พอยน์เตอร์วันนี้ในขณะที่ฉันกำลังเขียนโปรแกรมเพื่อทำงานดังนั้นฉันจึงสามารถตอบได้ว่าทำไมเราถึงต้องใช้มัน (เป็นครั้งแรกที่ฉันต้องใช้พอยน์เตอร์สองตัว) เราต้องจัดการกับการเข้ารหัสเฟรมแบบเรียลไทม์ที่มีอยู่ในบัฟเฟอร์ซึ่งเป็นสมาชิกของโครงสร้างบางอย่าง ในตัวเข้ารหัสเราต้องใช้ตัวชี้ไปยังหนึ่งในโครงสร้างเหล่านั้น ปัญหาคือว่าตัวชี้ของเราถูกเปลี่ยนให้ชี้ไปที่โครงสร้างอื่นจากเธรดอื่น เพื่อที่จะใช้โครงสร้างปัจจุบันในตัวเข้ารหัสฉันต้องใช้ตัวชี้คู่เพื่อที่จะชี้ไปยังตัวชี้ที่ถูกแก้ไขในเธรดอื่น ในตอนแรกมันไม่ชัดเจนอย่างน้อยก็สำหรับเราที่เราต้องใช้วิธีนี้ พิมพ์ที่อยู่จำนวนมากในกระบวนการ :))
คุณควรใช้พอยน์เตอร์คู่เมื่อคุณทำงานกับพอยน์เตอร์ที่เปลี่ยนไปในที่อื่น ๆ ของแอปพลิเคชันของคุณ คุณอาจพบว่าพอยน์เตอร์สองตัวเป็นสิ่งจำเป็นเมื่อคุณจัดการกับฮาร์ดแวร์ที่ส่งคืนและที่อยู่ให้กับคุณ
เปรียบเทียบการปรับเปลี่ยนค่าของตัวแปรเมื่อเทียบกับการปรับเปลี่ยนค่าของตัวชี้ :
#include <stdio.h>
#include <stdlib.h>
void changeA(int (*a))
{
(*a) = 10;
}
void changeP(int *(*P))
{
(*P) = malloc(sizeof((*P)));
}
int main(void)
{
int A = 0;
printf("orig. A = %d\n", A);
changeA(&A);
printf("modi. A = %d\n", A);
/*************************/
int *P = NULL;
printf("orig. P = %p\n", P);
changeP(&P);
printf("modi. P = %p\n", P);
free(P);
return EXIT_SUCCESS;
}
สิ่งนี้ช่วยให้ฉันหลีกเลี่ยงการคืนค่าของตัวชี้เมื่อตัวชี้ถูกแก้ไขโดยฟังก์ชันที่เรียกใช้ (ใช้ในรายการที่ลิงก์โดยลำพัง)
เก่า (ไม่ดี):
int *func(int *P)
{
...
return P;
}
int main(void)
{
int *pointer;
pointer = func(pointer);
...
}
ใหม่ (ดีกว่า):
void func(int **pointer)
{
...
}
int main(void)
{
int *pointer;
func(&pointer);
...
}
double*
ยังหมายถึงชนิด