第2章 基本数据类型与简单程序设计(2026.5更新完毕)

运行环境以Dev-C++、Visual Studio 2022、MacOS命令行与Xcode为主

1.C语言的基本数据类型

  • 1.1 常用数据类型

    • 不同编译系统或计算机系统对不同类型数据所占用的字节数有不同规定

    • 32bit计算机系统中,整型数据占4B,长整型数据占4B

    • 16bit计算机系统中,整型数据占2B,长整型数据占4B

    类型标识符 名称 长度 (B) 表示范围或注意事项
    char 字符型 1 有符号数:0 ~ 255,无符号数:-128 ~ +127;是否为signed char取决于编译器
    short 短整型 2 0 ~ 2^16 - 1,即 0 ~ 65535
    int 整型 4 C语言中默认为signed int
    long 长整型 4 C标准规定,sizeof(long) >= sizeof(int),即长整型数据至少应该和整型数据占据字节数相同
    float 单精度浮点型 4 7位有效数字,不算小数点和符号
    double 双精度浮点型 8 字面量小数默认类型,如3.4
    long long 超长整型 8 C99标准新增类型
  • 1.2 2的幂运算

    底数 指数 结果 指数 结果
    2 1 1 2 4
    2 3 8 4 16
    2 5 32 6 64
    2 7 128 8 256
    2 9 512 10 1024
    2 15 32768 16 65536
  • 1.3 编码类型

    • ASCII码:主要用于表示英文字符,共8bit,即用 8 位来表示 1 个字符,总共可以表示2^7=128种英文字符(第 8 位固定为 0,不用来编码)
    #include <stdio.h>
    int main(int argc, const char * argv[]) {
        // 循环打印ASCII码在32~127范围内的字符
        int i = 0;
        for (i = 32; i <= 127; i++) {
        		printf("%c ", i);
        }
        return 0;
    }
    
    • 万国码(Unicode码)

      • 计算机产生初期各厂商无统一的编码方式,ASCII码是第一个统一的标准字符编码。伴随计算机的发展,各国加入使用行列,ASCII码无法表示所有国家的语言,便产生了乱码,万国码由此产生

      • 万国码用于表示各国的文字字符,共32bit,可表示约 111 万种字符,也可以表示中文。它拓展了ASCII码的表示范围,如A的Unicode码为00000000 00000000 00000000 0100 0011,前方的 0 可省略,一定程度上浪费了硬盘的存储空间,UTF-8编码由此产生

    • UTF-8编码

      • 压缩Unicode码,位数为 8 的整数倍(8/16/32等),用尽量少的字符表示Unicode能表示的字符,如字符 'A' 的UTF-8编码ASCII码一致,均为0100 0011

      • 假设某字符的Unicode码为00000000 00000000 00000100 00111000,则用UTF-8表示为00000100 00111000,可显著节省存储空间

2.常量

  • 2.1 定义:程序运行过程中值保持不变的量,如π、性别、身份证号、血型等

    • 字面常量:30 3.14 'w' "abc"
    • const修饰的常变量
    const int a = 10;    // a 本质上是变量,但值不能被修改
    const int n = 10;
    int arr[n] = {10};    // 代码会报错,表明n不是常量
    
    • #define定义的标识符常量
    #define PI 3.1415926
    
    • 枚举常量
    enum Color {
        RED,
        GREEN,
        BLUE
    };
    enum Color c = RED;
    
  • 2.2 整型常量(需要先掌握进制相关知识)

    常量 说明
    -1234 十进制整型常量,基数为 0 ~ 9
    01234 以 0 开头,八进制整型常量,基数为 0 ~ 8
    0x1234 十六进制整型常量,以 0x 或 0X 开头,基数为0 ~ 9、字符 A ~ F 或 a ~ f
    0L 十进制长整型常量,以L或l结尾
    • 存储方式

      • 源码:最高位表示符号,其余位表示数值的绝对值。以8bit表示+7的源码为 0000 0111-7的源码为 1000 0111

      • 反码:正数的反码等于源码;负数的反码基于原码符号位不变,其余位按位取反(0 变 1,1 变 0)。以8bit表示+7的反码为 0000 0111-7的反码为 1111 1000

      • 补码:正数的补码等于源码;负数的补码是基于反码末位再加 1。以8bit表示+7的补码为 0000 0111-7的补码为 1111 1001

      // 正数的源码、反码和补码均相同
      // 负数的补码为反码加1
      // 已知补码,通过补码值减1,符号位不变,其余位取反可得到源码;或者直接用补码值的符号位不变,其余位取反,然后再加1也可以得到源码
      
      • 任何类型的数据在计算机中均以二进制补码形式表示和存储,其中最高位为符号位(无符号型除外),1表示负数,0表示正数。使用补码,可以将符号位和数值域统一处理,同时加法和减法也可以统一处理(CPU只有加法器),补码与源码相互转换的运算过程相同,不需要额外的硬件电路
      // 以计算 4 - 6 的结果为例分析运算过程
      // 由于 CPU 中只有加法器,4 - 6 修改为 4 + (-6)
      //                源码             反码             补码
      // 4         00000100           00000100        00000100
      // -6        10000110           111111001       111111010
      // 若以源码进行计算,则 4 + (-6) = 00000100 + 10000110 = 10001010 = -10 ❌
      // 若以补码进行计算,则 4 + (-6) = 00000100 + 111111010 = 11111110
      // 补码运算结果进行取反后加 1 得到 10000010,即 -2✅
      
      • 计算机中以二进制形式表示的数叫机器数,机器数通常带有符号,有正负数之分。计算机用最高位存放符号,这个bit一般叫做符号位
    • 整型提升:面向表达式中的 charshort 类型

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

      • 表达式的整型运算在 CPU 的相应运算器件中执行, CPU 内整型运算器 ALU 的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型数相加,在 CPU 执行时也要先转换为 CPU 内整型操作数的标准长度

      • 通用 CPU(general-purpose CPU) 难以实现两个 8 比特直接相加运算,因此表达式中各种长度可能小于 int 长度的整型值,都必须先转化为 int 或 unsigned int,然后再交给 CPU 去执行运算

      • 有符号整数提升是按照变量数据类型的符号位来提升的;无符号整数提升,高位补0。C语言中 int 类型默认为 signed intchar 类型是否为 signed char 取决于编译器,Visual Studio和Dev_C++中均属于 signed char

      image

      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          char a = 20;
          // 00000000 00000000 00000000 00010100
          // 00010100 --> a
      
          char b = 130;
          // 00000000 00000000 00000000 10000010
          // 10000010 --> b
      
          char c = a + b;
          // 00000000 00000000 00000000 00010100 --> 变量 a 的整型提升
          // 11111111      11111111      11111111     10000010 --> 变量 b 的整型提升
          // 11111111      11111111      11111111     10010110 --> 整型提升后的 a、b求和
          // 10010110 --> 截断 只留 8bit 赋值给变量 c
      
          printf("c=%d\n", c);
          // %d 打印有符号的整数
          // 11111111  11111111  11111111  10010110 --> 变量 b 的整型提升,值为补码
          // 10000000  00000000  00000000  01101010 --> 源码,值为 -106 
          return 0;
      }
      
    • 算术转换:面向类型大于等于整型的数据类型

      • 如果操作符的各操作数属于不同类型,除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行
      long double
      double
      float
      unsigned long int
      long int
      unsigned int
      int
      // 若某操作数的类型在上述列表中排名靠后,那么首先要转换为另一个操作数的类型后执行运算
      
  • 2.3 浮点型常量

    • 普通表示法/小数形式:1234.56、-1.2、0.0、.5(0.5)、6.(6.0)、1000000000.0

    • 科学计数法/指数形式

      • 由十进制数加阶码标志字母 e 或 E 和阶码组成

      • 字母 e 或 E 之前必须有数,正数、负数、整数、小数均可,若是小数,小数点左边或右边至少有一边要有数字;字母 e 或 E 之后必须是整数

      • 合法案例:1.23456e2、 0.345E-3、-765e12、-.5E-8、6.e-7、1.0e10

      • 非法案例:1.34E1.2、.e2

  • 2.4 字符型常量

    • 定义:用英文状态的单引号( 即撇号'' )括起来的一个字符

    • 特征

      • 字符常量只能用单引号标识,不能用双引号或其他符号标识

      • 字符常量只能是单个字符,不能是多个字符

      • 字符可以是字符集(一个允许使用的字符的集合)中的任意字符

      • 字符数据存放在内存时,并不是字符本身,而是字符的代码,称之为ASCII码,标准ASCII有 128 个字符

    • 控制字符:如换行、文件结束标志、字符串结束标志等,码值为0 ~ 31

    • 空格符:可以显示的字符中码值最小的字符,为 32

    • 数字字符:如'0' '1' '2' ... '9',对应的码值是连续的

      • 将数字字符转为对应数字的方法:字符值 - '0',如'5' - '0' = 5

      • 将数字转为对应数字字符的方法:数字 + '0',如5 + '0' = '5'

      字符 ASCII码 字符 ASCII码
      '0' 48 '1' 49
      '2' 50 '9' 57
    • 英文字符:如'a' 'b' 'c' ... 'z' 'A' 'B' 'C' ... 'Z'。由于 'Z' 和 'a' 之间还有 6 个其他字符,因此小写字母的 ASCII码 比大写字母的 ASCII码 大32,且码值连续

      字符 ASCII码 字符 ASCII码
      'a' 97 'A' 65
      'z' 122 'Z' 90
    • 转义字符:由一个反斜杠/撬棍(\)后跟规定字符构成具有特定含义的字符,表示一些特殊的、不可视或无法通过键盘输入的字符,如'\n' '\t' '\b' '\r' '\\' '\'' '\"'

      • 转义字符在程序编译期间作为一个字符处理,虽然看起来是两个字符

      • 可以使用\加上一个 1~3 位的八进制数\ddd\x(不是'\0x')加上一个 1~2 位的十六进制数\xdd表示的ASCII码值来表示转义字符。如'\101',八进制的 101 转为十进制是 65,所以它表示的字符为'A'

      • 关于'\b'(DevC++环境)

        • C语言中'\b'不能消除'\n''\b'是退格符,它的作用是光标回退到上一个位置,而不是删除字符或换行(作用不完全等同于退格键 BackSpace)

        • '\b'的作用是使光标左移一位,如果后面紧跟其他字符,则会覆盖前面的字符

        printf("12345\b");  // 12345
        printf("12345\ba");  // 1234a
        // '\b'只是用来移动光标位置,而不是删除字符。
        
      • 关于'\r'(DevC++环境)

        • 回车(不换行),用'\r'后边的字符替代本行开头相同数目的字符
        printf("aabbcc\rddee");  // ddeecc
        
        • 利用转义字符 '\r' 动态打印进度条【生产环境应用】
        #include <stdio.h>
        #include <Windows.h>  // Sleep睡眠函数的头文件
        
        int main() {
            int i = 0;
            while (i <= 100) {
                printf("%d%%\r", i);
                i++;
                Sleep(1000);  // 程序睡眠1000毫秒
             }
             return 0;
        }
        
        
        #include <stdio.h>
        #include <windows.h>
        
        int main(){
            int i = 0;
            for(i = 1; i <= 100; i++) {
                printf("%d%%\r", i);
                Sleep(200);  // 程序睡眠200毫秒
            }
            return 0;
        } 
        
        • 在读取大文件时,可结合 '\r' 打印文件的读取进度(百分比)【生产环境应用】
        import os
        import time
        
        # 1.读取文件的大小(字节)
        file_size = os.stat('./file/zhangsan.jpg').st_size
        
        # 2.逐渐地读取文件
        read_size = 0
        with open('./file/zhangsan.jpg', mode='rb') as f1:
            while read_size < file_size:
                chunk = f1.read(1000)  # 读取固定大小的内容,每次最多读取 1000B
                read_size += len(chunk)
                val =  int(read_size / file_size * 100)
                print("%s%%\r" %val, end="")
                time.sleep(0.01)  # 睡眠0.01秒
        
    • 其他字符 '+' '*' '/' '&'

  • 2.5 字符串型常量

    • 定义:简称字符串,是用一对双撇号(即双引号"")标识的一串字符

    • 长度:字符串中字符的个数

    • 特征

      • 必须由双引号标识,且包含 0 个或多个字符,如"a" 是字符串常量,'a' 是字符常量

      • 可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量

      • C 语言中没有专门的字符串变量,可以用一个字符数组存放字符串

      • 字符串常量所占内存的字节数等于其字符的个数加 1,因为计算机自动在字符串的结尾加上字符'\0'标识字符串的结束。字符个数为 0 的空串 "" 也存储了一个字符'\0'

3.变量

  • 3.1 特征

    • 程序运行过程中,其值可以改变的量。变量具有保值的性质,当给变量赋予新值时,新值会取代旧值

    • 需要在内存中占用一定的存储单元以存放变量的值

    • 变量需要有一个名字作为标识,即变量名,它要符合标识符的命名规则

  • 3.2 标识符

    • 用于标识变量、符号常量、数组、函数等名字的有效字符序列,只能由字母、数字和下划线组成,且数字不能开头

    • 标识符中的字母严格区分大小写,如sum、SUM是完全不同的标识符

    • 为方便读程序,标识符命名要见名知意,不建议使用拼音及其缩写。如姓名声明为name变量,成绩声明为score变量,不推荐用a、b、c、xm(xingming)、nl(nianling)

    • 合法标识符:__ aa INT abc word_0c12 非法标识符: a b(整体) 1a #abc xyz-1

  • 3.3 关键字

    • C 语言内部预先定义的标识符,关键字不能作为用户标识符

    • ANSI 标准规定了 32 个标识符为基本关键字,具体见第 1 章 2.C语言的特点

  • 3.4 变量的定义与初始化

    • C 程序中,使用变量必须先进行变量说明,指明其类型和名称,一般形式为类型标识符 变量名; / 数据类型 变量名1, 变量名2, ..., 变量名n;,如int a, b, c; float x; double c; char ch;,这些语句指定了变量名和数据类型

      • 允许在一个类型说明符后定义多个类型相同的变量,类型说明符和变量名之间至少用一个空格间隔,各变量间用逗号间隔

      • 最后一个变量名后用分号结尾

      • 变量定义必须放在变量使用的位置之前

      • 若未给变量赋值,则变量中存储随机值,直接使用可能会产生异常结果或运算错误

    • 变量名仅仅是给程序员看的,编译器不看名字,它通过变量的地址找内存单元。声明变量的本质即在内存中申请空间用于存放变量的值,编译系统依据变量类型在内存中分配合适大小的存储空间

    • 变量名命名规则即只能由字母、数字和下划线组成,且第一个字符必须为字母或下划线,不允许使用数字

    • 给变量赋初值的过程称为变量的初始化。程序运行过程中,从变量取值,实际上是通过变量名找到相应内存地址,从存储单元读取数据。对变量赋值,也是通过变量名找到相应内存地址,然后将数据写入存储单元

    // 1. 定义整型变量和字符型变量
    int i = 0, j = 0, k = 0;
    char c1 = 0, c2 = 0;
    int k = 50;  // 初始化,变量定义的同时赋初值,推荐使用该方式⭐️⭐️⭐️⭐️⭐️
    int k;
    k = 50;  // 赋值,先定义变量,然后赋初值。不推荐使用该方式❌❌❌❌❌
    
    // 2. 定义浮点型变量
    float x1 = 0.0, x2 = 0.0;
    double z1 = 0.0, z2 = 0.0;
    float x = 0.0;
    double y = 0.0;
    x = 123456.789;  // float x = 123456.789; 
    y = 1.23456789E5;
    // C语言中float的有效位数是7,即精确到有效数字的第7位;double的有效位数是17
    
    // 3.定义变量时的注意事项
    double y = k = 12.3;  // 错误,该语句并未完成变量k的声明,自然无法对其赋值
    double y = 0, k = 0;
    y = k = 12.3;  // 正确,先声明变量,再进行赋值
    double y = 12.3, k = 12.3;  // 正确,声明单个变量的同时接着赋值
    
    // 定义好变量后建议接着赋值,如 0,防止变量没有初值而编译不通过
    //  float f = 0.0; 该语句存在强制类型转换,0.0 是 double 类型,正确书写方式为 float f = 0.0f;
    

4.赋值语句

  • 4.1 赋值语句的格式与功能

    • 赋值即将数据存入若干个连续的存储单元中,该存储空间有一个名字,即变量名。由于未赋值的变量其值是不确定的,因此操作变量之前要进行赋值,建议定义变量的同时直接进行初始化

    • 一般形式:变量 = 表达式;,如x = 5; y = x; x = a + b;

    • 执行逻辑:首先计算赋值号=右边表达式的值,然后将结果值送给赋值号=左边的变量

    • 注意事项

      • 1.赋值运算符左边必须是一个变量名(不能是常量或表达式),右边允许是常量、变量和表达式(有值即可)
      // 非法赋值语句
      'A' = a + 1;
      3 = x / 2;
      a + b = 12;
      
      • 2.赋值运算符不同于数学中的等号,等号不具有方向性,而赋值运算符具有方向性

        • a = b 和 b = a在数学上的意义等价,作为程序表达式将产生不同的操作效果
        • 执行a = b;操作后,变量a的值被改写成了变量b的值,原来a中的值被覆盖,变量b的值不变
      • 3.赋值语句最后方会有;,而赋值表达式没有

      • 4.C 语言规定,将赋值表达式中左边变量得到的值作为赋值表达式的值,如表达式a = 5的值等于5

      • 5.赋值运算符具有右结合性,如 a = b = 5;a = (b = 5);等价

      • 6.在赋值运算符之前加上其他运算符,可以构成复合赋值运算符,如+= -= *= /= %=,其优先级和赋值运算符一致,也具有右结合性

      c += 32  等价于 c = c + 32
      i *= a + b  等价于 i *= (a + b)  i = i * (a + b),必须加上括号
      
    • 案例分析

    // 1.简单赋值语句
    g = 9.8;
    g = 10 + 20 - 9;
    
    // 2.定义两个字符型常量
    char  c1 = 0, c2 = 0;
    c1 = 'a';
    c2 = 'b';
    // 上面的程序语句定义了2个字符型变量 c1、c2,一个字符型变量可存放一个字符
    // 将一个字符常量存放到一个字符变量中,并不是把该字符本身放到内存中,而是将该字符的相应的ASCII码存放内存
    // 单元中,所以C语言字符型数据与整型数据之间可以通用
    
    // 3.给变量赋值
    # include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        char c1 = 'a', c2 = 0;
        int k = 0;
        k = c1;    // 将字符型变量 c1 赋值给了整型变量 k
        c2 = 97;    // 将整型字面值 97 赋值给了字符型变量 c2
    
        // 以下语句要用到第4节(数据的输出)知识
        printf("%c,%c,%d\n", c1, c2, c1);
        printf("%d,%c\n", k, k);
    
        return 0;
    }
    
    // 4.将小写字母转换成大写字母
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
      char c1 = 0, c2 = 0;
      c1 = 'a';
      c2 = c1 - 32;
    
      // 以下语句要用到第4节(数据的输出)知识
      printf("%c,%c", c1, c2);
    
      return 0;
    }
    
  • 4.2 赋值语句中的类型转换

    • 当赋值运算符=两侧的数据类型不一致时,系统将自动进行类型转换,将=右侧表达式值转换成与左侧变量相同的类型

    • 将实型数据赋给整型变量时,舍去实型数据的小数部分

    • 将整型数据赋给实型变量时,其值不变,以浮点数形式存放,即增加小数部分(小数部分值为 0)

    • 当左侧变量的数据类型比右侧表达式值的类型长时,只改变值的表示形式,一般不改变准确度,如将字符型数据1B赋值给整形变量4B时,字符的 ASCII 值放在整型变量的低8bit中,高位适情况补0 或 1(类似将铅笔放入尺寸不低于它的文具盒中,文具盒完全可容纳铅笔)

    • 当右侧表达式值的类型比左侧变量的类型要长时,转换时会截取右侧数据,仅取出与左边变量类型相同的长度(比特位),这意味着会丢失高位数据,可能导致精度降低或出现错误,将整型数据4B赋给字符变量1B时,只把其低8bit赋给字符变量(类似将笔记本放入尺寸小于它的抽屉,强行放入的话笔记本会被损坏)

    #include <stdio.h>
    int main(int argc, const char * argv[]) {
        double x = 0;
        int a = 350;  // 二进制为 1 0101 1110
        char c;
    
        // 将 4B 长的整型变量 a 赋值给只有 1B 长的字符型变量 c,变量a的低位 8bit 被存储
        c = a;
        x = a;
    
        // 以下语句要用到第4节(数据的输出)知识
        printf("a=%d, c=%d, x=%f\n", a, c, x);
    
        return 0;
    }
    
    // 打印结果:a=350, c=94, x=350.000000
    

5.数据的输出

  • 5.1 字符输出函数

    • 主要功能:把一字节的代码值所代表的字符输出到显示器上

    • 一般形式:putchar(c);

    • 注意事项

      • 1.参数c可以是字符常量、变量或字符表达式

      • 2.若参数c为十进制整型数,则将该值作为ASCII码值,输出其对应的字符到显示器上

      • 3.函数原型为int putchar(int character);

      image

    • 案例分析

    // 1.输出单个字符
    putchar(65);  // A
    putchar('0' + '1');  // a
    putchar('\101');  // A
    putchar('\\');  // \
    putchar('\'');  // '
    putchar('\"');  // "
    
    // 2.输出多个字符
    #include <stdio.h>
    int main() {
        char a = 'B', b = 'o', c = 'k';
        putchar(a);
        putchar(b);
        putchar(b);
        putchar(c);
        putchar('\t');
    
        putchar(a);
        putchar(b);
        putchar('\n');
        putchar(b);
        putchar(c);
    
        return 0;
    }
    
  • 5.2 格式化输出函数

    • 主要功能:按格式控制串中的格式依次输出“输出项表”中的输出项

    • 一般形式:printf("格式控制字符串", 输出项表); 或 printf("输出格式串", 表达式表);

      • 格式控制字符串用来说明“输出项表”中各输出项的输出格式

      • 输出项表列出要输出的项目(常量、变量或表达式),输出项间用逗号分隔

      • 若无输出项表,且格式控制字符串中不含格式信息,即printf("字符串")的形式,则直接输出格式控制字符串本身

    • 格式控制字符串

      • 非格式字符:即普通字符,一律按原样输出

      • 格式字符:即%[附加格式说明符]格式字符,与非格式符的区别在于带百分号%,如%d、%10.2f。在输出时,对不同类型的数据要使用不同的格式符

      格式符 功能说明
      %c 按字符形式输出一个字符
      %d 按十进制整型数的实际长度输出
      %md 右对齐,m 为指定输出字段的宽度。若数据位数少于 m,则在数据左端补以空格后输出;若数据位数大于 m,则按实际位数输出
      %-md 左对齐,m 为指定输出字段的宽度。若数据位数少于 m,则在数据右端补以空格后输出;若数据位数大于 m,则按实际位数输出
      %ld 输出长整型数据
      %o 以八进制形式输出整数
      %x 以十六进制形式输出整数,a ~ f 为小写形式,若为 %X 则以大写形式输出
      %f 整数部分全部输出,并输出 6 位小数
      %m.nf 右对齐,指定输出的数据共占 m 列,其中有 n 位小数。若数值长度小于 m,则左补空格
      %-m.nf 左对齐,指定输出的数据共占 m 列,其中有 n 位小数。若数值长度小于 m,则右补空格
      %lf 用于输出 double 类型数据
      %s 输出一个字符串
      %ms 右对齐,输出的字符串占 m 列。若字符串本身长度大于 m,则按实际输出,否则左补空格
      %-ms 左对齐,输出的字符串占 m 列。若字符串本身长度大于 m,则按实际输出,否则右补空格
      %zd 输出 size_t 类型的值,如 sizeof(int) 的返回值
      %p 输出地址,如指针变量的值
      int a = 123, b = 12345;
      printf("%4d, %4d\n", a, b);    // _123, 12345  _表示一个空格
      printf("%-4d, %-4d\n", a, b);    // 123_, 12345  _表示一个空格
      
      long a = 123456;
      printf("%8ld\n", a);    // __123456 __表示两个空格
      
      int a = 10;
      printf("%o\n", a);    // 12
      int b = 20;
      printf("%x\n", b);    // 14
      
      int a = 4;
      printf("%zd %zd\n", sizeof(int), sizeof(a));    // 4 4 int型数据占用 4B
      //    printf("%zd %zd\n", sizeof int, sizeof a);    // 当 sizeof 后方是变量时,加不加 () 都行,但后方是类型时必须加 ()
      
      int arr[] = {0, 5, 67, 12, 4, 5, 77};
      printf("%zd\n", sizeof(arr)/sizeof(int));    // 计算数组的长度,很常用
      
      int c = 20;
      printf("%p\n", &c);    // 0x16fdff278  变量 c 的地址
      
    • 注意事项

      • 1.输出格式串中格式符个数与表达式个数应保持一致

      • 2.输出格式符以%开头,若想打印出%,可通过%%打印

      • 3.%f中间的数若是负数,则表示输出时左对齐(输出串右边加空格),否则输出时右对齐(输出串左边加空格),%d类似

      • 4.若以%10f打印float型变量,则整数部分、小数点和小数部分总共 10 位

      • 5.printf()函数有一个正整数的返回值,它表示打印字符串的字符个数

      image
      image

      int ret;
      ret = printf("Hello world!");
      printf("\n%d\n", ret);    // 12
      
      ret = printf("%d %d", 1, 34);
      printf("\n%d\n", ret);    // 4
      
    • 案例分析

    // 1.格式符的作用
    #include  <stdio.h>
    
    int main(int argc, const char * argv[]) {
        float  x1 = 123.5678,  x2 = -4567.789;
        printf("x1 = %f, x2 = %f \n", x1, x2 );
        printf("x1 = %8.2f \t x2 = %-8.2f \n", x1, x2 );
        // %8.2f: 算上小数点共8位 小数点后2位 为右对齐
        // %-8.2f: 算上小数点共8位 小数点后2位 为左对齐
    
        printf("%c%3c \n", '*', '*' );
        printf("%d%3d \n", 11, 22 );
    
        return 0;
    }
    
    // 2.整数输出
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int a = 1234, b = 01234, c = 0x1234;
        printf("a=%d, b=%o, c=%x\n", a, b, c);
        printf("a=0%o, b=%d, c=%d\n", a, b, c); 
    
      return 0;    
    }
    
    // 3.浮点数输出
    #include <stdio.h>
    
    int main(int argc, const char * argv[]){
        float x = 0.0f;
        double y = 0.0;
        x = 1.23456789;      
        y = 1234.56E7;
    
        printf("%f,   %.1f,   %e\n", x, x, x);
        printf("%10.0f, %20e, %20.3e\n", y, y, y);
    
        return 0;
    }
    
    // 4.整形与字符型综合
    #include <stdio.h>
    int main(int argc, const char * argv[]){
        int a = 88, b = 89;
        printf("%d %d\n", a, b);    // 88 89
        printf("%d, %d\n", a, b);    // 88, 89
        printf("%c, %c\n", a, b);    // X, Y
        printf("a=%d, b=%d\n", a, b);    // a=88, b=89
    
        return 0;
    }
    
    // 5.特殊内容输出
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        printf("printf(\"Hello world!\\n\");\n");
        printf("cout<<\"Hello world!\"<<endl;\n");
    
        return 0;
    }
    

    // 6.打印windows下盘符路径:C:\test\basic\test.c
    printf("C:\\test\\basic\\test.c\n");
    

6.数据的输入

  • 6.1 字符输入函数getchar()

    • 主要功能:接收用户从键盘上输入的一个字符,通常把输入的字符赋给一个字符型变量,构成赋值语句

    • 一般形式:char c = getchar();

    • 注意事项

      • 1.执行上述语句后,程序等待用户输入,当用户按下某个键时,该字符首先会存于缓冲区中,按下回车后字符值赋给了变量c

      • 2.运行程序时,用户也可输入整数,输入的数字也按字符处理

      • 3.该函数不接收参数,但有整型返回值,该值就是从键盘上输入的字符

      • 4.该函数只能接收单个字符,输入字符多于一个时,只接收第一个字符

      • 5.函数原型为int getchar(void);

      image

    • 案例分析

    #include <stdio.h>
    int main(int argc, const char * argv[]) {
      char ch = 0; 
      ch = getchar(); 
      printf("ch = %c, ch = %d \n", ch, ch);
    
      return 0;
    }
    
  • 6.2 格式化输入函数scanf()

    • 主要功能:按指定的格式从键盘接收输入信息

    • 一般形式:scanf("格式控制字符串", 地址列表); scanf("格式串",地址表);

      • 格式控制字符串用于定义用户输入数据的格式

      • 取地址运算符为&,这个地址就是编译系统在内存中给变量分配的地址,一般情况下用户无需关注具体地址值

    • 格式控制字符串

      • 非格式字符:即普通字符,一律按原样输入

      • 格式符:即%[输入数据宽度]类型,与非格式符的区别在于带百分号%,如%2d、%3c。在输入时,对不同类型的数据要使用不同的格式符

      格式符 功能说明
      %c 从键盘接收(输入)一个字符型数据
      %d 接收一个整型数据,直至遇到回车
      %md 指定输入数据所占列数,系统自动按它截取所需数据,m 应为正整数
      %*md % 后的 * 附加格式说明符,表示跳过指定的列数
      %f 接收一个单精度浮点型数据(float)
      %lf 接收一个双精度浮点型数据(double)
      %s 接收一个字符串
      // 输入 123456,系统自动将 123 赋给变量 a,456赋给变量 b
      int a, b;
      scanf("%3d%3d", &a, &b);
      
      // 输入 12  345  67,系统将 12 赋给变量 a,%*3d 表示读入 3 位整数但不赋给任何变量,再读入 2 位整数 67 赋给变量 b
      int a, b;
      scanf("%2d  %*3d  %2d", &a, &b);
      
      // 输入数据时不能规定数据的精度,以下语句不合法
      scanf("%7.2f", &a);
      
    • 注意事项

      • 1.格式控制字符串中格式符个数与地址表中地址的个数应一致

      • 2.若格式控制字符串中没有非格式符在%d%d%d之间作输入时的间隔,则在输入时要用一个以上的空格、回车符或制表符作为每两个输入数之间的间隔,不能使用,等其他字符;若格式控制字符串中有非格式符,则输入时也要输入该非格式符,如scanf("%d,%d",&x, &y); 只能以,作为输入数据的分隔符

      • 3.输入字符型数据时,若格式控制字符串中无非格式字符,则认为所有输入的字符均为有效字符,如scanf(%c%c%c, &a, &b, &c);,输入d e f,则变量b接收的是空格字符

      • 4.若在格式控制字符串中加入空格作为间隔,如scanf("%c %c %c", &a, &b, &c);,则输入时各数据之间可加0个或多个空格

      • 5.若输入的数据与输出的类型不一致,虽然编译能够通过,但结果将不正确

      • 6.scanf()函数有一个正整数的返回值,它表示成功接收的参数个数

      image

      image

      image

    • 地址概念分析

      • 无论是在内存还是在硬盘中,存取数据都需要找到对应地址,它是一个十六进制的串
      // 以下代码打印了变量 k 的值,随后打印了变量 k 的地址。内存中首地址为 000000000062FE1C 的地址块存储了变量 k 的值
      #include <stdio.h>
      
      int main(int argc, const char * argv[]){
          int k = 5;
          printf("%d\n", k);   // 5
          printf("%p\n", &k);   // 000000000062FE1C
      
          return 0;
       }
      
      • 变量名是给用户操作使用的,代码转为汇编程序后不存在变量名,转而用一个十六进制数的内存地址代替

      image

    • 编程步骤分析:当我们拿到一个题目,如何编写程序完成呢?大概分为4个步骤

      • 1.需求分析,定义变量。分析完成题目需要用到的已知量和未知量,这些量均需要定义为变量,程序运行过程中用它们存储数据。比如要计算一个数的平方根,已知量是这个数,未知量是这个数的平方根,因此可定义2个变量:分别用于存储原数和求得的平方根

      • 2.数据输入。通常会用到scanf()函数。题目一般不会直接给定确认的数字,而是要求输入一个或多个的数据,根据scanf()函数的语法编写语句即可

      • 3.数据处理。根据步骤 2 输入的数据建立数学模型,用程序代码、语句实现需求。如计算圆的面积,用S=πR^2;计算实数的绝对值使用fabs()函数等等。求得的值通常需要变量来接收

      • 4.数据输出。写完代码后要验证所求值是否正确,所以要用到printf()函数打印输出,根据printf()的语法编写语句即可

    • 案例分析

    // 1.计算圆的面积和周长
    #include  <stdio.h>
    #define  PI  3.1415926
    
    int main(int argc, const char * argv[]){	
      float d = 0.0f, area = 0.0f, len = 0.0f, r = 0.0f;
      printf("请输入直径:");
      scanf("%f", &d);
    
      r = d / 2;
      area = PI * r * r;
      len =2 * PI * r;
      printf("半径r=%.2f, 面积S=%.2f, 周长L=%.2f\n", r, area, len );
    
      return 0;
    }
    
    // 2.逻辑错误代码
    #include <stdio.h>
    int main(int argc, const char * argv[]) {
      double a = 0.0, b = 0.0, c = 0.0;
      printf("input data:\n");
      scanf("%f,%f", &a, &b);
    
      c = a + b;
      printf("sum=%f\n", c);
    
      return 0;
    }
    
    // 3.编程实现输入圆的半径 r 以及圆柱的高 h,求圆球表面积、圆球体积和圆柱体体积并输出。输出时要有文字说明,结果取小数点后 2 位数字
    #include <stdio.h>
    #define PI 3.1415
    
    int main(int argc, const char * argv[]) {
        float r, h;
        printf("请输入圆半径和圆柱的高,如:1.5,3\n");
        scanf("%f,%f", &r, &h);
        printf("圆球表面积=%.2f\n", 4 * PI * r * r);
        printf("圆球体积=%.2f\n", (4.0 / 3) * PI * r * r * r);
        printf("圆柱体积=%.2f\n", PI * r * r * h);
    
        return 0;
    }
    
    // 4.从键盘键入一个小写字母,把小写字母转换成大写字母后输出
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        char c1, c2;
        printf("输入一个小写字母: ");
        c1 = getchar();
        c2 = c1 - 32;
        putchar(c2);
    
        return 0;
    }  
    

7.运算符和表达式

  • 7.1 算术运算符

    • 主要包括加、减、乘、除、求模/模除/取余等5种运算符

      • 1.两个整数相除的结果为整数,舍去小数部分,与数学中的除法运算有区别。若运算对象中有一个是实型数,则结果为实型

      • 2.模除运算即取两整数相除后的余数,运算结果的正负与左边操作数(即被除数)的正负一致。%两边必须是整型数据,实型数据不能进行模除运算

        • a % b == 0,说明a 能被 b 整除,否则不能整除
        • 除号/与模除号%在数字各位分解时应用广泛。假设a 为整型三位数,若a % 10 为 1,说明a 的个位数为 1;若a / 10 % 10 为 2,说明a 的十位数为 2;若a / 100 为 3,说明a 的百位数为 3
      • 3.每个运算符都有优先级,混合运算中要关注优先级,括号可改变运算次序

        • 按运算符的优先级高低次序进行。如先乘除,后加减

        • 若一个运算符对象两侧的运算符的优先级相同,则按规定的“结合方向”(左结合或右结合)处理

      运算符 运算 优先级 例子
      () 圆括号 1 5/(1-3)=-2
      [] 下标引用操作符 1 a[10]
      + 加号 4 7+3=10
      - 减号,双目运算符 4 7-3=4
      - 负号,单目运算符,取相反数 2 -4
      * 乘号 3 7*3=21
      / 除号 3 7/3.0=2.333333,7/3=2
      % 模除号 3 7%3=1,5%(1-3)=1
    • 用算术运算符和一对圆括号将操作数(常数、变量、函数等)连接起来,且符合 C 语言语法的表达式称为算术表达式

      • 1.C 语言的算术表达式中不能使用[] 方括号 {} 花括号,只能使用() 圆括号

      • 2.圆括号的优先级为 1,在所有运算符中优先级最高,多层圆括号出现时从里向外处理

      • 3.C 程序中的算术表达式(乘号*不能省略)与数学表达式(可省略乘号x)容易混淆,注意其书写方式

      image

    • 案例分析

    #include <stdio.h>
    int main(int argc, const char * argv[]) {
        printf("%d\n", 5/(1-3));  // -2
        printf("%d,%f\n",7/3, 7.0/3);  // 2,2.333333
        printf("%d\n", 5%(1-3));  // 1
    
        return 0;
    }
    
  • 7.2 自增、自减运算符

    • ++为自增运算符、--为自减运算符,优先级为 2

    • 一般格式:相对于变量本身来说,都表示自增 1 或自减 1

      • k ++; 等价于k = k + 1;
      • k --; 等价于k = k - 1;
    • 特殊形式:相对于表达式来说,运算次序不同

      • i = k++; 等价于 i = k; k = k + 1;即先将k最初的值赋值给i,再执行k 的自增
      • j = ++k; 等价于 k = k + 1; j = k;即先将k自增,新值赋给j
    • 注意事项

      • 1.自增、自减运算符多用于int型char型float、double也可以用但用的不多

      • 2.自增、自减运算符为右结合性,若表达式-a++相当于-(a++)而不是(-a)++

      • 3.避免写 a = b+++c; 类似的语句,容易产生歧义

      • 4.自增、自减运算符的运算对象必须是变量,不能为表达式或常量(如++5非法),因为常量的值无法改变

    • 案例分析

    #include  <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int  i = 0, j = 0, k = 0;
        k = 30;
        i = k++;  // 先将k的初值30赋给i, 再将k增1, 此时k = 31, i = 30   
        printf("i = %d, k = %d", i, k );
        j = ++k;  // k的值先增1, 再将k值赋给j, 此时k =32, j = 32      
        printf("\n j = %d, k = %d", j, k );
    }
    
  • 7.3 赋值运算符

    • =,右结合性,优先级为14。执行逻辑等知识可参考前面第 4 部分“赋值语句”
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {	
      double  k = 0, x = 0, y = 0;
    
      // 先将 9.8 赋给k,表达式(k = 9.8)的值为 9.8,再加 7 得 16.8
      x = (k = 9.8) +7;
      printf("k=%5.1f, x=%5.1f\n", k, x);
    
      // 运算符 + 的优先级高于 =,且 = 是右结合,先算 9.8 + 7,将结果 16.8 赋给 k,再将 k 的值 16.8 赋给 y
      y = k = 9.8 + 7;
      printf("k=%5.1f, y=%5.1f\n", k, y);
    }
    
    // 打印结果为:
    // k = 9.8, x = 16.8
    // k = 16.8, y = 16.8
    
  • 7.4 逗号运算符

    • 又称顺序求值运算符,用于将两个表达式用,连接起来,优先级为 15,在所有运算符中优先级最低

    • 一般形式:表达式1, 表达式2, ..., 表达式n

    • 执行逻辑:先求解表达式 1,再求解表达式 2,依次求解直到表达式 n。整个逗号表达式的值为表达式 n 的值

    • 案例分析

      • 1.简单的逗号表达式运算
      // 1.表达式 n 的值不受前方表达式的影响
      int i, j, k, x;
      x = (i=1, j=2, k=3)
       
      
      // 2.表达式 n 的值受前方表达式的影响
      int a, b;
      a=3, b=5, --a
      
      int a = 1, b = 2;
      // 小括号优先级更高,先算小括号中逗号表达式的值
      int c = (a > b, a = b + 10, a, b = a + 1);
      
      // 3.printf()结合逗号表达式
      int a = 4, b = 7;
      printf("%d", (a++, --b));  // 此处只有一个输出格式符,后方也应当只有 1 个值,需要把逗号表达式放括号中
      
      // 4.优先级确定,易错点
      int i  =5, j = 10, a;
      // 由于赋值号优先级更高,优先算 a = i++,其结果与 --j 构成逗号表达式,与 a = (i++, --j); 不同
      a = i++, --j;
      
      • 2.逗号表达式简化代码逻辑事例
      a = get_vla();
      count_val(a);
      while (a > 0) {
          // 业务处理
          // ......
          a = get_val();
          count_val(a);
      }
      
      // 如果使用逗号表达式,改写
      while (a = get_vale(), count_val(a), a > 0) {
          // 业务逻辑
          // ......
      }
      
  • 7.5 位运算

    • 对操作数以二进制位 bit 为单位进行的数据处理,运算的对象是一个二进制数位的集合

    • 位逻辑运算可方便地设置或屏蔽内存中某个字节或几位,或者将两个数按位相加

      • 按位与运算符&
        • 将参与运算的两数对应的各二进制位相与,只有对应两个二进制位均为 1 时结果为 1,否则为 0

        • 特殊用途:将数据的某些位清零;判断数据的某位是否为1;保留数据的某些位

        // 任意数 & 0 = 0  任意数 & 1 = 任意数本身
        a = a & 0;    // a 的值变为 0
        a & 0x8000;   // 若结果非 0,表示 a 的最高位为 1
        a = a & 0xff00    // 保留 a 中高 8bit 数据不变,低 8bit 数据被清空
        
        int a = 6, b = 10;
        printf("%d", a & b);
        
      • 按位或运算符|
        • 将参与运算的两数对应的各二进制位相或,只有对应两个二进制位均为 0 时结果为 0,否则为 1

        • 特殊用途:将数据的某些特定位置为 1

        // 任意数 | 0 = 任意数本身  任意数 | 1 = 1
        a = a | 0x00ff    // 保留 a 中高 8bit 数据不变,低 8bit 数据置为 1
        
        int a = 6, b = 10;
        printf("%d", a | b);
        
      • 按位异或运算符^
        • 将参与运算的两数对应的各二进制位相异或,对应两个二进制位相异时结果为 1,相同时结果为 0

        • 特殊用途:按位异或运算具有“与 1 异或的位其值翻转,与 0 异或的位其值不变”,常用语保留数据的原值或翻转其值

        // 按位异或运算可实现变量值互换
        a = a ^ b;
        b = b ^ a;
        a = a ^ b;
        
        int a = 6, b = 10;
        printf("%d", a ^ b);
        
      • 按位取反运算符~
        • 对参与运算的数的各二进制位按位求反,即 1 变 0,0 变 1
        int a = 6, b = 10;
        printf("%d %d", ~a, ~b);
        
    • 移位运算用于将内存中某个二进制数左移或右移若干位

      • 左移运算符<<
        • <<左侧数据中的各二进制位全部左移,左移的位数由<<右侧数据指定。移位后右边出现的空位补 0,舍弃移出存储空间的高位数据

        • 左移 1 位相当于该数乘以 2左移 n 位相当于该数乘以 2^n(适用于移出的高位不含 1 的情形,即未发生溢出)

        char a = 5;
        printf("%d", a << 2);
        
      • 右移运算符>>
        • >>左侧数据中的各二进制位全部右移,右移的位数由>>右侧数据指定。移位后舍弃移出存储空间的低位数据,若对无符号数右移,空出的高位补 0,即逻辑右移;若对有符号数右移,空出的高位全部以符号位填补,即正数补 0,负数补 1,即算术右移
        • 右移 1 位相当于该数除以 2右移 n 位相当于该数除以 2^n(适用于移出的低位不含 1 的情形)
        char a = 0x80;  
        printf("%d", a >> 1);
        
        char b = 0x60;
        printf("%d", a >> 1);
        
    • 注意事项

      • 1.位运算符的的运算对象只能是intchar型数据,不能是float double型数据,而逻辑运算符&&、||、!的运算对象为任意进制数据

      • 2.运算对象一律按二进制补码形式参与运算,并且是按位进行运算

      • 3.位运算的结果是一个int型数据

      • 4.除按位取反是单目运算符外,其余均为双目运算符

      image

      运算符 含义 优先级 格式 备注
      ~ 按位取反 2 ~ a ~0 =1, ~1 = 0
      << 按位左移 5 a << 2 左边丢弃,右边补 0
      >> 按位右移 5 b >> 2 区分算术右移和逻辑右移
      & 按位与 8 a & b 1 & 1 = 1,1 & 0 = 0,0 & 1 = 0,0 & 0 = 0
      ^ 按位异或 9 a ^ b 1 ^ 0 = 1,0 ^ 1 = 1,0 ^ 0 = 0,1 ^ 1 = 0
      | 按位或 10 a | b 1 | 1 = 1,1 | 0 = 1,0 | 1 = 1,0 | 0 = 0
    • 案例分析

      • 1.左移操作符性质与应用
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = 10;
          int n = num << 1;
          printf("n=%d\n", n);
          printf("num=%d\n", num);
      
          return 0;
      }
      

      image

      image

      • 2.右移操作符性质与应用
      // 右移运算的移位规则有两种:
      // 1. 逻辑右移:左边用 0 填充,右边丢弃
      // 2. 算术右移:左边用原该值的符号位填充,右边丢弃
      // 最终使用逻辑右移还是算术右移,取决于编译器,一般情况都为算术右移
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = 10;
          int n = num >> 1;
          printf("n= %d\n", n);    // n = 5
          printf("num= %d\n", num);  // num = 10
      
          return 0;
      }
      

      image

      image

      • 3.不创建临时变量,实现两个整数的交换
      // 方法1:利用加和,缺点是当值很大的时候容易溢出
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int a = 3, b = 5;
          a = a + b;
          b = a - b;
          a = a - b;
          printf("a=%d, b=%d\n", a, b);
      
          return 0;
      }
      
      // 方法2:利用异或运算
      // 3^3 = 0 ---> a^a = 0
      // 0^3 = 000^011 = 011 = 3  0^12 = 0000^1100 = 1100 = 12 ---> 0^a = a
      // 3^3^5 = 011^011^101 = 000^101 = 101 = 5  3^5^3 = 011^101^011 = 100^011 = 101 = 5 ---> 异或运算支持交换律
      // a^b^a = b  b^a^b = a
      

      image

      • 4.写代码计算一个整数存储在内存中的二进制中 1 的个数
      // 方法1:假设该数为num,依次进行 num % 2 与 num / 2 的运算
      // 弊端:当输入负数时,无法求得正确结果
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = 0;
          int count = 0;
          scanf("%d", &num);
          
          while (num) {
              if (num % 2 == 1) {
                  count++;
              }
              num /= 2;
          }
          printf("count= %d\n", count);
      
          return 0;
      }
      

      image

      // 方法2:借助 << 和 & 运算。若 num = -1,以 8bit 为例其补码为 1111 1111,共8个1
      // 首先计算 1111 1111 & 0000 0001 = 00000001 = 1 为真,结果为 1 则代表 num 的二进制补码串最后 1 位为 1。此时将 0000 0001 左移 1 位得 0000 0010
      // 然后计算 1111 1111 & 0000 0010 = 0000 0010 = 2 为真,代表 num 的二进制补码串倒数第 2 位为 1。继续将 0000 0010 左移 1 位得 0000 0100
      // 再计算 1111 1111 & 0000 0100 = 0000 0100  = 4 为真,代表 num 的二进制补码串倒数第 3 位为 1。如此循环
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = -1;
          int count = 0;
          int i = 0;
          
          for (i = 0; i < 32; i++) {
              if (num & (1 << i)) {
                  count++;
              }
          }
          printf("count= %d\n", count);
      
          return 0;
      }
      
      // 此方法解决了输入负数时结果错误的弊端,但依然要循环32次,也计算了二进制为 0 的比特位
      

      image

      // 方法3:借助 num 与 num - 1 做 & 运算
      // 假设 num = 11 = 1011,num - 1 = 10 = 1010,1011 & 1010 = 1010 ---> 原 num 的二进制值中最右边的 1 被去掉,这是第 1 次运算
      // 继续置 num = 10 = 1010,num - 1 = 9 = 1001,1010 & 1001 = 1000 ---> 新 num 的二进制值中最右边的 1 (即从右往左数第 2 位的1)被去掉,这是第 2 次运算
      // 再次置 num = 8 = 1000,num - 1 = 7 = 0111,1000 & 0111 = 0000 --->   num 的二进制值中最右边的 1 (即从右往左数第 4 位的1)被去掉,这是第 3 次运算
      // 这种方法能得到正确结果,且可以规避二进制位为 0 时的无效运算
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = -1;
          int count = 0;
          
          while (num) {
              count++;
              num = num & (num - 1);
          }
          printf("二进制数中1的个数为 %d\n", count);
      
          return 0;
      }
      
      //  以8bit为例,-1的源码为 1000 0001,反码为 1111 1110,补码为 1111 1111
      // 1111 1111 & 1111 1110 = 11111110
      // 1111 1110 & 1111 1101 = 1111 1100
      // 1111 1100 & 1111 1010 = 1111 1000
      // ...... 
      // 每运算一次,运算结果相比 1111 1111 就少一个 1 
      

      image

      • 5.写代码判断正整数 n 是否是 2 的整数次幂
      // 方法:与上题中的方法3一致。2 的整数次幂值化为二进制后,其显著特征为只有 1 bit为 1,其余全为0
      // 16 的二进制为 1 0000, 15 的二进制为 0 1111,1 0000 & 0 1111 = 00000
      // -4的二进制为 1100,-5的二进制为 1011, 1100 & 1011 = 1000 = 0
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = 2;
          
          if ((num & (num - 1)) == 0) {
              printf("%d 是2的整数次幂\n", num);
          }
          else{
              printf("%d 不是2的整数次幂\n", num);
          }
      
          return 0;
      }
      
      • 6.二进制位置 0 或者置 1。编写代码将 13 的二进制序列的第 5 位改为1,然后再改回0
      // 13 的二进制序列为: 0000 1101
      // 将第 5 位置为 1后:  0001 1101 <--- 0000 1101 | 0001 0000
      // 将第 5 位再置为0:  0000 1101 <--- 0001 1101 & 1110 1111
      // 由于是修改二进制位的第 5 位,0001 0000 由 1 左移 4(5 - 1) 位得到,再将该数按位取反得到 1110 1111
      
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int num = 13;
          
          num = num | (1 << 4);
          printf("num= %d\n", num);
          
          num = num & ~(1 << 4);
          printf("num= %d\n", num);
      
          return 0;
      }
      

      image

      • 7.获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列。这里的奇数位和偶数位指的是位置,假设二进制序列最右边的为第 1 位(奇数位),向左依次为第 2 位(偶数位)、第 3 位(奇数位)......
      // 方法1:用 num 和 1 做按位与,可以得到 num 最低位的值;然后 num 右移一位,继续和 1 做按位与......
      // 这样循环计算就可以得到 num 每一位的比特数,按位置的奇偶性分别存到两个数组中,再遍历数组即可
      // 以 11 的二进制 0000 1011 为例,奇数位序列从右往左为 1000,类似地偶数位为 1100
      #include <stdio.h>
      
      void print_arr(int a[], int len) {
          int i = 0;
          for (i = 0; i < len; i++) {
              printf("%d ", a[i]);
          }
          printf("\n");
      }
      
      int main(int argc, const char * argv[]) {
          int num = 0;
          int i = 0, j = 0, k = 0;
          int a[32] = {0}, b[32] = {0};
          scanf("%d", &num);
          
          // k 表示位置,从第 1 位开始,最后到第 32 位
          for (k = 1; k <= 32; k++) {
              if (k % 2 == 1) {
                  a[i++] = num & 1;
              }
              else {
                  b[j++] = num & 1;
              }
              num >>= 1;
          }
          printf("奇数位从低到高的比特位分别是:");
          print_arr(a, i);
          printf("偶数位从低到高的比特位分别是:");
          print_arr(b, j);
      
          return 0;
      }
      

      image

      // 方法2:将 num 向右移动 i 位,将移完位之后的结果与1按位与,如果结果是0,则第 i 个比特位是0 ;结果是非 0,则第 i 个比特位是 1
      
      #include <stdio.h>
      
      void print_bit(int n) {
          int i = 0;
          
          // 求偶数位的数字
          for (i = 31; i >= 1; i -= 2) {
              printf("%d ", (n >> i) & 1);
          }
          printf("\n");
      
          // 求奇数位的数字
          for (i = 30; i >= 0; i -= 2) {
              printf("%d ", (n >> i) & 1);
          }
          printf("\n");
      }
      
      int main(int argc, const char * argv[]) {
          int num= 0;
          scanf("%d", &num);
          print_bit(num);
      
          return 0;
      }
      

      image

      • 8.在一个整型数组中,只有一个数字出现一次,其他数字均成对出现,请找出仅出现一次的数字。例如:数组中有:1 2 3 4 5 1 2 3 4,只有 5 出现一次,其他数字都出现 2 次,找出 5
      // 分析:本题使用异或运算求解,解法巧妙
      // 两个相同整数的异或结果为 0,异或满足交换律。因此数组中出现 2 次的数异或结果为 0, 
      // 所有相同数的异或结果再进行异或的结果仍为 0,将 0 与只出现一次的数据 a 异或后结果就是 a
      
      #include <stdio.h>
      
      int find_single_num(int a[], int s) {
          int i = 0, ret = 0;
          
          for (i = 0; i < s; i++) {
              ret ^= a[i];
          }
          return ret;
      }
      
      int main(int argc, const char * argv[]) {
          int arr[] = {1, 2, 3, 4, 5, 1, 2, 3, 4 };
          int sz = sizeof(arr) / sizeof(arr[0]);
          int dog = find_single_num(arr, sz);
          printf("%d\n", dog);
      
          return 0;
      }
      
      
      • 9.根据位运算符的特征计算以下算式的结果
      // 要点:1.将十进制数转为二进制数 2.根据优先级确定运算顺序,根据位运算符的运算规则求得结果 3.将二进制结果转为十进制数
      
      unsigned char a = 2, b = 4, c = 5, d = 16, e = 7, y;
      y = a & b;
      y = a | b;
      y = a ^ c;
      y = a << 2;
      y = d >> 2;
      y = ~ e;
      y = d & e << 2
      
  • 7.6 强制类型转换

    • 主要功能:将表达式的结果类型强制转换成指定类型,运算符为(类型),优先级为 2

    • 一般形式:(类型) 表达式/(类型) (表达式),强制类型转换运算符和表达式都必须加括号(单个变量可以不加括号)

    • 注意事项

      • 1.浮点型强制转换为整型等取整类转换不是按照四舍五入原则处理,而是只取整数部分,忽略小数部分

      • 2.无论是强制类型转换或自动类型转换,都是为了本次运算的需求而对变量的数据长度进行的临时转换,原变量的数据类型和值没有改变

      • 3.当强制类型转换符和其他运算符一起出现时,根据优先级确定运算顺序

    • 案例分析

    // 1.优先级确定运算顺序
    // 下式中强制类型转换的优先级高于除号,所以先将 (float)1 转为 1.0,然后再计算 1.0/2 = 0.5
    (float)1/2
    
    // 2.保留小数点后面的两位有效数字,并四舍五入
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
          float x = 45.2353;
          x = (int)(x * 100 + 0.5) / 100.0;
          printf("x=%.2f\n", x);
    
          return 0;
    }
    // 语句 x = (int)(x * 100 + 0.5) / 100; 中用到了强制类型转换和自动类型转换。其中 (int)(x * 100 + 0.5) 结果为 4524,是整型,在除以 100.0 时,自动转换为实型数 4524.0,所以结果为 45.24
    
  • 7.7 问题表达式解析

    • 表达式a * b + c * d + e * f执行时,*的优先级高于+,只能保证*的计算比+早,但优先级并不能决定第 3 个*比第 1 个+早执行,执行顺序可能为1 3 2 5 41 3 5 2 4
      //1   2   3   4   5  --> 分别对应5个运算符
      a * b + c * d + e * f
      
    • 表达式c + --c执行时,操作符的优先级只能决定--的运算在+前面,但无法得知+操作符的左操作数的获取在右操作数之前还是之后,因此结果不可预测,有歧义

8.常用的数学函数

  • 8.1 数学函数的功能

    • C 语言中调用数学函数需要#include <math.h>,常用函数如下表,其参数类型和返回类型均为double

    • 各函数的官方介绍如下

    函数 功能说明
    sqrt(x) 求参数 x 的算术平方根,x >= 0
    pow(x, y) 幂运算,求 x 的 y 次方
    abs(x) 求参数 x 的绝对值,x 为 int 型数
    fabs(x) 求参数 x 的绝对值,x 为 double 型数
    sin(x) 求参数 x 的正弦值,x 为弧度制,不能是角度制
    cos(x) 求参数 x 的余弦值,x 为弧度制,不能是角度制
    tan(x) 求参数 x 的正切值,x 为弧度制,不能是角度制
    log(x) 求 lnx 的值,自然对数
    log10(x) 求 lgx 的值,常用对数

    image

    image

    image

    image

    image

    image

    image

  • 8.2 案例分析

    • 1.输入一个正数 x,求 x 的平方根
    #include <stdio.h>
    #include <math.h>
    
    int main(int argc, const char * argv[]){	
        float x = 0.0, y = 0.0;
        printf("input x:");
        scanf("%f", &x );
    
        y = sqrt(x);
        printf("sqrt(%-7.2f) = %7.2f\n", x, y);
    
        return 0;
    }
    
    • 2.使用pow()函数求幂函数 a^b
    #include <stdio.h>
    #include <math.h>
    
    int main(int argc, const char * argv[]){ 
        float a = 0.0, b = 0.0;
        scanf("%f %f", &a, &b);
        printf("a = %.1f, b = %.1f, a ^ b = %.1f\n", a, b, pow(a,b));
    
        return 0;
    }
    
    • 3.使用pow()函数求平方根或立方根
    #include <stdio.h>
    #include <math.h>
    
    int main(int argc, const char * argv[]){
        double  k = 0.0, j = 0.0;
        k = 25;
        j = 1.0 / 2;
        printf("%f, %f, %f\n ", pow(k, j), sqrt(k), pow(k, 1 / 3.0));
    
        return 0;
    }
    
    • 4.三角函数的应用
    #include <stdio.h>
    #include <math.h>
    #define  PI  3.1415926
    
    int main(int argc, const char * argv[]){	
        float x = 0.0;   
        printf("输入一个角度数:");
        scanf("%f", &x);
        printf("正弦值为: %7.1f\n", sin(x * PI / 180));
    
        return 0;
    }
    
    • 5.计算一元二次方程ax^2 + bx + c = 0的根
    #include <stdio.h>
    #include <math.h>
    int main(int argc, const char * argv[]){
      float a = 0.0, b = 0.0, c = 0.0, d = 0.0;
      double  x1 = 0.0, x2 = 0.0;
      a = 5;
      b = 17;
      c = 11;
      d = b * b - 4 * a * c;
      x1 = (-b + sqrt(d)) / (2 * a);
      x2 = (-b - sqrt(d)) / (2 * a);
      printf("x1=%f, x2=%f\n", x1, x2);
      return 0;
    }
    

9.顺序结构程序设计实例

  • 9.1 结构化程序

    • 结构化程序设计主要强调的是程序的易读性,其主要观点是一个程序的任何逻辑问题都可由顺序、选择、循环 3 种基本程序结构构造,它们不是孤立的,而是相互并列、嵌套或相互影响的

      • 顺序结构:按照相应的步骤顺序执行,直至程序结束,是最简单的一种程序结构,主要由赋值语句、表达式语句、输入输出语句和函数调用语句组成

      • 选择结构:又称分支结构,根据运行的情况(条件)自动选择要执行的语句组

      • 循环结构:又称重复结构,允许多次重复执行一组语句,直到循环条件不成立为止

    • 设计原则:自顶向下,逐步细化,模块化设计,限制使用goto语句

  • 9.2 案例分析

    • 1.已知三角形的两边a、b及其夹角α,求第三边长c及面积s

    image

    #include <stdio.h>
    #include <math.h>
    #define  PI  3.1415926
    
    int main(int argc, const char * argv[]){	
        float a = 0, b = 0, alfa = 0;
        double af = 0, s = 0, c = 0; 
        printf("input a、b、alfa:\n");
        scanf("%f%f%f", &a, &b, &alfa); 
    
        af = alfa * PI / 180;    // 将角度制转换为弧度制
        c = sqrt(a * a + b * b - 2 * a * b * cos(af));
        s = a * b * sin(af) / 2;
        printf("c=%.1f s=%.1f\n", c, s);
    
        return 0;
    }
    
    • 2.从键盘上输入一个 3 位数,然后将它反向输出。如输入 123,输出 321
    // 方法1:本题的考查核心是取余和除法运算。对于任意一个整数 n,n % 10 可得到 n 的个位数;再将 n 重新赋值为 n / 10,即原来 n 值的十位数变为新的个位数。如此反复,直到 n / 10 的结果为0
    #include <stdio.h>
    
    int main(int argc, const char * argv[]){
        int a, b, c; 
        printf("输入一个3位整数:"); 
        scanf("%d", &a );
    
        b = a %10;
        printf("%d", b ); 
        a = a / 10; 
        b = a %10;
        printf("%d", b ); 
        a = a / 10; 
        printf("%d \n", a ); 
    
        return 0;
    }
    
    // 方法2:直接使用在 7.1 算术运算符章节中介绍的分解三位数的方法
    #include <stdio.h>
    
    int main(int argc, const char * argv[]){
        int a, num1, num2, num3; 
        printf("输入一个3位整数:"); 
        scanf("%d", &a );
    
        num1 = a % 10;
        num2 = a / 10 % 10;
        num3 = a / 100;
        // printf("%d%d%d", num1, num2, num3);
        printf("%d", num1 * 100 + num2 * 10 + num3);
    
        return 0;
    }
    
posted @ 2025-01-23 09:06  pycoder_666  阅读(356)  评论(0)    收藏  举报