说明:笔记内容是观看翁恺老师的C语言课程视频中记录的,笔记内容有部分来自于原文内容,有部分来自于自身的思考实验。
以下排列顺序以一个视频为一个片段(内容比较多,可能不适合喜欢快餐方式的朋友):
根据测试,目录有时可能没法跳转准确(可能是因为内容太多的原因,可以考虑用左下方侧的目录跳转)
目录
①对指针做加一的运算时,所加的地址长度,等于sizeof(该指针类型)。
一:取地址运算:
运算符&
作用:获得变量的地址。
Ps:操作对象必须是变量。
对于下面输出的两段代码而言,它们的输出结果并不永远相同。
这与计算机的架构与编译器有关(32位架构与64位架构,可能导致上面两段代码输出不同)。
因此,在输出地址时,应该使用%p,而不是将其当成整数输出。
使用&的一些情况:
1、在取地址符&的右边,必须是明确的变量。
例如:a++,++a,a+b,以及纯粹的数字1均无法进行操作(会报错)。(其中a,b均为变量)。
2、对于相邻的变量,地址从高到低排列,每次少一个与下一个变量类型长度相等大小的字节。(不同计算机架构对应的int值不一样)。
情况一:
编译结果:
情况二:
编译结果:
原理:
其中本地变量i与p均存储在一个名为堆栈(stack)的地方,且从上往下堆积。
3、数组的地址为第一个数组单元的地址,且相邻的数组单元的地址差距永远是数组类型的字节长。
情况一:
编译结果:
情况二:
编译结果;
原理:
其中a相当于一个特殊的指针,储存着地址,其地址为数组第一个单元地址,因此可以 不用对数组a进行&来取地址。
二、指针
指针变量
含义:储存地址的变量(即该变量的值是具有实际值的变量的地址)
例如:对于下面一串代码
它的含义表示p指向了储存a这个普通变量的地址。
用法:int *p=&i;
(这里的*是为了说明p是指针变量,后续可以直接用p,其值表示i的地 址,而后续用*p则代表一个变量,即i。)
易错点:
对于这样两行代码,它们的含义都代表定义一个int类型的指针变量p跟一个int类型的普通变量q,与*是否靠近int无关。(不存在int*这种东西)
运算符*
作用:访问那个地址上的变量。
用法:在指针变量前加上*,代表该指针变量存储地址所指向的值。
意义:可通过指针传入外界函数,使得外界函数可对传入的变量进行修改,最终生效(避免了以普通变量作为参数时,传入的只是值,无法进行修改的问题)
&与*的关系
互相反作用
无论是*&a还是&*a,它们的含义都代表着原来的变量a。
疑惑解答:
对于一串代码而言,之所以有时没有报错,是因为编译器将i的值当成了地址,之前提到,在32位架构下的计算机,整数int的值跟地址是一样大的,所以编译器没有报错,但是运行会报错。
三、指针的使用
指针常见的应用场景
应用场景一:(交换两个变量值)
意义:
利用指针作为参数,可以解决传入的只是值而无法对其他函数内的本地变量做修改的问题,从而实现修改其他函数内的本地变量的值。
应用场景二:(函数返回多个值)
- 需要保存多个传入参数在函数内运算结束后的结果
如:
该项代码将传入的min和max的值进行修改,并对结果保存,使其对传入参数的原函数变量的值做改变。(解决了本地变量作用域的问题)
- 需要函数返回运算状态,指针返回运算结果。
如:
该项代码实现了状态跟结果分开处理,更便于处理与阅读。
指针最常见错误
定义了指针变量,在未指向任何变量时便开始使用指针。
错误原因:
在未进行指向时,该指针的值是随机且不确定的,如果此时对其做诸如*p=1的赋值,可能会导致因为试图对不该写的地方写入,从而出错(但不一定每次都会报错,需要注意)
解决方法:(自己认为的)
在需要用到指针时,再去定义,并及时给这个指针指向变量,做好注释。
四、指针与数组
函数参数表里的数组实际上是指针。
例如下面的代码:
运行结果:
可见传入divide函数的数组,变成了指针,即
sizeof(a)==sizeof(int*)
因此,在将数组作为函数参数传入的时候,可以以指针形式传入。
如:
数组变量是特殊的指针
一、[ ]运算符可以对数组做,也可以对指针做,但不可以对普通变量做。
(尝试了一些普通变量,但是编译器出现了报错)
举例解释:
p是一个指针,p[0]则是将p所指的变量当成一个数组,取出第一个整数作为p[0]。
二、*运算符可以对指针做,也可以对数组做,但无法对普通变量做。
(尝试了一些普通变量,但编译器出现了报错)
举例解释:
a是一个数组,也相当于是一个指针,其地址为数组首单元,也就是a[0],因此对a进行*运算,得出的*a等价于a[0](不是a[0]的地址,是a[0]的值)。
- 数组变量是const的指针,所以不能被赋值
(即int a[ ]<= =>int *const a=......)
举例解释:
对于该代码,编译器是可以通过的,但是会出现warning。(最终地址会发生改变)
而对于这个代码,编译器会报错,意味着无法改变其地址。
五、指针与const
本内容仅适用于C99
指针是const
含义:表示一旦得到了某个变量的地址,不能再指向其他变量。
举例:
前者是对指向内容做修改,因此可以通过;后者是对地址做修改,因此会报错。
总结:指针被const后,意味着锁定了地址位置,一直指向该变量地址。
所指是const
含义:表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
举例:
总结:所指被const后,只是无法通过*p的手段进行改值,但是其他方式仍然可以做。
判断哪个被const的标志,在于const在*号前还是后!
(在*前,表示所指不能修改,*后,表示指针不能修改。)
转换:
总是可以把一个非const的值转换为const。
举例:
作用:当传递的参数类型比地址大时,既可以用较少字节的方式传递值给参数,又可避免函数 对外面的变量进行修改。(常见于传结构时)
const数组:
形式:const int a[ ] = {1,2,3,4,5,6,};
含义:指数组的每个单元都是const int。(即无法再做修改)
注意事项:因为是const,所以必须通过初始化进行赋值。
如:
根据试验,不可以通过后续写循环方式更改,如:
因为此时的数组a各单元已经被填入0,无法更改。
用途:在传入数组进函数作为参数时,保护数组值不被更改。
例如:
这样使得该函数保证不更改传入的数组内的各单元的值。
六、指针运算
①对指针做加一的运算时,所加的地址长度,等于sizeof(该指针类型)。
如:
情况一:
其编译结果为:
可见地址加一,并不等于简单的1+1,而是在存储位置移动了类型长度的字节。
但是移动的是谁的类型长度字节?于是有了下面的尝试。
情况二:
其编译结果为:
可见移动的是指针类型的长度字节,且因为与数组类型不匹配的原因,出现了warning。因此最终的*(p+1)与*(q+1)也出现了错误(无论怎么调整加的数值,依然是乱码)。
总结:
- 移动的是sizeof(指针类型)的字节单位,且必须使指针跟变量类型匹配进行移动,否则会warning,甚至最后可能产生严重后果;
- 如果在二者类型匹配的情况下(如情况一),那么*指针= =数组[0],*(指针+1)= =数组[1],以此类推。
②两指针相减,所得的是地址差除以sizeof(指针类型)。
实验一:对不同类型指针相减
编译结果:
显然,不同类型的指针无法相减。(哪怕对应指针与对应数组类型相同,但相减指针类型不同也无法相减,这里没放出来)
实验二:不同类型的数组与指针(但两个相减指针类型相同)
无视warning运行结果:
显然,结果是地址差除以sizeof(指针类型)。(这里还交换了数组跟指针的类型试验,结果依然符合这个)
③*p++
含义:取出p所指数据,结束后移动p到下一位。
说明:*优先级没有++高(因此不是p所指的值加一,而是p指的地址加一)
作用:常用于数组类的连续空间操作(例如遍历)
④指针比较
指针比较实际上是地址的大小比较。
⑤0地址
含义:通常是不能随便碰的地址——>指针不应该具有零值
用法:1、使返回的指针无效;
2、指针没有背真正初始化(先初始化为0);
其他表示方法:NULL表示0地址(预定定义的符号)——>有些编译器不接受用0表示0地址。
⑥指针的类型转换
方法:同普通变量一样,在需要转换的指针变量前打(转换类型),不过需要*,如
(没有改变指针所指变量类型,只是改变看待的方式。)
意义:因为不同类型指针最好不要做相互赋值,因此,将其转换为相同类型的指针在进行赋值运算。
⑦void*
含义:表示不知道指向什么东西的指针
计算方式:与char*相同(但不相通)
七、动态内存分配
问题:在已知个数,再进行输入时应该如何定义数组大小
malloc函数(动态内存分配)
形式:void* malloc(size_t size);
用法:——>这里可以将指针当成数组形式利用(具体参考指针与数组的关系内容)
头文件:stdlib.h(用法:#include<stdlib.h>)
返回类型:void*——>需要自己转换类型。(申请失败返回0,或者叫NULL)
申请空间所用大小:以字节为单位
free( )
作用:将申请来的空间还给“系统”
特殊要求:1、只能还申请来的空间首地址;
2、free只需一次,多了会导致系统崩溃。
小技巧:
free(NULL);——>配合定义指针就初始化为0的好习惯
作用:使得malloc申请失败后,或者没有申请时,可以正常运行。