Using the C standard libraries for string processing is a terrible option when it comes to programming micro-controllers. The standard C string functions can be unsafe and most C string functions are prohibited by high integrity coding standards. They will also bloat compiled binaries by up to 30Kb.
The Embedded Tool Kit offers a lightweight C++ alternative. ETK contains everything you need to work with strings on micro-controllers in these classes.
- Rope – a C-string wrapper
- StaticString – a complete string class that uses static memory
- Tokeniser – a string splitting utility
Arduino StaticString Library Download
etk::Rope
Rope is a C string wrapper. It does everything that’s needed to use C-string effectively such as concatenation of other strings, floats and integers, converting strings to floats and integers, calculating string length, comparing strings and so on. Rope requires a pointer to a C string (character array) and the length of the array so it can avoid buffer overruns. Rope will then perform operations on this C string.
Let’s look at some examples
Rope creation
1 2 |
char buf[20]; etk::Rope rope(buf, 20); |
Construct and initialise
1 2 3 4 |
char buf[20]; etk::Rope rope(buf, 20, "Hello!"); if(rope == "Hello!") cout << "Rope has been initialised to 'Hello!'" << endl; |
String concatenation
1 2 3 4 5 |
char buf[100]; etk::Rope rope(buf, 100); //rope chomps through everything and appends it to the C-string rope << "Todays temperature is " << 24.3f << " degrees C"; Serial.println(buf); |
Set cursor
1 2 3 4 5 |
rope << "chomp chomp chomp"; rope.set_cursor(12); //move back to the 12 character rope << "buurp"; //starts appending at character 12, overwriting previous data cout << rope.c_str() << endl; //output: chomp chomp buurp |
Appending integers with padding
1 2 3 4 5 6 |
//appending the number 10 //5 specifies the total number of characters to add //since 10 is a two digit number, rope will insert three zeroes rope.append(10, 5); if(rope == "00010") cout << "Appended 10 with padding." << endl; |
Appending floats with precision parameter
1 2 3 4 5 |
rope.append(5.2176f); //buffer contains "5.21" rope.clear(); //empties buffer by setting all chars to null '\0' rope.append(5.2176, 1); //buffer contains "5.2" rope.clear(); rope.append(5.2176f, 4); //buffer contains "5.2176" |
Strings to integers
1 2 3 4 |
rope = "58"; int i = rope.atoi(); // i is now 58 rope = "RPM: 3765"; i = rope.atoi(5); // the parameter tells atoi how many leading characters to ignore |
Strings to floats
1 2 3 4 5 |
rope = "12.5"; float f = rope.atof(); //f is now 12.5 rope = "Temp: 86.3"; if(rope.compare("Temp: ", 6)) float temp = rope.atof(6); //ignoring the first 6 characters, extract temperature value |
String length
1 2 |
rope = "Hello" int len = rope.length(); //len == 5 |
Comparing strings
1 2 3 4 5 6 7 8 9 10 |
rope = "Hello world"; if(rope == "Hello world") cout << "string IS 'Hello world'" << endl; if(rope.compare("world", 6, 0, 5)) cout << "string contains 'world' starting at 6th character" << endl; rope = "Hello world"; if(rope.compare("a world", 6, 2, 5)) cout << "string contains 'world' starting at 6th and 2nd respectively" << endl; |
etk::StaticString
StaticString is a string template class that does not secretly allocate memory. The maximum length of a StaticString is specified by the template parameter.
StaticStrings are just a thin wrapper over the Rope class. They essentially are a rope, but contain their own memory.
Examples
1 2 3 4 5 |
StaticString<100> ss = "Hello world!"; ss.clear(); ss += 55; int a = ss.atoi(); //a now equals 55 ss.get_rope() << "StaticString is just a thin wrapper around Rope"; |
StaticString actually does provide a few more functions than Rope. It can insert and remove characters.
1 2 3 4 5 6 |
StaticString<100> ss = "Hello world"; ss.remove(0); ss.insert('Y', 0); ss.insert('w', 5); cout << ss.c_str() << endl; //output: Yellow world |
It also has a fill function
1 2 3 4 |
StaticString<100> ss = "Giggle"; ss.fill('0', 1, 2); cout << ss.c_str() << endl; //output: G00gle |
And finally, StaticString has to_upper and to_lower functions.
1 2 3 4 5 |
ss = "lowercase"; ss.to_upper(); //ss contains "LOWERCASE" ss.to_lower(); //ss contains "lowercase" |
String Tokeniser
The tokeniser splits strings and extracts the pieces. Let’s say you want to extract words from a phrase
1 2 3 4 5 6 7 8 9 10 11 12 |
etk::List<etk::StaticString<40>, 10> words; //create a list of strings to contain the words const char* s = "A bunch of words"; auto tok = etk::make_tokeniser(s, ' '); //make a tokeniser that splits strings by spaces etk::StaticString<40> token; //a temporary string for the tokeniser to extract tokens to while(tok.next(token, 40)) //tok.next returns true while there are tokens still available words.append(token); //append token to the words list for(auto& w : words) //print out the words cout << w.c_str() << " "; cout << endl; //output: A bunch of words |
The tokeniser can use virtually any type of string object. The only limitation is that the input string provided to etk::make_tokeniser MUST be null terminated (i.e. have a null character ‘\0’ at the end). Examples of string types that can be used are C strings (character arrays), StaticStrings, Rope, std::string and pretty much anything that allows the use of subscript operators ( operator [] ).
Here is an example using etk::Arrays – it’s a silly example but it showcases the diversity of the tokeniser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
etk::Array<char, 100> array("A bunch of words"); etk::Array<char, 20> token; auto tok = etk::make_tokeniser(array, ' '); while(tok.next(token, 20)) { //token is an etk::Array, which isn't easily printable, so copy chars from token to a C string char st[20]; int count = 0; for(auto c : token) st[count++] = c; //then print C string cout << st << " "; } cout << endl; //output: A bunch of words |