指针的进阶

目录

本章重点

一 字符指针

一道笔试题

二 指针数组

三 数组指针

3.1 数组指针的定义

3.2 &数组名VS数组名

3.3 数组指针的使用

 四 数组参数 指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一维指针传参

4.4 二维指针传参

五 函数指针

六 函数指针数组

七 指向函数指针数组的指针

 八 回调函数

8.1 排序

代码1展示:(整形冒泡排序)

代码2展示:(各种类型的排序)(qsort)

用 冒泡排序 实现qsort()的功能

九 指针和数组笔试题解析

十 指针笔试


励志环节

生活的理想,是为了理想的生活。


本章重点

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;
}

笔试题(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;
}

指针进阶就到此结束了!!!

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小刘同学啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值