C 语言动态字符串
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日。修复了文章的几个部分