同时拥有点和点运算符有什么意义
What is the point of having both points to and dot operator?
我知道点到(->)和点(.)运算符之间的区别,但我不明白为什么需要这两个运算符?不使用指针而只使用点运算符不是总是一样容易吗?从 http://www.programcreek.com/2011/01/an-example-of-c-dot-and-arrow-usage/
#include <iostream>
using namespace std;
class Car
{
public:
int number;
void Create()
{
cout << "Car created, number is: " << number << "n" ;
}
};
int main() {
Car x;
// declares x to be a Car object value,
// initialized using the default constructor
// this very different with Java syntax Car x = new Car();
x.number = 123;
x.Create();
Car *y; // declare y as a pointer which points to a Car object
y = &x; // assign x's address to the pointer y
(*y).Create(); // *y is object
y->Create();
y->number = 456; // this is equal to (*y).number = 456;
y->Create();
}
为什么要费心使用指针?只需像 X 一样创建 Y,它就会正常工作。如果你说你需要指针来动态定位内存,那么为什么要使用点运算符呢?
我认为您混合了两个单独的问题。
首先,->
运算符是不必要的,是的。 x->y
相当于(*x).y
,但->
运算符更容易输入,所以基本上只是为了方便。
第二部分是是否使用指针。你是对的,通常你不应该。默认情况下,只需在当时和那里创建您的对象,并直接引用它们:
Foo bar;
bar.baz():
但是对于很多情况,指针仍然是必要的。对象需要能够引用其他对象。引用可以执行此操作,但不能重新拔插。初始化后,它将始终指向同一对象。
指针可以更新为指向其他对象。
链表、树数据结构和无数其他东西都依赖于能够指向其他对象的对象。
所以是的,我们需要指针。但我们不需要->
运算符。我们只是使用它,因为它很方便。
它只是使语义上更容易理解代码,而无需查看类型或具有特殊的符号,如m_pszMyName
。您可以立即告诉阅读代码什么是指针,什么是值。
b. 考虑shared_ptr和覆盖运算符的情况。 shared_ptr<T>->get()
的意思不是shared_ptr<T>.get()
.第一个是指向对象中的函数,第二个是shared_ptr类本身的函数。这只是一个例子,但你明白了重点。
从您的链接:
以下示例应该是一个很好的示例。
这实际上有点令人困惑。为什么要在堆栈(Car x;
)上创建一个对象,然后创建一个指向它的指针来使用->
访问它?
而不是试图回答隐含的问题"为什么我们需要指针?我将尝试消除该示例可能引起的任何混淆。
在您的评论中,您说:
我想知道以不同方式创建的对象之间是否存在差异。
在示例中,只有一个对象,即Car x;
创建的堆栈上的Car
(为了完整起见,堆栈上还有一个由Car *y;
创建的Car
指针)。当main()
退出时,它们会超出范围,因此它们的内存会被清理干净。
但是还有另一种创建对象的方法,我想您已经根据您的评论知道了,这就是使用 new
在堆上初始化它们:Car *z = new Car;
.堆上的对象永远不会超出范围,因此您可以在调用new
函数退出后继续使用它们,但您必须使用 delete
显式清理它们以防止内存泄漏。
因此,指向对象的指针的更现实用法:new
的返回值。
不使用指针而只使用点不是总是那么容易吗? 算子?
与其他高阶语言一样,C/C++ 不会用一些糖衣语法封装指针。指针自然出现,下面的列表并不详尽
- 从堆中分配内存。静态数据分配或在堆栈中分配存储始终不可行。所有权转让、空间限制和程序的动态性质存在开销。
- 读取和写入文件。
- 遍历对象,包括 C 类型字符串。您可以使用数组访问语法,但几乎没有安全差异,并且当您传递给函数时,数组会退化为指针(大小信息丢失)。
以上所有内容都可以从C++角度封装到对象中。
- FILE IO 通过 iotream
- 通过智能指针的指针(一些来自 C++98,一些来自 C++11 或前夕加速)
- STL 类型对象的迭代器。
- 使用引用
尽管如此,指针即使在您没有明确看到它们的语言中也存在。它们只是封装到更高阶的对象中。
这在一定程度上解释了为什么我们不能超越指针思考,下一部分你可能感兴趣的是语法。为什么我们需要ptr->somemember
而不是(*ptr).somemember
.它只是重复用法的简写。C/C++程序员已经习惯了,到目前为止,我还没有看到一个程序使用多余的语法。
-> 只是简称。考虑一个表示树节点的类:
struct node {
int data;
node* left;
node* right;
};
成员left
是指向节点左侧子节点的指针点。假设我们有一个指向某个节点p
的指针,现在我们要得到指针指向p的左子节点的左子项的右子项,使用点我们要写(*(*(*p).left).left).right
,难以阅读且容易出错,使用->我们可以简单地写p->left->left->right
,非常清晰。
C++中 ->
和 .
运算符的存在是 C 的直接影响。 C 区分了通过指针访问对象和访问在当前作用域中声明的对象。
在C++引用是访问本地范围对象的自然扩展。
我不知道 C 的创建者是否考虑过这一点,但我总是把它作为一个小的优化指南。 查看一段代码,您可以看到->
将在运行时计算最终地址,而.
运算符将在编译时计算地址。 这甚至在访问结构成员时有效。 请考虑以下事项: myptr->mMember.mValue
从 mMember 到 mValue 的偏移量可以在编译时计算,而指针的最终地址计算必须在运行时计算。我承认,就优化而言,现在有一个很小的考虑因素,并且根据C++的参考资料,不再可能这样做,但 20 年前这是要记住的事情。
是的,您始终可以使用(*x).member
而不是x->member
,但是当x
是一个复杂的表达式时,您真的愿意吗?
将相关的东西(在这种情况下是*
和.
)放在很远的地方会使源代码的可读性降低,因此将->
"放在一个地方"只是一种更好的语法。
至于对指针概念的需求,主要有两个原因:
1. 对象生存期
有两种方法可以分配对象
- 在堆栈上。
- 在动态内存中。
随着执行流进入和退出函数而缠绕和展开,因此堆栈对象的生存期不可避免地与我们在创建它的函数中停留的时间有关。
如果你需要一个比创建它的函数生存期更长的对象,你需要在动态内存中创建它,而识别该对象的唯一方法是通过它的内存地址,也就是指针。
2. 对象共享
是否有多个其他对象需要访问该对象?如果是,则这些其他对象无法引用共享对象,只能保存其地址。
即使您只有一个其他对象,但它们的生存期不同,"生存期"原因也适用。如果只有一个其他对象并且它们的生存期匹配,则将其设置为字段。
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 如果我真的真的想从 STL 容器继承,并且我继承构造函数并删除新运算符,会发生什么?
- 这里的 = 运算符有什么用法?
- unique_ptr < 0 或小于运算符做什么?
- "operator()"在重载运算符方法中是什么意思,在priority_queue(STL)中用作C++中的比较器?
- 是什么让一些命名函数/运算符与众不同?
- C++,()运算符重载,它的工作是什么
- 提供运算符+或运算符到双向迭代器有什么缺点吗?
- std::set<Key,Compare,Allocator>::find() 函数使用"<"运算符而不是"=="运算符背后的直觉是什么?
- 有什么理由不扩展 std::set 以添加下标运算符吗?
- 在C++中,运算符 sizeof 返回什么数据类型?
- 运算符++();调用和++(*this)有什么区别?
- 第二个常量在运算符函数中做什么?
- 使输出流式处理运算符适用于 boost::variant<std::vector<int>、int、double 的正确方法是什么>
- 当值传递给C++中的运算符重载函数时会发生什么
- 除了调用全局删除运算符之外,删除一个void指针还能做什么呢
- 在复制构造函数中放入什么 = 运算符重载
- 什么C++运算符在这里被重载了
- 当类作为参数传递给printf()时,要重载什么运算符
- 如果我想使用什么运算符" a = {x, y};"