• 常量: 在程序运行过程中, 其值不能改变的量.
    • 直接常量/字面常量 Literal:
      A literal is a value that is expressed as itself.
      • 整型常量(-10,0-12,-0xa), 实型常量(1.2), 字符常量('c'), 字符串常量("str")
      • 直接常量 不需要实现定义, 使用时直接写出即可, 其类型由系统根据书写方法自动默认.
    • 符号常量 Constant:
      A constant is a data type that substitutes a literal.
      • 用宏定义一个标识符来带代表一个常量 #define PI 3.14159265
  • 变量: 在程序运行过程中, 其值可以改变的量.
    • 变量是一段有名字的连续存储空间
    • 变量赋初值: C语言允许在定义变量的同时使用变量 初始化.
      变量初始化不是在编译阶段完成的, 而是在程序运行时执行本函数时赋予初始的, 相当于有一个赋值语句;
      (int a = 5; 等价于 int a; a=5;)
      (int * p = &a; 等价于 int * p; p=&a;)
    • 常变量: const int PI = 3.14

类型说明符 Type Specifier

short & long

If you need to use a small/large number, you can use a type specifier short/long.
The long keyword cannot be used with float and char types.

类型修饰符 Type Modifier

signed & unsigned

You can alter the data storage of a data type by using them.

1. 基本数据类型 Basic types

数据类型 Data type 格式字符 Format character
char c
int d 或 i (十进制)
unsigned int u(十进制), o(八进制), x(十六进制)
float f(小数输出), e(指数输出)
double lf
long double llf
short int hd 或 hi
long int ld 或 li
long long int lld 或 lli
unsigned short int hu
unsigned long int lu
unsigned long long int llu
string s

1.1 整型 Integer

Integers are whole numbers that can have both zero, positive and negative values but no decimal values.

  • 默认为 10进制, 10 ,20
  • 0 开头为 8进制, 045,021
  • 0b 开头为 2进制, 0b11101101
  • 0x 开头为 16进制, 0x21458adf
  • 整型常量后可以用:
    • u 或 U 明确说明为无符号整型数;(unsigned int ui = 5U;)
    • l 或 L 明确说明为长整型数.(long int li = 5L;)
  • 整型数据在内存中的存放形式:
    二进制补码 形式存放(0正 1负).
#include<stdio.h>

int main() {
    int decimal = 10;
    int binary = 0b1010;
    int octal = 012;
    int hexadecimal = 0xa;

    // C 不支持直接通过格式控制输出内存中的二进制数.
    printf("变量名\t\t 十进制\t 八进制\t 十六进制\n");
    printf("decimal\t\t %d\t 0%o\t 0x%x\n", decimal, decimal, decimal);
    printf("binary\t\t %d\t 0%o\t 0x%x\n", binary, binary, binary);
    printf("octal\t\t %d\t 0%o\t 0x%x\n", octal, octal, octal);
    printf("hecadecimal\t %d\t 0%o\t 0x%x\n", hexadecimal, hexadecimal, hexadecimal);

    /* 计算机中数据以二进制补码的形式进行存储
        无符号数, 所有位都用来存储数据, 都是数据位;
        有符号书, 第一位用来存储符号 ( 0正 1负), 是符号位.
        其余所有位用来存储数据, 是数据位.

    -1 二进制原码:
        0b    10000000 00000000 00000000 00000001
    -1 的二进制补码形式(实际存储形式)
        0b    11111111 11111111 11111111 11111111
        0x    ff        ff        ff        ff
    */
    short int hInt = -0x1;
    unsigned int uInt = -0x1;    
    unsigned long int ulInt = -0x1;
    //unsigned long long int ullInt = -0x1;
    // 转换说明符 %u, %o, %x 都是按无符号数据的格式输出的.
    printf("\n变量名\t 有符号十进制\t 无符号十进制\t\t 八进制\t\t\t 十六进制\n");
    printf("hInt\t %d\t\t %hu\t\t\t %ho\t\t\t %hx\n", (int)hInt, hInt, hInt, hInt);
    printf("uInt\t %d\t\t %u\t\t %o\t\t %x\n", (int)uInt, uInt, uInt, uInt);
    printf("ulInt\t %d\t\t %lu\t %lo\t %lx\n", (int)ulInt, ulInt, ulInt, ulInt);
    //printf("ullInt\t %d\t\t %llu\t %llo\t %llx\n", (int)ullInt, ullInt, ullInt, ullInt);

    return 0;
}

1.2 浮点型 Floating-point

float and double are used to hold real numbers.

  • 十进制小数形式(.123, 0.123, 0.0)
    • 单精度浮点型, float , single precision float data type
    • 双精度浮点型, double, double precision float data type
  • 指数形式 exponential
    • e/E 之前必须有数字, e/E 后面的指数必须为 整数;(12e3, 12E3)
    • 规范化的指数形式: 小数点左边应当有且只有一位非 0 数字;
    • 浮点型数据默认为 双精度, 如果要指定它为 单精度, 可以加后缀 f.
  • 一般浮点数 默认为双精度类型
  • 浮点型数据在内存中的存放形式:
    • 符号(0正 1负) + 小数部分(有效位) + 指数部分
      floating-point
    • 有效位以外的数字将被舍去.(舍入误差)
    • 由于实数存在舍入误差, 使用时要注意:
      • 不要试图用一个实数精确表示一个大正数, 记住: 浮点数是不精确的;
      • 避免直接将一个很大的实数与一个很小的实数相加减, 否则会"丢失"小的数;

1.3 字符型 Character

  • 计算机无法直接存储字符, 而是将字符转化为对应的 ASCII 码, 以其 ASCII 码的 补码 的形式存储.(因为 ASCII 码第一位都是 0, 所以都是正数, 而正数的补码和原码相同)
  • 所有字符常量(包括可以显示的, 不可显示的)均可以使用字符的转义表示法(ASCII码表示):
    • '\ddd' 或 '\0dd' ===> ddd表示 ASCII 码对应的的八进制表示('\n' == '\12' == '\012' == '\xa')
    • '\xhh' ==========> hh 表示 ASCII 码对应的十六进制表示
  • C 语言中的 NULL 字符:
    • NULL 是 ASCII 码表中的第一个字符, 编码为 0000 0000;
    • NULL 可用作字符串结束的标志 '\0';
    • NULL 可用作 空指针, 表示该指针未指向任何有效地址.
  • 字符型数据在内存中的存放形式:
    以字符 ASCII 码对应的 二进制补码 形式存放, 占用一个字节.
#include<stdio.h>

int main() {
    char c    = 'A';
    char cd    = 65;
    char co    = 0101;
    char cx    = 0x41;

    // 通过字符对应的 ASCII 码也可输出控制/打印字符
    printf("变量名\t 字符\t 十进制\t 八进制\t 十六进制\n");
    printf("c\t %c\t %d\t 0%o\t 0x%x\n",     c, c, c, c);        // 通过 \n 输出换行
    printf("cd\t %c\t %d\t 0%o\t 0x%x\12",     cd, cd, cd, cd);    // 通过 \ddd 格式输出换行
    printf("co\t %c\t %d\t 0%o\t 0x%x\012", co, co, co, co);    // 通过 \0dd 格式输出换行
    printf("cx\t %c\t %d\t 0%o\t 0x%x\xa",     cx, cx, cx, cx);    // 通过 \xhh 格式输出换行

    /* 计算机中  char 型数据和 int 型数据一样, 都以二进制补码的形式进行存储
        所以, char 型数据在存储上也是整数, 可以通过格式字符 c 或 d 决定 到底是作为字符还是整数 来输入输出;
        同理, int 型数据在一定范围内, 也可以通过格式字符 c 作为字符输出.

        通过 ASCII 码表, 可以知道整数&字符的对应关系
    */
    unsigned short int huInt = 0b01000001;
    printf("\n变量名\t 符号\t 有符号十进制\t 无符号十进制\t 八进制\t 十六进制\n");
    printf("hInt\t %c\t %d\t\t %hu\t\t 0%ho\t 0x%hx\n", (char)huInt, (int)huInt, huInt, huInt, huInt);
    return 0;
}

2. 派生数据类型 Derived Data Types

  • 数组类型 Arrays
  • 指针类型 Pointers
  • 函数类型 Function types
  • 结构体类型 Structures
  • 共用体类型 Union

3. 枚举类型 Enumerated Type

4. 不完整类型/空类型 Incomplete type

  • void is an incomplete type. It means "nothing" or "no type".
    Note that, you cannot create variables of void type.

Other Types

  • bool type
    • C89 没有定义布尔类型, 使用整型 int 来表示真假:
      • 输入时: 非 0 值表示 true; 0 表示 false.
      • 输出时: true == 1; false == 0.
    • C99 提供了 _Bool 型, 所以布尔型可以声明为 _Bool identifierName;
      • C99 还提供了一个头文件 <stdbool.h> 定义了 bool 代表 _Bool, true==1, false==0.
  • Enumerated type
  • Complex types





输入/输出 Input/Output (I/O)

C 语言没有设计专门的输入输出语句:
实现数据的输入输出(I/O)操作, 是通过调用 C 语言提供的 I/O 库函数.(须包含头文件 stdio.h)

0. sizeof 运算符

  • sizeof 是一个单目运算符.
  • 作用是: 得到操作数在存储空间中 实际占用的存储空间 大小(单位: Byte), 即使未使用所有空间.
  • sizeof 不能用于函数类型.
  • 优先级: 2 级(比 3 级 的算数运算符要高)
#include<stdio.h>

int main() {
    int a;
    /* 错误用法示例
    size_t 类型, 在头文件 <stddef.h> 中 typedef 为 unsigned int 类型. 该类型保证能容纳实现所建立的最大对象的字节大小.(PS: 此处为 unsigned long int 类型)
    size_t t = sizeof int;
    */
    // 正确用法
    printf("sizeof(int) = %lu \nsizeo***sizeo***", sizeof(int), sizeof(a), sizeof a);
    return 0;
}

1. 输出函数

1.1 printf();

printf(格式控制, 输出项1, 输出项2,...); 实现在标准终端输出设备(显示器)上按指定的格式进行数据输出.

  • 格式控制的完整格式:
    %-0m.nl(或h)格式字符
    • % 区别于普通字符的标志, 格式字符的起始符号;
    • - 左对齐 输出, 如省略则表示右对齐输出;
    • 0 指定空位填 0, 如省略则不填充;
    • m.n
      • m 指 域宽, 即对应的输出项在输出设备上所占的列数
      • n 指 精度, 用于说明输出的实型数的小数位, 如省略则默认为 6
    • l 或 h
      • l 将 int 修正为 long int;将 float 修正为 double
      • h 将 int 修正为 short int
    • 格式字符 指定输出类型: c, d, u, o, x, f, s
      • f float(默认整数部分全部输出, 小数部分输出 6 位小数)
      • 输出实数时, 其数据的实际精度并不完全取决于格式控制中的域宽&小数的域宽, 而是取决于数据在计算机内的存储精度.
      • 因此, 若程序中指定的域宽&小数域宽超过相应类型数据的有效数字, 输出的多余数字是没有意义的, 只是系统用来填充域宽而已.
  • "格式字符" 必须和 "输出项" 一一对应:
    • 格式说明 > 输出项 ---> 多出部分输出无意义的数字乱码
    • 格式说明 < 输出项 ---> 缺少部分不予输出

1.2 putchar();

putchar(字符变量or常量); // 向显示器输出一个字符;

2. 输入函数

2.1 scanf();

scanf(格式控制, 地址列表); 实现从终端键盘上读入数据
scanf("%d%d%d", &a, &b, &c); // 1 2 3
scanf("a=%d,b=%d,c=%d", &a, &b, &c); // a=1,b=2,c=3
scanf("%d%d%*d%d", &a, &b, &c); // 跳过输入的第三个数据 ?

  • scanf函数的格式字符前可以加入一个正整数指定输入数据所占的宽度, 但不可以对实数指定小数位的宽度.
  • 用 scanf 函数从键盘输入数据时, 每行数据在 未按下回车键之前, 可以任意修改. 但按下回车键之后, scanf 函数即接受了这一行数据, 不能再修改.
  • 空白符间隔输入数据(空格, tab, enter 等)

2.2 getchar();

  • 从终端键盘读字符到 键盘缓冲区, 直到用户按回车(回车也被读入键盘缓冲区);
  • 回车之后, getchar 函数从缓冲区 每次读入一个字符;
  • getchar 返回值, 是用户输入的第一个字符的 ASCII 码; 如出错返回 -1, 且将用户输入字符回显到屏幕.
  • 如用户在按回车之前输入了多余一个的字符, 其他字符会保留在键盘缓存区, 等待后续 getchar 调用读取. 直到缓冲区中的字符读完后, 才等待用户按键输入.
#include<stdio.h>

int main() {
    int a, b, c;

    //scanf("%d%d%d", &a, &b, &c);            // 65 66 67    不规定特定的输入格式, 空白符号表示间隔, 回车后数据放到缓冲区, 不可修改
    //scanf("a=%d,b=%d,c=%d", &a, &b, &c);    // 严格规定输入格式为: a=65,b=66,c=67
    scanf("%d%d%*d%d", &a, &b, &c);           // 要求输入的 4 个数据, 但第三个数据为无效数据

    printf("%-5d%-5d%-5d\n", a, b, c);

    // putchar 输出字符
    putchar(a);
    putchar(b);
    putchar(c);

    /* getchar 接收字符
        调用 getchar 函数时, 如果缓冲区有字符则直接从缓冲区取出一个字符;
        如果缓冲区为空, 则开始等待用户输入, 可以输入多个字符, 回车表示输入完毕. 同时返回第一个输入的字符的 ASCII 码.

        注意, 上面使用 scanf 函数接收输入时, 输入的数据全部被保存在 缓冲区 中; 
        scanf 函数结束后, 最后输入的 '\n' 仍然保留在缓冲区中没有取出.(使用 scanf 函数时需要注意)

        这里先调用 getchar 函数取出最后一个 换行符;
        再调用 putchar 函数将取出的换行符直接打印打屏幕上.
    */
    putchar(getchar());    // 若不先取出残余的 '\n', 后面 a 将被赋值为 '\n'

    // 此时, 缓冲区为空, 调用 getchar 函数等待用户输入, 并取出第一个字符赋值给 a.(其他字符仍保留在缓冲区, 等待下次读取)
    a = getchar();    
    putchar(a);
    b = getchar();
    putchar(b);
    c = getchar();
    putchar(c);

    return 0;
}

Others

变量名 & 地址 的关联:

  • 编译阶段, 编译器会将合法的变量名放到一个叫 符号表 Symbol Table 的表中.
    • 每个符号对应一个地址。当你调用此变量时,就会根据此符号表找到对应的地址,然后进行操作.
    • 通过 Symbol Table 可以实现"变量名 - 地址"的关联.: 变量名 ---> 地址 ---> 存储空间中的值.
    • 编译完成后, 所有变量名都在 Symbol Table 中有一个对应的地址.(目标文件中也全部是对地址的操作)
    • 取地址运算符&, 可以取出变量所对应的地址
    • 变量名, 可以直接取出地址所对应的值(变量值)
    • 取值运算符*, 可以取出指针值所对应的地址上存放的值
  • 运行阶段, 内存中并不存在变量名, 只有相应的地址, 直接对进行地址操作.
    • 运行阶段, 变量名所标识的内存空间, 只有 首地址 & 大小 的概念了,
  • 在运行期间动态申请的空间,是需要额外的代码维护,以确保不同变量不会混用内存。
  • 内存申请和释放时机很重要,过早会丢失数据,过迟会耗费内存。