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