C语言 -- 再谈操作符
下标引用、函数调用和结构成员
[ ]下标引用操作符
操作数:一个数组名 + 一个索引值
#include<stdio.h>
void main()
{
int arr[10]; //创建数组
arr[9] = 10; //使用下标引用操作符
//[ ]的两个操作数是arr和9
}
( )函数调用操作符
函数操作符( ),接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
# include<stdio.h>
void test1()
{
printf("测试\n");
}
void test2(const char *str) //const限定一个变量不允许被改变,产生静态作用
{
printf("%s\n", str);
}
void main()
{
test1(); //( )作为函数调用操作符
const char *str = "hello,world";
test2(str); //( )作为函数调用操作符
}
访问一个结构的成员
两种方式:
.结构体.成员名
->结构体指针->成员名
#include<stdio.h>
//访问一个结构体成员
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18; //结构成员访问
}
void main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20; //结构成员访问
set_age1(stu);
pStu->age = 20; //结构成员访问
set_age2(pStu);
}
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定的;
有些表达式的操作数在求值过程中可能需要转换为其他类型
隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的;
为了获得这个精度,表达式中的字符和短整型操作数在使用之前需要被转换成普通整型,这种转换称为整型提升。
整型提升的意义:
- 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整形运算器(ALU)的操作数的字节长度一般就是int字节长度,同时也是CPU的通用寄存器长度;
- 因此,即使两个char类型相加,在CPU执行时实际上也要先转为CPU内整形操作数的标准长度;
- 通用CPU是难以直接实现两个8比特直接相加运算的,所以表达式中各种长度可能小于int长度的整型值都需要先转换为int或者unsigned int,然后才能送去CPU执行运算。
void main()
{
char a, b, c;
b = 33;
c = 34;
a = b + c;
printf("%c, %d\n", a, a);
printf("%c, %d\n", b, b);
printf("%c, %d\n", c, c);
}
在上述代码中,b和c的值会被提升为普通整型,然后再执行加法运算;
加法运算完成之后,结果将被阶段,然后存储到a中。
进行整型提升的方式:
有符号整型提升,高位补充符号位;
char c1 = -1;
char类型一个字节,-1的:
原码:1000 0001;
补码:1111 1111;
提升:1111 1111 1111 1111 1111 1111 1111 1111char c2 = 1;
原码:0000 0001
提升:0000 0000 0000 0000 0000 0000 0000 0001无符号整型提升,高位补0.
代码示例:
#include<stdio.h>
//整型提升示例1
void main()
{
char a = 0xb6;
//1011 0110
//1111 1111 1111 1111 1011 0110 -- 提升,补码
//1111 1111 1111 1111 1011 0101 -- 反码
//1000 0000 0000 0000 0100 1010 -- 原码
//-0x4a
short b = 0xb600;
//1011 0110 0000 0000
//1111 1111 1011 0110 0000 0000 -- 提升,补码
//1111 1111 1011 0101 1111 1111 -- 反码
//1000 0000 0100 1010 0000 0000 -- 原码
//-0x4a00
if (a == -0x4a)
printf("a提升\n");
if (b == -0x4a00)
printf("b提升\n");
}
//整型提升示例2
void main()
{
char c = 1;
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(c+c));
printf("%d\n", sizeof(!c));
}
算数转换
如果某个操作符的各个操作数属于不同类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个表中排名较低,那么首先要转换成另一个操作数的类型后再执行运算。
注:算术转化要合理,否则会存在一些潜在问题
float f = 3.14f;
int num = f; // 隐式转换,会有精度丢失
操作符的属性
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个 相邻的操作符先执行哪个,取决于他们的优先级顺序。如果两者优先级相同,则取决于他们的结合性。
在不同的编译器中,下面代码的执行结果可能不同,这主要由编译器自身的机制来决定。因为函数的调用先后顺序是无法通过操作符的优先级来确定。
问题代码示例:
int fun()
{
static int count = 1;
return ++count;
}
void main()
{
int answer;
answer = fun() + fun() * fun();
//由操作数的优先级我们只能得知先算乘法后算加法,但是我们无法得知哪个函数先被执行。
printf("%d\n", answer);
}