指针深入理解(中)
-
数组名的理解
-
指针访问数组
-
指针与一维数组
-
一维数组传参的本质
-
二级指针
-
冒泡排序
-
指针数组
-
数组指针
-
指针数组模拟一 / 二维数组
-
函数指针
-
函数指针数组
-
-转移表
前言
前面我们搞懂了指针的基础和运算,这篇要拆解 C 语言里最容易混淆的 “指针 + 数组 + 函数” 组合 —— 数组名、指针数组、数组指针、函数指针… 这些概念看似绕,其实都是 “指针规则 + 语法组合” 的产物。
1.数组名的理解:不是普通指针的 “指针”
数组名的本质是指向数组首元素的 “常量指针”,但它和普通指针有两个关键区别:
数组名不能被修改指向(是常量):
int arr[5] = {1,2,3,4,5};
arr++; // 错误:数组名是常量指针,不能改变指向
sizeof后面的数组名,计算的是全部指针的大小
int arr[5] = {1,2,3,4,5};
int* p = arr;
printf("sizeof(arr)=%zu\n", sizeof(arr)); // 输出20(5×4字节)
printf("sizeof(p)=%zu\n", sizeof(p)); // 输出8(64位系统指针大小)
arr=&arr[0] :指的是数组首元素的地址
&arr:整个数组的地址
2.指针访问数组:比下标更贴近内存
数组名是首元素指针,所以可以直接用指针运算访问数组:
int arr[5] = {10,20,30,40,50};
int* p = arr; // p指向arr[0]
// 等价于arr[i]
for(int i=0; i<5; i++){
printf("%d ", *(p+i)); // 输出:10 20 30 40 50
}
(p+i)=arr[i]=*(a+i) 写法等同
3.一维数组传参的本质
//打印数组内容
//#include<stdio.h>
//int print(int* arr, int sz)
//{
//
// for (int i=0;i<sz;i++)
// {
// printf("%d",*arr);
// arr++;
//
// }
// return 0;
//}
//
//
//int main()
//{
// int arr[] = {1,2,3,4,5};
// int sz = sizeof(arr) / sizeof(arr[0]);
// print(arr,sz);
// return 0;
//
//}
上代码
#include<stdio.h>
void printArr(int arr[], int len) //arr[]=*arr
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
}
int main() {
int arr[5] = { 1,2,3,4,5 };
printArr(arr, 5); // 传的是arr[0]的地址
return 0;
}
4.二级指针
二级指针
指针的指针就是二级指针,用于存储指针变量的地址:
int a = 10;
int *p = &a; // 一级指针:p存储a的地址
int **pp = &p; // 二级指针:pp存储p的地址
// 访问a的方式:
printf("%d", **pp); // 等价于 *p 或 a
5.指针数组
// 存储int指针的数组
int a=1, b=2, c=3;
int *arr[3] = {&a, &b, &c};
// 访问:
for(int i=0; i<3; i++){
printf("%d ", *(arr[i])); // 输出1 2 3
}
arr*p[10]指针数组
指的是指针数组,一个数组的元素为指针
// 指向“int[3]类型数组”的指针
int arr[3] = {1,2,3};
int (*p)[3] = &arr; // 注意括号:(*p)表示p是指针,[3]表示指向的数组有3个int元素
// 访问数组元素:
printf("%d ", (*p)[0]); // 等价于 arr[0],输出1
arr(*p)[10]类型为数组指针
指的是一个数组的地址,即为数组指针
这个类型要做好区分
冒泡排序代码示例
//冒泡排序
#include<stdio.h>
int bubble(int *arr,int sz)//传指针和个数
{
int *p = arr;//指针初始化
for (int i=0;i<sz-1;i++)//总数趟
{
for (int j=0;j<sz-i-1;j++)//一趟
{
if (arr[j] > arr[j + 1])
{
int temp = 0;
temp = arr[j];
arr[j] = arr[j+ 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = {4,6,2,3,9,1};
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz);
for (int s=0;s<sz;s++)
{
printf("%d",arr[s]);
}
return 0;
}
6.指针数组模拟一 / 二维数组
6.1行指针&列指针
如何理解二维数组?
二维数组的本质:“数组的数组”
从语法上,二维数组可以理解为:一个数组里的每个元素,又是一个一维数组。比如 int arr[3][4];
- 这是一个 “包含 3 个元素的数组”;
- 每个元素又是 “包含 4 个 int 的一维数组”;
- 可以拆解为:arr[0]、arr[1]、arr[2] 各自是一个长度为 4 的 int 数组。
关键结论:二维数组在内存中是 “连续存储” 的
行指针和列指针
arr 二维数组名,指向第 0 行的首地址,写法: int ( * )[4] &arr[0]
arr[i] 第 i 行的数组名,指向第 i 行第 0 列的地址,写法: int* ,&arr[i][0]
arr[i][j] 第 i 行第 j 列的元素值 类型:int
&arr 整个二维数组的地址 类型:int ()[3][4] -
&arr[i] 第 i 行的地址(指向第 i 行的数组指针)类型: int ()[4] arr + i
arr+i,跳 i 行 行指针
(arr+i)+j 跳过i 行, 跳过 j 列 列指针
实现数组倒序的代码
#include<stdio.h>
void reverse(int *arr,int n)
{
int temp=0;//定义临时变量存放
//找到最后一个下标,也是指针
int* q = arr + n - 1;//内存找到下一个下标
do
{
temp= *arr;
*arr = *q;
*q = temp;
arr++;
q--;
} while (q>arr);
}
void input(int *arr,int n)
{
for (int i = 0; i < n; i++)
{
scanf("%d", arr+i);//=arr[i]
}
}
void output(int* arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d",*(arr+i));
}
}
int main()
{
int p[10];
int s=10;
input(p,s);
reverse(p,s);
output(p,s);
return 0;
}

用指针模拟二维数组
#include<stdio.h>
void print(int(*arr)[3],int sz,int sa)
{
for (int i = 0; i <sz ; i++)
{
for (int j = 0; j <sa;j++)
{
printf("%d",*(*(arr+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][3] = { {1,2,3},{3,2,1},{1,2,3} };
int sz = sizeof(arr) / sizeof(arr[0]);
int sa = sizeof(arr) / sizeof(arr[0]);
print(arr,sz,sa);
}
#include<stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,0,1,2};
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d", *(*(arr + i) + j));
}
printf("\n");
}
}
二维数组的传参
二维数组传参时,必须指定列数(行数可省略),因为编译器需要知道 “一行有多少个元素” 才能计算偏移。
合法的传参写法(以 int arr[3][4] 为例):
// 写法1:直接写二维数组(列数必须写)
void printArr(int arr[][4], int rows) {
for(int i=0; i<rows; i++){
for(int j=0; j<4; j++){
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
// 写法2:用数组指针(更贴合本质)
void printArr(int (*arr)[4], int rows) {
for(int i=0; i<rows; i++){
for(int j=0; j<4; j++){
printf("%d ", (*(arr+i))[j]); // 等价于 arr[i][j]
}
printf("\n");
}
}
// 调用方式
int main() {
int arr[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
printArr(arr, 3); // 传数组名(本质是第0行的地址)
return 0;
}
!!!!!!错误!!!!!!!
// 错误:未指定列数,编译器不知道一行有多少元素
void printArr(int arr[][], int rows, int co// 函数原型
}
// 错误:arr是一维int指针,无法匹配二维数组的数组指针类型
void printArr(int *arr, int rows, int cols) {}
函数指针变量
什么是函数指针变量呢?
根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
函数指针是 “指向函数的指针”,存储函数的入口地址。
// 函数原型
int add(int a, int b){
return a + b;
}
int main(){
// ==函数指针:指向“参数为int,int,返回值为int”的函数
int (*p)(int, int) = add;
// 调用函数的方式:
printf("%d", p(2,3)); // 等价于 add(2,3),输出5
return 0;
}
函数指针变量的写法

函数指针数组
数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组。
那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int( * parr1[3])(int,int);

函数指针数组的应用实例——转移表(简易计算器)
函数指针数组转移表的实现
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 = 0;
int y = 0;
int net = 0;
int input = 1;
int(*p[5])(int x,int y) = {0,add,sub,mul,div};//函数指针数组,存放函数的指针,参数为int返回类型为int
while(input)
{
printf("*********菜单**************\n");
printf("*****1.add 2.sub*******\n");
printf("*****3.mul 4.div*******\n");
printf("********请选择**************\n");
scanf("%d", &input);//1,2,3,4就为函数的下标
if (input >= 1 && input <= 4)
{
printf("输入操作数");
scanf("%d %d",&x,&y);
net = (*p[input])(x, y);//input为函数的下标
printf("%d", net);
break;
}
else if (input == 0)
{
printf("退出");
break;
}
else
{
printf("输入错误\n");
}
}
return 0;
}
完
不积跬步,无以至千里。
437





