【C语言】带你层层深入指针------指针详解2
前言:
书接上回,由于文章过长,所以分了几篇文
若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持
谢谢大家 ! ! !
四、各种指针变量类型
1.字符指针变量
将常量字符串的地址串给指针时,
指针实际接收的是字符串的首字符地址
如下代码演示:
c
#include <stdio.h>
int main()
{
char* p = "abcdefg";
//将字符串首字符地址给p
printf("%c\n\n", *p);
//p是首字符地址,故会打印" a "
printf("%s\n\n", p);
//使用%s打印字符串时,
// 只需提供首字符的地址就行
//无需 *p
return 0;
}
运行结果:

另外:
当多个指针指向同一个常量字符串时它们的地址值相同
代码演示:
c
#include <stdio.h>
int main()
{
char* p1 = "abcdefg";
char* p2 = "abcdefg";
//p1,p2指向同一个常量字符串
if (p1 == p2)
{
printf("p1==p2\n\n");
}
else
printf("p1!=p2\n\n");
return 0;
}
运行结果:

结果显示:
当多个指针指向同一个常量字符串时,它们的地址值相同,
同一个常量字符串他们在内存中占用一块空间
2.数组指针变量
(1)指针与数组
数组名是首元素地址
arr == &arr[0] ;
但是也有例外:1.sizeof( arr ) ;这里计算的是整个数组
2.&arr; 这里 arr 表示整个数组,取出的是整个数组的地址
用以下各个值 +1 为例:
代码演示:
c
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n\n", arr + 1);
printf("%p\n", &arr[0]);
printf("%p\n\n", &arr[0] + 1);
printf("%p\n", &arr);
printf("%p\n\n", &arr + 1);
return 0;
}
运行结果:

(地址为十六进制)
通过运行结果,可以看到arr 和 &arr[ 0 ] 结果一致
可以见得数组名是首元素地址,二者可等价(除去例外)
但是,&arr是整个数组地址,+1 跳过整个数组
故地址 + 40(10 个 int 类型)
指针访问数组
int arr[10] = { 1,2,3,4,5,6 };
int * p = arr ;
printf( " %d " , *( p + 2) ) ;
*( arr + i ) <=> arr [ i ] ;
(2)数组指针变量
创建一个数组指针变量:
c
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;
在上面代码中,p 是数组指针变量,存放的是数组的地址
p 和 &arr 的类型是 int ( * ) [ 10 ] ;
(3) 一维数组传参本质
首先, arr 是数组名,是首元素地址
一维数组传参的本质是传递数组首元素的地址,而非整个数组
即使形参写成数组形式,本质上仍然是一个指针变量
以以下代码为例:
形参声明为int arr[10]时,编译器实际处理为int arr
c
#include <stdio.h>
void test(int arr[10])
{
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n\n", sz);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test(arr);
return 0;
}
运行结果:

为什么结果 sz 为 1 呢?
难道在该函数中 arr = arr [ 0 ] ?
确实如此,看似形参是 int arr [ 10 ] ,但是本质上是一个指向 arr 首元素地址的指针,也就是 arr [ 0 ] 。
所以,
一维数组传参的本质是传递数组首元素的地址,而非整个数组
即使形参写成数组形式,本质上仍然是一个指针变量
且注意:
不能像上述代码中在函数内部使用形参计算数组元素个数
(4) 二维数组传参本质
类比一维数组,一维数组传参时数组名是首元素地址,形参可写成数组或指针形式
二维数组名也是首元素地址,但二维数组相当于一维数组。
故二维数组的数组的数组名就是第一行一维数组的地址
因此二维数组传参时,形参可以写成数组形式或指向一维数组的指针。
代码演示(内有注释):
c
#include <stdio.h>
void test(int (*arr)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", *(*(arr + i) + j));
//此处等价于 arr[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
test(arr, 3, 5);
//传参本质是第一行地址
//故用int (*arr)[5]接收
//[5]表示五行一维数组
return 0;
}
运行结果:

二维数组名也是首元素地址,二维数组相当于一维数组
所以对于 int arr [ 3 ] [ 5 ] 来说:
第一行{1,2,3,4,5}是第一个元素
第二行{2,3,4,5,6}是第二个元素
第三行{3,4,5,6,7}是第三个元素
int arr [ 0 ] [ ] 就是第一行
int arr [ 1 ] [ ] 就是第二行
且注意:
arr + i 得到第i行的地址*( arr + i ) 解引用得到第i行数组名
*( arr + i ) + j 得到第i行第j列元素的地址
最后再解引用 * ( * ( arr + i ) + j ) 得到访问具体元素
通过双重解引用 * ( * ( arr + i ) + j ) 访问具体元素,等价于 arr [ i ] [ j ] 的写法。
3.函数指针变量
函数指针变量用于存放函数地址,通过地址能够调用函数 。
与整型指针、数组指针类比,函数指针也是一种特殊的指针类型。
那么该如何使用函数指针变量呢?
- 声明语法 :函数指针声明需包含返回类型 和参数列表,如 void ( * pf ) ( int , int ) 表示指向返回值为 void 类型且接受两个 int 参数的函数的指针。
- 赋值方式 :可以直接将函数名赋给指针变量(函数名即地址),如 pf = Add
- 调用方法:通过指针调用函数时,可以使用 ( * pf ) ( arg1, arg2 ) 或简写 pf ( arg1 , arg2) 两种等效形式。相当于 Add ( arg1 , arg2 ) .
代码演示:
c
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf)(int, int) = &Add;
printf("%d\n\n", pf(4, 5));
return 0;
}
运行结果:

五、指针的使用
1.传址调用
现在,我们要写一个函数,直接使主函数变量的值改变
那么该怎么写呢?
我们可以想到将主函数变量传参到函数中直接改变,如下代码:
c
#include <stdio.h>
void F(int g)
{
g = 100;
}
int main()
{
int g = 10;
F(g);
printf("%d\n\n", g);
return 0;
}
这样我们好像成功将 g 的值改为了100,可事实真的如此吗?
运行结果:

结果仍然是 10
这是为什么呢?
因为,上述方法是传值调用,当实参传给形参时,修改形参不会影响实参,形参只是实参的临时拷贝
而要使主函数变量的值直接改变就要用到传址调用
将主函数变量的地址传给函数,形参指向实参的内存,再在函数中通过地址对变量进行直接修改
代码演示:
c
#include <stdio.h>
void F(int* p)
{
*p = 100;
}
int main()
{
int g = 10;
int* p = &g;//拿到g的地址
F(p);//将g的地址传给函数
printf("%d\n\n", g);
return 0;
}
运行结果:

可以见得,在传址调用下,g 的值直接被改变了
故传址调用可以让函数和主函数建立真正的联系,若要在函数中修改主函数的值,只能用传址调用
2.指针数组
用于存放指针的数组,每个元素都是指针类型
例如:
c
int * arr[3] = { &a,&b,&c };
知道了指针数组,现在我们就可以用指针数组来模拟二维数组。
代码演示:
c
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 6,7,8,9,10 };
int arr3[5] = { 11,12,13,14,15 };
int* arr[3] = { arr1,arr2,arr3 };
//创造一个二维数组
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);//打印二维数组
}
printf("\n");
}
return 0;
}
运行结果:

3.函数指针数组(简易的计算器的实现)
有了数组指针,可以放置地址的数组,那么是不是可以将函数的地址放入数组中呢?
这就是函数指针数组
现在有 +(加) -(减) * (乘) / (除)四个函数
我们可以创建一个数组将其容纳,这就是函数指针数组
c
float (*Calc[5])(float, float) = { 0,Add,Sub,Mul,Div };
float ( * Calc [ 5 ] ) ( float , float ) 就是函数指针数组
前面的 flaot 表示函数的返回值类型是 float 类型
后面的两个float 表示函数的两个参数是 float 类型
里面存放了加减乘除四种函数,可以进行四种计算,所以我们可以用该函数指针数组来做一个简易的计算器
代码演示:
c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
float Add(float x, float y)//加法
{
return x + y;
}
float Sub(float x, float y)//减法
{
return x - y;
}
float Mul(float x, float y)//乘法
{
return x * y;
}
float Div(float x, float y)//除法
{
return x / y;
}
void menu()
{
printf("******************************\n");
printf("****** 简 易 计 算 器 ******\n");
printf("****** 1.Add 2.Sub ******\n");
printf("****** 3.Mul 4.Div ******\n");
printf("****** 0.exit ******\n");
printf("******************************\n");
printf("请输入:");
}
int main()
{
int input;
float x, y;
float (*Calc[5])(float, float) = { 0,Add,Sub,Mul,Div };//给数组前巧妙加一个0可以更方便地调用函数
do
{
menu();
scanf("%d", &input);
if (input == 1 || input == 2 || input == 3 || input == 4)
{
printf("请输入要计算的值:");
scanf("%f %f", &x, &y);
printf("结果是:%.2f\n\n", Calc[input](x, y));
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输入错误,请重新输入 \n");
}
} while (input);
return 0;
}
运行结果:

最后注意:
函数指针数组中的所有函数的参数和类型必须要一致
结语
本期资料来自于:

OK,本期的指针详解到这里就结束了
由于文章过长,所以分了几篇文
若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持
本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!
新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!
支持一下(三连必回QwQ)