写在前面:本笔记为个人学习各平台C语言系列课程所作,仅供交流学习,不得作他用。
1. 取地址运算


&运算符的作用是把作用变量的地址拿出来。地址一般是用十六进制表示的(0x),输出要求用%p输出:
cpp
int i = 0;
printf("%p",&i);
(1)&只能对变量取地址,不能对表达式取,比如i++、i+p等。
(2)相邻定义的变量在内存里会放在一起,先定义的变量位置更高,后定义的变量位置更低。这是因为内存是堆栈自顶向下分配的,比如int类型的两个相邻变量x和y,x的地址如果是6c,y的地址就是68,相差int类型的4个字节。
(3)32位架构下,int类型的变量与int的内存大小相同(4个字节)。64位架构下,int类型的变量是8个字节,int还是4个字节。
(4)输出数组的地址,会输出数组第一个元素的地址。数组内元素在内存中相邻放置。
2. 指针类型


scanf函数本质上也是取得了定义变量i的地址,然后把给它的值写进这个地址。右边指针类型的变量,就是用于保存地址的变量。int*表示指针类型,一般指针变量都用p定义(point),第二行相当于把i的地址赋值给了p。
一般说p指向了i,表示p变量里的值=i变量的地址。
上图左边后两行,不管*靠int还是p,意思都一样,表示p是一个指针类型的变量,但q是一个普通int类型变量。如果想要p和q都是指针,需要给q前面也加一个*。

指针作为自定义函数参数时,需要在调用时传入变量的地址,而非变量值。一般的自定义函数在传入参数时,传入的都是变量的值而不是变量本身,但这种传入地址的形式使得自定义函数也能访问外部变量。

如何访问这个地址上的变量?利用*。*p作为一个整体,就相当于i这个变量,所以上面的void f()里可以那样写。而且这种写法作为左值时,是可以改变i的值的。

优势scanf()忘记加&时编译不会报错,因为编译器会把输入值当作地址。但由于这样会把输入值写到不正确的地方,运行会报错。
3. 指针应用
(1)交换变量值

(2)函数返回多个值

比如:需要同时找到一个数组里的最大值和最小值。

(3)函数返回运算状态

这种场景多用于运算可能会出错(比如除数为0等)的地方。C语言里只能通过这种指针方式处理,但在C++和java里有异常处理机制。
4. 指针常见错误


上图中,给*p一个明显不可能的值0(效果等同于不给*p赋值),再往其中写入12,这个地址p会指向一个乱七八糟的地方。一定要让指针指向一个明确的变量才行。
5. 为什么数组传进函数后size不对?
数组传入自定义函数时,不论你写的是nums[]其中方括号带不带数字,它都只是个指针,sizeof的作用对象也是指针。完全可以改写成*nums。
但是,依然可以在函数中正常对数组进行操作!赋值什么的都OK。


数组变量就是特殊的指针:



数组实际上是一个const指针,被定义了之后就不能再改变(赋值),所以不能直接让一个数组等于另外一个数组。但是可以让一个数组赋值给另外一个数组的地址。
6. 指针与const
const是一个修饰符,加在变量前面表示这个变量不能被修改。指针是一种变量,碰到const时有两种情况,一是地址(指针本身)是const,二是它指向的那个变量值是const。



左图,指针是const时,可以改变*q,但不能改变q本身,表示q所指的对象永远只能为i。
右图,所指变量是const时,可以改变i和p本身,但是不能通过*p去对i做赋值。因为这种情况实际上是让指针对象为const了,而不是说让i为const,所以i可以变。

const在*前面,表示所指为const。const在*后面,表示指针为const。

传递的参数一般不会比地址大,但在后续自定义结构中,可以定义很复杂的结构。这种方式常用来传递结构。
const数组是什么意思?

当不希望自定义函数改变数组值时,可以采用如下方式保护数组值:

7. 指针运算
(1)加减常数
当指针做加减运算时,是以sizeof为单位进行的。比如char类型的数组,其指针+1地址值就是+1;int类型的数组,指针+1地址是+4。(借助下图理解)


意思是当int *p=a时,*(p+n)=a[n]。

指针不能做乘除,没有意义。
(2)指针加减
两个指针加减得到的结果是地址加减的结果/sizeof类型,比如一个数组num,*p=num,*q=num[5],q-p=5。
(3)*p++


注意:这样操作取出的值还是*p,同时把指针后移,多用于数组连续操作。
这条指令在早期C语言编译器上有一条专门机器指令对应,所以使用这条语句可以加快运行。
(4)指针比较

(5)0地址

进程指的是正在运行的一个程序,是最基本的管理单元,当前计算机基本都是多进程操作系统。对于进程,操作系统会给它一个虚拟的操作空间,对于32位机器,这个空间从0---4g。
NULL(必须全部大写)是预定的符号。
(6)易错点

(7)指针类型转换

(8)指针的作用总结

8. 动态内存分配

malloc函数收到总大小,然后根据该类型一个单元的大小即可知道有几个元素。使用这个函数需要引入头文件stdlib.h。

上图中注释部分是现在的做法。之前C语言使用malloc来实现多个数据读入,注意需要强制类型转换。之后可以正常拿a当数组使用。

在计算机里,内存就是一片连续存在的空间,只是使用者会拿int、double等类型来规范,问系统要空间分配。但空间是有限的,如果空间分配完了,会返回0或者NULL。
下面这段代码可用于测试电脑给程序分配了多大空间:

申请来的运行空间需要还给系统,使用free()函数操作:

注意:申请来的空间指的是使用malloc函数赋值的指针,如果对其进行了加减操作也不行,必须要原来那个。free(NULL)不会报错,所以一般定义指针时会初始化为0。
如果malloc了一直不free,会出问题:
