C语言的灵魂——指针基础
一、什么是指针
1、指针的定义:地址形象化为指针,通过指针能够找到内存单元。
指针变量就是地址变量,变量的值为指针。
指针其实是我们将变量名和地址的关系显化(独立)出来的一种形式,是为了我们更加方便的读取数据和进行操作而设计的,是c语言的灵魂。
解释:
int a; printf("%d,a");
如上,我们通常是通过变量名来引用变量的值,如上面的printf("%d,a");一句,我们就是通过引用a的变量名来输出变量a的值。
为了理解上述的概念,我们必须理解这两个概念:
存储单元的地址 系统分配地址 类似于房间号
存储单元的内容 自定义内容 类似于房间里的人
2、常用的两种访问方式
直接访问 间接访问
int a; a = 3; //直接访问 printf("%d,a"); //间接访问 int *a_pointer; a_pointer = &a; printf("%d,*a_pointer");
直接访问就是通过变量名进行访问,间接访问就是通过指针进行访问。
二、指针变量
定义:存放地址(指针)的变量。
1、定义指针变量
方式:类型名 *指针变量名
int *p;// int——基类型,不可少
基类型用来指定此类指针变量可以指向的变量类型。如上面的p指针只可以用来指向整数类型的变量。
Zu:指针变量是基本数据类型派生出来的数据类型不能脱离基类型而独立存在。
int a,*a; double b,*p; float *p;
上面的定义都是正确的。
“ * "用来表示该变量是指针变量.
如上面的 int a,*a;我同时定义了整数型变量a,和整数型指针变量a。
2、不同指针类型,不能混用
int a; int *p; char *t; p = &a;//√ t = &a;//×
变量的指针包含存储单元的纯地址和该变量的数据类型两方面。
3、指针变量只可以存储地址
int a, *pointer; a = 100;//√ pointer = 100;//×
三、两个重要的指针操作符
& 取地址运算符
* 指针运算符(我更习惯叫他取内容运算符)
int a=100; int *pointer; pointer = &a;//取出a的地址赋给pointer,此时pointer指向a(即pointer的值是a的地址) if(*pointer==100)//取出pointer所指向的变量的值(即取出pointer存储的地址下的内容),在这里是a的值,然后进行比较 printf(“ture!”);//输出ture!
四、指针变量作为函数参数
1、两种传递方式 (函数通过参数(实参、形参)的传递来实现数据或地址的相互调用)
①值传递
②地址传递(引用传递):实际上就是指针传递
值传递不改变实参的值,而是传递实参的值。
地址传递可能改变实参的值,传递的是实参的地址,可以通过地址访问数据。
实例:
//交换函数 #include<stdio.h> int main() { void swap(int *p1,int *p2); int a=4,b=5; int *k1,*k2; k1=&a;k2=&b; if(a<b) swap(k1,k2); printf("max=%d min=%d",a,b);//输出max=5 min=4 return 0; } void swap(int *p1,int *p2) { int temp; temp=*p1; *p1=*p2; *p2=*p1; }
五、通过指针引用一维数组
数组元素的指针就是数组元素的地址。
切记:c中,数组名代表数组中首元素的地址
所以:两式等价
int *p,a[10];
p=&a[0];
p=a;
1、数组中指针的运算
指针拥有两类运算,一类是赋值运算,一类是算术运算。
指针指向的是地址,赋值运算代表的是地址的赋值。那算术运算代表什么意思呢?
其实代表的是(通过算术运算)而导致指向的地址的改变。
1)运算规则:自加(p++,++p)、自减(p--,--p)
加减整数(p+n,p-n)
这些运算代表什么意思呢:
首先我们定义一个数组int a[10],一个指针变量int *p=a;并赋值
此时的p代表的是数组a首元素的地址——就是a[0]的地址
那么p+1代表什么呢?
代表的是,指向数组中的下一个元素的地址,p+1等同于&a[1];同理p-1也是一样的道理。
2)讲解一个a[i]和p+i的联系与区别
[ ] 是变址运算符,a[ i ]按a+i计算地址,然后找出此地址单元中的值,所以我们常用的a[i]法引用数组元素值的方法和指针调用其实区别不大。
2、通过指针引用数组元素的两种方法
1)下标法。a[ i ]形式
2)指针法。如*(a+i)的形式,a为数组名,i为数组的序号。
例子:便利数组元素。
前提是定义了数组a[10] for(int *p=a;p<(a+10);p++) *p=0;//数组元素置零 或 for(int i;i<10;i++) a[i]=0;
3)值得注意的一点就是数组名代表数组元素的首地址,那么他就是一个指针型常量,它的值在程序运行的过程中是不变的,无法实现自加自减运算(a++ a -- ×)
但是我们可以通过赋值将首地址复制到指针变量中,然后再进行运用 如:for(int *p=a;p<(a+10);p++)。
3、特殊表达式分析:
1、*p++ 由于++与*同优先级 所以:先输出*p的值,然后使p值加1,即现在的p是p+1 解释:如果有一个数组,p指向a[0],执行过一次*p++后,*p++代表的意义是a[1]的内容。 2、*p++等价于*(p++) 有时候我都感觉很奇葩,如果就从字面上来看*(p++),很明显,这里的意义是先使得p++,就是p自加之后取值,但是事实上却是和上面的意义是一样的。 3、*(++p) 相当于a[++i] 意义:先使p加1,在取*p。 4、*(p--) 相当于a[i--] 意义:先求*p,再使p减1. 5、*(--p) 相当于a[--i] 先使p自减1,然后取*p 6、++(*p) 意义:先取*p,然后加1.
六、数组名用作函数参数
我们之前在传递数组到函数里的时候,通常都是以数组名作为实参进行传递,而形参通常会设定为数组。
例子:
//主体省略,已定义数组 array[100] fun(array,100); void fun(int arr[],int a) { } //其实也可以这样做 void fun(int *arr,int a) { }
上面两种方法等价。为什么呢?
因为我们实际上传递的是地址,而arr[ ]和*arr是等价的。所以我们已数组作为形参和以指针变量作为形参其实意义是一样的。
我们在考略一些东西的时候我们一定要使用抓住主要,舍弃次要,理解关键,专注细节,明白实质的方法。
而我们函数传递数组的时候实质便是地址的传递,所以我们只要能够传递地址的方法不都是可以的吗?