指针进阶理解

1 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用 :
int main ()
{
    char ch = 'w' ;
    char * pc = & ch ;
    * pc = 'w' ;
    return 0 ;
}

还有一种写法:

int main ()
{
    const char* pstr = "hello bit." ; // 这里是把一个字符串放到 pstr 指针变量里了吗?
    printf ( "%s\n" , pstr );
    return 0 ;
}
  const char* pstr = "hello bit." ; 特别容易让同学以为是把字符串 hello bit放到字符指针 pstr 里了,但是 本质是把字符串 hello bit. 首字符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;
}

 

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

2 指针数组

指针数组是数组!

字符数组:存放字符的数组

整型数组:存放整形的数组

指针数组:存放指针(地址)的数组,存放在数组中的元素都是指针类型的

使用指针数组,模拟一个二维数组。 

int main()
{
    //使用指针数组,模拟一个二维数组
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 2,3,4,5,6 };
    int arr3[] = { 3,4,5,6,7 };
 
    //指针数组
    int* arr[] = { 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]);
        }
        printf("\n");
    }
    return 0;
}

3 数组指针

3.1 数组指针的定义

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint ; 能够指向整形数据的指针。
浮点型指针: float * pf ; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
在前面我们已经知道
对于数组名的理解:
数组名是数组首元素的地址
但是存在2个例外:
1. sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
2. &数组名,这里的数组名表示整个数组,取出的是 数组的地址

3.2 &数组名 VS 数组名

int main()
{
	int arr[10];
	printf("%p\n", arr);//int*
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);//int*
	printf("%p\n", &arr[0] + 1);

	printf("%p\n", &arr);//
	printf("%p\n", &arr+1);

	//指针类型决定了指针+1,到底+几个字节

	return 0;
}

前两个都+4,最后一个+了40.

根据上面的代码我们发现, &arr arr 虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是 数组的地址 ,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址 +1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40.
数组指针的写法
int ( * p )[ 10 ];  (方框里的数字不可省略)
// 解释: p 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为 10 个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。
// 这里要注意: [] 的优先级要高于 * 号的,所以必须加上()来保证 p 先和 * 结合。
int main()
{
    int arr[10] = {0};
    int (*p)[10] = &arr;
}


int main()
{
	char* arr[5];
	char* (*pc)[5] = &arr;
}

 3.3 数组指针的使用

一维数组:(不推荐)

#include <stdio.h>
int main ()
{
    int arr [ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 };
    int ( * p )[ 10 ] = & arr ; // 把数组 arr 的地址赋值给数组指针变量 p
    // 但是我们一般很少这样写代码
    return 0 ;
}

数组指针一般运用在二维数组上: 

这是最常见的一种写法,二维数组传参,形参也用了二维数组的形式

void print(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[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} };
	print(arr, 3, 5);

	return 0;
}

 已知一维数组

void print(int arr[], int sz)  //形参是数组的形式
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);//arr[i]===>*(arr+i)
	}
}


void print(int* arr, int sz)  //形参是指针的形式
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);//arr[i]===>*(arr+i)
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);

	return 0;
}

二维数组的数组名可以表示为数组首元素的地址,而二维数组的首元素地址表示的就是第一行的地址。 

回过头来看上面二维数组的传参,可以写为

void print(int (*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", p[i][j]); // p[i] <==> *(p+i)加1就跳过一行
		}
		printf("\n");
	}
}

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

	return 0;
}

形参部分为指针。

以上就是数组指针的真正用法

3.4 总结

说说以下代码的意义。

int arr [ 5 ];
arr是一个能够存放5个整型数据的数组
int * parr1 [ 10 ];
parr1是一个指针数组,数组里有10个元素,每个元素的类型是int*
int ( * parr2 )[ 10 ];
parr2是一个数组指针,该指针是指向数组的,指向的数组里有10个元素,每个元素的类型是int
int ( * parr3 [ 10 ])[ 5 ];

parr3是一个数组,是存放数组指针的数组,这个数组里面有10个元素,int(*)[5] 。 存放的这个数组指针,指向的数组有5个元素,每个元素是int类型。

数组参数、指针参数

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

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

数组传参,形参可以写成数组形式,也可以是指针形式。数组传参的本质是,传递了数组首元素的地址。 

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

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 函数能接收什么参数?
int a = 10;
int * ptr = &a;
int arr[5];
test1(&a); 传整型变量的地址
test1(ptr); 传整型指针
test1(arr); 传整型一维数组的数组名
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(int ** p)

{}

int n = 10;

int *p = &n;

int **pp = &p; 

int* arr[6];

test(&p);

test(pp);

test(arr);  //arr表示首元素地址,也就是int*的地址

 函数指针

数组指针 - 指向数组的指针 - 存放的是数组的地址 - &数组名就是数组的地址

函数指针 - 指向函数的指针 - 存放的是函数的地址 - 怎么得到函数的地址呢?&函数名?

答案是:是的。

需要注意的是:

&函数名 就是函数的地址
函数名 也是函数的地址 

并且他们类型相同。

int Add(int x, int y)
{
	return x + y;
}

int (*pf1)(int, int) = Add;  //pf1就是函数指针变量

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

写法和数组指针非常的类似。

int main()
{
	//&函数名就是函数的地址
	//函数名也是函数的地址

	printf("%p\n", &Add);
	printf("%p\n", Add);

	int (* pf2)(int, int) = &Add;
	int ret = (* pf2)(2, 3); //这里的*可以不用写,可以直接写为pf2(2,3);
//因为我们最开始的写法就是Add(2,3) ,这里的Add就是函数名也就是一个地址,所以在这里可以将*省略。

	printf("%d\n", ret); //5

	return 0;
}

6 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int * arr [ 10 ];
// 数组的每个元素是 int*
char *arr[5];
//字符指针数组 - 数组 - 存放的是字符指针char*
那么,函数指针数组
数组 - 存放的是函数指针 - 存放的是函数的地址

6.1 函数指针数组的定义 

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	int (*pf1)(int, int) = &Add;
	int (*pf2)(int, int) = &Sub;
	//数组中存放类型相同的多个元素
	int (* pfArr[4])(int, int) = {&Add, &Sub};//pfArr 是函数指针数组 - 存放函数指针的数组
	//pfArr中的每一个元素都是一个函数指针,存放函数的地址

	return 0;
}
int ( pfarr [ 10 ])();   [ ]比*优先级高
parr1 先和 [] 结合,说明 pfarr 是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。

 6.2 函数指针数组的用途

函数指针数组的用途:转移表  

例子:模拟计算机

初始写法

void menu()
{
	printf("****************************\n");
	printf("***  1. add      2. sub  ***\n");
	printf("***  3. mul      4. div  ***\n");
	printf("***  0. exit             ***\n");
	printf("****************************\n");
}
//+ - * / && || & | >> <<

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:");
			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;
}

用函数指针数组:

void menu()
{
	printf("****************************\n");
	printf("***  1. add      2. sub  ***\n");
	printf("***  3. mul      4. div  ***\n");
	printf("***  0. exit             ***\n");
	printf("****************************\n");
}
//+ - * / && || & | >> <<

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		//函数指针数组 - 转移表
		int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
		//                          0     1     2   3    4
		if (0 == input)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误,重新选择!\n");
		}
	} while (input);

	return 0;
}

这种写法很巧妙,但是有一个约束,就是只能往int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};传参数为int,int的函数,并且返回类型也为int。

7 指向函数指针数组的指针

首先我们先写指向整型指针数组的指针

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int* arr[] = { &a,&b,&c };//整形指针数组
	int* (*p)[3] = &arr;//p是指针,是指向整形指针数组的指针
	return 0;
}

类比的,我们可以写出指向函数指针数组的指针

#include<stdio.h>
int main()
{
	int(*arr[5])(int, int) = { NULL,&Add,&Sub,&Mul,&Div }; //arr是函数指针数组
	p = &arr;//存放函数指针数组的指针
	int(*(*p)[5])(int, int) = &arr;
	return 0;
}

p就是能够指向函数指针数组的一种指针。

无论是函数指针&函数指针数组&函数指针数组的指针等等,可以一直延申下去!

我们在书写变量时,首先将变量p写出来,在添加其类型。

也可以从简单的函数指针的基础上修改!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值