这个怎么样:让我们说出关于该演示代码的所有不想知道的内容。就演示代码而言,这非常具有挑衅性。它直接突破了作者的障碍,强大到足以释放出一连串的话语,这些话语会让大坝坍塌的感觉与国际空间站的轨道工作人员观察时一样可怕。几百英里的真空当然可以提供一个视角。
但我们不应该对此刻薄。好吧,我不能。有人写了那个演示代码,如果是我(谁知道我写了什么我不记得的演示代码)——这对自己来说是刻薄的。所以我们要放轻松。
range 是一个指针数组。但无论如何,你都不应该使用它,很有可能。
您的可变长度数组有一个名称:std::vector。就用那个。效果很好。
但回到指针等:指针数组就像一个非常精简的地址簿。每个指针都是某人或某物的地址。当你只有一个通讯录时,你的朋友就没有了。你只有他们的地址。您也可能碰巧拥有这些朋友的家,然后地址簿会列出您拥有的东西。但这需要购买土地。或者,在编程方面:内存分配和所有权。
因此,为了获得地址,首先需要有人购买土地。必须分配一些内存,然后您可以将地址放在通讯录中。第一个在零页(索引零),第二个在第一页,依此类推。
至于代码试图完成什么:也许有些愚蠢?
float range_[] = {0, 100};
第一行很明显:这是一块土地。两亩。在其中一英亩的土地上有一个零的大雕塑 - 离你更近的那个。一个 100 的雕塑在另一英亩上。它并没有全部写出来——编译器“填补了空白”。第一行真的是这个意思:
float range_[2] = {0, 100};
现在到第二行:
const float* range[] = {range_};
你可能知道它现在要去哪里了:让我们来填空吧:
const float* range[1] = {range_};
第二行是地址簿。它也需要一些空间:毕竟,即使是地址簿也不适合零英亩。什么都不会。所以你需要一点空间来存储地址簿,但它可以比它保存地址的东西小得多。即使英亩相对较小(floats 一点也不宽敞,就目前而言)。而地址簿上正好有一个地址,那个地址是两英亩的地块,上面有两个数字雕塑。但是等等:地址簿中的地址没有提到两英亩。它只是说这是一个几英亩的地址。它甚至可能是一个没有英亩的地址。该地址由nullptr 表示。
通讯录上还有一个注释,禁止你乱动那块土地。在 C 语言中,它是一个指向常量值的指针。或者一堆常量值。或者没有。看看它与指针的关系吗?真的很难知道你在做什么是好的。这是某人的地址,好吧——也许吧。它可能没有地址,或者是一个宿舍的地址,整个学校实地考察的学生都挤满了,或者可能是一个空电话亭的地址。你不相信我吗?这里:
double phoneBooth = std::nan("empty");
double *i_hope_my_friends_are_there = &phoneBooth;
这是真正的 C++。它编译。跟我说的差不多。如果您需要表示一个空的电话亭,而您得到的只是一个双倍,那么它会在紧要关头完成。你可能想把它放在一些演示代码中,以增加效果。所以,现在你明白为什么指针会很糟糕了。并且双打。双打当然很糟糕,但花车更糟糕。哦-除非您是大型机程序员。那么这应该是完全熟悉的:
float pbth = nan("MPT");
也是真正的 C++ 代码,只是有人在更早的地方放了 using namespace std。这几乎足够罗嗦,任何了解 COBOL 的人都可以接受。 pbth 的定义与 COBOL 背景形成鲜明对比。它散发着 System 360 组装的味道。
但是我们分心了。返回range_!没有人说拥有range_ 财产的人会用它做什么。他们可能会炸毁整个地方。您所知道的是,您 被禁止更改该属性上的任何内容 - 如果您得到的只是该地址簿。所以 - 也许你确实知道你也真的只是拥有那块土地,然后你可能成为炸毁它的人。但很难判断你是否只有那本通讯录。它只是说:
- 你可以来看看雕塑,哦——顺便说一下——展览在一天中随机变化,但前提是你要继续购买西红柿。 (我保证很快就会增加 1% 的意义)
现在等一下:为什么给定一个地址簿,你会想到两个维度,只是因为你得到地址的东西本身就有一个维度?你根本不需要那样想。至少,任何人都不会自然而然地出现这种情况(数学家除外)。如果您对实际上具有二维(空间、矢量空间或类似的东西)的问题进行建模,您只会想这样想。否则,就没有什么“2D”了。您几乎不会认为矩阵是“二维”对象。
如果您有一个实际的地址簿,其中写有您朋友的地址,并且到处说您的地址簿是 4D 的,因为它是大概 3D 空间中的“一维”邮政地址列表,人们可能会看起来很有趣整件事。这不是一个非常实用的观点。例外:只有当它可以帮助您处理问题时,它才是实用的。但不应该认为它对你有帮助,仅仅因为其他一些人似乎得到了帮助。最后,希望本段告诫中的讽刺意味得以揭露。
实际上,如果您有一个具有离散面积单位(“正方形”)的矩形区域,您会认为它具有两个维度,但通常最紧凑的存储方式并不是将其分割成更小的碎片,把它们扔在风中,看看它们落在附近的哪个地方,并收集它们休息的地址。这就是——不幸的是——学生,通常是他们倒霉的教授——期望使用一组指针。相反,只需获得一个足够大的长块,并保留它的地址。或者更好的是,把它围起来,种一些西红柿,然后按照你想要的顺序收集它们,让方法处理细节。
现在您的需求可能比这更高级,但只要矩形区域“小”,比如浮点数等不到一百个项目,将它们全部放在一个块中并四处移动元素可能是最快的方法来处理它。我的意思是:如果您关心实际性能,而不是某些数学家对计算机影子在完成此类任务时应该如何表现的想法。很少有广泛使用的实用计算机能够达到这样的理念。
就复杂性理论而言,基于 AVR 的 Arduino 有一些定义明确的阴影,您可能会发现链表的行为有点像您在算法课程中所学的。但是在你的 PC 上运行相同的程序,稍微调整一下,你就会遇到一些极端情况,在这种情况下它的性能可能比在 Arduino 上更差。我没有骗你——这肯定是一个病态的案例,但它并不难编造。词汇量比我高得多的人称其为“拒绝服务”,但我不确定向人们扔西红柿在多大程度上是不让他们吃饭,而不是说给他们吃西红柿。
然后你重写所述程序以使其在 Arduino 上慢 10 倍,但在 PC 上却快数千倍。我绝对不是在开玩笑。
从你的问题回到那个破烂的通讯录:它不是很有用,因为它只有一页,可以大声喊叫。而且没有办法改变这一点。单页,精装,就是你所拥有的。需要存储更多地址?去再买一个。您也可以只存储一个地址,而不需要完整的地址簿。一张纸 - 无需装订工作。它可以是单个地址,而不是具有单个元素的数组。像这样:
const float *range = range_;
当然,您正在编写 C++。而这一切都是愚蠢的。如果您只需要一块可以轻松调整大小的土地,请使用矢量:
struct MyClass {
std::vector<float> range;
};
它在一个结构中,而不是在一个类中,只是因为它向人类传达了一些信息——对于编译器来说,这两者实际上是一回事。它告诉人类的是:这是财产。没有疏忽。你明白了,敞开心扉,做你想做的事。有一台挖掘机,想要挖掘这个地方吗?继续前进。除非它是一个 const 结构——否则它是被禁止的。当然。但不总是。如果对象本身是 const,则禁止。但是,如果对象不是 const,但你有一个指向它的指针,标记为“指向 const 的指针”,那么你就有了一个后门。 const_cast,开动挖掘机,走。没关系。这不是UB。
通常我们保留关键字class 用于以某种方式管理属性的用途。土地将在栅栏后面,一群站在栅栏上的人会卖给你种植在土地上的西红柿。如果您当面称呼他们方法,他们可能会被冒犯,但在软件工程人员中,这样的谈话是司空见惯的。贬低软件工程的东西。
但是假设你确实想要一个向量的向量:
using A_vector_of_vectors = std::vector<std::vector<float>>;
您拥有城市街区,每个街区都有许多属性。与城市街区一样,这只有在街区有些分离而不是相互交织并且有些独立时才有意义:它们真正以自己的速度增长(或不增长),并且它们的大小需求各不相同。如果您想要总是一起访问的街区,并且它们的大小有一些规则的模式(全部相等,或者以算术或几何级数 - 类似的东西),你可能只想要一个大城市街区 - 只划定细分在某种地图上,而不是通过道路工程和绕道等来划分。
您可以拥有零个或多个城市街区:这是外部向量。每个城市街区可以有任意大小 - 甚至是零大小。当它的大小为零(为空)时,您所得到的只是它的“名称” - 可以用来称呼它,以便您稍后可以说“嘿,我们希望 Nomish Quarters 的大小适合四个 nomes,拜托! ”。这将使一个特定的内部向量增长到 4。
我们可以用简单的符号来表示这些向量和子向量的特定内容。假设有三个向量,第一个没有元素,第二个有一个元素,第三个有两个元素。你可以这样写:{{}, {50}, {80, 90}}。很容易。现在我们可以使用它来预填充向量的向量:
struct MyClass {
A_vector_of_vectors vov{{}, {50}, {80, 90}};
};
这是制作MyClass 的秘诀:目前这只是一个柏拉图式的想法。有点像纯素炸肉排的食谱,上面装饰着完美的图片。当你第一次制作它们时,它们就适合作为鞋底。不过,随后它们往往很棒。是的,这个类不存在,只是一个影子。如果你真的想吃一个,你必须创建它的一个实例:
MyClass anInstance;
现在我们有了一个小城市,由三个街区组成,第一个 0 英亩,第二个 1 英亩,第三个 2 英亩。而且他们很随和。您可以调整每一个的大小。您可以重新排列它们的顺序,或者重新排列其中的英亩数。您可以添加新的。删除旧的。最好的事情是什么?你让别人做所有的工作。 std::vector 和编译器负责处理细节。当您不再需要anInstance 时,您只需核对它即可。你结束了范围,城市也就结束了。就这样。
int main() {
MyClass anInstance;
} // bye bye, city
看,不是malloc,不是free。番茄人为我们做了这一切!