C++指针与引用

指针

每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。

    /**
     * 取址符 &
     */
    int  var1;
    char var2[10];

    cout << "var1 变量的地址: ";
    cout << &var1 << endl;

    cout << "var2 变量的地址: ";
    cout << &var2 << endl;
//var1 变量的地址: 0x61ff0c
//var2 变量的地址: 0x61ff02
复制代码

指针定义

指针是变量内存位置的直接地址,指针存储内存地址,即声明的指针只能赋值为地址,指针是一种类型,与某种数据类型绑定,单独使用指针则是内存地址,使用*访问则是变量值。

    int *ip = var1; //error
    cout << ip << endl;
//invalid conversion from 'int' to 'int*' [-fpermissive]
复制代码

通过*访问内存地址的值(访问指针的值)

//指针声明
int *ip = &var1;
// 0x61ff0c
复制代码

如果指针所指向的变量没有初始值则仍是内存地址值

NULL空指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为指针。

    //空指针
    int *ptr = NULL;
    cout << ptr << endl;
    //0
复制代码

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。如果访问*ptr空指针则会使程序崩溃。

指针运算

指针是一个用数值表示的地址。因此可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。无法对空指针进行运算。

指针会根据数据类型移动指针到下一个内存位置

    //指针运算
    int var[3] = {10, 100, 200};
    int *ptr1 = var;
    for (int i = 0; i < 3; i++) {
        cout << "Address of var[" << i << "] = " << ptr1 << endl;
        cout << "Value of var[" << i << "] = " << *ptr1 << endl;
        // 移动到下一个位置
        ptr1++;
    }
/**
Address of var[0] = 0x61fee4
Value of var[0] = 10
Address of var[1] = 0x61fee8
Value of var[1] = 100
Address of var[2] = 0x61feec
Value of var[2] = 200
*/
复制代码

指针与数组

一个数组名对应一个指针常量,因此可以直接用指针指向数组,但通过数组名来移动是非法的。换句话说“数组名是一个指针,指向数组第一个值的地址”

	int var[3] = {10, 100, 200};
    *var = 1;
    cout << var[0] << endl;
    //1
复制代码

可以通过数组名修改元素(var本身没改变),就和使用指针常量一样,但移动非法

指针数组

指针数组就是指针的数组,每个元素是一个指针

    int *ptr2[3];

    for (int i = 0; i < 3; i++) {
        ptr2[i] = &var[i]; // 赋值为整数的地址
    }
    for (int i = 0; i < 3; i++) {
        cout << "Value of var[" << i << "] = ";
        cout << *ptr2[i] << endl;
    }
/*
Value of var[0] = 1
Value of var[1] = 100
Value of var[2] = 200
*/
复制代码

可以用一个指向字符的指针数组来存储一个字符串列表

    const char *names[4] = {
            "Zara Ali",
            "Hina Ali",
            "Nuha Ali",
            "Sara Ali",
    };

    for (int i = 0; i < 4; i++) {
        cout << "Value of names[" << i << "] = ";
        cout << names[i] << endl;
    }
复制代码

多级指针

指向指针的指针,单纯的字面意思,和指针的含义一样,地址,存放指针的地址,值

    //多级指针
    int var3 = 2;
    int *ptr3 = &var3;
    int **pptr = &ptr3;
    cout << *ptr3 << endl << pptr << endl << *pptr << endl << **pptr << endl;
/*
2
0x61feb0 //pptr地址
0x61feb4 //ptr3地址
2 //值
*/
复制代码

指针作为函数参数

指针灵活性方面的代表,可以作为参数传递给函数并且参数值会被改变,因为传进去的就是个内存地址,灵活性非常强。

#include <iostream>
#include <ctime>
using namespace std;
 
// 在写函数时应习惯性的先声明函数,然后在定义函数
void getSeconds(unsigned long *par);
 
int main ()
{
   unsigned long sec;
   getSeconds( &sec );
   // 输出实际值
   cout << "Number of seconds :" << sec << endl;
   return 0;
}
 
void getSeconds(unsigned long *par)
{
   // 获取当前的秒数
   *par = time( NULL );
   return;
}
复制代码

从函数返回指针

还是字面意思,函数可以返回指针,但需要注意,不应该返回局部变量的地址,因为局部变量会在函数返回后被销毁。返回的地址指向一个过期的对象,后面可能发生不可预知的行为。需要将返回的地址定义为static

#include <iostream>
#include <ctime>
#include <cstdlib>
 
using namespace std;
 
// 要生成和返回随机数的函数
int * getRandom( )
{
  static int  r[10];
 
  // 设置种子
  srand( (unsigned)time( NULL ) );
  for (int i = 0; i < 10; ++i)
  {
    r[i] = rand();
    cout << r[i] << endl;
  }
 
  return r;
}
 
// 要调用上面定义函数的主函数
int main ()
{
   // 一个指向整数的指针
   int *p;
 
   p = getRandom();
   for ( int i = 0; i < 10; i++ )
   {
       cout << "*(p + " << i << ") : ";
       cout << *(p + i) << endl;
   }
 
   return 0;
}
复制代码

引用

引用就是变量别名,一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

引用定义

变量名称是变量附属在内存位置中的标签,可以把引用当成是变量附属在内存位置中的第二个标签。因此,可以通过原始变量名称或引用来访问变量的内容,引用必须被初始化,引用可以当做和变量一样使用。

   // 声明简单的变量
   int    i;
   double d;
 
   // 声明引用变量
   int&    r = i;
   double& s = d;
复制代码

引用作为参数

引用作为函数参数,即引用传递,传递变量地址,相当于传递实际的变量地址,而不是值传递(拷贝值)

#include <iostream>
using namespace std;
 
// 函数声明
void swap(int& x, int& y);
 
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
 
// 函数定义
void swap(int& x, int& y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
  
   return;
}
复制代码

引用作为返回值

与指针一样,被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

int& func() {
   int q;
   //! return q; // 在编译时发生错误
   static int x;
   return x;     // 安全,x 在函数作用域外依然是有效的
}
复制代码

指针与引用

指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不 同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。

指针和它存放的地址之间就没有这种限制了。和其他任何变量(只要不是引用)一样, 给指针赋值就是令它存放一个新的地址,从而指向一个新的对象