C进阶⚡- 02指针进阶

我知道的只是  “  肉随便加  ”  和  “ 要加多少加多少   ” 这些词。    ———— 路飞

阶段2目标:

此阶段开始大量刷题,多多参加编程类竞赛,在实战中锻炼编程思维和本领,并且要在不断复习夯实初阶的基础上,刻意地进行编程思维的训练。学无止境!为了精进编程,可以去学习一切为他服务的课程!

目录

1.本章重点

2.字符指针

 3.指针数组

4.数组指针

4.1数组指针的定义

4.2&数组名VS数组名

 4.3数组指针的使用

 5.数组参数,指针参数

5.1一维数组传参

5.2二维数组传参

 5.3一级指针传参

 5.5二级指针传参

6.函数指针

7.函数指针数组

8.指向函数指针数组的指针

9.回调函数

9.1回调函数典型举例

9.2通用的冒泡排序

10.指针和数组笔试题讲解


1.本章重点

1. 字符指针
2. 数组指针
3. 指针数组
4. 数组传参和指针传参
5. 函数指针
6. 函数指针数组
7. 指向函数指针数组的指针
8. 回调函数
9. 指针和数组面试题的解析
 

指针的主题,我们在C语言初阶阶段的《指针》章节已经接触过了,我们知道了指针的概念:

  • 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  • 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  • 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  • 指针的运算。

C语言进阶阶段,我们继续更加深入地讨论指针的高级主题。

2.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

一般使用:

#include<stdio.h>
int main()
{
	char c = 'w';
	char* pc = &c;//pc指向一个字符变量

	char* p = "hello bit";//这里是把一个字符串放到pstr指针变量里了吗?

	printf("%c\n", *pc);
	printf("%s\n", p);
    //上面表达式地作用是:把常量字符串“hello bit”的第一个字符h的地址赋值给p

	return 0;
}

解释:(    注意!!    )

char* pc = &c;   pc是指向一个变量地址的指针变量,解引用以后指向的是变量c的内容,printf("%c\n", *pc);即可。

char* p = "hello bit";//这里不是是把一个字符串放到p指针变量里,而是指针变量p指向字符串中的第一个字符h的地址,把常量字符串“hello bit”的第一个字符h的地址赋值给pprintf("%s\n", p);即可,不需要解引用

(     代码 char* p = "hello bit"; 特别容易让同学以为是把字符串 hello bit 放在了字符指针 p 里了,但本质是把字符串 hello bit. 首字符的地址放到了p中    )

但是我们不提倡这样写常量字符串赋值给指针,比如,我们想修改*p = 'Q';没有编译错误,但是运行起来会报错,显示常量字符串不能被修改。

所以我们一般在用指针接收常量字符串的时候往往会加个const 来修饰,让它不能被修改!!

const char* p = "hello bit";

面试题:选自《剑指offer》(       笔刷!! 

//下面程序输出什么结果?
#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	char* str3 = "hello bit.";
	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;
}

答案

str1 and str2 are not same

str3 and str4 are same

解释

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

 3.指针数组

在C语言初阶《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。这里我们再复习一下,下面指针数组是什么意思?

	char arr1[5];//字符数组 - 存放字符的数组
	int arr2[5]; //整型数组 - 存放整型的数组

	//指针数组 - 存放指针的数组
	int*
	char*
	short*

举个例子:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;

	int* arr[4] = { &a,&b,&c,&d };//arr是整型指针数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(arr[i]));
	}

	return 0;
}

很简单,不再赘述。

再举个例子,细品~~

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//p[i] == *(p+i)
			//parr[i][j] == *(parr[i]+j)
		}
		printf("\n");
	}
	return 0;
}

解释

指针数组parr存放的是arr1[ ],arr2[ ],arr3[ ]数组的首地址(  即:数组名是首元素的地址  ),我们想通过指针数组parr来访问每个数组arr1[ ],arr2[ ],arr3[ ]中的元素,该怎么做呢?

牢记:指针数组本质上也是数组 ,是数组就能通过下标的形式访问数组中的元素 ,只不过访问到的和普通数组访问到的不太一样,指针数组访问到的是地址 。

首先:先获取指针数组parr中存放的的每一个数组的首地址(parr[ i ]

其次:通过parr[ i ]访问到的数组地址,来遍历访问数组中的每个元素*( parr[ i ] + j ),也可以写成parr[ i ][ j ]。(  仔细体会~ )

 栗子3:

int main()
{
	const char* arr[5] = { "abcdef","bcdefg","hhhh","KKKK","OOPOO" };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

4.数组指针

4.1数组指针的定义

	int* p;//整型指针 - 指向整型的指针
	char* p;//字符指针 - 指向字符的指针
	//数组指针 - 指向数组的指针

说明:

	int a = 10;
	int* pa = &a;//整型的地址存放在整型指针中

	char ch = 'Q';
	char* pc = &ch;//字符的地址存放在字符指针中

	int arr[10] = { 0 };
	int* p = arr;//数组首元素的地址

	int* parr[10];//这样写是指针数组
	&arr;//取出的是数组的地址,应该存放在数组指针中
	//数组指针怎么写??


	int(*parr)[10] = &arr;//取出的是数组的地址,应该存放在数组指针中

思考对比:下面哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

解释

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

总结

整型指针:int* pa = &a;      类型是  int*

字符指针:char* pc = &ch;     类型是  char*

那么数组指针:int (*p)[10];    类型是   int  (* )[10]

4.2&数组名VS数组名

arr &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?我们来通过代码对比一下:

    int arr[10] = { 0 };
	arr;//数组名是首元素的地址
	&arr[0];//首元素的地址
	&arr;//数组的地址

	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);

	printf("---------------------\n");

	//地址+1跨越的长度取决于地址的变量类型
	printf("%p\n", arr + 1);//int*
	printf("%p\n", &arr[0] + 1);//int*
	printf("%p\n", &arr + 1);//int (*)[10]

解释

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。

 数组名就是首元素的地址的两个例外

  1. sizeof(数组名)  这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
  2. &数组名,这里的数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址
int main()
{
	int arr[10] = { 0 };

	//数组名是首元素的地址
	//但是有2个例外
	//1.sizeof(数组名)  这里的数组名表示的是整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
	//2.&数组名,这里的数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址

	printf("%d\n", sizeof(arr));//40
	return 0;
}

 4.3数组指针的使用

那数组指针是怎么使用的呢?既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址,那么先看如下代码,再用数组指针进行编写:

我们想要访问数组中的每一个元素,可以用整型指针来访问,如代码1:(  不再赘述   )

void Print1(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print1(arr, sz);

	return 0;
}

代码2:(数组指针的错误示范

//数组指针
void Print2(int(*parr)[10], int sz)//错误示范
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", parr[i]);//parr[i] == *(parr+i)
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print2(&arr, sz);//数组的地址

	return 0;
}

我们想通过数组指针直接来访问到每一个元素,而我们存储的是数组的地址,所以parr+i中,比如parr+1会直接跳过整个数组,移动到数组末尾。很明显这样的数组指针用法是错误的。

 代码3:(       多想想     )【    难点!!  

//数组指针
void Print3(int(*parr)[10], int sz)
{
	//*(parr+0) == parr[0]
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", parr[0][i]);
		//printf("%d ", (*(parr + 0))[i]);
		printf("%d ", (*parr)[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print3(&arr, sz);//数组的地址

	return 0;
}

我们也可以利用这个思想,来对数组指针应用:

数组指针parr是指里面存储的是数组的地址,对parr解引用,就是parr的“1行”,即:*parr,也可以写成*(parr+0),即:parr[0]

然后,我们通过这“1行”,来找到该行中的元素,那么parr[0][i]。也等价于:(*(parr + 0))[i],即:(*parr)[i]。

其实我们发现

不提倡代码3,显得有点复杂,其实一维数组用数组指针多多少少显得有点麻烦。主要是对二维数组应用比较方便:

void Print1(int a[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{ 
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Print1(arr, 3, 5);
	return 0;
}

改写成数组指针:

代码

void Print2(int(*p)[3], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{ 
		int j = 0;
		for (j = 0; j < c; j++)
		{
			//printf("%d ", *(*(p + i) + j));
			//printf("%d ", *(p[i] + j));
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };

	Print2(arr, 3, 5);

	return 0;
}

练习:

对比以下区别:

int arr[5];//整型数组

int* parr1[10];//parr1是一个数组,有10个元素,每个元素是int*类型

int(*parr2)[10];//parr2是一个数组指针,该指针指向的数组有10个元素,每个元素是int型

int(*parr3[10])[5];
//parr3是一个数组,该数组含有10个元素
//每个元素是一个数组指针,该指针指向的数组有5个元素
//每个元素都是int型

 5.数组参数,指针参数

在写代码的时候难免要把【数组】或者【指针】作为实参传给函数,那函数的形参该如何设计呢?

5.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int arr[100])//ok?
{}
void test(int arr[1])//ok?
{}
void test(int* p)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

注释讲解

#include <stdio.h>
//数组传参,用数组接收
void test(int arr[])//OK ——> 数组传参,用数组接收,当然是对的;数组大小可以省略,因为形参是不会真实创建数组的,所以数组大小没有用
{}
void test(int arr[10])//OK ——> 当然,加了数组大小也同样没问题
{}
void test(int arr[100])//OK ——> 当然,加了数组大小100也行,反正形参不会真实创建数组,数组大小是多少没关系
{}
void test(int arr[1])//OK  ——>  道理同上,但是以上两种写法虽然语法上可以,但是这样写毫无意义,数组大小随便起的毫无意义之处
{}
void test(int* p)//OK ——>  传过来的是数组名字,即:首元素地址,所以也可以写成void test(int* arr)
{}

void test2(int* arr[20])//OK ——>  和传过来的形式一样,肯定是可以的
{}
void test2(int* arr[])//OK ——>  形参是不会真实创建数组的,所以数组大小没有用
{}
void test2(int** arr)//OK ——> arr2是数组名,也是首元素的地址,而arr2是int* 类型的,所以需要二级指针int**来接收
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

5.2二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* p)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);

	return 0;
}

注释讲解

void test(int arr[3][5])//OK ——> 和传过来的二维数组形式一样,这是肯定没问题的
{}
void test(int arr[][])//err! ——> 二维数组,数组行,列都不写,那么无法确定这是几行几列的,这种写法是错误的
{}
void test(int arr[][5])//OK ——> 二维数组,确定列即可,行可以省略
{}
//总结:
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。

void test(int* p)//err ——> arr是二维数组,首元素表示的是第一行,所以用一个整型的指针变量来接收一行是不行的
{}
void test(int* arr[5])//err ——> 整型指针数组来接收?   完全不搭边,大错特错
{}
void test(int(*arr)[5])//OK ——> 看图解
{}
void test(int** arr)//err! ——> 二维数组,不是二级指针!
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);

	return 0;
}

 5.3一级指针传参

一级指针传参,参数写成一级指针当然没有问题。

void Print(int* p, int sz)//一级指针传参,参数写成一级指针当然没有问题
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);

	Print(p, sz);
	return 0;
}

一级指针传参,参数写成一维数组当然也没有问题,本质上还是指针。

void Print(int p[], int sz)//一级指针传参,参数写成一维数组当然也没有问题,本质上还是指针。
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);

	Print(p, sz);
	return 0;
}

倒着试想一下,我们能把什么作为实参传过去用一级指针接收呢?

void test(int* p)
{}
int main()
{
	int a = 10;
	int arr[10];
	int* p = arr;

	test(&a);
	test(arr);
	test(p);

	return 0;
}

 5.5二级指针传参

二级指针传参,参数写成二级指针当然没有问题。

void test(int** ppa)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int** ppa = &p;

	test(ppa);

	return 0;
}

倒着试想一下,我们能把什么作为实参传过去用二级指针接收呢?

void test(int** ppa)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int** ppa = &p;
	int* arr[5];//指针数组

	test(ppa);
	test(&p);
	test(arr);

	return 0;
}

6.函数指针

整型指针

字符指针

数组指针

函数指针? ——  存放函数的地址

//对比

//数组
//数组名  ——  数组首元素的地址
//&数组名 ——  数组的地址 

//函数
//函数名
//&函数名

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);//&函数名   
	printf("%p\n", Add);//函数名

	//结果都是005E13B6

	return 0;
}

总结

想拿到函数的地址,可以有两种方式

  1. 函数名
  2. &函数名

那么可以求得函数的地址了,那如何将函数地址存进一个变量里呢??

	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

解释

首先,存函数的地址,需要一个指针变量,我们姑且叫做函数指针变量pf,所以pf得是指针类型(*pf);之后在(*pf)后写出接收的参数的数据类型(int ,int ),

然后,函数的返回类型也是int,所以就有int (*pf)(int, int) = &Add;

类比记忆一下数组指针变量的写法

	int arr[10] = { 0 };
	int(*parr)[10] = &arr;//parr是数组指针变量

解释

首先,存数组的地址,需要一个指针变量,我们姑且就叫数组的指针变量parr,所以parr得失指针类型(*parr);之后再(*parr)后写出数组的元素个数[10],然后,这10个元素都是int类型,所以就有int(*parr)[10] = &arr;

那么既然是变量,就会有类型,那么函数指针的类型是什么?

    //类型:去掉名字剩下的就是类型
	
	//int型
	int a = 10;
	int arr[10] = { 0 };

	//int(*)[10]型
	int(*parr)[10] = &arr;//parr是数组指针变量

	//int (*)(int, int)型
	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

那么会有同学说了:>老师老师,你看你看,int a = 10;类型是int型,那是不是就意味着我都可以用类型,后面跟上变量名嘞?比如这个样子:

int(*)[10] parr = &arr;

int (*)(int, int)  pf= &Add;

这是肯定不行的!!我们不能想当然的自以为是这样子

因为

每一个类型对应的名字写在哪,都有自己的规则的。不能一概而论。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;//pf是用来存放函数地址的,pf就是函数指针变量

	int ret1 = Add(2, 3);
	printf("%d\n", ret1);

	//用函数指针变量

	int ret2 = (*pf)(2, 3);
	printf("%d\n", ret2);
	
	//结果都是5
	return 0;
}

🧨🧨:我们这的    int ret2 = (*pf)(2, 3);也可以写成 int ret2 = pf(2, 3);

原因:

我们的int (*pf)(int, int) = &Add;是等价于int (*pf)(int, int) = Add;,也就是说,Add这个函数的名字,和变量名pf一样,也就是Add(2,3)也就等于pf(2,3)。

⚡来看几个有意思的代码吧:    ——————   出自C语言陷阱和缺陷

//代码1
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

代码1解释

(  *     (    void (*)()   ) 0  )();堆叠在一起不好看出猫腻,所以分开一些距离。

总体来说是一次函数调用

首先,在最内层void (*)()是一个参数是空,无返回值的函数指针类型;然后,用函数指针类型强制转换数字0,即:将0强制转换成类型为void (*)() 的一个函数的地址;最后,再解引用0的地址,完成函数调用,即:去调用0地址处的这个函数(  被调用的函数是无参数,返回值类型是void  )

代码2解释

void ( *   signal(   int , void(*)(int)   )  )(int);

总体来说这是一次函数定声明         (      函数总共有三种功能:函数声明函数定义函数调用    )

声明的函数名是signal,signal函数有2个参数,第一个参数是int类型,第二个参数是 void(*)(int) 的函数指针类型

signal函数的返回值依然是: void(*)(int)的函数指针类型

是不是觉得有点  “变态”

其实有一种帮我们快速理清思路的方法:     ——————     简化代码

//例如:
//typedef int int32;      意思是把int这个类型重新命名成int32类型,  同理:
//typedef void(*)(int) $$$;   想要把void(*)(int)  重新命名成$$$类型,但是这样写并不正确,此void(*)(int)不能直接在后面跟变量,而是:
typedef void(* pfun_t)(int);

void (* signal(int, void(*)(int)))(int);
//等价于
pfun_t signal(int, pfun_t);

7.函数指针数组

就像整型指针数组,里面存放的是整型类型的数据的指针(即:地址);同样的,函数指针数组,也是有存在必要的,可以将相同数据类型的函数地址,放在数组中。

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
int main()
{
	//整型指针数组 - 存放整型指针的数组
	int* arr[10];

	//函数指针数组 - 存放函数指针的数组
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;

	return 0;
}

等价于:

	//pfArr就是一个函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };

pfArr 先和 [ ] 结合,说明pfArr是数组,数组的内容是什么呢? 是 int (* )( ) 类型的函数指针。

应用:计算器

不应用函数指针的代码:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("****  1:add    2:sub ****\n");
	printf("****  3:mul    4:div ****\n");
	printf("****      0:exit     ****\n");
	printf("*************************\n");
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
        menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

我们发现,每一次其实都是在进行重复的工作,我们的确可以再将他们封装成一个函数,来每次调用,可是这样的话函数里仍然需要每一出来一个新功能再一个一个添加,也是很麻烦,(见下面的代码1)而且,每次再去调用函数本身也是一个重复性的工作。那么我们可以将他们用函数指针来解决:

代码1:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("****  1:add    2:sub ****\n");
	printf("****  3:mul    4:div ****\n");
	printf("****      0:exit     ****\n");
	printf("*************************\n");
}
void Calc(int (*pf)(int, int))
{
	int x, y;
	int ret = 0;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	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;
}

应用函数指针的代码:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("****  1:add    2:sub ****\n");
	printf("****  3:mul    4:div ****\n");
	printf("****      0:exit     ****\n");
	printf("*************************\n");
}
int main()
{
	int input = 1;
	do
	{
		int x, y;
		int ret = 0;
		int(*pfArr[5])(int x, int y) = { 0, Add, Sub, Mul, Div }; //转移表 —— 《C和指针》
		                               //0   1    2    3    4
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input <= 4 && input >= 1)
		{
			printf("输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("输入有误\n");
		}
			
	}while (input);

	return 0;
}

8.指向函数指针数组的指针

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
//指向函数指针数组的指针
 

int arr[10];
int (*p)[10] = &arr;
//p是一个指向整型数组的指针 


int* arr[10];//整型指针的数组
int* (*p)[10] = &arr;//整型指针数组的地址
//p是一个指向整型指针数组的指针


int (*pf)(int, int) = Add;//pf是一个函数指针
int (*pfArr[5])(int, int);//pfArr是一个函数指针的数组
int (*(*ppfArr)[5])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针

9.回调函数

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

如上述代码1就是回调函数:

int Add(int x, int y)//int (*)(int , int)类型
{
	return x + y;
}
int Sub(int x, int y)//int (*)(int , int)类型
{
	return x - y;
}
int Mul(int x, int y)//int (*)(int , int)类型
{
	return x * y;
}
int Div(int x, int y)//int (*)(int , int)类型
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("****  1:add    2:sub ****\n");
	printf("****  3:mul    4:div ****\n");
	printf("****      0:exit     ****\n");
	printf("*************************\n");
}
void Calc(int (*pf)(int, int))
{
	int x, y;
	int ret = 0;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	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;
}

9.1回调函数典型举例

冒泡排序

#include<stdio.h>
void bubble(int a[], int len)
{
	int i, j;
	for (i = 0; i < len - 1; i++)
	{
		for (j = 0; j < len - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
}
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len ; i++)
	{
		printf("%d ", a[i]);
	}
}
int main()
{
	int a[] = { 3,2,5,1,7,8,4,0,9,6 };
	int len = sizeof(a) / sizeof(a[0]);

	bubble(a, len);

	print(a, len);

	return 0;
}

但是我们发现。冒泡排序用void bubble(int a[], int len)把接受的数组类型“写死了”,这个是整型排序,那我想对浮点型排序呢?怎么调用这个bubble函数?

所以,我们需要一个接受任何类型的函数来实现排序  ——   qsort()函数。

先说用法:qsort()函数形式:

void qsort( void *base, size_t num, size_t width, int *  compare )(const void *elem1, const void *elem2 ) );

void qsort(void* base, 
	       size_t num,     //待排序的元素个数
	       size_t width,   //一个元素的大小(单位是字节)
	       int (*cmp)(const void* elem1, const void* elem2));   //cmp指向的是:排序时,用来比较2个元素的函数

解释

  1. 因为我们不知道传进来的是什么类型,所以用“万能型”类型void来接收,即:void *base看代码1举例
  2. 传进来的数组,我们需要知道要排序的长度,所以 size_t num既然不知道传进来的是什么类型,那么我们需要对这个传进来的数组,说明一个元素所占大小(字节),即: size_t width这样我们就相当于知道了数据类型。
  3. 需要指定要排序的是什么,比如:一个结构体我们如何排序?——>  需要指定结构体中的哪一个来进行排序,所以写了函数指针int *  compare )(const void *elem1, const void *elem2 ) )

代码1

    int a = 110;
 	float b = 0.7f;

	//错误示范
	char* p1 = &a;  //会报错,应该是int*  
	char* p2 = &b;  //会报错,应该是float* 

	//正确示范
	void* p3 = &a;
	void* p4 = &b;

	//对比

	//void*无具体类型的指针
	//能够接收任意类型的地址
	//缺点:不能加减运算,不能解引用(因为不知道所占字节)

	//加减运算正确示范
	int* p5 = &a;
	p5++;
	p5 + 1;

	//缺点
	void* p6 = &a;
	p6++;//报错
	p6 + 1;//报错
	*p6;//报错

用qsort()函数来改写排序

#include<stdlib.h>
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", a[i]);
	}
}

//比较函数
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

int main()
{
	int a[] = { 1,4,2,3,6,8,0,9,5,7 };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp);

	print(a, len);
	return 0;
}

解释

  1. qsort(a, len, sizeof(a[0]), cmp);前面三个参数按要求写即可,最后一个是比较函数,用来指定比较的对象,需要自己来编写这个函数。
  2. int cmp(const void* e1, const void* e2)来定义返回值类型int(  稍会说明为什么是int类型 ),还有参数const void* e1, const void* e2。
  3. return *(int*)e1 - *(int*)e2;显得较为复杂,其实e1,e2指的是地址,我们想要通过解引用的方式来获取该地址的数据,但是void*类型的缺点是不能解引用,所以我们强制转换成int*类型再去解引用。为什么是减号➖呢??见下表(    ps.参考MSDN关于函数qsort的解释  )

解释解释的解释

(1)返回值是int类型是因为我们返回值对应的是和整数0比较大小

(2) 参考MSDN关于函数qsort的解释

 参考MSDN关于函数qsort的解释

 当然,想要实现降序排列即转换cmp返回值:

//比较函数
int cmp(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

可见,在函数qsort()函数中,使用了比较函数cmp,非常非常典型的回调函数函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数

那么如何用qsort函数来实现结构体的排序呢?(就不打印了哈,知道如何用即可

#include<stdlib.h>
#include<string.h>

struct Stu
{
	char name[20];
	int age;
};

//比较名字
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//字符串比较大小,即:判断字符串的长短,用strcmp函数,详情见strcmp函数应用
}

//比较年龄
int cmp_by_age(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void test1()
{
	struct Stu a[3] = { {"路飞",3},{"索隆",6},{"山治",8} };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp_by_name);

}

void test2()
{
	struct Stu a[3] = { {"路飞",3},{"索隆",6},{"山治",8} };
	int len = sizeof(a) / sizeof(a[0]);

	qsort(a, len, sizeof(a[0]), cmp_by_age);

}
int main()
{
	//按名字排序
	test1();

	//按年龄排序
	test2();

	return 0;

}

strcmp函数用法:

那么,我们可以   使用回调函数,实现一个

9.2通用的冒泡排序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//打印
void print(int a[], int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", a[i]);
	}
}

//比较函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

//交换
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 BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	int i, j;
	for (i = 0; i < num - 1; i++)
	{
		for (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 a[] = { 3,2,5,1,7,8,4,0,9,6 };
	int len = sizeof(a) / sizeof(a[0]);

	BubbleSort(a, len, sizeof(a[0]), cmp_int);

	print(a, len);

	return 0;
}

解释

  1. void BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))作为通用的冒泡排序
  2. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)之中,用回调函数cmp来通过其返回值判断前后数据大小(稍会要在上面写下cmp函数),那我们需要把什么参数传到cmp函数来判断前后数据大小呢?——>  肯定是前面一个数据的内容,和后面一个数据的内容比较,用因为是void*类型,不能解引用所以应该用强制类型转换,那么用什么类型转换??这是个通用的冒泡排序,是不可以随便用一个数据类型强转的呀!——>  用char*类型比较合适,因为char*只一次占一个字节大小(更细),所以(char*)base,通过和数据类型的所占字节长度width观察得到下一位数据,所以写出上述表达式。
  3. void Swap(char* buf1, char* buf2, int width)交换函数,(  假设我们想要冒泡完成升序。。如果前一位大于后一位,则调用Swap函数实现前后交换  ),当然需要传入前后两个数据的地址,当然,还必须得有数据类型所占字节数(  方便移动到下一个数据的位置上去  ),具体见下图实现过程:

10.指针和数组笔试题讲解

一维数组

	//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));

讲解

	//一维数组
	int a[] = { 1,2,3,4 };            
	printf("%d\n", sizeof(a));        //数组名a单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小 - 16
	printf("%d\n", sizeof(a + 0));    //a表示首元素的地址,a+0还是首元素的地址,是地址,大小就是 4/8
	printf("%d\n", sizeof(*a));       //a表示首元素的地址,*a就是首元素  ==>  a[0],大小就是 4
	//*a <==> (*a+0) <==> a[0]
	printf("%d\n", sizeof(a + 1));    //a表示首元素的地址,a+1就是第二个元素的地址,大小就是 4/8
	printf("%d\n", sizeof(a[1]));     //a[1]就是第二个元素  - 4
	printf("%d\n", sizeof(&a));       //&a  是整个数组的地址   是地址就是  4/8
	printf("%d\n", sizeof(*&a));      //*&a -  &a是数组的地址,对数组的地址解引用,得到的是数组,大小是 16
	//printf("%d\n", sizeof(*&a));  <==>  printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(&a + 1));   //&a是数组的地址,&a+1是跳过整个数组 之后的地址,但本质上还是地址,所以大小是 4/8
	printf("%d\n", sizeof(&a[0]));    //4/8
	printf("%d\n", sizeof(&a[0] + 1));//4/8

字符数组

题1

	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

讲解

	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));         //6
	printf("%d\n", sizeof(arr + 0));     //4/8
	printf("%d\n", sizeof(*arr));        //1
	printf("%d\n", sizeof(arr[1]));      //1
	printf("%d\n", sizeof(&arr));        //4/8
	printf("%d\n", sizeof(&arr + 1));    //4/8
	printf("%d\n", sizeof(&arr[0] + 1)); //4/8

顺便一提arr与&arr在sizeof()中的区别:

题2

	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

讲解

先来说说strlen和sizeof的区别:

strlen函数,直至遇见\0才会停止

	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));          //因为数组中并没有\0,所以是 - 随机值
	printf("%d\n", strlen(arr + 0));      //等价于第一个,   -   随机值
	printf("%d\n", strlen(*arr));         //*arr - arr[0] - 'a' - 97 - err
	//strlen就以为传进来的是'a'的ASCII码值97,而strlen函数中只能放地址,所以会进行错误的强制类型转换成地址,构成野指针,进而报错
	printf("%d\n", strlen(arr[1]));      //同上
	printf("%d\n", strlen(&arr));        //随机值
	//strlen函数默认的类型是char *
	//而&arr的类型是char (*)[6]
	//所以类型不匹配,但是并没有妨碍,因为&arr同样还是起初指向首个元素的地址,所以仍旧等同于第一个例子一样的随机值
	printf("%d\n", strlen(&arr + 1));    //随机值(但是比第一个随机值小6,因为跳过了数组的6个元素)
	printf("%d\n", strlen(&arr[0] + 1)); //随机值(但是比第一个随机值小1)

题3

sizeof不在乎你是不是\0,只要占空间,就纳入计算。

    //字符数组
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));        
	printf("%d\n", sizeof(arr + 0));    
	printf("%d\n", sizeof(*arr));       
	printf("%d\n", sizeof(arr[1]));     
	printf("%d\n", sizeof(&arr));    
	printf("%d\n", sizeof(&arr + 1));   
	printf("%d\n", sizeof(&arr[0] + 1));

讲解

	char arr[] = "abcdef";      
	//a b c d e f \0    ——>  共7个字符
	printf("%d\n", sizeof(arr));        //7字节
	printf("%d\n", sizeof(arr + 0));    //arr是一个地址,arr+0是首元素的地址,大小是 - 4/8字节
	printf("%d\n", sizeof(*arr));       //arr是首元素的地址,*arr是第一个元素a ,大小是 - 1字节
	printf("%d\n", sizeof(arr[1]));     //arr[1]是第二个元素 ,大小是 - 1字节
	printf("%d\n", sizeof(&arr));       //&arr是数组的地址,本质还是地址,是地址就是 - 4/8字节
	printf("%d\n", sizeof(&arr + 1));   //&arr是数组的地址,&arr+1是跳过整个数组所指的地址,大小是 - 4/8字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,大小是 - 4/8字节

题4

	char arr[] = "abcdef";    
	printf("%d\n", strlen(arr));     
	printf("%d\n", strlen(arr + 0)); 
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

讲解

	char arr[] = "abcdef";    
	printf("%d\n", strlen(arr));     //arr是首元素的地址,从首元素到字符串最后,共有6个字符(\0不纳入计算)
	printf("%d\n", strlen(arr + 0)); //arr+0还是首元素地址,所以是 - 6
	printf("%d\n", strlen(*arr));    //野指针,会报错
	printf("%d\n", strlen(arr[1]));  //同样还是野指针,会报错
	printf("%d\n", strlen(&arr));    //&arr是整个数组的地址,但也是从首元素地址开始的,所以是 - 6
	printf("%d\n", strlen(&arr + 1));//&arr是整个数组的地址,&arr+1是跳过整个数组后的地址,所以是随机值
	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,所以是 - 5

题5

	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));

讲解

	char* p = "abcdef";
	//常量字符串赋值给一个指针变量(该指针变量就不可修改了,可以在char前面加const),该指针指向第一个字符的地址
	//a  b  c  d  e  f  \0  共7个
	printf("%d\n", sizeof(p));       //p是一个指针变量,指向第一个字符的地址,大小是 - 4/8 个字节
	printf("%d\n", sizeof(p + 1));   //p是一个指针变量,指向第一个字符的地址,p+1指向第二个元素地址,大小是 - 4/8
	printf("%d\n", sizeof(*p));      //p是指向第一个元素地址的指针变量,*p则是第一个元素,大小是 - 1
	printf("%d\n", sizeof(p[0]));    //p[0] <==> *(p+0),即:第一个元素,大小是 - 1字节
	printf("%d\n", sizeof(&p));      //p是一个指针变量,&p是指针变量的地址,大小是 - 4/8
	printf("%d\n", sizeof(&p + 1));  //同上,大小是 - 4/8
	printf("%d\n", sizeof(&p[0] + 1));//p[0] <==> *(p+0) <==> 即:第一个元素,则&p[0]是第一个元素的地址,&p[0]+1是第2个元素的地址,所以是 - 4/8

题6

	char* p = "abcdef";
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));

讲解

	char* p = "abcdef";
	//a  b  c  d  e  f  \0   共7个
	printf("%d\n", strlen(p));        //6
	printf("%d\n", strlen(p + 1));    //5
	printf("%d\n", strlen(*p));       //野指针,会报错
	printf("%d\n", strlen(p[0]));     //野指针,会报错
	printf("%d\n", strlen(&p));       //随机值
	printf("%d\n", strlen(&p + 1));   //随机值(和&p随机值并没有什么关系,因为&p到&p+1之间可能就存在\0)
	printf("%d\n", strlen(&p[0] + 1));//5

题7

    //二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));         
	printf("%d\n", sizeof(a[0][0])); 
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));

讲解

	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));          //a是一个二维数组,共3x4=12个元素,每个元素都是int类型,所以12x4=48个字节
	printf("%d\n", sizeof(a[0][0]));    //a[0][0]是二维数组的第一个元素(int型),所以是4
	printf("%d\n", sizeof(a[0]));       //二维数组a[3][4]逻辑上是3行4列的数组,a[0]表示第一行(4个数据),所以是4x4=16个字节
	
	printf("%d\n", sizeof(a[0] + 1));  
	//a[0]是第一行的数组的数组名,此时并不是单独放在sizeof内部的,也没有&,所以不能代表二维数组第一行,而是表示第一行的第一个元素的地址,+1又表示第一行第二个元素的地址,所以是 - 4/8字节
	printf("%d\n", sizeof(*(a[0] + 1)));//a[0] + 1表示第一行的第二个元素,*(a[0] + 1)表示第二个元素的大小 ,所以是 - 4字节

	printf("%d\n", sizeof(a + 1));      
	//a没有单独放在sizeof内部,也没有&,所以a是表示二维数组【首元素】(第一行)的地址
	//所以a+1就是第二行的地址
	//是地址,大小就是 - 4/8字节

	printf("%d\n", sizeof(*(a + 1)));    //a+1就是第二行的地址,*(a+1)就是第二行的所有元素(4个),即:4x4=16字节
	printf("%d\n", sizeof(&a[0] + 1));   //4/8
	//a[0]是第一行的数组名。
	//&a[0]是第一行的地址
	//&a[0]+1是第二行的地址



	printf("%d\n", sizeof(*(&a[0] + 1)));//&a[0]+1是第二行的地址,*(&a[0]+1)表示的是第二行的所有元素(4个),即:4x4=16字节
	//当然也可以这样理解:!!
	//*(&a[0]+1)  ==>  *(&a[1])  ==>  a[1]
	//a[1]就表示第二行的数组名
	//sizeof(*(&a[0] + 1)))   <==>  sizeof(a[1])  ==>  数组名单独放在sizeof内部,就表示第二行的所有元素的大小


	printf("%d\n", sizeof(*a));
	//a单独放在sizeof内部,表示首元素(第一行)的地址,*a就表示第一行的所有元素,即:大小是 - 16字节


	printf("%d\n", sizeof(a[3]));    //16字节!!
	//a[3]表示二维数组a[3][4](三行四列)的第四行数组名,但是a[3][4]并没有第四行,会报错??
	//不会报错,因为sizeof不会让表达式参与运算,不会真的去访问这块第四行空间的,即:并没有去真正地访问第四行(a[3]表示第四行数组名)的数据,只是去看了一下
	//如果按照二维数组a[3][4]的规律推导下去的话,那第四行也同样是,四列数据,(    属性:类型是int(*a)[4]    )【a的属性类型是不会变的,即使没有数据】

总结

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

指针笔试题

笔试题1:程序的结果是什么?

	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;

笔试题2:程序的结果是什么?

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);

	return 0;
}

笔试题3:程序的结果是什么?

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);//%x 表示以16进制形式输出

	return 0;
}

讲解

  1. int* ptr1 = (int*)(&a + 1);中的&a是取的整个数组的地址,即:类型是int (*)[4]。加1,就跳到了整个数组的最后。用int*强制类型转换,用int*类型的指针ptr1来接收,ptr1[-1] <==> *(ptr1+(-1)) <==> *(ptr1-1),即:取向前跳4个字节的内容。
  2. int* ptr2 = (int*)((int)a + 1);中将a(数组名表示首元素的地址)用int类型强制转换十进制数字,eg:0x0012ff40 ——> 1,244,992,那么1,244,992+1——> 1,244,993 ——> 0x0012ff41 ,即:相当于地址加1(一个字节对应一个地址,所以地址加1,相当于字节加1),又因为是用int*类型强制类型转换并接收,所以往后4个字节。

笔试题4:程序的结果是什么?

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);

	return 0;
}

笔试题5:程序的结果是什么?

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

	return 0;
}

笔试题6:程序的结果是什么?

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

笔试题7:程序的结果是什么?

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);

	return 0;
}

笔试题8:程序的结果是什么?

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

.阿Q.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值