[C.C++] C语言入门进阶学习

492 0
Honkers 2025-5-13 08:36:24 来自手机 | 显示全部楼层 |阅读模式

C语言学习笔记

1. C 语言概述

  • 1.1 C 语言的历史与发展

    • 起源
      • C 语言由 Dennis Ritchie 在 1972 年于贝尔实验室开发,目的是为了实现 UNIX 操作系统。
      • C 语言是在 B 语言的基础上发展而来的,B 语言是由 Ken Thompson 开发的。
    • 发展
      • 1978 年,Brian Kernighan 和 Dennis Ritchie 合作出版了《C 程序设计语言》(K&R),这本书成为 C 语言的经典教材,并推广了 C 语言的使用。
      • 1983 年,C 语言被国际标准化组织(ISO)采纳,形成了 ISO C 标准。
      • 1999 年,发布了 C99 标准,增加了对新的数据类型(如 long long 和 bool)的支持,并引入了变量声明的灵活性。
      • 2011 年,发布了 C11 标准,增强了多线程支持、内存管理功能以及对泛型编程的支持。
  • 1.2 C 语言的特点与应用

    • 特点
      • 高效性:C 语言接近于计算机硬件,编写的程序执行效率高。
      • 灵活性:提供了低级的内存操作能力,允许直接操作硬件。
      • 可移植性:C 语言代码可在不同平台上编译运行,只需少量修改。
      • 丰富的库支持:标准库提供了大量的函数,可以用于输入输出、字符串处理、数学运算等。
      • 结构化编程:支持函数和模块化编程,使程序结构清晰、易于维护。
    • 应用
      • 系统软件:操作系统(如 UNIX、Linux、Windows)和编译器的开发。
      • 嵌入式系统:广泛应用于嵌入式设备开发,如微控制器和嵌入式操作系统。
      • 游戏开发:许多游戏引擎使用 C 语言进行开发,以提高性能。
      • 网络编程:在网络协议和服务器软件中广泛应用。
      • 科学计算与数值分析:用于开发高性能的计算程序。
  • 1.3 C 语言的编译过程

    • 源代码编写

      • 使用文本编辑器编写 C 语言源代码,文件扩展名通常为 .c。
    • 编译过程

      1. 预处理(Preprocessing)
        • 处理以 # 开头的预处理指令,如 #include 和 #define。
        • 将头文件的内容插入到源代码中,展开宏定义。
      2. 编译(Compilation)
        • 将预处理后的源代码转换为汇编代码。
        • 进行语法检查和类型检查,生成中间代码。
      3. 汇编(Assembly)
        • 将汇编代码转换为机器代码(目标文件)。
        • 目标文件通常包含未解决的符号(如函数引用)。
      4. 链接(Linking)
        • 将一个或多个目标文件与库文件链接,生成可执行文件。
        • 解决未定义的符号,将函数和变量的地址绑定。

      示例

      • 使用 GCC 编译器的命令行编译过程示例:
      1. gcc -o myprogram myprogram.c
      复制代码

      这里 -o 参数指定输出文件名为 myprogram,myprogram.c 是源代码文件。

      如:

      1. gcc test.c -o test.exe
      复制代码
  • 1.4 常用 C 语言开发工具与环境搭建

    • 开发环境
      • 文本编辑器:可使用任何文本编辑器,如 Vim、Nano、Notepad++、VS Code 等。
      • 集成开发环境(IDE):如 Code::Blocks、Dev-C++、Eclipse CDT、Visual Studio 等,这些 IDE 提供了代码高亮、调试和编译功能。
    • 编译器
      • GCC(GNU Compiler Collection):广泛使用的开源编译器,支持多种平台。
      • Clang:由 LLVM 项目开发,提供高效的编译速度和丰富的功能。
      • MSVC(Microsoft Visual C++):适用于 Windows 开发的专有编译器。
    • 调试工具
      • GDB(GNU Debugger):开源调试工具,用于调试 C 语言程序。
      • Valgrind:内存调试和泄漏检测工具,帮助查找内存管理问题。
    • 设置环境
      • Linux 系统:可以直接使用终端安装 GCC 和其他工具。
      • Windows 系统:可以使用 MinGW 或 Cygwin 安装 GCC,或者直接安装 Visual Studio。
      • macOS 系统:可以使用 Homebrew 安装 GCC 或 Clang。

2. C 语言基础

C语言是一种广泛使用的程序设计语言,因其高效性和灵活性而受到欢迎。理解C语言的基本概念是编程的基础,下面将详细介绍C语言的基础知识

  • 2.1 基本语法

    • 2.1.1 程序结构

      • C语言程序的基本结构包括头文件、主函数和其他函数的定义。一个典型的C语言程序如下所示:

        1. #include <stdio.h> // 引入标准输入输出库
        2. int main() { // 主函数,程序的入口
        3. printf("Hello, World!\n"); // 输出语句
        4. return 0; // 返回值
        5. }
        复制代码
        • #include :预处理指令,用于包含标准输入输出库,以便使用printf等函数。
        • int main():主函数,程序的执行从这里开始。
        • printf:用于输出字符串到控制台。
        • return 0;:返回值,表示程序正常结束。
    • 2.1.2 注释与格式化

      • C语言中的注释用于解释代码,可以提高代码的可读性。C语言支持两种注释形式:

        • 单行注释:以//开始,直到行末为止。
        • 多行注释:以/*开始,以*/结束,可以跨越多行。
        1. // 这是一个单行注释
        2. /*
        3. 这是一个多行注释
        4. 可以用于更详细的解释
        5. */
        复制代码

        在格式化方面,C语言并没有强制要求代码的书写格式,但推荐使用一致的缩进和换行方式,使代码更易读。

  • 2.2 数据类型

    C语言中的数据类型决定了变量所能存储的数据类型及其占用的内存大小。主要分为基本数据类型派生数据类型(也称构造类型)。

    • 2.2.1 基本数据类型(int, float, char, double)

      • int:整型,用于表示整数。
      • float:单精度浮点型,用于表示带小数的数值。
      • char:字符型,用于表示单个字符。
      • double:双精度浮点型,用于表示更精确的带小数的数值。
      1. int a = 10; // 整数
      2. float b = 5.5; // 单精度浮点数
      3. char c = 'A'; // 字符
      4. double d = 3.14159; // 双精度浮点数
      复制代码
      • 其中

        1.1 整数类型(Integer Types)

        整数类型用于存储整数值,即不带小数部分的数字。C语言提供了多种整数类型,按大小分为不同类别:

        • int:用于存储普通整数。
        • short:用于存储较小的整数。
        • long:用于存储较大的整数。
        • long long:用于存储更大的整数。

        1.1.1 整数类型的表示范围

        整数类型的范围和大小依赖于系统的架构(如32位、64位)和编译器实现。一般来说:

        • int:通常为4字节,范围为 -2,147,483,648 到 2,147,483,647。
        • short:通常为2字节,范围为 -32,768 到 32,767。
        • long:通常为4字节,范围为 -2,147,483,648 到 2,147,483,647(在64位系统上有时为8字节)。
        • long long:通常为8字节,范围为 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。
        1. #include <stdio.h>
        2. int main() {
        3. int a = 10;
        4. short b = 5;
        5. long c = 100000;
        6. long long d = 10000000000LL;
        7. printf("int: %d\n", a);
        8. printf("short: %d\n", b);
        9. printf("long: %ld\n", c);
        10. printf("long long: %lld\n", d);
        11. return 0;
        12. }
        复制代码

        1.2 浮动类型(Floating Point Types)

        浮动类型用于存储带小数点的数值。C语言提供了两种浮动类型:

        • float:用于存储单精度浮动数(4字节)。
        • double:用于存储双精度浮动数(8字节)。
        • long double:用于存储更高精度的浮动数(通常为8字节或16字节,取决于系统)。
        1. #include <stdio.h>
        2. int main() {
        3. float f = 3.14f;
        4. double d = 3.141592653589793;
        5. long double ld = 3.14159265358979323846264338327950288419716939937510L;
        6. printf("float: %.2f\n", f);
        7. printf("double: %.15f\n", d);
        8. printf("long double: %.50Lf\n", ld);
        9. return 0;
        10. }
        复制代码

        1.3 字符类型(Character Type)

        字符类型用于存储单个字符。它的基本类型是 char,通常占1字节(8位),用于存储一个字符或小整数(ASCII码)。

        • char:存储单个字符。
        • signed char:存储带符号的字符。
        • unsigned char:存储无符号字符,通常用于存储字节数据。
        1. #include <stdio.h>
        2. int main() {
        3. char ch = 'A';
        4. signed char sch = -100;
        5. unsigned char uch = 250;
        6. printf("char: %c\n", ch);
        7. printf("signed char: %d\n", sch);
        8. printf("unsigned char: %d\n", uch);
        9. return 0;
        10. }
        复制代码

        1.4 布尔类型(Boolean Type)

        C语言本身没有专门的布尔类型,但可以使用整数类型来表示布尔值:

        • 0 表示 false(假)。
        • 非0 表示 true(真)。

        从C99标准开始,C语言引入了 头文件,提供了 bool 类型。

        1. #include <stdio.h>
        2. #include <stdbool.h>
        3. int main() {
        4. bool isTrue = true;
        5. bool isFalse = false;
        6. printf("isTrue: %d\n", isTrue);
        7. printf("isFalse: %d\n", isFalse);
        8. return 0;
        9. }
        复制代码
    • 2.2.2 类型修饰符

      C语言提供了类型修饰符来改变基本数据类型的特性,包括:

      • signed:表示有符号类型(默认)。
      • unsigned:表示无符号类型,只能存储非负值。
      • short:表示短整型,通常占用较少的内存(例如2字节)。
      • long:表示长整型,通常占用更多的内存(例如4或8字节)。
      • long long:表示一个更大的整数类型(通常是8字节)。
      1. unsigned int u = 10; // 无符号整型
      2. short int s = -5; // 短整型
      3. long int l = 123456; // 长整型
      复制代码
    • 2.2.3 补充:ASCII表

      十进制十六进制字符描述
      00x00NUL空字符 (null)
      10x01SOH标题开始 (start of heading)
      20x02STX正文开始 (start of text)
      30x03ETX正文结束 (end of text)
      40x04EOT传输结束 (end of transmission)
      50x05ENQ查询 (enquiry)
      60x06ACK确认 (acknowledge)
      70x07BEL响铃 (bell)
      80x08BS退格 (backspace)
      90x09TAB水平制表符 (horizontal tab)
      100x0ALF换行 (line feed)
      110x0BVT垂直制表符 (vertical tab)
      120x0CFF换页 (form feed)
      130x0DCR回车 (carriage return)
      140x0ESO移出 (shift out)
      150x0FSI移入 (shift in)
      160x10DLE数据链路转义 (data link escape)
      170x11DC1设备控制 1 (device control 1)
      180x12DC2设备控制 2 (device control 2)
      190x13DC3设备控制 3 (device control 3)
      200x14DC4设备控制 4 (device control 4)
      210x15NAK拒绝确认 (negative acknowledge)
      220x16SYN同步 (synchronous idle)
      230x17ETB传输块结束 (end of transmission block)
      240x18CAN取消 (cancel)
      250x19EM结束介质 (end of medium)
      260x1ASUB替换 (substitute)
      270x1BESC转义 (escape)
      280x1CFS文件分隔符 (file separator)
      290x1DGS字符分隔符 (group separator)
      300x1ERS记录分隔符 (record separator)
      310x1FUS单元分隔符 (unit separator)
      320x20(space)空格
      330x21!感叹号 (exclamation mark)
      340x22"双引号 (double quote)
      350x23#井号 (hash)
      360x24$美元符号 (dollar sign)
      370x25%百分号 (percent)
      380x26&和 (ampersand)
      390x27单引号 (single quote)
      400x28(左圆括号 (left parenthesis)
      410x29)右圆括号 (right parenthesis)
      420x2A*星号 (asterisk)
      430x2B+加号 (plus)
      440x2C,逗号 (comma)
      450x2D-减号 (minus)
      460x2E.点 (dot)
      470x2F/斜杠 (slash)
      480x300数字 0
      490x311数字 1
      500x322数字 2
      510x333数字 3
      520x344数字 4
      530x355数字 5
      540x366数字 6
      550x377数字 7
      560x388数字 8
      570x399数字 9
      580x3A:冒号 (colon)
      590x3B;分号 (semicolon)
      600x3C<小于号 (less than)
      610x3D=等于号 (equal sign)
      620x3E>大于号 (greater than)
      630x3F?问号 (question mark)
      640x40@商标符号 (at symbol)
      650x41A字母 A
      660x42B字母 B
      670x43C字母 C
      680x44D字母 D
      690x45E字母 E
      700x46F字母 F
      710x47G字母 G
      720x48H字母 H
      730x49I字母 I
      740x4AJ字母 J
      750x4BK字母 K
      760x4CL字母 L
      770x4DM字母 M
      780x4EN字母 N
      790x4FO字母 O
      800x50P字母 P
      810x51Q字母 Q
      820x52R字母 R
      830x53S字母 S
      840x54T字母 T
      850x55U字母 U
      860x56V字母 V
      870x57W字母 W
      880x58X字母 X
      890x59Y字母 Y
      900x5AZ字母 Z
      910x5B[左方括号 (left square bracket)
      920x5C\反斜杠 (backslash)
      930x5D]右方括号 (right square bracket)
      940x5E^插入符号 (caret)
      950x5F_下划线 (underscore)
      960x60`反引号 (backtick)
      970x61a字母 a
      980x62b字母 b
      990x63c字母 c
      1000x64d字母 d
      1010x65e字母 e
      1020x66f字母 f
      1030x67g字母 g
      1040x68h字母 h
      1050x69i字母 i
      1060x6Aj字母 j
      1070x6Bk字母 k
      1080x6Cl字母 l
      1090x6Dm字母 m
      1100x6En字母 n
      1110x6Fo字母 o
      1120x70p字母 p
      1130x71q字母 q
      1140x72r字母 r
      1150x73s字母 s
      1160x74t字母 t
      1170x75u字母 u
      1180x76v字母 v
      1190x77w字母 w
      1200x78x字母 x
      1210x79y字母 y
      1220x7Az字母 z
      1230x7B{左花括号 (left curly brace)
      1240x7C|竖线 (vertical bar)
      1250x7D}右花括号 (right curly brace)
      1260x7E~波浪号 (tilde)
      1270x7FDEL删除 (delete)

      总结:

      • 控制字符(0 - 31): 用于控制文本格式和传输过程,通常不显示。
      • 可打印字符(32 - 126): 包括数字、字母、标点符号以及一些常见符号。
      • 删除字符(127): 用于删除字符。
    • 2.2 数组类型

    • 2.2.5 结构类型(struct)

    • 2.2.6 联合类型(union)

    • 2.2.7 枚举类型(enum)

      枚举类型用于定义一组命名的整型常量,C语言使用 enum 关键字来定义枚举类型,增加代码的可读性。

      1. #include <stdio.h>
      2. enum day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
      3. int main() {
      4. enum day today = Wednesday;
      5. printf("Today is day number: %d\n", today); // 输出: 3
      6. return 0;
      7. }
      复制代码

      在上面的例子中,Sunday 默认值为 0,Monday 为 1,依此类推。你可以显式地给枚举值赋值:

      1. enum day { Sunday = 1, Monday = 2, Tuesday = 3, Wednesday = 4 };
      复制代码
    • 2.2.8 其他

      1.1 空类型(Void Type)

      void 是一种特殊的数据类型,表示“无类型”(也称万能类型)。它通常用于以下情况:

      • 函数不返回值时,使用 void 来指定返回类型。

      • 函数参数为空时,可以用 void 来指定函数参数类型为空。

        1. #include <stdio.h>
        2. void greet() {
        3. printf("Hello, World!\n");
        4. }
        5. int main() {
        6. greet();
        7. return 0;
        8. }
        复制代码
      • 其中:void类型不可以直接定义数据,但可以作为函数的返回值类型,表示没有返回值。

      1.2 类型修饰符(类型限定符)

      • const:用于声明常量,表示该变量的值不可修改。

        • const 关键字可以放在不同的位置,改变其修饰对象的含义。常见的有两种用法:

          • const 在类型前: 表示常量对象。

          • const 在指针前: 表示指针常量。

            1. #include <stdio.h>
            2. int main() {
            3. int num = 10;
            4. const int *p = &num; // p 是指向常量的指针,不能通过 p 修改 num
            5. // *p = 20; // 错误,不能修改 num 的值
            6. p = NULL; // 可以改变 p 指向的位置
            7. const int **q = &p; // q 是指向指向常量的指针
            8. return 0;
            9. }
            复制代码

            如果将 const 放在指针变量本身的前面,它将意味着指针本身是常量,不能改变指向的位置:

            1. #include <stdio.h>
            2. int main() {
            3. int num1 = 10, num2 = 20;
            4. int * const p = &num1; // p 是一个常量指针,不能修改指针 p 的值(指向的位置)
            5. *p = 30; // 可以通过 p 修改 num1 的值
            6. // p = &num2; // 错误,不能修改指针 p 的指向
            7. printf("num1 = %d\n", num1); // 输出 num1 = 30
            8. return 0;
            9. }
            复制代码
      • volatile:告诉编译器该变量的值可能会被外部因素改变(例如硬件或多线程环境),因此不应对其进行优化。

      • extern: 声明一个变量,extern声明的变量没有建立存储空间。例:extern int a;

      • register: 定义寄存器变量,提高效率。register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那木register就生效,如果没有空闲寄存器,那么就无效。

        • register 修饰符用于局部变量,表示该变量可能会被存储在寄存器中,以提高访问速度。通常,局部变量使用 register 修饰,而全局变量或静态变量是不能使用 register 修饰的。

          1. #include <stdio.h>
          2. int main() {
          3. register int i; // 提示编译器将 i 存储在寄存器中
          4. for (i = 0; i < 1000000; i++) {
          5. // 循环体
          6. }
          7. printf("Loop completed.\n");
          8. return 0;
          9. }
          复制代码
      • restrict: 用于声明指针,表示该指针是唯一的,即没有其他指针会指向相同的内存区域,从而允许编译器进行更积极的优化。

        • restrict 只能用在指针声明中,不能用于其他类型。
        • 使用 restrict 时必须保证指针的确是唯一的,如果错误地使用了多个指针指向同一块内存区域,可能会导致未定义的行为。
  • 2.3 字符串格式化输出与输入

    • 字符串常量

      • 字符串常量是内存中一段连续的char空间,以\0(数字0)结尾。
      • 字符串常量是由双引号括起来的字符串序列,如"China","$12.5"等都是合法的字符串常量。

      其中:字符串常量与字符常量的不同:

      • 'a'为字符常量,"a"为字符串常量。

      • 每个字符串的结尾,编译器会自动的添加一个结束标志'\0',即"a"包含两个字符'a'和'\0'。

        例:

        1. char *b = "hello \0 world";
        2. printf("%s\n", b);// 输出hello
        复制代码
    • printf打印格式说明

      printf 是 print format 的缩写,意思是格式化打印,也就是向显示器上格式化输出数据。

      严格来说,printf() 是向标准输出文件中输出数据,而这个标准输出文件通常来说都是“黑底白字”的控制台,或者说是显示器。

      • 基本格式

        格式化字符串的基本结构是:

        1. printf("格式控制字符串", 参数1, 参数2, ...);
        复制代码
      • printf() 的标准用法(原型)为:

        1. int printf( const char * format, argument... );
        复制代码
        • format 为格式字符串,由格式说明符和普通字符构成。其中:

          • 普通字符按照原样输出,比如英文、数字、逗号、空格等;

          • 格式说明符以%开头,比如 %d、%s、%c 等。

        • argument 表示将要输出的数据,多份数据以,分隔。数据的个数和类型,要与格式说明符一一对应。

        • printf() 会将 argument 格式化,并插入到 format 字符串中,代替对应的格式说明符。

        • int 表示 printf() 的返回值类型,也即处理结果的数据类型。输出成功以后,printf() 将返回输出的字符的个数;如果输出失败,将返回一个负数。

      • 常见格式化标识符

        格式标识符描述示例输出
        %d打印带符号的十进制整数printf("%d", 123);123
        %i打印带符号的十进制整数printf("%i", -123);-123
        %u打印无符号十进制整数printf("%u", 123);123
        %o打印无符号八进制整数printf("%o", 8);10
        %x打印无符号十六进制整数 (小写字母)printf("%x", 255);ff
        %X打印无符号十六进制整数 (大写字母)printf("%X", 255);FF
        %f打印浮点数 (默认6位小数)printf("%f", 3.14159);3.141590
        %e科学计数法表示的浮点数 (小写e)printf("%e", 3.14159);3.141590e+00
        %E科学计数法表示的浮点数 (大写E)printf("%E", 3.14159);3.141590E+00
        %g根据值自动选择 %f 或 %e 格式printf("%g", 3.14159);3.14159
        %G根据值自动选择 %f 或 %E 格式printf("%G", 3.14159);3.14159
        %c打印字符printf("%c", 'A');A
        %s打印字符串printf("%s", "Hello");Hello
        %p打印指针地址printf("%p", &x);0x7ffd74c83d7c (根据平台而不同)
        %%打印百分号符号printf("%%");%
      • 宽度和精度控制

        • 宽度:指定输出的最小字符数,若实际输出字符数小于指定宽度,则会填充空格(默认为右对齐)。

        • 精度:对于浮点数,精度表示小数点后保留的位数。对于字符串,精度表示最大输出字符数。

          1. printf("%5d", 42); // 输出: 42(总宽度5,右对齐,前面补空格)
          2. printf("%-5d", 42); // 输出:42 (总宽度5,左对齐,后面补空格)
          3. printf("%.2f", 3.14159); // 输出:3.14(精度2,四舍五入)
          4. printf("%10.2f", 3.14159); // 输出: 3.14(总宽度10,精度2)
          复制代码
      • 长整型和短整型

        • %ld:长整型 (long int)

        • %lld:长长整型 (long long int)

        • %hd:短整型 (short int)

          1. long long int num = 1234567890;
          2. printf("%lld", num); // 输出:1234567890
          复制代码
      • 打印带符号和无符号整数的宽度、填充字符控制

        • +:强制显示符号(正数会显示加号,负数会显示减号)。

        • -:左对齐(默认为右对齐)。

        • 0:用零填充空白位置。

      • 打印指针地址

        • %p 用于打印指针的地址,输出为平台相关的地址格式(通常是十六进制表示)。
      • 格式化日期和时间

        • 可以通过 struct tm 和 strftime 结合使用来打印格式化的日期和时间。
      • 长整型与浮点型组合打印

        • printf 支持同时输出多个参数,并且允许不同类型混合使用。
      • 格式化输出中的转义字符

        转义字符描述示例输出
        \n换行printf("Hello\nWorld");Hello \n World
        \t水平制表符printf("a\tb");a b
        \\反斜杠printf("\\");\
        \'单引号printf("\'");
        \"双引号printf("\"");"
      • 补充说明

        • printf 的格式控制符可以组合使用,并且可以通过给定宽度和精度来自定义输出的样式和格式。可以用这种方式打印不同的数据类型,精确控制输出格式。

          1. printf("%-10s%-5d%.2f\n", "Apple", 10, 3.14159);// 输出Apple 10 3.14
          复制代码

          其中:

          • %10s:字符串右对齐,宽度为10。
          • %-5d:整数左对齐,宽度为5。
          • %.2f:浮点数保留两位小数。
  • 2.4 变量与常量

    • 2.3.1 变量的定义与初始化

      变量是程序运行时存储数据的基本单元。在C语言中,定义变量的语法为:

      1. 数据类型 变量名;
      复制代码

      变量可以在定义的同时进行初始化。

      1. int x; // 定义整型变量x
      2. x = 5; // 初始化变量x
      3. float y = 3.14; // 定义并初始化浮点型变量y
      复制代码
    • 2.3.2 常量的使用

      常量是不可改变的量,使用const关键字来定义常量。常量在程序执行过程中不会被修改(尽管使用 const 关键字声明的常量不允许直接修改,但在某些情况下,可以通过指针进行操作,后续会有补充)。

      1. const int MAX_SIZE = 100; // 定义一个整型常量
      复制代码

      使用常量可以提高代码的可读性和可维护性,避免魔法数字的使用。

      在编程中,“魔法数字”(magic number)指的是在代码中直接使用的数字字面量,这些数字没有上下文或说明,可能使代码难以理解和维护。例如,直接使用 3.14、42 或 60 等数字,而不对其含义进行解释,会让读者难以明白这些数字在程序中的意义。

  • 2.5 运算符

    C语言提供了多种运算符,用于执行不同类型的运算。主要运算符包括算术运算符、关系运算符、逻辑运算符、位运算符和赋值运算符。

    • 2.5.1 算术运算符

      算术运算符用于执行基本的数学运算。

      运算符描述示例
      +加法a + b
      -减法a - b
      *乘法a * b
      /除法a / b
      %取模(余数)a % b
      1. int a = 10, b = 3;
      2. int sum = a + b; // 加法
      3. int difference = a - b; // 减法
      4. int product = a * b; // 乘法
      5. int quotient = a / b; // 除法
      6. int remainder = a % b; // 取模
      复制代码
    • 2.5.2 关系运算符

      关系运算符用于比较两个操作数的关系,返回布尔值(真或假)。

      运算符描述示例
      ==等于a == b
      !=不等于a != b
      >大于a > b
      <小于a < b
      >=大于或等于a >= b
      <=小于或等于a <= b
      1. if (a > b) {
      2. printf("a is greater than b\n");
      3. }
      复制代码

      在C语言中,布尔值通常使用整数来表示。在没有 stdbool.h 的情况下,C语言通常使用 0 表示 false,使用非零值(通常是 1)表示 true。从C99标准开始,可以使用 stdbool.h 头文件来定义布尔类型。此时,bool 代表布尔类型,true 和 false 分别代表 1 和 0。

    • 2.5.3 逻辑运算符

      逻辑运算符用于组合布尔表达式。

      运算符描述示例
      &&逻辑与a && b
      ||逻辑或a || b
      !逻辑非!a
      1. if (a > 0 && b > 0) {
      2. printf("Both a and b are positive\n");
      3. }
      复制代码
    • 2.5.4 位运算符

      位运算符用于对整型数据的位进行操作。

      运算符描述示例
      &按位与a & b
      |按位或`a
      ^按位异或a ^ b
      ~按位取反~a
      <<左移a << 2
      >>右移a >> 2
      1. int a = 5; // 0000 0101
      2. int b = 3; // 0000 0011
      3. int result = a & b; // 结果是 0000 0001
      复制代码
    • 2.5.5 赋值运算符

      赋值运算符用于给变量赋值,常用的赋值运算符包括:

      运算符描述示例
      =赋值a = b
      +=加法赋值a += b
      -=减法赋值a -= b
      *=乘法赋值a *= b
      /=除法赋值a /= b
      %=取模赋值a %= b
    • 这些运算符的优先级别一共有15个。

      优先级运算符名称含义使用形式结合方向说明
      1[]
      ()
      .
      2->
      -
      ~
      ++
      --
      *
      &
      !
      (类型)
      sizeof
      3/
      *
      %
      4+
      -
      5<<
      >>
      6>
      >=
      <
      <=
      7==
      !=
      8&
      9^
      10|
      11&&
      12||
      13?:
      14=
      /=
      *=
      %=
      +=
      -=
      <<=
      >>=
      &=
      ^=
      !=
      15,

3. 控制结构

控制结构是编程语言的基本组成部分,用于控制程序的执行流程。主要包括条件语句和循环语句,这些结构帮助程序根据不同的输入和条件采取不同的执行路径,从而实现复杂的逻辑和功能。

  • 3.1 条件语句

    条件语句根据特定条件的真假来执行不同的代码块。它们用于决策过程,根据用户输入、计算结果或其他条件的不同来执行不同的代码。

    • 3.1.1 if 语句

      if 语句是最基本的条件控制结构。它通过检查条件表达式的结果来决定执行哪一部分代码。基本语法如下:

      1. if (条件) {
      2. // 如果条件为 true 执行的代码
      3. } else {
      4. // 如果条件为 false 执行的代码
      5. }
      复制代码

      示例:

      1. int age = 20;
      2. if (age >= 18) {
      3. printf("你是成年人。\n");
      4. } else {
      5. printf("你是未成年人。\n");
      6. }
      复制代码

      可以使用 else if 来处理多个条件:

      1. if (age < 13) {
      2. printf("你是儿童。\n");
      3. } else if (age < 18) {
      4. printf("你是青少年。\n");
      5. } else {
      6. printf("你是成年人。\n");
      7. }
      复制代码

      注意: 在 if 语句中,条件表达式的结果必须为布尔值(true 或 false)。在 C 语言中,非零值被视为 true,零被视为 false。

    • 3.1.2 switch 语句

      switch 语句用于根据变量的值选择不同的执行路径,适合处理多个分支的情况。基本语法如下:

      1. switch (表达式) {
      2. case 值1:
      3. // 执行的代码
      4. break;
      5. case 值2:
      6. // 执行的代码
      7. break;
      8. default:
      9. // 当没有匹配到的值时执行的代码
      10. }
      复制代码

      示例:

      1. int day = 3;
      2. switch (day) {
      3. case 1:
      4. printf("星期一\n");
      5. break;
      6. case 2:
      7. printf("星期二\n");
      8. break;
      9. case 3:
      10. printf("星期三\n");
      11. break;
      12. default:
      13. printf("未知的星期\n");
      14. }
      复制代码

      注意: 如果没有 break 语句,程序会继续执行下一个 case,这称为“穿透”,在某些情况下可能有用,但通常会导致意外行为。

      • switch 语句可以让你利用穿透特性将多个 case 语句组合在一起,以共享相同的执行代码。

        示例:

        1. int grade = 2;
        2. switch (grade) {
        3. case 1:
        4. case 2:
        5. case 3:
        6. printf("你是初学者。\n");
        7. break;
        8. case 4:
        9. case 5:
        10. printf("你是中级玩家。\n");
        11. break;
        12. case 6:
        13. printf("你是高级玩家。\n");
        14. break;
        15. default:
        16. printf("未知等级。\n");
        17. }
        复制代码
      • switch 语句不直接支持范围计算,但可以通过一些技巧来实现对范围的处理。

        1. #include <stdio.h>
        2. int main() {
        3. int age = 25; // 假设这是要检查的年龄
        4. switch (age / 10) { // 将年龄除以 10 进行分组
        5. case 0: // 0-9岁
        6. printf("儿童\n");
        7. break;
        8. case 1: // 10-19岁
        9. printf("青少年\n");
        10. break;
        11. case 2: // 20-29岁
        12. printf("年轻人\n");
        13. break;
        14. case 3: // 30-39岁
        15. printf("中年人\n");
        16. break;
        17. default: // 40岁及以上
        18. printf("中老年人\n");
        19. }
        20. return 0;
        21. }
        复制代码

        小练习,判断下列两段代码中最终的i值和j值分别是多少

        1. int main() {
        2. int i = 1;
        3. switch (i) {
        4. case 1: {
        5. int j = 0;
        6. case 2: {
        7. if (!!j != 1) {
        8. i = !!i, i++;
        9. printf("j=%d\n", (j = 3, j++));
        10. break;
        11. }
        12. j = !!j, j++;
        13. printf("j=%d\n", j);
        14. break;
        15. }break;
        16. }
        17. }
        18. printf("i=%d\n", i);
        19. return 0;
        20. }
        复制代码
        1. int main() {
        2. int i = 2;
        3. switch (i) {
        4. case 1: {
        5. int j = 0;
        6. case 2: {
        7. if (!!j != 1) {
        8. i = !!i, i++;
        9. printf("j=%d\n", (j = 3, j++));
        10. break;
        11. }
        12. j = !!j, j++;
        13. printf("j=%d\n", j);
        14. break;
        15. }break;
        16. }
        17. }
        18. printf("i=%d\n", i);
        19. return 0;
        20. }
        复制代码

        第一段:

        1. j=3
        2. i=2
        复制代码

        第二段:

        1. j=2
        2. i=2
        复制代码
  • 3.2 循环语句

    条件语句可以嵌套使用,即在一个条件语句内部再放置其他条件语句。这使得代码能够处理更复杂的逻辑。

    • 3.2.1 for 循环

      for 循环是用于已知循环次数的情况。它包括三个部分:初始化、条件和更新。基本语法如下:

      1. for (初始化; 条件; 更新) {
      2. // 循环体
      3. }
      复制代码

      示例:

      1. for (int i = 0; i < 5; i++) {
      2. printf("当前值: %d\n", i);
      3. }
      复制代码

      应用场景: 适合处理数组、列表等数据结构的遍历。

      for循环中的条件可以省略,但省略条件不能省略分号

      1. for (;;) {
      2. // 无限循环的内容
      3. if (some_condition) {
      4. break; // 退出循环
      5. }
      6. }
      复制代码
    • 3.2.2 while 循环

      while 循环在每次循环开始时检查条件,如果条件为 true,则执行循环体。基本语法如下:

      1. while (条件) {
      2. // 循环体
      3. }
      复制代码

      示例:

      1. int count = 0;
      2. while (count < 5) {
      3. printf("当前计数: %d\n", count);
      4. count++;
      5. }
      复制代码

      应用场景: 当循环次数未知时,适合等待某个条件成立的场景。

      示例:

      1. char src[] = "helloworld";
      2. char* i = &src;
      3. while (*i++) {
      4. printf("%s\n", i - 1);
      5. }
      复制代码

      小练习,判断下面两份代码分别输出什么

      1. int i = 0;
      2. while (i = 1, i++) {
      3. if (i == 2) {
      4. printf("退出循环");
      5. break;
      6. }
      7. }
      8. printf("%d\n", i);
      复制代码
      1. int i = 0;
      2. while (i = 0, i++) {
      3. if (i == 1) {
      4. printf("退出循环");
      5. break;
      6. }
      7. }
      8. printf("%d\n", i);
      复制代码

      A.退出循环 2,退出循环 1 B.2,1 C.退出循环 2,1 D.2,退出循环 1

    • 3.2.3 do while 循环

      do while 循环与 while 循环类似,但 do while 循环会在条件检查之前至少执行一次循环体。基本语法如下:

      1. do {
      2. // 循环体
      3. } while (条件);
      复制代码

      示例:

      1. int count = 0;
      2. do {
      3. printf("当前计数: %d\n", count);
      4. count++;
      5. } while (count < 5);
      复制代码

      应用场景: 适用于至少需要执行一次的场景,例如获取用户输入并进行验证。

    • 3.2.4 break 和 continue 的使用

      • break 语句用于立即终止循环,跳出循环体。

      示例:

      1. for (int i = 0; i < 10; i++) {
      2. if (i == 5) {
      3. break; // 当 i 为 5 时,跳出循环
      4. }
      5. printf("当前值: %d\n", i);
      6. }
      复制代码
      • continue 语句用于跳过当前循环的剩余部分,并进入下一次循环。

      示例:

      1. for (int i = 0; i < 10; i++) {
      2. if (i % 2 == 0) {
      3. continue; // 如果 i 是偶数,跳过后面的代码
      4. }
      5. printf("当前值: %d\n", i); // 只打印奇数
      6. }
      复制代码

4. 函数

函数是程序设计中的一个基本概念,用于将特定的操作或逻辑封装起来,以便重用和组织代码。在C语言中,函数的使用能够使程序更加模块化、易于理解和维护。

  • 4.1 函数的定义与声明

    函数声明是在使用函数之前对其名称、返回类型及参数类型的声明。它告诉编译器这个函数的基本信息,但并不提供具体的实现。

    函数定义则是对函数的具体实现,包含了函数的主体和逻辑。

    函数声明的语法:

    1. return_type function_name(parameter_type1 parameter_name1, parameter_type2 parameter_name2, ...);
    复制代码

    函数定义的语法:

    1. return_type function_name(parameter_type1 parameter_name1, parameter_type2 parameter_name2, ...) {
    2. // 函数的具体实现
    3. }
    复制代码

    示例:

    1. #include <stdio.h>
    2. // 函数声明
    3. int add(int a, int b);
    4. // 函数定义
    5. int add(int a, int b) {
    6. return a + b;
    7. }
    8. int main() {
    9. int sum = add(5, 10); // 调用函数
    10. printf("Sum: %d\n", sum);
    11. return 0;
    12. }
    复制代码

    其中,函数的声明有多种方式:

    在C语言中,函数声明是对函数名称、返回类型和参数类型的说明,目的是让编译器知道函数的基本信息,以便在调用函数之前进行正确的类型检查。函数声明可以有几种不同的形式,以下是详细说明:

    4.1.1 基本函数声明

    基本的函数声明包括返回类型、函数名称和参数列表。参数列表可以为空,也可以包含一个或多个参数。

    语法:

    1. return_type function_name(parameter_type1 parameter_name1, parameter_type2 parameter_name2, ...);
    复制代码

    示例:

    1. int add(int a, int b); // 返回类型为int,函数名为add,接受两个int类型的参数
    复制代码

    其中:

    编译器对C代码是顺序编译的,而且总是从main函数开始。因此,如果自定义函数位于main函数后,则必须在main函数前先声明该函数(即调用之前先声明);但如果自定义函数位于main函数前,则不用再进行函数声明,此时函数定义已包含了函数声明的作用。

    其中:

    在定义函数时指定的形参,在为出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以形参里的变量不能赋值。

    4.1.2 不带参数的函数声明

    如果函数不接受任何参数,可以使用void关键字表示参数列表为空。这是C语言中表明函数不接收参数的一种方式。

    语法:

    1. extern return_type function_name(void);
    复制代码

    其中extern声明关键字可以省略,void关键字也可以省略

    示例:

    1. void printMessage(void); // 返回类型为void,表示该函数没有参数
    复制代码

    4.1.3 函数声明中的参数类型

    在函数声明中,参数名称可以省略,只保留参数类型。这样做的好处是能够更简洁地声明函数,但编写代码的人必须清楚函数的参数类型。

    语法:

    1. return_type function_name(parameter_type1, parameter_type2, ...);
    复制代码

    示例:

    1. float calculateArea(float, float); // 省略参数名称,仅保留参数类型
    复制代码

    4.1.4 函数指针的声明

    函数指针可以用来指向具有特定参数和返回类型的函数。声明函数指针时,首先定义返回类型,然后是指针标识符,最后是参数列表。

    语法:

    1. return_type (*pointer_name)(parameter_type1, parameter_type2, ...);
    复制代码

    示例:

    1. int (*operation)(int, int); // 声明一个指向返回int类型并接受两个int参数的函数的指针
    复制代码

    4.1.5 带有数组或指针参数的函数声明

    如果函数接受数组或指针作为参数,参数可以声明为数组的指针类型。对于数组参数,通常在声明时省略数组的大小。

    语法:

    1. return_type function_name(type_name[]); // 数组形式
    2. // 或
    3. return_type function_name(type_name*); // 指针形式
    复制代码

    示例:

    1. void processArray(int arr[], int size); // 数组作为参数
    2. void processPointer(int* ptr, int size); // 指针作为参数
    复制代码

    4.1.6 变长参数的函数声明

    C语言允许使用变长参数的函数,这类函数可以接收不定数量的参数。通常使用stdarg.h库来处理这类参数。声明时使用...表示参数的可变性。

    语法:

    1. return_type function_name(parameter_type1, ..., parameter_typeN, ...);
    复制代码

    示例:

    1. #include <stdarg.h>
    2. void myPrintf(const char* format, ...); // 变长参数的函数声明
    复制代码

    4.1.7 函数的类型定义

    可以使用typedef定义一个函数类型,以简化函数指针的声明。这样在需要多个地方使用相同类型的函数指针时,可以提高可读性。

    示例:

    1. typedef int (*FuncPtr)(int, int); // 定义一个函数指针类型
    2. FuncPtr add; // 使用定义的类型声明函数指针
    复制代码
  • 4.2 函数参数传递

    C语言中的函数参数可以通过两种方式传递:值传递和引用传递(指针传递)。

    • 4.2.1 值传递

      在值传递中,函数接收参数的副本,这意味着在函数内部对参数的修改不会影响原始数据。这种方法的优点是安全,但对于较大的数据结构(如数组或结构体),可能会导致性能问题,因为需要复制数据。

      示例:

      1. #include <stdio.h>
      2. void modifyValue(int num) {
      3. num = num + 10; // 修改副本
      4. }
      5. int main() {
      6. int original = 5;
      7. modifyValue(original);
      8. printf("Original: %d\n", original); // 输出仍为 5
      9. return 0;
      10. }
      复制代码
    • 4.2.2 引用传递(指针)

      引用传递通过指针传递参数,允许函数直接修改调用者的变量。这样可以提高效率,特别是在处理大型数据结构时。

      示例:

      1. #include <stdio.h>
      2. void modifyValue(int* num) {
      3. *num = *num + 10; // 通过指针修改原始数据
      4. }
      5. int main() {
      6. int original = 5;
      7. modifyValue(&original); // 传递原始变量的地址
      8. printf("Original: %d\n", original); // 输出为 15
      9. return 0;
      10. }
      复制代码
  • 4.3 递归函数

    递归函数是一个直接或间接调用自身的函数。递归通常用于解决具有重复结构的问题,如树遍历和数学计算(例如阶乘和斐波那契数列)。每个递归函数必须有一个基准条件,以防止无限递归。

    示例:计算阶乘

    1. #include <stdio.h>
    2. int factorial(int n) {
    3. if (n == 0) { // 基准条件
    4. return 1;
    5. }
    6. return n * factorial(n - 1); // 递归调用
    7. }
    8. int main() {
    9. int num = 5;
    10. printf("Factorial of %d: %d\n", num, factorial(num)); // 输出 120
    11. return 0;
    12. }
    复制代码
  • 4.4 函数指针

    函数指针是指向函数的指针,可以用来实现回调机制、动态函数调用等。通过函数指针,可以将函数作为参数传递,或者在运行时决定要调用哪个函数。

    定义函数指针的语法:

    1. return_type (*pointer_name)(parameter_type1, parameter_type2, ...);
    复制代码

    示例:

    1. #include <stdio.h>
    2. // 定义一个函数
    3. int add(int a, int b) {
    4. return a + b;
    5. }
    6. int subtract(int a, int b) {
    7. return a - b;
    8. }
    9. // 定义函数指针
    10. typedef int (*operation)(int, int);
    11. int main() {
    12. operation op; // 函数指针
    13. op = add; // 指向 add 函数
    14. printf("Add: %d\n", op(5, 10)); // 调用 add
    15. op = subtract; // 指向 subtract 函数
    16. printf("Subtract: %d\n", op(10, 5)); // 调用 subtract
    17. return 0;
    18. }
    复制代码
  • 4.5 随机数

    在C语言中,生成随机数通常使用rand()函数,该函数定义在stdio.h头文件中。rand()函数每次被调用时都会返回一个伪随机数,范围从0到RAND_MAX,RAND_MAX是一个定义在stdio.h中的常量,通常为32767。

    其中:如果想生成特定范围内的随机数,可使用取模运算符%来实现

    例:

5. 数组

数组是一种数据结构,可以存储固定数量的相同类型的元素。数组的元素可以通过索引进行访问,索引从零开始。

  • 5.1 一维数组

    定义和初始化

    一维数组是线性的数据结构,元素在内存中是连续存储的。可以通过以下方式定义和初始化一维数组:

    1. type arrayName [ arraySize ];
    复制代码

    其中:

    • type 可以是任意有效的 C 数据类型。
    • arrayName必须是一个有效的标识符
    • arraySize 必须是一个大于零的整数常量

    例:

    1. int myArray[10];// 声明数组 声明一个类型为int,包含10个元素的数组myArray
    2. myArray[0] = 1;// 定义数组元素(初始化数组元素)
    3. int myNewArray[10] = { 1, 2, 3, 4, 7, 5, 6, 8, 9, 0 };// 批量初始化数组(定义数组)
    复制代码

    访问数组元素

    数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:

    1. int salary = myNewArray[9];
    复制代码

    其中:

    • 数组名[下标值] 可以访问数组。
    • [数组名]下标值 也可以访问数组,此方法不推荐使用,此为C语言语法糖之一,后续指针部分会有讲解。
    1. #include <stdio.h>
    2. int main() {
    3. // 定义并初始化数组
    4. int arr[5] = {1, 2, 3, 4, 5};
    5. // 访问数组元素
    6. for (int i = 0; i < 5; i++) {
    7. printf("%d ", arr[i]);
    8. }
    9. return 0;
    10. }
    复制代码

    在C语言中,标识符(Identifier)是用来给变量、函数、数组、结构体等程序元素命名的字符序列。标识符遵循以下规则:

    1. 首字符:标识符的第一个字符必须是字母(大写或小写)或下划线(_)。
    2. 后续字符:后续的字符可以是字母、数字或下划线。
    3. 长度限制:标识符的长度可能会受到编译器的限制,但至少可以有31个字符。
    4. 大小写敏感:C语言是大小写敏感的,这意味着identifier和Identifier被视为两个不同的标识符。
    5. 保留字:不能使用C语言的保留字作为标识符,例如int、if、while等。
    6. 可读性:为了代码的可读性,标识符应该具有描述性,能够让人理解其代表的含义。

    例如,myVariable、counter1、_value、temp123都是有效的C语言标识符。而2variable(以数字开头)、my-variable(包含减号)和if(C语言保留字)则不是有效的标识符。

    数组的特点

    • 固定大小:数组在定义时需要指定大小,无法动态改变。
    • 元素类型一致:数组中的所有元素必须是相同类型。

    常用操作

    • 遍历数组:使用循环结构访问每个元素。
    • 求和/平均值:可以通过遍历数组实现。
    1. int sum = 0;
    2. for (int i = 0; i < 5; i++) {
    3. sum += arr[i];
    4. }
    5. float average = sum / 5.0;
    复制代码
  • 5.2 二维数组

    定义和初始化

    二维数组可以视为数组的数组,常用于表示矩阵。定义和初始化的方式如下:

    1. #include <stdio.h>
    2. int main() {
    3. // 定义并初始化二维数组
    4. int matrix[3][3] = {
    5. {1, 2, 3},
    6. {4, 5, 6},
    7. {7, 8, 9}
    8. };
    9. // 访问二维数组元素
    10. for (int i = 0; i < 3; i++) {
    11. for (int j = 0; j < 3; j++) {
    12. printf("%d ", matrix[i][j]);
    13. }
    14. printf("\n");
    15. }
    16. return 0;
    17. }
    复制代码

    二维数组的特点

    • 行和列:访问方式为 matrix[row][column]。
    • 内存布局:在内存中,二维数组是以行优先的方式存储的。

    常用操作

    • 矩阵加法:可以通过遍历两个矩阵实现相应位置元素的加法。
    • 转置矩阵:可以通过交换行和列来实现。
  • 5.3 多维数组

    定义和初始化

    多维数组是指具有两个以上维度的数组,常用于更复杂的数据结构,如三维数组等。以下是一个三维数组的示例:

    1. #include <stdio.h>
    2. int main() {
    3. // 定义并初始化三维数组
    4. int array[2][3][4] = {
    5. {
    6. {1, 2, 3, 4},
    7. {5, 6, 7, 8},
    8. {9, 10, 11, 12}
    9. },
    10. {
    11. {13, 14, 15, 16},
    12. {17, 18, 19, 20},
    13. {21, 22, 23, 24}
    14. }
    15. };
    16. // 访问三维数组元素
    17. for (int i = 0; i < 2; i++) {
    18. for (int j = 0; j < 3; j++) {
    19. for (int k = 0; k < 4; k++) {
    20. printf("%d ", array[i][j][k]);
    21. }
    22. printf("\n");
    23. }
    24. printf("\n");
    25. }
    26. return 0;
    27. }
    复制代码

    多维数组的特点

    • 维度任意:可以定义任意维度的数组,但需要考虑内存使用情况。
    • 访问方式:访问元素时需要指定所有维度的索引。
  • 5.4 数组与函数的结合

    数组可以作为函数的参数传递。C语言中,数组的实际传递是以指针的形式进行的,因此在函数内对数组的修改会影响原数组。

    1. void modifyArray(int arr[], int size) {
    2. for (int i = 0; i < size; i++) {
    3. arr[i] *= 2; // 将数组中的每个元素乘以2
    4. }
    5. }
    复制代码

    在主函数中,可以这样调用:

    1. int main() {
    2. int myArray[] = {1, 2, 3, 4, 5};
    3. int size = sizeof(myArray) / sizeof(myArray[0]);
    4. modifyArray(myArray, size);
    5. // myArray 现在变成 {2, 4, 6, 8, 10}
    6. return 0;
    7. }
    复制代码

    虽然C语言不支持直接返回数组,但可以通过返回指针来实现。通常使用动态内存分配来创建数组。

    1. int* createArray(int size) {
    2. int* arr = (int*)malloc(size * sizeof(int));
    3. for (int i = 0; i < size; i++) {
    4. arr[i] = i + 1; // 初始化数组
    5. }
    6. return arr;
    7. }
    复制代码

    注意,调用者需要负责手动释放这个动态分配的内存。

    1. int main() {
    2. int size = 5;
    3. int* myArray = createArray(size);
    4. // 使用 myArray
    5. free(myArray); // 释放内存
    6. return 0;
    7. }
    复制代码

    对于多维数组,可以通过在函数参数中明确维度来传递。

    1. void printMatrix(int matrix[][3], int rows) {
    2. for (int i = 0; i < rows; i++) {
    3. for (int j = 0; j < 3; j++) {
    4. printf("%d ", matrix[i][j]);
    5. }
    6. printf("\n");
    7. }
    8. }
    复制代码

    在主函数中,可以这样调用:

    1. int main() {
    2. int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    3. printMatrix(matrix, 2);
    4. return 0;
    5. }
    复制代码
  • 5.4 获取数组长度

    数组长度可以使用 sizeof 运算符来获取数组的长度,例如:

    1. int numbers[] = {1, 2, 3, 4, 5};
    2. int length = sizeof(numbers) / sizeof(numbers[0]);
    复制代码

    在 C 语言中,一个数组只能存储一种有效的数据类型,所以同一个数组里面的元素数据类型都相同,求数组大小的常见方法是使用 sizeof 运算符来计算数组的总字节数,然后除以单个数组元素的字节数,从而得到数组的元素个数。这是因为 sizeof 运算符返回的是对象(包括数组)的字节大小,而不是元素的个数。我们需要知道每个元素的大小才能正确计算数组的元素个数。

    1. sizeof(numbers)
      sizeof(numbers) 返回的是整个数组 numbers 占用的字节数。对于数组 numbers,其类型是 int[5],即一个包含 5 个整数的数组。假设 int 类型在你的机器上占用 4 个字节,那么:
    • sizeof(numbers) 将返回数组 numbers 的总字节数:
      sizeof(numbers) = 5 * sizeof(int) = 5 * 4 = 20 字节。
    1. sizeof(numbers[0])
      numbers[0] 是数组中的第一个元素,也就是 int 类型的一个整数。sizeof(numbers[0]) 返回的是单个数组元素的大小。
    • sizeof(numbers[0]) = sizeof(int) = 4 字节。
    1. 元素个数

    length = 20 / 4 = 5

    因此,数组 numbers 中包含 5 个元素。

    1. 注意事项
    • 仅适用于静态数组:这种方法只能用于静态数组(即在编译时就已知大小的数组)。对于动态数组(例如通过 malloc 分配的数组),这种方法是不可行的,因为 sizeof 对动态数组返回的是指针的大小,而不是数组的实际大小。对于动态数组,你需要使用额外的变量来跟踪数组的大小。

    例:

    1. int *numbers = malloc(5 * sizeof(int)); // 动态分配的数组
    2. int length = 5; // 必须手动维护数组的长度
    复制代码
    • 数组的类型:如果数组是多维数组,例如 int numbers[3][4];,sizeof(numbers) 将返回整个数组的字节数(3 * 4 * sizeof(int))。此时,sizeof(numbers[0]) 返回的是第一维的元素大小(4 * sizeof(int)),所以在这种情况下你需要进行适当的计算,或者直接使用 sizeof(numbers) / sizeof(numbers[0]) 来获取某一维的大小。

6. 字符串处理

字符串是由字符组成的数组,以 '\0'(空字符)结尾。C语言中的字符串处理非常灵活,但也需要注意数组的边界和内存管理。

  • 6.1 字符串的定义与初始化

    定义字符串

    在C语言中,字符串可以通过字符数组或字符指针定义。以下是两种常见的定义方式:

    1. #include <stdio.h>
    2. int main() {
    3. // 使用字符数组定义字符串
    4. char str1[20] = "Hello, World!"; // 预留足够空间
    5. // 使用字符指针定义字符串
    6. const char *str2 = "Hello, C!"; // 字符串常量
    7. // 输出字符串
    8. printf("%s\n", str1);
    9. printf("%s\n", str2);
    10. return 0;
    11. }
    复制代码

    注意事项

    • 字符数组需要有足够的空间存放字符串及结束符 '\0'。
    • 字符指针指向的字符串常量是只读的,不能修改。
  • 6.2 字符串操作函数

    C标准库提供了一些函数用于字符串操作,包含在头文件 string.h 中。

    • 6.2.1 字符串长度计算

      使用 strlen 函数计算字符串的长度(不包括 '\0')。

      1. #include <stdio.h>
      2. #include <string.h>
      3. int main() {
      4. char str[] = "Hello, World!";
      5. size_t length = strlen(str); // 计算字符串长度
      6. printf("Length of string: %zu\n", length);
      7. return 0;
      8. }
      复制代码
    • 6.2.2 字符串拷贝与连接

      • 字符串拷贝:使用 strcpy 函数将一个字符串复制到另一个字符串中。

      • 字符串连接:使用 strcat 函数将两个字符串连接起来。

        1. #include <stdio.h>
        2. #include <string.h>
        3. int main() {
        4. char src[] = "Hello";
        5. char dest[20]; // 目标字符串要足够大
        6. strcpy(dest, src); // 字符串拷贝
        7. printf("Copied string: %s\n", dest);
        8. strcat(dest, ", World!"); // 字符串连接
        9. printf("Concatenated string: %s\n", dest);
        10. return 0;
        11. }
        复制代码

        注意事项

        • 使用 strcpy 和 strcat 时,确保目标字符串有足够的空间以存放结果。
        • 可以使用 strncpy 和 strncat 来限制复制和连接的字符数。
    • 6.2.3 字符串比较

      使用 strcmp 函数比较两个字符串的内容,返回值指示字符串的关系:

      • 返回 0:两个字符串相等。
      • 返回 > 0:第一个字符串大于第二个字符串。
      • 返回 < 0:第一个字符串小于第二个字符串。
      1. #include <stdio.h>
      2. #include <string.h>
      3. int main() {
      4. char str1[] = "Hello";
      5. char str2[] = "Hello, World!";
      6. char str3[] = "hello";
      7. // 比较字符串
      8. int result1 = strcmp(str1, str2);
      9. int result2 = strcmp(str1, str3);
      10. int result3 = strcmp(str1, str1);
      11. printf("Comparison result (str1 vs str2): %d\n", result1); // < 0
      12. printf("Comparison result (str1 vs str3): %d\n", result2); // > 0
      13. printf("Comparison result (str1 vs str1): %d\n", result3); // 0
      14. return 0;
      15. }
      复制代码
    • 6.2.4 字符串的输入

    • 6.2.5 字符串打印

7. 指针

在学习指针之前,我们需要了解计算机中内存的概念

指针是C语言中的一个重要特性,它允许程序直接访问内存地址,极大地增强了程序的灵活性和效率。

  • 7.1 指针的定义与使用

    指针是一个变量,其值为另一个变量的地址。可以通过取地址运算符&获取变量的地址,通过解引用运算符*访问指针所指向的值。

    • 7.1.1 指针的定义
    1. int a = 10; // 定义一个整数变量
    2. int *p = &a; // 定义一个指向整数的指针,并将其初始化为a的地址
    复制代码
    • 7.1.2 指针的使用
    1. #include <stdio.h>
    2. int main() {
    3. int a = 10; // 整数变量
    4. int *p = &a; // 指针p指向a的地址
    5. printf("a的值: %d\n", a); // 输出: a的值: 10
    6. printf("p的值: %p\n", (void*)p); // 输出p的地址
    7. printf("*p的值: %d\n", *p); // 输出: *p的值: 10
    8. *p = 20; // 通过指针修改a的值
    9. printf("修改后的a的值: %d\n", a); // 输出: 修改后的a的值: 20
    10. return 0;
    11. }
    复制代码

    其中,指针还能够改变某些常量的值

    1. #include<stdio.h>
    2. int main() {
    3. const int a = 10;
    4. printf("常量a的值是:%d\n", a);
    5. int* p = &a;
    6. *p = 100;
    7. printf("常量a的值是:%d\n", a);
    8. printf("指针p的值是:%d\n", *p);// 打印指针 p 指向的值,即 a 的值
    9. return 0;
    10. }
    复制代码
  • 7.2 指针与数组的关系

    在C语言中,数组名实际上是一个指向数组第一个元素的指针。因此,数组可以通过指针进行操作。

    • 7.2.1 数组与指针

      1. #include <stdio.h>
      2. int main() {
      3. int arr[] = {1, 2, 3, 4, 5}; // 数组
      4. int *p = arr; // 数组名作为指针
      5. // 通过指针遍历数组
      6. for (int i = 0; i < 5; i++) {
      7. printf("arr[%d] = %d\n", i, *(p + i)); // 使用指针访问数组元素
      8. }
      9. return 0;
      10. }
      复制代码
  • 7.3 指针与函数

    指针可以作为函数参数,使得函数能够直接修改传入的变量。此外,函数也可以返回指针。

    • 7.3.1 指针作为函数参数

      使用指针作为参数,可以让函数修改调用者的变量。

      1. #include <stdio.h>
      2. void modifyValue(int *p) {
      3. *p = 100; // 修改指针所指向的值
      4. }
      5. int main() {
      6. int a = 10;
      7. printf("修改前: a = %d\n", a); // 输出: 修改前: a = 10
      8. modifyValue(&a); // 传递a的地址
      9. printf("修改后: a = %d\n", a); // 输出: 修改后: a = 100
      10. return 0;
      11. }
      复制代码
    • 7.3.2 返回指针

      函数可以返回指向动态分配内存的指针。注意,返回指向局部变量的指针是不安全的,因为局部变量在函数返回后会被销毁。

      1. #include <stdio.h>
      2. #include <stdlib.h>
      3. int* allocateArray(int size) {
      4. return (int*)malloc(size * sizeof(int)); // 动态分配内存
      5. }
      6. int main() {
      7. int *arr = allocateArray(5); // 分配数组
      8. for (int i = 0; i < 5; i++) {
      9. arr[i] = i + 1; // 初始化数组
      10. }
      11. for (int i = 0; i < 5; i++) {
      12. printf("%d ", arr[i]); // 输出: 1 2 3 4 5
      13. }
      14. free(arr); // 释放动态分配的内存
      15. return 0;
      16. }
      复制代码
  • 7.4 指针的高级使用

    • 7.4.1 指向指针

      指针可以指向另一个指针。这种用法在处理多级指针(如指向指针的指针)时非常有用。

      1. #include <stdio.h>
      2. int main() {
      3. int a = 10;
      4. int *p = &a; // 指向a的指针
      5. int **pp = &p; // 指向p的指针
      6. printf("a的值: %d\n", a); // 输出: 10
      7. printf("通过pp访问a: %d\n", **pp); // 输出: 10
      8. **pp = 20; // 通过指向指针的指针修改a的值
      9. printf("修改后的a的值: %d\n", a); // 输出: 20
      10. return 0;
      11. }
      复制代码
    • 7.4.2 动态内存分配

      动态内存分配使用malloc()、calloc()和free()等函数,可以在运行时分配和释放内存。这对于处理未知大小的数据结构(如链表和动态数组)非常重要。

      • 7.4.2.1 使用malloc()

        1. #include <stdio.h>
        2. #include <stdlib.h>
        3. int main() {
        4. int *arr = (int*)malloc(5 * sizeof(int)); // 动态分配数组
        5. if (arr == NULL) {
        6. printf("内存分配失败\n");
        7. return 1; // 处理内存分配失败
        8. }
        9. for (int i = 0; i < 5; i++) {
        10. arr[i] = i + 1; // 初始化数组
        11. }
        12. for (int i = 0; i < 5; i++) {
        13. printf("%d ", arr[i]); // 输出: 1 2 3 4 5
        14. }
        15. free(arr); // 释放内存
        16. return 0;
        17. }
        复制代码
      • 7.4.2.2 使用calloc()

        calloc()与malloc()类似,但它会初始化分配的内存。

        1. #include <stdio.h>
        2. #include <stdlib.h>
        3. int main() {
        4. int *arr = (int*)calloc(5, sizeof(int)); // 动态分配并初始化数组
        5. if (arr == NULL) {
        6. printf("内存分配失败\n");
        7. return 1;
        8. }
        9. for (int i = 0; i < 5; i++) {
        10. printf("%d ", arr[i]); // 输出: 0 0 0 0 0 (初始值为0)
        11. }
        12. free(arr); // 释放内存
        13. return 0;
        14. }
        复制代码

8. 结构体与联合体

概述:

在C语言中存在一种数据结构:

  • 数组:描述一组具有相同类型数据的有序集合,用于大量处理相同类型的数据运算。

但数组只能处理单一数据类型,所以C语言中给出了另一种构造数据类型——结构体

  • 8.1 结构体的定义与使用
    • 结构体变量的定义和初始化
      • 定义结构体变量:
        1. 先声明结构体类型再定义变量名。
    • 结构体类型和结构体变量的关系:
      • 结构体类型
      • 结构体变量
  • 8.2 嵌套结构体
  • 8.3 结构体与指针
    • 通过结构体指针操作堆空间
    • .与->的区别
  • 8.4 结构体做函数参数
    • 1.结构体普通变量做函数参数
    • 2.结构体指针变量做函数参数
    • 3.结构体数组名做函数参数
    • 4.const修饰结构体指针形参变量
  • 8.5 typedef关键字详解
  • 8.6 联合体(共用体)的定义与使用
  • 8.7 枚举

9. 文件操作

  • 9.1 文件的打开与关闭
  • 9.2 文件的读写操作
    • 9.2.1 文本文件
    • 9.2.2 二进制文件
  • 9.3 错误处理

10. C 语言标准库

  • 10.1 常用头文件介绍
    • 10.1.1 :输入输出库
    • 10.1.2 :标准库(内存分配、随机数等)
    • 10.1.3 :字符串处理函数
    • 10.1.4 :数学函数
  • 10.2 使用标准库函数的注意事项

11. 进阶主题

  • 11.1 动态内存管理
    • 11.1.1 malloc()、calloc()、realloc()、free()
  • 11.2 数据结构
    • 11.2.1 链表
    • 11.2.2 栈与队列
    • 11.2.3 树
    • 11.2.4 图
  • 11.3 预处理器指令
    • 11.3.1 宏定义
    • 11.3.2 条件编译
  • 11.4 多文件项目与头文件

12. 调试与优化

  • 12.1 常见错误与调试技巧
  • 12.2 性能优化方法
  • 12.3 代码规范与注释

13. 实践项目

  • 13.1 简单计算器
  • 13.2 学生信息管理系统
  • 13.3 猜数字游戏
  • 13.4 文件系统模拟
  • 13.5 数据结构实现(如链表、栈、队列)

14. 拓展学习

  • 14.1 C++ 和其他语言的对比
  • 14.2 C 语言在嵌入式系统中的应用
  • 14.3 C 语言在操作系统中的应用
  • 14.4 学习算法与数据结构的结合

15. 资源推荐

  • 15.1 书籍推荐
  • 15.2 在线学习平台
  • 15.3 社区与论坛
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Honkers

荣誉红客

关注
  • 4013
    主题
  • 36
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行