指针
1. 指针概述
指针 在C语言中有着十分重要的作用,其特点体现在其极高的灵活性上,并且能够十分方便的在不同函数间传递。
要理解指针到底是个啥,需要从一个基础概念 地址 说起。
地址
此前的章节中,曾提到过编程时的各种变量以及算法是储存在 内存 中的,而内存在储存数据时对不同的数据都有一个编号,这个编号就被称为 地址 。
在编程过程中,系统会根据程序中的变量名对不同的数据进行存储,相应的,不同的变量占用的内存大小也不尽相同。
int //4个字节
char //1个字节
float //4个字节
double //8个字节
寻址
有了地址这个概念,相应的,程序需要调用相应变量时,就会通过地址找到内存中对应的部分进行调用,这个过程被称为 寻址 。
寻址有两种方式:
直接寻址
直接寻址 指的是直接按变量的地址存取变量的值
平时用的如:scanf("%d", &a); printf("%f", b);
这些都属于直接寻址。
间接寻址
与直接寻址不同, 间接寻址 是以地址跳转的方式来存取变量的值。
这就涉及到了本章的核心—— 指针 ,因为指针所存储的就是地址。
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);
...
所以,现在数组元素的访问便有了两种方法:
- 下标:
a[1], a[i], ...
- 指针:
*(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)做出一些解释:
- p+i:相当于第i行的地址
- *(p+i):相当于第i行第1个元素的地址
- *(p+i)+j:相当于第i行第j个元素的地址
- *( *(p+i)+j):相当于第i行第j个元素
一些额外说明
需要明确的是,上面的阐述过程中一直将数组名与数组首地址画了等号,这其实并不十分准确,但因为这种阐述方法易于理解,因此采纳了这种方式。
实际上,大多数情况下,数组名都可以与数组首地址划等号,除了以下两种情况:
- 使用sizeof运算符计算数组大小时
int a[5]; printf("%d", sizeof(a)); //结果为20
- 使用&取地址符号时
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[]){
}
这里做出一点说明:
argc是一个整形变量,由系统自动赋值,其值为后续*argv[]字符指针数组的个数。
*argv[]是一个字符指针数组,可以接受多个字符串。
至于这些字符串如何输入,是通过windows用户终端(cmd)里面执行相应程序的exe文件时需要输入的内容。
这里给出一例:
在终端中找到对应的exe文件(cd 对应文件地址),之后输入:你的文件名.exe Hello World
这样的话,argv[0]便被赋值为”你的文件名.exe”,argv[1]被赋值为”Hello”, argc[2]被赋值为”World”。
argc则被赋值为3。这些参数可以在C语言程序中使用。
这篇博文就到这里~~