C语言操作符详解 隐式类型转换 (整型提升 算数转换)

目录

  • 一、算术操作符
  • 二、左移操作符 右移操作符
    • 1、二进制序列
    • 2、左移操作符
      • 3、右移操作符
        • 3.1、逻辑运算/算术运算
        • 3.2、对于移位运算符,不要移动负数位,这是标准未定义的
        • 3.3、对于移位操作符 操作数必须是整数
  • 三、位操作符
    • 1、&按位与:只要有0就是0,两个同时为1才是1
    • 2、| 按位或:只要有1就是1
    • 3、^ 按位异或:相同为0,相异为1
    • 4、使用:
      • 4.1、可以求a的二进制序列的最低位是多少
      • 4.2、可以统计内存中补码二进制序列位有多少个1
    • 5、一道变态的面试题
      • 5.1、使用变量:
      • 5.2、用和减
      • 5.3、用按位异或
      • 结论:
  • 四、赋值操作符
    • 1、赋值操作符可以连续赋值(不建议)
    • 2、复合赋值符
  • 五、单目操作符
    • 1、! 真变假 假变真
    • 2、- 负值 +正值
    • 3、&取地址 *解引用操作符
      • *p 左值右值
      • 数组也可以&
    • 4、==sizeof==
    • 5、~ 对一个数的二进制按位取反(所有位)
      • 改二进制位的数再改回来
    • 6、-- ++前置,后置-- ++
    • 7、(类型)强制类型转换
    • sizeof和数组 此代码输出什么
  • 六、关系操作符
  • 七、逻辑操作符
    • 1、==只关注真假==
    • 2、逻辑与和或的特点:
      • 360笔试题
  • 八、条件操作符(三目操作符)
        • 使用条件表达式实现找两个数中较大值
  • 九、逗号表达式
  • 十、练习
    • 1、进制转换
    • 2、小乐乐排电梯
    • 3、输出一个正整数,为n和m的最大公约数与最小公倍数之和。
          • 超时 效率低
          • 辗转相除法求最大公约数 最小公倍数=n*m/max
    • 4、 从键盘任意输入一个字符,编程判断是否是字母
    • 5、空心正方形图案
        • 实心
        • 改判断条件
    • 6、去最高分和最低分的平均成绩
    • 7、判断三角形类型
    • 8、数组名
    • 9、将数组A中的内容和数组B中的内容进行交换。(数组一样大)
        • 数组传址解释
        • exchange
        • 打印
    • 10、整形数组 --- 初始化 打印 逆置
  • 十一、下标引用、函数调用和结构成员
    • 1、[]下标引用操作符
      • []是操作符 arr和4是两个操作数
    • 2、函数调用操作符 - ()
      • strlen返回无符号整型
    • 3、// . 结构体.成员名 // -> 结构体指针->成员名
      • 3.1、struct Book 结构体类型 struct Book b 结构体变量
      • 3.2、字符串拷贝 - strcpy
      • 3.3、传地址
      • 3.4、结构体成员访问操作符
  • 十二、表达式求值
    • 一、隐式类型转换(整型提升 算数转换)
      • 1、整型提升
        • 1.2、整型提升的意义
        • 1.3、如何进行整体提升呢?
        • 1.4、整形提升的例子
          • 1.4.1、只有char和short会整型提升成int
          • 1.4.2、参与运算
      • 2、算数转换
      • 3、操作符的属性
        • 3.1、复杂表达式的求值有三个影响的因素
          • 3.1.1、优先级 结合性
          • 3.1.2、是否控制求值顺序
        • 3.2、问题表达式


一、算术操作符

算术操作符 + - * / %
// % - 取模
// & 取模操作符只能针对整型类型

int main()
{
	int ret1 = 9 / 2;
	printf("%d\n", ret1); // 4
	// 对于 /(除号) 两边都是整数,执行的是整数除法

	double ret2 = 9 / 2.0;
	printf("%lf\n", ret2); // 4.500000

	return 0;
}

二、左移操作符 右移操作符

1、二进制序列

移位操作符,移动的是二进制位
对于整数的二进制有3种表示形式:原码,反码,补码

  1. 正整数 - 原码,反码,补码相同
  2. 负整数:
    原码 - 直接按照数字的正负写出的二进制序列
    反码 - 原码的符号位不变,其它位按位取反得到的
    补码 - 反码+1
    整数在内存中存储的是二进制的补码
int main()
{
	int a = 5; // 4byte = 32bit
	int b = a << 1;
	//00000000000000000000000000000101 - 正整数 原反补相同
	//00000000000000000000000000000101
	//00000000000000000000000000000101

	int c = -1;
	int d = c << 1;
	//10000000000000000000000000000001 - -1的原码
	//11111111111111111111111111111110 - 反码
	//11111111111111111111111111111111 - 补码

	return 0;
}

2、左移操作符

左边丢弃,右边补0

int main()
{
	int a = 5;
	int b = a << 1;
	printf("%d\n", b); // 10

	int c = -1;
	int d = c << 1;
	printf("%d\n", d); // -2 打印的是原码的值
	//11111111111111111111111111111111 - -1补码
	// (1)1111111111111111111111111111111(0) - 左移 左边丢弃右边补0
	//1111111111111111111111111111111(0) - -1左移1的补码
	//11111111111111111111111111111101 - 反
	//10000000000000000000000000000010 - 原 -2
	
	return 0;
}

3、右移操作符

3.1、逻辑运算/算术运算

右移运算分两种:
- 逻辑移位–右边丢弃,左边补0
- 算术移位–右边丢弃,左边补原符号位(VS2019采用算术右移)

int main()
{
	int a = -1;
	int b = a >> 1;
	printf("%d\n", b);
	//11111111111111111111111111111111 - -1补码
	//()1111111111111111111111111111111(1)
	// 如果是逻辑移位就补0
	// 如果是算术移位就补1 结果是还是-1(当前是算术移位)

	return 0;
}

3.2、对于移位运算符,不要移动负数位,这是标准未定义的
int main()
{
	int a = 15;
	int b = a >> -1; // err - 标准为定义行为

	return 0;
}

3.3、对于移位操作符 操作数必须是整数
int main()
{
	float c = 4.5f;
	c >> 1; // err
}

三、位操作符

他们的操作数必须是整数
& 按位与
| 按位或
^ 按位异或

1、&按位与:只要有0就是0,两个同时为1才是1

int main()
{
	int a = 3;
	int b = -2;
	int c = a & b;
	printf("%d\n", c);
	// 00000000000000000000000000000011 - 3

	// 10000000000000000000000000000010 - -2的原码
	// 11111111111111111111111111111101 - -2的反码
	// 11111111111111111111111111111110 - -2的补码

	// 00000000000000000000000000000011 - 3补码
	// 11111111111111111111111111111110 - -2的补码
	// 00000000000000000000000000000010 - a&b

	// %d - 说明我们要打印c的值,以有符号的形式
	// 00000000000000000000000000000010 - a&b
	// 正数 原反补相同 2

	return 0;
}

2、| 按位或:只要有1就是1

int main()
{
	int a = 3;
	int b = -2;
	int c = a | b;
	printf("%d\n", c);

	// 00000000000000000000000000000011 - 3补码
	// 11111111111111111111111111111110 - -2的补码
	// 11111111111111111111111111111111 - a|b

	// 以%d打印
	// 算的是补码 最高位是负 要算原码
	// 11111111111111111111111111111111 - a|b 补码
	// 11111111111111111111111111111110 - 反
	// 10000000000000000000000000000001 - 原 -1

	return 0;
}

3、^ 按位异或:相同为0,相异为1

int main()
{
	int a = 3;
	int b = -2;
	int c = a ^ b; // 二进制位异或
	printf("%d\n", c);

	// 00000000000000000000000000000011 - 3补码
	// 11111111111111111111111111111110 - -2的补码
	// 11111111111111111111111111111101 - a^b

	// %d打印 负数计算原码
	// 11111111111111111111111111111101 - a^b 补
	// 11111111111111111111111111111100 - 反
	// 10000000000000000000000000000011 - 原 -3

	return 0;
}

4、使用:

4.1、可以求a的二进制序列的最低位是多少

int main()
{
	int a = 15;
	a & 1;
	// 00000000000000000000000000001111 - 15
	// 00000000000000000000000000000001 - 1
	// 00000000000000000000000000000001 - a&1

	return 0;
}

4.2、可以统计内存中补码二进制序列位有多少个1

int main()
{
	// 可以统计所有的内存中的补码二进制序列位有多少个1 循环32次
	int a = 15;
	a & 1;
	a = a >> 1; // 让每一位都有机会来到最低位

	return 0;
}

5、一道变态的面试题

不能创建临时变量(第三个变量),实现两个数的交换。

5.1、使用变量:

int main()
{
	int a = 3;
	int b = 5;
	int tmp = 0;
	tmp = a;
	a = b;
	b = tmp;

	printf("a=%d b=%d\n", a, b);

	return 0;
}

5.2、用和减

存在溢出的问题

int main()
{
	int a = 3;
	int b = 5;
	
	a = a + b;
	b = a - b; // 和减b是a  此时b是a
	a = a - b; // 和减b(此时是a)是a 

	printf("a=%d b=%d\n", a, b);

	return 0;
}

5.3、用按位异或

int main()
{
	int a = 3;
	int b = 5;

	a = a ^ b;
	// 011      
	// 101        b
	// 110 a^b    a
	b = a ^ b;
	// 110        a
	// 101
	// 011 a^b    b
	a = a ^ b;
	// 110
	// 011        b
	// 101        a

	// a^b得出一个密码 密码与原来的a^得到原来的b 密码与原来的b^得到原来的a

	printf("a=%d b=%d\n", a, b);

	return 0;
}

用变量求最好 用异或代码的可读性不够好,且只适用于整形


结论:

a^a=0 对应的二进制位都相同,都为0
0^a=a 000 011–011
aab=b a^a=0 0^b=b
aba=b
异或是支持交换律的


四、赋值操作符

1、赋值操作符可以连续赋值(不建议)

int main()
{
	int a = 10;
	int x = 0;
	int y = 20;
	a = x = y + 1; // 连续赋值

	// 清晰且便于调试
	
	x = y + 1;
	a = x;

	return 0;
}

2、复合赋值符

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

int main()
{
	int a = 10;
	a = a >> 1;
	a >>= 1; // 符合赋值

	a = a + 10;
	a += 10;

	return 0;
}

五、单目操作符

只有一个操作数的操作符

! 逻辑反操作
负值- +正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
前置,后置-- ++
间接访问操作符(解引用操作符)*
(类型) 强制类型转换

1、! 真变假 假变真

int main()
{
	int a = 5;
	int b = !a;
	printf("%d\n", b); // 0

	int c = 0;
	int t = !c;
	printf("%d\n", t); // 1

	return 0;
}
int main()
{
	int a = 10;
	if (a) // a为真打印hehe
	{
		printf("hehe\n");
	}
	if (!a) // a为假打印haha
	{
		printf("haha\n");
	}
	return 0;
}

2、- 负值 +正值

int main()
{
	int a = 10;
	a = -a;
	printf("a=%d\n", a); // -10
	a = -a;
	printf("a=%d\n", a); // 10
	return 0;
}

3、&取地址 *解引用操作符

int main()
{
	int a = 10;
	int* p = &a;

	*p = 20;
	printf("%d\n", a);

	return 0;
}

*p 左值右值

左值 - 指向的空间
右值 - 空间里的内容

int main()
{
	int a = 10;
	int* p = &a;

	int b = *p; // 指向那块空间的值 赋给b

	*p = 20; // 通过解引用找到a的空间 把空间改成20
	printf("%d\n", a);

	return 0;
}

数组也可以&

int main()
{
	int arr[10] = { 0 };
	
	arr+1; // 取首元素的地址
	&arr[0]+1; // 取首元素的地址

	&arr+1; // 取出数组的地址

	// 此三个地址一样,但意义不同 
	// 加1 前两个加4 后一个加40

	return 0;
}

4、sizeof

sizeof是操作符,不是函数
sizeof是计算变量或者类型创建变量的内存大小,单位是字节,和内存中存放什么数据没有关系

int main()
{
	char arr[10] = "abc";
	printf("%d\n", sizeof(arr)); // 10
	printf("%d\n", strlen(arr)); // 3 字符串的长度,计算的是\0之前出现的字符个数
	return 0;
}
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a)); // 4
	printf("%d\n", sizeof(int)); // 4

	return 0;
}

sizeof内部的表达式是不参与运算

int main()
{
	int a = 5;
	short s = 10;
	printf("%d\n", sizeof(s = a + 2)); 
	printf("%d\n", s);

	// sizeof内部的表达式是不参与运算的!

	// 源文件sizeof变成可执行程序test.exe 会经过编译和链接 生成.exe后运行
	// 最终取决于short的类型 大小是2 在链接是已经是2 在运行时没有机会执行
	// 2 10
	return 0;
}

5、~ 对一个数的二进制按位取反(所有位)

int main()
{
	int a = 0;
	int b = ~a;
	printf("%d\n", b);

	// 00000000000000000000000000000000
	// 11111111111111111111111111111111 - -1的二进制位补码是全1
	// 打印%d 转原码
	// 11111111111111111111111111111110 - 反
	// 10000000000000000000000000000001 - 原 -1
	return 0;
}

改二进制位的数再改回来

int main()
{
	int a = 13;
	// 00000000000000000000000000001101 - 正数原反补相同
	a |= (1 << 1);
	printf("%d\n", a);
	// 1左移1  00000000000000000000000000000010
	// 按位或等到a  00000000000000000000000000001111  - 15


	// 00000000000000000000000000001111  - 15 此时的a
	// 11111111111111111111111111111101 只要用这个&就可以改回来
	// 这个二进制序列是由1<<1再按位取反得到
	a &= (~(1 << 1));
	printf("改回来%d\n", a);

	return 0;
}

6、-- ++前置,后置-- ++

后置++,先使用,后++
前置++,先++,后使用

int main()
{
	int a = 10;
	int b = a++; // 后置++,先使用,后++
	printf("%d\n", b); // 10
	printf("%d\n", a); // 11

	int c = 10;
	int e = ++c; // 前置++,先++,后使用
	printf("%d\n", e); // 11
	printf("%d\n", c); // 11

	return 0;
}

错误书写

//err
int main()
{
	int a = 1;
	int b = (++a) + (++a) + (++a); // err
	printf("b=%d\n", b); // 12 在不同编译器上结果不同
	return 0;
}

7、(类型)强制类型转换

int main()
{
	int a = (int)3.14; // 默认写出的是浮点数是double的
	printf("%d\n", a);

	return 0;
}

sizeof和数组 此代码输出什么

#include 

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
	printf("%d\n", sizeof(ch));//(4)
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));//(1)
	printf("%d\n", sizeof(ch));//(3)
	test1(arr);
	test2(ch);
	return 0;
}

数组名是单独放在sizeof内部,数组名表示整个数组计算的是整个数组的大小
&数组名,数组名表示整个数组,取出的是整个数组的地址

10个整形 40个字节。10个字符 10个字节
数组传参 首元素地址 本质上是void test1(int* arr) 求的是指针大小 4/8
40 4 10 4

C语言操作符详解 隐式类型转换 (整型提升 算数转换)_第1张图片


六、关系操作符

>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”

  1. 用于同类型变量
  2. 注意==与=写错导致的错误

七、逻辑操作符

&& 逻辑与
|| 逻辑或

1、只关注真假

逻辑与判断的真用1表示 假用0表示
逻辑与两个都为真是1 逻辑或只要有一个为真就是1

int main()
{
	int a = 0;
	int b = 5;

	int c = a && b; // 逻辑与
	int e = a || b; // 逻辑或
	printf("c=%d\n", c); // 0
	printf("e=%d\n", e); // 1

	return 0;
}

区分逻辑与与和按位与
区分逻辑或与和按位或

1&2 ----0
1&&2---->1

1 | 2----->3
1 || 2---- > 1


2、逻辑与和或的特点:

360笔试题

int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	//i = a++||++b||d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	return 0;
}

// a++先使用后++ 所以0为假 后面不运算 a++是1 bcd都没有变 1 2 3 4
// 对于逻辑与来说,左边为假,右边就不用计算了

int main()
{
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	// 改a=1,2 3 3 5
	return 0;
}
int main()
{
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	// a++是假 后面不用算了 只有a++了 2 2 3 4
	return 0;
}
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	// 1 3 3 4 ab自增
	return 0;
}

八、条件操作符(三目操作符)

exp1 ? exp2 : exp3

使用条件表达式实现找两个数中较大值
int main()
{
	int a = 3;
	int b = 5;
	int m = 0;
	m = (a > b ? a : b);
	// 如果a>b 就把a赋给m 否则将b赋给m
	// (a > b) ? (m = a) : (m = a);
}

九、逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行整个表达式的结果是最后一个表达式的结果

int main()
{
	//代码1
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式

	//代码2
	if (a = b + 1, c = a / 2, d > 0) // 前面依次计算 最后一个是if的判断条件

	//代码3
	a = get_val();
	count_val(a);
	while (a > 0)
	{
		//业务处理
		a = get_val();
		count_val(a);
	}

	//如果使用逗号表达式,改写:
	while (a = get_val(), count_val(a), a > 0)
	{
		//业务处理
	}
}


十、练习

1、进制转换

输入一个正整数n(1 ≤ n ≤ 109)
输出描述:
输出一行,为正整数n表示为六进制的结果

int main()
{
    int num = 0;
    int arr[50] = { 0 };
    //输入
    scanf("%d", &num);
    //转换
    int i = 0;
    while (num)
    {
        arr[i] = num % 6; // %6/6得到最低位 倒着打印出来
        i++;
        num = num / 6;
    }
    //输出
    for (i--; i >= 0; i--) //循环停下来时i++ 指向下一个 先i-- 倒着打印 调整也是i--
    {
        printf("%d", arr[i]);
    }
    return 0;
}

2、小乐乐排电梯

n个人在等电梯。电梯每次可以乘坐12人,每次上下需要的时间为4分钟(上需要2分钟,下需要2分钟)。
输入包含一个整数n (0 ≤ n ≤ 109)
输出一个整数,即小乐乐到达楼上需要的时间。

int main()
{
	int n = 0;
	int t = 0;
	//输入
	scanf("%d", &n);
	//计算
	int t = n / 12 * 4 + 2;
	//输出
	printf("%d\n", t);

	return 0;
}

3、输出一个正整数,为n和m的最大公约数与最小公倍数之和。

超时 效率低
int main()
{
	long n = 0;
	long m = 0;
	//输入
	scanf("%d %d", &n, &m);
	//计算
	//1. 计算最大公约数
	long long max = (m > n) ? (n) : (m);
	while (1)
	{
		if ((m % max == 0) && (n % max == 0))
		{
			break;
		}
		max--;
	}
	//2. 计算最小公倍数
	long long min = (m > n) ? (m) : (n); // 最小公倍数从两数中较大开始
	while (1)
	{
		if ((min % m == 0) && (min % n == 0))
		{
			break;
		}
		min++;
	}
	//3. 求和
	long long sum = max + min;
	//打印
	printf("%lld", sum);

	return 0;
}
辗转相除法求最大公约数 最小公倍数=n*m/max
int main()
{
	long long n = 0;
	long long m = 0;

	scanf("%d %d", &n, &m);

	long long n2 = n;
	long long m2 = m;

	long long c = 0;
	while (c = m2 % n2)
	{
		m2 = n2;
		n2 = c;
	}

	long long min = m * n / n2;

	long long sum = min + n2;

	printf("%lld", sum);

	return 0;
}

4、 从键盘任意输入一个字符,编程判断是否是字母

从键盘任意输入一个字符,编程判断是否是字母(包括大小写)。
多组输入,每行输入包括一个字符。
输出该字符是字母(YES)或不是(NO)。

int main()
{
	// 多组输入
	int ch = 0;
	while ((ch = getchar()) != EOF)
	{
		if (ch >= 'A' && ch <= 'Z' || (ch >= 'a' && ch <= 'z'))
		{
			printf("YES\n");
		}
		else
		{
			printf("NO\n");
		}
		getchar(); // 处理\n
	}

	return 0;
}

5、空心正方形图案

多组输入,一个整数(3~20)
C语言操作符详解 隐式类型转换 (整型提升 算数转换)_第2张图片

实心
#include 

int main()
{
    int n = 0;
    while (scanf("%d", &n) != EOF)
    {
        int i = 0;
        for (i = 0; i < n; i++)
        {
            int j = 0;
            for (j = 0; j < n; j++)
            {
                printf("* ");
            }
            printf("\n");
        }
    }

    return 0;
}
改判断条件
int main()
{
    int n = 0;
    while (scanf("%d", &n) != EOF)
    {
        int i = 0;
        for (i = 1; i <= n; i++) // i=0 i
        {
            // 打印一行
            int j = 0;
            for (j = 1; j <= n; j++)
            {
                // 如果判断条件是从0开始 这里的下标就是i==1和i==n-1
                if (i == 1 || i == n || j == 1 || j == n)
                {
                    printf("* ");
                }
                else
                {
                    printf("  ");
                }
            }
            printf("\n");
        }
    }

    return 0;
}

6、去最高分和最低分的平均成绩

输入7个整数(0-100)一行,输出去掉最高分和最低分的平均成绩,小数点后保留2位,每行输出后换行。

int main()
{
	int i = 0;
	int sc = 0;
	int sum = 0;
	int max = 0; // 假设0就是最高分
	int min = 100; // 假设100就是最低分

	for (i = 0; i < 7; i++)
	{
		scanf("%d", &sc);
		sum += sc;
		if (sc > max)
			max = sc;
		if (sc < min)
			min = sc;
	}

	double avg = (sum - max - min) / 5.0;

	printf("%.2lf\n", avg); // .2 保留两位小数点

	return 0;
}

7、判断三角形类型

每一行输入三个a,b,c(0 针对每组输入数据,输出占一行,如果能构成三角形,等边三角形则输出“Equilateral triangle!”,
等腰三角形则输出“Isosceles triangle!”,其余的三角形则输出“Ordinary triangle!”,反之输出“Not a triangle!”。

int main()
{
	int a = 0;
	int b = 0;
	int c = 0;

	//while (~scanf("%d %d %d", &a, &b, &c))
	// EOF是-1 -1补码是全1 按位取法是全0 不等于EOF就不会为0

	while (scanf("%d %d %d", &a, &b, &c) != EOF)
	{
		if ((a + b > c) && (a + c > b) && (b + c > a))
		{
			// 判断边
			if (a == b && b == c)
			{
				printf("Equilateral triangle!\n");
			}
			else if ((a == b && a != c) || (a == c && a != b) || (b == c && b != a))
			{
				printf("Isosceles triangle!\n");
			}
			else
			{
				printf("Ordinary triangle!\n");
			}
		}
		else
		{
			printf("Not a triangle!\n");
		}
	}

	return 0;
}

8、数组名

int main()
{
	int arr[] = { 1, 2, (3,4), 5 }; // 逗号表达式 1 2 4 5
	printf("%d\n", sizeof(arr));
	// 数组名表示整个数组 计算的是整个数组的大小 4个元素 每个元素是int 
	// 4*4 = 16
	return 0;
}

sizeof计算的是内存占用空间的大小
strlen求字符串长度 \0之前的字符


9、将数组A中的内容和数组B中的内容进行交换。(数组一样大)

数组传址解释
void test(int arr[], int sz) // 传的是地址
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", arr[i]); // arr[i] --->  *(arr+i)
		printf("%d ", *(arr + i));
	}
}

int main()
{
	int arr[10] = { 1, 2, 3, 4 ,5, 6, 7, 8, 9, 10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	test(arr, sz); // 数组传参过去的是数组的首元素的地址

	return 0;
}
exchange
void exchange(int a[], int b[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int tmp = a[i];
		a[i] = b[i];
		b[i] = tmp;
	}
}

int main()
{
	int a[] = { 1, 3, 5, 7, 9 };
	int b[] = { 2, 4, 6, 8, 10 };
	int sz = sizeof(a) / sizeof(a[0]);
	exchange(a, b, sz);

	return 0;
}
打印
#include 

int main()
{
    int arr1[10] = { 1, 2, 3, 4, 5 };
    int arr2[10] = { 6, 7, 8, 9, 10 };

    int i = 0;
    for (i = 0; i < 10; i++)
    {
        int temp = 0;
        temp = arr1[i];
        arr1[i] = arr2[i];
        arr2[i] = temp;
    }
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr1[i]);
    }
    printf("\n");
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }

    return 0;
}

10、整形数组 — 初始化 打印 逆置

创建一个整形数组,完成对数组的操作:
实现函数init() 初始化数组为全0
实现print() 打印数组的每个元素
实现reverse() 函数完成数组元素的逆置。

void Init(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		*(arr + i) = 0;
	}
}

void Print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

Reverse(int arr[], int sz)
{
	int l = 0; // 左下标
	int r = sz - 1; // 右下标

	while (l < r)
	{
		int tmp = arr[l];
		arr[l] = arr[r];
		arr[r] = tmp;
		l++;
		r--;
	}
}

int main()
{
	int arr[10];
	int sz = sizeof(arr) / sizeoff(arr[0]);
	Init(arr, sz); //初始化为全0   0-9
	Print(arr, sz); // 打印
	Reverse(arr, sz); // 逆置
	Print(arr, sz); // 逆置后再打印   9-0
	return 0;
}

十一、下标引用、函数调用和结构成员

1、[]下标引用操作符

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	int i = 0;

	for (i = 0; i < 5; i++)
	{
		printf("%p ---- %p\n", &arr[i], arr+i); 
		// &arr[i] 和 arr+i 打印的地址完全一样
		// arr+i - 就是数组arr中,下标为i的元素的地址
	}

	return 0;
}

[]是操作符 arr和4是两个操作数

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	int i = 0;
	//arr[4];  等价于*(arr+4)   
	// []是操作符 arr和4是两个操作数

	// arr[4] --- *(arr+4)
	// arr[4] ---> *(arr+4) ---> *(4+arr) ---> 4[arr]
	printf("%d\n", arr[4]);
	printf("%d\n", 4[arr]); // 可行 []仅仅是个操作符 
	
	return 0;
}

2、函数调用操作符 - ()

strlen返回无符号整型

#include 

int main()
{
	// printf("%d", strlen("abc"));
	// strlen返回的是size_t --- unsigned int 无符号整型
	// %d打印的是有符号数 用%u
	printf("%u\n", strlen("abc"));

	return 0;
}
void test()
{
	printf("hehe\n");
}

int main()
{
	test();
	//  () - 函数调用操作符 接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数

	return 0;
}

3、// . 结构体.成员名 // -> 结构体指针->成员名

3.1、struct Book 结构体类型 struct Book b 结构体变量

// 自定义类型
// struct Book 结构体类型
// struct Book b 结构体变量
struct book // 结构体关键字 - struct
{
	char name[20];
	float price;
	char id[10];
};

void print1(struct book b)
{
	printf("书名:%s\n", b.name);
	printf("价格:%f\n", b.price);
	printf("书名:%s\n", b.id);
}

int main()
{
	struct book b = { "c语言程序设计", 55.5f, "c23479383" };
	print1(b);

	return 0;
}

3.2、字符串拷贝 - strcpy

库函数 头文件string.h

int main()
{
	struct Book b = { "C语言程序设计", 55.5f, "C23479383" };
	Print1(b);

	b.price = 100.0f;// 可
	//b.name = "数据结构"; // err 
	//name是数组 数组名是常量的地址 应该放在地址所指向的空间里
	// 用==字符串拷贝 - strcpy==   库函数 头文件string.h
	strcpy(b.name, "数据结构");
	Print1(b);

	return 0;
}

3.3、传地址

struct Book
{
	char name[20];
	float price;
	char id[10];
};

void Print2(struct Book* pb) // 结构体指针接收
{
	printf("书名:%s\n", (*pb).name);
	printf("价格:%f\n", (*pb).price);
	printf("书名:%s\n", (*pb).id);
}

int main()
{
	struct Book b = { "C语言程序设计", 55.5f, "C23479383" };
	Print2(&b); // 传地址

	return 0;
}

3.4、结构体成员访问操作符

// 左 -----------// 右
// 结构变量.成员名
// 结构体指针->成员名

// (*结构体指针).成员名 —不常用

void Print2(struct Book* pb)
{
	// . 结构体.成员名
	//printf("书名:%s\n", (*pb).name);
	//printf("价格:%f\n", (*pb).price);
	//printf("书名:%s\n", (*pb).id);

	// -> 结构体指针->成员名
	printf("书名:%s\n", pb->name);
	printf("价格:%f\n", pb->price);
	printf("书名:%s\n", pb->id);
}

十二、表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。


一、隐式类型转换(整型提升 算数转换)

1、整型提升

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

1.2、整型提升的意义

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


1.3、如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的 - 高位补充符号位
负数的整形提升, 高位补充符号位补1
正数的整形提升, 高位补充符号位补0
无符号整形提升,高位补0

#include 

int main()
{
	//b和c的值被提升为普通整型,然后再执行加法运算。
	//加法运算完成之后,结果将被截断,然后再存储于a中。
	
	char a = 3; //a是1字节 - 8比特位
	//00000000000000000000000000000011 - 3
	//00000011 - a
	char b = 127; //b是1byte - 8bit
	//00000000000000000000000001111111
	//01111111 - b

	//a和b都是char类型,大小都是1byte,所以这里计算的时候都要进行整型提升
	//高位补符号位
	//00000000000000000000000000000011 a整型提升补0
	//00000000000000000000000001111111 b
	//00000000000000000000000010000010 a+b

	char c = a + b;
	//10000010 截断
	//11111111111111111111111110000010 整型提升 负数补1
	//打印原码
	//11111111111111111111111110000001 反
	//10000000000000000000000001111110 原 2^7-2=128-2
	//-126
	printf("%d\n", c);

	return 0;
}

1.4、整形提升的例子
1.4.1、只有char和short会整型提升成int
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;

	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");
	//11010110 - c
	//11111111111111111111111111010110 - 整型提升 负数补1
	//转为原码与0xb6不一样 第二个判断表达式也一样
	//c是int类型 不参与整型提升 判断条件一致 所以只打印c

	/*表达式a == 0xb6, b == 0xb600 的结果是假,
	但是c不发生整形提升, 则表达式 c==0xb6000000 的结果是真.*/
	return 0;
}
1.4.2、参与运算
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c)); //1
	printf("%u\n", sizeof(+c)); //4
	//+-是操作符 表达式参与运算 char需要整型提升 变成整型
	printf("%u\n", sizeof(-c)); //4
	//c只要参与表达式运算, 就会发生整形提升, 表达式 + c, 就会发生提升, 所以 sizeof(+c) 是4个字节.
	//表达式 - c 也会发生整形提升, 所以 sizeof(-c) 是4个字节, 但是 sizeof(c), 就是4个字节
	return 0;
}

2、算数转换

long double
double
float
unsigned long int
long int
unsigned int
int

向上转换 排名较低的,首先要转换为另外一个操作数的类型后执行运算。

int main()
{
	int a = 4;
	float f = 4.5f;
	float r = a + f; // a算数转换成float

	return 0;
}

3、操作符的属性

3.1、复杂表达式的求值有三个影响的因素
  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序
3.1.1、优先级 结合性

两个相邻的操作符优先级不同的情况下,取决于优先级
两个相邻的操作符优先级相同时,取决于他们的结合性
// int c = a + b * 3 优先级 先算* 再算+
// int c = a + b + 3 优先级相同 看结合性 L-R 从左向右 先算a+b再算+3

3.1.2、是否控制求值顺序

// && 逻辑与(左边为假 右边就不用算了)
// || 逻辑或(左边为真,右边也不用算了)
// ?: 条件操作符(表达式1为真 2算3不算)
// , 逗号表达式(真正结果是最后一个表达式)

3.2、问题表达式

表达式1
//a* b + c * d + e * f

所以表达式的计算机顺序就可能是:
a* b
c* d
a* b + c * d
e * f
a * b + c * d + e * f
或者:
a* b
c* d
e* f
a* b + c * d
a * b + c * d + e * f

表达式2
//c + --c;
操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值

代码3 非法表达式

int main()
{
 //在不同表达式结果都不同
	int i = 10;
	i = i-- - --i * (i = -3) * i++ + ++i;
	printf("i = %d\n", i);
	return 0;
}

代码4

int main()
{
	int a = 1;
	int d = 0;
	d = (++a) + (++a) + (++a);
	printf("%d\n", d);
	// vs2019是12 gcc是10
	//VS 三次加完4 4+4+4  GCC先算前两个是3 3+3+4

	//调试后 右击转到反汇编
	return 0;
}

C语言操作符详解 隐式类型转换 (整型提升 算数转换)_第3张图片


代码5

int fun()
{
    static int count = 1;
    return ++count;
}
int main()
{
    int answer;
    answer = fun() - fun() * fun();
    printf("%d\n", answer);//输出多少?
    return 0;
}

静态局部变量 出函数范围不销毁 VS -10

总结:

我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。


你可能感兴趣的:(C语言,c语言,操作符)