六、指针 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
注意事项
- 指针的类型必须和变量类型一致
- 指针也有自己的地址
- 可以用%p输出地址
- 只有地址才可以取间接 ( * ) 运算
- & 和 * 相互抵消
多级指针¶
多级指针就是套娃 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,还是直接让 指针名 = 数组名 都可以做到
对于指针的常用运算,当你理解了上述部分在理解起来就会很简单
- 指针+n的值是指针后方第n个元素的地址
- 指针-n的值是指针前方第n个元素的地址
- 指针++是指针向后移动1个元素位置
- 指针--是指针向前移动1个元素位置
那么等价关系在这里也是成立的
- p+i 等价于 &a[i]
- *(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)),所以走其他路线可以不用学。
至此,第六章指针结束!