文章目录

  • 一:指针入门
  • 二:数组入门
    • (1)数组的内存空间布局
    • (2)区分&arr[0]和&arr
  • 三:指针和数组的关系
    • (1)以指针的形式访问和以数组形式访问
    • (2)为什么C语言要这样设计?
  • 四:指针数组和数组指针
  • 五:多维数组和多级指针
    • (1)二维数组
    • (2)二级指针
    • (3)多级指针
  • 六:数组参数和指针参数
    • (1)一级指针传参
    • (2)一维数组传参
    • (4)二维数组传参
  • 七:函数指针
    • (1)函数指针
    • (2)函数指针数组
    • (3)指向函数指针数组的指针

指针是C语言中的一大重点,有关指针的理解一些基础性使用不在本篇文章介绍范围内,读者可以阅读站内其它博主的文章,写得都比较棒,本文主要是针对指针中的一些重点,难点做一些总结,以及最重要的是和数组的关系

一:指针入门

如果读者能够较深刻理解下面语句的含义,那么对于指针就算是达到入门的标准了

//第一组
int a=10;
int* p=&a;
//第二组
p=10;
int* q=p;
//第三组
*p=10;
int b=*p;
  • 第一组:定义一个整形变量a,并赋值10,然后定义一个指针变量p,用a的地址初始化
  • 第二组:把10赋值给指针变量p,此时指针p指向一个地址为10的地址,然后把p的内容同样赋值给一个同样类型的指针变量q
  • 第三组:*p表示解引用,将p所指向的空间里的内容改为10,然后赋值给变量b

二:数组入门

(1)数组的内存空间布局

数组是整体申请空间的,然后将地址最低的空间,作为a[0]元素


这就是为什么我们访问数组或者用指针访问数组时采用的是++,本质就是其元素排布时按照地址增大方向排布

(2)区分&arr[0]和&arr

首先我们需要搞清楚指针+1究竟是什么含义,看如下代码

int main()
{char* c = NULL;short* s = NULL;int* i = NULL;double* d = NULL;printf("%d\n", c);printf("%d\n\n", c + 1);printf("%d\n", s);printf("%d\n\n", s + 1);printf("%d\n", i);printf("%d\n\n", i + 1);printf("%d\n", d);printf("%d\n\n", d + 1);}

其运行结果如下

因此,指针+1实际加上的是该指针类型的大小

回到正题

int main()
{char arr[10] = { 0 };printf("%p\n", &arr[0]);printf("%p\n", &arr[0]+1);printf("%p\n", &arr);printf("%p\n", &arr+1);
}
  • &arrsizeof(arr)arr表示整个数组,
  • &arr[0]:表示数组首元素的地址,因为[]的优先级更高
  • &arr[0]+1:由于是char类型的数组,所以+1
  • &arr+1:这是数组的地址,可以理解横跨整个数组

还有,当数组名做右值时,代表数组首元素的地址,本质等价于&arr[0];但是,数组名不可以充当左值,能够充当左值的,必须是有空间且可被修改的

三:指针和数组的关系

指针和数组没有关系,他们是完全不同的两套规范,只不过在操作上有交集

(1)以指针的形式访问和以数组形式访问

C语言中保存字符串中有两种形式,如下

int main()
{char* str = "abcdef";//str指针在栈上保存,“abcdef”在字符常量区,不可修改char arr[] = "abcdef";//整个数组都在栈上保存,可以被修改
}

1:分别以指针和以下标的方式访问指针

int len = strlen(str);
for (int i = 0; i < len; i++)
{printf("%c ", *(str + i));//以指针方式访问printf("%c ", str[i]);//以数组方式访问
}
printf("\n");

指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性,但注意它们不是同一个东西

(2)为什么C语言要这样设计?

我们知道数组传参时可以采用这种方式

void Show(int arr[], int num)
{for (int i = 0; i < num; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = { 1,2,3,4,5 };int num = sizeof(arr) / sizeof(arr[0]);Show(arr, num);
}

其中的sizeof(arr)/sizeof(arr[0])语句不能放入在函数内进行,否则将会只打印一个元素,相信初学者在这里也犯过很多错误。

指针和数组互通的好处之一就在这里,因为如果不互通,那么在传参时对于数组这样庞大的东西就会浪费非常多的额外空间,所以干脆在传参时将其降维成指针,直接让指针访问即可

那么既然这样,形参中的int arr[]其实就可以写作int* arr

void Show(int* arr, int num)
{for (int i = 0; i < num; i++) {printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = { 1,2,3,4,5 };int num = sizeof(arr) / sizeof(arr[0]);Show(arr, num);
}

因此:所有的数组在传参时都会降维成指针——指向其内部类型的指针,一维数组当然就是对应数据类型的指针,二维数组就是一维数组的数组指针

四:指针数组和数组指针

指针数组: 它是一个数组,里面的元素均是指针,即int* p1[10]

数组指针: 它是一个指针,指向了一个数组,即int (*p2)[10]

  • []的优先级大于“*”
  • int* p1[10]可以理解为int* [10] p1
  • int (*p2)[10]可以理解为int[10]* p2
  • int (*p[4])[5][4]是一个数组,这个数组存放了四个数组指针p,分别指向含有5个int类型的数组

数组指针赋值时一定要注意

int a[10]={0};
int(*p)[10]=&a;

五:多维数组和多级指针

(1)二维数组

需要注意多维数组其内存空间布局仍然是线性连续递增的

int main()
{char c[4][3] = { 0 };for (int i = 0; i < 4; i++){for (int j = 0; j < 3; j++){printf("&a[%d][%d]:%p\n", i, j, &c[i][j]);}}
}


就二维数组而言,我们认为:二维也可以看做一维数组,只不过其内部元素仍然是一维数组

因此下面这三种地址虽然值是相同的但是其含义完全不同

char c[4][3] = { 0 };
printf("%p\n", &c);//这个“二维”数组的地址
printf("%p\n", c);//“二维”数组中第一个元素,也即第一个数组的地址
printf("%p\n", &c[0][0]);//“二维”数组第一个元素的第一个元素的地址


那么相应的+1操作也有各自的含义

char c[4][3] = { 0 };
printf("%p\n", &c);
printf("%p\n", &c+1);//直接越过12个元素
printf("\n");
printf("%p\n", c);
printf("%p\n", c+1);//越过“1”个元素,也即1维数组,也即3个char
printf("\n");
printf("%p\n", &c[0][0]);
printf("%p\n", &c[0][0]+1);//越过1个char

下面的练习题可以帮助你很好理解上面的概念

int a[3][4] = {0};//求整个数组的大小
printf("%d\n",sizeof(a));//(3×4)×4=48
//求单个元素的小
printf("%d\n",sizeof(a[0][0]))//1×4=4
//二维数组第一个“元素”,即第一个一维数组的大小
printf("%d\n",sizeof(a[0]))//4×4=16
//当sizeof()内部只含有一个数组名时表示整个数组,当内部数组名参与运算时表示单个元素
//因此这里就是第一个元素也即第一个数组的首元素地址进行偏移,仍然是一个指针
printf("%d\n",sizeof(a[0]+1))//4
//第一个元素也即第一个数组的首元素偏移至第一个数组的第二个元素进行解引用,也即a[0][1]
printf("%d\n",sizeof(*(a[0]+1)))//a[0][1],4
//表示该二维数组的第一个数组的地址,然后偏移至第二个数组的地址处,注意仍然是地址
printf("%d\n",sizeof(a+1));//4
//这是第二个数组的地址,对齐解引用相当于第二个数组的数组名
printf("%d\n",sizeof(*(a+1)));//4×4=16
//a[0]表示第一个数组,&a[0]第一个数组的地址,然后+1,偏移值第二个数组的地址处
printf("%d\n",sizeof(&a[0]+1));//4
//同上,它是第二个数组的地址,然后解引用就是第二个数组的大小
printf("%d\n",sizeof(*(&a[0]+1))); //4×4=16
//相当于*(a+0),于是表示第一个数组,然后解引用就是第一个数组的大小
printf("%d\n",sizeof(*a)); //4×4=16
//第四个一维数组
printf("%d\n",sizeof(a[3])); //16


其实以下写法是等价的

  • a[2]等价于*(a+2);
  • a[2][3]等价于*(*(a+2)+3)

(2)二级指针

准确理解以下例子的含义即可

int main()
{int a = 10;
int* p = &a;
int** pp = &p;p = 100;//将p指针的内容改为100
*p = 100;//将p指针指向的空间也即变量a的内容改为100
pp = 100;//pp之前指向了一级指针,现在更改为指向100
*pp = 100;//pp保存的是p的地址,因此现在相当于将p的指向改为了100
**pp = 100;//两次解引用就是修改变量a的内容100}

(3)多级指针

下面的这个例子有助于你深刻理解多级指针,其输出结果为“ink”

#include<stdio.h>int main()
{static char *s[] = {"black", "white", "pink", "violet"};char **ptr[] = {s+3, s+2, s+1, s}, ***p;p = ptr;++p;printf("%s", **p+1);return 0;
}

六:数组参数和指针参数

(1)一级指针传参

由于指针变量的特殊性,所以才传参时很多人就会将其特殊化,但是指针传参也会拷贝,函数内部和外部两个指针不是一个指针

void test(char* p)
{printf("test函数内的p的地址%p\n", &p);
}int main()
{char* p = "hello world";printf("main中p的地址:%p\n", &p);test(p);
}


因此,很多人会犯下面这样的错误

void GetStr(char* p)//传过来一个字符指针,然后为其申请空间,将字符串拷贝进去
{p = malloc(sizeof(char) * 10);strcpy(p, "hello");
}int main()
{char* p = NULL;GetStr(p);//调用该函数相当于执行赋值功能printf("%s\n", p);
}

结果显而易见,什么也不会打印,这是因为传过去的是一个一级指针,依然用一级指针接受的话,发生的是值拷贝,那个字符串拷贝函数只是对局部变量进行拷贝,函数调用结束,局部变量销毁相当于什么也没用做

因此,正确的做法是采用二级指针

void GetStr(char** pp)//使用二级指针接受
{*pp = malloc(sizeof(char) * 10);strcpy(*pp, "hello");//拷贝是就是拷贝到了一级指针对应的空间,这个一级指针是真正的main函数的一级指针
}int main()
{char* p = NULL;GetStr(&p);//传入一级指针地址printf("%s\n", p);
}

(2)一维数组传参

void test(int arr[])//比较常用的写法。注意传过来的是数组,但本质已经降维为了指针
{}
void test2(int arr[10])//同上
{}
void test3(int* arr)//数组名就是首元素地址,可以
{}
void test4(int** arr)//指针数组每个元素都是指针,可以用二级指针
{}
int main()
{int arr[10] = { 0 };//整形数组int* arr2[20] = { 10 };//指针数组
}

(4)二维数组传参

数组传参一定要发生降维,降维成指针,对于二维数组就会降维成数组指针

void test(int(*a)[5])//数组指针
{}int main()
{int a[6][5] = { 0 };test(a);
}

传参时当然可以使用int arr[6][5]这样的形式,由于它是依靠列来确定组数的,所以行可以省略,但是列不能省略,因此二维数组传参还可以这样写

void test(int a[][5])//数组指针
{}int main()
{int a[6][5] = { 0 };test(a);
}

七:函数指针

(1)函数指针

函数是代码的一部分,程序运行时也要加载进内存,以供CPU后续寻址。

函数指针的定义和数组指针基本类似,其定义和调用方式如下

int add(int x, int y)
{int z = x + y;return z;
}int main()
{int a = 10;int b = 20;int(*p)(int, int) = &add;//这个指针指向了一个函数,其参数有两个类型分别为int和int,所指向函数的返回值为intprintf("%d\n", (*p)(a, b));//第一种调用方式printf("%d\n", p(a, b));//第二种调用方式
}

(2)函数指针数组

函数指针数组本质是一个数组,存放的元素类型是函数指针

如下Add()Sub()这两个函数形参列表相同,返回值相同,因此可以将他们的地址存放在函数指针数组中

int Add(int x, int y)
{int z = x + y;return z;
}int Sub(int x, int y)
{int z = x - y;return z;
}int main()
{int(*parr[2])(int, int) = { &Add, Sub };//函数指针数组的定义for (int i = 0; i < 2; i++){printf("%d\n", parr[i](2, 3));//函数指针数组的使用}
}

函数指针数组能够很好的保存一组具有相同参数类型,相同返回值的函数的地址。它的一个经典例子就是“转移表”。比如在计算器例子中,使用switch case语句,如果使用普通方式,要增加一些其他运算时,其case语句要多次增加,显得很臃肿,而运用函数指针数组,则能避免这种情况,且在后期增加新的相同类型的运算时,在主函数内只需增加新函数地址

(3)指向函数指针数组的指针

int arr[10] = { 0 };
int(*p) = arr;//指向整形数组的指针int(*parr[4])(int, int);//函数指针数组
int(*(*pparr)[4])(int, int) = &parr;//指向函数指针数组的指针

【C语言重点难点精讲】C语言指针相关推荐

  1. 【C语言重点难点精讲】关键字精讲

    必读: C语言关键字是一个非常重要的话题,因为它能在相当的程度上将C语言的核心内容串联起来,起到一种提纲挈领的效果 下面的内容重点提及的是相应关键字特别值得注意的地方,这些地方是我们经常忽略的,而且考 ...

  2. 【C语言重点难点精讲】C语言文件

    文章目录 一:文件相关概念 (1)什么是文件 (2)文件名 (3)文件类型 二:文件指针 三:文件的打开和关闭 四:文件的顺序读写 (1)写 (2)读 五:文件的随机读取 (1)fseek (2)ft ...

  3. 【C语言重点难点精讲】C语言内存管理

    文章目录 一:相关动态内存函数 (1)malloc和free (2)calloc (3)realloc 二:进程地址空间 三:常见内存错误 C语言内存管理其实是一个很糟糕的话题,很烦这个,但是没有办法 ...

  4. 【C语言重点难点精讲】C语言预处理

    文章目录 一:C/C++程序程序编译过程 (1)预处理 (2)编译 (3)汇编 (4)链接 二:宏定义 (1)数值宏常量 (2)字符串宏常量 (3)使用宏充当注释 (4)使用宏充当表达式 三:宏其他 ...

  5. 【C语言重点难点精讲】C语言中的重要符号

    文章目录 一:续接符和转义符 (1)续接符 (2)转义字符 二:单引号和双引号 三:逻辑运算符 四:位运算 四:左移右移 五:前置++和后置++ 六:优先级 一:续接符和转义符 (1)续接符 如果一行 ...

  6. r语言 新增一列数字类型_R语言实战之R语言基础语法精讲(一)

    R是用于统计分析.绘图的语言和操作环境.R是属于GNU系统的一个自由.免费.源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具.在学习R数据科学之前,我们首先要对R语言的基础语法有一个良好的了 ...

  7. c语言程序设计冲刺串讲,C语言程序设计冲刺串讲.ppt

    C语言程序设计冲刺串讲.ppt C语言程序设计,冲刺串讲,温馨提示,离考试只有不足4周的时间了,我们的好多学员对自己没有信心,对此我建议大家不要放弃最后的一线希望,奇迹总是出现在最后.为此我们一定要加 ...

  8. R语言实战应用精讲50篇(十六)--如何实现文字云可视化

    前言 本文跟大家分享R语言信息可视化--文字云. R语言可以轻松处理信息可视化,并且很早就有专用的信息可视化包--WordCloud. 以下是我为大家准备的几个精品专栏,喜欢的小伙伴可自行订阅,你的支 ...

  9. R语言实战应用精讲50篇(十八)-R语言实现分词、词频与词云案例解析

    前言 我真的超爱R语言,原因之一就是R有许多已经写好."开箱即用"的程序包可以直接拿来用:要知道,程序包减少了多少工作量.当然,其他语言也有类似的包,但是貌似没那么多.没那么细.这 ...

最新文章

  1. 2022-2028年中国未硫化橡胶制品行业市场运行格局及未来前景展望报告
  2. iOS 图片处理-利用GPUImage 磨皮和美白图片
  3. Pyhon爬虫开发:URLError的使用
  4. 经典C语言程序100例之五七
  5. datagridview绑定数据源不显示_sharding-jdbc系列之 数据源配置(一)
  6. linux—命令汇总
  7. ASP.NETWebPage应用深入探讨
  8. HTTP请求方式: GET和POST的比较
  9. 虚继承 - C++快速入门29
  10. 基于神经网络的指纹识别,指纹比对技术何时出现
  11. 全流程+讲解+避坑指南 第一次使用vulhub搭建漏洞环境
  12. 我看过的世界历史纪录片和科技史、经济史、人类史笔记
  13. Android获取设备号SSAID (Android ID) 和 IMEI
  14. 客户机是计算机网络硬件吗,计算机网络中硬件连接设备有哪些?
  15. 在部队当程序员是什么体验?
  16. socket中pack 和 unpack 的使用
  17. jquery获取已选择和未选择的checkBox项以及清空所选项
  18. R时间序列分析|SP500股指的ARIMA模型预测与残差ARCH效应分析
  19. java 解析 json 索引对象_怎么获取json对象的属性和值
  20. iOS15 beta版本安装不成功 || app要求更新

热门文章

  1. lua软件测试自动化,一种基于Lua脚本的嵌入式软件自动化测试系统及方法专利_专利查询 - 天眼查...
  2. docker 其他电脑访问权限_docker – 从远程计算机连接到容器
  3. requestmapping配置页面后_SpringBoot2.0 基础案例(03):配置系统全局异常映射处理
  4. 嵌入式开发板01---点亮LED
  5. linux系统c++编译连接过程,动态库与静态库
  6. ggforce|绘制区域轮廓-区域放大-寻找你的“onepiece”
  7. 你的数据可也可以发三篇NAR的文章
  8. python pygame模块_python中pygame模块用法实例
  9. 简单英文题 24 Divisor and Multiple(python)
  10. 第30课 棋盘上的学问 《小学生C++趣味编程》