指针学习笔记
9.1指针
9.1-1:&运算符
初次见到&是在scanf()中,在刚开始学习时&符号也是常常会被漏掉的一个符号。
1.&的性质
&实际上是跟加减乘除一样的运算符。
2.&的作用
获得变量的地址,把它拿出来告诉你。(之前的课中有提到过变量是可操作的存储区,占有一定内存,因而存在地址。)
3.地址的输出
地址的输出用16进制会比较方便,但是输出地址不应该用%x,而是用%p(p为小写)。此时地址的输出会自动转换为16进制。
地址的大小取决于编译器的架构。
在不同架构中int的大小都是4个字节。
但地址的大小会有所不同:
64位时地址为8个字节;32位时地址为4个字节。
因此在64位架构如果把地址强制转换成int类型,会丢失前面四个字节。(在地址对应的数比较大的情况下。)
综上:地址的输出应该用%p,而不是%x,因为地址和整数的大小并不永远相同。
4.&使用规范
&取的地址必须是一个确定的变量。不能是i+j,i++,++i等等。
5.地址的存放
(1)相邻的两个int变量在堆栈中从高到低分配位置,每个占据4个字节。
(2)数组的地址:通过以下代码
int a[10]; printf("%p\n",&a); printf("%p\n",a); printf("%p\n",&a[0]); printf("%p\n",&a[1]);
得到0xbff8dd44;0xbff8dd44;0xbff8dd44;0xbff8dd48。
从而可以了解到&a==a==&a[0];&a[0]+4==&a[1]。(4==sizeof(int))
9.1-2:指针变量
1.指针的引入
前面说明了如何取出一个变量的地址,但单纯取出来没什么用。从而需要引入一个可以保存地址的变量——指针。
int i; int* p=i;//"*p"表示一个指针,这个指针指向一个int int* p,q; int *p,q;
后面两行表示的都是:星号指明p是一个指针,同时“*p”是一个int。
注意上面的格式并不能同时定义p,q两个为指针。
正确的应在q前也加上一个*。
2.作为参数的指针
指针可以作为一个函数的参数,通过取得变量的地址,可以实现在函数内对函数外变量的改变。同时需要用单目运算符*来访问地址上的变量。
void f(int *p); int main() { int i=6; f(&i);//f()函数调用。注意括号内的参数是变量的地址。 printf("i=%d",i);//输出函数调用后i的值 return 0; } void f(int *p) { printf("*p=%d\n",*p);//*p表示一个int,对应i的值 *p=26;//改变在该地址上的变量(也就是外面的i)的值 }
3.指针运算符&*互相反作用
&:取变量所对应的地址
*:取指针对应的地址所对应的变量的值
9.1-3:指针的使用
1.指针的应用场景一:交换变量
在之前的课程中说到过无法定义一个交换变量的函数,但现在可以通过指针来实现。
void swap(int *pa,int *pb); int main() { int a=5; int b=6; swap(&a,&b); printf("a=%d,b=%d",a,b); return 0; } void swap(int *pa,int *pb) { int t=*pa; *pa=*pb; *pb=t; }
运行结果a=6;b=5。两个变量的值成功被交换。
2.指针的应用场景二:返回多个值
函数只能返回一个值,但通过指针,我们可以实现多个值的返回。使得输入的参数运算出结果后返回。
void minmax(int a[],int len,int *max,int *min); int main() { int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,}; int min,max; minmax(a,sizeof(a)/sizeof(a[0]),&max,&min);//返回min和max。 printf("min=%d,max=%d",min,max);//此处不能用*min和*max。 return 0; } void minmax(int a[],int len,int *max,int *min) { int i; *max=*min=a[0]; for(i=1;i<len;i++) { if(a[i]<*min) { *min=a[i]; } if(a[i]>*max) { *max=a[i]; } } }
3.指针的应用场景二b
函数返回状态,指针返回结果。通常我们是用0或-1来表示程序出错,但如果0和-1都在可能输出的结果中,这种方法就不能用了,所以我们要让结果从指针输出,同时出错的判断交由函数返回值来完成。
/* return ret 除法成功返回1,否则返回0 */ int divide(int a,int b,int *result); int main(void) { int a=5; int b=2; int c; if(divide(a,b,&c)) {//只有返回ret=1,也就是除法成功时才进行输出 printf("%d/%d=%d",a,b,c); } return 0; } int divide(int a,int b,int *result) { int ret=1; if(b==0) ret=0; else { *result=a/b; } return ret; }
4.指针的常见错误
定义了指针变量,但还没有指向任何变量就开始使用指针
int *p; *p=12;
可能导致在重要位置写入12;导致程序崩溃。
9.1-4:指针与数组
1.数组变量
函数参数表中的数组实际上是const指针。
数组变量本身表示地址,每个数组单元表达单个变量。
int main() { int a[]={1,2,3,4,5,6,7,8,}; } void f(int a[]){} int b[]-->int *const b;
函数参数中的数组a[]实际上跟main函数中的a[]是同一个。
数组为指针常量,数组变量间不能传递赋值。(指向的地址是一定的,但其内容可以修改)。
注:不是常量指针(指针可以指向其他地址,但是内容不可以改变)。
2.运算符
[]运算符可以对数组做,也可以对指针做:
p[0]==*p;
*运算符可以对指针做,也可以对数组做:
*a==a[0];
9.1-5:指针与const
1.指针常量与常量指针
int *const q=&i;//q得到i的地址,指向i。不再指向其他变量 const int *p=&i;//不能通过*p修改i(*p=…),但可以改变*p的指向和i的值
判断哪个被const的标志的const在*的前面还是后面
const int* p1==int const*p2;通过指针不可修改
int *const p3 指针不可修改
2.const数组
const int a[]={1,2,3,4,5,6};
a[]本身是一个指针常量,前面加const说明数组单元是const,因此不能改变且需要初始化。
3.const保护
在传递参数进函数时,为了保护函数外的变量或者数组。通过以下方式实现:
void f(const int *p)或者void f(const int a[])
当传递的参数类型比地址大时,通过这个方式也能用较少字节数传值给参数。
参数小传参数,参数大传地址。
9.2指针运算
9.2-1指针运算
p为指针,p+1表示在地址上加上一个指针类型的大小(不是简单的加1)
如:char型加1;int型加4。
如果指针不是指向一片连续分配的空间,这种运算就没有意义。
1.指针计算
指针可以进行下面的操作:
1.指针加、减一个整数;
2.递增递减(++/--);
3.两个指针相减;(得到地址的差除以指针对应类型)
4.*p++:++的优先级比 *高,故不用加括号。该运算p位置上的地址会被取出来,然后p会加1,也就是位置后移一位。
2.指针比较
<,<=,==,>,>=,!=都可以对指针做。(比较在内存中的地址)
char ac[]={0,1,2,3,4,5,6,7,8,9}; char *p=ac; char *p1=&ac[5]; printf("p=%p\n",p); printf("p+1=%p\n",p+1); printf("*(p+1)=%d\n",*(p+1));// *(p+n)<==>ac[n] printf("p1-p=%d\n",p1-p);//结果为5(5个int) int ai[]={0,1,2,3,4,5,6,7,8,9}; int *q=ai; printf("p=%p\n",q); printf("p+1=%p\n",q+1); printf("*(q+1)=%d",*(q+1));
3. 0地址
内存中0地址是不能随便碰的,如果对其进行修改会导致程序崩溃。
但我们可以利用0地址表示返回的指针无效,程序无法运行。
NULL也可表示0地址。需要用0表示地址时可以直接用NULL。
4.指针的类型
任何类型的指针都是地址,大小都是一样的。但是指向不同类型的指针不能直接赋值。(避免覆盖赋值:改变int的一个单位时会改变char的四个单位)
5.用指针来做什么
1.需要传入较大的数据时用作参数;
2传入数组后对数组做操作;
3.函数返回不止一个结果/需要用函数修改不止一个变量
4.动态申请的内存
9.2-2动态内存分配
通过malloc函数可以进行内存的分配。
要用malloc需要先有 #include <stdlib.h> int *a;//定义指针 a=(int *)malloc(n*sizeof(int)); //malloc要的参数是占据的空间(多少字节) //(int*)是强制类型转换,因为malloc返回的是void*(指向类型不确定)
计算结束后需要用free(a);将内存还回去。而且应该还回原地址,不能是改变过的地址。
申请了没free对于大程序来说长时间运行会导致内存逐渐下降。
free过了再free会导致系统崩溃。