指针变量用于存储内存地址,与所有变量一样,指针变量也占用内存空间
int const p;
p是一个指针,这个指针指向const的int。
但很多人觉得这种“从右往左”的顺序反人类,所以主张写成这样:
const int p;
即左边类型,右边变量名。
这样写编译器也是接受的,语义相同,因为const的位置对编译器来说比较灵活。
C 语言声明指针的时候 int p 到底是什么意思?
很不幸的是,C 的声明是解方程,最「正确」的理解是
「声明一个变量 p,它的类型必须满足『p 属于 int』」
通常将指针声明为指向特定的类型,如 int,这意味着指针(内存地址)指向的内存单元存储了一个整数
PointedType * PointerVariableName;
也可将指针声明为指向一个内存块,这种指针被称为 void 指针
void * PointerVariableName;
指针的初始化,否则它包含的将是垃圾值。对指针来说,这种垃圾值非常危险,因为指针包含的值被视为地址
PointedType * PointerVariableName = NULL;
使用引用运算符(&)获取变量的地址
如果 varName 是一个变量,&varName 将是存储该变量的内存的地址。
int age = 30;
&age 将是存储该变量的值(30)的内存的地址
#include <iostream>
using namespace std;
int main(){
int age = 30;
const double Pi = 3.1415;
cout << "Integer age is located at: " << &age << endl;
cout << "double Pi is located at: " << &Pi << endl;
return 0;
}
打印值
Integer age is located at: 0x61fe1c
double Pi is located at: 0x61fe10
使用指针存储地址
假设您声明了一个某种类型的变量:
// Declaring a variable Type VariableName = InitialValue;
要将该变量的地址存储到一个指针中,需要声明一个同样类型的指针,并使用引用运算符(&) 将其初始化为该变量的地址:
// Declaring a pointer to Type and initializing to address Type Pointer = &Variable;
例子
int age = 30;
int age_point = & age
使用解除引用运算符(*)访问指向的数据
int age = 30;
int age_point = & age
age_point即为 age 右值
将 sizeof( )用于指针的结果
输出表明,虽然 sizeof(char)为 1 字节,而 sizeof(double)为 8 字节,但 sizeof(char)和 sizeof(double) 都是 4 字节。这是因为不管指针指向的内存单元是 1 字节还是 8 字节,存储指针所需的内存量都相同。
将递增和递减运算符(++和−−)用于指针的结果
int 指针包含 例如 0x002EFB34—int 在内存中的地址。int 本身长 4 字节,因此占用 0x002EFB34~0x002EFB37 的内存。如果您对指针执行递增或递减运算,编译器将认为您要指向内存块中相邻的值(并假定这个值的 类型与前一个值相同),而不是相邻的字节(除非值的长度刚好是 1 字节,如 char)。对其执行递增运算将导致它增加4字节, 即sizeof(int)。 将++用于该指针相当于告诉编译器,您希望它指向下一个 int,因此递增后该指针将指向0x002EFB38
Type* pType = Address;
++pType;
pType 将包含(指向)Address + sizeof(Type)。
将关键字 const 用于指针
通过将变量声明为 const 的,可确保变量的取值在整个生命周期内都固定为初始值。 这种变量的值不能修改,因此不能将其用作左值。
- 指针变量(包含的地址是)常量,不能修改,但可修改指针指向的数据:
int daysInMonth = 30;
int* const pDaysInMonth = &daysInMonth; // 理解为 int* const pDaysInMonth 指向为整型的常量指针
*pDaysInMonth = 31; // OK! Data pointed to can be changed
int daysInLunarMonth = 28;
pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address! 左值 pDaysInMonth
- 指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方:
int hoursInDay = 24;
const int* pointsToInt = &hoursInDay; //理解为 const int *pointsToInt 指向为整型常量的指针
int monthsInYear = 12;
pointsToInt = &monthsInYear; // OK!
*pointsToInt = 13; // Not OK! Cannot change data being pointed to
int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
- 指针包含的地址以及它指向的值都是常量,不能修改(这种组合严格)
int hoursInDay = 24;
const int* const pHoursInDay = &hoursInDay;
*pHoursInDay = 25; // Not OK! Cannot change data being pointed to
int daysInMonth = 30;
pHoursInDay = &daysInMonth; // Not OK! Cannot change address
数组与指针的类似之处
int myNumbers[5];
编译器将分配固定数量的内存,用于存储 5 个整数;同时向您提供一个指向数组中第一个元素的 指针,而指针由您指定的数组名标识。
换句话说,myNumbers 是一个指针,指向第一个元素 (myNumber[0])
#include <iostream>
#include <string>
using namespace std;
int main(){
int my_numbers[5];
int* point_to_nums = my_numbers; //数组名 代表 指向第一个元素的内存地址
cout << "point_to_nums is " << point_to_nums << endl;
cout << "&my_numbers[0] is " << &my_numbers[0] << endl;
return 0;
}
point_to_nums is 0x61fe00
&my_numbers[0] is 0x61fe00
要访问第二个元素:
- 可使用 myNumbers[1]
- 也可通过指针 pointToNums 来访问,其语法为 *(pointToNums + 1)
要访问静态数组的第三个元素
可使用 myNumbers[2]
而要访问动态数组的第三 个元素
可使用语法*(pointToNums + 2)
将指针传递给函数
#include <iostream>
#include <string>
using namespace std;
void CalculateArea(const double* const ptrPi, const double* const ptrRadius,double* const ptrArea)
{
if (ptrPi && ptrRadius && ptrArea){
*ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius);
}
}
int main(){
const double Pi = 3.1415926;
cout << "Enter radius of circle: ";
double radius = 0;
cin >> radius;
double area = 0 ;
CalculateArea(&Pi,&radius,&area);
cout << "area: " << area <<endl;
return 0;
}
但是为了避免出错,建议多用引用代替指针
引用
引用是变量的别名。声明引用时,需要将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式
VarType original = Value;
VarType& ReferenceVariable = original;
引用让您能够访问相应变量所在的内存单元,这使得编写函数时引用很有用
典型的函数声明类似于下面这样:
ReturnType DoSomething(Type parameter);
上述代码导致将 argument实参 的值复制给 Parameter形参,再被函数 DoSomething( )使用。如果 argument 占用了大量内存,这个复制步骤的开销将很大
如果能避免这些复制步骤,让函数直接使用调用者栈中的数据就太好了。为此,可使用引用,对数据本身进行调用
对于深度学习类似的大量的tensor是非常有意义的
#include <iostream>
#include <string>
using namespace std;
void GetSquare(int& number)
{
number *= number;
}
int main(){
cout << "Enter a number you wish to square: ";
int number = 0;
cin >> number;
GetSquare(number);
cout << "Square is : " << number <<endl;
return 0;
}
将关键字 const 用于引用
可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字 const
int original = 30;
const int& constRef = original; //左值是一个const的引用申明,右值是被引用对象
按引用向函数传递参数
引用的优点之一是,可避免将形参复制给形参,从而极大地提高性能。然而,让被调用的函数直接使用调用函数栈时,确保被调用函数不能修改调用函数中的变量很重要。为此,可将引用声明为 const 的
使用 const 引用确保被调用的函数不能修改按引用传入的值
#include <iostream>
#include <string>
using namespace std;
void GetSquare(int& number)
{
number *= number;
}
int main(){
cout << "Enter a number you wish to square: ";
int number = 0;
cin >> number;
GetSquare(number);
cout << "Square is : " << number <<endl;
return 0;
}
在前一个程序中,使用同一个参数来接受输入和存储结果,但这里使用了两个参数,一个用于接 受输入,另一个用于存储运算结果。为禁止修改传入的值,必须使用关键字 const 将其声明为 const 引 用,如第 3 行所示。这让 number 自动变为输入参数—其值不能修改的参数。
您可以尝试修改第 5 行
number *= number;
这将导致编译错误,指出 const 值不能修改。这说明 const 引用将参数标识为输入参数,并禁止对 其进行修改。乍一看,这可能微不足道,但在多名程序员合作编程时,编写第一个版本的人和改进的人可能不同,通过使用 const 引用可提高编程质量。
多用引用少用指针
void CalculateArea (const double const ptrRadius, double const ptrArea);
void CalculateArea (const double& radius, double& area);
使用引用的版本更好,因为引用不可能无效
而指针可能无效。另外,第二个版本也更简单
为何要按引用向函数传递值?
可以不这样做,只要对程序性能影响不大。然而,如果函数接受非常大的对象,则按值传递的开销将非常大,通过使用引用,可极大地提高函数调用的效率。别忘了将 const 用于引用参数,除非 函数需要将结果存储在参数中
数组
下面的两个声明有何不同
int myNumbers[100];
int myArrays[100];
myNumbers 是一个 int 数组,它指向这样的内存单元的开头,即其中存储了 100 个整数。
它是静态的,可替换如下代码:
int myNumbers = new int [100]; // dynamically allocated array
// use myNumbers
delete[] myNumbers;
而 myArrays 是一个包含 100 个元素的指针数组,其中的每个指针都可指向 int 或 int 数组