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); // 输出结果为 6
return 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表示地址,不能写成&P
printf ("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 + i
i 行 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青年