指针(四)

因为前期在学驱动,所以花了一天时间借鉴了别的资料,把本科学的C语言捡起来。

指针的基本概念

堆栈有栈顶指针,队列有头指针和尾指针,这些概念中的"指针"本质上是一个整数,是数组的索引,通过指针访问数组中的某个元素,经过学习我们在间接寻址那里看到了另一个指针的概念,把一个变量所在的内存单元的地址保存在另外一个内存单元中,保存地址的这个内存单元称为指针,通过指针和间接寻址访问变量,这种指针在C语言中可以用一个指针类型的变量表示,例如某程序中定义了以下全局变量:

int *pi=&i;表示定义一个指向int型的指针变量pi,并用i的地址来初始化pi.

int i;
int *pi = &i;
char c;
char *pc = &c;

在这里插入图片描述
注意:
后面两行代码定义了一个字符型变量c和一个指向c的字符型指针pc,注意pi和pc虽然是不同类型的指针变量,但它们的内存单元都占4个字节,因为要保存32位的虚拟地址,同理,在64位平台上指针变量都占8个字节。

pi是int型的,pc是char型的,pi=pc;这样赋值就是错误的。但是可以先强制类型转换然后赋值:

pi = (int *)pc;

在这里插入图片描述
但是:
**
现在pi指向的地址和pc一样,但是通过pc只能访问到一个字节,而通过pi可以访问到4个字节,后3个字节已经不属于变量c了,除非你很确定变量c的一个字节和后面3个字节组合而成的int值是有意义的,否则就不应该给pi这么赋值。因此使用指针要特别小心,很容易将指针指向错误的地址。
**
PI的地址和PC的地址一样了,这样就导致了可以访问的只有一个字节的地址了。原来是4字节。

为什么会出现野指针?

因为堆栈上分配变量的地址是随机的,也就是指针变量P所指向的内存地址也是不确定的。所以这种指向不确定地址的指针成为 “ 野指针 ”,为了避免野指针,在定义指针变量时就应该给它明确的初值,或者把它初始化位NULL:

int main(void)
{int *p = NULL;...*p = 0;...
}

空指针

把地址0转换成指针类型。
它的特殊之处在于,操作系统不会把任何数据保存在地址0及其附近,也不会把地址0-0xfff的页面映射到物理内存,所以任何对地址0的访问都会立刻导致段出错。*p=0;会导致段错误,就像放在眼前的炸弹一样很容易被找到。

void* 指针

ANSI在将C语言标准化时引入了void类型,void指针与其他类型的指针之间可以隐式转换,而不必用类型转换运算符。注意,只能定义void指针,而不能定义void型的变量,因为void指针和别的指针一样都占4个字节,而如果定义void型变量,编译器不知道该分配几个字节给变量。

void func(void *pv)
{/* *pv = 'A' is illegal */char *pchar = pv;*pchar = 'A';
}
int main(void)
{char c;func(&c);printf("%c\n", c);

指针类型的参数和返回值

#include <stdio.h>
int *swap(int *px, int *py)
{int temp;temp = *px;*px = *py;*py = temp;return px;
}
int main(void)
{int i = 10, j = 20;int *p = swap(&i, &j);printf("now i=%d j=%d *p=%d\n", i, j, *p);return 0;
}

通过地址访问到内部变量,将内部变量进行交换。

指针和数组

int a[10];
int *pa = &a[0];
pa++;

在这里插入图片描述
在函数原型中,如果参数是数组,则等价于参数是指针的形式,例如:

void func(int a[10])
{...
}

等价于

void func(int *a)
{...
}

等价于

void func(int a[])
{...
}

指针与const 限定符

  1. 如下
const int *a;
int const *a;

a是一个指向const int型的指针,a所指向的内存中的内容是不可改变的,所以(*a)++是不允许的,但a可以改写,所以a++是允许的。
2. 如下

int * const a;

a是一个指向int型的const指针,*a是可以改写的,但a不允许改写。
3.

int const* const a;

a是一个指向const int型的const指针,因此*a和a都不允许改写。
指向非const变量的指针或者非const变量的地址可以传给指向const变量的指针,编译器可以做隐式类型转换,例如:

char c = 'a';
const char *pc = &c;

但是,指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针,以免投过后者意外改写了前者所指向的内存单元,例如对下面的代码编译器会报警告:

const char c = 'a';
char *pc = &c;

即使不用const限定符也能写出正确的程序,但良好的编程习惯应该尽可能多的使用const,因为:

1.const给读代码的人传达非常有用的信息。比如一个函数的参数是const char *,你在调用这个函数时就可以放心地传给它char *或const char *指针,而不必担心指针所指的内存单元被改写。

2.尽可能多地使用const限定符,把不该变的都声明成只读,这样可以依靠编译器检查程序中的Bug,防止意外改写数据。

3.const对编译器优化是一个有用的提示,编译器也许会把const变量优化成常量。

指针与结构体

首先定义一个结构体类型,然后定义这种类型的变量和指针:

struct unit {char c;int num;
};
struct unit u;
struct unit *p = &u;

要通过指针p访问结构体成员可以写成(*p).c和(*p).num,为了书写方便,C语言提供了->运算符,可以写成p->c和p->num。

指向指针的指针与指针数组

也就是二级指针:

int i;
int *pi = &i;
int **ppi = &pi;

我们知道main函数的标准原型应该是int main(int argc,char *argv[]);argc是命令行参数的个数,而argv是一个指向指针的指针,为什么不是指针数组?因为前面讲过,函数原型中的[]表示指针而不表示数组,等价于char **argv.那为什么要写成char *argv[]而不写成char **argv?这样写给读代码的人提供了有用的信息,argv不是指向单个指针,而是指向一个指针数组的首元素。数组中每个元素都是char *指针,指向一个命令行参数字符串。

打开命令行参数

#include <stdio.h>
int main(int argc, char *argv[])
{int i;for(i = 0; i < argc; i++)printf("argv[%d]=%s\n", i, argv[i]);return 0;
}

编译:

$ gcc main.c
$ ./a.out a b c
argv[0]=./a.out
argv[1]=a
argv[2]=b
argv[3]=c
$ ln -s a.out printargv
$ ./printargv d e 
argv[0]=./printargv
argv[1]=d
argv[2]=e

在这里插入图片描述由于argv[4]是NULL,我们也可以这样循环遍历argv:

for(i=0; argv[i] != NULL; i++)

NULL标识着argv的结尾,这个循环碰到NULL就结束,因而不会访问越界,这种用法很形象地称为Sentinel,NULL就像一个哨兵守卫着数组的边界。

指向数组的指针与多维数组

指向数字的指针其实前面讲过的数组指针。

int (*a)[10];

int (*a)[10]可以拆分成

typedef int t[10];
t *a;

t代表由10个int组成的数组类型,a则是指向这种类型的指针。
也就是:

int a[10];
int (*pa)[10] = &a;

a是一个数组,在&a这个表达式中,数组名做左值,取整个数组的首地址赋给指针pa。注意,&a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址,显然这两个地址的数值是相同的,但这两个表达式的类型是两种不同的指针类型,前者的类型是int ,而后者的类型是int()[10]。

函数类型和函数指针类型

在C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元存放一个地址值,而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:

函数指针:

#include <stdio.h>
void say_hello(const char *str)
{printf("Hello %s\n", str);
}
int main(void)
{void (*f)(const char *) = say_hello;f("Guys");return 0;
}

分析一下变量f的类型声明void(*f)(const char ),f首先跟号结合在一起,因此是一个指针。(*f)外面是一个函数原型的格式,参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello的参数是const char *,返回值是void,正好是这种函数,因此f可以指向say_hello。注意,say_hello是一种函数类型,**而函数类型和数组类型类似,做右值使用时自动转换成函数指针类型,所以可以直接赋给f,当然也可以写成void (*f)(const char *) =&say_hello;**把函数say_hello先取地址再赋给f,就不需要自动类型转换了。

可以直接通过函数指针调用函数,如上面的f(“Guys”),也可以先用*f取出它所指的函数类型,再调用函数,即(*f)(“Guys”)。可以这么理解:函数调用运算符()要求操作数是函数指针,所以f(“Guys”)是最直接的写法,而say_hello(“Guys”)或(*f)(“Guys”)则是把函数类型自动转换成函数指针然后做函数调用。

文章来源:
https://blog.csdn.net/m0_58367586/article/details/128612310

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

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

相关文章

CnetSDK .NET OCR Library SDK Crack

CnetSDK .NET OCR Library SDK Crack CnetSDK .NET OCR Library SDK 是一款高精度 .NET OCR 扫描仪软件&#xff0c;用于从图像中识别字符&#xff0c;如文本、手写和符号。该.NET OCR库软件采用Tesseract OCR引擎技术&#xff0c;将字符识别准确率提高高达99%。通过将 .NET OC…

C++【智能指针】

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 目录 &#x1f449;&#x1f3fb;为什么需要智能指针&#x…

实验3.5 路由器的单臂路由配置

实验3.5 路由器的单臂路由配置 一、任务描述二、任务分析三、具体要求四、实验拓扑五、任务实施1.SWA的基本配置2.RA的基本配置3.在RA上查看接口状态 六、任务验收七、任务小结 一、任务描述 某公司对部门划分了需VLAN之后&#xff0c;发现两个部门之间无法通信&#xff0c;但…

机器学习——logistic回归

目录 一、线性模型与回归 二、基于logistic回归和Sigmoid函数的分类 三、最优化算法 1. 最大似然估计 2. 梯度上升法 3. 训练算法&#xff1a;梯度上升 4. 绘制决策边界 5. 训练算法&#xff1a;随机梯度上升 6. 改进的随机梯度算法 四、从疝气病症预测病马的死亡率 …

在jupyter notebook中修改其他文件的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

力扣题:数字与字符串间转换-12.8

力扣题-12.8 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;299. 猜数字游戏 解题思想&#xff1a;进行遍历&#xff0c;统计完全相同的数字和不相同的数字即可&#xff0c;然后统计不相同的数字在秘密数字和猜测数字中共同出现的次数 class Sol…

Kubernetes(K8s 1.27.x) 快速上手+实践,无废话纯享版(视频笔记)

视频源&#xff1a;1.03-k8s是什么&#xff1f;_哔哩哔哩_bilibili 1 基础知识 1.1 K8s 有用么&#xff1f; K8s有没有用 K8s要不要学&#xff1f; 参考资料: https://www.infoq.com/articles/devops-and-cloud-trends-2022/?itm_sourcearticles_about_InfoQ-trends-report…

SSL证书代理

众所周知&#xff0c;SSL证书已经成为当下网络安全中不可或缺的一个环节&#xff0c;对于很多开发公司来说&#xff0c;给自己的客户提供SSL证书安全服务也是最为基础的。 但是目前市面上像阿里云之类的证书服务商对于开发公司需要的证书并没有太大的一个优惠政策&#xff0c;给…

MySQL老是卸载不干净,不会删除注册表,安装总是报错

给大家推荐一款非常使用的工具 geek点击官网下载。 安装完成主页就长这样&#xff1a; 右键点击你要删除的MySQL卸载即可。自动帮你清空注册表等信息。 谁用谁知道&#xff01;&#xff01;&#xff01; 用了感觉不错的话记得回来给我点赞加评论哦&#xff01;&#xff01;&…

JVM 运行时参数

面试题 JVM的参数&#xff0c;你知道的说一下 (百度) 说说你知道的几种主要的JVM参数&#xff08;京东&#xff09; JVM调优调的哪些参数&#xff1f;在哪里写这些参数&#xff1f; &#xff08;亚信&#xff09; 内存调优参数都有什么&#xff1f;&am…

MTU TCP-MSS(转载)

MTU MTU 最大传输单元&#xff08;Maximum Transmission Unit&#xff0c;MTU&#xff09;用来通知对方所能接受数据服务单元的最大尺寸&#xff0c;说明发送方能够接受的有效载荷大小。 是包或帧的最大长度&#xff0c;一般以字节记。如果MTU过大&#xff0c;在碰到路由器时…

介绍java spring 提供的默认数据库持久化技术 JdbcTemplate基本演示

之前 我们说过spring贴心的内嵌了三种数据源形式 其中默认为HikariCP 其实 spring 也提供了持久化数据库连接技术 这个技术其实大部分都接触过 那就是 JDBC 随着时代的发展 用他的人也越来越少了 那么 我们要演示 JdbcTemplate 导入 mybatis 或 mybatis-plus 的片段 就要注掉了…

【从零开始学习JVM | 第五篇】快速了解运行时数据区

前言&#xff1a; 当谈论 Java 程序的运行机制时&#xff0c;JVM&#xff08;Java 虚拟机&#xff09;的运行时数据区是一个必不可少的话题。JVM 运行时数据区是 Java 程序在运行过程中分配内存和管理数据的重要区域&#xff0c;它包括了方法区、堆、虚拟机栈、程序计数器和本地…

Linux---日志管理

本章主要介绍Linux中的日志管理 了解rsyslog是如何管理日志的查看日志的方法 日志管理简介 工作当中的日志&#xff0c;特指硬件和软件的日志&#xff0c;管理员可以通过它来检查错误发生的原因&#xff0c;或者寻找受到攻击时攻击者留下的痕迹。日志管理包括管理系统日志、应…

智能外呼常见场景有哪些?

智能外呼常见场景是什么&#xff1f; 智能外呼在各种场景下都有应用&#xff0c;以下是一些常见的场景&#xff1a; 营销推广 通过智能外呼向潜在客户进行产品或服务的宣传和推广&#xff0c;收集客户对产品或服务的反馈。根据客户的反馈自动调整宣传策略&#xff0c;从而提…

mac本地部署stable-diffusion

下载Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" ①输入“1”选择中科大版本&#xff0c;然后输入Y(YES)&#xff0c;直接输入开机密码&#xff08;不显示&#xff09;然后回车确认&#xff0c;开始下载 ②…

小航助学2023年6月GESP_Scratch二级真题(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号 单选题3.00分 删除编辑附件图文 答案:D 第1题高级语言编写的程序需要经过以下&#xff08; &#xff09;操作&#xff0c;可以生成在计算机上运行的可执行代码。 A、编辑B、…

​LeetCode解法汇总1466. 重新规划路线

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; n 座城市&…

在王者荣耀中脸探草丛的正确姿势是什么?

引言 Cocos中躲草丛效果的实现原理。 在游戏开发中&#xff0c;我们经常用透视或者半透明效果去表现模型被遮挡的效果。 本文将介绍一下如何在Cocos中实现王者荣耀中的躲草丛效果。 本文源工程在文末获取&#xff0c;小伙伴们自行前往。 躲草丛效果的实现原理 要在Cocos中…

Android Studio的笔记--String和byte[]

String和byte[]的相互转换&#xff0c;字节数组转换 String转换byte[]文本16进制字节数组 byte[]转换String文本16进制 其它 String转换byte[] 文本 将字符串&#xff08;String&#xff09;转换为字节&#xff08;byte&#xff09;的方法。默认使用的是UTF-8编码 StandardCh…