D 语言 - 指针

D 编程指针学习起来既简单又有趣。 使用指针可以更轻松地执行某些 D 编程任务,而没有指针则无法执行其他 D 编程任务(例如动态内存分配)。 下面显示了一个简单的指针。

D 中的指针

指针不是直接指向变量,而是指向变量的地址。 如您所知,每个变量都是一个内存位置,并且每个内存位置都定义了其地址,可以使用表示内存中地址的与号 (&) 运算符来访问该地址。 考虑以下内容,它打印定义的变量的地址 −

import std.stdio;
 
void main () { 
   int var1; 
   writeln("Address of var1 variable: ",&var1);  
   
   char var2[10]; 
   writeln("Address of var2 variable: ",&var2); 
}

当上面的代码被编译并执行时,会产生以下结果 −

Address of var1 variable: 7FFF52691928 
Address of var2 variable: 7FFF52691930

什么是指针?

指针是一个变量,其值是另一个变量的地址。 与任何变量或常量一样,您必须先声明一个指针,然后才能使用它。 指针变量声明的一般形式是 −

type *var-name;

这里,type是指针的基类型; 它必须是有效的编程类型,并且 var-name 是指针变量的名称。 用于声明指针的星号与用于乘法的星号相同。 然而; 在此语句中,星号用于将变量指定为指针。 以下是有效的指针声明 −

int    *ip;    // pointer to an integer 
double *dp;    // pointer to a double 
float  *fp;    // pointer to a float 
char   *ch     // pointer to character

所有指针的值的实际数据类型,无论是整型、浮点型、字符型还是其他类型,都是相同的,都是表示内存地址的长十六进制数。 不同数据类型的指针之间的唯一区别是指针所指向的变量或常量的数据类型。

在 D 编程中使用指针

当我们非常频繁地使用指针时,几乎没有什么重要的操作。

  • 我们定义一个指针变量

  • 将变量的地址分配给指针

  • 最终访问指针变量中可用地址处的值。

这是通过使用一元运算符 * 来完成的,该运算符返回位于其操作数指定的地址处的变量值。 以下示例利用了这些操作 −

import std.stdio; 

void main () { 
   int var = 20;   // 实际变量声明。 
   int *ip;        // 指针变量
   ip = &var;   // 将 var 的地址存储在指针变量中 
   
   writeln("Value of var variable: ",var); 
   
   writeln("Address stored in ip variable: ",ip); 
   
   writeln("Value of *ip variable: ",*ip); 
}

当上面的代码被编译并执行时,会产生以下结果 −

Value of var variable: 20 
Address stored in ip variable: 7FFF5FB7E930 
Value of *ip variable: 20

null 空指针

如果您没有要分配的确切地址,将指针 NULL 分配给指针变量始终是一个好习惯。 这是在变量声明时完成的。 被赋值为 null 的指针称为 null 指针。

空指针是一个常量,其值为零,在多个标准库(包括 iostream)中定义。 考虑以下程序 −

import std.stdio;

void main () { 
   int  *ptr = null; 
   writeln("The value of ptr is " , ptr) ;  
}

当上面的代码被编译并执行时,会产生以下结果 −

The value of ptr is null

在大多数操作系统上,不允许程序访问地址 0 处的内存,因为该内存已被操作系统保留。 然而; 内存地址0有特殊意义; 它表明该指针无意指向可访问的内存位置。

按照约定,如果指针包含空(零)值,则假定它不指向任何内容。 要检查空指针,可以使用 if 语句,如下所示 −

if(ptr)     // 如果 p 不为 null,则成功
if(!ptr)    // 如果 p 为 null,则成功

因此,如果为所有未使用的指针赋予空值并且避免使用空指针,则可以避免意外误用未初始化的指针。 很多时候,未初始化的变量会保存一些垃圾值,导致程序调试变得困难。

指针运算

可以在指针上使用四种算术运算符:++、--、+ 和 -

为了理解指针算术,让我们考虑一个名为 ptr 的整数指针,它指向地址 1000。假设是 32 位整数,让我们对指针执行以下算术运算 −

ptr++ 

那么ptr将指向位置1004,因为每次增加ptr时,它都会指向下一个整数。 此操作会将指针移动到下一个内存位置,而不影响该内存位置的实际值。

如果ptr指向地址为1000的字符,则上述操作指向位置1001,因为下一个字符将在1001处可用。

指针递增

我们更喜欢在程序中使用指针而不是数组,因为变量指针可以递增,而数组名则不能递增,因为它是常量指针。 以下程序递增变量指针以访问数组的每个后续元素 −

import std.stdio; 
 
const int MAX = 3; 
 
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0];  

   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

当上面的代码被编译并执行时,会产生以下结果 −

Address of var[0] = 18FDBC 
Value of var[0] = 10 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 200

指针与数组

指针和数组密切相关。 然而,指针和数组不能完全互换。 例如,考虑以下程序 −

import std.stdio; 
 
const int MAX = 3;
  
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0]; 
   var.ptr[2]  = 290; 
   ptr[0] = 220;  
   
   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

在上面的程序中,您可以看到 var.ptr[2] 用于设置第二个元素,ptr[0] 用于设置第零个元素。 自增运算符可以与 ptr 一起使用,但不能与 var 一起使用。

当上面的代码被编译并执行时,会产生以下结果 −

Address of var[0] = 18FDBC 
Value of var[0] = 220 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 290

指向指针的指针

指向指针的指针是多重间接寻址或指针链的一种形式。 通常,指针包含变量的地址。 当我们定义一个指向指针的指针时,第一个指针包含第二个指针的地址,第二个指针指向包含实际值的位置,如下所示。

C++ 指针到指针

作为指向指针的指针的变量必须如此声明。 这是通过在其名称前面添加一个额外的星号来完成的。 例如,以下是声明指向 int 类型指针的语法 −

int **var; 

当目标值由指向指针的指针间接指向时,访问该值需要应用星号运算符两次,如下例所示 −

import std.stdio;  

const int MAX = 3;
  
void main () { 
   int var = 3000; 
   writeln("Value of var :" , var); 
   
   int *ptr = &var; 
   writeln("Value available at *ptr :" ,*ptr); 
   
   int **pptr = &ptr; 
   writeln("Value available at **pptr :",**pptr); 
}

当上面的代码被编译并执行时,会产生以下结果 −

Value of var :3000 
Value available at *ptr :3000 
Value available at **pptr :3000

将指针传递给函数

D 允许您将指针传递给函数。 为此,它只需将函数参数声明为指针类型。

以下简单示例将指针传递给函数。

import std.stdio; 
 
void main () { 
   // 具有 5 个元素的 int 数组。
   int balance[5] = [1000, 2, 3, 17, 50]; 
   double avg; 
   
   avg = getAverage( &balance[0], 5 ) ; 
   writeln("Average is :" , avg); 
} 
 
double getAverage(int *arr, int size) { 
   int    i; 
   double avg, sum = 0; 
   
   for (i = 0; i < size; ++i) {
      sum += arr[i]; 
   } 
   
   avg = sum/size; 
   return avg; 
}

当上面的代码一起编译并执行时,会产生以下结果 −

Average is :214.4 

函数返回指针

考虑下面的函数,它使用指针返回 10 个数字,表示第一个数组元素的地址。

import std.stdio;
  
void main () { 
   int *p = getNumber(); 
   
   for ( int i = 0; i < 10; i++ ) { 
      writeln("*(p + " , i , ") : ",*(p + i)); 
   } 
} 
 
int * getNumber( ) { 
   static int r [10]; 
   
   for (int i = 0; i < 10; ++i) {
      r[i] = i; 
   }
   
   return &r[0]; 
}

当上面的代码被编译并执行时,会产生以下结果 −

*(p + 0) : 0 
*(p + 1) : 1 
*(p + 2) : 2 
*(p + 3) : 3 
*(p + 4) : 4 
*(p + 5) : 5 
*(p + 6) : 6 
*(p + 7) : 7 
*(p + 8) : 8 
*(p + 9) : 9

指向数组的指针

数组名是指向数组第一个元素的常量指针。 因此,在声明中 −

double balance[50];

balance是指向&balance[0]的指针,它是数组balance的第一个元素的地址。 因此,以下程序片段将 p 分配给 balance 第一个元素的地址 −

double *p; 
double balance[10]; 
 
p = balance;

使用数组名作为常量指针是合法的,反之亦然。 因此,*(balance + 4) 是访问 Balance[4] 处数据的合法方式。

将第一个元素的地址存储在 p 中后,您可以使用 *p、*(p+1)、*(p+2) 等访问数组元素。 以下示例显示了上面讨论的所有概念 −

import std.stdio;
 
void main () { 
   // 具有 5 个元素的数组。
   double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0]; 
   double *p;  
   
   p = &balance[0]; 
  
   // 输出每个数组元素的值
   writeln("Array values using pointer " ); 
   
   for ( int i = 0; i < 5; i++ ) { 
      writeln( "*(p + ", i, ") : ", *(p + i)); 
   } 
}

当上面的代码被编译并执行时,会产生以下结果 −

Array values using pointer  
*(p + 0) : 1000 
*(p + 1) : 2 
*(p + 2) : 3.4 
*(p + 3) : 17
*(p + 4) : 50