指针
本文介绍了野指针及其防范措施,字符指针变量,二级指针及指针数组,数组指针变量及其传参本质,函数指针变量,函数指针数组

野指针
定义:野指针就是指向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针的成因
- 指针未初始化
#int main()int main(){ int *p;//局部变量指针未初始化,默认为随机值 *p=10; return 0;}- 指针越界访问
#include<stdio.h>int main(){ int arr[10]={ 0 }; int* p = arr; for(int i=0;i<11;i++) { //当i=10的时候,指针p超出数组arr的范围,p就成了野指针 *(p++) = i; } return 0;}- 指针指向的空间释放
#include<stdio.h>
int* test(){ int a = 10; return &a;}
int main(){ int* p=test();//这里返回的空间已经被释放,得到的是野指针 return 0;}野指针防范措施
- 指针初始化
如果没有要明确指向的目标,可以给指针赋值为NULL。
NULL:在C语言中是一个标识符常量,地址为0,数值为0,无法使用也无法被正确读写。
int main(){ int a = 10; int* pa = &a; int* pb = NULL;
return 0;}- 注意指针是否越界访问
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。 - 在使用完指针后,养成将其置空的习惯
#include<stdio.h>int main(){ int a = 1; int* pa = &a; printf("%d",*pa); //使用过程.... pa = NULL; return 0;}- 指针在使用前使用assert进行断言,检查指针是否有效
#include<stdio.h>#include<assert.h>
int main(){ int p1 = NULL; int a = 10; int* p2 = &a;
assert(p2);//p2不为NULL,程序继续运行 assert(p1);//p1为NULL,assert()进行报错,并在屏幕中打印出该错误信息并标明多少行 printf("%p %p",p1,p2); return 0;}assert
#include<assert.h>void assert(int expression);assert() 宏接受⼀个表达式作为参数,若这个参数为真(结果为true),assert()不会产生任何作用
程序继续运行;反之,这个参数为假(结果为false),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,包括这个表达式的文件名与行号。
#define NDEBUG
若我们已确保程序没有问题,便可以在#include<assert.h>语句的前面,定义一个宏NDEBUG,这样便可以禁用文件中所有的assert()语句。
#define NDEBUG#include<assert.h>若在程序中再次出现问题,我们也可以将#define NDEBUG指令注释掉,再次编译调试
字符指针变量
在指针的类型中,字符指针为char*
一般使用:
#include<stdio.h>int main(){ char ch = 'a'; char* pch = &ch; printf("%c",*pch); return 0;}另一种使用方式:
#include<stdio.h>int main(){ char* pstr = "hello,world"; printf("%s",pstr); return 0;}上述代码char* pstr=“hello,world”;指的是常量字符串hello,world的首地址放至pstr中,而不是将hello,world放到字符指针pstr中。
《剑指offer》中收录了一道和字符串相关的笔试题,如下:
#include <stdio.h>int main(){ char str1[] = "hello bit."; char str2[] = "hello bit."; const char *str3 = "hello bit."; const 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;}分析:
- str1与str2是先定义一个字符串数组,在将字符串
hello,bit放到字符串数组str1,str2,所以这里str1与str2的地址是不同的 - str3与str4都是先定义一个字符指针变量,在将常量字符串的首地址赋给str3与str4,所以str3与str4都指向
hello,bit的首地址,所以str3==str4成立。
二级指针变量
指针变量也是变量,是变量就有地址,因此也存在二级指针。
#include<stdio.h>int main(){ int a = 10; int* pa = &a; int** ppa = &pa; return 0;}- *ppa通过对ppa的地址的解引用,这样就可以找到pa,所以*ppa访问的就是pa
int a= 10; *ppa = &a;//pa = &a;- *ppa先通过ppa找到pa,然后对pa解引用来访问a
**ppa = 20; //*pa = 20; //a= 20;指针数组
顾名思义,指针数组的每个元素都是用来存放地址的,所以,指针数组的每一个元素都是地址,其中每个地址也都可以指向一块区域,例如:

指针数组模拟二维数组
#include<stdio.h>int main(){ int arr1[]={1,2,3,4,5}; int arr2[]={6,7,8,9,10}; int arr3[]={11,12,13,14,15}; int *parr[3]={arr1,arr2,arr3}; int 1=0 , j=0; int col=sizeof(parr)/sizeof(parr[0]); int row=sizeof(arr1)/sizeof(arr1[0]); for(i=0;i<col;i++) { for(j=0;j<row;j++) { printf("%d",parr[i][j]); } printf("\n"); }
return 0;}parr[i]是parr数组的元素,parr[i]找到的数组元素指向了一位数组的元素,所以parr[i][j]就可以表示在parr[i]中arr(i)[j]的元素
注意:上述代码只是模拟出二维数组的效果,实际上并不是二维数组,因为每一行并非是连续的。
数组指针变量
定义
数组指针变量是存放数组的地址是指向数组的指针变量
int (*p)[10];p先与*结合,说明p是一个指针变量,然后指着指向一个大小为10的整形数组,所以,p是一个指针,指向一个数组,叫做数组指针。
注意:[]的优先级要高于 * 号,所以,要加上括号来保证*与p最先结合。
二维数组的传参本质
二维数组的普通传参
#include<stdio.h>
void Print_arr(int arr[3][5],int x,int y){ for(int i=0;i<x;i++) { for(int j=0;j<y;j++) { printf("%d ",arr[i][j]); } printf("\n"); }}
int main(){ int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; Print_arr(arr,3,5); return 0;}根据数组名就是这个数组的首元素地址这个规则,如果我们把3个一维数组arr1,arr2,arr3看作3个元素,在将其放置到另一个一维数组nums中,那么nums的首元素就是arr1这个一维数组。
根据上面的例子,第一行的数组类型是int [5],所以第一行的地址类型就是数组指针类型int (*)[5]。
所以,这就意味着二维数组的传参本质也就是传递了地址,传递的是这一个一维数组的地址,那么我们的形式参数就可以使用指针来接收,如下:
形参为指针的二维数组的传参
#include<stdio.h>
void Print_arr(int (*pnums)[5],int x,int y){ for(int i=0;i<x;i++) { for(int j=0;j<y;j++) { printf("%d ",pnums[i][j]); } printf("\n"); }}
int main(){ int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}}; Print_arr(arr,3,5); return 0;}函数指针变量
我们先观察下面的一个代码:
#include<stdio.h>
void test(){ ;}int main(){ printf(" test = %p\n",test); printf("&test = %p\n",&test); return 0;}运行结果如下:
test = 00007FF7785C13F7&test = 00007FF7785C13F7由此,我们可以得出函数也有地址,而函数名就是函数的地址,当然也可以使用&函数名来表现。
因此,我们也可以使用指针指向函数的地址,也就是创建函数指针变量。
定义
函数指针变量用来存放函数的地址,通过地址调用函数
函数指针类型
#include<stdio.h>#include<assert.h>void print_1(){ printf("test_1 no problem\n");}
int cmp_max(int x, int y){ if (x < y) x = y; return x;}
char* my_strcat(char* dest, char* src){ assert(dest); if (src == NULL) return dest; char* ret = dest; while (*dest) { dest++; } while (*dest++ = *src++) { ; } return ret;}
int main(){ void(*pv)(void) = print_1; int(*pi)(int x, int y) = cmp_max; char* (*pc)(char* x, char* y) = my_strcat; (*pv)(); printf("cmp_max(4,2)=%d\n", (*pi)(4, 2)); char dest[50] = "my_strcat "; char src[20] = "no problem\n "; printf("%s", (*pc)(dest, src)); return 0;}运行结果如下:

函数指针类型解析:

int (*) (int x,int y)//cmp_max函数指针变量void (*) (void)//print_1函数指针变量char* (*) (char* a,char* b)//my_strcat函数指针变量函数指针数组
既然有函数指针变量,那么我们也可以把函数指针当作元素存放到指针数组中,这就叫做函数指针数组。
定义形式:(返回类型) (*(函数名)[存放个数]) (参数类型)
例如:
int (*parr[5]) (int x,int y)//这里的函数指针数组可以存放五个函数指针,函数指针类型为int (*) (int x,int y)例如:计算器的简易实现
#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
void menu(){ printf("+-------------------+\n"); printf("| 1.add(x,y) |\n"); printf("| 2.sub(x,y) |\n"); printf("| 3.mul(x,y) |\n"); printf("| 4.div(x,y) |\n"); printf("| 0.退出程序 |\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(){ menu(); int input = 0; int (*pi[5])(int x, int y) = { 0,add,sub,mul,div }; while (printf("输入你的选择:"), scanf("%d", &input), input) { if (input > 0 && input < 5) { int a = 0, b = 0; printf("请输入你要计算的数:"); scanf("%d %d", &a, &b); printf("%d\n", (*pi[input])(a, b)); //利用(*pi[input])(a, b)函数指针调用函数,从而返回计算结果 } } printf("成功退出\n"); return 0;}这里通过改变input,来调用函数指针,进而使用函数来进行计算。
好了,以上就是本期的全部内容,喜欢请多多关注吧!!!
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时