目录
励志环节
生活的理想,是为了理想的生活。
本章重点
1. 字符指针
2. 数组指针
3. 指针数组
4. 数组传参和指针传参
5. 函数指针
6. 函数指针数组
7. 指向函数指针数组的指针
8. 回调函数
9. 指针和数组面试题的解析
在C语言初阶中,对指针友友们应该有了一定的了解:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。(内存会被划分为以字节为单位的空间,每个空号间都有一个编号(地址、指针),地址需要 空间被存起来,这个空间就是指针变量)
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。(硬件 CPU 要么支持32位的虚拟空间地址,要么支持64位的虚拟空间地址,32位的虚拟空间地址提供32bit位的地址,64位的虚拟空间地址提供64bit的地址)(我们平常打印的地址是虚拟地址,CPU产生虚拟地址,通过虚拟地址找到物理地址,进而找到内容)
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。
接下来让我们再进一步的了解指针!!!
一 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* 。
#include <stdio.h>
int main()
{
char ch = 'w';
char* p = &ch;
char* pa = "abcdefg";//"abcdefg"储存在内存的只读存储区,只能读 该字符串有一个起始地址,也就是a的地址,pa存的就是a的地址
*pa = 'w';//这种情况是会报错的,a不能改变为w。
//所以我们一般写成const char* pa = "abcdefg"
return 0;
}
上图中两个圈圈是不同的,"abcdefg"储存在内存的只读存储区,只能读 该字符串有一个起始地址,也就是a的地址,pa存的就是a的地址
注意:所以我们一般写成const char* pa = "abcdefg"
一道笔试题
代码展示:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
arr1和arr2代表内存中两个不同的空间,所以数组首元素地址是不相等的,而'hello world'是一个常量表达式,arr3和arr4里面存放的地址是同一个,都是h的地址,所以是一样的。
代码展示:
#include <stdio.h>
int main()
{
int a, b;//a int类型 b int类型
int* pa, pb;//pa int*类型 pb int类型
int* pa, *pb;//pa pb都是int*类型,可以共用一个int,但是不能共用一个*
typedef int* pint;//将int*重新起了个名字,叫pine; typedef将一个自己命名的类型用已经有的类型来代替
pint pa, pb;//在这里,pa,pb都是int*类型
return 0;
}
typedef将一个自己命名的类型来代替已经有的类型
二 指针数组
在C语言初阶专栏里的指针中,友友们对指针数组肯定有了一定的了解,指针数组是一个存放指针的数组。
#include <stdio.h>
int main()
{
int arr1[3];//整形的数组 存放整形的数组 int int int
int* arr2[3];//整形指针的数组 存放整形指针的数组 int* int* int*
char* arr2[3]; //一级字符指针的数组 存放字符指针的数组 char* char* char*
char** arr3[3];//二级字符指针的数组 存放二级字符指针的数组 char** char** char**
return 0;
}
打印字符串,有首元素地址,就可以打印
代码展示:
#include <stdio.h>
int main()
{
char* arr[] = { "abcd", "efgh", "igkl" };//arr[],分别存放a,e,i的地址,也分别是字符串首元素地址
int sz = sizeof(arr) / sizeof(arr[0]);//计算有多少个char*
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", arr[i]);
}
return 0;
}
浅说一下数组,arr[5],arr是首元素的地址,首元素地址后面跟一个下标,就可以打印数组的每一个元素
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* arr[] = { arr1, arr2, arr3 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);//arr[i] 是数组首元素地址//*((*(arr+i))+j)
}
printf("\n");
}
return 0;
}
三 数组指针
3.1 数组指针的定义
数组指针落脚点在最后两个字,所以数组指针就是一个指针。
字符指针,指向字符的指针;
整形指针,指向整形的指针;
数组指针,指向数组的指针。
#include <stdio.h>
int main()
{
int a = 3;
int* p = &a;
int* arr[10];//指针数组 arr和[10]结合,arr是一个数组
int* arr[10];//指针数组 arr和[10]结合
int(*arr)[10];//数组指针 ,arr和*结合,arr是一个指针,数组指针,int类型 数组指针,指向数组的指针
return 0;
}
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
3.2 &数组名VS数组名
int arr[10];
arr 和 &arr 分别是啥?我们知道arr是数组名,数组名表示数组首元素的地址。那&arr数组名到底是啥?
代码1展示:
#include <stdio.h>
int main()
{
int arr[10];
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
代码2展示:
#include <stdio.h>
int main()
{
int arr[10];
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", arr + 1);
printf("%p\n", &arr + 1);
return 0;
}
比较代码1和代码2:根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)arr是数组首元素(第一个元素)地址,如果存放在一个指针变量里,就是int*类型 arr+1 是第二个元素的地址 ,如果存放在一个指针变量里,就是整形指针类型
本例中 &arr 的类型是: int(*)[10] , 是一种数组指针类型(int a;去掉变量就是类型)数组的指针存放起来,就是放在数组指针里。
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
(*p)说明是一个指针,(*p)[10]说明是一个数组,指向的数组里的元素是什么类型,int 类型, 数组的类型是int [10]
int* a;*说明a是一个指针。
3.3 数组指针的使用
数组指针指向的是数组,那数组指针中存放的应该是数组的地址
#include <stdio.h>
int main()
{
int arr[5];
int(*p)[5] = &arr;p的类型是int(*)[5]int* paa[3];//指针数组,也是一个数组,数组里面的元素是int*类型
int* (*pp)[3] = &paa;//数组指针,pp的类型是int*(*)[3]
return 0;
}
指针数组的用途:
打印数组元素地址
代码1展示:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
打印结果:1 2 3 4 5 6 7 8 9 10
代码2展示:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*p) + i));p是&arr,*p是arr。
}
return 0;
}
打印结果:1 2 3 4 5 6 7 8 9 10
代码2这种方法不建议,数组指针我们一般用在二维数组中。
打印二维数组:
代码1展示:
#include <stdio.h>
void print(int arr[3][5], int a, int b)
{
int i = 0;
int j = 0;
for (i = 0; i < a; i++)
{
for (j = 0; j < b; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
print(arr, 3, 5);
return 0;
}
打印结果:
代码2展示:
二维数组在传参的时候,数组名也代表首元素地址,这个首元素指的是 第一行 ;首元素地址指的是第一行的地址
#include <stdio.h>
void print(int(*p)[5], int a, int b)//p+1就是第二行的地址,p+2就是第三行的地址
{
int i = 0;
int j = 0;
for (i = 0; i < a; i++)
{
for (j = 0; j < b; j++)
{
printf("%d ", *(*(p + i) + j));// *(p+i)拿到的是第i行的地址,相当于第i行的数组名,也就是第i行的首元素地址,也是第一个元素的地址
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
print(arr, 3, 5);
return 0;
}
上图中的二维数组的首元素地址是 第一行的地址,传到函数里是int(*p)[5],
重点理解:
上述代码,p代表的是&第一行整个数组,p+i代表的是&第i行整个数组,*(p+i)代表的是第i行的数组名。数组首元素地址,数组首元素地址+i,指的是第i个元素。
#include <stdio.h>
int main()
{
int arr[5];//arr是一个整形数组,有5个元素,每一个元素都是int类型
int* parr1[10];//parr1是一个数组,数组有10个元素,每一个元素是int*类型
int(*parr2)[10];//parr2和*结合,说明parr2是一个指针,该指针指向一个数组,所以是一个数组指针
int(*parr3[10])[5];//把arr3[10],看做一个整体,指向数组为[5]的int的指针,这个指针是[10].
//parr3和[]结合。说明parr3是一个数组,数组是10个元素,数组每一个元素的类型是int(*)[5],该类型的指针指向的数组有5个int类型的元素。 数组的类型是int*[5],也就是这个指针是数组,一共10个元素,每一个元素(指针)指向的是int[5]
return 0;
}
理解: parr3的类型是,int(*)[5],可以看做 int(*)[5] parr3,此时就是一个数组指针,但是上图是一个数组,那么就是10个数组指针放在了一起,是parr3[10]。
四 数组参数 指针参数
4.1 一维数组传参
数组传参的时候,形参写成数组形式是可以的,例如:arr[10],,,,传参后写成int arr[] 是可以的,int arr[10],也是可以的,下标写错也是可以的。实际上,我们传递的是数组名,数组名也就是首元素地址 用指针来接收的话,也是可以的,要注意类型。
代码展示:
#include <stdio.h>
void test(int arr[])//ok? ok
{}
void test(int arr[10])//ok? ok
{}
void test(int* arr)//ok? ok
{}
void test2(int* arr[20])//ok? ok
{}
void test2(int** arr)//ok? ok
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
4.2 二维数组传参
void test(int arr[3][5])//ok? ok
{}
void test(int arr[][])//ok? not 行可以省略,列不可以省略
{}
void test(int arr[][5])//ok? ok 指向一行5个,类型为int的指针
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。//二维数组传参,传的是数组首元素地址,指的是二维数组第一行的地址,而不是第一行第一个元素的地址
void test(int* arr)//ok? not
{}
void test(int* arr[5])//ok? not 这是一个数组,不是一个指针
{}
void test(int(*arr)[5])//ok? ok 是一个指向 int [5]类型的指针
{}
void test(int** arr)//ok? not
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
4.3 一维指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
这种方法,不常见,最好别用。
一级指针重要的内容是,当函数的参数是一级指针的时候,可以接收什么样的参数。
代码展示:
#include <stdio.h>
void test(int* p)
{
}
int main()
{
int a = 10;
int* ptr = &a;
int arr[10] = { 0 };
test(&a);
test(ptr);
test(arr);
return 0;
}
4.4 二维指针传参
二级指针重要的内容是,当函数的参数是二级指针的时候,可以接收什么样的参数。
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
int* arr[10];
test(arr);
return 0;
}
五 函数指针
函数指针,指向函数的指针。
函数是有地址的,地址是 函数名或者&函数名,解引用时,pf和*pf都可以。
代码展示:
#include <stdio.h>
void Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);//这两行和数组指针不能类比,函数没有首元素地址。
int (*pf)(int, int) = &Add;//这一行可以和数组指针类比一下。pf是一个函数指针 &Add写成Add也是可以的,因为&Add和Add一样。
int sum = (*pf)(2, 3);//用指针调用这个函数
int sum2 = (pf)(3, 5);//所以*可要可不要
int (*pf1)(int, int) = Add;//用Add的时候 &Add和Add是一样的
int sum1 = (pf1)(3, 4);
printf("%d\n", sum);
return 0;
}
&函数名和函数名 得到的都是函数的地址。
无返回类型用void (*pf)(int, int)
&Add相当于Add,所以,*可要可不要。
(*(void (*)())0)();
//void(*)()无参数的函数指针,()0 把0强制类型转换,*{()0} 解引用这个函数,(*)(),调用这个函数
//0本来是int类型,然后强制转换成函数指针,然后此时,0就是一个函数的地址,式子就可以看成(*0)()解引用一个函数地址并且调用。
//就是一次函数调用
上个代码是函数调用
void (*signal(int, void(*)(int)))(int);
//signal是一个函数,signal后面括号里面不是参数,说明是一个函数声明,
//signal(int, void(*)(int),仅仅看这个,是一个缺少函数返回类型的函数声明,那么剩下的就是函数声明的返回类型 void(*)(int),
typedef void (* pfun_t)(int);
pfun_t signal(int, pfun_t);//简化
上个代码是一个函数声明
六 函数指针数组
函数指针数组,存放函数指针的数组,每一个元素都是函数指针类型。
#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 (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;//四个函数指针
int (*arr[4])(int, int) = { Add, Sub, Mul, Div};//数组;*,函数指针,(int, int)函数指针数组 arr[4] 数组,数组里面的每一个元素是 函数指针类型
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", arr[i](4, 2));
}
return 0;
}
应用:计算机
#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;
}
void menu()
{
printf("******1.Add 2.Sub*******\n");
printf("******3.Mul 4.Div*******\n");
printf("******0.exit *******\n");
printf("**********************8****\n");
}
int main()
{
int input = 0;
int a = 0;
int b = 0;
int (*arr[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("输入两个操作数:->");
scanf("%d %d", &a, &b);
printf("%d\n", arr[input](a, b));
}
else if (input == 0)
{
printf("退出程序");
}
else
{
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
七 指向函数指针数组的指针
指向函数指针数组的指针,是一个指针,指针指向存放函数指针的数组。
类型是:int (*(*)[5])(int, 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 (*pa)(int, int) = Add;函数指针
int (*arr[4])(int, int) = { Add };函数指针数组,存放函数指针的数组
int (*(*arr)[4])(int, int) = &arr;//指向函数指针数组的指针
return 0;
}
八 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
代码展示:
#include <stdio.h>
void menu()
{
printf("******1.Add 2.Sub*******\n");
printf("******3.Mul 4.Div*******\n");
printf("******0.exit *******\n");
printf("**********************8****\n");
}
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;
}
void calc(int (*pf)(int, int))
{
int a = 0;
int b = 0;
printf("请输入两个数字");
scanf("%d %d", &a, &b);
int ret = pf(a, b);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:->");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算机\n");
break;
default:
printf("输入错误\n");
break;
}
} while (input);
return 0;
}
8.1 排序
每一轮确定一个数字的位置,10个数字需要九轮才能排序完成。(冒泡排序,相邻两个两个的进行比较并交换位置)(在C语言初阶 数组知识点 里详细写了关于冒泡排序的知识点,友友们感兴趣的话可以看一下)
代码1展示:(整形冒泡排序)
#include <stdio.h>
void print(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void buttle_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)//确定打印趟数
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)//确定每一趟需要比较的次数,每一趟会把最大的元素放在最后面
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 1, 3, 6, 2, 0, 9, 4, 8, 5, 7 };
int sz = sizeof(arr) / sizeof(arr[0]);
buttle_sort(arr, sz);
print(arr, sz);
return 0;
}
qsort是一个库函数,快速排序的方法来实现的, 头文件是<stdlib.h>
qsort库函数,void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );传入的参数,一个是指针,一个整形,一个整形一个函数指针,base 数组首元素(就是数组名),num数组里有多少个元素,width每个元素的大小(单位是字节),compare比较两个指针指向的元素,小于 输出小于0的元素,等与 输出0,大于 输出大于0的元素 排序任意类型
无具体类型的指针,void*作用是 :void*的指针变量可以存放任意类型的指针,void*的指针不能直接进行解引用操作(进行解引用要进行强制转换)*(*int)pa';void*也不能进行+-整数;
代码2展示:(各种类型的排序)(qsort)
排序的内容可以是整形数组、浮点型数组、字符数组,结构体数组
把代码1用qsort表示:
使用qsort 函数,需要写一个compare函数(注意,返回类型是数字)
#include <stdio.h>
#include <stdlib.h>
void print(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//比较e1和e2指向的元素
int com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 1, 3, 6, 2, 0, 9, 4, 8, 5, 7 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), com_int);
print(arr, sz);
return 0;
}
比较结构体 score
#include <stdio.h>
#include <stdlib.h>
struct stu
{
char name[10];
int age;
float score;
};
int cmp_stu_by_score(const void* e1, const void* e2)
{
if (((struct stu*)e1)->score > ((struct stu*)e2)->score)//浮点数不能减,因为返回的是int
{
return 1;
}
else if (((struct stu*)e1)->score < ((struct stu*)e2)->score)
{
return -1;
}
else
{
return 0;
}
}
void print_stu(struct stu arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
}
printf("\n");
}
int main()
{
struct stu arr[] = { {"zhangsan", 20, 87.5f},{"lisi", 22, 99.0f},{"wangwu", 10, 68.5f} };//按照成绩来排序
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_score);
print_stu(arr, sz);
return 0;
}
比较结构体 age
#include <stdio.h>
#include <stdlib.h>
struct stu
{
char name[10];
int age;
float score;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void print_stu(struct stu arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
}
printf("\n");
}
int main()
{
struct stu arr[] = { {"zhangsan", 20, 87.5f},{"lisi", 22, 99.0f},{"wangwu", 10, 68.5f} };//按照成绩来排序
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
print_stu(arr, sz);
return 0;
}
比较结构体 name
#include <stdio.h>
#include <stdlib.h>
struct stu
{
char name[10];
int age;
float score;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void print_stu(struct stu arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
}
printf("\n");
}
int main()
{
struct stu arr[] = { {"zhangsan", 20, 87.5f},{"lisi", 22, 99.0f},{"wangwu", 10, 68.5f} };//按照成绩来排序
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_score);
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
print_stu(arr, sz);
return 0;
}
strcmp(s1, s2),比较字符串的大小,两个字符串从左向右逐个字符相比(按ASCII的值大小相比),直到某一个字符不相等或者其中一个字符比较完毕才停止比较,字符的比较为ASCII码的比较(若字符串1大于字符串2,返回结果大于0,若字符串1小于字符串2,返回结果小于0,若字符串1等于字符串2,返回结果等于0.)
strcmp的头文件是<string.h>
用 冒泡排序 实现qsort()的功能
#include <stdio.h>
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz; i++)
{
for (j = 0; j < sz - 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 com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 1, 3, 6, 2, 0, 9, 4, 8, 5, 7 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), com_int);
print(arr, sz);
return 0;
}
排序的作者:不知道排序的类型
排序的使用者:知道待排序的类型,以及每种类型的排序方法。
九 指针和数组笔试题解析
(1)代码1展示
#include <stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16(整个数组的大小) 数组名a单独放在sizeof内部,计算的是整个数组的大小 4*4=16
printf("%d\n", sizeof(a + 0));//4(首元素地址大小) 数组名没有单独放在sizeof内部,也没有&,所以这里的数组名是指的是数组首元素地址
// a+0 还是数组首元素地址,地址的大小4/8 在VS2019的环境内,是4
printf("%d\n", sizeof(*a));//4(首元素的大小) a表示首元素地址,*a 对首元素地址进行解引用,就是首元素。首元素的大小是int类型是4个字节
printf("%d\n", sizeof(a + 1));//4(第二个元素地址的大小) a表示首元素地址,a+1表示第二个元素的地址,4/8 在VS2019是4
printf("%d\n", sizeof(a[1]));//4(第二个元素的大小) 表示数组的第二个元素2,int类型 4
printf("%d\n", sizeof(&a));//4(整个数组的地址)a表示整个数组的大小,&a 整个数组的地址的大小 4/8 在VS2019是4
printf("%d\n", sizeof(*&a));//16(整个数组的大小) & 和 *相互抵消,相当于sizeof(a),所以是16;
printf("%d\n", sizeof(&a + 1));//4(地址的大小) &的优先级大于+的优先级 &a(数组的地址) 跨过一个数组的地址,也是地址 4/8 在VS2019是4
printf("%d\n", sizeof(&a[0]));//4 1这个元素的地址 4/8 在VS2019是4
printf("%d\n", sizeof(&a[0] + 1));//4 2 这个元素的地址 4/8 在VS2019是4
return 0;
}
(1)数组名是数组首元素地址,但是有两个例外:(1)sizeof(数组名),这里的数组名表示整个数组的大小,sizeof(数组名),计算的是整个数组的大小(单位是字节)(2)&数组名,这里的数组名,也表示整个数组,取出的是整个数组。 除了上面两种特殊情况外,剩下的所有数组名都是数组首元素地址。
(2)代码2展示
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6(整个数组的大小) 数组名arr单独放在sizeof内部,计算的是整个数组的大小 1*6=6
printf("%d\n", sizeof(arr + 0));//4(首元素地址大小) 数组名没有单独放在sizeof内部,也没有&,所以这里的数组名是指的是数组首元素地址
// arr+0 还是数组首元素地址,地址的大小4/8 在VS2019的环境内,是4
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8 在VS2019是4
printf("%d\n", sizeof(&arr + 1));//4/8 在VS2019是4 跳过整个数组的地址
printf("%d\n", sizeof(&arr[0] + 1));//4/8 在VS2019是4 跳过一个元素的地址 第二个元素的地址
//这个数组有6个元素是毫无疑问的,这个数组的后面和前面放的是什么,我们是不知道的,strlen关注的是\0,有\0才停止。
printf("%d\n", strlen(arr));// arr没有& 没有sizeof内部 所以arr是首元素地址,但是数组中没有\0,计算的时候不知道什么时候停止,所以就是随机值
printf("%d\n", strlen(arr + 0));// arr首元素地址,arr+0还是首元素地址, 所以就是随机值
printf("%d\n", strlen(*arr));//err, strlen需要的是一个地址,从这个地址开始向后找字符,直到\0,统计字符个数
//但是*arr是数组首元素,也就是'a',这时传给strlen就是'a'的ASCII值97,strlen函数会把97作为起始地址(违规地址),统计字符串,会形成内存访问冲突
printf("%d\n", strlen(arr[1]));// err 和上一个一样 内存访问冲突
printf("%d\n", strlen(&arr));// 随机值 整个数组的地址,arr代表整个数组,地址的类型是 char(*)[6],不是char*,所以会出现错误,虽然地址指针类型是不同的,
//但是 传参过去,按照strlen的类型开始运行,会出现随机值
printf("%d\n", strlen(&arr + 1));// 跳过整个数组的地址 随机值
printf("%d\n", strlen(&arr[0] + 1));// 第二个元素的地址
return 0;
}
strlen 求字符串的长度 关注的就是‘\0’前有多少个字符
sizeof()只关注占用内存空间大小,单位是字节
sizeof 不关注类型
sizeof 是操作符
strlen(const char*)只关注\0的位置,计算的是\0之前出现了多少个字符,
strlen 针对字符串
strlen 是库函数
(3)代码3展示
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//arr代表整个数组 sizeof 计算的是整个数组的大小 ,还有一个\0 7
printf("%d\n", sizeof(arr + 0));//arr首元素地址 4/8 4
printf("%d\n", sizeof(*arr));// arr首元素地址 1
printf("%d\n", sizeof(arr[1]));//首个元素 1
printf("%d\n", sizeof(&arr));// 整个数组的地址 4/8 4
printf("%d\n", sizeof(&arr + 1));// 跨过一个数组的地址 4/8 4
printf("%d\n", sizeof(&arr[0] + 1));// 跨过一个元素的地址 4/8 4
printf("%d\n", strlen(arr));//arr首元素地址, \0之前的元素 6
printf("%d\n", strlen(arr + 0));//6
//printf("%d\n", strlen(*arr));//err
//printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//整个数组的地址 地址参数不同 6
printf("%d\n", strlen(&arr + 1));// 跨过整个数组的地址 地址参数不同 随机值
printf("%d\n", strlen(&arr[0] + 1));//跨过一个元素的地址 5
return 0;
}
(4)代码4展示
#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcdef";//字符指针,存放'a'的地址
printf("%d\n", sizeof(p));//p是一个指针,sizeof计算的是指针变量的大小 4/8
printf("%d\n", sizeof(p + 1));// b的地址 4/8
printf("%d\n", sizeof(*p));// 字符'a' 1
printf("%d\n", sizeof(p[0]));//p[0]等价于*(p+0) 字符'a' 1
printf("%d\n", sizeof(&p));//二级地址 4/8
printf("%d\n", sizeof(&p + 1));// 二级地址 4/8 指向的是char*的地址,+1跨过一个字符指针(所有元素的地址)(四个字节)(也就是&p这个地址再加上四个字节),就是现在的地址
//例如 数组 &arr + 1,(arr里有3个元素(int)12个字节),那么&arr+1 就是&arr这个地址,再加上12个字节,就是现在的地址
printf("%d\n", sizeof(&p[0] + 1));//字符'b'的地址 4/8
printf("%d\n", strlen(p));// 6
printf("%d\n", strlen(p + 1));// 5
//printf("%d\n", strlen(*p));// err
//printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p));// 随机值
printf("%d\n", strlen(&p + 1));// 随机值
printf("%d\n", strlen(&p[0] + 1));//5
return 0;
}
sizeof 地址 4/8
strlen 地址 找 \0
(5)代码5展示
#include <stdio.h>
int main()
{
int a[3][4] = { 0 };//三行四列
printf("%d\n", sizeof(a));// 48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));//a[0]表示第一行数组名,a[0]作为数组名,存放在sizeof里,16
printf("%d\n", sizeof(a[0] + 1));//a[0] 没有取地址,没有放在sizeof内部,表示第一行首元素地址, 表示第二个元素的地址,4/8
printf("%d\n", sizeof(*(a[0] + 1)));//4
printf("%d\n", sizeof(a + 1));//a表示数组首元素地址(也就是第一行的地址),第二行的地址 4/8
printf("%d\n", sizeof(*(a + 1)));// 第二行地址是 &第二行数组名 解引用 是第二行数组名 16
printf("%d\n", sizeof(&a[0] + 1));// a[0]表示第一行的数组名,&第一行的数组名,就是第一行的地址, 表示第二行的地址 4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//第二行的数组名 16
printf("%d\n", sizeof(*a));// 第一行的数组名 16
printf("%d\n", sizeof(a[3]));//感觉是越界了,但是a[3],就不会去访问,就不会去计算,仅仅看类型 第四行的数组名,16
return 0;
}
二维数组的数组名,代表二维数组的首元素地址,就是第一行的地址,就是&第一行数组名。
a[i] 第i行数组名
十 指针笔试题
笔试题(1)
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
2 5
笔试题(2)
#include <stdio.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;//p是结构体指针,这个结构体有20个字节 4+4+2+1*2+2*4=20
int main()
{
p = (struct test*)0x100000;
printf("%p\n", p + 0x1);//指针+1
printf("%p\n", (unsigned long)p + 0x1);//整数+1
printf("%p\n", (unsigned int*)p + 0x1);//指针+1
printf("%x\n", (unsigned long)p + 0x1);
printf("%x\n", (unsigned int*)p + 0x1);
return 0;
}//考点 +1 ,加到哪里
指针+1,跨过多少个字节,取决于指针的类型。
%p 以地址的形式打印,不省略
%x 打印16进制,省略0;
笔试题(3)
#include <stdio.h>
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
4 2000000
大小端
笔试题(4)
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };//逗号表达式 结果为 1 2 3
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
1
笔试题(5)
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];//p是一个数组指针,指向的是4个元素的数组
p = a;//p和a地址一样
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);p[4][2]等价于*(*(p+4)+2)
//指针p的类型是int(*)[4] p+1,指针+1,跨过指针指向的类型,p+4跨过16*4个字节,解引用后,指针的类型是int类型,+2,跨过4*2个字节。 p[4][2]是这个二维数组的第18个元素 a[4][2]是这个二维数组的第22个元素,指针相减,得到的是中间元素的个数,4,因为是低地址减去高地址,所以是-4,%p打印的是地址(也就是补码)所以是ff ff ff fc
return 0;
}
ff ff ff fc (-4的16进制)(补码) -4
%p打印的是内存中的补码
两个地址相减,得到的是中间元素的个数,
笔试题(6)
#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
笔试题(7)
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };//存放字符指针的数组
char** pa = a;//a是数组首元素地址 ,首元素地址里面存放的是w的地址,
pa++;//跳过一个字符指针类型的地址,就是数组第二个元素的地址
printf("%s\n", *pa);//得到a的地址,打印字符串
return 0;
}
at
笔试题(8)
#include <stdio.h>
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };//c存放的是这四个字符串首元素地址
char** cp[] = { c + 3,c + 2,c + 1,c };//存放的是数组c的地址,四个字符存放位置的地址(倒过来)
char*** cpp = cp;//数组cp的首元素地址
printf("%s\n", **++cpp);//前置++,先++,后使用, cpp+1得到的是cp第二个元素的地址,*得到的是
//cp第二个元素,第二个元素又是c第三个元素的地址,*得到的是c的第三个元素,%s打印,得到POINT
printf("%s\n", *-- * ++cpp + 3);//在第一个printf的时候,cpp发生了变化,++的优先级大于+的优先级,所以cpp+1得到的是cp的第三个元素的地址,
//*的优先级大于+的优先级,解引用后得到cp的第三个元素,是c第二个元素的地址,--的优先级大于+的优先级,--第一个元素的地址,解引用,第一个元素,是字符指针,+3,因为是字符指针,指向的是字符,所以跨越3个字符的地址,来到了E的地址,打印结果为 ER
printf("%s\n", *cpp[-2] + 3);//因为上面两个printf,cpp现在指的是cp的第三个元素的地址,cpp[-2] 等价于 *(cpp-2),得到的是cp的第一个元素,就是c的的第四个元素的地址,*后,得到字符指针F的地址,+3,就是S的地址,打印结果为ST
printf("%s\n", cpp[-1][-1] + 1);//cpp还是cp第三个元素的地址,cpp[-1][-1] 等价于
// *(*(cpp-1) - 1),打印结果为 EW(NEW)
return 0;
}
指针进阶就到此结束了!!!