数组
一维数组
创建格式
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由此可得:数组名代表首元素地址
例外:
sizeof(数组名),此时表示整个数组&数组名,此时表示整个数组(即取出首元素地址)
数组名+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 0sizeof在计算指针变量大小时输出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