1、指针的概念
(1)指针也就是内存地址,指针变量是用来存放内存地址的变量。
(2)示例代码
#include <stdio.h>
int main ()
{
int var_runoob = 10;
int *p; // 定义指针变量
p = &var_runoob;
printf("var_runoob 变量的地址: %p\n", p);
return 0;
}
当上面的代码编译执行时,会产生如下过程:
(3)为什么需要指针
指针存在的目的就是间接访问。有了指针之后,我们访问变量a不必只通过a这个变量名来访问。而可以通过p = &a; *p = xxx;这样的方式来间接访问变量a。
2、指针的定义
(1)指针变量和其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行定义。指针变量定义的一般形式为:
type *var_name;
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var_name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
(2)指针变量的类型
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *cp; /* 一个字符型的指针 */
- 所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
- 不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
3、指针的使用
(1)两个重要的运算符:&和*
- 这是指针操作涉及的两个重要运算符
- &:取地址符。将它加在某个变量前面,则组合后的符号代表这个变量的地址值。
- *:指针符号。指针符号在指针定义和指针操作的时候,解析方法是不同的。
(2)示例代码
int main()
{
int a = 23;
int *p;
p = &a;
*p = 256;
return 0;
}
(3)上述代码中符号的理解
符号 | 含义 |
---|---|
a | 代表变量a本身 |
p | 代表指针变量p本身 |
&a | 代表变量a的地址值 |
*p (*p = 256; 指针操作) | 代表指针变量p所指向的那个变量,也就是变量a |
*p (int *p; 指针定义) | 定义时*含义是告诉编译器p是一个指针类型的变量 |
&p | 代表指针变量p本身的地址值 |
4、C中的NULL指针
(1)在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值得指针被称为空指针。
(2)NULL 指针是一个定义在标准库中的值为零的常量。示例程序:
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
ptr 的地址是 0x0
(3)在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */
5、指针与数组的关系
(1)在赋值运算中,数组名做右值时,数组名表示数组的首元素地址,因此可以直接赋值给指针。
(2)如果有 int a[5];
- a 和 &a[0] 都表示数组首元素a[0]的首地址。
- &a则表示数组的首地址。
注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组整体的地址。两个东西的含义不同,但是数值上是相同的。
(3)根据以上,就知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去逐个访问数组中各个元素。这样访问数组就有了两种方式。
- 有 int a[5]; int *p; p = a;
- 数组的方式依次访问:a[0] a[1] a[2] a[3] a[4]
- 指针的方式依次访问:*p *(p+1) *(p+2) *(p+3) *(p+4)
(4)数组名不能在赋值运算中做左值,因为数组声明时内存地址就固定了,相当于数组名是一个常量且赋值过,不能修改的。
int * p = null;
and [5].
a = p; // 编译会报错
6、指针的算数运算
(1)指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的地址值,因此该值进行* / %等运算是无意义的。两个指针变量相加本身也无意义,相减有意义(可计算两个变量之间相隔几个格子)。
(2)指针的算术运算:可以对指针进行四种算术运算:++、--、+、-。
(3)指针的运算
- p+1就代表指针所指向的格子向后挪一格,p-1代表指针所指向的格子向前挪一格。
- *p++:等同于 *p; p += 1;
- *++p:等同于 p += 1; *p;
- (*p)++:先计算*p取值,该值作为表达式的值,然后对*p整体的值++
- ++(*p):先计算*p取值,然后对*p整体的值++,该值+1后作为整个表达式的值
#include <stdio.h>
int main(void)
{
int a[5] = {555, 444, 333, 222, 111};
int *p;
p = a;
printf("*p = %d.\n", *p); // 555
// printf("*p++ = %d.\n", *p++); // 555,++后置,先运算后+1
// printf("*++p = %d.\n", *++p); // 444,++前置,指针先加1,然后再*p取值
// printf("(*p)++ = %d.\n", (*p)++); // 555,先*p取值,然后给值后置++
printf("++(*p) = %d.\n", ++(*p)); // 556
return 0;
}
7、传递指针给函数
(1)通过引用或地址传递参数,使传递的参数在调用函数中被改变。
(2)C语言中传参是通过传值调用。
- 当你将一个变量作为参数传递给函数时,实际上传递的是该变量值的一个副本,而不是变量本身。这意味着在函数内部对参数所做的任何修改都不会影响原始变量。
- 然而,如果你希望函数能够修改原始变量的值,你需要使用指针。指针是一个特殊的变量,它存储的是另一个变量的内存地址,而不是那个变量的值。因此,当你将指针作为参数传递给函数时,函数可以访问并修改该指针所指向的内存位置上的值,从而实现对原始变量的修改。
#include <stdio.h>
// 使用值传递的swap函数,无法修改原始变量
void swap_by_value(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
// 使用指针传递的swap函数,可以修改原始变量
void swap_using_temp(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 5;
int y = 10;
printf("Before swap_by_value: x = %d, y = %d\n", x, y);
// 这个调用不会改变x和y的值
swap_by_value(x, y);
// 输出仍然是 x = 5, y = 10
printf("After swap_by_value: x = %d, y = %d\n", x, y);
printf("Before swap_using_temp: x = %d, y = %d\n", x, y);
// 这个调用会改变x和y的值
swap_using_temp(&x, &y);
// 输出将是 x = 10, y = 5
printf("After swap_using_temp: x = %d, y = %d\n", x, y);
return 0;
}
8、C指针的其他内容
在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念:
- 指针数组:可以定义用来存储指针的数组。
- 指向指针的指针:C 允许指向指针的指针。
- 从函数返回指针:C 允许函数返回指针到局部变量、静态变量和动态内存分配。