基本概念
什么是指针?
我们把某个变量的地址称为“指向该变量的指针”。我们在写程序的时候怎么获得该变量的地址呢?很简单,只需要在变量的前面添加一个取地址运算符&就可以了:
int a = 10;
printf("变量a在内存空间当中的存储地址是:%p\\n",&a);
这样我们就能在控制台中打印输出变量a在内存空间中的地址,也就是指向变量a的指针了。
正如我们习惯于用一个变量来存储数值一样,我们也用“指针变量”来存储指针:
int a = 10;
int* p;
p = &a;
printf(" 指向变量a的指针: %p\\n",&a);
printf("指针变量p中存储的指针: %p\\n", p);
当指针变量p当中存储了变量a的地址(指向a的指针)后,我们称指针变量p指向了变量a。
那么当我们拿到一个指针变量的时候,怎么才能获得其指向的内容,也就是变量a呢,很简单,我们只需要在指针变量前添加一个运算符"*"即可:
int a = 10;
int* p;
p = &a;
printf(" 指向变量a的指针: %p\\n",&a);
printf("指针变量p中存储的指针: %p\\n", p);
printf(" 变量a: %d\\n",a);
printf("指针变量p指向的内容是: %d\\n", *p);
这里我们需要注意的是,用于取得指针变量所指向的内容的运算符""与声明指针变量时用的符号""虽然一样,但意义完全不同。声明指针变量时使用"*"只是用来说明声明的这个变量是一个指针变量。
指针变量,它也是一个变量,所以它自己也是有地址的,换句话说,指针变量也有指向它的指针变量。
int a = 10;
int* p;
p = &a;
printf(" 指向变量a的指针: %p\\n",&a);
printf(" 指针变量p中存储的指针: %p\\n", p);
printf(" 变量a: %d\\n",a);
printf(" 指针变量p指向的内容是: %d\\n", *p);
printf(" 指向指针变量p的指针:%p\\n",&p);
printf("指向指针变量p的指针所指内容:%p\\n",*(&p));
printf("被指针所指指针变量p所指内容:%d\\n",*(*(&p)));
有点晕,但是尝试着写一下的话,也就不会晕了。
指针变量的自增运算
指针变量里面存储的是变量的地址,笔者前面的文章写到过,地址使用数字来标识的,就像门牌号一样。那么,作为数字,又是变量,也就说明能够进行运算了,这里只讨论自增运算,目的只有一个:指针变量的运算并不只是单纯的数字运算。明白了这一点后,其他的运算也就能举一反三了。
int a[3] = {1,2,3};
int* p = &a[0];
printf(" p中存储的指针是:%d\\n",p);
printf("指针p所指向的内容是:%d\\n",*p);
printf("自增后p中的指针是:%d\\n",++p);
printf("自增后p指向的内容是:%d\\n", *p);
我们可以直观地看到,指针变量的值增加了4,而不是1,所以指针变量指向了这一片内存地址中所存储的下一个整数,而笔者前面的文章页写过整型变量占有四个字节,也就是四个内存地址。
char a[3] = {'a','b','c'};
char* p = &a[0];
printf(" p中存储的指针是:%d\\n",p);
printf("指针p所指向的内容是:%c\\n",*p);
printf("自增后p中的指针是:%d\\n",++p);
printf("自增后p指向的内容是:%c\\n", *p);
而在这个例子中,指针变量指向的是char类型的变量,所以在增加一的时候确实只增加一,因为char类型的变量所占用的内存空间是一个字节。
由此,我们得出结论,指针变量在进行运算的时候值的改变根据其类型而有所不同。
指针与数组
先看一个例子:
int a[3] = {1,2,3,4};
printf("%p\\n",a);
printf("%p\\n",&a[0]);
printf("%p\\n",&a);
printf("%d\\n",a[0]);
printf("%d\\n",*a);
我们发现,数组的名字就是数组的第一个元素的地址,换句话说,数组名是指向数组第一个元素的指针。
但是,我们还发现了一个问题,那就是a和&a打印出来的东西是相同的,真的是相同的吗?我们可以用代码来验证一下:
int a[3] = {1,2,3};
printf("%d\\n",a);
printf("%d\\n",&a);
printf("%d\\n",a+1);
printf("%d\\n",(&a)+1);
我们发现,在给a,加一的时候,a指向了数组第二个元素的地址,而在给&a加一的时候,&a指向了存储在数组之外的东西,也就是成了数组之外元素的地址。由此我们得到结论:a是指向数组首元素的指针,而&a则是指向整个数组的指针。
那么二维数组呢?依此类推,用相似的方法写出代码即可验证。
指针变量作函数的参数
先来看一个经典的例子:
void exchange(int a, int b){
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
exchange(a, b);
printf("a的值是:%d, b的值是:%d\\n", a, b);
}
我们发现a与b的值并没有发生交换。因为函数在传参数的时候,传入的是变量的副本,然后你在函数中不论对副本作什么改变,都不会影响到其本身的值。
那如果传入指向变量的指针呢:
void exchange(int* a, int* b){
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a = 10;
int b = 20;
exchange(&a, &b);
printf("a的值是:%d, b的值是:%d\\n", a, b);
}
可以看到,a,b的值相互交换了。为什么呢?因为传入的是指针的副本,所以副本依然是分别指向这两个变量的指针,所以在通过运算符来获取这两个变量的值的时候,获取到的就是原来的那两个变量,当用运算符获得原来的变量再通过赋值运算符来给两变量赋值的时候,改变的就是原来的变量的值。
const关键字
int a = 10;
int b = 20;
const int *p;
p = &a;
*p = 20;
我们会发现,在给*p赋值的时候编译器会报错
因为const int *意为定义一个指向整型常量的指针,而int const *与其意义相同,不过这种写法不被推荐。而int * const的意义则是定义一个指向整型的指针常量:
int a = 10;
int * const p;
p = &a;
既然是常量,在定义的时候就应该被赋值,然后在接下来的程序中就不能再改变指针常量中所存储的指针的值,但是指针常量所指向的整型变量的值还是可以被改变的
int a = 10;
int b = 20;
int * const p = &a;
*p = b;
printf("a的值为: %d\\n", a);
如果再加一个const呢?
int a = 10;
int b = 20;
const int * const p = &a;
*p = b;
编译器会再度报错:
由此我们验证了const int * const的含义:一个指向整型常量的指针常量
以上就是对C语言指针的一个基本的了解,光看别人写出来的总结是不够的,如果我们想要真正的理解并且达到完全不会被绕晕的状态,还是需要多多实践,多写多用。