前言
与炒作相反,Java与C ++完全不同。Java炒作机希望您相信,因为Java具有类似于C ++的语法,所以这些语言是相似的。没有什么比事实更遥远了。这种错误信息是Java程序员使用C ++并使用类Java语法而不了解其代码含义的部分原因。
往前走
但是我不知道为什么我们要这样做。我认为这与效率和速度有关,因为我们可以直接访问内存地址。我对吗?
相反,实际上。堆比堆栈慢得多,因为与堆相比,堆非常简单。一旦超出范围,自动存储变量(即堆栈变量)就会调用其析构函数。例如:
{
std::string s;
}
// s is destroyed here
另一方面,如果使用动态分配的指针,则必须手动调用其析构函数。delete
为您调用此析构函数。
{
std::string* s = new std::string;
}
delete s; // destructor called
这与new
C#和Java中普遍存在的语法无关。它们用于完全不同的目的。
动态分配的好处
1.您不必事先知道数组的大小
许多C ++程序员遇到的第一个问题是,当他们接受用户的任意输入时,您只能为堆栈变量分配固定大小。您也不能更改数组的大小。例如:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
当然,如果您使用的是std::string
,则在std::string
内部调整自身大小,这样就不会有问题。但本质上解决此问题的方法是动态分配。您可以根据用户的输入分配动态内存,例如:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
旁注:许多初学者犯的一个错误是可变长度数组的使用。这是一个GNU扩展,也是Clang中的一个扩展,因为它们反映了许多GCC扩展。因此,int arr[n]
不应依赖以下内容
。
由于堆比堆栈大得多,因此可以随意分配/重新分配所需的内存,而堆栈有一个局限性。
2.数组不是指针
您要求的好处如何?一旦您了解了数组和指针背后的困惑/误解,答案就会变得很清楚。通常假定它们是相同的,但事实并非如此。这个神话来自这样一个事实,即指针可以像数组一样被下标,并且由于数组会在函数声明的顶层衰减到指针。但是,一旦数组衰减到指针,该指针就会丢失其sizeof
信息。因此sizeof(pointer)
将给出指针的大小(以字节为单位),在64位系统上通常为8个字节。
您不能分配给数组,只能对其进行初始化。例如:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
另一方面,您可以使用指针执行任何操作。不幸的是,由于指针和数组之间的区别是在Java和C#中动摇的,因此初学者不了解它们之间的区别。
3.多态性
Java和C#具有允许您将对象视为另一个对象的功能,例如使用as
关键字。因此,如果有人想将一个Entity
对象视为一个Player
对象,则可以做到Player player = Entity as Player;
这一点。如果您打算在仅适用于特定类型的同类容器上调用函数,这将非常有用。该功能可以通过以下类似方式实现:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
假设如果只有Triangles具有Rotate函数,则尝试在该类的所有对象上调用它都将是编译器错误。使用dynamic_cast
,您可以模拟as
关键字。需要明确的是,如果强制转换失败,它将返回一个无效的指针。所以!test
本质上是一种检查test
NULL或无效指针的简写形式,这意味着强制转换失败。
自动变量的好处
在看到了动态分配可以做的所有很棒的事情之后,您可能想知道为什么没有人不一直使用动态分配?我已经告诉过您一个原因,堆很慢。而且,如果您不需要所有的内存,也不要滥用它。因此,以下是一些不利因素,没有特别的顺序:
这很容易出错。手动分配内存很危险,容易泄漏。如果您不熟练使用调试器或valgrind
(内存泄漏工具),则可以将头发拔掉。幸运的是,RAII的习语和智能指针可以缓解这种情况,但是您必须熟悉“三法则”和“五法则”之类的实践。需要吸收很多信息,不知道或不在乎的初学者都会陷入这个陷阱。
没有必要。与Java和C#不同new
,在C ++中,随处可见使用关键字是惯用的,在C ++中,仅应在需要时使用它。俗语说,如果你有锤子,一切看起来都像钉子。以C ++开头的初学者很害怕指针,并习惯地学习使用堆栈变量,而Java和C#程序员则是在不了解指针的情况下开始使用指针!从字面上看,这是错误的一步。您必须放弃所有已知的知识,因为语法是一回事,学习语言是另一回事。
1.(N)RVO-Aka,(命名)返回值优化
许多编译器进行的一种优化是称为省略和返回值优化。这些东西可以消除不必要的副本,这对于非常大的对象(例如包含许多元素的向量)很有用。通常,通常的做法是使用指针来转移所有权,而不是复制大对象来移动它们。这导致了移动语义和智能指针的诞生。
如果使用指针,则不会发生(N)RVO 。如果您担心优化,则利用(N)RVO而不是返回或传递指针会更有利且更不会出错。如果函数的调用者负责delete
动态分配的对象等,则可能发生错误泄漏。如果像热土豆一样传递指针,则可能很难跟踪对象的所有权。只需使用堆栈变量,因为它更简单,更好。