
回顾
首先我们来大概回顾一下指针初阶的知识
 内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号—编号也被称为地址,地址在C语言中也被称为指针,指针(地址)需要存储起来—存储到变量中,这个变量就被称为指针变量
 例如:
int a = 10;
int* pa = &a;
指针的大小固定是4/8个字节(32位/64位平台)
 地址是物理的电线上产生
 电信号转化为数字信号
 32位机器—32根地址线—1/0
 32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址
 也就是需要4个字节才能存储
 所以指针变量的大小就是4个字节
 同理在64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节,所以指针变量的大小就是8个字节
1 字符指针
在指针的类型中我们知道有一种指针类型为字符指针char*
 一般使用
int main()
{
 char ch = 'w';
 //ch = 'a'//这里可以理解成办事的方法有很多种,这只是其中一种
 char *pc = &ch;//pc就是字符指针
 *pc = 'w';
 return 0;
}
现在有以下代码
int main()
{
	char arr[] = "abcdef";
	const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改
	return 0;
}
这里本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改,另外注意一下const的用法,const放在的左边限制的是p,const放在*右边限制的p
 我们来将上面的代码打印出来看一看效果
int main()
{
	char arr[] = "abcdef";
	const char* p = "abcdef";//本质上是把字符串首字符的a地址放在p当中,相当于p指向了字符串首元素a的地址//这个字符串是一个常量字符串,常量字符串不能被修改,所以我们需要加上一个const防止被修改
	printf("%s\n", p);
	printf("%c\n", *p);
	return 0;
}

 
 大家思考一个问题(注意结合上述字符指针的知识),看下面代码
int main()
{
 const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
 printf("%s\n", pstr);
 return 0;
}
代码 const char* pstr = “hello bit.”,本质是把字符串 hello bit. 首字符的地址放到了pstr中
 
 上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量pstr中
 掌握了字符指针的基本知识过后我们来看一道笔试题,看下面代码
#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;
}

 
2 指针数组
整型数组—存放整型的数组
 字符数组—存放字符的数组
 指针数组—存放指针的数组
 这里我们再复习一下,下面的指针数组分别是什么意思
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
我们来使用指针数组模拟实现一个二维数组
int main(int argc, char* argv[], char* envy[])
{
	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[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(arr + i) + j));
			Sleep(1000);
		}
		printf("\n");
	}
	system("pause");
	return 0;
}
这里的*(*(arr + i) + j)意思是,arr这个指针变量里面存放的是首元素的地址,也就是arr1数组名第一个元素的地址,然后+依次遍历二维数组,我这里加的一个Sleep(1000)的意思是停留1秒钟打印一个数组
 这不是真正的二维数组,真正的二维数组在内存中是连续存放的
3 数组指针
3.1 数组指针的定义
数组指针
 类比:
 整型指针—指向整型变量的指针,存放整型变量的地址的指针变量
 字符指针—指向字符变量的指针,存放字符变量的地址的指针变量
 数组指针—指向数组的指针,存放的是数组的地址的指针变量
 我们已经熟悉:
 整形指针: int * pint; 能够指向整形数据的指针
 浮点型指针: float * pf; 能够指向浮点型数据的指针
 那数组指针应该是:能够指向数组的指针
 下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
解释
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指
向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 &数组名VS数组名
这里我们把数组名的理解讲一下
 数组名是数组首元素的地址
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}
但是数组名表示首元素的地址有两个例外
 1.sizeof(数组名),这里的数组名表示的不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
 2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址
 除此之外,所有的其他地方的数组名都是数组的首元素的地址
 这里有一个现象,大家看下面代码
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}
这里打印的地址都是一样的,但是本质上真的是一样的吗?
 我们将代码调试起来看一看
 
 这里虽然我们看到的地址是一样的,但是他们的类型有所差距
 这里取地址arr的类型应该是int(*)【10】
 正因为他们的类型不同
 现在我们来看下面代码
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}
你拷贝这串代码编译运行会发现各自加1的效果产生了不同的效果
 
 原因是指针类型的不同带来的不同效果
 对于数组名来说
 &数组名得到的是数组的地址
3.3 数组指针的使用
int main()
{
	int arr[10] = { 0 };
	//数组的地址存放到数组指针变量
	//int[10]*p=&arr;//error
	int(*p)[10] = &arr;
	//int*p1=&arr;
	return 0;
}
这里的int*p1=&arr可能会报警告,因为指针类型不符合,上面我们给大家介绍了&arr的指针类型是int(*p1)[10]
 那么数组指针怎么使用呢?一般放在二维数组上才方便
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
void print_arr2(int (*arr)[5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
int main()
{
 int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
 print_arr1(arr, 3, 5);
 //数组名arr,表示首元素的地址
 //但是二维数组的首元素是二维数组的第一行
 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
 //可以数组指针来接收
 print_arr2(arr, 3, 5);
 return 0;
}
看下面代码
//二维数组传参,形参是指针的形式
void Print(int (*p)[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 ", *(*(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 };
	//arr 是数组名,数组名表示数组首元素的地址
	Print(arr, 3, 5);
	return 0;
}
这里给大家解释一下*(*p+i)+j),加i相当于拿到某一行的地址,然后加j在进行遍历
 二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组,所以二维数组其实是一维数组的数组
 二维数组的数组名也是数组名,数组名就是首元素的地址,arr—首元素的地址—第一行的地址—一维数组的地址—数组的地址
 
 一维数组传参,形参的部分可以是数组,也可以是指针
 我们看下面代码
//void test1(int arr[5], int sz)
//{}
//void test2(int* p, int sz)
//{}
//
//int main()
//{
//	int arr[5] = { 0 };
//	test1(arr, 5);
//	test2(arr, 5);
//	return 0;
//}
形参部分可以写成数组,但是本质上是一个指针,所以建议用一个指针来接收
 二维数组传参。形参部分可以是数组,也可以是指针
//void test3(char arr[3][5], int r, int c)
//{}
//
//void test4(char (*p)[5], int r, int c)
//{}
//int main()
//{
//	char arr[3][5] = {0};
//	test3(arr, 3, 5);
//	test4(arr, 3, 5);
//
//	return 0;
//}
//
数组指针—是指针—是指向数组的指针
 指针数组—是数组—是存放指针的数组
 大家可以用好孩子这个例子去理解一下
4数组传参 指针传参
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//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);
}
第一个OK,数组传参用一个数组接收没毛病
 第二个OK,只是把元素个数写出来了
 第三种OK,用一个指针接收
 第四种OK,一个数组20个元素,每个元素为int*,用int*arr[20]接收没毛病
 第五种OK,一级指针的地址用一个二级指针来接收,所以也没毛病
4.2 二维数组传参
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//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);
}
第一种OK
 形参部分,行可以省略,但是列不能省略
 总结:二维数组传参,函数形参的设计只能省略第一个[]的数字
 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素
 这样才方便运算
 所以第二种不OK,第三种OK
 第四种,数组名是首元素的地址—二维数组是一行的地址,绝对不是某一个的地址,所以错误
 第五种不行,因为我传过去的是数组名,这里的形参只是个指针数组,那肯定不行,要么写成数组指针或者二维数组
 第六种OK,是一个数组指针,可以指向一行
 第七种错误,数组名表示首元素的地址,第一行的地址怎么可能会用二级指针来接收呢,肯定不对!
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;
}
大家自行理解一下,比较简单
 这里大家思考一个问题
 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
 比如
void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

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);
 return 0;
}
思考:
 当函数的参数为二级指针的时候,可以接收什么参数?
void test(char **p)
{
 
}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//Ok?
 return 0;
}

5函数指针
函数指针—指向函数的指针
 数组指针—指向数组的指针
 举个例子,看代码
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	int arr[10] = {0};
//	int (*pa)[10] = &arr;
//	//printf("%p\n", &Add);
//	//printf("%p\n", Add);
结果是一模一样的,你们可以在自己的编译器上面去试一试
 函数名是函数的地址
 &函数名也是函数的地址
//int Add(int x, int y)
//{
//	return x + y;
//}
//	int (*pf)(int, int) = &Add;//pf是函数指针变量
//	//int (*)(int, int) 是函数指针类型
//	return 0;
//}
来看一段代码
#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}
输出的结果
 
 看下面代码
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	//int (*pf)(int, int) = &Add;
//	int (*pf)(int, int) = Add;
//
//	int r = Add(3, 5);
//	printf("%d\n", r);
//
//	int m = (*pf)(4, 5);
//
//	printf("%d\n", m);
//
//	return 0;
//}
注意*要先和pf结合称为一个指针,再和参数类型结合组合称为函数指针
 来看看下面两段有趣的代码
 我们先看第一个
//代码1
(* (void (*)())0 )();
很多人看第一眼我去直接懵逼了,这个0是个什么东西,一个函数指针的相关知识搞这么复杂,其实这行代码是有他的特定意思的,我给大家解释一下
 0可以默认为是整型
 我可以把0认为是int类型,和x0012ff40没有区别各位
 也可以0x0012ff40—是一个以16进制表示的整数
 你也可以认为这个东西是地址—地址是编号—可以是整型的地址—Int
 举个例子
 //void(*p)()—p是函数指针
//void(*)()是函数指针类型
 我在0前面加了有个括号
 这里是把0强制类型转化为下面这种
void(*)()这种指针类型
 所以这行代码的意思是调用0地址处的函数,将0强制类型转化为
void(*)()类型的函数指针
 然后调用0地址处的这个函数
 这里面就运用到了函数指针的知识,尝试着去理解一下,这行代码出自于C陷阱与缺陷
 来看第二个代码
//代码2
void (*signal(int , void(*)(int)))(int);
signal是一个函数声明
 signal函数有两个参数,第一个参数的类型是Int,第二个参数类型是void(*)(int)函数指针类型
 该函数指针指向的函数有一个Int类型的参数,返回类型为void
signal函数的返回类型也是void(*)(int)函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型为void
 这个代码有点复杂,我们稍作修改
 我们需要了解一下typedef这个函数—类型重命名
//	typedef void(*pf_t)(int);
//	pf_t signal(int, pf_t);
//	void (* signal(int, void(*)(int) ) )(int);
//



















