P1:指针
指针的基本定义
当电脑运行程序时,运行时所产生的数据和所需要的数据都会存在内存里以供cpu使用,而在内存里,我们使用字节(Byte)储存数据,为了保证每一个字节都能被清晰有序的使用,它们都有一个独特的检索方式,被称为地址(address),地址就相当于图书馆里书籍的的号码,可以以此找寻到对应的字节
而指针是一种特殊的变量,它里面存储的数值是一个地址,也就是说,它会直接访问所对应的数据,因此,当在函数的参数里传递一个指针的话,当我们在函数内进行修改时,外面的数据也会改变,而当我们传递数据时,里面的修改不会影响外面,这就是二者的区别
如何声明一个指针
当我们要声明一个指针的时候,只要在变量名的前面加上‘*’即可,如下例
int *a;
char *b;
int **Ptr;
int (*P)[3];
int *ptr[2];
/*
其中,a是一个指向int类型的指针
b是一个指向char类型的指针
Ptr是一个指向了*int,也就是指向了指向int类型的指针的指针
P是一个指向了大小为3的int数组的指针
ptr是一个大小为2的*int数组,也就是相当于int类型的指针的数组
*/
当然,一次定义多个指针也不需要多写几行
int *a,*b,c,*d;
//其中a,b,d为指针,c为变量
需要注意的是指针本身也是数据,所以同样也可以让指针指向指针
空指针
当我们并未对指针变量赋初始值时,它会随机指向一个内存地址,这在程序中极其容易造成一些奇奇怪怪的错误并导致程序难以调试和纠错
因此,我们一般会在声明指针变量时对其进行初始化以防之后出错,而在我们不知道该将其初始化为什么的时候,我们可以用空指针初始化它
int *pa=NULL;
NULL就代表空指针,空指针本身并不代表任何含义,但可以作为‘0’以数的形式输出
cout<<NULL;
//会输出0
注意NULL仅适用于C语言与C++11前的C++,在C++11后,C++使用nullptr来代替了NULL作为空指针
P2:指针的相关运算
引用和解引用
当在使用指针时,我们就难免需要去分别使用指针本身和指针指向的数据,这时,就要用到引用符号‘&’和解引用符号‘*’
引用符号作用于数据,当我们对某个数据使用引用符号时,会返回它的当前地址
int a;
int *pa=&a;
//在这种情况下,&a是a的当前地址,将&a赋值给pa相当于让pa指向a
解引用符号作用于指针,效果是返回当前指针指向的数据
int main(){
int a[3]={2,3,4};
int *pa=&a[0];
printf("%d\n",*pa);
}
//因为pa指向a[0],所以&pa等效于a[0]
顺带一提,当在定义指针的时候,所使用的‘*’并非解引用符号,而是声明指针的意思
加法
是的,指针是可以进行加法运算的,当我们对指针进行加法时,每+1,都会令指针向后移动与指针本身指向的数据类型大小相等的字节数,举个例子
int a=0;
int *pa=&a;
printf("pa is %p.\n", pa);
printf("pa+1 is %p.\n",pa+1);
//pa is 000000d7785ffbfc.
//pa+1 is 000000d7785ffc00.
//在以上程序中,因为int类型占据4个字节,所以pa+1就增加了4
常见的数据类型大小见下表
char | 1 |
int | 4 |
long long | 8 |
float | 4 |
double | 8 |
当然我们也可以用sizeof函数直接看到数据大小
printf("%d",sizeof(int));
//会输出4,也就是int的大小
指针的加法运算常常被用于访问数组的下一个元素
int main(){
int a[3]={2,3,4};
int *pa=&a[0];
printf("%d\n",a[2]);
printf("%d\n",*(pa+2));
//prints out,4,4.
}
/*以上是ppt中的代码,我们可以看到当pa指向a[0]的时候,pa+2就与a[2]等价
而事实上,当我们声明一个数组的时候,实际上数组名(例如以上代码里的a)就是一个指向数组开头元素的指针,这也就是之前讲当我们在传递数组时,在函数内更改数组,外面的数组也会变化的原因,我们更改了指针指向的东西,所以无论是里面的还是外面的指针,指向的数据都是更改后的
EX:指向函数的指针(进阶)
实际上,指针也可以指向函数,一个标准的函数指针示例如下
int (*pt)(int,int);
int calc1(int a,int b){return a+b;}
int calc2(int a,int b){return a*b;}
int main(){
int op;
op=1;
if(op==1){
pt=calc1;
}
else{
pt=calc2;
}
int m=114,n=514;
printf("%d",op(m,n));
}
/*
以上程序定义了一个指向接受两个int参数并返回一个int的函数的指针pt(显然的,如果不在*pt上加括号,会变成返回值是一个int指针)
我们让该指针根据op的值分别指向了a+b与a*b
*/
需要注意的是,该指针的参数和返回值都要与准备指向的函数一致,在此之上
在 C 语言中,诸如
void (*p)() = foo;
、void (*p)() = &foo;
、void (*p)() = *foo;
、void (*p)() = ***foo
等写法的结果是一样的。因为函数(如
foo
)是能够被隐式转换为指向函数的指针的,因此void (*p)() = foo;
的写法能够成立。使用
&
运算符可以取得到对象的地址,这对函数也是成立的,因此void (*p)() = &foo;
的写法仍然成立。对函数指针使用
*
运算符可以取得指针指向的函数,而对于**foo
这样的写法来说,*foo
得到的是foo
这个函数,紧接着又被隐式转换为指向foo
的指针。如此类推,**foo
得到的最终还是指向foo
的函数指针;用户尽可以使用任意多的*
,结果也是一样的。同理,在调用时使用类似
(*p)()
和p()
的语句是一样的,可以省去*
运算符。参考资料:Why do function pointer definitions work with any number of ampersand
故在使用函数指针的时候并不需要特别注意引用符号与解引用符号