年末再看指针。看来搞C/C++,如影随形的指针就得门清~~~

news2024/9/28 3:30:36

继上篇博文因内核页表引出的指针问题,后来又研究了一番,这次应该比较清楚了,这里再总结一下。

目录

0 前言

1 普通指针:

2 指针的指针:

3 普通指针参数:

4 指针的指针参数:

5 函数指针:

6 指针函数:

7 指针数组:

8 数组指针:

9 其他:


0 前言

对于C/C++的学习和使用者而言,指针的重要性不言而喻。这不仅是初学者比较难理解的概念,也是很多老手容易犯错的地方。甚至因为过于灵活,容易引发问题,而发明了许多其他规避指针的语言。这从另一个角度说明,要掌握C/C++,指针是绕不过的。

C/C++里与指针有关的概念比较多,这里简单总结几个:

1 普通指针

定义一个指针变量,让其指向一个对应类型的变量。比如:

int *p;
int a;
p = &a;

这里定义了一个int指针变量和一个int的变量a,然后将a的地址给指针变量p,如此,就表示指针变量p指向了变量a。

 

2 指针的指针

定义一个指针变量,让其指向一个指针变量。比如:

int **pp;
int *p;
int a;
p = &a;
pp = &p;

这样,指针变量pp指向一个指针变量p,p又指向一个变量a。这是二级结构。当然还可以定义三级、四级等等,道理是一样的,不过会显得更加绕一些,使用和理解都比较费劲,实际中用的不多。

 

3 普通指针参数

在函数的参数传递中,使用指针变量。比如:

void func(int *pointer);
int *p;
int a;
p = &a;

func(p);

定义一个指针变量,指向a,定义一个带指针变量参数的函数func,调用时,传递p。我们知道,函数的参数传递是通过栈结构完成的,通过拷贝进行,这种传值方式可以分为两种,普通变量,传变量值本身,指针变量,传地址值。对于上面的函数,效果如下图:

 函数参数相当于是函数里定义的一个新变量,所以如上图,pointer跟p并不是一个变量,p传递给函数后,就是将p指向的变量a的地址给了函数参数变量pointer。这样,pointer也就指向了变量a。

4 指针的指针参数

在函数参数传递时,使用指针的指针变量。如下:

void func(int **ppointer) {
    int *p = (int *)malloc(1024);
    /*
    p[0] = xx;
    p[1] = yy;
    ...
    */
    *ppointer = p;
}

int *p;
func(&p);

从前面函数传参的原理可以看出,使用指针变量,虽然可以在函数里修改函数外面的内存,但是不能将函数里的内存传递出去。要做到这点,就需要使用指针的指针变量做参数。效果如下图:

参数ppointer获得了p的地址,也就是指向了p。而在函数里,可以对ppointer的内容做处理,也就是操作了p。这样,函数执行结束后,p指向了我们需要的地方。

5 函数指针

是一个指针,只不过这个指针指向一个函数。如下:

int (*pfunc)(int a);

int func (int a) {
   return a+1;
}

pfunc = func;

pfunc(1); //return 2;

函数名可以理解为函数所表示的一串汇编代码的开始地址。上面代码定义了一个函数指针pfunc,指向拥有一个int参数,返回int类型的函数。另外,实际定义了一个函数func,然后让上述函数指针pfunc指向func,也就是指向了这个函数的开始位置。最终,调用pfunc,执行func函数代码。效果如下图:

6 指针函数

是一个函数,只不过返回值是一个指针。如下:

int *func(int a) {
   int *p = (int *)malloc(a);
   return p;
}

我们可以将内存分配函数本身也看做是一个指针函数,返回分配内存的首地址。

7 指针数组

是一个数组,数组的每一个元素是指针。如下:

int* parray[10];
int *p;
int a;
p = &a;

parray[0] = p;
...

代码定义了一个指针数组,效果如下图:

8 数组指针

是一个指针,指向数组。代码如下:

int (*p) [2];
int array0[2];
int array1[2];
p[0] = array0;
p[1] = array1;

代码中定义了一个数组指针,指针指向含有两个int类型元素的数组。效果如下图:

9 其他:

大家可能觉得函数指针、指针函数,数组指针、指针数组不容易记住,不容易区分。这里有一个技巧,就是谁在后面,就是谁前面是一个修饰。比如,指针函数,函数在后面,就说这是一个函数,前面的指针是修改其返回值为指针。再比如,数组指针,指针在后面,就说明这是一个指针,前面用数组修饰,那么说明指针指向的是数组。

对于普通的指针,有三个与其相关的元素:指针的地址,指针本身和指针的内容。它们的关系如下图:

比如,如上图,一个指针变量p,其地址为123,123地址所在内存内容为456,是p本身的地址。p的内容为789,是所指向变量的地址,789地址内存处就是具体变量的值。

但是这处不能钻牛角尖。因为,对于指针和数组,有一些变通之处。我们知道,数组名是数组的首元素的地址,那么对于一个数组变量array, array 的值等于 &array[0]。进一步的,&array的值是什么呢?从含义上来讲,编译器会将其解释为数组的地址,这样看,其值本身跟array和&array[0]是相等的,但是含义不同。同样的,对于数组指针变量p, p指向数组,p的值就是数组的地址,自然也是数组首元素的地址。&p同样代表了数组的地址,自然跟p的值是相同的。*p同样代表了其指向的数组的值,自然也是数组的地址,所以跟p的值仍然是相同的。也就是这种情况下,p和&p和*p的内容是相同的。如果非要找出他们的不同,我们要从其含义上来理解其不同。

上面所述,最本质的理解是要注意:指针和数组并不同,即使在某些情况下值相同,但是表达的含义很可能不同。

关于上面的指针、数组、数组指针的取地址&和取内容*操作的值,可以通过下面的代码验证:

#include <stdio.h>
#include <stdlib.h>
 
typedef int pgd_t[2];
 
int main(int argc, char**argv) {
   int s[4];
   s[0] = 0x87654321;
   s[1] = 0x12345678;
   s[2] = 0x11112222;
   s[3] = 0x33334444;
 
   int testMem[4];
   testMem[0] = (unsigned int)s[0];
   testMem[1] = (unsigned int)s[1];
   testMem[2] = (unsigned int)s[2];
   testMem[3] = (unsigned int)s[3];
   
   pgd_t *pgd = NULL;
   pgd = (pgd_t *)testMem;
   //pgd = (pgd_t *)&testMem;
 
   int (*arrayPointer)[2];
   arrayPointer = pgd;

   int *p = NULL;
   p = &s[1];
 
   printf("\n");
   printf("Test pgd  : pgd is %lx,        pgd content is %lx,           pgd1 is %lx,          pgd1 content is %lx \n", 
         (unsigned long)pgd, (unsigned long)(*pgd), (unsigned long)pgd[1], (unsigned long)(*(pgd+1)));

   printf("\n");
   printf("Test pgd  : pgd0 content is %lx,        pgd1 content is %lx ,          pgd0 addr is %lx,       pgd1 addr is %lx\n", 
         (unsigned long)pgd[0], (unsigned long)pgd[1], (unsigned long)&pgd[0], (unsigned long)&pgd[1]);

   printf("\n\n");
   printf("Test mem  : array is %lx,      addr is %lx,      elem 0 addr is %lx,     elem 0 content is %x \n", 
         (unsigned long)testMem, (unsigned long)(&testMem), (unsigned long)&testMem[0], (unsigned int)testMem[0]);
   
   printf("\n");
   printf("Test mem  : array is %lx,      addr is %lx,      elem 1 addr is %lx,     elem 1 content is %x \n", 
         (unsigned long)testMem, (unsigned long)(&testMem), (unsigned long)&testMem[1], (unsigned int)testMem[1]);

   printf("\n");
   printf("Test mem  : array is %lx,      addr is %lx,      elem 2 addr is %lx,     elem 2 content is %x \n", 
         (unsigned long)testMem, (unsigned long)(&testMem), (unsigned long)&testMem[2], (unsigned int)testMem[2]);

   printf("\n");
   printf("Test mem  : array is %lx,      addr is %lx,      elem 3 addr is %lx,     elem 3 content is %x \n", 
         (unsigned long)testMem, (unsigned long)(&testMem), (unsigned long)&testMem[3], (unsigned int)testMem[3]);

   printf("\n\n");
   printf("Test local: local s0 is %lx,         local s1 is %lx,        local s2 is %lx,        local s3 is %lx,        local s is %lx \n", 
         (unsigned long)&s[0], (unsigned long)&s[1], (unsigned long)&s[2], (unsigned long)&s[3], (unsigned long)&s);

   printf("\n\n");

   printf("Test normal pointer, p is %lx,       p addr is %lx,          p content is %lx \n",
         (unsigned long)p, (unsigned long)&p, (unsigned long)(*p));
   printf("\n");
   
   return 0;
}

执行结果为:

这其中的差异,相信读者可以从中找到答案。

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

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

相关文章

[Kettle] 认识Kettle

1.初识Kettle Kettle是ETL数据整合与处理工具&#xff0c;翻译成中文是"水壶"的意思&#xff0c;可理解为希望把各种数据放到一个壶里&#xff0c;像水一样以一种指定的格式流出&#xff0c;表达数据流的含义 ETL(Extract - Transform - Load)是将数据从数据来源端…

centos7部署rancher2.5

一、 什么是 Rancher Rancher 是为使用容器的公司打造的容器管理平台。Rancher 简化了使用 Kubernetes 的流程&#xff0c;开发者可以随处运行 Kubernetes&#xff08;Run Kubernetes Everywhere&#xff09;&#xff0c;满足 IT 需求规范&#xff0c;赋能 DevOps 团队。 Ran…

单纯形法与对偶单纯形法的通俗理解

cigma<0,a>0 min cigma/(a) 决定出基变量 1对偶单纯形法 意思是看c就是所有货物的价值&#xff0c;去看一眼这些货物单价组合售卖的价值&#xff0c;这些价值肯定要都大于0&#xff0c;而且&#xff0c;组成这个c的系数也应该是都是正的&#xff0c; c最小证明对min&a…

港科夜闻|香港科大-越秀集团百万奖金国际创业大赛2022年度前8强20强项目评审结果公布...

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、“香港科大-越秀集团”百万奖金国际创业大赛2022年度前8强&20强项目评审结果公布。2022年赛事中的各赛区前三名项目&#xff0c;共计23个项目自动入围年度总决赛&#xff0c;本轮评审在这23个项目中&#xff0c;评选出了…

Hudi学习02 -- Hudi核心概念

文章目录基本概念时间轴&#xff08;Timeline&#xff09;文件布局&#xff08;File Layout&#xff09;索引&#xff08;Index&#xff09;索引原理索引类型索引的选择策略表类型&#xff08;Table Types&#xff09;查询类型&#xff08;Query Types&#xff09;写操作&#…

Qt第五十二章:Qt Design Studio使用技巧。

一、运行项目和Debugging项目【快捷键&#xff1a;CtrR】 二、 预览单Qml文件 三、添加资源文件 &#xff08;使用资源&#xff1a;将资源拖动到Editor中的矩形中即可&#xff09; 四、多状态【正常状态、按下状态、划过状态、已点击状态...】 注意&#xff1a;多状态看起来像…

java短网址平台

git地址 Reduce: 短网址平台&#xff0c;Coody Framework首秀&#xff0c;自写IOC、MVC、ORM、TASK、JSON、DB连接池、服务器。百毫秒启动&#xff0c;全项目仅2.1M&#xff08;低配服可运行&#xff09; reduce短网址平台 测试站地址&#xff1a;http://dev.icoody.cn/ 技…

DOM事件

鼠标事件监听 键盘事件监听 表单事件监听 常见的页面事件监听 事件传播 事件传播顺序&#xff1a;从内到外&#xff08;冒泡阶段&#xff09;onxxx这样写法只能监听冒泡阶段 addEventListener()方法第三个参数如果为true监听捕获阶段&#xff0c;false监听冒泡阶段(默认) 最…

C语言及算法设计课程实验二:数据类型、运算符和简单的输入输出

C语言及算法设计课程实验二&#xff1a;数据类型、运算符和简单的输入输出一、实验目的二、实验内容2.1、输入并运行教材第3章第4题给出的程序&#xff1a;2.2、输入第3章第5题的程序2.3、输入以下程序&#xff1a;2.4、程序设计题&#xff1a;假如我国国民生产总值的年增长率为…

遗传算法解决函数优化问题

遗传算法解决函数优化问题 作者: Cukor丘克环境: MatlabR2020a vscode 为什么要学习遗传算法 为什么要学习遗传算法&#xff0c;或者说遗传算法有什么厉害的地方。例如求解以下函数优化问题&#xff1a;minf(x1,x2)x12x1225∗(sin2x1sin2x2),−10≤x1≤10,−10≤x2≤10.min…

【ACWING】【图的深度优先遍历】【846树的重心】

给定一颗树&#xff0c;树中包含 n个结点&#xff08;编号 1∼n&#xff09;和 n−1条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义&#xff1a;重心是指树中的一个结点&#xff0c;如果将这个点删除后&…

js复习之正则表达式正向肯定与否定预查询

正则表达式(regular expression)描述了一种字符串匹配的模式&#xff08;pattern&#xff09;&#xff0c;可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。 正则表达式_百度百科 除开常用基本匹配模式&#xff0c;偶尔也会用到…

回顾艰难且不失温度的 2022 年 | 文中附「双12免单王」获奖名单

今天是 2022 年最后一天&#xff0c;回忆往昔&#xff0c;这一年经历了太多的不可思议和无可奈何之事。在年末的短短几周&#xff0c;寒气长驱直下以惊人的速度传给每一个人。我们真诚地希望大家都可以平安渡过这一难关。 即使步步难行&#xff0c;亦要踱步前行&#xff01;无…

力扣刷题记录——190. 颠倒二进制位、191. 位1的个数、202. 快乐数

本专栏主要记录力扣的刷题记录&#xff0c;备战蓝桥杯&#xff0c;供复盘和优化算法使用&#xff0c;也希望给大家带来帮助&#xff0c;博主是算法小白&#xff0c;希望各位大佬不要见笑&#xff0c;今天要分享的是——《190. 颠倒二进制位、191. 位1的个数、202. 快乐数》。 目…

Gradle学习笔记之依赖

文章目录依赖的方式直接依赖项目依赖本地jar包依赖依赖的类型api和implementation的区别依赖冲突及解决方案移除某个依赖不允许依赖传递强制使用某个版本依赖冲突时立刻构建失败依赖的方式 Gradle中的依赖方式有直接依赖、项目依赖和本地jar包依赖三种&#xff1a; dependenc…

【一起从0开始学习人工智能0x02】字典特征抽取、文本特征抽取、中文文本特征抽取

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录什么是特征工程&#xff1f;用什么做&#xff1f;1.特征提取特征值化&#xff1a;特征提取API字典特征提取---向量化---类别--》one-hot编码哑变量one-hot-------直接1234会产生歧义&#xff0c;不公平应用场…

Python 10k+ 面试试题,看看你是否掌握

前言 大家早好、午好、晚好吖 ❤ ~ 面试实战题&#xff1a;采集世界最大旅游平台Tripadvisor 另我给大家准备了一些资料&#xff0c;包括: 2022最新Python视频教程、Python电子书10个G &#xff08;涵盖基础、爬虫、数据分析、web开发、机器学习、人工智能、面试题&#xff…

Python GUI编程:音乐播放器(多线程、爬虫、进度条、文件)

文章目录1. 程序运行结果2.程序实现原理3. GUI布局4. 功能介绍5. 代码实现1. 程序运行结果 Python实现音乐播放器(爬虫、多线程、进度条、文件)2.程序实现原理 本音乐播放器GUI方面运用Python的tkinter实现&#xff0c;播放的音乐来自网络爬虫和本电脑已经有的。为了使整个程序…

Android studio设置全屏显示的两种方式

两种在Androidstudio中设置全屏的方式&#xff0c;推荐第二种 第一种&#xff08;Java文件中设置&#xff09; 直接在onCreate()函数中设置 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);package com.exa…

MARKETS AND MARKET LOGIC——The Market‘s Principles (6)_3

市场的组成——对行为观察的反思 制定市场理解 理解市场逻辑将有助于每个参与者提高其在市场上成功的可能性&#xff0c;因为他将能够阅读市场活动并接收市场生成的信息&#xff0c;这些信息很少有参与者承认或理解。这一信息特别涉及市场如何接受或拒绝随着时间的推移而升高或…