C 语言编程 — 高级数据类型 — 指针

目录

前文列表

程序编译流程与 GCC 编译器
C 语言编程 — 基本语法
C 语言编程 — 基本数据类型
C 语言编程 — 变量与常量
C 语言编程 — 运算符
C 语言编程 — 逻辑控制语句
C 语言编程 — 函数

指针

指令是 C/C++ 编程最大的特色,通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。将指针和其他数据类型进行结合会产生非常有趣的化学反应:

  • int i整型变量 i
  • int *p整型指针变量 p
  • int a[n]整型数组变量 a,具有 n 个整型数值元素
  • int *p[n]整型指针数组变量 p,具有 n 个指向整型数值的指针元素
  • int (*p)[n]数组指针,指向整型数组的指针变量 p
  • int func():返回整型数值的函数 func
  • int *func():返回整型的指针函数 func
  • int (*p)()函数指针 func,指向函数的指针
  • int **p指向整型指针的指针变量 p

要清晰区分上述绕口令一般的关系,就要弄清楚指针的本质:

  • 指针:一个变量的地址
  • 指针变量:一个存放其他变量地址的变量

引入了指针之后,C/C++ 中就有了两种访问变量数据值的方式:

  1. 通过变量名来直接访问
  2. 通过内存地址块的指针来间接访问

指针运算相关的运算符有以下两种:

  • 取地址运算符 &:获取变量所占用的存储空间的地址,为单目运算符(只有一个操作数)。
  • 取值运算符 *:也称指针运算符,获取指针变量所指向的存储空间内的数据值。取值运算符的操作数只能是一个指针变量。

NOTE:取值运算和取地址运算互为逆运算。

int a = 3int b;
int * p = NULL;

p = &a;
b = *p;

前门的文章中提到过,变量 = 变量名 + 变量值,而且 C 语言是值语义的,有别于 Python 的引用语义。所以变量名就是变量在内存中的入口地址,变量值就是变量在内存空间中实际的数值。在程序中可以使用取地址运算符 & 来获取变量的入口地址。如下:

#include <stdio.h>

int main(){
    int var1;
    char var2[10] = {10, 9, 8, 7};

    printf("var1: %p\n", &var1);
    printf("var2-0: %p\n", &var2[0]);
    printf("var2-1: %p\n", &var2[1]);
    printf("var2-2: %p\n", &var2[2]);

    return 0;
}

运行:

$ ./main
var1: 0x7ffc857d59bc
var2-0: 0x7ffc857d59b0
var2-1: 0x7ffc857d59b1
var2-2: 0x7ffc857d59b2

可见,不同变量之间的内存空间很可能不是连续的,但同一数值内的顺序元素的空间是连续的。

指针的本质也是一个变量,其变量值是另一个变量的入口地址,即一个变量存储了另一个变量的内存地址,是为指针。数组名本质上也是一个指针,并且是常量指针,记录了数组的入口地址,且不能够被修改。

声明指针

type *var-name;
  • type 是指针的基类型,是一个有效的 C 数据类型
  • var-name 是指针变量的名称
  • * 用来声明指针类型变量
int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;     /* 一个字符型的指针 */

需要注意的是,不管指针的基类型是什么,指针变量的数值的类型都是一个代表内存地址的十六进制数。指针的基类表示了指针所指向的变量或常量的数据类型。

使用指针

使用指针时会频繁进行以下几个操作:

  1. 定义一个指针变量
  2. 把变量的内存地址赋值给指针
  3. 访问指针变量存储的数值(内存地址)
#include <stdio.h>
 
int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("Address of var variable: %p\n", &var  );
 
   /* 在指针变量中存储的地址 */
   printf("Address stored in ip variable: %p\n", ip );
 
   /* 使用指针访问值 */
   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("ptr 的地址是 %p\n", ptr);
   return 0;
}

运行

ptr 的地址是 0x0

在大多数的操作系统上,不允许程序访问地址为 0x0 的内存,因为该内存是操作系统保留的。但按照惯例,如果指针变量的数值为 NULL 时,则假定它不指向任何东西。

判断一个空指针的方式:

if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr)    /* 如果 p 为空,则完成 */

指针的算术运算

C 指针的本质是一个十六进制数值,所以可以对指针执行算术运算,可以对指针进行四种算术运算:++--+-

  • 指针的每一次递增,它会指向下一个元素的存储单元。
  • 指针的每一次递减,它会指向前一个元素的存储单元。
  • 指针在递增和递减时的步进(跳跃的字节数)取决于指针所指向的变量的数据类型,比如 int 就是 4 个字节。

我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:

#include <stdio.h>


const int MAX = 3;


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

    /* 数组名就是一个指针,直接复制给指针类型变量 */
    ptr = var;
    for(i = 0; i < MAX; i++){
        printf("Address: var[%d] = %p\n", i, ptr);
        printf("Value: var[%d] = %d\n", i, *ptr);
        /* 移动到下一个位置 */
        ptr++;
    }
    return 0;
}

运行:

./main
Address: var[0] = 0x7ffe48f272d0
Value: var[0] = 10
Address: var[1] = 0x7ffe48f272d4
Value: var[1] = 100
Address: var[2] = 0x7ffe48f272d8
Value: var[2] = 200

可见,每递增一次,移动了 4 Byte。

同样地,对指针进行递减运算,即把值减去其数据类型的字节数,如下所示:

#include <stdio.h>

const int MAX = 3;

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

    /* 获得数组最后一个元素的指针,再复制给指令类型变量 */
    ptr = &var[MAX - 1];
    for(i = MAX; i > 0; i--){
        printf("Address: var[%d] = %p\n", i - 1, ptr);
        printf("Value: var[%d] = %d\n", i - 1, *ptr);

        /* 移动到下一个位置 */
        ptr--;
    }
    return 0;
}

运行:

./main
Address: var[2] = 0x7ffdbab78f88
Value: var[2] = 200
Address: var[1] = 0x7ffdbab78f84
Value: var[1] = 100
Address: var[0] = 0x7ffdbab78f80
Value: 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;
 
   /* 指针中第一个元素的地址 */
   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 );
 
      /* 指向上一个位置 */
      ptr++;
      i++;
   }
   return 0;
}

指向指针的指针

指向指针的指针是一种多级间接寻址的实现,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际数值的内存位置。

在这里插入图片描述

一个指向指针的指针变量必须如下声明,在变量名前放置两个 * 号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

#include <stdio.h>
 
int main ()
{
   int  var;
   int  *ptr;
   int  **pptr;

   var = 3000;

   /* 获取整型变量 var 的地址 */
   ptr = &var;

   /* 获取指向整型变量的指针变量 ptr 的地址 */
   pptr = &ptr;

   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;
}

将指针作为实际参数传入函数

C 语言允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。

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

int main ()
{
   unsigned long sec;

   getSeconds(&sec);
   
   /* 输出实际值 */
   printf("Number of seconds: %ld\n", sec);
   return 0;
}

void getSeconds(unsigned long *par)
{
   /* 获取当前的秒数 */
   *par = time(NULL);
   return;
}

从函数返回指针

类似地,C 语言允许从函数返回指针类型。只需要一个简单的函数声明:

int * myFunction(){}

需要注意的是,C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们:

#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
 
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
   static int r[10];
   int i;
 
   /* 设置种子 */
   srand((unsigned)time(NULL));
   for ( i = 0; i < 10; ++i)
   {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;
}


int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
   return 0;
}
发布了500 篇原创文章 · 获赞 1352 · 访问量 188万+

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/105282564
今日推荐