C语言 指针辨析(二级指针、数组指针、指针数组、指针作为函数参数、指向函数的指针)

本博文为半摘记性质。
——
声明:知识点引自《全国计算机等级考试-上机考试新版题库 二级C》,部分例程修改自https://www.runoob.com/cprogramming/c-tutorial.html 菜鸟教程,另有部分零散资料转自互联网,内容有一定改动,并非全文转载。
本人尊重各位的知识成果,大幅引用的文章原文网址已在各小节末尾给出。
——
C语言在线运行工具:http://c.jsrun.net/

编译预处理与指针

本文要点:

  1. 指针作为函数参数
  2. 指向一维数组的指针以及指针数组
  3. 指向函数的指针

1指针

https://www.jb51.net/article/80775.htm 简单总结C语言中各种类型的指针的概念

指针基础

预备知识
定义一个变量a,C语言规定a表示存储单元中的数据,&a表示存储单元的地址。a存储单元中的数据可以是一个普通数值,也可以是另一个存储单元的地址。

——

关于指针和指针变量:存在两种概念混用的情况,严格来讲:

  1. 系统为每一个内存单元分配一个地址值,C/C++把这个地址值称为“指针”。如有int i=5;,存放变量i的内存单元的编号(地址)&i被称为指针。
  2. “指针变量”则是存放前述“地址值”的变量,也可以表述为,“指针变量”是存放变量所占内存空间“首地址”的变量(因为一个变量通常要占用连续的多个字节空间)。比如在int i=5;后有一句int *p=&i;,就把i的指针&i赋给了int *型指针变量p,也就是说p中存入着&i。所以说指针变量是存放指针的变量。

星号* 的作用是解引用,即 *运算符给出指针变量p中存储的地址上存储的值。定义指针变量时加星号是为了防止和普通变量搞混。
p为指针变量中存储的地址,*p为指针访问值。要注意指针自己也是有地址的,因此又有了二级指针的概念(**p)。

https://blog.csdn.net/qq_26606969/article/details/82872693 C语言中“指针”和“指针变量”的区别,以及如何区分 * 得用法

指针变量的定义形式

类型名 *指针变量名1,*指针变量名2,...;

指针变量的赋值方式

int *p;
p=&a;
等价于:int *p=&a; 

注意:

  1. 指针变量只能保存同型元素的地址。对于类型不同的指针变量,其内容(地址值)增1,减1所跨越的字节数是不同的,因此基类型不同的指针变量不能混合使用。
  2. 指针变量在使用前必须赋初值。如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针
int  *ptr = NULL;
  1. 指针变量不能存放字符串,只有字符数组提供其存放的空间;不过字符串可以赋值给字符指针变量:p=“abcd”;双引号做了3件事:
    1.申请了空间(在常量区),存放了字符串 2. 在字符串尾加上了’/0’ 3.返回地址
    这里就是将 返回地址 赋值给了指针变量。
    当需要多个字符串时可以采用指针数组(见后文)。

https://blog.csdn.net/ss19890125/article/details/48689385 C语言中,为什么字符串可以赋值给字符指针变量

指针变量的算数运算

  1. 可以对指针变量进行四种算术运算:++、–、+、-。

指针变量的每一次递增,它其实会指向下一个元素的存储单元;指针变量的每一次递减,它都会指向前一个元素的存储单元。
在程序中常使用指针变量代替数组,因为指针变量可以递增,而数组不能递增,数组可以看成一个指针常量。
注意:函数指针运算不能参与算术运算。

 int  var[] = {10, 100, 200};
 int  *ptr;   
   ptr = var;
   ptr++;
结果为*ptr=100
  1. 指针可以用关系运算符进行比较,如 ==、< 和 >。

指向指针的指针(二级指针)

定义形式:

类型名 **指针变量名1,**指针变量名2,...;

赋值形式:

int  var=3000,*ptr,**pptr;
   ptr = &var;//ptr存着var的地址
   pptr = &ptr;//pptr存着ptr的地址
   ____
   *pptr=ptr=var's address
   **pptr=*ptr=3000

指向XX的指针

有:

  1. 指向变量的指针/指针变量(见前文)
  2. 指向数组的指针/数组指针(见下文)
  3. 指向字符串的指针/字符指针(见下文)
  4. 指向函数的指针/函数指针(见下文)
  5. 指向结构体的指针/结构指针
    (见https://blog.csdn.net/weixin_45263626/article/details/105520031 结构指针)

https://blog.csdn.net/qq_36243846/article/details/74929207 c语言常见的几种指针用法

指向一维数组的指针

同理可得出指向二维数组的指针

指向一维数组的指针定义形式(定义+赋值):

类型名 (*指针名)[数组长度] = &数组名[数组长度];//指针名加括号是为了同指针数组区分
//数组长度指访问的二维数组中每行的元素个数

注意: 注意区分指向数组的指针(见本章)和指向数组元素的指针(见【指针的算数运算】)。

int array[5] = {1, 2, 3, 4, 5}; // 定义数组
int *intptr = array; // 即ptr = &array[0],定义指向数组元素的指针,同理也可定义其它数组元素,例如:ptr = &array[1]
int (*arrayptr)[5] = &array; // 定义指向数组的指针

通过指向数组的指针访问数组的元素:

(*arrayptr)[n] == array[n]

二维数组实际上就是元素为一维数组的数组,二维数组名可以看做 指向其第一个元素(一维数组) 的指针,而一维数组可以看做一个1 x n的二维数组,即只有一个元素的二维数组。
arrayptr指向了该二维数组的第一个元素(相当于二维数组名) ,所以有:

arrayptr[0][0] == array[0]
同理arrayptr[0][1] == array[1]...

通过下标运算符 arrayptr[0] 获得二维数组的第一个元素(实际上就是array数组), 然后再次利用下标运算符 arrayptr[0][0] 获取array数组的第一个元素.
——
指向一维数组的指针可以作为函数的形参,来接收二维数组的首地址,在函数中按行访问二维数组。

https://blog.csdn.net/qq_31504597/article/details/79966023 C语言之指向一维数组的指针

指向字符串的指针

char *指针名;
指针名 = “字符串”;//不需要加&
或:char *指针名 = “字符串”

指向函数的指针

注意与指针函数的区别。

一个函数的函数名就是一个函数指针,通常是被隐式地转换的。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。

函数指针变量的声明格式有多种,标准格式(少见)为:

类型 (*指针变量名)();//类型标识符指函数的返回值类型
//该声明中采用括号将星号和指针变量名包围起来,这个括号很重要。如果没有它,则为函数原型,而不是指针定义。
//最后的空括号表示指针变量所指的是一个函数    
//类似数组指针                       

另一声明格式(常用)为:

类型 (*指针变量名)(参数类型1,参数类型2);
例:int (*p)(int,int);//形参可省
或:int (*p)(int a,int b);//形参保留

可以将函数入口地址(函数名) 赋予指针变量,以此来调用此函数。

函数指针变量 =  函数名;

用函数指针变量形式调用函数的一般形式(调用形式)为:

(*指针变量名)(实参表);

实例:

#include 

int max(int a, int b)
{
    return a > b ? a : b;
}

int main(void)
{
    int (*p)(int, int); //函数指针的定义   
    p = max;    //函数指针初始化

    int ret = p(10, 15);    //函数指针的调用
    //int ret = (*max)(10,15);
    //int ret = (*p)(10,15);
    //以上两种写法与第一种写法是等价的,不过建议使用第一种方式
    printf("max = %d \n", ret);
    return 0;
}

函数指针变量不能参与算术运算。

回调函数(callback)
函数指针的一个非常典型的应用就是回调函数。
回调函数就是一个通过函数指针调用的函数
如果你把函数的指针(地址)作为参数传递给另一个函数(登记回调函数),当这个指针被用来调用其所指向的函数时,则该函数为回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时(触发回调事件)由另外的一方间接调用的(调用回调函数),用于对该事件或条件进行响应(响应回调事件)。

回调函数的优势待补充

指针函数

注意函数指针与指针函数的区别。指针函数是指一个函数的返回值类型为指针。
声明格式为:

*类型标识符 函数名(形参表)
例:int *fun(int x,int y);

在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。
实例:

int *func_sum(int n)
{
    if (n < 0)
    {
        printf("error:n must be > 0\n");
        exit(1);
    }
    static int sum = 0;
    int *p = &sum;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return p;
}

int main(void)
{
    int num = 0;
    printf("please input one number:");
    scanf("%d", &num);
    int *p = func_sum(num); //在调用指针函数时,需要一个**同类型的指针**来接收其函数的返回值。
    printf("sum:%d\n", *p);
    return 0;
}

https://blog.csdn.net/u010280075/article/details/88914424 指针函数和函数指针

指针数组

指针数组可以保存多个地址。
当数组的基类型为指针类型时其定义形式为:

类型名 *数组名[常量表达式];

数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素。

指针数组存放的是数据的地址,多用于保存多个字符串的首地址

int main ()
{
   const char *names[] = {      //注意同字符数组的区别
                   "Zara Ali", //不需要二维数组,每个数组元素只是一个字符串的首地址
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
   int i = 0;
   for ( i = 0; i < 4; 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语言中实参和形参之间的数据传输是单向的“值传递”方式,也就是实参可以影响形参,而形参不能影响实参。但当指针变量作为函数的参数时,可以改变实参值
• 要实现这一功能,实参可以是变量的地址(&a)或指向变量的指针变量(p);利用指针变量作为函数的形参时,形参接收的是地址值
此外return语句只能返回一个数据,用指针变量作为函数的参数,此时调用函数就可以修改多个参数值。

void swap(int *p1, int *p2){  //*p1=&a,*p2=&b
    int temp;  //临时变量
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
int main(){
    int a = 66, b = 99;//或加上*q1=&a,*q2=&b;
    swap(&a, &b);//swap(q1,q2);这是因为q1=&a,q2=&b
    printf("a = %d, b = %d\n", a, b);
    return 0;
}
运行结果:a=99,b=66;

指针作为被调函数形参可以更改主调函数中数据的原因是函数调用时实参的地址传递给相应的形参,借助指针的访问,可以访问或改变主调函数中的数据。

优点: 指针作为函数参数的好处在于效率高,因为C语言的参数传递调用方式要求把参数的一份拷贝传递给函数,必须把结构体占用的内存空间复制到堆栈中,以后再丢弃。

二级指针作为函数参数

模仿上一例的操作方法,仍用一级指针,指针变量q指向a,现在让指针变量q指向b。

int a = 66, b = 99;
void swap(int *p1){  

    p1 = &b;
 
}
int main(){
    int *q1=&a;
swap(q1); 
    printf("*q1 = %d\n", *q1);
    return 0;
}
结果:*q1=66

测试失败,指针变量q1没有改变。这是因为:

*p1=q1,q1=&a;
所以*p1=&a;
接着*p1=&b;

第三步操作并没有涉及到指针变量q1,由于函数没有返回值,函数的作用相当于0。
现改用二级指针:

int a = 66, b = 99;
void swap(int **p1){  //p1=&a,p2=&b

    *p1 = &b;
 
}
int main(){
    int *q1=&a;
swap(&q1);
    printf("*q1 = %d\n", *q1);
    return 0;
}
结果:*q1=99

测试成功,这是因为:

**p1=&q1;
*p1=q1=&a;
*p1=&b;
因此 q1=&b;

一级指针作为函数参数的例子中涉及到了两个指针,可以直接交换地址;而第二个例子则不行。

更多参考:
http://blog.csdn.net/majianfei1023/article/details/46629065 二级指针的作用详解

指针数组作为函数参数

没什么特别的。

你可能感兴趣的:(编程语言)