一 , 野指针
概念:野指针就是指针指针指向的位置是不可知的。(随机的,不正确的,没有明确的限制)
野指针成因:
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
1.1 指针未初始化
int main()
{
int* p; //局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
1.2 指针的越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
1.3 指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
这里的 n 的局部变量,在进入test 的这个函数时,被创建,但是当出了这个函数 , n 就把空间还回去给内存,我们对n 原有的空间没有访问权限了,如果再去访问,就造成的非法访问;
1.4 规避野指针
指针初始化:
如果能够明确知道指针指向哪里,就应该直接赋值地址,但是如果不知道指针应该指向哪里,可以给指针赋值NULL , NULL是C语言中定义的一个常识符常量,值是0,0也是地址,但是这个地址是无法使用的,读写的时候会报错。
int main()
{
int a = 10;
int* pa =&a;
*pa = 50;
int* pb = NULL;
*pb = 20;
return 0;
}
小心指针越界:
一个程序向内存申请了那些空间,通过指针也能访问那些空间,不能超出范围访问。
指针变量不再使用的时候,即使置NULL,指针使用之前检查有效性
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL。 我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起来。不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去使用
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
for(i=0; i<10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}
避免返回局部变量的地址!
二 , 数组名的理解
我们一般说,数组名是数组首元素的地址
但是!在以下两种情况下,数组名表示的是整个数组:
- sizeof(数组名),sizeof 中单独放数组名时,这里的数组名表示的时整个数组,计算的是整个数组的大小。
- &数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址(整个数组的地址,与首元素的地址是有区别的)!
运行以下代码:
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
从中我们知道:数组名本来就是地址,是数组首元素的地址
2.1 sizeof(数组名)
这里我们输出的结果是40,但是如果是arr数组的首元素应该是4/8才对,所以我们知道,在sizeof(数组名)里面的数组名表示的是整个数组。
2.2 &数组名
int main()
{
int arr[10] = { 0 };
printf("&arr = %p\n", &arr);
printf("&arr +1 = %p\n", &arr+1);
printf("\n");
printf("arr = %p\n", arr);
printf("arr +1 = %p\n", arr+1);
printf("\n");
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
return 0;
}
//指针类型决定了,指针+-整数时跳过的步长
//&arr [0] ----> int*
//arr ----> int*
//&arr ----> 类型?
三 , 使用指针访问数组
在访问数组的时候:我们可以采用数组下标来访问,也可以采用指针来访问
//使用指针访问数组
int main()
{
//使用下标的方式访问数组
int arr[10] = {0};
int i = 0;
//输入
for (i = 0; i < 10; i++)
{
scanf("%d", arr+i);
}
//输出
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
//用指针来访问数组元素
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
//输入
for (i = 0; i <sz; i++)
{
scanf("%d", p+i);//p+i 就是下标为i元素的地址
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
*(i+p) *(i+arr)
* (p+i) *(arr+i)
p[i] arr[i]
i[p] i[arr]
这些都是正确的写法,因为arr[i] 与 *(arr+i) 完全等价
arr[i] 的这种写法,编译器在执行的时候也会转成*(arr+i)的方式执行
但是我们一般不会用i[arr]的这一种写法,可读性不强
/arr[i] --- *(arr+i) -- *(i+arr) -- i[arr]
//i[arr] 可读性弱,不建议这么写,但是可以了解
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i < sz; i++)
{
//printf("%d ", arr[i]);
//printf("%d ", *(arr + i));
//printf("%d ", *(i + arr));
printf("%d ", i[arr]);
}
return 0;
}
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
//arr是数组名,数组名是数组首元素的地址
//p 是数组首元素的地址
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (i = 0; i < sz; i++)
{
//printf("%d ", *(p + i));
//printf("%d ", *(arr + i));
//printf("%d ", p[i]);
printf("%d ", arr[i]);
}
//arr[i] ---- *(arr+i) 完全等价
//arr[i] 这种写法,编译器在执行的时候也会转化成*(arr+i)的方式执行
return 0;
}
四 ,一维数组的传参本质
数组名是数组首元素的地址;而在数组传参的时候,传递的是数组名 ,也就是说,数组传参传递的是数组首元素的地址。
运行如下代码:
void test(int arr[])
{
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d \n", sz);
}
int main()
{
int arr[10] = { 0 };
test(arr);
return 0;
}
我们发现结果并不是我们想要的数组元素个数,而是得到了2。所以我们知道,这里形参是指针变量,在x64环境下,大小为2;所以,函数形参的部分理论上应该使用指针变量来接受首元素的地址。那么我们在函数内部我们写sizeof(arr)计算的是一个地址的大小,而不是数组的大小。正是因为函数的参数部分的本质是指针,所以在函数内部是无法计算数组元素的个数。
如果想要使用数组元素个数,我们可以计算完后,在传递给函数
void test(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
test(arr, sz);
return 0;
}
一维数组传参,形参部分可以写成数组形式,也可以写成指针形式 。
五 , 冒泡排序
核心思想:两两相邻的元素进行比较
//冒泡排序
//void bubble_sort(int* arr, int sz)
//{
// int i = 0;
// //趟数
// for (i = 0; i < sz-1; i++)
// {
// //一趟的冒泡排序
// for (int j = 0; j < sz - i - 1; j++)
// {
// if (arr[j] > arr[j + 1])
// {
//
// int tmp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = tmp;
// }
// }
// }
//}
//
//int main()
//{
// int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// bubble_sort(arr, sz);
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", arr[i]);
// }
// printf("\n");
// return 0;
//}
//优化
//void bubble_sort(int* arr, int sz)
//{
// int i = 0;
// //趟数
// for (i = 0; i < sz - 1; i++)
// {
// //一趟的冒泡排序的细节
// int flag = 1;//假设已经有序了
// for (int j = 0; j < sz - i - 1; j++)
// {
// if (arr[j] > arr[j + 1])
// {
//
// int tmp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = tmp;
// flag = 0;
// }
// }
// //效率提高,如果一次都没有交换,flag 为1
// if (flag == 1)
// {
// break;
// }
// }
//}
//
//int main()
//{
// int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
// int sz = sizeof(arr) / sizeof(arr[0]);
// bubble_sort(arr, sz);
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", arr[i]);
// }
// printf("\n");
// return 0;
//}
//
六 , 二级指针
指针变量也是变量,是变量就有地址。而存放一级指针的变量就是二级指针。
int main()
{
int a = 10;
int* pa = &a; //pa是指针变量,一级指针变量
int** ppa = &pa; //ppa 就是二级指针变量,二级指针变量用来存放一级指针变量的地址
printf("%p\n", &a);
printf("%p\n", pa);
printf("%p\n", &pa);
printf("%p\n", ppa);
printf("%d\n", **ppa);
//int*** pppa = &ppa;
return 0;
}
七 , 指针数组
在了解指针数组之前,我们先类比一下:
整型数组 ---- int arr[5] ---- 存放整型的数组
字符数组 ---- char arr[6] ----存放字符的数组
所以
指针数组 ---- 是用来存放指针(地址)的数组
int main()
{
//int arr1[5];
//char arr2[6];
char * int* double*
//char* arr3[10]; //存放字符指针的数组
//int* arr4[7]; //存放整型指针的数组
int a = 10;
int b = 20;
int c = 30;
int* arr[] = { &a,&b,&c }; //指针数组
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
指针数组的每一个元素都是地址,又可以指向一块区域
八 ,指针数组模拟二维数组
//指针数组模拟二维数组
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 2,3,4,5,6,7 };
int arr3[] = { 3,4,5,6,7,8 };
// int* int* int* --->数组名是首元素的地址
int* arr[] = { arr1, arr2, arr3 };
//指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的