理解指针的关键在于理解变量,变量是一个“魔术盒”,学习内存和内存地址之后,我们应该将变量看作占据一部分内存空间的对象来理解,从而更好的进行运用。
在调用函数时,值的传递是单向的,即从实参传向形参,例如,我们已经有了一个计算两数和差的函数func(int num1,int num2,int sum,int diff),而当我们真正想利用这个函数求出两个数的和与差时却出现问题,因为函数的返回值只能是一个,而当我们重新定义一个初始化的变量带入后,这个变量的值并不会改变。例如:
int a=1,int b=2,int c=0,int d=0.
func(a,b,c,d)
此时调用函数之后abcd的值仍会保持不变,因为函数作用后的值并没有传递回来。而指针就能有效解决这个问题。
内存空间是广阔的,每个对象都占据着一定的内存空间,于是我们需要位置这个概念,即内存地址。取址运算符是&,我们只需将&加在对象名前面就可以得到该对象的位置,例如:
printf("%p",&a)就可以得到变量a的位置,其中%p是占位符,值得注意的是,内存的编码是以字节为基础的(8bit),而一个变量可能会占据不止一个字节,而由于对象占据的地址是连续的,我们只需要用对象占据内存空间的首地址来表明该对象的地址。
只显示一个对象的地址用处是不大,我们需要利用这个地址对对象进行操作。
定义指针变量int*p,与之区分的是int p。
这两者的区别在于后者是存储整数的盒子,前者加上*作为区分,标明是存放整数对象地址的盒子。对指针变量赋值时,是要将某个对象的位置赋值给指针变量,故需要加上取址运算符。如:
int a=10;
int*p=&a;
这就将a的地址赋值给变量p了。
此时就说p指向了a。
那么怎么从p得到a具体的值呢,我们就需要用到指针运算符,*p即为a的值,其中*为指针(又名间接访问运算符,这里的间接性在于利用a的地址间接的访问了a的值)运算符,换言之,*p就是a的别名。那么,这一切的做法有什么用处呢?
作为函数参数的指针
在最开始举出的例子中,形参的修改只是临时性的复制,无法反映到主调函数(主动调用其他函数的函数)的实参中,而利用指针能有效解决这个问题。
在 C 语言中,当以变量为实参调用函数时,函数是按值传递的,这意味着传递给函数的是变量的值的副本,而不是变量本身。函数内部对这个副本进行操作,不会影响到原始变量的值。
#include <stdio.h>
// 函数,交换两个变量的值
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
printf("交换前:x = %d, y = %d\n", x, y);
swap(x, y);
printf("交换后:x = %d, y = %d\n", x, y);
return 0;
}
在这个例子中,swap
函数接收的是x
和y
的副本,对副本进行交换操作,不会影响到x
和y
的原始值而当以变量的地址作为实参时,函数可以通过指针来间接访问和修改原始变量的值,因为指针存储的是变量的地址。
#include <stdio.h>
// 函数,交换两个变量的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
printf("交换前:x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("交换后:x = %d, y = %d\n", x, y);
return 0;
}
在这个改进的例子中,swap
函数接收的是指向x
和y
的指针(即&x
和&y
)。通过指针操作(使用*
运算符解引用),可以修改x
和y
的实际值。
类似的,在讲到scanf函数时,强调第二个参数前必须加上&,原因与此类似,在这个函数中,我们试图将某个接收到的值赋值给一个变量,就不能不利用指针对这个变量进行间接操作。
数组与指针
-
数组名是指针常量:
- 当声明一个数组时,例如
int arr[5];
,数组名arr
实际上是一个指向数组第一个元素的指针常量。 - 这意味着
arr
存储了数组第一个元素的地址,即&arr[0]
。
- 当声明一个数组时,例如
-
通过指针访问数组元素:
- 可以使用指针来访问数组元素。例如,如果
p
是一个指向arr
数组的指针,那么*(p + i)
等同于arr[i]
。 - 这里的
p + i
表示从p
所指向的位置向后移动i
个元素的位置,*(p + i)
则是对该位置元素的解引用操作,从而获取该元素的值。
- 可以使用指针来访问数组元素。例如,如果