本章我们继续介绍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的例子。
敬请期待。

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

467

被折叠的 条评论
为什么被折叠?



