指针变量用于存储内存地址,与所有变量一样,指针变量也占用内存空间

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)的内存的地址

  1. #include <iostream>
  2. using namespace std;
  3. int main(){
  4. int age = 30;
  5. const double Pi = 3.1415;
  6. cout << "Integer age is located at: " << &age << endl;
  7. cout << "double Pi is located at: " << &Pi << endl;
  8. return 0;
  9. }

打印值
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 的,可确保变量的取值在整个生命周期内都固定为初始值。 这种变量的值不能修改,因此不能将其用作左值。

  • 指针变量(包含的地址是)常量,不能修改,但可修改指针指向的数据:
  1. int daysInMonth = 30;
  2. int* const pDaysInMonth = &daysInMonth; // 理解为 int* const pDaysInMonth 指向为整型的常量指针
  3. *pDaysInMonth = 31; // OK! Data pointed to can be changed
  4. int daysInLunarMonth = 28;
  5. pDaysInMonth = &daysInLunarMonth; // Not OK! Cannot change address! 左值 pDaysInMonth
  • 指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方:
  1. int hoursInDay = 24;
  2. const int* pointsToInt = &hoursInDay; //理解为 const int *pointsToInt 指向为整型常量的指针
  3. int monthsInYear = 12;
  4. pointsToInt = &monthsInYear; // OK!
  5. *pointsToInt = 13; // Not OK! Cannot change data being pointed to
  6. int* newPointer = pointsToInt; // Not OK! Cannot assign const to non-const
  • 指针包含的地址以及它指向的值都是常量,不能修改(这种组合严格)
  1. int hoursInDay = 24;
  2. const int* const pHoursInDay = &hoursInDay;
  3. *pHoursInDay = 25; // Not OK! Cannot change data being pointed to
  4. int daysInMonth = 30;
  5. pHoursInDay = &daysInMonth; // Not OK! Cannot change address

数组与指针的类似之处

int myNumbers[5];
编译器将分配固定数量的内存,用于存储 5 个整数;同时向您提供一个指向数组中第一个元素的 指针,而指针由您指定的数组名标识。
换句话说,myNumbers 是一个指针,指向第一个元素 (myNumber[0])

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. int main(){
  5. int my_numbers[5];
  6. int* point_to_nums = my_numbers; //数组名 代表 指向第一个元素的内存地址
  7. cout << "point_to_nums is " << point_to_nums << endl;
  8. cout << "&my_numbers[0] is " << &my_numbers[0] << endl;
  9. return 0;
  10. }

point_to_nums is 0x61fe00
&my_numbers[0] is 0x61fe00

要访问第二个元素:

  1. 可使用 myNumbers[1]
  2. 也可通过指针 pointToNums 来访问,其语法为 *(pointToNums + 1)

要访问静态数组的第三个元素
可使用 myNumbers[2]
而要访问动态数组的第三 个元素
可使用语法*(pointToNums + 2)

将指针传递给函数

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. void CalculateArea(const double* const ptrPi, const double* const ptrRadius,double* const ptrArea)
  5. {
  6. if (ptrPi && ptrRadius && ptrArea){
  7. *ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius);
  8. }
  9. }
  10. int main(){
  11. const double Pi = 3.1415926;
  12. cout << "Enter radius of circle: ";
  13. double radius = 0;
  14. cin >> radius;
  15. double area = 0 ;
  16. CalculateArea(&Pi,&radius,&area);
  17. cout << "area: " << area <<endl;
  18. return 0;
  19. }

但是为了避免出错,建议多用引用代替指针

引用

引用是变量的别名。声明引用时,需要将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式
VarType original = Value;
VarType& ReferenceVariable = original;

引用让您能够访问相应变量所在的内存单元,这使得编写函数时引用很有用
典型的函数声明类似于下面这样:
ReturnType DoSomething(Type parameter);
上述代码导致将 argument实参 的值复制给 Parameter形参,再被函数 DoSomething( )使用。如果 argument 占用了大量内存,这个复制步骤的开销将很大
如果能避免这些复制步骤,让函数直接使用调用者栈中的数据就太好了。为此,可使用引用,对数据本身进行调用
对于深度学习类似的大量的tensor是非常有意义的

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. void GetSquare(int& number)
  5. {
  6. number *= number;
  7. }
  8. int main(){
  9. cout << "Enter a number you wish to square: ";
  10. int number = 0;
  11. cin >> number;
  12. GetSquare(number);
  13. cout << "Square is : " << number <<endl;
  14. return 0;
  15. }

将关键字 const 用于引用

可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字 const
int original = 30;
const int& constRef = original; //左值是一个const的引用申明,右值是被引用对象

按引用向函数传递参数

引用的优点之一是,可避免将形参复制给形参,从而极大地提高性能。然而,让被调用的函数直接使用调用函数栈时,确保被调用函数不能修改调用函数中的变量很重要。为此,可将引用声明为 const 的
使用 const 引用确保被调用的函数不能修改按引用传入的值

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. void GetSquare(int& number)
  5. {
  6. number *= number;
  7. }
  8. int main(){
  9. cout << "Enter a number you wish to square: ";
  10. int number = 0;
  11. cin >> number;
  12. GetSquare(number);
  13. cout << "Square is : " << number <<endl;
  14. return 0;
  15. }

在前一个程序中,使用同一个参数来接受输入和存储结果,但这里使用了两个参数,一个用于接 受输入,另一个用于存储运算结果。为禁止修改传入的值,必须使用关键字 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 数组