65.9K
CodeProject 正在变化。 阅读更多。
Home

C 语言动态字符串

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (12投票s)

2018 年 9 月 5 日

MIT

5分钟阅读

viewsIcon

25745

downloadIcon

759

C 语言的动态字符串

引言

本文介绍了C语言动态ANSI字符串的实现。

字符串,更准确地说,字符数组,是用于程序化文本操作和呈现的重要数据结构。标准库中已经涵盖了大部分内容。然而,我认为直到今天,C语言标准中仍然没有动态管理的字符串类型。市面上存在第三方库,无论免费还是收费,可能需要大量额外的代码才能引入。

我希望这个低级动态字符串的实现能在C语言编程中帮助到您,在那些必须使用动态字符串的场景中。

背景

在C语言中编写程序时,会遇到需要存储或操作可变大小文本的需求。通常,这需要编写代码来处理增长、收缩等。主要涉及指针算术和内存管理。这可能会导致非常混乱的代码。

我早在1994年就编写了这段代码,并且可能会制作Unicode版本,但在那个时候,这个ANSI实现已经足够满足我的需求了。

实现

动态C 字符串的实现旨在提高速度和内存操作效率。它会跟踪已分配的内存以及该内存所使用的字符串长度。这使得它能够在相同的字符串进行多次赋值操作时,如果之前分配的内存已经足够大,就可以忽略不必要的内存重新分配。多余的已分配内存可以由程序员在需要时随时收缩。

它不依赖任何框架和库。

它应该可以在任何平台上编译,并在任何运行编译后C代码的设备上运行。

字符串的基础类型是HSTRINGA。它代表ANSI 字符串句柄句柄通过隐藏实现细节来封装复杂的字符串数据细节,从而免除了用户的负担。HSTRINGA可以像普通指针一样,作为C结构体的成员存储、作为参数传递、复制或从函数返回。

它最初是用符合C89标准的编译器构建的,所以只要是C89标准之后的编译器,回溯使用这段代码应该不会有问题。

HSTRINGA内部封装了tagSTRINGA结构

typedef struct tagSTRINGA
{
         char*    data;             /* string */
         size_t   data_length;      /* length of string */
         size_t   alloc_length;     /* malloc size */
} STRINGA;

该结构对用户不可见,并以句柄的形式表示

DECLARE_HANDLE(HSTRINGA);

这种方法保证了类型安全,如果意外传递了其他指针,编译器会发出警告或报错。相反,如果使用void指针,则不会发出任何警告。同时,它隐藏了tagSTRINGA结构体的成员。用户无法访问这些成员,它们仅在内部用于跟踪字符串的状态。

API 概述

API函数被装饰以防止与任何其他类似库的命名空间冲突。

创建与销毁

  • Astr_Create()
  • Astr_Destroy(HSTRINGA hstr)

Copying

  • Astr_Copy(HSTRINGA hstr)

大小管理

赋值、连接、删除、替换

  • Astr_GetLength(HSTRINGA hstr)
  • Astr_GetAllocLength(HSTRINGA hstr)
  • Astr_IsEmpty(HSTRINGA hstr)
  • Astr_FreeExtra(HSTRINGA hstr)
  • Astr_Empty(HSTRINGA hstr)
  • Astr_Set(HSTRINGA hstr, const char* str)
  • Astr_Get(HSTRINGA hstr)
  • Astr_SetAt(HSTRINGA hstr, size_t index, char ch)
  • Astr_GetAt(HSTRINGA hstr, size_t index)
  • Astr_Cat(HSTRINGA hstr, const char* str)
  • Astr_Insert(HSTRINGA hstr, size_t index, const char* str)
  • Astr_InsertCh(HSTRINGA hstr, size_t index, char c)
  • Astr_Replace(HSTRINGA hstr, const char* pOld, const char* pNew)
  • Astr_ReplaceCh(HSTRINGA hstr, char chOld, char chNew)
  • Astr_Remove(HSTRINGA hstr, const char* str)
  • Astr_RemoveCh(HSTRINGA hstr, char chRemove)
  • Astr_Delete(HSTRINGA hstr, size_t index, size_t count)

大小写转换与反转

修剪空白字符

  • Astr_ToUpper(HSTRINGA hstr)
  • Astr_ToLower(HSTRINGA hstr)
  • Astr_Reverse(HSTRINGA hstr)
  • Astr_TrimRight(HSTRINGA hstr)
  • Astr_TrimLeft(HSTRINGA hstr)
  • Astr_Trim(HSTRINGA hstr)

搜索

  • Astr_Find(HSTRINGA hstr, const char* sub, size_t start)
  • Astr_FindCh(HSTRINGA hstr, char ch, size_t start)
  • Astr_ReverseFind(HSTRINGA hstr, char ch)
  • Astr_FindOneOf(HSTRINGA hstr, const char* char_set)

提取

  • Astr_Mid(HSTRINGA hstr, size_t start, size_t count)
  • Astr_Left(HSTRINGA hstr, size_t count)
  • Astr_Right(HSTRINGA hstr, size_t count)

格式

  • Astr_Format(HSTRINGA hstr, const char* fmt, ...)

分配与释放

此代码片段演示了HSTRINGA的基本用法

HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "Hello Dynamic C String!");
 
printf("%s\n", Astr_Get(hstr));
 
// Use hstr . . .
 
 
// Cleanup
Astr_Destroy(hstr); 

使用完字符串后,必须通过调用Astr_Destroy(HSTRINGA)函数来释放它。

API函数返回的任何HSTRINGA句柄都必须单独释放,因为它是原始字符串的完整副本。这样做是为了防止意外地删除指向同一字符串的两个指针,或避免其他指针相关的错误。

Copying

HSTRINGA hCopy;
HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "Hello Dynamic C String!");
 
printf("%s\n", Astr_Get(hstr));
 
hCopy = Astr_Copy(hstr);
 
// Use hstr and copy . . . 
 
// Cleanup
Astr_Destroy(hstr);
Astr_Destroy(hCopy);

Copy操作将创建原始字符串的独立副本。原始字符串的任何后续修改都不会影响副本,反之亦然。原始字符串的每个副本都必须单独释放。

大小管理

调用Astr_GetLength函数来获取此HSTRINGA对象中字节的数量。此计数不包括null终止符。

调用Astr_GetAllocLength函数来确定为字符串分配的总内存。它可能与字符串长度不同。如果您想手动收缩内存,可以调用此函数并将其与实际字符串长度进行比较。

Astr_IsEmpty测试HSTRINGA对象是否为空。

调用Astr_FreeExtra函数来释放字符串之前分配但不再需要的任何额外内存。这应该会减少字符串对象消耗的内存开销。该函数会将缓冲区重新分配到Astr_GetLength返回的精确长度。

调用Astr_Empty会将此HSTRINGA对象设置为空字符串并释放内存。它不会销毁字符串对象,该对象稍后可以重复使用。

printf("size:%d\n", Astr_GetLength(hstr));
printf("alloc:%d\n", Astr_GetAllocLength(hstr));
 
if(Astr_IsEmpty(hstr))
{
         // do something ..
}
else
{
         // do something ..
}
if(Astr_GetAllocLength(hstr) > Astr_GetLength(hstr))
{
         Astr_FreeExtra(hstr);
         assert(Astr_GetAllocLength(hstr) == Astr_GetLength(hstr));
}
Astr_Empty(hstr);
assert(Astr_Get(hstr) == NULL);
// can be repopulated later 

赋值、连接、插入、删除、替换

size_t i;
HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "Hello Dynamic C String!");
printf("%s\n", Astr_Get(hstr));
 
Astr_SetAt(hstr, 0, 'h');
Astr_SetAt(hstr, 6, 'd');
Astr_SetAt(hstr, 14, 'c');
Astr_SetAt(hstr, 16, 's');
 
printf("%s\n", Astr_Get(hstr));
  
for(i = 0; i < Astr_GetLength(hstr); i++)
{
         printf("%c\n", Astr_GetAt(hstr, i));
}
 
/* concatenate */
Astr_Cat(hstr, " For all");
Astr_Cat(hstr, " your c coding");
Astr_Cat(hstr, " needs");
printf("%s\n", Astr_Get(hstr));
 
/* insertion */
Astr_Insert(hstr, 6, "awesome ");
printf("%s\n", Astr_Get(hstr));
 
Astr_InsertCh(hstr, 6, '\'');
printf("%s\n", Astr_Get(hstr));
 
/* replacement */
Astr_Replace(hstr, "C", "awesome C");
printf("%s\n", Astr_Get(hstr));
 
Astr_ReplaceCh(hstr, 'l', 'L');
printf("%s\n", Astr_Get(hstr));
 
/* deletion */
Astr_Delete(hstr, 5, 7);
 
Astr_Destroy(hstr);

大小写转换与反转

HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "String to reverse");
printf("%s\n", Astr_Get(hstr));
 
Astr_Reverse(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_Reverse(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_ToUpper(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_ToLower(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_Destroy(hstr);

修剪空白字符

HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "String to trim   ");
printf("%s\n", Astr_Get(hstr));
 
Astr_TrimRight(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_Set(hstr, "    String to trim");
printf("%s\n", Astr_Get(hstr));
 
Astr_TrimLeft(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_Set(hstr, "    String to trim    ");
printf("%s\n", Astr_Get(hstr));
 
Astr_Trim(hstr);
printf("%s\n", Astr_Get(hstr));
 
Astr_Destroy(hstr);

搜索

size_t f;
HSTRINGA hstr = Astr_Create();
 
Astr_Set(hstr, "Hello Dynamic C String!");
printf("%s\n", Astr_Get(hstr));
 
f = Astr_Find(hstr, "String", 0);
printf("\nfound at %d\n", f);
 
f = Astr_Find(hstr, "to", f);
printf("found at %d\n", f);
 
f = Astr_Find(hstr, "tr", f);
printf("found at %d\n", f);
 
f = Astr_Find(hstr, "nonexitent", 0);
printf("found at %d\n", f);
 
f = Astr_FindCh(hstr, 'r', 0);
printf("found at %d\n", f);
 
f = Astr_FindOneOf(hstr, "tuwxyz");
printf("found at %d\n", f);
 
Astr_Destroy(hstr);

提取

提取函数是少数返回类型为HSTRINGA的函数之一。任何返回HSTRINGA的函数都可以说是创建了一个新的HSTRINGA。因此,它必须单独释放。

HSTRINGA h, hstr;
double d = 3.9876;
 
hstr = Astr_Create();
 
Astr_Format(hstr, "double %.*f", 10, d);
printf("%s\n", Astr_Get(hstr));
 
h = Astr_Mid(hstr, 6, 4);
printf("%s\n", Astr_Get(h));
Astr_Destroy(h);
 
h = Astr_Left(hstr, 8);
printf("%s\n", Astr_Get(h));
Astr_Destroy(h);
 
h = Astr_Right(hstr, 4);
printf("%s\n", Astr_Get(h));
Astr_Destroy(h);
 
Astr_Destroy(hstr);

格式化

HSTRINGA对象可以使用类似printf的语句进行格式化,并提供内部缓冲区的适当增长。

格式化示例

int i = 10;
double d = 3.9876;
float f = 3.14f;
__int64 i64 = 200;
const char* s = "Test";
 
Astr_Format(hstr, "int %d, double %f, float %f, int64: %I64d", i, d, f, i64);
printf("%s\n", Astr_Get(hstr));
 
Astr_Format(hstr, "string: %s, int %d, double %f, float %f", s, i, d, f);
printf("%s\n", Astr_Get(hstr));
 
 
Astr_Format(hstr, "double %.2f, float %.3f", d, f);
printf("%s\n", Astr_Get(hstr));
 
Astr_Format(hstr, "double %.*f", 10, d);
printf("%s\n", Astr_Get(hstr));

Using the Code

将“str.h”和“str.c”包含到您的项目中。

尽情享用!

关注点

这段代码展示了如何

  • 将C语言结构体封装起来,不对用户暴露
  • 低级别的内存管理
  • 指针算术

历史

  • 2018年8月31日。原始文章
  • 2018年11月30日。修复了示例代码中的几个拼写错误
  • 2019年12月20日。修复了文章的几个部分
© . All rights reserved.