C++ 指针和 C 风格的数组初始化

Posted

技术标签:

【中文标题】C++ 指针和 C 风格的数组初始化【英文标题】:C++ Pointers and C-style array initialization 【发布时间】:2021-02-16 15:26:13 【问题描述】:

这里是 Java 程序员,C++ 新手。我一直在使用 C 风格的“传统”数组(类似于 java 中的数组)。我理解在 C++ 中我们可以创建一个简单的数组,如下所示:

Person people[3];

这个数组的内容本质上是未初始化的垃圾值。当我打印出数组中每个元素的内容时,我(相信)正在获取每个元素的内存地址。

for(int i = 0; i < 3; i++)std::cout << &person[i] << std::endl;

结果:

//Note, I get different results here when I use an enhanced for loop vs an iterator for loop. Weird.
00EFFB6C
00EFFBA8
00EFFBE4

现在,这是我未能得到明确解释的部分。我创建了一个指向数组中元素之一的指针。然后我从那个指针中请求一些值 back 。在 java 中,我希望得到一个空指针,但在 C++ 中,这不会发生。

相反,我得到了默认值,就好像这个元素被初始化了:

Person* person1Ptr = &people[0];//Points to an uninitialized value
std::cout << person1Ptr->getFirstName() << std::endl;//Output: "Default First Name", expected nullptr

当我尝试使用引用获取元素的名字时,这不起作用(可能是因为该值不存在)。

完整粘贴代码:https://pastebin.com/cEadfJhr

根据我的研究,C++ 不会自动用指定类型的对象填充数组。

我的 person1Ptr 如何返回值?

【问题讨论】:

打错了,抱歉 - 现在修复 Person people[3]; 中,3 个Person 元素的初始状态取决于Person 是什么。如果它是 class 类型,它们将被默认初始化。这是否意味着存在未初始化的数据取决于Person 的实现方式。 “根据我的研究,C++ 不会自动用指定类型的对象填充数组。” 这是不正确的。由于您来自 Java 背景,您可能会先入为主地认为PersonPerson 实例的某种形式的句柄。在Person people[3]; 的情况下,无论如何,您都有三个Person 的实际实例。您可能会想到 C++ 可能不会将这些对象初始化为有意义的值。这是否发生取决于Person的详细信息。 您应该共享一个minimal reproducible example,包括Person 的简短定义,它会产生您目睹的行为。如果不了解更多关于您的测试的信息,很难具体回答问题。 Person 是一个带有默认构造函数的class 类型,因此所有三个元素都是默认构造的。 【参考方案1】:

我认为问题源于这种误解:

根据我的研究,C++ 不会自动用指定类型的对象填充数组。

C++ 对象具有值语义。定义T 类型的局部变量具体创建该类型的唯一实例,它不是潜在T 的句柄。表达式Foo f; 在概念上等同于Java 表达式Foo f = new Foo();。此外,值语义意味着赋值通常意味着副本。 C++ 表达式 Foo f; Foo g = f; 在概念上等同于 Java 表达式 Foo f = new Foo(); Foo g = f.Clone();

在数组的情况下,定义一个局部变量Foo f[3]; 会立即创建三个Foo 实例作为f 数组的元素。您的误解可能来自这样一个事实,即在 C++ 中创建对象并不意味着它已被初始化。对象可以以未初始化状态存在。例如int i; 创建一个由i 标识的int 对象,但其值是不确定的。在int i[3]; 的情况下,您将拥有一个包含三个int 的数组,每个int 具有不确定的值。

rules for initialization 在 C++ 中非常复杂。在Person people[3]; 的情况下,您有一个数组是default initialized。

您正在初始化Person[3] 类型的对象。根据默认初始化规则:

如果 T 是数组类型,则数组的每个元素都是默认初始化的;

这意味着每个Person 都有自己的默认初始化。要查看会发生什么,请考虑 TPerson

如果 T 是类类型,则考虑构造函数并对其空参数列表进行重载决策。调用选择的构造函数(默认构造函数之一)为新对象提供初始值;

所以每个Person 的默认构造函数都会被调用来初始化那个元素。您最终会得到 Person people[3]; 定义三个具有默认初始值的默认构造 Person 对象。

【讨论】:

在第一部分,你的意思是 Foo f = new Foo(); Foo g = f.clone();在java中,对吗?现在,您将其列为 Foo f = new Foo(); Foo f = f.clone();感谢您对 C++ 数组对象初始化的说明。然而,C++ 会为数组中的每个元素生成实例,这对我来说确实很奇怪。数组中每个元素的对象实例化可能会导致很大的性能开销,不是吗? @Matthew 是的,这是一个错字。这是Foo g = f.Clone()的意思,我在答案中更正了。 @Matthew 在 C++ 中,对象数组是对象的集合。如果你写 Foo f[3]; 你告诉编译器“我现在想要 3 Foo”。如果这是一个性能问题,那么您可能不想问这个问题,也不应该使用Foo[3]。如果您想要 3 个 handles 来处理潜在的未来实例,则可以使用其他东西。可能是Foo* f[3];std::optional&lt;Foo&gt; f[3]std::shared_ptr&lt;Foo&gt; f[3]std::unique_ptr&lt;Foo&gt; f[3] 或无数其他可能性中的任何一种,包括std::vector&lt;Foo&gt; f;,在大多数情况下会比Foo f[3] 更可取。 @Matthew 每种方法都适用于不同的用例。最佳方法取决于您实际想要完成的任务以及您想要完成它的环境。

以上是关于C++ 指针和 C 风格的数组初始化的主要内容,如果未能解决你的问题,请参考以下文章

动态数组C风格字符串字符串字面值

在C ++中初始化一个指向结构的指针数组

C#如何调用C++的DLL的结构体数组指针

C++ --- 字符串与字符数组

第四章 复合类型

c++初始化vector数组?