C 语言编程如何工作

The C programming language gives you more versatility than many other languages, including greater control over your computer's memory.
C 编程语言比许多其他语言提供更多通用性,包括对计算机内存的更大控制。
iStockphoto/Thinkstock

C 编程语言非常流行,原因显而易见。C 语言编程高效且赋予程序员很大的控制权。许多其他编程语言如 C++、Java 和 Python 都是使用 C 开发的。

如今,如果你是一名程序员,你很可能不会只使用 C 语言进行工作。然而,学习 C 语言有诸多益处,即使你并不经常使用它。原因如下:

广告

你将能够为各种计算机平台(从小型微控制器到桌面、笔记本电脑和移动操作系统)读写软件代码。

你将更好地理解高级语言在幕后是如何工作的,例如内存管理和垃圾回收。这种理解可以帮助你编写更高效的程序。

如果你是一名信息技术(IT)专家,学习 C 语言也会受益匪浅。IT 专业人员通常需要编写、维护和运行脚本。脚本是计算机操作系统遵循的一系列指令。为了运行某些脚本,计算机会设置一个受控的执行环境,称为shell。由于大多数操作系统运行基于 C 的 shell,因此 C shell 是 IT 专业人员常用的一种 C 语言脚本改编。

本文涵盖了 C 语言的历史,探讨了 C 语言的重要性,展示了一些基本的 C 代码示例,并深入探讨了 C 语言的一些重要特性,包括数据类型、操作、函数、指针和内存管理。尽管本文不是 C 语言编程的指导手册,但它以一种超越普通 C 编程指南前几章的方式,介绍了 C 语言的独特性。

我们首先来看看 C 编程语言的来源、它是如何发展的以及它在当今软件开发中的作用。

广告

什么是 C 语言?

定义 C 语言最简单的方法是称它为一种计算机编程语言,这意味着你可以用它编写计算机可以执行的软件。结果可能是一个大型计算机应用程序,比如你的网页浏览器,也可能是一组嵌入在微处理器或其他计算机组件中的微小指令。

C 语言是在 20 世纪 70 年代早期在贝尔实验室开发的,主要归功于 Ken Thompson 和 Dennis Ritchie 的工作。程序员需要一套对 UNIX 操作系统更友好的指令,而当时 UNIX 操作系统需要用汇编语言编写程序。汇编程序直接与计算机硬件对话,它们冗长难调试,并且添加新功能需要繁琐耗时的工作[来源:King]。

广告

Thompson 首次尝试的高级语言名为 B,这是为了向其所基于的系统编程语言 BCPL 致敬。当贝尔实验室购置了一台 Digital Equipment Corporation (DEC) UNIX 系统 PDP-11 模型时,Thompson 对 B 进行了改造,以更好地适应更新、更好的系统硬件的需求。于是,B 的继任者 C 诞生了。到 1973 年,C 已经足够稳定,以至于 UNIX 本身都可以用这种创新的新高级语言重写[来源:King]。

在 C 语言能在贝尔实验室之外有效使用之前,其他程序员需要一份文档来解释如何使用它。1978 年,由 Brian Kernighan 和 Dennis Ritchie 合著的《C 编程语言》(C 爱好者称之为 K&R 或“白皮书”)成为 C 编程的权威来源。截至本文撰写之时,K&R 的第二版(最初于 1988 年出版)仍然广泛可用。原始的、非标准版本的 C 语言根据该书被称为 K&R C。

为了确保人们不会随着时间的推移创建自己的方言,C 语言的开发者在 20 世纪 80 年代努力为该语言创建标准。美国 C 语言标准,美国国家标准协会 (ANSI) 标准 X3.159-1989,于 1989 年正式生效。国际标准化组织 (ISO) 标准 ISO/IEC 9899:1990 于 1990 年发布。K&R 之后的 C 语言版本参考了这些标准及其后来的修订版(C89、C90 和 C99)。你也可能看到 C89 被称为“ANSI C”、“ANSI/ISO C”或“ISO C”。

C 语言及其在 UNIX 中的应用只是 20 世纪 80 年代操作系统开发热潮的一部分。尽管 C 语言在其前身之上做了许多改进,但对于开发大型软件应用程序而言,它仍然不够轻松。随着计算机变得越来越强大,对更简便编程体验的需求也随之增加。这种需求促使程序员使用 C 语言构建自己的编译器,从而创建自己的新编程语言。这些新语言可以简化复杂任务(包含大量活动部件)的编码。例如,C++ 和 Java 等语言都源自 C 语言,它们简化了面向对象编程,这是一种优化程序员代码重用能力的编程方法。

现在你对背景有了一点了解,接下来让我们看看 C 语言本身的机制。

广告

编辑和编译 C 代码

C 语言是一种所谓的编译型语言,这意味着你必须使用编译器将代码转换成可执行文件才能运行它。代码被写入一个或多个文本文件,你可以用任何文本编辑器(例如 Windows 上的记事本、Mac 上的 TextEdit 和 Linux 上的 gedit)打开、读取和编辑这些文件。可执行文件是计算机可以运行(执行)的东西。编译器会检查代码中的错误,如果代码看起来没有错误,就会创建一个可执行文件。

在我们查看 C 代码的内容之前,请确保我们能够找到并使用 C 编译器。如果您使用的是 Mac OS X 和大多数 Linux 发行版(如 Ubuntu),如果您安装了该特定操作系统的开发工具软件,则可以将 C 编译器添加到您的计算机。这些免费的 C 编译器是命令行工具,这意味着您通常会从终端窗口的命令提示符下运行它们。运行这些 C 编译器之一的命令是“cc”或“gcc”以及一些命令行选项和参数,这些是您按下回车键之前在命令后输入的其他单词。

广告

如果您使用的是 Microsoft Windows,或者您更喜欢使用图形用户界面而不是命令行,您可以安装一个 C 编程集成开发环境 (IDE)。IDE 是一个单一的界面,您可以在其中编写代码、编译、测试并快速查找和修复错误。对于 Windows,您可以购买 Microsoft Visual C++ 软件,这是一个用于 C 和 C++ 编程的 IDE。另一个流行的 IDE 是 Eclipse,一个免费的基于 Java 的 IDE,可在 Windows、Mac 和 Linux 上运行,并提供用于编译 C 和许多其他编程语言的扩展。

对于 C 语言,以及其他计算机编程语言,您使用的编译器版本非常重要。您总是希望使用与您程序中使用的 C 语言版本相同或更新的 C 编译器版本。如果您正在使用 IDE,请务必调整您的设置,以确保 IDE 正在为您的工作程序使用目标 C 版本。如果您在命令行中,可以通过添加命令行参数来更改版本,如下所示:

gcc –std c99 –o myprogram.exe myprogram.c

在上面的命令中,“gcc”是运行编译器的调用,其余的都是命令行选项或参数。添加了“-std”选项,后面跟着“c99”,以告诉编译器在编译过程中使用 C99 标准版本的 C 语言。添加了“-o”选项,后面跟着“myprogram.exe”,以请求将可执行文件(编译器的输出文件)命名为 myprogram.exe。如果没有“-o”,可执行文件将自动命名为 a.out。最后一个参数“myprogram.c”表示包含要编译的 C 代码的文本文件。简而言之,这个命令的意思是:“嘿,gcc,使用 C99 C 编程标准编译 myprogram.c 并将结果放入名为 myprogram.exe 的文件中。”请在网上搜索您的特定编译器(无论是 gcc 还是其他编译器)可以使用的完整选项列表。

安装好编译器后,您就可以开始用 C 语言编程了。让我们先来看看您可以编写的最简单的 C 程序之一的基本结构。

广告

最简单的 C 程序

让我们来看一个简单的 C 程序,并用它来理解 C 语言的基础知识和 C 编译过程。如果您有自己的计算机,并安装了前面所述的 C 编译器,您可以创建一个名为 sample.c 的文本文件,并用它来跟随我们逐步完成这个示例。请注意,如果您在文件名中省略了 .c,或者如果您的编辑器在名称后附加了 .txt,那么在编译时您可能会遇到某种错误。

这是我们的示例程序:

广告

/* 示例程序 */

#include <stdio.h>

int main()

{

printf("这是我第一个程序的输出!\n");

return 0;

}

编译并执行后,该程序指示计算机打印出“这是我第一个程序的输出!”然后停止。没有比这更简单的了!现在让我们来看看每一行都在做什么:

第 1 行——这是在 C 语言中编写注释的一种方式,位于 /* 和 */ 之间,可以跨一行或多行。

第 2 行——`#include` 命令告诉编译器去其他来源查找已有的 C 代码,特别是库,库是包含常见可重用指令的文件。`<stdio.h>` 引用了一个标准 C 库,其中包含用于从用户获取输入和将输出写入屏幕的函数。我们稍后将更详细地探讨库。

第 3 行——这是函数定义的第一行。每个 C 程序至少有一个函数,或者表示程序运行时计算机应该做什么的代码块。函数执行其任务,然后产生一个副产品,称为返回值,可以被其他函数使用。程序至少有一个名为 main 的函数,如这里所示,其返回值为 int 数据类型,表示整数。当我们稍后更深入地研究函数时,您将看到空括号的含义。

第 4 行和第 7 行——函数中的指令用花括号括起来。有些程序员将花括号括起来的块的开始和结束放在单独的行上,如这里所示。其他人会将开花括号 ({) 放在函数定义的第一行的末尾。虽然程序中的代码行不一定非要写在单独的行上,但程序员通常会将每条指令放在单独的行上,并用空格缩进,以便于以后阅读和编辑代码。

第 5 行——这是一个对名为 printf 的函数的函数调用。该函数在第 1 行包含的 stdio.h 库中编码,因此您不必自己编写它。这个对 printf 的调用告诉它要打印什么到屏幕上。\n 在末尾,在引号内,不会被打印;它是一个转义序列,指示 printf 将光标移动到屏幕上的下一行。此外,正如您所看到的,函数中的每一行都必须以分号结尾。

第 6 行——每个返回值的函数都必须包含像这样的 `return` 语句。在 C 语言中,`main` 函数必须始终具有整数返回类型,即使它在程序中未使用。请注意,当您运行 C 程序时,您实际上是在运行其 `main` 函数。因此,在测试程序时,您可以告诉计算机显示运行程序的返回值。返回值为 0 更好,因为程序员在测试中通常会查找该值,以确认程序成功运行。

当您准备测试程序时,保存文件并编译运行程序。如果您在命令行使用 gcc 编译器,并且程序在名为 sample.c 的文件中,您可以使用以下命令编译它:

gcc -o sample.exe sample.c

如果代码中没有错误,运行此命令后,您应该在与 sample.c 相同的目录中找到一个名为 sample.exe 的文件。最常见的错误是语法错误,这意味着您输入错误,例如在一行末尾缺少分号或没有关闭引号或括号。如果需要进行更改,请在文本编辑器中打开文件,修复它,保存更改,然后再次尝试编译命令。

要运行 sample.exe 程序,请输入以下命令。请注意 ./,它强制计算机在当前目录中查找可执行文件:

./sample.exe

以上是 C 语言编码和编译的基础知识,尽管您可以从其他 C 编程资源中学到更多关于编译的知识。现在,让我们打开盒子,看看 C 语言有哪些构建程序的组件。

广告

C 语言中的常见编程概念

让我们来看看如何在 C 代码中实践一些常见的编程概念。以下是这些概念的快速总结:

函数——如前所述,函数是程序运行时计算机应该执行的某个操作的代码块。有些语言将这些结构称为方法,但 C 程序员通常不使用该术语。您的程序可以定义多个函数,并从其他函数中调用这些函数。稍后,我们将更深入地研究 C 语言中函数的结构。

广告

变量——当你运行程序时,有时你需要灵活性,以便在不知道值的情况下运行程序。像其他编程语言一样,C 语言允许你在需要这种灵活性时使用变量。就像代数中的变量一样,计算机编程中的变量是一个占位符,代表你不知道或尚未找到的某个值。

数据类型——为了在程序运行时将数据存储在内存中,并知道可以对该数据执行哪些操作,像 C 这样的编程语言定义了它将识别的某些数据类型。C 语言中的每种数据类型都有特定的大小(以二进制位或字节衡量),以及关于其位表示什么的特定规则。接下来,我们将看到在使用 C 语言时,为任务选择正确的数据类型是多么重要。

操作——在 C 语言中,你可以对数字执行算术运算(如加法),对字符串执行字符串操作(如连接)。C 语言还内置了专门为处理数据而设计的操作。当我们查看 C 语言中的数据类型时,我们也会简要介绍一下这些操作。

循环——程序员最基本的需求之一是根据程序运行过程中出现的某些条件重复执行某个动作一定次数。根据给定条件重复执行的代码块称为循环,C 语言提供了以下常见的循环结构:`while`、`do/while`、`for`、`continue/break` 和 `goto`。C 语言还包括常见的 `if/then/else` 条件语句和 `switch/case` 语句。

数据结构——当你的程序需要处理大量数据,并且你需要对这些数据进行排序或搜索时,你可能会使用某种数据结构。数据结构是表示相同数据类型的多条数据的一种结构化方式。最常见的数据结构是数组,它只是一个给定大小的索引列表。C 语言有可用的库来处理一些常见的数据结构,尽管你也可以编写函数并设置自己的结构。

预处理器操作——有时您会希望在将代码编译成可执行文件之前,给编译器一些关于如何处理代码的指令。这些操作包括替换常量值和包含 C 库中的代码(您在前面的示例代码中已经看到)。

C 语言还要求程序员处理一些许多编程语言已经简化或自动化的概念。这些包括指针、内存管理和垃圾回收。后面的页面将介绍在 C 语言编程时需要了解的关于这些概念的重要事项。

如果您还不是程序员,这个概念的快速概述可能会让您感到不知所措。在您继续深入研究 C 编程指南之前,让我们以用户友好的方式了解上述核心概念,从函数开始。

广告

C 语言中的函数

大多数计算机编程语言都允许您创建某种函数。函数允许您将一个长程序切割成命名部分,以便您可以在程序的整个过程中重用这些部分。某些语言的程序员,特别是那些使用面向对象编程技术的程序员,使用术语方法而不是函数

函数接受参数并返回结果。构成函数的代码块是其函数定义。以下是函数定义的基本结构:

广告

<返回类型> <函数名>(<参数>)

{

<语句>

return <适合返回类型的值>;

}

至少,一个 C 程序有一个名为 main 的函数。编译器将把 main 函数作为程序的起点,即使 main 函数在其内部调用其他函数。以下是我们在前面看到的简单 C 程序中的 main 函数。它有一个整数返回类型,不接受任何参数,并且有两个语句(函数内的指令),其中一个是它的 return 语句:

int main()

{

printf("这是我第一个程序的输出!\n");

return 0;

}

除 main 之外的函数都有一个定义和一个或多个函数调用。函数调用是另一个函数中的一个语句或语句的一部分。函数调用命名它所调用的函数,后面跟着括号。如果函数有参数,函数调用必须包含相应的值来匹配这些参数。函数调用的这个附加部分称为向函数传递参数。

但什么是参数呢?函数的参数是某种数据类型的一段数据,函数需要它来完成工作。C 语言中的函数可以接受无限数量的参数,有时也称为实参。添加到函数定义中的每个参数都必须指定两件事:它的数据类型和它在函数块中的变量名。多个参数用逗号分隔。在下面的函数中,有两个参数,都是整数:

int doubleAndAdd(int a, int b)

{

return ((2*a)+(2*b));

}

接下来,让我们通过放大来继续研究函数,看看它们如何融入更大的 C 程序。

广告

函数原型

在 C 语言中,你可以在程序的任何位置(除了另一个函数内部)添加函数定义。唯一的条件是你必须提前告诉编译器该函数稍后在代码中存在。你将在程序的开头用函数原型来完成这一点。原型是一个类似于函数定义第一行的语句。在 C 语言中,你不需要在原型中给出参数的名称,只需要数据类型。下面是 doubleAndAdd 函数的函数原型示例:

int doubleAndAdd(int, int);

广告

把函数原型想象成你程序的打包清单。编译器会像你拆箱并组装新书架一样,拆箱并组装你的程序。打包清单可以帮助你在开始组装书架之前,确保箱子里有所有需要的部件。编译器在开始组装你的程序之前,也以同样的方式使用函数原型。

如果您正在跟随我们前面讨论的 sample.c 程序,请打开并编辑文件,为此处所示的 doubleAndAdd 函数添加函数原型、函数定义和函数调用。然后,像以前一样编译并运行您的程序,看看新代码是如何工作的。您可以使用以下代码作为指导进行尝试:

#include <stdio.h>

int doubleAndAdd(int, int);

int main()

{

printf("这是我第一个程序的输出!\n");

printf("如果您将 2 和 3 加倍再相加,结果是:%d \n", doubleAndAdd(2,3));

return 0;

}

int doubleAndAdd(int a, int b)

{

return ((2*a)+(2*b));

}

到目前为止,我们已经了解了 C 程序中的一些基本结构元素。现在,让我们看看可以在 C 程序中处理的数据类型以及可以对这些数据执行的操作。

广告

C 语言中的数据类型和操作

From your computer's point of view, your program is all just a series of ones and zeros. Data types in C tell the computer how to use some of those bits.
从计算机的角度来看,你的程序只是一系列二进制数据(1 和 0)。C 语言中的数据类型告诉计算机如何使用这些二进制数据。
Hemera/Thinkstock

从计算机的角度来看,数据不过是一系列代表硬盘或计算机处理器或内存中电子位开/关状态的 1 和 0。正是你在计算机上运行的软件决定了如何理解这数十亿个二进制数字。C 语言是少数几种高级语言之一,除了根据给定数据类型解释数据之外,它还可以轻松地在位级别操作数据。

数据类型是一小组规则,指示如何理解一系列比特。数据类型具有特定的大小,以及其对该类型数据执行操作(例如加法和乘法)的特定方式。在 C 语言中,数据类型的大小与您使用的处理器相关。例如,在 C99 中,整数数据类型(int)的数据在 16 位处理器中长 16 位,而在 32 位和 64 位处理器中长 32 位。

广告

C 程序员需要了解的另一个重要事情是该语言如何处理有符号和无符号数据类型。有符号类型意味着它的一个位被保留作为指示它是正数还是负数的标志。因此,虽然 16 位系统上的无符号 int 可以处理 0 到 65,535 之间的数字,但同一系统上的有符号 int 可以处理 -32,768 到 32,767 之间的数字。如果一个操作导致 int 变量超出其范围,程序员必须通过额外的代码来处理溢出。

鉴于 C 语言数据类型和操作中的这些约束和系统特定的特性,C 程序员必须根据程序的需求选择其数据类型。他们可以选择的一些数据类型是 C 语言中的原始数据类型,这意味着它们内置于 C 编程语言中。请查阅您最喜欢的 C 编程指南,以获取 C 语言中数据类型的完整列表以及有关如何将数据从一种类型转换为另一种类型的重要信息。

C 程序员还可以创建数据结构,它结合了原始数据类型和一组定义数据如何组织和操作的函数。尽管数据结构的使用是一个高级编程主题,超出了本文的范围,但我们将重点介绍最常见的结构之一:数组。数组是一个虚拟列表,包含相同数据类型的数据。数组的大小不能更改,尽管其内容可以复制到其他更大或更小的数组中。

尽管程序员经常使用数字数组,但字符数组(称为字符串)具有最独特的功能。字符串允许您将您可能说的话(例如“hello”)保存为一系列字符,您的 C 程序可以从用户那里读取或在屏幕上打印出来。字符串操作具有如此独特的一组操作,它有自己专门的 C 库(string.h),其中包含您典型的字符串函数。

C 语言中内置的操作是你在大多数编程语言中都能找到的典型操作。当你将多个操作组合在一个语句中时,请务必了解运算符优先级,即程序在数学表达式中执行每个操作的顺序。例如,(2+5)*3 等于 21,而 2+5*3 等于 17,因为 C 语言会先执行乘法,除非有括号另行指示。

如果您正在学习 C 语言,请优先熟悉其所有原始数据类型和操作,以及同一表达式中操作的优先级。此外,尝试对不同数据类型的变量和数字进行不同操作。

至此,您已经初步了解了 C 语言的一些重要基础知识。接下来,我们将探讨 C 语言如何让您在编写程序时无需每次都从头开始。

广告

不要从头开始,使用库

库在 C 语言中非常重要,因为 C 语言只支持它所需的最基本功能。例如,C 语言不包含用于从键盘读取和向屏幕写入的输入-输出 (I/O) 函数。任何超出基本功能的东西都必须由程序员编写。如果代码块对多个不同的程序有用,它通常会被放入库中,以便于重复使用。

到目前为止,在我们的 C 语言讨论中,我们已经看到了一个库,即标准 I/O (stdio) 库。程序开头的 `#include ` 行指示 C 编译器从其头文件 stdio.h 中加载该库。C 语言维护者包含了用于 I/O、数学函数、时间操作以及对某些数据结构(如字符串)的常见操作的标准 C 库。请在网上或您最喜欢的 C 编程指南中搜索有关 C89 标准库以及 C99 中的更新和添加的信息。

广告

你也可以编写 C 语言库。通过这样做,你可以将程序分割成可重用的模块。这种模块化方法不仅可以轻松地将相同的代码包含在多个程序中,而且还可以生成更短的程序文件,从而更容易阅读、测试和调试。

要使用头文件中的函数,请在程序开头为其添加一行 `#include`。对于标准库,将库对应的头文件名放在大于号和小于号之间 (`< >`)。对于你自己创建的库,将文件名放在双引号之间。与 C 程序其他部分的语句不同,你不需要在每行末尾添加分号。以下显示了包含两种类型库的示例:

#include <math.h>

#include "mylib.h"

全面的 C 编程资料应该提供在 C 中编写自己库所需的说明。您将编写的函数定义无论是放在库中还是放在主程序中都没有任何不同。区别在于您将它们单独编译成一种名为对象文件(以 .o 结尾)的东西,并且您将创建第二个文件,称为头文件(以 .h 结尾),其中包含库中每个函数对应的函数原型。在每个使用您库的主程序中,您将引用头文件中的 `#include` 行,并且每次编译该程序时,您都会将对象文件作为参数包含在编译器命令中。

我们到目前为止探讨的 C 语言特性在其他编程语言中也很常见。接下来,我们将讨论 C 语言对内存管理的严格要求。

广告

关于 C 语言指针的一些提示

当您的 C 程序加载到内存中(通常是计算机的随机存取内存,即 RAM)时,程序的每个部分都与内存中的一个地址相关联。这包括您用于保存某些数据的变量。每次程序调用一个函数时,它都会将该函数及其所有相关数据加载到内存中,时间长度足以运行该函数并返回一个值。如果您将参数传递给函数,C 语言会自动复制该值以在函数中使用。

然而,有时当你运行一个函数时,你希望对其原始内存位置的数据进行永久性更改。如果 C 语言复制数据在函数中使用,那么原始数据保持不变。如果你想更改原始数据,你必须传递一个指向其内存地址的指针(按引用传递),而不是将其值传递给函数(按值传递)。

广告

指针在 C 语言中无处不在,所以如果你想充分利用 C 语言,就必须对指针有很好的理解。指针就像其他变量一样,但它的目的是存储其他数据的内存地址。指针也有一个数据类型,因此它知道如何识别该内存地址处的位。

当你在 C 代码中并排查看两个变量时,你可能不总是能认出指针。这甚至对最有经验的 C 程序员来说也是一个挑战。然而,当你第一次创建指针时,它会更明显,因为变量名之前必须立即有一个星号。这在 C 语言中被称为间接运算符。下面的示例代码创建了一个整数 i 和一个指向整数 p 的指针:

int i;

int *p;

目前 i 和 p 都没有赋值。接下来,我们给 i 赋值,然后将 p 赋值为指向 i 的地址。

i = 3;

p = &i;

在这里,您可以看到在 `i` 之前立即使用了“与号”(&) 作为地址运算符,表示“`i` 的地址”。您不需要知道该地址是什么就可以进行赋值。这很好,因为每次运行程序时它可能会有所不同!相反,地址运算符将在程序运行时确定与该变量相关联的地址。如果没有地址运算符,赋值 `p=i` 将直接将内存地址 3 赋值给 `p`,而不是变量 `i` 的内存地址。

接下来,让我们看看如何在 C 代码中使用指针,以及您需要为哪些挑战做好准备。

广告

在 C 语言中正确使用指针

If you want to become proficient in C programming, you'll need a firm grasp of how to effectively use pointers in your code.
如果你想精通 C 语言编程,你需要牢固掌握如何在代码中有效地使用指针。
©iStockphoto.com/DSGpro

一旦你拥有一个指针,你就可以在操作和函数调用中使用它来代替相同数据类型的变量。在下面的例子中,指向 i 的指针在更大的操作中被用来代替 i。与 p 一起使用的星号 (*p) 表示操作应该使用 p 指向该内存地址的值,而不是内存地址本身:

int b;

b = *p + 2;

没有指针,几乎不可能将任务分解成主函数之外的函数。为了说明这一点,假设您在 main 函数中创建了一个变量 h,它存储了用户身高(以厘米为单位)。您还调用了一个名为 setHeight 的函数,该函数会提示用户设置该身高值。您的 main 函数中的代码行可能看起来像这样:

int h;

setHeight(h); /* 这里可能存在问题。 */

这个函数调用会尝试将 h 的值传递给 setHeight。然而,当函数运行结束时,h 的值将保持不变,因为函数只使用了它的一个副本,并在运行结束后将其丢弃了。

如果您想更改 h 本身,您首先应该确保该函数可以接受指向现有值的指针,而不是值的新副本。那么,setHeight 的第一行将使用指针而不是值作为其参数(注意间接运算符):

setHeight(int *height) { /* 函数语句在此 */ }

然后,您有两种调用 setHeight 的选择。第一种是将 h 的地址运算符 (&h) 用作传递的参数。另一种是为 h 创建一个单独的指针,并将其传递。下面显示了两种选项:

setHeight(&h); /* 将 h 的地址传递给函数 */

int *p;

p = &h;

setHeight(p); /* 将一个单独的指向 h 地址的指针传递给函数 */

第二种选择揭示了使用指针时的一个常见挑战。这个挑战是拥有指向同一值的多个指针。这意味着该值发生的任何更改都会同时影响所有指向它的指针。这可能是好事也可能是坏事,具体取决于您在程序中尝试实现的目标。同样,掌握指针的使用是掌握 C 编程的重要关键。尽可能多地练习指针,这样您就可以准备好应对这些挑战。

我们到目前为止探讨的 C 语言特性在其他编程语言中也很常见。接下来,我们将探讨 C 语言对精确内存管理的要求。

C 语言中内存管理的重要性

C 语言之所以如此通用,原因之一是程序员可以将程序缩小,使其在非常小的内存量下运行。C 语言最初编写时,这是一项重要的功能,因为当时的计算机远不如今天强大。随着目前对小型电子设备(从手机到微型医疗设备)的需求,人们重新燃起了对某些软件内存需求保持较小的兴趣。C 语言是大多数需要对内存使用有大量控制的程序员的首选语言。

为了更好地理解内存管理的重要性,请考虑程序如何使用内存。当您首次运行程序时,它会加载到计算机内存中,并通过向计算机处理器发送和接收指令来开始执行。当程序需要运行特定函数时,它会将该函数及其所有相关数据加载到内存的另一个部分,并在该函数运行期间保留,然后在函数完成后释放该内存。此外,主程序中使用的每个新数据都会在程序运行期间占用内存。

如果您希望对此有更多控制,您需要动态存储分配。C 语言支持动态存储分配,即在需要时保留内存并在使用完毕后立即释放内存的能力。许多编程语言具有自动内存分配和垃圾回收功能,可以处理这些内存管理任务。然而,C 语言允许(在某些情况下要求)您使用标准 C 库中的以下关键函数明确地进行内存分配:

  • malloc -- malloc 是 memory allocation 的缩写,用于分配一块给定大小的内存块,以存储程序需要处理的特定类型数据。当您使用 malloc 时,您会创建一个指向已分配内存的指针。对于单个数据,例如一个整数,这不是必需的,因为它在您第一次声明它时(如 `int i` 中)就会被分配。但是,它是创建和管理数组等数据结构的重要组成部分。C 语言中其他内存分配选项有 calloc(它在保留内存时也会清除内存)和 realloc(它会重新调整先前保留的内存大小)。
  • free——使用 free 强制程序释放先前分配给给定指针的内存。

使用 `malloc` 和 `free` 的最佳实践是:任何分配的内存都应该被释放。无论何时分配内存,即使是在临时函数中,它也会保留在内存中,直到操作系统清理该空间。但是,为了确保内存是空闲并可以立即使用,您应该在当前函数退出之前释放它。这种内存管理意味着您可以将程序的内存占用量保持在最低限度,并避免内存泄漏。内存泄漏是一种程序缺陷,它会不断使用越来越多的内存,直到没有内存可供分配,导致程序停滞或崩溃。另一方面,不要因为急于释放内存而释放了您在同一函数中稍后需要的东西,从而导致丢失。

在本文中,您已经学习了 C 编程语言的一些基本结构和核心概念。我们回顾了它的历史、与其他编程语言的共同特点以及使其成为编写软件的独特且多功能选项的重要特性。请跳转到下一页以获取更多信息,包括一些能让您在 C 语言之旅中走得更远的编程指南。

常见问题

C 编程和 C++ 是相同的吗?
不。C++ 是 C 语言的扩展,但它不是同一种语言。
什么是 C 编程概念?
C 编程是一种允许程序员创建可移植和高效软件的概念。C 编程基于 20 世纪 70 年代在贝尔实验室开发的 C 语言。C 编程是一种强大而通用的语言,非常适合创建各种软件应用程序。

更多信息

相关文章

更多精彩链接

  • Kernighan, Brian W., and Ritchie, Dennis M. "C Programming Language, Second Edition." Prentice Hall. 1988.
  • King, K.N. "C Programming: A Modern Approach, Second Edition." W.W. Norton & Company, Inc. 2008.

广告

加载中...