指针
1.指针与地址
要了解指针是什么,首先要了解什么是地址。
可以这样理解地址:我们把某个物品放在了某个地方,这个地方的位置就是它的地址。这就好比我们的家庭地址,代表我们住的地方的位置。类比来说,计算机把一个变量所代表的数据放在内存里的某个地方,那个地方就有一个对应的地址,那么这样的位置就是指针。
在C语言中,对于每一个设置的变量,这个变量都有他对应的地址,计算机通过地址来访问数据。在C语言中我们用取地址符&来获取某个变量的地址,可以在printf格式中用%p以十六进制来输出地址。
int a=100;
int b=&a;
printf("%p",b);
这里我们定义了两个变量,用变量b来表示变量a的地址,再用printf以%p输出变量a的地址。
注意:&只能对某个变量取地址,如果&右边不是一个变量,就不能运行。比如不能对a++,a–这类不是变量的东西取地址。
2.指针变量
2.1指针变量的定义
指针变量就是保存地址的变量,它的值就是所指变量的地址。
定义指针变量需要使用*来定义
符号*是一个单目运算符,用来访问指针的值(所表示的地址上的变量)
可以做左值也可以做右值
int a=0;
int *p=&a;
这里我们定义了两个变量,其中的*p就是一个指针变量,这就是一个指向变量a的一个指针,
指针变量p的值就是变量a的地址。
这里要注意,这个*可以靠近p,也可以靠近int,也就是说
int* p;与int *p; 的效果是一样的。
另外,指针变量在使用之前一定要定义和赋值,如果没有赋值就就无法运行。而且给指针变量赋的值只能是地址,我们用符号&来获取了变量a的地址,然后把地址赋值给了指针变量,就完成了指针变量的定义。
可以定义不同类型的指针,而且无论指向什么类型,指针的大小都是一样的(因为都是地址),但为了避免用错指针,指向不同类型的指针是不能互相赋值的。
不要随便碰0地址,指针不应该具有0值,但你可以用0地址表示一些特殊含义,比如:
返回的指针是无效的
指针没有被真正初始化(先初始化为0,这样如果指针没有被真正初始化,指针就具有了0值。)
另外,我们可以用NULL表示0地址
2.2指针的类型转换
指针可以强制类型转换。方法与给变量做强制类型转换一样。
int a;
int *p=&a;
void* q=(void*)p;
这里我们虽然改变了指针p的类型,但变量a仍然是int 类型,给指针做强制类型转换并不会改变所指变量的类型。
3.指针的基本用法
3.1指针变量赋值
定义了指针变量之后,就可以通过指针变量来修改所指变量的值了。
int a=0;
int *p=&a;
*p=1;
这里我们借用上次的例子,在定义了变量a时,我们给a了一个初始值0,我们可以通过给*p赋值,来修改变量a的值。(如上,我们给 *p赋值为1,此时a的值也为1了。
类似的,当定义了指针变量之后,使用 *+指针变量 的格式可以修改所指地址的数据。
3.2用于在函数中做参数
当指针变量作为函数参数时,可以在函数里访问函数外的变量。
void fdd(int *a){
*a=8;
}
int main(){
int a=0;
int *p=&a;
fdd(p);
这里我们定义了一个函数fdd,它的参数是一个指针变量,通过这个指针,我们可以在函数fdd里面访问到函数外面的变量a,这里我们在函数fdd里对指针的值进行了修改,那么函数main中的变量a的值就被修改了。
当函数需要有多个返回值(两个及以上)的时候,就可以使用指针来帮忙了。
void exchange(int *a,int *b){
int c=*a;
*a=*b;
*b=c;
假如我们要定义一个函数用来交换两个变量的值,由于有两个返回值,就要用指针来帮忙了。
这里我们定义了函数exchange,exchange用两个指针变量作为参数,在函数exxhange中通过指针,可以来交换两个变量的值。
4.指针与数组
4.1用指针修改数组内数据
指针也可以指向数组,我们可以通过指针来改写数组里的数据。
int a[100];
int *p=a;
*p=1;
这里我们命名了一个数组a,一个指向a都指针变量p。指针p的初始位置是a[0]。当我们直接使用指针变量p来修改数组a的数据时,实际上修改的是啊a[0]。也就是说,一个数组a与a[0]的地址是同一个地址。
p++;
*p=2;
我们可以直接做p++,也就是指针的自加自减运算,如果做p++,能使指针指向下一位,如果做p–,就可以使指针指向上一位。我们做了p++后,使用指针来赋值时,修改的就是a[1]的数据。
4.2作函数参数的指针与数组
作为函数参数的时候void s(int *a)
与void s(int a[])
是等价的。可以这样去理解,数组变量是特殊的指针,因为,数组变量本身的值就是一个地址(指针),在使用指针变量指向数组是不需要使用取地址符&。(例int a[10]; int *p=a;
)
这里注意,虽然数组变量表达的是地址,但数组单元表达的是变量,因此需要使用取地址符&(举例
int *p=&a[1]
当然,运算符[]既可以对数组做运算也可以对指针做运算。也就是说int a[10];int *p=a;
的时候,p[0]与a[0]是等价的。
如果我们命名的指针变量指向的只是一个变量,而不是数组,我们也可以把指针p看成是一个长度为一的数组。
同理,运算符*既可以对数组做运算,也可以对指针做运算。
我们前面说,数组变量是特殊的指针,是因为数组是一个const的指针,不能对数组赋值。
5.指针与const
5.1指针是const
当指针是const时,表示指针一旦得到了某个变量的地址,就不能再指向其他的变量,即地址已经固定,不能更改了。
int i;
int *const q=&i;//命名了const 的指针
当我们命名了const的指针后,仍然可以通过指针来赋值,但不能做指针的自加自减运算了(因为指针做自加自减运算会改变指针所指的地址)。
5.2所指的是const
这是我们就不能通过指针去修改那个变量的值了,但这并不意味着那个所指的变量就是const。
int i;
const int *p=&i;//命名了所指是const的指针
这里我们命名了所指是const的指针,但我们仍然可以直接给i赋值,比如直接做i=10;
我们也可以修改指针p所指的地址,比如直接做p=&j;
5.3判断const
判断到底是指针不可修改
还是通过指针不可修改,
关键看const与*的位置,
如果const在* 前面,那就是 通过指针不可修改。
如果const在* 后面,就是指针不可修改。
5.4转换
总是可以把一个非const的值转换成const。
void f(const int *p)
这里定义的函数虽然要求要const的指针来作参数,我们也可以直接给他一个不是const的指针。所以这更像是一种保证,在函数f里面保证不会通过指针修改变量的值。
5.5const的数组
数组变量已经是const的指针了,这里const的数组表示数组的每个单元都是const,因此也必须对const的数组进行初始化赋值。。
const int a[]={1,2,3};
因为当数组作为函数参数的时候,传入函数的是地址,所以函数内部可以对函数值进行修改。但如果我们设置参数为const的数组,就可以保护数组的值不被函数破坏。
6.指针的运算
前面我们说过,指针可以通过自加自减运算来改变所指的变量,但如果指针不是指向一片连续分配的空间(比如数组),那这种运算毫无意义。
除此之外,指针与指针之间也可以做相减运算。
int a[10];
int *p=a[0];
int *p1=a[5];
int c=p1-p;
这里我们让指针p1与p相减,得到的值将会是p1与p地址之差,如果将地址之差除以数组a的单元大小,将得到p1与p之间有多少个东西。
当然,还有一种更为常用的指针运算,就是*p++
,它可以取出p所指的那个数据来,完事之后顺便将p移到下一个位置去。
这里有两个运算符,其中*的优先级低于++,但对变量p做p++,其所表示的值是p+1之前的值。
7.用指针动态申请内存
动态申请内存需要先引入头文件<stdlib.h>
,然后使用函数malloc。
#include <stdlib.h>
int* a=(int*)malloc(n);/*(由于函数malloc
的返回值类型是void*,我们还要做强制类型转换。)*/
free(a);/*使用malloc之后一定要用free函数
将动态申请的内存还回去。*/
使用malloc动态分配内存的时候,是以字节为单位的,也就是说n是多少就动态分配多少字节。当然,如果我们要动态分配内存是n个int(每个int是四个字节)的时候就可以用sizeof来计算。
#include <stdlib.h>
int *a=(int*)malloc(n*sizeof(int));
如果申请失败了,malloc函数将返回0,也就是NULL。
申请的动态内存用完之后要用free()函数将申请的内存还回去,否则能用的内存将变少。
注意:只能还申请空间的首地址。地址变了,就不能直接free。由于0地址是不可用来申请的,free(NULL)不会出错。因此我们可以初始定义指针地址为0,这样一旦用free()返还的时候,直接free(NULL)就好了。