C-指针与函数、数组混合使用详解


一、一级指针

一级指针通常用作函数的参数,在调用函数时,形参和实参占用不同的内存空间,但空间里存放的指针指向的是同一块内存。即一级指针做函数参数, 在函数内做形参做重新指向并不会影响实参的指向。

用一个小例子说明一下:

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

void point(int* p, int *q)
{
	p = q;
	printf("%d\n", *p);
}

int main()
{
	int a = 10;
	int b = 20;
	int* p = &a;
	int* q = &b;
	point(p, q);
	printf("%d\n", *p);
	system("pause");
	return 0;
}

在这个小例子中,输出的值是20和10。
 
结论:这里不能通过改变形参的指向来改变实参的指向,因为形参和实参只是两个指向同一空间的不同指针,形参改了但是实参不会被改变。
 
 
对上面的例子做一个小小的调整:

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

void point(int* p, int *q)
{
	*p = *q;
	printf("%d\n", *p);
}

int main()
{
	int a = 10;
	int b = 20;
	int* p = &a;
	int* q = &b;
	point(p, q);
	printf("%d\n", *p);
	system("pause");
	return 0;
}

输出的结果是20和20。
 
结论:因为形参和实参指向的是同一空间,在形参中改变指向空间的值,实参指向空间的值也发生改变。
 
 
分享一个指针的使用小例子:

int main()
{
	char* p = "hello world!";   //  第一种
	printf("%s\n", p);
	*p = 'x';
	printf("%s\n", p);

	char q[] = "hello world!";   //  第二种
	printf("%s\n", q);
	q[0] = 'x';
	printf("%s\n", q);
	
	system("pause");
	return 0;
}

分析这段代码,发现有什么问题?

运行之后发现,这段代码第五行会发生异常,说明不能修改该指针指向的内容。这是因为开始定义的是一个指针p,指针p里存放的是地址,指向常量区存放的字符串,而常量是不允许被修改的。
但是为什么第二种就没有异常,允许修改呢?
因为以数组的方式存放的是拷贝后的字符串,常量区的字符串已经被拷贝到栈区,此时可以进行修改。

在这里插入图片描述

二、二级指针

举一个小例子:

void point(int **q,int *a)
{
	*q = a;
}

int main()
{
	int a = 10;
	int *p = NULL;
	point(&p, &a);
	printf("%d\n", *p);
	system("pause");
	return 0;
}

输出10。

这里用二级指针改变形参指向,从而改变实参指向。
 
 

三、指针数组、数组指针、sizeof和strlen()的区别

1.指针数组:存放指针的数组。

int* arr[10];

2.数组指针:指向数组的指针。

int(*arr)[10];

 
 
指针和数组混合使用的一些小例子:

例一:

int main()
{
	int a[] = { 1,2,3,4 };// a表示首元素地址,&a表示数组地址
	printf("%d\n", sizeof(a));// 16  特殊处理  表示整个数组大小
	printf("%d\n", sizeof(a + 0));// 4  表示首元素地址
	printf("%d\n", sizeof(*a));// 4  表示首元素地址解引用,第一个元素
	printf("%d\n", sizeof(a + 1));// 4  表示第二个元素地址
	printf("%d\n", sizeof(a[1]));// 4  表示第二个元素
	printf("%d\n", sizeof(&a));// 4  特殊处理  表示首元素大小
	printf("%d\n", sizeof(*&a));// 16  相当于a,表示整个数组大小
	printf("%d\n", sizeof(&a + 1));// 4  表示整个数组的下一个空间地址
	printf("%d\n", sizeof(&a[0]));// 4  表示首元素地址
	printf("%d\n", sizeof(&a[0] + 1));// 4  表示第二个元素的地址
	system("pause");
	return 0;
}

在此应当注意:当用sizeof求字节的时候,数组名(a)和取地址数组名(&a)往往会特殊化处理,原本sizeof(a)表示首元素地址,sizeof(&a)表示数组地址;特殊化处理后,sizeof(a)表示整个数组的大小,sizeof(&a)表示首元素大小
 
 
例二:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };// arr表示首元素地址,&arr表示数组地址
	printf("%d\n", sizeof(arr));//  6  特殊处理  表示整个数组的大小
	printf("%d\n", sizeof(arr + 0));//  4  表示首元素地址
	printf("%d\n", sizeof(*arr));//  1  表示首元素地址解引用,第一个元素
	printf("%d\n", sizeof(arr[1]));//  1  表示第二个元素
	printf("%d\n", sizeof(&arr));//  4  表示首元素地址
	printf("%d\n", sizeof(&arr + 1));//  4  表示整个数组的下一个空间地址
	printf("%d\n", sizeof(&arr[0] + 1));//  4  表示第二个元素的地址

	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));//  随机值-1
	system("pause");
	return 0;
}

 
3.sizeof 和 strlen()的区别:

1、sizeof

sizeof()是运算符,而不是一个函数。
其值在编译时即计算好了。
参数可以是数组、指针、类型、对象、函数等。
功能是:获得保证能容纳实现所建立的最大对象的字节大小,包括’\0’
由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小
实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
数组——编译时分配的数组空间大小;
指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
类型——该类型所占的空间大小;
对象——对象的实际占用空间大小;
函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。

小例子:
i=10;
sizeof(i++);
这两句执行完成后,i的值会不会变?为什么呢?

答:不会变!
因为sizeof的求值对象如果是表达式,会根据表达式类型直接给出结果,但表达式并不会执行。所以 sizeof(i++) 相当于变成了 sizeof(int)。

 
 
2、strlen()
strlen()是函数
其值要在运行时才能计算。
参数必须是字符型指针(char*), 且必须是以’\0’结尾的。当数组名作为参数传入时,实际上数组就退化成指针了。
它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符’\0’。返回的长度大小,不包括’\0’

 
 

例三:多级指针举例:

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);
	system("pause");
    return 0;
}

图示,分析这道题:
在这里插入图片描述
 

四、函数指针、函数指针数组、转移表

1.函数指针:

先定义一个简单的指针

int main()
{
	int a = 10;
	int *p = &a;
	printf("%d\n", *p);
	system("pause");
	return 0;
}

很明显,上面的代码定义了一个p指针,它指向变量a,*p解引用得到a的值。
与这个指针类似,函数指针指向一个函数,而不是一个变量。
 
 
定义一个函数指针

void test(char *str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针的定义方式
	void(*p)(char *str) = test;
	void(*p1)(char *str) = &test;

	//调用函数test,这是两种定义方式
	(*p)("hello world!");// 第一种
	(*p1)("hello nature!");
	p("hello world!");// 第二种
	p1("hello nature!");

	system("pause");
	return 0;
}

 
下面是函数指针的两种定义方式:

	void(*p)(char *str) = test;
	void(*p1)(char *str) = &test;

解析:

  • 定义p和p1为一个指向函数的指针变量,它们指向的函数类型为void型,且有一个指针参数,并且用test函数名初始化这个函数指针。
     
  • 在上面两种调用方式中,所有的解引用操作符可以省略。同样,在两种调用方式中,&也可以省略。
     
  • 那为什么下面这种方式不可以?
void *p2(char *str) 

 因为,这种是函数p2的声明,其返回值是void* 。
 

  • test函数都是在调用时才能分配空间,才有地址,
void(*p)(char *str) = test;
void(*p1)(char *str) = &test;

 那这样两行代码在没有调用test()时,就已经传了test()的地址?
 因为此时给指针赋的是一个虚地址,等真正执行函数时,通过虚地址找到真实地址。
 
 

2.函数指针数组:

函数指针数组:是存放函数地址的数组。
 
用一个小例子说明一下:

void test(char *str)
{
	printf("%s\n", str);
}
int main()
{
	void(*arr[3])(char* str);
	// 初始化数组元素,指向函数test
	arr[0] = test;// 第一种
    arr[1] = test;
	arr[2] = test;

	arr[0] = &test;// 第二种
	arr[1] = &test;
	arr[2] = &test;
	
	// 调用函数test
	(*arr[0])("aaa");// 第一种
	(*arr[1])("bbb");
	(*arr[2])("ccc");

	arr[0]("aaa");// 第二种
	arr[1]("bbb");
	arr[2]("ccc");
	
	system("pause");
	return 0;
}

 
 
3.实现转移表
 
常规实现:

// 实现转移表(计算器)
void menu()
{
	printf("********************************************\n");
	printf("**          1.add          2.sub          **\n");
	printf("**          3.mul          4.div          **\n");
	printf("********************************************\n");
}

int my_add(int a, int b)
{
	return a + b;
}

int my_sub(int a, int b)
{
	return a - b;
}

int my_mul(int a, int b)
{
	return a * b;
}

int my_div(int a, int b)
{
	return a / b;
}

int main()
{
	int select = 1;
	int x = 0;
	int y = 0;
	int tmp = 0;
	int flag = 0;
	while (select)
	{
		menu();
		printf("请选择功能选项:");
		scanf("%d", &select);
		switch (select)
		{
		case 1:
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			tmp = my_add(x, y);
			flag = 1;
			break;
		case 2:
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			tmp = my_sub(x, y);
			flag = 1;
			break;
		case 3:
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			tmp = my_mul(x, y);
			flag = 1;
			break;
		case 4:
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			tmp = my_div(x, y);
			flag = 1;
			break;
		default:
			printf("输入错误!程序退出!\n");
			flag = 0;
			break;
		}
		if (flag)
		{
            printf("result = %d\n", tmp);
		}
	}
	system("pause");
	return 0;
}

 
函数指针数组实现:

// 函数指针数组 实现转移表(计算器)
void menu()
{
	printf("********************************************\n");
	printf("**          1.add          2.sub          **\n");
	printf("**          3.mul          4.div          **\n");
	printf("********************************************\n");
}

int my_add(int a, int b)
{
	return a + b;
}

int my_sub(int a, int b)
{
	return a - b;
}

int my_mul(int a, int b)
{
	return a * b;
}

int my_div(int a, int b)
{
	return a / b;
}
int main()
{
	int x = 0;
	int y = 0;
	int select = 1;
	int flag = 0;
	int tmp = 0;
	int(*p[5])(int x, int y) = { 0,my_add,my_sub,my_mul,my_div };// 转移表
	while (select)
	{
		menu();
		printf("请选择功能选项:");
		scanf("%d", &select);
		if ((select <= 4 && select >= 1))
		{
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			tmp = (*p[select])(x, y);
			flag = 1;
		}
		else
		{
			printf("输入错误!程序退出!\n");
			flag = 0;
		}
		if (flag)
		{
			printf("result = %d\n", tmp);
		}
		else break;
	}
	system("pause");
	return 0;
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值