C语言程序设计-Chap.6


指针

1. 指针概述

指针 在C语言中有着十分重要的作用,其特点体现在其极高的灵活性上,并且能够十分方便的在不同函数间传递。

要理解指针到底是个啥,需要从一个基础概念 地址 说起。

地址

此前的章节中,曾提到过编程时的各种变量以及算法是储存在 内存 中的,而内存在储存数据时对不同的数据都有一个编号,这个编号就被称为 地址

在编程过程中,系统会根据程序中的变量名对不同的数据进行存储,相应的,不同的变量占用的内存大小也不尽相同。

int    //4个字节
char   //1个字节
float  //4个字节
double //8个字节

寻址

有了地址这个概念,相应的,程序需要调用相应变量时,就会通过地址找到内存中对应的部分进行调用,这个过程被称为 寻址

寻址有两种方式:

  1. 直接寻址

    直接寻址 指的是直接按变量的地址存取变量的值
    平时用的如:

    scanf("%d", &a);
    printf("%f", b);

    这些都属于直接寻址。

  2. 间接寻址

    与直接寻址不同, 间接寻址 是以地址跳转的方式来存取变量的值。

    这就涉及到了本章的核心—— 指针 ,因为指针所存储的就是地址。

2. 指针的使用

指针的定义

同样的,指针变量也有不同的类型,以下给出几例:

int *pt;
char *pc;
float *pf;

需要提出的是,指针变量可以指向任何类型,不仅仅局限于常见的变量类型,还包括函数,结构体等等。

指针的赋值

int a=3;
int *p=&a;
//上面是一种赋值方式
int *p;
p=&a;
//这是另一种赋值方式

//请格外注意,这种赋值方式是不合规的!
int *p;
p=1000;

指针变量常用取地址符号 & 进行一系列的赋值操作。
此外,只能赋给指针与其指针类型对应的变量的地址。(如:整形指针只能赋予其整形变量的地址)

指针相关的运算符

与指针相关的运算符有两个: & 以及 * 。

&的意义为取地址,可以将其后方的变量的地址提取出来。
*的意义为解引用,只能操作有指针意义的值。

这里给出*的使用例子:

int a=3;
int *p=&a;
(*p)++;  //等价于a++;
printf("%d", *p);  //等价于printf("%d", a);

指针作为函数参数

int function(int *a, int *b){
    ......
}

这里还需要提一下指针的常用情景,此前曾经提过,由于形参的限制,在函数中直接交换另一个函数中的变量是不可取的:

int swap(int a, int b){
    int t;
    t=a;
    a=b;
    b=t;
}

int main(){
    int a=3, b=5;
    swap(a, b);
    printf("%d %d", a, b);  //输出为:“3 5”
    return 0;
}

但在这里,指针可以完成这个操作,因为指针直接指向相应变量的地址。

int swap(int *a, int *b){
    int t;
    t=*a;
    *a=*b;
    *b=t;
}

int main(){
    int a=3, b=5;
    swap(&a, &b);
    printf("%d %d", a, b);  //输出为:“5 3”
    return 0;
}

这也是为什么说指针很灵活的原因之一。

一些补充

空指针

C语言中提供了一个特殊的指针值,被称为空指针,表示该指针变量闲置。常见写法为:

int *p=NULL;  //也可以写成int *p=0; 但这种写法不常用

通用指针

void *p;

通过上述语句可以定义一个通用指针,该指针可以被任何类型的变量地址赋值。
但需要注意的是,当使用该指针为其他非通用指针赋值时,需要做类型转换。下面给出一例:

void *p
int *pt, a=3;
p=&a;  //p为通用指针,可以接受任何变量的地址。
pt=(int *)p;  //p为通用指针,但pt为整形指针,因此赋值时需要进行强制类型转换。

在日常使用中,通用指针的作用并非进行各类计算,而是常用于动态内存分配函数malloc, calloc中,这点在本文靠后的位置中会提及。

常见错误

野指针:也叫悬空指针,即指针的地址是无效地址,或者未分配存储空间的地址。 (指针指到别的地方去了)

3. 指针与数组

在使用多次后,会逐渐发现数组和指针其实十分类似 (就一个玩意)

数组的存储方式

在进入正题之前,咱们先来回顾一下数组如何在内存中进行存储。
在上一章,提到过数组名其实就是个地址,这里给出更具体的阐释:

数组名是一个 基类型为数组元素基类型 的地址常量。

解释一下,定义一个数组,其实就是在内存中开辟了一片区域,其每个单元大小都是一个数组元素的大小,然后给数组名关联上那片区域的起始位置的地址。

说到这里,是不是想到了什么?
指针也是个地址,因此用指针去访问数组中的元素是完全可行的。

访问数组的指针

int a[5], *p, i;
p=a;  //这里也可以写成p=&a[0]; 一个道理。
for(i=0;i<5;i++){
    a[i]=1;
    printf("%d ", *(p+i));
}
//上述代码的输出结果为"1 1 1 1 1"

通过上述例子,可以发现我们通过将数组名与指针关联,进而实现了通过指针对数组中的元素进行访问。

需要注意的是:
当我们将数组的首地址赋值给指针时,指针就同样具有了 单位 这一概念,即其关联的数组的一个元素的大小。
因此,这时下列表达式便都存在意义:

int *p, a[5];
p=a;
p++;
p--;
*(p+1);
...

所以,现在数组元素的访问便有了两种方法:

  1. 下标:
    a[1], a[i], ...
  2. 指针:
    *(p+i), *(p+3), ...

指针与二维数组

先回顾一下二维数组的定义:

int tdarray[5][5];

可以理解为先创建了一个长度为5的数组,这个数组里每个单位又有五个整形元素。

因此,如果用指针操作:

int tdarray[3][4]={{0,1,2,3},{4,5,6,7},{5,5,5,5}};
int (*p)[4]=tdarray;
//也可以通过这种方式进行赋值
int (*p)[4];
p=tdarray;

printf("%d", *(*(p+1)+2));  //输出6,相当于tdarray[1][2]

关于*(*(p+i)+j)做出一些解释:

  1. p+i:相当于第i行的地址
  2. *(p+i):相当于第i行第1个元素的地址
  3. *(p+i)+j:相当于第i行第j个元素的地址
  4. *( *(p+i)+j):相当于第i行第j个元素

一些额外说明

需要明确的是,上面的阐述过程中一直将数组名与数组首地址画了等号,这其实并不十分准确,但因为这种阐述方法易于理解,因此采纳了这种方式。

实际上,大多数情况下,数组名都可以与数组首地址划等号,除了以下两种情况:

  1. 使用sizeof运算符计算数组大小时
    int a[5];
    printf("%d", sizeof(a)); //结果为20
  2. 使用&取地址符号时

4. 指针与字符串

同样的道理,此前的字符数组定义为:

char str[]="C programming";

显然,str作为字符串名也是一个地址,因此,我们也可以用指针来达成同样的效果:

char *str;
str="C programming";

需要注意的是,指针所代表的字符串的起始位置是其地址,终止位置是第一个“\0”。

下面拿一道例题进行说明:

#include <stdio.h>
int main()
{
	char arr[] = "ABCDE";
  	char *ptr;
 	for(ptr = arr; ptr < arr+5; ptr++)
     	printf("%s\n", ptr);
	return 0;
}
//该程序运行结果为:
//  ABCDE
//  BCDE
//  CDE
//  DE
//  E

5. 指针数组

请务必区分指针数组与指针形式的二维数组

如果我们想定义一个由指针元素构成的数组:

int *p[4];

这个语句的意义为定义了一个有四个元素的指针数组。

注意:二维数组的定义方式:

int (*p)[4];

产生二者区别的根本原因在于[]的运算优先级高于*,因此前者代表着定义了一个由四个指针元素组成的数组,而后者则仅仅是一个指针,这个指针内对应的地址含有四个元素。

由于博主对于指针数组的应用实在过少,这里先暂且搁置,待后续补充。

6. 命令行参数

在每个程序的主函数中,都会有这样一个语句:

int main(){

}

事实上,main后面的括号里是可以填东西的。其完整形式为:

int main(int argc, char *argv[]){

}

这里做出一点说明:

  1. argc是一个整形变量,由系统自动赋值,其值为后续*argv[]字符指针数组的个数。

  2. *argv[]是一个字符指针数组,可以接受多个字符串。

  3. 至于这些字符串如何输入,是通过windows用户终端(cmd)里面执行相应程序的exe文件时需要输入的内容。

    这里给出一例:
    在终端中找到对应的exe文件(cd 对应文件地址),之后输入:

    你的文件名.exe Hello World

    这样的话,argv[0]便被赋值为”你的文件名.exe”,argv[1]被赋值为”Hello”, argc[2]被赋值为”World”。
    argc则被赋值为3。

    这些参数可以在C语言程序中使用。

这篇博文就到这里~~


文章作者: MUG-chen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 MUG-chen !
  目录
加载中...