【C语言基础】Chap. 3. 操作符、关键字、#define和存储

发布于:2023-01-04 ⋅ 阅读:(359) ⋅ 点赞:(0)

1. 初识操作符

C语言具有更多地强调表达式而不是语句的特点。

表达式是表示如何计算值的公式。最简单的表达式是变量和常量,而更复杂的表达式则将操作符(或称运算符)用于操作数(操作数自身也是表达式)。

比如:a+(b+c)

操作符是构建表达式的基本工具,C语言提供的运算符在其他的许多编程语言中都能找到对应的类似操作符。

操作符的定义是:指令系统的每一条指令都有一个操作符,它表示该指令应进行什么性质的操作。不同的指令用操作符这个字段的不同编码来表示,每一种编码代表一种指令。

操作符具有以下分类:

1. 算术操作符

  • +:一元正号运算符(很少用) 或加法运算符
  • -:一元负号运算符,或减法运算符
  • *:乘法运算符
  • /:除法运算符
  • %:取模运算符

在C语言中没有平时做数学题时的乘号×和除号÷,取而代之的是*/,但效果是基本一样的。

而日常生活中比较少见的%的作用是取模(取余),简单而言就是获取被除数除以除数之后剩下的余数,如7 % 2 = 1。它要求操作数都是整数,如果其中一个操作数不是整数,那么程序将无法编译通过。

值得注意的是,当除号两端都是整数时,即使定义的变量是浮点数,得出的结果也会是整数(或被截取为整数),除非其中一方带有小数

示例:

int a = 9 / 2;
printf("%d\n", a); //4

float b = 9 / 2;
printf("%d\n", b); //4.000000

float c = 9 / 2.0;
printf("%d\n", c); // 4.500000

2. 移位操作符

  • >>:右移操作符
  • <<:左移操作符

移位操作符移动的是二进制位。

示例:

int a = 2;
int b = a << 1;
printf("%d\n", b); //4

 

在这段代码中,a是整型类型,在存储空间中占有4个子节,即32个比特位。

因此在底层中,a的表现形式其实是 00000000000000000000000000000010

如果经过一次左移,则变成了 00000000000000000000000000000100,也就是十进制的4。

移位操作符后续会进一步讲解。

3. 位操作符

  • &:按位与
  • |:按位或
  • ^:按位异或

这三个位操作符也是从二进制层面操作数据的,先打个招呼,后续会详细讲解。

4. 赋值操作符

  • =:等于(这里的等于是赋值的意思)
  • +=:加等于
  • -=:减等于
  • *=:乘等于
  • /=:除等于
  • &=:按位与等于
  • |=:按位或等于
  • ^=:按位异或等于
  • >>=:右移等于
  • <<=:左移等于

第一个已经讲过,而后面的多个赋值操作符其实原理是相同的,举个例子:

int i = 0;
i += 1;
printf("%d\n", i); //1

+=操作符先使i与操作符右侧的内容相加,然后再赋值给i,实际上就相当于:

int i = 0;
i = i + 1;
printf("%d\n", i); //1

 

其他赋值操作符也是同理。

赋值操作符要求它的左操作数必须是左值(lvalue,是left value的缩写),即存储在计算机内存中的对象,而不是常量或计算的结果。

例如,变量是左值,而12或者2 * i这样的表达式则不是左值。

在赋值操作符的左侧放置非左值类型的表达式是不合法的。

:与左值相对应的“右值”也是存在的,它是可以出现在赋值操作符右侧的表达式,因此它可以是变量、常量或者更加复杂的表达式,但一般就用“表达式”来代称。

5. 单目操作符

单目操作符,简单而言就是只操作一个操作数的操作符。相反,比如 a+b 中的 + 就是双目操作符。

  • !:逻辑非,也被认为是逻辑操作符
  • -:负值
  • +:正值
  • &:取地址运算符
  • sizeof:操作数的类型长度(以字节为单位),操作数可以是数组
  • ~:对一个数的二进制按位取反
  • --:前缀自减、后缀自减
  • ++:前缀自增、后缀自增
  • *:间接访问操作符(解引用操作符)
  • (类型):强制类型转换

!用于对操作数取反。

在C语言中,0表示假,非0表示真,因此对0(假)取反则为真,对非0(真)取反则为假。

示例:


 

int a = 10;
printf("%d\n", !a); //0

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

if(a)
{
    //如果a为真,执行此段代码
}
if(!a) //这里也可以直接用else代替
{
    //如果a为假,执行此段代码
}

负值-和正值+跟数学题里面基本一样。

取地址操作符&和解引用操作符*会在后续讲解指针相关内容时进行详细讲解。

sizeof操作符在第二章已经出现过,不再重复演示,但需要补充的是:当sizeof后的()里是变量时,()可以省略,是类型时则不可以省略。

~操作符的功能是按(二进制)位取反,即把所有二进制位中的数字,1变成0,0变成1。

以0为例子:

其二进制表现为 0000 0000 0000 0000 0000 0000 0000 0000
经按位取反后为 1111 1111 1111 1111 1111 1111 1111 1111

自增操作符+和自减操作符-会使一个数+1或者-1,这也会产生副作用:改变操作数本身。同时,它们作为前缀(prefix)与作为后缀(postfix)时的效果是不同的。

  • 作为前缀时,先加减,后使用
int a = 10;
int b = ++a;
printf("%d\n", b); //11
printf("%d\n", a); //11

 作为后缀时,先使用,后加减

int a = 10;
int b = a++;
printf("%d\n", b); //10
printf("%d\n", a); //11

 尽量不要再同一个表达式中多此使用++或--,以免难以理解。如:

int a = 1, b = 0;
b = (++a) + (++a) + (++a);

 

类似这种情况在不同的编译器中可能会出现不同的结果。

类型转换操作符()会使一个数据的类型被强制转换为指定类型,比如:

int a = (int)3.14;

 

以上代码会使浮点型3.14被强制转换为整型,因此小数部分被截断。

强制类型转换一般不推荐使用,免得出现预料之外的错误。

6. 关系操作符

  • >:大于
  • <:小于
  • >=:大于等于
  • <=:小于等于
  • !=:不等于
  • ==:相等

这些操作符都是用于测试两端数据大小关系的,其中!===也称判等操作符(equality operator)。

由于某些编程语言(如js)中的判等操作符具有类型转换功能,这里我需要提一下,C语言中的判等操作符没有类型转换功能:


 

printf("%d\n", 0 == '0'); //0
printf("%d\n", 0 != '0'); //1
return 0;

7. 逻辑操作符

  • !:逻辑非
  • &&:逻辑与
  • ||:逻辑或

逻辑操作符主要用于处理逻辑链中的条件为真或假的问题。

它们将任何非零值操作数作为真值来处理,同时将任何零值操作数作为假值来处理。

逻辑非!已经介绍过。

逻辑与&&会判断其两端的操作数是否为真,当都为真时,其计算结果为真,当存在假值时,结果为假。

int a = 3, b = 5, c = 0, d = 0;
int e = a && b;
int f = a && c;
int g = c && d;

printf("%e %f %g\n", c); //1 0 0

逻辑或||则与逻辑与相反,当其两端操作数都为假时,其计算结果为假,当存在真值时,结果为真。

int a = 3, b = 5, c = 0, d = 0;
int e = a && b;
int f = a && c;
int g = c && d;

printf("%e %f %g\n", c); //1 1 0

 8. 条件操作符

exp1 ? exp2 : exp3

 

条件操作符可以视作简单版的if语句,

其逻辑是:当表达式exp1成立时,执行表达式exp2,整个条件表达式的结果是exp2的结果;exp1不成立时,执行exp3,整个表达式的结果是exp3的结果。

示例(伪码):

今天下雨了吗 ? 雨天 : 晴天;

 9. 逗号表达式

exp1, exp2, exp3, ...expN;

 

即逗号隔开的一串表达式。

逗号表达式是从左向右依次计算的,整个表达式的结果是最后一个表达式的结果

示例:

int a = 0;
int b = 3;
int c = 5;
int d = (a = b + 2, c = a - 4, b = c + 2);
printf("%d\n", d); //3

 

10. 下标引用、函数调用和结构成员

  • []:下标引用
  • ():函数调用
  • .:获取结构体成员
  • ->:用结构体地址获取结构体成员

这部分操作符也是先混个脸熟,后续会讲解。

2. 常见的关键字

关键字有以下两个特征:

  1. 关键字是由C语言提供的,不能自己创建。
  2. 关键字对于C语言有着特殊意义,不能作为变量名使用。

以下是C语言中的关键字

auto break case char const continue default do double
else enum extern float for goto if int long register
return short signed sizeof static struct switch typedef
union unsigned void volatile while

//以下为C99新增关键字
inline restrict _Bool _Complex _Imaginary

除此之外,某些编译器或库也有自己的关键字,这就需要在使用时再甄别了。

在这里先介绍一些常用的:

(1)auto

实际上每个变量都由auto定义,只不过被省略了。

示例:

auto int a = 10; //a是自动变量:自动创建,自动销毁

auto在新版规则的C语言语法中有其他用法,暂时不考虑。

(2)extern

用来声明外部符号,第二章出现过。

(3)register

寄存器关键字。

首先我们要知道在计算机中,数据可以存储到多个位置:

  • 寄存器(比高速缓存更小)
  • 高速缓存(几十MB)
  • 内存(8、16、32G)
  • 硬盘(500G±)
  • 网盘(N个T)

从下往上,造价越高、读写速度越快、空间越小(金字塔型)。

register关键字的作用是:告诉编译器这个数据要存到寄存器中。

示例:

register int num = 100; //建议num的值存放在寄存器中

一般是大量/频繁被使用的数据,才会被考虑放在寄存器中,以提升效率。

但如今的编译器已经足够聪明,能够自动判断哪些数据需要存放到寄存器中,所以使用这个关键字已经意义不大了。

(5)static

意为静态的。在C语言中,static用来修饰变量和函数。

  • 修饰局部变量-静态局部变量:此时static改变了局部变量的生命周期,本质上是改变了变量的存储类型。但它仍是局部变量,作用域外不存在。


 

void test()
{
    static int a = 1; //a是局部变量
    a++;
    printf("%d", a);
}

int main()
{
    int i = 0;
    while (i < 10)
    {
        test();	//原为10个2,加了static后为2-11
        i++;
    }
    printf("%d", a); //未定义

    return 0;
}
  • 修饰全局变量-静态全局变量:static修饰全局变量后,该全局变量只能在自己所在的源文件内部使用,其他源文件不能使用。全局变量在其他源文件内部可以被使用,是因为全局变量具有外部连接属性。但它被static修饰之后,所具有的就变成了内部链接属性,其他源文件就不能链接到这个静态的全局变量了。

  • 修饰函数-静态函数:static修饰函数,使得函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用。与修饰全局变量相同,本质上是将函数的外部链接属性变成了内部链接属性

补充:C语言中内存的三个区:

  • 栈区:用于存储局部变量和函数的参数。
  • 堆区:用于动态内存分配。
  • 静态区:用于存储全局变量和static修饰的静态变量,也叫数据段。

(6)typedef

顾名思义是类型定义,此处应该理解为类型重命名(类型重定义)。

示例:


 

typedef unsigned int u_int; //定义一个变量类型i_int,它本质上是无符号整型
int main()
{
    unsigned int num = 100;
    u_int num2 = 100;

    return 0;
}

(7)union

联合体(又叫共用体)声明关键字,讲结构时会进一步讲解。

(8)void

代表无或者空。

(9)补充

define和include不是关键字,而是预处理指令,在编译前修改程序的编辑命令。

3. #define 定义常量和宏

1. 定义常量、符号

示例:


 

#define MAX 1000

int main()
{
    printf("%d\n", MAX); //1000
    return 0;
}

2. 定义宏

C语言中的(Macro)即宏定义(macro dfinition),是一种预处理指令,它提供了一种机制,可以用来替换源代码中的字符串,用于批量处理。

宏的名字一般采用大写字母,这是一种规范,而非强制,但希望所有程序员都能遵守这种规范。注意,C语言是区分大小写的,大小写字母的ASCII码值是不同的。

示例:

#define ADD(X, Y) x + y
int main()
{
    printf("%d\n", 4 * ADD(2, 3)); //4 * 2 + 3 = 8
    //因为宏是完全替换此处代码
    //相当于 printf("%d\n", 4 * 2 + 3);

    return 0;
}

4. 存储

整数在内存中的存储形式是补码。

一个整数的二进制表示有3种:原码、反码、补码。

一个整数的32个比特中的最高位决定其正负(0为正,1为负),因此被称为符号位。

负数以-1为例:

  • 原码:100000000000000000000000000000001
  • 反码:111111111111111111111111111111110(符号位不变,其他位按位取反)
  • 补码:111111111111111111111111111111111(反码+1)

而正数的原码、反码、补码都相同。

可以用按位取反操作符来验证下,注意:按位取反操作符~不考虑符号位,全部取反。

示例:

int a = 0; //00000000 00000000 00000000 00000000
printf("%d\n", ~a); //-1
int b = 1; //00000000 00000000 00000000 00000001
printf("%d\n", ~b); //-2

可以尝试按照上面的原码、反码和补码计算来理解。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到