为什么<const char*>和<const char[]>具有非常不同的内存或指针行为?
Why do <const char*> and <const char[]> have very different memory or pointer behaviour?
我正在试验C++,发现const char*
和const char[]
在以下代码中的行为非常不同。如果我没有很好地表达这个问题,真的很抱歉,因为我不清楚代码中发生了什么。
#include <iostream>
#include <vector>
// This version uses <const char[3]> for <myStr>.
// It does not work as expected.
struct StrStruct
{
const char myStr[3];
};
// This program extracts all the string elements in <strStructList> and copy them to <strListCopy>
int main()
{
StrStruct strStruct1{"ab"};
StrStruct strStruct2{"de"};
StrStruct strStruct3{"ga"};
std::vector<StrStruct> strStructList{strStruct1, strStruct2, strStruct3};
std::vector<const char*> strListCopy{};
for (StrStruct strStructEle : strStructList)
{
strListCopy.push_back(strStructEle.myStr);
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl;
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "n" <<std::endl;
}
std::cout << "Show content of <strListCopy>:" << std::endl;
for (const char*& strEle : strListCopy)
{
std::cout << strEle << std::endl;
}
}
以下是其输出:
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Memory address for the string got pushed back in is [address#99]
Memory address for the first element of the string got pushed back in is [address#99]
Show content of <strListCopy>:
ga
ga
ga
但是,如果我只是简单地更改StrStruct
的实现
从:
// This version uses <const char[3]> for <myStr>.
// It does not work as expected.
struct StrStruct
{
const char myStr[3];
};
自
// This version uses <const char*> for <myStr>.
// It works as expected.
struct StrStruct
{
const char* myStr;
};
程序的输出变为:
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#1]
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#2]
Memory address for the string got pushed back in is [address#10]
Memory address for the first element of the string got pushed back in is [address#3]
Show content of <strListCopy>:
ab
de
ga
让我感到困惑的是以下几点:
为什么在第一个版本中所有字符串都具有相同的值?我试图在每个循环中使用
const strStruct&
而不是strStruct
来解决问题,但我不明白如何。为什么
const char*
和const char[]
的行为如此不同?由于以下原因,我认为它们大致相同:
const char myChars[] = "abcde";
const char* myCharsCopy = myChars;
std::cout << myChars << " vs " << myCharsCopy << std::endl;
它打印出abcde vs abcde
,您可以直接将const char[]
的值分配给const char*
,而不会出错。
- 为什么将
const char[]
更改为const char*
可以解决问题?
理解其余部分所需的基础知识:
数组和衰减
struct StrStruct
{
const char myStr[3];
};
包含数据
struct StrStruct
{
const char * myStr;
};
指向数据。
数组衰减到指针,但不是指针本身。
const char myChars[] = "abcde";
创建一个大小完全正确的数组(六个字符、五个字母和 null 终止符)来保存"abcde"
并将字符串复制到数组中。请注意,这不需要const
。
const char* myCharsCopy = myChars;
定义指向char
的指针,并为其分配数组myChars
。 在此过程中,myChars
会自动衰减到指针。myCharsCopy
不是myChars
的副本;它只保存myChars
的地址。注意,只要myChars
是const
,myCharsCopy
就必须const
。另请注意,您无法分配给数组,并且几乎不可能复制数组,除非您将其放置在另一个数据结构中。您通常能做的最好的事情是将数组中的内容复制到另一个数组(memcpy
或strcpy
取决于目标以及数组是否为以 null 结尾的字符数组)。
请注意,在许多用法中,例如函数参数const char[]
和const char*
表示相同的内容。
void func(const char a[], // accepts constant pointer to char
const char * b) // also accepts constant pointer to char
这些东西变得非常奇怪,原因(大部分)在 1970 年代非常有意义。今天,我强烈建议您使用像std::vector
和std::array
这样的库容器,而不是原始数组。
基于范围的循环
基于范围的 for 循环对列表中项的副本进行操作,除非您另行指定。在体内
for (StrStruct strStructEle : strStructList)
在循环的第一次迭代中,strStructEle
不是strStruct1
,甚至不是位于strStructList
中的strStructEle
的副本,它是第三个相同的对象。副本在正文末端被销毁,释放使用的存储空间。
跟
for (StrStruct & strStructEle : strStructList)
循环将对strStructList
中的项的引用进行操作,因此不会创建副本。
现在您已经掌握了速度...
要点 1
为什么在第一个版本中所有字符串都具有相同的值?我试图在每个循环中使用const strStruct&而不是strStruct来解决问题,但我不明白如何。
因为
struct StrStruct
{
const char myStr[3];
};
包含创建StrStruct
副本时的数据。代码在此处复制数据结构
std::vector<StrStruct> strStructList{strStruct1, strStruct2, strStruct3};
而且,更重要的是输出,这里
for (StrStruct strStructEle : strStructList) // strStructEle copied, so data in it is copied
{
strListCopy.push_back(strStructEle.myStr); //strStructEle.myStr decays to pointer,
// and pointer is stored in strListCopy
// this is not really a copy it's a pointer
// to data stored elsewhere
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl; // print address of array
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "n" <<std::endl;
// prints address of the first item in the array, the same as the array
} // strStructEle is destroyed here, so the stored pointer is now invalid.
// Technically anything can happen at this point
但在这种情况下,可能发生的任何事情似乎都是存储被重用于循环的下一次迭代中的strStructEle
。这就是为什么所有存储的指针看起来都相同的原因。它们是一样的。它们是不同的对象,它们都驻留在不同时间点的同一位置。所有这些对象都已过期,因此尝试查看它们不是一个好主意。
const strStruct&
"修复"了问题,因为没有创建副本。每次迭代都对不同位置的不同对象进行操作,而不是对同一位置的不同对象进行操作。
要点 3
为什么将
const char[]
更改为const char*
可以解决问题?
如果myStr
是指针而不是数组,则情况就不同了
for (StrStruct strStructEle : strStructList) // strStructEle copied, so data in it is copied
// BUT! The data in it is a pointer to data
// stored elsewhere that is NOT copied
{
strListCopy.push_back(strStructEle.myStr); //strStructEle.myStr is a pointer and is
// directly stored in strListCopy
// this is still not a copy
std::cout << "Memory address for the string got pushed back in is "
<< &strStructEle.myStr << std::endl; // print address of pointer, not what
// it points at
std::cout << "Memory address for the first element of the string got pushed back in is "
<< (void *) &strStructEle.myStr[0] << "n" <<std::endl;
// prints address of the first item pointed to by the pointer,
// and will be a totally different address
}
要点 2
为什么常量字符*和常量字符[]的行为如此不同?由于以下原因,我认为它们大致相同...
如上所述,这是阵列衰减的结果。
题外话:
当vector
被允许直接包含(并拥有)他们正在收集的数据时,它们处于最佳状态。它们处理所有的内存管理,并将数据保存在一个漂亮的、易于缓存的块中。
基于 Yakov Galka 和评论部分中的 user4581301
在回答之前要清除的几件事:
const char*
和const char[]
的区别:
从概念上讲:
const char*
是指向const char
的指针
const char[]
是字符数组本身。
在代码方面:
const char*
存储内存地址,并且它自己的内存地址与它存储的内存地址不同。
const char[]
存储数组中第一个元素的内存地址,并且它自己的内存地址与它存储的内存地址相同。
const char myCharsArray[] = "abcde"; // Writing like this guarrentees you have an null terminator at the end
const char* myCharsPointer = "qwert ";
std::cout << "The memory address for <myCharsArray> is "
<< &myCharsArray
<< std::endl;;
std::cout << "The memory address for the first element in <myCharArray> is "
<< (void *) &myCharsArray[0]
<< std::endl;
std::cout << "The memory address for <myCharsPointer> is "
<< &myCharsPointer
<< std::endl;
std::cout << "The memory address for the first element in <myCharsPointer> is "
<< (void *) &myCharsPointer[0]
<< std::endl;
它的输出是这样的:
The memory address for <myCharsArray> is [address#10]
The memory address for the first element in <myCharArray> is [address#10]
The memory address for <myCharsPointer> is [address#88]
The memory address for the first element in <myCharsPointer> is [address#99]
要回答这三个问题:
问题1:
在第一个版本中,std::vector::push_back
不断在其复制的字符数组中添加第一个元素的地址,这也是strStructEle.myStr
本身永远不会更改的地址。最后,列表是一堆内存地址,其值完全相同。
通过使用const strStruct&
,使用对原始内容的引用。因此,他们唯一和真实的内存地址被复制到列表中。
问题2:
如上所述的差异。
问题3:
它允许传递原始字符数组的原始内存地址,而不是复制原始字符数组的内容,然后复制临时对象的内存地址。