Судя по этому ответу, записи char**
и char*[]
в параметрах функции означают один и тот же тип. Зачем так сделано и в каких ситуациях они будут означать разные типы?
Тип char *[]
трансформируется (adjusted) в тип char **
в списках параметров функций. Во всех остальных случаях char *[]
и char **
- совершенно разные типы. (char *[]
- неполный тип. Не ясно, намеренно ли вы использовали в своем вопросе именно неполный тип.)
Ответ на вопрос о том, почему так сделано в параметрах функций, надо искать в истории языка С.
Короткий ответ на этот вопрос: это было сделано так для повышения обратной совместимости по исходному коду и семантике с языком-предшественником С - языком B.
Длинный ответ на этот вопрос: детальный ответ содержится в статье Дениса Ритчи "The Development of the C Language"
1. Как и почему массивы в С перестали быть указателями.
Подход к реализации массивов в языке С был изначально позаимствован из языков-прародителей - B и ВCPL (см. вторую часть раздела "Origins: the languages"). В этих языках "массивных" типов как таковых не было: объявление массива там всегда, во всех контекстах фактически объявляло обыкновенный указатель, вместе с которым сразу же автоматически выделялся отдельный блок памяти, на который этот указатель указывал. Доступ к элементам массива осуществлялся через обычную адресную арифметику указателей. Более того, такой указатель являлся самым обычным указателем - пользователь в любой момент мог присвоить ему новое значение и тем самым заставить указывать в любое другое место.
Изначально Ритчи планировал использовать такой подход к реализации массивов и в С. Однако (см. раздел "Embryonic C") этот подход быстро пришел в противоречие с идеей одного важного нововведения языка С: struct
типов (которых не было ни в В, ни в BCPL). Если бы массив в С реализовывался как явный указатель, то структурные типы, содержащие массивы, сразу же превратились бы в нетривиальные многоуровневые типы. Они бы требовали нетривиальной "конструкции", "деструкции" и, самое главное, нетривиального копирования. И это при том, что в раннем С применение оператора присваивания к тяжелым типам не поддерживалось вообще (!), а "тяжелые" типы копировались именно и только через memcpy
. Понятно, что копировать структуру, содержащую скрытые указатели через memcpy
бесполезно. Здесь понадобилось бы некое неявное "глубокое" копирование, о котором в С тогда не могло быть и речи. То есть, в рамках B-шного указательного подхода к массивам, struct
типы получались некопируемыми вообще.
Вот для того, чтобы решить эту проблему, Ритчи и отказался от идеи реализовывать массивы, как физические указатели. Массивы в С перестали быть указателями и превратились в непосредственные блоки памяти требуемого размера, т.е. в то, что мы имеем в С и С++ сегодня. При этом, во многом для того, чтобы сохранить совместимость с существующим кодом на B, массив в С стал на лету неявно приводиться к типу "указатель", через который и реализовывалась вся адресная арифметика для доступа к элементам массива. Так родилось всем нам известное неявное стандартное array-to-pointer conversion. Т.е. подход из языка B во многом сохранился практически неизменным на поверхности, но физический указатель исчез навсегда, заменившись на временный "воображаемый" указатель - результат неявного преобразования.
2. Почему эти изменения не задели параметры функций.
Однако в параметрах функций, как вы видите, был полностью сохранен подход из языка B - объявление массива автоматически преобразуется в объявление обыкновенного указателя. Т.е. в списках параметров функций (и только там) объявление int a[]
практически эквивалентно объявлению int *a
. Почему здесь все осталось по-старому?
Это объясняется в разделе "Critique". Буквально: "... это - живущее и поныне ископаемое, остаток B-шного подхода к объявлению указателей, в рамках которого массив, только в этом исключительном случае, интерпретируется как указатель. Этот вариант записи выжил частично ради обратной совместимости, частично в надежде на то, что он позволит программистам предупреждать читателя кода о том, что в данном месте ожидается указатель на элемент массива, а не указатель на отдельный объект. К несчастью, в итоге это скорее запутывает читателя, чем предупреждает его."
Это цитата - ответ на вопрос о том, зачем была сохранен синтаксис []
при объявлении параметров функций, несмотря на то, что он все равно эквивалентен указательному синтаксису. Т.е. это фактически прямой ответ на ваш вопрос.
Это всегда были разные типы. Указатель на указатель против указатель на массив и массив указателей. Вот примеры присвоения и приёмы аргументов разных типов:
// > g++ -Wall -Wpedantic -std=c++11 arrpoi.cpp
int * * pointerToPointer ;
//int * arrayOfPointers [ ] ;
// error: storage size of ‘arrayOfPointers’ isn’t known
int * arrayOfPointers [ 1 ] ;
int ( * pointerToArray ) [ ] ;
void ptp(int * * pointerToPointer );
void aop(int * arrayOfPointers [ ]);
//void pta(int ( * pointerToArray ) [ ]);
// error: parameter ‘pointerToArray’ includes pointer to array of unknown bound ‘int []’
void pta(int ( * pointerToArray ) [ 1 ]);
int main(int argn, char**args){
pointerToPointer = arrayOfPointers ;
//arrayOfPointers = pointerToPointer ;
// error: incompatible types in assignment of ‘int**’ to ‘int* [1]’
//arrayOfPointers = pointerToArray ;
// error: incompatible types in assignment of ‘int (*)[]’ to ‘int* [1]’
//pointerToArray = pointerToPointer ;
// error: cannot convert ‘int**’ to ‘int (*)[]’ in assignment
//pointerToPointer = pointerToArray ;
// error: cannot convert ‘int (*)[]’ to ‘int**’ in assignment
//pointerToArray = arrayOfPointers ;
// error: cannot convert ‘int* [1]’ to ‘int (*)[]’ in assignment
ptp(pointerToPointer);
ptp(arrayOfPointers);
//ptp(pointerToArray);
// cannot convert ‘int (*)[]’ to ‘int**’ for argument ‘1’ to ‘void ptp(int**)’
aop(pointerToPointer);
aop(arrayOfPointers);
//aop(pointerToArray);
// error: cannot convert ‘int (*)[]’ to ‘int**’ for argument ‘1’ to ‘void aop(int**)’
//pta(pointerToPointer);
// error: cannot convert ‘int**’ to ‘int (*)[1]’ for argument ‘1’ to ‘void pta(int (*)[1])’
//pta(arrayOfPointers);
// error: cannot convert ‘int**’ to ‘int (*)[1]’ for argument ‘1’ to ‘void pta(int (*)[1])’
//pta(pointerToArray);
// error: cannot convert ‘int (*)[]’ to ‘int (*)[1]’ for argument ‘1’ to ‘void pta(int (*)[1])’
return 0;}
void aop(int * arrayOfPointers [ ]) {
int * x = arrayOfPointers [ 10000 ] ; }
Самый всеядный аргумент типа char**
он может спокойно принимать массив указателей char*[]
, просто принимается первый элемент.
Но если вы поставите приём аргументов char*[]
то при аргументе типа char**
будет всё хорошо, но при присваивании двух переменных будет конфликт типов.
Вывод такой: если вы поставили аргумент как массив указателей неопределённой длины то можете исследовать данные на своё усмотрение, всё зависит от логики программы. А если поставили указатель на указатель, то это показывает, что функция читает/модифицирует только один указатель.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
По заданию нужно было сделать код как на картинке, но я, собрал вот такой, и хотел бы узнать, одинаковы ли они по функциональности и в чём плюсы...