IsaacAlves7/cpp

GitHub: IsaacAlves7/cpp

一个系统性的 C/C++ 编程学习资源库,涵盖语言基础、编译原理、数据类型与指针等核心知识,帮助学习者掌握底层系统开发技能。

Stars: 0 | Forks: 0

# 这是一个关于 C/C++ 语言的代码库 🔵 - https://exercism.org/tracks/c - http://www.dpi.inpe.br/~carlos/Academicos/Cursos/LinguagemC/Cap_1.html - https://exercism.org/tracks/cpp - https://cplusplus.com/ - https://medium.com/@yangpeng-tech/basic-design-patterns-in-c-39bd3d477a5c?source=email-afeafff77325-1699595564672-digest.reader-755611695f5a-39bd3d477a5c----0-59------------------2c4d77a2_af61_4746_ba5e_d672f0567ad9-1 # 🐒 编程语言
如今,系统开发基于多种不同的范式,如下所示: - **命令式(过程式)**:遵循按照特定逻辑排序的命令序列。 - **函数式**:通过函数划分来处理问题,分别解决较小的子问题,然后组合起来解决整个问题。 - **逻辑式**:致力于逻辑问题的开发,常用于计算智能系统。 - **面向对象(OO)**:定义一组类来划分问题,并通过不同类之间的交互来共同解决整个问题。 # ⚫ C 语言 在计算机科学领域,一项困难的任务是说服学生:学习一门新的编程语言,或使用一门非他们首选的语言,在一门课程中是必要且必不可少的。当涉及到一门被某些人认为已经过时的语言(例如 **C 语言**)时,这项任务就变得更加困难。 有很多理由说明学习 C 语言是至关重要的。许多人甚至将其视为“所有编程语言之母”。 它最初被设计用于实现 **Unix** 操作系统,因此非常贴近底层操作系统。由于其在系统层面上出色的资源管理能力,它成为一种极其高效的语言。 另一个重要的点是,这门语言并不受限,而是被广泛应用于: - 操作系统。 - 语言编译器。 - 网络驱动程序。 - 语言解释器。 - 系统实用程序开发领域。 - 嵌入式系统。 C 语言的其他优势还包括: - **无处不在**:无论是什么平台,C 语言都很可能可用。 - **可移植**:C 语言程序只需极少的修改即可在其他平台上编译——有时甚至可以直接运行。 - **简单**:C 语言非常容易学习,几乎不需要依赖项。只需一台简单的 PC 和编译器,就可以准备好编写程序的一切。 ## [C] C 程序的结构与编译过程 **C** 被认为是一门中级语言,需要一个编译器来生成可执行代码,以便程序能在机器上运行。
编译 是将编写的源代码翻译成机器代码的过程。它由一种被称为 编译器 的专门软件完成,该软件会检查源代码是否存在任何语法或结构错误,并在源代码无误的情况下生成扩展名为 .obj(在 Windows 下)或 .o(在 Linux 下)的目标代码。
我们将使用 Windows 下的 MinGW 编译器。 整个编译过程分为四个阶段: 1. **预处理**。 2. **编译**。 3. **汇编**。 4. **链接**。 图 1 描述了 C 语言的整个编译过程。
图 1:程序的编译过程
**IDE**,即 **集成开发环境**(Integrated Development Environment),汇集了辅助软件开发的各种特性和工具,旨在通过点击按钮即可完成整个编译过程,从而加快开发速度。 示例: 我们可以通过在 Linux 下编译一个简单的 C 程序来详细说明这个过程,代码如下,文件名为 `compilacao.c`,它会在屏幕上输出 `Hello, World!`: ``` #include int main() { printf("Hello World!"); return 0; } ``` `#include` 命令用于包含库,而 `` 命令用于数据输入和输出; `main ()` 函数及其 `{}` 内部的 `printf ("Ola mundo \n");` 命令 运行该函数需要使用 `;` ! 要编译上述程序,需打开命令提示符并输入以下命令: ``` gcc -save-tempscompilacao.c -o compilacao ``` `-save-temps` 选项将保留并保存在 **C** 编译过程中创建的所有临时文件。它将在同一目录下生成四个文件: - compilacao.i(由预处理器生成)。 - compilacao.s(由编译器生成)。 - compilacao.o(由汇编器生成)。 - compilacao(在 Linux 下由链接器生成)或(在 Windows 下为 compilacao.exe) 现在,让我们了解编译过程中每个元素的作用: ## [C] 预处理器 **预处理器** 是一个小型软件,它接收 C 源文件并执行以下任务。 - 删除源代码中的注释。 - 展开包含的头文件。 - 预处理后生成一个扩展名为 `.i` 的临时文件。它将头文件的内容插入到源代码文件中。预处理器生成的文件比原始源文件更大。 ## [C] 编译器 在 C 编译的下一阶段,编译器开始工作。它接收由预处理器生成的临时预处理文件 nome_do_arquivo.i,并执行以下任务: - 检查 C 程序是否存在语法错误。 - 将文件翻译成中间代码,即汇编语言。 - 可选择对翻译后的代码进行优化,以获得更好的性能。 - 编译后生成汇编语言的中间代码,例如 `nome_do_arquivo.s`。它是源代码的汇编版本。 汇编器 (Assembler) 进入编译的下一阶段,**汇编器** 接收编译后的源代码(`nome_do_arquivo.s`)并将其翻译成低级的机器代码。成功汇编后,生成名为 `nome_do_arquivo.o`(在 Linux 下)或 `nome_do_arquivo.obj`(在 Windows 下)的文件,即所谓的 **目标文件**。在我们的例子中,生成的是 **compilacao.o** 文件。 链接器 (Linker) 最后,**链接器** 开始工作并执行编译过程的最后任务。它接收由汇编器生成的中间文件 `nome_do_arquivo.o`。它将所有函数调用与其原始定义链接起来。这意味着 `printf ()` 函数被链接到其原始定义。**链接器** 生成最终的可执行文件。 ## [C] 变量与数据类型 在编程中,**变量** 是用于存储数据的 **容器**(存储区域)。 为了指示存储区域,每个变量都必须被赋予一个唯一的名称(标识符)。**变量名** 只是内存位置的符号表示。 示例: ``` int resultado = 95; ``` 在这里,resultado 是一个 **整型** (`int`) 变量。我们为这个变量赋了一个整数值 `95`。 变量的值是可以改变的,如下所示。因此得名为 **变量**。 ``` char ch = 'a'; // algum código ch = 'l'; ``` 变量名可以包含字母(变量的首字母必须是字母)、数字和下划线符号 "_"。
注意! 关于变量名(标识符)的长度没有任何规定。然而,如果变量名超过 31 个字符,某些编译器可能会出现问题。
**C** 是一种强类型语言。这意味着变量类型一旦声明就不能更改。 示例: ``` intnumero = 5; // variável inteira numero = 5.5; // erro floatnumero ; // erro ``` 在这里,数值变量的类型是 `int`。您不能为此变量分配 **浮点数** (`5.5`) 值。此外,您也不能将变量类型重定义为 `float`。
顺便说一下,要在 C 中存储带有小数位的值,您需要将其类型声明为 doublefloat
## [C] 常量 **常量** 是值(或标识符)在程序中不能被改变。
1, 2.5, 'c' 等。 在这里,1、2.5 和 'c' 是字面常量。不能为这些术语分配不同的值。 constfloat PI = 3,14; 请注意,我们添加了关键字 const。 在这里,PI 是一个符号常量。实际上,它是一个变量,但是,它的值不能被改变。
常量的类型: 请看可以在 C 中使用的常量类型: - 整型常量。 - 浮点型常量。 - 字符型常量。 字符常量是通过将单个字符放在单引号中创建的。 例如:'a', 'm', 'F', '2', '}' 等。 - 转义序列 有时,需要使用无法直接键入或在 C 编程中具有特殊含义的字符。要使用这些字符,就需要使用转义序列。 例如:`\n` 用于换行。`\t` 用于水平制表符。反斜杠 (`\`) 用于脱离编译器处理字符的正常模式。 - 字符串字面量 **字符串字面量** 是用双引号括起来的字符序列。 示例: ``` "legal" // constante de string "" // constante de cadeia nula " " // constante de seis espaços em branco "A" // constante de string com caractere único "Resultado eh\n" // imprime string com nova linha ``` - 枚举 关键字 `enum` 用于定义枚举类型。 示例: ``` enum cor {amarelo, verde, preto, branco}; ```
在这里,cor 是一个变量,而 amarelo、verde、preto 和 branco 是枚举常量,它们的值分别为 0、1、2 和 3。
也可以使用 **#define** 关键字来定义符号常量。 ## [C] 数据类型与修饰符 C 语言中有 5 种基本数据类型:
char 字符型
## [C] `Hello, World` ``` # include int main(void){ printf("Hello, World!\n"); } ``` C 代码的编译: **输入**: ``` make main.c ./main ``` **输出**: ``` Hello, World! ``` ## [C++] 指针 FB_IMG_1727351809178 # 🔵 C++ 语言 **C++ 语言** 是一种编译型的多范式通用编程语言(支持命令式、面向对象和泛型编程)。自 20 世纪 90 年代以来,它一直是最流行的商业编程语言之一,并因其极高的性能和庞大的用户基础在学术界得到广泛应用。 Bjarne Stroustrup 于 1983 年在贝尔实验室开发了 C++(最初命名为 "C with Classes"),作为对 C 语言的扩展。随着时间的推移,加入了许多新特性,如虚函数、运算符重载、多重继承、模板和异常处理。在 1998 年完成 ISO 标准化和 2003 年的后续修订之后,新版本的语言规范于 2014 年 12 月发布,非正式地被称为 C++14。 C++ 是一种通用编程语言,广泛应用于软件开发、游戏、嵌入式系统和人工智能等多个领域。它是一种编译型的高级语言,允许对硬件进行更直接的控制,使其非常高效。 有趣的是,实际上 C++ 官方并没有一个名为 "Keith" 的官方吉祥物。C++ 与名为 "Keith" 的吉祥物之间的联系是网络恶搞或梗的结果,并未得到社区或语言创建者 Bjarne Stroustrup 的官方认可。 "Keith" 的想法源于 Reddit 或 4chan 等社交媒体和论坛上的一个玩笑,用户们在上面为编程语言创作虚构的吉祥物。像 Go(有地鼠)、Rust(有 Ferris 螃蟹)和 JavaScript(在梗图中有时被描绘成小丑)等语言启发了社区去为那些从未有过吉祥物的语言 "赋予" 吉祥物。 "Keith, the C++ Mascot"(Keith,C++ 吉祥物)就这样诞生了,通常被描绘成一个略显格格不入的人类角色,有时是一个沮丧的程序员,以此反映 C++ 作为一门强大但复杂、冗长且难以掌握的语言所背负的刻板印象。 ## [C++] `Hello, World!` ## [C++] 抽象类与接口 在 C++ 中,提到接口和抽象类时往往令人困惑。与 Java 或 C# 等其他编程语言不同,C++ 中没有用于定义接口和抽象类的特定关键字。 然而,在 C++ 中同样可以实现接口和抽象类的功能。 首先,让我们比较一下接口和抽象类的概念: 1. 接口类不包含任何方法实现。它只有方法声明,而实现接口的类负责实现这些方法。 2. 接口没有定义的变量。这在 Java 中是存在的,但变量必须被声明为 final 和 static。 3. 实现接口的类必须实现接口中的所有方法。 4. 抽象类可以包含变量声明以及方法的声明/实现。此外,可以在不实现抽象方法的情况下继承抽象类。 5. 抽象类不能被实例化,但可以由另一个类继承。实例化抽象类会导致编译错误。 6. 在探讨如何定义抽象和接口之前,让我们先了解一下 C++ 中的虚方法和纯虚方法。 C++ 中的虚方法是指必须在派生类中被重新定义的方法,通过使用 virtual 关键字来指示编译器对该方法执行动态绑定或晚期绑定。 ## [C++] 哈希与比较器 ## [C++] FCFS 调度算法 **FCFS (先来先服务)** 是计算机科学中最简单的调度算法。它的核心思想就是排队:最先到达的进程最先执行,没有抢占,没有优先级,也没有中断。它常用于教学,因为它清晰地展示了等待、周转时间和执行时间等概念,并且清楚地暴露了像 *护航效应* 这样的经典问题。 在 FCFS 中,每个进程至少包含两个相关数据:到达时间和执行时间。进程按到达时间排序。第一个进程在到达时(或 CPU 空闲时)开始执行,其余进程则等待直到前面所有的进程执行完毕。一个进程的等待时间是它在开始执行前在队列中等待的时间,而周转时间则是从到达时刻到执行结束的总时间。 简单的 C++ 示例: ``` #include #include #include using namespace std; struct Process { int id; int arrivalTime; int burstTime; int waitingTime; int turnaroundTime; }; int main() { int n; cout << "Numero de processos: "; cin >> n; vector processes(n); for (int i = 0; i < n; i++) { processes[i].id = i + 1; cout << "Processo " << processes[i].id << " - Tempo de chegada: "; cin >> processes[i].arrivalTime; cout << "Processo " << processes[i].id << " - Tempo de execucao: "; cin >> processes[i].burstTime; } // Ordena os processos pelo tempo de chegada sort(processes.begin(), processes.end(), [](const Process& a, const Process& b) { return a.arrivalTime < b.arrivalTime; }); int currentTime = 0; for (int i = 0; i < n; i++) { if (currentTime < processes[i].arrivalTime) { currentTime = processes[i].arrivalTime; } processes[i].waitingTime = currentTime - processes[i].arrivalTime; currentTime += processes[i].burstTime; processes[i].turnaroundTime = processes[i].waitingTime + processes[i].burstTime; } double totalWaitingTime = 0; double totalTurnaroundTime = 0; cout << "\nID\tChegada\tExecucao\tEspera\tRetorno\n"; for (const auto& p : processes) { cout << p.id << "\t" << p.arrivalTime << "\t" << p.burstTime << "\t\t" << p.waitingTime << "\t" << p.turnaroundTime << "\n"; totalWaitingTime += p.waitingTime; totalTurnaroundTime += p.turnaroundTime; } cout << "\nTempo medio de espera: " << totalWaitingTime / n << endl; cout << "Tempo medio de retorno: " << totalTurnaroundTime / n << endl; return 0; } ``` 这段代码在概念上的作用是:首先,它读取带有到达时间和执行时间的进程。然后,按到达时间对进程进行排序,这是 FCFS 的基本规则。之后,模拟 CPU 的时间流逝,计算每个进程在执行前的等待时间以及从到达到结束所花费的总时间。最后,打印出包含结果的表格并计算平均值。 该算法是确定性的,在到达顺序的意义上是公平的,但在实际环境中效率低下。一个早到达的耗时长的进程可能会延误所有其他进程(即使它们很短),这很好地说明了为什么现代系统使用更复杂的算法,如 SJF、Round Robin、Priority Scheduling 或混合变体。 示例:**带有甘特图的 FCFS**、**CPU 空闲模拟**,或者处于教学目的直接将其与 **SJF 和 Round Robin** 进行比较。 ## [C++] 异常处理 # 💻 [C++] 数字系统 IT 和通信领域时刻都在发生改变、创新和调整;总之,它们对用户来说变得越来越有趣,对构建它们的专业人士来说则充满了挑战。因此,培养能够构思、规划和开发将在未来信息技术和通信(TIC)领域中运行的解决方案的团队,是 IT 领域相关课程的迫切需求,也是对教师在计算教学法实践方面的一项挑战。 数字电路被定义为一种电子电路,它利用仅有两个电流(或电压)水平的电信号来表示二进制数值。研究逻辑电路作为数字系统研究基础的重要性极为重大,因为它们是当今计算机以及各个领域中使用的大量设备和仪器的基础电路。 数字电路或逻辑电路被定义为利用仅有两个电流(或电压)水平的电信号来表示二进制数值的电子电路。[1] 逻辑电路的运行基于二进制逻辑,即所有信息都必须以两位数字的形式表达(无论是存储还是处理),这些数字是 `0` 或 `1`。由此产生了 "数字"(两个数字)的名称。这一特点有助于表示在两个不同水平上运行的设备状态,例如:开/关、高/低、真/假等。 计算机、手机以及 DVD 或蓝光播放器就是部分基于数字电路运行的设备示例。 # 🦾 [C++] 微控制器编程 ![JS](https://img.shields.io/badge/tinkercad-Arduino-tomato?style=flat&logo=tinkercad&logoColor=white) ![JS](https://img.shields.io/badge/Arduino-cheatsheet-blue?style=flat&logo=Arduino&logoColor=white) ![JS](https://img.shields.io/badge/RaspberryPi-cheatsheet-red?style=flat&logo=RaspberryPi&logoColor=white) ![JS](https://img.shields.io/badge/IoT-cheatsheet-gold?style=flat&logo=icloud&logoColor=white) ![JS](https://img.shields.io/badge/Digital_Systems-Cpp-green?style=flat&logo=GitHub&logoColor=white) ![JS](https://img.shields.io/badge/Machine_Learning-Cpp-indigo?style=flat&logo=microbit&logoColor=white) **嵌入式系统**代表了现代最重要和最通用的技术之一,它使设备能够在虚拟环境中智能且高效地执行特定任务。得益于这项技术,可编程代码可以被集成到从数字手表等简单设备到飞机上的先进系统等各种各样的设备中。 嵌入式系统由集成在电子设备中的 **微处理器** 上编写的程序代码所构成的计算机解决方案组成。这些系统被设计用于执行专用功能,通常作为更大系统的一部分运行。嵌入式系统的核心是微控制器——一种集成了处理器、内存和可编程外设的集成电路芯片,用于实时控制设备。 这些系统的复杂性根据其设计的目标大小和任务而有所不同。操作指令被称为 固件,存储在 ROM 或 flash 存储器中,以确保性能一致且可靠。 **微控制器**(也称为 MCU,即 Microcontroller Unit)是一种小芯片,在单个集成电路中包含了一整台微型计算机。 *微控制器编程* 涉及编写代码,使这些内部微型计算机能够控制电子设备,从工业系统到智能手机等日常设备。这种编程对于各种应用和系统的运行至关重要,是在自动化、电子和物联网等领域创建解决方案的关键。 在计算机领域,**Arduino** 是一系列带有集成组件的单板微型计算机。它是一系列用于电子原型设计(用于测试和电子项目)的单板、自由硬件(开源)可编程平台,允许用户使用带有内置输入/输出支持的 Atmel AVR 或 ARM 微控制器,创建交互式且独立的电子对象。该平台于 2005 年在意大利创建,旨在为初学者、业余爱好者和专业人士(特别是那些无法使用复杂控制器和工具的人)创建低成本、易于获取、灵活、独立且易于使用的工具。[10] 该平台目前由意大利公司 Smart Projects 以及美国公司 SparkFun Electronics 制造。 Arduino 是一款开源电子原型设计板和自由硬件。它和 Raspberry Pi 是最常用的两个原型设计平台。它由 Atmel 微控制器、输入输出电路和通过 IDE(集成开发环境)进行编程的组件构成。其软件使用基于 C/C++ 的语言,通过使用 Java 编写的图形化环境进行开发。因此,Arduino 编程除了 USB 数据线外不需要额外的设备。由于这些特点,它允许根据每个用户的需求进行无限的修改。 # 🖥️ [C++] QT **QT** 是一个跨平台的工具包,它结合了软件开发框架和高度高效的图形用户界面 (GUI),被广泛用于创建具有现代且响应式用户界面的应用程序。QT 最初由挪威公司 Trolltech 开发,现由 The Qt Company 维护,以其灵活性、性能以及在无需重写代码的情况下即可开发出能在 Windows、macOS、Linux、Android 和 iOS 等多种操作系统上运行的应用程序的便捷性而脱颖而出。简而言之,QT 是一个强大且通用的软件开发解决方案,使开发者能够使用单一代码库创建功能强大、美观且可在多平台上运行的应用程序。在那些要求跨设备和操作系统具有高性能、灵活性和一致性的行业中,它一直是一个受欢迎的选择。 QT 的主要吸引力之一是其面向对象的方法,该方法主要在 C++ 中实现。它提供了一套全面的库和 API,简化了图形组件开发、事件处理、数据库访问、网络甚至多线程处理的过程。此外,其信号与槽机制的集成简化了软件不同组件之间的通信,使代码更加模块化和有组织。 QT 的另一个显著特点是其与 Python 等脚本语言的兼容性(通过 PyQt 或 PySide),这使得它更容易受到那些希望学习曲线更平缓的开发者的青睐。其丰富的文档和活跃的社区也是为初学者和老手提供项目支持或额外资源的巨大优势。 QT 在嵌入式应用程序开发领域也大放异彩,特别是在物联网设备、工业自动化和汽车系统方面,这得益于其轻量级的性能以及在硬件受限的情况下依然能创建高质量图形界面的能力。其工具包括 Qt Creator,这是一个集成开发环境 (IDE),可简化界面的视觉设计、原型制作和调试。 尽管广受赞誉,但 QT 也面临一些挑战。其商业授权可能非常昂贵,特别是对于小公司或个人开发者,尽管在 LGPL 许可下有一个开源版本。对于不熟悉 C++ 或高级框架的人来说,最初的学习难度也可能是一个障碍。 # 🧪 [C++] DDD、BDD 与 TDD 可以在 **C++** 中应用 **DDD(领域驱动设计)**、**TDD(测试驱动开发)** 和 **BDD(行为驱动开发)**,尽管与那些更具动态性或拥有更现代的测试和建模支持的语言相比,这些实践都需要付出额外的努力。C++ 并不以容易实现这些方法而闻名,但只要具备良好的组织、优秀的库和严格的纪律,这完全是可行的。C++ 不像 Python、JavaScript 或 C# 那样提供开箱即用的支持,但借助 **Google Test**、**Catch2** 等工具和良好的设计实践,您完全可以有效地应用 TDD、BDD 和 DDD。这需要更多的组织架构和思考,但结果将是一个更可靠、可测试且设计良好的系统。 在 C++ 中应用 **TDD(测试驱动开发)** 在工业项目中非常普遍,尤其是在可靠性和安全性至关重要的场合(例如:嵌入式系统、游戏、实时系统)。TDD 的工作流程是一样的:**编写一个失败的测试,编写最少的代码使其通过,然后重构**。C++ 拥有多个测试库,例如: * [Google Test (gtest)](https://github.com/google/googletest):最常用、最成熟,且文档丰富。 * Catch2:较新,且语法更接近 BDD。 * Boost.Test:Boost 库的一部分,但相对较重。 CMake 是在真实的 C++ 项目中应用 TDD、BDD 和一般自动化测试的关键纽带。它允许集成诸如 Google Test 之类的测试工具、组织构建流程,甚至可以根据环境来控制测试的执行。如果没有它,管理依赖项和编译测试将成为一场噩梦。 编译非常简单,只需在仓库根目录下输入以下内容: ``` cmake -B build cmake --build build --config release ``` 使用 Google Test 的简单示例: ``` #include int soma(int a, int b) { return a + b; } TEST(SomaTest, SomaDoisValores) { EXPECT_EQ(soma(2, 3), 5); } ``` 在 C++ 中应用 **BDD(行为驱动开发)** 更具挑战性,但也是可行的。BDD 的核心理念是使用自然语言的高级规范来描述系统预期行为。在 C++ 中,像 **Catch2** 这样的框架允许使用类似 Gherkin 风格(Given/When/Then)的语法,尽管无法像 Python/JS 那样直接解析 `.feature` 文件。 Catch2 示例: ``` SCENARIO("Somar dois números", "[soma]") { GIVEN("Dois números positivos") { int a = 2; int b = 3; WHEN("Eu somo esses dois números") { int resultado = a + b; THEN("O resultado deve ser 5") { REQUIRE(resultado == 5); } } } } ``` 如果您想更原生态地使用 Gherkin(带有 `.feature` 文件),可以使用像 [Cucumber-cpp](https://github.com/cucumber/cucumber-cpp) 这样的库,它允许与 Cucumber 集成并使用 `.feature` 文件,但这需要更多的配置。 在 C++ 中应用 **DDD(领域驱动设计)** 是可能的,但语言的特性(更贴近底层硬件,较少侧重于领域建模)需要付出更大的架构设计努力。您完全可以通过使用 DDD 模式来构建您的项目: * **实体**:具有持久标识的类,如 `class Cliente { ... };` * **值对象**:具有值语义的不可变类型,如 `struct Dinheiro { int valor; string moeda; };` * **仓储**:用于持久化的接口(`interface ClienteRepository`),可以使用数据库、文件等实现。 * **聚合根**、**领域服务**、**工厂** 等,只要具备良好的建模和封装,都是可以实现的。 最大的困难在于缺乏原生语言结构的鼓励。但是,通过 **良好的封装实践**、**正确使用 headers 和 namespaces** 以及 **模块化设计**,在健壮的 C++ 项目中完全有可能应用 DDD。
标签:C++, Gophish, 客户端加密, 教程资源, 数据擦除, 数据结构与算法, 编程学习