这篇只是简单了解一下指针而已,其他更深入的我会在后面更新喔
在我们生活中无论是宿舍还是教学楼每间房间都有它们自己的编号,这是为了我们能够准确的找到它们的位置,也就是说编号就是地址。其实内存也是如此,在计算机中给内存的每一个字节编个号,如:
注:就是内存下面一般是高地址上面是低地址,还有一般内存是先使用高地址在使用低地址
这样在需要使用哪个变量时就能够顺利找到了。
在c语言中地址又叫指针,所以编号 == 地址 == 指针
每个变量在创建的时候都会有相应的地址,而 & 放在变量的前面就能将地址取出,如&a–代表的是变量a的地址,而打印地址的占位符是 %p , 这个取地址操作与我们下面的内容有着很大的关系,我们现在试试吧
#include
int main() {
int a = 10;
printf("&a=%p\n", &a);
return 0;
}
指针变量作用是接收地址,那么怎么创建呢。
创建:类型 int * 变量 p >> int *p.
这里的类型也可以是 char、double等等
int a = 10;
int * p = &a;
这⾥p左边写的是 int* , * 是在说明p是指针变量,⽽前⾯的 int 是在说明p指向的是整型(int)类型的对象。
我们在上面介绍的指针变量 p 是用来存放变量 a 的地址,即p=&a,那么我们想要通过变量指针p来改变a的值时,就要在指针变量 p
的前面加入解引⽤操作符 ( * )>>> * p, 即* p=a,此时改变 *p的值就相当于改变a的值了,因此解引用操作很重要
#include
int main() {
int a = 10;
int* p = &a;//把a地址给指针变量p
printf("a=%d\n", a);//先打印原来的值
*p = 20;//通过解引用p来改变a
printf("&a=%p\n&p=%p\n", &a,&p);//变量a,和指针变量p--这两用的不同空间,地址不同
printf("a=%d *p=%d", a, *p);//看看a的值是否改变了
return 0;
}
在了解指针大小之前我们先理解地址编号的过程
过程图:
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进⾏编址(就如同宿舍很多,需要给宿舍编号⼀样)。
CPU和内存之间是有⼤量的数据交互的,所以,两者必须也⽤线连起来。不过,我们今天关⼼⼀组线,叫做地址总线。
我们可以简单理解32位机器有32根地址总线,每根线只有两状态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。
而32位就要4个字节来存储,又因为指针变量是用来储存地址,所以此时指针变量大小为4字节,在64位机器上有64条地址总线,要8字节来存储,所以指针变量的大小为8字节。
因此指针变量大小跟是多少位机器有关的,与我们所创建的类型(这里指的是 int,char等)无关,只看机器。
那么来一段代码验证一下:
int main() {
char a;
int b;
char* p = &a;
int* q = &b;
printf("p=%zd , q=%zd", sizeof(p), sizeof(q));
return 0;
}
在64位机器下的结果:
32位机器下的结果:
由结果来看的确跟char 或int类型无关,只跟机器有关,机器一样大小一样
在上面我们知道了指针变量的大小和类型无关,那么是不是代表int 类型被char类型的指针变量接收和被int类型的指针变量接收一样呢,接下来我们通过解引用来观察
通过代码分析:
int main() {
int b=0x11223344;//16进制的数字,这样方便观察
char* q = &b;//用char类型指针变量来接收int类型的&b
*q = 0;//改变b的值
printf("%d ", b);
return 0;
}
看看对比
这是没运行到char那里的内存变化
这是运行到char的变化
两图对比 char*q 只是改变了一个字节的内容,也就是说类型不同访问字节大小不同。以此类推那么short就是访问2字节了,感兴趣的可以下去试试
我们从上面知道相同类型用不同类型指针变量接收时访问的大小会有所改变,那么在原来的基础(接受的地址)上加 1 呢,它们前进的大小又是多少呢,
通过代码我们来分析:
int main() {
int a = 10;
int* p = &a;//int *-4字节
char* q = &a;//--1字节
printf("&a=%p\n", &a);//原来的地址
printf("p=%p\nq=%p\n", p, q);//赋给之后
printf("p+1=%p\nq+1=%p\n", p + 1, q + 1);//加一后打印
return 0;
}
&a和p q都是一样的,因为打印的是首地址,&a和p没区别都可以往后访问4字节,而q只能访问一个字节,
那么加1后呢,我们可以看出p+1是跳了4字节,而q+1只是一个字节,这就是区别,再往后p+2的话就是在跳4 字节,而q+2还是再跳一个字节。
结论:指针的类型决定了指针向前或者向后走⼀步有多大(距离>>字节)。
在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的±整数和解引⽤的运算(上面讲的)。
但我们向上面那样的话会报警告,如果用void*就不回了,但是这样就不能计算,解引用了
这个其实我们在上面就用过了,就是指针变量可加可减,加减的是地址,加多少要看类型,如int就加4 个字节,char就加1字节
int main() {
int a = 10;
int* p = &a;//int *-4字节
printf("p=%p\n",p);//原来的地址
printf("p+1=%p\n",p+1);//原来的地址
printf("p+2=%p\n", p+2);//原来的地址
printf("p+3=%p\n", p+3);//原来的地址
return 0;
}
指针减指针得到的数的绝对值代表的是在该类型下它们之间有几个元素
如:
i
nt main() {
int a = 10;
int* p = &a;//int *-4字节
printf("%d", (p + 5) - p);
printf("%d", (p + 2) - (p + 6));
return 0;
}
从结果看出:p和p+5之间有5个元素,p+2和p+6之间有4个元素
**由于指针大小是地址(地址也是有大有小的),所以也是可以用来比大小的。
**
如:
int main() {
int b = 20;
int a = 10;
int* p = &a;
int* q = &b;
printf("p=%p\nq=%p\n", p, q);//打印地址
if (p < q) {//如果p的地址小于q的地址就执行
a = 1;
b = 1;
}
printf("a=%d,b=%d", a, b);
return 0;
}
什么是野指针呢,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
由于指针未初始化的话就会随机指向一个地址,而这个地址是未知的,因此就变成了野指针
如:
int main() {
int* p;
return 0;
}
解决办法:
如果不知道指针应该指向哪⾥,可以给指针赋值NULL . NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的。以后要使用时再将它赋地址。
int main() {
int* p=NULL;//向赋NULL
int a=10;
p = &a;//要使用时再用它
printf("%d", *p);
return 0;
}
由于指针越界就会指向一个我们没有申请空间的地址,这样超过了指针的范围,因此该指针就变成了野指针
如:
int main() {
int arr[5] = { 1,2,3,4,5 };
int* p = arr;//arr是首地址
for (int i = 0; i < 5; i++) {
printf("%d", *p);
p++;//最后一次时p指向了第6个空间这是我们没有申请的空间
}
return 0;
}
解决办法:
将p指针置NULL,后面用的时候再判断是否是NULL。是的话就不要用
如:
int main() {
int arr[5] = { 0};
int* p = arr;//arr是首地址
for (int i = 0; i < 5; i++) {
*p = i;
p++;//最后一次时p指向了第6个空间这是我们没有申请的空间
}
p = NULL;//发现问题置NULL
if (p != NULL) {//不是的话就证明这个指针没有问题,没有被我们置NULL
}
return 0;
}
指针指向的空间被释放了,如创建的函数,返回来的地址是在该函数里创建的变量,这样该指针就成野指针了
如:
int* te() {
int a = 10;
return &a;
}
int main() {
int* p = te();//返回的地址是在te函数里创造的
printf("%d\n", *p);
}
int main() {
int* p = te();
int* q=NULL;
printf("%d\n", *p);
}
以上三种情况就是野指针
想要避免野指针
1.未初始化要置NULL
2.避免出现越界的情况
3.不要返回局部变量的地址
4.发生了要及时置NULL,以后再用不确定的指针时要判断是否为NULL
一般情况下我们在函数传参时都是传值,再通过 retun 返回值
那么下面我们来实现一加法函数:
int add(int x, int y) {//x=a,y=b
return x + y;//返回和
}
int main() {
int a = 10, b = 20;
int sum = add(a, b);//传值调用
printf("%d\n", sum);
return 0;
}
加法函数成功算出来了,当我们只返回一个值时是可以这样做(return ), 那两个呢,那么我们要交换a,b两值也可以这么传吗?
我们来试试:
void jh(int x, int y) {
int t = x;//创建第三个变量来实现交换
x = y;
y = t;
}
int main() {
int a = 10, b = 20;
printf("a=%d,b=%d\n", a, b);//先打印等下与后面对比
jh(a, b);
printf("a=%d,b=%d", a, b);
return 0;}
运行结果:
我们发现并没有改变,这是什么原因呢,我们从地址的角度去分析:
先打印它们的地址观察
void jh(int x, int y) {
int t = x;//创建第三个变量来实现交换
x = y;
y = t;
printf("&x=%p\n&y=%p\n", &x, &y);//打印a,b地址
}
int main() {
int a = 10, b = 20;
printf("&a=%p\n&b=%p\n", &a, &b);//打印a,b地址
jh(a, b);
return 0;
}
结果:
它们的地址并不一样,我们又知道每个变量都有自己的空间,如果空间内容没改变时,它的值是不会改变的
所以当x,y(这里x,y得到的值就是a,b的值的一份临时拷贝)的值改变时是不会影响到a,b的值
那么我能用指针变量来接收它们的地址,再通过解引用来改变a,b呢,这就是我们下面要讲的传址调用了
还是上面的例子
这次我们将地址传过去
void jh(int* x, int* y) {//用指针变量来接收地址
int t = *x;//创建第三个变量来实现交换
*x = *y;
*y = t;
}
int main() {
int a = 10, b = 20;
printf("a=%d b=%d\n", a, b);//作前后对比
jh(&a, &b);//传地址
printf("a=%d b=%d", a, b);
return 0;
}
运行结果
哎,改变了耶。
这是为什么呢,我们再从地址角度分析:
我们来观察它们的地址:
void jh(int *x, int *y) {
int t = *x;//创建第三个变量来实现交换
*x = *y;
*y = t;
printf("x=%p\ny=%p\n", x, y);//打印x,y地址,这里x和y本身就是地址,所以不用加 &
}
int main() {
int a = 10, b = 20;
printf("&a=%p\n&b=%p\n", &a, &b);//打印a,b地址
jh(&a, &b);
return 0;
}
运行结果:
可以看到&a和x相等,&b和y相等,所以当*x和 *y改变时a,b所在的空间也会改变,值也会改变。
如果是传址时当我们的形参改变时实参也会改变
当我们只需要传回一个参数时可以用传值调用,但是当我们想要改变实参时就一定要用传址调用了
以上就是我的分享了,谢谢大家的观看,如果大家觉得有帮助的话就动动发财的小手点点赞和关注吧