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 背景,您可能会先入为主地认为Person
是Person
实例的某种形式的句柄。在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
都有自己的默认初始化。要查看会发生什么,请考虑 T
是 Person
:
如果 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<Foo> f[3]
、std::shared_ptr<Foo> f[3]
、std::unique_ptr<Foo> f[3]
或无数其他可能性中的任何一种,包括std::vector<Foo> f;
,在大多数情况下会比Foo f[3]
更可取。
@Matthew 每种方法都适用于不同的用例。最佳方法取决于您实际想要完成的任务以及您想要完成它的环境。以上是关于C++ 指针和 C 风格的数组初始化的主要内容,如果未能解决你的问题,请参考以下文章