指针是C语言中很是重要的数据类型,若是你说C语言中除了指针,其余你都学得很好,那你干脆说没学过C语言。究竟什么是指针呢?咱们先来看一个概念。html
直接引用ios
回想一下,以前咱们是如何更改某个变量的值?
咱们以前是经过变量名来直接引用变量,而后进行赋值:数组
char a; a = 10;
看上去是很简单,其实程序内部是怎么操做的呢?
其实,程序对变量的读写操做,其实是对变量所在的存储空间进行写入或取出数据。就上面的代码而言,系统会自动将变量名a转换为变量的存储地址,根据地址找到变量a的存储空间,而后再将数据10以2进制的形式放入变量a的存储空间中。安全
经过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式函数
1.咱们已经知道,"直接引用"是直接经过变量名来读写变量spa
2.C语言中还有一种"间接引用"的方式(以变量a为例):首先将变量a的地址存放在另外一个变量中,好比存放在变量b中,而后经过变量b来间接引用变量a,间接读写变量a的值
。这就是"间接引用"。指针
若是程序经过"间接引用"的方式来修改a的值,能够这样作:先根据 变量名b 获取 变量b 的地址ffc2,取出变量b中存储的内容ffc1,也就是变量a的地址,再根据变量a的地址ffc1找到a的存储空间,而后修改里面的数据。code
3.总结一句:用来存放变量地址
的变量,就称为"指针变量"
。在上面的状况下,变量b就是个"指针变量",咱们能够说指针变量b指向变量a。htm
通常形式:类名标识符 *指针变量名;
blog
int *p; float *q;
"*"是一个说明符,用来讲明这个变量是个指针变量,是不能省略的,但它不属于变量名的一部分
前面的类型标识符表示指针变量所指向的变量的类型,并且只能指向这种类型的变量
1 // 定义int类型的变量a 2 int a = 10; 3 4 // 定义一个指针变量p 5 int *p; 6 7 // 将变量a的地址赋值给指针变量p,因此指针变量p指向变量a 8 p = &a;
注意第8行,赋值给p的是变量a的地址&a
// 定义int类型的变量a int a = 10; // 定义一个指针变量p // 并将变量a的地址赋值给指针变量p,因此指针变量p指向变量a int *p = &a;
指针变量是用来存放变量地址的,不要给它随意赋值一个常数。下面的写法是错误
的
int *p; p = 200; // 这是错误的
1 char a = 10; 2 printf("修改前,a的值:%d\n", a); 3 4 // 指针变量p指向变量a 5 char *p = &a; 6 7 // 经过指针变量p间接修改变量a的值 8 *p = 9; 9 10 printf("修改后,a的值:%d", a);
当程序刚执行完第5行代码时,内存中大概的分布状况是这样的
a值是10,p值就是变量a的地址ffc3。
注意下第五、第8行,都有个"*",它们的含义是不同的:
(1) 第5行的"*"只是用来讲明p是个指针变量
(2) 第8行的"*"是一个指针运算符,这里的
*p表明根据p值ffc3这个地址访问对应的存储空间
,也就是变量a的存储空间,而后将右边的数值9写入到这个存储空间,至关于 a = 9;,因而内存中就变成这样了
输出结果为:
,能够发现,咱们经过变量p间接修改了变量a的值。
指针运算符除了能够赋值以外,还能够用于取值
1 char a = 10; 2 3 char *p; 4 p = &a; 5 6 char value = *p; 7 printf("取出a的值:%d", value);
输出结果:
,第6行中的*p的意思是:根据p值(即变量a的地址)访问对应的存储空间,并取出存储的内容(即取出变量a的值),赋值给value
在指针变量没有指向肯定地址以前,不要对它所指的内容赋值。下面的写法是错误
的
int *p; *p = 10; //这是错误的
应该在指针变量指向一个肯定的变量后再进行赋值。下面的写法才是正确
的
// 定义2个int型变量 int a = 6, b; // 定义一个指向变量b的指针变量p int *p; p = &b; // 将a的值赋值给变量b *p = a;
前面咱们经过指针变量p间接访问了变量a,在有些人看来,以为指针变量好傻B,直接用变量名a访问变量a不就行了么,干吗搞这么麻烦。别着急,接下来举个例子,让你们看看指针还能作什么事情。
如今有个要求:写一个函数swap,接收2个整型参数,功能是互换两个实参的值。
1> 若是没学过指针,你可能会这样写
void swap(char v1, char v2) { printf("更换前:v1=%d, v2=%d\n", v1, v2); // 定义一个中间变量 char temp; // 交换v1和v2的值 temp = v1; v1 = v2; v2 = temp; printf("更换后:v1=%d, v2=%d\n", v1, v2); } int main() { char a = 10, b = 9; printf("更换前:a=%d, b=%d\n", a, b); swap(a, b); printf("更换后:a=%d, b=%d", a, b); return 0; }
输出结果:
虽然v1和v2的值被交换了,可是变量a和b的值根本就没有换过来。由于基本数据类型做为函数实参时,只是纯粹地将值传递给形参,形参的改变并不影响实参。
咱们能够简要分析一下这个过程:
在第20行中,将变量a、b的值分别传递给了swap函数的两个形参v一、v2
2> 若是学了指针,就应该这样写
1 void swap(char *v1, char *v2) { 2 // 中间变量 3 char temp; 4 5 // 取出v1指向的变量的值 6 temp = *v1; 7 8 // 取出v2指向的变量的值,而后赋值给v1指向的变量 9 *v1 = *v2; 10 11 // 赋值给v2指向的变量 12 *v2 = temp; 13 } 14 15 int main() 16 { 17 char a = 10, b = 9; 18 printf("更换前:a=%d, b=%d\n", a, b); 19 20 swap(&a, &b); 21 22 printf("更换后:a=%d, b=%d", a, b); 23 return 0; 24 }
先看看输出结果:
变量a和b的值终于换过来了。
解释一下:
(在16位编译器环境下,一个指针变量占用2个字节)
先注意第20行,传递是变量的地址。所以swap函数的形参v1指向了变量a,v2指向了变量b
第6行代码是取出v1指向的变量的值,也就是变量a的值:10,而后赋值给变量temp
第9行代码是取出v2指向的变量(变量b)的值,而后赋值给v1指向的变量(变量a)
第12行代码是将temp变量的值赋值给v2指向的变量(变量b)
相信你已经感觉到指针的强大了,若是没有指针,在一个函数的内部根本改变不了外部的实参。
接下来再举一个指针的实用例子。默认状况下,一个函数只能有一个返回值,有了指针,咱们能够实现函数有"多返回值"。
如今有个要求:写一个函数sumAndMinus,能够同时计算2个整型的和与差,函数执行完毕后,返回和与差(注意了,这里要返回2个值)
// 计算2个整型的和与差 int sumAndMinus(int v1, int v2, int *minus) { // 计算差,并赋值给指针指向的变量 *minus = v1 - v2; // 计算和,并返回和 return v1 + v2; } int main() { // 定义2个int型变量 int a = 6, b = 2; // 定义2个变量来分别接收和与差 int sum, minus; // 调用函数 sum = sumAndMinus(a, b, &minus); // 打印和 printf("%d+%d=%d\n", a, b, sum); // 打印差 printf("%d-%d=%d\n", a, b, minus); return 0; }
输出结果:
,和与差都由同一个函数计算并返回出来。和是函数的直接返回值,差是经过函数的第3个指针参数间接返回。
所以有了指针,咱们可让函数有"无限个"返回值。
刚学完指针,均可能有一大堆的疑惑,这里我列出几个常见的疑惑吧。
1.一个指针变量占用多少个字节的内存空间?占用的空间是否会跟随所指向变量的类型而改变?
在同一种编译器环境下,一个指针变量所占用的内存空间是固定的。好比,在16位编译器环境下,任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变。
2.既然每一个指针变量所占用的内存空间是同样的,并且存储的都是地址,为什么指针变量还要分类型?并且只能指向一种类型的变量?好比指向int类型的指针、指向char类型的指针。
其实,我以为这个问题跟"数组为何要分类型"是同样的。
看下面的代码,利用指针p读取变量c的值
int i = 2; char c = 1; // 定义一个指向char类型的指针 char *p = &c; // 取出 printf("%d", *p);
这个输出结果应该难不倒你们:1,是能够成功读取的。
若是我改一下第5行的代码,用一个本应该指向int类型变量的指针p
,指向char类型的变量c
int *p = &c;
咱们再来看一下输出:513,c的原值是1,如今取出来倒是513,怎么回事呢?这个要根据内存来分析
根据变量的定义顺序,这些变量在内存中大体以下图排布:
其中,指针变量p和int类型变量i各占2个字节,char类型的c占一个字节,p指向c,所以p值就是c的地址
1> 最初的时候,咱们用char p指向变量c。当利用p来获取变量c的值时,因为指针p知道变量c是char类型的,因此会从ffc3这个地址开始读取1个字节的数据:0000 0001,转为10进制就是1
2> 后来,咱们用int p指向变量c。当利用p获取变量c的值时,因为指针p认为变量c是int类型的,因此会从ffc3这个地址开始读取2个字节的数据:0000 0010 0000 0001,转为10进制就是513
可见,给指针分类是多么重要的一件事,并且一种指针最好只指向一种类型的变量,那是最安全的。
// 定义一个int类型的数组 int a[2]; // 定义一个int类型的指针 int *p; // 让指针指向数组的第0个元素 p = &a[0]; // 修改所指向元素的值 *p = 10; // 打印第一个元素的值 printf("a[0] = %d", a[0]);
输出结果:a[0] = 10
,说明已经经过指针间接修改了数组元素的值,跟指向一个普通int类型变量是同样的。
因为数组名表明着数组的首地址
,即 a == &a[0]
,所以第8行代码等价于:
// 让指针指向数组的第0个元素 p = a;
内存分析图以下,一个指针变量占用2个字节,一个int类型的数组元素占用2个字节
// 定义一个int类型的数组 int a[4] = {1, 2, 3, 4}; int i; for (i = 0; i < 4; i++) { printf("a[%d] = %d \n", i, a[i]); }
输出结果:
先定义一个指针,指向数组的第一个元素
// 定义一个int类型的数组 int a[4] = {1, 2, 3, 4}; // 定义一个int类型的指针,并指向数组的第0个元素 int *p = a;
p的值是a[0]的地址,所以,如今咱们利用指针p只能访问数组的第0个元素a[0],用*p就可取出a[0]的值1。要想访问其余元素,就必须拿到元素的地址,能够发现每一个元素的地址差值为2,由于在16位编译器环境下,一个int类型的变量占用2个字节。如今只是知道a[0]的地址值为p,怎么根据a[0]的地址获取其余元素的地址呢?其实很是简单,p+1就是a[1]的地址。注意了,这里的p+1表明着p的值加2,并非p的值加1,好比p的值为ffc3,p+1则为ffc5,而非ffc4。依次类推,p+2就是a[2]的地址ffc7,p+3就是a[3]的地址ffc9。
我先解释一下,为何p+1表明p的值加2,而不是加1呢?
其实,p+1不必定表明p的值加2,也多是加一、加4或者加8。究竟加多少,这跟指针的类型有关。下图是在16位编译器环境下的状况。
聪明的你可能已经找到规律了,由于char类型的变量要占用1字节,因此p+1表明p的值加1;float类型的变量占用4字节,因此p+1表明p的值加4。从这一点,也能够很好地说明为何指针必定要分类型,不一样类型的指针,p+1的含义是不同的。
上述代码中的p指向了int类型的数组元素a[0],因此p+1表明p的值加2。知道怎么获取其余元素的地址了,那么就能够利用指针p遍历数组元素了。
1 // 定义一个int类型的数组 2 int a[4] = {1, 2, 3, 4}; 3 4 // 定义一个int类型的指针,并指向数组的第0个元素 5 int *p = a; 6 7 int i; 8 for (i = 0; i < 4; i++) { 9 // 利用指针运算符*取出数组元素的值 10 int value = *(p+i); 11 12 printf("a[%d] = %d \n", i, value); 13 }
注意第10行的代码,*(p+i)表明根据p+i的值(其实就是第i个数组元素的地址)访问对应的存储空间,并取出存储的内容(也就是取出第i个数组元素的值),赋值给左边的value。
最后的输出效果是同样的:
注意的是:遍历完毕后,指针变量p仍是指向a[0],由于p值一直没有变过,一直都是a[0]的地址ffc3。
补充一下,其实第10行改为下面的代码也是能够的:
int value = *(a+i);
你们都知道,a值表明数组的首地址,也就是a[0]的地址ffc3。a+1则表明a的值加2,即a[1]的地址ffc5,也就是说,a+i表明着元素a[i]的地址。相信你们也能猜出来了,a+1不必定表明着a值加2,究竟加多少,取决于数组的类型。a+i的计算方法与p+i相同
。
利用上面的方法遍历完数组元素后,p一直指向元素a[0]。其实咱们也能够直接修改p的值来访问数组元素,只须要改一下第10行的代码便可
// 利用指针运算符*取出数组元素的值 int value = *(p++);
p++其实就是至关于p = p + 1,直接修改了p值,并且每次是加2。所以,每执行一次p++,指针p就会指向下一个数组元素。
输出结果确定是同样的:
可是,遍历完毕后,指针变量p没有指向任何数组元素,由于一共执行了4次p++,最后p值为ffcb。固然,能够从新让p指向a[0]:p = &a[0];或者p = a;
注意,这里的写法是错误
的
int value = *(a++);
a++至关于a=a+1,数组名a是个常量!不能进行赋值运算!
p是指针,a是一个数组
1> 若是p指向了一个数组元素,则p+1表示指向数组该元素的下一个元素。好比,假设p = &a[0],则p+1表示a[1]的地址
2> 对于不一样类型的数组元素,p值的改变是不一样的。若是数组元素为int类型,p+1表明着p的值加上2(16位编译器环境下)
3> 若是p的初值是&a[0],那么
p+i和a+i均可以表示元素a[i]的地址,它们都指向数组的第i个元素。a表明数组首地址,a+i也是地址,它的计算方法与p+i相同
*(p+i)和*(a+i)都表示数组元素a[i]
虽然p+i和a+i都指向数组的第i个元素,但两者使用时仍是有区别的。由于做为指针变量的p能够改变自身值,如p++,使p的值自增。而数组名a是一个表明数组首地址的常量,它的值是不能改变的,即a++是不合法的
4> 引用一个数组元素能够有两种方法:
下标法: 如a[i]
指针法: 如(p+i) 或 (a+i)
1.用数组名做为函数实参时,是把实参数组的首地址传递给形参数组,两个数组共同占用同一段内存空间,这样形参数组中的元素值发生变化就会使实参数组的元素值也同时变化
void change(int b[]) { b[0] = 10; } int main() { // 定义一个int类型的数组 int a[4] = {1, 2, 3, 4}; // 将数组名a传入change函数中 change(a); // 查看a[0] printf("a[0]=%d", a[0]); return 0; }
change函数的形参是数组类型的,在第11行调用change函数时,将数组名a,也就是数组的地址传给了数组b。所以数组a和b占用着同一块内存空间。
输出结果:
2.这种地址的传递也能够用指针来实现。函数的实参和形参均可以分别使用数组或指针
。这样就有4种状况:
也就是说,若是一个函数的形参类型是一个数组,调用函数时,你能够传入数组名或者指针变量;
void change(int b[]) { b[0] = 10; } int main() { // 定义一个int类型的数组 int a[4] = {1, 2, 3, 4}; int *p = a; // 将数组名a传入change函数中 change(p); // 查看a[0] printf("a[0]=%d", a[0]); return 0; }
注意第1行的形参类型是个数组int b[],第10行定义了指针变量p,第13行将p当作实参传入函数
若是一个函数的形参类型是一个指针变量,调用函数时,你能够传入数组名或者指针变量。
1 void change(int *b) { 2 b[0] = 10; 3 // 或者*b = 10; 4 } 5 6 int main() 7 { 8 // 定义一个int类型的数组 9 int a[4] = {1, 2, 3, 4}; 10 11 // 将数组名a传入change函数中 12 change(a); 13 14 // 查看a[0] 15 printf("a[0]=%d", a[0]); 16 17 return 0; 18 }
注意第1行的形参类型是个指针变量int *b,第12行将数组名a当作实参传入函数。
由第2行能够看出,在不少状况下,指针和数组是能够相互切换使用的。可是,并不能
说指针就等于数组。
注:本文转自李明杰老师的博文。