从零开始的C语言: 指针深入理解从入门到实践(中)指针与数组的结合

2025博客之星年度评选已开启 10w+人浏览 702人参与

指针深入理解(中)

  • 数组名的理解

  • 指针访问数组

  • 指针与一维数组

  • 一维数组传参的本质

  • 二级指针

  • 冒泡排序

  • 指针数组

  • 数组指针

  • 指针数组模拟一 / 二维数组

  • 函数指针

  • 函数指针数组

  • -转移表

前言

前面我们搞懂了指针的基础和运算,这篇要拆解 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;

}


不积跬步,无以至千里。

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值