搞懂这些,指针也不过如此----指针进阶,指针?纸老虎?

字符指针

基本知识

int main()
{
	char p = 'a';
	printf("%c\n",p);  //a
	printf("%d\n",p);  //97
	system("pause");
	return 0;
}

计算机只认识二进制,字符存取过程中一定会存在一个映射关系
机器处理字符a 的过程
存储:当在键盘中输入 a时,首先电脑会将 字符a 转换成对应的十进制数据 -97 之后查找ascii 码表 找到其对应的二进制序列
0110 0001 将二进制序列写入内存中
读取:将二进制从内存中拿出来,将其转换为十进制数字 97 ,反向查找ascii 码表 然后用char 识别就读取完成。

用法

char a = 'a';
char *p = &a;
printf("%c  %d\n", *p,*p);

注意单引号引字符
双引号引字符串
c语言 没有字符串类型,但有字符串
字符串类型相当于是一种模板,就是说可以用该类型定义字符串
c语言的字符串不是定义出来的,是直接书写出来的。

const char *s = "hello,world";
printf("%s\n", s);
const char *s = "hello,world";
printf("%s  %c  %d\n", s,*s,*s);

在这里插入图片描述
本质是把字符串 hello bit. 首字符h 的地址放到了s中。

hello,world 其实是字符串常量,它的存储位置是在字符常量区 ,本身是不能被修改的。注意字符指针指向的是一个常量时,前面加const const 修饰的是 * 代表指向的内容不可改。关于const 的具体详解,请点这里.

而用数组存储 字符串实际上是可以更改的。

    char arr[] = "hello,world";
	printf("%s\n", arr);
	
	char *ss = arr;
	*ss = 'H';
	printf("%s\n", arr);
	

在这里插入图片描述
注意 只有数字存储的是字符串的时候才可以整体打印,其他的时候都是需要循环输出。

例题

#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[] = “hello bit.” 是数组,是一个个存起来的,数组是在栈区里面的保存的,是可以修改的。

*str3 = “hello bit.” hello bit. 它的存储位置是在字符常量区 。
是在栈区定义了一个 *str3 让它指向 字符常量区的hello bit.的h的首地址。

在这里插入图片描述

str1和str2是两个数组,数组的操作方式是将右边常量字符串的内容拷贝进来,所以他们是两个空间,只是内容相同,所以str1 != str2。
而str3和str4是两个指针,编译器在处理的时候,会将相同的常量字符串做成同一个地址,所以,str3和str4指向的是同一个常量字符串,所以str3 == str4,

数组指针

数组指针是指针?还是数组?
答案是:指针。
整形指针: int * pint;能够指向整形数据的指针。 浮点型指针: float * pf;能够 指向浮点型数据的指针。
数组也是一种类型 ,故而也会存在数组指针。能够指向数组的指针就称为数组指针。

也就是type * 此处的type 是数组 所以对指针加 是加上指针所指的类型。

int *p1[10];    //指针数组  【】的优先级高于*,所以p1 先和【】结合
int (*p2)[10];     //数组指针  

指针数组

指针数组是一个存放指针的数组。

int* arr1[10]; //整形指针的数组 
char *arr2[4]; //一级字符指针的数组 
char **arr3[5];//二级字符指针的数组

&数组名 与 数组名

int arr[10] = {0};    
printf("%p\n", arr);   //数组首元素的地址
printf("%p\n", &arr);   //数组的地址
printf("%p\n", arr+1);
printf("%p\n", &arr+1);

在这里插入图片描述
&数组名 与 数组名 数据值一样,但类型不一样,通过 +1 就能看出来
&数组名+1 实际上是加上了整个数组的大小。数组名+1 只不过是加上了一个数组元素的大小(请注意:是数组元素,而不是一个整型,或者其他类型,这点在二维数组里面尤其需要注意,后续会详细解释)

数组指针的使用

保存数组的地址,或者数组首元素的地址

数组的地址就用数组指针来保存
二维数组传参降维成一个一维的数组指针

int arr[10] = { 0 };
int *p1 = arr;
int(*p2)[10] = &arr;

数组类型的理解,不能仅仅看前面的Int 等等,更要看【】标定的元素个数

数组是具有相同元素类型的集合
注意这里的元素元素可以是 int double 自然也可使数组,因此也就会有二维数组

二维数组 内部的元素是一维数组
数组传参会降维成指针,降维成指向其内部元素类型的指针。

二维数组传参,只有第一个维度可以省略,第二个不能。 因此降维成一个一维的数组指针。

int arr[2][5]  //传参  int [][5]  本质是 int (*)[5]  

如果5省略,只知道是一个指针指向数组的,但是指向数组多少个元素是不确定的。因此只有第一个维度可以省略,第二个不能。

void show(int arr[][5], int num)//int arr[][5]  int (*p)[5]
{
	int i = 0;
	for (; i < num; i++)
	{
		int j = 0;
		for (; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{

	int arr[2][5] = {{ 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }};

	int num = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", num);
	show(arr,num);
}

show(arr,num);

数组名arr,表示首元素的地址 但是二维数组的首元素是一个数组
所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 ===>> 数组指针来接收

printf("%d ", arr[i][j]);
printf("%d", *(*(arr + i) + j));

理解这个语句
((arr + i) + j)
arr+i arr表示首元素的地址 arr+0 第 1个元素(一维数组)的地址 (因为涉及到数组下标从0 开始的问题)
*(arr + i) 第一个元素(一维数组) 的内容 在这里 把 (arr + i) 看成第一个数组的名字 x
(
(arr + i) + j) 就相当于 给 这个新的数组名 x 加 j 代表的是第一个整型元素的地址
((arr + i) + j) ((arr + 0) +0) 代表第一个整型元素

int arr[5];  //有5个整型元素的数组
int *parr1[10];//  指针数组
 int (*parr2)[10]; //指向数组大小为10的整型指针
 int (*parr3[10])[5]; //一个   整型数组的  指针数组

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

数组参数

一维数组传参

练习题

强调一遍概念
数组降维:降维成指向其内部元素的指针(仔细理解这句话)

#include <stdio.h> 
void test(int arr[]){ }   //ok?  1

void test(int arr[10]){ }  //ok?  2
 void test(int *arr){ }   //ok?  3
 
void test2(int *arr[20]){ } //ok? 4
void test2(int *arr[]){ } //ok? 5
 void test2(int **arr)   {}  //ok? 6
  
  int main() 
  {    
  int arr[10] = {0};    
  int *arr2[20] = {0};  
   
   test(arr);   // 1 2 3 都ok 
       
   test2(arr2);    //4 5  6  都ok
   }

重点强调一下 6 为什么可以

我们再来回顾一遍概念

数组传参,降维成指向其内部元素的指针
内部元素是一个指针
也就是降维成 指向指针的 指针 (二级指针)

二维数组传参

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

如上7中传参方式,哪一种可以成功
将 123 作为一组中 只有13 可以
将4567 作为一组 6是完全可以的 至于4 可以但需要多传递参数,一般不使用。

指针传参

一级指针传参

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

遵循传参的规则,形参p 是实参的一份拷贝,只不过指向的内容一样。

当一个函数的参数部分为一级指针的时候,函数能接收的参数类型:一维数组,或者一个一级指针

二级指针传参

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

函数指针

把函数的地址存到一个数组中,那这个数组就叫函数指针数组。
通常写法: int (* parr1 [10] ) ()

函数名本身就是一个地址
函数void func(void)中 func和&func的区别
func是函数的首地址,它的类型是void ()。
&func表示一个指向函数的地址,它的类型是void (*)()。
因此func和&func所代表的地址值是一样的,但类型不一样。func是函数首地址,&func是一个指向函数的指针!

函数指针数组的用途

一个计算器


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(*fun[5])(int x, int y) = { NULL, Add, Sub, Mul, Div };
	do
	{
		int select = 0;
		int x = 0;
		int y = 0;
		menu();
		printf("Please enter your select>");
		scanf("%d", &select);

		if (select<0 || select>4)
		{
			printf("plesse enter again\n");
			continue;
		}
		else if (select == 0)
		{
			printf("exit\n");
			break;
		}
		printf("please entern x and y\n");
		scanf("%d %d", &x, &y);
		int z= fun[select](x, y);
		printf("%d\n", z);

	} while (1);
	system("pause");
	return 0;
}

注:只简单写了框架,没有把完整的代码放上来

fun[select]和四个运算之间有了关系,可以达到简化代码的作用。

int(*fun[5])(int x, int y) = { NULL, Add, Sub, Mul, Div };
//定义一个函数指针数组 用从1开始的4个下标,0号下标不用。所以定义5个元素

int z= fun[select](x, y); 主要是这一条语句可以直接调用,不用像之前一样要判定选择的是哪一个的问题。很方便

回调函数

把一个函数的地址,作为形参传给另一个函数。

形参列表:调用的时候,一定要传入对应类型的变量(int,double,float,, struct,数组,多维数组)
//函数指针也可以看做一种类型,就如同int
, 函数指针类型当然可以定义变量。
//返回值 fun(形参列表(ps: 回调函数的语法格式,只不过是有一个参数是函数指针,调用的时候,你可以传进来一个函数指针), funptr(以他为例))

void show(){ };

fun(x, y, …, show(回调函数))
{
if (条件满足的情况){
show();
}

习题

1下面关于"指针"的描述不正确的是:( )

A.当使用free释放掉一个指针内容后,指针变量的值被置为NULL
B.32位系统下任何类型指针的长度都是4个字节
C.指针的数据类型声明的是指针实际指向内容的数据类型
D.野指针是指向未分配或者已经释放的内存地址
答案解析
注意一下c选项
A:free指针
//只是将p和所指向的那块内存取消联系,解除绑定,但是p本身保存的地址并没有改变,只是没有使用权了,那块内存的值也没有变,只是没有办法使用了。
free后,指针指向的内存被释放,但是指针仍然是存在的
此时的指针被称为“迷途指针”,就像迷途的羔羊一样
也称为“空悬指针”“悬浮指针”“野指针”。这个时候要把指针重新初始化为NULL后才能再使用,否则会得到我们不想要的结果。

B选项强调了32位系统,所以没问题。

CD选项是定义本身。

所以排除法也可以确定是A。

2

下面哪个是数组指针( )
A.int** arr[10]
B.int (*arr[10])
C.char *(arr)[10]
D.char(
)arr[10]

A是二级指针数组,B是指针数组,C是char *数组的指针,D是char *的数组

3

如何定义一个int类型的指针数组,数组元素个数为10个:( )
A.int a[10]
B.int (*a)[10]
C.int *a[10];
D.int (*a[10])(int);

题面要int的指针数组,A为int数组,B为int数组的指针,C为int的指针数组,D为int(*)(int)函数指针的数组,故选C

函数指针习题

下面哪个是函数指针?( )

作业内容
A.int* fun(int a, int b);
B.int(*)fun(int a, int b);
C.int (*fun)(int a, int b);
D.(int *)fun(int a, int n);

ABD没有区别,加的括号没有影响任何优先级,都是返回值为int *的函数,故选C。

2

定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数?下面哪个是正确的?( )
A.int (*(*F)(int, int))(int)
B.int (F)(int, int)
C.int (
(F)(int, int))
D.
(*F)(int, int)(int)

答题
答案解析
D类型不完整先排除,然后看返回值,B的返回值是int,C的返回值是int ,故选A。
判断返回值类型只需要删掉函数名/函数指针和参数列表再看就行了。int (
(*F)(int, int))(int)删掉(F)(int, int)后剩下int ()(int),符合题意

4

设有以下函数void fun(int n,char *s)(……),则下面对函数指针的定义和赋值均是正确的是:( )
A.void (*pf)(int,char); pf=&fun;
B.void (*pf)(int n,char *s); pf=fun;
C.void *pf(); *pf=fun;
D.void *pf(); pf=fun;

答案解析
CD前半句压根就不是定义而是声明,A选项参数列表的第二个参数错了。应为char*。选项正确。需要说明的是,对于函数名来说,前面的&和*都会被忽略,所以fun前面加不加取地址都没区别。只有定义出的函数指针变量(如题面中的pf)加上&后才会变成二级函数指针。

5

关于回调函数描述错误的是( )
A.回调函数就是一个通过函数指针调用的函数
B.回调函数一般通过函数指针实现
C.回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。
D.回调函数是调用函数指针指向函数的函数。
ABC就是基础概念,可以复习下。

6

下面程序的结果是:( )

int main()
{
int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int )((aa + 1));
printf( “%d,%d”, *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
A.1, 6
B.10, 5
C.10, 1
D.1, 5

答案解析
跟上题类似,&aa的类型是int ()[2][5],加一操作会导致跳转一个int [2][5]的长度,直接跑到刚好越界的位置。减一以后回到最后一个位置1处。(aa + 1)相当于aa[1],也就是第二行的首地址,自然是5的位置。减一以后由于多维数组空间的连续性,会回到上一行末尾的6处。故选A。

下面程序的结果是:( )

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

作业内容
A.5, 1
B.4, 1
C.4, 2
D.5, 2

答题
答案解析
(a + 1)等同于a[1],第一个是4,a的类型是int [5],&a的类型就是int()[5],是个数组指针。所以给int(*)[5]类型加一,相当于加了一个int [5]的长度。也就是这个指针直接跳过了a全部的元素,直接指在了刚好越界的位置上,然后转换成了int *后再减一,相当于从那个位置向前走了一个int,从刚好越觉得位置回到了1的地址处,所以第二个是1,故选B。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值