基础学习记录——C语言指针

指针

指针也就是内存地址,指针变量是用来存放内存地址的变量,指针变量在内存中也有自己的地址,只不过它的地址中存放的是别人的地址。

在C语言中是可以直接操作内存的,指针的意义就是指向某一个内存地址,最简单的示例如下:指针变量

#include <stdio.h>

int main ()
{
    int var_runoob = 10;
    int *p;              // 定义指针变量
    p = &var_runoob;

   printf("var_runoob 变量的地址: %p\n", p);
   return 0;
}

这里的*p就是定义一个指针变量,那么这里的p就可以通过&符号取其他变量内存中的地址后,赋值给p这个指针变量了。也就是说指针变量并不是一个实际存在的变量,只是指向了其他变量内存中的地址从而获得其他变量的值。

  • p是一个指针,存储着变量var_runoob的地址。
  • 指针p的类型必须与var_runoob的类型一致,因为整型的指针只能存储整型变量的指针地址

NULL指针

在变量声明的时候,如果没有确切的地址可以赋值,可以给指针变量赋一个NULL值,此时这个指针被称为空指针。

#include <stdio.h>

int main ()
{
   int  *ptr = NULL;

   printf("ptr 的地址是 %p\n", ptr  );

   return 0;
}

指针的算术运算

指针也可以进行++、--、+、-这四种算数运算,这里的运算也就是对指针指向的地址的运算,例如将*p进行++操作,那么就是根据p这个指针的指针类型,向后移动一个类型长度的地址,示例如下:

#include <stdio.h>

int main ()
{
   int  *ptr = NULL;
   int test = 1;
   ptr = &test;
   ptr++;

   printf("ptr 的地址是 %p\n", ptr);

   return 0;
}

这里ptr指针的类型为整型,一个整型数字在内存中占四个字节,那么ptr++就将ptr原本指向的000000000061FE14给变成了指向000000000061FE18。

如果ptr-1的话,那么此时的地址就应该为ptr减去一个整型的字节之后的地址,也就是指向的内存位置向前移动四个字节。

指针的比较

指针可以用关系运算符进行比较,例如==、<、>。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。

指针数组

在C语言中,指针类型可以为数组类型,与指针变量类似。示例如下:

#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]; /* 赋值为整数的地址 */
   }
   for ( i = 0; i < MAX; i++)
   {
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   return 0;
}

上面的代码首先定义了ptr指针为int类型的数组,然后再循环取var[]数组中的值的地址赋给ptr指针数组的键,也就是说再指针数组中,每一个键都是一个指针,可以指向一个int类型的内存地址。

指向指针的指针

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

示例代码:

#include <stdio.h>

int main ()
{
   int  V;
   int  *Pt1;
   int  **Pt2;

   V = 100;

   /* 获取 V 的地址 */
   Pt1 = &V;

   /* 使用运算符 & 获取 Pt1 的地址 */
   Pt2 = &Pt1;

   /* 使用 pptr 获取值 */
   printf("var = %d\n", V );
   printf("Pt1 = %p\n", Pt1 );
   printf("*Pt1 = %d\n", *Pt1 );
    printf("Pt2 = %p\n", Pt2 );
   printf("**Pt2 = %d\n", **Pt2);

   return 0;
}

在以上代码中,定义了Pt1整型指针变量,而Pt2是一个整型的指向指针的指针变量。

传递指针给函数

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

在函数定义中,在函数名前面加上*就可以指定该函数返回的结果为指针。

相应的,如果函数返回值为指针,则需要用一个指针变量去接受返回的结果,例如:

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

上面的p就是一个int类型的指针变量,那么getRandom返回的指针就可以被p接收,并且p接收到的是函数返回内容的内存地址块中最前面的地址,也就是r数组的第一个组值的内存地址。

如果想要取数组中的所有地址,那么可以使用上述代码中的这一块:

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

只需要遍历数组中的值,然后将值与p指针相加即可获得r数组相应的组值。

函数指针

函数指针是将指针指向一个函数,调用的时候也可以直接通过函数指针调用函数。示例如下:

#include <stdio.h>

int max(int x, int y)
{
    return x > y ? x : y;
}

int main(void)
{
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &可以省略
    int a, b, c, d;

    printf("请输入三个数字:");
    scanf("%d %d %d", & a, & b, & c);

    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c); 

    printf("最大的数字是: %d\n", d);

    return 0;
}

上述代码定义*p指针变量时,后面带上了(int,int),也就是参数类型,赋值为& max,也就是取max函数的地址,那么这个指针变量p现在就指向了max函数在内存中的地址。

此时调用max函数就可以通过p这个指针变量去调用,直接使用p(a,b)即可。