You get the number of elements in an array with sizeof(array) / sizeof(array[0]).
Supposing an int is 2 bytes, then sizeof(array) will be 12 because there are 6 ints in it, and sizeof(array[0]) will be 2, the size of an int, so 12 / 2 = 6 elements in the array.
So to get the last element in the array you can do array[ ( sizeof(array) / sizeof(array[0]) ) - 1 ] because array index start from 0
You don't need to know the indices to iterate over an array:
const int myArray [] = {4,5,6,7,8,9};
for (int element : myArray) { // for each element in the array
Serial.println(element);
}
If you do need the number of elements, you can use the sizeof approach, or you could define a len function like this:
// A function that accepts arrays of any type T and any length N,
// and returns the length N.
template <class T, size_t N> constexpr size_t len(const T(&)[N]) { return N; }
const int myArray [] = {4,5,6,7,8,9};
Serial.println(len(myArray)); // 6
for (size_t i = 0; i < len(myArray); ++i) {
Serial.println(myArray[i]);
}
const int myArray [] = {4,5,6,7,8,9};
for (int element : myArray) { // for each element in the array
Serial.println(element);
}
is the same as:
const int myArray [] = {4,5,6,7,8,9};
const int myArraySize = sizeof(myArray)/sizeof(myArray[0]);
for (int i = 0; i < myArraySize;i++)
{
Serial.println(myArray[i]);
}
const int myArray1 [] = {4,5,6,7,8,9};
const int myArray2 [] = {10,11,12,13,14,15};
for (int element : (myArray1+myArray2)) { // for each element in the two arrays
Serial.println(element);
}
so the result that will be printed should be:
4,5,6,7,8,9,10,11,12,13,14,15
It is definitely possible, but you need to define yourself how something like that should be iterated over:
Basically:
define a struct range_cat_ that contains (pairs of pointers to) all arrays/ranges you wish to iterate over
define range_cat_::begin and range_cat_::end methods that return iterators to iterate over this concatenation of ranges
define the behavior of these iterators: they increment a pointer to point to the next element in the current array, but if this was the last element in the current array, skip to the first element in the next array
define some boilerplate methods required for iterators, such as dereference/element access and comparison to the end iterator
define a function to concatenate/combine two instances of range_cat_, resulting in a bigger list of arrays to be iterated over
define a function range_cat that takes a list of arrays and returns a range_cat_ instance that iterates over these arrays in sequence
#include <cstddef>
namespace detail {
/// Represents a range that can be iterated over using pointers.
template <class T>
struct subrange {
T *begin, *end;
operator subrange<const T>() const { return {begin, end}; }
};
/// Contains an array of N ranges that can be iterated over.
/// If one range is finished, iteration continues at the start
/// of the next range, until all N ranges have been iterated over.
template <class T, size_t N>
struct range_cat_ {
subrange<T> subranges[N];
struct iterator {
subrange<T> *cur_subrange, *end_subrange;
T *elem_iter;
/// Advance the iterator.
iterator &operator++() {
++elem_iter;
if (elem_iter == cur_subrange->end) {
++cur_subrange;
if (cur_subrange != end_subrange)
elem_iter = cur_subrange->begin;
}
return *this;
}
T &operator*() { return *elem_iter; } ///< Element access
T *operator->() { return elem_iter; } ///< Element access
/// Compare the iterator to a given end iterator.
bool operator!=(iterator end) const { return cur_subrange != end.cur_subrange; }
};
// These begin/end methods are special, they are called
// implicitly when you use a range-based for loop:
iterator begin() { return { &subranges[0], &subranges[N], subranges[0].begin }; }
iterator end() { return { &subranges[N], &subranges[N], subranges[N-1].end }; }
};
// The following functions concatenate two range_cat_ instances
// into one bigger range_cat_.
template <class ResT, class T1, size_t N1, class T2, size_t N2>
range_cat_<ResT, N1 + N2> combine_range_cats(const range_cat_<T1, N1> &i1, const range_cat_<T2, N2> &i2) {
range_cat_<ResT, N1 + N2> res;
for (size_t i = 0; i < N1; ++i)
res.subranges[i] = i1.subranges[i];
for (size_t i = 0; i < N2; ++i)
res.subranges[i + N1] = i2.subranges[i];
return res;
}
template <class T, size_t N1, size_t N2>
range_cat_<T, N1 + N2> operator+(const range_cat_<T, N1> &i1, const range_cat_<T, N2> &i2) {
return combine_range_cats<T>(i1, i2);
}
template <class T, size_t N1, size_t N2>
range_cat_<const T, N1 + N2> operator+(const range_cat_<const T, N1> &i1, const range_cat_<T, N2> &i2) {
return combine_range_cats<const T>(i1, i2);
}
template <class T, size_t N1, size_t N2>
range_cat_<const T, N1 + N2> operator+(const range_cat_<T, N1> &i1, const range_cat_<const T, N2> &i2) {
return combine_range_cats<const T>(i1, i2);
}
} // namespace detail
template <class T, size_t N>
detail::range_cat_<T, 1> range_cat(T (&first)[N]) {
return detail::range_cat_<T, 1> {{{&first[0], &first[N]}}};
}
template <class T, size_t N, class... Others>
auto range_cat(T (&first)[N], Others&... others) { // return type deduction requires C++14, use traits/metaprogramming in C++11
return range_cat(first) + range_cat(others...);
}
#include <iostream>
struct {
template <class T>
void println(const T &t) { std::cout << t << std::endl; }
} Serial;
int main() {
const int myArray1[] = {4,5,6,7,8,9};
int myArray2[] = {10,11,12,13,14};
const int myArray3[] = {15,16,17};
for (int element : range_cat(myArray1, myArray2, myArray3)) {
Serial.println(element);
}
}
This requires some knowledge of iterators and how a range-based for loops work under the hood.
I wouldn't recommend this, unless you abstract it away in a library somewhere.
If you want to iterate over multiple arrays with the same loop body, simply extract your loop body as a function and write multiple for loops or calls to std::for_each:
void important_task(int element) {
Serial.println(element);
}
int main() {
const int myArray1[] = {4,5,6,7,8,9};
const int myArray2[] = {10,11,12,13,14,15};
for (int element : myArray1)
important_task(element);
for (int element : myArray2)
important_task(element);
}
If you only need important_task in a single place in your code, or if you need local variables, use a lambda function:
int main() {
const int myArray1[] = {4,5,6,7,8,9};
const int myArray2[] = {10,11,12,13,14,15};
int local_var = 42;
auto important_task = [&](int element) {
Serial.println(local_var + element);
};
for (int element : myArray1)
important_task(element);
for (int element : myArray2)
important_task(element);
}
const int myArray1 [] = {4,5,6,7,8,9};
const int myArray2 [] = {16,15,17,18};
const int myArray3 [] = {41,42,43,44,45,46};
const int myArray4 [] = {51,52,553,54};
const int main_ARRAY = myArray3;
for (int element : main_ARRAY) {
Serial.println(element);
}
// another lines of code here //
for (int element : main_ARRAY) {
Serial.println(element);
}
so basically instead of changing the array in each for loop inside my function I would like to change it one time up at the const int main_ARRAY = myArray3; without the need to look each time where is the 'for' function.
This defines a class template with the name span. (A class is a user-defined data type that contains other data members and member functions that can be applied to this data.)
It is a class template and not a class, because you first have to fill in the placeholder T into the template before you get a concrete class. The syntax template <class T> declares this placeholder T that can be replaced by any data type.
Further down in the code, where you write span<const int>, the compiler instantiates an actual class and replaces the template parameter T by const int, implicitly defining the following class (you don't see this, the compiler does this for you):
class span<const int> {
private:
const int *begin_, *end_;
[...]
};
The reason for using a template here is so you can use the span class for any type of arrays, arrays of integers, arrays of booleans, arrays of strings, etc. You make this class independent of the specific type by using templates (generic programming).
Similarly, you use a template argument size_t N in the constructor to support arrays of any length N.
There's no span::operator=(T (&)[N]) but you do have a converting constructor span::span(T (&)[N]) and the compiler-generated copy and move assignment operators span::operator=(const span &) and span::operator=(span &&).
That's non-intuitive. It looks like it constructs a span object using myArray3 and then constructs another another span object using the first one. Why?
I think the = syntax in the result is a bit confusing because it shows every constructor call explicitly.
The innermost constructor call is the converting constructor from array to span, and the outermost constructor call is the span move constructor. You get a copy constructor call because of the assignment, if you initialize it directly you only get a single constructor call:
Note that in the insights pseudocode, the = no longer implies any copy/move constructor or assignment operator calls, it just indicates the assignment to a variable.