C 进阶指针

目录

一、字符指针

储存字符串的指针

const修饰的字符指针在内存的储存

二、指针数组

 指针数组的定义

指针数组的简单运用

三、数组指针

数组指针的定义

数组指针的使用

四、数组参数、指针参数的传递

一维数组传参

 二维数组传参

指针传参

五、函数指针

函数指针的定义

函数指针的用法

函数指针数组

指向函数指针数组的指针

六、回调函数

回调函数的定义

一点鄙见


一、字符指针

储存字符串的指针

一般来讲,我们可以创建一个字符变量,并在其中,放一个字符,再用一个指针指向这个变量,这就是一个指向字符的指针。但是,指针里面还饿可以放字符串,如下:

当p里面放的是字符串时:此处赋给p的是首字符的地址(即a的地址)

此时里面放的字符串为常量字符串,相当于p的常量变大了(用 *p 修改字符串首位时会报错)(有的编译器会报警告)

因此可以用const修饰,更严谨(这样写的话,如果后面出现了改变*p的操作,编译器会直接提示错误)

在打印字符串时,提供首地址,程序就会开始打印,在遇到 \0 时停止打印

const修饰的字符指针在内存的储存

注意:这里比较的是地址。,而不是内容

p1==p2 说明p1和p2指向同意字符串(常量字符串放在内存的只读区内,不能被修改,为了节约资源,只会在内存里存一份,因此p1 p2 两个指针指向的是同一个位置)

而arr1 和arr2 是两个独立数组,需要各自独立的空间,即首元素地址不同

二、指针数组

 指针数组的定义

整形数组:存放整形的数组;字符数组:存放字符的数组;以此类推,指针数组就是存放指针的数组

指针数组的简单运用

由指针数组的定义可知,数组中储存的数据类型为指针,而c语言中,许多数据都可以是指针,比如数组名等,那么,通过对指针数组的运用,我们可以实现一些如类似二维数组的功能

int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 3,4,5,6 };
	int arr3[] = { 6,7,8,9 };

	int* parr[] = { arr1,arr2,arr3 };
	//这里的数组名前面如果加上&会报警告
	int i = 0;
	for (; i < 3; i++)
	{
		int j = 0;
		for (; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
			printf("%d ", *(parr[i] + j));
			//两种方法都可以成功输出
		}
	}
	return 0;
}

注意:通过指针数组实现二维数组的功能,本质不是二维数组(二维数组中的数据是在内存中连续存放的,这里的三个数组不一定)

三、数组指针

数组指针的定义

整形指针:指向整形的指针;字符指针:指向字符的指针;再一次以此类推,数组指针就是指向数组的指针。

int *p1[10];
//p1是指针数组,意为大小为10的数组内存的类型是int*

int (*p2)[10];
//p2是数组指针,即指向数组的指针,每个元素为int类型,大小为10
//注意:[] 的优先级高于*,因此要用()来保证p和*先结合

&数组名,得到数组地址,数组的地址需要一个变量来存,即数组指针就是用来存放数组地址的

即:arr 为 int ,&arr 为 类型* 地址名 [ ]

数组指针前面的int是数组类型,[] 中为数组大小(大小不能漏,否则会有警告)

(注意:数组指针不是二级指针)

数组指针的使用

当数组的大小未定义,而通过初始化自动分配时,数组指针也必须写清楚数组的大小

因为p是指向数组的,所以*p相当于数组名,即数组首元素地址

指针数组在一维数组的使用:

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	int(*parr)[10] = &arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (; i < sz; i++)
	{
		printf("%d ", *(*parr + i));
	}
	return 0;
}

可以用,但是及其别扭(合法,但有病)

数组指针一般不用在一维数组,而是二维甚至三维数组

void print1(int arr[3][5], int x, int y)
//对二维数组的每一行进行打印
{
    int i = 0;
    for (; i < x; i++)
    {
        int j = 0;
        for (; j < y; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

void print2(int(*p)[5], int x, int y)
//此处p指向第一行,p+1指向第二行,即*p相当于拿到一整行
{
    int i = 0;
    for (; i < x; i++)
    {
        int j = 0;
        for (; j < y; j++)
        {
            printf("%d ", *(*(p + i) + j));
            //此处*(p + i)拿到第i + 1行的首元素地址,*(*(p + i) + j)拿到第i + 1行第j + 1个元素的地址
            printf("%d ", p[i][j]);
            //效果同上
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
    print1(arr, 3, 5);//直接传递数组
    print2(arr, 3, 5);//传递数组名,而二位数组的首元素是它的第一行
    //即arr表示第一行的地址
    return 0;
}

这种用法只能说是能用,但还是麻烦,因为直接传递数组时,传递的也是地址,此处并未体现使用指针的好处。

关于数组的几个区别

int arr1[10];//数组

int *arr2[10];//整形指针数组

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

int (*arr4[5])[10];//存放数组指针的数组
//*arr4[10]即名为arr4指针数组,[10]代表指针指向的数组里有5个元素

四、数组参数、指针参数的传递

一维数组传参

//1、数组传参
//假设调用test函数,参数为数组arr,大小为10
//接收格式:
void test(int arr[]);
void test(int arr[10]);
void test(int *arr);

//2、指针数组传参
//假设调用test函数,参数为数组arr,数组内容为int*,大小为10
//即 
int *arr[10] = {0}
//接收格式:
void test(int*arr[10]);
//以数组形式接收
void test(int **arr)
//arr2是指针数组,以二级指针的形式接收

 二维数组传参

//假设调用test函数,传递啊一个arr[3][5]的数组
//接收格式:
void test(arr[3][5]);
void test(arr[][5]);
//对于二维数组传参,只能省略第一个[],第二个不能省
//或者用指针数组的方式接收
//注意:二维数组的地址为第一行的地址,即一个一维数组整个数组的地址,
//不能放在一个一级指针或者二级指针里面,即不能用一级/二级指针接收
void test(int (*arr)[5]);
//此处int (*arr)[5]的arr为数组指针,对应一个每行5个元素的数组

指针传参

指针传参时,是什么指针,形参一般就用什么接收

当函数接收的参数为指针时,只要传过去的参数类型能匹配,就可以传递

形参为二级指针时,可以传递的参数:

**p、&p、指针数组、三级指针解引用

五、函数指针

函数指针的定义

函数指针:指向函数的指针

每个函数都有自己的地址

函数指针的储存格式:返回类型 (*指针名)(参数) = &函数名

以test为例: int (*pf)(int,int) = &test(&可以不写)

函数指针的用法

定义函数指针的时候的星号不能去掉, 否则就不是指针了

直接用指针调用函数(调用时,pf前面的星号可以不写,或者随便写几个(写星号一定要带括号),写一个为标准写法)

通过该方法,可以把一个函数传到另一个函数里进行调用(直接调用也可以,这种情况下调用属于多此一举)

 以下是一个利用函数指针实现的简单计算器

//用函数指针解决代码冗余(回调函数)
void menu()
{
    printf("1.add\n");
    printf("2.sub\n");
    printf("3.mul\n");
    printf("4.div\n");
    printf("0.exit\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;
}

//重新封装一个函数
void calc(int(*pf)(int, int))
//通过函数指针调用函数,能够去掉每个case中的冗余代码
//否则需要在每个case中添加用于输入数字和调用对应函数的代码
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("enter two numbers:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y);
    printf("%d\n", ret);
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    do
    {
        menu();
        scanf("%d", &input);
        int ret = 0;
        switch (input)
        {
        case 1:
            calc(Add);//传的参为需要调用的函数对应的函数指针
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        case 0:
        {
            printf("exit");
            break;
        }
        default:
            printf("error");
            break;
        }
    } while (input);
    return 0;
}

函数指针数组

 既然可以用一个指针指向函数,那么把函数指针放在指针数组中,就是函数指针数组、

一个合理的函数指针数组,可以简化代码,使代码的可读性和美观性更好

int(*arr[4])(int, int) = { Add,Sub,Mul,Div };
    //arr就是函数指针数组
    //只有参数相同、返回类型相同的函数才能放进同一数组

比如,我们可以用函数指针数组来改进上面这段计算器的代码

void menu()
{
    printf("1.add\n");
    printf("2.sub\n");
    printf("3.mul\n");
    printf("4.div\n");
    printf("0.exit\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 (*parr[5])(int, int) = { 0,Add,Sub,Mul,Div };//前面加个0,使内容对应选项
    //又称转移表
    do
    {
        menu();
        scanf("%d", &input);
        int ret = 0;
        if (input == 0)
        {
            printf("exit");
        }
        else if (input >= 1 && input <= 4)
        {
            printf("enter two numbers:");//把输入两个数的选项放进每个case里面,会有很多冗余代码
            scanf("%d %d", &x, &y);
            ret = parr[input](x, y);//同input的值直接调用函数
            printf("%d\n", ret);
        }
        else
        {
            printf("error\n");
        }
    } while (input);
    return 0;
}//利用函数指针数组简化代码

指向函数指针数组的指针

六、回调函数

回调函数的定义

回调函数就是一个通过函数指针调用的函数。如果把一个函数的指针作为参数传递给另一个函数,当这个指针被调用时,其指向的函数就是回调函数。 

示例代码见:函数指针的用法

一点鄙见

        有一说一,作为一个小白,刚开始学指针的时候确实非常懵,。就是不太理解为什么要有这个东西,很多操作,比如调用函数、传递数组等,都是一行代码可以搞定的事情,为什么要多此一举去用指针,而且有时候很难理解代码写的是什么。后来学的东西多了,慢慢发现,好像指针还不错,很多地方可以用指针来简化代码,而且有些东西不用指针是没办法实现的,比如最基础的,在函数里面修改实参,很多人说指针是双刃剑,确实,指针用好了,写出来的代码可以非常优雅,指针用不好写出来的可能就是一个大bug,短短几行代码可以有一万个报错。怎么说呢,一个好的Coder可以被叫做代码诗人,写出来的代码可以像诗一样优美,那我觉得,用好指针应该是成为一名诗人的基础之一,希望我能早日掌握吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值