目录
1. 内存
2. 取地址&
3. 指针变量
4. 解引用
4.1 *解引用
4.2 []解引用
4.3 ->解引用
5. 指针变量的大小
5.1 结论
6. 指针运算
7. void* 指针
8. const修饰指针
8.1 const修饰变量
8.2 const修饰指针变量
8.3 结论
9. 野指针
9.1 为什么会出现野指针?
9.1.1 指针未初始化
9.1.2 指针越界访问
9.1.3 指针指向的空间释放
9.2 如何规避野指针
10. assert断言
11. 指针的作用
1. 内存
我们都知道,手机,电脑里都是有内存的,电脑CPU在处理数据时,需要的数据从内存中读取,处理后再放回内存中
每个内存单元为1个字节
具体的换算单位如下:
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
CPU为了更好的处理数据,找到这个数据,就需要有地址,有了地址才能找到所需要的数据
每个字节都有属于自己的名字(地址),有了这个名字才能更好的找到它
那我们如何获得地址呢?
2. 取地址&
&符号当然可以取地址了
int a = 10;
printf("%p\n", &a);
//%p是打印地址的
我们知道 int 有4个字节,这4个字节都有地址
如上图中,如果a是占这4个字节
那么打印出来的地址将会是较小的那个地址0x006FFD70
我们只要得到了较小的那个地址,剩下的三个地址就知道了
3. 指针变量
我们获得了地址之后,我们怎么存储起来方便我们后续使用呢?
所以就有了指针变量起作用的时候了
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
return 0;
}
在int的后面加上了个 * 号,它就是指针类型了,而这个p就是指针变量,且是整型的指针变量
我们看待这个指针的时候要把 int 和 * 看成一对,int* 就和上面的 int类似,都是个类型,它两组成了一个整型指针类型,这个p就是个名字,和 a 是一样的
然后我们就可以把整型a的地址放进这个指针变量里了
4. 解引用
我们现在有了地址,当然可以到地址里面去使用地址内部的东西了
具体使用方法需要解引用
当我们解引用后,就可以拿到内部的东西了
4.1 *解引用
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", *p);
return 0;
}
输出:10
对我们刚刚创建的指针变量名字前面加上个*即可解引用
4.2 []解引用
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", p[0]);
return 0;
}
输出:10
是不是很像数组?其实指针变量也可以算是一个数组,这与我们直接创建一个数组是一样的
#include <stdio.h>
int main()
{
int p[] = {1,2,3,4,5};
printf("%d\n", p[0]);
return 0;
}
数组名本质就是地址,地址加上解引用符号才能获得值,大同小异
4.3 ->解引用
可以把它叫做箭头解引用,要用到它得学到结构体指针才会用到它
#include <stdio.h>
struct s
{
int n;
};
int main()
{
struct s s1 = { 10 };
struct s* s2 = &s1;
printf("%d\n", s2->n);
return 0;
}
输出:10
把结构体指针解引用的符号就是使用箭头->
5. 指针变量的大小
指针变量也是有大小的,并且这个和运行环境有着部分关系
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char*));
printf("%zd\n", sizeof(short*));
printf("%zd\n", sizeof(int*));
printf("%zd\n", sizeof(double*));
return 0;
}
在x86的环境下结果是4,当我们改成x64后
结果变成了8
直接说结论
5.1 结论
1.指针变量的大小和类型无关,只要是指针类型变量,在相同的平台下,大小都是相同的
2.32位平台下地址是32个bit位,指针大小是4个字节
3.64位平台下地址是64个bit位,指针大小是8个字节
6. 指针运算
指针也是可以进行加减运算的,但是不能进行乘除运算,因为乘除运算没有意义
如果是int类型的指针,那么它每+1就跳过4个字节,char类型指针+1就跳过一个字节,由指针类型决定
#include <stdio.h>
int main()
{
int a = 10;
char b = 'a';
printf("&a = %p\n", &a);
printf("&a+1 = %p\n", &a+1);
printf("&b = %p\n", &b);
printf("&b+1 = %p\n", &b + 1);
return 0;
}
7. void* 指针
void*指针是一种特殊的指针,并且它不能进行指针运算和解引用运算,但是它可以接受任意类型的地址
例如:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
return 0;
}
这样想在char* 里存一个整型地址会报警告
但是我们可以使用void*放
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
void* pc = &a;
return 0;
}
8. const修饰指针
我们知道const修饰的变量是不可改变的,但是如果硬要改还是能改的
8.1 const修饰变量
#include <stdio.h>
int main()
{
const int a = 10;
a = 5;
return 0;
}
但是如果我们使用指针变量来改变呢?
#include <stdio.h>
int main()
{
const int a = 10;
int* p = &a;
*p = 5;
printf("%d\n", a);
return 0;
}
我们会发现它还是改变了
这是因为我们const的不够彻底,我们应该要让p就算拿到了a的地址也不能改变
8.2 const修饰指针变量
const的放置位置是有几种情况的
const int* p = &a;
int const * p = &a;
int* const p = &a;
第一行和第二行的const都放在了*p的前面,所以限制的是*p,作用是*p不能改变
#include <stdio.h>
int main()
{
int a = 10;
const int* p = &a;
//int const * p = &a;
*p = 5;//不可改变
printf("%d", *p);
return 0;
}
第三行const仅仅只放在p的前面,那么它只能限制p,p的地址就不能改变
#include <stdio.h>
int main()
{
int a = 10;
int b = 9;
int* const p = &a;
//int const * p = &a;
p = &b; //不可改变
printf("%d", *p);
return 0;
}
8.3 结论
主要是看const的位置在哪里,如果const是在*p的前面,限制的是*p,*p不可改变,如果是在*的后面p的前面,限制的是p,那么p不可改变
9. 野指针
概念:指针指向一片未知的区域
9.1 为什么会出现野指针?
9.1.1 指针未初始化
#include <stdio.h>
int main()
{
int* p;
*p = 20;
return 0;
}
*p并不知道指向了哪里,默认为随机值,会随机在一片地址把值改成20
9.1.2 指针越界访问
#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = a; //等同于int* p = &a[0];
for (int i = 0; i < 6; i++)
{
*(p++) = i;
}
return 0;
}
p一直加到了超越数组的位置,那么就是一片未知的区域,所以就变成了野指针
9.1.3 指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
这样*p虽然能获得值并且打印出来,函数每次调用都是建立栈空间,但是使用过后,函数建立的栈帧会消失, 那么地址就不存在了,p原本还指向这个函数,但函数消失后就变成了未知的区域,所以p就变成了野指针
9.2 如何规避野指针
1. 注意指针的初始化
2.小心指针越界访问
3.指针不使用时及时置空NULL
4.避免返回局部变量,如上述9.1.3
10. assert断言
#include <assert.h>
assert(p != NULL);
assert函数的作用是如果括号内为真,这个函数不会有任何反应,但如果为假,那么会报错,并且告诉你在第几行代码出错
assert()的使用对程序员是非常友好的,其一就是能精确报错,其二就是如果已经确定程序没有任何问题,不需要再做任何断言,我们是可以一键注释的
#define NDEBUG
#include <assert.h>
我们只需要在头文件前包括上这个宏定义即可
11. 指针的作用
在我们使用一个函数的时候如果不使用指针变量的话,只能传值,不能改变本身
例如:
#include <stdio.h>
void Swap1(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 5;
int y = 6;
Swap1(x, y);
printf("%d %d\n", x, y);
return 0;
}
这样是无法使两个值改变的,因为我们这里传的x和y只是它的值,并不是x和y的本身,所以交换a和b是不起交换x和y的作用的
但如果我们使用指针传它的名字(地址)过去,就可以找到x和y本身,然后将它们交换了
#include <stdio.h>
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int x = 5;
int y = 6;
Swap(&x, &y);
printf("%d %d\n", x, y);
return 0;
}
在函数内部要注意解引用
完