一、指针的导论
如果我们要了解指针,一定要了解电脑中内存的划分。内存划分为一个个内存单元,每个内存单元为一个字节,每个内存单元一个编号。(编号=地址=指针)
内存编号可以快速的让我们找到内存单元。
二、指针
(一)、取地址操作符(&)
当我们要使用地址时一定要是用取地址操作符。
区别:&a,他是单目操作符,而a&b他是双目操作符 与。
(二)、指针的类型
#include<stdio.h>
int main()
{
int a=10;
int * p=&a;
return 0;
}
其中,int * 是指针类型,也就是p的类型。
int表示指针指向的对象类型—> a
*(星号)说明p是指针变量。
存放在指针变量中的值,都会被当成地址来使用。指针变量就是用来存放地址的。
(三)、解引用操作符/间接访问操作符
解引用操作符是一个*(星号),
int main()
{
int a=0;
int *p=&a;
*p=10;
return 0;
}
p的意思就是通过p存放的地址,找到地址所指向的空间,也就是p就是a。
(四)、指针的大小
指针的大小到底是多少呢?可以思考一下以下代码
int main()
{
//在X86环境下
int a = 10;
char b = 'a';
int* ch = &b;
int* p = &a;
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(ch));
return 0;
}
你会发现不管是char类型还是int类型,都是4,同理在X64环境下都是8
结论:指针变量的大小与类型无关,与环境有关。
(五)、指针类型的意义
指针类型决定了指针在解引用操作的权限,也就是一次解引用访问几个字节。
指针类型决定了指针进行+1/-1操作时,一次跳过几个字节(指针的步长)
int main()
{
int arr[] = {1,2,3,4,5};
char* p = arr;
printf("%d\n", *p);
printf("%d\n", *(p+1));
return 0;
}
(六)、const修饰指针
const修饰变量,使得变量不可更改。const有两种使用方法,一种是放在星号左边,一个是放在星号右边。
(1).const放在星号左边,限制指针指向内容,但是能修改变量本身
(2).const放在星号右边,限制指针本身不能修改。
举一个例子,比如我有十块钱n=10,但是我朋友A有100块a=100,这个时候我想吃一碗凉面十块钱,但是这十块钱有一部分早饭的钱,我的钱不够,这个时候我就用const 锁住我的钱包,即:const int p=&n;这个时候我就不能把这十块钱花出去了,即:不能执行p=0这个操作;那么这个时候朋友A说你可以借我的钱,然后有空再还我,那么我就可以从A朋友的钱包中拿出,即:p=&a;在假设,我觉得借钱还钱太麻烦了,那么我就直接用我的十块钱买了,不从朋友A的钱包中借了,即: int * const p=10;*p=0;但是p=&a是不行的,我就直接花了我的钱,买了碗凉面。希望通过这个例子可以让你区分const在左右的用处。
(七)、指针的运算
指针的运算有两种
1.指针 + 或者- 整数
指针加减整数指指针跳过某类型大小的位置。
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5 };
int* ptr = arr;
printf("%d\n", *(ptr + 1));
printf("%d\n", *(ptr + 3));
return 0;
}
2.指针 — 指针
指针减去指针得到的绝对值是指针域指针之间的元素个数。(前提:两个指针必须是同一块空间)
int main()
{
int arr1[] = { 1,2,3,4 };
char arr2[] = "abcd";
//printf("%d ", (arr1[0] - arr2[0]));//大错特错,
//在内存中地址都是随机存储,你怎么知道这两块地址是否挨着?
//所以前提两个指针必须在同一块空间!!
printf("%d ", arr1[0] - arr1[3]);
return 0;
}
3.指针的运算关系(地址比较大小)
int main()
{
//指针大小的比较
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;//如果写成 int*p=&arr[0];也可以
while (p < arr + sz)
{
printf("%d " ,* p);
p++;
}
return 0;
}
三、野指针
什么事野指针?野指针是指针指向的位置不可知的(随机的,不正确的,没有明确限制的)。那么什么情况下可能会出现野指针呢?
1.指针未初始化
int main()
{
int a = 0;
int* p;//局部未初始化,得到随机值
return 0;
}
野指针未初始化,就像是你走在大街上,随便一个房子你就要进去住,还不给钱。
2.指针的越界访问
像上面情况就是越界访问,最后输出会出现6个0,但是VS已经发生报错行为。
3.指针指向的空间已经释放
像上面就是我需要放回数组里2的地址,但是当我的函数已经返回了的时候,空间释放。
4.避免野指针
既然这些情况会出现野指针,那么我们如何避免野指针的出现呢?
①初始化
可以用NULL(空指针)来初始化,即: int *ptr=NULL;(注意使用NULL一定要包括头文件: #intclude<stdio.h>),等到使用时在赋值或者先进行有效性判断。
注:NULL不能直接访问,有NULL大概率是指针。
当我们使用完指针不用的时候,也可以先置空,然后需要时在从新取地址。
②防止越界,谨防已释放空间
在书写时多认真注意界限,和空间是否合理
四、assert断言与传值调用和传址调用
(一)、assert
断言的主要作用是判断指针,你也许觉得if判断也可以,但是使用if效率低。
使用assert断言时要包括头文件:#include<assert.h>
断言为什么高效,就是因为他会给我们爆出错误的位置,并且提示在哪。
如果assert()括号中语句为真什么都不发生;如果语句为假,报错。
断言也是可以关闭的,用 #define NDEBUG 即可。
注意:assert在release版本下是不报错的。
(二)、传值调用和传址调用
传值调用和传址调用的含义
1.传值调用:实参传给函数的形参,只是一份临时拷贝,形参会开辟新的空间,所以形参的修改不能影响实参。
2.传址调用:当需要通过函数进行交换,修改,需要函数与主函数建立联系是,就可以使用传址调用。
扩展:鲁棒性(健壮性):
鲁棒性(robustness)就是系统的健壮性。它是指一个程序中对可能导致程序崩溃的各种情况都充分考虑到,并且作相应的处理,在程序遇到异常情况时还能正常工作,而不至于死机。比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。
五、数组名的理解
(一)、数组名的理解
我们一般说数组名表示首元素的地址,但是有两个例外,第一个是,sizeof(数组名),计算整个数组大小,单位字节;第二个是,&arr,表示取整个数组的地址,但是如果打印的话,显示首元素的地址。
区别:&arr和arr
&arr和arr打印出来都是首元素的地址,但是他们还是有本质上区别。
(二)、使用指针访问数组
六、二级指针与指针数组
(一)、二级指针
int main()
{
int a = 10;
int* p = &a;//我们将p叫做指针
//那么如果我们要存放p指针的地址又该怎么写呢?
int* * pp = &p;//我们就把pp叫做二级指针
return 0;
}
大致可以这么理解,p中存放的是a的地址,而pp里面存放了p的地址,当我第一次*pp;时,拿到了p的内容也就是a的地址,当我对第二次**pp;时,我就拿到了a的内容。
三级指针与上面相差不大,也不常用这里我们不再过多提及。
(二)、指针数组
指针数组顾名思义就是存放指针的数组。所以这个数组里面存放的都是指针。例如:int * p[10]={&a,&b};
如图例子:
int main()
{
int arr1[] = {1,2,3};
int arr2[] = { 2,3,4, };
int * parr[] = { arr1, arr2 };//既然是指针数组,要注意数组元素的类型!!!别粗心!
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", (*(*(parr+i)+j)));
}
printf("\n");
}
return 0;
}
七、字符指针、数组指针和二维数组传参本质
(一)、字符指针
字符指针顾名思义是存放字符的指针。例如:char * p=“abcdef”;(注意:并不是将abcdef\0放入p字符指针中,而是将第一个字符a的地址放入p中。并且abcdef属于常量字符串并不可以修改!!)
(二)、数组指针与二维数组传参的本质
数组指针是存放数组的地址。例如:int (*p) [10]=&arr;
void test(int (*arr)[3],int r,int w)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < w; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[2][3] = { {1,2,3}, {2,3,4,} };
test(arr,2,3);
return 0;
}
二维数组每一行是一个一维数组,这个一维数组可以看做二维数组一个元素,所以二维数组名就表示第一行的地址。在形参部分我们可以写成指向第一行的数组指针。
八、函数指针
函数指针是指向函数的指针,是存放函数地址的指针。
书写: int(*pf)(int ,int)=&Add;
int Add(int x,int y )
{
return (x + y);
}
int main()
{
int ret = Add(3,5);
printf("%d\n", ret);
printf("====================\n");
int (*pf)(int, int) = Add;
printf("%d\n", pf(3, 5));
printf("%d\n", (*pf)(3, 5));
return 0;
}
以上就是指针的总体大类,还有一些小细节需要你我共同思考,希望能给大家带来一定的解惑,谢谢观看~!