指针和字符串 C++

16

我正在自学C++,对指针有些困惑(具体来说是在以下源代码中)。但首先,我会展示我知道的东西(然后将代码与此进行对比,因为我觉得其中存在一些矛盾)。

我知道的内容:

int Age = 30;
int* pointer = &Age;

cout << "The location of variable Age is: " << pointer << endl;
cout << "The value stored in this location is: " << *pointer << endl;
指针保存内存地址。使用间接(解引用)运算符(*),可以访问指针所指向的内存位置中存储的内容。对于本书中的代码,我有些难以理解...
cout << "Enter your name: ";
string name;
getline(cin, name); //gets full line up to NULL terminating character

int CharsToAllocate = name.length() + 1; //calculates length of string input
                          //adds one onto it to adjust for NULL character
char* CopyOfName = new char[CharsToAllocate];
// pointer to char's called CopyOfName, is given the memory address of the 
//beginning of a block
//of memory enough to fit CharsToAllocate. Why we added 1? Because char's need a 
//NULL terminating character (\0)

strcpy(CopyOfName, name.c_str()); //copies the string name, into a pointer?

cout << "Dynamically allocated buffer contains: " << CopyOfName << endl;
delete[] CopyOfName; //always delete a pointer assigned by new to prevent memory leaks

输出:

Enter your name: Adam
Dynamically allocated buffer contains: Adam
上面代码的注释是我的。我的问题始于strcpy。为什么要将name.c_str()复制到指针CopyOfName中?这是否意味着所有字符串本质上都是指针?所以像string testing = "Hello world"这样的东西实际上是指向存储"H"的内存位置的指针吗?
接下来,为什么在打印输出语句中使用CopyOfName而不是* CopyOfName?指针保存内存地址?使用* CopyOfName将打印出内存位置的内容。我在Code :: Blocks中尝试过,如果输入文本是“Hello World”,则在print out语句中使用* CopyOfName只会给出“H”。这是有道理的,因为当我声明需要带有“new”东西的内存块时,这实际上返回了一个指向动态分配的内存块第一部分的指针。
唯一可以使我解决此问题的方法是假设字符串实际上是指针。
string testing = "Confused";
cout << testing << endl;

将会打印出单词"Confused"

然而,如果我尝试编译

string testing = "Confused";
cout << *testing; 

我收到了一个错误消息。

简单来说,总结我的问题,我正在尝试理解使用strcpycout语句的代码。


1
顺便提一下,不存在所谓的“NULL终止字符”。但是有一个“NUL”('\0')字符,这可能是你想要表达的。 - yzt
9个回答

19

看起来你已经理解了什么是C风格的字符串,但为了概括一下,它们只是内存中的字符数组,通常以空字符\0结尾。通常它们通过指向字符串中第一个字母的char*引用。当它们被打印时,通常从第一个字符开始打印字符串,当达到\0终止符时停止打印(或复制等)。

std::string是一个类,通常包装一个C风格的字符串。这意味着std::string对象(通常)具有一个私有的C风格的字符串,用于实现其功能。函数std::string::c_str()返回对此底层C风格字符串的指针。

假设char *str;指向一个C风格的字符串。如果你尝试运行cout << *str << endl;,你会注意到只有第一个字符被打印出来。这是因为C++的函数重载机制。 *str的数据类型是char,因此调用了coutchar版本,并忠实地打印了单个字符*str。为了与C风格字符串兼容,接受char*作为参数的cout版本将指针视为C风格字符串进行打印。如果你cout一个int*,例如,底层的int将不会被打印出来。

编辑:另一条评论:

你尝试解引用一个std::string对象失败的原因是,它确实不是一个指针。你可以对std::string::c_str()的返回值进行解引用,这样你就能得到字符串的第一个char

相关: std::string是如何实现的?.


这里我认为对于OP理解的重点是"指向字符串中第一个字符"这部分。 - yzt

5

在C语言中,字符串仅仅是字符数组。而当作为函数参数使用时,数组就会退化为指向它第一个元素的指针。

在C++中,std::string是一个类。它包含了一个C风格的字符数组,在c_str()方法中返回该数组。但是这个字符串本身并不是一个指针,所以你不能对它进行解引用操作;你必须使用c_str()方法来获取指向字符串内容的指针。


5

指针与数组不同。字符串文字是不可变的,当您有一个指向字符串文字的指针时,您可以检查其内容,但修改这些内容是未定义的行为。使用此语法时:

char arr[] = "hi there";

字符串字面量会被复制到数组中。因为您没有指定大小,编译器会自动推断出大小。NUL(空字符)终止符也会自动添加。如果您指定了大小,您必须确保缓冲区可以容纳NUL终止符。因此:

char arr[5] = "hello";

这是一个错误。如果您使用花括号初始化语法:

char arr[5] = { "h", "e", "l", "l", "o" };

这是一个错误,因为没有使用NUL终止符。如果使用strcpy,将会替你添加NUL终止符。 std::string提供了两种方法返回其底层内容的指针:datac_str。在C++11之前,它们唯一的区别在于data不包括NUL终止符。在C++11中,现在也包括,所以它们的行为是相同的。由于指针很容易失效,因此操作这些指针是不安全的。同时,做char * ptr = str.c_str();也是不安全的,因为c_str返回的数组的生命周期在分号处结束。你需要将它复制到缓冲区。

3
所以像 string testing = "Hello world"; 实际上是一个指向存储“H”的内存位置的指针吗?
不是的,上面有一个名为string的对象。对于char* testing = "Hello World"才是正确的。正如你所看到的,它被声明为指针,并且它指向字符串中的第一个字符-H。
接下来,为什么在打印输出语句中,CopyOfName不是*CopyOfName?指针保存内存地址吗?使用*CopyOfName将打印出内存位置的内容。我在代码块中尝试过,如果输入文本是“Hello World”。在打印输出语句中使用*CopyOfName将只给出“H”。
cout获取指向字符串第一个字符的指针,因此CopyOfName是正确的。在这种情况下,它将打印从H开始直到找到\0(空字符)的每个字符。像“hello”这样的字符串实际上有6个字符-'h' 'e' 'l' 'l' 'o' '\0'。当您写*CopyOfName时,您正在取消引用该指针,而*CopyOfName实际上只有一个字符。

快速格式化提示:您可以使用 > 开始一行以使其成为块引用,并使用反引号将代码片段括起来以进行内联格式化。如果您感兴趣,可以在此处查看完整的格式化参考 - icktoofay

3
按顺序回答你的问题:
“为什么将name.c_str()复制到指针CopyOfName中?这是否意味着所有的字符串都是实质上的指针?所以像string testing = “Hello world”;实际上是一个指向存储“H”的内存位置的指针?”
正如Yu Hao在他/她的评论中指出的那样,了解C++风格字符串和C风格字符串之间的区别非常重要。在前一种情况下,您处理的是一个“不透明”的对象,而在后一种情况下,您基本上处理的是字符数组。
对于C++字符串对象,您可以使用c_str()方法获取(指向)C风格的字符数组的指针。在C中,数组使用开始地址的指针表示,然后通过提供偏移量(数组中的索引)从该起始地址来实现引用。所以,这个bundle中最后一个问题的答案是“是的”,C风格字符串的指针是指向第一个字符'H'的指针。
“接下来,在打印输出语句中,为什么CopyOfName不是* CopyOfName?指针保存内存地址?”
因为<<运算符被重载以处理C字符串。该方法的实现“知道如何处理”指针。

2

作为一名学习者,您正在提出正确的问题。

回答:

  1. 在C++中,string是一个对象,c_str()实际上返回字符串的第一个字符的指针(C风格)。
  2. 关于C中的字符串,您是正确的,变量实际上指向字符串的第一个字符。
  3. C++根据变量类型执行许多操作。当您传递一个string对象时,cout会打印该字符串。此外,C++足够聪明,可以确定*testing是非法的。

@ Sudhee 我认为应该是 c_str() 而不是 c_ptr()。 - kernel

2
为什么需要将name.c_str()复制到指针CopyOfName中?
"name"是一个STL字符串,这是一个对象,与c字符串不同。c字符串是一组包含字符并带有空终止符的内存集合。因此,如果您使用STL字符串并想将它们转换为c字符串,则可以使用.c_str()方法来获取c字符串。
由于已分配了足够的内存来容纳名称,因此CopyOfName包含足够的内存来容纳名称。
cout有很多不同的输出选项,看起来它可以使用char *(即c字符串)或STL字符串。但是似乎不能使用指向STL字符串的指针。
当您介绍“testing”时,我有点困惑,但我认为您在混淆c字符串(char *)和STL字符串之间的区别,后者是对象。不要感到难过或放弃。这些东西很棘手,需要一段时间才能理解。
我建议尝试理解术语“c字符串”,“char *”,“stl字符串”以及可能的“指向stl字符串的指针”的区别。

1
在C语言中,由于没有C++标准字符串,char*被称为“字符串”。正如您所指出的那样,它是以NULL字符结尾的字符数组。任何接受C风格字符串的标准库函数都会取一个指向该字符串的指针,原因有两个:
  1. 将C风格字符串视为整体而不是一堆字符更容易,与其他数组不同,因此获取指针可以保持这种想法
  2. 这是获取数组作为函数参数的最简单方法,只需获取第一个元素的指针,特别是对于C字符串的情况,可以直接读取到NULL字符。

1

我认为你正在做的事情(如果我说错了,请纠正我),是将你的字符串复制到一个动态字符数组中。所以你没有将它复制到指针中。指针涉及其中的原因是,如果我没记错的话,动态数组需要指针来正确分配它们的内存。


数组是动态集合的相反,除非你指的是非常量与常量指针之间的区别。在strcpy的情况下,被复制到的数组必须已经分配了与原始字符串大小相同或更多的空间。 - IllusiveBrian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接