C语言之指针 (一)

[TOC]文章目录

目录

前言

取地址运算

运算符&

有关&

指针变量

指针

指针变量

运算符 *

*左值

指针的运算符&*

传入地址

指针的使用

指针应用场景一

指针应用场景二

指针最常见的错误

指针与数组

传入函数的数组成了什么?

数组参数

数组变量是特殊的指针

指针与常量(这里的知识点只适用于C99)

指针与const

const数组

总结


前言

本篇内容主要是关于网上学习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);

总结

有关指针的知识点还没有总结玩,让我们下一篇在见吧

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值