题
我过去不太使用 C++,最近做了很多 C# 工作,我真的很难再次回到 C++ 的基础知识。这尤其棘手,因为工作要求不能使用任何最方便的 C++ 结构,因此所有字符串都必须是 char *,并且没有提供 STL 列表。
我目前正在尝试做的是创建一个字符串列表,使用 STL 或 C# 不需要花费我任何时间。基本上我想要一个功能,例如:
char **registeredNames = new char*[numberOfNames];
然后,
RegisterName(const * char const name, const int length)
{
//loop to see if name already registered snipped
if(notFound)
{
registeredNames[lastIndex++] = name;
}
}
或者,如果是 C#...
if(!registeredNames.Contains(name))
{
registeredNames.Add(name);
}
我意识到这是行不通的。我知道传递的变量(const 指针和 const 字符串)的 const 性质使其相当困难,但我的基本问题是我过去总是通过使用 STL 列表等来避免这种情况。所以我从来不需要解决这个问题!
解决方案
您可能需要使用 strcmp 来查看字符串是否已存储:
for (int index=0; index<=lastIndex; index++)
{
if (strcmp(registeredNames[index], name) == 0)
{
return; // Already registered
}
}
然后,如果您确实需要存储字符串的副本,那么您需要分配一个缓冲区并复制字符。
char* nameCopy = malloc(length+1);
strcpy(nameCopy, name);
registeredNames[lastIndex++] = nameCopy;
您没有提到您的输入是否以 NULL 结尾 - 如果不是,则需要额外小心,并且 strcmp/strcpy 不适合。
其他提示
避免 STL 是有正当理由的。当在内存或速度很重要的固定环境中工作时,有时很难用 STL 来判断底层发生了什么。是的,您可以编写自己的内存分配器,是的,速度通常不是问题,但是跨平台的 STL 实现之间存在差异,并且这些差异可能很微妙并且可能存在错误。在考虑使用内存时,内存可能是我最关心的问题。
内存是宝贵的,我们如何使用它需要严格控制。除非你一直走这条路,否则这个概念可能没有意义,但它是事实。我们确实允许在工具中使用 STL(在游戏代码之外),但在实际游戏中是禁止的。另一个相关问题是代码大小。我有点不确定 STL 对可执行文件大小的影响有多大,但我们发现使用 STL 时代码大小显着增加。即使您的可执行文件“仅”大 2M,对于游戏的其他内容来说,RAM 也会少 2M。
STL 确实很好。但它可能会被不知道自己在做什么的程序员滥用。这不是故意的,但当您不想看到它们时,它可能会带来令人讨厌的惊喜(同样,内存膨胀和性能问题)
我确信您已经接近您的解决方案。
for ( i = 0; i < lastIndex; i++ ) {
if ( !strcmp(®isteredNames[i], name ) {
break; // name was found
}
}
if ( i == lastIndex ) {
// name was not found in the registeredNames list
registeredNames[lastIndex++] = strdup(name);
}
您可能不想使用 strdup。这只是如何存储给定示例的名称的示例。您可能需要确保您不想自己为新名称分配空间,或者使用应用程序中可能已经可用的其他内存构造。
并且请不要编写字符串类。我认为字符串类可能是如何不在 C++ 中重新设计基本 C 构造的最糟糕的例子。是的,字符串类可以向您隐藏许多漂亮的细节,但它的内存使用模式很糟糕,并且这些模式不太适合控制台(即PS3或360等)环境。大约8年前,我们也做了同样的事情。在我们点击主菜单之前已经分配了 200000+ 内存。内存非常碎片化,我们无法让游戏的其余部分适应固定的环境。我们最终把它撕了出来。
类设计对于某些事情来说非常有用,但这不是其中之一。这是一种观点,但它是基于现实世界的经验。
如果可移植性是一个问题,您可能需要查看 STL端口.
为什么不能使用STL?
无论如何,我建议您实现一个简单的字符串类并列出您自己的模板。这样您就可以使用与通常相同的技术,并将指针和内存管理限制在这些类中。如果能模仿STL那就更好了。
如果你真的不能使用 stl(我很遗憾在我从事游戏行业时相信这是真的)那么你不能创建自己的字符串类吗?最基本的字符串类将在构造和赋值时分配内存,并在析构函数中处理删除。稍后您可以根据需要添加更多功能。完全可移植,并且非常容易编写和单元测试。
使用 char* 需要您使用 C 函数。就您而言,您真正需要的是复制字符串。为了帮助您,您可以使用 strndup 函数。然后你必须写一些类似的东西:
void RegisterName(const char* name)
{
// loop to see if name already registered snipped
if(notFound)
{
registerNames[lastIndex++] = stdndup(name, MAX_STRING_LENGTH);
}
}
这段代码假设你的数组足够大。
当然,最好的办法是正确实现您自己的字符串、数组和列表,...或者让你的老板相信 STL 不再是邪恶的!
编辑:我想我误解了你的问题。据我所知,这段代码不存在常量问题。
我是从我的头脑中这样做的,但它应该是正确的:
static int lastIndex = 0;
static char **registeredNames = new char*[numberOfNames];
void RegisterName(const * char const name)
{
bool found = false;
//loop to see if name already registered snipped
for (int i = 0; i < lastIndex; i++)
{
if (strcmp(name, registeredNames[i] == 0))
{
found = true;
break;
}
}
if (!found)
{
registeredNames[lastIndex++] = name;
}
}
我可以理解为什么你不能使用 STL - 大多数代码都会使你的代码变得非常膨胀。然而,游戏程序员有为游戏程序员提供的实现 - RDESTL 就是这样一个图书馆。
使用:
const char **registeredNames = new const char * [numberOfNames];
将允许您分配一个 const * char const
到数组的一个元素。
只是出于好奇,为什么“工作要求不能使用任何最方便的 C++ 结构”?
所有建议的方法都是有效的,我的观点是,如果 C# 的做法很有吸引力,可以复制它,创建您自己的类/接口来呈现相同的抽象,即一个带有方法 Contains 和 Add 的简单链表类,使用其他答案提供的示例代码,这应该相对简单。
C++ 的一个伟大之处在于,通常您可以使其外观和行为按照您想要的方式进行,如果另一种语言有很好的实现,您通常可以复制它。
我已经使用这个 String 类很多年了。
http://www.robertnz.net/string.htm
它实际上提供了STL字符串的所有功能,但被实现为真正的类,而不是模板,并且不使用STL。
这是一个明显的例子,你可以自己动手。对向量类执行相同的操作。
- 通过测试优先编程来做到这一点。
- 把事情简单化。
如果您处于 MT 环境中,请避免对字符串缓冲区进行引用计数。
如果您不担心约定而只想完成工作,请使用 realloc。我一直对列表做这种事情,它是这样的:
T** list = 0;
unsigned int length = 0;
T* AddItem(T Item)
{
list = realloc(list, sizeof(T)*(length+1));
if(!list) return 0;
list[length] = new T(Item);
++length;
return list[length];
}
void CleanupList()
{
for(unsigned int i = 0; i < length; ++i)
{
delete item[i];
}
free(list)
}
您还可以做更多事情,例如仅在每次列表大小加倍时重新分配,通过索引或检查相等性从列表中删除项目的函数,创建用于处理列表等的模板类......(我很久以前写过一篇并且总是使用自己......但遗憾的是我正在工作,不能只是将其复制粘贴到这里)。不过,老实说,这可能不会优于 STL 的等效项,尽管如果您做了大量工作或 STL 的实现特别糟糕,它的性能可能与它相当。
令人烦恼的是,C++ 没有运算符 renew/resize 来替代 realloc,这将非常有用。
哦,如果我的代码出错了,我很抱歉,我只是从内存中把它拉出来。
不管你是否使用STL,const正确性仍然是const正确性。我相信您正在寻找的是使 RegisteredNames 成为 const char **
以便分配给 registeredNames[i]
(这是一个 const char *
)有效。
而且,这真的是你想做的吗?看来制作字符串的副本可能更合适。
此外,考虑到您正在对它执行的操作,您不应该考虑将其存储在列表中,一组会更好。