varch/doc/str.en.md

34 KiB

Introduction

A string is actually a collection of characters. In the C language, a string is an array of the char type with a terminating null character \0 at the end. Without the terminating character, it's just a character array. In C, there isn't a dedicated string variable. However, the str in varch encapsulates array strings, making it a string variable where you don't need to worry about the underlying storage structure. Although str still stores character sets in contiguous address spaces, the work related to storage is handled by the methods provided by str, and it also offers methods that are in line with string operations, facilitating flexible manipulation of strings. For convenience, traditional C language strings will be referred to as array strings later, and the ones provided by varch will be called str strings.

Interface

Creation and Deletion of str String Objects

str_t str_create(void *string);
void str_delete(str_t str);
#define str(str) // For more convenient use, a macro definition is wrapped around str_create
#define _str(str) // A macro definition is wrapped around str_delete, and the str is set to NULL after deletion

Here, str_t is the structure of str. The creation method will return an str object, and it will return NULL if the creation fails. The string parameter is used to pass in the initializing string. This string is defined as the void * type, so it can support passing both array strings and str strings for initialization (all subsequent mentions of void *string can support these two types of strings for initialization). The deletion method is used to delete the passed-in str object. The creation method and the deletion method should be used in pairs. Once created, the str object should be deleted when it's no longer in use.

void test(void)
{
    str_t ss = str("Hello str!"); // Use an array string for assignment construction
    str_t copy = str(ss); // Use an str string for copy construction
    _str(ss); // Delete str
    _str(copy); // Delete str
}

str String's Array String

#define str_c_str(str)
#define _S(str)

str stores strings in contiguous address spaces, and by obtaining this address, you can access the actually stored string. However, it's not recommended to modify the string as an array string in this way during use. There's also a method char* str_data(str_t str, int pos); that has a similar function. str_c_str() is implemented by using str_data, and it's not recommended to replace str_c_str() with this method. And _S() is a shorter version.

void test(void)
{
    str_t ss = str("Hello str!"); // Use an array string for assignment construction
    str_t copy = str(ss); // Use an str string for copy construction
    printf("ss: %s\r\n", _S(ss));
    printf("copy: %s\r\n", _S(copy));
    _str(ss); // Delete str
    _str(copy); // Delete str
}

Results:

ss: Hello str!
copy: Hello str!

str Assignment

str_t str_assign(str_t str, void *string);

str abstracts the string as a class, and the str_assign method performs an assignment operation similar to the = operator. As mentioned before, void *string can support both array strings and str strings.

void test(void)
{
    str_t ss = str(""); // Initialize as an empty string
    printf("ss: %s\r\n", _S(ss));
    str_assign(ss, "Hello str!"); // Assign the value "Hello str!"
    printf("ss: %s\r\n", _S(ss));
    str_assign(ss, "This is the assignment method!"); // Reassign the value
    printf("ss: %s\r\n", _S(ss));
    _str(ss);
}

Results:

ss: 
ss: Hello str!
ss: This is the assignment method!

From this, we can see that str doesn't require you to worry about the size of the defined space like array strings do. Instead, str internally adapts to the data.

str Concatenation

#define str_append(str,...)

Besides having an assignment operation similar to the = operator, str also has a concatenation operation similar to the += operator. The method has variable arguments after the formal parameter, which means it can concatenate at least one string of any quantity. Again, these can be either array strings or str strings. The prototype of the str_append method is str_t str_append_series(str_t str,...);. The prototype function with variable arguments requires ending with NULL, and it's not recommended to use the prototype method.

void test(void)
{
    str_t name = str("123456789");
    str_t ident = str("qq");
    str_t email = str("");

    str_append(email, name, "@", ident, ".com"); // Concatenate several strings
    printf("%s\r\n", str_c_str(email));

    _str(name);
    _str(ident);
    _str(email);
}

Results:

123456789@qq.com

str Insertion

str_t str_insert(str_t str, int pos, void *string);

This method inserts a string into str at the specified position and returns str itself. pos is the insertion position, and string is the source string to be inserted.

void test(void)
{
    str_t ss = str("0123456");
    str_insert(ss, 3, "|insert|"); // Insert the string at position 3
    printf("ss: %s\r\n", str_c_str(ss));
    _str(ss);
}

Results:

ss: 012|insert|3456

str Removal

str_t str_erase(str_t str, int pos, int len);

This method removes a segment of characters from str and returns str itself. pos is the removal position, and len is the length of the segment to be removed.

void test(void)
{
    str_t ss = str("0123456789");
    str_erase(ss, 3, 3); // Remove three characters starting from position 3, that is, remove "345"
    printf("ss: %s\r\n", str_c_str(ss));
    _str(ss);
}

Results:

ss: 0126789

str Push Back and Pop Back

int str_push_back(str_t str, char c);
char str_pop_back(str_t str);

These two methods are used to push and pop a character from the end of the str string respectively. The push_back method returns 1 for success and 0 for failure, and the pop_back method returns the popped character. The storage structure of str is similar to that of vector, and operations at the end are relatively efficient.

void test(void)
{
    str_t ss = str("0123456789");
    str_push_back(ss, 'A');
    printf("ss: %s\r\n", str_c_str(ss));
    printf("pop %c\r\n", str_pop_back(ss));
    printf("ss: %s\r\n", str_c_str(ss));
    _str(ss);
}

Results:

ss: 0123456789A
pop A
ss: 0123456789

str String Comparison

int str_compare(str_t str, void *string);

This method compares str with another string. A return value of 0 means they are equal, -1 means str is less than the other string, and 1 means str is greater than the other string.

void test(void)
{
    str_t ss = str("01234");
    str_t copy = str(ss);
    printf("compare: %d\r\n", str_compare(ss, copy));
    printf("compare: %d\r\n", str_compare(ss, "01233"));
    printf("compare: %d\r\n", str_compare(ss, "012345"));
    _str(ss);
    _str(copy);
}

Results:

compare: 0
compare: 1
compare: -1

str Creating a Substring

str_t str_substr(str_t str, int pos, int len);

This method creates a new substring from str. Remember to call the _str(str) method to delete it after you finish using it. pos is used to pass in the starting position, and len is used to pass in the length of the substring to be created.

void test(void)
{
    str_t ss = str("0123456789");
    str_t sub = str_substr(ss, 4, 2);
    printf("ss: %s\r\n", str_c_str(ss));
    printf("sub: %s\r\n", str_c_str(sub));
    _str(ss);
    _str(sub);
}

Results:

ss: 0123456789
sub: 45

str String Length

int str_length(str_t str);

This is the standard method for obtaining the length of an str string. Of course, you can also use the strlen method, but for str objects, it's not necessary because this standard method doesn't take time to measure the length as it already has the recorded length available.

void test(void)
{
    str_t ss = str("0123456789");
    printf("length: %d\r\n", str_length(ss));
    _str(ss);
}

Results:

length: 10

str Character Reading and Writing

#define str_at(str, i)

str provides operations for reading and writing characters within it, and you can use them just like elements of an array. However, remember not to modify a character to \0.

void test(void)
{
    str_t ss = str("0123456789");
    printf("ss[3] = %c\r\n", str_at(ss, 3));
    str_at(ss, 3) = 'A';
    printf("ss: %s\r\n", _S(ss));
    _str(ss);
}

Results:

ss[3] = 3
ss: 012A456789

str String Searching

int str_find(str_t str, void *string, int pos);
int str_rfind(str_t str, void *string, int pos);

These two methods are used to search for a character substring in the forward and reverse directions respectively. string is used to pass in the string to be searched for, and pos is used to pass in the starting index for the search. The return value is the position index of the substring if found, and str_npos is returned if not found.

void test(void)
{
    str_t ss = str("0123456789");
    printf("find = %d\r\n", str_find(ss, "3456", 0)); // Start searching from position 0
    printf("rfind = %d\r\n", str_rfind(ss, "23", str_length(ss) - 1)); // Start searching backward from the end
    printf("find = %d\r\n", str_find(ss, "0000", 0)); // Search for a non-existent string
    _str(ss);
}

Results:

find = 3
rfind = 2
find = 2147483647

str Searching for Matching Character Sets

int str_find_first_of(str_t str, void *string, int pos);
int str_find_first_not_of(str_t str, void *string, int pos);
int str_find_last_of(str_t str, void *string, int pos);
int str_find_last_not_of(str_t str, void *string, int pos);

These four methods are different from str_find and str_rfind. The find and rfind methods require a complete match of all characters, while these four methods only need to match any one of the characters in the character set to be considered a match. Similarly, string is used to pass in the string (the matching character set), pos is used to pass in the starting index for the search, and the return value is the position index of the substring if found, and str_npos is returned if not found. These four functions are used to find the "first occurrence of any character in the set", "first occurrence of any character not in the set", "last occurrence of any character in the set", and "last occurrence of any character not in the set" respectively. Example: To obtain the file name and the drive letter from a file path in a practical application.

void test(void)
{
    str_t ss = str("C:/workspace/project/C/00 - vlib/Project/main.c"); // Given a file path first
    str_t pan = NULL; // Initialize to NULL, not constructed
    str_t filename = NULL; // Initialize to NULL, not constructed
    pan = str_substr(ss, 0, str_find_first_of(ss, ":", 0)); // First find the first occurrence of ":"
    filename = str_substr(ss, str_find_last_of(ss, "\\/", str_length(ss)-1) + 1, str_length(ss)); // Find the last occurrence of "\\" or "/" as the path separator
    printf("pan: %s\r\n", str_c_str(pan));
    printf("filename: %s\r\n", str_c_str(filename));
    _str(ss);
    _str(pan);
    _str(filename);
}

Results:

pan: C
filename: main.c

str String Reversal

str_t str_reverse(str_t str, int begin, int end);

This method reverses the entire string within the specified interval of str. begin is the starting position of the interval, and end is the ending position of the interval.

void test(void)
{
    str_t ss = str("0123456789");
    str_reverse(ss, 2, 8);
    printf("ss = %s\r\n", str_c_str(ss));
    _str(ss);
}

Results:

ss = 0187654329

str Character Replacement

str_t str_replace(str_t str, int pos, int len, void *string);

This method replaces the characters at the specified position with a specified length by a given string.

void test(void)
{
    str_t ss = str("My name is ZhangSan!");
    printf("ss = %s\r\n", str_c_str(ss));
    str_replace(ss, 11, 8, "Lisi"); // Replace "ZhangSan" with "Lisi"
    printf("ss = %s\r\n", str_c_str(ss));
    _str(ss);
}

Results:

ss = My name is ZhangSan!
ss = My name is Lisi!

str String Swap

void str_swap(str_t str, str_t swap);

This method swaps two strings.

void test(void)
{
    str_t s1 = str("This s1!");
    str_t s2 = str("This s2!");
    str_swap(s1, s2);
    printf("s1 = %s\r\n", str_c_str(s1));
    printf("s2 = %s\r\n", str_c_str(s2));
    _str(s1);
    _str(s2);
}

Results:

s1 = This s2!
s2 = This s1!

Copying str String to an Array

  • Function Functionality and Parameter Introduction:
    • The function int str_copy(str_t str, int pos, int len, char *buf); is used to copy the characters at the specified position in the str string to the specified array buf. Among them, the parameter pos indicates the starting position of the copy, len represents the maximum length of the copy, and buf is the array used to receive the copied data. The function will finally return the actual length of the copied content.
    • For example, in the following test code:
void test(void)
{
    str_t ss = str("0123456789");
    char array[32] = { 0 };
    int length = 0;
    length = str_copy(ss, 5, sizeof(array), array); // Start copying from the position, and the maximum length of the copy is the length of the array
    array[length] = '\0'; // Add a terminator at the end for easy printing
    printf("length = %d, copy: %s\r\n", length, array);
    _str(ss);
}

The running result is:

length = 5, copy: 56789

From the code execution process, it starts copying from the 5th position of the str object ss. Since the set maximum copy length is the length of the array array (which is long enough here), 5 characters "56789" are actually copied to the array array. Then, a terminator \0 is added and the result is printed. Finally, the str object is correctly released.

String Formatting of str

  • Function Functionality and Features:
    • The function str_t str_format(str_t str, char *format,...); realizes the string formatting operation of str. It borrows from and rewrites the sprintf function. Its usage is basically the same as that of sprintf, but it also has unique features. It adds the function that the format specifier %s can support the str_t type. This means that when formatting a string, %s can not only support the char* type string as traditionally, but also support the str_t object, achieving compatibility between the two string types. In addition, the str_format function will perform segmented dynamic expansion during the formatting process. Users don't need to worry about whether the length of the buf exceeds the limit as when using the sprintf function, making the operation more convenient.
    • Here is a test example:
void test(void)
{
    str_t ss = str("");
    str_t pp = str("format");
    str_format(ss, "Hello str! %s %s! int:%d, float:%.2f, char:%c", pp, "function", 18, 175.5, 'A');
    printf("ss: %s\r\n", str_c_str(ss));
    _str(ss);
    _str(pp);
}

The output result is:

ss: Hello str! format function! int:18, float:175.50, char:A

From this example, it can be clearly seen that when using the str_format function, there is no need to consider the space size required for the content to be formatted in advance. As long as the parameters are passed in according to the format requirements, the formatting operation can be successfully completed, which is very convenient.

Reference Examples

Example 1: Judging Palindrome Strings

  • Code Logic and Implementation Steps:
    • The following is the code for judging whether the input string is a palindrome:
void test(void)
{
    str_t s = str("");
    str_t r = str("");
    char buf[64] = {0};
    while (1)
    {
        scanf("%s", buf); // Input a string
        str_assign(s, buf); // Assign the input string to s
        str_assign(r, s); // Assign s to r
        str_reverse(r, 0, str_length(r) - 1); // Reverse r
        if (str_compare(s, r) == 0) // Compare whether s and r are the same
        {
            printf("This is a palindrome string!\r\n");
        }
        else  
        {
            printf("This is not a palindrome string!\r\n");
        }
    }
    _str(s);
    _str(r);
}
  • This code continuously receives the strings input by the user in a loop. First, it assigns the input string to s through the str_assign function, then assigns s to r. Next, it uses the str_reverse function to reverse the r string. Finally, it compares whether s and the reversed r are the same through the str_compare function. If they are the same, it outputs that it is a palindrome string; if not, it outputs that it is not a palindrome string. For example, when inputting "qwewq", after the above operations, the comparison result is equal, and it will output "This is a palindrome string!". When inputting "zxcvfd", the comparison result is not equal, and it will output "This is not a palindrome string!".

Example 2: Deleting Repeated Characters in a String

  • Code Logic and Implementation Idea:
    • The following is the code for deleting repeated characters in a string:
void test(void)
{
    str_t s = str("");
    str_t r = str("");
    char buf[64] = {0};

    while (1)
    {
        scanf("%s", buf); // Input a string
        str_assign(s, buf); // Assign the input string to s
        str_assign(r, ""); // Set r to an empty string
        for (int i = 0, n = str_length(s); i < n; i++) // Traverse s
        {
            char t[2] = {str_at(s, i),0}; // Take out the character of s and form a short array string
            if (str_find(r, t, 0) == str_npos) // Judge whether this array string is in r, that is, judge whether the character of s is in r
            {
                str_append(r, t); // If it is not in r, append it to r
            }
        }
        printf("%s\r\n", _S(r));
    }

    _str(s);
    _str(r);
}
  • The code first receives the string input by the user and assigns it to s, and initializes r as an empty string. Then, it traverses each character of the s string. Each time, it takes out a character to form a short array string t, and then uses the str_find function to judge whether t has appeared in r. If it has not appeared (that is, returns str_npos), it uses the str_append function to append the character to r. Finally, it outputs the processed r string, thus realizing the function of removing repeated characters. For example, when inputting "16783679816488742135468794", after processing, it will output "167839425", and the repeated characters are removed.

Source Code Analysis

str Structure

  • Purpose of Structure Design and Meaning of Members:
    • The structures of the str container are implicit. Such a design ensures the independence and security of the module, avoids external arbitrary access and modification of structure members, and thus prevents the destruction of the str storage structure. Its type declaration is typedef struct STR *str_t;. When using it, str_t can be used to represent this structure type.
    • The specific definition of the STR structure is as follows:
typedef struct STR
{
    char ident;                             /* ident of str */
    char* base;                             /* base address of string */
    int length;                             /* length of str */
    int capacity;                           /* capacity of str */
} STR;
  • The ident member in the structure is the basis for distinguishing between array strings and str strings. The base member points to the base address where the string is actually stored. The length represents the size of str, that is, the length of the string. The capacity is the space capacity allocated for actually storing the string. However, in general usage scenarios, there is no need to pay special attention to capacity-related matters.

Distinguishing between Array Strings and str Strings

  • Distinguishing Mechanism and Code Implementation:
    • Array strings and str strings are distinguished by the ident member in the STR structure. The distinguishing code is as follows:
typedef struct 
{
    char *base;
    int length;
} str_info_t;

static str_info_t string_info(void *string)
{
    str_info_t info;

    if (((char *)string)[0] == 0) /* empty array string */
    {
        info.base = (char *)string;
        info.length = 0;
    }
    else if (((str_t)string)->ident == ident()) /* str type */
    {
        info.base = ((str_t)string)->base;
        info.length = ((str_t)string)->length;
    }
    else /* array string type */
    {
        info.base = (char *)string;
        info.length = strlen((char *)string);
    }

    return info;
}
  • Because ident is in the first position of the structure, when void *string is passed in, the content of the first byte of its address is first judged. If it is \0, it is an empty array string. If it is ident() (which is defined as -1), it represents an str string. Other values represent array strings. The reason for choosing -1 to distinguish is that in common ASCII encoding (the character range is 0 ~ 127), Unicode encoding (the range is 0x0000 ~ 0x10FFFF), and UTF-8 encoding, there will be no situation where it starts with -1 (the corresponding unsigned value is 0xFF). So it is feasible to use it as a distinguishing mark in these common encoding scenarios. In the APIs related to str, whenever it is necessary to distinguish the string type, the string_info function will be called to obtain the str_info_t type information containing the base address and length of the string.

str Self-Adapting Length

  • Implementation Principle Taking the Assignment Function as an Example:
    • Looking at the self-adapting length mechanism of str from the str_assign assignment function, the code is as follows:
str_t str_assign(str_t str, void *string)
{
    str_info_t info;
    if (!str) return NULL;
    if (!string) return NULL;;
    info = string_info(string); // Obtain the information (base address and length) of the string
    if (!str_alter_capacity(str, info.length)) return NULL; // Adjust the capacity according to the length
    strcpy(str->base, info.base); // Copy the string to the str space
    str->length = info.length; // Update the length
    return str;
}
  • First, it will obtain the relevant information (base address and length) of the string to be assigned through the string_info function. Then, it calls the str_alter_capacity function to adjust the capacity of the target str object according to the obtained length. If the capacity adjustment is successful, it copies the source string to the space of the target str object and updates its length. Finally, it returns the adjusted str object. If the capacity adjustment fails, it returns NULL. Inside the str_alter_capacity function, it will first judge whether the capacity needs to be changed. If the str object has not been allocated space originally, it will allocate new space through malloc. If there is already space, it will reallocate space through realloc and finally update the capacity value and return the corresponding result.

str Obtaining the Actual Stored Data

  • Function Implementation and Error Prevention Mechanism:
    • The function char* str_data(str_t str, int pos) is used to obtain the address of the character at the specified index position in the str object. Functions such as str_at, str_c_str, and _S are all implemented based on it. The code is as follows:
char* str_data(str_t str, int pos)
{
    if (!str || pos < 0 || pos >= str->length) 
    {
        error = 0; /* reset error area */
        return &error;
    }
    return &str->base[pos];
}
  • The function will first perform parameter validity judgment. If the passed-in str object is empty or the specified position pos is illegal (less than 0 or exceeding the string length range), it will reset a static variable error defined in the module and return the address of error. This is to prevent the situation that returning NULL causes the program to crash due to subsequent operations on the null address. If the parameters are legal, it will return the address after offsetting the address pointed to by base in the str structure by pos positions, that is, the address of the character at the corresponding index position.

str String Replacement Principle

  • Key Steps and Logical Processing in the Replacement Operation:
    • The function str_t str_replace(str_t str, which involves the following key aspects in the replacement operation:
str_t str_replace(str_t str, int pos, int len, void *string)
{
    str_info_t info;
    char *overlap = NULL;
    if (!str) return NULL;
    if (!string) return NULL;
    if (pos < 0 || pos > str->length) return NULL;
    if (len > str->length - pos) len = str->length - pos;
    info = string_info(string);

    /* Check if addresses overlap
    This step is to check whether the addresses overlap, that is, whether the address of the string information overlaps with the original address of str.
    The processing mechanism for overlap is to allocate a piece of space, copy the content to be replaced,
    and then replace the copied content later.
    */
    if (str->base <= info.base && info.base <= str->base + str->length && pos < str->length)
    {
        overlap = (char *)malloc(info.length + 1);
        if (!overlap) return NULL;
        strcpy(overlap, info.base);
        info.base = overlap;
    }

    /*
    This step judges the length relationship between the string to be replaced and the replacing string, that is, judges whether the capacity needs to be adjusted.
    */
    if (info.length > len) // The replacing string is longer and needs to expand the capacity
    {
        /* First expand the capacity, and the expanded capacity is the length difference */ 
        if (str_alter_capacity(str, str->length + (info.length - len)) == 0)
        {
            if (overlap) free(overlap);
            return NULL;
        }
        /* Move the latter part of the data backward to make room for storing the longer replacing string */
        memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len));
        /* Copy the replacing string into the vacated space */
        memcpy(&str->base[pos], info.base, info.length);
    }
    else if (info.length < len) /* The length becomes smaller */ 
    {
        /* Move the latter data forward, leaving the length of info for the replacing string */
        memmove(&str->base[pos + info.length], &str->base[pos + len], str->length - (pos + len));
        /* Copy the replacing string into the vacated space */
        memcpy(&str->base[pos], info.base, info.length);
        /* For shrinking, adjust the capacity later */
        str_alter_capacity(str, str->length + (info.length - len));
    }
    else /* The length has not changed */
    {
        /* Directly overwrite */
        memcpy(&str->base[pos], info.base, info.length);
    }
    str->length += (info.length - len);
    str->base[str->length] = 0;
    if (overlap) free(overlap);

    return str;
}

The function first performs some basic parameter validity checks and obtains information about the string to be inserted through the string_info function. Then, it checks whether the addresses of the original string and the string to be inserted overlap. If there is an overlap, it will allocate additional space to handle it properly. Next, it compares the lengths of the two strings to determine whether to expand, shrink, or directly overwrite the capacity of the original string. Finally, it updates the length of the string in the str object, adds a terminator, and releases the temporarily allocated space if necessary, and then returns the modified str object. And the functions str_insert and str_erase are implemented based on the str_replace function. For example, str_insert is implemented by calling str_replace with a length of 0 for the replacement part, and str_erase is implemented by calling str_replace with an empty string for the replacement part.

Principle of str Concatenation

The actual operation is to call the str_append_series function for parameter passing, and append a NULL at the end of the variable arguments.

Inside the str_append_series function, it processes the variable arguments. It inserts each segment of strings to the end of the str one by one through the str_insert function to complete the concatenation. The specific code is as follows:

#define str_append(str,...) str_append_series((str), ##__VA_ARGS__, NULL)
str_t str_append_series(str_t str,...)
{
    va_list args;
    void* s = NULL;
    if (!str) return NULL;
    va_start(args, str);
    s = va_arg(args, void*);
    while (s)
    {
        /* Insert at the end */
        if (!str_insert(str, str->length, s))
        {
            va_end(args);
            return NULL;
        }
        s = va_arg(args, void*);
    }
    va_end(args);
    return str;
}

Source Code of str Formatting

The source code of the str_format function is relatively long. The general process is to traverse the format string. When encountering the % symbol, specific formatting operations will be carried out. Taking the % symbol as the delimiter, the entire format string is divided into segments for dynamic expansion and assignment. The detailed code is shown below:

str_t str_format(str_t str, char *format,...)
{
    va_list args;
    int i = 0;
    int len = 0;                    /* length of sub string */
    char *s = NULL;                    /* temp string */
    str_info_t info;                /* str info */
    double dbl = 0.0;                /* double precision floating point */
    char *begin = NULL;                /* begin of format */
    char qualifier = 0;                /* conversion qualifier for integer as 'h','l','L' */
    int width = 0;                    /* transition field width */
    int precision = 0;                /* minimum digits of integers and maximum digits of strings */
    char tfmt[16] = "%";            /* temporary format */

    if (!str) return NULL;
    str_assign(str, "");
    va_start(args, format);
    for (; *format; format++)
    {
        /* When it is an ordinary character */
        if (*format!= '%')
        {
            if (!begin) begin = format; // Record the starting position of the ordinary character segment
            continue; // Skip this loop iteration and move to the next one
        }

        /* If the starting position of the ordinary character has been recorded, it means the previous part of the current position is ordinary characters */
        if (begin) 
        {
            len = format - begin; // Record the length for later expansion
            if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY;
            while (len--) str->base[str->length++] = *begin++; // Copy the ordinary characters in
            begin = NULL;
        }

        /* Start processing the formatted data segment from here */
        begin = format;
        while (1) // Record the prefix symbol flag
        {
            /* Also skips the first '%' */
            format++;
            if ((*format!= '0') &&
                (*format!= ' ') && 
                (*format!= '+') && 
                (*format!= '-') && 
                (*format!= '#')) break;
        }

        /* Get field width */
        width = -1;
        if (is_digit(*format)) 
        {
            format += get_digit(format, &width);
        }
        else if (*format == '*')
        {
            format++;
            width = va_arg(args, int);
            if (width < 0) width = -width;
        }

        /* Get the precision */
        precision = -1;
        if (*format == '.')
        {
            format++;
            if (is_digit(*format)) format += get_digit(format, &precision);
            else if (*format == '*')
            {
                format++;
                precision = va_arg(args, int);
            }
            if (precision < 0) precision = 0;
        }

        /* Get the conversion qualifier */
        qualifier = 0;
        if (*format == 'h' || *format == 'l' || *format == 'L')
        {
            qualifier = *format;
            format++;
            if (qualifier == 'l' && *format == 'l')
            {
                qualifier = 'L';
                format++;
            }
        }

        /* Format distribution */
        switch (*format)
        {
        case 'c': 
            // Estimate how much space is needed
            // Call sprintf to append a character at the end
           ...
        case 's': 
            // Estimate how much space is needed
            // Call sprintf to append a segment of string at the end
           ...
        case 'p': 
            // Estimate how much space is needed
            // Call sprintf to append a pointer at the end
           ...
        case 'a':
        case 'A':
        case 'e':
        case 'E':
        case 'g':
        case 'G':
        case 'f':
            // Estimate how much space is needed
            // Call sprintf to append a floating-point number at the end
           ...
        case 'o':
        case 'X':
        case 'x':
        case 'd':
        case 'i':
        case 'u':
            // Estimate how much space is needed
            // Call sprintf to append an integer at the end
           ...
        case '%':
            // Push in %
           ...
        default:
            // Push in %
           ...
        }
    }

    /* Copy the tail string to str */
    if (begin)
    {
        len = format - begin;
        if (!str_alter_capacity(str, str->length + len)) goto FAIL_CAPACITY;
        while (len--) str->base[str->length++] = *begin++;
    }

    if (!str_alter_capacity(str, str->length)) goto FAIL_CAPACITY;
    str->base[str->length] = '\0';
    va_end(args);
    return str;

FAIL_CAPACITY:
    str->base[str->length] = '\0';
    va_end(args);
    return NULL;
}