指针3
接上节课,最后讲到野指针的成因,今天会继续讲和指针相关的知识。
如何规避野指针
一共有下面几个办法:
- 给不知道指向的指针赋值为
NULL
; - 小心指针越界;
- 指针不使用时,及时置为
NULL
,使用前检查有效性; - 避免返回局部变量的地址。
NULL
就是0,这个地址不可以读也不可以写。
看一下代码(NULL处理指针
):
int*p;//这里没有初始化,所以p是野指针
int* p = NULL;//赋值为NULL,避免野指针
下面是如何检查有效性的:
if(p != NULL)
{
//...
}
如果p
是NULL
,就不会执行下面的代码。也可以直接写成if(p)
。
assert 断言
在上面检查有效性的时候,常常会使用assert
,因为它可以:
- 检查表达式的有效性,如果有效,无事发生,如果无效,程序会在
assert
处报错并终止运行; release
版本一般没有assert
,编译器会注释掉。也可以使用#define NDEBUG
来关闭assert
。
assert的语法:
assert(表达式)
表达式为真,无事发生。为假,则报错。
使用assert的时候,需要#include <assert.h>
,当需要关闭的时候,在这个头文件之前的位置加入:#define NDEBUG
。就是:
#define NDEBUG
#include <assert.h>
这样就会关闭assert
。
指针的使用
传址调用
写一个函数,交换两个整型变量的值。
void swap(int x, int y)
{
x ^= y;
y ^= x;
x ^= y;
}
int main()
{
int a = 10;
int b = 20;
swap(a,b);
return 0;
}
这段代码是不能交换a
和b
的。因为x
,y
是形参,而对形参的操作是不会改变实参a
,b
的值的。为了解决这个问题,就必须使用传址调用。重新写一下:
void swap_int(int* px, int* py)
{
*px ^= *py;
*py ^= *px;
*px ^= *py;
}
int main()
{
int a = 10;
int b = 20;
swap_int(&a,&b);
return 0;
}
这样改造后,swap_int
函数实际调用的是地址,swap_int
通过地址来改变实参的值。
strlen的模拟实现
直接上代码:
int my_strlen(const char* str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
分析一下。
- 函数参数使用const修饰,这样就保证了*str不会改变,因为我们只是在计算字符串长度,不会改变字符串的内容;
- 使用
assert(str)
来判断指针是否有效;
数组名的理解
数组名仅在以下两种情况表示整个数组:
- sizeof(数组名);
- &数组名;
其余情况,数组名都是数组元素的首地址。
看一下代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",sizeof(arr));//打印40,这里是数组的大小
printf("%p\n",&arr);//假设打印为0x1240ff00
printf("%p\n",&arr+1);//那么这里打印的就是0x1240ff28
这里,&arr
和&arr+1
差了0x28
,说明&arr
不是首元素地址。0x28
在十进制表示就是40,和数组大小一致。
使用指针访问数组
知道了数组名的意义,就可以使用指针来访问数组了。看一下代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i< sz;i++)
{
printf("%d ",p[i]);
printf("%d ",*(p+i));
printf("%d ",arr[i]);
printf("%d ",*(arr+i));
}
这些打印都是一样的,会打印数组中的每一个元素。所以可以捋一下:
p[i] == arr[i] == *(arr+i) == *(p+i) == *(i+p) == i[p];
当然,一般不会按照等式的最后书写,这样比较难理解。
一维数组传参的本质
数组名作为参数传递给函数的时候,实际传递的是数组首元素的地址。如果在函数内部计算数组长度的话,是无法得到正确结果的。最好的做法是将数组大小在函数外计算,然后作为参数一起传给函数。
冒泡排序
思想:冒泡排序就是将相邻的元素进行比较。如果满足某个条件,则交换,如果不满足,就保留。举个例子:
int arr[10] = {9,8,7,6,5,4,3,2,1,0};
使用冒泡排序将上面的数组排位升序(从小到大)。
先分析一下:
由于是升序,那么最后输出的应该是:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
依据冒泡排序的思想,开始的时候是将9
和8
比较,由于9>8
,又要求升序,所以交换位置,数组变成:
int arr[10] = {8,9,7,6,5,4,3,2,1,0};
然后继续从9
开始,比较9
和7
,又需要交换:
int arr[10] = {8,7,9,6,5,4,3,2,1,0};
9在和数组里每一个元素比较之后,数组变成:
int arr[10] = {8,7,6,5,4,3,2,1,0,9};
这样,一趟冒泡排序结束了。可以看到,数字9
已经到了它的最终位置。9
一共比较了9
次。
第二趟排序从8开始,经过同样的过程,得到:
int arr[10] = {7,6,5,4,3,2,1,0,8,9};
这里8
一共比较了8
次。
这样一直下去,最后冒泡排序的趟数一共是9次,每次比较的次数和当前的趟数有关系。
想到这里,可以尝试写代码了:
void bubble_sort(int arr[],int sz)
{
int i = 0;
for(i = 0; i<sz-1;i++)//趟数
{
int j = 0;
for(j=0;j<;j++)//次数
{
if(arr[j]>arr[j+1])
{
swap(&arr[j],&arr[j+1]);
}
}
}
}
这里次数到底怎么算呢?趟数是sz-1
,比较的次数肯定比sz-1
小,仔细思考一下。i = 0
的时候,9
次,i=1
的时候,8
次,i=2
的时候 7次
。那么次数就是sz-1-i
。也就是总的趟数-当前趟数(当前趟数从0开始)。
那么第一版代码就可以写了:
void bubble_sort(int arr[],int sz)
{
int i = 0;
for(i = 0; i<sz-1;i++)//趟数
{
int j = 0;
for(j=0;j<sz-1-i;j++)//次数
{
if(arr[j]>arr[j+1])
{
swap(&arr[j],&arr[j+1]);
}
}
}
}
这样写还是有些问题的。如果说数组本身就是有序的,那么还需要这样一趟一趟的排序吗?因此,可以引入一个判断,如果数组有序,那么就直接跳出循环。如果去判断呢?其实很简单。数组有序的话是不会有元素交换的。以此改一下代码:
void bubble_sort(int arr[],int sz)
{
int i = 0;
for(i = 0; i<sz-1;i++)//趟数
{
int flag = 1;//有序标记,假设是有序的
int j = 0;
for(j=0;j<sz-1-i;j++)//次数
{
if(arr[j]>arr[j+1])
{
swap(&arr[j],&arr[j+1]);
flag = 0; //发生了交换,标记为无序
}
}
if(1 == flag)//一趟下来没发生任何一次交换,数组就是有序的
break;
}
}
附带一下swap
函数:
void swap(int* px, int* py)
{
*px ^= *py;
*py ^= *px;
*px ^= *py;
}
一个练习:使用筛选法来打印1~100之间的素数
还是先说思想:
把一组数字从小到大排列,先划掉1,因为它不是素数也不是合数。然后保留2,因为2是素数。然后划掉1~100之间2的整倍数,因为他们都不是素数。然后保留3,划掉3的整倍数,如此继续下去直到10。为什么不是11?因为112 = 211,已经在2里面划过了,不需要再划一次。那么代码怎么实现呢?
看一下思路里面,由2
开始到10
结束,外层循环;内层的第一次划掉的数字是2*2
,然后是2*3
… 一直到2*50
。那么内层就是保证这两个循环数的乘积<=100
。如何划掉? 其实也很简单,可以创建一个数组,全部初始化为0。用数组的下标作为最后的输出,划掉的时候将这个数组下标对应的元素置1,最后打印值是0的下标就可以了。
看一下代码:
//筛选法求素数
int main()
{
int arr[101] = { 0 };//最终输出是下标,下标100对应数组中的第101个元素
int i, j;
for (i = 2; i <= 10; i++)//筛子的被乘数,2~10即可
{
for (j = 2; i*j < 101; j++)//筛子的乘数,保证i*j不超过101即可
{
arr[i*j] = 1;//i*j实际是筛子的整倍数,例如i=2的时候,i*j会筛掉100以内所有2的倍数,这里就是把数组下标的对应元素置为1,做个标记
}
}
for (i = 2; i < 101; i++)
{
if (arr[i] == 0)//数组下标为0,那就说明这个数是素数
{
printf("%d ", i);
}
}
}