C语言的指针
1.取地址运算
- scanf("%d",&i);里的&
- 获得变量的地址,它的操作数必须是变量
- int i; printf("%x",&i);
注意:
- 获取地址时,用%p来获取
- int i; printf("%p",&i);
- 地址的大小是否与int相同取决于编译器
#include <stdio.h>
int main()
{
int a[10];
printf("%p\n",&a);
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",&a[1]);
return 0;
}
000000000062FDF0
000000000062FDF0
000000000062FDF0
000000000062FDF4
1.1 &不能取的地址
&不能对没有的东西取地址
比如:
- &(a+b)
- &(a++)
- &(++a)
2.指针
定义:就是保存地址的变量
int i;
int* p=&i;//这个p指向的是个int,把i的地址交给了这个p,这个*p只会有别的变量的地址
int* p,q;//q只是个普通的b
int *p,q;
- 变量的值是内存的地址
- 普通变量的值是实际的值
- 指针变量的值是具有实际值的变量的地址
2.1 作为参数的指针
#include <stdio.h>
void f(int *p);
void g(int k);
int main()
{
int i = 6;
printf("&i=%p\n",&i);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf("p=%p\n",p);
}
void g(int k)
{
printf("k=%d\n",k);//得到的只是i的值
}
-
void f(int *p);
-
在被调用的时候得到了某个变量的地址;
-
int i=0; f(&i);
-
在函数里面可以通过这个指针访问外面的这个i
2.2 访问那个地址上的变量*
是一个单目运算符,用来访问指针的值所表示的地址上的变量*
可以做右值也可以做左值
- int k= *p;
- *p = k+1;
#include <stdio.h>
void f(int *p);
void g(int k);
int main()
{
int i = 6;
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf("p=%p\n",p);
printf("*p=%d\n",*p);
*p = 26;//可以通过这种方式访问外面的变量,可以修改,访问
}
void g(int k)
{
printf("k=%d\n",k);//得到的只是i的值
}
2.3 左值之所以叫左值
是因为出现在赋值好左边的不是变量,而是值,是表达式计算的结果(运算的结果):
- a[0]=2;
- *p=3
- 是特殊的值,所以叫做左值
2.4 指针的运算符&*
- 互相反作用
- *&yptr -> *(&yptr) -> *(yptr的地址) -> 得到那个地址上的变量 -> yptr
- &*yptr -> &( *yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr
3.指针的使用
3.1 指针的应用场景一
-
交换两个变量的值(函数)
#include <stdio.h> void swap(int *pa,int *pb); int main() { int a=5; int b=6; printf("a=%d,b=%d \n",a,b); 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=5,b=6 //a=6,b=5
3.2 指针的应用场景二
- 函数返回多个值,某些值就只能通过指针返回
- 传入的参数实际上是是需要保存带回的结果的变量
#include <stdio.h>
void minmax(int a[],int len,int *min,int *max);
int main(void)
{
int a[]={1,2,3,4,5,6,7,8,9,10};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d \n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max)
{
int i;
*min =*max =a[0];
for(i=1;i<len;i++){
if(*min>a[i]){
*min=a[i];
}
if(*max<a[i]){
*max=a[i];
}
}
}
应用场景二b(运算出错)
-
函数返回运算的状态,结果通过指针返回
-
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错
- -1或0(在文件操作会看到大量的例子)
-
但是当任何数值都是有效的可能结果时,就得分开返回
#include <stdio.h> /*如果除法成功,返回1;否者返回0 */ int divide(int a,int b,int *result); int main() { int a=5; int b=2; int c;//c是a/b的结果 if( divide(a,b,&c)){ 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;//除数不能是0 else{ *result=a/b; } return ret; }
4.指针最常见的错误
- 定义了指针变量,还没有指向任何变量,就开始使用
5.传入函数的数组成了什么?
函数参数表中的数组实际上是指针
- sizeof(a) == sizeof(int*)
- 但是可以用数组的运算符[]进行运算
#include <stdio.h>
void minmax(int *a,int len,int *max,int *min);
int main(void)
{
int a[] ={1,2,3,4,5,6,7,8,9,10};
int min,max;
printf("minmax sizeof(a)=%lu\n",sizeof(a));
printf("main a=%p\n",a);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("a[0]=%d\n",a[0]);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int *a,int len,int *max,int *min)
{
int i;
printf("minmax sizeof(a)=%lu\n",sizeof(a));//接收到的是首地址
printf("main a=%p\n",a);
a[0]=1000;
for ( i=1;i<len;i++){
if( a[i]< *min){
*min = a[i];
}
if( a[i]> *max){
*max = a[i];
}
}
}
minmax sizeof(a)=40
main a=000000000062FDF0
minmax sizeof(a)=8
main a=000000000062FDF0
a[0]=1000
min=10,max=0
5.1 数组的变量是特殊的指针
-
数组变量本身表达地址,所以不需要加
- int a[10];int *p=a;//无需用&取地址
- 但是数组的单元表达的是变量,需要用&去地址
- a == &a[0]
-
[]运算符可以对数组做,也可以对指针做:
- p[0] <==> a[0]
-
*运算符可以对指针做,(取出所指变量例的值),也可以对数组做
- *a =25;
-
数组变量是const的指针,所以不能被赋值
- int a[] <==> int * const a=...
6.指针与const
6.1 如果q是const
- 表示q的值(即i的地址)不能被改变,就是说不能再指向别人了
- int *const q =&i; //q是const
- *q=26;//ok
- q++;//ERROR
6.2 如果是const int
- 表示
- const int *p =&i;
- p = 26;//ERROR! ( *P)是const
- i = 26;
- p =&j;
在前面就代表int不能被修改,在后面表示指针不能被修改。
6.3 转换
-
总是可以把一个非const的值转换成const
void f(const int* x); int a=15; f(&a);//ok const int b=1; f(&b);//ok b = a+1;//Error!
-
当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改
6.4 const数组
- const int a[]={1,2,3,4,5,6};
- 数组变量已经全是const的指针了,这里的const表明数组的每个单元都是const
- 所以必须通过初始化进行赋值
保护数组值
- 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
- 为了保护数组不被函数破坏
- int sum(const int a[],int length);
7.指针运算
这些算术运算可以对指针做:
- 给指针加、减一个整数(+,+=,-,-=)
- 递增递减(++/--)
- 两个指针相减,可以得到两个地址差了几个单元(地址相减/sizeof(数据类型))
7.1 *p++
- 取出p所指的那个数据来,完事以后顺便把p移到下个位置去
- *的优先级虽然高,但是没有++搞
- 常用于数据类的连续空间操作
- 在某些CPU上,这可以直接被翻译成一条汇编指令
7.2 指针比较
- <,<=,==,>,>=,!= 都可以对指针做
- 比较它们在内存中的地址
- 数组中的单元的地址肯定是线性递增
7.3 0地址
-
当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
-
所以你的指针不应该具有0值
-
因此可以用0地址来表示特殊的事情
- 返回的指针是无效的
- 指针没有被真正初始化(先初始化为0)
-
NULL是一个预定定义的符号
- 有的编译器不愿意你来用0来表示0的地址
8. 指针的类型
- 无论指向什么类型, 所有的指针的大小都是一样的,因为都是地址
- 但是指向不同类型的指针是不能直接互相赋值的
- 这是为了避免用错指针
9. 指针的类型转换
- void*表示不知道指向什么东西的指针
- 计算时与char*相同(但不通)
- 指针也可以转换类型
- int *p = &i; void *q =(void *) p;
- 这并没有改变p所指的变量的类型,而是让后人用不同眼光通过p看它所指的变量
- 我不再当你是int啦,我认为你就是个void!
10. 总结
- 需要传入较大的数据时用作参数
- 传入数组后对数组做操作
- 函数返回不止一个结果
- 需要用函数来修改不止一个变量
- 动态申请内存时