C语言-指针篇

0 前言

>>返回AUTOSAR系列文章目录<<

1 指针是什么?

以下是常见的指针解释:

指针(Pointer)是一个变量,这个变量存放的是另一个变量的地址。

这个解释很大程度上误解了指针的作用,造成新人学习困难,以下是我自己的理解:

指针(Pointer)是一个助记符,记录了一个内存块的首地址和长度。

int* a;  // a 可以记录一个int型数据首地址和数据长度 4 
char* b; // b 可以记录一个char型数据首地址和数据长度 1 

程序在运行过程中需要的是数据和指令,变量、数组、字符串、结构体等等就是数据,函数就是指令。

数据和指令通常都不止一个字节,因此指针既包含代码块或者指令块的首地址,也包含这些代码块或指令块的长度。当我们调用指针时,实际上是让指针从首地址到尾地址跑了一遍。

指针记录的是一片内存块的首地址和长度,而不是单独一个字节的地址,但是打印时只显示首地址。

对于所有指针,编译器会自动转化以下语法,数组也是指针

ptr[i] => *(ptr+i)

1.1 指针的格式

声明一个指针需要用到间接运算符*,语法中并没有规定*前后空格。以下写法都合法:

int* ptr;  //推荐
int * ptr;
int *ptr;

推荐使用第一种写法,在声明时不要将*看成一个符号,而是将int* 整体看成一种全新的数据类型。这种数据类型定义了一片只能够存放int型数据的内存块。

char* p;    // 声明 char* 型数据类型
char a, b;  // 声明 char 型数据类型
p = &a;     // 地址运算符
b = *p;     // 间接运算符

指针可以声明时赋值:

char a;
char* p = &a;

C语言本身就提供很多助记符,其中变量名表示的是数据内容,而函数名字符串名数组名本身就是函数、字符串、数组首元素的指针,无需声明即可直接作为指针使用。结构体名不是结构体的指针。所以以下内容都合法:

/***** 示例程序 *****/
#include <stdio.h>
int main()
{
    int* ptr;
    int var;
    int arr[10];

    ptr = &var;    // var本身是内容,需要地址运算符
    printf("%p\n", ptr);
    ptr = arr;   // arr本身就是指针,可直接赋值
    printf("%p\n", ptr);
    return 0;
}


/***** 示例结果 *****/
0x7ffe074e6af4
0x7ffe074e6ac0

由于数组名就是数组第0个元素的指针,所以有:

*(arr + i)  == arr[i];

1.2 指针运算

  1. 指针初始化

使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL

  1. 指针与数字加减

指针变量可以与数字进行加减运算,例如p++p+1p-=1。指针变量+1运算并不是指向下一个内存地址,而是指向下一个内存块的地址。

/***** 示例程序 *****/
#include <stdio.h>
int main()
{
    int var1;
    long var2;
    int* ptr1 = &var1;
    long* ptr2 = &var2;
    
    printf("%p %p\n", ptr1, ptr1+1);
    printf("%p %p\n", ptr2, ptr2+1);
    
    return 0;
}


/***** 示例结果 *****/
0x7ffdc6edb08c 0x7ffdc6edb090
0x7ffdc6edb080 0x7ffdc6edb088

可以看到,int*型指针+1是位移了4字节(一个内存块),long*型指针+1是位移了8字节(一个内存块)。

  1. 指针与指针相减

两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。


2 指针类型

指针类型声明说明
指针int* p;p为指针,指向int类型的数据,或者一维数组 int[n]的元素
二级指针int** p;p 为指针,指向int*类型的指针
指针数组int* p[n];p为数组,每个元素都是int*型指针
数组指针int (*p)[n];p 为指针,指向int[n]型数组,或者int[m][n]数组的一行
指针函数int* p();p 为函数,它的返回值类型为 int*
函数指针int (*p)();p 为指针,指向原型为 int fun()的函数

2.1 指针

int* p = &var;
int* p = arr;
int* p = &arr[0];

p可以指向一个int型数据的地址,无论这个数据是否在一个数组中。

数组名就是数组第一个元素的地址,一维数组第一个元素是变量,二维数组第一个元素是数组。

p的长度为sizeof(int)

2.1.1 空指针

空指针NULL的定义如下:

#define NULL ((void*) 0)

NULL是指向地址0x0的指针,这个地址不能访问,所以以下程序会报错,但是避免了对内存造成不可预测的变化:

int* p = NULL;
printf("%d", *p);  // 报错

2.1.2 万能指针

指针包含内存块的首地址和长度,而万能指针void*只包含首地址,没有长度信息。万能指针和其他类型指针可以相互转换。

  1. 其他指针转换成万能指针

其他指针转换成万能指针,保留首地址信息,丢失内存块长度信息。万能指针无法使用间接运算符*

#include <stdio.h>
int main()
{
    int a = 10;
    int* p1 = &a;
    void* p2 = p1;
    
    printf("p1地址为%p\n", p1);     // p1地址为0x7ffc22d963bc
    printf("p1值为%d\n", *p1);      // p1值为10
    printf("p2地址为%p\n", p2);     // p2地址为0x7ffc22d963bc
    printf("p2值为%d\n", *p2);      // 报错
    
    return 0;
}
  1. 万能指针转换成其他指针
#include <stdio.h>
int main()
{
    int a = 10;
    void* p1 = &a;
    int* p2 = p1;
    
    printf("p1地址为%p\n", p1);     // p1地址为0x7fffaeb29a6c
    printf("p1值为%d\n", *p1);      // 报错
    printf("p2地址为%p\n", p2);     // p2地址为0x7fffaeb29a6c
    printf("p2值为%d\n", *p2);      // p2值为10
    
    return 0;
}

2.2 二级指针

指针可以指向一份普通类型的数据,例如 intdoublechar 等,也可以指向一份指针类型的数据,例如 int*double*char* 等。

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

int a =100;
int* p1 = &a;
int** p2 = &p1;

C语言不限制指针的级数,实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。想要获取指针指向的数据时,一级指针加一个*,二级指针加两个*,以此类推:

#include <stdio.h>
int main()
{
    int a =100;
    int *p1 = &a;    
    int **p2 = &p1;    
    
    printf("%d, %d, %d, %d\n", a, *p1, **p2, ***p3);    
    printf("&p2 = %#X, p3 = %#X\n", &p2, p3);    
    printf("&p1 = %#X, p2 = %#X, *p3 = %#X\n", &p1, p2, *p3);    

    return 0;
}

2.3 指针数组

如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:

int* p[n];
int* p[];    // 不指定长度
#include <stdio.h>
int main()
{
    int a = 16, b = 932, c = 100;    //定义一个指针数组
    int* arr[3] = {&a, &b, &c};     //也可以不指定长度,直接写作 int *arr[]    
    return 0;
}

2.4 数组指针

如果一个指针指向了数组,我们就称它为数组指针。声明一个指向int[n]型数组的指针为:

int (*p)[n];

理解二维数组a[n][m],他是一个int[n]型数组,有n个元素,每个元素又是int[m]型数组。而数组名a指向数组第一个元素的地址,所以数组名a是一个int[m]型数组的地址。所以如下赋值成立。

int a[n][m]int (*p)[m] = a;

对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的内存块有关,p 指向的数据类型是int[m],那么p+1就前进sizeof(int)*m个字节,p-1就后退 sizeof(int)*m 个字节,这正好是数组 a 所包含的每个一维数组int[m]的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。可以得到如下等效关系。

&a[i] == a+i == &p[i] == p+i;
a[i] == p[i] == *(a+i) == *(p+i);
&a[i][j] == a[i]+j == &p[i][j] == p[i]+j;
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*p+i)+j);

理解以上等式,核心是:指针在进行计算时,要带入指针指向的内存块的首地址和长度
第一行,a的内存块是a[0],首地址是a[0][0],长度是int[m],偏移i后首地址是是a[i][0],长度是int[m],所以a+i的内存块是a[i]
第三行,a[i]的内存块是a[i][0],长度是int,偏移j后内存块是a[i][j]

2.5 指针函数

指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。

int* fun(int x,int y);

和一般函数在返回值类型后面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int* 类型的指针,是一个地址。

/***** 示例程序 *****/
#include <stdio.h>

int* GetDate(int wk,int dy);     //声明 int* 型指针函数

int main()
{
    int week = 2;
    int day = 5;
    
    printf("%d\n", *GetDate(week,day));    // *p 得到值

    return 0;
}

int* GetDate(int wk,int dy)
{
    static int calendar[5][7]=
    {
        {1,2,3,4,5,6,7},
        {8,9,10,11,12,13,14},
        {15,16,17,18,19,20,21},
        {22,23,24,25,26,27,28},
        {29,30,31,-1}
    };
    return &calendar[wk-1][dy-1];      //返回 &int
}


/***** 示例结果 *****/
12

2.6 函数指针

函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。

int (*pfun)(int x,int y);

函数指针是需要把一个函数的地址赋值给它,有两种写法:

pfun = &fun;
pfun = fun;

取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。

调用函数指针的方式也有两种:

x = (*pfun)();
x = pfun();

两种方式均可。

建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。

第二种方式相当于把函数fun的标识符换成了pfun,改了个名字。

/***** 示例程序 *****/
#include <stdio.h>

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

int main()
{
    int x, y, maxval;
    int (*pmax)(int a, int b) = max;
    
    printf("Input two numbers:");    
    scanf("%d %d", &x, &y);    
    maxval = (*pmax)(x, y);    
    printf("Max value: %d\n", maxval);
    
    return 0;
}


/***** 示例结果 *****/
Input two numbers:10 50
Max value: 50

>>返回AUTOSAR系列文章目录<<

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛应用于计算机科学和软件开发的编程语言。它具有强大的功能和灵活性,适用于开发各种类型的应用程序。 C语言专题精讲是一个对C语言进行深入学习和讲解的系列文章或课程。它汇总了C语言相关的重要知识点和技巧,旨在帮助学习者更好地理解和运用C语言。 这个专题中的笔记涵盖了C语言的各个方面,包括基本语法、数据类型、运算符、流程控制、函数、数组、指针、结构体、文件操作等。通过系统性的学习和总结,这些笔记可以帮助学习者逐步掌握C语言的核心概念和常用技巧。 在这个专题中,学习者可以学到如何编写简单的C程序,如何使用变量和运算符进行计算,如何使用条件和循环语句控制程序流程,如何使用函数进行代码的模块化,如何使用数组和指针进行数据的处理,如何使用结构体组织复杂数据,如何进行文件的读写等等。 C语言专题精讲的目的是帮助学习者全面、深入地了解C语言的各个方面,并能够独立编写和调试简单到中等难度的C程序。通过反复实践和练习,学习者可以逐渐提高自己的编程能力,并为进一步学习更高级的编程语言打下坚实的基础。 总之,C语言专题精讲的笔记汇总是一份重要的学习资料,可以帮助学习者系统地学习和掌握C语言的基础知识和常用技巧,为他们未来的编程之路打下坚实的基石。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值