指针
一、 字符指针
字符指针有两种使用方法
(1)
int main()
{
char ch='w';
char *pc=&ch;
*pc='w';
printf("%c",*pc);//打印'w'
return 0;
}
(2)
int main()
{
char*pstr="hello world";//将"hello world"字符串中第一个字符'h'的地址放入到字符指针pstr中
printf("%s",pstr);//打印hello world
return 0;
}
例题:下列代码会发生什么?
(1)
char* p="abcdef";
*p='c';
(2)
char a1[]="abcdef";
*a1='c';
解释:
(1)报错
//首先会为指针变量p开辟一块空间,
//常量字符串"abcdef"会存储在内存中的只读数据区(值不可以改变),
//会将'a'的地址存到p而不是"abcdef"存到p中,
//因为p中存放的是a的地址,对p解引用,赋值,就会通过p中的地址找到只读数据区并改变内容
//但是只读数据区的内容无法改变,所以不可以通过对p解引用改变。
(2)a1数组的内容为"cbcdef"
//对a1数组进行初始化,就相当于将只读数据区中的内容复制一份到a1数组中。
//改变a1数组时,是改变数组a1的内容,而不是改变只读数据区中的常量字符串。
一道面试题:
#include<stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char *str3 = "hello bit.";
char *str4 = "hello bit.";
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;
}
解释:
我们需要知道数组的数组名除了(&数组名)和(sizeof(数组名))两种情况外都表示数组首元素的地址
(1)str1 and str2 are not same.
//str1[]和str2[]是两块不同的区域,内容都是"hello bit",但是首元素地址不同.
//可以理解为先开辟空间,再放入内容,在开辟空间时就确定了地址,
//所以首元素地址与内容无关。
(2)str3 and str4 are same.
//因为是常量字符串,存放在只读数据区,无法改变,所以不会存在多个相同的常量字符串,
//所以str3和str4指针变量存放的是同一个常量字符串首字符的地址。
二、指针数组
指针数组是数组,存放的每个元素是一个指针
int* arr1[10];//整型指针的数组
int** arr2[4];//二级整型指针的数组
char* ch[5];//字符指针的数组
三、数组指针
3.1 数组指针的定义
数组指针是一个指针,这个指针指向的是一个数组
这里我们需要明确一点:数组指针是一个独立的类型(int(*)[10]),并不是int**,就像整型指针,字符指针一样,是一个独立的类型。
整型指针:指向一个整型数据
字符指针:指向一个字符数据
数组指针:存储的时数组的地址,指向一个数组,对数组指针解引用相当于得到了数组名
例:数组指针
int main()
{
int a[4] = { 1,2,3,4 };
int(*arr)[4] = &a;
printf("%d\n", *((*arr) + 1));//(1)
return 0;
}
解释
(1)arr是一个数组指针,存放a数组的地址,
1、(*arr)
//首先将arr解引用,得到a数组的数组名,在这里,数组名表示a数组首元素的地址
2、*((*arr) + 1))——>*(a+1)
//在这里数组名表示首元素地址,(a+1)表示第二个元素的地址,再解引用,就拿到了a[1]
例题:下面代码分别是什么?
int *a[4];//(1)
int (*b)[4];//(2)
解释
(1)数组
//a先与[结合,表示a是一个数组,这个数组有4个元素,每个元素是int*
(2)数组指针
//因为有(),所以b先和*结合,表示b是一个指针,[]说明是数组指针,数组指针b指向的这个数组有4个元素,每个元素是int类型
3.2 &数组名VS数组名
在前面的内容我们已经提到了数组名和&数组名,在这里我们做一个总结:
数组的数组名在除了**&数组名和sizeof(数组名)**两种情况外都表示数组首元素的地址
例题:下列代码会打印什么?
int main()
{
int arr[10] = { 0 };
printf("%p %p\n", arr, &arr);//(1)
printf("%p %p\n", arr + 1, &arr + 1);//(2)
return 0;
}
解释
(1)00EFFDA4 00EFFDA4
(2)00EFFDA8 00EFFDCC
//我们可以看出虽然arr和&arr的值相等,但是表示的意义不同,
//arr表示的是首元素的地址,arr+1就是跳过一个元素
//&arr表示的是整个数组的地址,&arr+1就是跳过一个数组
3.3 数组指针的使用
例:数组指针的使用
void printf_arr(int(*arr)[5],int row,int col)
{
for (int i = 0; i < row;i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", *(*(arr+i)+j));
//*(*(arr+i)+j)等价于arr[i][j]
//如何转换可以看本篇博客——数组指针的定义——例:数组指针
}
}
}
int main()
{
int arr[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int row = 2;
int col = 5;
printf_arr(arr, row, col);//打印1,2,3,4,5,6,7,8,9,10
//arr为二维数组的数组名,这种情况数组名表示首元素地址
//二维数组的首元素是二维数组的第一行,第一行的地址就是一维数组的地址
//可以用一维数组指针接受
return 0;
}
习题:下列代码的意思?
int arr[5]; //(1)
int *parr1[10]; //(2)
int (*parr2)[10]; //(3)
int (*parr3[10])[5];//(4)
解析
(1)arr数组
//5个元素,每个元素的值为int类型
(2)parr1数组
//10个元素,每个元素为int*类型
(3)parr2数组指针
//指向的数组有10个元素,每个元素为int类型
(4)parr3数组
//10个元素,每个元素为数组指针,数组指针指向的数组有5个元素,每个元素是int类型
四、数组参数、指针参数
4.1 一维数组传参
数组传参的时候,形参可写成数组形式,可写成指针类型。(数组传参时,无论形参是数组还是指针实际上传的都是地址)
#include <stdio.h>
void test(int arr[])//正确传参
{}
void test(int arr[10])//正确传参
{}
void test(int *arr)//正确传参
{}
void test2(int *arr[20])//正确传参
{}
void test2(int **arr)//正确传参
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
4.2 二维数组传参
二维数组传参,形参是二维数组时,行可以省略,列不能省略,形参也可是数组指针类型。
void test(int arr[3][5])//正确传参
{}
void test(int arr[][])//错误传参
{}
void test(int arr[][5])//正确传参
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//错误传参
{}
void test(int* arr[5])//错误传参
{}
void test(int (*arr)[5])//正确传参
{}
void test(int **arr)//错误传参
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
4.3 一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz =sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
问:当一个函数的参数部分为一级指针的时候,函数能接受什么参数?
void test(int*p)
{}
int main()
{
int a=10;
int*ptr=&a;
int arr[10]={0};
test(&a);//可以传一个变量的地址
test(ptr);//可以传一个一级指针
test(arr);//可以传数组的数组名
return 0;
}
4.4 二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
问:当函数的参数为二级指针的时候,可以接受什么参数?
void test(char**p)
{}
int main()
{
char ch='w';
char*p=&ch;
char**pp=&p;
char* arr[5];
test(&p);//一级指针的地址
test(pp);//二级指针
test(arr);//指针数组的数组名
return 0;
}
五、函数指针
#include <stdio.h>
int ADD(int x,int y)
{
return x+y;
}
int main()
{
printf("%p\n", ADD);
printf("%p\n", &ADD);
return 0;
}
由上可得:&函数和函数名都表示函数的地址
问:那么我们如何将函数的地址存储起来?
答:我们需要使用函数指针
int ADD(int x,int y)
{
return x+y;
}
int main()
{
int(*pf)(int ,int)=&ADD;//pf是函数指针
int sum = (*pf)(2,3);//通过函数指针调用ADD函数。(*pf)中的*是个摆设,可以去掉
printf("%d",sum);//打印sum
return 0;
}
阅读两个代码
int main()
{
(*(void(*)())0)();
return 0;
}
void(*signal(int,void(*)(int)))(int)
解析:
1、代码1. (*(void(*)())0)();//调用0地址处函数
//1.把0强制类型转换为 void(*)() 函数指针
//2.再去调用0地址处这个参数为无参,返回类型是void的函数
2、代码2. void(*signal(int,void(*)(int))(int)
//1.signal是一个函数声明
//2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该指针指向的函数参数int,返回类型是void
//3.signal函数的返回类型也是函数指针,该指针指向的函数参数int,返回类型是void
3、简化代码2
//简化
typedef void (*pfun_f)(int);//把函数指针类型void(*)(int)命名为pfun_f(类型名)
pfun_t signal(int,pfun_t);//等同于代码2
六、函数指针数组
存放函数指针的数组,每个元素都是函数指针类型。
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(*pfarr[4])(int, int) = { Add,Sub,Mul,Div };
for (int i = 0; i < 4; i++)
{
int ret = pfarr[i](8, 4);//pfarr函数指针数组,也叫转移表
printf("%d\n", ret);
}
return 0;
}
运用场景:计算器(函数指针数组)
void menu()
{
printf("**************************\n");
printf("****1、Add 2、Sub****\n");
printf("****3、Mul 4、Div****\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 x = 0, y = 0;
int input = 0;
int ret = 0;
int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
scanf("%d",&input);
if (input > 0 && input < 5)
{
scanf("%d %d", &x, &y);
ret = pf[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出\n");
}
else
{
printf("输入错误,重新输入\n");
}
} while (input);
return 0;
}
七、回调函数
回调函数就是一个函数的参数包含函数指针,通过调用回调函数就可以调用函数指针指向的函数(比如:qsort
函数)
qsort使用
#include<stdio.h>
int cmp(const void* a, const void* b)
{
return *((int*)a) - *((int*)b);
}
int main()
{
int arr[10] = { 2,4,6,8,10,1,3,5,7,9 };
qsort(arr,10,sizeof(int),cmp);
for (int i = 0; i < 10; i++)//输出1 2 3 4 5 6 7 8 9 10
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort模拟实现(用冒泡排序实现)
#include<stdio.h>
int cmp(const void* a, const void* b)
{
return *((int*)a) - *((int*)b);
}
void swap(void* a, void* b,int size)
{
for (int i = 0; i < size; i++)
{
char temp = *((char*)a + i);
*((char*)a + i) = *((char*)b + i);
*((char*)b + i) = temp;
}
}
void my_qsort(void* p, int num, int size, int(*cmp)(const void*a,const void*b))
{
for (int i = 0; i < num-1; i++)
{
for (int j = 0;j<num-i-1;j++)
{
if (cmp((char*)p + j * size, (char*)p + (j + 1) * size) > 0)
{
swap((char*)p + j * size, (char*)p + (j + 1) * size, size);
}
}
}
}
int main()
{
int arr[10] = { 2,4,6,8,10,1,3,5,7,9 };
my_qsort(arr,10,sizeof(int),cmp);
for (int i = 0; i < 10; i++)//输出1 2 3 4 5 6 7 8 9 10
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
八、指向函数指针数组的指针(存放函数指针数组的地址)
指向函数指针数组的指针是一个 指针
指针指向一个 数组
,数组的元素都是 函数指针
;
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0;
}