0%

指针相关复习

1.基本内容

什么是指针

  • 在计算机中所有数据都存储在内存单元中,而每个内存单元都有一个对应的地址, 只要通过这个地址就能找到对应单元中存储的数据.
  • 由于通过地址能找到所需的变量单元,所以我们说该地址指向了该变量单元。将地址形象化的称为“指针”
  • 内存单元的指针(地址)和内存单元的内容是两个不同的概念。

什么是指针变量

在C语言中,允许用一个变量来存放其它变量的地址, 这种专门用于存储其它变量地址的变量, 我们称之为指针变量。

1
2
3
char ch = 'a';
char *p; // 一个用于指向字符型变量的指针
p = &ch;

*表示这是一个指针变量

1
2
3
4
5
int *p;
p = 250; // 错误写法
/****************************/
int *p;
*p=&a; //错误写法
  • 多个指针变量可以指向同一个地址。

对指针变量的理解

指针变量本身的作用是储存一个用户定义的变量的地址,但是其本身作为一个额外的变量,也有自己的地址,且这个地址不等于其储存的变量的地址;

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int a=1;
int *c;
c=&a;
printf("指针变量c自身的地址为:%d",&c);//6487568
printf("指针变量c存储单元内存储的内容(也就是a的地址)为:%d",c);//6487576
printf("变量a的地址为:%d",a);//6487576
}
// c代表c内存放的地址
// &c代表c自身的地址
// *c代表c内存放的地址中所存储的值
// (c+1)代表c内存放的地址+1*类型长度
// *c+1代表c内存放的地址中所存储的值本身+1
// *(c+1)代表c内存放的地址+1*类型长度后得到的地址中存储的值

取地址运算符(&)与解引用运算符(*)

  • 取地址运算符:&x,其作用为返回操作对象的地址

  • 解引用运算符:*x,其作用是返回x中存储的地址内存储的值

    例: int a,*p1;p1=&a;则此时*p1代表的就是变量a的内容,而不是a的地址

    这也就是为什么使用printf("%d",*p);时是用p而不是p了,因为 p才是代表a的值,p则存储的变量a的地址

    综上:除开定义时,其余时间*p等同a,p等同&a

    在定义时,int *p1=&a;也是合法的且作用与上相同,个人理解为是一种指针格式的“声明”(?)这也解释了当某个函数参数定义为为int *a时实际使用时传入的也是&的取地址变量了(个人理解)

2.一些用法

二级指针

  • 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”。

    1
    2
    3
    4
    5
    6
    char c = 'a';
    char *cp;
    cp = &c;
    char **cp2;
    cp2 = &cp;
    printf("c = %c", **cp2);// c=a

指针访问数组元素

  • 在指针指向数组元素时,允许以下运算:

    • 加一个整数(用+或+=),如p+1
    • 减一个整数(用-或-=),如p-1
    • 自加运算,如p++,++p
    • 自减运算,如p–,—p

    如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同 一数组中的上一个元素。【*(p+1)】

    (p+N)在地址上的变化为p内存放的地址+数组元素类型长度*N

    • 其对p所有的基本运算均符合上述规律

指针与字符串

定义字符串除了使用字符数组存放之外,还可以通过字符串指针的方式指向字符串。

1
2
// 数组名保存的是数组第0个元素的地址, 指针也可以保存第0个元素的地址
char *str = "abc"
  • 使用注意事项

    • 此方法不可修改字符串内容

      1
      2
      3
      4
      //使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有可以修改字符串中的的字符
      //使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符
      char *str = "lnj";
      *(str+2) = 'y'; // 错误
    • 不能够直接接收键盘输入

      1
      2
      3
      4
      // 错误的原因是:str是一个野指针,他并没有指向某一块内存空间
      // 所以不允许这样写如果给str分配内存空间是可以这样用 的
      char *str;
      scanf("%s", str);

指向函数的指针

函数在内存中占据空间,因此也有起始地址。

格式:返回值类型 (*指针变量名)(形参);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
int sum(int a,int b)
{
return a+b;
}
int main()
{
int (*p)(int x,int y);
p=sum;
printf("%d",(*p)(1,2)); //打印3
return 0;
}
  • 函数指针有以下几点特点:

    • 当把一个函数名(注意是名字,不带输入参数)作为一个值使用时,该函数会自动转换成指针,指向该函数的内存地址,因此在对指针初始化时取地址符可选:

      1
      2
      p=sum;
      p=&sum;//都正确
    • 当使用指针调用函数时,解引用也是可选的:

      1
      2
      printf("%d",(*p)(1,2));
      printf("%d",p(1,2));//都输出3
  • 当函数指针作形参时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdio.h"
#include "math.h"
void tran(double (*a)(double a1),double (*b)(double b1),double x);
int main()
{
double x=3.141592;
tran(sin,cos,x);
return 0;
}
void tran(double(*a)(double a1),double(*b)(double b1),double x)
{
printf("%f\n",(*a)(x/2));
printf("%f",(*b)(x/2));
}

调用函数时直接传入函数名即可。

同理的,也可以不声明参数为指针(因为会自动转变为指针)

1
2
3
4
5
void tran(double a(double a1),double b(double b1),double x)
{
printf("%f\n",(*a)(x/2));
printf("%f",(*b)(x/2));
}

通过形参影响实参

我们都知道,形参值的改变不能改变对应实参的值,把数据从被调用函数返回到调用函数的唯一途径时通过return。但如果通过传送地址给被调用函数,再在被调用函数中改变地址中存储的值,也就使通过形参改变对应实参的值成为了可能。

例如:交换值

1
2
3
4
5
6
7
8
9
10
11
12
void swap(int *x, int *y)
{
int c; // 因为c只是用来存放地址内保存的值,所以为一般变量
c=*x;*x=*y;*y=c; //将a、b地址中保存的值互换、地址不变
}
int main()
{
int a=1,b=2;
swap(&a,&b);//将a、b的地址传给形参x、y,此时指针变量x、y内保存的就是a、b的地址
printf("%d %d",a,b);//此时a、b地址内保存的值已经互换
return 0;
}

但如果swap函数定义成如下,又会怎么样呢

1
2
3
4
5
void swap(int *x, int *y)
{
int *c;
c=x;x=y;y=c;
}

如果这样定义函数,则程序的流程为:①调用swap函数时,x内保存的为a的地址,y内保存的为b的地址;②在swap函数中,x内保存的地址改变为b的地址,y内保存的地址改变为为a的地址;③swap函数调用结束,x,y的内存被释放;④此时a、b变量自身的地址并没有任何改变,改变的只用形参x、y所保存的地址,因此无法起到交换作用

二维数组地址详解+数组指针(重要!!!!!)

详见二维数组复习 | 小董的BLOG (gitee.io)

结构体指针

详见数据结构_链表 | 小董的BLOG (gitee.io)结构体复习 | 小董的BLOG (gitee.io)

2022.11.20:我是按照自己的理解写的,所以思路那些可能会有点奇怪,我感觉自己写的也不是很精练。。凑合看看吧