十一、指针
1、取地址运算:&运算符取得变量的地址
scanf("%d",&i); //里的&——获得变量的地址,它的操作数必须是变量。
int i; printf("%x",&i); //%x 是以十六进制形式输出指针的值(即内存地址) printf("%p",&i); //%p 是输出指针的值(即内存地址) //64位下结果不一样。一般用%p。
地址的大小是否与int相同取决于编译器。
&不能对没有地址的东西取地址
&(a+b) ? &(a++)? &(++a)?
2、指针:指针变量就是记录地址(或保存地址)的变量
int i; int* p=&i;
int* p,q; //p是指针变量,q不是
int *p,q; //p是指针变量,q不是。
变量的值是内存的地址
(1)普通变量的值是实际的值。
(2)指针变量的值是具有实际值的变量的地址
作为参数的指针
(1)在被调用的时候得到了某个变量的地址;
(2)在函数里面可以通过这个指针访问外面的这个i;
void f(int *p);
int i=0; f(&i);
访问那个地址上的变量*
(1)* 是一个单目运算符,用来访问指针的值所表示的地址上的变量。
(2)可以做右值也可以做左值
int k=*p; *p=k+1;
*左值之所以叫左值
(1)是因为出现在赋值号左边 的不是变量,而是值,是表达式计算的结果。
a[0]=2;
*p=3;
(2)是特殊的值,所以叫做左值。
指针的运算符&*
——互相反作用
(1)*&yptr ——> *(&yptr) ——> *(yptr的地址) ——> 得到那个地址上的变量 ——> yptr
(2)&*yptr ——> &(*yptr) ——> &(y) ——>得到y的地址,也就是yptr ——> yptr
传入地址
(1)为什么
int i; scanf("%d",i);
(2)编译没有报错?
3、指针的使用:指针有什么用呢?
——交换两个变量的值
void swap(int *pa, int *pb) { int t=*pa; *pa=*pb; *pb=t; }
——函数返回多个值,某些值就只能通过指针返回
传入的参数实际上是需要保存带回的结果的变量。
——函数返回运算的状态,结果通过指针返回
(1)常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
-1或0(在文件操作会看到大量的例子)。
(2)但是当任何数值都是有效的可能结果时,就得分开返回了
后续的语言(C++,Java)采用了异常机制来解决这个问题。
4、指针与数组:为什么数组传进函数后的sizeof不对了?
指针最常见的错误
定义了指针变量,还没有指向任何变量,就开始使用指针
传入函数的数组成了什么?
(1)函数参数表中的数组实际上是指针。
sizeof(a)=sizeof(int*)
但是可以用数组的运算符[]进行运算。
int isPrime(int x , int konwPrimes[] , int numberOfKnowPrimes) { int ret=1; int i; for(i =0 ; i< numberOfKnowPrimes; i++){ if(x % konwPrimes[i] == 0 ){ ret =0; break; } } return ret; }
数组参数
以下四种函数原型是等价的:
int sum(int *ar, int n);
int sum(int *,int);
int sum (int ar[] ,int n);
int sum (int [], int);
数组变量是特殊的指针
(1)数组变量本身表达地址,所以
int a[10]; int *p=a; //无需用&取地址
a== &a[0]; //但是数组的单元表达的是变量,需要用&取地址。
(2)[]运算符可以对数组做,也可以对指针做:
p[0] <==> a[0]
(3)* 运算符可以对指针做,也可以对数组做:
*a=25;
(4)数组变量是const的指针,所以不能被赋值
int a[] <==> int *const a=....
5、指针与const:指针本身和所指的变量都可能是const
指针是const
表示一旦得到了某个变量的地址,不能再指向其他变量。
int *const q = &i; //q是const *q = 26 ; //OK q++; //ERROR
所指是const
表示不能通过这个指针去修改那个变量的值(并不能使得那个变量成为const)
const int *p = &i; *p=26; //ERROR! (*p)是const i = 26 ; //OK p = &j; //OK
判断哪个被const了的标志是const在 * 的前面还是后面。
转换
(1)总是可以把一个非const的值转换成const的
void f(const int* x); int a=15; f(&a); //OK const int b = a; f(&b); //OK b = a+1; //ERROR!
(2)当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。
const数组
const int a[]={1,2,3,4,5,6};
(1)数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int。
(2)所以必须通过初始化进行赋值。
保护数组值
(1)因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值。
(2)为了保护数组不被函数破坏,可以设置参数为const
int sum( const int a[], int length);
6、指针运算
(1)给指针加1 表示要让指针指向下一个变量
int a[10]; int *p=a; *(p+1) ——> a[1]
(2)如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义。
指针计算
——这些算术运算可以对指针做:
(1)给指针加、减一个整数(+,+=,-,-=)
(2)递增递减(++/--)
(3)两个指针相减
*p++
(1)取出p所指的那个数据来,完事之后顺便把p移到下一个位置去。
(2)* 优先级虽然高,但是没有++高
(3)常用于数组类的连续空间操作
(4)在某些CPU上,这可以直接被翻译成一条汇编指令
指针比较
(1)<,<=,==,>,>=,!=都可以对指针做
(2)比较它们在内存中的地址
(3)数组中的单元的地址肯定是线性递增的
0地址
(1)当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
(2)所以你的指针不应该具有0值
(3)因此可以用0地址来表示特殊的事情:
——返回的指针是无效的;
——指针没有被真正初始化(先初始化为0)。
(4)NULL是一个预定定义的符号,表示0地址
——有的编译器不愿意你用0来表示0地址。
指针的类型
(1)无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
(2)但是指向不同类型的指针是不能互相赋值的
(3)只是为了避免用错指针
指针的类型转换
(1)void *表示不知道指向什么东西的指针
——计算时与char* 相同(但不相通)
(2)指针也可以转换类型
int *p = &i; void *q=(void*)p;
(3)这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
——我不再当你是int啦,我认为你就是个void
用指针来做什么
(1)需要传入较大的数据时用作参数
(2)传入数组后对数组做操作
(3)函数返回不止一个结果
——需要用函数来修改不止一个变量。
(4)动态申请的内存...
7、动态内存分配
输入数据
(1)如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
(2)C99可以用变量做数组定义的大小,C99之前呢
int *a = (int*) malloc (n*sizeof(int));
malloc
#include<stdlib.h> void* malloc(size_t size);
(1)向malloc申请的空间的大小是以字节为单位的
(2)返回的结果是void*,需要类型转换为自己需要的类型
(int*)malloc(n*sizeof(int))
没空间了?
(1)如果申请失败则返回0,或者叫做NULL
(2)你的系统能给你多大的空间?
free()
(1)把申请得来空间还给“系统”
(2)申请过的空间,最终都应该要还
(3)只能还申请来的空间的首地址
(4)free(0)?
常见问题
(1)申请了没free——>长时间运行内存逐渐下降
(2)free过来再free
(3)地址变过了,直接去free
8、函数间传递指针
好的模式
(1)如果程序中要用到动态分配的内存,并且会在函数之间传递,不要让函数申请内存后返回给调用者
(2)因为十有八九调用者会忘了free,或找不到合适的时机来free
(3)好的模式是让调用者自己申请,传地址进函数,函数再返回这个地址出来
除非函数的作用就是分配空间,否则不要在函数中malloc然后传出去用
函数返回指针
——返回指针没问题,关键是谁的地址?
(1)本地变量(包括参数)?函数离开后这些变量就不存在了,指针所指的是不能用的内存
(2)传入的指针?没问题
(3)动态申请的内存?没问题
(4)全局变量——>以后说
函数返回数组
和返回指针是一样的
(1)如果一个函数的返回类型是数组,那么它实际返回的也是数组的地址
(2)如果这个数组时这个函数的变量,那么回到调用函数那里,这个数组就不存在了
(3)所以只能返回
——传入的参数:实际就是在调用者那里
——全局变量或动态分配的内存