为啥会突然学这个呢?
因为长链剖分优化 DP 的状态转移用到了指针数组,平时的 STL 使用中也经常碰到指针。
So,就去学了一下,记录一下学习的笔记。我绝对不会告诉你另一个原因是因为最近做DP做累了想来写篇博文水水时间
引入
我们平时用 scanf
输入的时候,都会在变量名前加一个 &
,但是,字符数组除外。这个 &
其实是取地址符,什么是地址呢?(以下为我的理解)你可以把它想象成你家房子的定位,这个就算是一个地址,只不过你家定位指向的是你家房子,而我们程序中的地址指向的是它对应的变量。约等于你住进了一个变量 /dog
这也就引出了指针的意义所在,即是一个指向元素的指向标,但是请注意,指针也是数据,存放这类数据的变量就成为“指针变量”。
我们的 &
符号,则就是取出指向这个元素的指向标(指针)。
运行下面这段代码:
int main() {
int a;
cin >> a;
cout << a << '\n';
return 0;
}
你输入 \(3\),程序就输出 \(3\),这里我们输出的是 \(a\) 这个变量元素。
再运行这段代码:
int main() {
int a;
cin >> a;
cout << &a << '\n';
return 0;
}
你再次输入 \(3\),程序却输出了一个你看不懂的东西 0xe71e7ff82c
,这个就是变量 \(a\) 的存储地址,没错,已经不是元素了,这串十六进制的数字就是变量 \(a\) 的存储地址。
声明与使用
C++ 中,指针的定义实在变量类型的后面加个 *
,例如 int*
,double*
等等,想要获取指针指向的元素,只需要在指针变量前加一个 *
即可。
int main() {
int a;
int* p = &a;
cout << a << endl; // 3
cout << *p << endl; // 3
cout << &a << endl; // 0xe71e7ff82c
cout << p << endl; // 0xe71e7ff82c
}
对结构体变量也是类似。如果要访问指针指向的结构中的成员,需要先对指针进行解引用,再使用 .
成员关系运算符。不过,更推荐使用运算符 ->
这一更简便的写法。
struct ThreeInt {
int a;
int b;
int c;
};
int main() {
ThreeInt x{1, 2, 3}, y{6, 7, 8};
ThreeInt* px = &x;
(*px) = y; // x: {6,7,8}
(*px).a = 4; // x: {4,7,8}
px->b = 5; // x: {4,5,8}
}
当然了,也有指向指针的指针,指针的指针被我们称为二级指针,当然了,也会有三级指针、四级指针……
二级指针是在变量类型后面加 **
,三级指针是在变量类型后面加 ***
。
int main() {
int i = 30;
int *pi = &i;
int **ppi = π
cout << i << endl; // 30
cout << *pi << endl; // 30
cout << **ppi << endl; // 30
}
指针的偏移、操作
数组是一块连续的存储空间。而在 C++ 中,直接使用数组名,得到的是数组的起始地址,因此,我们在 scanf
输入字符数组时不需要加 &
,我们常用 []
运算符来访问数组中某一指定偏移量处的元素。比如 a[3]
或者 p[4]
。这种写法和对指针进行运算后再引用是等价的,即 p[4]
和 *(p + 4)
是等价的两种写法。
STL 中的指针
在我们常用的 STL 容器,像 vector
,set
等,还有一些 STL 函数,像 lower_bound
,upper_bound
,find()
等等,我们都可以见到指针的身影,像 vector
的首指针 begin()
,尾指针 end()
,find()
函数最后返回的类型是指针,等等。
指针定义需要一个关键字 iterator
,定义 vector
的指针如下
vector<int>::iterator it;
下面的代码片段
vector<int> v;
int main() {
v.emplace_back(1);
v.emplace_back(2);
vector<int>::iterator it = v.begin();
cout << *it << '\n';
return 0;
}
最后输出的是 v
的首元素。
当然,C++11 以后,可以直接用 auto
在大多数情况下来代替这个关键词。
有了指针,就可以遍历一些 STL 容器了,像 set
、map
这样我们无法直接用数组下标遍历的容器。
int main() {
set<int> s;
set<int>::iterator it = s.begin();
s.emplace(1);
s.emplace(2);
for (; it != s.end(); ++ it) {
cout << *it << '\n';
}
}
一些指针可以转化通过减去首指针或数组名转化成数组下标,在 vector
中是减去 begin()
,在数组中是减去数组名(常见于一些代码中的 lower_bound
、upper_bound
函数),但是,像 set
就无法实现这个转化(可能是因为你不能直接用数组下标访问 set
中的元素)。