假设字符串类具有以下私有数据成员:
char *strval;
int length;
以下字符串类的构造函数代码是否正确?如果不正确,为什么?
string::string(const char* s):length(strlen(s))
{
strval = s;
}
<击> 我的回答是strlen()正在评估指针值,但这听起来不对。
答案 0 :(得分:7)
给出的构造函数代码是正确的,因为它将为大多数输入成功编译和运行。然而,至少有几个原因,它的风格很差:
s
是否为NULL
(但这可以记录为非法输入)答案 1 :(得分:2)
代码不正确,甚至无法编译。 strval=s
会失败,因为strval
是const
的指针,s
不是。以下是一个更好的起点:
class string
{
const char *strval; // This must be const for 'strval = s' to work.
int length;
string (const char *s);
};
string::string(const char* s):length(strlen(s))
{
strval = s;
}
@Greg的观点仍然有效。我只是强调一个重点。
答案 2 :(得分:1)
字符串可以合理地工作有三种方式,但是如上所述,它不属于三种类别中的任何一种。他们是:
一个“价值语义”对象
复制指定的数据和
保持对它的所有权,就像
std::string
一个(const char*, length)
const
对某些文本的“引用”
调用者,保证
文本的生命周期将长于
“字符串”的用法
(char *, length)
的引用
一个可写的缓冲区
调用者,但其中的“字符串”
对象可能会更新(也许
甚至移动NUL终结器)
我们可以依次考虑每一个。
这个问题非常明确地指出这是一个“字符串”类,这意味着(弱,但我们没有太多东西要去......)该类旨在作为通用值语义字符串。假设当下是真的,我们可以考虑所呈现的实现:该类只记住调用者指定的非const字符缓冲区的地址和长度,而不占用缓冲区的所有权。即使从调用者的角度来看,字符串类被赋予该缓冲区的所有权(即调用者不会在不经过字符串API的情况下进一步修改内容),也没有证据表明字符串对象具有增长缓冲区的方法,这是通用字符串类的基本要求。
因此,如果它是一个通用字符串,那么它应该复制该值并取得所有权。最好通过重新安排数据成员来完成:
int length; // should really be size_t
char *strval;
...以便构造函数可以使用初始化列表并知道length的值将首先填充,因此可用于strval的初始化 - 这样就无需计算字符串长度两次......
string(const char* p, int n)
: length(strlen(p)), strval(new char[length + 1])
{
strcpy(strval, p);
}
如果字符串不意味着是一个通用字符串,那么它的第一个也是最大的问题是名称“string”。为了找到更好的名称,让我们回到所提供的功能。它记住了调用者指定的缓冲区的地址和当前内容长度:根据我的经验,这通常在指针是常量时完成 - 例如,在调用者的字符缓冲区中抽象非NUL终止的子串 - 例如,元素在内存映射文件中的位置。
第二个问题 - 首先由Aaron发现 - 变成构造函数的字符串参数为const
,并且需要更改为char*
以允许strval
从中进行初始化。或者,如果您将strval
更改为const char*
,我们将返回到我提到的上述有用的const
非NUL终止的子字符串引用。同样,应找到适当的名称,例如text_reference
,substring_reader
或任何适合您的工作。
显示的strval
指针不是const
,这表示一个对象能够覆盖提供的缓冲区内容,但仍然无法延长缓冲区。这在我的经验中很少有用,尽管它可能是某些特定程序需求的理想选择。应该找到这样一个类的更好的名称,而不是简单!,但是例如“buffer_overwriter”暗示 - 至少在我看来 - 在长度限制的性质和非const
访问。此外,提供的构造函数仅适用于已经NUL终止的缓冲区,但我们可以想象第二个构造函数(char*, int)
可以删除该需求,并可以使对象更广泛地使用。
否则,格雷格会彻底解决问题。