保姆式教学--指针的进阶-创新互联
共和国的的建设者可要好好学好指针哦!
文章目录- 引入!
- 1、 字符指针
- 2、 指针数组
- 3、 数组指针
- 3.1 数组指针的定义
- 3.2 &数组名VS数组名
- 3.3 数组指针的使用
- 4、 数组传参和指针传参
- 4.1一维数组传参
- 4.2二维数组传参
- 4.3一级指针传参
- 4.4二级指针传参
- 5、 函数指针
- 6、 函数指针数组
- 7、 指向函数指针数组的指针
- 8、 回调函数
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
5. 指针的运算。
1、 字符指针在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:
int main()
{char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有一种使用方式如下:
int main()
{const char* pstr = "hello bit.";//这里并不是把一个字符串放到pstr指针变量里,而是将字符串的首字符的地址放到了pstr中。
printf("%s\n", pstr);
return 0;
}
`直接上题目练习,在题目中深度了解字符指针
//输出什么?
#includeint main()
{char str1[] = "hello bit.";//————————1
char str2[] = "hello bit.";//————————2
const char *str3 = "hello bit.";//————————3
const char *str4 = "hello bit.";//————————4
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
答案:
分析:1和2分别创建了一个全新的数组,再进行比较的时候,操作系统会对他们的本质,即首字符地址,进行比较,既然是两个数组,那么他们的首字符地址一定不同(双胞胎都有不同的基因),所以拿1和2进行比较的时候会输出“str1 and str2 are not same”
str3是一个字符指针,指针的本质是字符串首字符的地址,str3、str4两个指针指向的都是同一个字符串的首字符的地址,所以str3和str4是相同的。
如何理解?:一家人有一个共同的家,通过一家三口中的每一个人我们都能找到这个家。
所以此时会输出“str3 and str4 are same”
再来一题关于const的使用的重要性
int main()
{char* pstr= "hello bit.";
*pstr = "666";
printf("%s\n", pstr);
return 0;
}
输出:
为什么这里我们没有任何输出?
分析:字符串首字符的地址首先传给了pstr,程序继续进行,走到27行,pstr被更改了,但是指针pstr仍是“h”的地址,内容变为“666”,此时进行输出,操作系统无法判断出输出内容。
假设我们使用了const进行修饰:
int main()
{const char* pstr= "hello bit.";
*pstr = "666";//————5
printf("%s\n", pstr);
return 0;
}
5处在编译器里会有提示,提示表达式左侧必须为可以修改的标量,所以,妈妈再也不用担心我的pstr会被改变了,在实际中大大增强了安全性、实用性。
结论:合理使用const进行限制可以减少某些bug的产生。
类比:
整型数组————存放整形的数组
字符数组————存放字符的数组
得出:
指针数组————存放指针(地址)的数组
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
const char *arr[4]={"EDG","AG","QG","AE"};//字符指针数组
模拟二重数组
#includeint arr1[4]={1,2,3,4};
int arr2[4]={2,3,4,5};
int arr3[4]={3,4,5,6};
int arr4[4]={4,5,6,7};
int *arr[4]={arr1,arr2,arr3,arr4};
for(int i=0;i<=4;i++)
{for(int j=0;j<=4;j++)
{printf("%d",arr[i][j]);
//printf("%d",*(arr[i]+j));
}
}
3、 数组指针
3.1 数组指针的定义数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针
int*p; //整形指针
float*q;//浮点型指针
int(*b)[];//数组指针
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 &数组名VS数组名对于下面的数组:
int arr[10];
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
#includeint main()
{int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
#includeint main()
{int arr[10] = {0 };
printf("arr = %p\n", arr);
printf("arr = %p\n", arr+1);
printf("&arr= %p\n", arr[0]);
printf("arr+1 = %p\n", arr[0]+1);
printf("&arr+1= %p\n", &arr);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
分析:
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义是不一样的.
事实上,&arr表示的是整个数组的地址,而arr仅仅表示数组首元素的地址,因为&arr表示整个数组的地址,所以才有&arr+1跳过整个数组的大小
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
来样例:
#includevoid ErWei(int arr[2][3],int m,int n)
{for(int i=0;ifor(int j=0;j printf("%d",arr[i][j]);
}
printf("\n")
}
}
int main()
{int arr[2][3]={1,2,3,4,5,6};
ErWei(arr,2,3);
return 0;
}
4、 数组传参和指针传参
4.1一维数组传参1:C语言中,当一维数组做函数参数时,编译器总是把它解析成一个指向其首元素的指针。
2:实际传递的数组大小与函数形参指定的数组大小没有关系。
3:数组传参本质上传递的是数组首元素的地址。
4:一维数组传参可以由:
4.1 一维数组接受
4.2 一级或二级指针接受
4.3 指针数组接受
4.2二维数组传参二维数组作为参数传递到函数有三种方式:
1:直接传递
2:指针传递,将二维数组的第一行传递
3:利用二级指针进行传递
如下形式都是允许的:
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算
void test(int (*arr)[5])
int main()
{int arr[3][5] = {0};
test(arr);
}
4.3一级指针传参直接上题目:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
答案:
1、(地址类)一级指针变量
2、(地址类)一维数组数组名
3、(地址类)整型变量
4.4二级指针传参直接上题目
当函数的参数为二级指针的时候,可以接收什么参数?
答案:(本质上都是地址)
1、二级指针变量
2、数组名
3、一级指针变量地址
5、 函数指针先推理:
数组指针:指向数组的指针
int (*p)[10];
函数指针:指向函数的指针
int (*p)(void,void);//p就是一个存放函数地址的指针
再看代码:
#includevoid test()
{printf("hehe\n");
}
int main()
{printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出的是两个地址,这两个地址是 test 函数的地址。
结论:函数名和&函数名都是函数的地址
保存test的方式:
void (*pfun1)()=&test;
写一个简单的函数指针:
#includevoid Add(int x, int y)
{return x + y;
}
int main()
{int (*pf)(int,int) = &Add;
//int (*pf)(int,int)=Add;
int ret = (*pf)(2, 3);
//int ret=pf(2,3);
printf("%d", ret);
}
《C陷阱和缺陷》中的题目:大家做着玩吧
//分析代码题目:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
结束函数指针。
6、 函数指针数组前面我们已经学习了函数指针
int(*p)(void,void);
数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组
在这里理解起来函数指针数组不会是困难的事情,我们先来瞧瞧函数指针数组的样子:
int (*p[10])(void,void)
p 先和 [] 结合,说明 p是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表
直接给兄弟们上例子:
未使用函数指针数组做转移表的计算器是非常臃肿的:
#includevoid menu()
{printf("***********************************\n");
printf("************1.Add 2.Sub***********\n");
printf("************3.Mul 4.Div***********\n");
printf("************5.exit******************\n");
printf("***********************************\n");
printf("***********************************\n");
}
int Add(int x, int y)
{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 main()
{int input = 0;
do
{int ret = 0;
int x, y = 0;
menu();
printf("你要进行什么计算:");
scanf_s("%d", &input);
switch (input)
{case 1:
{ printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
}
break;
case 2:
printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("已退出");
default:
printf("请重新输入");
break;
}
} while (input);
return 0;
}
此时我们加上函数指针数组用作转移表:
#includevoid menu()
{printf("***********************************\n");
printf("************1.Add 2.Sub***********\n");
printf("************3.Mul 4.Div***********\n");
printf("************5.exit******************\n");
printf("***********************************\n");
printf("***********************************\n");
}
int Add(int x, int y)
{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 main()
{int input = 0;
int ret = 0;
int x, y = 0;
do
{menu();
printf("请选择功能:");
scanf_s("%d", &input);
if (input >= 0&&input<=4)
{ printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
int (*pf[5])(int, int) = {NULL,Add,Sub,Mul,Div };
ret = pf[input](x, y);
printf("%d\n", ret);
}
else
{ printf("请重新输入......\n");
}
} while (input);
}
结论:代码2更加简洁,代码量更少,功能实现与1相同,而且2更让你更加便于后期增加功能。
听起来比较绕,但理解起来只需要在函数指针数组的基础上再加上一个指针,如下:
int(*p[10])(int,int);//函数中指针数组
int(*(*pp)[10])(int,int);//指向函数指针数组的指针
是不是很简单啊!
8、 回调函数先来看看来自维基百科的对回调(Callback)的解析:
In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback.
再来看看来自Stack Overflow某位大神简洁明了的表述:
A “callback” is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。到此应该明白回调函数的定义了吧?
机制
⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理
实例:用回调函数改进基础版计算器
#includevoid menu()
{printf("***********************************\n");
printf("************1.Add 2.Sub***********\n");
printf("************3.Mul 4.Div***********\n");
printf("************5.exit******************\n");
printf("***********************************\n");
printf("***********************************\n");
}
int Add(int x, int y)
{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;
}
void calc (int(*pf)(int, int))
{int ret = 0;
int x = 0;
int y = 0;
printf("请输入操作数:");
scanf_s("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{int input = 0;
do
{menu();
printf("你要进行什么计算:");
scanf_s("%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 0:
printf("已退出");
default:
printf("请重新输入");
break;
}
} while (input);
return 0;
}
回调函数结束。
本期就讲到这里。下期见友友们!
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
分享文章:保姆式教学--指针的进阶-创新互联
文章源于:http://hbruida.cn/article/cshchc.html