一、前言
指针是个非常强大的东西,同时很大程度上也是C/C++语言的核心与精髓,如果能彻底搞懂指针,你基本就算入了C/C++语言的大门。
指针的出现,包括引用的出现,本质上就是为了解决程序运行效率的问题,比如1M
字节大小的数据,如果不使用指针或者引用,那么你每次将它传递给函数,都需要将1M
大小的字节全部复制一遍。
而如果使用指针或者引用,那么我们只需要复制4字节、或者8字节即可,也就是这块内存的地址(指针大小取决于你当前是32位程序还是64位程序)。
这样一对比你就会发现,效率提高了不知多少倍!
直接复制:1M=1024kb=1024*1024B
64位程序复制指针:8字节=8B
,相差1024*1024/8=131072
倍
至于引用,本质上也是指针的一种变体,后面我们就会看到它的实现原理了。
二、基本使用
指针本质上就是一块固定大小内存的变量而已,无论你是什么类型的,它的大小都是固定不变的:
#include<iostream>
int main() {
int a;
double b;
char c;
printf("%d,%d,%d",sizeof(&a), sizeof(&b), sizeof(&c));
}
就像上面这样,定义了三个不同类型的变量,然后打印它们的指针大小(通过取址符号&
获取地址,sizeof
运算符获取大小):
因为此时我这里使用的x64
,所以它们的指针大小就会固定为8位,如果你使用的x86
,也就是32位程序,那么指针大小就会固定为4位大小。
而这也就是指针能提升效率的原因:无论什么类型,其指针大小都是不变的。
如果一个结构体或者类占1M
大小,那么通过指针传递,实际上也只需要拷贝指针大小的内存即可,也就是只需要将地址传递过去。
效率大大提升。
而存储地址的变量一般就称为指针:
int *pa=&a;
double *pb=&b;
char *pc=&c;
方式其实就是在类型后面添加一个*
符号而已。
那么此时变量pa
、pb
、pc
里面就存储的是三个变量的地址了,所以如果你直接打印的话,打印的也就是地址:
而如果你想通过地址来得到这个地址上的值,就需要用*
符号(也称解引用):
printf("%d\n", *pa);
printf("%f\n", *pb);
printf("%c\n", *pc);
结果:
这是对于基本类型而言的,如果使用结构体,那一般用的就是箭头->
,实际上就是一个减号与一个大于号的组合。
#include<iostream>
struct Test {
int a;
double b;
char c;
};
int main() {
Test t;
t.a = 0;
t.b = 1;
t.c = 'a';
Test* pt=&t;
printf("%d\n", pt->a);
printf("%f\n", pt->b);
printf("%c\n", pt->c);
}
结果:
这个箭头->
也称为偏移。
因为如果直接使用解引用的话,由于pt是Test这个结构体的类型,那么*pt
得到的就是这个结构体的本体,上面的代码等价于:
printf("%d\n", (*pt).a);
printf("%f\n", (*pt).b);
printf("%c\n", (*pt).c);
由于*pt
等价于t
,所以又等价于:
printf("%d\n", t.a);
printf("%f\n", t.b);
printf("%c\n", t.c);
而箭头偏移的作用就是,取得从变量pt
中地址偏移到指定变量位置的值,比如pt->a
,就是从pt存储的位置,偏移0
,取得4字节,得到a
的值(因为int占4字节,且是第一个属性)。
其它都是类似的。