指针详解
既然在32位系统下指针的大小都是4个字节,那不同数据类型的指针有什么区别?
指针类型决定了指针在进行解引用时,能够访问的空间的大小
int*类型的指针,能够访问4个字节;
char*类型的指针,能够访问1个字节;
double*类型的指针,能够访问8个字节
#include<stdio.h>
int main(void)
{
int a = 0x11223344;//16进制,两个数字即为32位,1个字节
int b = 0x11223344;
int* p_a = &a;//p_a能访问4个字节
char* p_b = &b;//p_b只能访问1个字节
*p_a = 0;//打断点可以看到,p_a对应的地址中,内容为00 00 00 00
*p_b = 0;//p_b对应的地址中,内容为00 33 22 11
getchar();
return 0;
}
同时指针类型也决定了,指针的步长
int*类型的指针p,p+1,向后移动4个字节
char*类型的指针,p+1,向后移动1个字节
double*类型的指针,p+1,向后移动8个字节
野指针
1.指针未初始化,未初始化的指针,里面放的是一个随机值
2.指针越界访问。比如指针指向一个数组,通过指针对数组进行操作的时候,超过了数组的长度,这样指针操作的就是未知的内存空间
3.指针指向的内存释放
如果不需要使用此指针了,可以把指针置为NULL,但是此时要注意,置为NULL之后,是不能给此指针指向的内存地址赋值的
int* p = NULL;
*p = 10;//这是错误的,NULL对应的地址是不能被访问的
利用指针,简单实现strlen,求字符串长度
#include<stdio.h>
int myStrlen(char* arr){
char* start = arr;
char* end = arr;
while(*end != '\0'){
end++;
}
return end - start;
}
int main(void)
{
char arr[] = "abc";
int length = myStrlen(arr);
printf("%d", length);
getchar();
return 0;
}
指针的比较
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
#include<stdio.h>
int main(void)
{
int arr[] = {1,2,3};
int* p_arr;
//这种for循环较优,符合标准规定
for(p_arr = &arr[3]; p_arr > &arr[0]; p_arr--){
*p_arr = 0;
printf("%d\n", *p_arr);
}
getchar();
return 0;
}
#include<stdio.h>
int main(void)
{
int arr[] = {1,2,3};
int* p_arr;
//这种for循环就不符合标准规定,但是也能执行,不会报错
for(p_arr = &arr[2]; p_arr >= &arr[0]; p_arr--){
*p_arr = 0;
printf("%d\n", *p_arr);
}
getchar();
return 0;
}
指针与数组
数组名和&数组名是不一样的
#include<stdio.h>
int main(void)
{
int arr[10] = {0};
printf("%p\n", arr);//输出的是数组首元素的地址
printf("%p\n", arr + 1);//地址向后挪4个字节
printf("%p\n", &arr[0]);//输出的是数组首元素的地址
printf("%p\n", &arr[0] + 1);//地址向后挪4个字节
printf("%p\n", &arr);//输出的是数组(整个数组)的地址
printf("%p\n", &arr + 1);//地址向后挪40个字节
getchar();
return 0;
}
二级指针
#include<stdio.h>
int main(void)
{
int a = 10;
int* p_a = &a;
int** p_p_a = &p_a;//p_p_a指的就是二级指针
//二级指针的解引用,也和普通指针的解引用类似
printf("%p\n", *p_a);//输出的是a变量的值
printf("%p\n", p_a);//输出的是p_a的地址
printf("%p\n", *p_p_a);//输出的是p_a的地址
printf("%p\n", **p_p_a);//输出的是a变量的值
getchar();
return 0;
}
对于指针中的*,可以这样理解,*后面的指针变量的类型是*前面数据类型的类型。
比如int** p_p_a = &p_a,p_p_a是int*类型的指针;int* p_a = &a,p_a是int类型的指针
结构体传参给函数的时候,参数是需要压栈的,如果直接传递结构体对象,对象是有大小的,如果结构体对象过大,压栈的开销就比较大,所以结构体传参一般是传递指针(结构体的地址)
指针详解
字符指针
字符指针char*
#include<stdio.h>
int main(void)
{
char c1[] = "abcd";//常量字符串
char c2[] = "abcd";
char* pc1 = "abcd";//将常量字符串的首字符的地址存放到指针变量中
char* pc2 = "abcd";
printf("%d\n", c1==c2);//输出0
printf("%d\n", pc1==pc2);//输出1
getchar();
return 0;
}
C语言中,会将常量字符串单独存储到内存的一个区域中,当几个指针指向同一个字符串的时候,实际会指向同一块内存。但是将相同的字符串赋值给数组时,会开辟不同的内存空间
指针数组
存放指针的数组,可以叫指针数组
int *p[10];//p是一个数组,能存放10个int类型变量的指针
数组指针
能够指向数组的指针,称为数组指针
int (*p)[10];//p会先和*结合,所以*p是一个指针,指向的数组中有10个int类型的变量
[]的优先级是要高于*的,所以int *p[10]表示的是一个指针数组
#include<stdio.h>
int main(void)
{
int* arr[5];
//(*p)后面跟的[]的值,一定要与指向的数组的大小一致
int* (*p)[5] = &arr;//要*p能够指向int*类型的数组arr,那么*p指针的类型也需要是int*
getchar();
return 0;
}
注意*p指向的是数组首元素的地址,而不是整个数组的地址(除了取数组名&arr,sizeof(arr)这两种情况,其他的一般都是拿到数组首元素地址),&arr是拿到整个数组的地址(数值上和数组首元素的地址一样),sizeof(arr)是计算整个数组的长度
举例说明,数组的解引用
int main(void)
{
int arr[3] = {1,2,3};
int *p = arr;//p保存的是数组首元素地址,arr可以等同于p
int i = 0;
for(i = 0; i < 3; i++){
printf("%d ", p+i);//p+i是指向数组中每个元素
//*(p+i),解引用,获取指针指向的每个元素。*(p+i)等同于*(arr+i)
//而数组的每个元素可以通过arr[i]的方式获取,所以arr[i]等同于*(arr+i)也等同于*(p+i)也等同于p[i]
printf("%d ", *(p+i));
}
getchar();
return 0;
}
更复杂的例子
#include<stdio.h>
//参数是指针的形式
void printTest(int (*p)[5], int x, int y){
int i = 0;
int j = 0;
printf("%d\n", p);//数值上等于&arr
printf("%d\n", p+1);//相比于p的地址值,p+1的地址值后移了20个字节,即5个数组元素的大小,即后移了一个一维数组的大小
for(i = 0; i < x; i++){
for(j = 0; j < y; j++){
//p是指向arr[3][5]的首元素的地址,而这种二维数组的首元素是一维数组
//p+i,是二维数组的数组指针后移一个单位,从指向第一个一维数组变成指向第二个一维数组,所以p解引用表示第一个一维数组的地址(首元素的地址),p+1解引用表示第二个一维数组的地址(首元素的地址)
//再接下来,*(p+i)+j就是在一维数组中指针的移动,逐个指向一维数组中每个元素
//*(p+i) == p[i],*(*(p+i)+j) == *(p[i]+j) == p[i][j]
printf("%d ", *(*(p+i)+j));//等同于printf("%d ", (*(p+i))[j]);
}
printf("\n");
}
}
//参数是数组的形式
void printTest(int p[3][5], int x, int y){
int i = 0;
int j = 0;
for(i = 0; i < x; i++){
for(j = 0; j < y; j++){
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main(void)
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
printTest(arr,3,5);
printf("%d\n", &arr[0]);//数组的首元素的地址值
printf("%d\n", &arr);//数组的地址值,数值上等于数组的首元素的地址值
printf("%d\n", &arr + 1);//相比于&arr的地址值,&arr+1的地址值后移了60个字节,即15个数组元素的大小,即后移了整个数组的大小
getchar();
return 0;
}
判断这个是个啥
int (*parr[10])[5]
[]的优先级很高,所以parr[10]是一个数组,该数组有10个元素
(*parr[10])表示这是一个数组指针
(*parr[10])[5]表示这个数组指针指向的数组有5个int类型的元素
所以总的来说,这是一个数组类型的数组指针,数组的大小为10,数组中每个元素都是一个指向5个元素的数组的数组指针
类比来说
int (*parr)[5],parr是数组指针,将parr去掉,剩下的是parr的指针类型,类型为:int (*)[5]
int (*parr[10])[5],parr[10]是数组,此数组中的10个元素,都是数组指针,把parr[10]去掉,剩下的是parr[10]的指针类型,类型为:int (*)[5]
数组的传参
一维数组传参
//test的参数可以是int* arr,也可以是int arr[]
//int* arr表示的是数组的首元素地址
void test(int* arr){
}
int main(void)
{
int arr[3] = {1,2,3};
test(arr);//传的是数组的首元素的地址
getchar();
return 0;
}
指针数组的传参
//test的参数可以是int** arr,也可以是int* arr[]
//int** arr表示的是int* arr[]数组的首元素地址
void test(int** arr){
}
int main(void)
{
int* arr[3];
test(arr);//传的是数组的首元素的地址
getchar();
return 0;
}
二维数组传参
//test的参数可以是int (*arr)[5],也可以是int arr[][5]
//int arr[][5]作为参数时,第一个[]行数可以省略,但是第二个[]每行元素的个数不能省略
//int (*arr)[5],数组指针,表示的是二维数组首元素的地址,二维数组的首元素是一维数组
void test(int arr[][5]){
}
int main(void)
{
int arr[3][5] = {0};
test(arr);//传的是二维数组的首元素的地址
getchar();
return 0;
}
指针的传参
当函数参数是二级指针时
//当函数参数是二级指针时
void test(int** p){
}
int main(void)
{
int* p;
int** p1 = &p;
int* arr[10];
test(p1);//当函数参数是二级指针时,可以传一级指针的地址
test(arr);//也可以传一级指针数组的数组名(数组首元素的地址)。因为首元素是一级指针,所以传递的时候是取首元素的地址传递,传递时变为二级指针
getchar();
return 0;
}
函数指针
函数指针-指向函数的指针
int ADD(int x, int y){
return x + y;
}
int main(void)
{
//函数指针的定义方式和数组指针的定义方式类似
//(*p)表示这是一个指针,(*p)后面的括号表示函数的参数类型,(*p)指针的类型即函数的返回类型
int (*p)(int, int) = ADD;
printf("%p ", ADD);//函数名和&函数名,都是函数的地址
printf("%p ", &ADD);
//函数指针调用的时候,*加不加都一样
printf("%d ", p(2,3));//不加*,因为函数名就是一个指针,p等价于ADD,p(2,3)等价于ADD(2,3)
printf("%d ", (*p)(2,3));//加*,解引用,*p就是ADD()函数
printf("%p ", *p);//输出的是函数的地址
getchar();
return 0;
}
复杂的表达式1
(*(void (*)())0)()
void (*)()表示的是函数指针类型
(void (*)())0表示的是将0强转为这种函数指针类型,再加*解引用表示调用这个函数
所以总表达式的表示,调用0地址处的该函数
复杂的表达式2
//test是一个函数声明
//test函数的参数有两个,第一个是int类型,第二个是函数指针类型,该函数指针指向的函数,参数为int类型,返回值为void类型
//test函数的返回值类型为函数指针类型,该函数指针指向的函数的参数是int类型,返回值为void类型
void (* test(int, void(*)(int)))(int);
//简化写法
//pfun_test就是函数指针void(* )(int)的别名
typedef void(* pfun_test)(int);//typedef定义函数指针的别名时,不能写在函数指针的后面,需要写在函数指针的里面
pfun_test test(int, pfun_test);//用别名来定义test函数
函数指针数组
用于存放函数指针的数组,即为函数指针数组
int (*parr[10])(int);
[]先与parr结合,所以parr是一个数组,数组大小为10
回调函数
回调函数就是一个通过函数指针调用的函数。如果把函数A的指针(地址)作为函数参数传递给函数B,当函数C调用函数B并传入函数A的指针(地址),此时通过函数A的指针(地址)调用了函数A,此时就将函数A称为回调函数。回调函数不由函数的实现方直接调用,在特定的事件发生时由另一方调用,用于对该事件进行响应。
指向函数指针数组的指针
int main(void)
{
int (*p_arr[5])(int, int);//函数指针数组
//int (*(*p)[5])(int, int)中,(*p)表示的是指针,而除了(*p)的部分,就是指针的类型
int (*(*p)[5])(int, int) = &p_arr;//p是指向函数指针数组p_arr的指针,称为函数指针数组的指针。p指向的函数指针数组有5个元素,每个元素都是int (*)(int, int)类型的函数指针
getchar();
return 0;
}
冒泡排序,实践指针的应用
#include<stdio.h>
//无论传入的什么类型的数据,只要知道数据的字节数,即传入的length参数
//就可以用char类型的指针去移动这么多字节数,在移动的过程中,两两交换char类型的指针指向的字节
//char类型的指针遍历完成后,两个数据的全部字节都被两两交换,即完成了两个数据的交换
void swap(char* p1, char* p2, int length){
int i = 0;
char temp = 0;
for(; i < length; i++){
temp = *p2;
*p2 = *p1;
*p1 = temp;
p1++;
p2++;
}
}
/*
* 冒泡排序,能够对任意类型的数组进行排序
* arr 数组首元素的地址
* size 数组的长度
* length 数组中每个元素的字节数
* cmp 自定义的排序函数
*/
void bubble_sort(void* arr, int size, int length, int(*cmp)(void* p1, void* p2)){
int i = 0;
int j = 0;
for(i = 0; i < size; i++){
for(j = 0; j < size -i; j++){
if(cmp((char*) arr+(j-1)*length, (char*) arr+j*length) > 0){
swap((char*) arr+(j-1)*length, (char*) arr+j*length, length);
}
}
}
}
//自定义的用于比较int类型数组的函数
//int cmp_int(void* p1, void* p2){
// return *(int*)p1 - *(int*)p2;
//}
//自定义的用于比较float类型数组的函数
int cmp_float(void* p1, void* p2){
return (*(float*)p1 - *(float*)p2);
}
int main(void)
{
//int arr[] = {6,5,4,3,2,1};
float arr[] = {3.0, 6.0, 5.0, 1.0, 2.0, 4.0};
int size = sizeof(arr) / sizeof(arr[0]);
int length = sizeof(arr[0]);
int i = 0;
int j = 0;
for(;i < size; i++){
printf("%f", arr[i]);
}
printf("\n");
bubble_sort(arr, size, length, cmp_float);
for(;j < size; j++){
printf("%f", arr[j]);
}
getchar();
return 0;
}