我知道'mutable'和'immutable'是应该用来描述对象在面向对象语言(如Java和Objective C)中改变值的能力的术语。但是,我想提出它,因为它与我关于原语数据的问题有关。我知道当我更改包含不可变对象的变量的值时,我实际上是在创建一个新对象。但是,我想知道C中的原语数据是否与不可变对象类似。我的意思是,当我更改保存原始数据的变量的值时,会创建一个新数据并由变量引用。或者现有的基元实际上是否突变/修改了存储的数据值?
编辑#1:
问题#1:我想澄清一些误解(无论是我还是其他人),因为当我说“当我改变变量的值时,我没有说清楚”拿着不可变对象,我实际上正在创建一个新对象。“当我这样说时,我并不是要将变量赋给现有对象。例如:
// Example 1: I did not mean this
-------------------------
String x = "Hello World";
String y = x;
-------------------------
// Example 2: What I meant is this
-------------------------
String x = "Hello World";
//This will print out "Hello World"
System.out.println(x);
x = "Goodbye World";
//This will print out "Goodbye World"
System.out.println(x);
-------------------------
当然,如示例1所示,将变量y赋值给变量x,这是你们提出的情况,只引用变量y到x引用的对象。当然,在这种情况下,没有新的对象;只有同一个对象“Hello World”被2个变量引用。
我的意思是当示例2中的x =“Goodbye World”时,变量x引用一个新的String对象,其值为“Goodbye World”,而是第一个String对象“Hello World”,x被初始化用。 String是Java中的Immutable对象。更改String变量值的唯一方法是让变量引用现有对象 OR 一个新的String对象。如果没有现有对象(“Goodbye World”字符串对象未在任何地方创建),则上面的代码只是创建了一个新的String对象并引用了它。我对吗?如果没有,请纠正我。
问题#2:我想总结答案,特别是来自 Ulfalizer 的答案:
1)实际上有两种形式可以存在变量:
a)“内存地址” - 对于基本类型数据,C语言中的变量以及Java和Objective C就是这种情况。例如:int x = 1.这里的变量x是一个实际的内存地址本身,它的类型为整数,并用值1初始化。
b)“引用” - 对于非原始类型数据(对象),Java中的大多数变量都是这种情况。例如:String x =“Hello World”。变量x只是指向“某处存在的存储器地址”的指针/引用,它将值“Hello World”保存为其内容。
2)C,Java和Objective C中原始类型数据的变量表现为“内存地址”。因此,当我这样做时:
-------------------------
int x = 10;
x = 2;
-------------------------
x变量(也称为 - 存储器地址)的值实际上从10变为2.换句话说,可以修改/更改存储在“存储器地址”变量中的值。
3)在C语言中,如果变量用'*' - 指针类型声明,它也可以作为引用。我将使用 Ulfalizer 的示例:int * ptr。 ptr是一个指针变量,可以指向另一个变量(也就是内存地址),例如:ptr =& x。如果x被初始化为:int x = 10,那么x是一个保存值为10的实际内存地址。所以在下面的代码中
-------------------------
int x;
int *ptr;
ptr = &x;
*ptr = 1;
-------------------------
我实际上可以通过ptr指针变量修改存储在x变量(也就是内存地址)中的值。
请确认我的解释是否正确/错误。谢谢。
答案 0 :(得分:6)
了解C如何工作的最佳方法是将其视为高级汇编语言。变量只是内存中的位置,为变量赋值会将值存储到该位置。从高级语言的角度来看,这将是最纯粹的变异。
在C中,声明/定义如
int x;
告诉编译器为int
变量x
保留一些内存。 (在函数内部,内存将来自堆栈。在全局范围内,它将来自数据段。)
像
这样的作业x = 7;
只需生成代码即可将值7复制到该内存位置。同样,像
这样的作业x = y;
生成代码,将存储在int
内存位置的y
值复制到x
的内存位置。 (假设y
是另一个int
。)
同样的逻辑也适用于比int
更复杂的类型。
在Java中,变量是引用(无论如何都是非基本类型),并且可以在不同时间引用不同的对象。要获得与C中的引用类似的东西,您必须显式定义指针变量 - 一个包含地址的变量 - 并在不同时间将其指向(为其分配地址)不同的对象
(如果您有兴趣,我会在下面的示例中添加。)
&
运算符用于获取C中变量的地址,因此&x
将是x
的地址。 *
运算符在应用于指针时给出指向对象。这是一个如何使用指针在不同时间引用不同变量的示例:
int x;
int y;
/* Declares 'ptr' as a pointer, and says that it points to an int.
The pointed-to type is used by the compiler for type checking
and type conversions. */
int *ptr;
ptr = &x; // Store the address of 'x' in 'ptr'.
*ptr = 1; // Store 1 into the memory 'ptr' points to ('x').
ptr = &y; // Store the address of 'y' in 'ptr'.
*ptr = 2; // Store 2 into the memory 'ptr' points to ('y').
上述代码的最终结果是将x
设置为1,将y
设置为2.这当然是一个愚蠢的例子,但希望它应该能够理解这一点。
(C99及以后支持//
- 顺便说一下样式评论。)
(警告:我的Java有点生疏,所以我不得不做一些阅读。希望细节应该是正确的。请随意纠正我。)
作业
x = "Goodbye World";
会使x
引用值为“Goodbye world”的String
对象。 当创建此String
对象时,只要在将其分配给x
(或任何其他变量)之前创建它就不会有所作为。
可能在执行赋值之前或程序启动时创建它。通常情况下,你无法区分它们。
听起来你有高级概念,你的C代码是正确的。
(更确切地说“ptr
是一个指针”,而不是说“*ptr
是一个指针”.C中有一些语法模糊,这使得放置它更自然*
接下来是声明(IMO)中的名称,但是你也可以为这种情况编写类似int* ptr;
的声明。当你在同一行中声明许多变量时,事情才开始变得奇怪。)< / p>
在谈论如何实现Java时,引用可能必须比仅仅指向引擎下的对象更高级。例如,JVM可能会在内存中移动对象(这将更改其地址)以减少内存碎片,但引用仍必须保持有效。一种方法是在移动对象时“修复”指针,另一种方法是将引用转换为指针表的索引。无论在JVM实现中如何完成,指针至少是正确的思路。
由于Java中的每个引用都有一个指针字段(在高级别,忽略了实现的确切方式),并且由于原始类型不需要该字段,因此一种可能性是重用指针字段并存储它的值代替原始类型。
例如,像
这样的作业x = 1;
可能会将值1直接存储到引用x
的指针字段中。像这样的技术很常见。
作为旁注,可以在C中使用union
将两种不同类型(例如,int
和指针)存储在内存中的相同位置,以使它们重叠。当然,分配其中一种类型也会改变另一种类型的值。
答案 1 :(得分:1)
应修改当前原语。我使用简单的代码here进行了测试,它引用了相同的地址。
#include <stdio.h>
int main(void) {
// your code goes here
int a = 5;
printf ("%p = %i\n", (void*) &a, a);
a = 10;
printf ("%p = %i\n", (void*) &a, a);
return 0;
}