Character Arrays vs. Strings

About this Post

If you work with character sequences in your programs, you can use character arrays and String objects. Beginners in particular tend to use String objects because they are more convenient than character arrays. However, character arrays are more resource-efficient and should therefore be preferred.

In this article, I will discuss and compare the essential operations that you can apply to character arrays and String objects. You will see that character arrays are in no way inferior to String objects in terms of functionality.

We will look at the following operations in detail:

OperationFunction (Character Arrays)Function (String objects)
Determine size / lengthsizeof() / strlen()sizeof() / length()
Concatenatestrcat() / strncat()Operator ′+=′
Duplicatestrdup()Operator ′=′
Convert to upper/lower case letters strupr() / strlwr()toUpperCase() / toLowerCase()
Read characters by indexcharArray[index]charAt()
Replace / delete characterscharArray[index] setCharAt() / remove()
Create substringsmemcpy() / strchr() / strrchr()substring()
Split by characterstrtok()substring() & indexOf()
Search charactersstrchr() / strrchr() / strstr()indexOf() / lastIndexOf()
Comparestrcmp()equals() / equalsIgnoreCase() /
compareTo() / Operator ′==′
Conversion Char Arrays ↔ Stringsto String: Operator ′=′to Char Array: toCharArray() /
c_str()
Create from numbersdtostrf() / sprintf()String()
Convert into numbers atof() / atoI() / atol()toInt() / toFloat()
Read in via serialreadBytes() / readBytesUntil()readString() / readStringUntil()
Transfer to functions
Functions covered in the article

Nomenclature

When I talk about character arrays in this article, I am referring to arrays of the data type char that end with the NULL character '\0' (also known as the NULL terminator). You can also create non-NULL-terminated character arrays. However, these are pure arrays, to which you cannot apply most of the functions discussed here. In this respect, the term “character array” is ambiguous, but I don’t want to add “NULL terminated” every time.

I refer to the instances of the String class that you create with String myString = "...." or String myString = String (....) as String objects. String objects are usually simply called strings. However, this designation is even more ambiguous than the character arrays just discussed. The term “string” could simply be the generic term for String objects and character arrays. Or take a look at the Arduino reference here, where character arrays are dealt with under the heading “string”.

To complete the confusion, there is a class called std::string in the C++ standard library, which is similar to the String class developed specifically for the Arduino environment. For these reasons, I will consistently use the term “String object” (with capital “S”) in this article. That is a bit unwieldy, but hopefully clear.

Length and Size – or: The Difference between Character Arrays and String Objects

In the first sketch, we define a character array and a String object. The length, i.e. the number of characters, and the size, i.e. the memory space occupied in bytes, are then determined.

You can find out the length of a character array with:

size_t length = strlen( myCharArray );

You can find out the length of a String object with :

size_t length = myString.length();

You can determine the size of a character array or a string object as follows:

size_t sizeInBytes = sizeof( myCharArrayOrMyString );

For the less experienced: The nice thing about the data type size_t is that you don’t have to worry about the actual size of the return value. You are on the safe side, regardless of the microcontroller used.

Now to the sketch:

void setup(){
    Serial.begin(9600);
    char myCharArray[] = "Hello, I am a character array";
    // char myCharArray[50] = "Hello, I am a character array";
    // char myCharArray[] = {'H','e','l','l','o',',',' ','I',' ','a','m',' ','a',' ','c','h','a','r','a','c','t','e','r',' ','a','r','r','a','y','0'};
    String myString = "Hi all, I am a string";

    Serial.println(myCharArray);
    Serial.print("Size of myCharArray: ");
    size_t charSize = sizeof(myCharArray);
    Serial.println(charSize);

    Serial.print("Length of myCharArray: ");
    size_t charLength = strlen(myCharArray); 
    Serial.println(charLength);
    Serial.println();

    Serial.println(myString);
    Serial.print("Size of myString: ");
    size_t stringSize = sizeof(myString);
    Serial.println(stringSize);
    
    Serial.print("Length of myString: ");
    size_t stringLength = myString.length();
    Serial.println(stringLength);
 }

 void loop(){}

 

And here is the output I got using an Arduino Nano (ATmega328P):

Output of length_vs_size.ino
Output of length_vs_size.ino

Conclusions on the Character Array

As you can see, the length of the character array is 29. If you count, this is actually the number of characters. However, the size is 30 bytes, as the invisible NULL terminator '\0' is appended at the end. The NULL terminator signals the end of the character array.

If you swap the comment line 3 and uncomment line 4, the length of the character array remains the same, but the size changes to 50 bytes.

The alternative definition of myCharArray in line 5 more complex, but makes it clearer that it is an array. With this notation, you must append the NULL terminator yourself. Or you can define the length explicitly and leave space for the NULL terminator, e.g.:

char charArray[6] = {'H','e','l','l','o'};

Conclusions on the String Object

Things get strange with the String object. The length correctly indicates the number of characters. But why is the size of the String object 6 bytes? Well, this is because the String object is stored in two parts. The actual character string (“string buffer”) is located in the heap. The 6 bytes in the stack are only a kind of table of contents. The pointer to the heap address of the string buffer, the capacity of the String object and its length are stored there.

When using other microcontrollers, things may be different. For example, with an ESP32, a string buffer is only stored in the heap if the length of the character string exceeds 13 characters. The size of the Atring object in the stack is always 16 bytes.

If you would like to know more about string objects in the stack and heap, you could read my post about SRAM management. There I also discuss the notorious heap fragmentation, which is often cited as an argument against the use of String objects.

Concatenating Character Arrays and String Objects

Concatenating Character Arrays

This is how you concatenate two character arrays:

strcat( char* destination, char* source );

The “source” character array is appended to the “destination” array. You must ensure that “destination” is large enough to accommodate “source”. If this is not the case, there is a risk that you will overwrite memory locations that could contain other variables. The consequences are unpredictable. The problem is that there is no error message when compiling.

Alternatively, you can append the first x characters of “source” to “destination”:

strncat( char* destination, char* source, size_t x);

Why do we pass “char* charArray”?

The name of an array represents the pointer to the beginning of the array. So if you define a character array as follows:  char myCharArray[] = "....", then myCharArray is a pointer of the data type char. When passing to a function, only the pointer is passed and not the entire array. I will discuss the passing of character arrays and String objects in detail later.

Concatenating String Objects

String objects are simply concatenated using the “+” operator:

String destination += String source;

The undeniably pleasant thing about String objects is that you (supposedly) don’t have to worry about their length. However, the extension of String objects can lead to heap fragmentation. To prevent this, you can reserve defined memory space for the String Object:

myString.reserve( unigned int size );

Where size is the expected maximum length of your string object.

Here is a small example sketch on the subject of concatenation:

void setup(){
   Serial.begin(9600);
   char myCharArray1[13] = "Hello";
   char myCharArray2[] = " World!";

   strcat(myCharArray1, myCharArray2);
   // strncat(myCharArray1, myCharArray2, 5);  // add the first 5 chars
   Serial.println(myCharArray1);

   String myString1 = "Hello";
   String myString2 = " Earth!";

   myString1 += myString2;

   Serial.println(myString1);
}

void loop(){}

The unsurprising output is:

Output of concatenate.ino
Output of concatenate.ino

Duplicating Character Arrays and String Objects

This is how you create a copy of a character array:

char* copy = strdup( const char* original );

It is even easier with String objects:

String copy = String original;

Here is an example:

void setup(){
    Serial.begin(9600);
    
    char myCharArray[] = "Hello, I am a character array";
    Serial.print("Original: ");
    Serial.println(myCharArray);

    char* myCharArrayCopy = strdup(myCharArray);
    Serial.print("Copy: ");
    Serial.println(myCharArrayCopy);
    Serial.println();

    String myString = "Hi all, I am a string";
    Serial.print("Original: ");
    Serial.println(myString);

    String myStringCopy = myString;
    Serial.print("Copy: ");
    Serial.println(myStringCopy);    
}

void loop(){}

And here, for the sake of completeness, the output:

Output duplicate.ino
Output duplicate.ino

Converting to Upper or Lower Case Letters

There are also very convenient functions for converting character arrays and String objects into upper or lower case letters.

You can convert a character array as follows:

strupr( char* charArray );  // Upper case letters

strlwr( char* charArray );  // Lower case letters

Use the following functions for String objects:

myString.toUpperCase();  or  myString.toLowerCase();

void setup(){
    Serial.begin(9600);
    
    char myCharArray[] = "Hello, I am a character array";
    Serial.print("Original: ");
    Serial.println(myCharArray);
   
    Serial.print("Upper Case: ");
    strupr(myCharArray);
    Serial.println(myCharArray);
   
    Serial.print("Lower Case: ");
    strlwr(myCharArray);
    Serial.println(myCharArray);
    
    Serial.println();

    String myString = "Hi all, I am a string";
    Serial.print("Original: ");
    Serial.println(myString);

    Serial.print("Upper Case: ");
    myString.toUpperCase();
    Serial.println(myString);
    
    Serial.print("Lower Case: ");
    myString.toLowerCase();
    Serial.println(myString);
}

void loop(){}

 

The output is:

Output upper_lower_case.ino
Output upper_lower_case.ino

Reading a Character by Index

I don’t really need to explain how to read a character at a specific position in the character array:

char charAtIndexI = charArray[i];

There is a separate function for String objects for this purpose:

char charAtIndexI = myString.charAt(i);

Here’s an example:

void setup(){
    Serial.begin(9600);
    char myCharArray[] = "Hello, I am a character array!";

    Serial.print("Tenth character: ");
    char charAtIndex10 = myCharArray[10];
    Serial.println(charAtIndex10);

    String myString = "Hi all, I am a string";
    
    Serial.print("Tenth character: ");
    charAtIndex10 = myString.charAt(10);
    Serial.println(charAtIndex10);
 }

 void loop(){}

The sketch gives the following output:

Output read_char_at_index_i.ino
Output read_char_at_index_i.ino

Replacing and Deleting Characters

In a character array, you replace characters by accessing them via the index:

charArray[index] = 'x'; // replace x by any other character

To delete characters, you have to “tinker” a little. You let the remaining characters move to the left after the characters to be deleted, and then attach the NULL terminator.

String objects have their own functions for replacing and deleting characters:

myString.setCharAt[index] = 'x';

myString.remove( index, length );

Example sketch:

void setup(){
    Serial.begin(9600);
    
    char myCharArray[] = "abcdefghijklm";
    Serial.print("Original: ");
    Serial.println(myCharArray);

    myCharArray[3] = 'c';
    Serial.print("\"d\" replaced by \"c\": ");
    Serial.println(myCharArray);

    remove(myCharArray, sizeof(myCharArray), 3, 5);
    Serial.print("Removed 5 chars from index 3: ");
    Serial.println(myCharArray);
    Serial.println();

    String myString = "nopqrstuvwxyz";
    Serial.print("Original: ");
    Serial.println(myString);
    
    myString.setCharAt(3, 'p');
    Serial.print("\"q\" replaced by \"p\": ");
    Serial.println(myString);

    myString.remove(3, 5);
    Serial.print("Removed 5 chars from index 3: ");
    Serial.println(myString);    
}

void loop(){}

void remove(char *cArr, size_t size, size_t index, size_t len){  // remove for char arrays
    for(size_t i = index; i < (size-len); i++){
        cArr[i] = cArr[i+len];
    }
    cArr[size-len] = '\0';
}

 

The output is as expected:

Output replace_remove.ino
Output replace_remove.ino

Substrings

Sections of Character Arrays Starting from a Certain Index

Creating substrings from character strings is a bit awkward. We use the memcpy() function for this. In general terms, the function is used as follows:

void* memcpy ( void* destination, const void* source, size_t num );

“Translated”, this means: memcpy() copies num bytes, starting at the address to which the pointer source points, and copies them to the address to which the pointer destination points. The data type void* makes memcpy() versatile. It does not matter which data type the pointers to be passed point to. To create a substring, we use the function as follows:

memcpy( subCharArray, charArray + index, length );

When you specify the length of subCharArray, please consider the NULL terminator. Regardless of this, you must actually attach the NULL terminator. Or, even simpler, intialize your subCharArray as {'\0'}. As a result, all elements of the array are initialized as NULL characters.

Substrings of Character Arrays starting From a Specified Character

If you want to create a substrings that begins at a specific character of the original array and captures all characters from there to the end, then the function strchr() comes into consideration. The following line of code defines the pointer subCharArray, which points to the address at which the character charToBeFound appears for the first time in charArray:

char* subCharArray = strchr(const char* charArray, char charToBeFound);

Alternatively, you can use strrchr() (with two “r “s). The function returns a pointer that points to the last occurrence of the character you are looking for.

You just need to be aware that subCharArray is still part of charArray. Changes to subCharArray also change charArray. If you do not want to do this, you should create a copy with strdup() or via memcpy().

Substrings from String Objects

For String objects, there is the extremely convenient function substring() available. You can use it to create a substring that starts at a specific index. If you only pass the index, all other characters are included in the substring until the end. Alternatively, you can pass a second index before which the substring ends.

String subString = myString.subtring( index );

String subString = myString.substring( startIndex, endIndex ); // substring from startIndex to endIndex - 1

Here is the example:

void setup(){
    Serial.begin(9600);
    
    char myCharArray[] = "abcdefghijklm";
    Serial.print("Original: ");
    Serial.println(myCharArray);

    char mySubCharArray[6] = {'\0'};
    Serial.print("Substring from index 3 to 7: ");
    memcpy(mySubCharArray, myCharArray+3, 5);
    Serial.println(mySubCharArray);

    char mySubCharArray_2[6] = {'\0'};
    Serial.print("Substring 3-7, alternative: ");
    subArray(mySubCharArray_2, myCharArray, 3, 5);
    Serial.println(mySubCharArray_2);
    
    char *mySubCharArray_3 = strchr(myCharArray, 'i');  // strchr(): first occurance / strrchr(): finds last occurance
    Serial.print("Substring, beginning at \"i\": ");
    Serial.println(mySubCharArray_3);
    Serial.println();
       
    String myString = "nopqrstuvwxyz";
    Serial.print("Original: ");
    Serial.println(myString);
    
    String mySubString = myString.substring(3, 8);
    Serial.print("Substring from index 3 to 7: ");
    Serial.println(mySubString);   

    String mySubString_2 = myString.substring(7);
    Serial.print("Substring from index 7: ");
    Serial.println(mySubString_2);   
}

void loop(){}

void subArray(char* destination, char* source, size_t index, size_t len){
    memcpy(destination, source + index, len);
}

 

The sketch generates the following output:

Output substrings.ino
Output substrings.ino

Complete Splitting of the Character Sequence by Specific Characters

You may want to split character arrays and String objects by certain characters. This could be necessary, for example, if a character sequence contains several measured values that are separated from each other by a specific character (delimiter).

Here, the character arrays offer the simpler solution:

char* chunk = strtok( char* charArray, char delimiter );

chunk is a character array that contains all characters up to the first occurrence of the delimiter. The pleasant but perhaps also confusing thing is that you pass the NULL pointer to extract the next section:

char* nextChunk = strtok( NULL, char delimiter );

This works because strtok() remembers the address of the character array and the index of the last delimiter. If no more delimiters are found, strtok() returns the NULL pointer.

You can split string objects according to delimiters by searching for the delimiters with indexOf() and creating the sections with substring().

Here is the example of this:

void setup(){
    Serial.begin(9600);
    char myCharArray[] = "Hello, I am a character array";
    String myString = "Hi all, I am a string";

    char *cArrChunk = strtok(myCharArray, " ");
    while(cArrChunk != NULL){
        Serial.println(cArrChunk);
        cArrChunk = strtok(NULL, " ");
    }
    Serial.print("myCharArray: ");
    Serial.println(myCharArray);
    Serial.println();

    String stringChunk = myString.substring(0, myString.indexOf(" "));
    while(myString != ""){
        Serial.println(stringChunk);
        myString.remove(0, stringChunk.length()+1);
        stringChunk = myString.substring(0, myString.indexOf(" "));
    }
    Serial.print("myString: ");
    Serial.println(myString);
}

void loop(){}

Here is the output:

Output mince_char_arrays_and_strings.ino
Output mince_char_arrays_and_strings.ino

As you can see, myCharArray and myString are modified by this procedure. If you don’t want that, you’ll have to work with copies.

Searching Characters

We have already searched for specific characters before, but I want to come back to this topic again, as we have omitted some aspects so far. This also includes determining the index of a specific character in a character array. The determination of the index for the last occurrence of a character in a string object is also missing. 

Searching Characters in a Character Array

As we have seen, strchr() returns the pointer to the address where the character we are searching is located for the first time. strrchr() provides the pointer to the last address where the searched character is located. To calculate the index from these pointers, simply subtract the pointer to the character array:

indexOfCharX = strchr( char* charArray, char charX ) - charArray;

Alternatively, you can, of course, compare every character in your array with the character you are searching for. But that is less elegant.

If you are not searching for a character but for a character sequence within the character array, you can use the strstr() function:

char* ptrToFirstString = strstr( char* charArray, char* searchString );

If strchr(), strrchr() and strstr() end their search unsuccessfully, they return the NULL pointer.

Searching Characters in a String Object

As we have seen, we can find out the index for the first occurrence of a character in a String object using indexOf(). However, the function also accepts character arrays and String objects:

int indexOfFirstStringX = myString.indexOf( char* / String stringX);

You can find the index for the last occurrence of one or more characters with :

int indexOfLastStringX = myString.lastIndexOf( char* / String stringX );

You can then determine the index for the first or last occurrence of a character or character string from a certain index:

int indexOfFirstStringXAfterIndexY = myString.indexOf( char* / String stringX, size_t indexY);

int indexOfLastStringXAfterIndexY = myString.lastIndexOf( char* / String stringX, size_t indexY);

If indexOf() and lastIndexOf() end their search unsuccessfully, they return -1.

Example sketch:

void setup(){
    Serial.begin(9600);
    char myCharArray[] = "I am a character array!";

    Serial.print("Rest of myCharArray beginning at first \"a\": ");
    char *ptrToFirstA = strchr(myCharArray, 'a');
    if (ptrToFirstA != NULL){
        Serial.println(ptrToFirstA);
    }
    else{
        Serial.println("Not found");
    }
    
    Serial.print("Rest of myCharArray beginning at first \"x\": ");
    char *ptrToFirstX = strchr(myCharArray, 'x');
    if (ptrToFirstX != NULL){
        Serial.println(ptrToFirstX);
    }
    else{
        Serial.println("Not found");
    }

    Serial.print("Rest of myCharArray beginning at last \"a\": ");
    char *ptrToLastA = strrchr(myCharArray, 'a');
    Serial.println(ptrToLastA);
    
    Serial.print("Last \"a\" is at position: ");
    int indexOfLastA = findLastIndexOf(myCharArray, sizeof(myCharArray), 'a'); 
    Serial.println(indexOfLastA);

    Serial.print("Alternative Method: Last \"a\" position: ");
    indexOfLastA = ptrToLastA - myCharArray;
    Serial.println(indexOfLastA);

    Serial.print("First \"ar\" position: ");
    char *ptrToFirstAR = strstr(myCharArray, "ar");
    int indexOfFirstAR = ptrToFirstAR - myCharArray;
    Serial.println(indexOfFirstAR);
}

void loop(){}

size_t findLastIndexOf(char *cArr, size_t size, char searchChar){
    size_t lastPos = -1;
    for(size_t i=0; i<size; i++){
        if(cArr[i] == searchChar){
            lastPos = i;
        }
    }
    return lastPos;
}
void setup(){
    Serial.begin(9600);
    String myString = "I am not a character array but a String";

    Serial.print("First index of \"t\": ");
    int indexOfFirstT = myString.indexOf("t");
    Serial.println(indexOfFirstT);

    Serial.print("First index of \"t\" after pos. 7: ");
    int indexOfFirstTAfterPos7 = myString.indexOf("t", 8);
    Serial.println(indexOfFirstTAfterPos7);

    Serial.print("First index of \"x\": ");
    size_t indexOfFirstX = myString.indexOf("x");
    if((int)indexOfFirstX != -1){
        Serial.println(indexOfFirstX);
    }
    else{
        Serial.println("Not found");
    }

    Serial.print("Last index of \"t\": ");
    int indexOfLastT = myString.lastIndexOf("t");
    Serial.println(indexOfLastT);

    Serial.print("Last index of \"ar\": ");
    char searchString[] = "ar";
    //String searchString = "ar";
    int indexOfLastAR = myString.lastIndexOf(searchString);
    Serial.println(indexOfLastAR);
}

void loop(){}

 

And here are the outputs:

Output find_characters.ino, left: character array, right: String object
Output find_characters.ino, left: character array, right: String Object

Compare

Comparing Character Arrays

This is how you compare two character arrays:

int result = strcmp( char* charArray1, char* charArray2 );

If you only want to compare the first n characters of the two arrays, then pass “n” as the third parameter:

int result = strcmp( char* charArray_1, char* charArray_2, size_t n );

The strcmp() function compares the arrays character by character starting at index 0. This continues until it encounters the first different character or reaches the end or the last of the characters to be compared. If the function encounters a different character, result corresponds to the difference between the ASCII codes of these characters. If result is negative, then charArray_1 contains the character with the lower ASCII code. If result is positive, then it is the other way around.  If result is 0, then the two character arrays are identical, or more precisely: they have an identical character sequence.

If you want to perform the comparison regardless of upper or lower case letters, you can convert the character arrays with strupr() or strlwr() before the comparison. However, this will permanently change them, so you may need to make copies.

Comparing String Objects

To check whether two String objects are the same, use the function equals():

bool result = myString.equals( myString_2 );

If you only want to check parts of myString_2 and myString for identity, you must combine equals() with substring(). You can ignore upper and lower case with the variant equalsIgnoreCase():

bool result = myString.equalsInoreCase( myString_2 );

As an alternative to equals(), use the function compareTo():

int result = myString.compareTo( myString_2 );

Just like strcmp(), compareTo() compares the two String objects character by character. If two characters are different, the function returns the difference between the ASCII codes of these characters.

void setup(){
    Serial.begin(9600);
    char myCharArray1[] = "I am a character array!";
    char myCharArray2[] = "I am also a character array!";
    char myCharArray3[] = "I am a character array!";
    char myCharArray4[] = "I aM A cHaRaCteR aRrAy!";
    char myCharArray5[] = "abcd";
    char myCharArray6[] = "aacd";
  

    Serial.print("Comparing 1, 2: ");
    int result = strcmp(myCharArray1, myCharArray2);
    Serial.println(result);

    Serial.print("Comparing 1, 2 (first 6 characters): ");
    result = strncmp(myCharArray1, myCharArray2, 6);
    Serial.println(result);

    Serial.print("Comparing 1, 3: ");
    result = strcmp(myCharArray1, myCharArray3);
    Serial.println(result);

    Serial.print("Comparing 1, 4: ");
    result = strcmp(myCharArray1, myCharArray4);
    Serial.println(result);

    Serial.print("Comparing 1, 4, non-case-sensitive: ");
    result = strcmp(strlwr(myCharArray1), strlwr(myCharArray3)); // changes the char arrays permanently!
    Serial.println(result); 
    Serial.println(myCharArray1);

    Serial.print("Comparing 5, 6: ");
    result = strcmp(myCharArray5, myCharArray6);
    Serial.println(result);

    Serial.print("Comparing 6, 5: ");
    result = strcmp(myCharArray6, myCharArray5);
    Serial.println(result);
 }

void loop(){}
void setup(){
   Serial.begin(9600);
   String myString1 = "I am a String!";
   String myString2 = "I am also a String!";
   String myString3 = "I am a String!";
   String myString4 = "I aM A sTrInG!";
   String myString5 = "abcd";
   String myString6 = "aacd";
 

   Serial.print("Comparing 1, 2: ");
   int result = (myString1.equals(myString2));
   Serial.println(result);

   Serial.print("Comparing 1, 2 (first 6 characters): ");
   result = (myString1.substring(0,6)).equals(myString2.substring(0,6));
   Serial.println(result);
  
   Serial.print("Comparing 1, 3: ");
   result = myString1.equals(myString3);
   Serial.println(result);

   Serial.print("Comparing 1, 4: ");
   result = myString1.equals(myString4);
   Serial.println(result);

   Serial.print("Comparing 1, 4, non-case-sensitive: ");
   result = myString1.equalsIgnoreCase(myString4);
   Serial.println(result);
   Serial.println(myString1);

   Serial.print("Comparing 5, 6: ");
   result = myString5.compareTo(myString6);
   Serial.println(result);

   Serial.print("Comparing 6, 5: ");
   result = myString6.compareTo(myString5);
   Serial.println(result);
}

void loop(){}

 

The outputs are:

left: output compare_char_arrays.ino, right: compare_strings.ino
left: output compare_char_arrays.ino, right: compare_strings.ino

Using the “==” Operator

If you use the operator == on two character arrays, it compares two pointers. A true  is only obtained if the two pointers point to the same address, i.e. if the two character arrays are not only the same, but actually identical.  

With String objects, on the other hand, the operator == checks whether the “content”, i.e. the string buffers, are the same. equals() and == are equivalent. 

Here is an example sketch:

void setup(){
    Serial.begin(9600);
    char myCharArray1[] = "I am a character array!";
    char myCharArray2[] = "I am a character array!";
    char* myCharArray3 = myCharArray2;
    String myString1 = "I am a String!";
    String myString2 = "I am a String!";

    Serial.print("Char array comparison 1: ");
    if(myCharArray1 == myCharArray2){   // Compares the pointers, not the arrays
        Serial.println("Identical");
    }
    else{
        Serial.println("Different");
    }

    Serial.print("Char array comparison 2: ");
    if(myCharArray2 == myCharArray3){   // Compares the pointers, not the arrays
        Serial.println("Identical");
    }
    else{
        Serial.println("Different");
    }

    Serial.print("Char array comparison 3: ");
    if(myCharArray1 == "I am a character array!"){  // Don't do this!!!!
        Serial.println("Identical");
    }
    else{
        Serial.println("Different");
    }

    Serial.print("String comparison 1: ");
    if(myString1 == myString2){
        Serial.println("Equal");
    }
    else{
        Serial.println("Different");
    }

    Serial.print("String comparison 2: ");
    if(myString1 == "I am a String!"){
        Serial.println("Equal");
    }
    else{
        Serial.println("Different");
    }
 }

 void loop(){}

 

Here is the output for compare_2.ino:

Output compare_2.ino
Output compare_2.ino

Creating Character Arrays from String Objects and vice versa

Creating a String object from a character array is simple:

String myString = ( const char* myCharArray );

There are two functions you can use to create character arrays from string objects:

myString.toCharArray( char* myCharArray, size_t length );

const char* myCharArray = myString.c_str();

Use the function toCharArray() to copy the string buffer, i.e. the character sequence, into a previously defined character array. The first parameter to be passed to toCharArray() is the character array, the second is the length of the character array. The NULL terminator is attached automatically. You must take this into account when defining the length.

The function c_str() does something entirely different by returning the pointer to the string buffer in the heap (i.e. where the content of the String object is stored). In other words, nothing new is created here, but you access the character sequence of the String object directly. In this respect, it makes sense for the return value to be a constant. This protects you from unintentionally changing your String object. If you intend to change the character array, it is best to create a copy with memcpy() (see example sketch). But then you could just as well use toCharArray().

So why do we need c_str()? This function is always useful if you want to pass on the content of the String object unchanged to another function. The c_str() function is much faster and saves SRAM.

void setup(){
    Serial.begin(9600);
    char myCharArray[] = "I was born as a character array!";

    String stringFromCharArray = String(myCharArray);
    Serial.println(stringFromCharArray);

    String myString = "I was born as a String";

    char charArrayFromString[myString.length() + 1];
    myString.toCharArray(charArrayFromString, myString.length() + 1);
    Serial.println(charArrayFromString);

    char charArrayFromString2[myString.length() + 1];
    memcpy(charArrayFromString2, myString.c_str(), myString.length() + 1);
    Serial.println(charArrayFromString2);
}

void loop(){}

And here is the unsurprising output:

Output char_array_to_string_and_string_to_char_array.ino
Output char_array_to_string_and_string_to_char_array.ino

Converting Numbers to Character Arrays and String Objects

Decimal Numbers into Character Arrays

Floating-point numbers (double and float) and integers (int, long) can be easily converted into character arrays using the dtostrf() function:

dtostrf( double myDouble, signed char min_width, unsigned char precision, char* myCharArray );

The parameters are:

  • myDouble: The number to be converted. Alternative data types: float, int, long.
  • min_witdth: Minimum width of the output. If your converted number is shorter, it will be padded with spaces and shifted to the right accordingly.
  • precision: The number of decimal places. If you convert integers, the decimal places are zeros.
  • myCharArray: The “target” character array.

You have to make sure that the character array is large enough.

Converting other Number Systems into Character Arrays

If you want to convert a number in the hexadecimal system into a character array, I recommend using the function sprintf(). I’ll show you how to do this in the example sketch below. Since you could write an entire article just about sprintf() (which I will do at some point), I will refrain from further explanations. A documentation of the sprintf() function can be found here, a documentation of the underlying printf() function can be found here.

There is no ready-made function for displaying numbers in the binary system as a character array. For the example sketch, I wrote the function toBinary(). It goes through the bits of the number to be converted one by one and checks which of the bits are set. All preceding zeros are truncated. I also wrote a second version. The number to be converted is shifted bit by bit to the left, and it is always checked whether the most significant bit (“msb”) is set.

Converting Numbers into String Objects

Converting numbers into string objects is much easier. The following applies to integers:

String myString = String( int myInt, base ); // base: BIN, HEX, DEC (optional)

Instead of integer values, you can also pass numbers of the type byte, long or the unsigned variants.

For floating-point numbers, use (in this example float):

String myString = String( float myFloat, precision ); // prescision: optional

The parameter precision specifies the number of decimal places. If you omit this parameter, two decimal places will be used for the String object.

Here are a few examples:

void setup(){
    Serial.begin(9600);
    int myInt = 4242;
    float myFloat = -123.4567;
    
    Serial.println("Converted to character arrays: ");
    char myCharArray_1[10];
    dtostrf(myFloat, 8, 4, myCharArray_1);
    Serial.print("myFloat, version 1: ");
    Serial.println(myCharArray_1);

    char myCharArray_2[14];
    dtostrf(myFloat, 13, 2, myCharArray_2);
    Serial.print("myFloat, version 2: ");
    Serial.println(myCharArray_2);

    char myCharArray_3[14];
    dtostrf(myInt, 13, 0, myCharArray_3);
    Serial.print("myInt             : ");
    Serial.println(myCharArray_3);

    char myCharArray_4[17];
    toBinary(myInt, myCharArray_4);
    Serial.print("myInt, binary     : ");
    Serial.println(myCharArray_4);
   
    char myCharArray_5[5];
    sprintf(myCharArray_5,"%X", myInt);
    Serial.print("myInt, hexadecimal: ");
    Serial.println(myCharArray_5);
    Serial.println();


    Serial.println("Converted to strings: ");
    String myString_1 = String(myFloat,4);
    Serial.print("myFloat, version 1: ");
    Serial.println(myString_1);

    String myString_2 = "     " + String(myFloat);
    Serial.print("myFloat, version 2: ");
    Serial.println(myString_2);

    String myString_3 = "        " + String(myInt);
    Serial.print("myInt             : ");
    Serial.println(myString_3);

    String myString_4 = String(myInt, BIN);
    Serial.print("myInt, binary     : ");
    Serial.println(myString_4);

    String myString_5 = String(myInt, HEX);
    Serial.print("myInt, hexadecimal: ");
    Serial.println(myString_5);
}

void loop(){}

void toBinary(uint16_t intVal, char *cArr){
    int bitNo = 15;
    int index = 0;
    
    int isBitSet = intVal & bit(bitNo);
    while(!isBitSet && (bitNo > 0)){
        bitNo--;
        isBitSet = intVal & bit(bitNo);
    }
    while(bitNo>=0){
        if(isBitSet){
            cArr[index] = '1';
        }
        else {
            cArr[index] = '0';
        }
        index++;
        bitNo--;
        isBitSet = intVal & bit(bitNo);
    }
    cArr[index] = '\0';  
}

/* Alternative way */
// void toBinary(uint16_t intVal, char *cArr){
//     int bitNo = 15;
//     int index = 0;
    
//     int msb = intVal & 0x8000;
//     while(!msb && (bitNo > 0)){
//         bitNo--;
//         intVal = intVal << 1;
//         msb = intVal & 0x8000;  
//     }
//     while(bitNo>=0){
//         msb = intVal & 0x8000;
//         intVal = intVal << 1;
//         if(msb){
//             cArr[index] = '1';
//         }
//         else {
//             cArr[index] = '0';
//         }
//         index++;
//         bitNo--;
//     }
//     cArr[index] = '\0';  
// }

And here is the output:

Output floats_integers_to_char_arrays_and_strings.ino
Output floats_integers_to_char_arrays_and_strings.ino

Converting Character Arrays and String Objects into Numbers

The functions atof(), atoi() and atol() generate floating-point numbers, integers or long integers from character arrays:

double myDouble = atof ( char* charArray ); // Alternative: float

int myInt = atoi( char* charArray );

long myLong = atol( char* charArray );

The following options are available for generating numbers from String objects:

float myFloat = myString.toFloat();

int myInt = myString.toInt(); // Alternative: long myLong = myString.toInt();

A few examples:

void setup(){
    Serial.begin(9600);
    char myCharArray_1[] = "123.4567";
    char myCharArray_2[] = "4242";
   
    float myFloatfromCharArray = atof(myCharArray_1);
    Serial.print("Float from char array: ");
    Serial.println(myFloatfromCharArray, 4);

    int myIntfromCharArray = atoi(myCharArray_2);
    Serial.print("Integer from char array: ");
    Serial.println(myIntfromCharArray);
    Serial.println();

    String myString_1 = "123.4567";
    String myString_2 = "4242";
    String myString_3 = "42.42blabla";

    float myFloatfromString = myString_1.toFloat(); // alternative: toDouble()
    Serial.print("Float from String: ");
    Serial.println(myFloatfromString);

    int myIntFromString = myString_2.toInt();
    Serial.print("Integer from String: ");
    Serial.println(myIntFromString);

    float myFloatfromString_2 = myString_3.toFloat();
    Serial.print("Float from String, 2: ");
    Serial.println(myFloatfromString_2);    
}

void loop(){}

 

And here is the output:

Output char_arrays_and_strings_to_integers_and_floats.ino
Output char_arrays_and_strings_to_integers_and_floats.ino

Reading Character Arrays and String Objects from Serial

If you receive data via the serial interface, you can convert it into character arrays or String objects. You have the choice of reading in all transmitted characters or only up to a specified character.

Let’s start with the character arrays:

size_t length = Serial.readBytesUntil( char terminator, char* charArray, int maxLength );

size_t length = Serial.readBytes( char* myCharArray, int maxLength );

You have the following options for String objects:

String myString = Serial.readStringUntil( char terminator );

String myString = Serial.readString();

Example sketch:

void setup(){
    Serial.begin(9600);
}

void loop(){
    char myCharArray[20] = {'\0'};

    if(Serial.available()){
        Serial.readBytesUntil('\n', myCharArray, sizeof(myCharArray));
        Serial.println(myCharArray);
    }
}

/* alternative loop() */
// void loop(){
//     char myCharArray[11] = {'\0'}; 

//     if(Serial.available()){
//         size_t length = Serial.readBytes(myCharArray, 10);
//         Serial.println(myCharArray);
//         Serial.println(length);
//     }
// }
void setup(){
    Serial.begin(9600);
}

void loop(){
    if(Serial.available()){
        String myString = Serial.readStringUntil('\n');
        Serial.println(myString);
    }
}

/* Alternative that also captures \n and \r */
// void loop(){
//     if(Serial.available()){
//         String myString = Serial.readString();
//         Serial.println(myString);
//     }
// }

Upload the example sketch(es) and send messages via the serial monitor. You could vary the length of the messages and see what you get as output.

Passing Character Arrays and String Objects to Functions

If you want to use a character array in a function, then you pass the name of the character array to the function. The name of the array represents the pointer to element zero. That means you use the original character array in the function. To use a local copy, you would first have to create it.

If you need the size of the character array in the function, you must pass it separately. The length, on the other hand, is not a problem as it can be determined using the NULL terminator.

If you want to use the original String object in a function, you must prefix the parameter in the receiving function with the reference operator “&”.

Let’s take a look at this in the following example sketch.

void setup(){
    Serial.begin(9600);
    char myCharArray[20] = "I am a char array";
    String myString = "I am a string";

    Serial.println(myCharArray);
    modifyCharArray(myCharArray, sizeof(myCharArray));
    Serial.println(myCharArray);
    Serial.println();

    Serial.println(myString);
    modifyString(myString);
    Serial.println(myString);
}

/* alternative loop() */
void loop(){}

void modifyCharArray(char* cArr, size_t size){
    Serial.print("Size of cArr: ");
    Serial.println(sizeof(cArr));
    Serial.print("Size of myCharArray: ");
    Serial.println(size);
    cArr[strlen(cArr)] = '!';
    cArr[strlen(cArr) + 1] = '\0';
    Serial.println(cArr);
}

void modifyString(String &str){
    str += "!";
    Serial.println(str);
}

 

As you can see from the output, we have changed the originals of the character array and the String object in the functions.

Output passing_char_arrays_and_strings_to_functions.ino
Output passing_char_arrays_and_strings_to_functions.ino

Acknowledgement

The background of my post picture comes from Daan Lenaerts on Pixabay. The foreground I modified comes from OpenClipart-Vectors, also on Pixabay.

Leave a Reply

Your email address will not be published. Required fields are marked *