扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的内存单元,可以说地址指向该内存单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
#includeint main()
{int a = 10;//在内存中开辟一块空间
int* p = &a;//将a的地址取出,放到指针变量p中
return 0;
}
指针的大小对于32位的机器,即有32根地址线,因为每根地址线能产生正电(1)或负电(0),所以在32位的机器上能够产生的地址信号就是32个0/1组成的二进制序列:一共 232 个地址。
同样的算法,在64位的机器上一共能产生 264 个不同的地址。
232 可以用32个bit位进行存储,而8个bit位等价于1个字节,所以在32位的平台下指针的大小为4个字节。
264 可以用64个bit位进行存储,所以在64位的平台下指针的大小为8个字节。
指针类型在32位平台下指针的大小为4个字节,在64位平台下指针的大小为8个字节。
指针有哪些类型?
指针的定义方式是type + *
char * 类型的指针存放的是char类型的变量地址;
int * 类型的指针存放的是int类型的变量地址;
float * 类型的指针存放的是float类型的变量地址等。
1.指针±整数
2.指针解引用
指针的类型决定了指针解引用的时候能够访问几个字节的内容。
总结:
概念:野指针就是指向位置是不可知的(随机的、不正确的、没有明确限制的)指针。
野指针的成因
1.指针未初始化
2.指针越界访问
3.指针指向的空间被释放
我们知道,指针变量是用于存放地址的变量。但是指针变量也是变量,是变量就有地址,那么存放指针变量的地址的变量是什么呢?
其实,存放普通变量的地址的指针叫一级指针,存放一级指针变量的地址的指针叫二级指针,存放二级指针变量地址的指针叫三级指针,以此类推。
#includeint main()
{int a = 10;
int* p1 = &a;
int** p2 = &p1;
return 0;
}
在这里,我们用一级指针p1存放了普通常量a的地址,用二级指针p2存放了一级指针p1的地址。
这时如果我们要得到a的值,就有两种方法:
方法一:对一级指针p1进行一次解引用操作即可得到a的值,即*p1。
方法二:对二级指针p2进行一次解引用操作即可得到p1的值,而p1的值就是a的地址,所以再对p2进行一次解引用操作即可得到a的值,也就是对二级指针p2进行两次解引用操作即可得到a的值,即**p2。
例:
#includeint main()
{const char* p = "hello csdn.";
printf("%c\n", *p);//打印字符'h'
printf("%s\n", p);//打印字符串"hello csdn."
return 0;
}
指针数组注意:常量字符串与普通字符串大的区别是,常量字符串是不可被修改的字符串,既然不能被修改,那么在内存中没有必要存放两个一模一样的字符串,所以在内存中相同的常量字符串只有一个。
指针数组也是数组,是用于存放指针的数组。
int* arr3[5];//数组arr3包含5个元素,每个元素是一个一级整型指针。
举例:
#includeusing namespace std;
int main()
{char a = 'm';
char* p =&a;
char* arr[1];
arr[0] = p;
cout<< *arr[0]<< endl;
return 0;
}
我们定义了一个p指针指向字符a,将p存到了指针数组arr[0]中,然后对arr[0]解引用取到了字符’m’。
数组指针数组指针就是指向数组的指针
#includeint main()
{int arr[10] = {0 };
int(*p)[10] = &arr;
//()优先级高,说明表示指针,是一个数组指针
return 0;
}
&数组名 VS 数组名对于一个数组的数组名,它什么时候代表数组首元素的地址,什么时候又代表整个数组的地址呢?
数组名代表整个数组的地址的情况其实只有两种:
除此之外,所有的数组名都是数组首元素地址。
比如:
int arr[5] = {1, 2, 3, 4, 5 };
对于该数组arr,只有以下两种情况数组名代表整个数组的地址:
&arr;
sizeof(arr);//arr单独放在sizeof内部
应用#includeint main()
{int arr[5] = {1, 2, 3, 4, 5 };
int* p1 = arr;//数组首元素的地址
int(*p2)[5] = &arr;//数组的地址
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p1+1);
printf("%p\n", p2+1);
return 0;
}
一维数组传参
#includevoid test1(int arr[10])//数组接收
{}
void test1(int *arr)//指针接收
{}
void test2(int *arr[20])//数组接收
{}
void test2(int **arr)//指针接收
{}
int main()
{int arr1[10] = {0 };//整型数组
int *arr2[20] = {0 };//整型指针数组
test1(arr1);
test2(arr2);
}
整型数组:
当向函数传入整型数组的数组名时,我们有以下几种参数可供接收:
整型指针数组:
当向函数传入整型指针数组的数组名时,我们有以下几种参数可供接收:
注意:一维数组传参,函数形参设计时[ ]内的数字可省略。
二维数组传参
#includevoid test(int arr[][5])//数组接收
{}
void test(int(*arr)[5])//指针接收
{}
int main()
{int arr[3][5] = {0 };//二维数组
test(arr);
}
当向函数传入二维数组的数组名时,我们有以下几种参数可供接收:
注意:二维数组传参,函数形参的设计只能省略第一个[ ]内的数字。
一级指针传参
#includevoid print(int* p, int sz)//一级指针接收
{int i = 0;
for (i = 0; i< sz; i++)
{printf("%d ", *(p + i));
}
}
int main()
{int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;//一级指针
print(p, sz);
return 0;
}
二级指针传参
#includevoid test(int** p)//二级指针接收
{}
int main()
{int a = 10;
int* pa = &a;
int** paa = &pa;
test(paa);//二级指针
return 0;
}
当我们传入的参数为二级指针时,我们可以用二级指针的形参对其进行接收,那么当函数形参为二级指针的时候,我们可以传入什么样的参数呢?
#includevoid test(int** p)
{}
int main()
{int a = 10;
int* pa = &a;
test(&pa);//可以传入一级指针的地址
int** paa = &pa;
test(paa);//可以传入二级指针
int* arr[10];
test(arr);//可以传入一级指针数组的数组名
//...
return 0;
}
总而言之,只要传入的表达式最终的类型是二级指针类型即可传入。
函数指针函数指针的定义
函数指针就是指向函数的指针。
和学习数组指针一样,学习函数指针我们也需要知道三点:
例子:
#includeint Add(int x, int y)
{return x + y;
}
int main()
{int(*p)(int, int) = &Add;//取出函数的地址放在函数指针p中
return 0;
}
那么,函数指针p的类型我们是如何创建的呢?
函数指针的使用
知道了如何创建函数指针,那么函数指针应该如何使用呢?
1.函数指针的赋值
int(*p)(int, int) = &Add;
int(*p)(int, int) = Add;
2.通过函数指针调用函数
方法一:我们知道,函数指针存放的是函数的地址,那么我们将函数指针进行解引用操作,便能找到该函数了,于是就可以通过函数指针调用该函数。
#includeint Add(int x, int y)
{return x + y;
}
int main()
{int a = 10;
int b = 20;
int(*p)(int, int) = &Add;
int ret = (*p)(a, b);//解引用找到该函数
printf("%d\n", ret);
return 0;
}
方法二:我们在函数指针赋值中说到,函数名和&函数名都代表函数的地址,我们可以赋值时直接赋值函数名,那么通过函数指针调用函数的时候我们就可以不用解引用操作符就能找到函数了。
#includeint Add(int x, int y)
{return x + y;
}
int main()
{int a = 10;
int b = 20;
int(*p)(int, int) = Add;
int ret = p(a, b);//不用解引用
printf("%d\n", ret);
return 0;
}
函数指针数组函数指针数组的定义
我们知道,数组是一个存放相同类型数据的空间,我们已经认识了指针数组,比如:
int* arr[10];//数组arr有10个元素,每个元素的类型是int*
那如果要将一系列相同类型的函数指针存放到一个数组中,那么这个数组就叫做函数指针数组,比如:
int(*pArr[10])(int, int);
//数组pArr有10个元素,每个元素的类型是int(*)(int,int)
函数指针数组的使用 - 模拟计算器函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。
- 比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[]即可,int(*pArr[10])(int,int)。
函数指针数组一个很好的运用场景,就是计算机的模拟实现:
#includevoid menu()
{printf("|-----------------------|\n");
printf("| 1.Add 2.Sub |\n");
printf("| 3.Mul 4.Div |\n");
printf("| 0.exit |\n");
printf("|-----------------------|\n");
}//菜单
double Add(double x, double y)
{return x + y;
}//加法函数
double Sub(double x, double y)
{return x - y;
}//减法函数
double Mul(double x, double y)
{return x*y;
}//乘法函数
double Div(double x, double y)
{return x / y;
}//除法函数
int main()
{int input = 0;
double x = 0;//第一个操作数
double y = 0;//第二个操作数
double ret = 0;//运算结果
double(*pArr[])(double, double) = {0, Add, Sub, Mul, Div };
//函数指针数组-转移表
int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
do
{menu();
printf("请输入:>");
scanf("%d", &input);
if (input == 0)
printf("退出程序\n");
else if (input >0 && input< sz)
{ printf("请输入两个操作数:>");
scanf("%lf %lf", &x, &y);
ret = pArr[input](x, y);
printf("ret=%lf\n", ret);
}
else
printf("选择错误,请重新选择!\n");
} while (input);//当input不为0时循环继续
return 0;
}
代码中,函数指针数组存放的是一系列参数和返回类型相同的函数名,即函数指针。将0放在该函数指针数组的第一位是为了让用户输入的数字input与对应的函数指针下标相对应。
该代码若不使用函数指针数组,而选择使用一系列的switch分支语句当然也能达到想要的效果,但会使代码出现许多重复内容,而且当以后需要增加该计算机功能时又需要增加一个case语句,而使用函数指针数组,当你想要增加计算机功能时只需在数组中加入一个函数名即可。
既然存在函数指针数组,那么必然存在指向函数指针数组的指针。
int(*p)(int, int);
//函数指针
int(*pArr[5])(int, int);
//函数指针数组
int(*(*pa)[5])(int, int) = &pArr;
//指向函数指针数组的指针
pa就是一个指向函数指针数组的指针,该函数指针数组中每个元素类型是int(*)(int, int)。
回调函数回调函数的定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
举个简单的例子:
void test1()
{printf("hello\n");
}
void test2(void(*p)())
{p(); //指针p被用来调用其所指向的函数
}
int main()
{test2(test1);//将test1函数的地址传递给test2
return 0;
}
在该代码中test1函数不是由该函数的实现方直接调用,而是将其地址传递给test2函数,在test2函数中通过函数指针间接调用了test1函数,那么函数test1就被称为回调函数。
回调函数的使用 - qsort函数其实回调函数并不是很难见到,在用于快速排序的库函数qsort中便运用了回调函数。
void qsort(void*base,size_t num,size_t width,int(*compare)(const void*e1,const void*e2));
列如,我们要排一个整型数组:
#includeint compare(const void* e1, const void* e2)
{return *((int*)e1) - *((int*)e2);
}//自定义的比较函数
int main()
{int arr[] = {2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };
int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
qsort(arr, sz, 4, compare);//用qsort函数将arr数组排序
return 0;
}
最终arr数组将被排为升序。
注意:qsort函数默认将待排序的内容排为升序,如果我们要排为降序可将自定义的比较函数的两个形参的位置互换一下即可。
在qsort函数中我们传入了一个函数指针,最终qsort函数会在其内部通过该函数指针调用该函数,那么我们的这个自定义比较函数就被称为回调函数。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流