WangShuXian6 / blog

FE-BLOG
https://wangshuxian6.github.io/blog/
MIT License
46 stars 10 forks source link

C 语言 #142

Open WangShuXian6 opened 2 years ago

WangShuXian6 commented 2 years ago

C 语言

C语言是20世纪70年代初期在贝尔实验室开发出来的一种广为使用的编程语言

C 参考手册 官方 - cppreference.com https://zh.cppreference.com/w/c

C 文档 - 入门、教程、参考。 | Microsoft Docs https://docs.microsoft.com/zh-cn/cpp/c-language/?view=msvc-170

基于C的语言

C语言对现代编程语言有着巨大的影响,许多现代编程语言都借鉴了大量C语言的特性。在众多基于C的语言中,以下几种非常具有代表性。

C++:包括了所有C特性,但增加了类和其他特性以支持面向对象编程。 Java:是基于C++的,所以也继承了C的许多特性。 C#:是由C++和Java发展起来的一种较新的语言。 Perl:最初是一种非常简单的脚本语言,在发展过程中采用了C的许多特性。

WangShuXian6 commented 2 years ago

C语言的优缺点

这些优缺点都源于该语言的最初用途(编写操作系统和其他系统软件)和它自身的基础理论体系。

C语言是一种底层语言 。 为了适应系统编程的需要,C语言提供了对机器级概念(例如,字节和地址)的访问,而这些是其他编程语言试图隐藏的内容。此外,C语言还提供了与计算机内置指令紧密协调的操作,使得程序可以快速执行。应用程序的输入/输出、存储管理以及其他众多服务都依赖于操作系统,因此操作系统一定不能运行得太慢。

C语言是一种小型语言 。 与其他许多编程语言相比,C语言提供了一套更有限的特性集合。(在K&R第2版的参考手册中仅用49页就描述了整个C语言。)为了使特性较少,C语言在很大程度上依赖一个标准函数的“库”(“函数”类似于其他编程语言中描述的“过程”、“子例程”或“方法”)。

C语言是一种包容性语言 。 C语言假设用户知道自己在做什么,因此它提供了比其他许多语言更广阔的自由度。此外,C语言不像其他语言那样强制进行详细的错误检查。

C语言的优点

高效

高效性是C语言与生俱来的优点之一。发明C语言就是为了编写那些以往由汇编语言编写的应用程序,所以对C语言来说,能够在有限的内存空间里快速运行就显得至关重要了。

可移植

虽然程序的可移植性并不是C语言的主要目标,但它还是成为了C语言的优点之一。当程序必须在多种机型(从个人计算机到超级计算机)上运行时,常常会用C语言来编写。C程序具有可移植性的一个原因是该语言没有分裂成不兼容的多种分支(这要归功于C语言早期与UNIX系统的结合以及后来的ANSI/ISO标准)。另一个原因是C语言编译器规模小且容易编写,这使得它们得以广泛应用。最后,C语言自身的特性也支持可移植性(尽管它没有阻止程序员编写不可移植的程序)。

功能强大

C语言拥有一个庞大的数据类型和运算符集合,这个集合使得C语言具有强大的表达能力,往往寥寥几行代码就可以实现许多功能。

灵活

虽然C语言最初设计是为了系统编程,但是没有固有的约束将它限制在此范围内。C语言现在可以用于编写从嵌入式系统到商业数据处理的各种应用程序。此外,C语言在其特性使用上的限制非常少。在其他语言中认定为非法的操作在C语言中往往是允许的。例如,C语言允许一个字符与一个整数值相加(或者是与一个浮点数相加)。虽然灵活性可能会让某些错误溜掉,但是它却使编程变得更加轻松。

标准库

C语言的一个突出优点就是它具有标准库,该标准库包含了数百个可以用于输入/输出、字符串处理、存储分配以及其他实用操作的函数。

与UNIX系统的集成

C语言在与UNIX系统(包括广为人知的Linux)结合方面特别强大。事实上,一些UNIX工具甚至假定用户是了解C语言的。

C语言的缺点

C语言的缺点和它的许多优点是同源的,均来自C语言与机器的紧密结合

C程序更容易隐藏错误

C语言的灵活性使得用它编程出错的概率较高。在用其他语言编程时可以发现的错误,C语言编译器却无法检查到。从这方面来说,C语言与汇编语言极为相似,后者直到程序运行时才能检查到大多数错误。更糟的是,C语言还包含大量不易觉察的隐患。在后续的章节中我们将会看到,一个额外的分号可能会导致无限循环,遗漏一个& 可能会引发程序崩溃。

C程序可能会难以理解

虽然根据大多数衡量标准C语言是一种小型语言,但是它也有许多其他通用语言没有的特性(并且常常被误解)。这些特性可以用多种方式结合使用,其中的一些结合方式尽管编程者心知肚明,但是其他人恐怕难以理解。另一个问题就是C程序简明扼要的特性。C语言产生的时候正是人机交互最为单调乏味的时期,因此设计者特意使C语言简明以便将录入和编辑程序的用时减到最少。C语言的灵活性也可能是一个负面因素,过于聪明的程序员甚至可以编写出除了他们自己几乎没人可以读得懂的程序。

C程序可能会难以修改

如果在设计中没有考虑到维护的问题,那么用C语言编写的大规模程序将很难修改。现代的编程语言通常都会提供“类”和“包”之类的语言特性,这样的特性可以把大的程序分解成许多更容易管理的模块。遗憾的是,C语言恰恰缺少这样的特性。

WangShuXian6 commented 2 years ago

c 编译器

对于当前主流桌面操作系统而言,可使用 Visual C++GCC 以及 LLVM Clang 这三大编译器。

Visual C++(简称 MSVC)是由微软开发的,只能用于 Windows 操作系统;GCC 和 LLVM Clang 除了可用于 Windows 操作系统之外,主要用于 Unix/Linux 操作系统。

像现在很多版本的 Linux 都默认使用 GCC 作为C语言编译器,而像 FreeBSD、macOS 等系统默认使用 LLVM Clang 编译器。由于当前 LLVM 项目主要在 Apple 的主推下发展的,所以在 macOS中,Clang 编译器又被称为 Apple LLVM 编译器。

MSVC 编译器主要用于 Windows 操作系统平台下的应用程序开发,它不开源。用户可以使用 Visual Studio Community 版本来免费使用它,但是如果要把通过 Visual Studio Community 工具生成出来的应用进行商用,那么就得好好阅读一下微软的许可证和说明书了。

而使用 GCC 与 Clang 编译器构建出来的应用一般没有任何限制,程序员可以将应用程序随意发布和进行商用。

MSVC 编译器对 C99 标准的支持十分有限,直到发布 Visual Studio Community 2019,也才对 C11 和 C17 标准做了部分支持。 所幸的是,Visual Studio Community 2017 加入了对 Clang 编译器的支持,官方称之为——Clang with Microsoft CodeGen,当前版本基于的是 Clang 3.8。

GCC 官网提供的 GCC 编译器是无法直接安装到 Windows 平台上的,如果我们想在 Windows 平台使用 GCC 编译器,可以安装 GCC 的移植版本。

目前适用于 Windows 平台、受欢迎的 GCC 移植版主要有 2 种,分别为 MinGW 和 Cygwin。其中,MinGW 侧重于服务 Windows 用户可以使用 GCC 编译环境,直接生成可运行 Windows 平台上的可执行程序,相比后者体积更小,使用更方便;而 Cygwin 则可以提供一个完整的 Linux 环境,借助它不仅可以在 Windows 平台上使用 GCC 编译器,理论上可以运行 Linux 平台上所有的程序。 如果读者仅需要在 Windows 平台上使用 GCC,可以使用 MinGW 或者 Cygwin;除此之外,如果读者还有更高的需求(例如运行 POSIX 应用程序),就只能选择安装 Cygwin。

GNU编译器集合和LLVM项目

GNU项目始于1987年,是一个开发大量免费UNIX软件的集合(GNU的意思是“GNU’s Not UNIX”,即GNU不是UNIX)。 GNU编译器集合(也被称为GCC,其中包含GCC C编译器)是该项目的产品之一。 GCC在一个指导委员会的带领下,持续不断地开发,它的C编译器紧跟C标准的改动。GCC有各种版本以适应不同的硬件平台和操作系统,包括UNIX、Linux和Windows。用gcc命令便可调用GCC C编译器。许多使用gcc的系统都用cc作为gcc的别名。

LLVM项目成为cc的另一个替代品。该项目是与编译器相关的开源软件集合,始于伊利诺伊大学的2000份研究项目。 它的 Clang编译器处理 C代码,可以通过 clang调用。 有多种版本供不同的平台使用,包括Linux。2012年,Clang成为FreeBSD的默认C编译器。Clang也对最新的C标准支持得很好。

GNU和LLVM都可以使用-v选项来显示版本信息,因此各系统都使用cc别名来代替gcc或clang命令。以下组合:

cc -v

显示你所使用的编译器及其版本。

gcc和clang命令都可以根据不同的版本选择运行时选项来调用不同C标准。


gcc -std=c99 inform.c[3]

gcc -std=c1x inform.c

gcc -std=c11 inform.c

>第1行调用C99标准,
>第2行调用GCC接受C11之前的草案标准,
>第3行调用GCC接受的C11标准版本。
>Clang编译器在这一点上用法与GCC相同。

## Linux
>在Linux中准备C程序与在UNIX系统中几乎一样,不同的是要使用GNU提供的GCC公共域C编译器。编译命令类似于:
```bash
gcc inform.c

在安装Linux时,可选择是否安装GCC。如果之前没有安装GCC,则必须安装。 通常,安装过程会将cc作为gcc的别名,因此可以在命令行中使用cc来代替gcc。

GCC http://www.gnu.org/software/gcc/index.html

ubuntu 安装 gcc

sudo apt install gcc

设置 vscode includePath (.vscode/c_cpp_properties.json)

IntelliSense 配置 中 编译器路径 为 /usr/bin/gccconfigurations.compilerPath

{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64",
"compilerPath": "/usr/bin/gcc"
}
],
"version": 4
}

Windows

C编译器不是标准Windows软件包的一部分,因此需要从别处获取并安装C编译器。 可以从互联网免费下载Cygwin和MinGW,这样便可在PC上通过命令行使用GCC编译器。 Cygwin在自己的视窗运行,模仿Linux命令行环境,有一行命令提示。 MinGW在Windows的命令提示模式中运行。这和GCC的最新版本一样,支持C99和C11最新的一些功能。 Borland的C++编译器5.5也可以免费下载,支持C90。

通常,C编译器生成的中间目标代码文件的扩展名是.obj(也可能是其他扩展名)。 与UNIX编译器不同,这些编译器在完成编译后通常不会删除这些中间文件。 有些编译器生成带.asm扩展名的汇编语言文件,而有些编译器则使用自己特有的格式。

一些编译器在编译后会自动运行链接器,另一些要求用户手动运行链接器。 在可执行文件中链接的结果是,在原始的源代码基本名后面加上.exe扩展名。 例如,编译和链接concrete.c源代码文件,生成的是concrete.exe文件。可以在命令行输入基本名来运行该程序:

C>concrete

WSL

Windows Subsystem for Linux https://docs.microsoft.com/zh-cn/windows/wsl/ 使用 WSL 在 Windows 上安装 Linux 适用于 Linux 的 Windows 子系统文档

使用 WSL 在 Windows 上安装 Linux

必须运行 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 Windows 11。

wsl --install

此命令将启用运行 WSL 并安装 Linux 的 Ubuntu 发行版所需的功能 运行 wsl --list --online 以查看可用发行版列表并运行 wsl --install -d <DistroName> 以安装发行版 将 <Distribution Name> 替换为要安装的发行版的名称。

wsl 安装 gcc

sudo apt install gcc

通过 WSL 使用 Visual Studio Code

https://docs.microsoft.com/zh-cn/windows/wsl/tutorials/wsl-vscode

安装 VS Code 远程 WSL 扩展 安装远程开发扩展包。 除了 Remote - SSH 和 Remote - Containers 扩展之外,此扩展包还包含 Remote - WSL 扩展,使你能够打开容器中、远程计算机上或 WSL 中的任何文件夹。

更新 Linux 发行版 某些 WSL Linux 发行版缺少启动 VS Code 服务器所需的库。 可以使用其他库的包管理器将其他库添加到 Linux 发行版中。

例如,要更新 Debian 或 Ubuntu,请使用:

sudo apt-get update

若要添加 wget(从 Web 服务器检索内容)和 ca 证书(允许基于 SSL 的应用程序检查 SSL 连接的真实性),请输入:

sudo apt-get install wget ca-certificates
在 Visual Studio Code 中打开 WSL 项目

从命令行中 若要从 WSL 发行版打开项目,请打Linux开发行版的命令行并输入:code .

从 VS Code 中 还可以通过使用 VS Code 中的快捷方式 CTRL+SHIFT+P 调出命令面板,以访问更多 VS Code 远程选项。 如果随后键入 Remote-WSL,将看到可用的 VS Code 远程选项列表,使你可以在远程会话中重新打开文件夹,指定要在哪个发行版中打开,等等。 例如从wsl环境打开 windows 指定目录的代码文件 ,选择 Remote-WSL:Open Folder in WSL image

Remote-WSL 扩展将 VS Code 拆分为“客户端-服务器”体系结构,使客户端(用户界面)在 Windows 计算机上运行,而使服务器(你的代码、Git、插件等)远程运行。

C/C++ 配置 vscode

https://code.visualstudio.com/docs/cpp/config-wsl#_cc-configurations

MinGw

MinGw 全称 Minimalist GNU for Windows,应用于 Windows 平台,可以为我们提供一个功能有限的 Linux 系统环境以使用一些 GNU 工具,比如 GCC 编译器、gawk、bison 等等。


https://www.mingw-w64.org/ MinGW-w64
https://osdn.net/projects/mingw/ MinGW - Minimalist GNU for Windows Project Top Page - OSDN 下载 MinGW 安装包

安装完成之后,我们会得到一个名为 "MinGW Installer Manager" 的软件,借助它,我们可以随时根据需要修改 GCC 编译器的配置。点击“continue”,会自动弹出配置界面,如下所示:

配置GCC编译器
![image](https://user-images.githubusercontent.com/30850497/189478457-be226964-af99-48e8-8699-4e31883a6ab8.png)

勾选完成后,在菜单栏中选择Installation -> Apply Changes,弹出如下对话框:
![image](https://user-images.githubusercontent.com/30850497/189478484-2bcc0d33-2d1d-44f3-bc2b-5a76a79204cb.png)
开始安装选中的编译环境
选择“Apply”。然后耐心等待,直至安装成功,即可关闭此界面。注意,整个安装过程中可能会提示某些组件下载失败,但没关系,后续需要时,可以通过 MinGw Installer 安装界面中的 “All Packages”选项中,手动选择指定的安装包进行安装。

在安装完成的基础上,我们需要手动配置 PATH 环境变量。
依次`右击计算机(我的电脑) -> 属性 -> 高级系统设置 -> 环境变量`,建议读者在当前用户的 PATH 环境变量中增加 MinGW 的安装路径,例如我将其安装到了`E:\MinGW`文件夹中,因此 PATH 环境变量的设置如下:
![image](https://user-images.githubusercontent.com/30850497/189478566-82415bbf-b03c-4b12-9539-117b08bb4364.png)

打开命令行窗口(通过在搜索栏中执行 cmd 指令即可),输入gcc -v指令,如果输出 GCC 编译器的具体信息,则表示安装成功

MinGW的使用

通过上面的安装,我们就可以在当前 Windows 平台上编译、运行 C 或者 C++ 程序了。

这里以运行一个 C 语言程序为例(存储路径为:D:\demo.c):

#include <stdio.h>
#include <stdlib.h>
int main(){
printf("Hello, World!");
system("pause");
return 0;
}

在此基础上,在命令行窗口中执行如下指令:

C:\Users\mengma>gcc D:\demo.c -o D:\demo.exe

其会在 D 盘生成一个 demo.exe 可执行文件,找到该文件并双击,即可看到程序的执行结果:

Hello, World!

cygwin

天鹅座 https://cygwin.com/ Cygwin是一个可原生运行于Windows系统上的POSIX兼容环境。

大量的 GNU 和开源工具,它们提供的功能类似于 Windows 上的 Linux 发行版。 一个 DLL (cygwin1.dll),它提供了大量的 POSIX API 功能。

首次安装程序包时,安装程序不会安装每个包。默认情况下,仅安装Cygwin发行版中的最小基础软件包,这大约占用100 MB。

安装所有包 这将安装大量您永远不会使用的软件包,包括每个软件包的调试信息和源代码。 如果您确实必须这样做,请单击“全部”类别旁边的“默认”标签将其更改为“安装”,将标记每个Cygwin软件包以进行安装。请注意,这将下载并安装数十 GB的文件到您的计算机

Linux 应用, UNIX® 功能(如信号、点等) 如果希望应用程序在 Windows 上运行,则必须从源代码重新生成应用程序。

Macintosh

苹果免费提供Xcode开发系统下载(过去,它有时免费,有时付费)。它允许用户选择不同的编程语言,包括C语言。 在Xcode 4.6中,通过【File】菜单选择【New Project】,然后选择【OS X Application Command Line Tool】,接着输入产品名并选择C类型。Xcode使用Clang或GCC C编译器来编译C代码,它以前默认使用GCC,但是现在默认使用Clang。可以设置选择使用哪一个编译器和哪一套C标准(因为许可方面的事宜,Xcode中Clang的版本比GCC的版本要新)。

嵌入式系统

在嵌入式系统方面,可用的C语言编译器就非常丰富了,比如: 用于 Keil 公司 51 系列单片机的 Keil C51 编译器; 当前大红大紫的 Arduino 板搭载的开发套件,可用针对 AVR 微控制器的 AVR GCC 编译器; ARM 自己出的 ADS(ARM Development Suite)、RVDS(RealView Development Suite)和当前最新的 DS-5 Studio; DSP 设计商 TI(Texas Instruments)的 CCS(Code Composer Studio); DSP 设计商 ADI(Analog Devices,Inc.)的 Visual DSP++ 编译器,等等。

通常,用于嵌入式系统开发的编译工具链都没有免费版本,而且一般需要通过国内代理进行购买。所以,这对于个人开发者或者嵌入式系统爱好者而言是一道不低的门槛。

不过 Arduino 的开发套件是可免费下载使用的,并且用它做开发板连接调试也十分简单。Arduino 所采用的C编译器是基于 GCC 的。

还有像树莓派(Raspberry Pi)这种迷你电脑可以直接使用 GCC 和 Clang 编译器。此外,还有像 nVidia 公司推出的 Jetson TK 系列开发板也可直接使用 GCC 和 Clang 编译器。树莓派与 Jetson TK 都默认安装了 Linux 操作系统。

在嵌入式领域,一般比较低端的单片机,比如 8 位的 MCU 所对应的C编译器可能只支持 C90 标准,有些甚至连 C90 标准的很多特性都不支持。因为它们一方面内存小,ROM 的容量也小;另一方面,本身处理器机能就十分有限,有些甚至无法支持函数指针,因为处理器本身不包含通过寄存器做间接过程调用的指令。

而像 32 位处理器或 DSP,一般都至少能支持 C99 标准,它们本身的性能也十分强大。而像 ARM 出的 RVDS 编译器甚至可用 GNU 语法扩展。

WangShuXian6 commented 2 years ago

C 语言基本概念

一个简单的C程序

pun.c


#include <stdio.h>

int main(void) { printf("To C, or not to C: that is the question.\n"); return 0; }


>`#include <stdio.h>`包含了C语言标准输入/输出库的相关信

>程序的可执行代码都在`main` 函数中
>`printf` 函数来自标准输入/输出库,可以产生完美的格式化输出。
>代码`\n` 告诉`printf` 函数执行完消息显示后要进行换行操作
>`return 0;`表明程序终止时会向操作系统返回值0
>`;`分号结尾必须

### 编译和链接
>创建文件`pun.c`
>编译器通常要求带上文件的扩展名`.c`

#### 把程序转化为机器可以执行的形式。对于C程序来说,通常包含下列3个步骤。
>该过程往往是自动实现的

##### 预处理 
>首先程序会被送交给预处理器 (preprocessor)。预处理器执行以# 开头的命令(通常称为指令 )。预处理器有点类似于编辑器,它可以给程序添加内容,也可以对程序进行修改。

##### 编译 
>修改后的程序现在可以进入编译器 (compiler)了。编译器会把程序翻译成机器指令(即目标代码 )。然而,这样的程序还是不可以运行的。

##### 链接
>在最后一个步骤中,链接器 (linker)把由编译器产生的目标代码和所需的其他附加代码整合在一起,这样才最终产生了完全可执行的程序。这些附加代码包括程序中用到的库函数(如printf 函数)。

##### 运行
>编译后默认在当前目录下生成`a.out`文件,输入该文件完整路径即可执行

```bash
./a.out

这回在控制台输出To C, or not to C: that is the question.

下次编译会覆盖该文件,除非手动指定不同的文件名

根据编译器和操作系统的不同,编译和链接所需的命令也是多种多样的

在UNIX系统环境下,通常把C编译器命名为cc 。为了编译和链接pun.c 程序,需要在终端或命令行窗口录入如下命令:

cc pun.c

在使用编译器cc 时,系统自动进行链接操作,而无需单独的链接命令。

在编译和链接好程序后,编译器cc 会把可执行程序放到默认名为a.out 的文件中。

编译器cc 有许多选项,其中有一个选项(-o 选项)允许为含有可执行程序的文件选择名字。 例如,假设要把文件pun.c 生成的可执行文件命名为pun ,那么只需录入下列命令:

cc -o pun pun.c

GCC编译器是最流行的C编译器之一,它随Linux发行,但也有面向其他很多平台的版本。这种编译器的使用与传统的UNIX cc 编译器相似。例如,编译程序pun.c 可以使用以下命令:

gcc -o pun pun.c

简单程序的一般形式

指令

int main(void)
{
  语句
}

即使是最简单的C程序也依赖3个关键的语言特性: 指令(在编译前修改程序的编辑命令)、函数(被命名的可执行代码块,如main 函数)和语句(程序运行时执行的命令)

指令

在编译C程序之前,预处理器会首先对其进行编辑。我们把预处理器执行的命令称为指令

所有指令都是以字符# 开始的。 这个字符可以把C程序中的指令和其他代码区分开来。 指令默认只占一行,每条指令的结尾没有分号或其他特殊标记。

#include 指令

程序pun.c 由下列这行指令开始:

#include <stdio.h>

这条指令说明,在编译前把 中的信息“包含”到程序中。 <stdio.h> 包含了关于C标准输入/ 输出库的信息。 C语言拥有大量类似于<stdio.h> 的头 (header),每个头都包含一些标准库的内容。 这段程序中包含<stdio.h> 的原因是:C语言不同于其他的编程语言,它没有内置的“读”和“写”命令。输入/输出功能由标准库中的函数实现。

函数

函数 类似于其他编程语言中的“过程”或“子例程”,它们是用来构建程序的构建块。 事实上,C程序就是函数的集合。

函数分为两大类:一类是程序员编写的函数,另一类则是作为C语言实现的一部分提供的函数。 我们把后者称为库函数 (library function),因为它们属于一个由编译器提供的函数“库”。

术语“函数”来源于数学。在数学中,函数是指根据一个或多个给定参数进行数值计算的规则: Image00004

C语言对“函数”这个术语的使用则更加宽松。 在C语言中,函数仅仅是一系列组合在一起并且赋予了名字的语句。某些函数计算数值,某些函数不这么做。 计算数值的函数用return 语句来指定所“返回”的值。 例如,对参数进行加1操作的函数可以执行语句

return x + 1 ;

而当函数要计算参数的平方差时,则可以执行语句

return y * y - z * z;

虽然一个C程序可以包含多个函数,但只有main 函数是必须有的。 main 函数是非常特殊的:在执行程序时系统会自动调用main 函数。

如果main 是一个函数,那么它会返回一个值吗?是的。它会在程序终止时向操作系统返回一个状态码。 我们再来看看pun.c 程序:


#include <stdio.h>

int main(void) { printf("To C, or not to C: that is the question.\n"); return 0; }

>main 前面的int 表明该函数将返回一个整数值。圆括号中的void 表明main 函数没有参数。
>语句`return 0;`有两个作用:
>一是使main 函数终止(从而结束程序),二是指出main 函数的返回值是0。
>现在我们始终让main 函数的返回值为0,这个值表明程序正常终止。

### 语句
>语句 是程序运行时执行的命令。
>程序pun.c 只用到两种语句。一种是返回(return )语句,另一种则是函数调用 (function call)语句。
>要求某个函数执行分派给它的任务称为调用 这个函数。例如,程序pun.c 为了在屏幕上显示一条字符串就调用了printf 函数:
```c
printf("To C, or not to C: that is the question.\n");

C语言规定每条语句都要以分号结尾。 (就像任何好的规则一样,这条规则也有一个例外:复合语句就不以分号结尾。) 由于语句可以连续占用多行,有时很难确定它的结束位置,因此用分号来向编译器显示语句的结束位置。 但指令通常都只占一行,因此不需要 用分号结尾。

显示字符串

printf 是一个功能强大的函数。到目前为止,我们只是用printf 函数显示了一条字符串字面量 (string literal)——用一对双引号包围的一系列字符。 当用printf 函数显示字符串字面量时,最外层的双引号不会出现。

当显示结束时,printf 函数不会自动跳转到下一输出行。 为了让printf 跳转到下一行,必须在要显示的字符串中包含\n (换行符 )。 写换行符就意味着终止当前行,然后把后续的输出转到下一行。

注释

符号/* 标记注释的开始,而符号*/ 则标记注释的结束。例如:

/* This is a comment */

注释几乎可以出现在程序的任何位置上。它既可以单独占行也可以和其他程序文本出现在同一行中。

注释还可以占用多行。一旦遇到符号/ ,那么编译器读入(并且忽略)随后的内容直到遇到符号/ 为止。 如果愿意,还可以把一串短注释合并成为一条长注释:

/* Name: pun.c
Purpose: Prints a bad pun.
Author: K. N. King */

但是,上面这样的注释可能难于阅读,因为人们阅读程序时可能不易发现注释的结束位置。所以,单独把*/ 符号放在一行会很有帮助:


/* Name: pun.c
Purpose: Prints a bad pun.
Author: K. N. King
*/

/**

/*

C99提供了另一种类型的注释,以// (两个相邻的斜杠)开始: 这种风格的注释会在行末自动终止。

// This is a comment

新的注释风格有两个主要优点:首先,因为注释会在行末自动终止,所以不会出现未终止的注释意外吞噬部分程序的情况;其次,因为每行前面都必须有// ,所以多行的注释更加醒目。

变量和赋值

变量 (variable)

大多数程序在产生输出之前往往需要执行一系列的计算,因此需要在程序执行过程中有一种临时存储数据的方法。 和大多数编程语言一样,C语言中的这类存储单元被称为变量 (variable)。

类型

每一个变量都必须有一个类型 (type)。类型用来说明变量所存储的数据的种类。

int 类型和float 类型。 由于类型会影响变量的存储方式以及允许对变量进行的操作,所以选择合适的类型是非常关键的。 数值型变量的类型决定了变量所能存储的最大值和最小值,同时也决定了是否允许在小数点后出现数字。

int (即integer的简写)型变量

可以存储整数,如0、1、392或者-2553。 但是,整数的取值范围是受限制的。最大的整数通常是2 147 483 647,但在某些计算机上也可能只有32 767。

float (即floating-point的简写)型变量

可以存储比int 型变量大得多的数值。而且,float 型变量可以存储带小数位的数,如379.125。 但float 型变量也有一些缺陷。 进行算术运算时float 型变量通常比int 型变量慢; 更重要的是,float 型变量所存储的数值往往只是实际数值的一个近似值。 如果在一个float 型变量中存储0.1,以后可能会发现变量的值为0.099 999 999 999 999 87,这是舍入造成的误差。

声明

在使用变量之前必须对其进行声明 (为编译器所做的描述)。 为了声明变量,首先要指定变量的类型 ,然后说明变量的名字 。 每一条完整的声明语句都要以分号结尾。

例如,我们可能这样声明变量height 和profit :

int height;
float profit;

第一条声明说明height 是一个int 型变量,这也就意味着变量height 可以存储一个整数值。 第二条声明则表示profit 是一个float 型变量。

如果几个变量具有相同的类型,就可以把它们的声明合并:

int height, length, width, volume;
float profit, loss;

当main 函数包含声明时,必须把声明放置在语句之前:

int main(void)
{
声明
语句
}

函数和程序块(包含嵌入声明的语句)一般都有这样的要求。就书写格式而言,建议在声明和语句之间留出一个空行。 在C99中,声明可以不在语句之前。例如,main 函数中可以先有一个声明,后面跟一条语句,然后再跟一个声明。 考虑到C++Java程序中在使用时才声明变量的情况很常见,估计将来在C99程序中这种做法也会很流行。

赋值

变量通过赋值 (assignment)的方式获得值。例如,语句

height = 8;
length = 12;
width = 10;

把数值8 、12 和10 分别赋给变量height 、length 和width ,8 、12 和10 称为常量 (constant)。

变量在赋值或以其他方式使用之前必须先声明。也就是说,我们可以这样写:


int height;
height = 8;

>但下面这样是不行的:
```c
height = 8;     /*** WRONG ***/
int height;

赋给float 型变量的常量通常都带小数点。例如,如果profit 是一个float 型的变量,可能会这样对其赋值:

profit = 2150.48;

当我们把一个包含小数点的常量赋值给float 型变量时,最好在该常量后面加一个字母f (代表float):

profit = 2150.48f;

不加f 可能会引发编译器的警告。

正常情况下,要将int 型的值赋给int 型的变量,将float 型的值赋给float 型的变量。 混合类型赋值(如把int 型的值赋给float 型变量或者把float 型的值赋给int 型变量)是可以的,但不一定安全

一旦变量被赋值,就可以用它来辅助计算其他变量的值:

height = 8;
length = 12;
width = 10;
volume = height * length * width;     /* volume is now 960 */

在C语言中,符号* 表示乘法运算,因此上述语句把存储在height 、length 和width 这3个变量中的数值相乘,然后把运算结果赋值给变量volume 。通常情况下,赋值运算的右侧可以是一个含有常量、变量和运算符的公式(在C语言的术语中称为表达式 )。

显示变量的值

用printf 可以显示出变量的当前值

printf("Height: %d\n", height);

输出Height: h 占位符%d 用来指明在显示过程中变量height 的值的显示位置。 注意,由于在%d 后面放置了\n ,所以printf 在显示完height 的值后会跳到下一行。

%d 仅用于int 型变量。如果要显示float 型变量,需要用%f 来代替%d 。 默认情况下,%f 会显示出小数点后6位数字。如果要强制%f 显示小数点后 位数字,可以把 放置在%f 之间。例如,为了显示信息Profit: $2150.48可以把printf 写为如下形式:

printf("Profit: $%.2f\n", profit);

C语言没有限制调用一次printf 可以显示的变量的数量。为了同时显示变量height 和变量length 的值,可以使用下面的printf 调用语句:

printf("Height: %d Length: %d\n", height, length);

在C语言中,如果两个整数相除,那么结果会被“截短”:小数点后的所有数字都会丢失(向下 取整)

初始化

当程序开始执行时,某些变量会被自动设置为零,而大多数变量则不会。 没有默认值并且尚未在程序中被赋值的变量是未初始化的 (uninitialized)。

如果试图访问未初始化的变量(例如,用printf 显示变量的值,或者在表达式中使用该变量),可能会得到不可预知的结果,如2 568、-30 891或者其他同样没有意义的数值。在某些编译器中,可能会发生更坏的情况(甚至是程序崩溃)。

可以在一步操作中声明变量height 并同时对其进行初始化:

int height = 8;

按照C语言的术语,数值8 是一个初始化式 (initializer)。

在同一个声明中可以对任意数量的变量进行初始化:

int height = 8, length = 12, width = 10;

显示表达式的值

printf("%d\n", height * length * width);

读入输入 scanf

为了获取输入,就要用到scanf 函数。它是C函数库中与printf 相对应的函数。 scanf 中的字母f 和printf 中的字母f 含义相同,都是表示“格式化”的意思。 scanf 函数和printf 函数都需要使用格式串 (format string)来指定输入或输出数据的形式。 scanf 函数需要知道将获得的输入数据的格式,而printf 函数需要知道输出数据的显示格式。

为了读入一个int 型值,可以使用下面的scanf 函数调用:

scanf("%d", &i);  /* reads an integer; stores into i */

其中,字符串"%d" 说明scanf 读入的是一个整数,而i 是一个int 型变量,用来存储scanf 读入的输入。 & 运算符在使用scanf 函数时通常是(但不总是)必需的。

读入一个float 型值时,需要一个形式略有不同的scanf 调用:

scanf("%f", &x);  /* reads a float value; stores into x */

%f 只用于float 型变量,因此这里假设x 是一个float 型变量。字符串"%f" 告诉scanf 函数去寻找一个float 格式的输入值(此数可以含有小数点,但不是必须含有)。

计算箱子的空间重量

在这个程序中,用户可以录入尺寸。 注意,每一个scanf 函数调用都紧跟在一个printf 函数调用的后面。这样做可以提示用户何时输入,以及输入什么。 dweight2.c


/* Computes the dimensional weight of a
box from input provided by the user */

include

int main(void) { int height, length, width, volume, weight;

printf("Enter height of box: "); scanf("%d", &height); printf("Enter length of box: "); scanf("%d", &length); printf("Enter width of box: "); scanf("%d", &width); volume = height length width; weight = (volume + 165) / 166;

printf("Volume (cubic inches): %d\n", volume); printf("Dimensional weight (pounds): %d\n", weight);

return 0; }

>这段程序的输出显示如下(用户的输入8,12,20):
```bash
Enter height of box: 8

Enter length of box: 12

Enter width of box: 10

Volume (cubic inches): 960
Dimensional weight (pounds): 6

提示用户输入的消息(提示符)通常不应该以换行符结束,因为我们希望用户在同一行输入。这样,当用户敲回车键时,光标会自动移动到下一行,因此就不需要程序通过显示换行符来终止当前行了。 dweight2.c 程序还存在一个问题:如果用户输入的不是数值,程序就会出问题

定义常量的名字

当程序含有常量时,建议给这些常量命名。 程序dweight2.c 都用到了常量166。在后期阅读程序时也许有些人会不明白这个常量的含义。所以可以采用称为宏定义 (macro definition)的特性给常量命名:

#define INCHES_PER_POUND 166

这里的#define 是预处理指令,类似于前面所讲的#include ,因而在此行的结尾也没有分号。

当对程序进行编译时,预处理器会把每一个宏替换为其表示的值。 例如,语句

weight = (volume + INCHES_PER_POUND - 1) / INCHES_PER_POUND;

将变为

weight = (volume + 166 - 1) / 166;

效果就如同在前一个地方写的是后一条语句。

此外,还可以利用宏来定义表达式:

#define RECIPROCAL_OF_PI (1.0f / 3.14159f)

当宏包含运算符时,必须用括号把表达式括起来。 宏的名字只用了大写字母。这是大多数C程序员遵循的规范,但并不是C语言本身的要求

标识符

在编写程序时,需要对变量、函数、宏和其他实体进行命名。这些名字称为标识符 (identifier)。 在C语言中,标识符可以含有字母、数字和下划线,但是必须以字母或者下划线开头。( 在C99中,标识符还可以使用某些“通用字符名”

C语言是区分大小写 的 因为C语言是区分大小写的,许多程序员都会遵循在标识符中只使用小写字母的规范(宏命名除外)。为了使名字清晰,必要时还会插入下划线:

symbol_table  current_page  name_and_address

而另外一些程序员则避免使用下划线,他们的方法是把标识符中的每个单词用大写字母开头:

symbolTable  currentPage  nameAndAddress

(第一个字母有时候也用大写。)前一种风格在传统C中很常见,但现在后面的风格更流行一些,这主要归功于它在Java和C#(以及C++)中的广泛使用。当然还存在其他一些合理的规范,只要保证整个程序中对同一标识符按照同一种方式使用大写字母就行。 C对标识符的最大长度没有限制,所以不用担心使用较长的描述性名字。诸如current_page 这样的名字比cp 之类的名字更容易理解。

关键字

所有关键字 (keyword)对C编译器而言都有着特殊的意义,因此这些关键字不能作为标识符来使用。 注意,其中有5个关键字是C99新增的。

auto enum restrict ① unsigned break extern return void case float short volatile char for signed while const goto sizeof _Boo l① continue if static _Complex ① default inline ① struct _Imaginary ① do int switch
double long typedef else register union

因为C语言是区分大小写的,所以程序中出现的关键字必须严格按照表2-1所示的格式全部采用小写字母。(C99关键字_Bool 、_Complex 和_Imaginary 例外。)标准库中函数(如printf )的名字也只能包含小写字母。

请注意有关标识符的其他限制。某些编译器把特定的标识符(如asm )视为附加关键字。属于标准库的标识符也是受限的。误用这些名字可能会导致编译或链接出错。以下划线开头的标识符也是受限的。

C程序的书写规范