第2章 基本数据类型与简单程序设计

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

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

类型标识符 名称 长度 (B) 表示范围或注意事项
char 字符型 1 无符号:0~255 有符号:-128~+127
short 短整型 2 0~65535
int 整型 4 -
long 长整型 4 C语言标准规定,sizeof(long) >= sizeof(int)
float 单精度浮点型 4 -
double 双精度浮点型 8 字面量小数默认类型,如3.4
long long 超长整型 8 C99标准新增类型
  • 常用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
  • 编码类型
    • ASCII码:主要用于表示英文字符,共8bit,即用8位来表示1个字符,总共可以表示2^8=256种英文字符
    #include <stdio.h>
    int main() {
        // 循环打印ASCII码在32~127范围内的字符
        int i = 0;
        for (i = 32; i <= 127; i++) {
        		printf("%c ", i);
        }
        return 0;
    }
    
    • 万国码(Unicode码)

      • 计算机产生初期的应用范围并不广泛,编码方式只有ASCII码,表示英文足够了。例如A的ASCII码为0100 0001,低层硬盘存储的实际上是二进制串

      • 随着计算机发展,各国加入使用行列,ASCII码无法表示所有国家的语言,产生了乱码

      • 万国码用于表示各国的文字字符,共32bit,可表示2^32种字符,也可以表示中文。它拓展了ASCII码的表示范围,但浪费了硬盘存储空间,如A的Unicode码为00000000 00000000 00000000 0100 0011

    • 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 long型十进制整型常量,以L或l结尾
    • 存储方式

      • 源码:最高位表示符号,其余位表示数值的绝对值。以8bit位表示+7的源码为 0000 0111,-7的源码为 10000111
      • 反码:正数的反码等于源码,负数的反码在原码基础上,符号位不变,其余位按位取反(0 变 1,1 变 0)。以8bit位表示+7的反码为 0000 0111,-7的源码为 11111000
      • 补码:正数的补码等于源码,负数的补码是在反码的基础上加 1。以8bit位表示+7的补码为 0000 0111,-7的补码为 11111001
      // 正数的源码、反码和补码均相同
      // 负数的补码为反码加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 中属于 signed char

      image

      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          // insert code here...
          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

    • 科学计数法

      • 判断规则
        • 1.字母e(或E)之前必须有数(正数、负数、整数、小数均可)

        • 2.如果字母e(或E)之前是小数,小数点左边和右边至少有一边要有数字

        • 3.在字母e(或E)之后必须是整数

      • 合法案例:1.23456e2、 0.345E-3、-765e12、-.5E-8、6.e-7、1.0e10 非法案例:1.34E1.2、.e2
  • 2.4 字符型常量

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

    • 特征:字符数据存放在内存时,并不是字符本身,而是字符的代码,称之为ASCII码

    • 数字字符:如'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',小写字母的ASCII码比大写字母的ASCII码大32

      字符 ASCII码 字符 ASCII码
      'a' 97 'A' 65
      'z' 122 'Z' 90
    • 转义字符:如'\n' '\t' '\b' '\r' '\\' '\'' '\"'

      • 转义字符看起来是两个字符,实际上只能算一个字符

      • 可以使用\加上一个1-3位的8进制数\ddd\x(不是'\0x')加上一个1~2位的16进制数\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 变量

    • 定义:程序运行期间值可以改变的量

    • 每个变量都有一个名字,称为变量名,且必须进行变量说明,指明变量的类型

      • 变量声明形式:类型标识符 变量名;

      • 变量声明的本质:在内存中申请空间用于存放变量的值,编译系统依据变量类型在内存中分配合适大小的存储空间。

      • 变量名(标识符)命名规则:只能由字母、数字和下划线组成,且第一个字符必须为字母或下划线,不允许使用数字。变量名要求见名知义,如姓名声明为name变量,成绩声明为score变量,不推荐用a、b、c、xm(xingming)、nl(nianling)等。变量的名字仅仅是给程序员看的,编译器不看名字,编译器是通过地址找内存单元

      • C语言严格区分大小写,aA是两个不同的变量

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

      // 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;

3.赋值语句

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

    • 赋值原理

      • 将数据存入若干个连续的存储单元中,该存储空间有一个名字,即变量名
      • 变量定义后如果没有进行赋值,它的值是不确定的,因此操作变量之前要进行赋值。建议定义变量的同时直接进行初始化
    • 一般形式与执行逻辑

      • 变量 = 表达式;
      • 首先计算赋值号=右边表达式的值,然后将结果值送给赋值号=左边的变量
      • 注意事项
        • 1.赋值语句看上去和数学中的等式一致,但两者的意义完全不同。它的执行逻辑是从右往左,跟数学表达式也不一样
        • 2.赋值语句最后方会有;,而赋值表达式没有
        • 3.赋值号的左边必须是一个变量,不能是常量或表达式
        // 非法赋值语句
        'A' = a + 1;
        3 = x / 2;
        a + b = 12;
        
        • 4.复合赋值运算符 += -= *= /=
        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() {
      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() {
      char c1 = 0, c2 = 0;
      c1 = 'a';
      c2 = c1 - 32;
    
      // 以下语句要用到第4节(数据的输出)知识
      printf("%c,%c", c1, c2);
    
      return 0;
    }
    
  • 3.2 赋值语句中的类型转换

    • 赋值语句(或赋值运算表达式)中=左边的变量与右边表达式的数据类型不一致时,C编译系统会自动实现数据类型的转换,将赋值号右边的表达式值的类型转换成与左边变量相同的类型后再赋值
      • 情形1:当左边变量的数据类型比右边表达式值的类型长时,如int型转为float型或double型,转换后的值不改变精度或准确度,只改变值的表示形式。这种赋值情形类似将铅笔放入尺寸不低于它的文具盒中,文具盒完全可容纳铅笔

      • 情形2:当右边表达式值的类型比左边变量的类型要长时,如int型转为char型,转换时会对右边的数据进行截取,仅取出与左边变量类型相同的长度(比特位),这意味着会丢失高位数据,可能导致精度降低或出现错误。这种赋值情形类似将笔记本放入尺寸小于它的抽屉,强行放入的话笔记本会被损坏

      #include <stdio.h>
      int main() {
          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
      

4.数据的输出

  • 4.1 字符输出函数putchar()

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

    • 一般形式:putchar(c);

    • 注意事项

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

      • 2.将参数c的值作为ASCII码值,并将其对应的字符输出到显示器上

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

    image

    • 案例分析
    putchar(65);  // A
    putchar('0' + '1');  // a
    
  • 4.2 格式化输出函数printf()

    • 主要功能:按指定的格式完成输出过程,向显示器输出信息

    • 一般形式:printf("输出格式串", 表达式表);

    格式符 功能说明
    %c 按字符形式输出
    %d 按十进制整型输出
    %ld 按十进制长整型形式输出
    %o 按八进制形式输出
    %x 按十六进制形式输出
    %f(%e) 按浮点形式输出,默认为6位小数(科学计数法)
    %lf 按浮点形式输出,默认为6位小数,与%f等价,建议使用%f
    %m.nf 按浮点形式输出,显示宽度不小于m,n位小数
    %zd 输出size_t类型的值,如sizeof(int)的返回值
    %p 输出指针的地址
    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.若以%m.nf的形式输出,当数据的位数高于m时原样输出;当数据的位数低于m时加空格

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

      • 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() {
      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 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(){
      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[]) {
        // insert code here...
        printf("printf(\"Hello world!\\n\");\n");
        printf("cout<<\"Hello world!\"<<endl;\n");
    
        return 0;
    }
    

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

5.数据的输入

  • 5.1 字符输入函数getchar()

    • 主要功能:接收从键盘上输入的字符。程序中使用该函数输入字符时,可以用另一个变量接收读取的字符

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

    • 注意事项

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

      • 2.该函数不接收参数,但有整型返回值

      • 3.用户在按下键盘时,可输入整数

      image

    • 案例分析

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

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

    • 一般形式:scanf("格式串",地址表);

    格式符 功能说明
    %c 接收(输入)一个字符型数据,从键盘接收
    %d 接收一个整型数据,直到遇到回车
    %f 接收一个浮点型数据(float)
    %lf 接收一个浮点型数据(double)
    %s 接收一个字符串
    • 执行逻辑

      • 将从键盘上接收的数据存入对应地址的存储空间中

      • 格式符:按指定格式输入数据

      • 非格式符:按原样输入

    • 注意事项

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

      • 2.若有多个输入项,各项信息之间可以用空格、Tab键或者回车键作为分隔符

        • scanf("%d%d", &x, &y); 可以以空格、Tab键或者回车作为输入数据的分隔符
        • scanf("%d %d", &x, &y); 可以以空格、Tab键或者回车作为输入数据的分隔符
        • scanf("%d,%d",&x, &y); 只能以,作为输入数据的分隔符
      • 3.scanf()函数有一个正整数的返回值,它表示成功接收的参数个数

      image

      image

      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          int a = 0, b = 0;
          char ch1 = 0, ch2  = 0, ch3 = 0;
      
          int ret1 = scanf("%d %d", &a, &b);    // 输入 22 78
          int ret2 = scanf("%c %c %c", &ch1, &ch2, &ch3);    // 输入 a x y
          printf("%d %d\n", ret1, ret2);    // 2 3
      
          return 0;
      }
      

      image

    • 地址概念分析

      • 无论是在内存还是在硬盘中,存取数据都需要找到对应地址,它是一个十六进制的串
      // 以下代码打印了变量 k 的值,随后打印了变量 k 的地址。内存中首地址为 000000000062FE1C 的地址块存储了变量 k 的值
      #include <stdio.h>
      
      int main(){
        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(){	
      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() {
      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;
    }
    

6.运算符和表达式

  • 6.1 算术运算符

    运算符 运算 优先级 例子
    () 圆括号 1 5/(1-3)=-2
    [] 下标引用操作符 1 a[10]
    - 负号 2 -4
    * 乘法 3 7*3=21
    / 除法 3 7/3.0=2.333333,7/3=2
    % 模除 3 7%3=1 5%(1-3)=1
    + 加法 4 7+3=10
    - 减法 4 7-3=4
    • 注意事项

      • 1.模除运算是两个整数相除后取余数,要求 %两边必须是整型数据,且最终结果的正负与左边操作数的正负一致

      • 2.若/两边均为整数,则结果仍为整数,不同于数学中的除法运算

      • 3.若参加运算的两个数中有一个数为实数,则结果为double

      • 4.每个运算符都有一个优先级,混合运算式中要考虑到优先级,括号可改变运算次序

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

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

    • 案例分析

    #include <stdio.h>
    int main() {
      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;
    }
    
  • 6.2 自增、自减运算符

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

    • 一般格式

      • 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 = b+++++c 类似的语句,有歧义

      • 3.自增、自减运算符不能用于常量,如++5非法

    • 案例分析

    #include  <stdio.h>
    int main() {
        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 );
    }
    
  • 6.3 赋值运算符

    • =,“右结合性”,优先级为14。执行逻辑等知识可参考前面第3部分“赋值语句”
    • 案例分析
    #include <stdio.h>
    int main() {	
      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,\tx=%5.1f\n", k, x );
      // 运算符"+"的优先级高于"=",且"="是右结合,先算9.8 + 7,值为16.8赋给k,再将k的值16.8赋给y
      y = k = 9.8 + 7;
      printf("k=%5.1f,\ty=%5.1f\n", k, y );
    }
    // k = 9.8,    x = 16.8
    // k = 16.8,    y = 16.8
    
  • 6.4 逗号运算符

    • 用于将两个表达式连接起来,优先级为15,最低

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

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

    • 案例分析

      • 1.简单的逗号表达式运算
      // 1.表达式n的值不受前方表达式的影响
      x = (i=1, j=2, k=3)
      
      // 2.表达式n的值受前方表达式的影响
      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个值
      
      • 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) {
          // 业务逻辑
          // ......
      }
      
  • 6.5 位运算

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

    • 分类与运算特征

      • 二进制数0000 1100左移3位后为0110 0000,对应的十进制数从12变为96

      • 二进制数0011 0000右移3位后为0000 0110,对应的十进制数从48变为6

      • 对于移位运算符,不要移动负数位

      • &、|、~的运算对象为二进制比特,且操作数必须是整数。&&、||、!的运算对象为任意进制数据,双方运算性质一致

      image

      运算符 含义 优先级 格式 备注
      ~ 取反 2 ~ a 0变1,1变0
      << 左移n位 5 a << n 左边抛弃、右边补0;新值在原值的基础上*2^n
      >> 右移n位 5 b >> n 新值在原值的基础上/2^n
      & 按位与 8 a & b 当两个对应位均为1时,结果为1,否则为0
      ^ 按位异或 9 a ^ b 当两个对应位相同时,结果为1,否则为0
      竖线 按位或 10 a 或 b 当两个对应位均为0时,结果为0,否则为1
    • 案例分析

      • 1.左移操作符性质与应用
      #include <stdio.h>
      
      int main(int argc, const char * argv[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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。此时将 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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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
      // ......  
      

      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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          int num = 0;
          int i = 0, j = 0, k = 0;
          int a[32] = {0}, b[32] = {0};
          scanf("%d", &num);
          
          for (k = 1; k <= 32; k++) {
              if (k % 2 == 1) {
                  a[i] = num & 1;
                  i++;
              }
              else {
                  b[j] = num & 1;
                  j++;
              }
              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[]) {
          // insert code here...
          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[]) {
          // insert code here...
          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
      
  • 6.6 强制类型转换

    • 运算符为(类型),优先级为2

    • 一般形式:(类型)表达式

    • 执行逻辑:将表达式的结果类型强制转换成指定类型

    • 注意事项

      • 1.浮点型强制转换为整型时,不是进行四舍五入,只是进行取整

      • 2.当强制类型转换符和其他运算符一起出现时,注意优先级和运算顺序

    • 案例分析

    // 下式中强制类型转换的优先级(2)高于除号的优先级(3),所以先将(float)1转为1.0,然后再计算1.0/2=0.5
    (float)1/2
    
  • 6.7 问题表达式解析

    • 1.分析以下表达式的执行过程

      • *的优先级高于+,只能保证*``的计算比+早,但优先级并不能决定第 3 个*比第 1 个+```早执行

      • 执行顺序1: 1 3 2 5 4

      • 执行顺序2:1 3 5 2 4

      //1   2   3   4   5  --> 分别对应5个运算符
      a * b + c * d + e * f
      
    • 2.分析以下表达式的执行过程

      • 操作符的优先级只能决定--的运算在+前面,但无法得知+操作符的左操作数的获取在右操作数之前还是之后,因此结果不可预测,有歧义
      c + --c
      

7.常用的数学函数

  • 头文件:#include <math.h>

  • 参数类型和函数值类型均为实型

    函数 功能说明
    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的单位为弧度

    image

    image

    image

    image

    image

    image

    image

  • 案例分析

    • 1.输入一个正数x,求x的平方根
    #include <stdio.h>
    #include <math.h>
    int main(){	
      float x = 0.0, y = 0.0;
      printf("input x:");
      scanf("%f", &x );
      y = sqrt(x);
      // %-7.2f中的"-"号表示输出格式为左对齐
      printf("sqrt(%-7.2f) = %7.2f\n", x, y);
      return 0;
    }
    
    • 2.使用pow()函数求幂函数a^b
    #include <stdio.h>
    #include <math.h>
    int main(){ 
      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(){
      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(){	
      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(){
      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;
    }
    

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

  • 结构化程序

    • 顺序结构:一组按书写顺序执行的语句,是最简单的程序设计结构,主要由赋值语句、表达式语句、输入输出语句和函数调用语句组成

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

    • 循环结构:允许多次重复执行一组语句

  • 案例分析

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

    image

    #include <stdio.h>
    #include <math.h>
    #define  PI  3.1415926
    int main(){	
        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("\n第三边c = %.1f\n面积s=%.1f\n", c, s);
        return 0;
    }
    
    • 2.从键盘上输入一个3位数,然后将它反向输出。例如输入123,则输出321
    // 分析:本题的考查核心是取余和除法运算。对于任意一个整数n,n % 10可得到n的个位数,然后再将n重新赋值为n / 10,即n原值的十位数变为新的个位数。如此反复,直到n / 10的结果为0
    #include <stdio.h>
    int main(){
        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;
    }
    
posted @ 2025-01-23 09:06  pycoder_666  阅读(249)  评论(0)    收藏  举报