我对C ++比较陌生......
我正在学习和编码,但我发现指针的想法有些模糊。据我所知*它指向一个值和&指向一个地址......很棒,但为什么?哪个是byval,哪个是byref,又是为什么?
虽然我觉得我正在学习和理解堆栈与堆,运行时间与设计时间等的想法,但我并不觉得我完全理解发生了什么。我不喜欢使用我不完全理解的编码技术。
有没有人可以详细说明下面使用这个相当“简单”函数的指针是什么以及为什么,尤其是指向函数本身的指针.. [得到它]
只是询问如何清理(删除[])str ...或者它是否超出范围..谢谢。
char *char_out(AnsiString ansi_in)
{
// allocate memory for char array
char *str = new char[ansi_in.Length() + 1];
// copy contents of string into char array
strcpy(str, ansi_in.c_str());
return str;
}
答案 0 :(得分:0)
第3版
TL; DR:
AnsiString似乎是一个通过值传递给该函数的对象。 char * str在堆栈中。
ansi_in.cstr(),将指向其字符串缓冲区的指针复制到堆栈上的未命名局部变量中。
str并将临时指针推入堆栈并调用strcpy。这具有将从临时指向的字符串(包括空终止符)复制到str的效果。
答案很长:
你似乎在努力理解堆栈与堆,指针与非指针。我会为你打破它们,然后回答你的问题。
堆栈是一个概念,其中在每个线程启动之前和任何用户代码运行之前为每个线程分配固定的内存区域。 忽略较低级别的详细信息(如调用约定和编译器优化),可以推断在调用函数时会发生以下情况:
这不仅限于函数调用。当您在函数体中声明对象和基元时,通过推送保留它们的空间。当他们超出范围时,他们会通过调用析构函数并弹出来自动清理。
当你的程序用完堆栈空间并开始使用它之外的空间时,你通常会遇到错误。无论实际错误是什么,它都被称为堆栈溢出,因为您已经过了它,因此"溢出"。
堆是一个不同的概念,系统的剩余未使用内存可供您手动分配和取消分配。这主要用于当您拥有一个对于堆栈来说太大的数据集,或者您需要数据在任意函数中保持不变时。
C ++是一个难以掌握的野兽,但是如果你能够掌握核心概念,那就更容易理解了。
假设我们想要为人类建模:
struct Human
{
const char* Name;
int Age;
};
int main(int argc, char** argv)
{
Human human;
human.Name = "Edward";
human.Age = 30;
return 0;
}
这至少在堆栈上分配sizeof(Human)字节,用于存储' human'宾语。就在main()返回之前,“人类”的空间就是这样。被释放了。
现在,假设我们想要一组10个人:
int main(int argc, char** argv)
{
Human humans[10];
humans[0].Name = "Edward";
humans[0].Age = 30;
// ...
return 0;
}
这至少在堆栈上分配(sizeof(Human)* 10)字节,用于存储' human'阵列。这也会自动清理。
注意使用"。"。当使用任何不是指针的东西时,您可以使用句点访问其内容。如果您不使用参考,这是直接内存访问。
这是使用堆的单个对象版本:
int main(int argc, char** argv)
{
Human* human = new Human();
human->Name = "Edward";
human->Age = 30;
delete human;
return 0;
}
这将在堆栈上为指针“人类”分配sizeof(Human *)字节,并在堆上分配至少sizeof(人类)字节,用于存储它指向的对象。 '人类' 不自动清理,您必须调用删除才能将其释放。注意使用" a-> b"。使用指针时,您可以使用" - >"来访问其内容。运营商。这是间接内存访问,因为您通过变量地址访问内存。
有点像邮件。当有人想给你发邮件时,他们会在信封上写一个地址并通过邮件系统提交。邮递员接收邮件并将其移至您的邮箱。为了比较,指针是写在信封上的地址,内存管理单元(mmu)是邮件系统,通过网络传递的电信号是邮件员,地址所指的内存位置是邮箱。
这是使用堆的数组版本:
int main(int argc, char** argv)
{
Human* humans = new Human[10];
humans[0].Name = "Edward";
humans[0].Age = 30;
// ...
delete[] humans;
return 0;
}
这会在堆栈上为指针' human'和(sizeof(Human)* 10)字节分配sizeof(Human *)字节,用于存储它指向的数组。 '人'也不自动清理;你必须调用delete []来释放它。
注意使用" a [i] .b"而不是" a [i] - > b"。 " []" operator(indexer)实际上只是" *(a + i)"的语法糖,这实际上只是意味着将它视为序列中的正常变量,因此我可以输入更少。
在上述两个堆示例中,如果您没有写入delete / delete [],则指针指向的内存将泄漏(也称为dangle)。这很糟糕,因为如果不加以控制,它可以吞噬所有可用内存,最终在没有足够的时候崩溃,或者操作系统决定其他应用程序比你的更重要。
使用堆栈通常是更明智的选择,因为您通过范围(aka RAII)和更好的数据位置获得自动生命周期管理。唯一的缺点是#34;这种方法是因为作用域生命周期,一旦范围退出,您就无法直接访问堆栈变量。换句话说,您只能在他们声明的范围内使用堆栈变量。尽管如此,C ++允许您复制指针和堆栈变量的引用,并在它们声明的范围之外间接使用它们。请注意,这几乎总是非常糟糕的主意,除非你真的知道自己在做什么,否则不要这样做,我不能强调这一点。
通过-ref传递参数意味着将指针或引用的副本推送到堆栈上的数据。就计算机而言,指针和引用是一回事。这是一个非常轻量级的概念,但您通常需要在接收指针的函数中检查null。
整数加法函数的指针变体:
int add(const int* firstIntPtr, const int* secondIntPtr)
{
if (firstIntPtr == nullptr) {
throw std::invalid_argument("firstIntPtr cannot be null.");
}
if (secondIntPtr == nullptr) {
throw std::invalid_argument("secondIntPtr cannot be null.");
}
return *firstIntPtr + *secondIntPtr;
}
请注意空检查。如果它没有验证其参数是否有效,那么它们很可能为空或指向应用程序无法访问的内存。尝试通过解除引用(* firstIntPtr / * secondIntPtr)读取此类值是未定义的行为,如果您的幸运导致分段错误(也就是Windows上的访问冲突),则会导致程序崩溃。当这种情况发生并且您的程序没有崩溃时,您的代码存在更深层次的问题,超出了本答案的范围。
整数加法函数的引用变体:
int add(const int& firstInt, const int& secondInt)
{
return firstInt + secondInt;
}
请注意缺少空检查。根据设计,C ++限制了如何获取引用,因此您不能假设能够传递空引用,因此不需要进行空检查。也就是说,通过将指针转换为引用仍然可以获得空引用,但是如果您在转换之前没有检查null,那么您的代码中就会出现错误。
通过-val传递参数意味着在堆栈上推送它的副本。您几乎总是希望按值传递小数据结构。传递值时,您不必检查null,因为您传递的是实际数据,而不是指向它的指针。
即
int add(int firstInt, int secondInt)
{
return firstInt + secondInt;
}
不需要空检查,因为使用了值而不是指针。值不能为空。
假设您有兴趣了解所有这些,我强烈建议您使用std::string(另请参阅this)了解所有字符串需求和std::unique_ptr(另请参阅{ {3}})用于管理指针。
即
std::string char_out(AnsiString ansi_in)
{
return std::string(ansi_in.c_str());
}
std::unique_ptr<char[]> char_out(AnsiString ansi_in)
{
std::unique_ptr<char[]> str(new char[ansi_in.Length() + 1]);
strcpy(str.get(), ansi_in.c_str());
return str; // std::move(str) if you're using an older C++11 compiler.
}