指针是什么
指针是指向内存单元的编号(地址),可以快速访问地址,加快程序运行速度.
在指针中一般用到两个操作符:
* 解引用操作符 也是定义指针时候的操作符
int *p;//定义一个类型为 int 的 指针 *p = 0;解引用p指向的地址 并且赋值为0
& 取地址操作符
int a; int *p; p = &a;//把a的地址给p
int * p 中 *说明p是指针变量 int 说明 p 指向的类型是整型
而且p中存储的是地址 *p是解引用p指向了 变量 也就是 p(地址) --> a(变量)
指针本身也占有大小,而且在32位平台下是4字节 64位平台下是8字节 而且指针大小与它所指向的东西无关,只于它运行的平台有关
指针类型意义
指针变量的类型虽然与大小无关,但是却决定每次或下次访问时访问的字节大小有关
比如char*类型的指针,每次只访问一个字节
int*类型的指针,每次访问四个字节
而且它也决定 与 - 运算时访问字节的大小 也就是指针的类型决定了指针向前或者向后走一步有多大距离.
比如char*类型的指针每次 1 跳过1个字节
int*类型的指针每次 1 跳过四个字节
而且指针的地址是可以求差的,用差除去每个元素的长度可以得到两个地址之间的元素个数
而且还有一种特殊指针 void* 它是无具体类型的指针(泛型指针),这种指针可以接收任何类型的地址,但是不可以与 - 和 解引用 因为它无法确定 步长.
一般情况下void*指针是使用在函数参数的部分,用于接收不同类型的地址,可以实现泛型编程的效果,例如
qsort使用-CSDN博客 中 qsort模拟部分
而且指针使用可以和 const(不可修改) 来修饰 而且 const的位置决定可改变和不可改变的位置
const int * p;//决定的是*p不能改变 int const *p;//同理
int * const p;//决定的是p指向的地址不可更改,但是地址包含的东西可以更改
野指针是指向未知位置的指针,一般是由于没有初始化,越界访问,指向了已经被释放的空间等.我们要规避野指针的存在,可以初始化的时候赋值NULL(空地址0) ,在使用完不使用指针的时候及时赋NULL,避免返回局部变量的地址等方法.
也可以使用assert断言 也就是assert.h的头文件包含的assert(),用于在运行时确保程序符合指定条件,如果不符合就报错并且终止运行程序
assert(p != NULL);//如果p不是空指针就报错
如果assert接收的返回值是0就回报错,不是0就继续运行,而且要关闭断言可以使用
#define NDEBUG
数组名指针
首先举个小例子
int arr[5] = {1,2,3,4,5}; int *pp = &arr[1];//取到了元素2的首地址 每次 1 会跳过一个元素 int *ppp = arr;//取到了数组第一个元素的首地址 每次 1会跳过一个元素 int *pppp = &arr;//取到了整个数组首元素地址 每次+1会跳过一个数组
我们根据例子也可以倒推如何使用指针去找元素,就和 1 -1和取得是什么类型的指针有关
printf("%d",arr[1]); printf("%d",*pp); printf("%d",pp[0]); printf("%d",*(pp 0);
这四个代码的效果是一样的 其实[]可以和*一样有解引用的功能
在本质上pp[i]和*(p i)是等价的 同理arr[i]和*(arr i)也是等价的
数组元素的访问在编译器处理的时候,也是转换成首元素的地址 偏移量 然后求出元素的地址,然后再解引用访问.
一维数组的本质
本质上素组窜惨本质上传递的是首元素(或者选择的元素)的地址,因为传过去的仅仅只是地址,所以在自定义函数里面无法直接用sizeof求出数组的大小,只能求出这个地址的大小.而且可以用指针的形式接收数组,因为是地址.
二级指针
指针变量也是变量,所以指针本身也有地址,而且这个地址也可以由其它指针接收,也就是二级指针.
int a; int *p = a; int * *pp = a;
在这种情况下 pp解引用调用p的内容(a的地址) 再对pp二次解引用才能找到a pp中存放的是p的地址 p中存放的是a的地址.
指针数组(与数组指针区分一下)
对于普通的数组 它存放的是int类型的内容
int a[5] == { int,int.......}
对于指针数组 他存放的是是int*类型的内容 也就是存放的全是地址 而且每个地址由可以单独指向一个区域
int* a[5] == { int*,int*,....}
指针数组模拟二维数组
以下两个东西是等价的
int arr[3][3];
int arr1 [3]; int arr2 [3]; int arr3 [3]; int* arr[3] = {arr1,arr2,arr3}
其中的arr的效果是相同的,而且根据 arr[i]和*(arr i)是等价的 可以用相同的方式访问某个元素
也就是 arr[1][1] 等于 *(arr[1] 1) 等于 *(*(arr 1) 1)
字符指针变量
再指针的类型中 还有 char* 的类型 ,它每次访问一个字节,可以逐字节访问
char a = 'w'; char *p = a;//那么p指向a的地址 解引用就是字符w
const char * pw ="wwww";//这个pw指向的是在内存中的字符串"wwww"的首地址 const char * pa ="wwww";//这个pa指向的首地址和pw相同
数组指针变量
int *pa[10];这个是指针数组 int (*pb)[10]; 这个是数组指针
int (*pb)[10] pa与*结合说明p是一个指针变量变量,然后指向的是一个大小为10个的整型的素组,p是指针,指向数组,所以是数组指针,( []的优先级高于*,所以要用()保证*先于P结合.
int arr[10] = {0}; int (*p)[10] = &arr;//这里的p就是arr的地址了
&arr 和 p的类型都是 int[10]*
其中 int是p指向的数组的元素类型, p是数组指针的变量名 [10]是p指向数组的元素个数
二维数组的传参本质
首先理解二维数组,二维数组可以看作是每个元素是一维数组的数组,这个数组中的每个元素都是一个一维数组,那么二维数组的首元素就是一个一维数组,取二维数组的数组名时,二维数组的数组名就是第一行的地址,取出来是一个一维数组.
第一行的地址类型是 int(*)[i],那就意味着二维数组传参本质上是传递了地址,传递的是第一行这个以为新数组的地址,那么形参也可以写成指针形式
void test(int (*p)[5]);//用这个来接收有五行的二维数组arr[5][i]
函数指针变量
在内存中创建的自定义函数本身也是有地址的,可以通过 &函数名 的方式得到函数的地址,如果我们要把函数的地址存起来,就要创建函数指针变量,函数指针变量的写法和数组指针类似
void test() { return 0; } void (*pf1)() = &text; void (*pf2)() = test; int add(int x,int y) { return x y; } int(*pf3)(int,int) = add; int(*pf4)(int x, int y) = &add;
对于 int(*pf3)(int,int) = add; 解析 int 是pf3指向的函数的返回类型 (*pf3)是函数指针变量名
(int,int)是主席昂的函数的阐述类型和个数
这个指针的类型是 int(*)(int,int)
而且用的方法和之间用函数一样的
a = (*pf3)(1,1); a = pf3(1,1);
这两行结果相同
typedef关键字
typedef是重命名例如把 unsigned int 重命名成 unit
typedef unsigned int uint;
函数指针数组
int (*p[5])(int,int);
这是定义了一个 有五个元素,返回类型是int 参数是两个int 的 函数指针数组
类型是 int(*)(int,int)
这个结合到使用的时候也就是转移表了