Ошибка с диапазоном для внутренней функции
У меня немного проблемы с диапазоном для С++. Я пытаюсь использовать его для отображения массива on и int (int []), и он отлично работает, когда я делаю это на главной функции, например, в:
int main(int argc, char const *argv[]) {
int v[] = {3, 4, 6, 9, 2, 1};
for (auto a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
return 0;
}
Я получаю желаемый и ожидаемый результат:
3 4 6 9 2 1
Но ситуация становится немного странной, когда я пытаюсь использовать диапазон для функции внутри, в качестве примера у меня проблема с этим кодом:
void printList(int *v);
int main(int argc, char const *argv[]) {
int v[] = {3, 4, 6, 9, 2, 1};
printList(v);
return 0;
}
void printList(int *v) {
for (auto a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
Для меня это то же самое, что я делал внутри основного, а также с обычным для работы полностью. Странная ошибка выглядит следующим образом:
p4.cpp: In function ‘void printList(int*)’:
p4.cpp:15:17: error: ‘begin’ was not declared in this scope
for (auto a : v) {
^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
from /usr/include/c++/5/bits/locale_classes.h:40,
from /usr/include/c++/5/bits/ios_base.h:41,
from /usr/include/c++/5/ios:42,
from /usr/include/c++/5/ostream:38,
from /usr/include/c++/5/iostream:39,
from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:105:37: note: ‘std::begin’
template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
^
p4.cpp:15:17: error: ‘end’ was not declared in this scope
for (auto a : v) {
^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
from /usr/include/c++/5/bits/locale_classes.h:40,
from /usr/include/c++/5/bits/ios_base.h:41,
from /usr/include/c++/5/ios:42,
from /usr/include/c++/5/ostream:38,
from /usr/include/c++/5/iostream:39,
from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:107:37: note: ‘std::end’
template<typename _Tp> const _Tp* end(const valarray<_Tp>&);
^
Я хотел бы знать, почему эта ошибка происходит, причина, по которой я думаю, что это может произойти, - это то, что я являюсь указателем представления массива, некоторая информация теряется, но почему эта информация потеряна. Я не знаю, Кто-нибудь знает это в глубине? Также я искал это альтернативное решение:
template <std::size_t len>
void printList(int (&v)[len]) {
for (int a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
Что работает отлично, но если я использую что-то вроде этого:
template <std::size_t len>
void printList(int (&v)[len]);
int main(int argc, char const *argv[]) {
.........
}
void printList(int (&v)[len]) {
for (int a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
Я получаю сообщение об ошибке:
p4.cpp:15:25: error: ‘len’ was not declared in this scope
void printList(int (&v)[len]) {
^
p4.cpp: In function ‘void printList(...)’:
p4.cpp:16:16: error: ‘v’ was not declared in this scope
for (int a : v) {
Почему досуг случается? Есть ли простое решение без использования формата шаблона? Есть ли способ, которым я могу использовать аргумент как способ передать массив и информацию о неявном размере?
Ответы
Ответ 1
Диапазон, основанный на циклах, по сути является ничем иным, как синтаксическим сахаром, то есть извлекается из cppreference
for ( range_declaration : range_expression ) loop_statement
функционально эквивалентно следующему:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
где begin_expr
и end_expr
формируются следующим образом
1) Если выражение range_expression является выражением типа массива, тогда begin_expr является __range и end_expr (__range + __bound), где __bound - это количество элементов в массиве (если массив имеет неизвестный размер или имеет неполный тип, программа плохо сформирована)
2) Если выражение range_expression является выражением типа класса C, которое имеет член с именем begin и/или член с именем end (независимо от типа или доступности такого элемента), begin_expr является __range.begin() и end_expr __range.end();
3) В противном случае begin_expr запускается (__ диапазон), а end_expr - конец (__ диапазон), которые находятся через зависящий от аргумента поиск (не-ADL-поиск не выполняется).
Посмотрите, как это относится к вашему делу:
В первом случае v
, безусловно, является выражением типа массива или, если быть точным, типа int(&)[6]
, поэтому мы используем case (1), где __bound = 6
и т.д. (для краткости опущены полные вычитаемые замены)
Во втором случае, когда у вас есть функция, v
имеет тип int*
, и поскольку он не является типом массива и не имеет указателя, члены по умолчанию имеют случай (3), который использует ADL, чтобы определить функцию для вызова begin(__range)
, которая не дает результата для типов указателей, поэтому компилятор жалуется на error: ‘begin’ was not declared in this scope
.
В третьем случае вы сделали ошибку при попытке определить шаблон функции printList
. Вы должны сохранить часть template<...>
, включенную в объявление, иначе это просто определение функции. Вот почему компилятор сообщает вам error: ‘len’ was not declared in this scope
. Таким образом, правильный и рабочий код
template <std::size_t len>
void printList(int (&v)[len]);
int main(int argc, char const *argv[]) {
int v[] = {3, 4, 6, 9, 2, 1};
printList(v);
return 0;
}
template <std::size_t len>
void printList(int (&v)[len]) {
for (int a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
В других ответах уже предлагается использовать другой тип контейнера, например std::array<int, 6>
, чтобы повысить допустимую точку. Определенно взгляните на них, особенно с инициализацией скобок, которую вы можете обновить до них практически без затрат.
Ответ 2
"причина, по которой я думаю, что это может произойти, так как я указательное представление массива некоторая информация теряется, но почему эта информация потеряна, я не знаю".
Да. Массивы легко распадаются на указатели, а указатель не знает длины массива. Диапазон, основанный на цикле, необходим для оценки begin()
и end()
из типа данных.
Мое предложение состоит в том, чтобы избежать массивов стилей C и вместо этого использовать std::array
:
#include <iostream>
#include <array>
void printList(std::array<int,6> const& v);
int main(int argc, char const *argv[]) {
std::array<int,6> v{{3, 4, 6, 9, 2, 1}};
printList(v);
return 0;
}
void printList(std::array<int,6> const& v) {
for (auto a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
Ответ 3
Существует большая разница между массивом и указателем.
Вы можете выполнять итерацию массива с использованием range-based, поскольку размер массива известен во время компиляции. Однако то, что вы передаете функции - это указатель на первый элемент массива. Размер не известен на этом шаге, поэтому сбой на основе диапазона.
В вашем втором примере с шаблоном, уловка, вы забыли template <std::size_t len>
в определении printList
, так что у вас есть две разные функции, шаблонные и нетемплированные, которые вызывается.
В этом точном случае - я бы рекомендовал использовать более современные std::array
Ответ 4
Такие циклы for
используют функции-члены begin
и end
, чтобы определить, где начало и где конец последовательности.
Например, std::vector
имеет эти функции и, таким образом, поддерживает такую итерацию. Указатели фактически представляют собой целые числа, которые представляют адреса памяти и не имеют этих функций, что делает невозможным итерацию по указателю (что само по себе не имеет смысла).
Вместо этого вы можете сделать итерацию:
void printList(int *begin, int *end) {
for(; begin < end; ++begin)
std::cout << *begin;
std::cout << std::endl;
}
Это работает внутри main
в вашем случае, потому что массивы имеют begin
и end
, поскольку размер массива известен. Однако передача массива в функцию заставляет его распадаться на указатель.
Ответ 5
При передаче массива в функцию он распадается на указатель, поэтому он теряет способность использоваться с std:: begin и std:: end. Современный способ сделать то, что вы хотите, это использовать std:: array (вы не должны использовать C-образные массивы на С++, если это возможно):
#include <iostream>
#include <array>
template <typename T, size_t ArraySize>
void printList(const std::array<T, ArraySize>& v)
{
for (auto a : v) {
std::cout << a << " ";
}
std::cout << std::endl;
}
int main(int argc, char const *argv[]) {
std::array<int,6> v = {3, 4, 6, 9, 2, 1};
printList(v);
return 0;
}
Ответ 6
Массив внутри main
имеет известный размер.
После перехода к функции printList
он заглох на указатель на int
s, следовательно, вы получите ошибки.
Ответ 7
Вы можете передать массив фиксированного размера функции:
void printList(int (&v)[6]) { // pass by reference
for (auto& a : v) { // use reference to avoid making a copy
std::cout << a << " ";
}
}
Однако, конечно, мы не хотим писать функцию, которая работает только для массивов определенного размера. Здесь имеет смысл использовать шаблон:
template <int size>
void printList(int (&v)[size]) {
for (auto& a : v) {
std::cout << a << " ";
}
}
Приятно то, что когда вы вызываете функцию, вы даже не замечаете, что это шаблон, потому что компилятор может вывести параметр шаблона (он знает размер, конечно):
int main() {
int v[] = {3, 4, 6, 9, 2, 1};
printList(v);
}
печатает:
3 4 6 9 2 1
Ответ 8
int v[] = {3, 4, 6, 9, 2, 1};
получил тип int[6]
, а int *v
.. ну его тип int *
. Вы можете использовать указатель на int для доступа к массиву, но он не несет информацию о размере этого массива.
Вы можете передать массив, как это, но вы ограничите себя размером массива:
void foo(int (&p)[6])
или создать шаблон:
template <std::size_t size> void foo( int (&p)[size] )
Если по какой-то причине вы не можете использовать automic для() циклов (например, для переносимости на платформы, где поддержка С++ 11\14 сомнительна), вам нужно использовать либо std:: array\ std::vector, либо передать указатель и размер массива