目录
1. 指针类型
指针以字节为单位;
指针类型决定了解引用时能访问的空间的大小;也决定了指针的步长(指针+1走多远)
2. 野指针
- 指针未初始化
- 指针越界访问
- 指针指向的空间已释放
int* test()
{
int a = 10;//野指针
return &a;
}
int main(){
int* p = test();
//test函数里的a是局部变量,出函数时,储存变量的空间已经还给操作系统了
printf("%d\n",*p);
//此时通过解引用不能再访问到a;
}
如何规避野指针呢?
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
3. 指针的运算
3.1 指针+-整数
int arr[5] = {0,1, 2, 3, 4};
int* p = arr;
int i = 0;
for(i=0; i<5; i++)
{
//printf("%d ",*p);
//p++;
printf("%d ",*p++);//以上两种写法相同
}
3.2 指针-指针
得到的是中间元素的个数,可以用来求字符串的长度
int my_strlen(char* arr)
{
char* start = arr;
char* end = arr;
while(*end != '\0')
{
end++;
}
return end - start;
}
3.3 指针的关系运算
其实就是指针比较大小。实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
float values[5];
float *vp;
int i = 0;
for (vp = &values[0]; vp < &values[5];)
{
*vp++ = 0;
}
4. 字符指针
注意两种写法:
char* p = "abcdef";
//p本身是栈区的一个指针变量,p的内容指向常量字符串a的地址
char arr[] = "abcdef";
//这种定义方式将常量字符串"abcdef"拷贝一份到栈区,arr指向栈区字符串的a
//arr本身是栈区的一个指针变量
5. 指针数组
存放指针的数组
int a; int b; int c;
int* arr[3] = {&a,&b,&c};
int (*pa) [5]; //表示数组指针//这个指针的类型是int * [5];
//如果将以上指针建立数组
int (*paa[10]) [5]; //这个是指针数组,每一个元素都是一个数组指针
//以上*paa[10],paa先与[]结合(优先级),所以为数组
6. 数组指针
int a[5] = {0};
int (*pa) [5] = &a; //表示数组指针,这个指针的类型是int * [5]
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//int arr[10]中,arr是数组名,int [10]则是数组类型,那么指针的定义方式就显然了
int (*pa)[10] = &arr;//取出的是数组的地址 //注意:arr首元素地址;&arr[0]首元素地址;&arr是数组的地址;
//以上parr就是数组的指针
int i = 0;
for (i = 0; i < 10; i++)
{
//printf("%d ", (*pa)[i]);//*pa实际上就是arr,数组名
printf("%d ", *((*pa) + i));//*pa实际上就是arr,数组名,也就是首元素的地址
}
printf("\n");
printf("%p\n",pa);//数组地址
printf("%p\n",*pa);//数组名,又是首元素地址
printf("%d\n",*(*pa));//首元素
//注意以上说法:pa是数组arr的地址,*pa是数组名(首元素地址),*(*pa)就是首元素
二维数组指针
void print1(int arr[3][5], int r, int c)//参数是数组的形式
{
int i = 0;int j = 0;
for (i = 0; i < r; i++){
for (j = 0; j < c; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
}
//p是一个数组指针
void print2(int(*p)[5], int r, int c)//参数是指针的形式
{
int i = 0; int j = 0;
for(i=0; i<r; i++){
for(j=0; j<c; j++){
//printf("%d ", (*(p+i))[j]);
//printf("%d ", *(*(p+i)+j));
printf("%d ", p[i][j]);//因为*(p+i)可以写成p[i]
//此时p相当于arr,首元素地址=数组名,类比一维数组;
}
printf("\n");
}
}
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
//print1(arr, 3, 5);
print2(arr, 3, 5);
//arr数组名,表示数组首元素的地址,但此时首元素不是1,而是{1,2,3,4,5},把二元数组看成三个大的元素
7. 函数指针
存放函数的地址,函数名和对函数取地址在用法上完全一样的,&Add=Add
int x= 3; int y =2;
int (*pa)(int, int) = &Add;//函数指针的类型
int (*paa)(int, int) = Add;//这两种写法一样
printf("%p\n", &Add);//函数的地址
printf("%p\n", Add);//以上两种写法一样
printf("%p\n", pa);
printf("%p\n", *pa);
printf("%d\n",Add(x,y));
printf("%d\n",(&Add)(x,y));
printf("%d\n",(*pa)(x,y));
printf("%d\n",(pa)(x,y));
阅读两段有趣的代码:
(*(void (*)())0)();//其实是一次函数调用
//把0强制类型转换成void(*)()函数指针类型,(*(void (*)())0)解引用后就是0这个地址上的函数,(*(void (*)())0)();这就是进行了一次函数调用;
void (*signal(int , void(*)(int)))(int);
//以上等同于 void (*)(int) signal(int, void(*)(int)),是一个函数,函数的返回类型是void (*)(int),但要按照以上的书写;
//以上代码可以简化成
typedef void (*pfun_t)(int); //相当于定义void (*)(int) = pfun_t,类似于typedef unsigned int unit;
pfun_t signal(int, pfun_t);
8. 函数指针数组
int (*p[10]) [5]; //数组指针的数组
void (*pr[10]) (); //函数指针的数组
函数指针数组的用途:***转移表***
例子:计算器
//在上面分别实现add,mul,sub,div函数
int x, y;
int input = 1;
int ret = 0;
int (*p[])(int, int) = {0, Add, Sub, Mul, Div};//函数指针数组,也就是转移表
while(input)
{
printf("0.exit 1.add 2.sub 3.mul 4.div \n");
printf("please choose: ");
scanf( "%d", &input);
if(input>=1 && input<=4)
{
printf("please iput two nums:");
scanf("%d %d",&x,&y);
ret = p[input](x,y);
printf("the ans is %d\n",ret);
}
else if(input == 0)
printf("exited\n");
else
printf("input error, input again\n");
}
9. 指向函数指针数组的指针
实际上就是一个数组的指针(该数组的元素是函数指针)
int arr[10]; //数组
int (*p)[10] = &arr; //数组的指针
int (*p[6])[10]; //数组指针的数组
int (*(*pp)[6])[10]; //指向(数组指针数组)的指针
void(*f)(); //函数指针
void (*pf[10])(); //函数指针的数组
void(*(*ppf)[10])() = &pf; //指向函数指针数组的指针
10. 回调函数
具体使用见下面的案例
11. void* 的使用
可以接受任意类型的地址,但是不能解引用操作(解引用后不知道访问几个字节);
也不能进行+-整数的操作,因为不能确定步长的大小;
12. qsort库函数讲解
对数组快速排序
//void qsort( void *base, size_t num, size_t width, int (*cmp)(const void *elem1, const void *elem2 ) ); //qsort函数包括四个参数,其中第四个参数是比较函数的指针,由使用者自己实现;
int int_cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main(){
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr, sz, sizeof(int), int_cmp);
//这里需要自己定义一个比较函数,这就是回调函数
for (i = 0; i<sz; i++)
{printf( "%d ", arr[i]);}
printf("\n");
}
下面使用回调函数模拟实现qsort库函数(了解)
void _swap(void *p1, void * p2, int size)
{
int i = 0;
for (i = 0; i< size; i++)
{
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count , int size, int(*pf)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++)
{
for (j = 0; j<count-i-1; j++)
{
if (pf ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
//这里的传参需要技巧;
{
_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main(){
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(int), int_cmp);
for (i = 0; i<sz; i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
}