实参和形参
我们声明一个函数时,有函数头和函数体,函数头中需要有:访问修饰符、返回值类型、函数名称、形参列表。也就是说我们声明函数时,函数名后面括号里面的参数,是形式参数,即形参。
形参是在函数定义时使用的变量名,它们用于接收传递给函数的值。形参在函数调用时创建,在函数执行完成后销毁。形参的主要作用是在函数内部提供一个局部变量的名称,用于存储传递进来的值。
我们调用一个函数的时候,直接写对应的函数名(),如果函数需要参数,就传入参数,这时【调用函数时】传入的参数是实际参数,即实参。实参可以是常量、变量或者表达式,它们在调用函数时提供具体的值。
public static void main(String[] args) {
String greeting = "Hello, World!";
// 调用printMessage函数,greeting是实际参数
printMessage(greeting);
}
// message是形式参数
public void printMessage(String message) {
System.out.println(message);
}
函数调用的时候,根据实参和形参之间的关系、如何影响等,可以将参数的传递方式分为值传递、指针传递、引用传递【不同编程语言规定有不同传递方式】。
C/C++的参数传递方式
值传递
值传递(pass by value),即形参拷贝实参的值,形参、实参是两个不同的变量,仅仅只是值相等。
在被调用的函数中,操作形式参数,能够修改形参,对实参没有影响。调用结束,形参生命周期也结束了。
void modify(int a) {
// 这里的修改不会影响原始的变量
a = 10;
}
int main() {
int x = 5;
// 传递x的副本
modify(x);
// 调用结束,x的值仍然是5
return 0;
}
指针传递
c/c++是更偏底层的语言,提供了指针,可以直接操作内存空间。指针传递(pass by pointer),即将实参的地址传递给形参,后续操作形参,就是直接操作内存空间,进而也会影响实参。【因为它们对应的是同一个地址,同一块内存空间】。
void modify(int *a) {
if (a != nullptr) {
// 直接修改内存空间存储的值,这里的修改会影响原始的变量
*a = 10;
}
}
int main() {
int x = 5;
// 传递x的地址
modify(&x);
// 调用modify函数后,x的值被修改变成10
return 0;
}
引用传递
使用引用传递(Pass by Reference)时,传递的是变量的别名,而不是变量的副本。这意味着函数内部对参数的任何修改都会反映到原始的变量上。引用传递通常用于大型对象,以避免不必要的复制。
void modify(int &a) {
// 形参a是实参的别名,对应的同一块内存空间
// 这里的修改会影响原始的变量
a = 10;
}
int main() {
int x = 5;
// 传递x的引用
modify(x);
// x的值现在是10
return 0;
}
引用其实就是变量的别名,因此使用引用传递时,形参和实参是同一块内存空间的两个名字;和指针传递一样,形参的改变会反应到实参上。
Java中的值传递【只有一种!!】⭐
Java中参数传递只有一种方式——值传递。这里需要注意以下java中的引用和c/c++中的引用的含义。
java的引用和C++引用的区别
- C++中的引用:
C++中的引用是一种特殊的别名,一旦声明,就不能再引用其他对象。在函数参数传递中使用引用时,它实际上是通过地址传递的,但是语法上看起来像是直接使用变量本身。 - Java中的引用:
Java中的引用是指向对象的变量,它本身是一个变量,可以重新赋值指向另一个对象。Java中的所有对象都是通过引用来操作的,基本数据类型则不是。在方法调用中,传递的是引用的一个副本,这个副本和原始引用指向同一个对象。
java的值传递
在Java中,方法参数的传递方式总是按值传递,这意味着不管一个变量是基本数据类型还是引用数据类型,传递的都是变量的一个副本。
对于基本数据类型(如int、float等),传递的是实际值的一个副本。如果在方法中改变了这个值,原始的变量不会受到影响。
对于引用数据类型(如对象、数组等),传递的是引用的一个副本,但原始引用(实参)和引用的副部(形参)指向的是堆内存中的同一个对象。如果在方法中改变了引用指向对象的字段,原始对象将会受到影响,因为两者指向的是同一个对象。但是,如果在方法中将形参引用指向一个新的对象,那么原始引用仍然指向方法外的原始对象。
public class TestZhiChuanDi {
public static void main(String[] args) {
int num = 10;
changeValue(num); // 基本数据类型传递
System.out.println(num); // 输出10,num的值没有改变
StringBuilder sb = new StringBuilder("Hello");
changeReference(sb); // 引用数据类型传递
System.out.println(sb.toString()); // 输出HelloWorld,sb的内容改变了
StringBuilder sb2 = new StringBuilder("World");
System.out.println("交换前sb指向的对象是:"+ System.identityHashCode(sb)); // 输出sb指向的对象
System.out.println("交换前sb2指向的对象是:"+ System.identityHashCode(sb2)); // 输出sb2指向的对象
swap(sb, sb2); // 尝试交换两个StringBuilder对象
System.out.println("交换后sb指向的对象是:"+ sb.hashCode()); // 输出sb指向的对象,并没有改变
System.out.println("交换后sb2指向的对象是:"+ sb2.hashCode()); // 输出sb2指向的对象
System.out.println(sb.toString()); // 输出HelloWorld,sb指向的对象没有改变
}
public static void changeValue(int value) {
value = 20;
}
public static void changeReference(StringBuilder builder) {
builder.append("World");
}
public static void swap(StringBuilder x, StringBuilder y) {
System.out.println("交换前x指向的对象是:" + x.hashCode() );
System.out.println("交换前y指向的对象是:" + y.hashCode() );
StringBuilder temp = x;
x = y;
y = temp;
System.out.println("交换后x指向的对象是:" + x.hashCode() ); // 输出x指向的对象,已经改变
System.out.println("交换后y指向的对象是:" + y.hashCode() ); // 输出y指向的对象,已经改变
}
}
输出内容:
10
HelloWorld
交换前sb指向的对象是:1435804085
交换前sb2指向的对象是:328638398
交换前x指向的对象是:1435804085
交换前y指向的对象是:328638398
交换后x指向的对象是:328638398
交换后y指向的对象是:1435804085
交换后sb指向的对象是:1435804085
交换后sb2指向的对象是:328638398
HelloWorld
引用变量,是存在栈中的,其指向的对象,是存在堆中的,引用变量的值其实就是指向的对象在堆中的具体的地址。java中没有指针,不能直接获取地址,但是可以通过hashCode看其指向的对象。如果两个引用类型变量的hashCode一样,说明其指向的就是同一个对象。