我猜你错过了 C++ 和 C++ 语义的重点。您错过了C++ 通过值传递(几乎)所有内容是正确的事实,因为它是在 C 中完成的方式。 Always。但不仅在 C 中,我将在下面向您展示...
C上的参数语义
在 C 中,一切都是按值传递的。“primitives”和“PODs”通过复制它们的值来传递。在你的函数中修改它们,原来的不会被修改。尽管如此,复制一些 POD 的成本可能并不小。
当您使用指针表示法(*)时,您不会通过引用传递。您正在传递地址的副本。这或多或少是相同的,只有一个细微的区别:
typedef struct { int value ; } P ;
/* p is a pointer to P */
void doSomethingElse(P * p)
{
p->value = 32 ;
p = malloc(sizeof(P)) ; /* Don't bother with the leak */
p->value = 45 ;
}
void doSomething()
{
P * p = malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
/* Value of p ? 25 ? 32 ? 42 ? */
}
p->value 的最终值是 32。因为 p 是通过复制地址的值来传递的。所以原来的 p 没有被修改(并且新的 p 被泄露了)。
Java 和 C Sharp 上的参数语义
有些人可能会感到惊讶,但在 Java 中,所有内容也是按值复制的。上面的 C 示例将在 Java 中给出完全相同的结果。这几乎是您想要的,但您无法像在 C 中那样轻松地“通过引用/指针”传递原始数据。
在 C# 中,他们添加了“ref”关键字。它的工作方式或多或少类似于 C++ 中的参考。关键是,在 C# 中,您必须在函数声明和每次调用中都提及它。我想这又不是你想要的。
C++ 上的参数语义
在 C++ 中,几乎所有内容都是通过复制值来传递的。当您只使用符号的类型时,您正在复制符号(就像在 C 中所做的那样)。这就是为什么当您使用 * 时,您传递的是符号地址的副本。
但是,当您使用 & 时,假设您传递的是真实对象(无论是结构、int、指针等):引用。
很容易将其误认为是语法糖(即,在幕后,它像指针一样工作,生成的代码与用于指针的代码相同)。但...
事实是,引用不仅仅是语法糖。
- 与指针不同,它授权像在堆栈上一样操作对象。
- Unline 指针,当与 const 关键字相关联时,它授权从一种类型到另一种类型的隐式提升(主要通过构造函数)。
- 与指针不同,符号不应为 NULL/无效。
- 与“按副本”不同,您不会花费无用的时间来复制对象
- 与“by-copy”不同,您可以将其用作 [out] 参数
- 与“by-copy”不同,您可以在 C++ 中使用全方位的 OOP(即,您将完整的对象传递给等待接口的函数)。
因此,引用具有两全其美的优点。
让我们看看 C 示例,但在 doSomethingElse 函数上有一个 C++ 变体:
struct P { int value ; } ;
// p is a reference to a pointer to P
void doSomethingElse(P * & p)
{
p->value = 32 ;
p = (P *) malloc(sizeof(P)) ; // Don't bother with the leak
p->value = 45 ;
}
void doSomething()
{
P * p = (P *) malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
// Value of p ? 25 ? 32 ? 42 ?
}
结果是 42,旧的 p 被泄露,被新的 p 代替。因为,与 C 代码不同,我们传递的不是指针的副本,而是指针的引用,即指针本身。
使用 C++ 时,上面的示例必须非常清楚。如果不是,那么你错过了一些东西。
结论
C++ 是按复制/值传递的,因为它是一切工作的方式,无论是在 C、C# 还是在 Java 中(甚至在 JavaScript 中...... :-p ...)。和 C# 一样,C++ 有一个引用运算符/关键字,作为奖励。
现在,据我了解,您可能正在做我所说的半开玩笑的C+,即具有一些有限 C++ 功能的 C。
也许您的解决方案是使用 typedefs(不过,它会激怒您的 C++ 同事,看到代码被无用的 typedefs 污染......),但这样做只会混淆您确实在那里缺少 C++ 的事实。
正如在另一篇文章中所说,您应该将您的思维方式从 C 开发(无论什么)转变为 C++ 开发,或者您可能应该转向另一种语言。但是不要继续使用 C++ 特性以 C 方式编程,因为通过有意识地忽略/混淆您使用的习语的力量,您将生成次优代码。
注意:并且不要通过复制除原语之外的任何其他内容。你会从它的面向对象能力中阉割你的函数,而在 C++ 中,这不是你想要的。
编辑
问题有所修改(请参阅https://stackoverflow.com/revisions/146271/list)。我让我原来的答案,并回答下面的新问题。
您如何看待 C++ 上的默认传递引用语义?就像您说的那样,它会破坏兼容性,并且您将对原语(即内置类型,仍将通过副本传递)和结构/对象(将作为引用传递)进行不同的传递。您必须添加另一个运算符来表示“按值传递”(外部“C”非常糟糕,并且已经用于其他完全不同的东西)。不,我真的很喜欢今天用 C++ 完成的方式。
[...] 在我看来,引用运算符是一种获取变量地址的方法,这就是我用来获取指针的方法。我的意思是,它是同一个运算符,但在不同的上下文中具有不同的语义,这对您来说是不是也有点不对劲?是和不是。运算符 >> 在与 C++ 流一起使用时也改变了它的语义。然后,您可以使用运算符 += 替换 strcat。我猜运算符 & 之所以被使用是因为它的含义是“与指针相反”,并且因为他们不想使用另一个符号(ASCII 是有限的,范围运算符 :: 以及指针 -> 表明很少有其他符号可用)。但是现在,如果 & 困扰你,&& 真的会让你感到不安,因为他们在 C++0x 中添加了一个一元 && (一种超级引用......)。我自己还没消化...