C语言之指针(4)

1.字符指针变量

在指针的类型中我们知道有一种指针类型为字符指针 char* 。

有两种使用方式:

 char arr[10] = "abcdef";
 char* p1 = arr;  
char* p2 = "abcdef";

 代码 char* p2 = "abcdef";这里并不是把字符串放到p2中,本质上是把字符串首字符a的地址放到了p2中。需要注意的是常量字符串的内容不能被修改,所以我们用const来修饰,警示自己这个字符串不能被修改。 

我们来打印一下 :

#include <stdio.h>
int main()
{
    char arr[10] = "abcdef";
    char* p1 = arr;
    *p1 = 'w';
    const char* p2 = "abcdef";
    printf("%s\n", p1);
    printf("%s\n", p2);//%s打印字符串,需要提供起始地址
    return 0;
}

 输出结果:


《剑指offer》中收录了一道和字符串相关的笔试题,我们看一下输出结果是什么?

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

输出结果:

这里str3和str4指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际上会指向同一块内存。相同的常量字符串没必要保存2份,因为常量字符串不能被修改,所以str3和str4共用一份,也能节省空间。

但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同。


2.数组指针变量

我们知道,指针数组是一种数组,数组中存放的是地址(指针)。 数组指针变量是指针变量?还是数组? 答案是:指针变量。

整型指针变量: int * pint; 存放的是整型变量的地址,能够指向整型数据的指针。

浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

数组指针变量是:存放的是数组的地址,能够指向数组的指针变量

int *p1[10];   //指针数组
int (*p)[10]; //数组指针

解释:p先和 * 结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以 p是一个指针,指向一个数组,叫数组指针。

注意:[] 的优先级要高于 * 的,所以必须加上()来保证 p 先和 * 结合。


3.二维数组传参的本质

我们可以这样理解二维数组:可以看做每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。 如下图:

根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。

二维数组传参的本质:也是传递了地址,传递的是第一行一维数组的地址。 

数组接收:

#include <stdio.h>
void Print(int arr[][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 ", 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;
}

指针接收:

#include <stdio.h>
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));   //==(*(p+i))[j] ==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} };
	Print(arr, 3, 5);//数组名表示首元素的地址,也就是第一行的地址
	return 0;
}

运行结果: 

我们来理解一下 *(*(p+i)+j) 的含义

  • p+i表示第i行的数组地址
  • *(p+i)表示第i行数组名,因为对整个一维数组的地址解引用就相当于拿到了这个数组的数组名 (*p=*&arr=arr) ,数组名又表示首元素的地址。
  • (*(p+i)+j)表示该一维数组下第j个元素的地址。

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。


4.函数指针变量

4.1函数指针变量的创建

函数指针变量是用来存放函数地址的,未来通过地址能够调用函数。

那么函数是否有地址呢?我们测试一下:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("test: %p\n", test);
    printf("&test: %p\n", &test);
    return 0;
}

运行结果:

确实打印出来了地址,所以函数是有地址的,函数名就是函数的入口地址,当然也可以通过 &函数名 的方式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量,如下:

int Add(int x, int y)
{
    return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的


4.2函数指针变量的使用

#include <stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int(*pf)(int, int) = Add;

    printf("%d\n", (*pf)(2, 3));
    printf("%d\n", pf(3, 5));   //两种写法都可行
    return 0;
}

《C陷阱和缺陷》中提到,因为pf是一个函数指针,那么*pf就是指针所指向的函数,所以(*pf)()就是调用该函数的方式。ANSIC标准允许程序员将上式简写为pf(),但是一定要记住这种写法只是一种简写形式。


出自《C陷阱和缺陷》中两段有趣代码:

代码1:

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

解释:

  • void (*)()              函数指针类型
  • (void (*)())            强制类型转换
  • (void (*)())0          将0强制类型转换成void (*)() 函数指针类型,就意味着0地址处放着无                              参,返回类型是void的函数
  •  (*(void (*)())0)()   解引用操作,调用0地址处放的这个函数

代码2: 

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

这是一次函数声明,可以理解为  void (*)(int) signal(int, void(*)(int));其实这种写法是错的,只是为了方便理解。

  • 函数名:signal
  • 函数的参数类型:int 和 void(*)(int)
  • 函数返回类型:void (*)(int)

我们可以利用typedef关键字简化代码,如下: 

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

 4.3 typedef关键字

typedef 是用来类型重命名的,可以将复杂的类型简单化。

typedef unsigned int uint;
//将unsigned int 重命名为uint
int main()
{
uint a1;   // unsigned int a1;
return 0;
}
typedef int* ptr_t;  //将 int* 重命名为 ptr_t 
int main(0
{
ptr_t p1;  //int* p1;
return 0;
}
typedef int(*parr_t)[5];//数组指针类型 int(*)[5]重命名为 parr_t 
                        //新的类型名必须在*的右边
int main()
{
parr_t  pa1;   //int(*pa1)[5];
return 0;
}
typedef void(*pfun_t)(int);// void(*)(int) 类型重命名为 pfun_t 
                           //新的类型名必须在*的右边

5.函数指针数组

把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针数组如何定义

#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;
}
int main()
{
    int (*pf1)(int,int) = Add;//pf1是函数指针变量
    int (*pfarr[4])(int, int) = { Add,Sub,Mul,Div };//pfarr是函数指针数组
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值