C语言指针2

1.字符指针

 

第一种写法

#include <stdio.h>
int main()
{
	char ch = 'w';
	char* p = &ch;
	*p = 'd';
	printf("%c", ch);
	return 0;
}

第二种写法

#include <stdio.h>
int main()
{
	char arr[] = "hello";
	const char* pstr = "hello world.";
	printf("%s\n", arr);//hello
	printf("%s\n", pstr);//hello world.
	printf("%c", *pstr);//h
	return 0;
}

加const是为了不发生数据改变,如果你后面执意*pstr等于其他东西,这种是错误的行为。

有些人认为第二行的printf里面的那个pstr的前面应该要加*,这也是错误的。

首先第二行我们写的是%s,这是为了打印字符串的,而不是打印字符的,如果要打印字符应该用%c才合适。因此我前面用arr这个字符数组与pstr做一个比较,之前我们知道arr代表的是数组首元素的地址,那么pstr也代表了字符串首个字符的地址。C语言中的字符串本质上也是数组,数组的本质又是指针,数组名表示的是数组首元素的地址,因此这个pstr这个指针和数组名是等价的。

以一道面试题为例

#include <stdio.h>
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	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不等于str2,str3等于str4,对于同一个常量字符串,赋值给两个不同的数组,两个数组会开辟各自的内存块来存放该字符串。而对于指针来说,就是指向同一块内存。

2.指针数组

        指针数组我们关注后面两个字“数组”,因此它的意思是存放指针的数组。

        例子如下

int* arr1[2] = {&a,&b};整形指针的数组        
char* arr2[2] = { "hello","world"};一级字符指针的数组
char** arr3[2] ={&str1,&str2};二级字符指针的数组

使用指针数组打印二维数组

#include <stdio.h>
int main()
{
	int i = 0;
	int j = 0;
	int arr[2][3] = { {1,2,3},{4,5,6} };
	int* p[3] = { arr[0],arr[1] };
	for ( i = 0; i < 2; i++)
	{
		for ( j = 0; j < 3; j++)
		{
			//printf("%d ", p[i][j]);
			//printf("%d ", *(*(p + i)+j));
			printf("%d ", *(p[i] + j));
		}
		printf("\n");
	}
	return 0;
}

 三个printf的写法,其结果都是一样的。

int* p[3];//p是一个3个元素是int* 的数组,那么在二维数组中是否有int* 类型的数据,答案是肯定的,这里二维数组的数组名是arr,代表的是首行数组的地址,也就是第一行arr[0]的地址,既然是地址那么它的类型就是int* ,因此我才会把arr[0]h和arr[1]给我的指针数组。

                                                                p[i] == *(p+i)

                                                                p[0][0] == **p == *(p+0)[0] == *(*(p+0)+0)

3.数组指针

        数组指针我们关注后面两个字”指针“,因此它的意思是指向数组的指针,存放的是数组的地址。

        基本样子是:int (*p)[10]; 其中int可以换成别的类型,元素个数也可以修改,这里只是个例子

        可以发现这和上面的指针数组很相似,那么如何识别呢,就看优先级。因为有了括号,p先和*号结合,说明这是一个指针变量,后面跟着【10】因此该指针变量指向的是一个大小为10个整型的数组,因此叫数组指针。

        数组指针前面还有两个字”数组“,这我们再熟不过了,我们知道数组名是数组首元素的地址,那&数组名和数组名有什么区别呢,例子如下

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };//一定要写明元素个数,不然看不出效果
	printf("%p\n", arr);
	printf("%p\n", arr+1);
	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	return 0;
}

 我们可以发现&arr和arr的地址表面是一样的,但是加了1之后结果大不一样,arr+1是跨了4步,&arr+1是跨了40步。因此得出结论arr是数组首元素地址,但&arr是整个数组的地址。

如果用数组指针打印二维数组,以指针数组打印二维数组的代码为例,只需要把数组指针初始化即可,其他代码不变,即int (*p)[3] = arr; 因为只有一个中括号,因此该指针变量指向的是一维数组,至于为什么填3,是因为该代码的二位数组是二行三列的数据,每一行都是一个独立的一维数组,一维数组里面又有三个元素,因此填3。且arr表示的是二维数组第一行的地址,第一行只有三个元素,因此也是填3。

来看看这四行代码分别代表了什么

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

第一行是普通的整型数组,没什么疑问。

第二行是指针数组,该数组有10个元素,每个元素是int*类型

第三行是数组指针,parr2是指针变量指向了一个有10个元素的数组,每个元素的类型为int

第四行是数组指针的数组,parr3和【10】结合,这是一个很常规的数组,它能放10个元素,如果我们把parr3[10]去掉,那剩下的int (*)[5]是不是跟数组指针的形式很像,因此parr3是一个有10个元素的数组,这里的元素是数组指针,每个数组指针指向一个数组,数组有5个元素,每个元素是int类型。

4.数组传参

一维数组传参

#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);
	return 0;
}

以上传参都是正确的

二位数组传参

#include <stdio.h>
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);
}

第二、第四、第五、第七的传参是不行的

第二个错在第二个中括号没有填数字,这个数字是必须填的,第一个中括号可以省略

第四个,二维数组的数组名是首行元素的地址也就是一整个数组的地址,而普通的指针是无法接收的因为它们的内存布局不同。数组在内存中是连续排列的一组数据,而指向整型变量的指针是指向单个整型变量的,它只知道如何操作单个整型变量的内存。

第五个,形参是一个指针数组,存放的五个类型为int*的指针,但是二维数组的元素都是int类型的,因此不可以

第七个,形参是二级指针,存放的是一级指针的地址,而二维数组的地址是第一行数组的整个地址,与第四个类似,因此不行。

5.指针传参

一级指针传参

代码如下

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	print(p, sz);
	print(arr, sz);
	return 0;
}

print函数的第一个形参是一个指针,需要传给它地址或者指针,p和arr都能传进去。

二级指针传参

代码如下

#include <stdio.h>
void test(int** p)
{
	printf("%d\n", **p);
}
int main()
{
	int a = 110;
	int* pa = &a;
	int** ppa = &pa;
	test(ppa);
	test(&pa);
	return 0;
}

指针数组的数组名也可以作为实参

6.函数指针

我们知道数组指针的意思是指向数组的指针,存储的是数组的地址,那么函数指针同理,它是指向函数的指针,存储的是函数的地址,让我们看下面的代码

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;
	printf("%p\n", Add);
	printf("%p\n", &Add);
	printf("%d\n", (*pf)(2, 3));
	printf("%d\n", pf(2,3));
	printf("%d\n", Add(2, 3));
	return 0;
}

结果如下

  

由此我们可以知道

①函数指针的一般写法是type_t    (*)(type_t 形参1,type_t 形参2,......)

②函数名与&函数名意思一致

③函数指针要使用时,要么像第三个printf一样,若加*号需要再包一个括号。要么像第四个printf一样什么都不加。

让我们看一下这个代码

(*(void (*)())0)();

void (*)()代表函数指针

(void (*)()) 0  对0进行强制转换,成为一个函数地址

(*(void (*)())0) 对0地址进行解引用

(*(void (*)())0)() 调用0地址处的函数

还有这个代码

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

因为*号没有和signal用括号抱在一起,因此signal会和后面的括号结合那么就有如下的解释,

signal(int , void(*)(int)),signal是一个函数,第一个形参是类型是int,第二形参的类型是函数指针类型,这个函数指针指向的函数参数是int,返回类型是void。

如果我们把signal(int , void(*)(int))去掉,那么剩下的就是void (*)(int)这又是一个函数指针。

总结:这是一个函数声明,声明的signal函数的第一个参数的类型是int,第二参数的类型是函数指针,该函数指针指向的函数是int,返回类型是void。signal函数的返回类型也是一个函数指针,该函数指针指向的函数参数是int,返回类型是void。

可以将其改写为如下的代码

typedef void(*)(int) pfun_t;//这是错误写法
typedef void(*pfun_t)(int);//正确写法
pfun_t signal(int,pfun_t);

接下来我们可以用函数指针简单实现一个计算器的效果

首先先上不使用函数指针的方法

#include <stdio.h>
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;
}
void menu()
{
	printf("***********************\n");
	printf("***1.add       2.sub***\n");
	printf("***3.mul       4.div***\n");
	printf("***********************\n");
}
int main()
{
	int input = 0;
	int a = 0;
	int b = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				printf("请输入两个数:>");
				scanf("%d %d", &a, &b);
				ret = add(a, b);
				printf("%d+%d=%d\n", a, b, ret);
				break;
			case 2:
				printf("请输入两个数:>");
				scanf("%d %d", &a, &b);
				ret = sub(a, b);
				printf("%d-%d=%d\n", a, b, ret);
				break;
			case 3:
				printf("请输入两个数:>");
				scanf("%d %d", &a, &b);
				ret = mul(a, b);
				printf("%d*%d=%d\n", a, b, ret);
				break;
			case 4:
				printf("请输入两个数:>");
				scanf("%d %d", &a, &b);
				ret = div(a, b);
				printf("%d/%d=%d\n", a, b, ret);
				break;
			default:printf("输入错误,请重新输入\n"); break;
		}
	} while (input);
	return 0;
}

我们可以发现switch里面的重复语句过多,也就是冗余。用函数指针就可以简化这个过程

下面是函数指针版本的代码

#include <stdio.h>
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;
}
void menu()
{
	printf("***********************\n");
	printf("***1.add       2.sub***\n");
	printf("***3.mul       4.div***\n");
	printf("***********************\n");
}
int calc(int (*pf)(int, int))
{
	int a = 0;
	int b = 0;
	int ret = 0;
	printf("请输入两个数:>");
	scanf("%d %d", &a, &b);
	ret = pf(a, b);
	if (pf == add)
		printf("%d+%d=%d\n", a, b, ret);
	if (pf == sub)
		printf("%d-%d=%d\n", a, b, ret);
	if (pf == mul)
		printf("%d*%d=%d\n", a, b, ret);
	if (pf == div)
		printf("%d/%d=%d\n", a, b, 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;
			default:printf("输入错误,请重新输入\n"); break;
		}
	} while (input);
	return 0;
}

函数指针数组

指针数组我们知道它是用来存放指针的数组

函数指针数组同理,它是用来存放函数指针的数组

一般的样子是

int (*p[5])(int ,int)

类型,个数自己修改

那我们就可以用函数指针数组来进行加减乘除运算

#include <stdio.h>
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;
}
void menu()
{
	printf("***********************\n");
	printf("***1.add       2.sub***\n");
	printf("***3.mul       4.div***\n");
	printf("***********************\n");
}
int main()
{
	int input = 1;
	int a = 0;
	int b = 0;
	int ret = 0;
	int (*pf[5])(int, int) = { 0,add,sub,mul,div };
	while (input)
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		if ((input <= 4) && (input >= 1))
		{
			printf("请输入数字>:");
			scanf("%d %d", &a, &b);
			ret = (*pf[input])(a, b);
		}
		else
			printf("输入错误\n");
		printf("ret=%d\n", ret);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值