• 指针大小与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;
    }