(七)C语言之指针

c语言相比其他高级语言来说,更接近于对计算机硬件的操作,而指针的应用更是为我们对硬件的操作插上了翅膀,所以指针是嵌入式编程不可少的一部分,在一定意义上说,指针是c语言的精髓。

一、 什么是指针

在计算机中,数据时存放在内存中的,而内存其实就是一组有序字节组成的数组,一般以一个字节为一个内存单元,每个字节都有唯一的地址。cpu通过寻址的方式去查找内存中某个变量的位置,我们知道定义变量就是向CPU申请一个某一类型的空间,这个空间也有自己的地址,同样地址也需要一种类型去存储,C语言规定用指针类型的变量去存储地址类型。记住一点:指针就是地址,指针变量时存放地址类型的变量。

二、指针变量的定义

2.1 声明并初始化一个指针

可以保存地址值的变量称为指针变量,指针变量定义如下:

数据类型  *  变量名

这里的数据类型为基本数据类型、构造类型,指针变量的声明比普通变量的声明多了一个’ * ‘,运算符’ * '就是间接引用或间接寻址。例如:

int *p;        // 声明一个 int 类型的指针 p
char *p        // 声明一个 char 类型的指针 p
int *arr[10]   // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
int (*arr)[10] // 声明一个数组指针,该指针指向一个 int 类型的一维数组
int **p;       // 声明一个指针 p ,该指针指向一个 int 类型的指针

在上面的声明中:p就是一个指针变量,里面存着一个地址。
这里要注意**指针在使用前一定要初始化,否则就会指针就会变成野指针。初始化有3种方式:

/* 方法1:使指针指向现有的内存 */
int x = 1;
int *p = &x;  // 指针 p 被初始化,指向变量 x ,其中取地址符 & 用于产生操作数内存地址/* 方法2:动态分配内存给指针 */
int *p;
p = (int *)malloc(sizeof(int) * 10);    // malloc 函数用于动态分配内存
free(p);    // free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用,要使用这两个函数需要头文件 stdlib.h/*方法3:定义为NULL */
int *p=NULL;

指针的初始化就是给指针一个合理的指向,让程序知道往哪指,上述NULL是一个特殊的指针变量,相当于0。地址为0的内存一般都不允许访问,但是内存地址为0有一个重要的意义,它表明指针指向不指向一个可访问的内存地址。

2.2 指针的调用

访问内存空间,一般分为直接访问和间接访问。

如果知道内存空间的名字,可通过名字访问该空间,称为直接访问。由于变量即代表有名字的内存单元,故通。过变量名操作变量,也就是通过名字直接访问该变量对应的内存单元。

如果知道内存空间的地址,也可以通过该地址间接访问该空间。对内存空间的访问操作一般指的是存、取操作,即向内存空间中存入数据和从内存空间中读取数据。

在 C 语言中,可以使用间接访问符(取内容访问符)*来访问指针所指向的空间,例如:

int *p,a=3;//p中保存变量a对应内存单元的地址
p=&a;

在该地址 p 前面加上间接访问符 *,即代表该地址对应的内存单元。而变量 a 也对应该内存单元,故 *p 就相当于 a。

printf("a=%d\n",a); //通过名字,直接访问变量a空间(读取)
printf("a=%d\n",*p); //通过地址,间接访问变量a空间(读取)
*p=6;//等价于a=6;间接访问a对应空间(存)

2.3 野指针

一般我们把没有合法指向的指针称为“野”指针。因为“野”指针随机指向一块空间,该空间中存储的可能是其他程序的数据甚至是系统数据,故不能对“野”指针所指向的空间进行存取操作,否则轻者会引起程序崩溃,严重的可能导致整个系统崩溃。例如:

int *pi,a; //pi未初始化,无合法指向,为“野”指针
*pi=3; //运行时错误!不能对”野”指针指向的空间做存入操作。该语句试图把 3 存入“野”指针pi所指的随机空间中,会产生运行时错误。
a=*pi; //运行时错误!不能对”野”指针指向的空间取操作。该语句试图从“野”指针pi所指的空间中取出数据,然后赋给变量a同样会产生运行时错误。

正确的应该是:

pi=&a;//让pi有合法的指向,pi指向a变量对应的空间
*pi=3;//把3间接存入pi所指向的变量a对应的空间

三、指针的运算

c指针的算术运算只限两种形式:

(1)指针+/-整数

可以对指针变量加减整数,例如对指针变量p进行p++,p–,p+i等操作,所得结果任然是一个指针,只是指针p所指的内存地址前进或后退了i个操作数。
指针加减
在上图中假设p指向内存10000008,p是一个int型指针可以看出对p进行加减所指向的内存。下面进行一个例子来说明指针变量自增自减运算:

#include<stdio.h>
#include<string.h>
void main(void)
{char str[]="hello";char *p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*p);//打印结果为'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*p++);//打印结果为'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",(*p)++);//打印结果为'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*(p++));//打印结果为'h'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",++*p);//打印结果为'i'p=str;strcpy(str,"hello");printf("str=%s\n",str); printf("%c\n",*++p);//打印结果为'e'}

对于指针++*p和 *p++来说是依据就近原则运算的,而对y=*p++则相当于y=*p;p++;这里如果加上括号则为y=(*p)++则相当于y=*p;(*p)++;

(2)指针-指针

只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。举个例子:

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

(3)指针的比较

指针在一定条件下可以比较,这里的一定条件指两个指针指向同一个对象才有意义,例如两个指针变量p,q指向同一数组,则<,>,>=,<=,== 等关系运算符都能正常运行。若q==p为真,则表示p和q为同一元素;若p<q为真,则表示p所指向的数组元素在q所指向的数组元素之前。

四、指针和数组

指针和数组的关系十分密切。在前面的文章(c语言二维指针数组详解)中我们推算到a[i]=*(a+i),一般来说通过数组完成的工作都可以用指针来完成,但是使用数组更容易理解。

4.1 一维数组与指针

一维数组的数组名表示该数组的首地址,c语言中指针变量加1表示跳过该指针变量所指类型占用的空间大小。如果指针变量指向数组,那么指针加1表示指向数组的下一个元素。

int  *p;//声明一个int类型的指针变量
int a[5];//声明一个int型数组
p=a;//数组名表示数组首地址,把数组首地址赋给指针变量,p指向数组的第0个元素a[0]

在上面的程序中,数组名等价于数组的首地址,即&a[0]。
访问数组的元素有三种方式:
(1)直接访问: 数组名[下标]的形式

int a[5]={1,2,3,4,5};
int b=0;
b=a[3];//b=4,直接使用数组下标访问数组元素

(2)间接访问:*(数组名+i)的形式

int a[5]={1,2,3,4,5};
int b=0;
b=*(a+3);//b=4,直接使用*(数组名+i)访问

(3)间接访问:*(指针变量)的形式

int a[5]={1,2,3,4,5};
int b=0;
int *p=a;
b=*(p+3);//b=4,直接使用指针间接访问数组元素

【例 1】通过指针变量实现对数组元素的输入和输出操作。
实例代码为:

#include <stdio.h>
#define N 10
int main (void)
{int *p,a[N],i;p=a; //p初始指向a[0]printf("Input the array:\n");for(i=0;i<N;i++) //用整型变量i控制循环次数scanf ("%d",p++); //指针P表示地址,不能写成&Pprintf ("the array is :\n");for(p=a;p<a+N;p++) //用p的移动范围控制循环次数printf("%d\t", *p);return 0;
}

4.2 二维数组与指针

二维数组实际上还是一维数组,它的存储结构仍是顺序存储,即二维数组中的元素在内存中的存储地址是连续的,所以可以用指针变量访问数组的各个元素。具体的解释请看(c语言二维指针数组详解)

*(a[i] + j) <--> *(*(a + i) + j) <-->*&a[i][j]<-->a[i][j]

4.3 指针数组

无论是指针数组还是数组指针都看后面两个字区分,后两个字为数组,那么它是一个存放指针元素的数组。指针数组定义:

数据类型 *数组名[数组大小]

在上面的声明中,由于[]的优先级高于*,所以先形成数组。

4.4 数组指针

同样看后面两个字知道它是一个指针,指向数组。声明一个数组指针方法如下:

//数据类型 (* 数组名)[元素个数]
int (*p)[5];//声明一个数组指针p,它指向含有5个int类型元素的二维数组

上面p指向二维数组,它指向二维数组的每一行
二维数组 a[M][N] 分解为一维数组元素 a[0]、a[1]、…、a[M-1] 之后,其每一行 a[i] 均是一个含 N 个元素的一维数组。如果使用指向一维数组的指针来指向二维数组的每一行,通过该指针可以较方便地访问二维数组中的元素。
使用数组指针访问二维数组中的元素。

#define M 3
#define N 4
int a[M][N],i,j;
int (*p)[N]=a; // 等价于两条语句 int (*p)[N] ; p=a;

以上语句定义了 M 行 N 列的二维整型数组 a 及指向一维数组(大小为 N)的指针变量 p,并初始化为二维数组名 a,即初始指向二维数组的 0 行。
i 行首地址与 i 行首元素地址的区别如下。

  • i 行首元素的地址,是相对于 i 行首元素 a[i][0] 来说的,把这种具体元素的地址,称为一级地址或一级指针,其值加 1表 示跳过一个数组元素,即变为 a[i][1] 的地址。
  • i 行首地址是相对于 i 行这一整行来说的,不是具体某个元素的地址,是二级地址,其值加 1 表示跳过 1 行元素对应的空间。
  • 对二级指针(某行的地址)做取内容操作即变成一级指针(某行首元素的地址)。
    两者的变换关系为:
*(i 行首地址)=i 行首元素地址0 行首地址:p + 0 <--> a + 0
1 行首地址:p + 1 <--> a + 1
...
i 行首地址:p + i <--> a + ii 行 0 列元素地址:*(p + i) +0 <—> *(a + i) +0 <—>&a[i][0]
i 行 1 列元素地址:* (p + i) +1 <--> *(a + i) +1 <—>&a[i][1]
...
i 行 j 列元素地址:* (p + i) + j <--> * (a + i) + j <--> &a[i][j]
i 行 j 列对应元素:* (* (p + i) + j) <--> * (* (a + i) + j) <--> a[i][j]

由此可见,当定义一个指向一维数组的指针 p,并初始化为二维数组名 a 时,即 p=a;, 用该指针访问元素 a[i][j] 的两种形式 ((p + i) + j) 与 ((a + i) + j) 非常相似,仅把 a 替换成了 p 而已。
由于数组指针指向的是一整行,故数组指针每加 1 表示跳过一行,而二维字符数组中每一行均代表一个串,因此在二维字符数组中运用数组指针能较便捷地对各个串进行操作。

五、指针和函数

c语言函数参数传递有两种方式:值传递和地址传递。本节主要讨论下地址传递,传递地址能够改变主调函数对象中的值。

5.1指针函数

有时函数调用结束后,需要函数返回给调用者某个地址即指针类型,以便于后续操作,这种函数返回类型为指针类型的函数,通常称为指针函数。
指针函数的定义格式为:

类型*函数名(形参列表)
{... /*函数体*/
}

5.2函数指针

C语言中,函数本身不是变量,但是可以定义指向函数的指针,也称作函数指针,函数指针指向函数的入口地址。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。 声明一个函数指针的方法如下:

返回值类型 (* 指针变量名)([形参列表]);int (*pointer)(int *,int *);        // 声明一个函数指针

上述代码声明了一个函数指针 pointer ,该指针指向一个函数,函数具有两个 int * 类型的参数,且返回值类型为 int。

六、指针与字符串

6.1常量字符串与指针

常量字符串返回来的就是一个存放该字符串的首地址。**注意:**常量字符串不能改变,即使通过指针也不能改变,因为它存放在文字常量区。假设字符串常量 “abcd” 表示一个指针,那么该指针指向字符 ‘a’,表达式 “abcd”+1,是在指针 “abcd” 值的基础上加 1,故也是一个指针,指向字符串中第二个字符的指针常量。同理,“abcd”+3 表示指向第 4 个字符 ‘d’ 的指针常量。
我们还可以这样定义:

char *p="hello";

【例1·】如下代码段通过指针变量依次遍历输出所指串中每个字符

#include<stdio.h>
int main (void)
{//初始指向首字符//间接访问所指字符 //pc依次指向后面的字符char *pc="hello,world!";while (*pc! = '\0'){putchar(*pc);pc++;
}return 0;
}

6.2 变量字符串

我们想要存放可以改变的字符串,可以放在字符数组中。例如:

char str[]="i love china"

这里的str内容可以改变,通过指针或者下标都可以去操作其内容。

七、总结

到这里指针的相关内容已经讲解完了,记住以下几点:

  • 指针就是地址,指针变量存放的是地址类型的变量
  • 定义指针和调用指针的*作用不一样
  • 在指针变量前每加一个* 表示取一次内容,类似[],在调用时每加一个*表示取一次内容
  • 指针变量一定要初始化,c语言不允许野指针出现
  • 指针指向字符串的首地址
  • 注意辨别指针的自增自减操作
  • 了解指针数组和数组指针、指针函数和函数指针的应用

本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

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

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

相关文章

(八)C语言之结构

今天来说一下C语言里的结构体(struct)、共用体(l联合体)union、枚举。 &#xff08;一&#xff09;结构体&#xff1a;struct 1.1 概念 是一种自定义的数据类型结构体是构造类型的一种不同数据类型的集合地址空间连续&#xff0c;每次分配最大数据类型的宽度占用内存为所有变…

插入排序之表插入排序

1.表插入排序只是求得一个有序的链表&#xff0c;它是修改指针的值来代替移动记录&#xff0c;操作过程如下 2.但是这样只能进行顺序查找&#xff0c;不能进行随机查找&#xff0c;为了能实现有序表的折半查找&#xff0c;需要对记录进行重新排列。操作过程如下&#xff1a; 3.…

电容降压LED驱动电路

电容降压电路具有体积小、成本低、电流相对稳定等优点&#xff0c;可应用于小功率的LED驱动电路中&#xff0c;本文主要介绍了电容降压电路的基本电路 图一&#xff1a; 电容降压式简易电源的基本原理如图一所示&#xff0c;C3为降压电容器&#xff1b;D4为半波整流二极管&…

延时电路分析

延时电路经常会用到&#xff0c;RC电路是比较简单的电路。在电路设计中经常会用到将电阻和电容正极连接&#xff0c;电阻另一端接上电源&#xff0c;电容负极接地。 简单的延时电路 上面就是延时的电路图了&#xff0c;延时的时间为T-ln((VCC-Vout)/VCC)RC&#xff0c;公式中的…

恒流电路的分析(一)

在这里分析一个简单的LED恒流电路&#xff0c;软件采用Multisim进行波形采集 一、元器件 R1为80KΩ左右的金属膜电阻&#xff1b;Q选取耐压值超过350V的VPN三极管&#xff1b;D选取2V左右的稳压二极管(如1N4680)&#xff1b;C2选取10V、100UF以上的电解电容&#xff1b;R2选择…

ST-LINK USB communication error解决方法

今天在用stlink-v2下载程序时出现ST-LINK USB communication error&#xff0c;突然就出现了这个问题&#xff0c;在网上找了好多解决办法都不可以用&#xff0c;下面给出我的解决方案&#xff0c;文章末尾给出了网上的几种解决办法&#xff0c;仅供参考。 第一步&#xff1a;找…

利用STM32制作红外测温仪之硬件设计

最近受疫情的影响详细大家都在家里没事干&#xff0c;这里利用stm32最小系统做一个红外测温仪 这篇教程里我们来制作红外测温仪需要用到的硬件&#xff0c;关于PCB的工程文件&#xff0c;后文会给出。 &#xff08;一&#xff09;系统分析 由于我们的功能比较单一&#xff0c;…

如何在博客中插入背景音乐

1.首先进入网音乐官方网站&#xff1b; 2.查找自己喜欢的歌&#xff0c;看到如下界面 3.点击"生成外链播放器" 4.看到下面的html代码了吗&#xff1f;将代码进行复制。 5.进入博客园&#xff0c;点击 "管理" ->"设置"&#xff0c; 将代码复制…

常用存储器介绍

注意&#xff1a;"易失/非易失"是指存储器断电后&#xff0c;它存储的数据内容是否会丢失的特性。 &#xff08;一&#xff09;RAM和ROM 1.1 RAM RAM即随机存储器&#xff0c;它是指存储器中的数据被读入或者写入与信息所在位置无关&#xff0c;时间都是相同的 1…

TortoiseGit与github实现项目的上传

1. 下载并安装相关软件 这里主要涉及的软件包括msysgit和TortoiseGit。 msysgit的下载地址&#xff1a;http://msysgit.googlecode.com/files/Git-1.7.4-preview20110204.exe TortoiseGit的下载地址&#xff1a;http://code.google.com/p/tortoisegit/downloads/list&#xff0…

uboot启动流程分析

Uboot的启动流程分为两个阶段&#xff0c;第一阶段主要是汇编语言编写&#xff0c;第二阶段是C语言编写&#xff0c;每个阶段所做的工作不同&#xff0c;这篇文章分析的是uboot 2010版&#xff0c;以tiny4412的uboot为例。 启动过程涉及的主要文件&#xff1a; arch/arm/cpu/a…

(二)linux内核镜像制作

&#xff08;一&#xff09;目的 在进行嵌入式开发的时候&#xff0c;我们往往会先在电脑上安装交叉编译器&#xff0c;然后编译目标板上的代码&#xff0c;最后把代码下载到电路板中&#xff0c;嵌入式系统组成包括&#xff1a;BootLoaderkernelfilesystemapplication&#x…

js+css实现骰子的随机转动

网上找的例子&#xff0c;然后增添了新的东西&#xff0c;在这里展示一下...... 效果图预览&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html x…

linux的mount和umount指令使用

mount即挂在操作&#xff0c;磁盘或分区创建好文件系统后需要挂载到一个目录&#xff08;一般mount在/mnt下&#xff09;才能使 用&#xff0c;和winsdows不同的是在linux下需要手动挂载。 用法&#xff1a;mount [-t文件系统] [选项] 设备目录注意&#xff1a;[ ]为可选项…

斐波那契的四种求法

首先看一下斐波那契的矩阵表示&#xff1a; 数列的递推公式为&#xff1a;f(1)1&#xff0c;f(2)2&#xff0c;f(n)f(n-1)f(n-2)(n>3) 用矩阵表示为&#xff1a; 进一步&#xff0c;可以得出直接推导公式&#xff1a; #include<iostream> #include<cstring> #i…

利用STM32制作红外测温仪之软件设计(MLX90614)

目录&#xff08;一&#xff09;工程目录如图&#xff1a;&#xff08;二&#xff09;main函数实现&#xff1a;&#xff08;三&#xff09;MLX90614测温代码实现前面介绍了使用 STM32制作红外测温仪硬件设计,今天来说一下软件的实现&#xff0c;具体的程序&#xff0c;完整的k…

Windows下使用Dev-C++开发基于pthread.h的多线程程序

一、下载Windows版本的pthread 目前最新版本是&#xff1a;pthreads-w32-2-9-1-release.zip。 二、解压pthread到指定目录 我选择的目录是&#xff1a;E:\DEV-CPP\Pthread完成后&#xff0c;该目录会多出三个文件夹&#xff1a;Pre-built.2&#xff0c;pthreads.2&#xff0c;Q…

(三)linux之根文件系统的制作

&#xff08;一&#xff09;准备工作 Ubuntu 16.04系统linux-3.5内核:linux-3.5-20190929交叉编译工具arm-linux-gcc-4.5.1-v6-vfp-20120301.rarbusybox源码包&#xff1a;busybox-1.21.1.rar &#xff08;二&#xff09;工具介绍 &#xff08;1&#xff09;交叉编译器 这个…

(四)Linux内核模块化编程

目录&#xff08;一&#xff09;模块化编程简介&#xff08;二&#xff09;安装卸载模块命令.&#xff08;三&#xff09;将自定义功能添加到内核三种方法&#xff08;1&#xff09;修改Kconfig和Makefile&#xff08;2&#xff09;直接修改功能对应目录下的Makefile文件&#…

基于X86平台的PC机通过网络发送一个int(32位)整数的字节顺序

1.字节顺序  字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序&#xff0c;通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处&#xff0c;高字节数据存放在内存高地址处&#xff1b;大端字节序是高字节数据存放在低地址处&#xff0c;低字…