【c进阶】指针进阶(为零基础学者提供的详细讲解)

目录

1,字符指针(char*)

2,指针数组

3,数组指针

4,数组参数,指针参数

4.1二维数组传参

4.2二级指针传参

5,函数指针

6, 函数指针数组

7,回调函数

                  【重量级】指针进阶的经典试题


大家好,博主今天来给大家讲解进阶指针,希望喜欢的朋友三连一下哦!

1,字符指针(char*)

字符指针有两种使用方法:

1,

2,

注意:这里是将字符串的首个字符的地址赋给了pstr.

3,常量字符串

在2例中,"hello bit."是常量字符串,创建在常量区(只读存储器),无法修改。

我们发现程序崩了,没有打印任何东西。

然而如果没有去强行修改(这里只是这么说,实际上我们无法修改),程序是会正常运行的,

但是如果用一个数组存储字符串的话,这里的字符串存储在栈区,是可以修改的。

为了防止错误,最好使用const修饰,如:

这样就会起到提醒自己的作用。

还有一个方法证明:

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域(常量区),当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块(栈区)。所以str1和str2不同,str3和str4不同。

2,指针数组

即存放指针的数组

要重点和数组指针区分开

int* arr[5]----指针数组(arr先和[]结合)

int  (*arr)[5]------数组指针(arr先和*j结合)

3,数组指针

数组指针是一种指向数组的指针。

使用:

 数组指针常用于二维数组,如:

数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行

4,数组参数,指针参数

4.1二维数组传参

以上是以数组形式设置形参,以下是以指针形式设置形参:

 

 运行证明,在例子中,只有int (*arr)[5]可以作为二维数组的形参,是一个数组指针。

4.2二级指针传参

当函数的参数为二级指针的时候,可以接收什么参数?

 可以接受一个普通的二级指针,也可以接受一个指针数组的数组名,这个数组名就相当于一个二级指针。 

5,函数指针

首先看一段代码:

 输出的地址相同,这说明  &函数名  和  函数名本身  都可以表示函数的地址。

那函数的地址怎么存储呢?

比如这个函数

void test()
{
printf("hehe\n");

}

要用void (*pfun1)();来存储。

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。

注:括号代表着函数。

6, 函数指针数组

如何定义?

int (*parr1[10])();

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。

函数指针数组的应用(简化代码),例如:

我们要写一个简单的计算器:

 但是我们发现这个代码有个问题,就是非常的冗余,比如:在switch中,我们反复写了好几个printf();

为什么会这样呢?

因为这个代码的加减乘除计算器的实现各不相同,所以我们必须写出四种情况,每种情况又不得不

写一些必要的代码:   printf();  scanf();

经过分析,要解决冗余问题,需要一个能够将上述代码不同之处(四种计算的实现)统一起来的结构,这个时候就需要函数指针数组。

 这样就可以将四种计算用一个函数指针数组统一起来,简洁许多。

7,回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

那么,回调函数有什么用呢?

比如:在6.函数指针数组中,我们尝试用函数指针数组简化了一个计算器代码,而回调函数同样可以:

这样写就可以做到:传哪个函数的地址就调用哪个函数,当然要保证函数的返回值和参数类型相同。

再比如:使用qsort();函数时需要用到回调函数

嗯,那么这里需要介绍一下qsort();函数

 如上,qsort();是用于进行快速排序的,返回值为void,参数有四个:

1,void*base

它代表着目标数组的首个元素的地址,那为什么是void*呢,因为我们无法预测数组元素的类型,所以我们不能写成任意一个确定的类型,必须要写成可以接受任何类型指针的void*.但是void*不是确切类型,所以不能进行解引用和加减整数。在使用时,需要将其强制转换为具体类型的指针。

2,size_t  num

 size_t  num其实是unsigned int类型,代表着数组元素的个数。

3,size_t  width

 width代表着数组中每个元素的字节大小

4,int (__cdecl *compare )(const void *elem1, const void *elem2 )

这位更是重量级,它是一个函数指针,而它指向的函数,则需要qsort();的使用者来进行设计

 如上,此函数的返回值是int类型,共有三种情况:大于0,小于0,等于0。而函数则需要根据这三种情况来排序。

函数的参数是两个void*指针,并且由const修饰,此处用void*的原因同上,都是因为不知道数组元素的具体类型。

接下来,上实例:

这样就可以实现排序,可以再试试排列其他的元素。

 如上,结构体的成员也可以很好的排序。

                  【重量级】指针进阶的经典试题

首先,我们先总结一些知识点:

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

4,对于二维数组,首个元素就是第一行,是一个一维数组,首个元素的地址就是第一行的地址。

5,对于看不懂的题目,可以用arr[i]==*(arr+i)来转换。

6,如果往一个字符数组里存储字符串,会自带‘\0’    ,   但是如果是一个一个的存储字符,就没有‘\0’,如下图

 

 7,对于二维数组,比如arr[3][5]

arr 是整个二维数组的数组名,是首个元素的地址,也是第一行的地址,相当于(arr+0)

arr[0]相当于第一行的数组名,是个一维数组的数组名,相当于第一行的首元素的地址,即*(arr+0)

8.最重要的就是不要马虎,要细致,要熟知内存如何访问,运算符的优先级

1,

分析:&a拿到的是整个数组的地址,其实就相当于int (*p)[4],  解引用访问的是整个数组,所以打印的结果是整个数组的字节大小,即16.

 另外:sizeof;的返回类型是size_t,其实就是unsigned long long ,所以要用%llu来打印。

2,

此题结果是16。

 为什么呢?难道不会越界访问吗?

其实在运算符(操作符)中我们就提到过(不知道的可以去看,就在我的主页上),sizeof括号内部的内容是不会真正被执行的,所以我们根本就没有访问数组,何谈越界访问?只是看一下大小而已。

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值