(七)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);    // 输出结果为 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青年

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值