深入理解C语言指针(下)
一、字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针 char * ,一般使用方法如下:
但是还有一种使用方式如下:
对于const char* pstr = "hello world!";我们会自然而然地觉得这行代码是把hello world!这个字符串放到了pstr这个字符指针中了,类似与数组,但是其实本质上是把字符串hello world!的首地址(即首字符h的地址)放在了pstr这个字符指针中了,记住指针就是地址,放在指变量中的一定是地址。
char* pstr = "hello world!"是常量字符串,它和数组是非常相似的,也是在一个连续空间中存放了多个字符,但是不同于数组的是,常量字符串中的内容不能修改。
二、数组指针变量
请问:数组指针变量是指针变量还是数组呢?答:数组指针变量是指针变量。
数组指针变量存放的是数组的地址,是能够指向数组的指针变量。
int ( * p )[ 10 ];
这行代码的意思是:p 先和 * 结合,说明 p 是一个指针变量,然后指针指向的是一个大小为 10 个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。
这里要注意的是:[ ]的优先级是高于 * 的,所以必须加上()来保证p先和*结合。如果不加() int * p [ 10 ];这行代码表示的就是一个指针数组,该数组的类型是int * [10],数组中存放的元素类型是int *。
如果要存放整个数组的地址,就得存放在数组指针变量中,想要获得数组的地址,就得用到&数组名:
int ( * p ) [ 10 ] = &arr;
int 说明p指向的数组的元素类型是整型;p是数组指针的变量名;*说明这是一个指针变量;[10]说明p指向的数组的元素个数是10。该数组指针变量的类型是 int ( * ) [ 10 ]。
三、二维数组传参的本质
过去当一个函数的其中一个参数是二维数组是,我们是这样写的 :
void test ( int arr[ 3 ] [ 5 ] , int r , int c );
当我们学习了指针后,我们是否能写出其他形式呢?首先让我们再次来理解一下二维数组:二维数组可以看做是每个元素是一维数组的数组。该二维数组的每个元素是一个一维数组。
所以,根据数组名是数组首元素的地址这个规则,不难得到二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是 int [5],所以第一行的地址的类型就是数组指针类型 int (*)[5]。那就意味着二维数组传参本质上是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:
void test ( int (*p) [ 5 ] , int r , int c )
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
四、函数指针变量
1、函数指针变量的创建和使用
由前面学习整型指针,数组指针铺垫,我们不难得出结论:函数指针变量应该是用来存放函数地址的,未来我们是可以通过地址来调用函数的。
函数是有地址的,函数名就是函数的地址,当然也可以通过 & 函数名 的方式获得函数的地址。如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针变量非常类似。如下:
int ( * p ) ( int x , int y );
int 说明p指向的函数的返回类型是整型;p是函数指针变量名;*说明这是一个指针变量;int x , int y 说明p指向的函数的参数类型和个数。该函数指针变量的类型是int ( * ) ( int x , int y )。
综上可知,指针可以指向任何内存中的对象。
2、typedef关键字
typedef是用来对类型重命名的,它可以将复杂的类型简单化。
(1)typedef int* ptr_t; 将int* 重命名为ptr_t;
(2)typedef int (*parr_t) [5];将数组指针类型int (*) [5] 重命名为parr_t;(新的类型名必须在*的右边)
(3)typedef void(*pf_t)(int);将函数指针类型void(*)(int)重命名为pf_t。(新的类型名必须在*的右边)
五、函数指针数组
把多个函数的地址存到一个数组中,这个数组就叫函数指针数组。
函数指针数组的定义方法:int ( * p [ 3 ] ) ( ) ; p先和[]结合,说明p是数组,该数组内的元素的类型是int ( * ) ( );该函数指针数组的类型是int ( * [ 3 ] ) ( )。
函数指针数组的用途是转移表。
转移表的实例:通过函数指针数组实现简易计算器:
六、回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
七、qsort使用举例及模拟实现
qsort(quick sort)是一个库函数,它是用来对数据进行排序的。不管是什么类型的数据,它都能对其进行排序。qsort函数的使用需要包含头文件<stdlib.h>。
void qsort ( void * base , size_t num , size_t size , int(* cmp)(const void*,const void*) );
base指向了待排序数组中的第一个元素的地址;(使用void*是因为待排序的数据的类型是不确定的)num是待排序的数据的元素个数;size是待排序数组中一个元素的大小,单位是字节。int(* cmp)(const void*,const void*)这是一个函数指针变量,指针指向的函数可以用来比较base指向的数组中的任意两个元素的大小。
1、使用qsort函数排序整型数据
#include <stdio.h>
#include<stdlib.h>
int cmp(const void* x, const void* y);
int main()
{
int arr[10] = { 1,3,4,5,8,9,2,10,7,6 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
int cmp(const void* x, const void* y)
{
return (*(int *)x) - (*(int *)y);
}
2、使用qsort排序结构数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_by_age(const void* x, const void* y); //按照年龄排序
int cmp_by_name(const void* x, const void* y); //按照名字排序
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s[3] = { {"zhangsan",18},{"wangwu",35},{"lisi",15} };
size_t sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_by_age);
for (int i = 0; i < 3; i++)
{
printf("%s %d \n", s[i].name,s[i].age);
}
qsort(s, sz, sizeof(s[0]), cmp_by_name);
for (int i = 0; i < 3; i++)
{
printf("%s %d \n", s[i].name, s[i].age);
}
return 0;
}
int cmp_by_age(const void* x, const void* y)
{
return ((struct stu*)x)->age - ((struct stu*)y)->age;
}
int cmp_by_name(const void* x, const void* y)
{
return strcmp(((struct stu*)x)->name, ((struct stu*)y)->name);
}
3、qsort函数的模拟实现
使用回调函数,模拟实现qsort(采用冒泡的方式)。
#include<stdio.h>
void bubble(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*));
int cmp(const void* x, const void* y);
void swap(void* p1, void* p2, size_t size);
int main()
{
int arr[] = { 1,3,4,6,8,2,7,10,9,5 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(arr[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
void bubble(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))
{
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int cmp(const void* x, const void* y)
{
return *(int*)x - *(int*)y;
}
void swap(void* p1, void* p2, size_t size)
{
for (int i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
八、 sizeof 和 strlen 的对比
sizeof | strlen |
1、sizeof是操作符; 2、sizeof计算操作数所占内存的大小,单位是字节; 3、不关注内存中存放什么数据。 | 1、strlen是库函数,使用需要包含头文件string.h; 2、strlen是求字符串长度的,统计的是\0之前字符的个数; 3、关注内存中是否有\0,如果没有\0,就会持续往后找,可能会越界 |
原文地址:https://blog.csdn.net/2401_86861045/article/details/143880404
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!