指针的一步说明(form rubbom编程)

C 指针

学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。

正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。

请看下面的实例,它将输出定义的变量地址:

实例

#include <stdio.h>
 
int main ()
{
    int var_runoob = 10;
    int *p;              // 定义指针变量
    p = &var_runoob;
 
   printf("var_runoob 变量的地址: %p\\n", p);
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var_runoob 变量的地址: 0x7ffeeaae08d8

https://www.runoob.com/wp-content/uploads/2014/09/c-pointer.png

通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。

什么是指针?

指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 ***** 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

如何使用指针?

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 ***** 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:

实例

#include <stdio.h>
 
int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("var 变量的地址: %p\\n", &var  );
 
   /* 在指针变量中存储的地址 */
   printf("ip 变量存储的地址: %p\\n", ip );
 
   /* 使用指针访问值 */
   printf("*ip 变量的值: %d\\n", *ip );
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var 变量的地址: 0x7ffeeef168d8
ip 变量存储的地址: 0x7ffeeef168d8
*ip 变量的值: 20

C 中的 NULL 指针

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

NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

实例

#include <stdio.h>
 
int main ()
{
   int  *ptr = NULL;
 
   printf("ptr 的地址是 %p\\n", ptr  );
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

ptr 的地址是 0x0

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

如需检查一个空指针,您可以使用 if 语句,如下所示:

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

C 指针详解

在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念:

Untitled

  1. 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。

    要理解指针就要先理解计算机的内存。计算机内存会被划分为按顺序编号的内存单元。每个变量都是存储在内存单元中的,称之为地址。

    #include <stdio.h>
    
    int main ()
    {
        int var = 20;   /* 实际变量的声明 此时的 VAR 这个变量是存在某个地址的,地址对应某个内存单元,该单元中存储了数据20 */
        int *ip;         /* 指针变量的声明 定义了一个指针 即一个内存单元的地址变量 */
    
        ip = &var;      /* 在指针变量中存储 var 的地址 就是将地址值赋值给指针这个变量*/
    
        /* 在指针变量中存储的地址 利用&符号直接输出了var所存储的数据的内存单元的地址*/
        printf("Address of var variable: %p\\n", &var );
    
        /* 在指针变量中存储的地址 ip代表的是这个赋值到的地址的值 所以输出的是地址值 */
        printf("Address stored in ip variable: %p\\n", ip );
    
        /* 使用指针访问值 *ip代表的是定义到这个内存单元之后,内存单元中所存储的数据的值也就是将20赋值给var中20这个值 */
        printf("Value of *ip variable: %d\\n", *ip );
    
        return 0;
    }
    

指针是一个变量,所以可以使用任何合法的变量名。在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。

然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。

但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

所有指针在创建时都要初始化,如果不知道他指向什么就将 0 赋值给他。必须初始化指针,没有被初始化的指针被称为失控指针(野指针)。

#include <stdio.h>

int main ()
{
    int *p = 0;
    int a ;
    p = &a;
    printf ("输入一个数字\\n");
    scanf ("%d",p);
    printf("%d\\n",*p);
}

实例定义了变量 a 和指针变量 p。**p = &a;**表示指针变量指向了变量 a,p 中存放的地址为 a 的地址 &a,*p 所指的是 p 中存放的地址 a 内存单元中的值。

/*按照偏移值访问函数形参内容实验*/
//二级指针
void Pros(char* a,int b,int e,char et)
{
    char **p=&a;
    //a==*p
    printf("%p %p %p %p \\n%p\\n",&a,p,a,*p,&b);
    printf("%s\\n",*p);
    p++;
    printf("%d\\n",*p);
    p++;
    printf("%d\\n",*p);
    p++;
    printf("%c\\n",*p);
    return;
}

//一级指针访问
void Test(char* a,int b)
{
    char *p=(char*)&a;
    //a!=*p;
    //printf("%p %p %p %p\\n",&a,p,a,*p);
    //printf("%p\\n",&b);
    //得出结果一级指针自加+1 二级指针自按照元素内容大小自加
    //printf("%d %p\\n",*(++p),p);
    //printf("%d %p\\n",*(p+8),p+8);
    //a=a[0]一个printf函数以'\\0'结束
    //此时p=&a把元素首地址给了p或者说a只记录一个元素首地址的地址
    //同等汇编语句 a:db 'Hello' b:db '16'
    //所以 p=&a != p=a ;
    /*
    char *a="Hello";
    char *b=(char*)&a;
    printf("%p %p %p %p",&a,b,a,&(a[0]));
    */
    //printf("%c %p %p\\n",*a,a,&(a[0]));
    //printf("%c %p %p\\n",*(a+1),a+1,&(a[1]));
    printf("%c\\n",*(*(char**)p));
    //if p=a; *p=a;
    p=a;
    printf("%s",p);
    return;
}

int main()
{
    //Pros("Hello",5,66666,'a');
    Test("Hello",16);
    //指针转换问题
    /*
    char *a="Hello";//&a变量里面存储着a所指向的变量地址
    //char **p=&a;
    char *b=(char*)&a;
    char **p=&a;
    printf("%p %p %p %p\\n",&a,b,a,*b);
    printf("%p %c\\n",&(*a),*(&(*(a+1))));
    printf("%p %c\\n",a,*a);//此时a->H,*a=H;
    printf("%p %c\\n",(*p),*(*p));
    //p=&a,*p=a所指向的第一个元素的地址还需要一解才能访问正确数据
    //所以1级指针需要解2次 所以进行强制转换
    printf("%c \\n",*(*(char**)b));
    //原试解 现在b=&a,*b= &a->a所以如果此时想正确访问H必须在解
    */
    return 1;
}

指针的一些复杂说明:

  • int p; -- 这是一个普通的整型变量
  • *int p; -- 首先从 p 处开始,先与*结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
  • int p[3] -- 首先从 p 处开始,先与[] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
  • *int p[3]; -- 首先从 p 处开始, 先与 [] 结合, 因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
  • *int (p)[3]; -- 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
  • **int p; -- 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
  • int p(int); -- 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
  • *int (p)(int); -- 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
  • **int (p(int))[3]; -- 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。

更多内容参考:C 指针详解

指针实例说明:

int board[8][8];    /* int 数组的数组 */
int ** ptr;         /* 指向 int 指针的指针 */
int * risks[10];    /* 具有 10 个元素的数组, 每个元素是一个指向 int 的指针 */
int (* rusks) [10];  /* 一个指针, 指向具有 10 个元素的 int 数组 */
int * oof[3][4];    /* 一个 3 x 4 的数组, 每个元素是一个指向 int 的指针 (指针数组)*/
int (* uuf) [3][4]; /* 一个指针, 指向 3 X 4 的 int 数组 */
int (* uof[3]) [4]; /* 一个具有 3 个元素的数组, 每个元素是一个指向具有 4 个元素的int 数组的指针 */
//核心是优先级顺序的问题
()>[]>*

数组指针

给定义好的数组中赋值时, 指针可以通过调整地址给数组赋值。

例: 创建一个包含 3 个元素的一维数组, 并给它赋值。

int* array0 = (int*)malloc(sizeof(int) *3);
for(int i=0; i<3; i++){
    scanf("%d", array0+i);
}

指向函数的指针

代码和数据是一样的,都需要占据一定内存,那当然也会有一个基地址,所以我们可以定义一个指针来指向这个基地址,这就是所谓的函数指针。

假设有函数:

double func(int a,char c);
double (*p)(int a,char c);
p=&func;

即可以定义一个函数指针。

调用函数

double s1=func(100,'x');
double s2=(*p)(100,'x');

上面两个语句是等价的。

函数指针形式的传递,实质却是地址传递的一个例子:

#include <stdio.h>

void func1(int *a, int **b);

void func1(int *a, int **b)
{
   (*a)++;
   (*b)++;//这里虽然传进来的是指针的形式,但其实是指针c的地址,
          //可以认为这里本质还是值传递,只不过这个值是地址值
}

int main()
{
    int a[2] = {10, 20};
    int *b = &a[0];
    int *c = a+1;
    int **d = &c;

    func1(b, d);
    printf("a[0] = %d   a[1] = %d\\n", a[0], a[1]);

    return 0;
}

执行结果:a[0] = 11 a[1] = 20

由上可知,虽然传递参数时,是以指针形式进行的,但有时候会发现其实还是值传递,是地址值的传递,特别是在多维数组进行参数传递的时候,特别容易出现这种情况。

指针初学者

理解指针

2016 年接触 C 语言 ,当年愣是看不懂!尤其是指针,到现在 2 年多了,有点小理解。回顾一下,一说到指针就会说到地址的概念,所以跟指针有关的概念,跟 C 的内存管理结合起来理解会更好点,下面说说如何更好的理解指针。

为什么叫指针,指针其实是一种很形象的比喻,下面说说我的个人理解。

int 变量存的是 int 型的值,char 变量存的是 char 型的值,而指针,它是一种特殊的变量,存的是内存地址,按照这个模板可以把它理解为:“内存地址变量” 存的是 “内存地址”,等价于:“指针变量” 存的是 “内存地址”

操作系统进行资源调度时,会根据这些变量存的地址去请求和使用那个地址代表的内存区域,这就仿佛像是这个变量存的地址指向了某片内存,人们用 “指针” 来统称所谓的 “内存地址变量”

因此,任何跟指针有关的概念,都可以联系内存地址加以理解,二者必然有联系,数组与指针,函数与指针,都是如此。

  • 内存是线性的,内存以地址空间的形式呈现给我们看的,所以可以说所谓的地址空间也是线性的,指针存放的是内存地址,所以你可以对地址做 ++,或者 -- 这样的运算。
  • 两个指针不赋 NULL,是坏习惯
  • 初始化指针不赋 NULL,因为这样的指针会指向一片未知的区域,这样的指针不是空指针,但指向一片访问受限制的内存区域,你无法使用它,这样的情况下的指针,业界给了它一个形象的名字:“野指针”,而且难以调试,在许多编译器单步 debug 会出现奇怪的错误,但经常看见的 "Segmentation Fault" 这样的错误,实测当代码多的时候,这是一个非常蛋疼的错误,野指针就是成因之一,所以看到这样的错误,首先是想想,是否有某些指针没有初始化引起的
  • free() 后指针不赋 NULL,为指针分配内存后,指针便可以指向一片合法可使用的内存,但使用 free() 释放那片内存时,指针依旧存放着那片内存的地址,也就是依旧指向那片内存,但这片内存已经释放,不可访问,这时若不小心使用了这个指针,便会内存错误,又是会有奇怪的 bug ,代码几百行多点就会难以调试,业界给这样的指针也有个统称:“悬空指针”,为了避免这种蛋疼的情况出现,一定要释放内存后,给指向这片内存的指针,都赋值为 NULL,从中也可以看出,free() 这个函数释放内存时跟指向这片内存的指针并没有什么卵关系,不会连着把指针一起搞定掉的! 珍爱生命,远离 "野指针" 与 "悬空指针" !
  • 多级指针,指向指针的指针,有时人们也管它叫多维指针。既然指针变量是一个变量,指针变量能存变量的内存的地址。

像 int * 存 int 型变量的地址,char * 存 char 型的地址,那指针理所当然可以存指针变量的地址啊。

例如,int ** 存 int * 的地址,int *** 存 int ** 的地址。

这就是一个二级指针存一级指针的地址,三级指针存二级指针的地址,人们把这样的过程叫指向指针的指针,但其实也就是一个上一级的指针存了下一级的指针的地址而已。

因此,像上面说的,你存了它的地址,你就是指向它,所以:

  • 二级指针存一级指针的地址,那么可以说二级指针指向一级指针
  • 三级指针存二级指针的地址,那么可以说二级指针指向一级指针
  • 多级指针用处多多, 这里暂不举例详细说明。

个人认为指针可以说是 C 的最伟大的特性,通过这样的一个模型可以形象地管理部分内存!

C 指针的算术运算

C 指针

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

假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:

ptr++

在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。

我们概括一下:

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

递增一个指针

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

实例

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中的数组地址 */
   ptr = var;
   for ( i = 0; i < MAX; i++)
   {
 
      printf("存储地址:var[%d] = %p\\n", i, ptr );
      printf("存储值:var[%d] = %d\\n", i, *ptr );
 
      /* 指向下一个位置 */
      ptr++;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200

递减一个指针

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

实例

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中最后一个元素的地址 */
   ptr = &var[MAX-1];
   for ( i = MAX; i > 0; i--)
   {
 
      printf("存储地址:var[%d] = %p\\n", i-1, ptr );
      printf("存储值:var[%d] = %d\\n", i-1, *ptr );
 
      /* 指向下一个位置 */
      ptr--;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[2] = 518a0ae4
存储值:var[2] = 200
存储地址:var[1] = 518a0ae0
存储值:var[1] = 100
存储地址:var[0] = 518a0adc
存储值: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("存储地址:var[%d] = %p\\n", i, ptr );
      printf("存储值:var[%d] = %d\\n", i, *ptr );
 
      /* 指向上一个位置 */
      ptr++;
      i++;
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[0] = 0x7ffeee2368cc
存储值:var[0] = 10
存储地址:var[1] = 0x7ffeee2368d0
存储值:var[1] = 100
存储地址:var[2] = 0x7ffeee2368d4
存储值: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]; /* 赋值为整数的地址 */
   }
   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 ()
{
   const 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

C 指向指针的指针

C 指针

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

https://www.runoob.com/wp-content/uploads/2014/09/pointer_to_pointer.jpg

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

int **var;

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

https://www.runoob.com/wp-content/uploads/2014/09/c-pointerxxxxx.png

实例

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

当上面的代码被编译和执行时,它会产生下列结果:

var = 100
Pt1 = 0x7ffee2d5e8d8
*Pt1 = 100
Pt2 = 0x7ffee2d5e8d0
**Pt2 = 100

C 传递指针给函数

C 指针

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

下面的实例中,我们传递一个无符号的 long 型指针给函数,并在函数内改变这个值:

实例

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

当上面的代码被编译和执行时,它会产生下列结果:

Number of seconds :1294450468

能接受指针作为参数的函数,也能接受数组作为参数,如下所示:

实例

#include <stdio.h> 
*/* 函数声明 */*
double getAverage(int *arr, int size);
 int main ()
{  
 */* 带有 5 个元素的整型数组  */* 
  int balance[5] = {1000, 2, 3, 17, 50}; 
  double avg;    */* 传递一个指向数组的指针作为参数 */* 
  avg = getAverage( balance, 5 ) ;   
 */* 输出返回值  */*  
 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
  1. 通过传递指针给函数,可以直接修改原参数(实参),而不是引用实参到形参。Sigma2年前 (2019-11-14)

    如上例中:

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

    直接修改了sec 的值。

    Sigma

    175***2252@qq.com

  2. int ((*fp)(int)) [10]; (fp)(int)是一个指向函数的指针ptr(*fp)(int)相当于一个指针ptr1(指针函数的返回值)最后剩下int *ptr1[10],可以理解。Wanghaha23332年前 (2020-04-18)

    Wanghaha2333

    297***8120@qq.com

    参考地址

    120

    指针和函数的关系

    1、函数指针(指向函数的指针)

    一个函数在编译之后,会占据一部分内存,而它的函数名,就是这段函数的首地址。

    可以把一个指针声明成为一个指向函数的指针。

    C 语言规定函数名会被转换为指向这个函数的指针,除非这个函数名作为 & 操作符或 sizeof 操作符的操作数(注意:函数名用于 sizeof 的操作数是非法的)。也就是说 f = test; 中 test 被自动转换为 &test,而 f = &test; 中已经显示使用了 &test,所以 test 就不会再发生转换了。因此直接引用函数名等效于在函数名上应用 & 运算符,两种方法都会得到指向该函数的指针。

    指向函数的指针必须初始化,或者具有 0 值,才能在函数调用中使用。

    与数组一样:

    • (1)禁止对指向函数的指针进行自增运算++
    • (2)禁止对函数名赋值,函数名也不能用于进行算术运算。

    示例1:

    int fun1(int,int);
    int fun1(int a, int b)
    {
        return a+b;
    }
    int main()
    {
        int (*pfun1)(int,int);
        pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓
        int a=(*pfun1)(5,7); //通过函数指针调用函数。
    }
    

    示例2:

    #include <stdio.h>
    #include <stdlib.h>
    int Max(int x, int y)  //定义Max函数
    {
        int z;
        if (x > y) {
            z = x;
        }else {
            z = y;
        }
        return z;
    }
    int main() {//定义一个函数指针
        int(*p)(int, int);
        int a, b, c;//把函数Max赋给指针变量p, 使p指向Max函数
        p = Max;//不能缺漏
        printf("please enter a and b:");
        scanf("%d%d", &a, &b);//通过函数指针调用Max函数
        c = (*p)(a, b);
        printf("a = %d\\nb = %d\\nmax = %d\\n", a, b, c);
        system("pause");
        return 0;
    }
    

    示例2:

    #include <stdio.h> 
    void test( )
    {
        printf("test called!/n");
    } 
    int main( )
    {
        void (*f) ( );
        f = test; 
        f ( );
        (*f)( );
        //test++;             // error,标准禁止对指向函数的指针进行自增运算
        //test = test + 2;    // error,不能对函数名赋值,函数名也不能用于进行算术运算
        printf("%p/n", test);
        printf("%p/n", &test);
        printf("%p/n", *test);
        return 0;
    }
    

    运行结果为:

    test called!
    test called!
    004013EE004013EE004013EE
    

    这里的玄学就是 *test 为什么能和上面两个之前介绍过的输出一样的值。

    首先来看函数名 test,是一个符号用来标识一个函数的入口地址,在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址,&test 在前面已经说了:显示获取函数的地址。*test 可以认为由于 test 已经被转换成了函数指针, 指向这个函数,所以 *test 就是取这个指针所指向的函数名,而又根据函数名会被转换指向该函数的指针的规则,这个函数也转变成了一个指针,所以 *test 最终也是一个指向函数 test 的指针。也就是说:**test --> (&test) --> test --> &test

    上述关系十分重要!

    为了更加明确,把示例 1 做补充:

    #include <stdio.h>
    int fun1(int,int);
    int fun1(int a, int b)
    {
        return a+b;
    }
    /* 要调用上面定义函数的主函数 */
    int main ()
    {
        int (*pfun1)(int,int);
        pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓
        int a=(*pfun1)(5,7); //通过函数指针调用函数。
        printf("%d\\n",a);
        int e = fun1(5,7);
        printf("%d\\n",d)
        int b = (&fun1)(5,7);
        printf("%d\\n",b);
        int c = (*fun1)(5,7);
        printf("%d",c);
        return 0;
    }
    //根据关系 *fun1==*&fun1==fun1==&fun1 可知,以上的运行结果会得到4个5+7。
    //因此在下面的函数指针数组实例中,action[2]()就相当于这里的(&fun1(5,7)),这点务必搞清楚。
    

    2、指针函数(返回值为指针的函数)

    所谓指针函数,就是返回指针的函数。在前面笔记中“从函数返回数组”中已经介绍。

    C 语言的库函数中有很多都是指针函数,比如字符串处理函数,下面给出一些函数原型:

    char *strcat( char *dest, const char *src );
    char *strcpy( char *dest, const char *src );
    char *strchr( const char *s, int c );
    char *strstr( const char *src, const char *sub );
    

    3、两者混用(不常用)

    注意函数的返回值不仅仅局限于指向变量的指针,也可以是指向函数的指针。

    首先来看这个声明:int (function(int)) (double, char); 要了解此声明的含义,首先来看 function(int),将 function 声明为一个函数,它带有一个 int 型的形式参数,这个函数的返回值为一个指针,正是函数指针 int () (double*, char); 这个指针指向一个函数,此函数返回 int 型并带有两个分别是 double* 型和 char 型的形参。

    如果使用typedef可以将这个声明简化:(没看懂。。。。之后的结构体再补充)

    typedef  int (*ptf) (double*, char);
    ptf  function( int );
    

    另一个例子:

    void (*signal (int sig, void (*func) (int siga)) ) ( int siga );
    

    现在要分析的是 signal,因为紧邻 signal 的是优先级最高的括号,首先与括号结合,所以 signal 为一个函数,括号内为 signal 的两个形参,一个为int型,一个为指向函数的指针。接下来从向左看,* 表示指向某对象的指针,它所处的位置表明它是 signal 的返回值类型,现在可以把已经分析过的 signal 整体去掉,得到 void (*) ( int siga )。又是一个函数指针,这个指针与 signal 形参表中的第二个参数类型一样,都是指向接受一个 int 型形参且不返回任何值的函数的指针。

    用 typedef 可以将这个声明简化:

    typedef int (*p_sig) (double*, char);
    p_sig signal(int sig, p_sig func);
    

    这个 signal 函数是 C 语言的库函数,在 signal.h 中定义,用来处理系统中产生的信号。

    4、函数指针数组

    假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作(打开文件,读文件,写文件,关闭文件)。这些操作都实现为函数且类型相同,分别为:

    void open();
    void read();
    void write();
    void close();
    

    现在定义一个函数指针类型的别名PF:

    typedef void (*PF) ( );
    

    把以上 4 种操作取地址放入一个数组中,得到:

    PF file_options[ ] = {
        &open,
        &read,
        &write,
        &close
    };
    

    如果不使用 typedef,那么分析起来就会比较复杂,结果是 void (*file_options[ ]) ( );

    这个数组中的元素都是指向不接受参数且不返回任何值的函数的指针,因此这是一个函数指针数组。接下来,定义一个函数指针类型的指针action并初始化为函数指针数组的第一个元素:PF* action = file_options;,如果不好理解,可以类比一下:

    int ia[4] = {0, 1, 2, 3};
    int *ip = ia;,
    

    这里 PF 相当于 int,这样应该比较好懂了。

    复习:

    int ia[4] = {0, 1, 2, 3};
    int *ip = ia; //ia就是&ia[0],因此ip指向ia[0]。与此同时ip[1]的含义又和*(ip+1)一样。
    printf("%p\\n",ip);
    printf("%p\\n",ip+1);
    printf("%d\\n",ip[1]);
    printf("%d\\n",*(ip+1));/*输出结果0x7ffee4cca9b00x7ffee4cca9b411*/
    

    通过对指针 action 进行下标操作可以调用数组中的任一操作,如:action2 会调用 write 操作,以此类推。在实际中,指针 action 可以和鼠标或者其他 GUI 对象相关联,以达到相应的目的。

    5、函数与指针的复杂声明(不做要求,一般用 typedef 代替它)

    只举一个例子:

    int *(*(*fp)(int)) [10];
    

    阅读步骤:

    1.从未定义的变量名开始阅读 -------------------------------------------- fp
    2.往右看,什么也没有,遇到了),因此往左看,遇到一个* ------ 一个指向某对象的指针
    3.跳出括号,遇到了(int) ----------------------------------- 一个带一个int参数的函数
    4.向左看,发现一个* --------------------------------------- (函数)返回一个指向某对象的指针
    5.跳出括号,向右看,遇到[10] ------------------------------ 一个10元素的数组
    6.向左看,发现一个* --------------------------------------- 一个指向某对象指针
    7.向左看,发现int ----------------------------------------- int类型
    

    所以 fp 是指向函数的指针(函数指针), 该函数返回一个指向数组的指针,此数组有 10 个 int* 型的元素。

    Wanghaha2333

    297***8120@qq.com

    参考地址

  3. rookie9个月前 (02-19)

    rookie

    liy***312999538@163.com

    5

    指出楼上第3个小目录一个小问题

    ps: 应该是大佬无意写错了,无伤大雅,其他知识点理的都很好

    void (*signal (int sig, void (*func) (int siga)) ) ( int siga );
    

    用 typedef 可以将这个声明简化,应该是:

    typedef void (*p_sig) (int);
    p_sig signal(int sig, p_sig func);
    

    rookie

    liy***312999538@163.com

    C 从函数返回指针

    C 指针

    在上一章中,我们已经了解了 C 语言中如何从函数返回数组,类似地,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;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    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 指针

    C enum(枚举)

    C 函数指针与回调函数

    1 篇笔记 写笔记

    tuingke   142***2460@qq.com210 C 不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。 因为局部变量是存储在内存的栈区内,当函数调用结束后,局部变量所占的内存地址便被释放了,因此当其函数执行完毕后,函数内的变量便不再拥有那个内存地址,所以不能返回其指针。 除非将其变量定义为 static 变量,static 变量的值存放在内存中的静态数据区,不会随着函数执行的结束而被清除,故能返回其地址。tuingke    tuingke   142***2460@qq.com3年前 (2018-07-15)

    函数指针

    函数指针是指向函数的指针变量。

    通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

    函数指针可以像一般函数一样,用于调用函数、传递参数。

    函数指针变量的声明:

    typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
    

    实例

    以下实例声明了函数指针变量 p,指向函数 max:

    实例

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

    编译执行,输出结果如下:

    请输入三个数字:1 2 3
    最大的数字是: 3
    

    回调函数

    函数指针作为某个函数的参数

    函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。

    简单讲:回调函数是由别人的函数执行时调用你实现的函数。

    以下是来自知乎作者常溪玲的解说:

    你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

    实例

    实例中 populate_array 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。

    实例中我们定义了回调函数 getNextRandomValue,它返回一个随机值,它作为一个函数指针传递给 populate_array 函数。

    populate_array 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

    实例

    #include <stdlib.h>  
    #include <stdio.h>
     
    // 回调函数
    void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
    {
        for (size_t i=0; i<arraySize; i++)
            array[i] = getNextValue();
    }
     
    // 获取随机值
    int getNextRandomValue(void)
    {
        return rand();
    }
     
    int main(void)
    {
        int myarray[10];
        /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
        populate_array(myarray, 10, getNextRandomValue);
        for(int i = 0; i < 10; i++) {
            printf("%d ", myarray[i]);
        }
        printf("\\n");
        return 0;
    }
    

    编译执行,输出结果如下:

    16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
    

    shujiniji

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值