1.内存和地址
电脑内存有8GB、16、32GB等等,说明了电脑储存数据的大小,那么问题来了,这么大的空间如何精确地找到每一个数据所在的位置呢?😊😊😊😊😊
生活中有地址,其实数据在内存中的储存也有地址,也就是耳熟能详的指针,让我们一起来了解指针吧
内存编址
内存编址就是给每个地址编号,不过并不是记录下来,而是通过硬件设计来完成,这里就要介绍地址总线
一根线有正负两种信号,32位(32根)就是232个,64位就是264个,这样地址信息可以下达到内存,而内存作出反应,将对应空间数据传给CPU寄存器
2.指针变量和地址
地址本身就是数据,也需要指针变量存起来
2.1&取地址操作符
int main()
{
int a=10;
printf("%p\n",&a);
return 0;
}
内存一个字节代表一个空间
整型4个字节,因此
&a取出地址较小的地址,即0x006FFD70
2.2指针变量和解引用操作符*
int* pa=&a;
*指明pa为指针变量,int为指针变量的类型
int a=0;
int* pa=&a;
*pa=2;//*pa指到达指针指向的地方,并可以修改对应的数值
pa=&b;//只使用pa表示修改指针变量所储存的地址
指针大小
32位,即4个字节
64位,即8个字节
3.指针变量类型的意义
3.1指针解引用
首先指针变量类型决定指针解引用的权限大小
int n = 0x11223344;
int* pi = &n;
*pi = 0;
我们清楚的看到,int 类型指针可以访问4个字节空间并修改为0
而char类型的指针只能修改一个字节空间
3.2指针±整数
上代码
int a = 1;
int* pi = &a;
char* po = &a;
printf("pi = %p\n", pi);
printf("pi + 1 = %p\n", pi+1);
printf("po = %p\n", po);
printf("po + 1 = %p\n", po+1);
我们清晰的看到指针变量类型决定了±整数能前进或后退一步能有多大距离
4.const修饰指针
4.1const修饰变量
上代码
通过const修饰的变量无法再次通过变量修改
但可以使用指针变量修改
4.2const修饰指针
同样const也可以修饰指针
但有两种情况
int const n = 12;
const int* pi = &n;
*pi = 2;
printf("%d\n", *pi);
consr在*前面表示指针变量指向的地址数据无法更改,但变量本身可以修改,且指针储存的地址也可以修改
int const n = 12;
int* const pi = &n;
pi = 2;
const位于*后面则指针变量储存的地址不可更改
5.指针运算
5.1指针±整数
int arr[8] = { 1,2,3,4,5,6,7,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* pi = arr;
for (i;i < sz;i++)
{
printf("%d ", *pi+i);
}
通过知道数组的首元素地址,指针不断加一就能访问每个元素
5.2指针±指针
指针-指针,指二者之间相同类型元素的个数
以数组为例
int arr[8] = { 1,2,3,4,5,6,7,8 };
//int sz = sizeof(arr) / sizeof(arr[0]);
//int i = 0;
int* pi =&arr[0];
int* po = &arr[7];
printf("%d\n", po - pi);
为什么是7而不是8呢?
5.3指针的关系运算
指针之间也可以比较大小
int arr[8] = { 1,2,3,4,5,6,7,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* pi =arr;
while (pi < arr + sz)
{
printf("%d ", *pi);
pi++;
}
6.野指针
野指针就是指指针指向的位置是不确定的(随机的、没有明确限定的)
6.1成因
1.指针未初始化
int *p;
*p=20;
2.指针越界访问
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 1;i < 12;i++)
{
*p = i;
printf("%d ", *p);
p++;
}
3.指针指向的空间释放
int* test()
{
int a=0;
return &a;
}
int main()
{
int* p=test();
*p=20;
reurn 0;
}
a为临时变量,函数调用后就会销毁,此时*就指向了不确定位置
6.2如何规避
6.2.1指针初始化
如果知道指针指向哪里,就初始化地址,否则置NULL。
NULL1是C语言定义的一个标识符常量,值为0,0也是地址,只是无法使用,读写时会报错
int num=10;
int* po=#
int *Pi=NULL;
6.2.2小心指针越界
使用指针就养成检查的习惯
6.2.3指针使用之前检查有效性,使用后及时置NULL
int arr[10]={0};
int i=0;
int*p=arr;
for(i=0;i<10;i++)
{
*p=i;
p++;
}
*p=NULL;//使用后即使置空
6.2.4避免返回局部变量的地址
7.assert断言
在assert.h中定义了宏assert(表达式),符合表达式条件就返回非0,程序正常运行,如果不符合,返回0,程序报错,停止运行
assert(*p!=NULL);
assert()不仅可以自动标识文件和指出出问题的行号,还有一种无需更改就能开启或关闭assert的机制
如果程序运行已经没有问题,那我们可以在#include<assert.h>前加上#define NDEBUG
然后重新编译程序,编译器会自动禁用所有assert语句
防止assert语句增加程序陨星的时间
一般我们可以在debug版本中使用,在release版本中禁用
8.指针的使用和传址调用
如果我们要实现两个整型的交换,我们很有可能做出以下代码
void swap(int a, int b)
{
int i = 0;
i = a;
a = b;
b = i;
return;
}
int main()
{
int a = 12;
int b = 32;
printf("a=%d b=%d\n", a, b);
printf("交换后:\n");
swap(a, b);
printf("a=%d b=%d\n", a, b);
return 0;
}
还记得我们说过,函数的形参是实参的一份拷贝
形参xy的确交换了,但不会影响实参,也就是传值调用
这就是典型只能用指针解决的问题
来看看代码
void swap(int* x, int* y)
{
int i = 0;
i =* x;
*x =* y;
*y = i;
return;
}
int main()
{
int a = 12;
int b = 32;
int* pi = &a;
int* po = &b;
printf("a=%d b=%d\n", a, b);
printf("交换后:\n");
swap(pi,po );
printf("a=%d b=%d\n", a, b);
return 0;
}
这就是传址调用