文章

C 基础语法

AUTOGEN db1cfcb4a66a48a0907746848a657f32

C语言基础语法

本文档介绍了基本的C语言相关语法和介绍

本系列仍有C语言的内存管理专题、指针专题和数据结构专题。

1.1 介绍

早期历史

  • 1969-1970 年,Tompson 在 BCPL 语言上创造 B 语言。
  • 1971-1977 年,Ritchie 改造 B 语言增加数据类型创造了 C 语言,伴随 Unix 产生而产生。
  • 1977-1979 年,C 语言伴随 Unix 移植性需求而繁荣发展。

Ken Thompson 与 Dennis M. Ritchie 这对好基友都是图灵奖获得者。

为PDP7编写操作系统,使用C改写

POSIX 标准

可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003

C标准化

1989年ANSI C作为C89

1990年ISO采纳标准为C90

1999年发布C99

在 C 语言高速发展时期,从大型主机到小型微机,衍生了 C 语言的很多不同版本。

为统一 C 语言版本,1983 年美国国家标准局 ANSI - American National Standards Institute 成立了一个委员会,来制定 C 语言标准。1989 年 C 语言标准被批准,被称为 ANSI X3.159-1989 Programming Language C。第一个版本的 C 语言标准通常被称为 ANSI C。又由于这个版本是 89 年完成制定的,因此也被称为 C89。

后来 ANSI 把这个标准提交到 ISO 国际化标准组织,1990 年被 ISO 采纳为国际标准,称为 ISO C。又因为这个版本是 1990 年发布的,因此也被称为 C90。

ANSI C(C89) 与 ISO C(C90)内容基本相同,主要是格式组织不一样。看到 ANSI C、ISO C、C89、C90,要知道这些标准的内容都是一样的。

2011年12月8日,ISO 又正式发布了新的标准,称为 ISO/IEC9899: 2011,简称为 C11。

目前,几乎所有的开发工具都支持 ANSI/ISO C 标准,是 C 语言用得最广泛的一个标准版本。

在 ANSI C 标准确立之后,C 语言的规范在很长一段时间内都没有大的变动。1995 年 C 程序设计语言工作组对 C 语言进行了一些修改,成为后来的 1999 年发布的 ISO/IEC 9899:1999 标准,通常被成为 C99。

但是商业公司对 C99 的支持所表现出来的兴趣不同,GCC 和其它一些商业编译器支持 C99 的大部分特性的時候,微软和 Borland 却没有什么动作。

GCC - GNU Compiler Collection 编译器套件作为 GNU 工程开发的支持多种编程语言的编译器,在 C 言语标准的支持上一直走在前面。

例如,GCC 9.3.0 支持以下标准,使用 gcc -v –help 可以查询当前版本支持的标准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|       Standard      | GCC 9.3 | GCC 8.1 | GCC 5.3 |                     Note                     |
|---------------------|---------|---------|---------|----------------------------------------------|
| -std=c11            | ✅     | ✅      | ✅     | ✓ ISO 2011 C                                 |
| -std=c17            | ✅     | ✅      | ❌     | ✓ ISO 2017 C (2018)                          |
| -std=c18            | ✅     | ✅      | ❌     | ✓ ISO 2017 C (2018),同 `-std=c17`           |
| -std=c1x            | ✅     | ✅      | ✅     | ✗ 弃用,`-std=c11` 替代,同 `-std=c11`       |
| -std=c2x            | ✅     | ❌      | ❌     | ✓ ISO 202X C 标准草案 [体验]                 |
| -std=c89            | ✅     | ✅      | ✅     | ✓ ISO 1990 C 标准,同 `-std=c90`             |
| -std=c90            | ✅     | ✅      | ✅     | ✓ ISO 1990 C                                 |
| -std=c99            | ✅     | ✅      | ✅     | ✓ ISO 1999 C                                 |
| -std=c9x            | ✅     | ✅      | ✅     | ✗ 弃用,`-std=c99` 替代,同 `-std=c99`       |
| -std=iso9899:1990   | ✅     | ✅      | ✅     | ✓ ISO 1990 C 标准,同 std=c90                |
| -std=iso9899:199409 | ✅     | ✅      | ✅     | ✓ ISO 1990 C as amended in 1994              |
| -std=iso9899:1999   | ✅     | ✅      | ✅     | ✓ ISO 1999 C 标准,同 std=c99                |
| -std=iso9899:199x   | ✅     | ✅      | ✅     | ✗ 弃用,`-std=iso9899:1999` 替代,同 std=c99 |
| -std=iso9899:2011   | ✅     | ✅      | ✅     | ✓ ISO 2011 C 标准,同 std=c11                |
| -std=iso9899:2017   | ✅     | ✅      | ❌     | ✓ ISO 2017 C (2018),同 `-std=c17`           |
| -std=iso9899:2018   | ✅     | ✅      | ❌     | ✓ ISO 2017 C (2018),同 `-std=c17`           |

指令集

RISC精简指令集-ARM,M1芯片,苹果A系列芯片

CISC复杂指令集-X86

1.2 基本元素

关键字(Keywords)

关键字是C语言中预先保留的单词,具有特定意义,不能用作变量名或其他标识符。以下是一些常见的C语言关键字:

  • auto:用于声明自动存储期的变量。
  • break:用于跳出最近的一个循环或switch语句。
  • case:用于switch语句中的分支选择。
  • char:用于声明字符型变量。
  • const:用于定义常量。
  • continue:用于跳过当前循环的剩余部分,直接进入下一次循环。
  • default:用于switch语句中的默认分支。
  • do:用于do-while循环的开始。
  • double:用于声明双精度浮点型变量。
  • else:用于if语句的”否则”分支。
  • enum:用于声明枚举类型。
  • extern:用于声明外部链接的变量或函数。
  • float:用于声明单精度浮点型变量。
  • for:用于for循环。
  • goto:用于无条件跳转到程序中的某个位置。
  • if:用于条件判断。
  • int:用于声明整型变量。
  • long:用于声明长整型变量。
  • register:用于声明寄存器变量。
  • return:用于从函数返回值。
  • short:用于声明短整型变量。
  • signed:用于声明有符号类型。
  • sizeof:用于获取变量或类型的大小。
  • static:用于声明静态存储期的变量或函数。
  • struct:用于声明结构体类型。
  • switch:用于多条件选择。
  • typedef:用于为类型创建别名。
  • union:用于声明联合体类型。
  • unsigned:用于声明无符号类型。
  • void:用于声明没有值的变量或函数返回类型。
  • volatile:用于声明易失性变量。
  • 等。

标识符(Identifiers)

标识符是程序员定义的变量名、常量名、类型名等。它们必须遵循以下规则:

  • 标识符的第一个字符必须是字母或下划线(_)。
  • 标识符的其余部分可以是字母、数字或下划线。
  • 标识符不能是C语言的关键字。
  • 标识符是大小写敏感的。

常量(Constants)

常量是程序中不会改变的值。常量可以是整型、浮点型、字符型等。例如:

  • 整型常量:42, -10, 0x1A(十六进制)。
  • 浮点型常量:3.14, -0.001, 1.2e10(科学记数法)。
  • 字符型常量:'A', '\n'(换行符)。

字符串字面量(String Literals)

字符串字面量是括在双引号中的字符序列。例如:

  • "Hello, World!"
  • "C Programming"

字符串字面量在内存中以字符数组的形式存储,并且以空字符('\0')结尾。

运算符(Operators)

运算符用于执行程序中的操作。C语言中的运算符包括:

  • 算术运算符:+, -, *, /, %, ++, --
  • 关系运算符:==, !=, <, >, <=, >=
  • 逻辑运算符:&&, ||, !
  • 位运算符:&, |, ^, <<, >>
  • 赋值运算符:=, +=, -=, *=, /=, %=
  • 条件运算符:?:
  • 其他运算符:sizeof, &(取地址),*(解引用)。

分隔符(Separators)

分隔符用于分隔程序中的不同部分。常见的分隔符包括:

  • 分号(;):用于结束语句。
  • 逗号(,):用于分隔参数列表或声明中的多个变量。
  • 括号((), [], {}):用于定义作用域或数组索引。
  • 点(.):用于访问结构体或联合体的成员。

1.3 数据类型

基本数据类型

  1. 整型(Integer Types)

    • 1
      
      int
      

      :标准整型,通常为4个字节。

      • 例子:int age = 30;
    • 1
      
      short
      

      :短整型,通常为2个字节。

      • 例子:short year = 2024;
    • 1
      
      long
      

      :长整型,通常为4个字节或8个字节(取决于平台)。

      • 例子:long population = 1000000000;
    • 1
      
      long long
      

      :更长的整型,至少64位。

      • 例子:long long bigNumber = 1234567890123456789LL;
  2. 字符型(Character Type)

    • 1
      
      char
      

      :用于存储单个字符,通常为1个字节。

      • 例子:char letter = 'A';
  3. 浮点型(Floating-Point Types)

    • 1
      
      float
      

      :单精度浮点型,通常为4个字节。

      • 例子:float pi = 3.14159265f;
    • 1
      
      double
      

      :双精度浮点型,通常为8个字节。

      • 例子:double area = 3.14159265358979323846;
    • 1
      
      long double
      

      :更长的双精度浮点型,精度和大小依赖于编译器。

      • 例子:long double largeNumber = 12345678901234567890.123456789012L;
  4. 无符号类型(Unsigned Types)

    • 可以用于整型和字符型,表示只能存储非负数。
    • 例子:
      • unsigned int uInt = 42;
      • unsigned char uChar = 255;
  5. 布尔类型(Boolean Type)

    • C99标准中引入了

      1
      
      _Bool
      

      1
      
      bool
      

      (通常使用宏定义)。

      • 例子:_Bool isTrue = 1;

派生数据类型

  1. 枚举类型(Enumeration Types)

    • 用于定义一组命名的整型常量。

      • 例子:

        1
        2
        
        cenum Color { RED, GREEN, BLUE };
        int favorite = GREEN;
        
  2. 结构体(Structures)

    • 允许将多个不同类型的数据项组合成一个单一的类型。

      • 例子:

        1
        2
        3
        4
        5
        6
        
        cstruct Student {
            int id;
            char name[50];
            float gpa;
        };
        struct Student alice = {1, "Alice", 3.5};
        
  3. 联合体(Unions)

    • 类似于结构体,但所有成员共享相同的内存位置。

      • 例子:

        1
        2
        3
        4
        5
        6
        
        cunion Data {
            int i;
            float f;
            char *s;
        };
        union Data data = { .f = 3.14 };
        
  4. 数组(Arrays)

    • 相同类型的元素集合。

      • 例子:

        1
        2
        
        cint numbers[5] = {1, 2, 3, 4, 5};
        char str[] = "Hello";
        
  5. 指针(Pointers)

    • 存储另一个变量的内存地址。

      • 例子:

        1
        2
        3
        
        cint *p = NULL; // 指针初始化为NULL
        int value = 10;
        p = &value; // p现在指向value的地址
        
  6. 函数(Functions)

    • 可以返回基本数据类型或派生数据类型。

      • 例子:

        1
        2
        3
        
        cint add(int a, int b) {
            return a + b;
        }
        
  7. void类型

    • 表示没有值或不关心返回类型的函数。

      • 例子:

        1
        2
        3
        
        cvoid printHello() {
            printf("Hello, World!\n");
        }
        

每种数据类型都有其特定的用途和内存占用,选择合适的数据类型对于编写高效和可读的代码至关重要。

1.4 变量与常量

变量(Variables)

变量是程序中用于存储数据的容器。变量必须在使用前声明,声明时需要指定变量的类型,因为C语言是一种静态类型语言。变量的命名遵循一定的规则,通常以字母或下划线开头,后跟字母、数字或下划线的组合。

变量的声明和初始化:

1
2
3
4
5
6
7
8
cint age;      // 声明一个整型变量age
age = 25;     // 初始化变量age为25

float salary; // 声明一个浮点型变量salary
salary = 3500.0; // 初始化变量salary为3500.0

char initial; // 声明一个字符型变量initial
initial = 'A'; // 初始化变量initial为'A'

变量的作用域:

  • 局部变量:在函数内部声明,只在该函数内部可见。
  • 全局变量:在所有函数外部声明,可以在程序的任何部分访问。

变量的生命周期:

  • 自动存储期的变量(如局部变量)在声明它们的块进入时创建,在块退出时销毁。
  • 静态存储期的变量(使用static关键字声明)在程序开始时创建,在程序结束时销毁。

常量(Constants)

常量是程序中一旦初始化后其值就不可改变的数据。常量可以是数值常量、字符常量、字符串常量等。

数值常量:

  • 整型常量可以是十进制、八进制(以0开头)或十六进制(以0x开头)。
  • 浮点型常量通常带有小数点或使用科学记数法表示。

字符常量:

  • 使用单引号括起来的单个字符。
  • 可以表示为普通字符或转义序列。

字符串常量:

  • 使用双引号括起来的字符序列。
  • 字符串字面量在内存中以字符数组的形式存在,以空字符\0结尾。

定义和使用常量:

1
2
3
4
5
cconst int MAX_USERS = 100; // 定义一个整型常量MAX_USERS
const float PI = 3.14159; // 定义一个浮点型常量PI
const char NEWLINE = '\n'; // 定义一个字符常量NEWLINE

const char greeting[] = "Hello, World!"; // 定义一个字符串常量

常量的优点:

  • 使程序更易读,因为它们提供了有意义的名称。
  • 使程序更易维护,因为改变常量的值只需在一处进行。
  • 编译器可以对常量进行优化,提高程序效率。

常量的限制:

  • 常量必须在编译时就已知其值。
  • 常量不能被修改。

变量和常量是C语言编程中不可或缺的部分,正确地使用它们可以提高代码的可读性、可维护性和效率。

预定义常量(Micro Variable)

在C语言中,定义好的量通常指的是预定义的宏和常量,它们在标准库中定义,用于提供一些特定的、在编译时已知的数值。这些宏和常量通常定义在<limits.h>(C89标准)或<stdint.h>(C99标准及以后)等头文件中。以下是一些常见的预定义量:

  1. 整数类型的最大值和最小值
    • INT_MAXint 类型能表示的最大值。
    • INT_MINint 类型能表示的最小值(通常是 -INT_MAX - 1)。
    • LONG_MAXLONG_MIN:长整型(long int)的最大值和最小值。
    • SHORT_MAXSHORT_MIN:短整型(short int)的最大值和最小值。
  2. 字符类型的最大值
    • CHAR_MAXCHAR_MINchar 类型的最大值和最小值,取决于char是否是有符号类型。
  3. 浮点类型的最大值和最小值
    • FLT_MAXFLT_MIN:单精度浮点型(float)的最大值和最小正非零值。
    • DBL_MAXDBL_MIN:双精度浮点型(double)的最大值和最小正非零值。
  4. 浮点类型的精度和epsilon值
    • FLT_DIGDBL_DIG:单精度和双精度浮点数的位数。
    • FLT_EPSILONDBL_EPSILON:单精度和双精度浮点数的最小正非零值,使得 1.0 + epsilon 不等于 1.0。
  5. 其他宏
    • CHAR_BIT:每个字符中位数的位数,通常是 8。
    • SCHAR_MAXSCHAR_MIN:有符号字符类型(signed char)的最大值和最小值。
    • UCHAR_MAX:无符号字符类型(unsigned char)的最大值。
  6. 宽字符和长长类型的最大值
    • WCHAR_MAXWCHAR_MIN:宽字符类型(wchar_t)的最大值和最小值。
    • LLONG_MAXLLONG_MIN:长长整型(long long int)的最大值和最小值。
  7. 浮点类型的特殊值
    • HUGE_VAL:表示无穷大的宏。

要获取这些宏和常量的值,你需要包含相应的头文件,例如:

1
2
c#include <limits.h>
#include <float.h>

然后,你可以在你的程序中使用这些宏。例如:

1
2
cprintf("The maximum value of int is %d\n", INT_MAX);
printf("The minimum value of double is %e\n", DBL_MIN);

这些预定义的量对于编写可移植的程序非常有用,因为它们提供了与平台无关的数值。不同的编译器和平台可能会有不同的具体数值,但是它们都会遵循C标准的规定。

1.5 存储类

在C语言中,存储类定义了变量或函数的生命周期和作用域。以下是C语言中的几种存储类:

  1. auto

    • 这是默认的存储类,用于函数内部声明的局部变量。auto存储类表明变量的生命周期仅限于函数的执行期间,当函数调用结束时,这些变量的存储空间会被释放。

    • 例子:

      1
      2
      3
      
      cvoid func() {
          auto int x = 10; // 声明一个自动存储的整型变量x
      }
      
    • 注意:在现代C语言标准中(C99及以后),auto关键字是可选的,因为局部变量默认就是自动存储的。

  2. register

    • register存储类用于声明寄存器变量。寄存器变量通常存储在CPU的寄存器中,而不是内存中,这可以加快访问速度。然而,由于寄存器的数量有限,编译器可以决定是否真的将变量存储在寄存器中。

    • 例子:

      1
      2
      3
      
      cvoid func() {
          register int x = 10; // 尝试将变量x存储在寄存器中
      }
      
    • 注意:register关键字在现代编程中很少使用,因为现代编译器通常能够自动优化变量的存储位置。

  3. static

    • static存储类用于声明具有静态存储期的变量或函数。对于变量,这意味着变量的生命周期贯穿整个程序的运行期,即使声明它们的函数或代码块已经执行完毕。对于函数,static函数只能在定义它们的文件内部访问。

    • 例子:

      1
      2
      
      c
      static int x = 10; // 声明一个静态变量x,它在程序的整个生命周期内都存在
      
    • 静态变量的初始化只在程序的第一次运行时发生。

  4. extern

    • extern存储类用于声明具有外部链接的变量或函数。这意味着这些变量或函数可以在程序的其他文件中访问。extern关键字通常用于在头文件中声明全局变量或函数,以便在多个源文件中共享。

    • 例子:

      1
      2
      3
      4
      5
      
      c// file1.c
      int x; // 定义一个全局变量x
           
      // file2.c
      extern int x; // 声明x为外部变量,允许在file2中访问
      
    • 当使用extern声明全局变量时,必须在程序的某个地方有一个对应的定义。

这些存储类提供了不同的内存管理和访问控制机制,使得程序员可以根据需要选择最合适的存储类来声明变量和函数。

1.6 程序控制

C语言提供了多种逻辑语句来控制程序的流程,包括条件判断、循环和跳转语句。下面是对这些逻辑语句的详细说明:

条件判断语句

  1. if 语句

    • 用于根据条件执行不同的代码块。

    • 语法:

      1
      2
      3
      4
      5
      
      cif (condition) {
          // 条件为真时执行的代码
      } else {
          // 条件为假时执行的代码
      }
      
    • 可以嵌套使用,形成多重条件判断。

  2. switch 语句

    • 用于基于不同的情况执行不同的代码块。

    • 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      
      cswitch (expression) {
          case constant-expression:
              // 当expression与constant-expression相等时执行的代码
              break;
          // ...
          default:
              // 如果没有匹配的case,则执行这里的代码
      }
      
    • break语句用于退出switch结构,防止执行后续的case。

  3. 条件运算符(三元运算符)

    • 用于基于条件选择两个值中的一个。

    • 语法:

      1
      2
      
      c
      result = condition ? value_if_true : value_if_false;
      
    • 这是一种简洁的条件表达式,常用于简单的条件赋值。

循环语句

  1. for 循环

    • 用于重复执行一段代码,直到给定的条件不再满足。

    • 语法:

      1
      2
      3
      
      cfor (initialization; condition; increment) {
          // 循环体
      }
      
    • 循环开始前初始化变量,然后检查条件,如果条件为真,执行循环体,然后进行增量操作,再次检查条件。

  2. while 循环

    • 先判断条件,如果条件为真,则执行循环体,然后重复这个过程。

    • 语法:

      1
      2
      3
      
      cwhile (condition) {
          // 循环体
      }
      
  3. do-while 循环

    • 先执行循环体,然后判断条件,如果条件为真,则重复执行循环体。

    • 语法:

      1
      2
      3
      
      cdo {
          // 循环体
      } while (condition);
      
    • 至少执行一次循环体。

goto 语句

  • goto语句用于实现无条件跳转,即跳转到程序中预先定义的标签位置。

  • 语法:

    1
    2
    3
    4
    5
    6
    7
    
    clabel:
    // 一些代码
      
    // 某个条件满足时,跳转到标签位置
    if (some_condition) {
        goto label;
    }
    
  • 使用goto可以实现复杂的跳转逻辑,但过度使用或不当使用会导致程序的流程难以理解和维护,因此通常不推荐使用goto语句。

这些逻辑语句是C语言控制流的基础,它们允许程序员根据不同的条件和需求来控制程序的执行路径。在实际编程中,合理使用这些语句可以提高代码的效率和可读性。

在C语言中,函数是执行特定任务的代码块,可以包含输入参数和返回值。作用域规则定义了变量和函数的可见性范围。下面是C语言中函数和作用域规则的详细说明:

1.7 函数

函数(Functions)

  1. 函数定义

    • 函数由返回类型、函数名、参数列表和函数体组成。

    • 语法:

      1
      2
      3
      
      creturnType functionName(parameterType parameterName, ...) {
          // 函数体
      }
      
  2. 返回类型

    • 指定函数执行完成后返回的数据类型,可以是基本数据类型、结构体类型或void(无返回值)。
  3. 函数名

    • 唯一标识函数的名称,遵循标识符的命名规则。
  4. 参数列表

    • 函数可以接收零个或多个参数,参数是传递给函数的值或变量的引用。
  5. 函数体

    • 包含实现函数功能的语句序列。
  6. 函数原型

    • 在函数定义之前声明函数的类型和参数,以便编译器在调用函数时检查类型匹配。
  7. 递归函数

    • 函数在其定义中直接或间接调用自身。
  8. 函数指针

    • 变量可以存储函数的地址,指向函数的变量称为函数指针。

作用域规则(Scope Rules)

  1. 局部作用域
    • 局部变量(使用auto或不指定存储类)仅在其定义的函数或代码块内部可见。
    • 当控制流离开定义局部变量的代码块时,局部变量的生命周期结束。
  2. 全局作用域
    • 全局变量(使用static关键字或在所有函数外部定义的变量)在整个程序中可见。
    • 全局变量的生命周期贯穿整个程序的运行期。
  3. 复合语句作用域
    • 在复合语句(由花括号{}包围的代码块)中定义的变量仅在该复合语句内部可见。
  4. 块作用域
    • 使用{...}定义的代码块可以创建一个新的作用域,变量在这个作用域内声明,离开作用域后变量的生命周期结束。
  5. 函数作用域
    • 函数内部定义的变量仅在该函数内部可见,函数调用结束后,这些变量的生命周期结束。
  6. extern关键字
    • 使用extern关键字可以声明一个具有外部链接的变量或函数,这意味着它可以在其他文件中访问。
  7. 静态函数
    • 使用static关键字定义的函数只能在定义它们的文件内部访问,具有内部链接。
  8. 静态全局变量
    • 即使使用static关键字,全局变量仍然可以在其他文件中通过extern关键字访问,但它们在初始化时具有静态存储期。

理解函数和作用域规则对于编写结构清晰、易于维护的C语言程序至关重要。合理地使用函数可以提高代码的复用性,而正确地管理作用域可以避免变量名冲突和意外的变量覆盖。

1.8 指针

在C语言中,指针是一种特殊的变量,它存储了另一个变量的内存地址。指针提供了一种间接访问和操作内存的方式,是C语言中非常强大和灵活的特性之一。以下是对C语言指针的详细说明:

指针的基本概念

  1. 指针变量
    • 指针变量用于存储另一个变量的内存地址。
    • 声明指针时,需要指定指针指向的变量类型。
  2. 地址运算符 &
    • 用于获取变量的内存地址。
  3. 间接引用运算符 \*
    • 用于访问指针指向的内存地址中存储的值。

指针的声明和初始化

1
2
3
cint var = 10;      // 声明一个整型变量var
int *ptr;          // 声明一个整型指针ptr
ptr = &var;        // 将ptr初始化为var的地址

指针的类型

  • 指针的类型决定了指针可以指向哪种类型的数据。例如,int *是指向int的指针,double *是指向double的指针。

指针的算术运算

  • 指针可以进行加法和减法运算,步长取决于指针指向的数据类型的大小。

指针与数组

  • 指针和数组在C语言中密切相关。数组名本身就是一个指向数组首元素的指针。

指针的高级用法

  1. 指针数组
    • 一个数组,其元素都是指针。
  2. 函数指针
    • 指针可以指向函数,用于调用函数或在不同情况下动态选择函数。
  3. 指针和结构体
    • 结构体指针用于访问结构体的成员。
  4. 动态内存分配
    • 使用malloc, calloc, reallocfree等函数在堆上分配和释放内存。
  5. 指针的指针
    • 一个指针,它存储了另一个指针的地址。
  6. 空指针
    • 一个指针被初始化为NULL,表示它不指向任何地址。

指针的安全和陷阱

  • 内存泄漏:忘记释放动态分配的内存。
  • 野指针:未初始化或已释放内存的指针。
  • 缓冲区溢出:错误的指针操作可能导致数据溢出到相邻的内存区域。
  • 指针混淆:错误的指针运算可能导致程序崩溃或不可预测的行为。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c#include <stdio.h>

int main() {
    int num = 20;
    int *p = &num; // p是一个指针,存储了num的地址

    printf("Value of num = %d\n", num);
    printf("Value available at address in p = %d\n", *p);

    int arr[] = {10, 20, 30};
    int *p2 = arr; // p2是指向数组首元素的指针

    printf("Value of arr[0] = %d\n", *(p2 + 0)); // 输出10
    printf("Value of arr[1] = %d\n", *(p2 + 1)); // 输出20

    return 0;
}

指针是C语言中一个非常强大的特性,但也需要谨慎使用。正确地理解和使用指针可以提高程序的性能和灵活性,但错误的指针操作也可能导致程序错误和安全问题。

1.9 输入输出

标准输入输出库函数

C语言标准库中的<stdio.h>头文件提供了一系列的标准输入输出函数,这些函数通过标准输入输出流(stdin、stdout)进行操作。

  1. printf()
    • 用于格式化输出到标准输出(stdout)。
    • 语法:int printf(const char *format, ...);
    • 可以包含多种格式说明符,如%d(整数)、%f(浮点数)、%s(字符串)等。
  2. scanf()
    • 用于从标准输入(stdin)读取并格式化数据。
    • 语法:int scanf(const char *format, ...);
    • 同样支持多种格式说明符,与printf()相对应。
  3. fprintf()
    • 类似于printf(),但输出到指定的文件流。
  4. fscanf()
    • 类似于scanf(),但从指定的文件流读取数据。
  5. sprintf()
    • 将格式化的数据写入字符串。
  6. sscanf()
    • 从字符串中读取数据。

输入输出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
c#include <stdio.h>

int main() {
    int age;
    float salary;
    char name[50];

    // 使用printf()输出文本
    printf("Please enter your name: ");

    // 使用scanf()从标准输入读取字符串
    scanf("%s", name);

    // 继续读取其他数据
    printf("Please enter your age: ");
    scanf("%d", &age);
    printf("Please enter your salary: ");
    scanf("%f", &salary);

    // 输出读取的数据
    printf("Name: %s\n", name);
    printf("Age: %d\n", age);
    printf("Salary: %.2f\n", salary);

    return 0;
}

文件输入输出

除了标准输入输出外,C语言还提供了文件输入输出功能,允许程序读写文件。

  1. fopen()
    • 用于打开文件,并返回一个文件指针。
    • 语法:FILE *fopen(const char *filename, const char *mode);
  2. fclose()
    • 用于关闭文件,释放文件资源。
    • 语法:int fclose(FILE *stream);
  3. fread()
    • 从文件中读取数据到缓冲区。
  4. fwrite()
    • 将数据从缓冲区写入文件。
  5. fgets()
    • 从文件中读取一行数据到字符串。
  6. fputs()
    • 将字符串写入文件。

文件输入输出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
c#include <stdio.h>

int main() {
    FILE *file;
    char filename[] = "example.txt";
    char buffer[100];

    // 打开文件用于写入
    file = fopen(filename, "w");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 写入数据到文件
    fputs("Hello, World!\n", file);

    // 关闭文件
    fclose(file);

    // 打开文件用于读取
    file = fopen(filename, "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 从文件读取数据
    fgets(buffer, sizeof(buffer), file);

    // 输出读取的数据
    printf("%s", buffer);

    // 关闭文件
    fclose(file);

    return 0;
}

作用域和可移植性

  • 输入输出函数通常具有全局作用域,可以在程序的任何地方使用。
  • 使用标准输入输出函数可以提高程序的可移植性,因为这些函数与操作系统无关。

注意事项

  • 始终检查文件操作的返回值,确保操作成功。
  • 使用完毕后应关闭所有打开的文件。
  • 格式化字符串应谨慎使用,避免产生安全漏洞,如缓冲区溢出。

C语言的输入输出机制为程序提供了与用户和文件系统交互的能力,是程序设计中不可或缺的一部分。正确地使用这些功能可以提高程序的用户体验和数据管理能力。

本文由作者按照 CC BY 4.0 进行授权