C语言之指针

  • 指针大小与32位、64位系统: https://www.cnblogs.com/HOMEofLowell/p/12944900.html
    64位计算机架构一般具有 64 位宽的整数型寄存器,而只有整数寄存器(integer register)才可存放指针值(内存数据的地址),因此指针的大小就自然是 8 个字节了。在 32 位系统中,地址的大小是 32 bit.
    32位系统或者64位系统,实际上说的是 CPU 一次处理数据的能力。
  • 64位系统,这个位数指的是CPU 里面的通用寄存器的数据宽度为64位,也就是说一个地址占二进制位数是64,所以sizeof(double *)==sizeof(int *)==sizeof(char *)==64/8==8
    32位系统,同理,他的一个地址占32位二进制空间,sizeof(double *)==sizeof(int *)==sizeof(char *)==32/8==4
    其实明白了两个系统的寻址原理就能明白,大体就是这个原因。
    地址跟系统有关,但是基本数据类型占的大小是由C语言本身决定。

指针 : 保存地址的变量

  • p代表自身值.
    *p是用来操作指针的, 取指针指向的值.
    &p 操作任何变量, 用来取变量地址.
    int *p; p就是一个变量,存放一个值。这个值有点特殊,它是个存储空间的地址。这是,p成为指针变量。*p如果出现在表达式中,表示一个值,这个值为p中存放的地址处的内容。
    int p;的情况下,p也是一个变量,存放一个整型值。&p也是一个特殊值,这个值为p这个变量在存储空间中的地址。
  • 传指针 f(int * p)定义 ----> f (&i)传;
    f(int * p) 的 p是地址 *p取对应地址的变量

运算符 &
地址跟整数是不一定是相等的 下面是证明 也就是影响sizeof

  1. 64位架构
    #include<stdio.h>
    int main(){
     int i = 0 ;
     printf("%d\n",sizeof(int));4
     printf("%d\n",sizeof(&i));8
     return 0;
    }
  2. 32位架构
    #include<stdio.h>
    int main(){
     int i = 0 ;
     printf("%d\n",sizeof(int));4
     printf("%d\n",sizeof(&i));4
     return 0;
    }
  3. 代码
    #include<stdio.h>
    int main(){
     int i = 0 ; 
     int ans ;
     ans = (int)&i;
     printf("0x%x\n",ans);
     printf("%p\n",&i);
     return 0;
    }
    64位架构
    //0x813f35d0
    //0x7ffc813f35d0
    32位架构
    //0x813f35d0
    //0x813f35d0
  1. 当指针的前一个或下一个有意义的值时 才可以进行赋值
    图片说明
    #include <stdio.h>
    int main() {
     int i = 2 ; 
     int j;
     printf("%p<--- \n",&i);
     printf("%p<--- \n",&j);
     printf("%d<--- \n",sizeof(int));
     return 0 ;
    }
    0x7ffcbe7f207c<--- 
    0x7ffcbe7f2078<--- 
    4<--- 
    数组
    是常量指针
    #include <stdio.h>
    int main() {
     int arr [10] ;
     printf("%p<--- \n",&arr[0]);
     printf("%p<--- \n",&arr);
     printf("%p<--- \n",arr);
     printf("%p<--- \n",&arr[1]);
     printf("%d<--- \n",sizeof(int));
     return 0 ;
    }
    0x7ffc7c6ace40<--- 
    0x7ffc7c6ace40<--- 
    0x7ffc7c6ace40<--- 
    0x7ffc7c6ace44<--- 
    4<--- 

scanf : scanf("%d\n",&x) 记得加& 不然会放到不该放到地方去

  • int i;

  • int* p= &i;

  • int *p,q; : p是 一个地址变量 q是一个普通int类型

  • int* p,q; :p是 一个地址变量 q是一个普通int类型【没有int* 这种类型】

  • int * q, * p; :两者都是地址变量 具体实际值的变量的地址

     void f(int * p ); // 再调用的时候得到某个变量的地址
     int i = 0 ; f(&i); // 在函数里面可以通过这个指针访问外面的这个i
  • scanf("%d",i) ; 传入的是i未初始化的值 被当作地址使用 ,然后拿这个“地址”进行写值

访问那个地址上的变量*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量!!!!
  • 可以做右值也可以为左值
  • int k = * p;
  • *p = k + 1
    void f (int * p)
    {
      printf("%d\n", *p);
      printf("%p\n", p);
      *p = 26;
    }
    int main(void)
    {  
      int i = 6;
      printf("%d\n",i);
      f(&i); //i的值被改
      printf("%d\n",i);
      return 0;
    }
    //6
    //6
    //0x7ffde9dfbfbc
    //26

左值之所以叫左值

  • 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果
  • a[0] = 2; a[0]不是变量
  • *p = 3; *p不是变量
  • 是特殊的值,所以叫做左值

指针的运算符 & *

  • 互相反作用
  • *&y -> *(&y) -> *(y) -> 得到那个地址上的变量 -> y
  • &y -> &(y) -> &(y) -> 得到那个地址上的变量 -> y

♥ 应用场景swap(int *a ,int *b) 通过地址进行交换位置

#include <stdio.h>
void swap(int *pa ,int *pb){
    int t = *pa;
    printf("%d\n",t);//访问那个地址上的变量*, pa是地址 ,*pa是变量
    *pa = *pb;
    *pb = t;
    printf("%p\n",pa);
    printf("0x%x\n",pa);
    //0x7ffc5d313ebc  64位架构 多了7ffc
    //0x5d313ebc
}
int main(){
    int a = 1;
    int b = 2;
    swap(&a,&b);
    printf("%d\n",a);
    printf("%d\n",b);
    return 0;
}

♥ 应用场景:例如int divide(...)里面出现除以0报错 可以用return 0; 来处理,而指针直接用来保存值

  • 可以用于多个返回值,函数返回运算的状态,结果通过指针返回

    int divide(int a, int b , int *res)
    {
        int ret = 1;
        if(b == 0 )ret = 0;
        else
        {
            * res = a/b;
        }
        return ret;
    }
    int main(void)
    {  
        int a = 5 ;
        int b = 2 ;
        int c;
        if(divide(a,b,&c))
            printf("%d / %d = %d \n",a,b,c);
        return 0;
    }
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错

  • -1 或者0

  • 但是当任何数值都是有效的可能结果时,就得分来返回了

  • 后续的语言c++ java采用的是异常机制来解决这个问题的

指针常见错误

p 的定义下来 *p也是被初始化

  • int p = 12;

  • *p = xxxx ;

如果直接int *p ; *p = 12 的话就会报错

  • *p的初始值并非一个明确的值, 如果这样定义的话,会将某个不知道在哪里的区域赋值为12
  • 上面的实例 如下

    int main(void)
    {
        int a = 12 ;
        int *p = &a;  //将a的地址给p   等价于定义一个(int*)p用来存放地址   p = &a
        printf("p=%d\n",p);
        printf("*p=%d\n",*p);
        *p = 2;    //将p的地址对应的值更改为12  *p取出p对应地址的值
        printf("p=%d\n",p);
        printf("*p=%d\n",*p);
        return 0;
    }
    执行完成,耗时:4 ms
    p=2015583504
    *p=12
    p=2015583504
    *p=2

    示例二

    #include <stdio.h>
    int main(){
    int i = 1;
    int * q ;
    int * p ;
    p = &i; //地址赋值
    int j = 2; 
    printf("&i %p\n",&i);
    printf("q %p\n",q);
    printf("p %p\n",p);
    printf("&j %p\n",&j);
    return 0;
    }
    &i 0x7ffca350805
    q  (nil)
    p  0x7ffca350805
    &j 0x7ffca350809

传入函数的数组成了什么

f(int arr[])

  • 函数参数表中的数组实际上是指针
  • sizeof(a) == sizeof(int *)
  • 但是可以用数组的运算符[]进行运算
    #include <stdio.h>
    void print(int arr[]){  //---> 指针!!!
    printf("siezof(arr) = %lu\n",sizeof(arr));
    printf("p = %p\n",arr); 
    }
    int main(){
    int arr [10]  = {1,3,4,5};
    printf("siezof(arr) = %lu\n",sizeof(arr));
    printf("siezof(arr) = %lu\n",sizeof(arr)/sizeof(arr[0]));
    printf("p = %p\n",arr);
    printf("\n");
    print(arr);
    return 0;
    }
    siezof(arr) = 40
    siezof(arr) = 10
    p = 0x7ffc97cd4d1
    siezof(arr) = 8  64位 指针为8个字节
    p = 0x7ffc97cd4d1

int a(int arr[]) ... 格式不比java int[]arr 格式是错的

数组参数 下面四种是等价的

  • int sum(int * ar ,int n )
  • int sum(int * , int )
  • int sum(int ar[] , int n)//看上去像一个数组 实际上是一个指针
  • int sum(int [] , int )

数组变量是特殊的指针 可以用int * arr 代替 int arr[]

  • 数组变量本身表达地址,所以

  • int a[10] ; int *p = a //无需用&取地址

  • 但是数组的单元表达的是变量,需要用&取地址

  • a==&a[0]

  • []运算符可以对数组做,也可以对指针做

  • p[0] = *p 且数组有效长度为1

    void a(int *arr){ //直接对arr进行遍历
    for(int i = 0 ; i < 10 ; i++)
      printf("%d ",arr[i]);
      //1 2 3 5 4 7 8 9 2 5
    }
    int main(void)
    {
      int arr[10] = {1,2,3,5,4,7,8,9,2,5};
      a(arr);
      return 0;
    }
  • 运算符可以对指针做,也可以对数组做

  • *a = 12

  • 数组变量是const的指针 不可变的,所以不能被赋值 !!!!!!!!!!!!
    int b[] => int * const b
    所以不能使用 即int b[] =x= arr

指针跟const

指针是const类型的话

  • 表示一旦得到了某个变量的地址,不能再指向其他变量 / 或者指向别的地址
    int * const q = & i ; // q是const
    *q = 26 ;//ok
    q++ ; // ERROR

所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为了const)
    const int * p = &i; 不能通过p去修改
    *p = 26 ; //ERROR !(*p)是const
    i = 26 ; //OK
    p = & j ; // OK
    图片说明

解释:指针不可修改 / 不可用指针修改 看看 const 在* 号前还是后

  • const在号前 const 所指的东西不能被修改 *p = 26 ; //ERROR
  • const在*号后 *const 指针地址不能被修改 值可以改变
     int i;
     const int* p1 = &i; 所指不能修改 =   int const* p2 = &i;
     int * const p3 = &i; 指针不能修改

转换

  • 总是可以把一个非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};

  • 数组变量已经是const指针(a),这里的const表明数组的每个单元都是const int(1 2 3 4 5后面不能修改了)

  • 所以必须通过初始化进行赋值,不然不能赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
  • int sum(const int a[] , int length);不会对数组进行修改

指针运算

  • 这些算术运算可以对指针做

  • 给指针加,减一个整数(+ += - -= ++ --)往后挪/往后挪

  • 对指针互减

    #include <stdio.h>
    int main(void)
    {
     char ac [] = {0,1,2,3,4,5,6,7,8,9,};
     char *p = ac;              //*p = ac[0]
     printf("p = %p\n", p);
     printf("p+1 = %p\n", p+1);
    
     int ai [] = {0,1,2,3,4,5,6,7,8,9,};
     int *q = ai;               //*p = ai[0]
     printf("q = %p\n", q);
     printf("q+1 = %p\n", q+1);
     return 0;
    }
    p = 0x7ffe0300e2d6   + sizeof(char    )
    p+1 = 0x7ffe0300e2d7
    q = 0x7ffe0300e2a0   + sizeof(int)
    q+1 = 0x7ffe0300e2a4

    图片说明

    #include <stdio.h>
    int main(void)
    {
    int ac [] = {0,11111111,2,3,4,5,6,7,8,9,};
    int* p = ac;//而在地址上+1是没有意义的 char * o = ac 这就是地址加1
    printf("p = %d\n", *(p+1)); ////我们写的代码 p+1  = p + sizeof(?) *(p+1) = > arr[x+1]
    return 0;
    }
    11111111
  • 一个指针加1 表示让指针指向下一个变量
    int a[10]; int * p = a; *(P+1) - > a[1]

  • 如果指针不是指向一片连续分配的空间,如数据 则这种运算是没有任何意义的

  • *(p+n) ac[n]

    #include
    int main(void)
    {
        int ac [] = {0,11111111,2,3,4,5,6,7,8,9,};
        int *p = &ac[0];
        int * q = &ac[6];
        printf("q-p = %d\n", q-p);
        return 0;
    }
    q-p = 6   指针减去指针等于地址差处于sizeof(...)

*p++ --> p++ ---> *P

用于循环遍历

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  • *的优先级虽然高 但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这样直接被翻译成一条汇编指令

指针可以比较

  • 相同?大于?小于?

0地址

  1. 内存都是有0地址的,通常0地址通常是不能碰的地址,所以你的指针不应该具有0值,是系统具有的
  2. 可以用0地址来表示特殊的事情 返回指针无效的,指针没有被真正初始化(先初始化为0)
  3. Null是一个预定定义的符号,表示0地址 有的编译器不愿意你用0来表示0地址

指针的类型

  1. 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  2. 但是指向不同类型的指针是不能直接相互赋值的 ,这是为了避免用错指针 char的指针 int的指针之间强行转换后 char四个才是 int的一个 导致需要贡献出4个char来给int
    char * q = ...;
    int * p = .... ;  
    p =x= q;

指针可以强行转换

  1. void* 表示不知道指向什么东西的指针,计算时与char*相同(但不相通)
  2. 指针可以转换类型 int * p = &i; void* q = (void*)p ;
    这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看到它所指的变量
    我不再当你是int了,而是把你当成void

动态内存分配

int * a = (int)malloc(nsizeof(int)) 然后a就可以当作一个数组使用

  • #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
      int length;
      scanf_s("%d", &length);
      int* arr = (int *)malloc(length * sizeof(int));//默认是void* 类型
      for (int i = 0; i < length; i++) {
          scanf_s("%d", &arr[i]);
      }
      for (int i = length - 1; i >= 0; i--) {
          printf("%d ", arr[i]);
      }
      return 0;
    }
  • 还 free() 因为地址需要初始化为int * p = 0;
    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
      int length;
      scanf_s("%d", &length);
      int* p = (int *)malloc(length * sizeof(int));
      //free(p + 1); //借来在那个地址就在哪个地址上free
      free(p);
      free(NULL);
      return 0;
    }
  • 案例 计算机有多少MB
    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
      void* p;
      int i = 0; 
      while((p = malloc(100 * 1024 * 1024 ))){
          i++;
      }
      printf("%d00MB\n", i);
      return 0;
    }
C/C++语言 文章被收录于专栏

记录学习笔记

全部评论
数组表示的是地址,是一个常数
1 回复 分享
发布于 2020-07-31 14:32
区别: (1):传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。 (2):使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。 (3):使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
1 回复 分享
发布于 2020-10-02 19:31

相关推荐

周述安:这都能聊这么多。别人要是骂我,我就会说你怎么骂人?他要是继续骂我,我就把评论删了。
点赞 评论 收藏
分享
vegetable_more_exercise:1-1.5万,没错啊,最少是1人民币,在区间内
点赞 评论 收藏
分享
1 收藏 评论
分享
牛客网
牛客企业服务