Коли слід застосовувати подвійну непрямість у С? Хтось може пояснити прикладом?
Що я знаю, це те, що подвійне непряме є вказівником на вказівник. Навіщо мені потрібен вказівник на покажчик?
Коли слід застосовувати подвійну непрямість у С? Хтось може пояснити прикладом?
Що я знаю, це те, що подвійне непряме є вказівником на вказівник. Навіщо мені потрібен вказівник на покажчик?
Відповіді:
Якщо ви хочете мати список символів (слово), ви можете використовувати 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);
}
Вихід:
всього слів у моєму лол: 243
arr[a][b][c]
не є ***arr
. Покажчик покажчиків використовує посилання на посилання, при цьому arr[a][b][c]
він зберігається як звичайний масив у основному порядку рядків.
Однією з причин є те, що ви хочете змінити значення вказівника, переданого на функцію як аргумент функції, для цього вам потрібен вказівник на покажчик.
Простими словами, Використовуйте, **
коли ви хочете зберегти (АБО зберегти зміни) пам'яті-розподілу або призначення, навіть поза функцією виклику функції. (Отже, передайте таку функцію з подвійним аргументом вказівника.)
Це може бути не дуже вдалим прикладом, але покаже вам основне використання:
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
, ви даєте pointer1 адресу pointer2.але! якщо ви робите це в межах функції, і хочете, щоб результат зберігався і після того, як функція виконана, вам потрібно виконати додаткову роботу. вам потрібен новий pointer3, щоб просто вказати на pointer1. передайте покажчик3 на функцію.
ось приклад. Подивіться на вихід нижче, щоб зрозуміти.
#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!!!.
Якщо додати відповідь Аша , якщо ви будете використовувати один вказівник на приклад нижче (наприклад, 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' містить адресу масиву символьних вказівників. (як у 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
а char
лише для 10 ..
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.
Зауважте, що позначення вказівника ( 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
- в С
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 ("передай значення 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
. Тому в пам'яті зараз є дві ці адреси, обидві нульові вказівники. У рядку 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 відстежуючи це, ви *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] |
------------- ---------------------------------------------------------
Подвійний вказівник на подвійний вказівник A вказує на перший елемент A [0] блоку пам'яті, елементи якого є самими подвійними вказівниками. Ви можете уявити ці подвійні покажчики як рядки матриці. Саме тому кожен подвійний покажчик виділяє пам'ять для елементів num_cols типу double. Крім того, A [i] вказує на i-й ряд, тобто A [i] вказує на A [i] [0], і це лише перший подвійний елемент блоку пам'яті для i-го рядка. Нарешті, ви можете легко отримати доступ до елемента i-го рядка та j-го стовпця за допомогою 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*
.