C语言—>指针

  • 当两个指针 p1, p2相减时, p2-p1就是从 p1p2,不包含 p2的元素个数,结果的类型是 ptrdiff_t
#include
int main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    int sub;
    int *p1 = &a[2];
    int *p2 = &a[8];

    sub=p2-p1;
    printf("%d\n",sub);    // 输出结果为 6

    return 0;
}

先来定义如下的二维数组:

   int a[3][4] =
    {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}
    };

a的意义

首先,对于一个数组而言,数组名就是该数组的首地址。

首地址:一段存储空间中的第一个存储单元的地址

所以对于这个二维数组 a[3][4],数组名 a指向的就是第一个数组,用如下代码可以进行验证:

    printf("a=%p\n", a);
    printf("a+1=%p\n", a + 1);
    printf("a+2=%p\n", a + 2);

输出如下:

a=000000C63835F628
a+1=000000C63835F638
a+2=000000C63835F648

可以看到,每 +1地址递增 16
a是数组名,是该数组的首地址,
指向该数组的第一个存储单元(一个一维数组),a类型为 int(*)[4]
所以 a+1 会跳到第二个数组,地址加上 16B

*a的意义

    printf("*a=%p\n", *a);
    printf("*a+1=%p\n", *a + 1);
    printf("*(a+1)=%p\n", *(a + 1));

输出如下:

*a=000000C63835F628
*a+1=000000C63835F62C
*(a+1)=000000C63835F638

可以看到,每 +1地址递增 4
*a指向以一个一维数组的首地址即

*a==a[0]==&a[0][0]

所以 *a+1,地址会偏移 4B,即指向下一个数据,

*a类型为 int*

*(a+1),地址会偏移 16B,即指向下一个一维数组的首地址。

&a的意义

    printf("&a=%p\n", &a);
    printf("&a+1=%p\n", &a + 1);
    printf("&(a+1)=ERORR\n");

输出如下:

&a=000000C63835F628
&a+1=000000C63835F658
&(a+1)=ERORR

&a指向整个二维数组,是取这个二维数组的地址。

&a类型为 int(*)[3][4]

&a+1 地址偏移了 48B,跳过了整个二维数组

&a[0]的意义

    printf("&a[0]=%p\n", &a[0]);
    printf("&a[0]+1=%p\n", &a[0] + 1);
    printf("&a[0]+1=%p\n", &a[0]);
    printf("&(a[0]+1)=ERORR\n");

输出如下:

&a[0]=000000C63835F628
&a[0]+1=000000C63835F638
&a[0]+1=000000C63835F628
&(a[0]+1)=ERORR

&a[0]指向第一个数组,是取第一个数组的地址

&a[0]类型为 int(*)[4]

&a[0]+1 地址偏移了16B,跳过了第一个一维数组

a[0]的意义

    printf("a[0]=%p\n", a[0]);
    printf("a[0]+1=%p\n", a[0] + 1);
    printf("&a[0][0]%p\n", &a[0][0]);

a[0]是第一个数组的数组名,是第一个数组的首地址,即 a[0]指向指向第一个存储单元 a[0][0]

a[0]类型为 int*

a[0]+1,指向了第二个存储单元,地址偏移了 4B
&a[0][0],是指向 a[0][0]的指针,

&a[0][0]类型为 int*

指针数组

int *p[10]

[]的优先级比 * 高,故 p先与 [] 结合,成为一个数组 p[];再由 int *指明这是一个 int的指针。数组的第 i 个元素是 *p[i],而 p[i] 是一个指针。

数组指针

int (*p)[10]

由于 ()的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,这也是指针 p 的步长。也就是说,执行 p+1 时,p 要跨过 10int 型数据的长度。数组指针与二维数组联系密切,可以用数组指针来指向一个二维数组,如下:

#include

 int main()
    {
        int arr[2][3] =
            {
             {1,2,3},
             {4,5,6}
            };             // 定义一个二维数组并初始化

        int (*p)[3];       // 指针指向一个含有3个元素的一维数组

        p = arr;           // p 指向 arr[0]==&arr[0][0]
        printf("%d\n",(*p)[0]);  // 输出结果为 1
        p++; 
        printf("%d\n",(*p)[1]);  // 输出结果为5
        return 0;
    }

访问数组中的元素

    printf("a[i][j]\n");
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
            printf("%5d", a[i][j]);

        printf("\n");
    }
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
            printf("%5d", *(a[i] + j));
        printf("\n");
    }
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
            printf("%5d", *(*(a + i) + j));
        printf("\n");
    }

亦即 a[i][j]==*&a[i][j]==*(a[i]+j)==*(*(a+i)+j)

综合上面的分析,对于二维数组 a[3][4]有如下结论:

表一 表达式 数据类型 指向

均指向第一个一维数组

均指向第一个一维数组的第一个单元

指向整个二维数组

指向数组i的第一个存储单 表二 运算 意义

指向第一个一维数组,所以

地址偏移

指向第一个数组的第一个单元,所以

地址偏移

指向下一个数组,

所以a+1地址偏4×4=16B

指向整个二维数组,所以

地址偏移

数组名

是指针常量,不能更改了,此种写法错误

指向第一个数组,

,指向下一个数组,所以地址偏移

此种写法错误

指向第一个数组的首地址

,所以

指向下一个数据

,地址移

数组指针

类型 (*指针名)[N]; //N元素个数
数组指针是指向含 N 个元素的一维数组的指针。由于二维数组每一行均是一维数组,故通常使用指向一维数组的指针指向二维数组的每一行。

  • 注意: []运算的优先级高于 *int *p[N]为指针数组,每个元素类型为 int*
#include
int main()
{
    int a[3][4];
    int(*p)[4]=a;//a是首地址,指向一维数组,类型为int(*)[4]与p吻合
    //第i行首地址 p+i==a+i,其余操作与上文一直
}

指针数组

指针数组最主要的用途是处理字符串。在 C 语言中,一个字符串常量代表返回该字符串首字符的地址,即指向该字符串首字符的指针常量,而指针数组的每个元素均是指针变量,故可以把若干字符串常量作为字符指针数组的每个元素。通过操作指针数组的元素间接访问各个元素对应的字符串。

#include
#include
//#define NULL ( (void*) 0)
int main()
{
    char *c[]={"if","else","for","while",NULL};
    for(int i=0;c[i]!=NULL;i++)
        puts(c[i]);
    system("pause");
    return 0;
}
  1. 首地址:一段存储空间中的第一个存储单元的地址
  2. 分析指针:关键不在指针的值,而实指针的类型及其指向
  3. *a==a[0]==&a[0][0]
  4. 访问数组元素:

  5. 下标法

  6. 指针法
    *(a[i]+j)== *(*(a+i)+j)

结构指针是指向结构的指针,使用 -> 操作符来访问结构指针的成员。

#include
typedef struct{
    char name[10];
    int age;
    int score;
}message;
int main()
{

    message mess={"elio",18,92};
    message *p=&mess;
    printf("%s\n",p->name);//输出elio
    printf("%d\n",p->score);//输出92

    return 0;
}

C语言的所有参数均是以”传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

指针作为函数的参数

  • 传值调用:实参为要处理的数据,函数调用时,把要处理数据(实参)的一个副本复制到对应形参变量中,函数中对形参的所有操作均是对原实参数据副本的操作,无法影响原实参数据。且当要处理的数据量较大时,复制和传输实参的副本可能浪费较多的空间和时间。
  • 传址调用:顾名思义,实参为要处理数据的地址,形参为能够接受地址值的”地址箱”即指针变量。函数调用时,仅是把该地址传递给对应的形参变量,在函数体内,可通过该地址(形参变量的值)间接地访问要处理的数据,由于并没有复制要处理数据的副本,故此种方式可以大大节省程序执行的时间和空间。
  • 传值调用的好处是是被调函数不会改变调用函数传过来的值,可以放心修改。但是有时候需要被调函数回传一个值给调用函数,这样的话,传值调用就无法做到。为了解决这个问题,可以使用传指针调用。指针参数使得被调函数能够访问和修改主调函数中对象的值。
#include
void swap1(int a,int b) // 参数为普通的 int 变量
{
int temp;
temp = a;
a = b;
b = temp;
}
void swap2(int *a,int *b) // 参数为指针,接受调用函数传递过来的变量地址作为参数,对所指地址处的内容进行操作
{
int temp; // 地址本身并没有改变,地址所对应的内存段中的内容发生了变化
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int x = 1,y = 2;
swap1(x,y); // 将 x,y 的值本身作为参数传递给了被调函数
printf("%d %5d\n",x,y); // 输出结果为:1 2
swap(&x,&y); // 将 x,y 的地址作为参数传递给了被调函数,传递过去的也是一个值,与传值调用不冲突
printf("%d %5d\n",x,y); // 输出结果为:2 1
return 0;
}

指向函数的指针

指针做函数返回值 类型*函数名(形参)

有时函数调用结束后,需要函数返回给调用者某个地址即指针类型,以便于后续操作,这种函数返回类型为指针类型的函数,通常称为指针函数。在处理字符串中常见。

#include
#include
char *link(char*str1,char*str2);
int main()
{
    char s1[20]="Chinese";
    char s2[10]="Dream";
    char *p=link(s1,s2);
    puts(p);
    system("pause");
    return 0;
}
char *link(char*str1,char*str2)
{
    char*p1=str1;
    char*p2=str2;
    while(*p1!='\0')
        p1++;//结束时p1指向字符串str1的结尾
    *p1=' ';
    p1++;
    while(*p2!='\0')
    {
        *p1=*p2;
        p2++;
        p1++;//*p1++=*p2++
    }
    return str1;
}

指向函数的指针————函数指针

函数像其他变量一样,在内存中也占用一块连续的空 间,把该空间的起始地址称为函数指针。而函数名就是该空间的首地址,故函数名是常量指针。可把函数指针保存到函数指针变量中。

返回类型(*指针变量名)(函数参数表);
定义中,括号不能省略。

int *p1(int,int)//声明了函数原型,函数名为p1,含有俩int参数,返回值int*
int (*p2)(int,int)//定义了一个函数指针变量p2,p2指向任意含有俩int参数,返回值为整型的函数

定义如下函数

int f1(int a,int b)
{
    //...

}
p2=f1//p2=&f1;

在给函数指针变量赋值时,函数名前面的取地址操作符 & 可以省略。因为在编译时,C 语言编译器会隐含完成把函数名转换成对应指针形式的操作,故加 & 只是为了显式说明编译器隐含执行该转换操作。

当函数指针变量p2被初始化,指向f1之后,调用f1(),有以下几种方式

int res;
res=f1(a,b);
res=p1(a,b);
res=(*p1)(a,b)

下面的程序,是一个应用函数指针的例子。

#include
#include
void cal(void(*ptr)(int,int),int op1,int op2);
void add(int a,int b);
/* sub mult div */
int main()
{
    int num;
    int a,b;
    printf("Operation menu:\n");
//    printf("1 for add       2 for sub\n");
//    printf("3 for mult      4 for div\n");
    printf("Enter the operator:");
    scanf("%d",&num);
    printf("Input 2 numbers:\n");
    scanf("%d %d",&a,&b);
    switch (num){
    case 1:
        cal(add,a,b);
        break;
    /*codes*/
    default:
        printf("Input error!");
    }
    system("pause");
    return 0;
}
void cal(void(*ptr)(int a,int b),int op1,int op2){
    ptr(op1,op2);
}

Original: https://www.cnblogs.com/oasisyang/p/13218506.html
Author: OasisYang
Title: C语言—>指针

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585697/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 关于docker中容器可以Ping通外网,真机无法Ping通容器的问题

    首先我们要知道整体的框架结构,docker是我们安装在centos7上的,而centos7是安装在vmware上。其中docker中还有若干容器运行。 整体框架图如下: 我们将它分…

    Linux 2023年5月27日
    0174
  • 数据转换-整数字节数组

    任务详情 任务详情0. 在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务 1 参考《GMT 0009-2012 SM2密码算法使用规范》第6节&#…

    Linux 2023年6月8日
    0140
  • 【电子取证:FTK Imager篇】DD、E01系统镜像仿真

    星河滚烫,人生有理想!​—【suy999】 一、DD、E01系统镜像动态仿真 在电子取证分析过程中,我们经常遇到DD、E01等系统镜像,然而,并非所有工作者手边都有自动…

    Linux 2023年6月13日
    0110
  • linux安装Oracle11G

    1、Linux下以Oracle帐户进入Linux系统。 2、执行以下命令查看数据库监听器的状况: lsnrctl status 3、执行以下命令停止数据库监听器运行: lsnrct…

    Linux 2023年6月13日
    079
  • Git常用命令

    克隆拉取远程代码 git clone https://xxxxxxxxx 本地添加远程仓库地址 git remote add origin(设定名字,随意。不过一般都叫这个名字) …

    Linux 2023年6月8日
    091
  • Linux安装mysql8.0

    MySQL 配置MySQL8安装源 sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3….

    Linux 2023年6月6日
    097
  • 项目相关环境docker版安装教程总结

    项目环境docker及docker-compose文档 1、Linux环境介绍 centos7.6 16G以上内存空间(至少8G) 2、静态IP设置 1、找到配置文件 cd /et…

    Linux 2023年6月7日
    081
  • Linux网络服务之部署YUM仓库

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 1 YUM简介 1.1 YUM简介 CentOS使用yum和dnf 解决rpm的包依赖关系。 YUM:rpm的前端程序,可解决软…

    Linux 2023年5月27日
    0121
  • 【设计模式】Java设计模式-组合模式

    Java设计模式 – 组合模式 😄 不断学习才是王道🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆原创作品,更多关注我CSDN: 一个有梦有戏的人…

    Linux 2023年6月6日
    0122
  • redis

    常用操作 LLEN KEY_NAME # &#x8FD4;&#x56DE;&#x5217;&#x8868;&#x7684;&#x95…

    Linux 2023年5月28日
    0101
  • OSPF之Default-router-advertise 解析

    1、关于default-route-advertise命令 Ospf是可以通过import-route命令引入外部路由的,但很少有人会注意到,在默认情况下,ospf是不会引入来自外…

    Linux 2023年6月14日
    098
  • 001 研发同学必学哪些 Linux 命令?

    身为研发同学,Linux 是绕不过去的一个小山包,不是说要掌握的十分精通,在程序员界里做个极客,也不是说要抢了 Devops 同学的饭碗,但至少要做到摆脱对 Linux 命令认知的…

    Linux 2023年5月27日
    088
  • PHP array_values()

    array_values array_values() 函数返回一个包含给定数组中所有键值的数组,但不保留键名。 示例: function arrayValues() { $dat…

    Linux 2023年6月7日
    0103
  • mysql存储中文乱码

    表现 前端页面,封装到html里的中文可以显示,和后台相关的中文显示都是 ??? 。查看network请求,传参正常。 查看mysql编码,是utf8。再看连接数据库的JDBC设置…

    Linux 2023年6月8日
    089
  • 解决微信Windows客户端无法播放视频问题

    问题描述 我的Windows端微信版本是3.6.0,更新后点开视频,没有播放按钮出现,并且过一会就会卡死,并且整个微信程序崩掉。 问题解决 后来发现,是微信客户端的 播放器插件问题…

    Linux 2023年6月14日
    0375
  • 迭代

    1.迭代的概念: 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次”迭代”,而每一次迭代得到的结果会作为下一次迭代的…

    Linux 2023年6月8日
    0108
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球