什么是指针?
在C语言当中,我们可以将指针理解为内存当中存储的地址,就像生活当中,一个小区里面,在小区里面有很单元,每一栋单元,单元内的房间有着不同的房间号,我们可以同过几栋几单元去寻找我们想要找到的房间,同样的,我们在C语言当中,指针就是我们寻找想要的房间的一种手段。
指针变量和地址
上面那一段话,已经让我们初步理解了指针
我们可以看一下这一段代码
其中 0x0115F95C 0x0115F95D 0x0115F95E 0x0115F95F这4个字节代表着a=10所代表的地址,并且每一个字节都是代表着地址
下面我们将通过&(取地址操作符来得到a的地址)
这边显示的结果是同上面通过内存调试出来的结果是相同的
这里我们通过&符号将a的地址存储到指针变量p 并且类型为int*的指针
指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
上面那张图我们可以看到 int*p=&a;
那么*p=&a有什么用处呢?
#include<stdio.h>
int main(){int a = 100;int* pa = &a;*pa = 0;return 0;}
上面那一段代码我们通过*pa来改变了a的值,结果如下图
有同学肯定在想,这⾥如果⽬的就是把a改成0的话,写成 a = 0; 不就完了,为啥⾮要使⽤指针呢? 其实这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活, 后期慢慢就能理解了。
指针变量的大小(在VS2022中)
分别为x86以及x64环境下面
我们可以看到指针变量的大小在统一环境下面他们的大小是相同的
结论:
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节 X64环境输出结果
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
指针变量的意义以及和地址的关系
那么指针变量大小有什么意义呢?
我们看下面两段代码
//代码1
#include <stdio.h>int main(){int n = 0x11223344;int *pi = &n; *pi = 0; return 0;}
//代码2
#include <stdio.h>int main(){int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;}
这两个代码的结果是代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
指针变量的地址变化
#include <stdio.h>int main(){int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;}
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。
结论:指针的类型决定了指针向前或者向后走⼀步有多大(距离)。
指针运算 指针的基本运算有三种:
• 指针+-整数
• 指针-指针
• 指针的关系运算
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
导致野指针的原因:1. 指针未初始化 2. 指针越界访问 3. 指针指向的空间释放
避免野指针的方面:1.指针初始化 2.⼩⼼指针越界 3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性 4.避免返回局部变量的地址
assert断言包含在assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
assert(p != NULL);
用来 验证变量p是否等于NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
传值调用和传址调用
传值调用
#include <stdio.h>void Swap1(int x, int y){int tmp = x;x = y;y = tmp;}int main(){int a = 0;int b = 0;scanf("%d %d", &a, &b);Swap1(a, b);printf("交换前: a=%d b=%d\n", a, b);printf(" 交换后:a=%d b=%d\n", a, b);return 0;}
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。 所以Swap1是失败的了。
传址调用
#include<stdio.h>
void Swap2(int*px, int*py){int tmp = 0;tmp = *px;*px = *py;*py = tmp;}
int main{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;}
我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这种函数调用方式叫:传址调用。
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调用。