指针详解(进阶篇)

前言

上一篇我们讲了指针的基础篇,这一篇我们讲述指针的进阶篇。

一.字符指针

指针类型中有一种类型是char*,基本使用如下:

int main()
{
	char a = 'x';
	char* p = &a;
	*p = 'a';
	printf("%c", a);
}

这里还有另外一种使用方式:

int main()
{
	const char* pstr = "hello world";
	printf("%s\n", pstr);
	return 0;
}

这里我们定义了一个常量字符串,我们这里并不是将整个字符串放在pstr里,而是将字符串的首字符地址放进pstr里。

int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

在这里插入图片描述

这段代码的结果如上,为什么会如此,常量字符串和普通字符串有什么区别?常量字符串是存储在内存的静态区是不可修改的,也可以理解为只读,而普通字符串是可以修改的,存放在内存的栈区,这里str1和str2虽然内容相同,但完全是开辟了不同的两个空间,而在静态区的str3和str4编译器发现他们相同,直接存在了一个相同的空间。

二.指针数组

整型数组是用来存放整型的,那指针数组顾名思义就是用来存放指针的,例如:

char* arr[];//一级指针数组
int* arr[];
char** arr[];//二级指针数组

arr是指针名,而char* []才是指针类型。

int main()
{
	int* a=NULL;
	int* b=NULL;
	int* arr[2] = { a,b };
}

三.数组指针

很容易将指针数组和数组指针弄混,指针有整型指针,字符指针,那数组指针就是一个指向数组的指针,它的基本形式如下:

int (*p)[10];//指向一个含有10个元素的数组,指针类型为int(*)[10]

这里如果不加()那么p会先和[ ]结合,就会变成指针数组。

四.数组指针的使用

数组指针的使用大多数用在二维数组传参上,下面是数组指针最基本的使用:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	printf("%d", *(*p + 1));//*p=arr,*(arr+1)=arr[1]
}

这里必须使用&arr将数组的地址赋给p,如果这里只是arr的话,只表示数组的首地址。
二维数组传参:

void print(int(*p)[4],int row,int col)
{
	int i;
	int j;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%-2d ", p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	print(arr,3,4);
}

这里二维数组的数组名arr实际上数组第一行的地址,也就是说是一个数组,数组里有4个元素,所以这里我们使用数组指针进行传参。

五.函数指针

函数指针指的是指针指向的是一个函数,基本形式如下:

	int (*p)(int, int);//指针类型为int(*)(int, int)

*p后面的()内即是函数参数类型。

int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	void (*p)(int(*)[4], int, int);
	p = print;//&print=print
	p(arr,3,4);
}

上例就是运用函数指针调用函数,这里&print和print其实是一样的,函数名即地址
大家是否好奇函数指针的运用呢?比如我们经常会使用的qsort函数

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

qsort的最后一个参数即是函数指针。
qsort函数参数介绍:

  1. base,排序起始位置的地址
  2. num,需要排序的元素个数
  3. size,排序元素每个的大小
  4. 排序规则,>1则交换元素位置
int cmp_num(const void* a, const void* b)
{
	return *((int*)a) - *((int*)b);
}
int main()
{
	int arr[10] = { 5,6,11,55,21,3,7,9,1,2 };
	qsort(arr, 10, sizeof(arr[0]), cmp_num);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
}

这里我们就是使用函数指针进行传参,将cmp_num传入了qsort函数,决定了排序的规则。

用冒泡排序实现qsort

首先我们需要将大问题化成小问题,我们先将普通的冒泡函数写出来。

void bubble_sort(int* base, int num)
{
	int i;
	int j;
	int tmp;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 1; j < num - i - 1; j++)
		{
			if (base[j]>base[j-1])//判断就是我们需要换成函数的地方
			{
				tmp = base[j];
				base[j] = base[j + 1];
				base[j + 1] = tmp;
			}
		}
	}
}

前面我们说到qsort的第四个参数是用来决定排序规则的,冒泡排序里的判断语句我们就需要用第四个参数来替换他,从而决定什么条件下我们需要调换两个元素的位置,那么这里我们就需要更多的参数。

void bubble_sort(const void* base,int num,int sz,int(*p)(const void*,const void*))

将判断语句替换成判断函数,由于我们不清楚传入的数组类型,我们只能使用void来接收,我们传入函数的指针不知道每次应该跳过多少个字节,所以我们这里要先将base强转成char*,因为char的指针一次只跳过一个字节,在后面再加上jsize(每个元素的大小),就相当于跳过了j个元素。

if (p((char*)base+j*sz, (char*)base+(j+1)*sz)>0)/*将base强转成char*类型,
+j*sz就是跳过j个相同类型的元素*/

当我们满足调换条件的时候,我们需要考虑怎么调换两个元素,因为你不清楚他的元素类型。这里采用一个字节一个字节的交换,比如int四个字节,两个元素一个字节一个字节的交换,就不需要去考虑是什么数据类型,只需要知道占几个字节。

void swap(const void* e1, const void* e2, int sz)
{
	int i;
	char tmp;
	for (i = 0; i < sz; i++)//因为类型不定,这里一个字节一个字节交换
	{
		tmp = *(char*)e1;
		*(char*)e1= *(char*)e2;
		*(char*)e2 = tmp;
		((char*)e1)++;
		((char*)e2)++;
	}
}

最后就是应用,和qsort的应用基本一样,完整代码如下。

void swap(const void* e1, const void* e2, int sz)
{
	int i;
	char tmp;
	for (i = 0; i < sz; i++)//因为类型不定,这里一个字节一个字节交换
	{
		tmp = *(char*)e1;
		*(char*)e1= *(char*)e2;
		*(char*)e2 = tmp;
		((char*)e1)++;
		((char*)e2)++;
	}
}
void bubble_sort(const void* base,int num,int sz,int(*p)(const void*,const void*))
{
	int i;
	int j;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - i - 1; j++)
		{
			if (p((char*)base+j*sz, (char*)base+(j+1)*sz)>0)//将base强转成char*类型,+j*sz就是跳过j个相同类型的元素
			{
				swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
			}
		}
	}
}
//int cmp(const void* e1, const void* e2)//排序整型
//{
//	return *(int*)e1 - *(int*)e2;
//}
int cmp(const void* e1, const void* e2)//排序字符串
{
	return strcmp(*(char**)e1 , *(char**)e2);//strcmp(const char*,const char*),str的类型是char**,这里的e1,e2的类型是char**。
}
int main()
{
	int i;
	//int arr[] = { 4,2,6,7,1,2,3 };
	char* str[] = { "lyh","cgf","zjc","xhj" };
	//bubble_sort(arr,sizeof(arr)/sizeof(arr[0]),4,cmp);
	bubble_sort(str, sizeof(str) / sizeof(str[0]), 4, cmp);
	//for (i = 0; i < 7; i++)
	//{
	//	printf("%d ", arr[i]);
	//}
	for (i = 0; i < 4; i++)
	{
		printf("%s ", str[i]);
	}
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值