C语言String结构体,模仿Java的String类(七)

本文详细介绍了C语言中模仿Java String类的String结构体,包括contains函数、trim_string函数、to_string函数、equals函数、string_length函数、split_by函数及其核心辅助函数index_of_str_from_static。此外,讨论了C语言处理字符串时对于中文字符的局限性,以及未来可能使用wchar_t处理宽字符以支持Unicode的可能性。

本章我们继续介绍String结构体中的函数。

bool contains(String* thisptr, const char* str)函数,判断字符串是否包含str。本函数调用了index_of_str函数。

/**
 * 判断字符串是否包含str
 * 
 * @param thisptr 字符串结构体指针
 * @param str 字符串指针 是否被包含的字符串
 * 
 * @return 字符串是否包含str true:包含 false:不包含
 *  
 */
bool contains(String* thisptr, const char* str){
    int pos = index_of_str(thisptr, str);
    if(pos != -1){
        return true;
    }
    return false;
}

void trim_string(String* thisptr)函数,去除字符串两端的空格。主要思路是分别找到去除两端空格后的start和end索引,然后计算所占用空间大小,然后释放原value所占端空间,为去除了两端空格后的字符串重新申请对空间,并将其赋给thisptr->value.

/**
 * 去除字符串两端的空格
 * 
 * @param thisptr 字符串结构体指针
 */
void trim_string(String* thisptr){
    if(thisptr == NULL){
        return;
    }
    if(thisptr->value == NULL){
        return;
    }
    int start = 0; // 初始搜索的位置,从0开始
    int end = thisptr->length - 1;// 搜索的结束位置
    while(start < thisptr->length && isspace(thisptr->value[start])){
        start++;
    }
    while(end >= 0 && isspace(thisptr->value[end])){
        end--;
    }

    if(start > end){
        char* newstr = (char*)malloc(1); // 在堆上申请内存
        newstr[0] = '\0';
        free(thisptr->value);
        thisptr->value = newstr; // 释放原字符串的堆内存, 并将新的字符串赋值给原字符串的value
        thisptr->length = 0; // 更新字符串的长度

    }else{
        int len = end - start + 1;
        char* newstr = (char*)malloc(len + 1); // 在堆上申请内存
        for(int i = 0; i < len; i++){
            newstr[i] = thisptr->value[start + i]; // 复制子串的字符到堆内存
        }
        newstr[len] = '\0'; // 字符串末尾加上'\0'
        free(thisptr->value);
        thisptr->value = newstr; // 释放原字符串的堆内存, 并将新的字符串赋值给原字符串的value
        thisptr->length = len; // 更新字符串的长度
    }

}

const char* to_string(String* thisptr)函数,打印字符串结构体的内容。

/**
 * 打印字符串内容
 * @param thisptr 字符串结构体指针
 */
const char* to_string(String* thisptr){
    if(thisptr == NULL){
        // printf("NULL\n");
        return NULL;
    }
    // int len = thisptr->length;
    //printf("String(%p), addr(%p), content(%s), length(%d)\n", thisptr, thisptr->value, thisptr->value, len);
    return thisptr->value;
}

本函数的返回值中有const,解释如下:

当 C 语言函数的返回值类型为 const char* 时,表示该函数返回的指针所指向的字符数据是常量,不能通过这个指针来修改所指向的数据。这意味着你可以使用这个指针来读取字符数据,但不能通过它进行写操作(例如修改字符的值)。这样可以在一定程度上保证数据的完整性和安全性,防止函数的调用者意外地修改了函数内部不希望被修改的数据。

bool equals(String* thisptr, const char* str)函数,该函数判断字符串是否完全相同,逻辑是比较的char*必须和String结构体中的value的每个字符都相同。否则返回false。

/**
 * 判断字符串是否和str相同,每个字符都一样
 * @param thisptr 字符串结构体指针
 * @param str 字符串指针
 * @return 字符串是否相同 true:相同 false:不同
 */
bool equals(String* thisptr, const char* str){    
    
    if(thisptr == NULL || str == NULL){
        return false;
    }
    
    if(thisptr->length != string_length(str)){
        return false;
    }
    
    for(int i = 0; i < thisptr->length; i++){
        if(thisptr->value[i] != str[i]){
            return false;
        }
    }
    
    return true;
}

int string_length(IN_PARAM const char* str)函数,该函数返回字符串的长度。

/**
 * 计算字符串的长度,不包含结束符'\0'
 * @param str 字符串指针
 * @return str为NULL  返回-1
 *         或者空字符串 返回0
 *         否则返回字符串的长度
 */
int string_length(IN_PARAM const char* str){
    
    if(str == NULL){
        return -1;
    }
    if(*str == '\0'){
        return 0;
    }
    
    int len = 0;
    while(*str != '\0'){
        len++;
        str++;
    }
    
    return len;
}

struct string_segment* split_by(String* thisptr, const char* chptr)函数,该函数返回按chptr分割的字符串数组, 数组中包含按chptr分割的字符串,有可能是\0, 空字符串,即两个chptr紧挨在一起时会有\0。

/**
 * 保存分割后的字符串数组以及数量的结构体
 */
struct string_segment{
    String** strptr; // 字符串数组指针
    int count;   // 字符串数组的长度
};
/**
 * 返回按chptr分割的字符串数组, 数组中包含按chptr分割的字符串,有可能是\0, 空字符串
 * 因为两个chptr紧挨在一起时
 * @param thisptr 字符串结构体指针
 * @param chptr 分割字符串的字符指针
 * @return 字符串数组指针, 数组中包含按chptr分割的字符串
 *         返回空指针,说明无法分割
*/
struct string_segment* split_by(String* thisptr, const char* chptr){
    if(thisptr == NULL || chptr == NULL){
        return NULL; //如果thisptr或chptr为NULL,返回NULL
    }
    if(thisptr->value == NULL || *thisptr->value == '\0'){
        return NULL; //如果thisptr为空字符串,返回NULL
    }
    if(*chptr == '\0'){
        return NULL; //如果chptr为空字符串,返回NULL
    }
    
    int count = thisptr->str_count(thisptr, chptr); // 计算子串的数量
    //printf(">>> chptr(%s) 出现的次数: %d\n", chptr, count);
    if(count == 0){
        return NULL;
    }

    int* indexs = thisptr->str_indexs(thisptr, chptr, count); // 获得各子串的起始位置索引数组
    const int segcount = count + 1; // 子串的数量
    String** segstrs = (String**)malloc(sizeof(String*) * segcount); //申请了一个存放二级指针的内存空间
    
    //printf(">>> &segstrs(%p), segstrs(%p)\n", &segstrs, segstrs);

    for(int i = 0; i < count + 1; i++){
        int start = 0;
        int end = 0;
        if(i == 0){
            start = 0;       // 第一个被分割的子串的起始位置
            end = indexs[i]; // 第一个被分割的子串的结束位置(不包含)
            //printf(">>> 第%d个子串的起始位置: %d, 结束位置: %d\n", i, start, end);
            if(start == end){
                segstrs[i] = build_empty_string(); 
            }else{
                segstrs[i] = thisptr->substring(thisptr,start, end);
            }
        }else{
            start = indexs[i-1] + strlen(chptr); // 之后被分割的子串的起始位置
            end = 0;
            if(i == count){
                end = thisptr->length; // 之后被分割的子串的结束位置(不包含)
            }else{
                end = indexs[i];
            }
            if(start == end){
                segstrs[i] = build_empty_string(); 
            }else{
                segstrs[i] = thisptr->substring(thisptr,start, end);
            }
            //printf(">>> 第%d个子串的起始位置: %d, 结束位置: %d, strlen(%d)\n", i, start, end, thisptr->length);
        }
        // printf(">>> segstrs[%d]: addr1(%p), addr2(%p), value(%p), value(%s); index:[%d-%d]; len: %d\n",
        //              i,       &segstrs[i], segstrs[i],
        //     &(segstrs[i]->value), segstrs[i]->value, start, end, thisptr->length);
    }

    struct string_segment* seg = (struct string_segment*)malloc(sizeof(struct string_segment));
    seg->count = segcount;
    seg->strptr = segstrs;
    //printf(">>> segment: %p; **strptr: %p; segcount: %d\n", seg, seg->strptr, seg->count);
    return seg; 
}

static int index_of_str_from_static(const char* source, const char* target, int fromIndex)函数,这是一个核心函数,被声明为static的。该函数返回搜索目标在源字符串中第一次被找到时的索引。如果没有找到时,返回-1。该函数有详细的注释,请仔细阅读。String结构体中的很多函数都会调用它。

/**
 * @source 要被搜索的字符串(源字符串)
 * @target 要搜索的字符串 (在源字符串中搜索的字符串)
 * @fromIndex 开始搜索的位置(源字符串中搜索的开始位置)
 * 
 * @return 第一次出现的索引,如果没有找到,返回-1
 */
static int index_of_str_from_static(const char* source, 
            const char* target, int fromIndex) {

        // 如果source或target为NULL,那么返回-1,说明在source中搜索target失败
        if(source == NULL || target == NULL){
            return -1;
        }
        
        int sourceOffset = 0;
        int sourceCount = string_length(source);
        int targetOffset = 0;
        int targetCount = string_length(target);

        if (fromIndex >= sourceCount) {
            // 如果开始搜索的索引位置大于等于源字符串的长度,那么说明搜索的位置已经超过了源字符串的长度
            // 那么返回-1,说明在source中搜索target失败
            return -1;
        }
        if (fromIndex < 0) {
            // 如果开始搜索的索引位置小于0,那么从0开始搜索
            fromIndex = 0;
        }

        if (targetCount == 0) {
            // 如果要搜索的字符串为空,那么返回开始搜索的索引位置
            return fromIndex;
        }

        if(targetCount > sourceCount){
            // 如果要搜索的字符串长度大于源字符串的长度,那么说明在源字符串中肯定搜索不到target
            // 那么返回-1,说明在source中搜索target失败
            return -1;
        }

        char first = target[targetOffset]; // 要搜索目标字符串的第一个字符
        // 计算最大的搜索位置, 最大的搜索位置是源字符串的长度减去要搜索的字符串的长度
        // 如果targetCount大于源字符串的长度,那么max值为负, 下边的循环不会执行,也就是不会搜索到target
        int max = sourceOffset + (sourceCount - targetCount);

        // 循环搜索, 直到搜索到max
        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                // 如果当前字符不等于要搜索的字符串的第一个字符,那么跳过当前字符
                // ++i 先递增当i小于等于max并且source[i]不等于first时,i才会递增
                // 否则循环结束  imax, 或者source[i]等于first
                while (++i <= max && source[i] != first);
            }
            // 这时说明找到了第一个字符, 开始比较后续字符是否相同
            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                // i <= max 说明i还没有超过max, 说明source[i]等于first
                // 开始比较后续字符是否相同

                int j = i + 1; // 第二个字符的位置 
                int end = j + targetCount - 1;  // 要搜索的字符串的结束位置
                for (int k = targetOffset + 1; j < end && source[j]== target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
}

在 C 语言中,static 修饰函数具有以下特点:

作用域限制

  • 被 static 修饰的函数其作用域被限制在声明它的源文件内。这意味着该函数只能在其所在的源文件中被调用,其他源文件即使知道函数的名称也无法直接调用它。

隐藏性

  • 可以起到一定的信息隐藏作用,有助于提高代码的模块性和安全性。

这样做的好处有:

  • 避免命名冲突:在大型项目中多个源文件可能有相同名称的函数,通过将一些函数声明为 static 可以避免潜在的冲突。
  • 实现内部细节的封装:只供本文件内使用的函数可以被很好地隔离,不被外部不必要地干扰。

到此为止,String结构体中的绝大部分函数都已经介绍完了,后续我将给出完整的代码,以及一些测试的例子。方便大家合并到自己的C语言工程中。

String结构体中保存的value,以及各种函数都是以char来计算的,目前没有考虑到中文的情况,因此在获取长度时,其实是字符的数量,例如:"abc中", 按包含中文来看,长度应该是4,但是由于我们是以char为单位计算的,所以长度是6。

考虑到中文的情况,我们可以使用wchar_t,也就是宽字符,主要作用是处理Unicode字符集,以支持全球各种语言的字符表示。后续我们会给出一些wchar_t的例子。

敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会C、Java多种开发语言的金哥

您的鼓励是我创作的动力源泉!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值