ndk学习之C语言基础复习----指针、函数、预处理器
指针:
指针乃C、C++的灵魂之所在,所以有必要好好的复习复习。什么是指针?一句话来概括:“指针是一个变量,它的值是一个地址。”,其中指针变量的声明有如下三种形式:
其中第一种是被推荐的写法。
其中还需要注意:在声明指针时如果未赋值,则是一个野指针【也就是有可能指向了一个不能被使用的地址从而造成程序的错误】,所以在声明时一定要赋值,如下:
那如果想取变量的地址则可以用“&”符,如下:
那如果想获取指针指向变量地址的值则需要用“*”解引用的操作,如下:
下面来看一下p指针占用了几个字节:
需要注意的是:由于目前是在64位系统上运行的,所以是8个字节,如果是在32位运行则长度是4个字。
有了指针之后就可以用它去操纵内存,下面来通过指针的形式来修改变量的值,如下:
指针是可以进行++、--操作的,比如用指针来遍历数组,下面来看下:
其中“*array_p1++”是先取了值,然后再对其指针进行++,如果是写成"*++array_p1",则是先对指针进行加加,然后再取值,最终输出就会漏掉一个,如下:
其中还有一种直接通过数组来进行相加也能达到遍历的目的,如下:
要取其数组的内容则需要解引用:
另外还有一个细节:为啥数组取地址时木有加“&”符号:
这是因为在C中数组名就是数组的首地址,下面来看下:
下面有个概念需要弄清楚:“数组指针”和“指针数组”,这个在面试可能会经常变问到,下面来看下:
其中指向的数组的元素个数为3,如果咱们想要通过数组指针array_p2来获得第二维的55,如何来写呢?
首先肯定得要将数组的指针+1,来定位到第二维的数组,所以array_p2+1,然后再取出它的值则是*(array_p2+1),接着这个值是一个数组,所以还得数组名+1来将指针移到要输出的第二个元素上来,所以此时为*(array_p2+1)+1,最后再解引用取出指针的值,所以整个的式子如:*(*(array_p2+1)+1),下面来验证一下:
接下来更绕的来了,先把代码写出来:
我去!!什么鬼~const char~~char const~~搞死人的节奏~~但是在学C时必须要理解它的,下面一个个来理解,在理解之前,先记着这个原则:“从右往左看 const 修饰谁 谁就不可变”:
意味着不能通过p2来修改tem的值,如下:
因为const是修饰的char,而非p2变量,所以p2的内容可以被更改,如下:
继续来理解下一个:
这个跟上一个效果是一模一样的,为啥?因为const只能修饰char,不能修饰*。
继续看下一个:
还是按照从右往左的原则,const这次修饰的是变量p4,也就是说p4的内容是不允许修改的,如下:
但是可以通过指针修改指向地址的值,如下:
好,继续,下面两个是啥都不能变了,如下:
拿p5举例,既不能修改p5指针的值,如下:
下面再来看一个跟指针相关的东东---多级指针:
解引用则为:
函数:
函数声明:
C中的函数跟Java的方法基本类似,但是在C中的函数需要注意:我们使用的函数必须在之前声明,否则会编译不过,如下:
可以在之前做一个声明既可:
所以一般函数都声明在头文件中,然后一.c文件中头部进行include,这样就如同上面的声明一样了。
函数传参:
- 传值:把参数的值给函数,如下:
也就是说不会改变原有变量的值。
- 传引用:
也就是可以通过指针来修改原值,有了这个特性,那么多级指针就变得非常有意义了,如下:
可变参数:
在Java中我们知道可变参数是由...来弄的,其实在C中也类似,其中我们经常打印的printf()函数就接收一个可变参数,查看一下源码便知:
所以咱们也来弄一个可变参数:
呃~~报错了~~参数中不能只有可变参数,必须要有一个确定参数,所以修改如下:
接着问题来了,如何来取出可变参数的值呢?看下面:
然后接着进行遍历,根据类型:
注意:其确定参数给NULL值是可以的,反正是要有一个,什么类型的都可以,不能没有确参,如下:
函数指针:
定义:指向函数的指针。举个例子:
其中"void (*p) (char*)"就是一个函数指针,void表示该函数无返回值;(char*)表示函数的参数列表,目前只接收一个参数;(*p)表示指向函数的指针。
接着咱们来调用一下:
其实也就相当于Java中的方法回调的意思,另外可以将函数的声明定义成一个typedef,如下:
可以用函数指针模拟HTTP请求,如果成功就执行某个函数,失败则执行某个函数,如下:
预处理器:
预处理器主要是完成文本替换的,常用的预处理器如下:
下面具体来看一下:
- #include:这个就不多说了。
- #if、#elif、#else、#endif:在实际代码编写中会遇到这样的写法,如下:
假如不想要这段代码了,则直接更改条件既可:
适用的场合就是假如写的代码不想要了,则不用注释掉了。
-
#define、#ifdef、#ifndef:这里可以配合#define的宏定义来配合上面的一些条件来使用,如下:
其中定义的宏是可以被取消的,如下:
其中#define宏定义分为两种:宏变量和宏函数,具体如下:
这样在代码中就可以使用I来表示1了,如下:
而在之前说过预处理其实也就是做文本替换用的,所以代码中所有的I就会被预处理器替换为1。
接下来看一下宏函数:此时就可以在代码中进行调用了,如下:
但是宏函数也有陷阱需要注意,看下面这个:
如果修改一下:
期望的结果应该是(1 + 10)* (10 + 10) = 220,但是运行看:
居然变成了:1 + 10 * 10 + 10了,所以需要特别注意,可以加个括号解决:
下面来看一下宏函数有哪些优缺点:
优点:它只是文本替换,使用到宏函数的地方会执行替换,不会有函数调用的开销(将参数压栈,释放栈之类的)。
缺点:1、不会对我们的代码执行检查,不像普通的函数在编写阶段就会给出相印的错误提示。2、假如宏函数是一个非常复杂的函数,那么每个调用它的地方就会完全替换,造成代码冗余使得最终生成的目标文件(如so)增大了,比如:如果代码中调了两次它,如下:
实际上文本替换之后就是:
其实内联函数跟宏函数的执行模式是一样的,也是执行代码替换,但不是一个概念,内联函数在编写时会做检查,另外它里面的代码不能编写过于复杂的代码,如使用了switch、while等复杂控制逻辑,否则会将内联函数降级为普通函数,那何为内联函数呢?其实就是inline关键字,如下:
- #pragma:这个用得较少,在VS中在头文件中会自动有一个如下东东:
它表示该头文件只能被引用一次,其实通用的写法是用它:
其效果都是一样的。