Lesson 13 指针3

指针3

上节课,最后讲到野指针的成因,今天会继续讲和指针相关的知识。

如何规避野指针

一共有下面几个办法:

  1. 给不知道指向的指针赋值为NULL
  2. 小心指针越界;
  3. 指针不使用时,及时置为NULL,使用前检查有效性;
  4. 避免返回局部变量的地址。
    NULL就是0,这个地址不可以读也不可以写。
    看一下代码(NULL处理指针):
int*p;//这里没有初始化,所以p是野指针
int* p = NULL;//赋值为NULL,避免野指针

下面是如何检查有效性的:

if(p != NULL)
{
	//...
}

如果pNULL,就不会执行下面的代码。也可以直接写成if(p)

assert 断言

在上面检查有效性的时候,常常会使用assert,因为它可以:

  1. 检查表达式的有效性,如果有效,无事发生,如果无效,程序会在assert处报错并终止运行;
  2. 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;
}

这段代码是不能交换ab的。因为xy是形参,而对形参的操作是不会改变实参ab的值的。为了解决这个问题,就必须使用传址调用。重新写一下:

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;
}

分析一下。

  1. 函数参数使用const修饰,这样就保证了*str不会改变,因为我们只是在计算字符串长度,不会改变字符串的内容;
  2. 使用assert(str)来判断指针是否有效;

数组名的理解

数组名仅在以下两种情况表示整个数组:

  1. sizeof(数组名);
  2. &数组名;
    其余情况,数组名都是数组元素的首地址
    看一下代码:
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};

依据冒泡排序的思想,开始的时候是将98比较,由于9>8,又要求升序,所以交换位置,数组变成:

int arr[10] = {8,9,7,6,5,4,3,2,1,0};

然后继续从9开始,比较97,又需要交换:

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);
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值