一、字符指针
字符指针一般有两种写法:
第一种:
char c = 'a';
char* p = &c;
第二种:
char* p = "abcde";
在第二种方法中,“abcde”被放在只读数据区,p存放的是第一字符‘a'的地址。
注:区别
char p[] = "abcde";
当我们想通过p来修改字符串内容的时候,程序不能正常执行。
所以一般在用第二种方法来定义字符串的时候,都会使用const关键字。
const char* p = "abcde";
为了加深印象,我们来看下面这段代码:
#include <stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdef";
const char* str1 = "abcdef";
const char* str2 = "abcdef";
if (arr1 == arr2)
{
printf("arr1 == arr2\n");
}
else
{
printf("arr1 != arr2\n");
}
if (str1 == str2)
{
printf("str1 == str2\n");
}
else
{
printf("str1 != str2\n");
}
return 0;
}
对于arr1和arr2分别在内存中创建了一个字符串,而str1和str2所指向的是同一个字符串,因为用第二种方法所创建出的字符串内容是无法修改的,所以对于指向相同内容的指针没别要才开辟一个空间来存放字符串。
二、指针数组
用来存放指针的数组。
int* arr1[10];//整型指针的数组
char* arr2[10];//字符指针的数组
char** arr3[5];//二级字符指针的数组
三、数组指针
int (*p)[10] :指向存放10整型数据数组的指针(注意与指针数组相区别)。
我们都知道数组名是首元素地址,而取地址数组名是数组的地址,具体参考我之前写的一篇文章。
(13条消息) 数组名是什么?_Patrick star`的博客-CSDN博客https://blog.csdn.net/holle_world_ldx/article/details/124522964数组指针就是用来接收数组的地址。
int arr[6] = {1,2,3,4,5,6};
int (*p)[6] = &arr;
数组指针一般用于二维数组中,二维数组中的数组名表示第一行的地址
例如:
#include <stdio.h>
void print(int (*p)[3],int x,int y)
{
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[2][3] = { {1,2,3},{2,3,4} };
print(arr,2,3);
return 0;
}
p是第一行的地址,p+1就是第二行的地址,对p进行解引用得到就是当前行的首元素地址,因此
*(p) +j可以理解为arr+j,*(*(p)+j) 等价于*(arr+j)。
为了加深理解,让我们解析下面这段代码
int arr[5]; //arr是一个整型数组,有5个元素,每个元素是int类型。
int *parr1[10];//parr1是一个数组,每个元素的类型是int*,所以parr1是指针数组。
int (*parr2)[10];//parr2与*结合,说明parr2是一个指针,该指针指向一个数组,
每个数组是10个元素,每个元素是int类型。
int (*parr3[10])[5];//parr3先与[]结合,说明parr3是一个数组,数组的元素是10个,
每个数组的类型是int(*)[5],该类型是一个指针,指向的是元素
个数为5的整数数组。
四、函数指针
首先,需要明确的一点是函数名是函数的地址。
函数指针就是指向函数的指针,定义如下:
int (*p)(int, int) = &add;
第一个int是add函数返回的类型,小括号中的两个int代表的是调用函数时需要传入的参数类型。
我们可以对p进行解引用的方式来调用函数
由于函数名本身就是地址,我们在正常调用函数的时候就是通过地址来调用的,所以也可以直接用p来调用函数
在了解了函数指针后,让我们一起来分析下面这两段代码
(* ( void(*)() ) 0)();
这段代码是将0强制类型转换,转换成void(*)()类型,也就是函数指针类型,再对该指针进行解引用,调用地址为0的这个函数。
void(*signal(int, void(*)(int)))(int);
这是一个返回类型为void(*)(int)的函数声明,signal这个函数有2个参数,一个是int类型,第二个是函数指针,该指针指向的函数参数是int,返回的类型是void。
用一个实例加深对函数指针的理解
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
void menu()
{
printf("*********************************\n");
printf("********* 1.加 ************\n");
printf("********* 2.减 ************\n");
printf("********* 3.乘 ************\n");
printf("********* 0.退出 ************\n");
printf("*********************************\n");
}
void cal(int (*p)(int,int))
{
int a = 0;
int b = 0;
printf("请输入操作数:\n");
scanf("%d %d", &a, &b);
int ret = p(a, b);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入选择:> ");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出\n");
break;
case 1:
cal(add);
break;
case 2:
cal(sub);
break;
case 3:
cal(mul);
break;
default:
printf("输入错误,请重输\n");
break;
}
} while (input);
return 0;
}
可以通过接收函数指针的方式,对相同返回类型和相同参数的函数进行同一操作。
五、函数指针数组
存放函数指针的数组,每个元素都是函数指针类型。
int (*p[5])(int,int);
我们用一个实例来加深对函数指针数组的理解
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int a,int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
void menu()
{
printf("*********************************\n");
printf("********* 1.加 ************\n");
printf("********* 2.减 ************\n");
printf("********* 3.乘 ************\n");
printf("********* 0.退出 ************\n");
printf("*********************************\n");
}
int main()
{
int input = 0;
int (*pf[4])(int, int) = { 0,add,sub,mul};
do
{
menu();
printf("请输入选择:> ");
scanf("%d", &input);
if (input>=1 && input <=3)
{
printf("\n请输入参数:");
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int ret = pf[input](a,b);
printf("ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出\n");
break;
}
else
{
printf("输入错误,请重输\n");
}
} while (input);
return 0;
}
这是一个简易的计算器模拟代码,可以发现通过函数指针数组将返回类型和参数类型相等的函数统一处理。
六、指向函数指针数组的指针
int main()
{
int (*p)(int, int) = add;
int (*parr[2])(int, int) = { 0,add };
int (*(*pparr)[2])(int, int) = &parr;
return 0;
}
七、冒泡排序
通过上面的学习,想必对指针有了一个更深刻的认识,接下来我们要实现一个泛用性更广的冒泡函数,使整个函数可以对任何类型的参数进行冒泡排序。
在这之前需要讲解一个知识点
void* :无类型指针,这个指针可以存放任意类型的地址,无法被解引用也不能对其进行加减。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct Stu
{
char name[20];
int age;
};
int cmp_Stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int cmp_char(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;
}
void swap(void* e1, void* e2,int width)
{
char* c1 = (char*)e1;
char* c2 = (char*)e2;
char temp = 0;
for (int i = 0; i < width; i++)
{
temp = *(c1 + i);
*(c1 + i) = *(c2 + i);
*(c2 + i) = temp;
}
}
void bubble_sort(void* p, int sz, int width, int (*cmp)(const void* e1,const void* e2))
{
int flag = 0;
char* pc = (char*)p;//因为无法对无类型指针进行解引用操作,所以将他强制转换成char*类型
for (int i = 0; i < sz; i++)
{
flag = 0;
for (int j = 0; j < sz - i - 1; j++)
{
if (cmp(pc+j*width,pc+ (j+1)*width) > 0)
{
swap(pc + j * width, pc + (j + 1) * width,width);
flag = 1;
}
}
if (flag == 0)//说明没有交换,无需继续排序
{
break;
}
}
}
int main()
{
int arr1[] = { 1,3,2,4,5,6 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
bubble_sort(arr1, sz1, 4,cmp_int);
for (int i = 0; i < sz1; i++)
{
printf("%d ", arr1[i]);
}
char arr2[] = { 's','a','q','w','j','k'};
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
bubble_sort(arr2, sz2, 1, cmp_char);
printf("\n");
for (int i = 0; i < sz2; i++)
{
printf("%c ", arr2[i]);
}
printf("\n");
struct Stu arr3[] = { {"zhangsan",20},{"wangwu",19},{"lisi",35} };
int sz3 = sizeof(arr3) / sizeof(arr3[0]);
bubble_sort(arr3, sz3, sizeof(arr3[0]), cmp_Stu_by_age);
for (int i = 0; i < sz3; i++)
{
printf("%s %d\n", arr3[i].name,arr3[i].age);
}
return 0;
}
当我们需要对不同类型的元组进行排序的时候,只需写一下它们的比较函数。