【指针】

C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:
1.统一数据的调用方式,因为指针是调用数据的中间层,修改指针即可调用不同的数据,当代码需要在不同情况下调用不同数据时,可以使用指针统一调用,只需要在不同情况下修改指针为不同的内存地址即可,无需编写多组代码分别调用这些数据,当数据需要在多处代码中同时调用、并在不同情况下同时修改时,使用指针的优势更明显,修改了指针就等于修改了所有使用指针调用数据的代码。
2.突破编译器限制,使用指针调用数据时编译器不会进行限制,只要操作系统不禁止即可,可以通过此特性绕过编译器的某些限制,比如在一个函数内调用另一个函数的局部数据。
3.接收未知的内存地址,比如向操作系统申请内存,比如调用动态链接库成员。

指针的长度在不同计算机中是不同的,在32位处理器程序中指针是无符号4字节整数,在64位处理器程序中指针是无符号8字节整数。
依据指向数据的类型可以将指针分为多种,指针用于存储哪种类型数据的地址就属于哪种类型的指针,编译器通过指针类型确定要操作多少个内存单元,int类型指针操作4个内存单元。

#include <stdio.h>
int main()
{
    int a = 9;
    int * p1 = &a;    //int指定指针类型,*符号表示定义指针,p1为指针名,使用&符号提取一个数据的地址进行赋值,注意这里的&符号并非表示与运算
    
    printf("变量a的值为:%d\n", *p1);     //使用 *p1 调用指针指向的数据
    printf("变量a的地址为:%p\n", p1);    //使用 p1 调用指针本身
    
    p1 = 0;         //指针不再使用后修改为0,避免错误调用
    
    return 0;
}

使用指针可以随意调用数据,数据调用方式更灵活,灵活的代价是容易出错,使用指针时应该做到代码严谨,同时定义指针暂时不使用时应该将其赋值为0,防止直接使用未赋值的指针,若指针占用的内存原有数据可以当做合规的内存地址使用,将会使用一个未知的内存地址,另外指针不再使用后也应该修改为0。

多重指针

指针也可以存储另一个指针的地址,相当于多层间接寻址。

#include <stdio.h>
int main()
{
    int a = 5;
    int * p1 = &a;      //指针
    int ** p2 = &p1;    //指针的指针
    int *** p3 = &p2;   //三重指针,很少使用
    
    printf("p2存储的数据为:%p\n"
        "p2指向的数据为:%p\n"
        "p2最终指向的数据为:%d\n",
        p2, *p2, **p2);
    
    return 0;
}

指针变量、指针常量

1.指针变量,指针本身是变量,赋值后可以修改,可以存储变量、常量的地址。
2.指针常量,指针本身是常量,赋值后不能修改,可以存储变量、常量的地址。
3.变量指针,指向变量的指针,存储变量的地址,可以通过指针修改指向的数据,不能存储常量的地址,但编译器默认只是给一个警告,不会禁止。
4.常量指针,指向常量的指针,存储常量的地址,不能通过指针修改指向的数据,也可以存储变量的地址,此时指针将变量认为是常量,并且不能通过指针修改它,若一个指针即需要指向变量也需要指向常量、同时不需要修改指向的数据,可以将其定义为常量指针。

#include <stdio.h>
int main()
{
    int a = 0;
    const int b = 9;
    
    int * const p1 = &a;    //const在指针名之前,定义指针常量
    const int * p2 = &b;    //const在类型名之前(或类型名与*符号之间),定义常量指针
    
    const int * const p3 = &b;    //组合使用
    
    return 0;
}

指针作为数组元素

#include <stdio.h>
int main()
{
    int i1=1, i2=2, i3=3, i4=4, i5=5;
    int * p1[5] = {&i1, &i2, &i3, &i4, &i5};    //p1为存储指针的数组,元素为int类型指针
    printf("%d\n", *p1[0]);                     //调用数组第一个元素指向的数据
    
    char * p2[2] = {"阿狸", "喜羊羊"};     //可以使用数组为p2赋值,编译器自动取每个数组的地址作为p2元素的值
    printf("%s\n%s\n", p2[0], p2[1]);    //输出p2两个元素指向的字符串
    
    return 0;
}

● restrict关键词

定义指针时添加restrict关键词表示代码可以保证指针指向的数据不会通过其他指针或者数据名进行修改,只会通过此指针修改,让编译器放心进行高效优化。

int * restrict p1;

【数组指针】

数组指针表示存储数组地址的指针,数组第一个元素的地址就是数组的地址,可以使用数组首元素的指针表示数组指针。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", *p1);
        p1++;                   //指针加1,指针记录的地址增加数据类型长度,这里的int类型指针会+4,定位到下一个元素
    }
    
    return 0;
}

数组指针可以使用下标的方式调用数组元素,原理与使用数组名相同,“指针地址+下标*数组元素长度”得出数组元素地址。

#include <stdio.h>
int main()
{
    /* 单层指针 */
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    /* 遍历数组 */
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", p1[i]);    //将指针当做数组名使用,使用下标的方式调用数组元素
    }
    
    return 0;
}

数组指针还有另一种定义方式,它记录了数组长度信息,用于对指针赋值时进行检查,若赋值为长度不同的数组的地址则会发出警告,但默认不会禁止编译,这个长度信息只在编译器中记录,编译后的程序并没有记录此信息。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int (*p1)[5] = &a;         //p1为数组指针的名称,只能赋值为5个int类型元素数组的地址
    
    printf("数组第一个元素的值:%d\n", (*p1)[0]);    //调用指针指向的数据
    printf("数组第一个元素的地址:%p\n", p1);        //调用指针存储的地址
    
    return 0;
}

【结构体指针】

#include <stdio.h>
int main()
{
    struct zoo
    {
        char name[20];
        int age;
    } ali = {"阿狸", 8};
    
    struct zoo * p1 = &ali;    //定义结构体指针,只能赋值为同类型结构体实例的地址
    
    printf("%s:%d岁\n", p1->name, p1->age);        //使用->符号调用结构体成员
    printf("%s:%d岁\n", (*p1).name, (*p1).age);    //另一种使用方式
    
    return 0;
}

共用体指针使用方式与结构体指针相同,不再重复介绍。

结构体成员指针

#include <stdio.h>
int main()
{
    struct zoo
    {
        char name[20];
        int age;
    } ali = {"阿狸", 8};
    
    char * p1 = &ali.name[0];
    int * p2 = &ali.age;
    
    printf("%s:%d岁\n", p1, *p2);
    
    return 0;
}

【指针运算】

指针变量可以进行数学运算,不同类型的指针运算结果不同,运算规则如下:
1.单数据指针,指针+1,指针本身增加数据的长度,定位到下一个同类型数据,比如int类型指针+1等于指针值+4,减法同理。
2.数组指针,指针+1,指针本身增加数组元素的长度,定位到数组下一个元素。
3.结构体指针,指针+1,指针本身增加结构体的长度,注意结构体长度需要额外计入成员地址对齐占用的存储空间。
4.函数指针,不支持数学运算。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    /* 遍历数组 */
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", *p1);    //输出
        p1++;                   //每次循环指针+1,定位到下一个元素
    }
    
    return 0;
}

数组元素指针相减

指向同一个数组不同元素的指针之间可以进行减法运算,编译器会转换为计算两个指针指向元素的下标相减。

#include <stdio.h>
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int * p1 = &a[5];
int * p2 = &a[8]; printf("%lu\n", p2 - p1); //p2-p1 = 8-5,输出3 return 0;
}

【指针类型转换】

指针类型可以使用代码转换,编译器使用转换后的类型操作内存地址、进行指针运算,转换语法如下:(类型名*)指针名。
指针类型转换只存在于转换运算式中,不影响指针原来的类型,若需永久转换类型可以使用另一个指针接收转换后的指针。

指针类型可以随意转换,编译器不做限制,若一个未知数据需要通过指针逐个操作其中每个字节,可以将指针转换为char类型,之后即可按字节读写其中的数据。

#include <stdio.h>
int main()
{
    int a = 257;
    int *p1 = &a;
    printf("%d\n", *(char*)p1);    //int指针转换为char类型,只调用第一个字节,257转二进制 = 0001 0000 0001,低8位为1,输出1
    
    return 0;
}

结构体指针类型转换

#include <stdio.h>
int main()
{
    struct k1
    {
        char c[4];
        //......
    } ali = {"ali"};
    
    struct k2
    {
        int i;
        //......
    };
    
    struct k1 * p1 = &ali;
    struct k2 * p2 = (struct k2 *)p1;    //p1转换为k2类型
    
    printf("0x%x\n", p2->i);
    
    return 0;
}

空类型指针

指针类型可以定义为void,表示类型为空,空类型指针可以存储任何类型数据的地址。
空类型指针不能直接使用,需要首先转换为具体的类型再使用,否则编译器无法确定要操作多少内存单元。

当你需要接收一个指针但又不确定它的类型时,可以定义一个空类型指针接收,之后再转换为具体类型使用。

#include <stdio.h>
int main()
{
    int a = 9;
    
    int * p1 = &a;
    void * p2 = p1;    //空类型指针
    
    //printf("%d\n", *p2);          //错误,不能使用
    printf("%d\n", *(int *)p2);     //正确,转换为int类型再使用
    
    return 0;
}

06. C语言指针的更多相关文章

  1. C语言指针转换为intptr_t类型

    1.前言 今天在看代码时,发现将之一个指针赋值给一个intptr_t类型的变量.由于之前没有见过intptr_t这样数据类型,凭感觉认为intptr_t是int类型的指针.感觉很奇怪,为何要将一个指针 ...

  2. [转]C语言指针学习经验总结浅谈

    指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...

  3. 不可或缺 Windows Native (7) - C 语言: 指针

    [源码下载] 不可或缺 Windows Native (7) - C 语言: 指针 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 指针 示例cPointer.h #i ...

  4. C语言指针学习

    C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...

  5. (转载)c语言指针学习

    前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...

  6. 关于C语言指针的问题

    在学习关于C语言指针的时候,发现这样一个问题,代码如下: #include<stdio.h> #include<stdlib.h> #include<string.h&g ...

  7. C语言指针类型 强制转换

    关于C语言指针类型 强制转换  引用一篇文章: C语言中,任何一个变量都必须占有一个地址,而这个地址空间内的0-1代码就是这个变量的值.不同的数据类型占有的空间大小不一,但是他们都必须有个地址,而这个 ...

  8. C语言指针和数组知识总结(上)

    C语言指针和数组知识总结(上) 一.指针的基础 1.C语言中,变量的值能够通过指针来改变,打印指针的语句符号可以是:  %08x 2.指针的本质 指针的本质就是变量,那么既然是变量,那么一定会分配地址 ...

  9. C语言指针操作

    欢迎访问我的新博客:http://www.milkcu.com/blog/ 原文地址:http://www.milkcu.com/blog/archives/pointer-manipulation. ...

  10. C语言指针声明探秘

    C语言指针声明探秘

随机推荐

  1. 高德地图和echarts结合实现地图下钻(二)

    一.学习ajax发送异步请求 1 $(function(){ 2 //请求参数 3 var list = {}; 4 // 5 $.ajax({ 6 //请求方式 7 type : "POS ...

  2. 【已解决】Exception in thread "main" java.lang.RuntimeException: java.net.ConnectException

    没有启动hadoop集群

  3. .Net Core AutoFac 使用方法讲解大全,具体详细使用知识总结

    AutoFac 具体使用知识总结 阅读前提示 AutoFac 只是众多IOC框架的其中一种, 比较主流的有Unity.autofac.spring.net.MEF.Injection.Asp.Net ...

  4. 优先队列(PriorityQueue)

    > 此代码是在最大堆的基础上二次封装,请先阅读底层代码MaxHeap 优先队列 普通队列:先进先出:后进后出 优先队列:出队顺序和⼊入队顺序无关:和优先级相关: 为什么使用堆 代码清单 Queu ...

  5. CSP2019-S2总结

    目录 前言 洛谷 5657 格雷码 代码(找规律) 洛谷 5658 括号树 分析 代码 洛谷 5659 树上的数 洛谷 5664 Emiya 家今天的饭 洛谷 5665 划分 分析 代码 洛谷 566 ...

  6. 面向OpenHarmony终端的密码安全关键技术

      本文转载自 OpenHarmony TSC 官方微信公众号<峰会回顾第17期 | 面向OpenHarmony终端的密码安全关键技术> 演讲嘉宾 | 何道敬 回顾整理 | 廖   涛 排 ...

  7. 灵活配置 Spring 集合:List、Set、Map、Properties 详解

    使用<property>标签的value属性配置原始数据类型和ref属性配置对象引用的方式来定义Bean配置文件.这两种情况都涉及将单一值传递给Bean 那么如果您想传递多个值,例如Jav ...

  8. 如何在现实场景中随心放置AR虚拟对象?

    随着AR的发展和电子设备的普及,人们在生活中使用AR技术的门槛降低,比如对于不方便测量的物体使用AR测量,方便又准确:遇到陌生的路段使用AR导航,清楚又便捷:网购时拿不准的物品使用AR购物,体验更逼真 ...

  9. 使用 K8S 部署 RSS 全套自托管解决方案- RssHub + Tiny Tiny Rss

    前言 什么是 RSS? RSS 是一种描述和同步网站内容的格式,是使用最广泛的 XML 应用.RSS 搭建了信息迅速传播的一个技术平台,使得每个人都成为潜在的信息提供者.发布一个 RSS 文件后,这个 ...

  10. Docker安装使用--Centos

    目录 前言 Docker安装使用 安装步骤 卸载旧版本 安装utils 配置utils的存储库 安装Docker 安装指定版本Docker 验证安装 官网安装说明 阿里云镜像加速 Docker使用 帮 ...