草庐IT

筑基九层 —— 指针详解

要一杯卡布奇诺 2023-04-05 原文

目录

前言:

指针详解


前言:

1.CSDN由于我的排版不怎么好看,我的有道云笔记比较美观,请移步有道云笔记

2.修炼必备

  1)入门必备:VS2019社区版,下载地址:Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com)

  2)趁手武器:印象笔记/有道云笔记

  3)修炼秘籍:牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网 (nowcoder.com)

  4)雷劫必备:leetcode 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 

  注:遇到瓶颈怎么办?百度百科_全球领先的中文百科全书 (baidu.com)

指针详解

        ——指针是C语言中最核心的部分,不了解指针,就掌握不了C语言的精髓

1.指针是什么?

1)指针是内存中一个最小单元的编号,即指针就是地址

2)指针变量是用来存放内存地址的变量

        图解:

2.我们如何取出变量的地址?

        ——使用&符号取出变量的地址,使用相应数据类型的指针变量保存该地址

#include <stdio.h>

int main()
{
    int num = 10;
    int* p = &num;//取出了num的地址赋给了p
    
    //打印查看地址
    printf("&num = %p\n", &num);
    printf("p = %p\n", p);
    return 0;
}

         运行结果:

3.指针的大小

        ——32位平台下的指针大小是4个字节,64位的平台下指针的大小是8个字节

#include <stdio.h>

int main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(long*));
    printf("%d\n", sizeof(long long*));
    printf("%d\n", sizeof(float*));
    printf("%d\n", sizeof(double*));
    return 0;
}

        32位平台运行结果:

        64位平台运行结果: 

 

4.指针和指针类型

        1)指针的定义的方式

a.指针的定义方式:数据类型*;

        char*:字符指针

        int*:整型指针

        long long*:长长整型指针

        float*:单精度指针

        double*:双精度指针

b.一般情况下,那种类型的指针则存储那种类型变量的地址

#include <stdio.h>

int main()
{
    char c = 'a';
    int num = 10;
    float f = 1.342;
    double data = 13.14;
    
    //一般情况,那种类型的指针变量存储那种类型变量的地址
    char* ch = &c;
    int* p = &num;
    float* p1 = &f;
    double* p2 = &data;
    return 0;
}

        2)指针的类型决定了指针向前或向后走一步有多大的字节距离

#include <stdio.h>

int main()
{
    char c = 'c';
    int num = 10;
    
    char* p1 = &c;
    int* p2 = &num;
    
    printf("%p\n", p1);
    printf("%p\n", p1+1);
    printf("%p\n", p2);
    printf("%p\n", p2+1);
    return 0;
}

       运行结果:

        3)指针的类型决定了指针在解引用的时候能操作几个字节【访问权限多大】 

#include <stdio.h>

int main()
{
    int num = 0x44332211;
    
    char* p1 = &num;
    int* p2 = &num;
    printf("%d\n", *p1);
    printf("%d\n", *p2);
    
    *p1 = 0;
    *p2 = 0;
    return 0;
}

        调试查看结果:

5. 野指针的问题

野指针就是指针指向的位置是不可知的【随机、不正确、无限制】

        1)指针未初始化

#include <stdio.h>

int main()
{
    int* p;
    printf("%p\n", p);
    return 0;
}

        运行结果: 

        2)指针越界访问 

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    
    for (int i = 0; i <= 11; i++)
    {
        //p的访问范围超过了数组的下标范围,p就是野指针
        printf("%d ", *p);
        p++;
    }
    return 0;
}

        3)返回局部变量的地址

#include <stdio.h>
int test()
{
    int a = 10;
    return &a;
}

int main()
{
    int *p = test();
    return 0;
}

        4)指针指向的空间未释放

6.防止野指针的问题

1)指针初始化

2)小心指针越界

3)指针指向空间释放,及时置为NULL

4)避免返回局部变量的地址

5)使用指针之前检查指针的有效性

#include <stdio.h>

void test(int* p)
{
    //检查指针的有效性
    if(p == NULL){}
}

int main()
{
    int* p = NULL;//不知道指针指向哪里的时候置为NULL
    test(p);
    return 0;
}

 

7.指针运算

        1)指针+-整数

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p;
    for (p = arr; p < &arr[10]; p++)
    {
        printf("%d ", *p);
    }
    return 0;
}

        2)指针-指针【地址-地址】

两个指针指向的是同一块空间且类型是一致的,两者相减得到是元素个数

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p1 = arr;
    int* p2 = &arr[10];
    
    //指针-指针
    printf("%d\n", p2 - p1);//10
    return 0;
}

        3)指针的关系运算

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p;
    for (p = &arr[10]; p > &arr[0];)
    {
        *--p = 0;
    }
    return 0;
}

        为什么这种方法也可以,但是不使用呢?

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p;
    for (p = &arr[9]; p >= &arr[0]; p--)
    {
        *p = 0;
    }
    return 0;
}

C语言中的标准规定,允许指向数组元素的指针能与指向数组最后一个元素的后面那个内存位置的指针比较,但不允许与数组元素第一个元素前面的那一个内存地址的指针比较

8.指针与数组

        1)数组名是数组首元素的地址,&数组名是取出整个数组的地址

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    
    printf("arr = %p\n", arr);
    printf("arr + 1 = %p\n", arr + 1);
    printf("&arr = %p\n", &arr);
    printf("&arr + 1 = %p\n", &arr + 1);
    return 0;
}

         运行结果:

        2)指针能指向数组的任意一个元素,即数组每个元素的地址指针均能获取 

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    
    for (int i = 0; i < 10; i++)
    {
        printf("%p == %p\n", &arr[i], p+i);
    }
    return 0;
}

        运行结果:

9.二级指针【指针的指针】 

int num = 10;

int* p = &num;//*p代表这是指针,int表示指向的类型是int类型

int**pp = &p;//*pp代表是指针,int*表示指向的类型是int*类型

#include <stdio.h>

int main()
{
    int num = 10;
    int* p = &num;//*p表示这是一个指针,指向的类型是int
    int** pp = &p;//*pp表示这是一个指针,指向的类型是int*
    
    printf("%d\n", num);//10
    printf("%d\n", *p);//10
    printf("%d\n", **pp);//10
    return 0;
}

 

10.字符指针的使用方式

1)字符指针指向一个字符变量

2)字符指针指向一个常量字符串【常量字符串的首元素地址赋给字符指针】

#include <stdio.h>

int main()
{
    char ch = 'a';
    //字符指针指向一个字符变量
    char* p1 = &ch;
    printf("%c\n", *p1);
    
    //字符指针指向一个常量字符串
    char* p2 = "abcdef";
    printf("%c\n", *p2);
    printf("%s\n", p2);
    return 0;
}

        运行结果: 

        一道简单的笔试题:

#include <stdio.h>

int main()
{
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    const char* arr1 = "abcdef";
    const char* arr2 = "abcdef";
    
    if (str1 == str2)
    {
        printf("str1 and str2 are same\n");
    }
    else
    {
        printf("str1 and str2 are not same\n");
    }
    
    if (arr1 == arr2)
    {
        printf("arr1 and arr2 are same\n");
    }
    else
    {
        printf("arr1 and arr2 are not same\n");
    }
    return 0;
}

        运行结果:

 为什么?当const char*是存储字符串常量的时候,两个指针均指向字符串常量首元素地址

11.指针数组【数组】

整型数组:数据是整型的数组

浮点数组:数据是浮点型的数组

指针数组:数据是指针的数组【即数组中的元素是指针类型】

        图解三种数组类型:

#include <stdio.h>

int main()
{
    int arr1[5] = { 1,2,3,4,5 };
    int arr2[5] = { 2,3,4,5,6 };
    int arr3[5] = { 3,4,5,6,7 };
    
    int* arr[3] = { arr1,arr2,arr3 };//存储了三个数组的首元素地址
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            printf("%d ", *(arr[i] + j));//arr[i][j]
        }
        printf("\n");
    }
    return 0;
}

        运行结果:

12.数组指针【指针】

        ——指向数组的类型 (*p)[大小] = &所指数组名

int num = 10; int* p = &num;//指向int的指针

double data = 13.14; double* p = &data;//指向double的指针

int arr[10]; int (*p)[10] = &arr;//指向数组的指针

        ——如何判断数组指针

()的结合性比[]高,所以先与*()里面的*号结合,再与[]结合 -> 指针

#include <stdio.h>

void printArr(int(*p)[5], int row, int col)
{
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            //printf("%d ", p[i][j]);
            //printf("%d ", *(*(p + i) + j));
            printf("%d ", *(p[i] + j));
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    int row = sizeof(arr) / sizeof(arr[0]);
    int col = sizeof(arr[0]) / sizeof(arr[0][0]);
    printArr(arr, row, col);
    return 0;
}

 

13.数组传参和指针传参

        1)一维数组传参

#include <stdio.h>

//一维数组传参
//传参方式1
void test(int arr[]){}
//传参方式2
void test1(int arr[10]){}
//传参方式3
void test2(int* arr){}

//一维指针数组传参
//传参方式1
void demo1(int* p[10]){}
//传参方式2
void demo2(int** arr){} 
//解释:*arr接收str数组,另一个*表示元素是指针

int main()
{
    int arr[5] = { 0 };
    test(arr);
    test1(arr);
    test2(arr);
    
    //一维指针数组传参
    int* str[10] = { 0 };
    demo1(str);
    demo2(str);
    return 0;
}

        2)二维数组的传参方式

#include <stdio.h>

//传参方式1
void test(int arr[3][3]){}
//传参方式2
void test1(int arr[][3]){}
//传参方式3
void test2(int(*arr)[3]){}

int main()
{
    int arr[3][3] = { 0 };
    test(arr);
    test1(arr);
    test2(arr);
    return 0;
}

        3)一级指针传参

#include <stdio.h>
//传参方式1
void test(int* p){}
//传参方式2
void test1(int* *p){}

int main()
{
    int* p = NULL;
    test(p);
    return 0;
}

        4)二级指针传参

#include <stdio.h>

void test(int** p) {}

int main()
{
    int** p = NULL;
    test(p);
    return 0;
}

14.函数指针

        ——指向的函数返回值 (*p)(指向函数的形参) = &所指函数名

int* p; //指向int的指针

double* p; //指向double的指针

int (*p)(int,int);//指向函数的指针

//解答:*和p结合,说明是指针,然后和(int,int)结合,说明是函数,int是返回值

#include <stdio.h>

int add(int x, int y)
{
    return x + y;
}

int main()
{
    //函数是有地址的
    //printf("%p\n", &add);
    
    int (*p)(int, int) = &add;
    int ret = p(5, 3);
    printf("%d\n", ret);//8
    return 0;
}

         思考以下代码

//代码1

(*(void (*)())0)();

//代码2

void (*signal(int , void(*)(int)))(int);

解释
代码1:把0强制转为为void(*)()的函数指针,在0地址处有一个函数,
函数无返回值,无形参,这个地方表调用

代码2:函数名是signal,参数为int和函数指针void(*)(int),
signal的返回类型是函数指针,该函数指针的函数返回值是void,参数是int

 

15.函数指针数组

        ——把函数地址存储在数组里面,这个数组就叫做函数指针数组

int (*p[])(形参);

        ——使用途径:转移表

#include <stdio.h>

int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}

void menu()
{
    printf("******************************\n");
    printf("****   1. add    2.sub   *****\n");
    printf("****   3. mul    4.div   *****\n");
    printf("****   0. exit           *****\n");
    printf("******************************\n");
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    //转移表 - 函数指针的数组
    int (*pfArr[])(int, int) = { NULL, Add, Sub, Mul, Div };
                //0    1    2    3    4

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input == 0)
        {
            printf("退出计算器\n");
            break;
        }
        else if (input >= 1 && input <= 4)
        {
            printf("请输入两个操作数:>");
            scanf("%d %d", &x, &y);
            ret = pfArr[input](x, y);
            printf("%d\n", ret);
        }
        else
        {
            printf("选择错误\n");
        }
    } while (input);
    return 0;
}

有关筑基九层 —— 指针详解的更多相关文章

  1. ruby 变量作为同一对象(指针?) - 2

    >>a=5=>5>>b=a=>5>>b=4=>4>>a=>5如何将“b”设置为实际的“a”,以便在示例中,变量a也将变为4。谢谢。 最佳答案 classRefdefinitializeval@val=valendattr_accessor:valdefto_s@val.to_sendenda=Ref.new(4)b=aputsa#=>4putsb#=>4a.val=5putsa#=>5putsb#=>5当您执行b=a时,b指向与a相同的对象(它们具有相同的object_id).当你执行a=some_other_thing时,a将指向

  2. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  3. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  4. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  5. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  6. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

  7. IDEA 2022 创建 Spring Boot 项目详解 - 2

    如何用IDEA2022创建并初始化一个SpringBoot项目?目录如何用IDEA2022创建并初始化一个SpringBoot项目?0. 环境说明1.  创建SpringBoot项目 2.编写初始化代码0. 环境说明IDEA2022.3.1JDK1.8SpringBoot1.  创建SpringBoot项目        打开IDEA,选择NewProject创建项目。        填写项目名称、项目构建方式、jdk版本,按需要修改项目文件路径等信息。        选择springboot版本以及需要的包,此处只选择了springweb。        此处需特别注意,若你使用的是jdk1

  8. ruby - 对象分配和指针 - 2

    我对Ruby中的对象分配和指针有点困惑,编写了这段代码来测试我的假设。classFooattr_accessor:one,:twodefinitialize(one,two)@one=one@two=twoendendbar=Foo.new(1,2)beans=barputsbarputsbeansbeans.one=2putsbarputsbeansputsbeans.oneputsbar.one我曾假设,当我将bar分配给beans时,它会创建该对象的副本,并且修改一个不会影响另一个。唉,输出显示不是这样。^_^[jergason:~]$rubytest.rb####22我相信这些

  9. ruby - 为什么 Gosu 隐藏我的鼠标指针? - 2

    我正在使用Gosugem进行一些图形编程。问题是,当我创建一个窗口时,我的鼠标指针被隐藏了。我可以猜到鼠标在某个时刻的位置,我可以凭直觉点击,但我的用户可能不会。如何显示指针? 最佳答案 如果你想使用系统光标你可以这样做classWindow查看libgosu的文档RubyGosurdocReference/Window 关于ruby-为什么Gosu隐藏我的鼠标指针?,我们在StackOverflow上找到一个类似的问题: https://stackoverf

  10. 详解Unity中的粒子系统Particle System (二) - 2

    前言上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解。从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状态和全局属性的,以下是关于该模块的介绍,请大家指正。目录前言本系列提要一、粒子系统主模块1.阅读前注意事项2.参考图3.参数讲解DurationLoopingPrewarmStartDelayStartLifetimeStartSpeed3DStartSizeStartSize3DStartRotationStartRotationFlipRotationStartColorGravityModif

随机推荐