I wanted to dust off my C skills, so I decided to write a couple string manipulation functions. I aimed for simplicity and safety. Performance was not a concern.
Please be a thorough as you like. C is not my everyday language, so I might have made some obvious mistakes. I would also appreciate extra attention to the test cases I've provided. Are they easy to understand and expand? Is coverage good enough?
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
// Erases a part of the string's content, filling the end with null chars.
// `startIndex` must be in a valid range inside this string. The char pointed by `startIndex` is included.
// If `count` is less than 0 or greater than the string's length, erases until the end, starting at `startIndex`.
// Does nothing if the string is empty or if `count` is zero.
size_t strErase(char * str, size_t len, size_t startIndex, int count)
{
assert(str != NULL);
if (*str == '\0')
{
assert(len == 0); // This would indicate a parameter mismatch if not true.
return len;
}
// If the string is not empty, a length must be provided.
assert(len != 0);
assert(startIndex < len && "Index out of range!");
// Do nothing in this case.
if (count == 0)
{
return len;
}
// Erase ALL till the end of the string, starting from `startIndex`, if `count` is not in range.
if (count < 0 || (size_t)count >= len)
{
memset(&str[startIndex], 0, len - startIndex);
return len - startIndex; // New length.
}
// Erase `count` chars from an arbitrary position:
size_t charsToMove = len - (startIndex + count) + 1; // Including the null terminator (+1)
memmove(&str[startIndex], &str[startIndex + count], charsToMove);
return len - count; // New length.
}
// Replaces a sequence of characters in a string by a single character.
// `startIndex` must be in a valid range inside this string. The char pointed by `startIndex` is included.
// If `count` is less than 0 or greater than the string's length, fills with `replaceWith` char starting at `startIndex`.
// Does nothing if the string is empty or if `count` is zero.
void strReplace(char * str, size_t len, size_t startIndex, int count, char replaceWith)
{
assert(str != NULL);
if (*str == '\0')
{
assert(len == 0); // This would indicate a parameter mismatch if not true.
return;
}
// If the string is not empty, a length must be provided.
assert(len != 0);
assert(startIndex < len && "Index out of range!");
// Do nothing in this case.
if (count == 0)
{
return;
}
// Fill until end with the given char, starting at pos:
if (count < 0 || (size_t)count >= len)
{
memset(&str[startIndex], replaceWith, len - startIndex);
return;
}
int charsLeft = len - startIndex;
memset(&str[startIndex], replaceWith, (count <= charsLeft) ? count : charsLeft);
}
// Thin wrapper over strcmp(str1, str2) == 0 for the test cases.
bool strEquals(const char * str1, const char * str2)
{
return strcmp(str1, str2) == 0;
}
void Test_strReplace(void)
{
// Replace end of string:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 5, 6, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "Hello++++++"));
}
// Replace beginning of string:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 0, 6, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "++++++World"));
}
// Replace middle of string:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 3, 5, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "Hel+++++rld"));
}
// Replace end with count overflow:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 5, 999, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "Hello++++++"));
}
// Replace the whole string from the start:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 0, strlen(s), '+');
assert(strlen(s) == 11);
assert(strEquals(s, "+++++++++++"));
}
// Replace the whole string from the start with count overflow:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 0, 999, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "+++++++++++"));
}
// Count equal to zero is a no-op:
{
char s[] = "Hello World";
strReplace(s, strlen(s), 2, 0, '+');
assert(strlen(s) == 11);
assert(strEquals(s, "Hello World"));
}
}
void Test_strErase(void)
{
// Erase end of string:
{
char s[] = "Hello World";
strErase(s, strlen(s), 5, 6);
assert(strlen(s) == 5);
assert(strEquals(s, "Hello"));
}
// Erase beginning of string:
{
char s[] = "Hello World";
strErase(s, strlen(s), 0, 6);
assert(strlen(s) == 5);
assert(strEquals(s, "World"));
}
// Erase from the middle of the string:
{
char s[] = "Hello World";
strErase(s, strlen(s), 3, 5);
assert(strlen(s) == 6);
assert(strEquals(s, "Helrld"));
}
// Erase end with count overflow:
{
char s[] = "Hello World";
strErase(s, strlen(s), 5, 999);
assert(strlen(s) == 5);
assert(strEquals(s, "Hello"));
}
// Erase the whole string from the start:
{
char s[] = "Hello World";
strErase(s, strlen(s), 0, strlen(s));
assert(*s == '\0');
}
// Erase the whole string from the start with count overflow:
{
char s[] = "Hello World";
strErase(s, strlen(s), 0, 999);
assert(*s == '\0');
}
// Count equal to zero is a no-op:
{
char s[] = "Hello World";
strErase(s, strlen(s), 2, 0);
assert(strlen(s) == 11);
assert(strEquals(s, "Hello World"));
}
}
int main(void)
{
Test_strReplace();
Test_strErase();
printf("Passed all tests!\n");
}