C语言进阶之指针(1)

指针进阶

本章重点:

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
    在初阶的时候,我就介绍了 指针相关基础知识,在进行进阶之前,我想把几个重点再提出来,不然进阶很多东西容易混。
    1、指针是用来存放地址的变量;
    2、指针的大小是固定的,32位平台就是四个字节,64位平台就是8个字节;
    3、指针的类型决定了指针的步长即指针在加减整数时候跳过的字节。

字符指针

字面意思 指向字符的指针变量叫字符指针,例如下面的代码,将字符‘w’,存入到 变量 ch中,ch的类型是char,取出ch的地址,存放在指针变量pc中,pc的类型是什么呢? 类比 char ch,ch的类型是char;所以指针变量pc的类型 就是 char* 。

int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

在这里插入图片描述

需要提醒的是如果存放常量字符串,常量字符串在内存中,是数据存储区,是不可以被修改的,所以当写下一串字符串,其实是将常量字符串的首地址存放在指针变量中的时候,最好用const修饰,保证*pa不可被修改 例如:

int main()
{
const char* pstr = "hello world.";//这里是把一个字符串首地址存放到pstr指针变量中
printf("%s\n", pstr);//从首地址往后打印,直到遇到‘\0’
return 0;
}

我们可以看一道例题,来区分,常量字符串在内存中存储首地址是什么样的


```c
#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存储的都是 字符串首地址 h的地址。

指针数组

这个在初阶的时候介绍过,我想的再次回顾一下,为了给下面的数组指针做铺垫,首先什么整形数组?,即int arr【5】可以解释为数组arr有五个元素,每个元素都为int。类比推出 指针数组 即为存放指针的数组,存放整形int 的指针就是整形指针数组,存放字符型char的指针就是字符数组指针。可以写成 int* arr1【5】arr1优先与数组结合说明他是个数组,数组有五个元素 每个元素都为 int * 如图:
在这里插入图片描述

数组指针

数组指针的定义

我们还是用类比的方法,字符指针是存放字符地址的指针,它指向的内容是字符,类型为 char * ;整形指针 存放整形地址的指针,它指向的内容是整形,类型为int* ;
那数组指针 就是能够存放数组的的指针咯,那应该怎么写呢?怎么写才能指向int arr[5] 整个数组呢?首先,他是数组指针是指针 所以 要先与 * 结合 (*pa),还得能够指向数组 即**(pa)[10]**,(因为 [ ]优先级高于所以为了让pa是指针 要用括号括起来,提高其优先级,如果是整形数组就是 *int (pa)[5] = &arr;
pa的类型就是 int(*)[5],说明pa是数组指针,pa指向的内容就是 int [ 5],即 有五个元素的指针,如图:
在这里插入图片描述

&数组名VS数组名

我在初阶的时候,介绍过 arr只有单独是sizeof的操作数的时候,才是整个数组地址,其他全部代表首元素地址,那我在指针进阶这里 又重复提出来干啥的呢? 我们先看一段代码:

int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}

在这里插入图片描述
我们发现,arr 和&arr的地址是一样的,不是说arr是首元素地址,&arr是真个元素地址吗?既然都一样 可不可以直接用arr代替&arr呢?我们再往下看 arr + 1的地址,和 &arr + 1的地址,FA8C和FAB0相差四十个字节,这是怎么回事,都是 + 1竟然一个增涨了四个字节,一个增长了40个字节,想想我们在指针这一章一直说的 什么决定步长,是类型哇,我们用指针变量 接受数组首元素地址 所以 它的类型就是 int* (32位四个字节)它的步长可不是一个整形吗,那整个数组的的类型是 int(*)[ 10],它自增一 就相当于 10 * sizeof(int) 即为 40个字节;所以虽然 &arr和arr值是一样的 但是它代表的意义不一样。

数组指针的使用

数组指针怎样使用呢,既然能指向整个数组,我们就举个例子来看看数组指针的使用:


#include<stdio.h>

void print(int(*pa)[5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{

			printf("%d ", ( * (pa + i))[j]);
			//  *(pa + i) =  pa[i]  所以 可以改写成pa[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;
}

将数组名 arr传给函数print打印的时候,我们都知道 arr是首元素地址,但是二维数组首元素是二维数组第一行,传arr相当于传第一行的地址,即一维数组的地址,所以我们用数组指针接收:
在这里插入图片描述
我们用一组例子来,回顾数组指针和指针数组

int arr[5]; //整形数组
int *parr1[10];//指针数组 ,数组10个元素,没个元素是int*
int (*parr2)[10];// 数组指针,指针类型是 int(*)[10],只想的内容是 int [10]指向整形数组
int (*parr3[10])[5];

最后一个 int (*parr3[10])[5],parr3首先跟数组结合,我们可以先将parr3[10]看成一个整体 pa 及int (*pa)[5].这就是一个数组指针,指向int [5],整体再看就是 parr3[10]这个数组有十个元素 每个元素都是一个数组指针 指向int[5];如图所示:在这里插入图片描述

数组参数、指针参数

当我们学会 指针,数组之后,我们传参都会穿数组或者 指针,那应该怎么设计呢??

一维数组传参

#include <stdio.h>
void test(int arr[])//可以用数组接收,不指定元素个数
{}
void test(int arr[10])//指定元素个数
{}
void test(int *arr)//数组名就是地址 也就是指针,我们可以用指针接收
{}
void test2(int *arr[20])//传指针数组,我们就用指针数组接收
{}
void test2(int **arr)//数组名 首元素地址 首元素是 int* 他的地址 我们用二级指针接收一级指针的地址。
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

二维数组传参

void test(int arr[3][5])//二维数组传参,用二维数组接收
{}
void test(int arr[][])//不指定二维数组大小肯定是不行的
{}
void test(int arr[][5])//必须知道步长,所以列得告诉
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//不可以 arr是第一行整个元素地址,也就是数组地址,可以接受但是不好操作
{}
void test(int* arr[5])//指针数组 怎么能接收一个数组地址?肯定错的
{}
void test(int (*arr)[5])//这个可以有 用数组指针 接收整个数组
{}
void test(int **arr)// 用 二级指针接受 整个数组的地址 是不可行的 除非接受某个地址的地址、
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

一级指针传参

一级指针传参,这个倒没什么 就是用一级指针接收

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

提醒:
二级指针是存放一级指针地址的;数组的地址 要用数组指针接收。
例如:

int *arr[5]
//传参
test(arr)//数组名 是首元素的地址 首元素是 int* 它的地址 也就是一级指针的地址所以要用二级指针接收
void test(int ** pa)

二级指针传参

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

函数指针

首先看一段代码:

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

在这里插入图片描述
我们发现 test函数的地址,和取地址&test的地址是一样的,那函数的地址该如何存储呢,也就是函数指针,首先他得是指针,指向函数:void (*pa)();
让我们来看两端有趣的代码:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

分析:代码1
在这里插入图片描述
代码2
在这里插入图片描述
我们将signal指向的函数拿出 单独看返回类型:
在这里插入图片描述发现它跟函数参数一样,所以我们可以类型重定义一下即typedef void(*pfun_t)(int);需要提醒的是 重定义函数的特点不是将我们重定义的部分卸载后面 typedef void(*)(int) ptfun_t这种写法是错误的。改完之后我们就可以看得清晰了:

pfun_t signal(int, pfun_t);
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值