指针基础知识:C语言学习笔记之指针(一)-CSDN博客
目录
字符指针
代码分析
指针数组
数组指针
函数指针
代码分析(出自《C陷阱和缺陷》)
函数指针数组
指向函数指针数组的指针
回调函数
qsort()
字符指针
一般用法:
特殊用法:
上述代码的本质是把字符串 "hello bit." 的首字符的地址放到了pstr中。因为"hello bit."是一个常量字符串,是存放在代码段的数据,所以不能被修改,因此要用const 修饰来防止权限的放大。
代码分析
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
指针数组
指针数组是一种数组,数组内的每个元素的类型是指针类型。
用指针数组模拟实现一个二维数组:
#include <stdio.h>
void print(int** arr, int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", *(*(arr + i) + j)); //等价于 printf("%d ", arr[i][j]);
//printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = { 2, 3, 4, 5, 6 };
int arr3[] = { 3, 4, 5, 6, 7 };
int* arr[] = { arr1, arr2, arr3 };
print(arr, 3, 5);
return 0;
}
注:模拟的二位数组在内存中并不一定连续存放,因此它不是真的二维数组。
数组指针
我们已经熟悉:
- 整形指针: int * pint; 能够指向整形数据的指针。
- 浮点型指针: float * pf; 能够指向浮点型数据的指针。
同理,数组指针就是指向数组的指针,即存放数组地址的指针变量。
写一个打印二维数组的函数,形参是数组指针:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col) //数组指针接收
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", *(*(arr + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行(二维数组可以看成是一维数组的数组,即二维数组的每个元素是一个一维数组)
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
printf("\n");
print_arr2(arr, 3, 5);
return 0;
}
类型分析:
函数指针
函数也是有地址的,顾名思义,函数指针就是指向函数的指针,即存放函数地址的指针变量。
函数指针类型:函数返回值类型 (*指针变量名)(函数参数)。
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
根据上述代码的执行结果我们可以看出,函数名表示函数地址,&地址函数名也表示函数地址,即 函数名 == &函数名,所以 *(&函数名)== * 函数名 ,而*(&函数名)== 函数名,因此 函数名 == &函数名 == * 函数名。
首先,能存储地址,就要求pfun1或者pfun2是指针,那哪个是指针? 答案是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。pfun2先和()结合,表示pfun2是一个没有参数,返回类型是void* 的函数。
代码分析(出自《C陷阱和缺陷》)
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
利用 typedef 简化上述代码:
函数指针数组
存放函数指针的数组就叫做函数指针数组。
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表
示例(计算器):
#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;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else if (input == 0)
{
printf("程序退出\n");
break;
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
指向函数指针数组的指针
顾名思义,指针指向的是一个每个元素是函数指针的数组。
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。回调函数是一种泛型编程思维,库函数 qsort() 就运用了回调函数。
qsort()
qsort()可以给任意类型(整型,浮点型,结构体等)的数据排序,但是需要我们按要求提供一个比较函数。
用冒泡排序模拟实现一个类似 qsort() 的函数:
#include <stdio.h>
void swap(char* p1, char* p2, size_t size) //交换arr[j],arr[j+1]这两个元素
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))
{
int i = 0;
//趟数
for (i = 0; i < num - 1; i++)
{
int j = 0;
//一趟内部比较的对数
for (j = 0; j < num - 1 - i; j++)
{
//假设需要升序cmp返回>0,交换
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) //两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
{
//交换
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int cmp_int(const void* p1, const void* p2) //回调函数
{
return *(int*)p1 - *(int*)p2;
}
//测试bubble_sort 排序整型数据
void test1()
{
int arr[10] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(int), cmp_int);
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2) //回调函数
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
//测试bubble_sort 排序结构体数据(比较年龄)
void test2()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2) //回调函数
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
//测试bubble_sort 排序结构体数据(比较名字)
void test3()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sizeof(struct Stu));
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test1();
test2();
test3();
return 0;
}