所以我想确切地知道声明字符串的方式有多少。我知道有几次问过类似的问题,但我认为我的重点是不同的。作为C语言的初学者,我想知道哪种声明方法是正确和可取的,这样我才能坚持下去。
我们都知道我们可以通过以下两种方式声明字符串。
char str[] = "blablabla";
char *str = "blablabla";
在堆栈溢出中读取一些Q& A之后,我被告知字符串文字放在内存的只读部分。因此,为了创建可修改的字符串,您需要在堆栈中创建字符串长度为+ 1的字符数组,并将每个字符复制到数组中。 所以这就是第一行代码。
但是,第二行代码的作用是创建一个字符指针,并为指针指定位于内存只读部分的第一个字符的地址。所以这种声明不涉及逐字符复制。
如果我错了,请告诉我。
到目前为止它似乎很容易理解,但让我感到困惑的是修饰语。例如,有人建议
const char *str = "blablabla";
而不是
char *str = "blablabla";
因为如果我们做类似
的事情*(str + 1) = 'q';
这将导致未定义的行为,这是非常可怕的。
但有些人甚至更进一步,建议像
static const char *str = "blablabla";
并说这会将字符串放入静态内存中 永远不会被修改以避免未定义的行为。
那实际上是#right#方式声明一个字符串?
此外,我也有兴趣在声明字符串时了解范围。
例如,
(你可以忽略这些例子,如其他人所指出的那样,它们都是错误的)
#include <stdio.h>
char **strPtr();
int main()
{
printf("%s", *strPtr());
}
char **strPtr()
{
char str[] = "blablabla";
char *charPtr = str;
char **strPtr = &charPtr;
return strPtr;
}
将打印一些垃圾值。
但是
#include <stdio.h>
char **strPtr();
int main()
{
printf("%s", *strPtr());
}
char **strPtr()
{
char *str = "blablabla";
/*As point out by other I am returning the address of a local variable*/
/*This causes undefined behavior*/
char **strPtr = &str;
return strPtr;
}
将完美无缺。 (不,它没有,它是未定义的行为。)
我想我应该把它作为另一个问题。 这个问题太长了。
答案 0 :(得分:4)
您的许多困惑来自对C和字符串的常见错误理解:在您的问题标题中明确说明了这一点。 C语言没有原生字符串类型,因此实际上有完全零方式在C中声明字符串。
花一些时间阅读Does C Have a String type?,这很好地解释了这一点。
事实证明,你不能(理智地)做到以下事实:
char *a, *b;
// code to point a and b at some "strings"
if (a == b)
{
// do something if strings compare equal
}
if
语句将比较指针的值,而不是它们所寻址的内存的内容。因此,如果a
和b
指向两个不同的内存区域,每个区域包含相同的数据,则比较a == b
将失败。只有当a
和b
保持相同的地址(即指向内存中的相同位置)时,比较才会评估为“真”(即除零之外的东西)的唯一时间。
C有什么常规,以及一些使生活更轻松的语法糖。
约定是“字符串”表示为以值零结尾的char
序列(通常称为NUL并由特殊字符转义序列'\0'
表示)。这个约定来自原始标准库的API(早在70年代),它提供了一组字符串基元,如strcpy()
。这些原语对于在语言中做任何真正有用的事情都非常重要,因为通过添加语法糖,这对于程序员来说更容易生活(这是在语言从实验室中逃脱之前)。
句法糖是“字符串文字”的存在:不多也不少。在源代码中,用双引号括起来的任何ASCII字符序列都被解释为字符串文字,编译器会生成字符的副本(在“只读”内存中)和终止NUL字节符合约定。现代编译器检测重复的文字,只生成一个副本 - 但上次我看起来并不是标准的要求。因此:
assert("abc" == "abc");
可能会也可能不会引发断言 - 这强化了C没有本机字符串类型的陈述。 (就此而言,C ++也没有 - 它有一个String类!)
除此之外,如何使用字符串文字来初始化变量?
您将看到的第一个(也是最常见的)表单是
char *p = "ABC";
这里,编译器在程序的“只读”部分中留出4个字节(假设sizeof(char) ==1
)并用[0x41, 0x42, 0x43, 0x00]
初始化它。然后,它使用该数组的地址初始化p
。您应该注意,此处有一些const
转换,因为字符串文字的基础类型是const char * const
(指向常量字符的常量指针)。这就是为什么通常建议您将其写为:
const char *p = "ABC";
这是一个“指向常量字符的指针” - 另一种说“指向只读内存”的方法。
接下来的两个表单使用字符串文字初始化数组
char p1[] = "ABC";
char p2[3] = "ABC";
请注意,两者之间存在严重差异。第一行创建一个4字节数组。第二个创建一个3字节的数组。
在第一种情况下,与以前一样,编译器创建一个包含[0x41, 0x42, 0x43, 0x00]
的4字节常量数组。请注意,它会添加尾随NUL以形成“C String”。然后它保留4个字节的RAM(在堆栈中用于本地变量,或在“静态”内存中用于文件范围的变量)并插入代码以在运行时初始化它通过复制“只读” “阵列进入分配的RAM。您现在可以随意修改p1
的元素。
在第二种情况下,编译器创建一个包含[0x41, 0x42, 0x43]
的3字节常量数组。请注意,有没有尾随NUL。然后它保留3个字节的RAM(在堆栈上用于本地变量,或在“静态”内存中用于文件范围的变量)并插入代码以通过复制“只读”来在运行时初始化它 “阵列进入分配的RAM。您现在可以随意修改p2
的元素。
两个数组p1
和p2
的大小差异至关重要。以下代码(如果您运行它)将演示它。
char p1[] = "ABC";
char p2[3] = "ABC";
printf ("p1 = %s\n", p1); // Will print out "p1 = ABC"
printf ("p2 = %s\n", p2); // Will print out "p2 = ABC!@#$%^&*"
第二个printf的输出是不可预测的,理论上可能会导致代码崩溃。它似乎有效,只是因为大量的RAM充满了零,最终printf
找到了一个终止的NUL。
希望这有帮助。