C++中我们可以使用兼容C的指针特性,此外C++还引入了引用的概念,这篇笔记我们介绍指针和引用的用法。
指针是C++中的C兼容语法。指针本身也是一个变量,但它存储的是另一个变量的地址。指针类型使用数据类型和星号定义,例如int *
、char *
等,对一个变量使用取地址符&
即可得到它的指针,对于指针则可以使用解引用操作符*
获取指针指向的值,下面是一个例子。
#include <iostream>
int main() {
int a = 10;
int *p = &a;
// 打印指针p的值
std::cout << p << std::endl;
// 打印p指向的值
std::cout << *p << std::endl;
return 0;
}
代码中,我们尝试打印指针的值,它其实是一个内存地址。
对于32位程序,指针占4字节,对于64位程序,指针占8字节。我们可以使用sizeof
操作观察指针所占的字节数。
#include <iostream>
int main() {
int a = 10;
int *p = &a;
std::cout << sizeof(p) << std::endl;
return 0;
}
空指针是指向地址为0
的指针,通常在指针变量定义但我们暂时还不能给它设置值时,我们习惯将其设置为空(C++中NULL
是0
的宏定义)。0
这个内存地址本身不可能被访问,我们将其设置为0
的目的在于后续用于判断该指针是否已被初始化。
int *p = NULL;
C++中野指针是一种错误,它表示指针指向了非法的内存地址。下面例子中,0x100
是我们随意指定的不合法地址,此时p
就是一个野指针。
int *p = (int *)0x100;
指针和const
组合使用时,根据const
的位置不同可以表达常量指针、指向常量的指针或是指向常量的常量指针,C++初学者对这些情况经常发生混淆,其实它很简单,我们倒着观察即可。
const int *p
倒着观察,p
是一个指针,指向int
类型的常量。
const int a = 1;
const int *p = &a;
int const *p
倒着观察,p
是一个指针,它是常量的(即常量指针),指向int
类型的变量。
int a = 1;
int const *p = &a;
const int const *p
同理,倒着观察可以解释为指向常量的常量指针。
const int a = 1;
const int const *p = &a;
数组名其实可以被视为一个常量指针,我们基于这个指针可以访问数组元素或是遍历数组,下面是一个例子。
#include <iostream>
int main() {
int arr[] = {1, 2, 3};
for (int i = 0; i < std::size(arr); i++) {
std::cout << *(arr + i) << std::endl;
}
return 0;
}
指针作函数参数时,它虽然仍是值传递,但我们传入的是变量的地址,因此变量本身仍可被修改。下面例子中,变量a
经过func()
函数操作后,它的值会被修改为10
。
#include <iostream>
void func(int *a) { *a = 10; }
int main() {
int a = 0;
func(&a);
std::cout << a << std::endl;
return 0;
}
引用和指针有很多容易让初学者混淆的相似点,但它和指针的本质不同。指针是一个内存中确实存在的存储内存地址的变量,引用则是C++语言层面的概念,它是变量的别名。下面例子代码定义了一个变量a
的引用b
。
#include <iostream>
int main() {
int a = 0;
int &b = a;
std::cout << b << std::endl;
// 变量a被改变,使用b访问时得到的也是新的值
a = 1;
std::cout << b << std::endl;
return 0;
}
引用的使用有一些注意事项:
NULL
空指针不同,没有“空”引用的概念。引用作函数参数意味着参数是引用传递的,下面例子中,变量a
引用传递到函数func()
中,函数对其值的修改会反映到函数作用域的外部。
#include <iostream>
void func(int &a) { a = 10; }
int main() {
int a = 0;
func(a);
std::cout << a << std::endl;
return 0;
}
有时我们需要引用传递避免值传递造成拷贝开销,但又不希望函数在函数内修改参数,这可以使用常量引用。常量引用其实就是使用const
修改函数的形参,这种写法主要用于告知编译器该参数在函数内是不可变的,防止代码出现Bug造成误修改。
#include <iostream>
struct Data {
int value;
};
void printData(const Data &data) {
std::cout << "Data value: " << data.value << std::endl;
}
int main() {
Data data;
data.value = 42;
printData(data);
return 0;
}