指针
一、初识指针
理解指针的两个含义:
1、指针是内存中最小单元(每个字节)的编号,也就是地址。
2、我们通常学习、使用和口头讲述的指针指的是指针变量,是用来存放地址的变量。
总结:指针,通常指的是指针变量,是储存地址的变量。
二、指针类型与指针运算
2.1 指针类型的意义
指针类型的定义方式是: type + *
指针类型的意义:
1、指针类型决定了指针解引用时一次访问内存的字节数。
例:char* 类型的指针,解引用时一次访问一个字节
int* 类型的指针,解引用时一次访问四个字节
double*类型的指针,解引用时一次访问八个字节
即:指针指向的变量的类型在内存中有多大,指针解引用一次就能访问多少个字节。
2、指针类型决定了指针的步长(指针 + - 1 越过的字节数)。
例:char* 类型的指针,+1指针向前(向高地址)越一个字节,-1指针向后(向低地址)越一个字节。
itn* 类型的指针,+1指针向前(向高地址)越四个字节,-1指针向后(向低地址)越四个字节。
即:指针指向的变量的类型在内存中有多大,指针 ±1 就能越过多少个字节。
2.2 指针的相关运算
- 指针 ± 整数
指针 ± 整数就是指针中的地址向前/向后跳过 整数*步长 个字节。
- 指针 ± 指针
前提:两个指针指向的是同一块内存区域。
意义:两个指针相减得到的是两个指针之间的元素个数(指针相减值的类型:int 或 _int64)。
#include <stdio.h>
int main()
{
int arr[6] = { 1,2,3,4,5,6 };
int* p1 = &arr[0];
int* p2 = &arr[5];
printf("%d", (int)(p2 - p1));
return 0;
}
- 指针的关系运算
指针间可以进行大小比较:高地址 > 低地址
注:标准规定:允许指向数组元素的指针与指向数组最后一个元素后的那个内存位置的指针比较,但是不允许与指向数组第一个元素之前的那个内存位置的指针进行比较。
三、各种类型的指针
字符指针
字符指针的指针类型为:char*
字符指针的一般使用方法:
int main()
{
char ch = 'w';
char* pch = &ch;
*pch = 'x';
return 0;
}
不过字符指针还可以用来储存字符串:
int main()
{
char* pstr = "hello world";//这里是把字符串的首字母的地址储存在了指针中,从首字母开始向后读取就可以获得整个字符串
//其实标准的写法为:const char* pstr = "hello world"; 因为右值不可被修改,加const更标准
printf("%s", pstr);
return 0;
}
注意:
const char* str1 = "hello world";
const char* str2 = "hello world";'
//str1与str2指向的是同一个常量字符串,C/C++会把常量字符串存储在一个单独的内存区域中,
//当多个指针指向同一个常量字符串时,它们实际会指向同一块内存
二级指针
指针变量也是变量,是变量就有地址,储存(一级)指针变量地址的指针就是二级指针。
二级指针类型的定义方式:type * + *
int a = 10;
int* pa = &a;
int** ppa = &pa; //指向int*类型指针的二级指针
*ppa访问的是pa,**ppa访问的是a。
指针数组
指针数组:存放了一组相同类型指针的数组,叫指针数组。
(优先与 [ ] 结合的是数组,优先与 * 结合的是指针,[ ] 的优先级高于 * )
指针数组的定义方式为:type* name[ ]
int a = 1, b = 2, c = 3;
int* pa = &a, pb = &b, pc = &c;
int* pp[3]={pa, pb, pc}; //整型指针数组
指针数组可以模拟二维数组:
int arr1[3] = {1,2,3};
int arr2[3] = {4,5,6};
int arr3[3] = {7,8,9};
int* parr[3] = {arr1, arr2, arr3};
//模拟了一个 3x3 的二维数组,这个一维数组还可以换成malloc出来的,可以模拟变长二维数组
数组指针
数组指针:储存整个一维数组地址的指针,叫数组指针。
数组指针的定义方式为:type (* name)[ ]
( [ ]的优先级高于*,所以要加上()使 * 与name先结合才能形成指针 )
( [ ]中存的值是一维数组的元素个数,或者讲是列数 )
数组指针的常见用法:接收、储存二维数组。
int arr1[5] = {1,2,3,4,5};
int (*parr1)[5] = &arr1;
//&数组名表示整个数组,int(*parr1)[5]表示parr1指向一个一维数组,一维数组有五个元素
//但是这样写没有什么实际价值,*parr1表示整个数组,没有什么操作空间
int arr2[3][4] = {{1,2,3,4},{5,6,7,8,9},{10,11,12}};
int(*parr2)[4] = arr2;
//数组名表示数组首元素地址,二维数组首元素是整个第一行,可以看成是一个一维数组
//int(*parr2)[4]表示parr2指向一个二维数组的第一行,这个二维数组共有4列
//*parr2表示第一行,即可看成*parr2为一个一维数组,所以可以通过*parr2[i]来操作二维数组的第一行
//由上知,可以通过parr2[i][j]来操作整个二维数组, 因为 *(p + 1) 等价于 p[1]
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", parr2[i][j]);
}
printf("\n");
}
函数指针
函数指针:储存函数地址的指针。
函数指针的定义方式为:type (*p)(参数)
(注:type:指针指向的那个函数的返回类型
参数:指针指向的那个函数的参数,只写类型也可以,参数数量要相同)
代码演示:
int Add(int x, int y)
{
return (x + y);
}
int main()
{
int(*padd)(int, int) = Add; // &函数名 与 函数名 等效
printf("%d\n", padd(3, 5)); //不需解引用,函数名是地址与指针等价,指针名可直接调用所指函数
return 0;
}
函数指针数组
函数指针数组:元素为函数指针的数组。
函数指针数组的定义方式为:type (*p[ ])(参数)
用途:转移表
代码演示:转移表
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; // 转移表
while (input)
{
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
}
return 0;
}
四、数组传参和指针传参
所传实参是什么类型,就用对应接收这种类型的指针做形参
详解为以下几点:
1、一维数组:形参有两种形式,第一种 可用相同形式的数组接收,第二种 可用一级指针接收
2、一级指针:一级指针传参的两种类型:传值和传址。传值是临时拷贝,所以要传值,一级指针用一级指针接收;如果要修改一级指针的内容,就需要用二级指针接收。二级及以上指针传参类推。
3、其他:函数传参用函数指针接收,二维数组传参用二维数组(列不能省略)或数组指针接收。
五、回调函数
回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当接收这个函数的形参指针被用来调用其所指向的函数时,我们就说这是回调函数。
辨析:回调函数:把函数传给另一个函数,在那个函数中调用接收的指针。
递归:在函数中调用自身。
链式访问:一个函数的返回值用作另一个函数的实参。