C 是一种相对低级的语言,它使我们更接近我们使用的机器的裸芯片,因此关于变量存储的概念一开始可能会令人困惑。尤其是在处理字符串时。如果您打算擅长用 C 进行编码,那么深入了解这些元素将非常重要——正如我们假设您打算这样做的那样。
就字符串而言,您需要注意 3 种存储方式; 'literals'、'automatic'变量和动态分配。这是三种截然不同的动物。将它们混合起来既不明智,甚至也不可能。
-
文字。当您使用
"Hello " 和 "There" 之类的语句声明常量字符串时,您正在创建字符串“文字”。这些是不可更改的实体,硬编码到最终的可执行文件中,并存储在特定的段中,该段并非设计为在运行时更改。大多数操作系统甚至不允许您这样做:当您这样做时,您的代码会遇到段错误。您通常也不希望这样做,因为如果您这样做了,您可能会在其他对您很重要的事情上进行写作。
-
'Automatic' 变量分配在 cpu 寄存器或堆栈中。当 C 最初被构想时,这个空间受到了极大的限制。今天,情况有所不同,尽管它仍然有限。当你声明一个具有显式大小的字符数组时,你通常会创建一个基于堆栈的变量:char a[16];
这样做既简单又方便,因为您不负责自己清理,但它有几个缺点。创建后无法更改数组的大小,更重要的是;因为它会在声明它的函数退出时消失,所以无法从函数中返回内容。
- 稍后,您可能会调查扩展程序(例如
alloca),以便我们在一定程度上规避这些限制。
-
动态分配将变量存储在'堆'上,它代表了可用内存的大部分。这就是malloc、realloc 和 co。参加进来。创建和使用这些变量更复杂,但也更强大。正如您在代码中所展示的那样;这些函数返回指向所请求内存的指针(地址)。
但是,在实例化这些时必须特别小心。这正是您的代码误入歧途的地方。
您清楚地了解字符串只是一个字符序列。当您打算修改字符串时,您必须使用足够大小的“自动”变量来包含您打算容纳的最长系列,或者使用堆。
您必须不做的是获取文字的地址,然后尝试写入或附加到该地址。
您需要修改createString 以通过创建必要的存储并将源字符串复制到新缓冲区中来解决这个问题。在您的函数中,您在堆栈上分配res,然后按值返回该变量——这意味着编译器将创建一个副本。但是,它只会复制结构本身;它不会为您分配任何指针或复制它们的内容为。由于您的结构只有一个指针宽,因此按值返回可能会按您的意愿工作,但做一些不同的事情可能会更稳定。您使用的习语在某种程度上是 C++ 和 C 的混合体,这很可能会出现问题。
在 C 中,最好将变量的声明与其实例化分开。
数组大小。 某些编译器也可以帮助您,尤其是使用literals,但一般来说,使用sizeof(x) 来获取长度是不可能的或可移植的一个 C 字符串。如果您使用标准 C 方法以零终止字符串,那么 strlen() 函数将按照您的意图实现该目的。当您声明一个文字时,编译器会自动为您添加该终止符。
或者,您可以自己跟踪长度(可能还有容量),并将其存储在结构中。
您还需要设计一个互补的destroyString 函数来将内存返回给系统,否则您的应用程序可能会导致内存“泄漏”——无法释放分配的内存。
鉴于您正在选择这种工作方式,因此也可以创建一个appendString 函数来执行相应的任务。
在每种情况下,将指向对象的指针传递给函数更可靠——就像 C++ 等在幕后所做的那样。
所以, 在 main 中:分别声明和初始化对象,然后适当地使用和销毁它们。
String a, b;
createString( &a, "Hello " );
createString( &b, "There" );
appendString( &a, &b );
puts( a.chars );
destroyString( &a );
destroyString( &b );
并在文件的前面声明实现函数。
void createString( String *s, char* chars ) {
int len = strlen( chars );
s->chars = malloc( len + 1 ); // strlen does not count the terminating null
if( s->chars ) { // make sure the pointer is valid: malloc may fail
for( int i = 0; i <= len; ++i ) // make sure to copy the terminator as well
s->chars[i] = chars[i];
}
}
void appendString( String *a, String *b ) {
int alen = strlen( a->chars ),
blen = strlen( b->chars );
char *tmp = realloc( a->chars, alen + blen + 1 );
if( tmp ) {
a->chars = tmp; // realloc will have copied the buffer for you
for( int i = 0; i <= blen; ++i )
a->chars[alen+i] = b->chars[i]; // start at the position of the terminator in 'a'
}
}
void destroyString( String *s ) {
free( s->chars );
}
当然,此代码还有许多重要的进一步改进。我们希望您能在发现它们的过程中获得乐趣...