【基础C语言】四、指针与字符串(下)
四、指针与字符串
&
&可以取出一个地址,而取出的地址的大小是由当前的架构环境所确定的
字节
在64位架构之下,int为4个字节,而其所对应的地址是8个字节
在32位架构下,二者都是4个字节
&取地址
首先它不可以对没有地址的东西取地址,比如&(i+P)
(i+p是变量)
换言之,必须在&的右侧,*存在一个明确的变量,才可以去取得它的地址
对于数组而言,比如有数组a[]
当我们取出地址时,&a,a,a[0]
是相同的
而相邻数组之间的差距一直为4
变量地址
指针
指针地址的变量,就是保存地址的变量
1 | int i ; |
指针变量
变量的值是内存的地址
普通变量的值是实际的值
指针变量的值是具体实际值的变量的地址
作为参数的指针
void f(int *p);
在被调用的时候需要了某个变量的地址
int i = 0 ;
f(&i);
在函数里面可以通过这个指针访问外面这个i
运算符*
*是一个单目运算符,用来访问指针的值所表示的地址上的变量
可以做右值(读)也可以做左值(写)
1 | int k = *p ; |
小结
综上,我们可以使用&来取得所需要的变量的地址,而使用*来访问某个指针所指向的变量
A.
*&a -> *(&a) -> *(a的地址) ->得到地址上的变量 -> a
B.
&*a -> &(*a)-> &(指针所指向的变量) -> 得到a的地址
指针的使用
使用指针交换两个变量的值
1 | void swap(int *pa , int *pb){ |
函数返回多个值
有的时候函数返回一个值是不够的,想要函数返回多个值,某些值就只能通过指针返回
传入的参数实际上就包含需要保存并带回结果的变量
再次以刚才的swap函数,我们传入的两个参数需要互换值,也就说最后需要两个返回值,于是我们就需要两个指针
虽然这些参数是主函数传进去的参数,但是它们作用的结果是把结果“带”出来
函数返回运算的状态,而结果通过指针返回
实际上和前文的意思是相同的
比如常用的情况就是让函数返回不属于有效范围内的值表示出错(比如下标是-1)
但如果返回任何值都是有效值,就无法通过返回值来表示其结果了,于是就需要分开返回
一般的做法是“运算状态”用函数返回,而实际的值通过指针参数来返回
在java或者c++中,可以通过”异常”这个机制来解决这个问题
常见错误
在任何一个地址变量被赋值/得到一个地址 之前,不能通过它(使用*)访问任何变量
指针和数组
传入的数组
实际上,在调用函数的时候,我们所传入的数组就是一个指针,这也是为什么在函数里面使用sizeof
时得到的是4(32位架构)
因为此时所谓的“数组”,其实就是一个指针
事实上,如果我们把原本函数中形参的数组都改成指针,比如int a[] 改为 *a , 并不会影响编译
总言之,函数参数表中的数组实际上就是指针,sizeof(a) == sizeof(int*)
,而它可以用数组的运算符[]进行运算
所以,下面四种函数原型是等价的
1 | int sum(int *ar,int n); |
数组变量是特殊的指针
数组与数组单元
数组变量本身就可以表达地址,所以使用int a[10] ; int*p = a ;
的时候,无需使用&来获取其地址
但是数组内的单元,表达的都是变量,需要使用&来获取地址
另外,a == &a[0]
[]运算符是可以对数组做,亦可以对指针做
p[0]:当做这里有一个数组,它所指向的第一个位置就是所需的值
1 | int *p = &min ; //这里我们假设之前得到了一个最小值min |
同理,*运算符可以对指针做,亦可以对数组做
数组变量是const的指针,也就是说它不能被赋值
指针和所对应的值的const情况(C99)
指针是const
指针是const,换言之,就是指针是固定的
也就是说,该指针,指向了某个位置,这个事实是不能改变的
1 | int *const q = &i ; //q指向了i |
所指的位置是const
表示的是,不能再通过这个指针去修改那个变量
值得注意的是,该操作不代表“那个变量”成为了const
1 | const int *p = &i ; |
简而言之,就是p指针,和它所指向的变量都是可以改变的
但是“通过p指针来修改该变量(*p)”这一方法是不可行的
判断方式
const在前面:它所指的东西不能被修改
1 | const int* p1 = &i ; |
const在后面: 表示指针不能被修改
1 | int *const p3 = &i ; |
const+指针被用于函数
比如void f(const int* x) ;
其表示“在这个函数的范围内,保证int* x 是不会被修改的”
const与数组
比如const int a[] = {1,2,3,4,5,6} ;
实际上所谓“数组变量”就已经是const的指针了
而这里我们加入了const,代表数组内的每个单元都是const int
所以必须且只能通过初始化来赋值
所以说在把数组传到函数里面的时候,如果你不希望函数修改你的数组,则使用const
指针运算
普通加减
对于
1 | char ac[] = {0,1,2,3,4,5,} ; |
得到的结果(示例)是
p = 0xbffbbd5e
p+1 = 0xbffbbd5f
但是如果是
1 | int ai[] = {0,1,2,3,4,5,} ; |
得到的结果却\是
p = 0xbffbbd2c
p+1 = 0xbffbbd30
差值是4
原因是sizeof(char) = 1 ; sizeof(int) = 4
所以
指针上的+1指的是增加一个sizeof()的单位
比如此时 *p代表的是ac[0],那么*(p+1)代表的就是ac[1]
则指针和数组的转换方式为 *(p+n) –> ac[n]
实质上,如果你的指针原先并不是指向了一片连续的空间,那么这种运算是没有意义的
同理,也可以给指针使用+,+=,-,-=,++,–等等
指针之间的运算
两个指针是可以相减的(相加大概率没有实际意义)
结果并不是地址的差,而是(地址差)/sizeof()
也就是说,指针相减,表示的是二者中间有多少”这种类型的东西”
*p++
意义是“取出p所指的那个数据,然后再利用指针++,把p移到下一个位置去”
*的优先级没有++高
这个操作常用于数组类的连续空间操作,而在某些cpu上,这可以直接被翻译成一条汇编指令
比如我们就可以把遍历数组的代码写为
1 | int main(void){ |
指针的比较
进行比较的操作,<,<= , == , > , >= , != 都可以被用于指针的比较
当我们进行指针的比较的时候,比较的是指针在内存中的地址
此外,数组中的单元的地址肯定是线性递增的
0地址
理所当然的,我们的内存中是存在0地址的,但是这个位置通常是一个不能随便用的地址
因此我们的指针不可以具有0值
由此,我们可以利用这个特性,用0地址来表示一些特殊的事情,比如:
1.返回的指针是无效的
2.指针并没有被真正初始化(先初始化为0)
在很多时候,NULL就是一个预定义好的符号,它表示0地址(在C语言的编译器里就是NULL,需要全部大写)
此外,有的编译器不愿意你用0来表示0地址,因此想这么用的时候,尽量用NULL
指针类型与大小
无论指向的是什么类型,所有的指针的大小都是一样的,因为它们本质上都是地址
但是指针存在类型的差别,不同类型的指针是不能相互赋值的
(这是避免用错指针)
不过,如果真的需要的话,是可以进行指针类型转换的
指针类型转换
void*表示不知道指向什么东西的指针,在计算时与char* 相同(但二者并不相通)
指针是具有转换类型的,比如int *p = &i ; void*q = (void*)p ;
这种操作并不会改变p所指向的变量的类型,而是让后面的程序以”不同的眼光”看p所指的变量
比如这个时候,后续的程序就不认为p指向的是int,而是认为它指向了p
(尽量不要使用)
void* : 表示这是一个指针,但不确定它指向的是什么
小结:我们可以用指针来做什么
需要传入一个较大的数据的时候的参数
传入数组后对数组进行操作
函数返回不止一个结果
需要用函数来修改不止一个变量
动态申请内存
动态分配
在C99之前的事
在C99之前,我们不可以使用变量作为数组定义的大小,因此需要手动为它分配好内存int* a = (int*)malloc(n*sizeof(int)) ;
下面我们就尝试一下这件事情
在使用malloc之前,我们需要引入一个全新的头文件stdlib.h>
1 |
|
malloc
来自#include <stdlib.h>
void* malloc(size_t size) ;
向malloc申请的空间的大小是以字节为单位的
返回的结果是void*,需要类型转换为自己需要的类型(int*)malloc(n*sizeof(int))
申请失败时会返回一个0,或者NULL
free()
free是和malloc配套的函数,把申请来的空间重新归还给系统
只能还申请来的空间的首地址,也就是地址改变之后(比如p++,p–)是不可以归还的
必须归还最开始的,申请来的那个地址
为了配合,建议在初始化指针的时候都给它一个0地址,如 void *p = 0;
如此一来,若我们在运行过程中没有malloc这个指针,最终归还的时候也是free(p)也是就free(NULL),不会报错
free(NULL)总是可以的
常见问题
1.申请了不free
在小程序里面当然没有影响,但是越大越重要的程序,在长时间运行中,内存就会逐渐下降
2.free再free
要是之前已经free过了,系统会把这个地址从申请名单中删去,若是再free,就会崩溃
3.free变过的地址
前文已经说过