数组

  • 一维数组

    • 创建格式

      type_t arr_name[const_n];
      // type_t - 数组的元素类型
      // arr_name - 数组名
      // const_n - 一个常量表达式,用来指定数组的大小
    • 初始化

      和变量初始化相同,若非静态区定义的数组,若没有给予初始值,则为栈区空间的随机值;若给予了初始值,则为初始值(不足的默认为0)

    • 内存的占用

      数组在内存中是连续的,变量占用内存哪个区取决于定义的方法

  • 二维数组

    • 创建格式

      // 方式一
      int arr[3][4] = { 1,2,3,4,5 };
      // 初始化时,会按照上面行、列的顺序,依次初始化为1,2,3,4,5,剩余默认初始化为0
      
      // 方式二
      int arr[3][4] = { {1,2,3},{4} };
      // 此时会把第一行初始化为1,2,3,第二行初始化为4,剩余默认初始化为0

      注:二维数组初始化时,列不能省略

    • 内存中的占用

      数组元素的地址是从左往右,从上往下依次相邻的

  • 数组名与数组指针

    • 数组名的含义

      int arr[10] = { 0 };
      printf("%p\n%p", arr, &arr[0]);
      
      // 输出结果:000000DBEB1FF838、000000DBEB1FF838

      由此可得:数组名代表首元素地址

      例外:

      1. sizeof(数组名),此时表示整个数组
      2. &数组名,此时表示整个数组(即取出首元素地址)
    • 数组名+1的理解

      (&A)+1 :+1加的是一个A的大小,越界了数组

      (&A[0])+1 +1加的是一个A[0]的大小

    • 数组指针

      (&arr) 就是数组指针,下面用代码演示它的细节

      int arr[10] = { 0 };
      printf("arr = %p\n",arr);
      printf("arr+1 = %p\n", arr+1);
      printf("&arr[0] = %p\n", &arr[0]);
      printf("&arr[0]+1 = %p\n",&arr[0]+1);
      printf("&arr = %p\n",&arr );
      printf("&arr+1 = %p\n", &arr+1);

      输出结果:

      arr = 000000FC165EF5E8
      arr+1 = 000000FC165EF5EC
      // (相差4)
      &arr[0] = 000000FC165EF5E8
      &arr[0]+1 = 000000FC165EF5EC
      // (相差4)
      &arr = 000000FC165EF5E8
      &arr+1 = 000000FC165EF610
      // (相差40)

      由此可得,(&arr)+1跳过了整个数组,故数组指针类型可以访问的内存大小为数组的字节大小


操作符

操作符详解

  • 算术操作符

    操作符 含义 说明
    + 加法 /
    - 减法 /
    * 乘法 /
    / 除法 当两个操作数都为整型时,得到的结果为整型
    % 取模 两个操作数必须都为整型
  • 移位操作符

    操作符 含义 说明
    >> 右移 算术右移(主流编译器):右边丢弃,左边补符号位(正 0、负 1)
    对正数等价:x / 2ⁿ
    注:对于逻辑右移,不管正负一律补 0(Java等语言:>>>)
    << 左移 逻辑 / 算术左移效果一样:左边丢弃,右边补 0
    对正数等价:x * 2ⁿ

    注:

    • 移位对象:整数内存中存储的补码
    • 不能移动负数位,这个操作是未定义的
    • 移动位数必须为整数
    • 这只是一个表达式,并没有改变操作数的值
  • 位操作符

    • 常用的位操作符

      操作符 含义 说明
      & 按位与 对应二进制位全1为1,否则为0
      | 按位或 对应二进制位有一个为1则为1,否则为0
      ^ 按位异或 对应二进制位相同为0,不同为1
    • 使用位操作交换两个变量的内容

      a = a ^ b;
      b = a ^ b;
      a = a ^ b;

      原理是:a ^ b = c,则c ^ b = a,且c ^ a = b

    • 使用位操作访问变量的每个位

      // 统计变量二进制位中1的个数
      int count = 0;
      while (num) {
          count++;
          num = num & (num - 1);
      }
      printf("%d\n", count);
  • 单目操作符

    操作符 说明
    ! 逻辑反操作
    + 正值(一般省略)
    - 负值,取负数
    & 取地址
    sizeof 操作数的类型长度(以字节为单位)
    – 前置–、后置–
    ++ 前置++、后置++
    * 间接访问操作符(解引用操作符)
    (类型) 强制类型转化
  • 复合赋值操作符

    • 算数复合赋值:+= -= *= /= %=
    • 移位复合赋值:>>= <<=
    • 位复合赋值:&= |= ^=
  • 关系操作符

    操作符 含义 操作符 含义
    > 大于 >= 大于等于
    < 小于 <= 小于等于
    == 等于 != 不等于
  • 逻辑操作符

    操作符 含义
    && 逻辑与,表示且的关系
    || 逻辑或,表示或的关系
  • 三目操作符

    exp1? exp2 : exp3
    • 表达式一为真,则表达式二执行,且整个表达式的结果为表达式二的结果
    • 表达式一为假,则表达式三执行,且整个表达式的结果为表达式三的结果

操作符实例

  • sizeof的使用

    • 数组也有类型,也可以对其使用

      int arr[20] = { 0 };
      printf("%d", sizeof(int[20])); // 对数组类型使用
      printf("%d", sizeof(arr)); // 对数组名使用
    • sizeof后面接非类型可以省略括号

      int arr[20] = { 0 };
      printf("%d", sizeof arr);
    • sizeof()括号内的值并不进行运算

      short a = 0;
      int b = 10;
      printf("%d\n", sizeof(a = b + 2));
      printf("%d\n", a);

      输出结果:

      2
      0
    • sizeof在计算指针变量大小时输出4或8

  • 位操作符的使用

    • 把一个数的二进制位上指定一位改成1

      int a = 11;
      // 0000 0000 0000 0000 0000 0000 0000 1011(目标:把1011改成1111)
      // 0000 0000 0000 0000 0000 0000 0000 0100(与上面的数字按位或即可)
      a = a | (1 << 2);
    • 把一个数的二进制位上指定一位改成0

      int a = 11;
      // 0000 0000 0000 0000 0000 0000 0000 1011(目标:把1011改成1001)
      // 1111 1111 1111 1111 1111 1111 1111 1101(与上面的数字按位与即可)
      // 0000 0000 0000 0000 0000 0000 0000 0010(按位取反得到第二行的数)
      a = a & ~(1 << 1);
  • 逻辑操作符的短路

    int i = 0, a = 1, b = 2, c = 3, d = 4;
    i = a++ || ++b || d++;

    执行到a++时为真,逻辑或短路,直接结束逻辑或。故a=2,b=2,c=3,d=4

    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++; 

    a++先调用a,而a为假,故a++执行后,逻辑与短路,直接结束逻辑与。故a=1,b=2,c=3,d=4


操作符属性、隐式类型转化

  • 复杂表达式的求值的影响因素

    • 操作符的优先级
    • 操作符的结合性(优先级相同时)
    • 是否控制求值顺序

    注:操作符的优先级、结合性需要查阅资料获得,这些属于操作符的属性

  • 问题表达式

    a*b + c*d + e*f;

    此表达式计算路径不唯一,执行过程是未定义的,每个编译器输出可能不同

    注:写项目时,为了避免问题表达式,尽量使用括号来规定求值顺序

  • 隐式类型转化(整型提升)

    • 整型提升的意义

      • 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度
      • 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度
      • 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令),所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
    • 转化过程

      • C语言整型算术运算总是至少以缺省整型类型的精度来进行的
      • 为了获得这个精度,表达式中的字符和短整型操作数在使用之前,被转化为普通整型,这种转换称为整型提升
      • 整型提升是按照变量的数据类型的符号位来提升的
      符号位 整型提升规则
      0 在左边补足够的0
      1 在左边补足够的1

      注:无符号数(如unsigned char)统一在左边补足够的0

    • 转化实例

      char a = 3; // 将0000 0000 0000 0000 0000 0000 0000 0011放入,保留右边截断1byte大小,即0000 0011
      char b = 127; // 同理,截断后为0111 1111
      char c = a + b; // 没有发生类型转化,结果为1000 0010
      printf("%d\n", c); // 注意,这里以整型打印,符号为0,在左边补0提升至int的大小
      
      // 最终打印时,整型提升为:0000 0000 0000 0000 0000 0000 1000 0010
      // 补码为:0111 1111 1111 1111 1111 1111 0111 1110
  • 隐式类型转化(算术转化)

    • 概念

      如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行

    • 算术转化体系

      下面的层次体系称为寻常算术转换,优先级低的类型,在表达式中会被转化为优先级高的类型

      优先级(从高到低)
      long double
      double
      float
      unsigned long int
      long int
      unsigned int
      int
« 函数与递归 ← 返回列表 变量存储 »