In C there is no such thing as an "empty array". All arrays have a finite pre-defined size.
String sensors[10]; // Room for up to 10 sensors
The usual way of storing an unknown number of entries that will be added to "as and when" is to use a linked list or doubly-linked list.
A linked list is a list of items where each item also contains a pointer to say where the next item in the list is located.
Also you shouldn't use the String class as it is not good when working with linked lists (or in general - stick to C strings).
Warning: linked lists aren't for the faint of heart.
First you would define a struct to store the data you want in the linked list:
struct sensor {
char *name;
int currentValue;
struct sensor *next;
};
That would store the name of the sensor ("name"), the current value from the sensor ("currentValue") and also contains a pointer ("*next") to the next entry in the list. That last bit is the most important.
Now you create a "head" pointer variable to start your list off:
struct sensor *mySensors = NULL;
That starts out as NULL to say "This variable has nothing in it at the moment".
Now that was the easy part. The hard part is adding entries to the list...
A couple of helper functions can be of use here. Let's start by defining a function that will look for a specific entry by name in the list:
struct sensor *findSensor(const char *name) {
for (struct sensor *scan = mySensors; *scan; scan = scan->next) {
if (strcmp(scan->name , name) == 0) {
return scan;
}
}
return NULL;
}
That will start off with a pointer variable scan pointing to the first entry in the linked list (mySensors). While it's pointing to a valid entry (not NULL) compare the name to the given string and if they match then return the entry we are pointing to. Next point to whatever the "next" pointer is pointing to, so you move down the list.
Now to add a sensor:
struct sensor *addSensor(const char *name, int value) {
// First look to see if it exists:
struct sensor existing = findSensor(name);
if (existing != NULL) { // We found it
existing->currentValue = value; // Update the value
return existing;
}
// We haven't got it in our list, so create a new entry:
struct sensor newsensor = (struct sensor *)malloc(sizeof(struct sensor));
// And fill in the data:
newsensor->name = strdup(name); // Create a COPY of the string
newsensor->currentValue = value; // Store the value
newsensor->next = NULL;
// IMPORTANT: The new entry must point to NULL to mark the end of the list:
newsensor->next = NULL;
// Now where to add it in the list? Is there even a list?
if (mySensors == NULL) {
// No? Then we just make our new entry the head of the list:
mySensors = newsensor;
return newsensor;
}
// Otherwise we need to scan through the whole list to find
// where the tail of the list is:
struct sensor *scan = mySensors;
// While the current entry points to a valid next entry
while (scan->next) {
// move on to that next entry
scan = scan->next;
}
// There must be no valid next entry now (scan->next == NULL), so
// add our new sensor as the next valid entry:
scan->next = newsensor;
return newsensor;
}
Removing a sensor from the list is a much more involved process (even more than adding a sensor). You need to "splice" the list around the deleted entry. I'll add detail on that if you need it, but by the sound of things you won't be removing entries, only adding them.
The routines shown there (I haven't actually tested them, but it is standard code I write all the time) aren't as efficient as they could be. If you are adding and removing entries all the time and you end up with quite a large list it can take longer and longer to scan right through the list to find the last entry to add to. If that is the case you can maintain a "tail" pointer that you always update to point to the last added entry whenever you add one. That can then be used to rapidly add to the end of the list without having to scan through the whole list every time.
Another optimisation if you do a lot of scanning around within a list is to make a doubly linked list. This is just like the linked list above except it works in both directions. Not only do you store a next pointer, you also store a previous pointer. You can now scan in either direction - from head to tail or from tail to head, or hopping around at will inside the list. That is especially useful if you want to do things like ordering the list alphabetically.