筑基九层 —— 指针详解

news2024/11/25 13:38:27

目录

前言:

指针详解


前言:

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类

专栏&#xff1a;神经网络复现目录 本章介绍的是现代神经网络的结构和复现&#xff0c;包括深度卷积神经网络&#xff08;AlexNet&#xff09;&#xff0c;VGG&#xff0c;NiN&#xff0c;GoogleNet&#xff0c;残差网络&#xff08;ResNet&#xff09;&#xff0c;稠密连接网络…

【Hello Linux】进程控制 (内含思维导图)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念 进程控制介绍进程创建fork函数fork函数的返回值fork函数的使用…

利用Cookie劫持+HTML注入进行钓鱼攻击

目录 HTML注入和cookie劫持&#xff1a; 发现漏洞 实际利用 来源 HTML注入和cookie劫持&#xff1a; HTML注入漏洞一般是由于在用户能够控制的输入点上&#xff0c;由于缺乏安全过滤&#xff0c;导致攻击者能将任意HTML代码注入网页。此类漏洞可能会引起许多后续攻击&#…

高德地图绘制图层

效果图&#xff1a; //初始数据 data(){return{//地图map:{address:,map:null,//当前鼠标绘制mouseTool:null,//当前编辑polyEditor:null,//覆盖物的绘制点【用于编辑】mouseToolArr:[],//覆盖物的poly对象polyArr:[],//地图右侧功能按钮signNumber:0,//保存提交的覆盖物的点数…

win10添加右键菜单打开VSCode

当进入一个文件夹后&#xff0c;想右键直接打开我的工程&#xff0c;用发现没有vscode项。 我了方便&#xff0c;把 VSCode添加到右键菜单吧。 1. 在桌面新建一个txt文档&#xff0c;用文本编辑器打开 2. 查看vscode所在位置 在桌面找到vscode快捷键图标&#xff0c;右键--&g…

2023年考PMP真的有用吗?含备考资料分享~

对于项目管理者来说&#xff0c;是真的有用&#xff0c;前提是你真的学进去了&#xff0c;是为了学习而考&#xff0c;而不是为了考证而考&#xff0c;考试的作用不是为了让你得到证书&#xff0c;而是考校你的水平&#xff0c;知识是知识&#xff0c;经验是经验&#xff0c;缺…

【浅学Java】MySQL索引七连炮

MySQL索引面试七连炮0. 谈一下你对索引的理解1. MySQL索引原理和数据结构能介绍一下吗2. B树和B树的区别3. MySQL聚簇索引和非聚簇索引的区别4. 使用MySQL索引都有什么原则4.1 回表4.2 索引覆盖4.3 最左匹配4.4 索引下推5. 不同的存储引擎是如何进行数据的存储的6. MySQL组合索…

根据文件内容批量更改文件名称

注意的问题&#xff1a; ★★★待处理的文件顺序要与excel中新的文件名称顺序一致&#xff0c;我直接复制文件地址到excel中顺序与原来顺序不一样&#xff0c;也不能通过排序得到原来的顺序&#xff0c;这里给出一种解决办法&#xff0c;具体步骤见2数据预处理阶段。 1. 这是我…

最新版本OpenAI怎么调试--用Postman调试最新版OpenAI-API

动动小手指&#xff0c;去到openai的API介绍页面。 https://platform.openai.com/docs/api-reference/models 通过官网的提示&#xff0c;可以copy对应的调试命令进行测试。 本文主要通过curl命令实现。 打开Postman&#xff0c;对&#xff0c;就是那个测试接口用的postman ​…

浙江首场千人大会现场爆满!实在智能九哥专题演讲:企业数字化转型,从实在RPA开始!

为帮助众多电商商家探索数字时代下新赛道、新趋势、新方向&#xff0c;制定有目标、有节奏的全年生意规划&#xff0c;“未来电商高峰论坛暨电商生态赋能大会”于3月4日在杭州正式拉开序幕。本次大会旨在向品牌电商企业主、运营操盘手分享数字电商时代的黄金趋势及运营策略&…

【C++】30h速成C++从入门到精通(继承)

继承的概念及定义继承的概念继承&#xff08;inheritance&#xff09;机制是面向对象程序设计使代码可以复用的重要手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序…

数据结构——链表OJ题目讲解(1)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年3月7日 内容&#xff1a;数据结构链表OJ题目讲解 题目来源&#xff1a;力扣和牛客 目录 前言&#xff1a; 刷题&#xff1a; 1.移出链表元素&#xff1a; 2.链表的中间结点&#xff1a; 3. 链表中倒数第k个结点&#xff1…

第六届省赛——8移动距离(总结规律)

题目&#xff1a;X星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为1,2,3...当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。比如&#xff1a;当小区排号宽度为6时&#xff0c;开始情形如下&#xff1a;1 2 3 4 5 612 11 10 9 8 713 14 1…

【论文简述】MVSTER: Epipolar Transformer for EfficientMulti-View Stereo(ECCV 2022)

一、论文简述 1. 第一作者&#xff1a;Xiaofeng Wang 2. 发表年份&#xff1a;2022 3. 发表期刊&#xff1a;ECCV 4. 关键词&#xff1a;MVS、3D重建、Transformer、极线几何 5. 探索动机&#xff1a;融合多视图代价体很关键&#xff0c;现有的方法效率低&#xff0c;引入…

【Git】P2 分支(创建分支,合并分支,分支冲突,分支分类)

分支分支的概念2077 与 分支git - 分支分支语句查看与创建分支切换与删除分支合并分支分支冲突分支分类分支的概念 什么是分支&#xff1f; 2077 与 分支 我最喜欢的游戏就是 赛博朋克2077&#xff0c;美国末日 和 GTA&#xff0c;下图是2077的存档。 存档非常多的原因是因为…

JavaScript 语句、注释和代码块实例集合

文章目录JavaScript 语句、注释和代码块实例集合JavaScript 语句JavaScript 代码块JavaScript 单行注释JavaScript 多行注释使用单行注释来防止执行使用多行注释来防止执行JavaScript 语句、注释和代码块实例集合 JavaScript 语句 源码 <!DOCTYPE html> <html> &…

Springboot 读取模板excel信息内容并发送邮件, 并不是你想想中的那么简单

Springboot 读取模板excel信息内容并发送邮件 背景技术选型搭建过程数据加密隐藏问题暴露背景追溯解决背景 在我们日常开发中, 会遇到这样一种场景, 就是读取表格中的数据, 并将数据以附件的形式通过邮箱发送到表格中的每个人 即: excel 读取 excel 写入 发送邮件(携带附件), 例…

Volsdf Sampling algorithm

l论文作者开发一个算法计算抽样S方程中使用 I(c,v)≈I^S(c,v)∑i1m−1τ^iLiI(\boldsymbol{c}, \boldsymbol{v}) \approx \hat{I}_{\mathcal{S}}(\boldsymbol{c}, \boldsymbol{v})\sum_{i1}^{m-1} \hat{\tau}_{i} L_{i} I(c,v)≈I^S​(c,v)i1∑m−1​τ^i​Li​ 首先是通过利用…

小区业主入户安检小程序开发

小区业主入户安检小程序开发 可针对不同行业自定义安检项目&#xff0c;线下安检&#xff0c;线上留存&#xff08;安检拍照/录像&#xff09;&#xff0c;提高安检人员安检效率 功能特性&#xff0c;为你介绍小区入户安检系统的功能特性。 小区管理;后台可添加需要安检的小区…

LeetCode-96. 不同的二叉搜索树

题目来源 96. 不同的二叉搜索树 递归 1.我们要知道二叉搜索树的性质&#xff0c;对于一个二叉搜索树&#xff0c;其 【左边的节点值 < 中间的节点值 < 右边的节点值】&#xff0c;也就是说&#xff0c;对于一个二叉搜索树&#xff0c;其中序遍历之后形成的数组应该是一…