C 指针

预计时间: 16分钟

C指针(pointer)是存放内存地址的容器。这里的地址一般指内存中另一个变量的位置。

每个变量都是一个内存位置,每个内存位置都定义了它的地址,可以使用 & 运算符访问它,它表示内存中的一个地址。 考虑以下示例,该示例打印定义的变量的地址 -

#include <stdio.h>

int main () {

   int  var1;
   char var2[10];

   printf("Address of var1 variable: %x\n", &var1  );
   printf("Address of var2 variable: %x\n", &var2  );

   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Address of var1 variable: bff5a400
Address of var2 variable: bff5a3f6

什么是指针?

指针是一个变量,它的值是另一个变量的地址,即内存位置的直接地址。像任何变量或常量一样,您必须在使用指针存储任何变量地址之前声明一个指针。

指针变量

指针变量声明的一般形式是 -

type *var-name;

这里,type是指针的基类型;它必须是有效的 C 数据类型,并且var-name是指针变量的名称。用于声明指针的星号 * 与用于乘法的星号相同。但是,在此语句中,星号用于将变量指定为指针。看看一些有效的指针声明 -

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

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

如何使用指针?

有一些重要的操作,我们会经常借助指针来完成。

  • 我们定义一个指针变量,

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

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

这是通过使用一元运算符*来完成的,该运算符返回位于其操作数指定的地址的变量的值。

  • & 一元操作符,返回操作数的地址。
  • * 一元操作符,返回操作数所指变量的值。

以下示例利用了这些操作 -

#include <stdio.h>

int main () {

   int  var = 20;   /* actual variable declaration */
   int  *ip;        /* pointer variable declaration */

   ip = &var;  /* store address of var in pointer variable*/

   printf("Address of var variable: %x\n", &var  );

   /* address stored in pointer variable */
   printf("Address stored in ip variable: %x\n", ip );

   /* access the value using the pointer */
   printf("Value of *ip variable: %d\n", *ip );

   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20

空指针

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

NULL 指针是一个常量,在几个标准库中定义为零值。考虑以下程序 -

#include <stdio.h>

int main () {

   int  *ptr = NULL;

   printf("The value of ptr is : %x\n", ptr  );
 
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

The value of ptr is 0

在大多数操作系统中,不允许程序访问地址 0 处的内存,因为该内存是由操作系统保留的。但是,内存地址0具有特殊意义;它表示指针不打算指向可访问的内存位置。但按照惯例,如果指针包含空(零)值,则假定它不指向任何内容。

要检查空指针,您可以使用“if”语句,如下所示 -

if(ptr)     /* succeeds if p is not null */
if(!ptr)    /* succeeds if p is null */

指针算术

c 中的指针是一个地址,它是一个数值。因此,您可以像对数值一样对指针执行算术运算。

有四种算术运算符可用于指针:++--+-

为了理解指针运算,让我们认为ptr是一个指向地址 1000 的整数指针。

假设 32 位整数,让我们对指针执行以下算术运算 -

ptr++

经过上述操作后,ptr将指向位置 1004,因为每次 ptr 递增时,它将指向下一个整数位置,即当前位置旁边的 4 个字节。此操作会将指针移动到下一个内存位置,而不会影响内存位置的实际值。如果ptr指向地址为 1000 的字符,则上述操作将指向位置 1001,因为下一个字符将在 1001 处可用。

指针递增

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

#include <stdio.h>

const int MAX = 3;

int main () {

   int  var[] = {10, 100, 200};
   int  i, *ptr;

   /* let us have array address in pointer */
   ptr = var;
	
   for ( i = 0; i < MAX; i++) {

      printf("Address of var[%d] = %x\n", i, ptr );
      printf("Value of var[%d] = %d\n", i, *ptr );

      /* move to the next location */
      ptr++;
   }
	
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Address of var[0] = bf882b30
Value of var[0] = 10
Address of var[1] = bf882b34
Value of var[1] = 100
Address of var[2] = bf882b38
Value of var[2] = 200

指针递减

同样的考虑也适用于递减指针,它通过其数据类型的字节数减少其值,如下所示 -

#include <stdio.h>

const int MAX = 3;

int main () {

   int  var[] = {10, 100, 200};
   int  i, *ptr;

   /* let us have array address in pointer */
   ptr = &var[MAX-1];
	
   for ( i = MAX; i > 0; i--) {

      printf("Address of var[%d] = %x\n", i-1, ptr );
      printf("Value of var[%d] = %d\n", i-1, *ptr );

      /* move to the previous location */
      ptr--;
   }
	
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Address of var[2] = bfedbcd8
Value of var[2] = 200
Address of var[1] = bfedbcd4
Value of var[1] = 100
Address of var[0] = bfedbcd0
Value of var[0] = 10

指针比较

可以使用关系运算符来比较指针,例如 ==<>。如果 p1 和 p2 指向彼此相关的变量,例如同一个数组的元素,那么 p1 和 p2 可以有意义地进行比较。

下面的程序修改了前面的例子 - 只要变量指针指向的地址小于或等于数组最后一个元素的地址,即 &var[MAX - 1],就增加变量指针 -

#include <stdio.h>

const int MAX = 3;

int main () {

   int  var[] = {10, 100, 200};
   int  i, *ptr;

   /* let us have address of the first element in pointer */
   ptr = var;
   i = 0;
	
   while ( ptr <= &var[MAX - 1] ) {

      printf("Address of var[%d] = %x\n", i, ptr );
      printf("Value of var[%d] = %d\n", i, *ptr );

      /* point to the next location */
      ptr++;
      i++;
   }
	
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Address of var[0] = bfdbcb20
Value of var[0] = 10
Address of var[1] = bfdbcb24
Value of var[1] = 100
Address of var[2] = bfdbcb28
Value of var[2] = 200

指针数组

在我们理解指针数组的概念之前,让我们考虑以下示例,它使用 3 个整数的数组 -

#include <stdio.h>
 
const int MAX = 3;
 
int main () {

   int  var[] = {10, 100, 200};
   int i;
 
   for (i = 0; i < MAX; i++) {
      printf("Value of var[%d] = %d\n", i, var[i] );
   }
   
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

可能存在我们想要维护一个数组的情况,该数组可以存储指向 int 或 char 或任何其他可用数据类型的指针。以下是指向整数的指针数组的声明 -

int *ptr[MAX];

它将ptr声明为 MAX 整数指针数组。因此,ptr 中的每个元素都包含一个指向 int 值的指针。以下示例使用三个整数,它们存储在指针数组中,如下所示 -

#include <stdio.h>
 
const int MAX = 3;
 
int main () {

   int  var[] = {10, 100, 200};
   int i, *ptr[MAX];
 
   for ( i = 0; i < MAX; i++) {
      ptr[i] = &var[i]; /* assign the address of integer. */
   }
   
   for ( i = 0; i < MAX; i++) {
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

您还可以使用指向字符的指针数组来存储字符串列表,如下所示 -

#include <stdio.h>
 
const int MAX = 4;
 
int main () {

   char *names[] = {
      "Zara Ali",
      "Hina Ali",
      "Nuha Ali",
      "Sara Ali"
   };
   
   int i = 0;

   for ( i = 0; i < MAX; i++) {
      printf("Value of names[%d] = %s\n", i, names[i] );
   }
   
   return 0;
}

编译并执行上述代码时,会产生以下结果 -

Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali

指向指针的指针

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

作为指向指针的指针的变量必须这样声明。这是通过在其名称前面放置一个额外的星号来完成的。例如,以下声明声明了一个指向 int 类型指针的指针 -

int **var;

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

#include <stdio.h>
 
int main () {

   int  var;
   int  *ptr;
   int  **pptr;

   var = 3000;

   /* take the address of var */
   ptr = &var;

   /* take the address of ptr using address of operator & */
   pptr = &ptr;

   /* take the value using pptr */
   printf("Value of var = %d\n", var );
   printf("Value available at *ptr = %d\n", *ptr );
   printf("Value available at **pptr = %d\n", **pptr);

   return 0;
}

编译并执行上述代码时,会产生以下结果 -

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

指针做为函数参数

指针可以做为参数传递给函数,只需将函数参数声明为指针类型。

以下是一个简单的示例,我们将无符号长指针传递给函数并更改函数内部的值,该值反映在调用函数中 -

#include <stdio.h>
#include <time.h>
 
void getSeconds(unsigned long *par);

int main () {

   unsigned long sec;
   getSeconds( &sec );

   /* print the actual value */
   printf("Number of seconds: %ld\n", sec );

   return 0;
}

void getSeconds(unsigned long *par) {
   /* get the current number of seconds */
   *par = time( NULL );
   return;
}

编译并执行上述代码时,会产生以下结果 -

Number of seconds :1294450468

该函数可以接受一个指针,也可以接受一个数组,如下例所示 -

#include <stdio.h>
 
/* function declaration */
double getAverage(int *arr, int size);
 
int main () {

   /* an int array with 5 elements */
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
 
   /* pass pointer to the array as an argument */
   avg = getAverage( balance, 5 ) ;
 
   /* output the returned value  */
   printf("Average value is: %f\n", avg );
   return 0;
}

double getAverage(int *arr, int size) {

   int  i, sum = 0;       
   double avg;          
 
   for (i = 0; i < size; ++i) {
      sum += arr[i];
   }
 
   avg = (double)sum / size;
   return avg;
}

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

Average value is: 214.40000

函数返回指针

从函数返回指针,需要声明一个返回指针的函数,如下例所示 -

int * myFunction() {
   .
   .
   .
}

不要返回局部变量的地址,局部变量在进入函数时创建并在离开时销毁。可返回静态局部变量的地址。

以下函数,它将生成 10 个随机数并使用表示指针的数组名称返回它们,即第一个数组元素的地址。

#include <stdio.h>
#include <time.h>
 
/* function to generate and return random numbers. */
int * getRandom( ) {

   static int  r[10];
   int i;
 
   /* set the seed */
   srand( (unsigned)time( NULL ) );
	
   for ( i = 0; i < 10; ++i) {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;
}
 
/* main function to call above defined function */
int main () {

   /* a pointer to an int */
   int *p;
   int i;

   p = getRandom();
	
   for ( i = 0; i < 10; i++ ) {
      printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
 
   return 0;
}

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

1523198053
1187214107
1108300978
430494959
1421301276
930971084
123250484
106932140
1604461820
149169022
*(p + [0]) : 1523198053
*(p + [1]) : 1187214107
*(p + [2]) : 1108300978
*(p + [3]) : 430494959
*(p + [4]) : 1421301276
*(p + [5]) : 930971084
*(p + [6]) : 123250484
*(p + [7]) : 106932140
*(p + [8]) : 1604461820
*(p + [9]) : 149169022

函数指针

函数具有可赋给指针的内存地址。一个函数的地址是该函数的进入点。因此可以通过函数指针调用函数。

用函数名称表示函数地址。

动态分配函数

动态分配是程序在运行中取得内存的方法。全局变量是编译时分配的,局部变量使用栈空间,二者都不能在运行中增减。 然而,程序运行中也可能需要数量可变的内存空间。这类程序可通过C的动态分配系统,按需分配存贮空间。

动态分配函数从堆(heap)中取得内存。

动态分配的数组

按一维数据访问动态申请的内存是直观的,但处理多维数组时并不简单。

更新于2022年04月13日