目录
一、内存与地址
二、指针变量
2.1指针变量
2.2解引用操作符(*)
2.3 指针变量的大小
三、指针运算
3.1指针+(-)整数
3.2指针-指针
编辑
3.3指针的关系运算
四、野指针
4.1指针未初始化
4.2指针越界
编辑
4.3指针指向的空间已经释放
4.4NULL
五、指针传值调用与传址调用
5.1传值调用
5.2传址调用
七、一维数组的传参
八、二维数组的传参
九、二级指针
9.1概念
9.2二级指针的使用
十、指针数组
十、回调函数
在购买电脑时,常常可以看到电脑的内存为8GB/16GB/32GB等,为了可以有效的管理内存,就把内存分成一个个的内存单元,每个内存单元的大小是1个字节。
bit 比特位 | ||||
byet 字节 | 1 byte= 8 bit | |||
KB | 1 KB= 1024 byte | |||
MB | 1 MB= 1024 KB | |||
GM | 1 GB= 1024 MB | |||
TB | 1 TB= 1024 GB | |||
PB | 1 PB= 1024 TB |
每个内存单元都有编号,CPU就是通过内存单元的编号找到相对应的内存空间。
计算机中内存单元的编号就是地址,在C语言中地址就叫做指针。
内存单元的编号==地址==指针
在C语言中,通过&操作符可以得到一个地址,为了方便的使用这个地址,可以将该地址存储到指针变量中。
#include
int main()
{
int a=1;
int *P=&a;//将a的地址存储到p中
return 0;
}
将地址储存在指针里后,就可以用解引用操作符(*),通过指针(地址)找到指针(地址)所指向的对象。
#include
int main()
{
int a = 0;
int* p = &a;
*p = 1;//通过*找到a,并更改a的值
printf("%d", a);
return 0;
}
指针变量的大小取决与地址的大小,不管储存的是什么类型的变量。
在32位平台下就是4个字节。
在64位平台下就是8个字节。
我所用的编译器在X64的环境下就是64位平台
X86的环境下就是32位平台
由上面两幅图可以清楚的看到,无论是上面类型的指针,指针的大小只取决与地址的大小。
因为数组在内存中是连续存放的,所以知道首元素的地址,就可以找到所以元素的地址。
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
//指针加法打印
for (int i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
//将指针调整到arr[9]的位置
p = p + 9;
//指针减法打印
for (int i = 9; i >= 0; i--)
{
printf("%d\n", *(p - i));
}
return 0;
}
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int* x = &arr[5];
int a=x - p;
printf("%d", a);
return 0;
}
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
while (p < arr + sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
定义:指针指向的位置是不确定的或随机的。
#include
int main()
{
int* p;//指针未初始化
p = 20;
return 0;
}
#include
int main()
{
int a[5] = { 0 };
int* p = &a;
for (int i = 0; i < 10; i++)
{
*p++ = 2;//p访问的范围超过了数组的大小,超过部分即为野指针
}
return 0;
}
#include
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();//test的空间已经释放
printf("%d\n", *p);
return 0;
}
当指针变量使用完成后,可以将其设为空指针(NULL)。在C语言中,如果一个指针的内容为NULL,就不会再取访问指针。
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
for (int i = 0; i < 10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if (p != NULL) //判断
{
//...
}
return 0;
}
传值调用:函数在使用时,根据参数内容创建临时变量完成函数。
创建的临时变量的修改并不会改变参数的值。
#include
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);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
传址调用:函数在使用时,找到参数的地址,用参数完成函数。
#include
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;
}
本质:传递数组首元素的地址
#include
void test(int arr[])
{
int sz2 = sizeof(arr);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr);//计算数组的字节大小
printf("sz1 = %d\n", sz1);
test(arr);//传的是首元素的地址,所以字节大小大小永远为4/8
return 0;
}
#include
void test(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
test(arr, 3, 5);
return 0;
}
指针变量也是变量,也有它自己的地址。指向指针的指针就是二级指针。
上面的图片中,ppa就是二级指针,它指向了pa这个指针。
对ppa使用解引用操作符(*),访问的就是pa的地址
如果连续使用两个*,访问的就是a的地址。
#include
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
**ppa = 30;//找到a
printf("%d\n", a);
*ppa =0x27849912;//找到pa
printf("%p", pa);
return 0;
}
指针数组:存放指针的数组 。
指针数组中每一个数都是指针。
#include
int main()
{
int* arr[5];
return 0;
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
可以留个免费的赞吗?