2009-04-17 3 views
37

Когда использование указателей на любом языке требует, чтобы кто-то использовал несколько, скажем, тройной указатель. Когда имеет смысл использовать тройной указатель вместо обычного указателя?Используется для нескольких уровней разметки указателя?

Например:

char * * *ptr; 

вместо

char *ptr; 
+19

Это очень _indirect_ вопрос. –

+5

Чтобы стать http://c2.com/cgi/wiki?ThreeStarProgrammer –

+0

Вы можете наслаждаться [cdecl] (http://cdecl.org/) – bobobobo

ответ

57

каждая звезда должна пониматься как «на который указывает указатель» так

char *foo; 

является «символ, который указывает на указатель Foo». Однако

char *** foo; 

является «символ, который указывает указатель на который указывает на указатель на который указывает на указатель Foo». Таким образом, foo является указателем. При этом адрес является вторым указателем. По адресу, указанному этим, является третьим указателем. Выделение третьего указателя приводит к появлению символа. Если это все, что нужно, трудно сделать для этого многое.

Его все еще можно получить полезную работу. Представьте, что мы пишем замену bash или какой-либо другой программе управления процессом. Мы хотим управлять вызовами наших процессов объектно-ориентированным способом ...

struct invocation { 
    char* command; // command to invoke the subprocess 
    char* path; // path to executable 
    char** env; // environment variables passed to the subprocess 
    ... 
} 

Но мы хотим сделать что-то фантастическое. Мы хотим иметь возможность просматривать все различные переменные среды, как видно из каждого подпроцесса. чтобы сделать это, мы собираем каждый набор env членов из экземпляров Призыва в массив env_list и передать его функции, которая имеет дело с этим:

void browse_env(size_t envc, char*** env_list); 
+4

Извините, я не anglophone, и я почти не понимаю предложение _ "char, на которое указывает указатель, который указывает на указатель, который указывает на указатель foo "_. Если _" foo "_ находится в конце предложения, не следует, чтобы предложение было _" char, на которое указывал указатель, на который указывает ** BY ** указатель, на который указывает ** BY ** указатель f оо "_? – eyquem

4

N-мерных динамически выделенных массивов, где N> 3, требуется три или более уровней косвенности в C.

+0

Кроме того, при передаче двумерного массива функции, которая будет изменять Это. Если у вас есть двойной ** ptr, вы передадите его как fn (& ptr), функция fn() будет иметь аргументы fn (double *** ptr). –

+0

Это немного вводит в заблуждение, потому что myarray [0] [1] [2] часто не является фактически 3 указателями (хотя это может быть). Память обычно соприкасается, и компилятор выполняет арифметику для вас. –

+0

Вышеописанное редактирование решает вашу проблему? То есть, можем ли мы согласиться, что динамически распределенные 3 + -мерные массивы требуют разыменования 3 + указателей? –

1
int main(int argc, char** argv); 
+0

Я думаю, что несколько более идиоматично писать char * argv []. То есть массив указателей на символы. Но затем мы попадаем во весь массив «массив против указателя» (: –

+7

«Считаете ли вы, что есть устрицы, чтобы быть нравственными и есть улитки, чтобы быть аморальными? Конечно, нет. Это все дело вкуса, isn «Это вкусно - это не то же самое, что аппетит, и поэтому не вопрос морали ... Мой вкус включает в себя как улитки, так и устрицы». – tpdi

3

Стандартное использование двойных указателей, например: myStruct ** ptrptr, является указателем на указатель. Например, как параметр функции, это позволяет вам изменить фактическую структуру, на которую указывает вызывающий объект, вместо того, чтобы только изменять значения внутри этой структуры.

0

Если вам нужно изменить указатель внутри функции, вы должны передать ссылку на него.

5

Указатель - это просто переменная, содержащая адрес памяти.

Значит, вы используете указатель на указатель, если хотите сохранить адрес переменной указателя.

Если вы хотите вернуть указатель, и вы уже используете возвращаемую переменную для чего-то, вы перейдете в адрес указателя. Функция затем разыгрывает этот указатель, чтобы он мог установить значение указателя. То есть параметр этой функции будет указателем на указатель.

Несколько уровней косвенности также используются для многомерных массивов. Если вы хотите вернуть 2-мерный массив, вы должны использовать тройной указатель. При использовании их для многомерных массивов, но будьте осторожны, чтобы правильно отображать, когда вы проходите через каждый уровень косвенности.

Вот пример возвращает значение указателя с помощью параметра:

//Not a very useful example, but shows what I mean... 
bool getOffsetBy3Pointer(const char *pInput, char **pOutput) 
{ 
    *pOutput = pInput + 3; 
    return true; 
} 

И вы называете эту функцию следующим образом:

const char *p = "hi you"; 
char *pYou; 
bool bSuccess = getOffsetBy3Pointer(p, &pYou); 
assert(!stricmp(pYou, "you")); 
+0

вы могли бы прокомментировать метод getOffsetBy3Pointer, потому что я не понимаю, как двойной указатель пошел один указатель – Jake

+0

* pOutput имеет тип char *. Точно так же, как с int ** p; * p имеет тип int *. –

0

Это имеет смысл использовать указатель на указатель когда это указатель фактически указывает на указатель (эта цепочка не ограничена, поэтому возможны «тройные указатели» и т. д.).

Причина для создания такого кода заключается в том, что вы хотите, чтобы компилятор/интерпретатор мог правильно проверять используемые вами типы (предотвращать загадки).

Вам не нужно использовать такие типы - вы всегда можете просто использовать простые «void *» и typecast всякий раз, когда вам нужно реально разыменовать указатель и получить доступ к данным, на которые указывает указатель. Но это, как правило, плохая практика и склонны к ошибкам - конечно, есть случаи, когда использование void * действительно хорошо и делает код намного более элегантным. Подумайте об этом больше, как о вашем последнем курорте.

=> В основном это помогает компилятору убедиться, что все используется так, как предполагается, они будут использоваться.

1

функций, которые инкапсулируют создание ресурсов часто используют двойные указатели. То есть вы передаете адрес указателя на ресурс. Затем функция может создать соответствующий ресурс и установить указатель на него. Это возможно только в том случае, если у него есть адрес указателя, о котором идет речь, поэтому он должен быть двойным указателем.

0

Если честно, я редко видел тройной указатель.

Я взглянул на поиск кода Google, и есть someexamples, но не очень освещает. (см. ссылки на конец - SO не нравится)

Как уже упоминалось, двойные указатели, которые вы увидите время от времени. Обычные одиночные указатели полезны, потому что они указывают на некоторый выделенный ресурс. Двойные указатели полезны, потому что вы можете передать их функции, а функция заполнить «простой» указатель для вас.

Похоже, возможно, вам нужно какое-то объяснение о том, какие указатели и как они работают? Вам нужно понять, что во-первых, если вы этого еще не сделали.

Но это separatequestion (:

http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/security/nss/lib/ckfw/capi/ckcapi.h&q=***%20lang:c&l=301

http://www.google.com/codesearch/p?hl=en#eVvq2YWVpsY/openssl-0.9.8e/crypto/ec/ec_mult.c&q=***%20lang:c&l=344

2

Вы используете дополнительный уровень косвенности - или наведение - в случае необходимости, а не потому, что это было бы интересно вы редко видите тройными. я не думаю, что когда-либо видел четырехкратный указатель (и мой ум был бы болит, если бы я это сделал).

Таблицы состояний могут быть представлены двумерным массивом a pproitable тип данных (например, указатели на структуру). Когда я написал некоторый почти общий код для создания таблиц состояний, я помню, что у меня была одна функция, которая использовала тройной указатель, который представлял 2D-массив указателей на структуры. Ой!

0

Указатели указателей редко используются в C++. Они имеют в основном два вида использования.

Первое использование - передать массив. char**, например, является указателем на указатель на char, который часто используется для передачи массива строк. Указатели на массивы не работают по уважительным причинам, но это другая тема (см. Раздел comp.lang.c FAQ, если вы хотите узнать больше). В некоторых редких случаях вы можете увидеть третий *, используемый для массива массивов, но обычно более эффективно хранить все в одном непрерывном массиве и индексировать его вручную (например, array[x+y*width], а не array[x][y]). Однако в C++ это гораздо менее распространено из-за классов контейнеров.

Второе использование - по ссылке. Параметр int* позволяет функции изменять целое число, на которое указывает вызывающая функция, и обычно используется для предоставления нескольких возвращаемых значений. Эта схема передачи параметров по ссылке, позволяющей несколько возвратов, по-прежнему присутствует на C++, но, как и другие применения pass-by-reference, обычно заменяется введением фактических ссылок. Другая причина для передачи по ссылке - избегая копирования сложных конструкций - также возможна с помощью ссылки C++.

У C++ есть третий фактор, который уменьшает использование нескольких указателей: он имеет string. Ссылка на строку может принимать тип char** в C, так что функция может изменить адрес передаваемой строковой переменной, но в C++ обычно мы видим string&.

0

Когда вы используете вложенные динамически распределенные (или связанные с указателем) структуры данных. Все эти вещи связаны указателями.

3

Char *** foo может быть интерпретирован как указатель на двумерный массив строк.

4

ImageMagicks Жезл имеет функцию, которая объявлена ​​как

WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *) 

I am not making this up.

+0

О, мой бог, какой запаздывающий стиль они используют. Typcasting «NULL» указатели, содержащие все параметры, '(void)' возвращаемые значения. Все вещи, которые, я думаю, не имеют оправдания на равнине C. –

+0

это не может быть правдой, и это не так. http://www.aerophile.org/ImageMagick/doxygen/html/drawing__wand_8c-source.html – u0b34a0f6ae

+0

@ u0b34a0f6ae Ваша ссылка 404 сейчас, но я нашел это: http://imagemagick.spd.co.il/www/api /MagickWand/drawing-wand_8h.html#85b1ab97c892eda1fc1d493148354ab2 –

10

Если вы работаете с «объектами» в C, вы, вероятно, это:

struct customer { 
    char *name; 
    char *address; 
    int id; 
} typedef Customer; 

Если вы хотите создать объект, вы могли бы сделать что-то вроде этого:

Customer *customer = malloc(sizeof Customer); 
// Initialise state. 

Мы» re, используя указатель на struct здесь, потому что аргументы struct передаются по значению, и нам нужно работать с одним объектом. (Также: Objective-C, объектно-ориентированный язык обертка для C, использует внутренне, но явно указатели struct с.)

Если мне нужно хранить несколько объектов, я использую массив:

Customer **customers = malloc(sizeof(Customer *) * 10); 
int customerCount = 0; 

Поскольку переменная массива в C указывает на первый элемент, я использую указатель ... снова. Теперь у меня есть двойные указатели.

Но теперь представьте, что у меня есть функция, которая фильтрует массив и возвращает новый. Но представьте, что он не может через механизм возврата, потому что он должен вернуть код ошибки - моя функция обращается к базе данных. Мне нужно сделать это через аргумент by-reference.Это подпись моей функции в:

int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount); 

Функция принимает массив клиентов и возвращает ссылку на массив клиентов (которые являются указателями на struct). Он также принимает количество клиентов и возвращает количество фильтрованных клиентов (опять же, аргумент ссылки).

Я могу назвать это таким образом:

Customer **result, int n = 0; 
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n); 

Я мог бы себе больше ситуаций ... Это одно без typedef:

int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns); 

Очевидно, я был бы ужасным и/или садистский разработчик, чтобы оставить его таким образом. Таким образом, используя:

typedef Customer *CustomerArray; 
typedef CustomerArray *CustomerMatrix; 

я могу просто сделать это:

int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns); 

Если приложение используется в гостинице, где вы используете матрицу на уровень, вам, вероятно, потребуется массив для матрицы :

int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels); 

Или просто это:

typedef CustomerMatrix *Hotel; 
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels); 

Не поймите меня даже начал массив отелей: (? Какой-то большой гостиницы корпорации)

int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels); 

... расположены в виде матрицы:

int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns); 

То, что я пытаюсь сказать заключается в том, что вы можете представить себе сумасшедшие приложения для нескольких целей. Просто убедитесь, что вы используете typedef, если многопозиционные указатели - хорошая идея, и вы решили использовать их.

(ли это количество сообщений в качестве приложения для SevenStarDeveloper?)

0

В частности, в однопоточных диалектах C, которые не агрессивно использовать тип анализа на основе наложения спектров, иногда это может быть полезно, чтобы написать менеджер памяти которые могут размещать перемещаемые объекты. Вместо того чтобы давать приложениям прямые указатели на куски памяти, приложение получает указатели в таблицу дескрипторов дескрипторов, каждая из которых содержит указатель на фактический кусок памяти вместе со словом, обозначающим его размер. Если нужно выделить место для struct woozle, можно было бы сказать:

struct woozle **my_woozle = newHandle(sizeof struct woozle); 

и затем доступ (несколько неловко в синтаксисе C - синтаксис уборщик в Pascal): (* my_woozle) -> SomeField = 23 ; важно, чтобы приложения не сохраняли прямые указатели на любую цель дескриптора при вызовах функций, которые выделяют память, но если существует только один указатель на каждый блок , идентифицированный дескриптором, менеджер памяти сможет перемещать объекты вокруг в фрагментация случаев станет проблемой.

подход не работает почти так же хорошо, в диалектах C, которые агрессивно преследующих ступенчатости типа на основе, так как указатель, возвращенный NewHandle не определить указатель типа struct woozle*, но вместо этого идентифицирует указатель типа void* и даже на тех платформах, где эти типы указателей имели бы такое же представление, что стандарт не требует, чтобы реализации интерпретировали указатель указателя, чтобы он мог ожидать, что может возникнуть псевдонимы .

0

Двойная косвенность упрощает многие алгоритмы балансировки дерева, где обычно нужно иметь возможность эффективно «отвязывать» поддерево от своего родителя. Например, реализация дерева AVL может использовать:

void rotateLeft(struct tree **tree) { 
    struct tree *t = *tree, 
       *r = t->right, 
       *rl = r->left; 
    *tree = r; 
    r->left = t; 
    t->right = rl; 
} 

Без «двойной указатель», мы должны сделать что-то более сложное, как явно отслеживании родительского узла, и является ли это левая или правая ветвь.

Смежные вопросы