指针重难、易错点

(一)函数指针

在前边的blog中,已经整理归纳了数组和初级指针,接下来,我来继续整理高级指针和如何正确使用指针。

我们说过,指针数组是一个数组,每个元素是指针;数组指针是个指针,指向的是数组。所以:

函数指针就是指向函数的指针。我们先看以下代码:

<pre name="code" class="cpp">void fun()
{
}
int main()
{
printf("%p",fun);
printf("%p",&fun);
printf("%p",fun+1);
printf("%p",&fun+1);
fun();
(*fun)();
}


 

我们来分析以下这个程序运行出来的结果是什么。这个程序如果你拿到自己的编译环境下测试,是编译不通过的。函

名被编译之后其实就是一个地址,所以最后两句代码作用一样。对函数名加1的操作编译不通过。

注意区分一下两句代码:

void *p(int,char);//这是一个函数,返回值类型void*

void(*q)(int,char);//定义了函数指针变量q,q指向的函数有两个参数,int和char,返回值void

我们之前说过,给定数组指针int (*parr)[5];该数组指针的类型是int (*)[5].同样类比,函数指针变量的类型是什么(针

对上边例子的函数指针)?它的类型是void (*)(int,char);所以我们可以将上式理解为:void(*)(int,char) q;这样子是不是

是不是好理解一点呢?只是编译器不同意你这么写~~~知道这个很重要。

下边我们来看一下《c陷阱与缺陷》里的这个例子:

(*(void(*)())0)()这个表示的是什么??

void(*)(),这是函数指针类型;

(void(*)())0,将0强制转化成函数指针类型

(*(void(*)())0),取0开始的一段内存里边的内容,其内容就是保存在首地址为0的一段区域内的函数;

(*(void(*)())0)(),这就是对这个函数的调用。

再看下边一个例子:

(*(char **(*)(char**,char**))0)(char**,char **)表示什么呢??

char **(*)(char**,char**)这是一个函数指针类型;

*(char **(*)(char**,char**))0,将0强制转化成函数指针类型,并解引用找到该内存的内容,即就是一个函数。

这个函数的参数有两个,char**,char**。

继续下边一个例子:void (*signal(int,void(*)(int)))(int);

分析:signal是一个函数,有两个参数,int型和void(*)(int),返回值是一个函数指针,该函数指针指向的函数参数为int

型,返回值为void。

由于void(*)(int)的多次出现,我们可以使用typedef关键字简化,如下:

typedef void(*)(int) p;//我们可以这样理解,但是不可以这样对编译器~~

typedef void(*pfun)(int);

所以根据上边的分析,原式子就可以简化写为:pfun signal(int,pfun);

函数指针数组:这是一个数组,数组里的每一个元素都是指向函数的指针。

void (*parr[3])(int);这就是函数指针数组,如果看不出来我们这样来:void (*)(int) parr[3];这样就可以理解了,只是编

译器不同意我们这样~~

函数指针数组指针:原型举例:void (*(*parr)[3])(int) ;可以理解为:void (*)(int) (*parr)[3];

写了这么多,貌似我们还没有了解函数指针可以用来干什么。其实,函数指针可以用于很多地方。当我们需要对一组

整形数排序时,我们可以用最基本的冒泡排序(虽然效率不是很高)来实现,可是,如果我要求你排序结构体时,你

是不是又要写一个函数去被调用,我们发现,这两个函数基本是一样的,所以,我们可以直接用回调函数来实现~~

下边我们就用回调函数实现排序各种类型的数~~

<pre name="code" class="cpp">int compare(const void *elem1, const void *elem2)
{
	return *(const int *)elem1 - *(const int *)elem2;
}
void sort(void *base, unsigned int num,
 unsigned int byte, int(*cmp)(const void *elem1, const void *elem2))
{
	char *pbase = (char *)base;
	int flag = 0;
	int i = 0;
	int j = 0;
	int k = 0;
	const void *p1 = NULL;
	const void *p2 = NULL;
	for (i = 0;i < num - 1;i++)
	{
		flag = 0;
		for (j = 0;j < num - 1 - i;j++)
		{
			p1 = (const void *)(pbase + j*byte);
			p2 = (const void *)(pbase + (j + 1)*byte);
			int ret = cmp(p1, p2);
			if (ret > 0)
			{
				for (k = 0;k < byte;k++)
				{//交换秘诀
					pbase[j*byte + k] = pbase[j*byte + k] + pbase[(j + 1)*byte + k];
					pbase[(j + 1)*byte + k] = pbase[j*byte + k] - pbase[(j + 1)*byte + k];
					pbase[j*byte + k] = pbase[j*byte + k] - pbase[(j + 1)*byte + k];
				}
				flag = 1;
			}
			if (flag == 0)
				break;
		}
	}
}
int main()
{
	int(*cmp)(const void *elem1, const void *elem2) = compare;
	int arr[4] = { 6,7,4,2 };
	int i = 0;
	sort(arr,4,4,cmp);
	for (i = 0;i < 4;i++)
	{
		printf("%d ",arr[i]);
	}
	system("pause");
	return 0;
}


 交换两个数有多种方法,创建临时变量,想必大家都知道~而不创建临时变量的交换,我们来归纳一下: 

比如,交换整形数a和b:

方法一:创建临时变量;

方法二:利用加减的方法;

代码:

a = a+b;
b = a-b;
a = a-b;
方法三:利用乘除的方法;

a = a *b;
b = a/b;
a= a/b;

方法四:异或的方法:

int a= 3;//011
int b = 5;//101
a = a^b;//110
b=a^b;//011
a = a^b;//101

在《剑指offer》这本书中又看到这样一道题:将一个数组中的所有奇数调到所有偶数之前。我们用指针或者数组的方

法,都可以实现~下边给出指针的方法~~

void swap_odd_even(int arr[], int n)
{
	if (n <= 1)
		return;
	int *pstart = arr;
	int *pend = arr + n - 1;
	int tmp = 0;
	while (pstart < pend)
	{
		while (pstart < pend && *pstart % 2 == 1)//如果是奇数,继续向后遍历
		{
			pstart++;
		}
		while (pstart < pend && *pend % 2 == 0)//如果是偶数,就向前遍历
		{
			pend--;
		}
		tmp = *pstart;
		*pstart = *pend;
		*pend = tmp;
	}

}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int i = 0;
	swap_odd_even(arr,10);
	for (i = 0;i < 10;i++)
	{
		printf("%d ",arr[i]);
	}
	system("pause");
	return 0;
}
看了上述代码之后,会不会有人觉得奇偶交换之后指针没有变动呢~~告诉你吧,交换之后质变指针指向奇数,右边指

针指向偶数,两个while循环依然可以执行向后遍历~~

但是,但是,如果要求我们将能被3整除的放在数组左边,不能被3整除的放在数组右边,我们是不是又要写出一个函

数呢??不用的,我们可以使用回调函数来实现~

int ope(const void *elem)
{
	return *(const int *)elem % 3;
}
void swap_odd_even(int arr[], int n, void(*p)(const void *))
{
	if (n <= 1)
		return;
	int *pstart = arr;
	int *pend = arr + n - 1;
	int tmp = 0;
	while (pstart < pend)
	{
		while (pstart < pend && ope((const void *)pstart))//如果是不能整除的数,继续向后遍历
		{
			pstart++;
		}
		while (pstart < pend && ope((const void *)pend) == 0)//如果是可以整除,就向前遍历
		{
			pend--;
		}
		tmp = *pstart;
		*pstart = *pend;
		*pend = tmp;
	}

}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int i = 0;
	void(*p)(const void *) = ope;
	swap_odd_even(arr,10,p);
	for (i = 0;i < 10;i++)
	{
		printf("%d ",arr[i]);
	}
	system("pause");
	return 0;
}

我们只需要加上一个小小的操作函数判断一下就可以了~~

(二)指针使用过程中的易错点和难点:

1.空指针:前边模拟实现memmove,memcpy等函数时,我们就使用了空指针,希望她可以接收任意类型的数,我们

也说过,空指针不能自加,也不能解引用,但是只是在windows下,那么我们来看看在gcc环境下的情况:

代码如图:

运行通过~~~

2.野指针:野指针就是对内容不明确的指针进行解引用~

int *p;
*p =10;

p就是一个野指针,我们并不知道p里边存放着哪个变量的地址,所以解引用时,你懂~~再看下边一个例子:

struct student
{
    char *name;
    int score;
}stu,*pstu;
int main()
{
    strcpy(sty.name,"yaoyao");
    stu.score = 100;
    return 0;
}

乍一看这段代码没有任何问题,其实,我们只知道指针变量name被分配了4字节(32位系统),并不知道里边的内容

,所以复制时无效。解决办法:给那么指针动态分配空间,这里就不给出代码了,可以自己实现~

注意:函数入口时,一定要对指针变量是否为NULL进行判断,我们可以通过assert断言实现。需要注意的是,这个宏

只能在debug版本使用,release版本并不可以。原因在于:assert的一切作用就是尽可能的在调试函数的时候吧错误

定位,而不是等到release之后。assert是定位错误,不是排除错误。当程序被完整地测试完毕后,可以在预编译中通

过定义NDEBUG来消除所有的断言。assert宏所在头文件是,assert.h。

3.内存泄漏:没有释放向系统申请的内存,举例:

<pre name="code" class="cpp"><pre name="code" class="cpp">void getmemory(char *p,int num)
{
    p = (char *)malloc(num * sizeof(char));
}
int main()
{
    char *str = NULL;
    getmemory(str,10);
    strcpy(str,"hello");
    free(str);
    return 0;
}

 
 

这个程序运行后会崩溃。在main函数中将参数str传进getmemory函数,此时p = NULL,而这个p是在getmemory的栈

帧中,str在main函数栈帧中,在函数中动态分配内存赋给p,改变了p,并不能改变str。如何更正使得程序运行通过

呢?给出两种方法:

1.将str的地址传过去。

2.将分配好的内存首地址返回。下边给出一种更正方法的代码:

char *getmemory(int num)
{
    char *p = (char *)malloc(num * sizeof(char));
    return p;
}
int main()
{
    //char *str = NULL;
    char *ret = getmemory(10);
    strcpy(ret,"hello");
    free(ret);
    ret = NULL;
    return 0;
}

注意:malloc和free要成对,就像上边那个代码,虽然成对,但是跟没成对一样。free可以释放空指针,释放完后要置

空。

不可返回局部变量的地址。上边代码返回的是局部变量,不是局部变量的地址。

看下边的例子:

int* fun()
{
    int tmp = 10;
    return &tmp;
}
int main()
{
    int *pret = fun();
    printf("%d",*pret);
    return 0;
}
这段代码或许可以运行通过,输出结果是10,那是因为fun函数的那块内存还在(没有被别人占用),要是被占用

了,程序就会运行出错,因为出了那个函数,那块空间就已经不属于那个函数,返回的地址肯定是不对的。

指针很重要,使用好指针更重要~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值