资讯专栏INFORMATION COLUMN

C语言进阶:指针进阶续

ingood / 1353人阅读

摘要:故使用无具体类型,又称通用类型,即可以接收任意类型的指针,但是无法进行指针运算解引用,整数等。求指针所占字节而不是解引用访问权限大小。数组就是整个数组的大小,数组元素则是数组元素的大小,指针大小都为。

指针进阶续

续前文《C语言进阶:指针进阶》

回调函数

回调函数定义

回调函数:通过函数指针调用的函数,或者说使用函数指针调用函数这样的机制被称为回调函数。回调函数不由实现方直接调用,而是作为特殊条件下的响应。

概念无关紧要,理解并熟练运用这种方法才更为重要。

快速排序 qsort

qsort函数逻辑
void qsort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2));

qsort无返回值,有四个参数。分别为base:起始地址,num:元素个数,width:元素大小以及compare:比较函数。可与冒泡排序作对比。

//冒泡排序void Bubble_sort(int arr[], int sz) {	for (int i = 0; i < sz - 1; i++) { 		for (int j = 0; j < sz - 1 - i; j++) { 			//比较函数			if (arr[j] > arr[j + 1]) {				int tmp = arr[j];				arr[j] = arr[j + 1];				arr[j + 1] = tmp;			}        }    }}

与冒泡排序作对比发现,冒泡排序仅需起始地址和元素个数即可,暗含了其他信息。由于过度具体化,冒泡排序只能排序整型数组,且比较函数过于简单无需多带带列出。

因为qsort排序可适用于多种类型如浮点型,字符型,自定义类型的数据,故无法规定具体类型,所以需要多个参数去描述元素的基本信息。

qsort之所以能够适应多种数据,是因为参数void* base再搭配上numwidth就描述出任意一种类型。

为什么将参数base的类型定义为void*呢?如下述代码所示。

char* p1 = &a;//从int*到char*类型不兼容char* p2 = &f;//从float*到char*类型不兼容void* p1 = &a;void* p2 = &f;

确定类型的地址之间直接赋值会提示类型不兼容,强制转化也可能会导致精度丢失。

故使用无(具体)类型void*,又称通用类型,即可以接收任意类型的指针,但是无法进行指针运算(解引用, ± ± ±整数等)。

p1++;   *p1;   p1 - p2;   p1 > p2;//表达式必须是指向完整对象类型的指针
  1. base:用于存入数据的起始地址。类型定义为void*,可接受任意类型的指针。

  2. num:待排序的元素个数。

  3. width:元素宽度,所占字节大小。

明确了排序的起始位置,元素个数和元素大小,貌似已经够了。但是并无法排序所有类型,因此必须自定义一个抽象的比较函数指定元素的比较方式。

  1. cmp:比较函数,用于指定元素的比较方式。

    • elem1小于elem2,返回值小于0
    • elem1大于elem2,返回值大于0
    • elem1等于elem2,返回值为0
  2. elem1elem2:进行比较的两个元素的地址作参数。

qsort可以说是一个半库函数半自定义函数。自定义在于其函数最后一个参数为比较函数,该函数内部实现自由,但返回值必须按照规定返回相应的数值。

小结

需要qsort函数排序各种类型的数据,

  • base起始地址不可为固定的指针类型,只能用void*
  • 既然是通用类型还要明确比较元素的个数和大小。
  • 最后,排序最核心的比较大小,为适应不同的类型元素必须自定义专门的比较函数。
qsort实现冒泡排序
//比较函数:整型#include int int_cmp(const void* e1, const void* e2) {	return *(int*)e1 - *(int*)e2;}int main() {	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };	int sz = sizeof(arr) / sizeof(arr[0]);	qsort(arr, sz, sizeof(arr[0]), int_cmp);	return 0;}

比较函数int_com不需要传参,作为回调函数由qsort直接调用。比较函数的传参过程由qsort内部实现。

qsort实现结构体排序
#include struct stu {	char* name;	short age;	float score;};//按照成绩排序int score_cmp(const void* e1, const void* e2) {	//1.升序    return ((struct stu*)e1)->score - ((struct stu*)e2)->score;	//2.降序    return ((struct stu*)e2)->score - ((struct stu*)e1)->score;}//按照名字排序int name_cmp(const void* e1,const void* e2) {	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);}int main() {	struct stu s[3] = {         { "张三", 22, 99.5f },{ "李四", 21, 66.4f },{ "王五", 18, 80.1f }     };	int sz = sizeof(s) / sizeof(s[0]);	//1.    qsort(s, sz, sizeof(s[0]), name_cmp);	//2.    qsort(s, sz, sizeof(s[0]), score_cmp);	return 0;}

由此可得,提取出一个比较函数,具体交换的方式由qsort内部实现。

模拟实现qsort

qsort的函数逻辑,实现冒泡排序。

//打印函数void print_arr(int arr[],int sz) {	for (int i = 0; i < sz; i++) {		printf("%d ", arr[i]);	}}//交换函数void Swap(char* buf1, char* buf2, size_t width) {	for (size_t i = 0; i < width; i++) {//宽度次		char tmp = *buf1;		*buf1 = *buf2;		*buf2 = tmp;		buf1++;		buf2++;	}}//比较函数int cmp(const void* e1, const void* e2) {	return *(int*)e1 - *(int*)e2;}//排序函数void my_bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2)) {	for (size_t i = 0; i < num - 1; i++) {		for (size_t j = 0; j < num - 1 - i; j++) {			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) {//以字节为单位				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);			}		}	}}int main() {	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };	int sz = sizeof(arr) / sizeof(arr[0]);	my_bubble_sort(arr, sz, sizeof(arr[0]), cmp);	print_arr(arr, sz);	return 0;}

地址统一强转为char*,以最小字节单位一个字节进行比较和交换,使代码更具有普适性。

如果需要排序结构体则只需要在前文代码中主函数里替换my_qsort且把比较函数替换Name_cmp即可。

//1.my_qsort(s, sz, sizeof(s[0]), name_cmp);//2.my_qsort(s, sz, sizeof(s[0]), score_cmp);

 

指针和数组笔试题解析

数组辨析题

注意点。数组名代表整个数组:

  1. sizeof(数组名)
  2. &数组名

除此以外,数组名都是代表首元素地址。

一维数组
int a[] = { 1,2,3,4 };printf("%d/n", sizeof(a));//16printf("%d/n", sizeof(a + 0));//4/8printf("%d/n", sizeof(*a));//4printf("%d/n", sizeof(a + 1));//4/8printf("%d/n", sizeof(a[1]));//4printf("%d/n", sizeof(&a));//4/8printf("%d/n", sizeof(*&a));//16printf("%d/n", sizeof(&a + 1));//4/8printf("%d/n", sizeof(&a[0]));//4/8printf("%d/n", sizeof(&a[0] + 1));//4/8
  1. 只有数组名多带带放在sizeof内部才是整个数组。

    a+0放在sizeof内部表示首元素地址+0。

  2. 只要是地址,不管是什么类型的地址大小都是4/8

    基本类型指针,数组指针,函数指针大小都是4/8个字节,故sizeof(&a)=sizeof(int(*)[4])=4sizeof()求指针所占字节而不是解引用访问权限大小。

  3. *&在一起会抵消。

    sizeof(*&a),&a为整个数组的地址类型int(*)[4],解引用后int[4]大小为16。

字符数组
char arr[] = { "a","b","c","d","e","f" };printf("%d/n", sizeof(arr));//6printf("%d/n", sizeof(arr + 0));//4/8printf("%d/n", sizeof(*arr));//1printf("%d/n", sizeof(arr[1]));//1printf("%d/n", sizeof(&arr));//4/8printf("%d/n", sizeof(&arr + 1));//4/8printf("%d/n", sizeof(&arr[0] + 1));//4/8printf("%d/n", strlen(arr));//随机值xprintf("%d/n", strlen(arr + 0));//随机值xprintf("%d/n", strlen(*arr));//报错printf("%d/n", strlen(arr[1]));//报错printf("%d/n", strlen(&arr));//随机值xprintf("%d/n", strlen(&arr + 1));//随机值x-6printf("%d/n", strlen(&arr[0] + 1));//随机值x-1
  1. sizeof(*arr)*arr对首元素地址解引用,计算首元素所占空间大小。

    strlen(*arr)*arr依然是首元素,strlen把a也就是97当成地址,访问到非法内存所以报错。

2.strlen(&arr)虽然是整个数组的地址,但依然是从首元素开始的,所以strlen依然从第一个元素开始找。

strlen(&arr+1),先计算&arr+1然后再传参过去,也就是跳过了整个数组去找。

sizeofstrlen的区别

  • sizeof — 操作符 — 以字节为单位,求变量或类型所创建变量的所占空间的大小

sizoef不是函数,计算类型是必须带上类型说明符()sizoef内容不参与运算,在编译期间便转化完成。

  • strlen — 库函数 — 求字符串长度即字符个数,遇/0停止。

库函数,计算字符串长度没有遇到/0就会一直持续下去。返回类型size_t,参数char* str ,接收的内容都会认为是char*类型的地址。

一个求变量所占空间,一个求字符串大小,二者本身是没有关系的,但总有人把二者绑在一起“混淆视听”。

字符串数组

首先明确二者的区别:

//1.字符初始化数组char arr[] = { "a","b","c","d","e","f" };//[a] [b] [c] [d] [e] [f]//2.字符串初始化数组char arr[] = "abcdef";//[a] [b] [c] [d] [e] [f] [/0]

字符初始化数组,存了什么元素数组里就是什么元素。而字符串初始化数组,除了字符串中可见的字符外,还有字符串末尾隐含的/0/0存在于字符串的末尾,是自带的,虽不算字符串内容,但是字符串中的字符。

char arr[] = "abcdef";printf("%d/n", sizeof(arr));//7printf("%d/n", sizeof(arr + 0));//4/8printf("%d/n", sizeof(*arr));//1printf("%d/n", sizeof(arr[1]));//1printf("%d/n", sizeof(&arr));//4/8printf("%d/n", sizeof(&arr + 1));//4/8printf("%d/n", sizeof(&arr[0] + 1));//4/8printf("%d/n", strlen(arr));//6printf("%d/n", strlen(arr + 0));//6printf("%d/n", strlen(*arr));//报错printf("%d/n", strlen(arr[1]));//报错printf("%d/n", strlen(&arr));//6printf("%d/n", strlen(&arr + 1));//随机值printf("%d/n", strlen(&arr[0] + 1));//5
  1. sizeof计算变量的长度,变量可以是数组,数组元素以及指针。数组就是整个数组的大小,数组元素则是数组元素的大小,指针大小都为4/8。
  2. strlen把传过来的参数都当作地址,是地址就从该地址处向后遍历找/0,不是地址当作地址非法访问就报错。
常量字符串
char* p = "abcdef";

"abcdef"是常量字符串,用一个字符指针p指向该字符串,实质是p存入了首字符a的地址。由于字符串在内存中连续存放,依此特性便可以遍历访问整个字符串。

char* p = "abcdef";
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119679.html

相关文章

  • C语言进阶指针进阶

    摘要:本章节在此基础上,对语言阶段指针进行更深层次的研究。数组指针的类型由数组类型决定,先找出数组的类型去掉名就是类型。相当于数组指针所指向数组的数组名。数组指针指向整个数组,将其看作二维数组并解引用得到一行的首元素,从而遍历访问。 ...

    浠ラ箍 评论0 收藏0
  • 玩转指针,手撕c语言——(指针进阶

    摘要:函数的返回值为指针就按照字面意思,指针函数的定义顾名思义,指针函数即返回指针的函数。 目录 前言指针与函数函数的返回值为指针作为函数参数的指针指针函数可以改变变量...

    genedna 评论0 收藏0
  • C语言进阶:动态内存管理

    摘要:释放不完全导致内存泄漏。既然把柔性数组放在动态内存管理一章,可见二者有必然的联系。包含柔性数组的结构用进行动态内存分配,且分配的内存应大于结构大小,以满足柔性数组的预期。使用含柔性数组的结构体,需配合以等动态内存分配函数。 ...

    shinezejian 评论0 收藏0
  • 三文读透指针语法【中篇】@指针进阶---函数指针+函数指针数组+指向函数指针数组的指针

    摘要:三文读透指针上篇本文将继续介绍有关函数指针的相关内容。在大型工程里,函数指针应用还是挺普遍的。首先看阅读下面两段有趣的代码出自语言陷阱与缺陷看看他们是什么意思代码代码函数指针数组函数指针数组,即存放函数指针的数组。 ...

    blastz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<