当前位置: 首页 > news >正文

11.【C语言学习笔记】指针(三)(回调函数、qsort排序函数、sizeof关键字和strlen函数)

目录

1. 回调函数

2. qsort函数 

2.1 使用qsort函数排序整型数据

2.2 使用qsort排序结构体数据

2.3 qsort函数的模拟实现

3. sizeof和strlen的对比

3.1 sizeof

3.2 strlen

3.3 sizeof 和 strlen的对比

4. 数组和指针笔试题解析

4.1 一维数组

4.2 字符数组

4.3 二维数组

5. 指针运算笔试题解析

5.1 题目1

5.2 题目2

5.3 题目3

5.4 题目4

5.5 题目5

5.6 题目6


1. 回调函数

        回调函数就是一个通过函数指针调用的函数。

        如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. qsort函数 

void qsort(void* base,  // 指针,指向的是待排序的数组的第一个元素size_t num,  // 是base指向的待排序数组的元素个数size_t size, // base指向的是待排序数组的元素的大小int (*compar)(const void*, const void*)// 函数指针 - 指向的是两个元素的比较函数);// qsort 函数的实现者
// qsort 函数的使用者 - 明确的知道要排序的是什么数据,这些数据应该如何比较,所以提供两个元素的比较函数
//void* 类型的指针是无具体类型的指针,这种类型的指针不能直接解引用,也不能进行+-整数的运算

2.1 使用qsort函数排序整型数据

#include <stdio.h>// 比较函数
int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}// 打印函数
void print_arr(int arr[], int sz)
{for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}
void test1()
{int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}int main()
{test1();return 0;
}

2.2 使用qsort排序结构体数据

struct Stu
{char name[20];int age;
};
// 结构体排序
// 这里的两个结构体元素怎么比较大小
// 1. 按照名字比较 - 字符串比较 - strcmp
// 2. 按照年龄比较 - 整型比较// 按照姓名比较
int cmp_stu_by_name(const void* p1,const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}// 按照年龄比较
int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}// 打印函数
void print_stu(struct Stu* ps, int sz)
{for (int i = 0; i < sz; i++,ps++){printf("%s %d\n", ps->name, ps->age);}
}void test2()
{struct Stu arr[3] = { {"zhangsan",20},{"lisi", 35},{"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu(arr, sz);
}int main()
{test2();return 0;
}

2.3 qsort函数的模拟实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 比较函数
int cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}// 打印函数
void print_arr(int arr[], int sz)
{for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}struct Stu
{char name[20];int age;
};
// 结构体排序
// 这里的两个结构体元素怎么比较大小
// 1. 按照名字比较 - 字符串比较 - strcmp
// 2. 按照年龄比较 - 整型比较// 按照姓名比较
int cmp_stu_by_name(const void* p1,const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}// 按照年龄比较
int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}// 打印函数
void print_stu(struct Stu* ps, int sz)
{for (int i = 0; i < sz; i++,ps++){printf("%s %d\n", ps->name, ps->age);}
}// 交换函数
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++;}
}
// 冒泡排序函数
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{// 趟数for (size_t i = 0; i < sz - 1; i++){// 一趟内两两比较for (size_t j = 0; j < sz-i-1; j++){if (cmp((char*)base + j * width, (char*)base + (j+1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}void test3()
{int arr[] = { 3,1,7,8,5,2,4,9,0,6 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}void test4()
{struct Stu arr[3] = { {"zhangsan",20},{"lisi", 35},{"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu(arr, sz);
}
int main()
{test3();	// 排序整型数组test4();	// 排序结构体return 0;
}

3. sizeof和strlen的对比

3.1 sizeof

        sizeof 计算变量所占内存内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。

        sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。

比如:

#include <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));return 0;
}

3.2 strlen

        strlen 是C语言库函数,功能是求字符串长度。函数原型如下:

size_t strlen(const char* str);

        统计的是从strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。strlen 函数会一直向后找 \0 字符,直到找到为止,所以可能存在越界查找。

#include <stdio.h>
int main()
{char arr1[3] = { 'a', 'b', 'c' };char arr2[] = "abc";printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));printf("%d\n", sizeof(arr1));printf("%d\n", sizeof(arr1));return 0;
}

3.3 sizeof 和 strlen的对比

sizeof关键字strlen函数

1. sizeof是操作符

1. strlen是库函数,使用需要包含头文件string.h

2. sizeof计算操作数所占内存的大小,单位是字节

2. srtlen是求字符串长度的,统计的是 \0 之前字符的隔个数

3. 不关注内存中存放什么 数据

3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

4. 数组和指针笔试题解析

4.1 一维数组

#include <stdio.h>
int main()
{int a[] = { 1,2,3,4 };printf("%d\n", sizeof(a));		// 16 -- sizeof(数组名)的场景printf("%d\n", sizeof(a + 0));	// 4/8 a是首元素的地址-类型是int*, a+0还是首元素的地址,是地址大小就是4/8printf("%d\n", sizeof(*a));		// 4   a是首元素地址,*a就是首元素printf("%d\n", sizeof(a + 1));	// 4/8 a是首元素的地址-类型是int*, a+0还是首元素的地址,是地址大小就是4/8printf("%d\n", sizeof(a[1]));	// 4   a[1]就是第二个元素,大小4个字节printf("%d\n", sizeof(&a));		// 4/8 &a是数组的地址printf("%d\n", sizeof(*&a));	// 16// 1. *& 相互抵消,sizeof(*&a) = sizeof(a)// 2. &a 是数组的地址,类型是int(*)[4],对数组指针解引用访问的是数组,计算的是数组的大小printf("%d\n", sizeof(&a + 1));	// 4/8 &a+1是跳过整个数组后的那个位置的地址printf("%d\n", sizeof(&a[0]));	//4/8 首元素的地址printf("%d\n", sizeof(&a[0] + 1));	// 4/8 第二个元素的地址return 0;
}

4.2 字符数组

代码1:sizeof        (arr[] = { 'a','b','c','d','e','f' };)

#include <stdio.h>
int main()
{char arr[] = { 'a','b','c','d','e','f' };printf("%d\n", sizeof(arr));	// 6 数组名单独放在sizeof内部,计算的是整个数组的大小printf("%d\n", sizeof(arr + 0));// 4/8 arr是数组名首元素地址,arr+0还是首元素的地址printf("%d\n", sizeof(*arr));	// 1 arr是首元素的地址,*arr就是首元素printf("%d\n", sizeof(arr[1]));	// 1 数组的第二个元素printf("%d\n", sizeof(&arr));	// 4/8 数组名取地址是整个数组的地址printf("%d\n", sizeof(&arr + 1));// 4/8 &arr+1,跳过整个数组,指向数组后面的空间printf("%d\n", sizeof(&arr[0] + 1));// 4/8 第二个元素的地址return 0;
}

代码2:strlen        (arr[] = { 'a','b','c','d','e','f' };)

#include <stdio.h>
#include <string.h>int main()
{char arr[] = { 'a','b','c','d','e','f' };printf("%d\n", strlen(arr));	// 随机值 arr是首元素的地址,数组中没有\0,就会导致越界访问,结果就是随机的printf("%d\n", strlen(arr + 0));// 随机值 arr+0是数组首元素的地址,数组中没有\0,就会越界访问,结果就是随机的printf("%d\n", strlen(*arr));	// 报错  arr是首元素的地址,*arr是首元素,就是‘a’,'a'的ascii码是97// 就相当于把97作为地址传递给了strlen,strlen得到的就是野指针,代码是有问题的printf("%d\n", strlen(arr[1]));	// 报错  arr[1] -- 'b' -- 98printf("%d\n", strlen(&arr));	// 随机值 &arr是数组的地址,起始位置是数组的第一个元素的位置xprintf("%d\n", strlen(&arr + 1));// 随机值 x-6printf("%d\n", strlen(&arr[0] + 1));// 随机值 x-1,从第二个元素开始向后统计return 0;
}

代码3:sizeof        (char arr[] = "abcdef";)

#include <stdio.h>
#include <string.h>int main()
{char arr[] = "abcdef";printf("%d\n", sizeof(arr));	// 7 arr是数组名,单独放在sizeof内部,计算数组总大小,包括'\0'printf("%d\n", sizeof(arr + 0));// 4/8 arr表示数组首元素地址,arr+0还是数组首元素地址printf("%d\n", sizeof(*arr));	// 1 arr表示数组首元素地址,*arr是数组首元素printf("%d\n", sizeof(arr[1]));	// 1 第二个数组元素printf("%d\n", sizeof(&arr));	// 4/8 &arr是数组地址printf("%d\n", sizeof(&arr + 1));// 4/8 &arr+1是跳过整个数组,还是地址printf("%d\n", sizeof(&arr[0] + 1));// 4/8 第二个元素的地址return 0;
}

代码4:strlen        (char arr[] = "abcdef";)

#include <stdio.h>
#include <string.h>int main()
{char arr[] = "abcdef";printf("%d\n", strlen(arr));	// 6 字符串个数printf("%d\n", strlen(arr + 0));// 6 arr首元素地址,arr+0还是首元素地址,向后再\0之前有6个字符printf("%d\n", strlen(*arr));	// 'a' - 97 出错printf("%d\n", strlen(arr[1])); // 'b' - 98 出错printf("%d\n", strlen(&arr));	// 6 &arr是数组的地址,也是数组第一个元素向后找printf("%d\n", strlen(&arr + 1));//随机值  printf("%d\n", strlen(&arr[0] + 1));//5,从第一个元素向后到'\0'之前有5个字符return 0;
}

代码5:sizeof       (char* p = "abcdef";) 

#include <stdio.h>
#include <string.h>int main()
{char* p = "abcdef";printf("%d\n", sizeof(p));		// 4/8 p是指针变量,计算的是指针变量的大小printf("%d\n", sizeof(p + 1));	// 4/8 p+1是b的地址,printf("%d\n", sizeof(*p));		// 1   p的类型是char*,*p就是char类型printf("%d\n", sizeof(p[0]));	// 1   1.p[0]-->*(p+0)-->*p-->'a',大小是1字节// 2.把常量字符串想象成数组,p可以理解为数组名,p[0],就是首元素printf("%d\n", sizeof(&p));		// 4/8 取出的是p的地址,地址大小就是4/8printf("%d\n", sizeof(&p + 1));	// 4/8 &p+1是跳过p指针变量后的地址printf("%d\n", sizeof(&p[0] + 1));// 4/8 &p[0]-去除字符串首字符的地址return 0;
}

代码6:strlen        (char* p = "abcdef";)

#include <stdio.h>
#include <string.h>int main()
{char* p = "abcdef";	//abcdef\0printf("%d\n", strlen(p));		// 6printf("%d\n", strlen(p + 1));	// 5//printf("%d\n", strlen(*p));	// 出错	*p就是'a'-97//printf("%d\n", strlen(p[0]));	// 出错printf("%d\n", strlen(&p));		// 随机值 &p是指针变量p的地址,和字符串“abcdef”关系不大,// 从p这个指针变量的起始位置开始数的,p变量存放的地址是什么?不知道,所以答案是随机值printf("%d\n", strlen(&p + 1));	// 随机值printf("%d\n", strlen(&p[0] + 1));// 5return 0;
}

4.3 二维数组

#include <stdio.h>
#include <string.h>int main()
{int a[3][4] = { 0 };printf("%d\n", sizeof(a));			// 48 a是数组名,单独放在sizeof内部,计算数组大小,单位字节 48=3*4*sizeof(int)printf("%d\n", sizeof(a[0][0]));	// 4  a[0][0]是第一行第一个元素,大小4个字节printf("%d\n", sizeof(a[0]));		// 16 a[0]是第一行的数组名,数组名单独放在sizeof内部,计算数组总大小16字节printf("%d\n", sizeof(a[0] + 1));	// 4/8 a[0]是第一行的数组名,但是a[0]没有单独放在sizeof内部,所以这里的数组名a[0]就是//数组首元素的地址,就是&a[0][0],+1后是a[0][1]的地址,大小是4/8字节printf("%d\n", sizeof(*(a[0] + 1)));// 4 *(a[0]+1)表示第一行的第一个元素,大小是4printf("%d\n", sizeof(a + 1));		// 4/8 a没有单独放在sizeof内部,则a表示数组首元素地址,也就是二维数组首元素的地址,// 也就是第一行的地址,即&a[0],+1就是跳过一行指向了第二行,即a+1是第二行的地址, &a[1]printf("%d\n", sizeof(*(a + 1)));	// 16 1.a+1是第二行的地址,*(a+1)就是第二行,计算第二行的大小就是16//2.*(a+1) == a[1],a[1]是第二行的数组名,sizeof(*(a+1))就相当于sizeof(a[1]),意思是把第二行数组名单独放在sizeof内部,计算第二行的大小printf("%d\n", sizeof(&a[0] + 1));	// 4/8 a[0]是第一行的数组名,&a[0]是第一行的地址,&a[0]+1就是第二行的地址printf("%d\n", sizeof(*(&a[0] + 1)));//16  对第二行的地址解引用,访问的就是第二行数组,大小是16printf("%d\n", sizeof(*a));			// 16 a作为数组名没有单独放在sizeof内部,则a为数组首元素地址,// 也就是第一行的地址,即&a[0],*a就是*&a[0]就是第一行的数组名,计算第一行数组的大小,就是16printf("%d\n", sizeof(a[3]));		// 16 a[3]无需真实存在,仅仅通过类型的推断就能算出长度// a[3]是第四行的数组名,单独放在sizeof内部,计算的是第四行的大小,16字节return 0;
}

数组名的意义:

        1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

        2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

        3. 除此之外所有的数组名都表示首元素的地址。

5. 指针运算笔试题解析

5.1 题目1

//在X86环境下
//假设结构体的大小是20个字节
//程序输出的结构是啥?
struct Test
{int Num;char* pcName;short sDate;char cha[2];short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{printf("%p\n", p + 0x1);    //加一个类型的大小20printf("%p\n", (unsigned long)p + 0x1); //整数+1printf("%p\n", (unsigned int*)p + 0x1); //加1个整型大小return 0;
}
输出:
0x00100014
0x00100001
0x00100004

5.2 题目2

#include <stdio.h>
int main()
{int a[3][2] = { (0, 1), (2, 3), (4, 5) }; // ()圆括号里是逗号表达式// 1, 3, 5// 0, 0, 0int* p;p = a[0];	// a[0]数组名,即数组首元素地址,&a[0][0]printf("%d", p[0]);return 0;
}
//输出:1

5.3 题目3

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{int a[5][5];int(*p)[4];	// p是一个数组指针,p指向是4哥整形元素p = a;		// 类型的差异,警告printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);return 0;
}

5.4 题目4

#include <stdio.h>
int main()
{int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int* ptr1 = (int*)(&aa + 1);int* ptr2 = (int*)(*(aa + 1));printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));return 0;
}
// 输出
// 10
// 5

5.5 题目5

#include <stdio.h>
int main()
{char* a[] = { "work","at","alibaba" };char** pa = a;pa++;printf("%s\n", *pa);return 0;
}

5.6 题目6

#include <stdio.h>
int main()
{char* c[] = { "ENTER","NEW","POINT","FIRST" };char** cp[] = { c + 3,c + 2,c + 1,c };char*** cpp = cp;printf("%s\n", **++cpp);printf("%s\n", *-- * ++cpp + 3);printf("%s\n", *cpp[-2] + 3);printf("%s\n", cpp[-1][-1] + 1);return 0;
}
//20240307-01:00:00讲解

http://www.lryc.cn/news/596193.html

相关文章:

  • Mixed Content错误:“mixed block“ 问题
  • 西门子 S7-1500分布式 I/O通信 :PROFINET IO 与 PROFIBUS DP核心技术详解(上)
  • 知识库搭建之Meilisearch‘s 搜索引擎-创建搜索引擎项目 测评-东方仙盟测评师
  • 【Godot4】状态栏组件StatusBar
  • python中 tqdm ,itertuples 是什么
  • RabbitMQ--批量处理
  • halcon手眼标定z方向实操矫正
  • VUE 中父级组件使用JSON.stringify 序列化子组件传递循环引用错误
  • 机器人氩弧焊保护气降成本的方法
  • Apache Ignite 的 SQL 功能和分布式查询机制
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ImageCarousel(图片轮播组件)
  • 深度学习篇---车道线循迹
  • FPGA自学——存储器模型
  • Kafka单条消息长度限制详解及Java实战指南
  • Apache Ignite 中 WHERE 子句中的子查询(Subqueries in WHERE Clause)的执行方式
  • Android 中 实现日期选择功能(DatePickerDialog/MaterialDatePicker)
  • 【无标题】buuctf-re3
  • JAVA中的IO流(四)数据流
  • 一个电脑抓包工具
  • 黄仁勋强调:首先,我是中国人
  • Python进阶第三方库之Numpy
  • 用手机当外挂-图文并茂做报告纪要
  • 云祺容灾备份系统Hadoop备份与恢复实操手册
  • 如何在 Windows 10 下部署多个 PHP 版本7.4,8.2
  • WIFI路由器长期不重启,手机连接时提示无IP分配
  • Android接入RocketMQ的文章链接
  • Spring Boot 使用Jasypt加密
  • Cy3-COOH 花菁染料Cy3-羧基
  • 《小白学习产品经理》第八章:方法论之马斯洛需求层次理论
  • 用ffmpeg 进行视频的拼接