参考文档:《21天学通C++(第8版)》
C++中对于指针、引用和数组使用时,充斥着 * 、& 、[]符号,对于像我这样的初学者面对这些符号难免会陷入混乱。
当然,C++中对符号 * 、& 、[] 赋予了多重意义也是让人容易混乱的原因。
这篇文章是在《21天学通C++》第8章内容基础上,进行总结和提炼,以方便自己和网友们回顾使用。没有基础的同学,可以参考书籍中第8章内容进行学习。
版本号:C++14
先说说三者的概念:
指针:指针是一个变量,用于存储内存地址。
引用:引用是一个别名,它提供了对变量的另一个名称。一旦引用初始化为一个对象,就不能再改变为指向另一个对象。
数组:数组是一种数据结构,用于存储多个相同类型的元素。
来一个表格总结下三个符号的用法,可以先过目一下,不理解没关系,看完下文回头再看会更清晰:
符号 |
声明 |
运算符 |
* |
声明指针 |
解除引用运算符(获取地址所存储的值) |
& |
声明引用 |
引用运算符(获取变量地址) |
[] |
声明数组 |
数组下标运算符 |
三者在C++中是如何声明的呢:
PointedType * PointerVariableName;
int *pointsToInt = NULL;
在C++中,声明指针有三种方式,且都是合法的,可以根据自己的喜好选择其中一种:
int* p
int *p
int * p
VarType original = Value;
VarType& ReferenceVariable = original;
int original = 30;
int& ref1 = original;
同样引用的声明也有三种方式:
int& ref
int &ref
int & ref
// elements为数组长度
type name[elements];
int myNumbers [5];
分别用三种符号对三者进行了声明。让我们造成困惑的是,这三种符号,在表达式中还可以作为运算符来使用。
在表达式中,这些运算符的使用场景:
&作为运算符还有个场景是 "按位AND" 运算,这里不做介绍
获取变量地址,用指针来存储地址。(引用运算符也叫地址运算符)
// 定义一个变量
int a = 10;
// 定义一个指向int类型的指针,并初始化地址
int* pointer = &a;
*还有个作用是乘法运算符,这里不做介绍
解引用运算符用于访问指针所指向的内存地址中存储的值。(解除引用运算符也叫间接运算符)
int age = 30;
int* pointsToInt = &age;
// 打印指针地址指向的内存中的值(*pointsToInt)
cout << "*pointsToInt = " << dec << *pointsToInt << endl;
将解除引用运算符(*)用于指针时,如果指针未初始化,它所在的内存单元将包含随机值,此时对其解除引用可能会导致程序崩溃或产生意外结果。
用于访问数组中的元素。下标运算符使用方括号 [],并且可以用于一维数组和多维数组。
int arr[5] = {1, 2, 3, 4, 5};
int x = arr[2]; // 获取数组arr中索引为2的元素,即第3个元素
使用new关键字来动态申请内存,如果成功,将返回一个指针,指向分配的内存
delete对动态申请的内存进行释放
Type* Pointer = new Type; // request memory for one element
Type* Pointer = new Type[numElements]; // request memory for numElements
delete Pointer; // release memory allocated above
delete[] Pointer; // release block allocated above
关键字const在指针前修饰,可以对指针指向的地址或地址指向的数据能否修改进行限制。
限制 |
例子 |
允许✔️ |
不允许❌ |
|
常量指针 |
指针本身为常量,指向地址不能修改,但可修改指针指向的数据 |
int daysInMonth = 30; int* const pDaysInMonth = &daysInMonth; |
*pDaysInMonth = 31; |
(int daysInLunarMonth = 28; ) pDaysInMonth = &daysInLunarMonth; |
指向常量的指针 |
指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方 |
int hoursInDay = 24; const int* pointsToInt = &hoursInDay; int monthsInYear = 12; |
pointsToInt = &monthsInYear; |
*pointsToInt = 13; int* newPointer = pointsToInt;(不允许负值给非const变量) |
指向常量的常量指针 |
指针包含的地址以及它指向的值都不能修改(这种组合最严格) |
int hoursInDay = 24; const int* const pHoursInDay = &hoursInDay; |
*pHoursInDay = 25; int daysInMonth = 30; pHoursInDay = &daysInMonth; |
将指针传递给函数时,这些形式的 const 很有用。函数参数应声明为最严格的 const 指针,以确保函数不会修改指针指向的值。这可禁止程序员修改指针及其指向的数据。
数组名是一个指针,指向第一个元素。由于数组变量就是指针,因此也可将用于指针的解除引用运算符(*)用于数组。同样,可将数组下标运算符([])用于指针。
int main()
{
const int ARRAY_LEN = 5;
// 有5个元素的静态数组
int myNumbers[ARRAY_LEN] = {24, -1, 365, -999, 2011};
// 指向数组第一个元素的指针
int* pointToNums = myNumbers;
// 打印数组中的元素
cout << "使用*运算符打印数组" << endl;
for (int index = 0; index < ARRAY_LEN; ++index)
cout << "Element " << index << " = " << *(myNumbers + index) << endl;
cout << "使用[]运算符打印数组" << endl;
for (int index = 0; index < ARRAY_LEN; ++index)
cout << "Element " << index << " = " << pointToNums[index] << endl;
return 0;
}
数组的限制:
数组类似于在固定内存范围内发挥作用的指针。可将数组赋给指针,但不能将指针赋给数组,因为数组是静态的,不能用作左值。
引用是变量的别名。声明引用时,需要将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式。
引用可以使用在函数的参数中,当参数占用内存过大时,形参的复制步骤会有很大开销,引用的使用可以避免这种开销浪费。
ReturnType DoSomething(Type& parameter);
下面解释一下引用传递为何可以避免开销浪费:
C++中有两种参数传递方式:值传递 和 引用传递。
在值传递中,C++会在内存中创建该值的一个拷贝,并将这个拷贝传递给函数。这意味着函数内部对参数的修改不会影响到原始值。如下是值传递的例子:
#include
using namespace std;
void modifyValue(int x) {
x = 10;
}
int main() {
int num = 5;
modifyValue(num);
cout << num; // 输出为5,因为函数内部对参数的修改不会影响原始值
return 0;
}
使用引用传递,可以避免创建拷贝,还可以直接操作原始值。如下是引用传递参数的例子:
#include
using namespace std;
void modifyValue(int &x) {
x = 10;
}
int main() {
int num = 5;
modifyValue(num);
cout << num; // 输出为10,因为函数内部直接修改了原始值
return 0;
}
将const 用于引用,可以限制通过引用修改它指向的变量的值:
int original = 30;
const int& constRef = original;
constRef = 40; // 无法通过编译