Loading...

C语言基础(二)——指针

C语言基础(二)——指针
  iwehdio的博客园:https://www.cnblogs.com/iwehdio/

导图

1、指针的概念

  • 取地址运算符 & :获得变量的地址,它的操作数必须是变量。
  • 输出变量 i 的地址:printf("%p", &i)。(int)&i 与取到的地址是否一致取决于编译器位数。
  • 相邻的变量的地址是紧挨着的,但是由于内存模型是堆栈,地址分配是自顶而下的,后面的元素地址反而靠前。
  • & 的结果的地址 sizeof 在32位下是 4,在64位下是 8。
  • 数组的地址:
    • 对于数组 int a[10],a 与 &a 都表示数组a中第一个元素的地址,即 &a[0]。
    • 每两个相邻的数组单元的地址是顺序紧邻的。
  • 指针:保存地址的变量。
    • 指针的定义:int *p = &i,无论 * 号靠近 int 还是靠近 p,表示的都是 p 是指向 int 类型的指针, 变量 p 存储的地址指向变量 i 。
    • 指针变量的值是内存的地址。
    • 指针作为函数的参数时:
      • 函数原型中,指定了参数类型是 指向 int 的指针:void f(int *p)
      • 调用时,传入的要是某个变量的地址:f(&i)
    • 建议在指针变量定义时,赋值为所指向的变量或者指向0。
  • 取内容运算符 * :访问指针的值所表示的地址上的变量的值。
    • 如果指针 p 是指向 int 类型的,则 *p 可以被看作一个整体,这个整体是 int 类型的变量。
    • 可以通过将 *p 作为左值,更改 p 存储的地址指向的变量的值:*p = 新值
    • 左值:出现的赋值号左边的不是变量,而是值,是表达式运算的结果。

2、指针的使用

  • 指针的应用场景:

    • 需要传入较大的数据时用作参数;
    • 传入数组后对数组做操作;
    • 函数返回不止一个结果;
    • 需要用函数来修改不止一个变量;
    • 动态申请的内存。
  • 指针的使用:

    • 经典的交换两个变量的值:

      void swap(int *a, int *b){
          int t = *a;
          *a = *b;
          *b = t;
      }
      
    • 函数返回多个值,某些值就只能通过指针返回。

      • 传入的参数实际上是需要保存带回的结果的变量。
      • 或者,函数返回运算的状态,结果通过指针返回。
      • C++/Java 会使用异常来处理。
    • 不能对没有设置指向的指针进行操作。

    • 如果在函数声明或指针定义时,* 只是表示所接收的变量或定义的变量是指针类型。这时 * 表示其后定义的变量是指针类型。

    • 如果在函数声明或指针定义以外,* 表示取地址运算符,构成一个整体表示指向的变量内容。

  • 指针与数组:

    • 当数组作为参数传入函数时,实际上传入的是指针。

    • 以下表示在函数的参数列表中是等价的:int *aint *int a[]int []

    • 数组变量是特殊的指针,数组变量本身就表示地址。

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

    • * 运算符可以对指针做,也可以对数组做。

    • 数组变量是 const 的指针,所以不能被赋值。

  • 指针与 const :

    • const 表示其修饰的变量是不可改变的。

    • 如果指针是 const :

      • 表示一旦得到了某个变量的地址,不能再指向其他变量。

      • 如:

        int * const q = &i;		//q是const
        *q = 26;			//可以做
        q++;				//不可以做
        
    • 如果指针指向的变量是 const :

      • 表示不能通过指针去修改这个变量。

      • 如:

        const int * p = &i;		//*p是const
        *p = 26;			//不可以做
        i = 26;				//可以做
        p = &j;				//可以做
        
    • 判断哪个被 const 修饰的标志是 const 在 * 的前面还是后面,在星号前边则所指被修饰,在星号后边则指针被修饰。

    • const 数组:

      • 定义格式:const int a[]={1,2,3}
      • 数组变量已经是 const 的指针了,这里的 const 表明数组的每个单元都是 const int。
      • 所以必须通过初始化进行赋值。
      • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值。
      • 为了保护数组不被函数破坏,可以设置参数为 const 。int f(const int a[])
  • 转换:总是可以把一个非 const 的值转换成 const 的。

    • 如:

      void f(const int* x);
      //如果传入的不是 const
      int a = 15;
      f(&a);		//在函数f内部转换成const,也就是说在函数f内部不会更改指针所指向的值
      //如果传入的是const
      const int b = a;
      f(&b);
      
    • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。比如传入数据结构时。

  • 指针运算:

    • 对指针加一时,指针存储的地址实际上是加了 sizeof(指针指向的数据类型) 。表示的意义是将指针指向下一个元素。
    • 如:对于 数组 a,char *p = a*p表示 a[0] 时,则 *(p+n)表示 a[n]。
    • 如果指针不是指向一片连续分配的空间如数组,则这种运算没有意义。
    • 指针相减,得到的是两个地址的差除以 sizeof(指针指向的数据类型) 。表示的意义时两个指针之间的元素个数。
    • 经典用法:*p++
      • 取出 p 所指的那个数据来,完事之后顺便把 p 移到下一个位置去。
      • * 的优先级虽然高,但是没有 ++ 高。
      • 常用于数组类的连续空间操作。
      • 在某些CPU上,这可以直接被翻译成一条汇编指令。
    • 指针的比较:
      • 大于、等于、相等,比较的是内存中的地址。
      • 数组中单元的地址是线性递增的。
  • 0 地址:

    • 内存中有0地址,但是0地址通常是个不能随便碰的地址。
    • 所以指针不应该具有0值,因此可以用0地址来表示特殊的事情:
      • 返回的指针是无效的。
      • 指针没有被真正初始化(先初始化为0)。
    • NULL是一个预定义的符号,表示0地址。
  • 指针的类型:

    • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址。
    • 但是指向不同类型的指针是不能直接互相赋值的,这是为了避免用错指针。
  • 指针的类型转换:

    • void* 表示不知道指向什么东西的指针。
      • 计算时与 char* 相同(但不相通)。
    • 指针可以转换类型:
      • 指针的强制类型转换:int*p=&i;void*q=(void*)p
      • 这并没有改变 p 所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量。
  • 动态内存分配:

    • 导入头文件:#include <stdlib.h>
    • 申请空间:int* a = (int*)malloc(n * sizeof(int))
      • 用指向 int 的指针 a 指向分配到的数组。
      • n * sizeof(int) 表示存储 n 个 int 类型单元的空间。
      • (int*) 表示强制类型转换 从 void* 到 int* 。
    • 将申请到的空间返还给系统:free(a)
      • 只能还申请来的空间的首地址。
    • 向 malloc 申请的空间的大小是以字节为单位的。
    • 返回的结果是 void*,需要类型转换为自己需要的类型。
    • 内存申请失败时会返回 null 。

参考:C语言程序设计(浙江大学):https://www.bilibili.com/video/av15267247
iwehdio的博客园:https://www.cnblogs.com/iwehdio/
posted @ 2020-03-07 18:07  iwehdio  阅读(421)  评论(0编辑  收藏  举报