温馨提示:这篇文章已超过468天没有更新,请注意相关的内容是否还可用!
摘要:本文介绍了指针的基本概念和应用,从入门到熟练掌握的过程。通过逐步学习指针的定义、语法和用法,读者可以逐渐掌握指针在实际编程中的应用,包括指针与数组、指针与函数等高级应用。掌握指针是编程的重要一环,本文旨在为初学者提供指南,帮助他们在指针的学习和实践中逐步成长为熟练掌握指针的高手。
✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转C语言
💬保持学习、保持热爱、认真分享、一起进步!!
目录
- 前言
- 一. 什么是指针
- 1.地址如何产生
- 2.指针变量和指针和地址关系
- 3.指针的大小和类型
- 4.指针的运算
- 5.二维指针
- 6.字符指针
- 7.指针的类型,与指向元素的类型
- 二.野指针
- 1.野指针怎么来
- 2.如何规避野指针
- 三.指针与一维数组(要理解后面这里很重要)
- 1.数组名
- 2.指针与数组深度理解
- 3.数组的类型
- 四.指针数组
- 1.指针数组的概念
- 2.指针数组的应用
- 五.数组指针
- 1.数组指针的概念
- 2.指向指针数组的指针
- 3.数组指针与二维数组
- 六.数组与指针传参
- 1.一维数组传参
- 2.二维数组传参
- 3.一级指针传参
- 4.二级指针传参
- 5.总结
- 七.函数指针
- 1.函数的地址
- 2.定义一个函数指针
- 3.函数指针数组
- 4.用函数指针数组实现简易计算器
- 5.指向函数指针数组的指针
- 八.回调函数
- 用回调函数实现简易计算器
- 九.汇总
前言
指针就是C语言的灵魂,想要学好C语言指针这一关必须过,既然是灵魂必须难度必然不小,但并没有想象中那么难,向学好指针必须要学会类比,深入理解你会发现都是按照套路来的,学指针前字符串,一维数组,二维数组,函数,指针的类型,数组的类型,函数的类型等基本概念弄清。
一. 什么是指针
在计算机中,所有的数据都是存放在存储器中的,不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址而这个地址就是指针,后续可以通过指针来访问内存单元的内容
1.地址如何产生
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元, 每个内存单元的大小是1个字节 。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
如何产生地址:
若是32位的操作系统,则有32地址线/数据线,就可以简单理解成电线则没根地址线都可以产生高电平和低电平也就是数字信号 1 和 0,这也是计算机只能存储二进制数的原因。
变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。
2.指针变量和指针和地址关系
我们对整形变量已经变成熟悉,整形变量是存放整形的数据的变量,指针前面已经说了,指针就等于地址,而指针变量当然是存放指针的变量,而因为指针等于地址,则指针变量就是存放地址的变量
int * p=&a 这里指针变量p可以存储整型变量的地址p类型为 int* 这里&a为整型变量的地址的类型也为 int*
既然存储了地址那这么将地址的内容取出来呢,这里就涉及到一个解引用操作符 * 在指针变量前面加上一个 *就可以取出地址对应的内存单元的内容取出来。
3.指针的大小和类型
指针就是地址,而地址又有地址线产生,则地址的大小取决于是多少位的平台,若是32位的系统则地址也是32位的二进制数则大小就是4个字节,若存储一个地址则需要4个内存单元。若是64位的系统则地址也是64位的二进制数则大小就是8个字节,若存储一个地址则需要8个内存单元。
这里以32位系统为例
这里不同指针类型大小都为4个字节,因为只要是地址不管什么类型就是4或8的字节的大小,但是为什么要分不同的指针类型呢。
char* 类型的指针变量是为了存放 char 类型变量的地址。 short* 类型的指针变量是为了存放 short 类型变量的地址。 int* 类型的指针变量是为了存放 int 类型变量的地址。
1.指针的类型决定了,对指针解引用的时候可以操作几个字节。
int *类型的指针变量
char *类型的指针变量
int *类型的指针变量解引用,一下可以操作4个字节将4的字节的内容全部变成0,而char *类型的指针变量解引用,一下可以只能操作1个字节让一个字节的内容变为0。
2.指针的类型决定了指针向前或者向后走一步地址变化的大小。
int *类型的指针变量加一 向后偏移了4个地址,也就是跳过了4个字节内存单元 int *类型的指针变量加一 向后偏移了1个地址,也就是跳过了1个字节内存单元
用指针给数组赋一个初值
4.指针的运算
指针可以加减整数
指针关系运算(比较大小)
- 指针减指针
前提条件:必须要在内存中连续存储的元素,比如数组
*指针减指针的值为 (指针-指针)/sizeof(type ),等于元素个数的偏移量。
也就是说从下标为0的元素到下标为9的元素,偏移量为9
指针减指针实现strlen函数
//指针减指针实现strlen函数 int My_Strlen( char *str) { char *start=str;//存储初始地址 char *end=str; while(*end!= '
5.二维指针
')//找到'int main() { int a=10; int* pa=&a; int** ppa=&pa; printf("%d\n",**ppa); return 0; }
'地址 { end++; } return end-start;//两个地址中间元素的个数 } int main() { char ch[]="hello"; printf("%d\n",My_Strlen(ch)); return 0; }int main() { int a=10; int* pa=&a; int** ppa=&pa; int*** pppa=&ppa; printf("%d\n",***pppa); return 0; }
不就是指向一维指针的指针嘛
6.字符指针
- 三维指针
char* 类型的指针变量是可以存放 char 类型变量的地址。
后面四维五维…一样的套路
7.指针的类型,与指向元素的类型
二.野指针
- 字符串指针
"abcdefg"本质是首字符的a的地址
像"abcdefg"这种字符串是常量字符串 类型为 const char * ——>字符串的内容不能被修改
看一道面试题
1.野指针怎么来
- 变量地址的类型
这里看不懂没关系,先往后看,看完了在回来你就懂了
2.如何规避野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址, 意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的 。
三.指针与一维数组(要理解后面这里很重要)
- 指针未初始化
- 指针的越界访问
- 指针指向的空间释放
1.数组名
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
- &arr-数组名代表整个数组,取出的是整个数组的地址
- 数组名是首元素的地址
有两种情况例外:
- sizeof(arr)-数组名代表整个数组,计算的是整个数组的大小(字节)
这里在来验证一下&arr 到底是不是整个数组的地址,如果是那&arr的数据类型是什么
结论:&arr是整个数组的地址,而且它的类型为 int (*)[10]—>这个类型的指针就是一个数组指针->指向一个数组的地址
数组类型 :去掉数组名剩余的部分 int arr[10]; ->类型为int[10]表示数组存储10个整形元素 char arr[10];->类型为char[10]表示数组存储10个字符元素 int*arr[10];->类型为int*[10]表示数组存储10个整形指针 int (*parr[10])(int ,int );->类型为int(*[5])(int ,int ) 表示数组存储10个函数指针 //以此类推
首先看一段代码
四.指针数组
数组的在C语言中没有指定的类型但为了方便理解后面数组指针
1.指针数组的概念
一定要理解下面这张图
类型说明符 数组名 [常量表达式]; int arr[5];
2.指针数组的应用
指针数组–>存放指针的数组
指针数组说到底还是一个数组,既然是一个数组就符合数组的特征
先看一下一维数组的定义方式:
五.数组指针
接下来如何定义一个指针数组,很明显要搞定存储元素的类型,既然要存储指针也就是地址,只要搞清楚指针(地址)的类型不就迎刃而解了嘛。
假设我要存储几个整形元素的地址存放到数组里,那这个数组不就是指针数组嘛,而存储的元素类型不就是整形元素地址的类型嘛那不就是int * 嘛就是这么简单。
这里判断到底是数组指针 还是指针数组看数组名与 *结合还是与 [] 结合
- 变量名与[] 结合 -->指针数组
- 变量名与 * 结合 -->指针数组
1.数组指针的概念
- 打印多个数组的内容
既然指针数组可以存放指针,那将多个数组的数组名首地址都存进指针数组不就很好的解决了嘛。
是不是很像二维数组,先理解这个
int main() { int arr[10]={1,2,3,4,5,6,7,8,9,10}; int (*p)[10]=&arr; return 0; }
2.指向指针数组的指针
数组指针–>指向数组的指针
指针数组说到底还是一个指针,既然是一个数组就符合指针的特征
先看一下指针的定义方式:
int* p;–>指向整形变量的指针-指针变量的类型为int * ,存储元素的类型为int char *
char* p;–>指向字符变量的指针-指针变量的类型为char * ,存储元素的类型为char
假设我要定义一个数组指针,指向存储10个整型元素的数组 int arr[10],接下来如何定义一个数组指针,首先我们知道数组指针指向元素是数组,而数组的类型是什么— int [10]
根据我们的逻辑 我们定义出来的数组指针 —> int[10] *parr这样写出来会不会很奇怪 其实正确的写法应该为 int(*p)[10]
用数组指针就可以存储数组的地址,地址就是指针则&arr的类型与数组指针的类型是一致的。
int *arr [10]->是一个指针数组存放10个int*类型的指针
如果要储存一个数组的地址(指针),相应的存储该地址的数组指针的类型应该与数组地址的类型都是 int (*)[10]
利用数组指针打印一个一维数组
3.数组指针与二维数组
- &arr-数组名代表整个数组,取出的是整个数组的地址
现在我要指针数组的地址&arr存储起来,这时是不是需要一个数组指针来存储。
废话不多说看图
这几张图一定一定要细细品味,这样就不会搞不清楚方向
int main() { int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; return 0; }
1.二维数组
二维数组的特点基本与一维数组一致
- 数组名是首元素的地址
这里二维数组的首元素是一个一维数组是第一行,首元素的地址就是第一行的地址
有两种情况例外:
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; void print1(int arr[3][5], int x, int y ) { int j=0; int i=0; for(i=0; i for(j=0; j //printf("%d ",arr[i][j]); //printf("%d ",*(arr[i]+j)); //printf("%d ",(*(arr+i))[j]); printf("%d ",*(*(arr+i)+j)); } printf("\n"); } } void print2(int (*p)[5], int x, int y ) { int j=0; int i=0; for(i=0; i for(j=0; j //printf("%d ",p[i][j]); //printf("%d ",*(p[i]+j)); //printf("%d ",(*(p+i))[j] ); printf("%d ",*(*(p+i)+j)); } printf("\n"); } } int main() { print1(arr, 3 , 5); printf("\n"); print2(arr, 3 , 5); return 0; } return x+y; } int main() { int a=10; int b=20; int (*p)(int , int )=Add; printf("%d\n",p(a,b)); printf("%d\n",(*p)(a,b)); return 0; } pfun_t signal(int, pfun_t); return 0; } return x+y; } int Sub(int x, int y) { return x-y; } int Mul(int x, int y) { return x*y; } int div(int x, int y) { return x/y; } int Xor(int x, int y) { return x^y; } void menu(void) { printf("**********1.加法 2.减法****************\n"); printf("**********3.乘法 4.除法****************\n"); printf("**********5.异或 0.退出****************\n"); printf("****************************************\n"); printf("****************************************\n"); } int main() { int input=0; //定义一个函数指针数组存储各个函数 int (*pf[6])(int , int)={0, Add, Sub, Mul, div, Xor}; menu(); do { int x,y; printf("请选择要进行的运算:"); scanf("%d",&input); if ( 0 printf("请输入两个操作数:"); scanf("%d%d",&x,&y); //选择一个函数进行计算 printf("%d\n", pf[input](x, y)); } else if (input == 0) { printf("退出\n"); } else { printf("选择错误\n"); } }while(input); return 0; } int (*parr[5])(int ,int ); int (*(*pparr)[5])(int ,int)=&parr; return 0; } return x+y; } int Sub(int x, int y) { return x-y; } int Mul(int x, int y) { return x*y; } int div(int x, int y) { return x/y; } int Xor(int x, int y) { return x^y; } void calc(int (*pf)(int , int )) { int x,y; printf("请输入两个操作数:"); scanf("%d%d",&x,&y); printf("%d\n",pf(x,y) ); } void menu(void) { printf("**********1.加法 2.减法****************\n"); printf("**********3.乘法 4.除法****************\n"); printf("**********5.异或 0.退出****************\n"); printf("****************************************\n"); printf("****************************************\n"); } int main() { int input=0; menu(); do { printf("请选择要进行的运算:"); scanf("%d",&input); switch(input) { case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; case 4: calc(div); break; case 5: calc(Xor); break; case 0: printf("退出\n"); break; default: printf("选择错误\n"); break; } }while(input); return 0; }
- sizeof(arr)-数组名代表整个数组,计算的是整个数组的大小(字节)
详细分析二维数组
总结:二维数组的数组名是一个一维数组的地址,也就是第一行的地址
*arr就是第1行第1个元素的地址,详情参考上图
2.用数组指针打印一个二维数组
既然二维数组名是一个一维数组的地址,那不就可以用数组指针来存储这个地址。
- 打印多个数组的内容
- 变量名与[] 结合 -->指针数组
3.数组的类型
- sizeof(arr)-数组名代表整个数组,计算的是整个数组的大小(字节)
2.指针与数组深度理解
- 指针未初始化
- 变量地址的类型
- 字符串指针
- 三维指针
- 指针减指针
还没有评论,来说两句吧...