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讲解