[TOC]文章目录
目录
前言
本篇内容主要是关于网上学习C语言指针的知识点的总结。
取地址运算
运算符&
前面在scanf里见过&,如scanf("%d",&i);这里面必须有&,不然会出错。
&作用:获得变量的地址,它的操作数必须是变量。
”&“可给我们取出变量的地址,但是地址的大小,它的数据类型和int是否相等取决于编译器,取决于是64位架构还是32位架构。所以用printf输出地址,需要用%p,而不是真的直接把它当成整数,地址和整数并不永远是相同的,这和架构是有关的。
那么为什么变量会有地址?C语言的变量是放在内存中的,如四个字节的int在内存中要占据一定的地方,它放在某个地方它就有地址。地址这些东西用十六进制表达比较方便。
格式:int i; int i;
printf("%p\n",&i); printf("%x\n",&i);
%x、%X和%p的相同点都是16进制,不同点是%p按编译器位数长短(32位/64位)输出地址,不够的补零。
%p中的p是pointer(指针)的缩写。%p是打印地址(指针地址)的,十六进制形式,但会全部打完,即有多少位打印多少位。32位编译器的指针变量为4个字节(32位),64位编译器的指针变量为8个字节(64位)。
%x:无符号十六进制整数(字母小写,不补零)
%X:无符号十六进制整数(字母大写,不补零)
有关&
&不能对没有地址的东西取地址,如:&(a+b),&(a++) ,&(++a)等
有关变量的地址,相邻的变量的地址,&的结果sizeof:
#include<stdio.h>
int main(void)
{
int i=0;
int p;
printf("%p\n",&i);
printf("%p\n",&p);
return 0;
}
输出:0xbff81d6c
0xbff81d68
c在十六进制中表示12,6c与68相差4。i在内存中的位置更高,p在更低的地方。i与p都是本地变量,分配在内存中叫做stack(堆栈)的地方,这个地方中分配内存是自顶向下分配的,所以先写的变量地址更高,后写的变量地址更低,但它们是紧挨着的,因为它们之间的差距为4,刚好为sizeof(int)
有关数组的地址,数组单元的地址,相邻的数组单元的地址:
#include<stdio.h>
int main(void)
{
int a[10];
printf("%p\n",&a); //把a交给取地址符取出它的地址
printf("%p\n",a); // 直接把数组变量名a当作它的地址
printf("%p\n",&a[0]); //取出数组中第一个元素的地址
printf("%p\n",&a[1]); //取出数组中第二个元素的地址
return 0;
}
输出:
0xbff8dd44
0xbff8dd44
0xbff8dd44 // 地址&a等于a等于&a[0]
0xbff8dd48 //相邻的数组单元之间的差距永远是4
指针变量
指针
指针类型的变量就是保存地址的变量(就是保存&取地址符得到的其他变量地址的变量)
int i;
int* p=&i; //“*”在这里表示p是一个指针(point),它指向的是一个int,现在把i的地址交给p,即p的
值为i的地址,叫做p指向i
int* p,q; //“*”可以靠近int,也可以靠近p,意思是一样的,都表示p是一个指针,指向int。
int *p,q; //q只是一个普通的int类型的变量。所以我们是把星号(*)加给了p,*p是一个int,于是 p是一个指针
指针变量
指针类型的变量的值是内存的地址,普通变量的值是实际的值,指针变量的值是具有实际值的变量的地址。
把一个指针作为参数的时候,可以这样写:
- void f(int *p); // f函数要一个int的指针
- 在被调用的时候得到了某个变量的地址
- int i=0; f(&i); //当我们去调用f函数的时候,要交给它一个地址
- 在函数里面可以通过这个指针访问外面的这个i
#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=0xbff17d70
p=0xbff17d70
k=6
运算符 *
*是一个单目运算符,用来访问指针的值所表示的地址上的变量
它可以做右值也可以做左值(可放在赋值号的右边读它的值,也可放在赋值号左边写它的值)
int k=*p; //*p这个整体看作一个整数
*p=k+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);
printf("*p=%d\n",*p);
*p=26;
}
void g(int k)
{
printf("k=%d\n",k);
}
输出:
&i=0xbffbcd70
p=0xbffbcd70
*p=6 //通过p这个指针,我们访问到了p所指的int i里面的值
k=26 //意味着经历 f函数的调用之后,i的值被改变了
在前面函数中说过,C语言的函数调用的时候发生的参数的转移是一种值的传递,所以在函数里面函数的参数和调用它的地方没有任何的联系。
现在发生的仍然是值的传递,i的地址值被传进了f函数。因为传进来的是地址,所以通过这个地址在f函数内部可以以这种方式去访问到外边的i变量。p的值就是i的地址,*p就代表了i变量。
*左值
左值之所以叫左值是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果:
a[0]=2; //数组的方括号也是运算符,是取下标(单元)的运算符
*p=3; //a[0]与*p都不是变量
是特殊的值,所以叫左值
指针的运算符&*
&与*相互反作用
- *&yptr --> *(&yptr) --> *(yptr的地址) -->得到那个地址上的变量 --> yptr
- &*yptr --> &(*yptr) --> &(y) --> 得到y的地址,也就是yptr --> yptr
传入地址
为什么忘了&符号 int i;scanf("%d",i); 编译没有报错?
因为输入i的值正好是整数,编译器是32位架构,整数和地址是一样大的。把一个整数输进去与把一个地址输进去对于scanf来说它没辨别出区别,它以为输进去的i是i的地址,所以编译没有报错,但是运行一定会出错,因为scanf把它读进来的数字写到不该写的地方。
指针的使用
指针应用场景一
交换两个变量的值
#include<stdio.h>
void swap(int *pa,int *pb);
int main(void)
{
int a=5,b=6;
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
void swap(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
}
输出: a=6,b=5
指针应用场景二
1)这个场景是说函数要返回多个值,某些值就只能通过指针返回,也就是说,传入的参数实际上是需要保存带回的结果的变量。
#include<stdio.h>
void minmax(int a[],int len,int *min,int *max); /*len表达数组有多大。返回值只能返回一个
因此用指针来做*/
int main(void)
{
int a[]={1,2,3,4,5,6,7,8,9,12,13,14,46,17,21,22,55};
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(a[i]<*min){
*min=a[i];
}
if(a[i]>*max){
*max=a[i];
}
}
}
输出: min=1,max=55
2)函数返回运算的状态,结果通过指针返回
常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:-1或0(在文件操作会看到大量的例子)。但是当任何数值都是有效的可能的结果时,就得分开返回了。返回时,状态用函数的返回来返回,即return;而实际的值通过指针来返回。
这种指针应用的场景是运算可能会出错,因此错误要通过另外的途径来表达出来。
后续的语言(C++,Java)采用了异常机制来解决这个问题。
指针最常见的错误
定义了指针变量,还没有指向任何变量,就开始使用指针。
指针与数组
传入函数的数组成了什么?
void minmax(int a[],int len,int *min,int *max);
函数参数表中的数组实际上是指针,即 sizeof(a)==sizeof(int*)。但是可以用数组的运算符[]进行运算
数组参数
在参数表中以下四种函数原型是等价的:
int sum(int *ar,int n);
int sum(int *,int );
int sum(int ar[],int n);
int sum(int [],int );
数组变量是特殊的指针
- 数组变量本身表达地址,所以 inta[10];int *p=a; //无需用&取地址
但是数组的单元表达的是变量,需要用&取地址
a==&a[0]
- []运算符可以对数组做,也可以对指针做: p[0]<==>a[0]。 p[0]是把p所指的地方当作一个数组
组,把p所指的地址上面的第一个整数取出来作为 p[0]
- *运算符可以对指针做,也可以对数组做:*a=25;
- 数组变量是const的指针,所以不能被赋值(两个数组之间不能直接赋值
int a[]<==>int *consra=……
指针与常量(这里的知识点只适用于C99)
const是一个修饰符,它加在变量的前面,这个变量便不能被修改。指针是一种变量,在指针变量里有两个东西,一个是指针本身,一个是指针所指的那个变量
指针与const
当指针遇上const,指针指向了一个变量,指针本身可以是const,它所指的变量也可以是const
指针是const
表示一旦得到了某个变量的地址,不能再指向其他变量
int *const q=&i; //q是const,q的值(i的地址)不能被改变,即q指向了i这个事实不能被改变
*q=26; //OK 因为q所指的i不是const
q++; //ERROR
所指是const
表示不能通过这个指针去修改那个变量(并不能使那个变量成为const)
const int *p=&i; //p所指的int是个const,p得到了i的地址
*p=26; //ERROR! (*p是const)。p可以指向别的,i可以被赋值,但不能通过p去修改i
i=26; //OK
p=&j; //OK
区分
int i;
const int* p1=&i;
int const* p2=&i; //const 在*前,表示它所指的东西不能被修改
int *const p3=&i; //const在*后,表示指针不能被修改
判断哪个被const了的标志是const在*的前面还是后面
转换
总是可以把一个非const的值转换成const的
void f(const int *x); //表示在f函数内部不会动指针x所指的值
int a=15;
f(&a); //ok
const int b=a;
f(&b); //ok
b=a+1; //error!
当要传递的参数类型比地址大的时候,这是常用的手段;既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。
const数组
const inta[]={1,2,3,4,5,6};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int。所以必须通过初始化进行赋值。
因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值。为了保护数组不被函数破坏,可以设置参数为const
int sum(const int a[],int length);
总结
有关指针的知识点还没有总结玩,让我们下一篇在见吧