跳转至

📔 Lecture03 C Intro: Basics

更新日期:2023.11.02

1. 概述和背景

1.1 计算机结构的历史

  • ENIAC(1946年)

    • 当时第一台电子通用计算机,也是为了早期弹道轨道的计算。
    • 用于太多的真空管,重启新的程序需要2~3天。
  • EDSAC(1949年)

    • 剑桥大学的edsack是第一台通用存储用途的计算机。
    • 存储概念的计算机诞生。

1.2 C语言

  • 书籍推荐:
    • K & R 《C语言程序设计》

对于C语言,可以用来写操作系统,而不是直接使用汇编语言去进行。

对于UNIX是一个可移植操作系统。

对于C语言来说,可以编写C代码,然后将其移植到不同的体系结构。但是对于汇编语言来说,因为体系结构的更新,移植性就很差。

对于课程来说,使用的是C语言的原因:

  • 可以使用C语言去探索更底层的机器特性。例如:
    • 内存管理
    • 特殊指令
    • 并行性

对于语言来说,语言的抽象层次结构处在越高的位置,那么在编程中放弃的控制权就会越多。

其他C语言来说,也是现目前最流行的编程语言之一。但如果说需要追求安全性角度考虑,可以使用Rust或者Go

  • Rust

    • 可以将解决指针和内存泄漏的问题。也可以使得代码的清晰度增加。
    • 因为C是弱类型语言,编译器不会类型检查的操作。所以使用Rust(强类型语言)可以解决类型检查的问题。
  • Go

    • 可以执行多个core和线程的轻量级的编程语言。

2. 编译器和解释器

计算机运行程序的两种方式:编译和解释

C编译器可以直接将C程序直接映射为特定体系结构的机器代码(0和1组成的字符串)

  • C和Java不同之处,Java是直接转换为独立于体系结构的字节码,然后通过JIT进行编译。

    • 架构独立性。
    • 通过可移植的字节码将要传递的信息传递到不同的机器上,然后执行它。
    • Java同时具有解释器和编译器。
  • C和Python环境不同,Python环境在运行时转换为字节码。

    • Python是解释型的语言。
    • 本质区别:程序何时转换为低级机器指令(从“解释”级别角度分析)。
  • C通常是分为两个编译过程:

    • .c文件 ----> .o文件
    • .o文件 ----> 与库进行链接,生成可执行文件
    • 也包括了汇编,但一般情况下,汇编步骤一般直接被默认隐藏。

2.1 C编译的简要视图

2.2 编译的优缺点

  • 优点

    • 合理的运行时间:
      • Makefiles的编译过程是重新编译已修改的内容,适用于代码分段(大小文件较多)的情况下,只需要关注已修改的内容。
    • 运行时性能
      • 没有语言会比C更快。C编译为原始汇编语言,一般来说是最快的。
      • 但是现目前来看,Python在科学计算的角度也是很好的性能:
        • 在Python中可以使用更好的库来访问GPU特定的资源。
        • Python也可以调用C代码来执行功能:Cpython
  • 缺点

    • 编译文件(包括可执行文件)都是依赖于特定的计算机体系结构(例如:MPIS、x86、RISC-V)和操作系统(例如:Windows、Linux和MacOS)。
    • 必须在每个新系统上重新构建可执行文件。(代码的移植性问题,需要适配新的架构和系统)
    • 对于迭代周期来说很慢。原因:更改 ----> 编译 ----> 运行(这是重复周期)。
      • 对于make的编译方式来说,只编译已修改的方式。也可以同时并行编译:make -j
      • 链接器是连续的 ----- Amdahl’s Law

2.3 C 预处理器

  • 仅处理C预处理器的命令。
    • 任何以 "#" 开头的内容都是C预处理器的命令。
1
2
3
4
#include "file.h"       /* Inserts file.h into output */
#include <stdio.h>      /* Looks for file in standard location, but no actual difference! */
#define PI (3.14159)    /* Define constant */
#if/#endif              /* Conditionally include text */

对于宏定义,编写快速代码很有帮助,但是也会使得代码调试带来困难。例如比较常见的C宏定义就是创建一个小型函数:

#define min(x, y)   ((x) < (y)> ? (x) : (y))

3. C VS Java

  • ANSI C也被叫做C99 或者 C9x 标准。

    • 为了安全,可以使用 "gcc -std=c99" 编译

    printf("%ld\n", __STDC_VERSION__); // 打印C实际发布的版本
    
    - 该版本的Hightlight - 类似Java中的for循环的声明 - 注释和Java一样,可以使用// - 变长非全局数组 - <inttypes.h>:显式整数类型 - <stdbool.h>:用于布尔逻辑定义

  • C99升级为C11(C18修复一些Bugs)

    • 编译使用:gcc -std=c11
      printf("%ld\n", __STDC_VERSION__);  // 201112L
      printf("%ld\n", __STDC_VERSION__);  // 201710L
      
    • 该版本的Hightlight
      • 支持多线程
      • Unicode字符串和常量
      • 因为安全漏洞,移除gets()
      • 类型通用宏(根据类型调度)
      • 支持复数值
      • 支持静态断言、除了创建和打开。

4. C语法

4.1 main函数

  • 如果要处理函数能输入参数
    1
    2
    3
    int main(int argc, char *argv[])
    // argc 表示多少个参数
    // argv 表示数组索引的个数,索引0(第一个元素)表示的是程序本身的可执行文件名称。
    

4.2 bool类型:TrueFalse

在C语言中,不同的计算方式会有不同的结果。

4.3 Typed Variables in C

对于变量来说,必须要声明类型。所以在使用变量时,一定要声明其对应的类型,并且不能改变它。

目的:保证变量在程序的生命周期内处于被锁定,类型不能改变。

int var = 2;    // 对于类型不能改变

4.4 整型:Python vs Java vs C

不同程序语言对于int类型的大小定义不同。

C中int应该是目标处理器最能高效处理的整数类型。对于C来说,依赖于计算机(16位、32位或者64位)。

对于sizeof()来说,就是计算数据类型的字节宽度

// 对于不同的平台来说,可能存在的不同质疑。32位和64位就一定会遵守这样的规律?并不一定,要勇于去提出质疑。质疑它的字节宽度。
sizeof(long long) >= sizeof(long) >= sizeof(int) >= sizeof(short);

对于int类型,Python、Java和C对大小进行了比较:

对于int类型,也会遇到类似 intN_tuintN_t 格式的。

4.5 consts and Enums in C

  • const 类型

    • 常量是在声明中被分配一次类型值。
    • 在程序的整个执行过程中值都不会被改变。

      1
      2
      3
      const float  golden_ratio = 1.618;
      const int    days_in_week = 7;
      const double the_law      = 2.99792458e8;
      
  • enum 类型

    • 枚举常量为每个常量提供一个值。

      enum cardsuit {
          CLUBS,
          DIAMONDS,
          HEARTS,
          SPADES
      };
      enum color {
          RED,
          GREEN,
          BLUE
      };
      

4.6 C中的类型化函数

对于所有声明的函数都必须有返回类型。必须提前明确告知函数的预期返回类型是什么。

  • 返回类型可以是任何C变量类型,并放在函数名称的左侧。
  • 可以指定返回值类型为 void。

    • void表示不会返回任何值。
  • 对于函数的参数,也需要对其值进行类型声明。

  • 对于变量和函数,需要在使用前进行声明。

1
2
3
4
5
6
7
8
9
int number_of_people() 
{
    return 3
}

float dollars_and_cents()
{
    return 10.33;
}

4.7 Structs in C

struct(结构体) 是创建抽象数据类型的一种方法。

  • Typedef 允许声明定义新类型。

    typedef uint8_t BYTE;
    BYTE b1, b2;
    
  • 结构体是结构化的变量组

    typedef struct {
        int length_in_seconds;
        int year_recorded;
    } SONG;
    
    SONG song1;
    song1.length_in_seconds     = 213;
    song1.year_recorded         = 1994;
    
    SONG song2;
    song2.length_in_seconds     = 248;
    song2.year_recorded         = 1988;
    

4.8 C语法:控制流

  • 在一个函数中,C语言中的控制流和Java非常接近。

  • if-else的两种语法格式:

    • if格式
      1
      2
      3
      4
      5
      6
      7
      8
      // 语法格式
      if (expreassion)
          statement
      // 如果只有一个语句,可以不使用 {}
      // 例子
      if (x == 0) y++;
      if (x == 0) {y++;}
      if (x == 0) {y++; j = j + y;}
      
    • if-else
      // 语法格式
      if (expression)
          statement1
      else
          statement2
      
      // 例子
      if (score > 90)
          printf("Your grade level is A");
      else if (score > 60)
          printf("Your grade level is A");
      else
          printf("You are failed!")
      
  • while相关的语法格式:

    • 两者的区别:

      1
      2
      3
      4
      5
      6
      7
      8
      // while 的语法格式
      while (expression)
          statement;
      
      // do-while语法格式
      do {
          statement;
      } while (expression);
      
      - 对于while来说,直接判断表达式真假,如果为真则继续执行,否则直接结束。 - 对于do-while语句来说,会先执行一次逻辑,然后再去判断表达式的真假。

  • for循环语句

    for (initialize; check; update)
        statement
    
  • switch语句

    // break 很重要,否则会一直到case执行完毕,最后执行default完毕后退出
    switch (expression)
    {
        case const1:
            statement01;
            break;
        case const2:
            statement02;
            break;
        case const2:
            statement03;
            break;
        default:
            statement;
            break;
    }
    
  • goto语法

    • goto不推荐使用
    • 表示可以直接随机跳转到某个标签中。

5. 本Lecture代码实例

#include <stdio.h>
#include <math.h>

int main(void)
{
    int     angle_degree;
    double  angle_radian, pi, value;

    printf("\nCompute a table of the sine function\n\n");
    pi = 4.0 * atan(1.0);   /*could also just use pi = M_PI */
    printf("PI = %f \n\n", pi);
    printf("Angle\tsine\n");
    angle_degree = 0;   /*initial angle value*/
    while (angle_degree <= 360)
    {
        angle_radian = pi * angle_degree / 180.0;   /*loop til angle_degree > 360*/
        value = sin(angle_radian);
        printf("%3d\n%f\n", angle_degree, value);
        angle_degree += 10;
    }
    return 0;
}