跳转至

六、指针 Pointer

指针是一个比较难的地方,对于python、java来说,c/c++难的点就在指针和多继承等部分,所以在此章节也不会详细讲述。

指针是什么?

指针是用来存储内存地址的变量。

通过地址 直接指向内存中该地址的值。由于通过地址能够找到所需的变量存储单元,可以说地址指向该变量存储单元,因此将地址形象化称为"指针" 。总而言之,指针就是变量,存放内存单元的地址,存放在指针中的值都会被当作地址处理。

对于指针来说,定义稍微与普通的变量定义不同

定义指针

数据类型标识符 ==*==指针名;(注意指针名前有一个星号)

int *p1;
float *p2;
char *p3;
// 第1条语句定义了一个int型的指针p1
// 第2条语句定义了一个float型的指针p2
// 第3条语句定义了一个char型的指针p3

这里是普通定义,那么要使指针指向变量的定义方式又有所不同

数据类型标识符 *指针名 = &变量名

或者

数据类型标识符 *指针名

指针名 = &变量名

int x;
int *px = &x;

char c;
char *pc;
pc = &c;

我们知道了如何定义,接下来就是如何用指针处理变量

指针名 等价于 &变量名

*指针名 等价于 变量名

有些拗口,可以理解为指针名前有星号就是变量里的值,没有星号就是变量的地址

“有星无址“ 这是我记忆的口诀:有星号就没有地址,有地址有没有星号

所以我们可以使用指针进行一些操作

#include<stdio.h>
int main(){
    int a = 5;
    int b = 6;
    int *pa = &a;
    int *pb = &b;
    printf("%d",*pa + *pb);
}
// 这段代码会输出11

注意事项

  1. 指针的类型必须和变量类型一致
  2. 指针也有自己的地址
  3. 可以用%p输出地址
  4. 只有地址才可以取间接 ( * ) 运算
  5. & 和 * 相互抵消

多级指针

多级指针就是套娃 p *p 以此类推

int   x = 100;
int   *px = &x;       
int   **ppx = &px;
int   ***pppx = &ppx;
// 第2条语句定义了一级指针px指向了变量x
// 第3条语句定义了二级指针ppx指向了一级指针px
// 第4条语句定义了三级指针pppx指向了二级指针ppx

存在的等价关系:

pppx==等价于 ==&ppx==等价于 ==ppx==等价于 ==&px==等价于==px==等价于&x==

* pppx==等价于 == * * &ppx==等价于 == * ppx==等价于 == &px==等价于 ==* px==等价于==x

// 因为markdown格式下的笔记的加粗和斜体是用 * 来标记,所以上面的 * 会用空格隔开,在代码书写的时候尽量不要空格

结论:

n级指针取n-1次间接运算得变量的地址

n级指针取n次间接运算的变量的值

用指针处理一维数组

一维数组元素的存储方式是按下标递增顺序连续存放

数组名是首元素的地址

a等价于&a[0]

所以指针指向元素的格式为

指针名 = &数组名[i];

如果你只是想指向首元素,无论填写0,还是直接让 指针名 = 数组名 都可以做到

对于指针的常用运算,当你理解了上述部分在理解起来就会很简单

  1. 指针+n的值是指针后方第n个元素的地址
  2. 指针-n的值是指针前方第n个元素的地址
  3. 指针++是指针向后移动1个元素位置
  4. 指针--是指针向前移动1个元素位置

那么等价关系在这里也是成立的

  1. p+i 等价于 &a[i]
  2. *(p+i) 等价于 a[i]

来个程序举个例子

// 输入10个成绩,求总分

#include <stdio.h>
#define N 10

int main()
{
    int score[N];
    int *p=score;  //指针p指向首元素
    int sumScore;
    int i; 

    for(i=0;i<N;i++){ //录入成绩
    printf(“请输入第%d个成绩: ”,i+1);
    scanf(“%d”,p+i);
    } 

    for(i=0, sumScore=0;i<N;i++) sumScore += *(p+i); //求总成绩

    for(i=0;i<N;i++) printf(“%5d”,*(p+i)); //输出成绩
    printf(“\n\n”); 

    printf(“总成绩是%d\n”,sumScore); //输出总成绩
}

用指针处理二维数组

对于二维数组来说,二维数组的元素按先行后列下标递增的顺序连续存放

比如 :int a [2] [3] = [1,2,3,4,5,6];

那么存储方式就是

a[0] [0] 1
a[0] [1] 2
a[0] [2] 3
a[1] [0] 4
a[1] [1] 5
a[1] [2] 6

也可以理解为

[0] [1] [2]
a[0] 1 2 3
a[1] 4 5 6

数组名加行标是该行的行首地址

a[0] 是第1行行首地址

a[1] 是第2行行首地址

数组名是地址常量

a 等价于 &a[0] [0] 等价于 a[0]

定义指针二维数组

数据类型标识符 *数组名[行数];

int  a[2][3] = {1,2,3,4,5,6};
int *pa[2];

那么该如何指向数组的对应行

for(i=0;i<R;i++)
  指针名[i] = 数组名[i];
// 跟正常指向没什么区别

利用等价关系处理数组

pa[i]+j     等价于 &a[i][j]
*(pa[i]+j)  等价于 a[i][j]

既然对列进行了操作,那么二维数组中的行可不可以也进行操作,答案还是可以的

定义行指针并指向首元素

数据类型标识符  (*指针名)[列数];

int  a[2][3] = {1,2,3,4,5,6};
int (*pa)[3] = a; //指向第1行行首 

利用等价关系处理数组

*(pa+i)+j     等价于 &a[i][j]
*(*(pa+i)+j)  等价于 a[i][j]

举个栗子

// 使用指针数组录入和输出数据

#include <stdio.h>
int main(){
    int  a[2][3];
    int (*pa)[3] = a; //行指针  
    int i;
    int j;
    //输入数据
    for(i=0;i<2;i++)
        for(j=0;j<3;j++)
            scanf(“%d”,*(pa+i)+j); 
    //输出数据
    for(i=0;i<2;i++){
        for(j=0;j<3;j++)
        printf(“%5d”,* (*(pa+i)+j)); 
    printf(“\n”);
    }
}

动态内存分配

这块作为一个对指针的扩展,虽然也得学吧

你要记住,一旦沾上内存这个玩意,就会伴随一些危险性。有可能内存泄漏,或者不清楚内存,使你的计算机炸掉

四个动态内存分配函数

malloc函数

  • void *malloc(unsigned int size);
  • 动态分配size字节的内存空间,返回首地址

calloc函数

  • void *calloc(unsigned int count,unsigned int size);
  • 动态分配size字节的内存空间count个,返回首地址

realloc函数

  • void realloc(void ptr, unsigned int newSize);
  • 把ptr指向的内存空间调整为newSize个字节,返回首地址

free函数

  • void free(void *ptr);
  • 把由ptr指向的内存空间交还给系统

调用上述函数需包含头文件

动态内存分配处理方法

// 举个栗子

#include <stdio.h>
#include <stdlib.h>
int main(void){
    // 定义指针
    int   *p;
    float *q;
    // 令指针指向分配的空间
    p = (int *)malloc(sizeof(int));
    q = (float *)malloc(sizeof(float));
    // 通过指针处理数据
    printf("请输入一个整数:  ");
    scanf("%d",p);
    printf("请输入一个小数:  ");
    scanf("%f",q);
    printf("%d*%f=%f\n",*p,*q,*p**q);
    // 释放空间
    free(p);
    free(q);
}

OK啊家人们,到此呢指针的基础也差不多ok了,学长才疏学浅,也不能对指针进行一个更多的解释,而且如果你不在后续走C/C++路线,这个玩意对你来说是不会用到的( 学长是机器学习(python)),所以走其他路线可以不用学。

至此,第六章指针结束!