Bootstrap

最全C语言个人笔记【第二章节-数据类型-运算符-位运算】

1.数据类型

什么是数据类型,为什么会有数据类型

数据类型是用于描述不同类型的数据存放在内存中的形式

**以睁眼(1)闭眼(0)形式描述,整数,那英文呢? **

数据类型分类:
1.整型(短整型,整型,长整型,长长整型,无符号短整型,无符号整型,无符号长整形,无符号长长整型)
2.字符
3.浮点
4.字符串
  • 什么是数据?
    • 有用的信息称为数据(比如各种的文件,视频,图片,音频,…)
  • 数据如何存储在内存
1. 确认数据的类型  10 3.14 'a' '1' "10" "100" 'a'
2. 根据数据的类型在c语言中确认分配的数据类型 int char float double
3. 确认分配的空间是否满足数据的存放 char1字节 short 2字节 int 4字节

在这里插入图片描述

  • 字节(byte B):计算机存储容量的一种单位
  • 比特位(bit) :二进制数,cpu计算的基本单位
  • 二进制数 0 1
    • 1字节 = 8位
    • 1个千字节(KB) = 1024字节
    • 1M = 1024KB
    • 1G = 1024M
    • 1T = 1024G

1.整型

  • 概念:表达数据类型的数据

  • 语法

    int a = 123; // 定义一个专门用来存储整数的变量a
    int = 4个字节
    
  • 需要注意的地方:

    1. int 的本意是integer,即整数的意思

    2. int a代表在内存中开辟一块区域,称为a,用来存放整数,a称为变量

  1. 变量a所占用的内存大小,在不同的系统中是不一样的,64位系统典型的大小是4字节。

    1. 变量a有固定的大小,因此也有取值范围,典型的范是:-2147483648到2147483647。
  • 整型修饰符

    • short:用来缩短整型变量的尺寸,减少取值范围并节省内存,称为整型。

    • long:用来增长整型变量的尺寸,增大取值范围并占用更多的内存,称为长整型

    • long long:用来增长整型变量的尺寸,增大取值范围并占用更多的内存,称为长长整型

    • unsigned:用来去除整型变量的符号位,使得整型变量只能表达非负整数

      short int a; // 短整型 %hd  half
      long int b; // 长整型 %ld
      long long int c; // 长长整型 %lld
      
      unsigned int e; // 无符号整型 %u
      
      unsigned short int f; // 无符号短整型 %hu
      unsigned long int g;  // 无符号长整型 %lu
      unsigned long long int h; // 无符号长长整型 %llu
      
  • 使用整型修饰符后,关键字int可以省略:

short a; // 短整型  
long b;  // 长整型  
long long c; // 长长整型 

unsigned e;  // 无符号整型

unsigned short f; // 无符号短整型
unsigned long g;  // 无符号长整型
unsigned long long h; // 无符号长长整型
  • 个数据类型的尺寸如下

    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        printf("%ld\n",sizeof(char)); // 1字节
        printf("%ld\n",sizeof(short));// 2字节
        printf("%ld\n",sizeof(int)); // 4字节
        printf("%ld\n",sizeof(long)); // 32位系统4字节,64系统8字节
        printf("%ld\n",sizeof(long long));// 8字节
        printf("%ld\n",sizeof(float)); // 4字节
        printf("%ld\n",sizeof(double)); // 8字节
        printf("%ld\n",sizeof(long double));// 32位系统12字节,64位系统16字节
    
        return 0;
    }
    
    

格式控制符

  • int 整型:%d

  • short整型:%hd,h代表half,即一半的存储字节

  • long整型:%ld

  • long long整型:%lld

  • 显示不同进制的前缀 : %#o、%#x

  • 符号位:

    • 有符号的整型数据,首位为符号位,0表示正数,1表示负数。
    • 无符号的整型数据,没有符号位

练习:

定义以上类型的数据19,通过格式控制符输出
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char a = 19;
    printf("%hhd\n",a); // 短短整型

    unsigned char b = 19;
    printf("%hhu\n",b); // 无符号短短整型

    short int c = 19;
    printf("%hd\n",c);

    // 等价于 unsigned short int d = 19;
    unsigned short d = 19;
    printf("%hu\n",d);

    int d1 = 19;
    printf("%d\n",d1);

    unsigned int e = 19U;
    printf("%d\n",e);

    long h = 19L;
    printf("%ld\n",h);

    unsigned long i = 19UL;
    printf("%ld\n",i);

    long long j = 19LL;
    printf("%lld\n",j);

    float fl = 19;
    printf("%f\n",fl);

    double dl = 19;
    printf("%lf\n",dl);
	
    // 32位系统越界,不能使用
    long double ld = 19;
    printf("%LF\n",ld);

    return 0;
}

  • 数据类型的取值范围
unsigned char 0~255(1111 1111)
char -128... + 127(0111 1111)
short -32768~32767
unsigned short 0~65535(1111 1111 1111 1111) 2^16-1
unsigned int 0... 2^32-1 4294967295
  • 编码形式:

    • 原码:正数直接使用二进制来表达,比如a=100,在内存中是00…001100100
    • 补码:负数用绝对值取反加一来表达,比如a=-3,在内存中是11…1111111101
  • 注意负数的补码在取反加一的时候,符号位是不变的

在这里插入图片描述

总结

在这里插入图片描述

1.当编译器以整型输出的时候%d,是以补码还原的方式解读
2.当cpu将数据进行运算的时候,直接以内存中存放的方式来运行,也就是以补码	的方式参与运算
3.%u输出的时候,值区域范围:0-4294967295(有符号转为无符号的时候)
4.%hhu方式打印,值域范围:0-255
1unsigned char a = 255;// 0 - 255
char b = 255; // -128 - +127
printf("%d , %u\n",a,a);// 255 255
printf("%d , %u\n",b,b);// -1 42949672952unsigned short a = -1;// 0-65535
int b = a;
printf("a = %d\n",a);//65535
printf("b = %u\n",b);//655353unsigned char a = -1;// 0-255
unsigned int b = -1; // 0-4294967295
printf("a = %d,b = %u\n",a,b);//255,4294967295

(1)b

在这里插入图片描述

(2)a

在这里插入图片描述

  • 溢出:

    • 超出数据所能表达的范围,称为溢出,就像汽车里程表,最大值和最小值是相邻的

      在这里插入图片描述

    unsigned char a = 257;
    char b = 129;
    printf("a:%hhu\n",a);//half
    // 练习
    printf("b:%hhd\n",b);
    // 作业1
    printf("b:%hhu\n",b);
    
  • 进制:源码中可以使用八进制、十进制或者十六进制,但实际数据中一律是二进制

    • 十进制(默认),比如1234 %d %u
    • 八进制,比如013 %o
    • 十六进制,比如0x6FF0A %x
  • 进制转换

    • 十进制–》二进制、八进制、十六进制

    • 十进制转二进制除2取余数,倒序排序,高位补0

      在这里插入图片描述

    十进制转八进制

    ​ 除8取余,倒叙排序

    在这里插入图片描述

    ​ 则12(10)=14(8).
    ​ 在编程语言中,一般使用0开头来表示八进制,如上例中的十 进制数12就可以写成014。

  • 在这里插入图片描述

    二进制八进制十六进制转十进制

    在这里插入图片描述

2.浮点型

  • 概念:用来表达实数(有理数和无理数)的数据类型
https://www.cnblogs.com/jack-ping/articles/12174949.html

[浮点数在内存中的存放方式][https://www.cnblogs.com/jack-ping/articles/12174949.html]

  • 分类:

    • 单精度浮点型(float),典型尺寸是4字节
    • 双精度浮点型(double),典型尺寸是8字节
    • 长双精度浮点型(long double),典型尺寸是16字节
    • 占用内存越多,能表达的精度越高
    float f1;//单精度
    double f2;//双精度
    long double f3;//长双精度
    

在这里插入图片描述 「课堂练习1」

指出下列常量的类型

‘\b’ 字符
1066 整型
99.44 浮点型
0XAA 十六进制的整型
2.0e30 2.0 *10^30
0x00EEABCDUL 十六进制的无符号长整形

3.字符

char ch1 = 'a'; // 'a'是字符常量,代表字母a
char ch2 = '\n'; // '\n'是不可见字符常量,代表回车

计算机中存储都是1和0,因此各种字符都必须被映射位某个数字才能存储到计算机中,这种映射关系形成的表称为ASCII码表。

在这里插入图片描述

在这里插入图片描述

字符本质上就是一个单字节的整型,支持整型所有的运算。比如:

char a = 'a';// 存储在内存中是97
char b = '1';// 存储在内存中是49
char c1 = 20;
char c2 = c1 + 'a'; // 等价于char c2 = 20+97;

printf("%c\n",c2); // 以字符形式输出117,即 'u'
printf("%d\n",c2); // 以整型形式输出117

// 小写字符转大写字符 ' ' = 32
char a7 = 'b'- ' ';
printf("%c\n",a7);

转义字符

转义字符,所有的ASCII码都可以用\加数字(一般是8进制数)来表示,而在C中
定义了一些字母前加\来表示常见的哪些不能显示ASCII字符,如\0,\t,\n称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思
当编译器遇到转义字符的时候,会用特殊的方式进行处理,'\n'表示换行
'\b' : 表示退格符
 printf("abcd\b");
 printf("bcde\n");
'\a': 表示告警,主板蜂鸣器
'\t': tab键
'\ddd' ddd 表示八进制数,打印出来的是该数字对应的ascii码
格式为'\'后面又三个数,每个数的范围在  0 - 7
printf("%c\n",'\102');// 八进制

'\xhh'hh 表示十六进制 打印出来的是该数字对应的ascii码
printf("%c\n",'\41');//A

4.字符串

  • 定义
// 字符串的定义方式有两种:指针和数组
char *s1 = "abcd"; // 使用字符指针来表示字符串
char s2[5]= "abcd"; // 使用字符数组来表示字符串

// 注意,使用数组来定义字符串时,方括号[]里面的数字可以省略
// 不省略也可以,但必须必字符串实际占用的内存字节数要大,比如:
char s3[] = "apple";
  • 在内存中的存储

    • 在内存中实际上是多个连续字符的组合
    • 任何字符串都以一个’\0’作为结束标记,例如:"funny story"的内存如下

    在这里插入图片描述

    ​ 字符串的内部存储细节

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char ch1 = 'a';
    char ch2 = ' '; // 32
    printf("%c\n",ch1-ch2);

    // 转义字符
    printf("%c,%d\n",'\0','\0'); // 空字符
    printf("%c,%d\n",'\t','\t'); // tab键
    printf("%c,%d\n",'\n','\n'); // 回车键

    printf("abcd\b"); // 退格符
    printf("abcd\n");

    // 转义字符--八进制
    // 转义必须是0-7,其他的数值错误
    printf("%o,%d\n",'\102','\102');// 八进制

    // 转义字符--十六进制
    printf("%x,%d\n",'\x4F','\x4F');

    // char *p = "abcd";
    // printf("%p\n",p);
    
    // *p = 'b'; // 错误,因为p所指向的空间是常量,不能修改
    // printf("%s\n",p);

    char s2[] = "abcd";
    *s2 = 'b';
    printf("%s\n",s2);

    printf("%c",s2[0]);



    return 0;
}

5.布尔类型数据

  • 概念:布尔型数据只有真、假两种取值,非零为真,零为假。
  • 语法:
bool a = -10// 逻辑真,此处1可以取其他任何非零数值
bool b = 0; // 逻辑假 c
  • 注意:

    1. 逻辑真除了 1 之外,其他任何非零数值都表示逻辑真,等价于 1。
    2. 使用布尔型 bool 定义变量时需要包含系统头文件 stdbool.h。
  • 布尔型数据常用语逻辑判断、循环控制等场合。

#include <stdio.h>
#include <stdbool.h>

int main(int argc, char const *argv[])
{
    // 在计算机里头,非0为真,0为假
    bool a = -1;
    printf("%d\n",a); // 1

    bool b = 0;
    printf("%d\n",b); // 0

    bool c = true; // 1
    printf("%d\n",c);

    bool d = false; // 0
    printf("%d\n",d);

    return 0;
}

6.常量与变量

  • 概念:不可改变的内存称为常量,可以改变的内存称为变量
  • 举例:
int a = 100;    // a是变量,而100是常量
float f = 3.14; // f是变量,而3.14是常量
char s[] = "abcd"; // s是变量,"abcd"是常量
char ch = 'a';
  • 常量的类型
常量举例说明类型
100整型int
100L长整型long
100LL长长整型long long
100ULL无符号长长整型unsigned long long
3.14双精度浮点型double
3.14L长双精度浮点型long double
‘a’字符型char
“abcd”字符指针(字符串)char *

7.标准输入

  • 概念:键盘是系统的标准输入设备,从键盘中输入数据被称为标准输入
  • 相关函数:
scanf(); // 格式化输入函数
fgets(); // 字符串输入函数

int a;
float f;
scanf("%d", &a);  // 从键盘输入一个整型,放入指定的内存地址 &a 中
scanf("%f", &f); // 从键盘输入一个浮点数,放入指定的内存地址 &f 中
// 从键盘依次输入一个整型和一个浮点型数据,用空白符隔开
scanf("%d%f", &a, &f); 

char c;
char s[10];
// 从键盘输入一个字符,放入指定的内存地址 &f 中
scanf("%c", &c);  
// 从键盘输入一个单词,放入指定的数组 s 中(注意不是&s)
scanf("%s", s );  

fgets(s, 10, stdin); // 从键盘输入一行字符串,放入数组 s 中
  • 注意1:
    • scanf() 与 printf() 不同,scanf() 的格式控制串不可乱写,尤其是结尾处的 \n
    • 用户必须完全按照 scanf() 中描述的输入格式控制串来输入数据,否则将出错。
    • 示例:
// 此处输入时必须带逗号
scanf("%d,%d", &a, &b);

// 此处必须先输入a=,然后才能输入整数
scanf("a=%d", &a); 

// 此处结束输入时按下的回车符将被scanf()误以为格式控制符,无法正常结束输入
scanf("%d\n", &a); 
  • 注意2:
    • scanf() 的返回值,代表成功从键盘读取的数据的个数
    • 无法匹配 scanf() 指定格式的数据,将被遗留在输入缓冲区中,不会消失
    • 示例:
// scanf() 试图从键盘读取两个整数
// 返回值 n 代表成功读取的个数,比如:
// 输入100 200,则 n 将等于2
// 输入100 abc,则 n 将等于1
// 输入abc xyz,则 n 将等于0;输入abc 200,n也将等于0
int n = scanf("%d%d", &a, &b);

// 根据 scanf() 的返回值,判断用户是否输入了正确的格式
while(n != 2)
{
    // 需要清空缓冲区并提示用户重新输入
    char s[50];
    fgets(s, 50, stdin);
    printf("请重新输入两个整数\n");

    n = scanf("%d%d", &a, &b);
}

在这里插入图片描述

在这里插入图片描述

「课堂练习2」

编程实现如下功能:

  • 如果用户输入大小写字母,则输出字母对应的ASCII码值。
  • 如果用户输入ASCII码值,则输出对应的大小写字母。

在这里插入图片描述

#include <stdio.h>

int main(void)
{
    char ch;
    int ret = scanf("%c",&ch);
    if(ret != 1)
    {
        printf("输入有误,请重新输入\n");
    }
    printf("%d\n",ch);

    // 先清空输入缓冲区
    //int get_data = getchar();
    //printf("get char %c\n",get_data);
    int data = 0;
    ret = scanf("%d",&data);
    if(ret != 1)
    {
        while(getchar() != '\n');
        printf("输入有误,请重新输入\n");
    }
    printf("%c\n",data);
    
    return 0;
}

8.类型转换

  • 概念:不一致但相互兼容的数据类型,在同一表达式中将会发生类型转换。
  • 转换模式:
    • 隐式转换:系统按照隐式规则自动进行的转换
    • 强制转换:用户显式自定义进行的转换
  • 隐式规则:从小类型向大类型转换,目的是保证不丢失表达式中数据的精度

在这里插入图片描述

隐式转换示例代码

char  a = 'a';
int   b = 12;
float c = 3.14;
float x = a + b - c; // 在该表达式中将发生隐式转换,所有操作数被提升为float
  • 强制转换:用户强行将某类型的数据转换为另一种类型,此过程可能丢失精度
char  a = 'a';
int   b = 12;
float c = 3.14; 
float x = a + b - (int)c; // 在该表达式中a隐式自动转换为int,c被强制转为int

不管是隐式转换,还是强制转换,变换的都是操作数在运算过程中的类型,是临时的,操作数本身的类型不会改变,也无法改变。

数据类型的本质

  • 概念:各种不同的数据类型,本质上是用户与系统对某一块内存数据的解释方式的约定。
  • 推论:
    • 类型转换,实际上是对先前定义时候的约定,做了一个临时的打破。
    • 理论上,可以对任意的数据做任意的类型转换,但转换之后的数据解释不一定有意义。

整型数据尺寸

  • 概念:整型数据尺寸是指某种整型数据所占用内存空间的大小
  • C语言标准并未规定整型数据的具体大小,只规定了相互之间的 “ 相对大小 ” ,比如:
    • short 不可比 int 长
    • long 不可比 int 短
    • long 型数据长度等于系统字长
  • 系统字长:CPU 一次处理的数据长度,称为字长。比如32位系统、64位系统。
  • 典型尺寸:
    • char 占用1个字节
    • short 占用2个字节
    • int 在16位系统中占用2个字节,在32位和64位系统中一般都占用4个字节
    • long 的尺寸等于系统字长
    • long long 在32位系统中一般占用4个字节,在64位系统中一般占用8个字节
  • 存在问题:
    • 同样的代码,放在不同的系统中,可能会由于数据尺寸发生变化而无法正常运行。
    • 因此,系统标准整型数据类型,是不可移植的,这个问题在底层代码中尤为突出。

可移植性整型

  • 概念:不管放到什么系统,尺寸保持不变的整型数据,称为可移植性整型
  • 关键:typedef
typedef int int32_t;  // 将类型 int 取个别名,称为 int32_t
typedef long int64_t; // 将类型 long 取个别名,称为 int64_t
  • 思路:
    1. 为所有的系统提供一组固定的、能反应数据尺寸的、统一的可移植性整型名称
    2. 在不同的系统中,为这些可移植性整型提供对应的 typedef 语句
  • 系统预定义的可移植性整型:
int8_t
int16_t
int32_t
int64_t

uint8_t
uint16_t
uint32_t
uint64_t
练习:自定义以上数据类型
pid_t
time_t
size_t
... ...

demo:

#include <stdio.h>

typedef char      mc_int8_t;
typedef short     mc_int16_t;
typedef int       mc_int32_t; 
typedef long long mc_int64_t;

// 如果我的操作系统是64位
#ifdef  __WORDSIZE64__
    #define int mc_int64_t // 将int64_t改名为int
#endif

#ifdef __WORDSIZE32__
    #define int mc_int32_t // 将int32_t改名为int
#endif

int main(int argc, char const *argv[])
{
    printf("%d\n",sizeof(int));
    return 0;
}

编译流程

gcc demo.c -o demo -D__WORDSIZE64__
说明:-D 表示选择哪一个字长的设备,比如__WORDSIZE64__ 64位系统的设备,__WORDSIZE32__ 32位系统的设备

「课堂练习3」

有时候我们需要使用 int32_t 类型变量代替 int 类型变量的原因是什么?

在这里插入图片描述

int是系统的基本数据类型,可能不同操作系统int大小不一致,为了提高代码的复用性,一套代码可以适配多套不同字长的设备,我们可以使用可移植数据类型比如 typedef char int8_t; 表示8为位单片机的设备的字长,typedef int int32_t; 表示32位单片机的设备的字长。具体实现可以查阅linux  /user/include/stdint.h

1. 算术运算符

运算符功能说明举例
+加法,一目取正a+b
-减法,一目取负a-b
*乘法a*b
/除法a/b
%取模(求余)a%b
++自加1a++, ++b
自减1a–-, --b
  • % 是算术运算符的重点,一般将某个数值控制在一定范围内,还有数据解析上
// demo1:产生一个随机数,并将此数控制在5的范围内
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    // 产生随机因子
    srand(time(NULL));

    // 产生随机数,控制在5以内
    int ret = rand() % 5;
    printf("%d\n",ret);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;
    printf("%d\n",a+b);
    printf("%d\n",a-b);
    printf("%d\n",a*b);
    printf("%d\n",a/b); // 除数
    printf("%d\n",a%b); // 取余数

    // 将1,2,3单独输出
    int data = 123;
    // 案例1:一般用于数据解析,比如温湿度模块获取的数据上位机需要解析并处理
    printf("%d\n",data%10);
    printf("%d\n",data/10%10);
    printf("%d\n",data/100);

    // 案例2:通过%将数据控制在某个范围内有效
    // while(1)
    // {
    //     int ret = scanf("%d",&data);
        
    //     if(ret != 1) break;

    //     printf("%d\n",data%10);
    // }

    // 根据时间产生随机数
    srand(time(NULL));

    int rad = rand();
    printf("%d\n",rad%5); // 将随机数控制在某个范围内有效
    
    return 0;
}

  • 关注点

    • 减号也是负号,比如-a是取变量a的相反数
    int a = 10;
    printf("%d\n",-a); // -10
    
    • 取模运算要求左右两边操作数必须是整型数据
    1.5 % 0.5 (错误)
    
    • 自加自减运算不仅可以对整型操作,也可以对浮点数、指针操作

1)单目运算符 目:目标操作数

​ ++ – +(正号) -(负号)

  • 前后缀运算:
    1. 前缀自加自减运算:先进行自加自减,再参与表达式运算
    2. 后缀自加自减运算:先参与表达式运算,在进行自加自减
int a = 100;
int b = 200;
int ret = a + b;
int c = ++a; // a先自加1,变成101;然后再赋值给c,因此c等于101
int d = b++; // b先赋值给d,因此d等于200,然后b自加1,变成201

int a = 3;
int x = a+++a++;(错误的)
int b = 3;
int y = b++++b;(错误的)
error: multiple unsequenced modifications to [-Werror,-Wunsequenced]//多次非顺序修改
总结就是不要将自增或自减运算符与赋值运算符等其他运算符复合使用。
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10;
    // 单目运算符, a先赋值给b 然后a自己加一
    int b = a++;
    int c = a+b
    printf("%d,%d\n",a,b);

    // 单目运算符, a1先自己加一 然后a1赋值给b1
    int a1 = 10;
    int b1 = ++a1;
    printf("%d,%d\n",a1,b1);

    b1 = --a1;
    printf("%d,%d\n",a1,b1);

    b1 = a1--;
    printf("第%d行结果为:%d,%d\n",__LINE__,a1,b1);// 9 10

    // 不规范,单目运算符不能多级运算
    int c = (a1++) + (++a1);
    printf("%d\n",c);
    return 0;
}

2)双目运算符

+ - * /
printf("%d\n",3/2);// 一个整数除以另一个整数,小数被舍弃
printf("%f\n",3*1.0/2);// 如果想要小数,那么可以*1.0,隐式类型转换 1.500000

printf("%d\n",10%3);//%取余运算符的左右两边必须都是整数
// 一般取余运算符在我们编程开发中用于控制数据的大小范围
srand(time(NULL));// 获取随机数因子
int a9 = rand()%5;// rand()得到一个随机数0-4
printf("%d\n",a9);

2. 关系运算符

运算符功能举例说明
>大于a > b判断a是否大于b
>=大于或等于a >= 5判断a是否大于或等于5
<小于3 < x判断3是否小于x
<=小于或等于x <= (y+1)判断x是否小于或等于y+1
==等于(x+1) == 0判断x+1是否等于0
!=不等于c != ‘\0’判断c是否不等于’\0’
注意关系运算符的值为布尔值 也就是说要么关系成立(1) 要么不成立(01 > 10 20 < 30
int a = 10; int b = 20;
a != b;// 1
a == b; // 0
a = b;// 恒定为真,这是赋值不是比较关系,不能这么写
demo:
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10, b = 20;
    // 关系运算符,结果只能是真为1或者假为0
    printf("%d\n",a > b);  // 0
    printf("%d\n",a < b);  // 1
    printf("%d\n",a == b);  // 0
    /*
    printf("%d\n",a = b); // 为真,但是这么写不对
    // if(a=b)
    {
        printf("错误的示范\n");
    }
    */
   // 大于或者等于
    printf("%d\n",a>=b);
    // 小于或者等于
    printf("%d\n",a<=b);
    printf("%d\n",a != b);

    return 0;
}
  • 关注点:
    • 关系运算符用于判断运算符两边的表达式是否满足给定的大小条件
    • 由关系运算符组成的表达式称为关系表达式,其值为布尔型
    • 判断是否相等是双等号==,而不是一个等号

3. 逻辑运算符

运算符功能说明举例
逻辑反!(x==0)
&&逻辑与x > 0 && x < 10 // 0<x<10数学写法
||逻辑或y < 10 || x > 10
  • 运算规则:

    • 逻辑反:将逻辑真、假翻转,即真变假,假变真

    • 逻辑与:将两个关系表达式串联起来,当且仅当左右两个表达式都为真时,结果为真。

    • 逻辑或:将两个关系表达式并联起来,当且仅当左右两个表达式都为假时,结果为假。

      在这里插入图片描述

  • 特殊规则

    • 在逻辑与运算中,如果左边表达式的值为假,那么右边表达式将不被执行。
    • 在逻辑或运算中,如果左边表达式的值为真,那么右边表达式将不被执行。
    逻辑与 && ---而且,同时 两个操作数都必须为真,结果为真
    逻辑或 || ---或者 只要有一个为真,那么结果为真
    逻辑非 ! ----反 
    非0 为真,0 为假
    逻辑表达式结果有两种 1为真 0为假
    
    int a = 10;
    int b = 20;
    //c语言 惰性运算,也就是说 如果前面的结果为假,后面就不会运行--知难而退
    if(a > b && ++a)
    {
        printf("111\n");
    }
    printf("%d\n",a);
    
    demo2:
    #include <stdio.h>
    #include <stdbool.h>
    
    int main(int argc, char const *argv[])
    {
        int a = 10, b = 5, c = 6, d = 7;
        // 10大于5并且6小于7为真,结果为1
        printf("%d\n",a > b && c < d);
        
        // 10小于5或者6小于7为真,结果为1
        printf("%d\n",a < b || c < d);
    
        // -1为真取反为假,结果为0
        printf("%d\n",!-1);
    
        // c语言为惰性语言,只要表达式1能确定整条语句结果,则表达式2不执行
        printf("%d,%d\n",a < b && a++,a);
    
        return 0;
    }
    
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        int a = 10, b = 20;
        // 取反
        printf("%d\n",!(a > b)); // 1
        printf("%d\n",!a); // 0
    
        // 逻辑与
        printf("%d\n",(a>b) && (a != b));
        // 不能这么写,它的意思是先判断 a < 15 为真结果是1
        // 1<b为真,最终结果是1
        //printf("%d\n",a<15<b); // 1 错误写法
        printf("%d\n",a < 15 && b > 15);// 正确写法
    
        // 逻辑或
        printf("%d\n",a != b || a < b);
        printf("%d\n",a != b || a > b);
        printf("%d\n",a == b || a > b);
        printf("%d\n",a == b || a < b);
    
        // 惰性运算,笔试常考
        int c = 0;
        // 左边为真,右边不执行
        (a != b) || (c = 100);
        // 左边为假,右边执行
        (a == b) || (c = 100);
        printf("%d\n",c);
    
        return 0;
    }
    
    

4. 位运算符(重点,对整个变量的某一位操作)

运算符名称举例功能说明
~位逻辑反~a将变量 a 中的每一位取反
&位逻辑与a & b将变量 a 和 b 逐位进行与操作
|位逻辑或a | b将变量 a 和 b 逐位进行或操作
^位逻辑异或a ^ b将变量 a 和 b 逐位进行异或操作
<<左移a << 4将变量 a 中的每一位向左移动4位
>>右移x >> n将变量 x 中的每一位向右移动4位
  • ~ : 按位取反—单目运算符,数据将每一个bit位取反,1变0,0变1
unsigned char ret = ~0x05;// 0000 0101--> 1111 1010
//%u无符号输出,直接以补码的方式输出,%d以补码还原的方式输出
printf("%hhu\n",ret);// 250
printf("%hhd\n",ret);// -6
0x05 ----> 0000 0101

~0x05---->       1111 1010 ---》250

%d是以补码还原的方式进行输出
补码: 1111 1010
反码: 1111 1001
原码: 1000 0110--》-6


~0x15 %d %u

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char a = 0x03;
    printf("%d\n",~a);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

  • &:按位与,两个都为1,结果才为1

    • 1 & 1 == 1
    • 1 & 0 == 0
    • 0 & 0 == 0
    • 0 & 1 == 0
    unsigned char ret = 0x12;
    unsigned char ret1 = ret & 0x34;
    printf("%hhu\n",ret1);
    
  • | :按位或,两个都为1,结果才为1

    • 1 | 1 == 1
    • 1 | 0 == 1
    • 0 | 1 == 1
    • 0 | 0 == 0
  • ^ : 按位异或 — 不同为1,相同为0

    • 1 ^ 0 == 1
    • 0 ^ 1 == 1
    • 0 ^ 0 == 0
    • 1 ^ 1 == 0
    /********异或操作***********/
    unsigned char a1 = 0x3c;
    unsigned char b1 = 0x47;
    printf("%d\n",a1 ^ b1);
    
  • << : 左移,按bit位往左移动

    • 1.无符号左移:
    unsigned int a = 3 << 3;
    printf("%d\n",a);// 24
    

    在这里插入图片描述

    • ​ 2.有符号左移

      int a = -3 << 3;
      printf("%d\n",a);
      

      在这里插入图片描述

  • >> : 右移

    • 无符号右移
    unsigned char a = 151 >> 5;
    printf("%d\n",a);//4
    

    在这里插入图片描述

    • ​ 有符号右移
    char a = -96 >> 5;
    printf("%d\n",a);
    

    注意:

    ​ 1.在进行移位运算的时候,凡是被移出去的位都丢弃,凡是空出来的都补零,移位运算是针对的是无符号整数。

    ​ 2. 如果非要进行有符号的移位运算,那么左移的时候,空出来的补0,右移的时候,空出来不符号位(原码阶段);

在这里插入图片描述

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 位运算符操作的对象是数据中的每一位

  • 运算规则:

    • 位逻辑反、位逻辑与、位逻辑或拥有与逻辑运算相似的规则和一样的真值表。

    • 异或运算:相同为0,不同为1

    • 移位运算:移出去的不要,空出来的补零。移位运算都是针对无符号数的运算。

在这里插入图片描述

​														左移运算

在这里插入图片描述

​														右移运算

​				

```c
结论:
无符号整数左移,并且有效数据1不丢失时,假设 data << n,等于 data * 2^n
无符号整数右移,并且有效数据1不丢失时,假设 data >> n,等于 data /  2^n
```
**问题1**
假设有如下程序,程序输出的结果是什么?

int main(void)
{
    unsigned char a, b, c;
    a = 0x3; // 0000 0011
    b = a|0x8; // 0000 0011 | 0000 1000 == 0000 1011
    c = b<<1; // 0000 1011 << 1 == 0001 0110

    printf("%d%d\n", b, c);// b:11 c:22
}

作业1:

假设有一个无符号32位整型数据

unsigned int data=0x12ff0045 

请编写一个程序用位运算把data的第14、15位修改1,把22、23位修改为0, 并且输出修改后的数据。

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    unsigned int data=0x12ff0045; 

    /*
        总结:
        如果某位置1,则将数据位或对应的位即可
        如果某位置0,将对应的位置1,其它位置0,最后位与数据
    */
    // 将14,15位置1
    printf("%x\n",data |  0x03 << 14);

    // 将22,23位值0
    printf("%x\n", data & ~(0x03 << 22));


    return 0;
}

5. 特殊运算符

  • 赋值运算符
    • 不能对常量赋值
    • 只能对变量赋值
    • 不能对数组赋值
    • 可以连续赋值,从右往左
    • 赋值运算符的左边(左操作数)必须是可写的地址空间
int a, b;
int x[5];

a = 100; // 对变量 a 赋值,正确
3 = 100; // 对常量 3 赋值,错误!
x = 123; // 对数组 x 赋值,错误!

// 连续赋值
a = b = 50; // 先将 50 赋给 b,再将 b 的值赋给 a,正确
  • 复合赋值符
    • 当左右两边有相同的操作数时,采用复合赋值符不仅直观,且能提高运算效率
    • 除了下述10个复合运算符之外,生造别的复合运算符是非法的
// 加减乘除:
a += n; // 等价于 a = a+n; // a+=100;  //a = a+100
a -= n; // 等价于 a = a-n;
a *= n; // 等价于 a = a*n;
a /= n; // 等价于 a = a/n;

// 求余:
a %= n; // 等价于 a = a%n;

// 位运算:
a &= n; // 等价于 a = a&n;
a |= n; // 等价于 a = a|n;
a ^= n; // 等价于 a = a^n;
a >>= n; // 等价于 a = a>>n;
a <<= n; // 等价于 a = a<<n;
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 100;
    a+=100; // a = a+100
    printf("%d\n",a);

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

    int b = 2;
    printf("%d\n",b<<=2);
    
    return 0;
}

6. 条件运算符(三目运算符,重点)

  • 唯一需要三个操作数的运算符
  • 语法:表达式1?表达式2:表达式3
  • 释义:当表达式1为真时,去表达式2,否则取表达式3
  • 举例:
int a = 100;
int b = 200;
int m = (a>b) ? a : b;  // 如果 a>b 为真,则 m 取 a 的值,否则取 b 的值

7. sizeof 运算符

  • 含义:计算指定数据类型或者变量所占据内存的字节数
  • 语法:sizeof(类型)、sizeof(变量),计算变量的字节数时圆括号可以省略
  • 举例:
printf("%d\n", sizeof(int));// 4
printf("%d\n", sizeof(long double));//12

int a[5];
printf("%d\n", sizeof(a));//4*5=20
printf("%d\n", sizeof a);

8. return运算符

  • 含义:退出某个函数(如果退出的是主函数main,那么整个程序也就推出)
  • 语法:必须出现在函数体内,可以带函数对应类型的数据
  • 举例:
int main()
{
    return 0;
}

9.逗号表达式:

(表达式1,表达式2,表达式3... 表达式n)
求值顺序:
	先求表达式1,再求表达式2,再求表达式3,最后求表达式n
	整个逗号表达式的值为n的值
注意:
    1.都好表达式的优先级最低
    2.运算顺序是从左往右
    3.整个都好表达式的值取决于最右边的表达式的值
int ret = (a>b,a++,b++,a);
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10, b = 20;
        // a>b==0  11  21  11
    // ret 的结果为逗号运算符的最后一条语句的结果
    int ret = (a>b,a++,b++,a);
    printf("%d\n",ret); // 11
    return 0;
}

10. 优先级与结合性

  • 当表达式出现不同的运算符时,根据优先级来决定谁先执行,比如先乘除后加减
  • 当表达式中出现多个相同优先级的运算符时,更具结合性决定谁先运行,比如从左到右
  • 如果不知道哪个优先级高,则加()区分

在这里插入图片描述

2.运算符

1. 算术运算符

运算符功能说明举例
+加法,一目取正a+b
-减法,一目取负a-b
*乘法a*b
/除法a/b
%取模(求余)a%b
++自加1a++, ++b
自减1a–-, --b
  • % 是算术运算符的重点,一般将某个数值控制在一定范围内,还有数据解析上
// demo1:产生一个随机数,并将此数控制在5的范围内
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    // 产生随机因子
    srand(time(NULL));

    // 产生随机数,控制在5以内
    int ret = rand() % 5;
    printf("%d\n",ret);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;
    printf("%d\n",a+b);
    printf("%d\n",a-b);
    printf("%d\n",a*b);
    printf("%d\n",a/b); // 除数
    printf("%d\n",a%b); // 取余数

    // 将1,2,3单独输出
    int data = 123;
    // 案例1:一般用于数据解析,比如温湿度模块获取的数据上位机需要解析并处理
    printf("%d\n",data%10);
    printf("%d\n",data/10%10);
    printf("%d\n",data/100);

    // 案例2:通过%将数据控制在某个范围内有效
    // while(1)
    // {
    //     int ret = scanf("%d",&data);
        
    //     if(ret != 1) break;

    //     printf("%d\n",data%10);
    // }

    // 根据时间产生随机数
    srand(time(NULL));

    int rad = rand();
    printf("%d\n",rad%5); // 将随机数控制在某个范围内有效
    
    return 0;
}

  • 关注点

    • 减号也是负号,比如-a是取变量a的相反数
    int a = 10;
    printf("%d\n",-a); // -10
    
    • 取模运算要求左右两边操作数必须是整型数据
    1.5 % 0.5 (错误)
    
    • 自加自减运算不仅可以对整型操作,也可以对浮点数、指针操作

1)单目运算符 目:目标操作数

​ ++ – +(正号) -(负号)

  • 前后缀运算:
    1. 前缀自加自减运算:先进行自加自减,再参与表达式运算
    2. 后缀自加自减运算:先参与表达式运算,在进行自加自减
int a = 100;
int b = 200;
int ret = a + b;
int c = ++a; // a先自加1,变成101;然后再赋值给c,因此c等于101
int d = b++; // b先赋值给d,因此d等于200,然后b自加1,变成201

int a = 3;
int x = a+++a++;(错误的)
int b = 3;
int y = b++++b;(错误的)
error: multiple unsequenced modifications to [-Werror,-Wunsequenced]//多次非顺序修改
总结就是不要将自增或自减运算符与赋值运算符等其他运算符复合使用。
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10;
    // 单目运算符, a先赋值给b 然后a自己加一
    int b = a++;
    int c = a+b
    printf("%d,%d\n",a,b);

    // 单目运算符, a1先自己加一 然后a1赋值给b1
    int a1 = 10;
    int b1 = ++a1;
    printf("%d,%d\n",a1,b1);

    b1 = --a1;
    printf("%d,%d\n",a1,b1);

    b1 = a1--;
    printf("第%d行结果为:%d,%d\n",__LINE__,a1,b1);// 9 10

    // 不规范,单目运算符不能多级运算
    int c = (a1++) + (++a1);
    printf("%d\n",c);
    return 0;
}

2)双目运算符

+ - * /
printf("%d\n",3/2);// 一个整数除以另一个整数,小数被舍弃
printf("%f\n",3*1.0/2);// 如果想要小数,那么可以*1.0,隐式类型转换 1.500000

printf("%d\n",10%3);//%取余运算符的左右两边必须都是整数
// 一般取余运算符在我们编程开发中用于控制数据的大小范围
srand(time(NULL));// 获取随机数因子
int a9 = rand()%5;// rand()得到一个随机数0-4
printf("%d\n",a9);

2. 关系运算符

运算符功能举例说明
>大于a > b判断a是否大于b
>=大于或等于a >= 5判断a是否大于或等于5
<小于3 < x判断3是否小于x
<=小于或等于x <= (y+1)判断x是否小于或等于y+1
==等于(x+1) == 0判断x+1是否等于0
!=不等于c != ‘\0’判断c是否不等于’\0’
注意关系运算符的值为布尔值 也就是说要么关系成立(1) 要么不成立(01 > 10 20 < 30
int a = 10; int b = 20;
a != b;// 1
a == b; // 0
a = b;// 恒定为真,这是赋值不是比较关系,不能这么写
demo:
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10, b = 20;
    // 关系运算符,结果只能是真为1或者假为0
    printf("%d\n",a > b);  // 0
    printf("%d\n",a < b);  // 1
    printf("%d\n",a == b);  // 0
    /*
    printf("%d\n",a = b); // 为真,但是这么写不对
    // if(a=b)
    {
        printf("错误的示范\n");
    }
    */
   // 大于或者等于
    printf("%d\n",a>=b);
    // 小于或者等于
    printf("%d\n",a<=b);
    printf("%d\n",a != b);

    return 0;
}
  • 关注点:
    • 关系运算符用于判断运算符两边的表达式是否满足给定的大小条件
    • 由关系运算符组成的表达式称为关系表达式,其值为布尔型
    • 判断是否相等是双等号==,而不是一个等号

3. 逻辑运算符

运算符功能说明举例
逻辑反!(x==0)
&&逻辑与x > 0 && x < 10 // 0<x<10数学写法
||逻辑或y < 10 || x > 10
  • 运算规则:

    • 逻辑反:将逻辑真、假翻转,即真变假,假变真

    • 逻辑与:将两个关系表达式串联起来,当且仅当左右两个表达式都为真时,结果为真。

    • 逻辑或:将两个关系表达式并联起来,当且仅当左右两个表达式都为假时,结果为假。

      在这里插入图片描述

  • 特殊规则

    • 在逻辑与运算中,如果左边表达式的值为假,那么右边表达式将不被执行。
    • 在逻辑或运算中,如果左边表达式的值为真,那么右边表达式将不被执行。
    逻辑与 && ---而且,同时 两个操作数都必须为真,结果为真
    逻辑或 || ---或者 只要有一个为真,那么结果为真
    逻辑非 ! ----反 
    非0 为真,0 为假
    逻辑表达式结果有两种 1为真 0为假
    
    int a = 10;
    int b = 20;
    //c语言 惰性运算,也就是说 如果前面的结果为假,后面就不会运行--知难而退
    if(a > b && ++a)
    {
        printf("111\n");
    }
    printf("%d\n",a);
    
    demo2:
    #include <stdio.h>
    #include <stdbool.h>
    
    int main(int argc, char const *argv[])
    {
        int a = 10, b = 5, c = 6, d = 7;
        // 10大于5并且6小于7为真,结果为1
        printf("%d\n",a > b && c < d);
        
        // 10小于5或者6小于7为真,结果为1
        printf("%d\n",a < b || c < d);
    
        // -1为真取反为假,结果为0
        printf("%d\n",!-1);
    
        // c语言为惰性语言,只要表达式1能确定整条语句结果,则表达式2不执行
        printf("%d,%d\n",a < b && a++,a);
    
        return 0;
    }
    
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        int a = 10, b = 20;
        // 取反
        printf("%d\n",!(a > b)); // 1
        printf("%d\n",!a); // 0
    
        // 逻辑与
        printf("%d\n",(a>b) && (a != b));
        // 不能这么写,它的意思是先判断 a < 15 为真结果是1
        // 1<b为真,最终结果是1
        //printf("%d\n",a<15<b); // 1 错误写法
        printf("%d\n",a < 15 && b > 15);// 正确写法
    
        // 逻辑或
        printf("%d\n",a != b || a < b);
        printf("%d\n",a != b || a > b);
        printf("%d\n",a == b || a > b);
        printf("%d\n",a == b || a < b);
    
        // 惰性运算,笔试常考
        int c = 0;
        // 左边为真,右边不执行
        (a != b) || (c = 100);
        // 左边为假,右边执行
        (a == b) || (c = 100);
        printf("%d\n",c);
    
        return 0;
    }
    
    

4. 位运算符(重点,对整个变量的某一位操作)

运算符名称举例功能说明
~位逻辑反~a将变量 a 中的每一位取反
&位逻辑与a & b将变量 a 和 b 逐位进行与操作
|位逻辑或a | b将变量 a 和 b 逐位进行或操作
^位逻辑异或a ^ b将变量 a 和 b 逐位进行异或操作
<<左移a << 4将变量 a 中的每一位向左移动4位
>>右移x >> n将变量 x 中的每一位向右移动4位
  • ~ : 按位取反—单目运算符,数据将每一个bit位取反,1变0,0变1
unsigned char ret = ~0x05;// 0000 0101--> 1111 1010
//%u无符号输出,直接以补码的方式输出,%d以补码还原的方式输出
printf("%hhu\n",ret);// 250
printf("%hhd\n",ret);// -6
0x05 ----> 0000 0101

~0x05---->       1111 1010 ---》250

%d是以补码还原的方式进行输出
补码: 1111 1010
反码: 1111 1001
原码: 1000 0110--》-6


~0x15 %d %u

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char a = 0x03;
    printf("%d\n",~a);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

image-20240628155809288
  • &:按位与,两个都为1,结果才为1

    • 1 & 1 == 1
    • 1 & 0 == 0
    • 0 & 0 == 0
    • 0 & 1 == 0
    unsigned char ret = 0x12;
    unsigned char ret1 = ret & 0x34;
    printf("%hhu\n",ret1);
    
  • | :按位或,两个都为1,结果才为1

    • 1 | 1 == 1
    • 1 | 0 == 1
    • 0 | 1 == 1
    • 0 | 0 == 0
  • ^ : 按位异或 — 不同为1,相同为0

    • 1 ^ 0 == 1
    • 0 ^ 1 == 1
    • 0 ^ 0 == 0
    • 1 ^ 1 == 0
    /********异或操作***********/
    unsigned char a1 = 0x3c;
    unsigned char b1 = 0x47;
    printf("%d\n",a1 ^ b1);
    
  • << : 左移,按bit位往左移动

    • 1.无符号左移:
    unsigned int a = 3 << 3;
    printf("%d\n",a);// 24
    

    在这里插入图片描述

    • ​ 2.有符号左移

      int a = -3 << 3;
      printf("%d\n",a);
      

      在这里插入图片描述

  • >> : 右移

    • 无符号右移
    unsigned char a = 151 >> 5;
    printf("%d\n",a);//4
    

    在这里插入图片描述

    • ​ 有符号右移
    char a = -96 >> 5;
    printf("%d\n",a);
    

    注意:

    ​ 1.在进行移位运算的时候,凡是被移出去的位都丢弃,凡是空出来的都补零,移位运算是针对的是无符号整数。

    ​ 2. 如果非要进行有符号的移位运算,那么左移的时候,空出来的补0,右移的时候,空出来不符号位(原码阶段);

在这里插入图片描述

  • 位运算符操作的对象是数据中的每一位

  • 运算规则:

    • 位逻辑反、位逻辑与、位逻辑或拥有与逻辑运算相似的规则和一样的真值表。

    • 异或运算:相同为0,不同为1

    • 移位运算:移出去的不要,空出来的补零。移位运算都是针对无符号数的运算。
      在这里插入图片描述

      ​ 左移运算

在这里插入图片描述

​														右移运算

​				

```c
结论:
无符号整数左移,并且有效数据1不丢失时,假设 data << n,等于 data * 2^n
无符号整数右移,并且有效数据1不丢失时,假设 data >> n,等于 data /  2^n
```
**问题1**
假设有如下程序,程序输出的结果是什么?

int main(void)
{
    unsigned char a, b, c;
    a = 0x3; // 0000 0011
    b = a|0x8; // 0000 0011 | 0000 1000 == 0000 1011
    c = b<<1; // 0000 1011 << 1 == 0001 0110

    printf("%d%d\n", b, c);// b:11 c:22
}

作业1:

假设有一个无符号32位整型数据

unsigned int data=0x12ff0045 

请编写一个程序用位运算把data的第14、15位修改1,把22、23位修改为0, 并且输出修改后的数据。

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    unsigned int data=0x12ff0045; 

    /*
        总结:
        如果某位置1,则将数据位或对应的位即可
        如果某位置0,将对应的位置1,其它位置0,最后位与数据
    */
    // 将14,15位置1
    printf("%x\n",data |  0x03 << 14);

    // 将22,23位值0
    printf("%x\n", data & ~(0x03 << 22));


    return 0;
}

image-20240701110053514

5. 特殊运算符

  • 赋值运算符
    • 不能对常量赋值
    • 只能对变量赋值
    • 不能对数组赋值
    • 可以连续赋值,从右往左
    • 赋值运算符的左边(左操作数)必须是可写的地址空间
int a, b;
int x[5];

a = 100; // 对变量 a 赋值,正确
3 = 100; // 对常量 3 赋值,错误!
x = 123; // 对数组 x 赋值,错误!

// 连续赋值
a = b = 50; // 先将 50 赋给 b,再将 b 的值赋给 a,正确
  • 复合赋值符
    • 当左右两边有相同的操作数时,采用复合赋值符不仅直观,且能提高运算效率
    • 除了下述10个复合运算符之外,生造别的复合运算符是非法的
// 加减乘除:
a += n; // 等价于 a = a+n; // a+=100;  //a = a+100
a -= n; // 等价于 a = a-n;
a *= n; // 等价于 a = a*n;
a /= n; // 等价于 a = a/n;

// 求余:
a %= n; // 等价于 a = a%n;

// 位运算:
a &= n; // 等价于 a = a&n;
a |= n; // 等价于 a = a|n;
a ^= n; // 等价于 a = a^n;
a >>= n; // 等价于 a = a>>n;
a <<= n; // 等价于 a = a<<n;
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 100;
    a+=100; // a = a+100
    printf("%d\n",a);

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

    int b = 2;
    printf("%d\n",b<<=2);
    
    return 0;
}

6. 条件运算符(三目运算符,重点)

  • 唯一需要三个操作数的运算符
  • 语法:表达式1?表达式2:表达式3
  • 释义:当表达式1为真时,去表达式2,否则取表达式3
  • 举例:
int a = 100;
int b = 200;
int m = (a>b) ? a : b;  // 如果 a>b 为真,则 m 取 a 的值,否则取 b 的值

7. sizeof 运算符

  • 含义:计算指定数据类型或者变量所占据内存的字节数
  • 语法:sizeof(类型)、sizeof(变量),计算变量的字节数时圆括号可以省略
  • 举例:
printf("%d\n", sizeof(int));// 4
printf("%d\n", sizeof(long double));//12

int a[5];
printf("%d\n", sizeof(a));//4*5=20
printf("%d\n", sizeof a);

8. return运算符

  • 含义:退出某个函数(如果退出的是主函数main,那么整个程序也就推出)
  • 语法:必须出现在函数体内,可以带函数对应类型的数据
  • 举例:
int main()
{
    return 0;
}

9.逗号表达式:

(表达式1,表达式2,表达式3... 表达式n)
求值顺序:
	先求表达式1,再求表达式2,再求表达式3,最后求表达式n
	整个逗号表达式的值为n的值
注意:
    1.都好表达式的优先级最低
    2.运算顺序是从左往右
    3.整个都好表达式的值取决于最右边的表达式的值
int ret = (a>b,a++,b++,a);
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int a = 10, b = 20;
        // a>b==0  11  21  11
    // ret 的结果为逗号运算符的最后一条语句的结果
    int ret = (a>b,a++,b++,a);
    printf("%d\n",ret); // 11
    return 0;
}

10. 优先级与结合性

  • 当表达式出现不同的运算符时,根据优先级来决定谁先执行,比如先乘除后加减
  • 当表达式中出现多个相同优先级的运算符时,更具结合性决定谁先运行,比如从左到右
  • 如果不知道哪个优先级高,则加()区分

在这里插入图片描述

3.控制流

二路分支

  • 逻辑:程序中某段代码需要在满足某个条件时才能运行
  • 形式:
    1. if语句:表达一种 如果-则的条件执行关系
    2. if-else语句:表达一种 如果-否则 的互斥分支关系
  • 举例:
// if语句 a = 1
if(a%2 == 0)
{
    printf("a 是偶数\n");
}
// if-else 语句9
if(a%2 == 0)
{
    printf("a 是偶数\n");
}
else
{
    printf("a 是奇数\n");
}
if(表达式1)// 如果为真执行复合语句1
{
    复合语句1}
else if(表达式2)//如果表达式1,2同时满足,则只会执行语句1
{
    复合语句2}
else if(表达式3)
{
     复合语句3;
}
else // 以上3个都不满足的时候执行此语句
{
     复合语句4}
if(表达式1)// 如果为真,执行复合语句1
{
    if(表达式11)
    {
        复合语句1}
}
else if(表达式2)
{
    复合语句2
}
  • 注意:

    1. 只要表达式的值为真(非0),则执行{}复合语句
    2. if语句可以单独使用,else语句不可以,else语句必须根if语句配套使用
    3. 不管是if语句还是else语句,代码块都必须使用大括号{}括起来,否则只有首句有效
    4. 如果只有一条执行语句,可以这样写:
    if(表达式)
        执行语句xxx;如果表达式为真,那么执行该语句
    

    5.编程规范,写完if或else后,不管后面又没有语句,先写{}圈定为它的范围,然后再到{}里面补充语句,请用4个格缩进

demo:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int money = 0;
    printf("请输入金额: ");
    int ret = scanf("%d",&money);
    if(!ret)
    {
        printf("输入有误,请重新输入\n");
    }

    // 二路分支只能进入一个结果为真的条件
    // 如果两个判断语句条件同时为真,则只执行首个,后面不执行
    // 如果判断语句后面作用域只有一条表达式可以不加{}
    if(money >=0 && money <= 10)
        printf("只能吃白米饭\n");
    else if(money > 10 && money <= 20)
    {
        printf("只能打一个荤菜\n");
    }
    else if(money >= 20 && money < 30)
    {
        printf("只能打一个荤菜,一个素菜\n");
    }
    else // 结束
    {
        printf("随便吃\n");
    }

    return 0;
}

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int score;
    // 不能加转义字符,加了以后输入的时候要自行添加
    //scanf("%d\n",&score); // 错误演示
    scanf("%d",&score);
    // ;号的作用是一条语句结束
    //if(score < 610); // 错误演示,判断语句结尾不能加;因为;表示结束
    if(score > 610)
    {
        int money;
        printf("输入金额:");
        scanf("%d",&money);
        if(money > 5000)
        {
            printf("可以进入合工大学习\n");
        }
        else
        {
            printf("可以办理分期助学\n");
        }
        
    }
    else
    {
        printf("复读或者选其它学校\n");
    }
    return 0;
}

练习1:制作一个简单的打分系统(满分制:100),使用if … else if … else方法,分数需整数

​ 60分以下:评级为D

​ 60-80分 : 评级为C

​ 80-90分 : 评级为B

​ 90-100分: 评级为A

​ 不在0-100范围内:错误

#include <stdio.h>

int main(int argc, char const *argv[])
{
    float score;
    scanf("%f",&score);

    if(score < 60)
        printf("D\n");

    else if(score >= 60 && score < 80)
        printf("C\n");

    else if(score >= 80 && score < 90)
        printf("B\n");

    else if(score >= 90 && score <= 100)
        printf("A\n");
    
    else
        printf("输入错误\n");

    return 0;
}

多路分支

  • 辑:根据不同的条件执行不同的代码片段
  • 语法:
switch(n)//条件
{
    case 1:
       printf("one\n");
   	   break;
    case 2:
   		printf("two\n");
   		break;
  
	case 3:
   		printf("three\n");
   		break;
  
	default:
    	printf("其他数字\n"); 
}
  • 要点解析
    1. switch(n)语句中的n必须是一个整型表达式,即switch判断的数据必须是整型或者字符
    2. case语句只能带整型常量,包括普通整型或字符,不包括const型数据
    3. break语句的作用是跳出整一个switch结构,没有break程序会略过case往下执行
    4. default语句不是必须的,一般放在最后面(因此不需要break)
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("请输入成绩: ");
    int score;
    scanf("%d",&score);
    switch (score) // 表达式必须是整型比如整数或者字符
    {
    case 0 ... 60: // 请注意格式
        printf("D\n");
        break;

    case 61 ... 80:
        printf("C\n");
        break;
    
    case 81 ... 90:
        printf("B\n");
        break;

    case 91 ... 100:
        printf("A\n");
        break;
    
    default:
        printf("输入错误\n");
        break;
    }
    return 0;
}

练习2:计算加盟费

​ 小张加盟一家餐饮企业,在每个季度结束后,小张必须向总部缴纳加盟费,加盟费与营业额的提成关系如下:

​ 营业额 提成系数

​ 0<=n<10w 0.1

​ 10<=n<20w 0.075

​ 20<=n<40w 0.05

​ 40<=n<60w 0.03

​ 60<=n<100w 0.015

​ n >= 100w 0.01

​ 请输入小张的营业额,然后求出它需要的提交的加盟费

#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("请输入营业额(万元): ");
    float money,vol;
    int flage = 0;
    scanf("%f",&money);

    if(money < 0) // 亏钱
    {
        printf("倒闭了,找个班上\n");
    }
    else if(0 < money && money < 10)
    {
        flage = 1;
    }
    else if(10 <= money && money < 20)
    {
        flage = 2;
    }
    else if(20 <= money && money < 40)
    {
        flage = 3;
    }
    else if(40 <= money && money < 60)
    {
        flage = 4;
    }
    else if(60 <= money && money < 100)
    {
        flage = 5;
    }
    else if(money >= 100)
    {
        flage = 6;
    }

    // 计算提成
    switch (flage)
    {
    case 1:
        vol = 0.1;
        break;

    case 2:
        vol = 0.075;
        break;

    case 3:
        vol = 0.05;
        break;

    case 4:
        vol = 0.03;
        break;

    case 5:
        vol = 0.015;
        break;
    
    case 6:
        vol = 0.01;
        break;

    default:
        break;
    }

    // 输出加盟费
    printf("加盟费: %.2f万元\n",money*vol);

    return 0;
}

const

  • 逻辑:使一个变量不可修饰,变量属性为只读
  • 作用:
    • 修饰普通变量,使之不可修改
    • 修饰指针变量,使之不可修改或者使其指向的目标不可修改
  • 示例:
int const a = 100; // 定义了一个不可修改的变量a
const int b = 200; // 定义了一个不可修改的变量b

a = 101; // 错误
b = 202; // 错误
#include <stdio.h>

int main(int argc, char const *argv[])
{
    //const int a = 10;
    int const a = 10;
    // a被修饰为只读属性,即为常量属性
    a = 20;
    printf("a=%d\n",a);

    const int b;// 没有意义因为定义的时候为常量属性,所有不能修改b的值,b是随机数
    
    return 0;
}

while与do…while循环

  • 逻辑:使得程序中每一段代码可以重复循环的运行

  • 形式:

    • while循环:先判断,再循环
    while(表达式)// 表达式的值为真的时候进入循环体,为假退出
    {
        
    }
    while(1)// 死循环 等价于 while(1);
    {
        
    }
    如果循环体内只有一条语句,那么{}可以省略
    while(1)
        printf("11\n");
    

while循环在这里插入图片描述

while循环:入口判断

// 循环输出一系列整数,直到100为止
int a;
scanf("%d", &a);
while(a <= 100)
{
    printf("%d\n", a);
    a++;
}
#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 打印1...10
    int i = 0;
    // 如果while()内的表达式为真则一直重复
    // 运行while{}内的内容,直到while()内的表达式为
    // 假则退出while{}
    while(i != 10)
    {
        printf("%d\n",++i);
    }
    return 0;
}
  • do-while循环:先循环,再判断
格式
do
{
	循环体;
}while(表达式)

在这里插入图片描述

do-while循环:出口判断

// 循环输出一系列整数,直到100为止
int a;
scanf("%d", &a);
do
{
    printf("%d\n", a);
    a++;
}while(a <= 100);
  • 语法点:
    • while 循环先进行判断,条件为真后才执行循环体,因此循环体可能一遍也不执行。
    • do-while 循环先执行循环体,再进行判断,因此循环体至少会执行一遍。
    • do-while 循环中的 while 语句后面有分号;

练习:

1.使用while实现1+2+...+100的和
2.使用do...while实现1+2+...+100的和
3.每次从键盘输入两个整型数据,并且比较两个数据的最大值进行输出,当输入相等时结束输入,退出程序。

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int number = 1;
    int sum = 0;
    // while一定要有退出条件
    // while (number <= 100)
    // {
    //     sum += number; // sum = sum+number
    //     printf("%d\n",sum);
    //     number++;
    // }

    // while(1)
    // {
    //     // 一定要有退出条件,否则是死循环,程序异常
    //     if(number > 100)
    //         break;

    //     sum += number;
    //     printf("%d\n",sum);
    //     number++;
    // }

    do // 先进来
    {
        sum += number;
        printf("%d\n",sum);
        number++;
    } while (number <= 100); // 再判断
    
    
    return 0;
}

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int number1 = 0, number2 = 0;
    while(1)
    {
        printf("请输入两个数: ");
        int ret = scanf("%d%d",&number1,&number2);
        if(ret != 2) // 表示输入异常
        {
            // 清空缓冲区
            while(getchar() != '\n');
            // 回到起始位置重新输入
            continue;
        }

        // 两数相等则退出
        if(number1 == number2)
            break;

        printf("max = %d\n",number1 > number2 ? number1:number2);
    }
    
    return 0;
}

for循环

  • 逻辑:与 while 循环类似,但更加紧凑,for 循环将控制循环的变量集中在一行
for(初始表达式1;判断表达式2;循环操作表达式3)
{
    循环体;
}
// 第一条表达式只执行一遍i = 0只执行一遍
// 然后执行表达式2,判断i 是否小于等于5,如果为真则执行{}里面的内容
// 最后执行表达式3,i++
// 然后一次循环完成再重新执行表达式2,判断i是否小于5,如果为真会执行
// {}里面的内容,最后执行表达式3,i++,依次类推,直到表达式2为假
// 则退出循环体
for(int i = 0; i <= 5; i++)
{
    printf("i:%d\n",i);
}
// 死循环
for(;;)
{
    
}
或者
for(;;);
//多变量
for(int i = 0,j = 0; i < 5; i++,j++)
{
    printf("i:%d j:%d\n",i,j);
}
  • 示例:
// 循环输出一系列整数,直到100为止
int a;
for(a=30; a<=100; a++)
{
    printf("%d\n", a);
}
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int i = 1;
    while(i < 10)
    {
        printf("%d\n",i);
        i++;
    }  

    for(int i = 1; i < 10; i++)
    {
        printf("%d\n",i);
    }

    // 如果我们不能确定程序什么时候能退出则使用while
    while(1) // while循环为死循环
    {
        printf("hello world\n");
    }

    // 如果我们能确定程序什么时候能退出则使用for
    for(;;) // for循环为死循环
    {
        printf("hello world\n");
    }
    return 0;
}

  • for嵌套
int main(int argc, char const *argv[])
{
    for(int i = 0; i < 5; i++)// 外循环
    {
        for(int j = 0; j < 4; j++)// 内循环
        {
            printf("i:%d j:%d\n",i,j);
        }
    }
    return 0;
}

练习3:使用for循环实现1+2+…+100的和

  • 语法点:

    • 循环的标准语法是: for(表达式1 ; 表达式2 ; 表达式3)
    • 表达式1一般用来初始化循环控制变量
    • 表达式2一般用来作为循环判定条件,为真则进入循环,为假则跳出循环
    • 表达式3一般用来更新循环控制变量
    • 三个表达式均可以省略,但分号不能省略
    • while注重循环条件,for注重循环次数

break(退出)与continue(继续)

  • 运用场景与特点
    • break关键字只能用于循环(while for do…while)和switch语句中,表示结束循环或者跳出switch语句
    • break关键字只能跳出最近一层的循环,也就是说,如果有多重循环,只能跳出最靠近break语句那层循环
    • break关键字 不能 单独用于 if语句中,除非if语句外面包含了循环
  • 逻辑:
    • continue关键字只能用于循环体中(while do…while for),用于提前结束当前循环体后,又从最近一层循环体开始执行
    • continue关键字不能 单独用于 if语句或者switch 语句中,除非外面包含了循环
    • break:① 跳出 switch 语句; ② 跳出当层循环体
    • continue:结束当次循环,进入下次循环
  • 举例:
switch(n)
{
case 1:
    printf("one\n");
    break;  // 跳出 switch 语句
case 2:
    printf("two\n");
    break;        
}

while(1)
{
    int n = scanf("%d", &a);
    if(n == 0)
        break; // 跳出当层 while 循环体
}

for(int i=1; i<=100; i++)
{
    if(i%7 == 0)
        continue; // 跳过所有能被7整除的数
     
    printf("%d\n", i);       
}

练习4:每次判断输入的单个字符,如果是字符a~z或者A-Z,则打印yes,否则打印no。如果输入的是’#‘ 则退出程序

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char ch;
    while(1)
    {
        scanf("%c",&ch);
        if(ch == '#')
            break;

        if(ch >= 'a' && ch <= 'z' || ch >= 'a' && ch <= 'Z')
        {
            printf("yes\n");
        }
        else if(ch != '\n') // 过滤'\n'
        {
            printf("no\n");
        }
    }
    return 0;
}

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 输入字符
    char ch;
    printf("请输入字符: ");
    scanf("%c",&ch);   

    // 开发的时候要做好出错处理
    if(ch < 'A' || ch > 'Z')
    {
        printf("输入错误,请重新运行\n");
        return -1;
    }

    // 计算行数
    int line = ch - 'A'+1;
    //printf("line = %d\n",line);
    int i , j;
    for(i = 1; i <= line; i++)// i 表示字母
    {
        // 输出空格
        for(j = 0; j < line-i; j++)
            printf(" ");

        // 升序
        for(j = 0; j < i; j++)
            printf("%c",'A'+j);

        // 降序
        for(j -= 2; j >= 0; j--)
            printf("%c",'A'+j);

        printf("\n");
    }

    return 0;
}

输出26字母

#include <stdio.h>

int main(int argc, char const *argv[])
{
    for(int ch = 'a'; ch <= 'z'; ch++)
        printf("%c\n",ch);
        
    return 0;
}

goto语句

  • 逻辑:无条件跳转
  • 格式
goto 语句标号;//程序会直接跳转到语句标号的地方执行

语句标号:
  • 示例:
int main()
{
    printf("%d\n", __LINE__); // 打印第3行 
    
    // 无条件跳转到label处 
    goto label;
    printf("%d\n", __LINE__); // 打印第7行,此处被略过
label:  
    printf("%d\n", __LINE__); // 打印第9行
}
  • 语法:
    • goto语句直接跳转到本代码块中的标签处
    • 标签指的是以冒号结尾的标识符
  • 作用:
    • goto语句的无条件跳转不利于程序的可读性,一般不建议使用,linux内核常用
    • goto语句常被用在程序的错误处理中
#include <stdio.h>

int main(int argc, char const *argv[])
{
// 注意:千万不要使用goto语句实现死循环,程序
// 无法退出    
// lable:
//     printf("当前在第:%d 行\n",__LINE__);
//     goto lable;

// goto 的唯一使用场景为跳转到出错处理
    printf("请输入字符:");
    int ch = getchar();
    if(ch == '#')
    {
        goto error;
    }
    printf("当前在第:%d 行\n",__LINE__);

// 出错处理标签一定是在程序的末尾
// 程序正常退出还是异常退出最终还是得要执行error标签的语句
error:
    printf("process exit\n");
    return 0;
}

demo2:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int number;
lable:
    printf("输入整数: ");
    int ret = scanf("%d",&number);
    if(ret != 1)
    {
        while(getchar() != '\n');
        goto lable; // 慎重
    }

    if(number > 100)
    {
        printf("数值过大,程序异常结束\n");
        goto error;
    }
    printf("__%d__\n",__LINE__);
error:
    printf("释放程序资源\n");
    return 0;
}

    break; // 跳出当层 while 循环体

}

for(int i=1; i<=100; i++)
{
if(i%7 == 0)
continue; // 跳过所有能被7整除的数

printf("%d\n", i);       

}


练习4:每次判断输入的单个字符,如果是字符a~z或者A-Z,则打印yes,否则打印no。如果输入的是’#‘  则退出程序

```c
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char ch;
    while(1)
    {
        scanf("%c",&ch);
        if(ch == '#')
            break;

        if(ch >= 'a' && ch <= 'z' || ch >= 'a' && ch <= 'Z')
        {
            printf("yes\n");
        }
        else if(ch != '\n') // 过滤'\n'
        {
            printf("no\n");
        }
    }
    return 0;
}

[外链图片转存中…(img-NWIL0i4w-1721047269103)]

#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 输入字符
    char ch;
    printf("请输入字符: ");
    scanf("%c",&ch);   

    // 开发的时候要做好出错处理
    if(ch < 'A' || ch > 'Z')
    {
        printf("输入错误,请重新运行\n");
        return -1;
    }

    // 计算行数
    int line = ch - 'A'+1;
    //printf("line = %d\n",line);
    int i , j;
    for(i = 1; i <= line; i++)// i 表示字母
    {
        // 输出空格
        for(j = 0; j < line-i; j++)
            printf(" ");

        // 升序
        for(j = 0; j < i; j++)
            printf("%c",'A'+j);

        // 降序
        for(j -= 2; j >= 0; j--)
            printf("%c",'A'+j);

        printf("\n");
    }

    return 0;
}

输出26字母

#include <stdio.h>

int main(int argc, char const *argv[])
{
    for(int ch = 'a'; ch <= 'z'; ch++)
        printf("%c\n",ch);
        
    return 0;
}

goto语句

  • 逻辑:无条件跳转
  • 格式
goto 语句标号;//程序会直接跳转到语句标号的地方执行

语句标号:
  • 示例:
int main()
{
    printf("%d\n", __LINE__); // 打印第3行 
    
    // 无条件跳转到label处 
    goto label;
    printf("%d\n", __LINE__); // 打印第7行,此处被略过
label:  
    printf("%d\n", __LINE__); // 打印第9行
}
  • 语法:
    • goto语句直接跳转到本代码块中的标签处
    • 标签指的是以冒号结尾的标识符
  • 作用:
    • goto语句的无条件跳转不利于程序的可读性,一般不建议使用,linux内核常用
    • goto语句常被用在程序的错误处理中
#include <stdio.h>

int main(int argc, char const *argv[])
{
// 注意:千万不要使用goto语句实现死循环,程序
// 无法退出    
// lable:
//     printf("当前在第:%d 行\n",__LINE__);
//     goto lable;

// goto 的唯一使用场景为跳转到出错处理
    printf("请输入字符:");
    int ch = getchar();
    if(ch == '#')
    {
        goto error;
    }
    printf("当前在第:%d 行\n",__LINE__);

// 出错处理标签一定是在程序的末尾
// 程序正常退出还是异常退出最终还是得要执行error标签的语句
error:
    printf("process exit\n");
    return 0;
}

demo2:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int number;
lable:
    printf("输入整数: ");
    int ret = scanf("%d",&number);
    if(ret != 1)
    {
        while(getchar() != '\n');
        goto lable; // 慎重
    }

    if(number > 100)
    {
        printf("数值过大,程序异常结束\n");
        goto error;
    }
    printf("__%d__\n",__LINE__);
error:
    printf("释放程序资源\n");
    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

;