OverIQ-中文系列教程-一-

OverIQ 中文系列教程(一)

原文:OverIQ Tutorials

协议:CC BY-NC-SA 4.0

C 编程教程

C 语言基础

C 编程导论

原文:https://overiq.com/c-programming-101/intro-to-c-programming/

最后更新于 2020 年 7 月 27 日


C 语言是为在名为 UNIX 的操作系统中编程而创建的。它是丹尼斯·里奇在 20 世纪 70 年代创造的。实际上,C 源自一种叫做 B 的语言,它是由肯·汤普森写的,因此得名 C,以表明它是 B 语言的继承者

C 语言不是高级语言,而是具有高级语言的简单性和低级语言的强大性的中级语言。因此,用 C 语言编写的程序非常快。

c 是一种只有 32 个关键字的小语种(关键字是该语种用于做特定任务的保留词)。因此,它很容易学习。

C 语言的特点

c 是一种编译语言

编译器是将高级语言或中级语言转换为机器语言的程序,以便计算机能够执行代码。

效率

C 语言是一种非常有效的语言。用 C 写的程序非常快。因为 C 是为编程 UNIX 操作系统而设计的,所以 C 程序能够在有限的内存下快速运行是至关重要的。

轻便的

C 程序是可移植的。这意味着用 C 语言编写的程序可以在各种各样的操作系统上运行,几乎没有或根本没有修改。

力量

最强大和灵活的操作系统之一是用 C 语言编写的。C 语言仍然被用来为 Python、Perl、PHP、BASIC 等语言创建编译器和解释器。

与 UNIX 的集成

c 与 Unix、Linux 操作系统联系紧密。大多数 UNIX 和 Linux 系统已经捆绑了名为 GCC (GNU 编译器集合)的 C 编译器。事实上,有一些工具需要用户对 c 有很好的了解。

用 C 语言可以开发什么样的应用?

任何你想要的。C 语言用于创建 UNIX、Linux、Windows、Photoshop、Apache 网络服务器等。

像 MySQL、PostgreSQL、SQLite 等流行的数据库应用也是用 C 语言编写的。

有像 OpenGL 这样的图形库,用于渲染 2D,3D 矢量图形也是用 c 写的。

c 也被用来创建一个非常流行的版本控制系统,叫做 Git。

像 GTK 和 wxWidgets 这样的跨平台小部件工具包也是用 C 语言创建的

在下一课中,我们将学习如何安装 IDE 来创建和运行 C 程序。



安装 Code::Blocks

原文:https://overiq.com/c-programming-101/installing-code-blocks/

最后更新于 2020 年 7 月 27 日


要创建和运行程序,您需要两件事:

  1. 文字编辑器
  2. 编译程序

文本编辑器是您编写程序的地方。编译器把你的程序翻译成计算机能理解的 1 和 0 的二进制流。因此,每当您编译一个程序时,编译器都会创建一个名为可执行文件(having。. exe 扩展名),计算机可以执行该文件。

编写 C 程序时需要遵循的步骤。

  1. 创建程序
  2. 编译程序
  3. 运行程序

您可以使用默认的文本编辑器(如 Windows 中的记事本或 Linux 中的 vim)来创建程序,但是为了方便起见,我们将安装一个名为 Code::Blocks 的跨平台 IDE(集成开发环境)。

为什么要使用 IDE?

集成开发环境允许您从单个环境创建、编译和运行程序。Code::Blocks 附带一个名为 GCC 的编译器,用于编译 C 和 C++程序。如果您没有使用集成开发环境,那么您需要使用文本编辑器创建程序。然后要编译程序,你需要打开命令提示符或终端,输入一些命令。我们将首先学习如何使用集成开发环境编写一个简单的程序,然后我将向您展示如何使用文本编辑器创建程序,并使用 GCC 编译器编译它。

在窗口中安装 Code::Blocks

  1. 转到http://www.codeblocks.org/downloads/26并在“Windows XP / Vista / 7 / 8.x / 10”部分下向下滚动一点:“选择“Code::Blocks-16.01 Ming-nostup . zip”。

    对您来说,选择 MinGW 安装程序至关重要,因为这将在您的系统上安装 GCC 编译器。

  2. 下载后,双击安装程序运行它,您将看到以下窗口。单击“下一步”继续。

    code-blocks-installaton-finished

  3. 接受许可协议,然后单击“下一步”。

  4. 在下一个窗口中,Code::Blocks 将要求您选择要安装的组件。选择完全安装,它看起来像这样:

    确保选择了“MinGW 编译器套件”,因为此选项将在您的系统上安装 GCC。

  5. 选择目标文件夹(保持默认),然后单击下一步。

  6. 安装程序将继续完成安装。

  7. 安装完成后,Code::Blocks 将提示您运行它。

    单击否,然后单击下一步按钮。

    单击“完成”退出安装程序。

    这就完成了 Code::Blocks 在 Windows 上的安装。

在 Linux 中安装 Code::Blocks

Code::Blocks 可以在各种 Linux 发行版上运行。例如 Ubuntu、Fedora、CentOS 等。在本节中,我们将学习如何在 Ubuntu 16.04.1 上安装 Code::Blocks。

打开终端并键入以下命令。

x@ubuntu-pc:~$ sudo add-apt-repository ppa:damien-moore/codeblocks-stable

输入此命令后,系统会要求您按回车键。按回车键并输入下一个命令:

x@ubuntu-pc:~$ sudo apt-get update

该命令将更新系统上的所有包。最后,输入以下命令:

x@ubuntu-pc:~$ sudo apt-get install codeblocks codeblocks-contrib

此命令将在您的系统上下载并安装 Code::Blocks。

注意:如果是慢速连接,整个过程可能需要一些时间。所以,请耐心等待。

第一次跑步

在 Windows 上,转到开始>所有程序>Code::Blocks 以启动 Code::Blocks。或者,双击桌面上 Code::Blocks 的快捷图标。

在 Ubuntu 上,您可以通过发出以下命令从终端打开 Code::Blocks。

x@ubuntu-pc:~$ codeblocks

或者,点击启动器的顶部,键入一些关键字,如下图所示:

Code::Blocks 现在将要求您选择默认编译器。Code::Blocks 支持来自不同供应商的几个编译器。如果您的计算机除了 GCC 之外还有其他编译器,Code::Blocks 会检测到所有这些编译器,并在窗口中显示一个列表。这台电脑除了 GCC 没有任何编译器,这就是为什么只检测到 GCC 的原因。选择 GCC 编译器,点击“设为默认”,如下图截图所示:

单击“确定”继续。接下来,您将看到一个 Code::Blocks 闪屏。加载完成后,您将看到以下窗口。

注意:如果这是您第一次运行 Code::Blocks,可能会出现一个窗口,要求您选择 Code::Blocks 作为处理 C/C++源文件的默认应用。

选择“否,保持一切原样”,然后单击确定。

菜单栏和工具栏:像许多其他图形用户界面程序一样,Code::Blocks 也有菜单栏。工具栏提供了所有常用的命令,如构建、运行、保存、查找等。

起始页和编辑器:起始页是启动 Code::Blocks 时的默认页面,编辑器是我们编写代码的地方。

管理窗口:该窗口显示项目中所有可用的源文件。

日志窗口:在这里你会得到各种各样的日志,比如构建消息、编译错误、警告等等。



创建和运行第一个 C 程序

原文:https://overiq.com/c-programming-101/creating-and-running-the-first-c-program/

最后更新于 2020 年 9 月 24 日


创建新项目

要在 Code::Blocks 中创建和运行程序,首先必须创建一个项目。

那么什么是项目呢?

最简单地说,您可以将一个项目视为不同源文件的集合。一个小项目也可以有一个单独的源文件。

要创建一个新程序,我们必须先创建一个项目。

1)转到文件>新建>项目。将显示一个向导,如下图所示。

选择控制台应用,然后单击开始。

2)将显示控制台应用向导。点击下一步按钮。

3)在控制台应用向导的下一个窗口中,选择要在项目中使用的语言。

选择 C,然后单击下一步。

4)在下一个窗口中,输入项目标题为“第一个应用”,并选择保存“第一个应用”项目的路径。

单击“下一步”按钮继续。

5)此窗口允许您为项目选择编译器。选择 GNU GCC 编译器,其他设置保持默认。

单击完成。将为您创建一个新项目以及一些默认代码。

一旦创建了项目,Code::Blocks 集成开发环境将如下所示:

双击“源”文件夹以查看其下的文件。看看已经填充了新创建的项目文件的管理窗口。

可以看到,此时项目只包含一个文件main.c。双击打开编辑器窗口中的main.c

让我们用下面的代码替换默认值。

#include <stdio.h>

int main()
{
    printf("My First App");
    return 0;
}

现在试试

注意:不要复制粘贴程序,只需打字,对你会更有利。我们将在后面的章节中详细讨论这个程序是如何工作的。

通过按 Ctrl + S 或点击工具栏中的保存图标来保存程序。

通过从菜单栏中选择Build > Build或按 Ctrl + F9 来编译程序。如果编译成功,您将在日志窗口的构建日志选项卡上看到一些消息。

注意日志的最后一行写着"0 error(s), 0 warning(s) "。它只是意味着程序编译成功,没有任何错误和警告。

通过从菜单栏中选择构建>运行或按下 Ctrl + F10 来运行程序。当您运行该程序时,您将看到如下窗口:

要关闭此窗口,请按键盘上的任意键。

提示:也可以按 F9 或 Build > Build and Run 一步编译运行程序。

救救我!编译时出错

当您在键入程序时出错时,就会出现编译错误或编译时错误。这些打字错误被称为语法错误。就像英语有语法规则一样,计算机语言也有语法规则。换句话说,语法规定了语言应该如何书写。例如,这样一个规则是:C 中的每个语句都必须以分号(;)结束。

编译器会在以下情况下报告语法错误:

  • 以不带分号(;)的语句结尾。
  • 键入错误的关键字。
  • 有左大括号({)没有右大括号(})。
  • 试图使用未声明的变量。等等...

因此,请确保您已经按原样键入了代码,没有错别字或拼写错误。

编译器在编译程序时遇到语法错误。它报告语法错误消息。此消息包含发现错误的行号和错误描述。

编译器可以在两个级别检测问题:警告和错误。

警告:简单来说就是你做错了什么。虽然它在语法上是有效的,但它可能会在将来引起问题。Code::Blocks 以蓝色显示警告消息。警告不会停止编译过程。

错误:错误是程序中的致命缺陷。错误停止了程序的编译。要编译程序,您必须首先解决所有错误(语法错误)。Code::Blocks 以红色显示错误。

遇到语法错误时,Code::Blocks 会在构建消息选项卡中显示大量信息。例如:假设错误地将分号留在了第 5 行的末尾。

#include <stdio.h>

int main()
{
    printf("My First App")
    return 0;
}

现在试试

如果你编译了这个程序,你会得到以下错误。

从日志中可以看到,编译器在第 6 行报告了一个关于缺少分号的错误。尽管编译器提供的错误消息无疑是有用的,但它们可能非常准确,也可能不太准确。因此,编译器报告的错误可能不会反映问题的原始原因。例如:在上面的程序中,编译器在第 6 行报告了一个错误,但是我们知道实际问题是在第 5 行,因为缺少分号(;)。因此,本次讨论的要点是,当编译器报告语法错误时,不要照原样接受编译器的消息,要找到实际错误,请查看实际报告错误的位置上方或下方的几行。

您程序中的错误现在应该已经解决了,如果没有,请在下面评论,我们将尝试一起解决它。



C 程序的基本要素

原文:https://overiq.com/c-programming-101/basic-elements-of-a-c-program/

最后更新于 2020 年 9 月 24 日


一个基本的 C 程序有以下形式。

comments
preprocessor directives

int main()
{
    statements;
}

这是典型的 C 程序的结构。让我们稍微详细地讨论一下每个部分的含义。

预处理器指令

在编译一个程序之前,它要经过一个叫做预处理器的特殊程序(它内置于编译器中)。以井号(#)符号开头的行称为预处理器指令或只是指令。预处理器指令必须放在文件的开头。这些指令执行不同类型的功能,但是现在,我们将使用它们来包含一个头文件。那么什么是头文件呢?头文件包含我们希望在程序中使用的函数的信息。它总是以.h扩展名结束。例如,stdio.h头文件包含关于输入和输出功能的信息。包含头文件后,您可以使用头文件中定义的任何函数。预处理器指令不以分号(;)结束。要在程序中包含stdio.h头文件,请执行以下操作:

#include<stdio.h>

上一行使预处理器在程序的这一点上包含一份stdio.h头文件。头文件由 C 编译器提供。如果你的程序需要一个以上的头文件,那么把它们放在自己的行上。例如,C 标准库包含一个名为math.h的头文件,其中包含数学函数和常量。要在程序中包含stdio.hmath.h,请执行以下操作:

#include<stdio.h>
#include<math.h>

功能

函数是一个独立的代码块,其他语言称之为过程或子程序。一个函数只是一系列组合在一起并给定名称的语句。一个函数做一些非常具体的事情,例如计算一个数的阶乘,求两个数的和,等等。一个 C 程序可能包含许多功能,但main()是强制性的。main()功能很特别,因为当操作系统开始执行程序时,main()会被自动调用。所以你有必要定义这个函数。

声明

您可以将该语句视为程序运行时要执行的计算机命令。一般来说,所有语句都以分号(;)结束,尽管也有一些例外。

评论

注释用于在编程时写一些有价值的笔记。它们也增加了程序的可读性。注释可以解释程序的目的,也有助于理解程序如何工作。注释不是编程语句,它们在编译程序时被编译器忽略。注释几乎可以出现在程序的任何地方。写评论有两种方法:

  1. 单行注释。
  2. 多行注释。

单行注释

单行注释从//开始,一直到行尾。

// including stdio.h header file

#include<stdio.h>

#include<math.h> // math.h contains all mathematical related function

多行注释

多行注释以/*开始,以*/结束。在/**/之间的一切都将被编译器忽略。

/*
Author: overiq.com
Purpose: Learning C
*/

#include<stdio.h>

现在你知道了 C 程序的基本结构。让我们创建一个简单的 C 程序。在 Code::Blocks 中创建一个名为"Hello"的新项目,并用以下代码替换默认代码:

/*
  Project: Hello
  Author: overiq.com
*/

#include<stdio.h> // include stdio.h library

int main(void)
{
    printf("Hello"); // print "Hello" to console
    return 0; // return 0 to operating system
}

现在试试

工作原理: C 语言没有任何输入输出的设施。因此,输入和输出操作由编译器提供的一组库来执行,它们在形式上不是 C 语言的一部分,但它们被认为是 C 语言中输入和输出操作的标准。执行输入和输出操作的一组库被称为标准输入/输出库。为了在我们的程序中包含输入和输出工具,我们需要包含stdio.h头文件。要包含头文件,必须在文件开头使用#include预处理器指令,如下所示:

#include<stdio.h>

int main(void)行启动main()功能,int表示main()功能返回整数值。那么我们为什么要返回值呢?当程序运行时,操作系统需要某种方式来决定程序是否成功运行。0的返回值表示程序运行成功,反之,非零值表示有问题。接下来,注意关键字 main 后面括号中的关键字voidvoid表示main()函数不接受任何参数。在单词main(void)之后,第 9 行的左括号({)开始函数的主体。第 12 行中相应的右括号(})结束了函数的主体。您必须始终关闭函数体,否则编译器将报告语法错误。在大括号({})之间,我们有函数的主体。main()功能仅由两个语句组成。第 10 行的语句使用printf()库函数将"Hello"打印到控制台(我们在第课【输入和输出】中详细讨论了printf()函数)。声明。

return 0;

做两件事。首先导致main()函数终止,其次提供0返回值。请注意,main()函数仅由两个语句组成(在第 10 行和第 11 行),它们都以分号(;)结束。这是一个基本的 C 程序是如何工作的。本章已经介绍了 C 程序的基本组成部分。在下一章中,我们将学习 C 语言中的数据类型



关键字和标识符

原文:https://overiq.com/c-programming-101/keywords-and-identifiers/

最后更新于 2020 年 7 月 27 日


C 语言字符集

在 C 语言中,您可以使用以下字符。字母

a, b, c, ... z
A, B, C, ... Z

数字

0,1,2,3,4,5,6,7,8,9

特殊符号下表显示了 C 语言中广泛使用的一些特殊字符

性格;角色;字母 意义
+ 加号
- 减号
* 增加
/ 分开
% 百分比符号或模数运算符
() 圆括号
{} 大括号
[] 方括号
= 等号
, 逗号
; 分号
: 结肠
' 单引号
" 双引号
? 问号
. 句点或点符号
# 混杂
^ 插入符号
~ 颚化符
! 感叹号
& &的记号名称
&#124; 管道字符

转义序列

转义序列用于打印一些不能用键盘直接打印的特殊字符。例如,换行符、制表符、回车符等。转义序列由一个反斜杠字符(\\)后跟一个特定的转义字符组成。下表列出了常见的转义序列。

换码顺序 意义 它有什么作用?
\n 新行 将光标移动到下一行的开头。
\t 标签 将光标移动到下一个制表位。
\b 退格键 在当前行上将光标向后移动一个空格。
\r 回车 将光标移动到当前行的开头。
\a 铃声(警报) 发出嘟嘟声。
\\ 反斜线符号 打印反斜杠()字符。
\0 \0 字符表示空字符。
\' 单引号 打印单引号(')字符。
\" 双引号 打印双引号(")字符。

!换行符(\n)、退格符(\b)、回车符(\r)、制表符(\t)、空格()被称为空白字符。

`## 关键字

关键字是 C 语言用来表示特定事物的一些保留词。在 C 语言中,关键字是用小写字母写的。c 只有 32 个关键字。

标识符

标识符是我们用来命名实体的词,如变量、函数、数组、结构、符号常量等。标识符命名规则如下:

  1. 标识符只能由字母、数字或下划线(_)组成。
  2. 第一个字符应该是字母或下划线(_)。
  3. 标识符不应是关键字。
  4. 标识符可以是任何长度。

c 是区分大小写的语言,因此my_varMY_VAR是两个不同的标识符。有效标识符的一些示例:num_addressuser_nameemail_1无效标识符的示例:1digit–标识符不能以数字my var开头–标识符不能包含空格字符intint是关键字some#–不允许使用井号(#)字符


    • *`

C 语言中的数据类型

原文:https://overiq.com/c-programming-101/data-types-in-c/

最后更新于 2020 年 9 月 24 日


C 语言支持四种基本数据类型:

  1. (同 Internationalorganizations)国际组织
  2. 漂浮物
  3. 两倍

int -用于存储1-991000等整数值。

char -用于存储'a''b''z'等单个字符。

float -用于存储单精度浮点数。

double -用于存储双精度浮点数。

double型比float型精度更高。这仅仅意味着double类型比float类型在小数点右边提供了更多的数字。准确地说,float提供 6 位数的精度,double提供 14 位数的精度。

需要注意的是floatdouble代表相同的类型——浮点数。唯一的区别在于精度的多少。

C 语言也有一种叫做类型限定符的东西,你可以把它应用到这些基本的数据类型上来获得更多的类型。

两种类型的限定符:

  1. 尺寸限定符- shortlong
  2. 符号限定词- signedunsigned

提供这些限定符的原因是程序员可以尽可能精确地选择适合程序的数字范围,这使得程序更加高效。

有符号和无符号限定符

当使用unsigned限定符时,数字总是正数,当使用signed时,数字可以是正数或负数。如果没有提到限定词,则假定为signed限定词。unsigned限定词常用于我们事先知道数字总是正数的时候。signed数据类型的值的范围总是小于unsigned类型的值的范围。此外,这些限定符只能用于intchar类型。

短而长

当使用short限定符时,一个类型的范围缩小,另一方面,使用long限定符增加了该类型的范围。int类型可以同时使用两个限定词,double只能使用long。它们不能与charfloat一起使用。

不像 Java、C#这样的语言,数据类型的大小是固定的。在 C 语言中,数据类型的大小取决于机器。对于一台旧的 16 位机器来说,int的大小是 2 字节。由于 2 字节等于 2*8=16 位,在 16 位机器上int可以采用从-3276832767的值。

另一方面,如果你在 32 位或 64 位机器上,那么int的大小是 4 字节。换句话说,在 32 位或 64 位系统上,int可以采用从-21474836482147483647的值。

下表显示了 32 位机器上不同数据类型的大小和范围。

数据类型 带限定符的数据类型 大小(字节) 范围
char charsigned char 1 -128127
unsigned char 1 0255
int intsigned int 4 -21474836482147483647
unsigned int 4 04294967295
short intshort signed int 2 -32768 到 32767
unsigned short int 2 065535
long intsigned long int 4 -21474836482147483647
unsigned long int 4 04294967295
float float 4 1.1754e-383.4028e+38
double double 8 2.2250e-3081.7976e+308
long double 10 3.4E-49323.4E+4932

要确定系统上不同类型的范围和大小,请运行以下程序:

/**************************************************
 Program to determine size and range of data types
***************************************************/

#include<stdio.h> // include stdio.h library
#include<limits.h>
#include<float.h>

int main(void)
{    
    printf("%30s %12s %28s\n", "", "Size", "Range");

    printf("%-30s %10lu %25d - %d\n", "char or signed char", sizeof(char), CHAR_MIN, CHAR_MAX);
    printf("%-30s %10lu %25d - %d\n", "unsigned char", sizeof(unsigned char), 0, UCHAR_MAX);
    printf("%-30s %10lu %25d - %d\n", "int or signed int", sizeof(int), INT_MIN, INT_MAX);
    printf("%-30s %10lu %25d - %ud\n", "unsigned int", sizeof(unsigned int), 0, UINT_MAX);
    printf("%-30s %10lu %25hd - %hd\n", "short int or short signed int", sizeof(short int), SHRT_MIN, SHRT_MAX);
    printf("%-30s %10lu %25d - %d\n", "unsigned short int", sizeof(unsigned short int), 0, USHRT_MAX);
    printf("%-30s %10lu %25ld - %ld\n", "long int or signed long int", sizeof(long int), LONG_MIN, LONG_MAX);    
    printf("%-30s %10lu %25d - %lu\n", "unsigned long int", sizeof(unsigned long int), 0, ULONG_MAX);

    printf("%-30s %10lu %25le - %le\n", "float", sizeof(float), FLT_MIN, FLT_MAX);
    printf("%-30s %10lu %25le - %le\n", "double", sizeof(double), DBL_MIN, DBL_MAX);
    printf("%-30s %10lu %25Le - %Le\n", "long double", sizeof(long double), LDBL_MIN, LDBL_MAX);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

Size                        Range
char or signed char                     1                      -128 - 127
unsigned char                           1                         0 - 255
int or signed int                       4               -2147483648 - 2147483647
unsigned int                            4                         0 - 4294967295d
short int or short signed int           2                    -32768 - 32767
unsigned short int                      2                         0 - 65535
long int or signed long int             8      -9223372036854775808 - 9223372036854775807
unsigned long int                       8                         0 - 18446744073709551615
float                                   4              1.175494e-38 - 3.402823e+38
double                                  8             2.225074e-308 - 1.797693e+308
long double                            16            3.362103e-4932 - 1.189731e+4932

!前面的输出来自 64 位机器,可能会因您的系统而异。

!头文件limits.hfloat.h定义了一些符号常量,分别表示整数和浮点数的最小值和最大值。

不要深究这个程序是如何工作的。在接下来的课程中,我们将详细讨论一切。

你可能想知道char类型是否用于字符,为什么它有一个整数范围。

答案是——内部字符用数字表示。我们将在下一课详细讨论这个话题。



C 语言中的常量

原文:https://overiq.com/c-programming-101/constants-in-c/

最后更新于 2020 年 7 月 27 日


常量或有时称为文字是一个不变的实体,而变量是一个可以变化的实体。在本节中,我们将讨论常量,变量在第课中讨论,变量在 C 中讨论。

常量的类型

在 C 语言中有两种类型的常量:

  1. 主要常量。
  2. 次要常量。

如图所示,这两种类型进一步分为更多类别。

这里我们将只讨论主要常量,次要常量将在后面讨论。要创建主常量,您需要遵守以下规则。

数字常量

数字常量是可能有也可能没有小数点的数字。创建数字常量的规则是:

  1. 必须至少有一个数字。
  2. 不允许使用空格、逗号或任何其他特殊符号。
  3. 它可以是正的,也可以是负的,如果整数常量前面没有符号,那么它就被认为是正的。

数字常量有两种类型:

  1. 整数常量
  2. 浮点或实常量

整数常量

整数常量没有小数点(.)。它们可以用十进制数(以 10 为基数)、八进制数(以 8 为基数)和十六进制数(以 16 为基数)来写。

十进制常量

它包含介于09之间的数字,但不应以零开头。

43, 199, 3452, -100

八进制常量

包含07之间的数字,必须以零开头。

012, 034, 01144

十六进制常量

包含从09的数字,以及从a - f的字母(大写或小写),必须始终以0x0X开头。

0x23, 0Xff, 0x37a

如前所述,字母可以是大写或小写。

0x23, 0XFF, 0x37A

我们也可以混合十进制、八进制和十六进制。

int i = 12 + 045 + 0x3a

永远记住,无论我们用什么数字系统来表示一个数字;它们对如何在内存中内部存储没有影响(最终每个数字和字符都存储为二进制数字流)。八进制和十六进制是编写低级程序时常用的,所以,在开始的时候,你不会看到太多。

默认情况下,整数常量的类型为int。在 32 位系统中,int类型占用 4 字节内存。如果一个整数的值太大而不适合int的话,那么常量的类型就提升为long int。同样,如果这个值对于long int来说仍然太大,那么它最终将被提升到unsigned long int

确定八进制和十六进制类型的规则略有不同,工作原理如下:

编译器将遍历以下类型(从上到下),直到找到可以表示常量的合适类型。

unsigned int
long int
unsigned long int

也可以通过追加lL将整数常量的类型明确指定为long int:

10l, 3789L, 094l, 0xabL

同样,要将整数常量的类型指定为unsigned int并在其后追加uU:

23u, 034U, 0x8au

我们也可以使用uUlL将整数常量的类型指定为unsigned long int89ul -类型为unsigned long int

浮点或实常量

有小数点的数字常量称为浮点或实常量。浮点常量可以写成两种形式:

  1. 分数形式
  2. 指数形式或科学符号

以下是以分数形式创建浮点常量的规则:

  1. 必须至少有一个一位数
  2. 必须有小数点
  3. 可以是正数或负数,默认值为正数
  4. 不允许使用逗号、空格或任何其他符号

以下是一些例子:

3.14
899.0
-0.999

指数形式用于数字太小或太大的情况。例如0.00000941可以表示为9.41e-6e之前的部分称为尾数即9.41,而e之后的部分称为指数即-6

以下是以指数形式创建浮点常量的规则:

  1. 尾数和指数必须用eE隔开。
  2. 尾数可以是正数,也可以是负数,默认值为正数。
  3. 指数必须至少有一个数字。
  4. 指数可以是正的或负的,缺省值是正的

指数形式的浮点数的一些例子是:

100.34e4
-56E10
0.233E10
-0.94e15

默认情况下,浮动常量的类型为double。我们可以通过在常量的末尾添加fF来明确地将浮点常量的类型称为float。例如:

12f , -0.87f

同样,我们可以通过在末尾追加lL来明确地将浮点常量的类型称为long double

12.13l, -98.12L

字符常量

字符常量是用单引号括起来的单个字母、数字或任何特殊符号。以下是一些例子:

'A', 'c', '4', '$', '^'

注意:字符常量必须始终用单引号括起来,所以下面是错误的。

"a"
"e"

字符常量的最大长度为 1 个字符。这意味着您不能在单引号中放一个以上的字符,如下所示:

'ab' // Wrong
'12' // Wrong

你可能已经知道,在计算机内存中,所有东西都是以二进制形式存储的。很容易看出整数和浮点常量是如何存储为二进制数的。但是字符常量是如何存储在内存中的呢?事实证明,每个字符常量都有一个唯一的整数与之相关联。ASCII 表表示用于表示英语中每个已知字符的十进制数。

请考虑以下陈述:

char ch = 'a'; // declaring a variable ch and assigning 'a' to it

这里我们声明一个类型为char的变量ch,并为其分配一个字符常量'a'(现在不要太担心语法,在下一章中我们将详细讨论变量)。虽然看起来我们将'a'分配给变量ch,但实际上分配给ch的是'a'的 ASCII 值,即 97。这里有一个小秘密——在 C 语言中,字符被视为小整数,所以它们实际上有int类型而不是char类型。A charsigned char的范围为-127 至 128,而 aunsigned char的范围为 0 至 255。

串常量

字符串常量由用双引号("")括起来的零个或多个字符组成。在字符串的末尾,编译器会自动放置空字符'\0'。以下是字符串常量的一些示例:

"hello"
"123"
"" // This is empty string it consists of only one character '\0' which is added by the compiler automatically.

注意:虽然不是 Primary 常量的正式组成部分,但为了完整起见,这里给出了字符串常量。c 没有字符串的数据类型,它们存储为字符数组。我们将在它自己的章节中详细了解字符串。

符号常量

如果我们想在一个程序中多次使用常量,那么我们可以为它提供一个名称。例如:如果程序中有几个地方需要用到常量Π = 3.141592,那么我们可以给它取一个名字,用那个名字,而不用写这个长数字。这个常量叫做符号常量。它通常在程序开始时定义。以下是创建符号常量的语法。

#define NAME VALUE

#define是一个预处理器指令,就像#include一样,这就是它不以分号(;)结尾的原因。

NAME表示我们想给常量取的名字,一般用大写。

VALUE可以是数字、字符或字符串常量。让我们创建一个名为PI的符号常量。

#define PI 3.141592

程序编译时,预处理器用其值替换PI的每次出现。因此声明:

printf("Circumference of circle = %f", 2*PI*4);

成为

printf("Circumference of circle = %f", 2*3.141592*4);

符号常量的使用使程序更易于维护和阅读。例如:假设我们想要更精确的结果,所以我们决定将Π的值从3.141592更新为3.14159265359,如果我们没有使用符号常量,那么我们将不得不经历3.141592的每次出现并更新它们。然而,由于我们已经在#define指令中定义了PI,我们只需要在单个地方进行更改。



C 语言中的变量

原文:https://overiq.com/c-programming-101/variables-in-c/

最后更新于 2020 年 7 月 27 日


变量是用来存储数据的,它们之所以这样命名是因为它们的内容可以改变。c 是一种强类型的语言,它只是意味着一旦你声明了某个数据类型的变量,那么你就不能在程序的后面改变变量的类型。回想一下,C 提供了 4 种基本类型:

  1. int
  2. float
  3. double
  4. char

声明变量

在使用变量之前,必须先声明它。声明变量包括指定变量的类型和名称。请始终记住,命名变量的规则与命名标识符的规则相同。变量的类型和取值范围取决于变量的类型。下面是变量声明的语法。

语法: datatype variablename;

让我们创建并声明一个变量i

int i; // declaring an int variable

这里i声明为int类型的变量,所以只能取整数值,不能用i存储字符串常量。在 16 位系统变量上i可以从-3276832767取值,而在 32 位系统上i可以从-21474836482147483647取值。

如果需要,可以声明多个相同类型的变量,如下所示:

int x,y,z; // declaring three variables x,y and z of type int

这里xyzint型。

初始化变量

当一个变量被声明时,它包含一个未定义的值,也称为垃圾值。如果需要,可以使用赋值运算符(即=)为变量赋值。给变量赋值称为变量初始化。下面是一些变量初始化的例子:

int a = 12, b = 100;
float f = 1.2;
char ch = 'a';
double d1, d2, d3 = 1.2;

注意:在最后一条语句中,只有d3变量被初始化,d1d2变量包含一个垃圾值。



输入和输出

C 语言的输入和输出

原文:https://overiq.com/c-programming-101/input-and-output-in-c/

最后更新于 2020 年 9 月 24 日


大家已经知道,C 语言的输入输出操作需要stdio.h头文件,本章我们将讨论两个输入函数:scanf()getchar()以及两个输出函数:printf()putchar()。但是首先,我们将研究一些叫做转换规范的东西,因为像scanf()printf()这样的函数使用这个工具。

转换规范

转换规范用于指定数据类型。每个转换规范都以(%)符号开头。以下是一些常见的转换规格:

转换规格 描述
%c 单个字符
%d 整数
%f 浮点数
%x 十六进制整数
%o 八进制整数
%i 整数、十六进制或八进制
%s 一根绳子
%u 无符号整数
%h 短整数
%lf 一个长范围浮点数

输出数据

printf()功能用于向控制台输出数据。语法: printf("Control string", variable1, variable2 , ...); 控制字符串:包含转换规范和双引号内的文本。此参数控制输出在屏幕上的显示方式。变量:我们希望在控制台中打印其数据的变量。除了传递变量,我们还可以传递常量和表达式。此参数是可选的。如果控制字符串不包含任何转换规范,则不指定变量。示例 1:打印字符串以下程序使用printf()语句将字符串打印到控制台。

#include<stdio.h>

int main()
{
    printf("Control string with no conversion specification");

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Control string with no conversion specification

这里控制字符串只包含文本,没有转换说明。所以不需要指定任何变量或表达式。

例 2:打印整数

#include<stdio.h>

int main()
{
    int ival = 100;

    printf("%d", ival);

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

100

这里,控制字符串包含单个%d字符,这意味着将显示一个整数值。我们还可以在控制字符串中使用文本以及转换规范。

示例 3:打印整数和一些文本

#include<stdio.h>

int main()
{
    int sal = 200000;
    printf("Basic salary: %d", sal);

    return 0;
}

现在试试

预期输出:

Basic salary: 10000

这里,控制字符串包含文本"Basic salary: "以及转换规范%d。文本将按原样显示,%d将被变量sal的实际值替换。

示例 4:打印浮点数

#include<stdio.h>

int main()
{
    float ival = 3.14551;
    printf("%f", ival);
    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

3.145510

这里,控制字符串包含单个%f转换规范字符,这意味着将显示一个浮点值。

例 5:打印字符

#include<stdio.h>

int main()
{
    char ival = 'z';
    printf("%c", ival);
    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

z

这里,控制字符串包含单个%c转换规范,这意味着将显示一个字符。

示例 6:打印阵列

#include<stdio.h>

int main()
{

    char str[] = "Testing printf()";
    printf("%s", str);
    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Testing printf()

这里,控制字符串包含单个%s转换规范,这意味着将显示一个字符串。

例:7

#include<stdio.h>

int main()
{
    int salary = 20000;
    int expenses = 15000;
    int saving = 5000;

    printf("Salary: %d , Expenses: %d, Saving: %d", salary, expenses, saving);
    return 0;
}

现在试试

预期输出:

Salary: 20000 , Expenses: 15000, Saving: 5000

这里控制字符串包含文本以及三个转换规范。一般情况下,转换规范和变量的数量是相等的,这对scanf()printf()函数都是如此。

例:8

我们已经知道,就像%d用来表示十进制数一样。类似地%o%x分别用来表示八进制和十六进制数字。

#include<stdio.h>

int main()
{
    int num=100;
    printf("Octal equivalent of %d = %o\n", num, num);
    printf("Hexadecimal equivalent of %d = %x", num, num);
    return 0;
}

现在试试

预期输出:

Octal equivalent of 100 = 144
Hexadecimal equivalent of 100 = 64

示例 9:打印换行符

我们在前面的章节中已经学习了转义序列。让我们看看如何使用它们来正确格式化我们的输出。下面的程序演示了如何使用转义序列正确格式化数据。

#include<stdio.h>

int main()
{
    int salary = 20000;
    int expenses = 15000;
    int saving = 5000;

    printf("Salary: %d \nExpenses: %d \nSaving: %d\n", salary, expenses, saving);
    return 0;
}

现在试试

预期输出:

Salary: 20000
Expenses: 15000
Saving: 5000

当遇到\n换行符时,它将光标移动到下一行的开头,并从那里开始打印。

示例 10:打印标签

#include<stdio.h>

int main()
{
    int salary = 20000;
    int expenses = 15000;
    int saving = 5000;
    printf("Salary: %d \tExpenses: %d \tSaving: %d", salary, expenses, saving);
    return 0;
}

现在试试

预期输出:

Salary: 20000 Expenses: 15000 Saving: 5000

\t被称为制表符。当遇到\t时,它将光标移动到下一个制表位。\t常用于以表格形式显示数据。

例 11:

另一个常用的转义序列是\",表示"字符。由于"字符标志着一个字符串的开始和结束,我们不能直接在字符串中使用它。

#include<stdio.h>

int main()
{
    printf("Enough \"Hello World\" programs");
    return 0;
}

现在试试

预期输出:

Enough "Hello World" programs

例 12:

我们知道\标志着转义序列的开始,这就是为什么我们不能直接在字符串内部使用它,因为编译器会假设它是转义序列的开始。要打印单个\我们必须在一个字符串中使用两个\字符。

#include<stdio.h>

int main()
{
    printf("Path : C:\\Users\\X");
    return 0;
}

现在试试

预期输出:

Path : C:\\Users\\X

\转义序列常用于显示窗口路径名。

从键盘读取输入

scanf()功能用于读取键盘输入。

语法: scanf("Control string", address1, address2 , ...);

您必须向该函数传递至少两个参数。

控制字符串:包含一个或多个用双引号括起来的转换规范的字符串。转换规范的数量取决于我们想要输入的变量的数量。

下一个参数address1是变量的地址,scanf()函数至少需要一个地址。变量的地址可以通过在变量名前面加上(&)符号来找到。

scanf()函数语法中...(称为省略号)表示scanf()可以接受可变数量的参数。

下面的程序演示了如何使用scanf()功能从键盘接收输入。

#include<stdio.h>

int main()
{

    // declare variable i
    int i;

    // Ask user to enter a number
    printf("Enter a number: ");

    // accept input from keyboard
    scanf("%d", &i);

    // print the entered number
    printf("You entered %d", i);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter a number: 99
You entered 99

在上面的程序中,我们希望用户输入一个数字,这就是为什么在scanf()中使用单个%d转换规范的原因。如果我们想让用户输入一个字符串,我们应该使用%s。同样,单个字符使用%c,而float使用%f

阅读角色

#include<stdio.h>

int main()
{

    // declare variable i
    char ch;

    // Ask user to enter a character
    printf("Enter a character: ");

    // accept input from keyboard
    scanf("%c", &ch);

    // print the entered character
    printf("You entered %c", ch);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter a character: q
You entered q

这里控制字符串包含单个%c转换规范,这意味着应该输入单个字符。同样,您可以要求用户输入一个浮点数。

#include<stdio.h>

int main()
{

    // declare variable i
    float fp;

    // Ask user to enter a floating point number
    printf("Enter a floating point number: ");

    // accept input from keyboard
    scanf("%f", &fp);

    // print the entered float point number
    printf("You entered %f", fp);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter a floating point number: 212.3441
You entered 212.344101

接受多个值

单个scanf()功能也可用于输入多个值。

#include<stdio.h>

int main()
{

    // declare variable x and y
    int x, y;

    // Ask user to enter 2 number
    printf("Enter two numbers: ");

    // accept input from keyboard
    scanf("%d%d", &x, &y);

    // print the entered numbers
    printf("Value of x = %d and y = %d", x, y);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter two numbers: 1100 3341
Value of x = 1100 and y = 3341

这里使用了两个%d转换规范字符,这意味着应该输入两个整数值。要同时输入两个数字,您需要输入第一个数字,按空格键,然后输入第二个数字。

1100 3341

scanf()输入多个值时,这些值可以用空格、制表符或换行符(默认)等空白字符分隔,但您可以通过在转换规范之间放置特定字符来更改此行为。让我们举个例子来说明这一点。

#include<stdio.h>

int main()
{

    // declare variable x and y
    int x, y;

    // Ask user to enter 2 number
    printf("Enter two numbers: ");

    // accept input from keyboard
    scanf("%d:%d", &x, &y);

    // print the entered numbers
    printf("Value of x = %d and y = %d", x, y);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter two numbers: 145:631
Value of x = 145 and y = 631

这里冒号(:)字符用在两个%d之间。这意味着现在你必须输入第一个数字,然后是一个冒号(:),接着是第二个数字。

以下程序要求用户输入由逗号(,)分隔的三个整数。

#include<stdio.h>

int main()
{

    // declare variable x, y and z
    int x, y, z;

    // Ask user to enter 3 number
    printf("Enter three numbers: ");

    // accept input from keyboard
    scanf("%d,%d,%d", &x, &y, &z);

    // print the entered numbers
    printf("Value of x = %d , y = %d, z = %d", x, y, z);

    // signal to operating system everything works fine
    return 0;

}

现在试试

预期输出:

Enter three numbers: 341,881,4124
Value of x = 341 , y = 881, z = 4124

为了更好的可读性,我们可以在转换规范之间包含一些空格。例如:

#include<stdio.h>

int main()
{

    // declare variable x, y and z
    int x, y, z;
    ...

    // accept input from keyboard
    scanf("%d %d %d", &x, &y, &z);
    ...
}

除了更好的可读性,它们没有任何意义。所以上面的代码本质上与:

#include<stdio.h>

int main()
{

    // declare variable x, y and z
    int x, y, z;
    ...

    // accept input from keyboard
    scanf("%d%d%d", &x, &y, &z);
    ...
}

字符输入输出

getchar()putchar()宏用于 C 语言中的字符 i/o,我们将在后面的章节中讨论什么是宏,但目前将其视为函数。getchar()从标准输入即键盘读取单个字符,putchar()向标准输出即控制台输出一个字符。

// Program to input a character then print it
#include<stdio.h>

int main()
{
    char ch;

    printf("Enter a character: ");
    ch = getchar();

    printf("The entered character is: ");
    putchar(ch);

    // signal to operating system everything works fine
    return 0;
}

预期输出:

现在试试

Enter a character: a
The entered character is: a

在下一章中,我们将学习 C 语言的格式化输入和输出

致命扫描 f()

scanf()函数包含几个我们没有讨论过的陷阱。考虑以下程序:

#include <stdio.h>

int main()
{
    int n;
    char ch;

    printf("Enter a number: ");
    scanf("%d", &n);

    printf("Enter a character: ");
    scanf("%c", &ch);

    printf("\n\n");

    printf("n = %d\n", n);
    printf("c = %c\n", ch);

    return 0;
}

现在试试

这里没有什么特别的事情发生,只是一个简单的程序,要求用户输入一个数字和一个字符,但这是一个骗局,运行程序,自己去看。

预期输出:

Enter a number: 100
Enter a character:

n = 100
c =

一输入数字程序就显示出来,不用等你输入字符,为什么会这样?

让我们更详细地讨论一下scanf()函数。

当输入被输入时,它被存储在一个叫做输入缓冲器的临时存储器中。考虑以下scanf()呼叫:

scanf("%d", %a);

假设用户进入445\n。现在输入缓冲区中的内容是:

445\n

这里我们提供了%d转换规范,意思是我们想scanf()读取一个数字。但是由于scanf()不知道你的号码会有多长,所以它会一直读取数字,直到遇到非数字字符(在本例中为\n)。scanf()读取\n字符,因为它不是一个数字,所以它将\n 推回到输入缓冲区。

此时,输入缓冲区的内容为:

\n

这里是规则 1:被推回输入缓冲区的字符将首先被scanf()的后续调用读取。

现在我们可以解释我们的程序中发生了什么。

假设用户输入了 100。现在输入缓冲区的内容是:

100\n

首先,第 9 行的scanf()调用读取 100 并将\n字符推回到输入缓冲区。现在缓冲区的内容是:

\n

第 12 行的第二个scanf()语句是\n字符。所以现在变量ch包含一个换行符。我们可以通过打印换行符\n的 ASCII 值 10 来验证这个事实。在第 17 行的printf()声明之后添加以下print()声明。

printf("ASCII value of c = %d\n", ch);

运行程序并输入如下输入:

100\n

预期输出:

Enter a number: 100
Enter a character:

n = 100
c =
ASCII value of c = 10

这验证了 ch 包含换行符(\n)的事实。显然,问题出现了如何解决这个问题?

事实证明,如果控制字符串中有一个或多个空白字符,scanf()会从输入缓冲区中反复读取空白字符,直到遇到非空白字符。格式字符串中的空白字符匹配输入中的任意数量的空白字符,包括无。

因此,如果我们在%c之前添加一个或多个空白字符,这将导致scanf()在读取字符之前读取所有空白字符。

scanf(" %c", &ch); // notice the white-space preceding %c

另一种解决方案是在读取字符之前,使用以下函数刷新缓冲区。

fflush(stdin);

调用此函数将从输入缓冲区中移除所有数据。这是我们修改后的程序:

#include <stdio.h>

int main()
{
    int n;
    char ch;

    printf("Enter a number: ");
    scanf("%d", &n);

    printf("Enter a character: ");
    scanf(" %c", &ch); // notice the space preceding %c

    printf("\n\n");

    printf("n = %d\n", n);
    printf("c = %c\n", ch);

    return 0;
}

现在试试

预期输出:

Enter a number: 100
Enter a character: a
n = 100
c = a



使用 C 语言格式化输入和输出

原文:https://overiq.com/c-programming-101/formatted-input-and-output-in-c/

最后更新于 2020 年 9 月 24 日


格式化的输入和输出允许程序员以特定的方式执行输入和输出。

格式化整数输入

%wd

这里%d是整数的转换规格,w表示输入数据的最大宽度。如果输入的长度大于宽度,则值存储不正确。

让我们举一些例子:

scanf("%2d%3d", &a, &b);

在这种情况下,变量a的宽度为2b的宽度为3

ab的值可以通过以下方式输入:

案例 1:

当输入的数据长度小于字段宽度时,输入值将正确存储在给定的变量中。

输入: 4 34

这种情况下,4存储在a中,34存储在b中。

现在试试

案例 2:

当输入的数据长度等于字段宽度时,输入值将正确存储在给定的变量中。

输入: 23 456

这种情况下,23存储在a中,456存储在b中。

现在试试

情况 3: 当输入的数据长度大于字段宽度时,则输入值没有正确存储在给定的变量中。

输入: 234 99

由于a的宽度为 2,因此只有23存储在a中,4存储在b中,而其余的输入被忽略。

现在试试

格式化整数输出

%wd

在这种情况下,w表示数据的最小宽度,d表示整数。如果变量的长度小于宽度,则该值将打印为右对齐,并带有前导空格。例如:

情况 1: 当变量长度小于指定宽度时。

printf("a=%2d,b=%3d", a, b);

如果a = 4b = 23,那么输出将是:

预期输出:

a=•4,b=•23

现在试试

在这种情况下,为第一个变量指定的宽度是 2,输出的长度只有 1 位(因为数字是4),因此在4之前增加了一个前导空格。空格字符用字符表示。同样,第二个变量的宽度是 3,而输出的长度只有 2 位数(因为数字是23,所以再次在23之前添加一个前导空格。

情况 2: 当变量的长度等于指定的宽度时,不添加前导空格。

printf("a=%3d,b=%4d", a, b);

如果a = 456b = 2234,那么

预期输出:

a=456,b=2234

情况 3: 当变量的长度大于指定的宽度时,不管变量的长度如何,输出都会被正确打印。

printf("a=%2d,b=%3d", a, b);

如果a = 1221b = 19234,那么

预期输出:

a=1221,b=19234

格式化浮点输入

%wf

这里w是指定输入数据最大宽度的整数,包括小数点前后的数字和小数本身。

情况 1: 当输入数据的长度小于给定的宽度时,则值被正确地存储在给定的变量中。

scanf("%3f%4f", &a, &b);

输入: 4 1.2

在这种情况下,第一个变量的最大宽度是 3,而输入的长度是 1,类似地,第二个变量的宽度是 4,而输入的长度是 3。因此这些值被正确地存储在变量中。即a = 4b = 1.2

情况 2: 当输入数据的长度等于宽度时,则值被正确地存储在变量中。

scanf("%3f%4f", &a, &b);

输入: 1.2 33.1

在这种情况下,输入的宽度和长度是相同的,因此值被正确地存储在变量中。即a = 1.2b = 33.1

情况 3: 当输入数据的长度大于指定的宽度时,这些值不会正确存储在变量中。

scanf("%3f%4f", &a, &b);

输入: 5.21 983.71

由于第一个变量的宽度为 3,因此只有5.2存储在变量a中,而1存储在b中,其余的输入被忽略。

格式化浮点输出

%w.nf

w是输出数据的最小宽度,n是小数点后要打印的位数。请注意,宽度包括小数点前后的数字和小数本身。

情况 1: 当输出数据的长度小于指定的宽度时,则数字用前导空格右对齐。

printf("a=%5.1f, b=%5.2f", a, b);

其中a = 3.1b = 2.4

预期输出:

a=••3.1, b=•2.40

在这种情况下,变量的宽度a是 5,输出数据的长度是 3,这就是为什么在3.1之前增加了两个前导空格。同样,变量b的宽度是 5,输出数据的长度是 3,但是由于小数点后要打印的位数是 2,所以在2.4之前只添加了一个前导空格。

情况 2: 当数据的长度等于指定的宽度时,则打印的数字没有任何前导空格。

printf("a=%4.2f, b=%4.2f", a, b);

其中a = 32.1b = 45.11

预期输出:

a=32.10, b=45.11

情况 3: 当数据的长度大于指定的宽度时,则打印数字时没有任何前导空格。

printf("a=%5.2f, b=%4.3f", a, b);

其中a = 34189.313b = 415.1411

预期输出:

a=34189.31, b=415.141

格式化字符串输入

%ws

这里w指定要存储在变量中的输入长度。

char str[20];
scanf("%4s", str)

注意:C 中的字符串被声明为字符数组,我们将在第课【C 中的字符串基础】中学习更多关于数组和字符串的知识。如果输入是earning,那么变量str中只存储earn

格式化字符串输出

%w.ns

w是弦的宽度。wn后的点(.)字符可选。如果存在,将仅显示n字符,并在字符串前添加(w-n)前导空格。另一方面,如果只指定了字符串的宽度(即w),并且字符串的长度小于指定的宽度,则输出将使用前导空格右对齐。否则,不添加前导空格。案例 1:

printf("%4s", "codeindepth");

预期输出:

codeindepth

这里字符串的宽度小于输入的长度,所以字符串将被打印出来,没有前导空格。

案例 2:

printf("%10s", "code");

预期输出:

•••••••code

这里字符串的宽度是 10,长度是 4,所以字符串将打印 6 个前导空格。

案例 3:

printf("%10.3s", "code");

预期输出:

•••••••cod

这里输出的宽度是 10,但是.3表示只显示 3 个字符。字符串的长度为 4,因此只会显示"cod"和 7 (10-3=7)个前导空格。

案例 4:

printf("%.6s", "codeindepth");

预期输出:

codein

这里没有指定输入的宽度,但是.6表示无论输入字符串的长度是多少,都只显示字符串的前 6 个字符。在下一章中,我们将学习中的运算符和表达式。



C 语言中的表达式和运算符

C 语言中的算术运算符

原文:https://overiq.com/c-programming-101/arithmetic-operators-in-c/

最后更新于 2020 年 7 月 27 日


运算符:运算符指定对数据的运算,该运算产生一个值。

操作数:操作符作用的数据项称为操作数。

有些运算符需要两个操作数,而有些只需要一个。C 语言提供了以下运算符:

  1. 算术运算符
  2. 关系运算符
  3. 逻辑运算符
  4. 条件运算符
  5. 赋值运算符
  6. 按位运算符
  7. 运算符的大小
  8. 算术运算符

在本章中,我们将讨论算术运算符。下表列出了算术运算符。

操作员 名字
+ 添加
- 减法
* 增加
/ 分开
% 模数运算符

前四个操作员照常工作,但您可能没有遇到%操作员。%运算符被称为模数运算符或模除法运算符。模数运算符(%)用于计算余数。例如9 % 2会产生1,因为当92除时,它会留下1的剩余部分。需要注意的重要一点是,它只对整数起作用,不能在floatdouble类型上应用%运算符。

上表中列出的所有运算符都是二进制运算符,因为它们需要两个操作数来操作。但是+-运算符也有它们的一元版本,例如:

+x-y

在这种情况下,+运算符对数字x没有影响,但是,-运算符会更改操作数y的符号。

整数运算

当两个操作数都是整数时,两个整数操作数之间的算术运算结果产生一个整数值。我们取两个变量ab,比如a = 10b = 4。下表显示了在ab上执行的算术运算的结果。

表示 结果
a + b 14
a - b 6
a * b 40
a / b 2
a % b 2

我们知道10/4 = 2.5,但是因为两个操作数都是整数,所以十进制值被截断。除法和模数运算符工作时,第二个操作数b必须非零,否则程序将崩溃。

下面的程序演示了整数运算:

#include<stdio.h>

int main()
{
    // Declare and initialize variable a and b
    int a = 11, b = 4;

    printf("a + b = %d\n", a + b);

    printf("a - b = %d\n", a - b);

    printf("a * b = %d\n", a * b);

    printf("a / b = %d\n", a / b);
    // because both operands are integer result will be an integer

    printf("a %% b = %d\n", a % b);
    // % operator returns the remainder of 11/4 i.e 3

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

a + b = 15
a - b = 7
a * b = 44
a / b = 2
a % b = 3

密切关注第 17 行printf()语句中%字符的用法。我们已经知道%字符用于指定控制字符串内部的转换规范。要在控制台中打印单个%字符,我们必须写%两次。

浮点运算

两个浮点操作数之间的运算总是产生浮点结果。我们取两个变量ab,比如a = 11.2b = 4.5。下表显示了在ab上执行的算术运算的结果。

表示 结果
a + b 15.7
a - b 6.700000
a * b 50.400000
a / b 2.488889

下面的程序演示了浮点运算。

#include<stdio.h>

int main()
{
    // Declare and initialize variable a and b
    double a = 9.2, b = 2.1;

    printf("a + b = %f\n", a + b);
    printf("a - b = %f\n", a - b);
    printf("a * b = %f\n", a * b);
    printf("a / b = %f\n", a / b);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

a + b = 11.300000
a - b = 7.100000
a * b = 19.320000
a / b = 4.380952

注意:%模数运算符不能用于浮点常量。

混合模式算术

整数和浮点之间的运算产生浮点结果。在此操作中,首先将整数值转换为浮点值,然后执行该操作。我们取两个变量ab,比如a = 14b = 2.5。下表显示了在ab上执行的算术运算。

表示 结果
a + b 16.500000
a - b 12.500000
a * b 35.000000
a / b 5.600000

如我们所见,当14除以2.5时,小数部分不会丢失,因为intdouble之间的算术运算会产生一个double值。所以我们可以使用混合模式算法来解决我们在划分10/4时遇到的问题。要得到正确答案,只需将操作中涉及的一个操作数设为浮点数。例如,10/4.010.0/4都会给出正确的结果,即2.5

#include<stdio.h>

int main()
{
    printf("%f\n", 10/4.0);
    printf("%f\n", 10.0/4);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

2.500000
2.500000



C 语言中的运算符优先级和关联性

原文:https://overiq.com/c-programming-101/operator-precedence-and-associativity-in-c/

最后更新于 2020 年 7 月 27 日


运算符优先级:它规定了表达式中运算符的求值顺序。

结合性:它定义了在表达式中计算具有相同优先级的运算符的顺序。关联性可以是从左到右或从右到左。

考虑以下示例:

24 + 5 * 4

这里我们有两个运算符+*,你觉得哪个运算会先求值,加法还是乘法?如果首先应用加法,则答案为116,如果应用乘法,则答案为44。要回答这样的问题,我们需要查阅运算符优先级表。

在 C 语言中,每个运算符相对于其他运算符都有固定的优先级。因此,优先级较高的运算符在优先级较低的运算符之前计算。出现在同一组中的运算符具有相同的优先级。下表列出了运算符优先级和关联性。

顶部的运算符具有更高的优先级,随着我们向底部移动,优先级会降低。

从优先级表中我们可以得出*运算符在+运算符之上,因此*运算符的优先级高于+运算符,因此在表达式24 + 5 * 4中,子表达式5 * 4将首先被求值。

这里还有一些例子:

例 1:

34 + 12/4 - 45

这里/运算符具有更高的优先级,因此12/4首先被求值。操作员+-具有相同的优先级,因为它们在同一个组中。那么会先评估哪一个呢?要解决这个问题,您需要参考运算符的关联性。从表中可以看出,运算符+-具有相同的优先级,从左到右相关联,因此在我们的表达式34 + 12/4 - 45中,除法之后,加法(+)将在减法(-)之前进行。

例 2 :

12 + 3 - 4 / 2 < 3 + 1

这里/运算符具有更高的优先级,因此4/2首先被求值。+-运算符从左到右具有相同的优先级和关联,因此在我们的表达式12 + 3 - 4 / 2 < 3 + 1中,除法之后,+运算符将被求值,然后是-运算符。从优先级表可以看出<运算符的优先级低于/+-。因此,最后将对其进行评估。

使用括号

如果您查看优先表,您会发现括号(())运算符的优先级最高。因此,就像我们在学校里做的那样,我们可以用括号来改变操作的顺序。考虑以下示例:

3 + 4 * 2

这里首先评估*运算符,然后评估+运算符。

如果你希望加法先发生,然后是乘法呢?

我们可以使用括号来实现这一点,如下所示:

(3 + 4) * 2

括号内的内容将首先被计算。因此,在这个表达式中,加法将首先发生,然后是乘法。

您也可以像这样嵌套括号:

(2 + (3 + 2) ) * 10

在这种情况下,首先计算最里面括号内的表达式,然后计算下一个最里面的括号,依此类推。

我们还可以使用括号来使复杂的表达式更易读。例如:

age < 18 && height < 48 || age > 60 && height > 72
(age < 18 && height < 48) || (age > 60 && height > 72) // much better than the above

这两个表达式给出了相同的结果,但是添加括号使我们的意图更加明确。

我们还没有讨论关系运算符和逻辑运算符。所以上面的表达可能没有完全的意义。关系运算符和逻辑运算符分别在 C 中的关系运算符和 C 中的逻辑运算符中详细讨论。在下一章中,我们将学习 C 语言中的 if else 语句。



C 语言中的赋值运算符

原文:https://overiq.com/c-programming-101/assignment-operator-in-c/

最后更新于 2020 年 7 月 27 日


我们之前已经多次使用赋值运算符(=)。我们在这里详细讨论一下。赋值运算符(=)用于给变量赋值。其一般格式如下:

variable = right_side

赋值运算符左侧的操作数必须是变量,右侧的操作数必须是常量、变量或表达式。以下是一些例子:

x = 18  // right operand is a constant
y = x    // right operand is a variable
z = 1 * 12 + x   // right operand is an expression

赋值运算符的优先级低于我们到目前为止讨论的所有运算符,它从右向左关联。

我们也可以同时给多个变量赋值。

x = y = z = 100

这里xyz初始化为100

因为赋值运算符(=)的关联性是从右向左的。上述表达式相当于以下内容:

x = (y = (z = 100))

请注意,表达式如下:

x = 18
y = x
z = 1 * 12 + x

被称为赋值表达式。如果我们在表达式的末尾放一个分号(;)如下:

x = 18;
y = x;
z = 1 * 12 + x;

那么赋值表达式就变成了赋值语句。

复合赋值运算符

使用变量的旧值来计算其新值的赋值操作称为复合赋值。

考虑以下两种说法:

x = 100;
x = x + 5;

这里第二个语句将5添加到x的现有值中。该值随后被分配回x。现在,x的新价值是105

为了更简洁地处理这些操作,C 语言提供了一种特殊的操作符,称为复合赋值操作符。

复合赋值运算符的一般格式如下:

variable op= expression

其中op可以是任意算术运算符(+-*/%)。上述语句在功能上等同于以下内容:

variable = variable op (expression)

:除了算术运算符,op还可以是>>(右移)、<<(左移)、|(按位 OR)、&(按位 AND)、^(按位异或)。我们还没有讨论这些操作符。

计算表达式后,op运算符应用于表达式的结果和变量的当前值(在 RHS 上)。这个操作的结果然后被分配回变量(在 LHS 上)。让我们举一些例子:声明:

x += 5;

相当于x = x + 5;x = x + (5);

同样,该声明:

x *= 2;

相当于x = x * 2;x = x * (2);

由于op运算符右侧的expression首先被求值,因此语句:

x *= y + 1;

相当于x = x * (y + 1)

复合赋值运算符的优先级相同,它们从右向左关联(参见优先级表)。

下表列出了一些复合赋值运算符:

操作员 描述
+= x += 5相当于x = x + 5
-= y -= 5相当于y = y - 5
/= z /= 3相当于z = z / 5
%= m %= 10相当于m = m % 10

以下程序演示了复合赋值运算符的作用:

#include<stdio.h>

int main(void)
{

    int i = 10;

    char a = 'd';
    printf("ASCII value of %c is %d\n", a, a); // print ASCII value of d

    a += 10; // increment a by 10;
    printf("ASCII value of %c is %d\n", a, a); // print ASCII value of n

    a *= 5; // multiple a by 5;
    printf("a = %d\n", a); 

    a /= 4; // divide a by 4;
    printf("a = %d\n", a); 

    a %= 2; // remainder of a % 2;
    printf("a = %d\n", a); 

    a *= a + i;  // is equivalent to  a = a * (a + i)
    printf("a = %d\n", a);

    return 0; // return 0 to operating system
}

预期输出:

ASCII value of d is 100
ASCII value of n is 110
a = 38
a = 9
a = 1
a = 11



C 语言中的递增和递减运算符

原文:https://overiq.com/c-programming-101/increment-and-decrement-operators-in-c/

最后更新于 2020 年 7 月 27 日


c 有两个特殊的一元运算符,分别叫做增量(++)和减量(--)运算符。这些运算符通过1增加和减少变量值。

++xx = x + 1x += 1
--xx = x - 1x -= 1

递增和递减运算符只能用于变量。它们不能与常量或表达式一起使用。

int x = 1, y = 1;

++x;     // valid

++5;      // invalid - increment operator operating on a constant value

++(x+y);  // invalid - increment operating on an expression

递增/递减运算符有两种类型:

  1. 前缀递增/递减运算符。
  2. 后缀递增/递减运算符。

让我们从第一个开始。

前缀递增/递减运算符

前缀递增/递减运算符会立即增加或减少变量的当前值。然后在表达式中使用该值。让我们举个例子:

y = ++x;

这里首先x的当前值增加1x的新值被分配给y。同样,在声明中:

y = --x;

x的当前值递减1x的新值被分配给y

以下程序演示了前缀递增/递减运算符的作用:

#include<stdio.h>

int main()
{
    int x = 12, y = 1;

    printf("Initial value of x = %d\n", x); // print the initial value of x
    printf("Initial value of y = %d\n\n", y); // print the initial value of y

    y = ++x; // increment the value of x by 1 then assign this new value to y

    printf("After incrementing by 1: x = %d\n", x);
    printf("y = %d\n\n", y);

    y = --x; // decrement the value of x by 1 then assign this new value to y

    printf("After decrementing by 1: x = %d\n", x);
    printf("y = %d\n\n", y);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Initial value of x = 12
Initial value of y = 1

After incrementing by 1: x = 13
y = 13

After decrementing by 1: x = 12
y = 12

后缀递增/递减运算符

后缀递增/递减运算符使变量的当前值在表达式中使用,然后该值递增或递减。例如:

y = x++;

这里首先将x的当前值赋给y,然后x递增。

同样,在声明中:

y = x--;

x的当前值分配给y,然后x递减。

以下程序演示了后缀递增/递减运算符的作用:

#include<stdio.h>

int main()
{
    int x = 12, y = 1;

    printf("Initial value of x = %d\n", x); // print the initial value of x
    printf("Initial value of y = %d\n\n", y); // print the initial value of y

    y = x++; // use the current value of x then increment it by 1

    printf("After incrementing by 1: x = %d\n", x);
    printf("y = %d\n\n", y);

    y = x--; // use the current value of x then decrement it by 1

    printf("After decrementing by 1: x = %d\n", x);
    printf("y = %d\n\n", y);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Initial value of x = 12
Initial value of y = 1

After incrementing by 1: x = 13
y = 12

After decrementing by 1: x = 12
y = 13

优先

递增和递减运算符的优先级高于我们到目前为止讨论过的运算符(唯一的例外是括号)。此外,后缀递增/递减运算符的优先级高于前缀递增/递减运算符。

下表列出了我们到目前为止讨论过的运算符的优先级和关联性:

经营者 描述 结合性
() 圆括号 从左到右
++-- 后缀递增运算符、后缀递减运算符 从左到右
++--+- 前缀递增运算符、前缀递减运算符、一元加号、一元减号 从右向左
*/% 乘法、除法和模数 从左到右
+- 加法和减法 从左到右
=+=-=*=/=%= 赋值运算符和复合赋值运算符 从右向左

我们取一些表达式,在运算符优先的基础上求解。

例 1 :

int x, y, z;
x = 5;
y = 8;
z = ++x + y++;

解决方案:

第一步:评估y++。由于++是后缀,y的当前值将用于表达式中,然后递增。

z = ++x + 8;

第二步:评估++x。由于++是前缀,x的值将立即递增。

z = 6 + 8;

第三步:评估6 + 8

z = 14;

例 2 :

int a, b, c;
a = 10;
b = 20;
c = 1;
c += a++ * 5 - --b;

解决方案:

第一步:评估a++。由于++是后缀,a的当前值将用于表达式中,然后递增。表达式现在变成:

c += 10 * 5 - --b;

第二步:评估--b。由于--是前缀,b的值将立即递减。表达式现在变成:

c += 10 * 5 - 19;

第三步:评估10 * 5

c += 50 - 19;

第四步:评估50 - 19

c += 31;

第五步:评估+=

c = 32;



C 语言中的关系运算符

原文:https://overiq.com/c-programming-101/relational-operators-in-c/

最后更新于 2020 年 7 月 27 日


关系运算符用于比较两个表达式的值。关系运算符是二进制运算符,因为它们需要两个操作数来操作。包含关系运算符的表达式称为关系表达式。如果关系为真,则关系表达式的结果为1,如果关系为假,则关系表达式的结果为0

下表列出了关系运算符以及一些示例:

操作员 描述 例子 结果
> 大于 1 > 2 0
>= 大于或等于 3 >= 2 1
< 小于 10 < 5 0
<= 小于或等于 6 <= 7 1
== 等于 98==98 1
!= 不等于 10 != 9 1

在 C 中,所有非零值被认为是真,而0被认为是假。以下程序演示了关系运算符的作用:

#include<stdio.h>

int main()
{
    int x = 12, y = 13;

    printf("x = %d\n", x);
    printf("y = %d\n\n", y);

    // Is x is greater than y?
    printf("x > y : %d\n", x > y);

    // Is x is greater than or equal to y?
    printf("x >= y : %d\n", x >= y);

    // Is x is smaller than y?
    printf("x < y : %d\n", x < y);

    // Is x is smaller than or equal to y?
    printf("x <= y : %d\n", x <= y);

    // Is x is equal to y?
    printf("x == y : %d\n", x == y);

    // Is x is not equal to y?
    printf("x != y : %d\n", x != y);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

x = 12
y = 13

x > y : 0
x >= y : 0
x < y : 1
x <= y : 1
x == y : 0
x != y : 1

优先

<<=>>=操作符的优先级相同,从左到右关联。然而,==!=的优先级比其他关系运算符低,它们从左到右关联。关系运算符的优先级低于算术运算符。

为了清楚起见,让我们评估一些涉及关系运算符的表达式:

例 1 :

4 + 2 * 3 > 12 - 2

第一步:评估2 * 3

4 + 6 > 12 - 2

第二步:评估4 + 6,然后是12 - 2

10 > 10

第三步:10不大于10,所以上面的表达式计算为假(0)。因此整个表达式的结果是0

结果:

4 + 2 * 3 > 12 - 2 => 0

例 2 :

(4 % 2 == 0) <= (8 * 2)

步骤 1:括号操作符具有最高的优先级,它从左向右关联。所以表达式(4 % 2 == 0)将首先被求值。%运算符的优先级高于等于==运算符。因此,首先应用%运算符,然后应用==运算符。表达式现在变成:

(4 % 2 == 0) <= (8 * 2)
=> (0 == 0) <= (8 * 2)
=> 1 <= (8 * 2)

第二步:评估(8 * 2)

1 <= (8 * 2)
=> 1 <= 16

第三步:1小于16。所以上面的表达式评估为真(1)。因此整个表达式的结果是真的。

结果:

(4 % 2 == 0) <= (8 * 2) => 0

不要将赋值运算符(=)与等于运算符(==)混淆。第一个用于给变量赋值,第二个用于测试两个值是否相等。

要充分利用关系运算符,您必须学习如何使用 if-else 语句。if-else 语句在 If… else 语句 C 章节中详细讨论。



C 语言中的逻辑运算符

原文:https://overiq.com/c-programming-101/logical-operators-in-c/

最后更新于 2020 年 7 月 27 日


逻辑运算符用于计算两个或多个条件。一般来说,逻辑运算符用于组合关系表达式,但它们不仅限于关系表达式,您可以使用任何类型的表达式,甚至常量。如果逻辑运算符的结果为真,则返回1,否则返回0

逻辑运算符有三种类型:

操作员 意义
&& 逻辑积算符
&#124;&#124; 或运算符
! “非”算符

AND ( &&)和 OR ( ||)是二元运算符,而 NOT ( !)是一元运算符。

在我们开始解释&&运算符之前,请记住-在 C 中,所有非零值都被认为是真(1),而0被认为是假。

AND (&&)运算符

如果两个操作数都为真,该运算符给出真(即1)的净结果,否则为假(即0)。

操作数 1 操作数 2 结果
真实的 真实的 真实的
真实的 错误的 错误的
错误的 真实的 错误的
错误的 错误的 错误的

让我们举个例子:

int a = 12, b = 3;

假设我们有以下逻辑表达式:

(a==12) && (b<5)

在上面的表达式中,条件a == 12b < 5都为真,因此整个表达式为真。因此,整个逻辑表达式的价值是1

为了可读性,上面的表达式添加了括号。它不会以任何方式改变操作顺序。所以这个表达:

(a<12) && (b<5)

相当于:

a < 12 && b < 5

当然,前者比后者可读性更强。

再考虑一些例子:

表示 中间表达 结果
(a==4) && (b==2) false && false = > false 0
(a>100) && (b<10) false && true = > false 0
a && b true && true = > true 1
a && 0 true && false = > false 0

特别注意最后两个例子,如上所述,逻辑表达式中的操作数可以是任何表达式、变量或常量。

这是&&符最重要的一点。

&&(与)运算符中,如果第一个操作数的计算结果为假,则第二个操作数根本不会计算。

考虑以下示例:

int a = 12;
(a==11) && (a++);

上式中a==11为假,所以右操作数a++根本不求值。以下程序演示了这一概念:

#include<stdio.h>

int main()
{
    int a = 12, result;

    printf("Initial value of a = %d\n", a);

    // result of the logical expression is stored in result
    result = (a==11) && (a++); 

    printf("Final value of a = %d\n", a);
    printf("Result of logical expression = %d\n", result);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Initial value of a = 12
Final value of a = 12
Result of logical expression = 0

如果条件a==11为真,那么a的值就会增加1

或(||)运算符

如果至少有一个操作数为真,该运算符给出真(即1)的最终结果,否则为假。

操作数 1 操作数 2 结果
真实的 真实的 真实的
真实的 错误的 真实的
错误的 真实的 真实的
错误的 错误的 错误的

让我们举个例子:

int a = 10, b = 9;

假设我们有以下逻辑表达式:

(a<12) || (b<5)

上式中a < 12为真,b < 5为假。因此整个表达式评估为真,逻辑表达式的值为1

再考虑一些例子:

表示 中间表达 结果
(a==4) &#124;&#124; (b==2) false &#124;&#124; false = >假 0
(a>10) &#124;&#124; (b<10) false &#124;&#124; true = >真 1
a &#124;&#124; b true &#124;&#124; true = >真 1
a &#124;&#124; 12.12 true &#124;&#124; true = >真 1

请注意,上面两个表达式中使用了括号来提高可读性,当然表达式(a==4) && (b==2)a==4 && b==2更易读。

请注意,在最后一条语句中,第二个操作数的类型是 double,这是完全可以接受的。

OR( ||)运算符最重要的一点是,如果第一个操作数的计算结果为真,那么第二个操作数就不会被计算。考虑以下示例:

int a = 10;
(a==10) || (a--);

上式中a==10为真,故表达式a--不求值,整体表达式的结果为1。以下程序演示了这一概念:

#include<stdio.h>

int main()
{
    int a = 10, result;

    printf("Initial value of a = %d\n", a);

    // result of the logical expression is stored in result
    result = (a==10) || (a--); 

    printf("Final value of a = %d\n", a);
    printf("Result of logical expression = %d\n", result);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Initial value of a = 10
Final value of a = 10
Result of logical expression = 1

如果条件a==10为假,则 a 的值将减少1

!(非)操作员

!(非)运算符否定条件的值。如果条件是假的,那么它就变成真的,如果它是真的,那么它就变成假的。与&&(与)和||(或)运算符不同,!(非)运算符是一元运算符。

操作数 结果
真实的 错误的
错误的 真实的

让我们举个例子:

int a = 10, b = 9;

假设我们有以下逻辑表达式。

!(a > 5)

我们可以看到,条件a > 5是真的。而! (NOT)运算符否定条件的值,所以整体表达式的结果为假,即0

以下是更多的例子:

表示 中间表达 结果
!(a==4) !false = > true 1
!(a &#124;&#124; b) !true = > false 0
!(a && b) !true = > false 0
!(a > b) !true = > false 0

注意:要充分发挥关系和逻辑的潜力,必须首先掌握 if-else 语句和循环。如果-否则语句和循环分别在章节中详细讨论。

以下程序演示了非(!)运算符的作用:

#include<stdio.h>

int main()
{
    int a = 100, result;
    printf("Initial value of a = %d\n", a);

    // result of the logical expression is stored in result
    result = (a>10); 

    printf("Is a > 10 : %d\n", result);
    printf("After applying not operator\n");
    printf("Is a > 10 : %d\n", !result);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Initial value of a = 100
Is a > 10 : 1
After applying not operator
Is a > 10 : 0

优先

在逻辑运算符中,NOT ( !)运算符的优先级最高,它从右向左关联。“与”(&&)运算符的优先级高于“或”(||)运算符,它们都从左向右关联(参见完整的优先级表)。

现在让我们解决一些涉及逻辑运算符的表达式。例 1 :

int age = 10, height = 45;
(age < 12 && height < 48) || (age > 65 && height > 72);

解决方案:这种情况下,运算符的求值顺序为圆括号(())、关系运算符(<>)、AND ( &&)运算符、OR ( ||)运算符。

(age < 12 && height < 48) || (age > 65 && height > 72)
=> (10 < 12 && 45 < 48) || (10 > 65 && 45 > 72)
=> (1 && 1) || (10 > 65 && 45 > 72)
=> 1 || (10 > 65 && 45 > 72)
=> 1 || (0 && 0)
=> 1 || 0 
=> 1

例 2 :

int year = 2000;
(year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0);

解决方案:在这种情况下,运算符的求值顺序将是圆括号(())、模除法(%)、关系运算符(==!=)和(&&)运算符,或者(||)运算符。

(year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0)
=> (2000 % 4 == 0 && 2000 % 100 != 0 ) || (2000 % 400 == 0)
=> (0 == 0 && 2000 % 100 != 0 ) || (2000 % 400 == 0)
=> (0 == 0 && 0 != 0 ) || (2000 % 400 == 0)
=> (1 && 0 != 0 ) || (2000 % 400 == 0)
=> (1 && 0 ) || (2000 % 400 == 0)
=> 0 || (2000 % 400 == 0)
=> 0 || (0 == 0)
=> 0 || 1
=> 1



C 语言中的条件运算符、逗号运算符和sizeof()运算符

原文:https://overiq.com/c-programming-101/conditional-operator-comma-operator-and-sizeof-operator-in-c/

最后更新于 2020 年 7 月 27 日


条件运算符

条件运算符(?:)是一种特殊的运算符,需要三个操作数。它的语法如下:

语法: expression1 ? expression2 : expression3

下面是条件运算符的工作原理。

第一个expression1被求值,如果它是真的,那么expression2的值成为整个表达式的结果。另一方面,如果expression1为假,那么expression3的值就成为整体表达式的结果。

让我们举个例子:

int a = 5, b = 3;
a > b ? a : b

在上式中,a>b为真,因此变量a的值成为整体条件表达式的结果。

由于a > b ? a : b是一个表达式,我们可以将其值赋给一个变量。

max = a > b ? a : b

条件运算符有时也称为三元运算符。

下面的程序演示了如何使用条件运算符找到两个数字中最大的一个

#include<stdio.h>

int main()
{
    int a, b, max;

    printf("Enter a and b: ");
    scanf("%d%d", &a, &b);

    max = a > b ? a : b;

    printf("Largest of the two numbers = %d\n", max);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Enter a and b: 1993 1534
Largest of the two numbers = 1993

条件运算符的优先级远远低于算术、逻辑和关系运算符。但它比赋值和复合赋值运算符更高。条件运算符的结合性是从右向左的(参见 C 中的运算符优先级)。

考虑以下条件表达式:

x ? y : a ? b : c

在这种情况下,expression3本身就是一个条件表达式。此外,由于条件运算符从右向左关联,因此上述表达式相当于:

x ? y : (a ? b : c)

如果x的值为真(即非零),则整个表达式的值为y。否则,整个表达式的值将是(a ? b : c)

逗点算符

逗号运算符允许我们在 C 语法只允许一个表达式的地方放置一个或多个表达式。每个表达式必须用逗号(,)隔开,并从左到右计算。最右边表达式的值成为整个表达式的值。举个例子就能说明一切。

a=2, a++, a+10

这里我们结合了三个表达式,让我们看看它是如何工作的。首先2被分配给变量a,然后a的值增加1。最后对a+10进行了评价。所以整体表达的价值是13

我们再举一个例子。

sum = (a=3, b=4, c=5, a+b+c);

这里首先将3赋给变量a,然后将4赋给变量b5赋给变量c。最后a+b+c被求值,并且整个表达式的结果(即最右边的表达式)被分配给sum

逗号运算符(,)的优先级最低,从左向右关联(参见中的运算符优先级和关联性)。因此,上面表达式中的括号是必要的,否则,变量sum将被赋予一个值3

逗号运算符(,)帮助我们使代码更加简洁。如果不使用逗号运算符,上述任务至少需要 2 条语句。

a=3, b=4, c=5;
sum = a+b+c;

以下程序演示了逗号运算符(,)的作用:

#include<stdio.h>

int main()
{
    int a, b, c, sum;
    sum = (a=3, b=4, c=5, a+b+c);
    printf("Sum = %d\n", sum);
    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Sum = 12

操作符的大小

sizeof是一元运算符,用于确定其操作数的大小。sizeof运算符的一般形式是:

sizeof(object)

其中对象可以是数据类型关键字,如intfloatdouble或表达式或变量。

例如,sizeof(int)给出了一个int数据类型所占的大小。sizeof运算符以字节为单位返回大小。

下面的程序演示了如何使用sizeof()操作符检查系统中基本类型的大小。

#include<stdio.h>

int main()
{
    printf("Size of short = %lu\n", sizeof(short));
    printf("Size of int = %lu\n", sizeof(int));
    printf("Size of unsigned int = %lu\n", sizeof(unsigned int));
    printf("Size of char = %lu\n", sizeof(char));
    printf("Size of float = %lu\n", sizeof(float));
    printf("Size of double = %lu\n", sizeof(double));
    printf("Size of long double = %lu\n", sizeof(long double));

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Size of short = 2
Size of int = 4
Size of unsigned int = 4
Size of char = 1
Size of float = 4
Size of double = 8
Size of long double = 16

因为 C 在存储需求方面相当灵活。上述程序的输出可能因您的机器而异。

sizeof运算符的优先级与前缀递增/递减运算符相同,从右向左关联(参见中的运算符优先级和关联性)。



C 语言中的隐式类型转换

原文:https://overiq.com/c-programming-101/implicit-type-conversion-in-c/

最后更新于 2020 年 7 月 27 日


c 允许我们在表达式中混合基本类型。在 C 的算术运算符一章中讨论混合模式算术时,我们已经看到了这种行为的一瞥。在这样的表达式中,一种类型的操作数被转换成另一种类型。这个过程称为类型转换。

有两种类型的类型转换:

  1. 隐式类型转换。
  2. 显式类型转换

在本章中,我们将讨论隐式类型转换。

隐式类型转换

这种类型的转换由编译器根据以下规则完成:

  1. 如果一个操作数是类型long double,那么另一个操作数将被转换为long double,然后运算的结果将是一个long double
  2. 否则,如果一个操作数的类型为double,那么另一个操作数将被转换为double,运算结果将是double
  3. 否则,如果一个操作数的类型为float,那么另一个操作数将被转换为float,运算结果将是float
  4. 否则,如果一个操作数的类型为unsigned long int,那么另一个操作数将被转换为unsigned long int,运算结果将是unsigned long int
  5. 否则,如果一个操作数的类型为long int,另一个操作数的类型为unsigned int,则有两种可能:
    1. 如果long int可以表示一个unsigned int的所有值,那么类型unsigned int的操作数将被转换为long int,结果将是一个long int
    2. 否则,如果long int不能代表一个unsigned int的所有值,则两个操作数的操作数都转换为unsigned long int,结果为unsigned long int
  6. 否则,如果一个操作数的类型为long int,那么另一个操作数将被转换为long int,运算结果将是long int
  7. 否则,如果一个操作数的类型为unsigned int,那么另一个操作数将被转换为unsigned int,运算结果将是unsigned int
  8. 否则,如果一个操作数的类型为int,那么另一个操作数将被转换为int,运算结果将是int

我们举几个例子把事情说清楚。

例 1:

int a = 100;
double b = 12.5;

a + b;

这里一个操作数是类型int,另一个是类型double。因此根据规则 3,变量a将被转换为double,整体运算的结果将是一个double,即112.500000

例 2:

char ch = 'a';
int a = 10;

a + c;

根据规则 8,在任何操作之前,char将被转换为int,并且整体操作的结果将是int。因为ch的整数值是97(即字符'a'的 ASCII 值)。因此,97 + 10 = 107

例 3:

char ch = 'A';
unsigned int a = 10;

a * b;

这里根据规则 7,类型为char的变量ch将首先转换为unsigned int,即65 (ASCII 值为'A',然后进行加法运算,整体运算结果为unsigned int。因此,65 + 10 = 75

下面是更多的例子:

char ch;
short int si;
int i;
unsigned int ui;
float f;
long double ld;

i = ch + si;  // both ch and si will be converted to int before addition - rule 8
i = si + i;   // si will be converted to int before addition - rule 8
ui = i + ui;  // i will be converted to unsigned int before addition - rule 7
f = ui + f;   // ui will be converted to float before addition - rule 3
ld = f + ld;  // f will be converted to long double before addition - rule 1

通过给每种类型分配一个等级,可以简化上述所有规则。以下是它的工作原理。

每当表达式中涉及两个不同数据类型的操作数时,较低等级的操作数将被转换为较高等级的数据类型。这个过程叫做型的提升。

:为简单起见,图中省略了规则 5。

分配中的类型转换

如果赋值表达式中的操作数类型不同,那么右边的操作数将根据以下规则转换为左边的操作数类型。

  1. 如果右侧操作数的等级较低,则它将被提升到左侧操作数的等级。这里有一个例子:

    int i = 'z';
    
    

    这里右操作数'z'是类型char,右操作数是类型int。根据规则——较低等级的操作数(在本例中为char)将被提升到较高等级(在本例中为int)。所以赋值前'z'122 (ASCII 值)会提升到int再赋值到i

  2. 否则,如果右侧操作数的等级较高,则它将被降级为左侧操作数的等级。例如:

    float a = 120.33;
    
    

    回想一下,默认情况下浮点常量的类型是double。在这种情况下,右侧操作数即120.33double类型,左侧操作数为float类型。因此在赋值操作之前120.33将被降级为float类型,然后赋值将会发生。

赋值中类型转换的一些结果是:

  1. long int转换为intint转换为short intint转换为char时,高阶位可能会丢失。让我们举个例子来清楚地理解这一点。假设我们有以下陈述:

    unsigned char ch = 257;
    
    

    这里我们试图将257 ( int类型)分配给一个char变量。根据类型转换规则 2:如果右手操作数的等级较高,那么它将被降级为左手操作数的等级。但是有一个问题,回想一下unsigned char型只能取0255的数值。Cleary,257超出变量ch的范围。在这种情况下,从范围的另一侧拾取适当的值并存储在ch中。所以最终存储在ch变量中的是一个带有 ASCII 值2的笑脸字符。

  2. 从浮点类型(如doublefloat)转换为int类型时,小数部分将被截断。

  3. double类型转换为float类型时,数字四舍五入。

  4. int转换为floatfloat转换为double时,精度没有增加。

  5. signed类型变为unsigned类型时,标志可能会掉落。

下面的示例演示了如何进行类型转换。

#include<stdio.h>

int main()
{
    float f_val1 = 97.12, f_val2;
    int i_val1, i_val2;
    char ch_val1, ch_val2;

    // float is demoted to int, only 97 is assigned to i_val1
    i_val1 = f_val1;

    // int is demoted to char,
    ch_val1 = i_val1;

    // float is demoted to int, only 12 is assigned to i_val2
    i_val2 = 12.45f;

    // char is promoted to int, now
    // i_val1 contains ASCII value of character 'e' i.e 101
    i_val2 = 'e';

    /*
        double is demoted to float, since by
        default floating point constants
        are of type double
    */

    f_val2 = 12.34;

    // Print the value of i
    printf("Value of i_val1 = %d\n", i_val1);

    // Print the character corresponding to ASCII value 97
    printf("Value of ch_val1 = %c\n", ch_val1);

    // Print the ASCII value of character 'e'
    printf("Value of i_val2 = %d\n", i_val2);

    // Print f_val2 with 2 digits of precision
    printf("Value of f_val2 = %.2f\n", f_val2);

    // Signal to operating system everything works fine
    return 0;
}

预期输出:

Value of i_val1 = 97
Value of ch_val1 = a
Value of i_val2 = 101
Value of f_val2 = 12.34



C 语言中的显式类型转换

原文:https://overiq.com/c-programming-101/explicit-type-conversion-in-c/

最后更新于 2020 年 7 月 27 日


上一章讨论的隐式类型转换是由编译器自动完成的。在某些情况下,我们可能希望对转换如何发生有更多的控制。举个例子吧。

float f;
int a = 20, b = 3;
f = a/b

f的值将是6.000000而不是6.666666,因为两个整数之间的运算会产生一个整数值。当然解决这个问题的一个方法是使用混合模式算法,并将ab的类型更改为doublefloat。改变变量的类型并不总是可行的,当然也不是一个好的编程。在 c 中输入显式类型转换。

演职人员

强制转换运算符是一元运算符,用于将常量、变量或表达式临时转换为特定类型。强制转换运算符的语法是:

语法: (datatype)expression

其中datatype指您希望表达式转换成的类型。所以如果我们把上面的陈述写成:

f = (float)a/b;

然后我们会得到正确的答案,即6.666666

以下是演职人员的工作方式。

首先,它将类型为int的变量a临时转换为类型为float。我们已经知道floatint操作数之间的运算会产生float结果,这就是为什么答案是6.666666而不是6.000000

请注意,在上面的陈述中,cast 运算符仅适用于变量a,而不适用于ba/b

另一个需要注意的要点是,变量a的数据类型是float,直到语句执行为止。之后,将被视为int

在我们离开这个话题之前,请考虑以下声明:

f = (float)(a/b);

你可能会认为这个说法和上一个(即f = (float)a/b;)是一样的,但其实不是,这里是为什么。

这里首先对表达式a/b进行求值,然后由于类型转换,其结果被转换为float,并最终分配给f

以下程序演示了 cast 运算符的作用:

#include<stdio.h>

int main()
{
    int a = 25, b = 13;
    float result;

    result = a/b;

    // display only 2 digits after decimal point
    printf("(Without typecasting) 25/13 = %.2f\n", result );  

    result = (float)a/b;

    // display only 2 digits after decimal point
    printf("(With typecasting) 25/13 = %.2f\n", result ); 

    // signal to operating system everything works fine
    return 0;
}

预期输出:

(Without typecasting) 25/13 = 1.00
(With typecasting) 25/13 = 1.92



C 语言中的控制语句

C 语言中的if-else语句

原文:https://overiq.com/c-programming-101/if-else-statements-in-c/

最后更新于 2020 年 7 月 27 日


C 语言中的控制语句

到目前为止,在我们编写的所有程序中,语句按照它们出现的顺序依次执行。但是有时我们希望只有在某些条件为真时才执行语句。例如,如果银行余额超过七位数,购买一辆新车,否则续订公共汽车月票。为了做出这样的决定,C 提供了一种称为控制语句的工具。

控制语句用于改变程序的流程。它们用于指定语句的执行顺序。它们通常用于定义控制如何从程序的一部分转移到另一部分。

C 语言有以下控制语句:

  • 如果…否则
  • 转换
    • 正在…
    • 做...正在…

复合语句

复合语句是使用大括号({})组合在一起的语句块。在复合语句中,所有语句都是按顺序执行的。复合语句也称为块。它采用以下形式:

{
    statement1;
    statement2;
    statement3;
    ...
    statementn;
}

我们了解到所有语句都以分号(;)结尾,但复合语句是这一规则的例外。另一个需要理解的重要事情是,复合语句在语法上等同于单个语句,这意味着我们可以将复合语句放在允许单个语句的地方。这意味着下面的代码完全有效。

#include<stdio.h>

int main()
{
    int i = 100;

    printf("A single statement\n");

    {
        // a compound statement
        printf("A statement inside compound statement\n");
        printf("Another statement inside compound statement\n");
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

A single statement
A statement inside compound statement
Another statement inside compound statement

如果语句

If 语句用于测试条件并采取两种可能的操作之一。if 语句的语法是:

语法:

if (condition)
{
    // if block
    statement1;
    statement2;
}

条件可以是任何常量、变量、表达式、关系表达式、逻辑表达式等等。请记住,在 C 中,任何非零值都被认为是真,而0被认为是假。

工作原理:

if 块内的语句(即statement1statement2)只有在条件为真时才执行。如果为假,则跳过 If 块内部的语句。当条件为真时,当您想要执行多个语句时,大括号({})总是必需的。此外,请注意 if 块中的语句略有缩进。这样做是为了提高可读性,语法上不需要缩进。

如果您想在条件为真时只执行一条语句,那么可以省略大括号({})。通常,即使只有一条语句要执行,也不应该省略大括号。

if (condition)
    statement1;

如果用户输入的数字是偶数,下面的程序将打印一条消息。

#include<stdio.h>

int main()
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    if(n % 2 == 0)
    {
        printf("%d is even", n);
    }

    // signal to operating system everything works fine
    return 0;
}

第一次运行:

运行程序并输入一个偶数,您将获得以下输出:

预期输出:

Enter a number: 46
46 is even

第二次运行:

再次运行程序,但这次输入一个奇数。

预期输出:

Enter a number: 21

这一次,条件(n % 2 == 0)的计算结果为 false,因此 if 块中的语句被跳过。

哪种说法属于 if?

if (condition)
statement1;
statement2;
statement3;

如果条件为假,你能找到哪个语句将被省略吗?

如果 If 语句后面没有大括号({}),那么只有下一个立即语句属于 if 语句。else 和 else-if 子句也是如此(else 和 else-if 子句将在本章后面讨论)。

因此,只有statement1属于 if 语句。因此,如果条件为假,则仅省略statement1。无论条件如何,statement2statement3将始终被执行。以下示例说明了这一事实:

#include<stdio.h>

int main()
{
    if(0)
    printf("statement 1\n");
    printf("statement 2\n");
    printf("statement 3\n");

    // signal to operating system everything works fine
    return 0;
}

预期输出:

statement 2
statement 3

这里的条件是假的,这就是为什么只执行最后两个语句。这验证了第 6 行中的语句只属于 if 语句的事实。乍看之下,确定哪个语句属于 if 语句有点混乱,这就是为什么建议总是使用大括号({})来包装要用 if 语句执行的语句。

#include<stdio.h>

int main()
{

    if(0)
    {
        printf("statement 1\n");
    }
    printf("statement 2\n");
    printf("statement 3\n");

    // signal to operating system prgram ran fine
    return 0;
}

现在您可以清楚地看到,只有第一个语句属于 if 语句。

else 子句

else子句允许我们为if条件添加一个替代路径。只有当if条件为假时,才会执行else块下的语句。

语法:

if (condition)
{
    // if block
    statement1;
    statement2;
}

else
{
    // else block
    statement3;
    statement4;
}

通常,如果 else 块中只有一条语句,那么大括号({})可以省略。虽然,不推荐。

if (expression)
    statement1;
else
    statement2;

如前所述,缩进不是必需的,因此上面的代码也可以写成:

if (expression)
statement1;
else
statement2;

但是为什么要抹杀可读性呢?做一个好的程序员,总是缩进我们的代码。

现在,让我们在之前编写的程序中添加一个else子句。

#include<stdio.h>

int main()
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    if(n % 2 == 0)
    {
        printf("%d is even", n);
    }

    else
    {
        printf("%d is odd", n);
    }

    // signal to operating system everything program ran fine
    return 0;
}

第一次运行:运行程序并输入一个偶数。

Enter a number: 44
44 is even

第二次运行:

再次运行程序,但这次输入一个奇数。

Enter a number: 91
91 is odd

再考虑一个例子。以下程序确定两个输入数字中较大的一个:

#include<stdio.h>

int main()
{
    int a, b;

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    if(a > b)
    {
        printf("%d is greater than %d", a, b);
    }

    else
    {
        printf("%d is greater than %d", b, a);
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

第一次运行:

Enter two numbers: 344 200
344 is greater than 200

第二次运行:

Enter two numbers: 99 999
999 is greater than 99

嵌套 if… else

我们可以添加if..else声明在if街区或else街区内。这叫做【T4 的筑巢】..else语法:

if(condition1)
{
    if(condition2)
    {
        statement1;
        statement2;
    }

    else
    {
        statement3;
        statement4;
    }
}

else
{
    if(condition3)
    {
        statement5;
        statement6;
    }

    else
    {
        statement7;
        statement8;
    }
}

我们可以筑巢if..else陈述到任何深度。

工作原理:

首先检查condition1,如果为真,则检查condition2,如果为真,则执行if块内的语句(第 4-7 行)。

否则,执行else块(第 10-13 行)中的语句。否则,如果condition1为假,则检查condition3,如果为真,则执行第 19-22 行 if 块下的语句。否则,执行else块(第 25-28 行)中的语句。

以下程序使用两个嵌套的 if-else 语句来确定三个数字中较大的一个:

#include<stdio.h>

int main()
{
    int a, b, c, larger;

    printf("Enter three numbers: ");
    scanf("%d %d %d", &a, &b, &c);

    if(a > b)
    {
        if(a > c)
        {
            larger = a;
        }

        else
        {
            larger = c;
        }
    }
    else
    {
        if(b > c)
        {
            larger = b;
        }

        else
        {
            larger = c;
        }
    }

    printf("Largest number is %d", larger);

    // signal to operating system everything works fine
    return 0;
}

预期输出:

第一次运行:

Enter three numbers: 12 35 54
Largest number is 54

第二次运行:

Enter three numbers: 77 23 871
Largest number is 871

匹配 if..其他部分

有时将 else 子句与if语句联系起来会变得令人困惑。考虑以下示例:

if(a<10)
    if (a % 2 ==0)
        printf("a is even and less than 10\n");
else
printf("a is greater than 10");

哪个if语句与else块相关联?根据代码缩进的方式,您可能会认为else属于第一个if语句,但事实并非如此。编译器不会根据缩进将ifelse语句关联起来,而是将 else 部分与最接近的不匹配的if部分进行匹配。所以else语句与第二个if语句相关联。

使用牙套({})我们总能避免这样的并发症。

if(a < 10)
{
    if (a % 2 ==0)
    {
        printf("a is even and less than 10\n");
    }

    else
    {
        printf("a is greater than 10");
    }
}

现在一切都清楚了。

else if 子句

if-else是一个双向语句,用于测试一个条件并采取两个可能的动作之一。如果我们要进行一系列测试呢?检查多个条件的一种方法是使用嵌套的if-else语句。我们在本章前面已经看到了这种技术的一个例子。实现这一点的另一种方法是使用 else-if 子句。else-if 子句扩展了基本的 if-else 语句,并允许我们执行一系列测试。if-else 语句的更新语法如下所示:

if(condition1)
{
    statement1;
}

else if(condition2)
{
    statement2;
}

else if(condition3)
{
    statement3;
}

...

else
{
    statement4;
}

这里,逐一检查每个条件。一旦发现一个条件为真,则执行对应于该块的语句。if-else 语句的其余部分中的条件和语句被跳过,程序控制从if-else语句中出来。如果所有条件都不成立,则执行else块中的语句。

使用else-if子句,我们可以以更紧凑的形式编写嵌套的if-else语句。

让我们重写程序,使用 else-if 子句来确定这两个数字中最大的一个。

#include<stdio.h>

int main()
{
    int a, b, c, larger;

    printf("Enter three numbers: ");
    scanf("%d %d %d", &a, &b, &c);

    if(a > b && a > c)
    {
        larger = a;
    }

    else if(b > a && b > c)
    {
       larger = b;
    }

    else
    {
        larger = c;
    }

    printf("Largest number is %d", larger);

    // signal to operating system everything works fine
    return 0;
}

这个版本的程序在功能上等同于使用嵌套 if-else 语句的程序。但它避免了深缩进,使代码更易读。



C 语言中的while循环

原文:https://overiq.com/c-programming-101/the-while-loop-in-c/

最后更新于 2020 年 7 月 27 日


循环用于重复执行语句或语句块。例如,假设我们想写一个程序打印"Hello" 5 次。实现这一点的一种方法是将下面的语句写 5 遍。

printf("hello\n");

但是如果我们想打印1001000次呢。当然,把同样的语句写 100 次或 1000 次是疯狂的。使用循环我们可以很容易地解决这类问题。c 提供了三种类型的循环。

  1. while 循环
  2. 边循环边做
  3. for 循环

while 循环

语法:

while(condition)
{
    // body of while loop
    statement 1;
    statement 2;
}

就像 if-else 语句一样,while 循环以一个条件开始。首先,评估condition,如果为真,则执行 while 主体中的语句。在执行 while 循环的主体后,再次检查条件,如果条件仍然为真,则再次执行 while 主体中的语句。这个过程一直重复,直到condition变为假。因此,你必须始终包含一个改变condition价值的陈述,这样它最终会在某个时候变成假的。循环体的每次执行都被称为迭代。

以下程序使用 while 循环打印1100之间的所有偶数:

例 1:

#include<stdio.h>

int main()
{
    int i = 1;

    // keep looping while i < 100
    while(i < 100)
    {
        // if i is even
        if(i % 2 == 0)
        {
            printf("%d ", i);
        }
        i++; // increment i by 1
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50
52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96
98

工作原理:

在第 5 行,我们已经声明了一个变量i,并将其初始化为1。首先,检查条件(i < 100),如果是真的。控制在 while 循环的主体内传递。在循环体内部,如果条件(i % 2 == 0)被检查,如果它为真,那么 if 块内部的语句被执行。然后使用表达式i++增加i的值。由于在 while 循环的主体中没有更多的语句可以执行,因此第一次迭代就完成了。再次检查条件(i < 100),如果它仍然为真,则再次执行循环主体。只要i的值小于100,这个过程就会重复。当i到达100时,循环终止,控制脱离 while 循环。

再考虑一个例子:

例 2:

以下程序计算用户输入的数字的十进制位总和。

#include<stdio.h>

int main()
{
    int n, num, sum = 0, remainder;

    printf("Enter a number: ");
    scanf("%d", &n);

    num = n;

    // keep looping while n > 0
    while( n > 0 )
    {
        remainder = n % 10;   // get the last digit of n
        sum += remainder;     // add the remainder to the sum
        n /= 10;              // remove the last digit from n
    }

    printf("Sum of digits of %d is %d", num, sum);

    // signal to operating system everything works fine
    return 0;
}

预期输出:第一次运行:

Enter a number: 222
Sum of digits of 222 is 6

第二次运行:

Enter a number: 456
Sum of digits of 456 is 15

工作原理:

假设用户输入了123,那么下面是求位数之和的步骤。

第一次迭代

n = 123

第一步:

通过评估123 % 10取出123的最后一位数字,并将结果存储在变量remainder中。

remainder = n % 10;
remainder = 123 % 10
remainder = 3

第二步:

将最后一步得到的数字加到变量sum中。

sum += remainder
sum = sum + remainder
sum = 3

第三步:

现在不需要123的最后一位数字了,通过评估123 / 10去掉。

n /= 10
n = 123 / 10
n = 12

第二次迭代

n = 12

第一步:

remainder = n % 10;
remainder = 12 % 10
remainder = 2

第二步:

sum += remainder
sum = sum + remainder
sum = 3 + 2
sum = 5

第三步:

n /= 10
n = 12 / 10
n = 1

第三次迭代

n = 1

第一步:

remainder = n % 10;
remainder = 1 % 10
remainder = 1

第二步:

sum += remainder
sum = sum + remainder
sum = 5 + 1
sum = 6

第三步:

n /= 10
n = 1 / 10
n = 0

n到达0时,while 条件变为假,并且控制脱离 while 循环。因此123的十进制位总和为6



C 语言中的do-while循环

原文:https://overiq.com/c-programming-101/the-do-while-loop-in-c/

最后更新于 2020 年 7 月 27 日


做…同时循环

语法:

do{
   // body of do while loop
   statement 1;
   statement 2;
}while(condition);

在 do while 循环中,首先执行主体中的语句,然后检查条件。如果条件为真,则再次执行正文中的语句。这个过程一直重复,直到条件变为假。通常,如果 do while 循环的主体只包含一个语句,那么可以省略大括号({})。请注意,与 while 循环不同的是,在 do while 中,条件后会放置一个分号(;)。

do while 循环与 while 循环有很大的不同,因为在 do while 循环中,即使条件为假,主体中的语句也至少执行一次。在 while 循环的情况下,首先检查条件,如果条件为真,则只执行循环体中的语句。

以下程序使用 do while 循环打印1100之间的数字,它们是3的倍数:

#include<stdio.h> // include the stdio.h

int main()
{
    int i = 1; // declare and initialize i to 1

    do
    {
        // check whether i is multiple of 3 not or not
        if(i % 3 == 0)
        {
            printf("%d ", i);   // print the value of i
        }
        i++; // increment i by 1
    }while(i < 100);  // stop the loop when i becomes greater than 100

    // signal to operating system everything works fine
    return 0;
}

预期输出:

3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 7
5 78 81 84 87 90 93 96 99

工作原理:

在第 5 行,我们已经声明并初始化了变量i。然后,控件进入 do while 循环的主体。在循环体内部,测试 if 条件(i%3==0),如果为真,则执行 if 块内部的语句。声明i++i的值增加1。最后,检查边做边条件(i<100)。如果为真,则循环体内部的语句将再次执行。只要i的值小于100,这个过程就一直重复。

我应该在哪里使用 do while 循环?

大多数时候你会用 while 循环代替 do while。然而,在一些场景中,while loop 最适合。考虑以下问题。

假设你想创建一个程序来寻找一个数的阶乘。你可能知道阶乘只对0和正数有效。这里有一种方法可以解决这个问题。

假设用户输入了一个负数,与其显示错误消息并退出程序,不如再次要求用户输入一个数字。你要一直问,直到用户输入一个正数或者0。一旦输入正数或0,计算阶乘并显示结果。

让我们看看如何使用 while 和 do while 循环来实现它。

使用 while 循环

#include<stdio.h> // include the stdio.h

int main()
{
    int num;
    char num_ok = 0;

    // keep asking for numbers until num_ok == 0
    while(num_ok==0)
    {
        printf("Enter a number: ");
        scanf("%d", &num);

        // if num >= 0 set num_ok = 1 and stop asking for input
        if(num>=0)
        {
            num_ok = 1;
        }
    }

   // calculate factorial
}

使用 do while 循环

#include<stdio.h> // include the stdio.h

int main()
{
    int num;

    do
    {
        printf("Enter a number: ");
        scanf("%d", &num);
    }while(num<0); // keep asking for numbers until num < 0

   // calculate factorial
}

请注意,使用 while 循环的解决方案更加复杂,为了实现同样的目的,我们必须创建一个额外的变量num_ok,以及一个额外的 if 语句。另一方面,do while 循环实现了同样的事情,没有任何欺骗,它更优雅和简洁。

在我们离开 do while 循环之前,让我们再举一个例子。

以下程序计算简单利息:

/******************************************
 Program to calculate the Simple interest 
 *
 * SI = (Principal * Rate * Time) / 100
 *
******************************************/

#include<stdio.h> // include stdio.h

int main()
{
    float p, r, t;
    char ch = 'y';

    do
    {
        printf("Enter principal: ");
        scanf("%f", &p);

        printf("Enter rate: ");
        scanf("%f", &r);

        printf("Enter t: ");
        scanf("%f", &t);

        printf("SI = %.2f", (p *r * t)/100 );

        printf("\n\nCalculate SI one more time ? ('y' for Yes, 'n' for no ) : ");
        scanf(" %c", &ch);    // notice the preceding white space before %c 
    }while(ch == 'y');        // keep asking for P, R and T til the input is 'y'

    // signal to operating system everything works fine
    return 0;
}

预期输出:

Enter principal: 15000
Enter rate: 4.5
Enter t: 3
SI = 2025.00

Calculate SI one more time ? ('y' for Yes, 'n' for no ) : y
Enter principal: 20000
Enter rate: 5.4
Enter t: 4
SI = 4320.00

Calculate SI one more time ? ('y' for Yes, 'n' for no ) : n



C 语言中的for循环

原文:https://overiq.com/c-programming-101/the-for-loop-in-c/

最后更新于 2020 年 7 月 27 日


在前两章中,我们已经学习了 whiledo while loop 。在本章中,我们讨论 for 循环:for 循环的语法如下:语法:

for(expression1; expression2; expression3)
{
    // body of for loop
    statement1;
    statement2;
}

expression1是初始化表达式。
expression2是测试表达式或条件。
这个expression3就是更新表情。

工作原理:

首先执行初始化表达式(即expression1)初始化循环变量。当循环开始时,expression1只执行一次。然后检查条件(即expression2,如果是真的,则执行循环的主体。在执行循环体之后,程序控制被转移到更新表达式(expression3)。expression3修改循环变量。然后再次检查条件(即expression2)。如果条件仍然为真,则再次执行循环体。这个过程一直持续到expression2变为假。

如果 for 循环的主体只包含一条语句,那么大括号({})可以省略。

for(expression1; expression2; expression3)
    statement1;

// The above for loop is equivalent to:

for(expression1; expression2; expression3)
{
    statement1;
}

以下程序计算从1100的数字总和。

#include<stdio.h>

int main()
{  
    int i;   // loop variable
    int sum = 0;    // variable to accumulate sum

    for(i = 1; i <= 100; i++)
    {
        sum += i;
    }

    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

预期输出:

Sum = 5050

工作原理:

在第 5 行,我们声明了一个名为i的循环变量。在第 6 行,我们将名为sum的变量声明并初始化为0。然后程序控制进入 for 循环。首先执行初始化语句(i=1)初始化循环变量i。然后检查条件(i<100),如果为真,则执行 for 循环体内部的语句。在执行循环体之后,程序控制转移到更新表达式(i++),并且i的值增加1。然后再次检查条件(i<100),如果仍然为真,则执行循环体。只要变量i小于或等于100,该过程就会继续。当i到达101时,条件(i<100)变为假,控制从 for 循环中出来,执行后面的语句。

while 和 for 循环有什么区别吗?

while 和 for 循环本质上是以不同的方式做同样的事情。事实上,除了在极少数情况下,for 循环总是可以被 while 循环替换,反之亦然。

expression1;
while(expression2)
{
    expression3;
}

在上面的片段中,expression1可以被视为初始化表达式,因为它在 while 循环的开始只执行一次。expression2是测试表达式,expression3是更新表达式。将这个模式应用到我们前面的 for 循环示例中,让我们使用 while 循环重写它。

#include<stdio.h>

int main()
{
    int i = 1, sum = 0;

    while(i <= 100)
    {
        sum += i;
        i++;
    }

    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

for 循环中的表达式是可选的

for 循环中的所有三个表达式都是可选的,但两个分号必须始终存在。

  • 如果初始化是在 for 循环之外完成的,我们可以省略expression1
  • 如果省略expression2,那么条件总是真的,导致无限循环的产生——一个永不停止执行的循环。为了避免无限循环,您应该在循环体中包含一个中断返回语句。我们将在接下来的章节中详细讨论breakreturn陈述。
  • 如果 for 循环体中存在更新表达式,我们可以省略expression3

以下是基于省略表达式的 for 循环的一些简单变体:

例 1: 省略expression1

/*
     1st variation - expression1 is omitted
*/

#include<stdio.h>

int main()
{
    int i = 1, sum = 0;

    //expression 1 is omitted

    for( ; i <= 100; i++)
    {
        sum += i;
    }

    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

预期输出:

Sum = 5050

在这种情况下,expression1被省略,因为循环变量的初始化是在 for 循环之外执行的(第 9 行)。请注意,虽然省略了expression1,但分号(;)必须存在。

例 2: 省略expression2

/*
  2nd variaton - expression2 is omitted
*/

#include<stdio.h>

int main()
{
    int i, sum = 0;

   for(i = 1 ; ; i++)  // expression 2 is omitted
   {
       if(i > 100)
       {
            /* the break statement causes the loop to terminate.
               We will discuss the break statement in detail
               in later chapters.
             */
            break;
        }
        sum += i;
    }
    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

预期输出:

Sum = 5050

这里省略了条件。为了补偿这种情况,我们添加了 if 语句。当控制进入 for 循环的主体时,检查条件(i>100),如果为假,则省略 if 块中的语句。当i到达100时,条件(i>100)变为真,执行break语句,导致循环终止,程序执行随循环后的语句恢复。

注意:语句导致退出循环。在章节中的中断和continue语句中有详细讨论。

例 3: 省略expression3

/*
 3rd variation - expression3 is omitted
*/

#include<stdio.h>

int main()
{
    int i, sum = 0;

    // expression3 is omitted

    for(i = 1 ; i <= 100 ; )
    {
        sum += i;
        i++; // update expression
    }

    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

预期输出:

Sum = 5050

这里省略了第三个表达式。为了补偿第三个表达式,我们在sum += i;语句后添加了i++;

例 4:

/*
   4th variation - all the expressions are omitted
*/
#include<stdio.h>

int main()
{    
    int i = 0; // initialization expression
    int sum = 0;

    for( ; ; )
    {
        if(i > 100) // condition
        {
            break; // break out of the for loop
        }
        sum += i;
        i++; // update expression
    }

    printf("Sum = %d", sum);

    // return 0 to operating system
    return 0;
}

预期输出:

Sum = 5050

循环的嵌套

正如 if-else 语句可以嵌套在另一个 if-else 语句中一样,我们可以将任何类型的循环嵌套在任何其他类型的循环中。例如,一个 For 循环可以嵌套在另一个 for 循环中,或者嵌套在 while 或 do while 循环中。同样,while 和 do while 也可以嵌套。

以下程序使用嵌套 for 循环打印半金字塔图案:

#include<stdio.h>

int main()
{
    int row = 0, col = 0;

    for(row = 0; row < 10; row++)  // number of lines
    {
        for(col = 0; col < row; col++)  // num of * in each lines
        {
            printf(" * ");
        }
        printf("\n");
    }

    // return 0 to operating system
    return 0;
}

预期输出:

*
* *
* * *
* * * *
* * * * *
* * * * * *
* * * * * * *
* * * * * * * *
* * * * * * * * *

工作原理:

在第 5 行,我们已经声明并初始化了两个整数变量rowcol

在第 7-14 行,我们有一个嵌套的 for 循环。外部 for 循环控制要打印的行数,内部 for 循环控制每行要打印的*数。

当执行外部 for 循环时,row变量的值被初始化为0,然后条件(row<10)被测试,因为它是真的(0<10)控制进入外部 for 循环的主体,这是另一个 for 循环。在内部 for 循环中,变量col被初始化为0,然后检查条件(col<=row),因为它是真的(0<=0)。执行内部循环体内部的语句,即printf(" * ")。然后,使用更新表达式col++col增加1(现在col的值为1)。再次测试条件(col<=row),因为它是假的(1<=0)。控件脱离内部 for 循环。第 13 行的printf()语句打印一个换行符(\n)。由于没有更多的语句可以执行,控制转移到外部 for 循环的更新表达式。row的值增加1(现在row1)。测试条件(row<10),因为它是真的(1<10)。再次执行外部 for 循环的主体。这个过程会一直重复直到row<10。当row到达10时,条件row < 10变为假,控制从外环出来。



C 语言中的无限循环

原文:https://overiq.com/c-programming-101/the-infinite-loop-in-c/

最后更新于 2020 年 7 月 27 日


无限重复且永不终止的循环称为无限循环。

大多数时候,我们会错误地创建无限循环。然而,这并不意味着无限循环没有用。无限循环通常用于长时间运行直到像 web 服务器一样停止的程序。

在下面的例子中,我们演示了什么样的错误会导致无限循环:

例 1:

short int i;

for (i = 32765; i < 32768; i++) 
{
    printf("%d\n", i);
}

这个循环是一个无限循环。这是为什么?根据条件,循环将执行到(i < 32768)。最初,i的值是32765,每次迭代后,它的值由更新表达式(i++)递增。但是short int型的数值从-3276832767不等。如果你试图将i的值增加到32767之外,它会向负面发展,这个过程会无限期地重复下去。因此条件(i < 32768)将永远是真的。

例 2:

int i = 1;

while(i<10)
{
    printf("%d\n", i); 
}

这里我们不更新i的值。所以每次迭代后i的值保持不变。因此,条件(i<10)永远是真的。为使循环正常工作,在printf()语句后添加i++;

永远记住,在 whiledo while loop 中比在 for loop 中更容易忘记更新表达式。

例 3:

导致无限循环的另一个常见错误是在需要等式运算符(==)的地方使用赋值运算符(=)。

int i = 1;

while(i=10)
{
    printf("%d", i);
}

我们真正想要的是循环应该执行到i等于10。整个表达式(i=10)的值是10,因为非零值被认为是真的,所以条件总是真的,并且循环将继续无限期执行。要解决问题,请将表达式(i=10)替换为(i==10)

例 4:

float f = 2;

while(f != 31.0)
{
    printf("%f\n", f);
    f += 0.1;
}

这个循环是无限的,因为计算机将浮点数表示为近似数,所以3.0可以存储为2.9999993.00001。所以条件(f != 31.0)永远不会变成假的。要解决这个问题,请将条件写为f <= 31.0

例 5:

int i = 0;

while(i<=5);
printf("%d\n", i);

这个循环将不会产生任何输出,并且将无限期地继续执行。仔细看看,注意 while 条件末尾的分号(;)。我们知道条件后的分号(;)是空语句。因此,该循环相当于以下内容:

int i = 0;

while(i<=5)
{
    ; // a null statement
}

printf("%d\n", i);

我们现在可以清楚地看到,如果i在 while 循环中,我们没有更新该值。因此,它将无限期地继续执行。



C 语言中的breakcontinue语句

原文:https://overiq.com/c-programming-101/the-break-and-continue-statement-in-c/

最后更新于 2020 年 7 月 27 日


break 语句

假设我们正在编写一个程序,在1000个数字中搜索一个特定的数字。在第十次迭代中,我们找到了想要的数字。此时,我们不想遍历剩余的 990 个数字,而是希望循环终止并继续执行循环后面的语句。这就是break语句发挥作用的地方。

当循环中遇到break语句时,程序控制立即脱离循环,用循环后的语句恢复执行。break语句通常与条件结合使用。

让我们举个例子:

#include<stdio.h>

int main()
{
    int i;

    for(i = 1; i < 10 ; i++)
    {
        if(i==5)
        {
            break; // breaks out of the for loop
        }
        printf("Value of i = %d\n", i);
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

Value of i = 1
Value of i = 2
Value of i = 3
Value of i = 4

工作原理:

在第 5 次迭代中i的值变为5(i==5)的条件是检查,因为它是真实的。执行break语句,控制从 for 循环中出来,执行其后的语句。如果没有break语句,这个循环会被执行 9 次。

让我们创造一些更有用的东西。以下程序确定用户输入的数字是否为质数。

#include<stdio.h>

int main()
{
    int i, num, flag=1;

    printf("Enter a number: ");
    scanf("%d", &num);

    for(i = 2; i < num-1 ; i++)
    {
        /*
            when this condition becomes true,
            it means that entered number is
            divisible by a number between 2 and num-1.
            So it can't be prime.
        */
        if(num%i==0)
        {
            flag = 0; // number is not prime
        }
    }

    if(flag==1)
    {
        printf("%d is prime", num);
    }

    else
    {
        printf("%d is not prime prime", num);
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

第一次运行:

Enter a number: 5
5 is prime

第二次运行:

Enter a number: 6
6 is not prime

工作原理:

如果一个数只能被 1 和它自己整除,它就是质数。这里有一种方法可以测试一个数是否是质数。试着把这个数从 2 除以小于这个数的 1,如果发现它能被这个范围内的任何数整除,那么它就不是质数,否则就是质数。

2n-1循环所有数字后。如果变量flag的值保持设置为1。那么n这个数字就是质数,否则就不是质数。

假设在第一次运行中,用户输入了5

在 for 循环中i被初始化为2。检查条件(i < num-1)(2 < 4),因为它是真的,控制向前移动以执行循环的主体。在循环体中,测试 if 条件num%i == 0,即5%2 == 0,因为它是假的。if 主体中的语句被省略。然后使用i++增加i的值。再次检查条件(i < num-1),即2 < 4,因为它仍然是真的,所以再次执行 for 的主体。重复该过程直到i < num-1。即使在第三次和第四次迭代后,flag的值保持不变,即1。当控制脱离 for 循环时,检查 if 条件,即flag==1,因为它是真的,if 语句体内部的语句被执行。

同样,假设在第二次运行中,用户输入了6

一切都和上面描述的完全一样。除了在第二次迭代中i达到 3 时,检查 if 条件(num%i==0),即6%3 == 0,因为它是真的。控件向前移动以在 if 块的主体内执行语句。在 if 块中,变量flag被初始化为1,这意味着数字不是质数。break语句打破了 for 循环。如果再次测试循环外的条件,即flag==1,因为它是假的,否则执行 else 块中的语句。

嵌套循环中的 break 语句

当在嵌套循环中使用break语句时,它只导致从最里面的循环退出。

#include<stdio.h>
int main()
{
    int i, j;

    for(i = 1; i < 5 ; i++)
    {
        printf("Outer for loop, i = %d\n", i);

        for(j = 1; j < 5 ; j++)
        {
            if(j==3)
            {
                 printf("Breaking out from the inner loop \n");  
                 break; // break out from inner loop only
            }
            printf("Inner for loop, j = %d\n", j);
        }
        printf("\n");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Outer for loop, i = 1
Inner for loop, j = 1
Inner for loop, j = 2
Breaking out from the inner loop

Outer for loop, i = 2
Inner for loop, j = 1
Inner for loop, j = 2
Breaking out from the inner loop

Outer for loop, i = 3
Inner for loop, j = 1
Inner for loop, j = 2
Breaking out from the inner loop

Outer for loop, i = 4
Inner for loop, j = 1
Inner for loop, j = 2
Breaking out from the inner loop

在这种情况下,当j的值达到3时,条件j == 3被评估为真,break语句导致从内部 for 循环退出(外部 for 循环将继续执行),并且程序控制被转移到循环之后的语句。

连续语句

continue语句用于提前结束当前迭代,并继续进行下一次迭代。当在循环中遇到continue语句时,continue语句之后的所有语句都被省略,循环继续下一次迭代。continue语句与条件结合使用。

有时人们会混淆breakcontinue语句。请始终记住,当遇到break语句时,它会跳出循环,但是当遇到continue语句时,循环不会终止,而是将控制传递到循环的开头。

当在 while 和 do while 循环中遇到continue语句时,控制转移到测试条件,然后循环继续。而在 for 循环中,当遇到continue语句时,控制转移到更新表达式,然后测试条件。

以下程序使用continue语句打印010之间所有不能被4整除的数字。

#include<stdio.h>

int main()
{
    int i;

    for(i = 0; i < 10; i++)
    {
        if( i % 4 == 0 )
        {
            /*
                when i is divisible by 4
                continue to the next iteration
            */
            continue;
        }
        printf("%d\n", i);
    }

    // signal to operating system everything works fine
    return 0;
}

预期输出:

text 1 2 3 5 6 7 9 `

工作原理:

当 for 循环开始时,变量i被设置为0,然后测试条件(i < 10)(0 < 10),因为它是真的,程序控制向前移动以执行循环体。在循环体内部,检查 if 条件i % 4 == 00 % 4 == 0,因为它是假的,所以跳过 if 体中语句的执行。在第 17 行,我们使用printf()语句打印i的值。循环体中没有更多的语句要执行,因此程序控制转移到更新表达式(i++)。这就完成了第一次迭代。

在第 5 次迭代中,i的值达到4。然后测试 if 条件(i % 4 == 0),即(4 % 4 == 0),因为是真的,所以执行continue语句,控制转移到更新表达式。表达式i++将变量i的值增加1。然后,再次测试条件i < 10。这个过程一直持续到i < 10



C 语言中的switch语句

原文:https://overiq.com/c-programming-101/the-switch-statement-in-c/

最后更新于 2020 年 7 月 27 日


转换语句

switch 语句是一个用于处理决策的多方向语句。它的工作原理几乎与 if-else 语句完全一样。不同之处在于,与 if-else 语句相比,switch 语句生成的代码可读性更好。此外,有时它的执行速度比 if-else 对应的更快。switch 语句的语法如下:

语法:

switch(expression)
{
    case constant1:
        statement1;
        statement2;
        ...
    case constant2:
        statement3;
        statement4;
        ...
    case constant3:
        statement5;
        statement6;
        ...
    default:
        statement7;
        ...
}

switch 语句中的expression可以是任何产生整数值的有效表达式。expression也可以是字符常量(因为所有字符在任何操作之前最终都会转换成整数),但不能是浮点或字符串。

constant1constant2等跟在case后面的关键字必须是整数类型(如intlong int等)或字符类型。它也可以是产生整数值的表达式。每个case语句只能有一个常量。单个case语句中不允许有多个常量。此外,所有大小写常量必须是唯一的。

在每个 case 常量之后,我们可以有任意数量的语句,或者根本没有语句。如果有多个语句,则不需要用大括号({})将它们括起来。

这里有一些有效的开关表达式和大小写常量。

int a,b,c;
float, f1, f2;
char ch1, ch2;

有效的开关表达式

switch(a)
switch(a + b + c)
switch(ch1 + a)
switch(a < b)
switch(my_func(12))
switch('a')

无效的开关表达式

switch(a + 12.2) // expression must yield an integer value not double or float.
switch(f1)       // same reason as above.
switch("string") // string is not allowed.

有效大小写常量

case 1
case 1 + 2
case 'a'
case 'a' < 'b'

无效的大小写常量

case "string"  // string constants are not allowed
case 1.2       // floating point constants are not allowed
case a         // variables are not allowed
case a + b     // variables are not allowed
case 1,2,3     // each case must contain only one constant</pre>

工作原理:

首先,计算开关后面的表达式,然后将该表达式的值与每种情况逐一进行比较。如果表达式的值与任何大小写常量匹配,则执行该大小写下的语句。如果表达式的值与任何大小写常量都不匹配,那么将执行默认情况下的语句。默认语句是可选的,如果省略,并且没有大小写匹配,则不执行任何操作。

现在让我们看看 switch 语句的实际应用。

以下程序要求用户输入一个数字,并相应地打印消息:

#include<stdio.h>

int main()
{
    int i, sum;

    printf("Enter a number: ");
    scanf("%d", &i);

    switch(i)
    {
        case 1:
        printf("Number is one\n");

        case 2:
        printf("Number is two\n");

        case 3:
        printf("Number is three\n");

        case 4:
        printf("Number is four\n");

        default:
        printf("something else\n");
    }

    // return 0 to operating system
    return 0;
}

预期输出:

Enter a number: 2
Number is two
Number is three
Number is four
something else

工作原理:

假设用户进入2。然后计算开关表达式,将表达式的值与每种情况进行比较。当找到匹配时,将执行该情况下的所有语句。在我们的例子中,第二种情况的值匹配表达式的值(即 2)。因此,该情况下的所有语句都会被执行。需要注意的是case 3case 4default下的语句也会被执行。这就是所谓的失败案例,这就是 switch 语句默认的工作方式。

大多数情况下,我们不希望控制通过所有案例的语句,相反,我们只想执行匹配案例下的语句,并脱离 switch 语句。我们可以使用break语句来实现这一点。当break语句在 switch 语句中被反击时,程序控制立即脱离 switch,并使用其后的语句恢复执行。

让我们重写我们以前的程序,但是这一次,将只执行匹配案例的语句。

#include<stdio.h>

int main()
{
    int i, sum;

    printf("Enter a number: \n");
    scanf("%d", &i);

    switch(i)
    {
        case 1:
            printf("Number is one\n");
            break;
        case 2:
            printf("Number is two\n");
            break;
        case 3:
            printf("Number is three\n");
            break;
        case 4:
            printf("Number is four\n");
            break;
        default:
            printf("something else\n");
    }

    // return 0 to operating system
    return 0;
}

预期输出:

第一次运行:

Enter a number:
3
Number is three

第二次运行:

Enter a number:
11
something else

使用 Switch 语句的简单计算器

#include<stdio.h>

int main()
{
    int a =1, b = 2;
    char op;

    printf("Enter first number: ");
    scanf("%d", &a);

    printf("Enter second number: ");
    scanf("%d", &b);

    printf("Enter operation: ");
    scanf(" %c", &op);

    switch(op)
    {
        case '+':
            printf("%d + %d = %d", a, b, a+b);
            break;
        case '-':
            printf("%d - %d = %d", a, b, a-b);
            break;
        case '*':
            printf("%d * %d = %d", a, b, a*b);
            break;
        case '/':
            printf("%d / %d = %d", a, b, a/b);
            break;
        default:
            printf("Invalid Operation \n");
    }

    // return 0 to operating system
    return 0;
}

预期输出:

第一次运行:

Enter first number: 34
Enter second number: 13
Enter operation: *
34 * 13 = 442

第二次运行:

Enter first number: 441
Enter second number: 981
Enter operation: +
441 + 981 = 1422

工作原理:

在第 5 行中,声明了两个类型为int的变量ab。这些变量将存储用户输入的数字。

在第 6 行,声明了类型为char的变量op。这将存储用户输入的操作员标志。

在第 8-9 行,程序要求用户输入第一个数字。输入的数字存储在变量a中。

在第 11-12 行,程序再次要求用户输入第二个数字。输入的数字存储在变量b中。

在第 14-15 行,程序要求用户输入他/她想对这两个数字执行的操作的符号。输入的符号被分配给变量op

在第 17-33 行,我们有一个 switch 语句。变量op在 switch 语句中用作表达式。然后将op的值与每种情况逐一进行比较。如果op的值与任何情况匹配,则执行该情况下的语句,并且break语句使程序控制脱离switch语句。



函数

C 语言中的函数基础

原文:https://overiq.com/c-programming-101/function-basics-in-c/

最后更新于 2020 年 7 月 27 日


函数是 C 语句的集合,用来做特定的事情。C 程序由一个或多个函数组成。每个程序都必须有一个名为main()的函数。

函数优势

  • 一个大问题可以分成子问题,然后用函数求解。
  • 这些函数是可重用的。一旦你创建了一个函数,你就可以在程序的任何地方调用它,而不需要复制和粘贴整个逻辑。
  • 程序变得更容易维护,因为如果你想在以后修改程序,你只需要在一个地方更新你的代码。

函数类型

  1. 图书馆函数
  2. 用户定义函数

图书馆函数

c 有很多内置的库函数来执行各种操作,例如:sqrt()函数用来求一个数的平方根。同样的,scanf()printf()也是库函数,我们从第 1 章-C 编程入门就开始使用了。

要使用库函数,我们必须首先使用#include预处理器指令包含相应的头文件。对于scanf()printf()对应的头文件是stdio.h,对于sqrt()等数学相关的函数,则是math.h

// Program to find the square root of a number
#include<stdio.h>
#include<math.h>

int main()
{
    float a;

    printf("Enter number: ");
    scanf("%f", &a);

    printf("Square root of %.2f is %.2f", a, sqrt(a));

    // signal to operating system program ran fine
    return 0;
}

预期输出:

第一次运行:

Enter number: 441
Square root of 441.00 is 21.0

第二次运行:

Enter number: 889
Square root of 889.00 is 29.82

常见的数学函数

函数 描述 例子
sqrt(x) x的平方根 sqrt(900.0)30.0
exp(x) 指数函数 exp(1.0)2.718282
log(x) x(以 e 为基数)&#124;对数的自然对数(2.718282) is 1.0 `
log10(x) x的对数(以10为基数) log10(1.0)0.0
fabs(x) x作为浮点数的绝对值 fabs(13.5)13.5
ceil(x) x舍入到不小于x的最小整数 ceil(9.2)10.0
floor(x) x舍入到不大于x的最大整数 floor(9.2)9.0
pow(x,y) x升至动力y pow(2, 7)128.0
sin(x) x ( x以弧度为单位)的三角正弦 sin(0.0)0.0
cos(x) x ( x弧度)的三角余弦 cos(0.0)1.0
tan(x) x ( x弧度)的三角正切 tan(0.0)0.0

要使用这些函数,您必须首先包含头文件math.h

用户定义函数

用户创建的函数称为用户定义函数。要创建自己的函数,你需要知道三件事。

  1. 函数定义。
  2. 函数调用。
  3. 函数声明。

函数定义

函数定义由构成函数的代码组成。函数由函数头和函数体两部分组成。下面是函数的一般语法。

return_type function_name(type1 argument1, type2 argument2, ...)
{
    local variables;
    statement1;
    statement2;
    return (expression);
}

函数的第一行称为函数头。它由return_typefunction_ name和函数参数组成。

return_type表示值函数返回的类型,例如intfloat等。return_type是可选的,如果省略,则默认为int。一个函数可以返回一个值,也可以不返回任何值,如果一个函数不返回任何值,则使用void代替return_type

function_name是函数的名称。它可以是任何有效的 C 标识符。在函数名之后,括号内是参数声明。它由参数的类型和名称组成。论点也被称为正式论点。一个函数可以有任意数量的参数,甚至没有参数。如果函数没有任何参数,那么括号就留空,或者有时用 void 来表示不接受任何参数的函数。

函数的主体是函数的肉,这是您编写业务逻辑的地方。函数的主体是一个复合语句(或块),它由任何有效的 C 语句和一个可选的return语句组成。函数内部声明的变量称为局部变量,因为它们是函数的局部变量,这意味着您不能从另一个函数访问一个函数内部声明的变量。当一个函数需要向它的调用者传递一些东西时,使用 return 语句。return的说法是随意的。如果一个函数没有返回值,那么它的return_type必须是void,同样,如果一个函数返回一个int值,它的return_type必须是int

你可以在程序的任何地方写函数定义,但通常是放在main()函数之后。

让我们创建一个小函数。

void my_func()
{
    printf("Hello i am my_func()");
}

my_func()函数不返回值,所以return_typevoid。此外,它不接受任何圆括号为空的论点。

也可以在括号内写void,明确表示这个函数不接受任何参数。

void my_func(void)
{
    printf("Hello i am my_func()");
}

在整个教程中,我们将使用这种方法。

my_func()函数的主体只有一行,每次调用函数时都会打印"Hello i am my_func()"

让我们创建另一个小函数。

int product(int num1, int num2)
{
    int result;
    result = num1 * num2;
    return result;
}

该函数接受两个参数并返回一个整数值。变量result在函数内部声明,所以它是一个局部变量,只在函数内部可用。第 5 行的return语句将num1num2的乘积返回给调用方。另外需要注意的一点是,和变量result一样,num1num2都是局部变量,这意味着我们不能在函数product()之外访问它们。

函数调用

定义函数后,下一步是使用函数,要使用函数,必须调用它。要调用一个函数,您必须在括号()中写下它的名称,后面跟以逗号(,)分隔的参数。

例如,下面是我们如何调用上面创建的product()函数。

product(12, 10);

这里我们将两个参数1210传递给函数product()。值1210将分别分配给变量num1num2

如果我们这样调用product()函数:

product(12);

我们会得到如下语法错误:

正如您所看到的,编译器抱怨“函数产品的参数太少”,这仅仅意味着调用函数的参数数量少于所需数量。

如果一个函数不接受任何参数,那么它必须使用空括号来调用。

my_func();

下图描述了调用函数时发生的情况。

当从 main()调用my_func()函数时,控制传递给 my_func()。此时main()函数的活动暂停;当 my_func()函数开始工作时,它会进入睡眠状态。当my_func()函数完成其任务或者当没有更多的语句要执行时,控制返回到main()函数。main()唤醒并执行statement2。然后在下一行sum()函数被调用,控制转到sum()。再次暂停main()函数的活动,直到sum()正在执行。当sum()没有要执行的语句时,控制返回到main()。函数main()再次唤醒,执行statement3。需要注意的重点是main()函数正在调用my_func()sum(),所以main()调用函数,而my_func()sum()调用函数

如果一个函数返回值,那么它可以像操作数一样用在任何表达式中。例如:

a = product(34, 89) + 100;
printf( "product is = %d", product(a, b) );

你没有义务使用函数的返回值。

product();

这里product()的返回值被丢弃。

如果一个函数没有返回值,那么我们就不能在表达式中使用它,如下所示:

s = myfunc();

还有一点需要注意,函数内部的语句只有在调用函数时才会执行。如果您已经定义了一个函数,但从未调用过它,那么它里面的语句将永远不会被执行。

函数声明

调用函数需要一些关于被调用函数的信息。当函数定义先于调用函数时,就不需要函数声明。例如:

#include<stdio.h>

// function definition

int sum(int x, int y)
{
    int s;
    s = x + y;
    return s;
}

int main()
{
    // function call
    printf("sum = %d", sum(10, 10));

    // signal to operating system everything works fine
    return 0;
}

注意函数sum()的定义在调用函数之前,即main(),这就是不需要函数声明的原因。

一般函数定义在main()函数之后。在这种情况下,需要函数声明。

函数声明由函数头组成,函数头末尾有一个分号(;)。

这里是函数my_func()sum()的函数声明。

void my_func(void);

int product(int x, int y);

函数声明中的参数名称是可选的,

int product(int x, int y)

可以写成:

int product(int , int )

请注意,返回类型和参数类型必须与创建函数时定义的类型相同。所以你不能写以下内容:

float product(int a, int b)–错误,因为product()函数返回类型为int
int product(float a, int b)–错误,因为product()函数第一个参数是int类型。

我想提到的另一个要点是,函数声明中定义的参数名称不必与函数定义中定义的相同。

int sum(int abc, int xyx)  // Function declaration

int sum(int x, int y)      // Function definition
{ 
    int s; 
    s = x + y; 
    return s; 
}

这个代码完全有效。

函数声明通常放在预处理器指令的下面。

下面的程序演示了我们在本章中所学的一切。

#include<stdio.h>

// function declaration
int sum(int x, int y);

int main()
{
    // function call
    printf("sum = %d", sum(10, 10));

    // signal to operating system everything works fine
    return 0;
}

// function definition
int sum(int x, int y)
{
    int s;
    s = x + y;
    return s;
}

预期输出:

sum = 20

以下程序使用函数打印最大的数字。

#include<stdio.h>

// function declaration
int max(int x, int y);

int main()
{
    // function call

    max(100, 12);
    max(10, 120);
    max(20, 20);

   // signal to operating system program ran fine
   return 0;
}

// function definition

int max(int x, int y)
{
    if(x > y)
    {
        printf("%d > %d\n", x, y );
    }

    else if(x < y)
    {
        printf("%d < %d\n", x, y );
    }

    else
    {
        printf("%d == %d\n", x, y );
    }
}

预期输出:

100 > 12
10 < 120
20 == 20



C 语言中的return语句

原文:https://overiq.com/c-programming-101/the-return-statement-in-c/

最后更新于 2020 年 7 月 27 日


return 语句用于返回一些值,或者简单地将控件传递给调用函数。return 语句可以通过以下两种方式使用。

  1. return;
  2. return expression;

return 语句的第一种形式用于终止函数并将控件传递给调用函数。当使用这种形式的return语句时,不会返回被调用函数的值。

下面的程序演示了第一种形式的return语句的使用。

#include<stdio.h>
void eligible_or_not(int x);

int main()
{
    int n;
    printf("Enter age: ");
    scanf("%d", &n);
    eligible_or_not(n);

    // signal to operating system everything works fine
    return 0;
}

void eligible_or_not(int age)
{
    if(age >= 18)
    {
        printf("You are eligible to vote");
        return;
    }

    else if(age == 17 )
    {
        printf("Try next year");
        return;
    }

    else
    {
        printf("You are not yet ready");
        return;
    }
}

预期输出:

第一次运行:

Enter age: 16
You are not yet ready

第二次运行:

Enter age: 17
Try next year

第三次运行:

Enter age: 19
You are eligible to vote

工作原理

假设用户输入了17(变量n的值),然后调用eligible_or_not()函数,该函数需要一个类型为int的参数,我们已经正确地将其提供为n。变量 n 的值分配给变量age。记住age是一个局部变量,因此只在eligible_or_not()函数中可用。测试 if 条件(age >= 18),由于为假,省略 if 块下的语句。控制转到 else 语句,测试条件(age == 17),因为它是真的,所以执行 else if 块下的语句。else if 块下的return语句将控制传递回调用函数,即main()。如果return语句不在 else if 块中,控件将会被移到前面,执行 if-else 语句之后的语句。

return 语句的第二种形式用于从函数返回值。返回后的表达式可以是任何常量、变量、函数调用等。

下面的程序使用一个函数计算一个数的阶乘。

#include<stdio.h>
int factorial(int x); // declaration of factorial function

int main()
{
    int n;
    printf("Calculate factorial: \n\n");
    printf("Enter number : ");
    scanf("%d", &n);

    if(n < 0)
    {
        printf("\nFactorial is only defined for positive numbers");
    }

    else
    {
        printf("\n%d! = %d", n, factorial(n)); // calling factorial function
    }

    // signal to operating system everything works fine
    return 0;
}

// definition of factorial function

int factorial(int n)
{
    if(n == 0)
    {
        return 1;
    }

    int f = 1, i;

    for(i = n; i > 0; i-- )
    {
        f = f * i;
    }
    return f;
}

预期输出:

Calculate factorial:

Enter number : 5

5! = 120

工作原理

main()函数没有什么异常发生,所以我们只解释一下factorial()函数是如何工作的。假设用户输入了 5,在第 18 行中调用了factorial()函数,以及 int 类型的参数。在第 30 行,如果条件(n==0)被选中,由于它是假的,if 块中的语句被省略。如果条件(n==0)为真,那么语句return 1;(我们为什么返回1,因为0的阶乘是1)将被执行,控制将被转移回main()功能,有效终止factorial()功能。

在第 34 行,声明了 int 类型的两个变量fi,并且变量f被赋值为1

在第 36 行,对于执行的循环初始化表达式,i被赋予n的值。然后测试条件(i>0),既然是真的,就执行 for 体内的语句。此时,f的价值是5。这就完成了第一次迭代。然后执行更新表达式i--。这个过程一直持续到i大于0。下表显示了每次迭代后ifact的值。

循环 I 的值 事实价值
1 5 5
2 4 5*4 = 20
3 3 20* 3 = 60

i到达0时,条件(i > 0)变为假,控制脱离 for 循环,执行循环后的语句。

在第 40 行,return语句导致factorial()函数终止,并将变量f的值返回给调用函数main()



C 语言中的实际参数和形式参数

原文:https://overiq.com/c-programming-101/actual-and-formal-arguments-in-c/

最后更新于 2020 年 7 月 27 日


实际论据

函数调用中提到的参数称为实际参数。例如:

func1(12, 23);

这里1223是实际的论点。

实际参数可以是常量、变量、表达式等。

func1(a, b); // here actual arguments are variable
func1(a + b, b + a); // here actual arguments are expression

形式论证

函数定义中提到的参数称为形式参数。形式参数与函数内部的局部变量非常相似。就像局部变量一样,形式参数在函数结束时会被销毁。

int factorial(int n)
{
    // write logic here
}

这里n是形式论证。关于实际和形式上的争论需要记住的事情。

  1. 函数调用中实际参数的顺序、数量和类型必须与函数的形式参数相匹配。
  2. 如果实际参数和形式参数之间存在类型不匹配,那么编译器将尝试将实际参数的类型转换为形式参数(如果它是有效的),否则,垃圾值将被传递给形式参数。
  3. 在正式参数中所做的更改不会影响实际参数。

下面的程序演示了这种行为。

#include<stdio.h>
void func_1(int);

int main()
{
    int x = 10;

    printf("Before function call\n");
    printf("x = %d\n", x);

    func_1(x);

    printf("After function call\n");
    printf("x = %d\n", x);

    // signal to operating system program ran fine
    return 0;
}

void func_1(int a)
{
    a += 1;
    a++;
    printf("\na = %d\n\n", a);
}

这里变量x的值是调用函数func_1()前的10,调用func_1()main()内的x的值仍然是10。在功能func_1()内所做的更改不影响x的值。这是因为当我们将值传递给函数时,会生成一个值的副本,并将该副本传递给形式参数。因此,形式上的争论是基于原始值的副本,而不是原始值本身,这就是为什么在func_1()中所做的更改没有反映在main()中的原因。这个过程被称为使用按值调用传递参数,我们将在后面的章节中更详细地讨论这个概念。



C 语言中的局部、全局和静态变量

原文:https://overiq.com/c-programming-101/local-global-and-static-variables-in-c/

最后更新于 2020 年 7 月 27 日


局部变量

在函数、复合语句(或块)中声明的变量称为局部变量。

void function_1()
{
    int a, b; // you can use a and b within braces only
}

void function_2()
{
    printf("%d\n", a); // ERROR, function_2() doesn't know any variable a
}

ab称为局部变量。它们仅在定义它们的函数中可用(在本例中为function_1())。如果您试图在定义这些变量的函数之外使用它们,您将会得到一个错误。另一个要点是变量ab只存在于function_1()执行之前。一旦功能function_1()结束,变量ab被破坏。

考虑以下代码:

#include<stdio.h>

int main()
{
    int a = 100;

    {
        /*
            variable a declared in this block is
            completely different from variable
            declared outside.
        */
        int a = 10;  
        printf("Inner a = %d\n", a);
    }

    printf("Outer a = %d\n", a);

    // signal to operating system everything works fine
    return 0;
}

预期输出:

Inner a = 10
Outer a = 100
d

在复合语句或块内创建的变量a,即大括号({})与块外声明的变量a完全不同。一旦块结束,块内声明的变量a就被销毁。

您可以在不同的函数中使用相同的变量名,它们不会相互冲突。例如:

void function_1()
{
    int a = 1, b = 2;
}

void function_2()
{
    int a = 10, b = 20;
}

在这种情况下,function_1()内部的变量ab是 function_1()的局部变量,而function_2()内部的变量abfunction_2()的局部变量。他们彼此完全独立。如果改变function_1()a的值,则不会改变function_2()a的值。

全局变量

在任何函数之外声明的变量称为全局变量。它们不限于任何功能。任何函数都可以访问和修改全局变量。申报时全局变量自动初始化为0。全局变量一般写在main()函数之前。

#include<stdio.h>
void func_1();
void func_2();
int a, b = 10;  // declaring and initializing global variables

int main()
{
    printf("Global a = %d\n", a);
    printf("Global b = %d\n\n", b);

    func_1();
    func_2();

    // signal to operating system program ran fine
    return 0;
}

void func_1()
{
    printf("From func_1() Global a = %d\n", a);
    printf("From func_1() Global b = %d\n\n", b);
}

void func_2()
{
    int a = 5;
    printf("Inside func_2() a = %d\n", a);
}

预期输出:

Global a = 0
Global b = 10

From func_1() Global a = 0
From func_1() Global b = 10

Inside func_2() a = 5

在第 4 行中,ab被声明为两个类型为int的全局变量。变量 a 将自动初始化为 0。你可以在任何函数中使用变量ab。请注意,在函数func_2()内部有一个与全局变量同名的局部变量。当全局变量和局部变量发生冲突时,局部变量优先,这就是为什么会打印局部变量afunc_2()值内部。

与局部变量不同,函数一结束,全局变量就不会被破坏。在程序执行之前,它们可用于任何功能。

静态变量

静态变量能够在不同的函数调用之间保持其值。静态变量只初始化一次,如果没有初始化,则自动初始化为0。下面是如何声明一个静态变量。

语法: static type var_name;

#include<stdio.h>

void func_1();
int a, b = 10;

int main()
{
    func_1();
    func_1();
    func_1();

    // signal to operating system everything works fine
    return 0;
}

void func_1()
{
    int a = 1;
    static int b = 100;
    printf("a = %d\n", a);
    printf("b = %d\n\n", b);
    a++;
    b++;
}

预期输出:

a = 1
b = 100

a = 1
b = 101

a = 1
b = 102

func_1()中,变量b被声明为静态变量。第一次调用func_1()b初始化为100,第 22 行b的值递增。下次调用func_1()时,将保留b的新值。当第二次调用func_1()时,变量b保留了它的值,即第 20 行的 101,通过打印b的值来证明它,b 的值再次增加 1。同样,第三次调用func_()时,b的值为102。请注意,只有变量b能够保留其值,因为变量b被声明为静态的,但是变量a不是这样,变量a在每次调用func_1()时都会被初始化。另外,注意静态变量b在第一次调用func_1()时只初始化一次。



C 语言中的递归函数

原文:https://overiq.com/c-programming-101/recursive-function-in-c/

最后更新于 2020 年 7 月 27 日


在 C 语言中,一个函数可以自己调用。这个过程被称为递归。

调用自身的函数称为递归函数。起初,递归可能会显得有些棘手。让我们举一个简单的例子:

int main()
{
    callme();
    ...
    return 0;
}

void rec()
{
    statement 1;
    ...
    rec();
}

一开始main()函数调用rec(),然后在rec()函数内部,它又调用了自己。正如你所猜测的,这个过程会无限期地重复下去。因此,在递归函数中,必须有终止条件来停止递归。这种情况称为基本情况。

int main()
{
    callme();
}

void callme()
{
    if(base_condition)
    {
        // terminating condition
    }
    statement 1;
    ...
    callme();
}

在可以使用循环的地方,经常可以使用递归。一般来说,递归解决方案是优雅的,但不如循环解决方案有效。那么为什么要使用递归呢?因为使用像 quicksort 这样的递归可以更清楚、更容易地实现一些算法。

递归函数分两个阶段工作:

  1. 绕组相位。
  2. 展开阶段。

绕线阶段:在绕线阶段,递归函数不断调用自己。当达到基本条件时,此阶段结束。

解套阶段:当达到基础条件时,解套阶段开始,控制返回初始调用。

让我们举个例子:

例 1:

#include<stdio.h>
void rec();

int main()
{
     rec(1);

    // signal to operating system program ran fine
    return 0;
}

void rec(int n)
{
    printf("Winding phase: Level = %d\n", n);

    if(n<3)
    {
        rec(n+1);
    }

    printf("Unwinding phase: Level = %d\n", n);
}

预期输出:

Winding phase: Level = 1
Winding phase: Level = 2
Winding phase: Level = 3
Unwinding phase: Level = 3
Unwinding phase: Level = 2
Unwinding phase: Level = 1

工作原理:

绕组阶段 1:

首先,main()调用实际参数为 1 的rec()函数。因此,rec()函数的形式参数被初始化为1的值。在第 14 行中,执行printf()语句并打印n的值。

"Winding phase: Level = 1"

然后测试 if 条件(n < 3)(1 < 3),因为是真的,rec()1 级称为rec()2 级,实际参数为 2。

绕组阶段 2:

现在,控制再次传递到第 2 级rec()函数,其形式参数为2。第 14 行的printf()语句再次执行并打印。

"Winding phase: Level = 2"

如果条件(n < 3)(2 < 3)再次被测试,由于是真的,第二级rect()3的实际论证称为第三级rec()

绕组阶段 3:

一旦控制传递到第 3 级rec()函数,其形式参数为3。第 14 行的printf()语句再次执行并打印。

"Winding phase: Level = 3"

如果条件(n < 3)(3 < 3)被检查,但这次是假的,结果,对rec()的呼叫被跳过。现在我们的程序已经达到了基础条件。这就完成了缠绕阶段。

展开阶段 1:

在这个三级调用中,第一次执行并打印第 21 行的printf()语句。

"Unwinding phase: Level = 3"

一旦缠绕阶段 3 中的rec()功能结束,控制就传递回其调用者(即 2 级调用),并从那里恢复执行。

展开阶段 2:

由于在第 2 级调用中执行的最后一条语句是对 if 语句中第 3 级rec()函数的调用,因此,第 2 级rec()函数继续执行以下语句,并打印出来。

"Unwinding phase: Level = 2"

然后 2 级rec()功能结束,将控制传递给 1 级rec()功能。

展开阶段 3:

就像在第 2 级rec()调用中一样,第 1 级 rec()中的执行继续执行 if 语句之后的语句,该语句会打印出来。

"Unwinding phase: Level = 1"

然后 1 级 rec()结束,控制返回到main()功能。

例 2:

下面的程序使用递归计算一个数的阶乘。

#include<stdio.h>
int factorial(int n);

int main()
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    printf("%d! = %d", n, factorial(n));

    // signal to operating system program ran fine
    return 0;
}

int factorial(int n)
{
    if(n == 0) // base condition
    {
        return 1;
    }

    else
    {
        return n * factorial(n-1);
    }
}

预期输出:

Enter a number: 5
5! = 120

工作原理:

假设我们要计算5的阶乘。

main()5 != 0factorial(5)
-factorial(5)factorial(4)
4 != 0 - factorial(4)factorial(3)
3 != 0 - factorial(3)factorial(2)
2 != 0 - factorial(2)factorial(1)1 != 0 - factorial(1)factorial(0)

当用n = 0调用factorial()时,如果条件变为真,递归停止,控制返回到factorial(1)。从现在开始,每个被调用的函数都将按照与函数调用相反的顺序返回一个值给前一个函数。

例 3:

使用递归计算一个数的幂的程序。

#include<stdio.h>
int power(int base, int exp);
int main()
{
    int base, exp;

    printf("Enter base: ");
    scanf("%d", &base);

    printf("Enter exponent: ");
    scanf("%d", &exp);

    printf("%d ^ %d = %d", base, exp, power(base, exp));

    // signal to operating system everything works fine
    return 0;
}

int power(int base, int exp)
{
    if(exp == 0) // base condition
    {
        return 1;
    }

    else
    {
        return base * power(base, exp - 1);
     }
}

预期输出:

Enter base: 4
Enter exponent: 3
4 ^ 3 = 64



数组

C 语言的一维数组

原文:https://overiq.com/c-programming-101/one-dimensional-array-in-c/

最后更新于 2020 年 9 月 23 日


这个变量允许我们一次存储一个值,如果我们想存储 100 个学生的卷号呢?对于这个任务,我们必须声明 100 个变量,然后给每个变量赋值。如果有 10000 名或更多的学生呢?正如您所看到的,声明单个实体(即学生)的许多变量不是一个好主意。在这种情况下,这些数组提供了更好的数据存储方式。

什么是阵列?

数组是一个或多个相同类型的值的集合。每个值称为数组的一个元素。数组的元素共享相同的变量名,但每个元素都有自己唯一的索引号(也称为下标)。数组可以是任何类型,例如:intfloatchar等。如果一个数组是int类型,那么它的元素只能是int类型。

为了存储100学生的卷号,我们必须声明一个大小为100的数组,即roll_no[100]。这里数组的大小是100,所以能够存储100值。在 C 中,索引或下标从0开始,所以roll_no[0]是第一个元素,roll_no[1]是第二个元素,以此类推。请注意,数组的最后一个元素将位于roll_no[99]而不是roll_no[100],因为索引从0开始。

数组可以是单个的,也可以是多维的。下标或索引的数量决定了数组的维数。一维数组称为一维数组或一维数组,而二维数组称为二维数组或二维数组。

让我们从一维数组开始。

一维阵列

从概念上讲,您可以将一维数组视为一行,其中元素一个接一个地存储。

语法: datatype array_name[size];

数据类型:表示数组中元素的类型。

数组 _ 名称:数组的名称。它必须是有效的标识符。

大小:数组可以容纳的元素数量。下面是一些数组声明示例:

int num[100];
float temp[20];
char ch[50];

numint类型的数组,只能存储int类型的100元素。
tempfloat类型的数组,只能存储20类型的float元素。
chchar类型的数组,只能存储char类型的50元素。

注意:当一个数组被声明时,它包含垃圾值。

数组中的各个元素:

num[0], num[1], num[2], ....., num[99]
temp[0], temp[1], temp[2], ....., temp[19]
ch[0], ch[1], ch[2], ....., ch[49]

我们还可以使用变量和符号常量来指定数组的大小。

#define SIZE 10

int main()
{
    int size = 10;

    int my_arr1[SIZE]; // ok
    int my_arr2[size]; // not allowed until C99
    // ...
}

:在 C99 标准之前,我们是不允许用变量来指定数组的大小的。如果您使用支持 C99 标准的编译器,上述代码将会编译成功。但是,如果您使用的是像 Turbo C++这样的旧版本的 C 编译器,那么您将会得到一个错误。

符号常量的使用使得程序具有可维护性,因为以后如果你想改变数组的大小,你需要立刻修改它,只需放入#define指令即可。

访问数组的元素

数组的元素可以通过在方括号内指定数组名后跟下标或索引来访问(即[])。数组下标或索引从0开始。如果数组的大小是10,那么第一个元素在索引0处,而最后一个元素在索引9处。第一个有效下标(即0)称为下限,最后一个有效下标称为上限

int my_arr[5];

那么这个数组的元素是;

第一元素–my_arr[0]
第二元素–my_arr[1]
第三元素–my_arr[2]
第四元素–my_arr[3]
第五元素–my_arr[4]

数组下标或索引可以是任何产生整数值的表达式。例如:

int i = 0, j = 2;
my_arr[i]; // 1st element
my_arr[i+1]; // 2nd element
my_arr[i+j]; // 3rd element

在数组my_arr中,最后一个元素在my_arr[4]处,如果试图访问数组最后一个有效索引以外的元素怎么办?

printf("%d", my_arr[5]); // 6th element
printf("%d", my_arr[10]); // 11th element
printf("%d", my_arr[-1]); // element just before 0

确定索引510-1无效,但是 C 编译器不会显示任何错误消息,而是会打印一些垃圾值。C 语言不检查数组的边界。程序员有责任随时检查数组边界。

处理一维数组

下面的程序使用 for 循环来获取一维数组的输入和打印元素。

#include<stdio.h>

int main()
{
    int arr[5], i;

    for(i = 0; i < 5; i++)
    {
        printf("Enter a[%d]: ", i);
        scanf("%d", &arr[i]);
    } 

    printf("\nPrinting elements of the array: \n\n");

    for(i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

现在试试

预期输出:

Enter a[0]: 11
Enter a[1]: 22
Enter a[2]: 34
Enter a[3]: 4
Enter a[4]: 34

Printing elements of the array:

11 22 34 4 34

工作原理:

在第 5 行中,我们已经声明了一个由int类型的5整数和变量i组成的数组。然后使用 for 循环将五个元素输入数组。在scanf()中,我们在数组的元素arr[i]上使用了&运算符(也称为运算符的地址),就像我们对类型为intfloatchar等的变量所做的那样。第 13 行将"Printing elements of the array"打印到控制台。第二个 for 循环逐个打印数组的所有元素。

以下程序打印数组元素的总和。

#include<stdio.h>

int main()
{
    int arr[5], i, s = 0;

    for(i = 0; i < 5; i++)
    {
        printf("Enter a[%d]: ", i);
        scanf("%d", &arr[i]);
    }

    for(i = 0; i < 5; i++)
    {
        s += arr[i];
    }

    printf("\nSum of elements = %d ", s);

    // signal to operating system program ran fine
    return 0;
}

现在试试

预期输出:

Enter a[0]: 22
Enter a[1]: 33
Enter a[2]: 56
Enter a[3]: 73
Enter a[4]: 23

Sum of elements = 207

工作原理:第一个 for 循环要求用户在数组中输入五个元素。第二个 for 循环逐个读取数组的所有元素,并累加变量s中所有元素的总和。注意需要将变量s初始化为0,否则会因为s的垃圾值而得到错误的答案。

初始化数组

当在函数内部声明数组时,数组的元素具有垃圾值。如果一个数组是全局的或静态的,那么它的元素会自动初始化为0。我们可以使用以下语法在声明时显式初始化数组的元素:

语法: datatype array_name[size] = { val1, val2, val3, ..... valN };

datatype是数组的元素类型。

array_name是变量名,必须是任何有效的标识符。

size是数组的大小。

val1val2...被称为初始值设定项的常量。每个值用逗号(,)分隔,在右花括号(})后有一个分号(;)。

下面是一些例子:

float temp[5] = {12.3, 4.1, 3.8, 9.5, 4.5}; // an array of 5 floats

int arr[9] = {11, 22, 33, 44, 55, 66, 77, 88, 99}; // an array of 9 ints

现在试试

初始化一维数组时,指定数组的大小是可选的,因此您也可以将上面的语句写成:

float temp[] = {12.3, 4.1, 3.8, 9.5, 4.5}; // an array of 5 floats

int arr[] = {11, 22, 33, 44, 55, 66, 77, 88, 99}; // an array of 9 ints

现在试试

如果初始值设定项的数量小于指定的大小,则数组的剩余元素被赋予一个值0

float temp[5] = {12.3, 4.1};

这里temp数组的大小是5但是只有两个初始值设定项。初始化之后,数组的元素如下:

temp[0]12.3
temp[1]4.1
0
temp[3]0
temp[4]0

现在试试

如果初始值设定项的数量大于数组的大小,那么旧的编译器将报告一个错误。然而,大多数新编译器只是发出警告消息。

int num[5] = {1, 2, 3, 4, 5, 6, 7, 8} // Error in old compilers, warning in new ones

现在试试

以下程序查找数组中最高和最低的元素。

#include<stdio.h>
#define SIZE 10

int main()
{
    int my_arr[SIZE] = {34,56,78,15,43,71,89,34,70,91};
    int i, max, min;

    max = min = my_arr[0];

    for(i = 0; i < SIZE; i++)
    {
        // if value of current element is greater than previous value
        // then assign new value to max
        if(my_arr[i] > max)
        {
            max = my_arr[i];
        }

        // if the value of current element is less than previous element
        // then assign new value to min
        if(my_arr[i] < min)
        {
            min = my_arr[i];
        }
     }

    printf("Lowest value = %d\n", min);
    printf("Highest value = %d", max);

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Lowest value = 15
Highest value = 91

工作原理:在第 6 行,首先,我们已经声明并初始化了一个 10 个整数的数组。在下一行中,我们声明了另外三个类型为int的变量,即:imaxmin。在第 9 行中,我们将my_arr的第一个元素的值分配给了maxmin。for 循环用于迭代数组的所有元素。在 for 循环中,第一个 if 条件(my_arr[i] > max)检查当前元素是否大于max,如果大于,我们将当前元素的值赋给max

第二个 if 语句检查当前元素的值是否小于min的值。如果是,我们将当前元素的值赋给min。这个过程一直持续到数组中还有元素需要迭代。

当过程结束时,maxmin变量将分别有最大值和最小值。



C 语言中的一维数组和函数

原文:https://overiq.com/c-programming-101/one-dimensional-array-and-function-in-c/

最后更新于 2020 年 7 月 27 日


将一维数组元素传递给函数

我们可以像传递任何普通变量一样传递一维数组的元素。下面的示例演示了相同的内容。

#include<stdio.h>
void odd_or_even(int a);

int main()
{
    int my_arr[] = {13,56,71,38,93}, i;

    for(i = 0; i < 5; i++)
    {
        // passing one element at a time to odd_or_even() function
        odd_or_even(my_arr[i]); 
    }

    // signal to operating system program ran fine
    return 0;
}

void odd_or_even(int a)
{
    if(a % 2 == 0)
    {
        printf("%d is even\n", a);
    }

    else
    {
        printf("%d is odd\n", a);
    }
}

预期输出:

13 is odd
56 is even
71 is odd
38 is even
93 is odd

将整个数组传递给函数

就像普通变量一样,您可以将数组变量传递给函数。但是在此之前,请确保将形式参数声明为相同数据类型的数组变量。例如:

int main()
{
    ...
    int a[10];
    ...
    function_1(a);
    ...
    return 0;
}

void function_1(int arr[10])
{
    ...
    statement ;1
    ...
}

这里我们将10整数数组传递给function_1(),这就是为什么function_1()的形式参数也被声明为10整数数组。

在形式参数中指定数组的大小是可选的。这意味着您也可以如下声明function_1()的形式参数:

void function_1(int arr[])
{
    ...
    statement 1;
    ...
}

在学习形式和实际论点的同时,我们了解到形式论点中的变化不会影响实际论点。数组不是这样。当数组作为实际参数传递时,函数可以访问原始数组,因此函数内部所做的任何更改都会影响原始数组。

#include<stdio.h>
void new_array(int a[]);

int main()
{
    int my_arr[] = {13,56,71,38,93}, i;

    printf("Original array: \n\n");

    for(i = 0; i < 5; i++)
    {
        printf("%d ", my_arr[i]);
    }

    new_array(my_arr);

    printf("\n\nModified array : \n\n");

    for(i = 0; i < 5; i++)
    {
        printf("%d ", my_arr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

void new_array(int a[])
{
    int i;

    // multiply original elements by 2

    for(i = 0; i < 5; i++)
    {
        a[i] = 2 * a[i];
    }
}

预期输出:

Original Array:

13 56 71 38 93

Modified array:

26 112 142 76 186

工作原理:

main()函数中的第一个 for 循环打印数组元素的初始值。在第 15 行中,new_array()函数被调用,实际参数为my_arr。控制转移到功能new_array()。该函数将数组的每个元素乘以2,并将这个新值赋回当前索引。由于new_array()正在处理原始数组,而不是原始数组的副本,因此new_array()函数所做的任何更改都会影响原始数组。当函数结束时,控制再次传递回main()函数,其中第二个 for 循环打印数组的元素。



C 语言中的二维数组

原文:https://overiq.com/c-programming-101/two-dimensional-array-in-c/

最后更新于 2020 年 7 月 27 日


二维阵列

二维数组的语法声明和一维数组没有太大区别。在二维数组中,要声明和访问二维数组的元素,我们使用 2 个下标而不是 1 个下标。

语法: datatype array_name[ROW][COL];

二维数组中的元素总数为ROW*COL。举个例子吧。

int arr[2][3];

这个数组可以存储2*3=6元素。您可以将这个二维数组可视化为 2 行 3 列的矩阵。

上述数组的单个元素可以通过使用两个下标而不是一个下标来访问。第一个下标表示行号,第二个下标表示列号。如上图所示,行和列都是从0开始索引的。所以这个数组的第一个元素在arr[0][0],最后一个元素在arr[1][2]。以下是访问所有其他元素的方法:

arr[0][0] -指第一元素
arr[0][1] -指第二元素
arr[0][2] -指第三元素
arr[1][0] -指第四元素
arr[1][1] -指第五元素
arr[1][2] -指第六元素

如果试图访问有效ROWCOL之外的元素,C 编译器将不会显示任何类型的错误消息,而是打印一个垃圾值。处理界限是程序员的责任。

arr[1][3] -将打印一个垃圾值,因为COL的最后一个有效索引是2
arr[2][3] -将打印一个垃圾值,因为ROWCOL的最后一个有效索引分别是12

就像一维数组一样,我们也只能用常量和符号常量来指定二维数组的大小。

#define ROW 2
#define COL 3

int i = 4, j = 6;
int arr[ROW][COL]; // OK
int new_arr[i][j]; // ERROR

处理二维数组的元素

为了处理二维数组的元素,我们使用两个嵌套循环。外部 for 循环遍历所有行,内部 for 循环遍历所有列。以下程序将清除所有内容。

#include<stdio.h>
#define ROW 3
#define COL 4

int main()
{
    int arr[ROW][COL], i, j;

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("Enter arr[%d][%d]: ", i, j);
            scanf("%d", &arr[i][j]);
        }
    }

    printf("\nEntered 2-D array is: \n\n");

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("%3d ", arr[i][j] );
        }
        printf("\n");
    }
    // signal to operating system everything works fine
    return 0;
}

预期输出:

Enter arr[0][0]: 11
Enter arr[0][1]: 35
Enter arr[0][2]: 73
Enter arr[0][3]: 831
Enter arr[1][0]: 3
Enter arr[1][1]: 40
Enter arr[1][2]: 31
Enter arr[1][3]: 93
Enter arr[2][0]: 35
Enter arr[2][1]: 10
Enter arr[2][2]: 52
Enter arr[2][3]: 81

Entered 2-D array is:

11 35 73 831
3 40 31 93
35 10 52 81

工作原理:

在之前的节目中没有什么值得解释的新内容。我们只是使用两个嵌套的 for 循环。第一个嵌套的 for 循环接受用户的输入。第二个 for 循环像矩阵一样打印二维数组的元素。

初始化二维数组

二维数组的初始化类似于一维数组。例如:

int temp[2][3] = {
    { 1, 2, 3 }, // row 0
    {11, 22, 33} // row 1
};

初始化后,每个元素如下:

temp[0][0] : 1
temp[0][1] : 2
temp[0][2] : 3
temp[1][0] : 11
temp[1][1] : 22
temp[1][2] : 33

考虑另一个初始化。

int my_arr[4][3] = {
                       {10},
                       {77, 92},
                       {33, 89, 44},
                       {12, 11}
                   };

my_arr的大小是4*3=12,但是在初始化的时候,我们只指定了8元素的值。在这种情况下,剩余的元素将被赋予0的值。

各个要素如下:

my_arr[0][0] : 10
my_arr[0][1] : 0
my_arr[0][2] : 0

my_arr[1][0] : 77
my_arr[1][1] : 92
my_arr[1][2] : 0

my_arr[2][0] : 33
my_arr[2][1] : 89
my_arr[2][2] : 44

my_arr[3][0] : 12
my_arr[3][1] : 11
my_arr[4][2] : 0

在二维数组中,指定第一维是可选的,但第二维必须始终存在。只有当您同时声明和初始化数组时,这才有效。例如:

int two_d[][3] = {
                     {13,23,34},
                     {15,27,35}
                 };

与...相同

int two_d[2][3] = {
                      {13, 23, 34},
                      {15, 27, 35}
                  };

如前所述,您可以将二维数组可视化为矩阵。下面的程序演示了两个矩阵的加法。

#include<stdio.h>
#define ROW 2
#define COL 3

int main()
{
    int mat1[ROW][COL], mat2[ROW][COL], mat3[ROW][COL];
    int i, j;

    printf("Enter first matrix: \n\n");

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("Enter a[%d][%d]: ", i, j);
            scanf("%d", &mat1[i][j]);
        }
    }

    printf("\nEnter Second matrix: \n\n");

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("Enter a[%d][%d]: ", i, j);
            scanf("%d", &mat2[i][j]);
        }
    }

    // add mat1 and mat2

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            mat3[i][j] = mat1[i][j] + mat2[i][j] ;
        }
    }

    printf("\nResultant array: \n\n");

    // print resultant array

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("%5d ", mat3[i][j]);
        }
        printf("\n");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter first matrix:

Enter a[0][0]: 12
Enter a[0][1]: 32
Enter a[0][2]: 13
Enter a[1][0]: 35
Enter a[1][1]: 54
Enter a[1][2]: 35

Enter Second matrix:

Enter a[0][0]: 57
Enter a[0][1]: 64
Enter a[0][2]: 58
Enter a[1][0]: 72
Enter a[1][1]: 84
Enter a[1][2]: 29

Resultant array:

mat1 + mat2 =

69 96 71
107 138 64

工作原理:

只有当两个矩阵具有相同的维数时,它们才能相加或相减。换句话说,一个大小为2*3的矩阵可以和另一个 2*3 的矩阵相加,但是不能和2*43*2的矩阵相加或相减。得到的数组将是一个与原始数组相同维数的矩阵。前两个 for 循环要求用户输入两个矩阵。第三个 for 循环在新数组mat3中添加了mat1mat2的相应元素。第四个为循环打印数组的元素mat3

二维以上的数组

你甚至可以创建一个 3 维或更多维的数组,但一般来说,你永远不需要这样做。因此,我们将仅限于三维阵列。

下面是如何声明一个三维数组。

int arr[2][3][2];

三维数组使用三个索引或下标。这个数组可以存储2*3*2=12元素。

下面是如何初始化一个三维数组。

int three_d[2][3][4] = {
                           {
                               {12,34,56,12},
                               {57,44,62,14},
                               {64,36,91,16},
                           },

                           {
                               {87,11,42,82},
                               {93,44,12,99},
                               {96,34,33,26},
                           }
                      };

你可以把这个数组想象成 2 个二维数组,每个二维数组都有3行和4列;

以下是数组的各个元素:

第一排

three_d[0][0][0] : 12
three_d[0][0][1] : 34
three_d[0][0][2] : 56

three_d[0][0][3] : 12
three_d[0][1][0] : 57
three_d[0][1][1] : 44

three_d[0][1][2] : 62
three_d[0][1][3] : 14
three_d[0][2][0] : 64

three_d[0][2][1] : 36
three_d[0][2][2] : 91
three_d[0][2][3] : 16

第二排

three_d[1][0][0] : 87
three_d[1][0][1] : 11
three_d[1][0][2] : 42

three_d[1][0][3] : 82
three_d[1][1][0] : 93
three_d[1][1][1] : 44

three_d[1][1][2] : 12
three_d[1][1][3] : 99
three_d[1][2][0] : 96

three_d[1][2][1] : 34
three_d[1][2][2] : 33
three_d[1][2][3] : 26

将多维数组传递给函数

您可以像一维数组一样将多维数组传递给函数,但是您需要指定除第一个维度之外的所有其他维度的大小。例如:

如果需要将arr[2][3]传递给一个名为func_1()的函数,那么需要这样声明func_1():

void func_1(int my_arr[2][3]) // OK
{
    //...
}

或者像这样:

void func_1(int my_arr[][3]) // OK
{
   //...
}

宣布正式论点如下是无效的:

void func_1(int my_arr[][]) // error
{
    //...
}

类似地,要传递三维数组,您需要声明如下函数:

int arr[2][3][4];

void func_1(int my_arr[][3][4])
{
    //...
}



指针

C 语言中的指针基础

原文:https://overiq.com/c-programming-101/pointer-basics-in-c/

最后更新于 2020 年 7 月 27 日


C 的真正力量在于指针。指针一开始有点难掌握。看完指针的基础知识后,你会对它们是什么以及如何使用它们有更好的了解。

什么是指针?

指针是用于存储内存地址的变量。让我们首先了解一下内存在计算机内部是如何组织的。

计算机中的内存是由字节(一个字节由8位组成)按顺序排列而成的。每个字节都有一个与之相关联的数字,就像数组中的索引或下标一样,这被称为字节的地址。字节的地址从0开始到比内存小一个字节。例如,假设在 64MB 的内存中,有64 * 2^20 = 67108864字节。因此这些字节的地址将从0开始到67108863

让我们看看当你声明一个变量时会发生什么。

int marks;

我们知道一个int占用4字节的数据(假设我们使用的是 32 位编译器),所以编译器从内存中保留4连续字节来存储一个整数值。4分配字节的第一个字节的地址被称为变量marks的地址。假设4连续字节的地址为5004500550065007,那么可变标记的地址为5004

地址运算符(&)

为了找到变量的地址,C 提供了一个名为地址运算符(&)的运算符。要找出可变标记的地址,我们需要在它前面放置&运算符,如下所示:

&marks

下面的程序演示了如何使用地址运算符(&)。

// Program to demonstrate address(&) operator

#include<stdio.h>

int main()
{
    int i = 12;

    printf("Address of i = %u \n", &i);
    printf("Value of i = %d ", i);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of i = 2293340
Value of i = 12

注意:每次运行程序时i的地址可能会有所不同。

工作原理:

要找到变量的地址,在变量名前加&运算符。关于程序还有一点需要注意的是%u转换规范的使用。回想一下%u转换规范用于打印无符号十进制数,由于内存地址不能为负数,所以必须始终使用%u而不是%d

运算符(&)的地址不能用于常量或表达式,只能用于变量。

&var; // ok

&12; // error because we are using & operator with a constant

&(x+y) // error because we are using & operator with an expression</pre>

我们一直在函数scanf()中使用地址运算符(&),不知道为什么?变量的地址被提供给scanf(),这样它就知道在哪里写数据了。

声明指针变量

如前所述,指针是存储内存地址的变量。就像任何其他变量一样,您需要先声明一个指针变量,然后才能使用它。以下是如何声明指针变量。

语法: data_type *pointer_name;

data_type是指针的类型(也称为指针的基类型)。
pointer_name是变量的名称,可以是任何有效的 C 标识符。让我们举一些例子:

int *ip;
float *fp;

int *ip表示ip是能够指向int类型变量的指针变量。换句话说,指针变量ip只能存储类型为int的变量的地址。同样,指针变量fp只能存储类型为float的变量的地址。变量的类型(也称为基类型)ip是指向int的指针,fp的类型是指向float的指针。指向 int 类型的指针变量可以象征性地表示为(int *)。类似地,指针浮动类型的指针变量可以表示为(float *)

就像其他变量一样,指针也是一个变量,所以编译器会在内存中保留一些空间。所有指针变量,无论其基类型如何,都将占用相同的内存空间。通常4字节或2字节(在 16 位编译器上)用于存储指针变量(这可能因系统而异)。

给指针变量分配地址

声明指针变量后,下一步是给它分配一些有效的内存地址。如果没有给指针变量分配有效的内存地址,就不应该使用指针变量,因为在声明之后,它包含垃圾值,并且可能指向内存中的任何地方。使用未赋值的指针可能会产生不可预测的结果。它甚至可能导致程序崩溃。

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

这里ip被声明为指向int的指针,所以它只能指向一个int变量的内存地址。同样,fp只能指向一个float变量的地址。在最后两个语句中,我们已经将if的地址分别分配给了ipfp。现在,ip指向变量ifp指向变量f。需要注意的是,即使您将float变量的地址分配给int指针,编译器也不会向您显示任何错误,但您可能不会得到所需的结果。所以一般来说,你应该总是把一个变量的地址分配给相应的相同类型的指针变量。

我们可以在声明时初始化指针变量,但是在这种情况下,变量必须在指针变量之前声明和初始化。

int i = 10, *iptr = &i;

如果一个指针变量的基类型相同,可以将它们的值赋给另一个指针变量。例如:

int marks = 100, *p1, *p2;

p1 = &marks;

p2 = p1;

赋值后,p1p2指向同一个变量marks

如前所述,当一个指针变量被声明时,它包含垃圾值,并且它可以指向内存中的任何地方。您可以为任何指针变量分配一个名为NULL(在stdio.h中定义)的符号常量。NULL的赋值保证了指针不指向任何有效的内存位置。

int i = 100, *iptr;

iptr = NULL;

取消指针变量的引用

取消指针变量的引用仅仅意味着访问存储在指针变量中的地址上的数据。到目前为止,我们一直使用变量的名称来访问其中的数据,但是我们也可以使用指针间接访问变量数据。为了实现这一点,我们将使用一个名为间接运算符(*)的新运算符。通过将间接运算符(*)放在指针变量之前,我们可以访问地址存储在指针变量中的变量的数据。

int i = 100, *ip = &i;

这里ip存储变量i的地址,如果我们把*放在ip之前,那么我们就可以访问存储在变量i中的数据。这意味着下面两个语句做了同样的事情。

printf("%d\n", *ip); // prints 100
printf("%d\n", i); // prints 100

间接运算符(*)可以作为地址处的值读取。例如,*ip可以作为地址ip的值读取。

注意:建议千万不要对未初始化的指针变量应用间接操作符,这样做可能会导致意外行为,甚至程序可能会崩溃。

int *ip;
printf("%d", *ip); // WRONG

现在我们知道,通过取消指针变量的引用,我们可以访问存储在指针变量中的地址值。让我们深入了解一下编译器实际上是如何检索数据的。

char ch = 'a';
int i = 10;
double d = 100.21;

char *cp = &ch;
int *ip = &i;
double *ip = &d;

假设指针cp包含地址1000。当我们写*cp时,编译器知道它必须从起始地址1000检索信息。现在问题来了,从起始地址1000检索多少数据?1字节,2字节;你怎么想呢?为了知道从起始地址1000检索多少信息,编译器查看指针的基本类型,并将根据指针的基本类型检索信息。例如,如果基本类型是指向char的指针,则从起始地址检索1字节的信息,如果基本类型是指向int的指针,则从起始地址检索4字节的信息。需要注意的是,如果您所在的系统中int的大小为2字节,那么将从起始地址检索2字节的信息。

因此,在我们的例子中,只有来自起始地址的1字节的数据将被检索。即只检索存储在地址2000的数据。

同样,如果ip指向地址2000。在编写*ip时,编译器将从地址 2000 开始检索4字节的数据。

在下图中,阴影部分显示了检索到的字节数。

在继续之前,请解释以下表达式的含义:

*(&i),其中iint类型的变量。

从优先级表中我们知道括号()的优先级最高,所以&i先求值。由于&i是变量i的地址,因此将其与*运算符解引用会给出变量i的值。所以我们可以断定写*(&i)和写i是一样的。

下面的例子演示了到目前为止我们所学到的关于指针的一切。

#include<stdio.h>

int main()
{
    int i = 12, *ip = &i;
    double d = 2.31, *dp = &d;

    printf("Value of ip = address of i = %d\n", ip);
    printf("Value of fp = address of d = %d\n\n", d);

    printf("Address of ip = %d\n", &ip);
    printf("Address of dp = %d\n\n", &dp);

    printf("Value at address stored in ip = value of i = %d\n", *ip);
    printf("Value at address stored in dp = value of d = %f\n\n", *dp);

    // memory occupied by pointer variables 
    // is same regardless of its base type

    printf("Size of pointer ip = %d\n", sizeof(ip));
    printf("Size of pointer dp = %d\n\n", sizeof(dp));

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Value of ip = address of i = 2686788
Value of fp = address of d = 1202590843

Address of ip = 2686784
Address of dp = 2686772

Value at address stored in ip = value of i = 12
Value at address stored in dp = value of d = 2.310000

Size of pointer ip = 4
Size of pointer dp = 4

注意:每次运行程序时,内存地址可能会有所不同。

以上节目没有什么值得解释的新内容。在我们进入下一章之前,请始终记住指针变量的大小是相同的,不管它的基类型是什么,但是在解引用时将被访问的内存地址的大小取决于指针变量的基类型。



C 语言中的指针算法

原文:https://overiq.com/c-programming-101/pointer-arithmetic-in-c/

最后更新于 2020 年 9 月 24 日


现在你应该知道指针只不过是一个用来存储内存地址的变量。如果这对你来说仍然是新闻,那么在继续本章之前,回去阅读指针基础知识

在本章中,我们将讨论可以对指针执行的算术运算。

我们不能用指针执行所有类型的算术运算。指针算术与我们日常生活中通常使用的算术略有不同。适用于指针的唯一有效算术运算是:

  1. 向指针添加整数
  2. 指针的整数减法
  3. 减去两个相同类型的指针

指针算术是相对于指针的基本类型执行的。例如,如果我们有一个包含地址1000的整数指针ip,那么将其递增1,我们将得到1004(即1000 + 1 * 4)而不是1001,因为int数据类型的大小是4字节。如果我们使用的系统中int的大小是2字节,那么我们会得到1002(即1000 + 1 * 2)。

同样,递减它,我们将得到996(即1000 - 1 * 4)而不是999

所以,表达式ip + 4将指向地址1016(即1000 + 4 * 4)。

我们再举几个例子。

int i = 12, *ip = &i;
double d = 2.3, *dp = &d;
char ch = 'a', *cp = &ch;

假设idch的地址分别为100020003000,因此ipdpcp最初位于100020003000

整数上的指针算法

指针表达式 如何评价?
ip = ip + 1 ip = > ip + 1 = > 1000 + 1*4 = > 1004
ip++++ip ip++ = > ip + 1 = > 1004 + 1*4 = > 1008
ip = ip + 5 ip = > ip + 5 = > 1008 + 5*4 = > 1028
ip = ip - 2 ip = > ip - 2 = > 1028 - 2*4 = > 1020
ip----ip ip = > ip + 2 = > 1020 + 2*4 = > 1028

浮点指针算法

指针表达式 如何评价?
dp + 1 dp = dp + 1=>T1】=>T2】
dp++++dp dp++ = > dp+1 = > 2008+1*8 = > 2016
dp = dp + 5 dp = > dp + 5 = > 2016+5*8 = > 2056
dp = dp - 2 dp = > dp - 2 = > 2056-2*8 = > 2040
dp----dp DP = > DP-1=>2040-1 * 8=>2032’

字符上的指针算法

指针表达式 如何评价?
cp + 1 cp = cp + 1=>T1】=>T2】
cp++++cp cp = > cp + 1 = > 3001 + 1*1 = > 3002
cp = cp + 5 cp = > cp + 5 = > 3002 + 5*1 = > 3007
cp = cp - 2 cp = > cp + 5 = > 3007 - 2*1 = > 3005
cp----cp cp = > cp + 2 = > 3005 - 1*1 = > 3004

注意:当我们使用指针算法递增或递减指针变量时,变量idch的地址不会受到任何影响。

对类型char的算术运算看似普通的算术运算,因为char类型的大小是1字节。另一个需要注意的要点是,当我们通过增加或减少数字来增加或减少指针变量时,指针变量不必仍然指向有效的内存位置。所以,我们在这样移动指针的时候,一定要特别注意。通常,我们对数组使用指针算法,因为数组的元素排列在连续的内存位置,这将在下一章中详细讨论。

下面的程序展示了指针算法。

#include<stdio.h>

int main()
{
    int i = 12, *ip = &i;
    double d = 2.3, *dp = &d;
    char ch = 'a', *cp = &ch;

    printf("Value of ip = %u\n", ip);
    printf("Value of dp = %u\n", dp);
    printf("Value of cp = %u\n\n", cp);

    printf("Value of ip + 1 = %u\n", ip + 1);
    printf("Value of dp + 1 = %u\n", dp + 1);
    printf("Value of cp + 1 = %u\n\n", cp + 1);

    printf("Value of ip + 2 = %u\n", ip + 2);
    printf("Value of dp + 2 = %u\n", dp + 2);
    printf("Value of cp + 2 = %u\n", cp + 2);

    return 0;
}

现在试试

预期输出:

Value of ip = 2293316
Value of dp = 2293304
Value of cp = 2293303

Value of ip + 1 = 2293320
Value of dp + 1 = 2293312
Value of cp + 1 = 2293304

Value of ip + 2 = 2293324
Value of dp + 2 = 2293320
Value of cp + 2 = 2293305

两个指针之间的指针算法

如果我们有两个基类型指针p1p2分别指向地址为10001016int,那么p2 - p1将给出4,因为int类型的大小是4字节。如果从p1p1 - p2中减去p2,那么答案将是否定的,即-4

下面的程序演示了相同类型的两个指针之间的指针算法。

#include<stdio.h>

int main()
{
    int i1 = 12, *ip1 = &i1;
    int i2 = 12, *ip2 = &i2;

    printf("Value of ip1 or address of i1 = %u\n", ip1);
    printf("Value of ip2 or address of i2 = %u\n\n", ip2);

    printf("ip2 - ip1 = %d\n", ip1 - ip2);
    printf("ip1 - ip2 = %d\n", ip2 - ip1);

    // signal to operating system program ran fine
    return 0;
}

现在试试

预期输出:

Value of ip1 or address of i1 = 2686788
Value of ip2 or address of i2 = 2686780

ip2 - ip1 = 2
ip1 - ip2 = -2

组合间接运算符(*)和递增/递减运算符

在处理数组元素时(正如您将在下一章中看到的),C 程序员经常混合使用间接运算符(*)和递增/递减运算符(++--)。

请始终记住,间接运算符(*)和递增/递减运算符的优先级是相同的,并且它们是从右向左关联的(参见中的运算符优先级和关联性)。

假设x是整数变量,p是指向int的指针。现在考虑以下陈述,并尝试解释它们。

例 1:

x = *p++;

由于*++操作符具有相同的优先级,从右向左关联++将应用于p,而不是*p。因为递增运算符是后缀,所以表达式中首先使用p的值,然后它将递增。因此p指向的第一个整数将被取消引用并分配给x,然后p的值将增加1

例 2:

x = ++*p;

这里*运算符首先应用于p,然后++应用于*p。因此,第一个整数指针被取消引用,从取消引用中获得的值递增,并最终分配给x

例 3:

x = *++p;

++运算符有前缀,因此首先,p将递增,然后新地址的值被取消引用并分配给x

注意:如果你还有什么疑惑,可以随时用()围绕你想先评估的表情。

指针比较

可以使用带指针的关系运算符(<<=>>===!=)。==!=运算符用于比较两个指针是否包含相同的地址。当两个指针都为 null 或包含同一变量的地址时,它们是相等的。这些(即==!=)运算符的使用仅在指针属于相同的基类型时有效,或者在空指针和任何其他指针之间有效,或者在空指针(将在后面讨论)和任何其他指针之间有效。只有当两个指针都指向同一个数组的元素时,使用其他关系运算符(<<=>>=)来比较两个指针才有意义。

指针指向指针

我们知道指针是一个包含内存地址的变量。指针变量本身在内存中占据一些空间,因此它也有一个内存地址。我们可以将指针变量的地址存储在其他变量中,这就是所谓的指针对指针。将指针声明为指针的语法如下:

语法: data_type **p;

让我们举个例子:

int i = 10;
int *ip = &i;
int **iip = &ip;

这里ip是类型(int *)或者指向int的指针,iip是类型(int **)或者指向int的指针。

我们知道*ip将给出地址ip的值,即i的值。你能猜到**iip会返回什么值吗?

**iip

我们知道间接运算符是从右向左计算的,因此**iip也可以写成

*(*iip)

*iip指地址iip的值或存储在ip的地址。在取消引用存储在ip的地址时,我们将获得存储在变量i中的值。

*(*iip)
=> *ip
=> i

因此**iip给出存储在变量i中的值。

下面的程序演示了如何在int中使用指针对指针。

#include<stdio.h>

int main()
{
    int i = 10;
    int *ip = &i;
    int **iip = &ip;

    printf("Value of i = %d\n\n", i);

    printf("Address of i = %u\n", &i);
    printf("Value of ip = %d\n\n", ip);

    printf("Address of ip = %u\n", &ip);
    printf("Value of iip = %d\n\n", iip);

    printf("Value of *iip = value of ip = %d\n", *iip);
    printf("Value of **iip = value of i = %d\n\n", **iip);

    return 0;
}

现在试试

预期输出:

Value of i = 10

Address of i = 2293332
Value of ip = 2293332

Address of ip = 2293320
Value of iip = 2293320

Value of *iip = value of ip = 2293332
Value of **iip = value of i = 10



指针和一维数组

原文:https://overiq.com/c-programming-101/pointers-and-1-d-arrays/

最后更新于 2020 年 7 月 27 日


在 C 语言中,数组的元素存储在连续的存储单元中。例如:如果我们有以下数组。

int my_arr[5] = {1, 2, 3, 4, 5};

然后,这就是元素在数组中的存储方式。

这里第一个元素在地址5000,因为每个整数占用4字节,下一个元素在5004等等。

在 C 语言中,指针和数组的关系非常密切。我们可以使用指针访问数组的元素。在幕后,编译器还使用指针表示法而不是下标表示法访问数组元素,因为与下标表示法相比,使用指针访问元素非常有效。关于阵列,需要记住的最重要的事情是:

数组的名称是一个常量指针,指向数组第一个元素的地址或数组的基址。

我们可以使用下标符号(即使用方括号)来找到数组元素的地址。例如:

int my_arr[5] = {11, 22, 33, 44, 55};

这里&my_arr[0]指向数组第一个元素的地址。由于数组的名称是指向数组第一个元素的常量指针,my_arr&my_arr[0]代表相同的地址。&my_arr[1]指向第二个元素的地址。类似地&my_arr[2]指向第三个元素的地址等等。

注: my_arr(int *)类型或指向int的指针。

下面的程序演示了数组的元素存储在连续的内存位置。

#include<stdio.h>

int main()
{
    int my_arr[5] = {1, 2, 3, 4, 5}, i;

    for(i = 0; i < 5; i++)
    {
        printf("Value of a[%d] = %d\t", i, my_arr[i]);
        printf("Address of a[%d] = %u\n", i, &my_arr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Value of a[0] = 1 Address of a[0] = 2293312
Value of a[1] = 2 Address of a[1] = 2293316
Value of a[2] = 3 Address of a[2] = 2293320
Value of a[3] = 4 Address of a[3] = 2293324
Value of a[4] = 5 Address of a[4] = 2293328

注意:每次运行程序时,内存地址可能会有所不同。

使用指针访问数组中的元素和元素地址

我们知道数组的名字是指向第一个元素的常量指针。考虑以下片段:

int arr[] = {1,2,3,4,5};

这里arr是指向第一个元素的指针。但是,指针arr的基本类型是什么?如果你的答案指向int(int *)。干得好;).

在这种情况下,arr指向一个整数的地址,即整数1的地址。所以arr的基本类型是指向int(int*)的指针。

让我们再举一些例子:

char arr[] = {'A','B','C','D','E'};

arr指针的类型是什么?。

这里arr指向第一个元素的地址,它是一个字符。所以arr的类型是指向char(char *)的指针。

同样的,

double arr[] = {1.03, 29.3, 3.42, 49.3, 51.2};

这里arr是指向double(double *)的指针类型的指针。

注意:这些概念是接下来章节的构建模块,所以不要跳过。如果你还不明白,再看一遍。

现在,您可以使用指针算法轻松访问元素的值和地址。假设my_arr是一组5整数。

int my_arr[5] = {11, 22, 33, 44, 55};

这里my_arr是指向int(int *)的基类型指针的常量指针,根据指针算法,当一个整数加到一个指针上时,我们得到下一个相同基类型元素的地址。所以在上面的例子中,my_arr 指向第一个元素的地址,my_arr+1指向第二个元素的地址,my_arr + 2指向第三个元素的地址,以此类推。因此,我们可以得出结论:

my_arr&my_arr[0]
my_arr + 1&my_arr[1]
my_arr + 2&my_arr[2]
my_arr + 3&my_arr[3]
my_arr + 4&my_arr[4]

总的来说(my_arr + i)和写&my_arr[i]是一样的。

现在我们知道如何获取数组中每个元素的地址,通过使用间接运算符(*)我们可以获取地址处的值。如果我们取消引用my_arr,那么我们得到数组的第一个元素,即*my_arr。类似地,*(my_arr + 1)将返回数组的第二个元素,以此类推。

*(my_arr)my_arr[0]
*(my_arr + 1)my_arr[1]
*(my_arr + 2)my_arr[2]
*(my_arr + 3)my_arr[3]
*(my_arr + 4)my_arr[4]

总的来说*(my_arr+i)和写my_arr[i]是一样的。

以下程序使用指针表示法打印数组元素的值和地址。

#include<stdio.h>

int main()
{
    int my_arr[5] = {1, 2, 3, 4, 5}, i;

    for(i = 0; i < 5; i++)
    {
        printf("Value of a[%d] = %d\t", i, *(my_arr + i) );
        printf("Address of a[%d] = %u\n", i, my_arr + i );
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Value of a[0] = 1 Address of a[0] = 2293312
Value of a[1] = 2 Address of a[1] = 2293316
Value of a[2] = 3 Address of a[2] = 2293320
Value of a[3] = 4 Address of a[3] = 2293324
Value of a[4] = 5 Address of a[4] = 2293328

注意:每次运行程序时,内存地址可能会有所不同。

将一维数组赋给指针变量

是的,您可以将一维数组赋给指针变量。考虑以下示例:

int *p;
int my_arr[] = {11, 22, 33, 44, 55};
p = my_arr;

现在,您可以使用指针 p 来访问数组中每个元素的地址和值。需要注意的是,将一维数组分配给指向int的指针是可能的,因为my_arrp是相同的基类型,即指向int的指针。一般来说(p+i)表示 ith 元素的地址,*(p+i)表示 ith 元素的值。

数组的名字(即my_arr)和指针变量(即p)有些区别。数组的名字是一个常量指针,因此你不能改变它来指向其他内存位置。你不能给它分配其他地址,也不能像在指针变量中那样应用递增/递减运算符。

my_arr++; // error
my_arr--; // error
my_arr = &i // error

但是p是一个普通的指针变量,所以你可以应用指针算法,甚至给它分配一个新的地址。

p++; // ok
p--; // ok
p = &i // ok

下面的程序演示了如何通过将一维数组的元素赋给指针变量来访问作为其地址的值。

#include<stdio.h>

int main()
{
    int my_arr[5] = {1, 2, 3, 4, 5}, i;
    int *p;
    p = my_arr;
    // p = &my_arr[0]; // you can also do this

    for(i = 0; i < 5; i++)
    {
        printf("Value of a[%d] = %d\t", i, *(p + i) );
        printf("Address of a[%d] = %u\n", i, p + i );
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Value of a[0] = 1 Address of a[0] = 2293296
Value of a[1] = 2 Address of a[1] = 2293300
Value of a[2] = 3 Address of a[2] = 2293304
Value of a[3] = 4 Address of a[3] = 2293308
Value of a[4] = 5 Address of a[4] = 2293312

注意:每次运行程序时,内存地址可能会有所不同。



指针和二维数组

原文:https://overiq.com/c-programming-101/pointers-and-2-d-arrays/

最后更新于 2020 年 7 月 27 日


在最后一章中,我们创建了一个指针,它指向基类型为(int *)的数组的第 0 个元素或指向int的指针。我们还可以创建一个指针,它可以指向整个数组,而不是数组中的一个元素。这就是所谓的指向数组的指针。下面是如何声明指向数组的指针。

int (*p)[10];

这里p是一个可以指向10整数数组的指针。在这种情况下,p的类型或基类型是指向10整数数组的指针。

注意p周围的圆括号是必须的,所以不能这样做:

int *p[10];

这里p10整数指针的数组。指针数组将在接下来的章节中讨论。

指向数组第 0 个元素的指针和指向整个数组的指针完全不同。下面的程序演示了这个概念。

#include<stdio.h>

int main()
{
    int *p; // pointer to int
    int (*parr)[5]; // pointer to an array of 5 integers
    int my_arr[5]; // an array of 5 integers

    p = my_arr; 
    parr = my_arr;

    printf("Address of p = %u\n", p );
    printf("Address of parr = %u\n", parr );

    p++;
    parr++;

    printf("\nAfter incrementing p and parr by 1 \n\n");
    printf("Address of p = %u\n", p );
    printf("Address of parr = %u\n", parr );

    printf("Address of parr = %u\n", *parr );

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of p = 2293296
Address of parr = 2293296

After incrementing p and parr by 1

Address of p = 2293300
Address of parr = 2293316

工作原理:

这里p是指向数组第 0 个元素my_arr的指针,parr是指向整个数组my_arr的指针。p的基类型是类型(int *)或指向int的指针,parr的基类型是指向5整数数组的指针。由于指针算术是相对于指针的基本类型执行的,这就是为什么parr增加20字节(即5 x 4 = 20字节)的原因。另一方面,p只增加4字节。

关于指向数组的指针,需要记住的重要一点是:

每当一个指向数组的指针被取消引用时,我们就会得到它所指向的数组的地址(或基址)。

因此,在取消引用parr时,您将获得*parr。需要注意的重要一点是虽然parr*parr指向同一个地址,但是 parr 的基类型是指向5整数数组的指针,而*parr基类型是指向 int 的指针。这是一个重要的概念,将用于访问二维数组的元素。

指针和二维数组

在前面的章节中讨论二维数组时,我们告诉您将二维数组可视化为矩阵。例如:

int arr[3][4] = {
                    {11,22,33,44},
                    {55,66,77,88},
                    {11,66,77,44}
                };

上面的二维数组可以可视化如下:

在讨论数组时,我们使用了行和列这样的术语。这个概念只是理论上的,因为计算机内存是线性的,没有行和列。那么二维数组实际上是如何存储在内存中的呢?在 C 语言中,数组是按行顺序存储的。这仅仅意味着第一行 0 被存储,然后紧挨着它的行 1 被存储,紧挨着它的行 2 被存储,以此类推。

下图显示了二维数组是如何存储在内存中的。

关于多维数组,这里是你需要记住的最重要的概念。

二维数组实际上是一维数组,其中每个元素本身就是一维数组。所以 arr 是一个由 3 个元素组成的数组,其中每个元素是一个由 4 个整数组成的一维数组。

在前一章中,我们已经讨论过一维数组的名称是指向第 0 个元素的常量指针。在二维数组的情况下,第 0 个元素是一维数组。因此,在上面的例子中,arr的类型或基类型是指向4整数数组的指针。因为指针算术是相对于指针的基本大小来执行的。在arr的情况下,如果arr指向地址2000,那么arr + 1指向地址2016(即2000 + 4*4)。

我们知道数组的名称是指向数组第 0 个元素的常量指针。在二维数组的情况下,第 0 个元素是一维数组。因此在二维数组的情况下,数组的名称表示指向第 0 个一维数组的指针。因此在这种情况下arr是指向4元素数组的指针。如果第 0 个 1-D 的地址是2000,那么根据指针算法(arr + 1)将代表地址2016,同样(arr + 2)将代表地址2032

从以上讨论中,我们可以得出结论:

arr指向第 0 个一维数组。
(arr + 1)指向 1st 一维阵列。
(arr + 2)指向第二个一维阵列。

一般来说,我们可以这样写:

(arr + i)指向一维数组。

正如我们在本章前面讨论的,对数组指针的解引用给出了数组的基址。所以解引用arr我们会得到*arr,基础类型*arr(int*)。同样,在取消arr+1的引用时,我们会得到*(arr+1)。总的来说,我们可以说:

*(arr+i)指向第 ith 个一维数组的基址。

同样重要的是要注意类型(arr + i)*(arr+i)指向相同的地址,但是它们的基本类型完全不同。(arr + i)的基类型是指向 4 个整数的数组的指针,而*(arr + i)的基类型是指向int或(int*)的指针。

那么如何使用 arr 来访问二维数组的各个元素呢?

由于*(arr + i)指向每个 ith 一维数组的基址,并且是指向int的基类型指针,通过使用指针算法,我们应该能够访问 ith 一维数组的元素。

让我们看看如何做到这一点:

*(arr + i)指向一维数组第 0 个元素的地址。因此,
*(arr + i) + 1指向一维数组第一个元素的地址
*(arr + i) + 2指向一维数组第二个元素的地址

因此,我们可以得出结论:

*(arr + i) + j指向一维数组 jth 元素的基址。

在解引用*(arr + i) + j时,我们将得到一维数组的第一个元素的值。

*( *(arr + i) + j)

利用这个表达式,我们可以求出一维数组的 jth 元素的值。

此外,指针符号*(*(arr + i) + j)相当于下标符号。

下面的程序演示了如何使用指针表示法访问二维数组元素的值和地址。

#include<stdio.h>

int main()
{
    int arr[3][4] = {
                        {11,22,33,44},
                        {55,66,77,88},
                        {11,66,77,44}
                    };

    int i, j;

    for(i = 0; i < 3; i++)
    {
        printf("Address of %d th array %u \n",i , *(arr + i));
        for(j = 0; j < 4; j++)
        {
             printf("arr[%d][%d]=%d\n", i, j, *( *(arr + i) + j) );
        }
        printf("\n\n");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of 0 th array 2686736
arr[0][0]=11
arr[0][1]=22
arr[0][2]=33
arr[0][3]=44

Address of 1 th array 2686752
arr[1][0]=55
arr[1][1]=66
arr[1][2]=77
arr[1][3]=88

Address of 2 th array 2686768
arr[2][0]=11
arr[2][1]=66
arr[2][2]=77
arr[2][3]=44

将二维数组赋给指针变量

您可以将数组的名称分配给指针变量,但与一维数组不同,您需要指向数组的指针,而不是指向int或(int *)的指针。这里有一个例子:

int arr[2][3] = {
                    {33, 44, 55},
                    {11, 99, 66}
                };

请始终记住,二维数组实际上是一维数组,其中每个元素都是一维数组。所以arr是一个由2个元素组成的数组,其中每个元素是一个由 3 个整数组成的一维arr。因此,为了存储arr的基址,您需要一个指向3整数数组的指针。

同样,如果二维数组有3行和4列,即int arr[3][4],那么你需要一个指向4整数数组的指针。

int (*p)[3];

这里p是一个指向3整数数组的指针。所以根据指针算法p+i指向第 I 个一维数组,换句话说,p+0指向第 0 个一维数组,p+1指向第 1 个一维数组,以此类推。(p+i)的基本类型是指向3整数数组的指针。如果我们取消引用(p+i),那么我们将得到一维数组的基地址,但是现在*(p + i)的基类型是指向int或(int *)的指针。再次访问具有一维数组的 jth 元素的地址,我们只需将j添加到*(p + i)。所以*(p + i) + j指向一维数组的 jth 元素的地址。因此表达式*(*(p + i) + j)给出了一维数组的 jth 元素的值。

下面的程序演示了如何使用指向数组的指针来访问二维数组的元素。

#include<stdio.h>

int main()
{
    int arr[3][4] = {
                        {11,22,33,44},
                        {55,66,77,88},
                        {11,66,77,44}
                    };

    int i, j;
    int (*p)[4];

    p = arr;

    for(i = 0; i < 3; i++)
    {
        printf("Address of %d th array %u \n",i , p + i);
        for(j = 0; j < 4; j++)
        {
            printf("arr[%d][%d]=%d\n", i, j, *( *(p + i) + j) );
        }
        printf("\n\n");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of 0 th array 2686736
arr[0][0]=11
arr[0][1]=22
arr[0][2]=33
arr[0][3]=44

Address of 1 th array 2686752
arr[1][0]=55
arr[1][1]=66
arr[1][2]=77
arr[1][3]=88

Address of 2 th array 2686768
arr[2][0]=11
arr[2][1]=66
arr[2][2]=77
arr[2][3]=44



C 语言中的按值调用和按引用调用

原文:https://overiq.com/c-programming-101/call-by-value-and-call-by-reference-in-c/

最后更新于 2020 年 7 月 27 日


c 提供了两种向函数传递参数的方式。

  1. 按值调用或按值传递。
  2. 通过引用调用。

让我们从“价值召唤”开始。

按值调用

在这种方法中,首先复制每个实际参数,然后将这些值分配给相应的形式参数。

这意味着被调用函数所做的更改不会影响调用函数中实际参数的值。在上图所示的示例中,my_func()函数修改了val1val2值的副本。但是val1val2的原值不变。

到目前为止,我们编写的所有函数都使用按值调用,除了我们向函数传递数组的函数。

引用调用

在这种方法中,实际参数的地址被复制,然后分配给相应的形式参数。现在,形式参数和实际参数都指向相同的数据(因为它们包含相同的地址)。因此,被调用函数所做的任何更改也会影响实际参数。

让我们举一些例子:

以下程序演示了按值调用:

#include<stdio.h>
void try_to_change(int, int);

int main()
{
    int x = 10, y = 20;

    printf("Initial value of x = %d\n", x);
    printf("Initial value of y = %d\n", y);

    printf("\nCalling the function\n");

    try_to_change(x, y);

    printf("\nValues after function call\n\n");

    printf("Final value of x = %d\n", x);
    printf("Final value of y = %d\n", y);

    // signal to operating system program ran fine
    return 0;
}

void try_to_change(int x, int y)
{
    x = x + 10;
    y = y + 10;

    printf("\nValue of x (inside function) = %d\n", x);
    printf("Value of y (inside function) = %d\n", y);
}

预期输出:

Initial value of x = 10
Initial value of y = 20

Value of x (inside function) = 20
Value of y (inside function) = 30

Values after function call

Final value of x = 10
Final value of y = 20

工作原理:

函数main()内部的变量xy与函数try_to_change()形式自变量中的变量xy完全不同。第 13 行,调用try_to_change()函数时,复制xy的值,并将该副本传递给函数try_to_change()的形式参数xy。在函数try_to_change()中,我们试图通过为其分配新值来更改xy的原始值。由于try_to_change()正在处理xy的副本,因此try_to_change()函数所做的更改不会对实际参数xy产生影响。

要使用引用调用,我们需要做两件事:

  1. 传递实际参数的地址,而不是将值传递给函数。
  2. 将函数的形式参数声明为适当类型的指针变量。

下面的程序演示了引用调用。

#include<stdio.h>
void try_to_change(int *, int *);

int main()
{
    int x = 10, y = 20;

    printf("Initial value of x = %d\n", x);
    printf("Initial value of y = %d\n", y);

    printf("\nCalling the function\n");

    try_to_change(&x, &y);

    printf("\nValues after function call\n\n");

    printf("Final value of x = %d\n", x);
    printf("Final value of y = %d\n", y);

    // signal to operating system everything works fine
    return 0;
}

void try_to_change(int *x, int *y)
{
    (*x)++;
    (*y)++;

    printf("\nValue of x (inside function) = %d\n", *x);
    printf("Value of y (inside function) = %d\n", *y);
}

预期输出:

Initial value of x = 10
Initial value of y = 20

Calling the function

Value of x (inside function) = 11
Value of y (inside function) = 21

Values after function call

Final value of x = 11
Final value of y = 21

这里我们将整数变量的地址传递给一个函数。所以形式参数必须声明为指向int(int *)的指针。表达式(*x)++意味着首先在x取消引用该值,然后增加它。类似地,(*y)++意味着首先在y取消引用该值,然后增加它。当功能try_to_change()结束时,控制返回到第 17 行和第 18 行的main()printf()语句,分别打印xy的新值。



从 C 语言中的函数返回多个值

原文:https://overiq.com/c-programming-101/returning-more-than-one-value-from-function-in-c/

最后更新于 2020 年 7 月 27 日


在 C 语言的 return 语句一章中,我们了解到 Return 语句用于从函数中返回值。但是有一个限制,一个return语句只能从一个函数中返回一个值。在本章中,我们将看到如何通过引用调用来克服这个限制。

考虑以下问题。

Q -创建一个函数,返回传递给它的两个数的和、差、积。

告诉我你将如何处理这个问题?

解决这个问题的一种方法是为 3 个操作创建三个函数,然后在每个函数中使用 return 语句返回 sum、difference 和乘积。通过引用调用,我们可以很容易地解决这个问题。

下面的程序演示了如何通过引用调用从一个函数返回多个值。

#include<stdio.h>
void return_more_than_one(int a, int b, int *sum, int *diff, int *prod);

int main()
{
    int x = 40, y = 10, sum, diff, prod;

    return_more_than_one(x, y, &sum, &diff, &prod);

    printf("%d + %d = %d\n",x, y, sum);
    printf("%d - %d = %d\n",x, y, diff);
    printf("%d * %d = %d\n",x, y, prod);

    // signal to operating system program ran fine
    return 0;
}

void return_more_than_one(int a, int b, int *sum, int *diff, int *prod)
{
    *sum = a+b;
    *diff = a-b;
    *prod = a*b;
}

预期输出:

40 + 10 = 50
40 - 10 = 30
40 * 10 = 400

工作原理:

return_more_than_one()中,函数 a 和 b 通过值调用传递,而sumdiffprod通过引用调用传递。因此return_more_than_one()函数知道sumdiffprod变量的地址,所以它使用指针间接访问这些变量并改变它们的值。



从 C 语言中的函数返回指针

原文:https://overiq.com/c-programming-101/returning-a-pointer-from-a-function-in-c/

最后更新于 2020 年 7 月 27 日


我们已经看到一个函数可以返回 int、float、char 等类型的数据。同样,函数可以返回指向数据的指针。函数返回指针的语法如下。

语法: type *function_name(type1, type2, ...);

一些例子:

int *func(int, int); // this function returns a pointer to int

double *func(int, int); // this function returns a pointer to double

下面的程序演示了如何从函数返回指针。

#include<stdio.h>
int *return_pointer(int *, int); // this function returns a pointer of type int

int main()
{
    int i, *ptr;
    int arr[] = {11, 22, 33, 44, 55};
    i = 4;

    printf("Address of arr = %u\n", arr);

    ptr = return_pointer(arr, i);

    printf("\nAfter incrementing arr by 4 \n\n");

    printf("Address of ptr = %u\n\n" , ptr);
    printf("Value at %u is %d\n", ptr, *ptr);

    // signal to operating system program ran fine
    return 0;
}

int *return_pointer(int *p, int n)
{
    p = p + n;
    return p;
}

预期输出:

Address of arr = 2686736

After incrementing arr by 4

Address of ptr = 2686752

Value at 2686752 is 55

工作原理:

因为数组的名称是指向数组第 0 个元素的指针。这里我们将两个参数传递给函数return_pointer()。通过引用调用传递arr(注意数组的名称前面没有&运算符,因为数组的名称是指向一维数组第 0 个元素的常量指针),通过值调用传递i。在功能指针内p增加n并重新分配到p。最后,指针p返回到main()功能并重新分配给ptr

永远不要从函数返回指向局部变量的指针。

考虑下面的代码。

#include<stdio.h>
int *abc(); // this function returns a pointer of type int

int main()
{
    int *ptr;
    ptr = abc();
    return 0;
}

int *abc()
{
    int x = 100, *p;
    p = &x;
    return p;
}

你能指出上面代码的问题吗?

在函数abc()中,我们返回一个指向局部变量的指针。回想一下,局部变量只存在于函数内部,一旦函数结束,变量x就不复存在,因此指向它的指针只在函数abc()内部有效。

即使abc()返回的地址分配给了main()内部的ptr,但是ptr指向的变量不再可用。取消引用ptr你会得到一些垃圾值。

注:有时你甚至可能得到正确的答案即100,但你绝不能依赖这种行为。



将一维数组传递给 C 语言中的函数

原文:https://overiq.com/c-programming-101/passing-1-d-array-to-a-function-in-c/

最后更新于 2020 年 7 月 27 日


在 C 语言的一维数组和函数一章中,我们讨论了当一个数组传递给一个函数时,该函数所做的改变会影响原始数组。在研究了指针之后,我们能够理解为什么会发生这种情况。但是在我们研究这个之前,我想明确几点。

在上述章节中,我们还了解到,当一维数组传递给函数时,可以选择在形式参数中指定数组的大小。因此,如果我们传递一个 5 个整数的数组,那么函数的形式参数可以用以下两种方式编写。

int my_arr[5] = [11,44,66,90,101];

第一种方式:

void function(int a[]) // here the size of the array is omitted
{
    // statements;
}

第二种方式:

void function(int a[5]) // here the size of the array is specified
{
    // statements;
}

在指针和一维数组一章中,我们还了解到数组的名称是指向数组第 0 个元素的常量指针。在我们的例子中my_arr是指向数组第 0 个元素的指针,换句话说,my_arr指向元素 11 的地址。所以my_arr的基本类型是指向int(int *)的指针。因此,函数的形式参数也可以声明为指向int(int *)的指针:

第三种方式:

void function(int *a) 
{
    // statements;
}

本质上,在所有这三种情况下,a的基本类型是指向int(int *)的指针,我们只是用三种不同的方式来表示它们。

好了,让我们回到最初的讨论:为什么对函数内部的数组所做的更改会影响原始数组?以下程序回答了这个问题。

#include<stdio.h>
void new_array(int a[]);

int main()
{
    int my_arr[] = {1,4,9,16,23}, i;
    printf("Original array: \n\n");

    for(i = 0; i < 5; i++)
    {
        printf("%d ", my_arr[i]);
    }

    my_func(my_arr);

    printf("\n\nModified array : \n\n");

    for(i = 0; i < 5; i++)
    {
        printf("%d ", my_arr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

void my_func(int a[5])
{
    int i;

    // increment original elements by 5

    for(i = 0; i < 5; i++)
    {
        a[i] = a[i] + 5;
    }
}

预期输出:

Original array:

1 4 9 16 23

Modified array:

6 9 14 21 28

工作原理:

我们知道my_arr是指向数组第一个元素的指针。所以我们可以不使用&运算符将my_arr传递给功能my_func()。在第 15 行中,my_func()用一个实际的参数my_arr来调用,然后分配给a。再次注意,我们将my_arr的地址传递给a,这意味着我们使用的是引用调用,而不是值调用。所以现在my_arra都指向同一个数组。在函数内部,我们使用 for 循环将数组的每个元素增加5。因为我们是在原始数组上操作,所以这里所做的所有更改都会影响原始数组。



将二维数组传递给 C 语言中的函数

原文:https://overiq.com/c-programming-101/passing-2-d-array-to-a-function-in-c/

最后更新于 2020 年 7 月 27 日


就像一维数组一样,当二维数组传递给函数时,函数所做的更改会影响原始数组。但是在我们研究这个之前,我想明确几点。

我们在第章中了解到,当二维数组传递给函数时,可以选择指定最左边维度的大小。因此,如果我们有一个 2 行 3 维的数组,那么它可以通过以下两种方式传递给函数:

int two_d[2][3] = {
                      {99,44,11},
                      {4,66,9}
                  };

第一种方式:

void function(int a[][3])
{
    // statements;
}

第二种方式:

void function(int a[2][3])
{
    // statements;
}

回想一下,二维数组是按行主顺序存储的,即第一行 0 被存储,然后紧挨着它的第一行 1 被存储,以此类推。因此,在 C 语言中,二维数组实际上是一维数组,其中每个元素本身都是一维数组。因为数组的名称指向数组的第 0 个元素。在二维数组的情况下,第 0 个元素是数组。因此,从这个讨论中,我们可以得出two_d是指向 3 个整数的数组的指针。

因此,我们也可以声明一个函数,其中形式参数是指向数组的指针类型。

第三种方式:

void function(int (*a)[3])
{
    // statements; 
}

本质上,在讨论的所有三种情况下,变量a的类型是指向 3 个整数的数组的指针,它们的区别仅在于它们的表示方式。

好了,让我们回到最初的讨论——为什么函数所做的更改会影响原始数组?以下程序回答了这个问题。

#include<stdio.h>
void change_twod(int (*a)[3]);

int main()
{
    int i,j, two_d[2][3] = {
                               {99,44,11},
                               {4,66,9}
                           };

    printf("Original array: \n\n");

    for(i = 0; i < 2; i++)
    {
        for(j = 0; j < 3; j++)
        {
            printf("%3d ", two_d[i][j]);
        }

        printf("\n");
    }

    change_twod(two_d);

    printf("\n\nModified array : \n\n");

    for(i = 0; i < 2; i++)
    {
        for(j = 0; j < 3; j++)
        {
            printf("%3d ", two_d[i][j]);
        }
        printf("\n");
    }

    // signal to operating system everything works fine
    return 0;
}

void change_twod(int (*arr)[3])
{
    int i, j;

    printf("\n\nIncrementing every element by 5\n");
    // increment original elements by 6

    for(i = 0; i < 2; i++)
    {
        for(j = 0; j < 3; j++)
        {
            arr[i][j] = arr[i][j] + 5;
        }
    }

}

预期输出:

Original array:

99 44 11
4 66 9
Incrementing every element by 5
Modified array :

104 49 16
9 71 14

工作原理:

如本节前面所讨论的,two_darr是指向一个3整数数组的指针类型。在第 25 行中,change_twod()two_d的实际参数调用,然后分配给arr。现在two_darr都指向同一个二维数组,因此,函数内部所做的更改将在函数main()中可见。



C 语言中的指针数组

原文:https://overiq.com/c-programming-101/array-of-pointers-in-c/

最后更新于 2020 年 7 月 27 日


就像我们可以声明一个由intfloatchar等组成的数组一样,我们也可以声明一个指针数组,下面是做同样事情的语法。

语法: datatype *array_name[size];

让我们举个例子:

int *arrop[5];

这里arrop5整数指针的数组。这意味着这个数组可以保存5整数变量的地址。换句话说,您可以将指向int类型指针的5指针变量分配给该数组的元素。

下面的程序演示了如何使用指针数组。

#include<stdio.h>
#define SIZE 10

int main()
{
    int *arrop[3];
    int a = 10, b = 20, c = 50, i;

    arrop[0] = &a;
    arrop[1] = &b;
    arrop[2] = &c;

    for(i = 0; i < 3; i++)
    {
        printf("Address = %d\t Value = %d\n", arrop[i], *arrop[i]);
    }

    return 0;
}

预期输出:

Address = 387130656      Value = 10
Address = 387130660      Value = 20
Address = 387130664      Value = 50

工作原理:

注意我们如何分配abc的地址。在第 9 行,我们将变量a的地址分配给数组的第 0 个元素。类似地,bc的地址分别分配给第一和第二元素。此时,arrop看起来是这样的:

arrop[i]给出数组第 I 个元素的地址。所以arrop[0]返回变量a的地址,arrop[1]返回b的地址等等。要获取地址值,请使用间接运算符(*)。

*arrop[i]

因此,*arrop[0]给出地址arrop[0]处的值,类似地*arrop[1]给出地址arrop[1]处的值,以此类推。



C 语言中的空指针

原文:https://overiq.com/c-programming-101/void-pointers-in-c/

最后更新于 2020 年 7 月 27 日


我们在 C 语言的指针基础一章中了解到,如果一个指针是指向int(int *)的指针类型,那么它只能保存int类型变量的地址。如果我们将一个float变量的地址分配给一个指向int的指针类型的指针,这将是不正确的。但是void指针是这个规则的例外。一个void指针可以指向任何数据类型的变量。以下是void指针的语法。

语法: void *vp;

让我们举个例子:

void *vp;

int a = 100, *ip;
float f = 12.2, *fp;
char ch = 'a';</pre>

这里vp是一个void指针,所以你可以给它分配任何类型变量的地址。

vp = &a; // ok
vp = ip; // ok
vp = fp; // ok

ip = &f; // wrong since type of ip is pointer to int
fp = ip; // wrong since type of fp is pointer to float</pre>

void指针可以指向任何数据类型的变量,void指针可以被分配给任何类型的指针。

取消引用空指针

我们不能仅仅使用间接(*)操作符来取消引用一个空指针。例如:

void *vp;
int a = 100;

vp = &a;
printf("%d", *vp); // wrong

它根本不是那样工作的!。在取消引用空指针之前,必须将其类型转换为适当的指针类型。让我告诉你我的意思。

例如:在上面的片段中void指针vp指向整数变量 a 的地址,所以在这种情况下vp充当指向int(int *)的指针。因此在这种情况下正确的类型是(int*)

(int *)vptr

现在vptr的类型暂时从void指针变为指向int(int*)的指针,我们已经知道如何去引用指向int的指针,只需在它前面加上间接操作符(*)

*(int *)vptr

注意:类型转换暂时改变vp的类型,直到表达式求值,程序中的其他地方vp仍然是一个空指针。

下面的程序演示了如何取消引用void指针。

#include<stdio.h>
#define SIZE 10

int main()
{
    int i = 10;
    float f = 2.34;
    char ch = 'k';

    void *vptr;

    vptr = &i;
    printf("Value of i = %d\n", *(int *)vptr);

    vptr = &f;
    printf("Value of f = %.2f\n", *(float *)vptr);

    vptr = &ch;
    printf("Value of ch = %c\n", *(char *)vptr);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Value of i = 10
Value of f = 2.34
Value of ch = k

空指针中的指针算法

我想提的另一个重要的点是关于带 void 指针的指针算法。在 void 指针中应用指针算法之前,请确保首先提供正确的类型转换,否则可能会得到未接受的结果。

考虑以下示例:

int one_d[5] = {12, 19, 25, 34, 46}, i;
void *vp = one_d;

printf("%d", one_d + 1); // wrong

这里我们已经将数组的名称one_d分配给了空指针vp。由于one_d的基本类型是指向int(int*)的指针,因此空指针vp的作用类似于指向int(int*)的指针。所以合适的类型是(int*)

int one_d[5] = {12, 19, 25, 34, 46}, i;
void *vp = one_d;

printf("%d", (int *)one_d + 1); // correct

下面的程序演示了 void 指针中的指针算法。

#include<stdio.h>
#define SIZE 10

int main()
{
    int one_d[5] = {12, 19, 25, 34, 46}, i;

    void *vp = one_d;

    for(i = 0; i < 5; i++)
    {
        printf("one_d[%d] = %d\n", i, *( (int *)vp + i ) );
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

one_d[0] = 12
one_d[1] = 19
one_d[2] = 25
one_d[3] = 34
one_d[4] = 46

void 指针在动态内存分配中被广泛使用,我们将在下面讨论。



C 语言的malloc()函数

原文:https://overiq.com/c-programming-101/the-malloc-function-in-c/

最后更新于 2020 年 7 月 27 日


到目前为止,在我们的程序中,我们一直使用静态内存分配。在静态内存分配中,程序的大小是固定的,我们不能在程序运行时增加或减少大小。那么,为什么我们要在程序运行时增加或减少程序的大小呢?

考虑以下情况。

假设我们正在创建一个程序来计算一个班级学生的平均分数。这里有一个解决问题的方法。

#include<stdio.h>
#define STUDENT 100

int main()
{
    float marks[STUDENT], sum = 0;
    int i;

    for(i = 0; i < STUDENT; i++)
    {
        printf("Enter marks for %d student: ", i+1);
        scanf("%f", &marks[i]);
    }

    // calculate sum

    for(i = 0; i < STUDENT; i++)
    {
        sum += marks[i];
    }

    printf("\nAverage marks = %.2f\n", sum/STUDENT );

    // signal to operating system everything works fine
   return 0;
}

关于程序要注意的重要一点是学生的大小是固定的也就是100

此时,可能会出现两种类型的问题。假设又有 20 名学生加入了这个班。由于我们的程序只能处理 100 名学生,解决这个问题的一种方法是改变学生的大小,重新编译并再次运行程序。如果过了一段时间,又有 50 名学生加入了这个班,那么我们必须修改程序并重新编译。当然,这不是理想的方式。

让我们面对硬币的另一面。如果有 40 个学生离开了这个班。在这种情况下,要存储的值的数量小于数组的大小,因此(40*4 = 160 字节)内存将被浪费。

正如你所看到的,我们的程序由于阵列大小固定面临两大缺点。

那么解决办法是什么呢?

解决方案是使用动态内存分配。它只是意味着我们可以在程序运行时随时分配/释放内存。

内存的分配/释放是借助头文件 stdlib.h 中定义的三个函数来完成的。

每当您调用这些函数时,它们都会从称为堆的内存区域中获取内存,并在不需要时释放内存,这样就可以重用它。

malloc()函数

它用于在运行时分配内存。该函数的语法是:

语法: void *malloc(size_t size);

该函数接受一个名为size的参数,该参数的类型为size_tsize_tstdlib.h中被定义为unsigned int,现在,你可以把它当成unsigned int的别名。

如果成功,malloc()返回一个指向内存第一个分配字节的空指针。在使用指针之前,必须将其转换为适当的类型。所以malloc()功能一般使用如下:

p = (datatype *)malloc(size);

其中p是类型为(datatype *)的指针,size是您想要分配的内存空间(以字节为单位)。

让我们举一个简单的例子:

假设我们想使用malloc()动态分配20字节(用于存储5整数,其中每个整数的大小为4字节)。我们可以这样做:

int *p; // p is pointer to int or (int*)
p = (int*)malloc(20); // allocate 20 bytes

该语句从堆中分配20个连续字节的内存,并将第一个字节的地址分配给变量p。注意从malloc()函数返回的 void 指针是如何被类型化然后分配给p的。分配的内存包含垃圾值,因此在为其分配适当的值之前,不要尝试取消引用它。

正如我们所知,C 语言中数据类型的大小因系统而异,这就是为什么malloc()函数与sizeof运算符结合使用的原因。

int *p; // p is pointer to int or (int*)
p = (int*)malloc(5*sizeof(int)); // allocate sufficient memory for 5 integers

我们仍在分配20字节的内存,但现在我们的程序是可移植的(即它可以在各种操作系统上运行,无需任何修改。)当然更易读。

现在我们有p指向分配内存的第一个字节,我们可以使用指针算法轻松访问后续字节。

当堆用完空闲空间时,malloc()函数返回NULL。所以在以任何方式使用指针变量之前,我们必须首先检查malloc()函数返回的值。

if(p == NULL)
{
    printf("Memory allocation failed");
    exit(1); // exit the program
}

让我们重写程序,使用malloc()函数计算一个班级学生的平均成绩。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    float *p, sum = 0;
    int i, n;

    printf("Enter the number of students: ");
    scanf("%d", &n);

    // allocate memory to store n variables of type float
    p = (float*)malloc(n*sizeof(float));

    // if dynamic allocation failed exit the program
    if(p==NULL)
    {
        printf("Memory allocation failed");
        exit(1); // exit the program
    }

    // ask the student to enter marks
    for(i = 0; i < n; i++)
    {
        printf("Enter marks for %d student: ", i+1);
        scanf("%f", p+i);
    }

    // calculate sum
    for(i = 0; i < n; i++)
    {
        sum += *(p+i);
    }

    printf("\nAverage marks = %.2f\n", sum/n);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter the number of students: 4
Enter marks for 1 student: 12.12
Enter marks for 2 student: 34.14
Enter marks for 3 student: 43.1
Enter marks for 4 student: 45.87

Average marks = 33.81
2nd run:

Enter the number of students: 2
Enter marks for 1 student: 13.41
Enter marks for 2 student: 56.31

Average marks = 34.86

工作原理:

在第 6 行,我们声明了一个指向float pa浮动变量s的指针,在这里它被初始化为0

在第 7 行,我们已经声明了两个类型为int的变量in

在第 9 行中,printf()功能将"Enter the number of students: "打印到控制台。

在第 10 行中,scanf()用于读取来自用户的输入,然后存储在变量n中。

第 12 行使用malloc()功能动态分配内存来存储float类型的n号。变量p是指向float(float*)的类型指针,这就是为什么malloc()函数的结果是使用(float*)类型铸造的。

第 15 行,if 条件检查malloc()返回的指针是否为空指针。如果pNULL,则内存分配失败,程序终止。

在第 21 行,我们有一个 for 循环,它反复要求用户输入 n 次标记。注意在scanf()语句中p + i不用&符号,因为p是指针。

在第 29 行,我们有另一个 for 循环,它将n学生的marks累加到变量sum中。

在第 34 行中,平均分数是用总分数除以学生总数显示的。



C 中的calloc()函数

原文:https://overiq.com/c-programming-101/the-calloc-function-in-c/

最后更新于 2020 年 7 月 27 日


c 提供了另一个动态分配内存的函数,它有时比 malloc()函数更好。它的语法是:

语法: void *calloc(size_t n, size_t size);

它接受两个参数第一个参数是元素的数量,第二个参数是元素的大小。假设我们想为5整数分配内存,在这种情况下,5是元素的数量,即n,每个元素的大小是4字节(可能因系统而异)。以下是如何使用calloc()为 5 个整数分配内存。

int *p;
p = (int*)calloc(5, 4);

这将从堆中分配20字节的连续内存空间,并将第一个分配字节的地址分配给指针变量p

以下是如何使用malloc()功能实现同样的事情:

int *p;
p = (int*)malloc(5 * 4);

为了使我们的程序可移植性和可读性更强sizeof()运算符与calloc()一起使用。

int *p;
p = (int*)calloc(5, sizeof(int));

那么除了争论的数量之外calloc()malloc()还有其他区别吗?

calloc()malloc()函数的区别在于malloc()分配的内存包含垃圾值,而calloc()分配的内存总是初始化为0

以下程序使用calloc()创建动态(运行时大小可以变化)一维数组。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *p, i, n;

    printf("Enter the size of the array: ");
    scanf("%d", &n);

    p = (int*)calloc(n, sizeof(int));

    if(p==NULL)
    {
        printf("Memory allocation failed");
        exit(1); // exit the program
    }

    for(i = 0; i < n; i++)
    {
        printf("Enter %d element: ", i);
        scanf("%d", p+i);
    }
    printf("\nprinting array of %d integers\n\n", n);

    // calculate sum

    for(i = 0; i < n; i++)
    {
        printf("%d ", *(p+i));
    }
    // signal to operating system program ran fine
    return 0;
}

预期输出:第一次运行:

Enter the size of the array: 5
Enter 0 element: 13
Enter 1 element: 24
Enter 2 element: 45
Enter 3 element: 67
Enter 4 element: 89

printing array of 5 integers

13 24 45 67 89

第二次运行:

Enter the size of the array: 2
Enter 0 element: 11
Enter 1 element: 34

printing array of 2 integers

11 34



C 语言中的realloc()函数

原文:https://overiq.com/c-programming-101/the-realloc-function-in-c/

最后更新于 2020 年 7 月 27 日


假设我们已经使用malloc()calloc()分配了一些内存,但是后来我们发现内存太大或太小。realloc()函数用于在不丢失旧数据的情况下调整已分配内存的大小。它的语法是:

语法: void *realloc(void *ptr, size_t newsize);

realloc()函数接受两个参数,第一个参数ptr是一个指针,指向先前使用malloc()calloc()函数分配的内存的第一个字节。newsize 参数以字节为单位指定块的新大小,该大小可以小于或大于原始大小。而size_t只是stdlib.h头文件里面定义的unsigned int的别名。

让我们举个例子:

int *p;
p = (int*)malloc(5*sizeof(int)); // allocate memory for 5 integers</pre>

假设以后有时候我们想增加分配内存的大小来存储6更多的整数。为此,我们必须分配额外的6 x sizeof(int)字节内存。下面是如何调用realloc()函数来分配6 x sizeof(int)字节的内存。

// allocate memory for 6 more integers integers i.e a total of 11 integers
p = (int*)realloc(p, 11*sizeof(int));

如果在已经使用的字节之后有足够的内存(在这种情况下是6 * sizeof(int)字节)可用,那么realloc()函数只分配已经使用的字节旁边的6 * sizeof(int)字节。在这种情况下,ptr指向的记忆不会改变。需要注意的是,这样做不会丢失旧数据,但新分配的字节不会初始化。

另一方面,如果在已经使用的字节之后没有足够的内存(在这种情况下为6 * sizeof(int)字节),则realloc()在堆的其他地方重新分配整个11 * sizeof(int)字节的内存,并将内容从旧的内存块复制到新的内存块。在这种情况下,ptr所指向的地址发生变化。

如果realloc()未能按照请求扩展内存,则返回NULL,旧内存中的数据不受影响。

以下程序演示了realloc()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *p, i, n;

    printf("Initial size of the array is 4\n\n");
    p = (int*)calloc(4, sizeof(int));

    if(p==NULL)
    {
        printf("Memory allocation failed");
        exit(1); // exit the program
    }

    for(i = 0; i < 4; i++)
    {
        printf("Enter element at index %d: ", i);
        scanf("%d", p+i);
    }

    printf("\nIncreasing the size of the array by 5 elements ...\n ");

    p = (int*)realloc(p, 9 * sizeof(int));

    if(p==NULL)
    {
        printf("Memory allocation failed");
        exit(1); // exit the program
    }

    printf("\nEnter 5 more integers\n\n");

    for(i = 4; i < 9; i++)
    {
        printf("Enter element at index %d: ", i);
        scanf("%d", p+i);
    }

    printf("\nFinal array: \n\n");

    for(i = 0; i < 9; i++)
    {
        printf("%d ", *(p+i) );
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Initial size of the array is 4

Enter element at index 0: 11
Enter element at index 1: 22
Enter element at index 2: 33
Enter element at index 3: 44

Increasing the size of the array by 5 elements ...

Enter 5 more integers

Enter element at index 4: 1
Enter element at index 5: 2
Enter element at index 6: 3
Enter element at index 7: 4
Enter element at index 8: 5

Final array:

11 22 33 44 1 2 3 4 5



字符串

C 语言中的字符串基础

原文:https://overiq.com/c-programming-101/string-basics-in-c/

最后更新于 2020 年 7 月 27 日


到目前为止,我们编写的程序只处理数字或字符,但一个真实世界的程序应该能够在需要时存储和操作文本。不幸的是,C 没有为字符串提供单独的数据类型,像 Java 和 C#这样的语言为字符串提供了单独的类型,但 C 却不是这样。在 C 中,字符串存储为以空字符结尾的字符数组。字符数组只有在最后一个元素是空字符('\0')时才是字符串。空字符是一个转义序列,就像\n(换行符)、\t(制表符)一样,ASCII 值为0。例如:

char name[10] = {'s', 't', 'r', 'i', 'n', 'g' ,'\0'};

因此,我们可以说字符串只是一维字符数组,最后一个元素是空字符(“\0”)。

字符串文字

字符串文字只是用双引号("")括起来的一系列字符。它也被称为一个弦常量。以下是一些字符串的例子:

"I am learning C"
"My Lucky Number is 1"
"Hello World!"
""

双引号("")不是字符串文字的一部分,它们只是用来描述(即标记边界)一个字符串。每当您在程序中创建字符串文字时,编译器都会自动在末尾添加空字符('\0')。

字符串是如何存储的?

如上所述,字符串实际上是以空字符('\0')结尾的字符数组。每当编译器看到长度为n的字符串时,它就为该字符串分配n + 1个连续字节的内存。该内存将包含字符串中的所有字符,以及字符串末尾的空字符('\0')。因此字符串文字"Hello World"将存储在内存中,如下所示:

如您所见,字符串"Hello World"存储为一个12字符数组(包括'\0')。

字符串也可以为空。

""(空字符串,只包含'\0')。它将作为一组1字符存储在内存中。

作为指针的字符串文字

字符串像数组一样存储。要理解的最重要的一点是,字符串文字是指向数组第一个字符的指针。换句话说"Hello World"是指向字符'H'的指针。由于"Hello World"指向字符'H'的地址,它的基本类型是指向char(char *)的指针。这意味着,如果我们有一个指向char(char*)类型的指针变量,我们可以将字符串指定为:

char *str = "Hello World";

在此赋值后str指向第一个元素的地址,使用指针算法我们可以访问字符串文本中的任何字符。

printf("%c" ,*(str+0) ); // prints H
printf("%c" ,*(str+4) ); // prints o

即使您可以访问字符串文字的单个元素。试图修改字符串文字是一种未定义的行为,可能会导致程序崩溃。

*str = 'Y'; // wrong

由于"Hello World"是指针,我们可以直接对其应用指针算法。例如:

"Hello World" + 0指向字符'H'的地址。
"Hello World" + 1指向字符'e'的地址。
"Hello World" + 2指向字符'l'的地址。

等等。

要获取地址"Hello World" + 1处的值,只需取消引用表达式。

*("Hello World" + 1)给出'e'T4*("Hello World" + 2)给出'l'

等等。

在第一章一维数组中,我们讨论了:

int arr[] = {16,31,39,59,11};

那么写arr[i]和写*(arr+i)是一样的。

因此*("Hello World" + 1)也可以写成"Hello World"[1]

重新审视 printf()和 scanf()

如果你看一下scanf()print()的原型,你会发现这两个函数都期望一个类型为(char*)的值作为它们的第一个参数。

int printf (const char*, ...);
int scanf (const char*, ...);

注意:暂时忽略关键字const。这将在接下来的章节中详细讨论。

现在你知道当你调用printf()函数时:

printf("Hello World");

您实际上是在传递"Hello World"的地址,即指向数组第一个字母'H'的指针。

字符串文字 v/s 字符文字

初学者经常混淆"a"'a',前者是字符串,其中"a"是指向包含字符'a'的内存位置的指针,后跟一个空字符('\0')。另一方面,字符文字,'a'代表字符'a'的 ASCII 值,即97。因此,在需要字符串文字的地方千万不要使用字符文字,反之亦然。

多行字符串文字

您不限于单行字符串。如果您的字符串足够大,可以容纳在一行中,那么您可以通过在行尾添加反斜杠来扩展它。例如:

printf("This is first line \
some characters in the second line \
even more characters in the third line \n");

使用转义序列

您可以在字符串中使用像\n(换行符)、\t(制表符)这样的转义序列。例如:

printf("Lorem ipsum \ndolor sit \namet, consectetur \nadipisicing elit \nsed do eiusmod");

预期输出:

dolor sit
amet, consectetur
adipisicing elit
sed do eiusmod

字符串文字后跟字符串文字

当两个字符串文字彼此相邻时,编译器会将它们串联起来,并在串联字符串的末尾追加空字符(“\0”)。

print("Hello"" World"); // prints Hello World

和写作一样:

print("Hello World");

字符串变量

由于字符串是一个字符数组,我们必须声明一个足够大的数组来存储所有字符,包括空字符('\0')。

char ch_arr[6];

这里ch_arr只能容纳6个字符,包括空字符('\0')。如果在声明时初始化数组的元素,那么可以省略大小。

char ch_arr[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};

C 语言还为初始化字符串提供了一种更简洁、更容易键入的语法。例如,上述语句也可以写成:

char ch_arr[] = "Hello World";

我们已经研究过字符串文字是数组第一个字符的指针,但是这个规则有一个例外:当字符串文字被用来初始化一个字符数组,就像上面的语句一样,那么它不代表任何地址。这意味着我们不能使用"Hello World"进行指针运算。数组ch_arr的所有字符将存储在存储器中,如下所示:

如果要存储的字符数(包括'\0')小于数组的大小怎么办。在这种情况下,编译器会添加额外的空字符('\0')。例如:

char name[10] = "john";

数组name将存储在存储器中,如下所示:

如果要存储的字符数(包括'\0')大于数组的大小,那么编译器会显示一条警告消息:数组初始值设定项中有多余的元素。

一般来说,创建字符串的最好方法是忽略数组的大小,在这种情况下,编译器会根据初始值设定项中的字符数来计算数组的大小。例如:

char str[] = "this is the best way";

需要注意的是,省略大小并不意味着数组字符串的长度可以在程序后期增加或减少(使用malloc()calloc()来调整数组的长度)。一旦程序被编译,字符串的大小被固定为21字节。由于对长字符串中的字符进行计数是一个容易出错的过程,因此当字符串太长时,此方法也是首选方法。

让我们通过创建两个简单的程序来结束这一章。

例 1:

下面的程序打印字符串的字符和每个字符的地址。

#include<stdio.h>

int main()
{
    int i;
    char str[6] = "hello";

    for(i = 0; str[i] != '\0'; i++)
    {
        printf("Character = %c\t Address = %u\n", str[i], &str[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Character = h Address = 2686752
Character = e Address = 2686753
Character = l Address = 2686754
Character = l Address = 2686755
Character = o Address = 2686756

注意:每次运行程序时,地址可能会有所不同。

在程序中需要注意的重要事情是 for 循环中的终止条件,它说:继续循环,直到遇到空字符。

例 2:

下面的程序使用指针打印字符串中的字符和字符的地址。

#include<stdio.h>

int main()
{
    int i;
    char str[6] = "hello";
    char *p;

    for(p = str; *p != '\0'; p++)
    {
        printf("Character = %c\t Address = %u\n", *(p), p);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Character = h Address = 2686752
Character = e Address = 2686753
Character = l Address = 2686754
Character = l Address = 2686755
Character = o Address = 2686756

注意:每次运行程序时,地址可能会有所不同。

工作原理:

这里,我们已经将数组名称字符串(指向char(char*)的指针)分配给指针变量p。这个说法之后pstr都指向了同一个同一个阵。现在我们可以使用指针算法来回移动来访问数组中的元素。对于循环的每次迭代,将p的值增加1。当p指向空字符('\0')的地址时,for 循环停止。



C 语言中的strlen()函数

原文:https://overiq.com/c-programming-101/the-strlen-function-in-c/

最后更新于 2020 年 7 月 27 日


本节讨论 C 语言中字符串库提供的一些常用函数,这些函数是在头文件string.h中声明的,所以在使用这些函数之前,您必须在程序中包含string.h

#include<string.h>

strlen()函数

语法: size_t strlen (const char* str);

注意:本章忽略关键字 const。稍后会讨论。

strlen()接受指向char(char*)的类型指针参数,因此您可以传递字符串或字符数组。它返回字符串中除空字符'\0'之外的字符数。回想一下size_t只是unsigned int的别名。

以下是一些例子:

strlen("a string constant"); // returns 17

char arr[] = "an array of characters";
strlen(arr); // returns 22

以下程序计算用户输入的字符串长度。

#include<stdio.h>
#include<string.h>

int main()
{
    int len;

    // destination array can store only 30 characters including '\0'
    char destination[30];

    printf("Enter your dream destination: ");
    gets(destination);

    // calculate length of characters in destination
    len = strlen(destination); 

    printf("Your dream destination %s has %d characters in it", destination, len);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter your dream destination: Bermuda Triangle
Your dream destination Bermuda Triangle has 16 characters in it

我们对弦的一般工作原理有很强的基础。所以让我们创建我们自己版本的strlen()函数。

unsigned int my_strlen(char *p)
{
    unsigned int count = 0;

    while(*p!='\0')
    {
        count++;
        p++;
    }

    return count;
}

工作原理:

就像原来的strlen()函数一样,这个函数接受一个指向char(char*)的类型指针的参数,并返回unsigned int。在函数内部,我们声明了一个变量count,并将其初始化为0。while 循环用于计算字符数。每次迭代后,p增加1。当p指向空字符('\0')的地址时,循环停止,变量count的值返回到调用函数。

让我们重写之前的程序,加入my_strlen()函数的定义。

#include<stdio.h>
unsigned int my_strlen(char *p); // function declaration

int main()
{
    int len;

    // destination array can store only 30 characters including '\0'
    char destination[30];

    printf("Enter your dream destination: ");
    gets(destination);

    // calculate length of characters in destination
    len = my_strlen(destination);

    printf("Your dream destination %s has %d characters in it", destination, len);

    // signal to operating system program ran fine
    return 0;
}

// definition of my_strlen() function

unsigned int my_strlen(char *p)
{
    unsigned int count = 0;

    while(*p!='\0')
    {
        count++;
        p++;
    }
    return count;
}

预期输出:

Enter your dream destination: Bermuda Triangle
Your dream destination Bermuda Triangle has 16 characters in it

my_strlen()函数给出的输出与strlen()函数相同,所以我们的函数工作正常。



C 语言中的strcmp()函数

原文:https://overiq.com/c-programming-101/the-strcmp-function-in-c/

最后更新于 2020 年 7 月 27 日


strcmp()函数的语法是:

语法: int strcmp (const char* str1, const char* str2);

strcmp()功能用于比较两根弦两根弦str1str2。如果两个字符串相同,则strcmp()返回0,否则返回非零值。

该函数使用字符的 ASCII 值逐个字符地比较字符串。当到达字符串的任一端或对应的字符不同时,比较停止。不匹配时返回的非零值是两个字符串的不匹配字符的 ASCII 值之差。

让我们通过一个例子来看看strcmp()函数是如何比较字符串的。

strcmp("jkl", "jkq");

这里我们有两条弦str1 = "jkl"str2 = "jkq"。比较开始于比较来自str1str2的第一个字符,即来自"jkl"'j'和来自"jkm"'j',因为它们相等,比较接下来的两个字符,即来自"jkl"'k'和来自"jkm"'k',因为它们也相等,再次比较接下来的两个字符,即来自"jkl"'l'和来自"jkm"'q',因为'q' ( 113)的 ASCII 值大于【的】

需要注意的是,并非所有系统都返回字符 ASCII 值的差值,在某些系统中,如果str1大于str2,则返回1。另一方面,如果str1小于str2,则返回-1。您很可能会在系统中遇到这种行为。

让我们举一些例子:

strcmp("a", "a"); // returns 0 as ASCII value of "a" and "a" are same i.e 97

strcmp("a", "b"); // returns -1 as ASCII value of "a" (97) is less than "b" (98)

strcmp("a", "c"); // returns -1 as ASCII value of "a" (97) is less than "c" (99)

strcmp("z", "d"); // returns 1 as ASCII value of "z" (122) is greater than "d" (100)

strcmp("abc", "abe"); // returns -1 as ASCII value of "c" (99) is less than "e" (101)

strcmp("apples", "apple"); // returns 1 as ASCII value of "s" (115) is greater than "\0" (101)

以下程序比较用户输入的两个字符串。

#include<stdio.h>
#include<string.h>

int main()
{
    char strg1[50], strg2[50];

    printf("Enter first string: ");
    gets(strg1);

    printf("Enter second string: ");
    gets(strg2);

    if(strcmp(strg1, strg2)==0)
    {
        printf("\nYou entered the same string two times");
    }

    else
    {
        printf("\nEntered strings are not same!");
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

第一次运行:

Enter first string: compare
Enter second string: compare

You entered the same string two times

第二次运行:

Enter first string: abc
Enter second string: xyz

Entered strings are not same!

带字符串的关系运算符

当关系运算符(><>=<===!=)用于字符串时,它们的行为方式略有不同。考虑以下示例:

char *s1 = "hello";
char *s2 = "yello";

你能猜出下面的表达是什么意思吗?

s1 == s2

这个表达式比较的是s1s2所指向的字符串的地址,而不是字符串文字的内容。

下面的示例演示了这种行为。

#include<stdio.h>
#include<string.h>

int main()
{
    char *s1 = "hello";
    char *s2 = "world";

    printf("Address of string pointed by s1 = %u\n", s1);
    printf("Address of string pointed by s2 = %u\n\n", s2);

    printf("Is s1 == s2 ? %u\n", s1 == s2);
    printf("Is s1 > s2 ? %u\n", s1 > s2);
    printf("Is s1 < s2 ? %u\n", s1 < s2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of string pointed by s1 = 4206592
Address of string pointed by s2 = 4206598

Is s1 == s2 ? 0
Is s1 > s2 ? 0
Is s1 < s2 ? 1

让我们回到最初的讨论,尝试创建我们自己版本的strcmp()函数。

int my_strcmp(char *strg1, char *strg2)
{
    while( ( *strg1 != '\0' && *strg2 != '\0' ) && *strg1 == *strg2 )
    {
        strg1++;
        strg2++;
    }

    if(*strg1 == *strg2)
    {
        return 0; // strings are identical
    }

    else
    {
        return *strg1 - *strg2;
    }
}

工作原理:

my_strcmp()函数接受两个指向 char 的类型指针参数,并返回一个整数值。while 循环中的条件可能看起来有点吓人,所以让我来解释一下。

( *strg1 != '\0' && *strg2 != '\0' ) && (*strg1 == *strg2)

条件只是说继续循环,直到没有到达字符串的末尾,并且对应的字符相同。

假设调用my_strcmp()有两个参数"abc" ( strg1)和"abz" ( strg2),其中strg1指向地址2000strg2指向地址3000

第一次迭代

在第一次迭代中strg1strg2都指向字符'a'的地址。因此

*strg1返回'a'
*strg2返回'a'

测试条件时:

( 'a' != '\0' && 'a' != '\0' ) && ('a' == 'a')

当条件为真时,执行循环体内部的语句。现在strg1点寻址2001strg2点寻址3001。这将结束第一次迭代。

第二次迭代

在第二次迭代中strg1strg2都指向字符'b'的地址。因此

*strg1返回'b'
*strg2返回'b'

再次测试条件时:

( 'b' != '\0' && 'b' != '\0' ) && ('b' == 'b')

当条件为真时,循环体中的语句将再次执行。现在strg1点寻址2002strg2点寻址3002。这将结束第二次迭代。

第三次迭代

在第三次迭代中strg1strg2分别指向字符'c''z'的地址。因此

*strg1返回'c'
*strg2返回'z'

再次测试条件时:

( 'c' != '\0' && 'z' != '\0' ) && ('c' == 'z')

while 条件变为 false,控件脱离 while 循环。检查 while 循环之后的 if 条件。

if( *strg1 == *strg2)
{
   return 0;  // strings are identical
}

因为

*strg1返回'c'
*strg2返回'z'

因此条件'c' == 'z'为假。控制来到 else 块,并执行下面的语句。

return *strg1 - *strg2;

表达式*strg1 - *strg2计算字符的 ASCII 值之差。

*strg1 - *strg2
=> 'c' - 'z'
=> 99 - 122
=> -23

最后-23返回到调用函数。

下面的程序演示了我们新的字符串比较函数my_strcmp()

#include<stdio.h>
int my_strcmp(char *strg1, char *strg2);

int main()
{

    printf("strcmp(\"a\", \"a\") = %d\n", my_strcmp("a", "a") );
    printf("strcmp(\"a\", \"b\") = %d\n", my_strcmp("a", "b") );
    printf("strcmp(\"a\", \"c\") = %d\n", my_strcmp("a", "c") );
    printf("strcmp(\"z\", \"d\") = %d\n", my_strcmp("z", "d") );
    printf("strcmp(\"abc\", \"abe\") = %d\n", my_strcmp("abc", "abe") );
    printf("strcmp(\"apples\", \"apple\") = %d\n", my_strcmp("apples", "apple") );

    // signal to operating system program ran fine
    return 0;
}

int my_strcmp(char *strg1, char *strg2)
{

    while( ( *strg1 != '\0' && *strg2 != '\0' ) && *strg1 == *strg2 )
    {
        strg1++;
        strg2++;
    }

    if(*strg1 == *strg2)
    {
        return 0; // strings are identical
    }

    else
    {
        return *strg1 - *strg2;
    }
}

预期输出:

strcmp("a", "a") = 0
strcmp("a", "b") = -1
strcmp("a", "c") = -2
strcmp("z", "d") = 22
strcmp("abc", "abe") = -2
strcmp("apples", "apple") = 115

可以看到,my_strcmp()返回不匹配字符的 ASCII 值。作为作业,修改此功能,如果strg1大于strg2则返回1,如果strg1小于strg2则返回-1



C 语言中的strcpy()函数

原文:https://overiq.com/c-programming-101/the-strcpy-function-in-c/

最后更新于 2020 年 7 月 27 日


strcpy()函数的语法是:

语法: char* strcpy (char* destination, const char* source);

strcpy()功能用于复制字符串。它将source指向的字符串复制到destination中。该函数接受指向char或字符数组的类型指针的两个参数,并返回指向第一个字符串的指针,即destination。注意source前面有const修饰符,因为strcpy()功能不允许改变source字符串。

以下程序演示了strcpy()功能的作用。

#include<stdio.h>
#include<string.h>

int main()
{
    char ch_arr1[20];
    char ch_arr2[20];

    printf("Enter first string (ch_arr_1): ");
    gets(ch_arr1);

    printf("Enter second string(ch_arr_1): ");
    gets(ch_arr2);

    printf("\nCopying first string into second... \n\n");
    strcpy(ch_arr2, ch_arr1); // copy the contents of ch_arr1 to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    printf("\nCopying \"Greece\" string into ch_arr1 ... \n\n");
    strcpy(ch_arr1, "Greece"); // copy Greece to ch_arr1

    printf("\nCopying \"Slovenia\" string into ch_arr2 ... \n\n");
    strcpy(ch_arr2, "Slovenia"); // copy Slovenia to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter first string (ch_arr_1): Mexico
Enter second string(ch_arr_1): South Africa

Copying first string into second...

First string (ch_arr_1) = Mexico
Second string (ch_arr_2) = Mexico

Copying "Greece" string into ch_arr1 ...
Copying "Slovenia" string into ch_arr2 ...

First string (ch_arr_1) = Greece
Second string (ch_arr_2) = Slovenia

需要注意的是strcpy()功能不检查destination是否有足够的大小来存储源中存在的所有字符。程序有责任确保destination数组有足够的空间容纳源字符串的所有字符。

关于strcpy()需要注意的另一个要点是,您永远不应该将字符串作为第一个参数传递。例如:

char ch_arr[] = "string array";

strcpy("destination string", c_arr); // wrong

这里您试图将ch_arr的内容复制到“目标字符串”中,它是一个字符串。由于修改字符串文字会导致未定义的行为,以这种方式调用strcpy()可能会导致程序崩溃。

让我们创建自己版本的strcpy()函数。

char *my_strcpy(char *destination, char *source)
{
    char *start = destination;

    while(*source != '\0')
    {
        *destination = *source;
        destination++;
        source++;
    }

    *destination = '\0'; // add '\0' at the end
    return start;
}

工作原理:

my_strcpy()函数接受指向char(char*)的类型指针的两个参数,并返回指向第一个字符串的指针。

在第 18 行中,我们已经将destination的基址分配给了start,这是必要的,否则我们将会丢失字符串开头的地址。

在第 20 行,我们有 while 循环,while 循环将字符从source逐个复制到destination。当源指向空字符('\0')的地址时,复制停止。

此时,start 指向的字符串包含除空字符('\0')之外的所有源字符。第 13 行的语句将一个空字符('\0')附加到字符串中。

在第 14 行中,return语句返回调用函数的字符指针。

让我们重写之前的程序,加入my_strcpy()函数的定义。

#include<stdio.h>
char *my_strcpy(char *destination, char *source);

int main()
{
    char ch_arr1[20];
    char ch_arr2[20];

    printf("Enter first string (ch_arr_1): ");
    gets(ch_arr1);

    printf("Enter second string(ch_arr_1): ");
    gets(ch_arr2);

    printf("\nCopying first string into second... \n\n");
    my_strcpy(ch_arr2, ch_arr1); // copy the contents of ch_arr1 to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    printf("\nCopying \"Greece\" string into ch_arr1 ... \n");
    my_strcpy(ch_arr1, "Greece"); // copy Greece to ch_arr1

    printf("\nCopying \"Slovenia\" string into ch_arr2 ... \n\n");
    my_strcpy(ch_arr2, "Slovenia"); // copy Slovenia to ch_arr2

    printf("First string (ch_arr_1) = %s\n", ch_arr1);
    printf("Second string (ch_arr_2) = %s\n", ch_arr2);

    // signal to operating system program ran fine
    return 0;
}

char *my_strcpy(char *destination, char *source)
{
    char *start = destination;

    while(*source != '\0')
    {
        *destination = *source;
        destination++;
        source++;
    }

    *destination = '\0';
    return start;
}

预期输出:

Enter first string (ch_arr_1): Mexico
Enter second string(ch_arr_1): South Africa

Copying first string into second...

First string (ch_arr_1) = Mexico
Second string (ch_arr_2) = Mexico

Copying "Greece" string into ch_arr1 ...
Copying "Slovenia" string into ch_arr2 ...

First string (ch_arr_1) = Greece
Second string (ch_arr_2) = Slovenia

strcpy()my_strcpy()的输出相同,说明我们的程序工作正常。



C 语言中的strcat()函数

原文:https://overiq.com/c-programming-101/the-strcat-function-in-c/

最后更新于 2020 年 7 月 27 日


strcat()函数的语法是:

语法: char* strcat (char* strg1, const char* strg2);

该函数用于连接两个字符串。该函数接受两个指向char(char*)的类型指针参数,因此您可以传递字符串或字符数组。删除第一个字符串中的空字符,然后在第一个字符串的末尾追加第二个字符串。它返回一个指向结果字符串(strg1)的指针。一般情况下strcat()的返回值会被丢弃。

以下是一些例子:

char strg1[40] = "Hello";

/*
returns a pointer (which is discarded) to the string literal
"Hello World" and now strg1 contains "Hello World"
*/
strcat(strg1, " World");

/* 
returns a pointer (which is discarded) to the string
to "Hello World :)" and now strg1 contains "Hello World :)"
*/
strcat(strg1, " :)");

您不应该将字符串作为第一个参数传递,因为如果您这样做了,那么strcat()函数将试图修改字符串,这是一种未定义的行为,可能会导致程序崩溃。

strcat("Yello", " World"); // wrong

strg1指向的数组的大小不足以容纳来自strg2的所有字符时,strcat()的行为是未定义的。程序员的责任是确保strg1指向的数组的大小足够长,可以容纳所有来自strg2的字符。

下面的程序演示了如何使用strcat()功能。

#include<stdio.h>
#include<string.h>

int main()
{
    char strg1[40];
    char strg2[40];

    printf("Enter first string: ");
    gets(strg1);

    printf("Enter second string: ");
    gets(strg2);

    printf("\nConcatenating first and second string .. \n\n");
    strcat(strg1, strg2);

    printf("First string: %s\n", strg1);
    printf("Second string: %s", strg2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter first string: top
Enter second string: pot

Concatenating first and second string ..

First string: toppot
Second string: pot

让我们创建自己版本的strcat()函数。

char *my_strcat(char *strg1, char *strg2)
{
    char *start = strg1;

    while(*strg1 != '\0')
    {
        strg1++;
    }

    while(*strg2 != '\0')
    {
        *strg1 = *strg2;
        strg1++;
        strg2++;
    }

    *strg1 = '\0';
    return start;
}

工作原理:

my_strcat()函数接受指向char(char*)的类型指针的两个参数,并返回指向第一个字符串即strg1的指针。

在第 3 行中,我们已经将指针strg1分配给了start,这一步是必要的,否则我们将失去对第一个字符串(strg1)开头的地址的跟踪。

第一个 while 循环的工作是将指针strg1移动到最后一个字符,即'\0'。以便第二个 while 循环可以在这个位置开始追加字符。

第二个 while 循环将第二个字符串中的字符逐个追加到第一个字符串中。因为在第一个 while 循环之后strg1指向第一个字符串的空字符,所以在第一次迭代中,语句:

*strg1 = *strg2;

将第二个字符串中的第一个字符追加到第一个字符串的末尾(即代替空字符'\0')。然后strg1++strg2++递增。这个过程一直重复,直到在第二个字符串中遇到空字符(strg2)。

此时,start 指向的字符串仍然缺少一个东西,空字符('\0')。该声明:

*strg1 = '\0';

在第一个字符串的末尾追加空字符。

最后,return语句将指向第一个字符串的指针返回给调用函数。



C 语言中的字符数组和字符指针

原文:https://overiq.com/c-programming-101/character-array-and-character-pointer-in-c/

最后更新于 2020 年 7 月 27 日


在本章中,我们将研究字符数组和字符指针之间的区别。考虑以下示例:

char arr[] = "Hello World"; // array version
char ptr* = "Hello World";  // pointer version

你能指出它们之间的相似之处或不同之处吗?

相似之处在于:

这两个变量的类型都是指向char(char*)的指针,因此您可以将它们中的任何一个传递给其形式参数接受字符数组或字符指针的函数。

以下是不同之处:

  1. arr12字符的数组。当编译器看到以下语句时:

    char arr[] = "Hello World";
    
    

    它分配12个连续字节的内存,并将第一个分配字节的地址与arr相关联。
    T3】

    另一方面,当编译器看到语句时。

    char ptr* = "Hello World";
    
    

    它为字符串文字"Hello World"分配12连续字节,为指针变量ptr分配4额外字节。并将字符串文字的地址分配给ptr。所以,在这种情况下,总共分配了16个字节。

  2. 我们已经了解到数组的名称是一个常量指针。所以如果arr指向地址2000,在程序结束前它会一直指向地址2000,我们无法更改它的地址。这意味着字符串赋值对于定义为数组的字符串无效。

    arr = "Yellow World"; // Wrong
    
    

    相反,ptr是一个类型为char的指针变量,所以可以取任何其他地址。作为结果字符串,赋值对指针有效。

    ptr = "Yellow World"; // ok
    
    

    上述赋值后,ptr指向存储在内存某处的"Yellow World"的地址。

    显然,问题出现了,那么我们如何给arr分配不同的字符串呢?

    我们可以通过使用gets()scanf()strcpy()或者通过逐个分配字符来为arr分配一个新字符串。

    gets(arr);
    scanf("%s", arr);
    strcpy(arr, "new string");
    arr[0] = 'R';
    arr[1] = 'e';
    arr[2] = 'd';
    arr[3] = ' ';
    arr[4] = 'D';
    arr[5] = 'r';
    arr[6] = 'a';
    arr[7] = 'g';
    arr[8] = 'o';
    arr[9] = 'n';
    
    
  3. 回想一下,修改字符串文字会导致未定义的行为,因此以下操作无效。

    char *ptr = "Hello";
    ptr[0] = 'Y'; or *ptr = 'Y';
    gets(name);
    scanf("%s", ptr);
    strcpy(ptr, "source");
    strcat(ptr, "second string");
    
    
  4. 使用未初始化的指针也可能导致未定义的行为。

    char *ptr;
    
    

    这里ptr是未初始化的一个包含垃圾的值。所以下面的操作是无效的。

    ptr[0] = 'H';
    gets(ptr);
    scanf("%s", ptr);
    strcpy(ptr, "source");
    strcat(ptr, "second string");
    
    

    只有当ptr指向一个有效的内存位置时,我们才能使用它。

    char str[10];
    char *p = str;
    
    

    现在上面提到的所有操作都有效。我们可以使用 ptr 的另一种方法是使用malloc()calloc()函数动态分配内存。

    char *ptr;
    ptr = (char*)malloc(10*sizeof(char)); // allocate memory to store 10 characters
    
    

    让我们通过创建动态的一维字符数组来结束这一章。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int n, i;
    char *ptr;

    printf("Enter number of characters to store: ");
    scanf("%d", &n);

    ptr = (char*)malloc(n*sizeof(char));

    for(i=0; i < n; i++)
    {
        printf("Enter ptr[%d]: ", i);
        /* notice the space preceding %c is
          necessary to read all whitespace in the input buffer
        */
        scanf(" %c", ptr+i); 
    }

    printf("\nPrinting elements of 1-D array: \n\n");

    for(i = 0; i < n; i++)
    {
        printf("%c ", ptr[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter number of characters to store: 6
Enter ptr[0]: a
Enter ptr[1]: b
Enter ptr[2]: c
Enter ptr[3]: d
Enter ptr[4]: y
Enter ptr[5]: z

Printing elements of 1-D array:

a b c d y z



C 语言中的字符串数组

原文:https://overiq.com/c-programming-101/array-of-strings-in-c/

最后更新于 2020 年 7 月 27 日


什么是字符串数组?

字符串是一维字符数组,因此字符串数组是二维字符数组。就像我们可以创建intfloat等的二维数组一样;我们还可以创建一个二维字符数组或字符串数组。下面是我们如何声明一个二维字符数组。

char ch_arr[3][10] = {
                         {'s', 'p', 'i', 'k', 'e', '\0'},
                         {'t', 'o', 'm','\0'},
                         {'j', 'e', 'r', 'r', 'y','\0'}
                     };

用空字符结束每个一维数组很重要,否则,它将只是一个字符数组。我们不能把它们当作琴弦。

以这种方式声明字符串数组相当繁琐,这就是为什么 C 语言提供了一种替代语法来实现同样的事情。上述初始化相当于:

char ch_arr[3][10] = {
                         "spike",
                         "tom",
                         "jerry"
                     };

数组的第一个下标,即3表示数组中的字符串数量,第二个下标表示字符串的最大长度。回想一下,在 C 语言中,每个字符占用1字节的数据,所以当编译器看到上面的语句时,它会分配30字节(3*10)的内存。

我们已经知道数组的名称是指向数组第 0 个元素的指针。你能猜出ch_arr的类型吗?

ch_arr是指向一组10字符或int(*)[10]的指针。

因此,如果ch_arr指向地址1000,那么ch_arr + 1将指向地址1010

由此,我们可以得出结论:

ch_arr + 0指向第 0 个字符串或第 0 个一维数组。
ch_arr + 1指向第一个字符串或第一个一维数组。
ch_arr + 2指向第二个字符串或第二个一维数组。

一般来说,ch_arr + i指向第 ith 个字符串或者第 ith 个一维数组。

我们知道,当我们取消引用指向数组的指针时,我们得到了数组的基址。因此,在解引用ch_arr + i时,我们得到第 0 个一维数组的基址。

由此我们可以得出结论:

*(ch_arr + 0) + 0指向第 0 个一维数组的第 0 个字符(即s )
*(ch_arr + 0) + 1指向第 0 个一维数组的第 1 个字符(即p )
*(ch_arr + 1) + 2指向第 1 个一维数组的第 2 个字符(即m

总的来说,我们可以说:*(ch_arr + i) + j指向一维数组的 jth 字符。

注意*(ch_arr + i) + j的基类型是指向char(char*)的指针,ch_arr + i的基类型是 10 个字符的数组或int(*)[10]

要获取一维数组第十个位置的元素,只需取消引用整个表达式*(ch_arr + i) + j

*(*(ch_arr + i) + j)

我们在“指针和二维数组”一章中了解到,在二维数组中,指针符号相当于下标符号。所以上面的表达式可以写成如下:

ch_arr[i][j]

下面的程序演示了如何打印字符串数组。

#include<stdio.h>

int main()
{
    int i;

    char ch_arr[3][10] = {
                             "spike",
                             "tom",
                             "jerry"
                         };

    printf("1st way \n\n");

    for(i = 0; i < 3; i++)
    {
        printf("string = %s \t address = %u\n", ch_arr + i, ch_arr + i);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

string = spike address = 2686736
string = tom address = 2686746
string = jerry address = 2686756

对字符串数组的一些无效操作

char ch_arr[3][10] = {
                         {'s', 'p', 'i', 'k', 'e', '\0'},
                         {'t', 'o', 'm','\0'},
                         {'j', 'e', 'r', 'r', 'y','\0'}
                     };

它分配30字节的内存。即使我们在声明时没有初始化数组的元素,编译器也会做同样的事情。

我们已经知道数组的名称是一个常量指针,所以下面的操作是无效的。

ch_arr[0] = "tyke";   // invalid
ch_arr[1] = "dragon"; // invalid

在这里,我们试图将一个字符串(一个指针)赋给一个常量指针,这显然是不可能的。

要为ch_arr分配新字符串,请使用以下方法。

strcpy(ch_arr[0], "type"); // valid
scanf(ch_arr[0], "type");  // valid

让我们通过创建另一个简单的程序来结束这一章。

这个程序要求用户输入用户名。如果输入的用户名是主列表中的一个名称,则允许用户计算一个数字的阶乘。否则,将显示一条错误消息。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    int i, found = 0, n;

    char master_list[5][20] = {
                                  "admin",
                                  "tom",
                                  "bob",
                                  "tim",
                                  "jim"
                              }, name[10];

    printf("Enter username: ");
    gets(name);

    for(i = 0; i < 5; i++)
    {
        if(strcmp(name, master_list[i]) == 0 )
        {
            found = 1;
            break;
        }
    }

    if(found==1)
    {
        printf("\nWelcome %s !\n", name);
        printf("\nEnter a number to calculate the factorial: ");
        scanf("%d", &n);
        printf("Factorial of %d is %d", n, factorial(n));
    }

    else
    {
        printf("Error: You are not allowed to run this program.", name);
    }

    // signal to operating system program ran fine
    return 0;
}

int factorial(int n)
{
    if(n == 0)
    {
        return 1;
    }

    else
    {
        return n * factorial(n-1);
    }
}

预期输出:第一次运行:

Enter username: admin

Welcome admin !

Enter a number to calculate the factorial: 4
Factorial of 4 is 24

第二次运行:

Enter username: jack
Error: You are not allowed to run this program.

工作原理:

程序要求用户输入姓名。输入名称后,它会使用strcmp()功能将输入的名称与master_list数组中的名称进行比较。如果找到匹配,则strcmp()返回0,如果条件strcmp(name, master_list[i]) == 0变为真。找到的变量被赋值为1,这意味着允许用户访问程序。程序要求用户输入一个数字,并显示一个数字的阶乘。

如果输入的名称不是master_list数组中的一个名称,则程序通过显示错误信息退出。



C 语言中指向字符串的指针数组

原文:https://overiq.com/c-programming-101/array-of-pointers-to-strings-in-c/

最后更新于 2020 年 7 月 27 日


在上一章中,我们已经学习了如何使用字符串数组或二维字符数组。它可能会在您需要存储多个字符串时出现,那么字符串数组就是您要走的路,不幸的是,事实并非如此。考虑下面的例子。

char sports[5][15] = {
                         "golf",
                         "hockey",
                         "football",
                         "cricket",
                         "shooting"
                     };

sports数组存储在存储器中,如下所示:

正如您所看到的,并不是所有的字符串都足够长来填充数组的所有行,这就是编译器用空字符('\0')填充这些空格(使用浅灰色突出显示)的原因。运动阵列的总大小为75字节,但仅使用了34字节,浪费了41字节。41字节可能不会出现很多,但是在一个大型程序中,相当多的字节会被浪费掉。我们需要的是一个交错数组:一个二维数组,它的行可以有不同的长度。C 语言不提供交错数组,但是我们可以使用指向字符串的指针数组来模拟它们。

指向字符串的指针数组

指向字符串的指针数组是一个字符指针数组,其中每个指针指向字符串的第一个字符或字符串的基址。让我们看看如何声明和初始化指向字符串的指针数组。

char *sports[5] = {
                      "golf",
                      "hockey",
                      "football",
                      "cricket",
                      "shooting"
                  };

这里sports是一个指向字符串的指针数组。如果数组的初始化是在声明时完成的,那么我们可以省略数组的大小。所以上面的说法也可以写成:

char *sports[] = {
                     "golf",
                     "hockey",
                     "football",
                     "cricket",
                     "shooting"
                 };

需要注意的是,sports 数组的每个元素都是字符串,由于字符串指向第一个字符的基址,因此 sports 数组每个元素的基类型都是指向char(char*)的指针。

第 0 个元素即arr[0]指向字符串"golf"的基址。同样,第一个元素arr[1]指向字符串"hockey"的基址,以此类推。

以下是指向字符串的指针数组是如何存储在内存中的。

34 + 20 = 54

在这种情况下,所有字符串文字占用34字节,20字节由指针数组(即 sports)占用。因此,仅仅通过创建一个指向字符串的指针数组,而不是字符的二维数组,我们就节省了21字节(75-54=21)的内存。

需要强调的是,在指向字符串的指针数组中,不能保证所有字符串都存储在连续的内存位置。虽然特定字符串文字的字符总是存储在连续的内存位置。

下面的程序演示了如何访问字符串指针数组中的字符串文字,并在此过程中打印每个字符串文字的地址。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    int i = 1, *ip = &i;

    char *sports[] = {
                         "golf",
                         "hockey",
                         "football",
                         "cricket",
                         "shooting"
                     };

    for(i = 0; i < 5; i++)
    {
        printf("String = %10s", sports[i] );
        printf("\tAddress of string literal = %u\n", sports[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

String = golf Address of string literal = 4206592
String = hockey Address of string literal = 4206597
String = football Address of string literal = 4206604
String = cricket Address of string literal = 4206613
String = shooting Address of string literal = 4206621

在上一章中,我们了解到不能使用赋值运算符(=)将新字符串赋给二维字符数组。

char games[3][10] = {
                        "roadrash",
                        "nfs",
                        "angrybirds"
                    };

games[0] = "hitman";   // wrong

但是同样的事情也可以用指向字符串的指针数组来完成。

char *games[3] = {
                     "roadrash",
                     "nfs",
                     "angrybirds"
                 };

games[0] = "hitman";   // ok

由于games数组的每个元素都是指向char(char*)的指针,因此它可以指向分配给它的任何字符串。

对字符串指针数组的一些无效操作

让我们讨论一些不能在指向字符串的指针数组中直接执行的操作。考虑以下示例:

char *top_games[5];

当编译器看到上面的语句时,它会保留20字节的内存(4*5)来存储类型为char5指针,但不会为字符串分配任何内存。此时,top_games数组的所有元素都包含垃圾值,并且可能指向内存中的任何位置。这意味着以下操作无效。

scanf("%s", top_games[0]);             // invalid
strcpy(top_games[0], "mario");         // invalid
gets(top_games[0]);                    // invalid
strcat(top_games[0], "needforspeed");  // invalid



C 语言中的sprintf()函数

原文:https://overiq.com/c-programming-101/the-sprintf-function-in-c/

最后更新于 2020 年 7 月 27 日


sprintf()的工作方式与printf()类似,但它不是向控制台发送输出,而是返回格式化的字符串。

语法: int sprintf(char *str, const char *control_string, [ arg_1, arg_2, ... ]);

sprintf()函数的第一个参数是指向目标字符串的指针。其余参数与printf()函数相同。

该函数将数据写入str指向的字符串,并返回写入str的字符数,不包括空字符。返回值通常会被丢弃。如果操作过程中出现错误,它将返回-1

下面的程序演示了如何使用sprintf()功能。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{

    int sal;
    char name[30], designation[30], info[60];

    printf("Enter your name: ");
    gets(name);

    printf("Enter your designation: ");
    gets(designation);

    printf("Enter your salary: ");
    scanf("%d", &sal);

    sprintf(info, "Welcome %s !\nName: %s \nDesignation: %s\nSalary: %d",
        name, name, designation, sal);

    printf("\n%s", info);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter your name: Bob
Enter your designation: Developer
Enter your salary: 230000

Welcome Bob!
Name: Bob
Designation: Developer
Salary: 230000

sprintf()函数的另一个重要用途是将整数值和浮点值转换为字符串。

#include<stdio.h>
#include<string.h>
int factorial(int );

int main()
{
    char s1[20];
    char s2[20];

    int x = 100;
    float y = 300;

    sprintf(s1, "%d", x);
    sprintf(s2, "%f", y);

    printf("s1 = %s\n", s1);
    printf("s2 = %s\n", s2);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

s1 = 100
s2 = 300.000000



C 语言中的sscanf()函数

原文:https://overiq.com/c-programming-101/the-sscanf-function-in-c/

最后更新于 2020 年 7 月 27 日


sscanf()功能允许我们从字符串而不是标准输入或键盘中读取格式化数据。它的语法如下:

语法: int sscanf(const char *str, const char * control_string [ arg_1, arg_2, ... ]);

第一个参数是指向我们要从中读取数据的字符串的指针。sscanf()其余论点与scanf()相同。如果遇到错误,它将返回从字符串中读取的项目数和-1

以下程序演示了sscanf()是如何工作的:

#include<stdio.h>
#include<string.h>

int main()
{
    char *str = "Tom Manager 28";
    char name[10], designation[10];
    int age, ret;

    ret = sscanf(str, "%s %s %d", name, designation, &age);

    printf("Name: %s\n", name);
    printf("Designation: %s\n", designation);
    printf("Age: %d\n", age);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Name: Tom
Designation: Manager
Age: 28

工作原理:

在第 6 行,我们已经声明并初始化了一个类型为char的变量str

在第 7 行,我们已经声明了两个字符数组namedesignation,大小为10字符。

在第 8 行,我们已经声明了类型为int的变量age

第 10 行调用sscanf()函数,从str指向的字符串中读取数据。请注意,字符串"Tom Manager 28"包含由空格分隔的三条信息名称、名称和年龄。要读取所有三个项目,我们需要向scanf()函数提供三个适当类型的变量。然后变量ret被分配由sscanf()功能读取的项目数。在这种情况下,我们从字符串str中读取三个项目,因此3将被分配给ret

我们没有义务阅读字符串中的所有条目,如果我们愿意,我们也可以从中阅读一到两个条目。

ret = sscanf(str, "%s %s", name, designation);

这里我们只是阅读和名称和名称,这就是为什么只有两个变量提供给sscanf()

最后用printf()功能显示namedesignationageret



结构和联合

C 语言中的结构基础

原文:https://overiq.com/c-programming-101/structure-basics-in-c/

最后更新于 2020 年 7 月 27 日


C 语言中的结构用于创建新的数据类型。那么为什么我们需要创建新的数据类型呢?考虑以下示例:

假设我们正在创建一个存储学生记录的程序。一个学生有许多属性,如姓名、学号、分数、出勤率等。有些项目是字符串,有些是数字。这是解决这个问题的一种方法。

#include<stdio.h>
#include<string.h>

int main()
{
    char name[20];
    int roll_no, i;
    float marks[5];

    printf("Enter name: ");
    scanf("%s", name);

    printf("Enter roll no: ");
    scanf("%d", &roll_no);

    printf("\n");

    for(i = 0; i < 5; i++)
    {
        printf("Enter marks for %d: subject: ", i+1);
        scanf("%f", &marks[i]);
    }

    printf("\nYou entered: \n\n");

    printf("Name: %s\n", name);
    printf("roll no: %d\n", roll_no);

    printf("\n");

    for(i = 0; i < 5; i++)
    {
        printf("Marks in %d subject %f: l\n", i+1, marks[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

毫无疑问,使用这种方法,我们将能够存储学生的姓名、学号和分数。但问题是这种方法的可扩展性不是很好。如果我们想存储更多的学生,那么程序就变得很难处理。这种方法最大的缺点是,它模糊了我们正在与一个单一的实体——学生打交道的事实。

利用结构,我们可以很容易地解决这类问题。该结构允许我们将不同类型的相关数据组合在一个名称下。每个数据元素(或属性)都被称为成员。

定义结构

语法:

struct tagname
{
    data_type member1;
    data_type member2;
    ...
    ...
    data_type memberN;
};

这里struct是一个关键字,告诉 C 编译器正在定义一个结构。member1member2……memberN是结构的成员或者只是结构成员,必须在花括号({})内声明。每个成员声明都以分号(;)结束。标记名是结构的名称,用于声明这种结构类型的变量。需要注意的一点是,结构定义必须始终以右大括号后面的分号(;)结束。

如上所述,除了内置数据类型之外,该结构还提供了一种数据类型。从结构类型中声明的所有变量都将采用该模板的形式。

定义一个新的结构不会保留任何空间和内存,只有当我们声明这种结构类型的变量时,才会保留内存。还有一点很重要,结构定义里面的成员是依附于结构变量的,没有结构变量他们就没有任何存在。结构内部的成员名称必须不同,但两个不同结构的成员名称可以相同。

让我们定义一个叫做学生的简单结构。

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

这里我们定义了一个名为student的结构,它有三个结构成员nameroll_nomarks。您可以在全局和本地定义结构。如果结构是全局的,那么它必须放在所有函数之上,这样任何函数都可以使用它。另一方面,如果在函数内部定义了一个结构,那么只有该函数可以使用该结构。

创建结构变量

除非我们声明结构变量,否则我们不能以任何方式使用结构定义。

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

有两种方法可以声明结构变量:

  1. 有了结构定义
  2. 使用标记名

让我们从第一个开始。

有了结构定义

struct student
{
char name[20];
int roll_no;
float marks;
} student1, student2;

这里student1student2struct student类型的变量。如果在定义结构模板时声明了结构变量,则tagname是可选的。这意味着我们也可以将上述结构声明为:

struct
{
    char name[20];
    int roll_no;
    float marks;
} student1, student2;

以这种方式定义结构有几个限制:

  1. 由于这个结构没有与之相关的名称,我们不能在程序的任何其他地方创建这种结构类型的结构变量。如果您有必要声明这种结构类型的变量,那么您必须再次编写相同的模板。
  2. 我们不能将这些结构变量发送给其他函数。

由于上述限制,这种方法没有得到广泛应用。

使用标记名

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

要使用标记名声明结构变量,请使用以下语法:

语法: struct tagname variable_name;

其中variable_name必须是有效的标识符。

下面是我们如何创建struct student类型的结构变量。

struct student student1;

我们还可以通过用逗号(,)符号分隔来声明多个结构变量。

struct student student1, student2, student3;

当一个变量被声明时,编译器只在内存中保留空间。理解一个结构的成员按照它们被定义的顺序存储在内存中是很重要的。在这种情况下,学生类型的每个结构变量有 3 个成员,即:nameroll_no、标记。因此,编译器将分配足够的内存来容纳该结构的所有成员。所以这里每个结构变量占用28字节(20+4+4)的内存。

注:在此图中,我们假设结构的成员之间没有间隙。正如您将在本章后面看到的,结构的成员通常会在它们之间留下一些间隙。

初始化结构变量

为了初始化结构变量,我们使用与初始化数组相同的语法。

struct student
{
    char name[20];
    int roll_no;
    float marks;
} student1 = {"Jim", 14, 89};

struct student student2 = {"Tim", 10, 82};

这里student1的成员值将有name"Jim"roll_no14marks89。同样的,student2的成员值为name"Tim"10roll_no82marks

成员的值必须按照结构模板中定义的相同顺序和相同类型放置。

要理解的另一件重要的事情是,我们不允许在定义结构时初始化成员。

struct student
{
    char name[20] = "Phil";   // invalid
    int roll_no = 10;         // invalid
    float marks = 3.14;       // invalid
};

定义结构只会创建一个模板,在创建结构变量之前不会分配内存。因此在这一点上不存在称为nameroll_nomarks的变量,那么我们如何将数据存储在一个不存在的变量中呢?我们不能。

如果初始值设定项的数量少于成员的数量,那么剩余的成员被赋予一个值0。例如:

struct student student1 = {"Jon"};

与...相同

struct student student1 = {"Jon", 0, 0.0};

结构上的操作

创建结构定义和结构变量后。显然,下一个逻辑步骤是学习如何访问结构的成员。

点(.)运算符或成员运算符用于使用结构变量访问结构的成员。以下是语法:

语法: structure_variable.member_name;

我们可以通过编写结构变量,后跟点(.)运算符,后跟成员名称来引用结构的成员。例如:

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

struct student student1 = {"Jon", 44, 96};

要访问student1的名称,请使用student1.name,同样,要访问roll_nomarks,请分别使用student1.roll_nostudent1.marks。例如,以下语句将显示student_1成员的值。

printf("Name: %s", student_1.name);
printf("Name: %d", student_2.roll_no);
printf("Name: %f", student_1.marks);

我们可以像使用其他普通变量一样使用student1.namestudent1.roll_nostudent1.marks。它们可以被读取、显示、赋值、在表达式中使用、作为参数传递给函数等。

让我们尝试更改结构成员的值。

student_1.roll_no = 10; // change roll no of student_1 from 44 to 10
student_1.marks++;      // increment marks of student_1 by 1

回想一下运算符优先级和结合性一章,点(.)运算符的优先级高于++运算符和赋值运算符(=)。所以在上面的表达式中,第一个点(.)运算符应用于后面跟有++运算符的表达式中。

看看下面的陈述。

scanf("%s", student_1.name);

这里structure studentname成员是一个数组,数组名是指向数组第 0 个元素的常量指针。所以我们不需要在student_1.name之前加上&运算符。另一方面在声明中:

scanf("%d", &student_1.roll_no);

要求在student_2.roll_no前加&运算符,因为roll_no是变量名,不是指针。另一点值得注意的是,在上面的表达式中,&运算符之前应用了点(.)运算符。

我们还可以将一个结构变量赋给另一个相同类型的结构变量。

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

struct student student1 = {"Jon", 44, 96}, student2;

student2 = student1;

本声明将student1.name复制成student2.namestudent1.roll_no复制成student2.roll_no等等。

需要注意的是,我们不能对结构变量使用算术、关系和按位运算符。

student1 + student2;  // invalid
student1 == student2; // invalid
student1 & student2;  // invalid

下面的程序演示了我们如何定义一个结构和读取结构成员的值。

#include<stdio.h>
#include<string.h>

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

int main()
{
    struct student student_1 = {"Jim", 10, 34.5}, student_2, student_3;

    printf("Details of student 1\n\n");

    printf("Name: %s\n", student_1.name);
    printf("Roll no: %d\n", student_1.roll_no);
    printf("Marks: %.2f\n", student_1.marks);

    printf("\n");

    printf("Enter name of student2: ");
    scanf("%s", student_2.name);

    printf("Enter roll no of student2: ");
    scanf("%d", &student_2.roll_no);

    printf("Enter marks of student2: ");
    scanf("%f", &student_2.marks);

    printf("\nDetails of student 2\n\n");

    printf("Name: %s\n", student_2.name);
    printf("Roll no: %d\n", student_2.roll_no);
    printf("Marks: %.2f\n", student_2.marks);
    strcpy(student_3.name, "King");
    student_3.roll_no = ++student_2.roll_no;
    student_3.marks = student_2.marks + 10;

    printf("\nDetails of student 3\n\n");

    printf("Name: %s\n", student_3.name);
    printf("Roll no: %d\n", student_3.roll_no);
    printf("Marks: %.2f\n", student_3.marks);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Details of student 1

Name: Jim
Roll no: 10
Marks: 34.50

Enter name of student2: jack
Enter roll no of student2: 33
Enter marks of student2: 15.21

Details of student 2

Name: jack
Roll no: 33
Marks: 15.21

Details of student 3

Name: King
Roll no: 34
Marks: 25.21

工作原理:

这里我们已经初始化了三个类型为struct student的变量。第一个结构变量student_1在声明时初始化。第一个学生的详细信息会用printf()语句打印出来。然后程序要求用户输入nameroll_nomarks作为结构变量student_2。然后使用printf()语句打印student_2的详细信息。

我们知道student_3.name是一个数组,所以我们不能只给它分配一个字符串,这就是为什么在第 37 行中使用strcpy()函数给student_3.name分配一个字符串。

因为点(.)运算符的优先级大于++运算符。因此在表达式++student_2.roll_no中,点(.)运算符首先应用,然后student.roll_no的值递增,并最终分配给student_3.roll_no。同样在表达式student_2.marks + 10中,由于点(.)运算符的优先级大于+运算符,首先得到student_2的标记,然后将其值增加10并最终赋给student_3.marks。最后打印student_3的详细信息。

结构如何存储在内存中

结构的成员总是存储在连续的内存位置,但是每个成员占用的内存可能会有所不同。考虑以下程序:

#include<stdio.h>

struct book
{
    char title[5];
    int year;
    double price;
};

int main()
{
    struct book b1 = {"Book1", 1988, 4.51};

    printf("Address of title = %u\n", b1.title);
    printf("Address of year = %u\n", &b1.year);
    printf("Address of price = %u\n", &b1.price);

    printf("Size of b1 = %d\n", sizeof(b1));

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Address of title = 2686728
Address of year = 2686736
Address of price = 2686744
Size of b1 = 24

在结构上,书名占据5字节,年份占据4字节,价格占据8字节。所以结构变量的大小应该是17字节。但是,正如您在输出中看到的,变量b1的大小是24字节,而不是17字节。为什么会这样?

这是因为有些系统要求某些数据类型的地址是248的倍数。例如,有些机器只在偶数地址存储整数,unsigned long intdouble存储在4的倍数等地址。在我们的例子中,名称成员的地址是2686728,因为它是5字节long,所以它占据了2686728 - 2686732的所有地址。

我运行这些示例程序的机器以4的倍数存储整数,这就是为什么2686732之后的三个连续字节(即268673326867342686735)没有使用的原因。这些未使用的字节称为。需要注意的是,这些孔不属于结构的任何成员,但它们确实会影响结构的整体尺寸。所以下一个成员year存储在2686736(是 4 的倍数)。它占用从26867362686739的地址4字节。同样,2686739 之后的四个字节未被使用,最终price成员存储在地址2686744(是8的倍数)。



C 语言中的结构数组

原文:https://overiq.com/c-programming-101/array-of-structures-in-c/

最后更新于 2020 年 7 月 27 日


声明结构数组与声明基本类型数组是一样的。因为数组是同一类型元素的集合。在结构数组中,数组的每个元素都属于结构类型。

让我们举个例子:

struct car
{
    char make[20];
    char model[30]; 
    int year;
};

下面是我们如何声明structure car的数组。

struct car arr_car[10];

这里arr_car10元素的数组,其中每个元素都是struct car类型。我们可以使用arr_car来存储struct car类型的10结构变量。为了访问单个元素,我们将使用下标符号([]),为了访问每个元素的成员,我们将像往常一样使用点(.)运算符。

arr_stu[0] : points to the 0th element of the array.
arr_stu[1] : points to the 1st element of the array.

等等。同样的,

arr_stu[0].name : refers to the name member of the 0th element of the array.
arr_stu[0].roll_no : refers to the roll_no member of the 0th element of the array.
arr_stu[0].marks : refers to the marks member of the 0th element of the array.

回想一下[]数组下标和点(.)运算符的优先级是相同的,它们从左到右求值。因此,在上面的表达式中,首先应用数组下标([]),然后应用点(.)运算符。数组下标([])和点(.)运算符是相同的,它们从左到右计算。因此,在上面的表达式中,首先应用[]数组下标,然后应用点(.)运算符。

让我们重写上一章中作为结构介绍的程序。

#include<stdio.h>
#include<string.h>
#define MAX 2

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

int main()
{
    struct student arr_student[MAX];
    int i;

    for(i = 0; i < MAX; i++ )
    {
        printf("\nEnter details of student %d\n\n", i+1);

        printf("Enter name: ");
        scanf("%s", arr_student[i].name);

        printf("Enter roll no: ");
        scanf("%d", &arr_student[i].roll_no);

        printf("Enter marks: ");
        scanf("%f", &arr_student[i].marks);
    }

    printf("\n");

    printf("Name\tRoll no\tMarks\n");

    for(i = 0; i < MAX; i++ )
    {
        printf("%s\t%d\t%.2f\n",
        arr_student[i].name, arr_student[i].roll_no, arr_student[i].marks);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter details of student 1

Enter name: Jim
Enter roll no: 1
Enter marks: 44

Enter details of student 2

Enter name: Tim
Enter roll no: 2
Enter marks: 76

Name Roll no Marks
Jim 1 44.00
Tim 2 76.00

工作原理:

在第 5-10 行,我们已经声明了一个名为student的结构。

在第 14 行,我们已经声明了一个类型为struct student的结构数组,其大小由符号常量MAX控制。如果你想增加/减少数组的大小,只要改变符号常量的值,我们的程序就会适应新的大小。

在第 17-29 行,第一个 for 循环用于输入学生的详细信息。

在第 36-40 行,第二个 for 循环以表格形式打印学生的所有详细信息。

初始化结构数组

我们还可以使用与初始化数组相同的语法来初始化结构数组。让我们举个例子:

struct car
{
    char make[20];
    char model[30]; 
    int year;
};
struct car arr_car[2] = {
                            {"Audi", "TT", 2016},
                            {"Bentley", "Azure", 2002}
                        };



作为 C 语言中结构成员的数组

原文:https://overiq.com/c-programming-101/array-as-member-of-structure-in-c/

最后更新于 2020 年 7 月 27 日


从本章开始,我们已经在结构中使用数组作为成员。不过,让我们再讨论一次。例如:

struct student
{
    char name[20];
    int roll_no;
    float marks;
};

上面定义的student结构有一个成员name,它是一个 20 个字符的数组。

让我们创建另一个名为 student 的结构来存储 5 个科目的名称、卷号和分数。

struct student
{
    char name[20];
    int roll_no;
    float marks[5];
};

如果student_1是类型为struct student的变量,那么:

student_1.marks[0] -指第一科的标记
student_1.marks[1] -指第二科的标记

等等。同样,如果arr_student[10]是类型为struct student的数组,那么:

arr_student[0].marks[0] -指第一科第一名学生的成绩arr_student[1].marks[2] -指第三科第二名学生的成绩

等等。

以下程序要求用户在 2 个科目中输入姓名、卷号和分数,并计算每个学生的平均分数。

#include<stdio.h>
#include<string.h>
#define MAX 2
#define SUBJECTS 2

struct student
{
    char name[20];
    int roll_no;
    float marks[SUBJECTS];
};

int main()
{
    struct student arr_student[MAX];
    int i, j;
    float sum = 0;

    for(i = 0; i < MAX; i++ )
    {
        printf("\nEnter details of student %d\n\n", i+1);

        printf("Enter name: ");
        scanf("%s", arr_student[i].name);

        printf("Enter roll no: ");
        scanf("%d", &arr_student[i].roll_no);

        for(j = 0; j < SUBJECTS; j++)
        {
            printf("Enter marks: ");
            scanf("%f", &arr_student[i].marks[j]);
        }
    }

    printf("\n");

    printf("Name\tRoll no\tAverage\n\n");

    for(i = 0; i < MAX; i++ )
    {
        sum = 0;

        for(j = 0; j < SUBJECTS; j++)
        {
            sum += arr_student[i].marks[j];
        }
        printf("%s\t%d\t%.2f\n",
             arr_student[i].name, arr_student[i].roll_no, sum/SUBJECTS);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Enter details of student 1

Enter name: Rick
Enter roll no: 1
Enter marks: 34
Enter marks: 65

Enter details of student 2

Enter name: Tim
Enter roll no: 2
Enter marks: 35
Enter marks: 85

Name Roll no Average

Rick 1 49.50
Tim 2 60.00

工作原理:

在第 3 行和第 4 行,我们已经声明了两个符号常量MAXSUBJECTS,分别控制学生和科目的数量。

在第 6-11 行,我们已经声明了一个结构学生,它有三个成员,即nameroll_nomarks

在第 15 行,我们已经声明了一系列大小为MAX的结构arr_student

在第 16 行,我们已经声明了两个int变量ij来控制循环。

在第 17 行,我们已经声明了一个float变量sum,并将其初始化为0。这个变量将用于累积某个学生的分数。

在第 19-34 行,我们有一个 for 循环,要求用户输入学生的详细信息。在这个 for 循环中,我们有一个嵌套的 for 循环,它要求用户输入学生在不同科目中获得的分数。

在第 40-50 行,我们有另一个 for 循环,其工作是打印学生的详细信息。请注意,每次迭代后sum被重新初始化为0,这是必要的,否则我们将不会得到正确的答案。嵌套 for 循环用于在变量 sum 中累积特定学生的分数。最后,第 48 行的打印语句打印了学生的所有详细信息。



C 语言中的嵌套结构

原文:https://overiq.com/c-programming-101/nested-structures-in-c/

最后更新于 2020 年 7 月 27 日


一个结构可以嵌套在另一个结构中。换句话说,结构的成员可以是任何其他类型,包括结构。下面是创建嵌套结构的语法。

语法:

structure tagname_1
{
    member1;
    member2;
    member3;
    ...
    membern;

    structure tagname_2
    {
        member_1;
        member_2;
        member_3;
        ...
        member_n;
    }, var1

} var2;

注意:结构的嵌套可以扩展到任意级别。

为了访问内部结构的成员,我们写一个外部结构的变量名,后面跟一个点(.)运算符,后面跟内部结构的变量,后面跟一个点(.)运算符,然后跟我们要访问的成员的名称。

var2.var1.member_1 -指结构的member_1``tagname_2
var2.var1.member_2-指结构的member_2``tagname_2
以此类推。

让我们举个例子:

struct student
{
    struct person
    {
        char name[20];
        int age;
        char dob[10];
    } p ;

    int rollno;
    float marks;
} stu;

这里我们把结构人定义为结构学生的一员。下面是我们如何访问人员结构的成员。

stu.p.name -指人的名字
stu.p.age -指人的年龄
stu.p.dob -指人的出生日期

需要注意的是,结构人不是独立存在的。我们不能在程序的任何其他地方声明类型为struct person的结构变量。

而不是在另一个结构中定义该结构。我们可以在外部定义它,然后在我们想要使用它的结构内部声明它是变量。例如:

struct person
{
    char name[20];
    int age;
    char dob[10];
};

我们可以把这个结构作为一个更大结构的一部分。

struct student
{
    struct person info;
    int rollno;
    float marks;
}

这里第一个成员是类型struct person。如果我们使用这种创建嵌套结构的方法,那么在创建其类型的变量之前,您必须首先定义结构。因此,在将人员结构变量用作结构学生的成员之前,您必须首先定义人员结构。

使用这种方法的好处是,现在我们可以在程序的任何其他地方声明一个类型为struct person的变量。

现在允许结构本身嵌套。例如:

struct citizen
{
    char name[50];
    char address[100];
    int age;
    int ssn;
    struct citizen relative; // invalid
}

初始化嵌套结构

嵌套结构可以在声明时初始化。例如:

struct person
{
    char name[20];
    int age;
    char dob[10];
};

struct student
{
    struct person info;
    int rollno;
    float marks[10];
}

struct student student_1 = {
                               {"Adam", 25, 1990},
                               101,
                               90
                           };

下面的程序演示了我们如何使用嵌套结构。

#include<stdio.h>

struct person
{
    char name[20];
    int age;
    char dob[10];
};

struct student
{
    struct person info;
    int roll_no;
    float marks;
};

int main()
{
    struct student s1;

    printf("Details of student: \n\n");

    printf("Enter name: ");
    scanf("%s", s1.info.name);

    printf("Enter age: ");
    scanf("%d", &s1.info.age);

    printf("Enter dob: ");
    scanf("%s", s1.info.dob);

    printf("Enter roll no: ");
    scanf("%d", &s1.roll_no);

    printf("Enter marks: ");
    scanf("%f", &s1.marks);

    printf("\n*******************************\n\n");

    printf("Name: %s\n", s1.info.name);
    printf("Age: %d\n", s1.info.age);
    printf("DOB: %s\n", s1.info.dob);
    printf("Roll no: %d\n", s1.roll_no);
    printf("Marks: %.2f\n", s1.marks);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Details of student:

Enter name: Phil
Enter age: 27
Enter dob: 23/4/1990
Enter roll no: 78123
Enter marks: 92

*******************************

Name: Phil
Age: 27
DOB: 23/4/1990
Roll no: 78123
Marks: 92.00

工作原理:

在第 3-8 行,我们已经声明了一个名为person的结构。

在第 10-15 行,我们已经声明了另一个名为student的结构,它的一个成员是类型struct student(如上声明)。

在第 19 行,我们已经声明了类型为struct student的变量s1

接下来的五个scanf()语句(第 23-36 行)要求用户输入学生的详细信息,然后使用printf()(第 40-44 行)语句打印出来。



指向 C 语言结构的指针

原文:https://overiq.com/c-programming-101/pointer-to-a-structure-in-c/

最后更新于 2020 年 7 月 27 日


我们已经知道,指针是一个变量,它指向任何数据类型的另一个变量的地址,如intcharfloat等。同样,我们可以有一个指向结构的指针,其中指针变量可以指向结构变量的地址。下面是我们如何声明一个指向结构变量的指针。

struct dog
{
    char name[10];
    char breed[10];
    int age;
    char color[10];
};

struct dog spike;

// declaring a pointer to a structure of type struct dog
struct dog *ptr_dog

这声明了一个指针ptr_dog,可以存储类型为struct dog的变量的地址。我们现在可以使用&运算符将变量spike的地址分配给ptr_dog

ptr_dog = &spike;

现在ptr_dog指向结构变量spike

使用指针访问成员

使用指针访问结构成员有两种方式:

  1. 使用间接(*)运算符和点(.)运算符。
  2. 使用箭头(->)运算符或成员运算符。

让我们从第一个开始。

使用间接(*)运算符和点(。)操作员

此时ptr_dog指向结构变量spike,因此通过对其解引用,我们将得到spike的内容。这意味着spike*ptr_dog在功能上是等同的。要访问一个结构成员,写*ptr_dog后跟一个点(.)运算符,后跟成员的名称。例如:

(*ptr_dog).name -指狗的name
(*ptr_dog).breed-指狗的breed

等等。

*ptr_dog周围的括号是必要的,因为点(.)运算符的优先级大于间接(*)运算符的优先级。

使用箭头运算符(-->)

上述使用指针访问结构成员的方法稍微有些混乱,可读性较差,这就是为什么 C 提供了另一种使用箭头(->)运算符访问成员的方法。要使用箭头(->)运算符访问成员,请编写指针变量,后跟->运算符和成员名称。

ptr_dog->name   // refers to the name of dog
ptr_dog->breed  // refers to the breed of dog

等等。

这里不需要括号、星号(*)和点(.)运算符。这种方法可读性和直观性强得多。

我们还可以使用指针表示法修改成员的值。

strcpy(ptr_dog->name, "new_name");

这里我们知道数组的名字(ptr_dog->name)是一个常量指针,指向数组的第 0 个元素。所以我们不能用赋值运算符(=)给它赋值一个新的字符串,这就是为什么要用strcpy()函数。

--ptr_dog->age;

在上面的表达式中,箭头运算符(->)的优先级大于前缀递减运算符(--),因此首先在表达式中应用->运算符,然后其值递减 1。

下面的程序演示了如何使用指针来构造。

#include<stdio.h>

struct dog
{
    char name[10];
    char breed[10];
    int age;
    char color[10];
};

int main()
{
    struct dog my_dog = {"tyke", "Bulldog", 5, "white"};
    struct dog *ptr_dog;
    ptr_dog = &my_dog;

    printf("Dog's name: %s\n", ptr_dog->name);
    printf("Dog's breed: %s\n", ptr_dog->breed);
    printf("Dog's age: %d\n", ptr_dog->age);
    printf("Dog's color: %s\n", ptr_dog->color);

    // changing the name of dog from tyke to jack
    strcpy(ptr_dog->name, "jack");

    // increasing age of dog by 1 year
    ptr_dog->age++;

    printf("Dog's new name is: %s\n", ptr_dog->name);
    printf("Dog's age is: %d\n", ptr_dog->age);

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Dog's name: tyke
Dog's breed: Bulldog
Dog's age: 5
Dog's color: white

After changes

Dog's new name is: jack
Dog's age is: 6

工作原理:

在第 3-9 行中,我们已经声明了类型狗的结构,它有四个成员,即namebreedagecolor

在第 13 行,声明并初始化了类型为struct dog的名为my_dog的变量。

在第 14 行,声明了类型为struct dog的指针变量ptr_dog

在第 15 行中,使用&运算符将my_dog的地址分配给ptr_dog

在第 17-20 行中,printf()语句打印了狗的详细信息。

在第 23 行中,使用strcpy()函数为ptr_dog分配了一个新名称,因为我们不能使用赋值运算符直接为ptr_dog->name分配字符串值。

在第 26 行中,ptr_dog->age的值使用后缀增量运算符增加1。回想一下后缀++运算符和->从左到右具有相同的优先级和关联。但是由于表达式中使用了后缀++,所以表达式中首先使用ptr_dog->age的值,然后它的值增加1



作为 C 语言中的结构成员的指针

原文:https://overiq.com/c-programming-101/pointers-as-structure-member-in-c/

最后更新于 2020 年 7 月 27 日


我们也可以有一个指针作为结构的成员。例如:

struct test
{
    char name[20];
    int *ptr_mem;
};

struct test t1, *str_ptr = &t1;

这里ptr_memint的指针,也是test结构的成员。

我们有两种方式可以访问ptr_mem的值(即地址):

  1. 使用结构变量- t1.ptr_mem
  2. 使用指针变量- str_ptr->ptr_mem

同样,我们有两种方式可以访问ptr_mem所指向的值。

  1. 使用结构变量- *t1.ptr_mem
  2. 使用指针变量- *str_ptr->ptr_mem

由于点(.)运算符的优先级大于间接(*)运算符的优先级,因此在表达式*t1.ptr_mem中,点(.)应用于间接(*)运算符之前。类似地,在表达式*str_ptr->ptr_mem中,箭头(->)运算符之后是间接(*)运算符。

下面的程序演示了本课到目前为止我们所学的一切。

#include<stdio.h>

struct student
{
    char *name;
    int age;
    char *program;
    char *subjects[5];
};

int main()
{
    struct student stu = {
                             "Lucy",
                             25,
                             "CS",
                             {"CS-01", "CS-02", "CS-03", "CS-04", "CS-05" }
                         };

    struct student *ptr_stu = &stu;
    int i;

    printf("Accessing members using structure variable: \n\n");

    printf("Name: %s\n", stu.name);
    printf("Age: %d\n", stu.age);
    printf("Program enrolled: %s\n", stu.program);

    for(i = 0; i < 5; i++)
    {
        printf("Subject : %s \n", stu.subjects[i]);
    }

    printf("\n\nAccessing members using pointer variable: \n\n");

    printf("Name: %s\n", ptr_stu->name);
    printf("Age: %d\n", ptr_stu->age);
    printf("Program enrolled: %s\n", ptr_stu->program);

    for(i = 0; i < 5; i++)
    {
        printf("Subject : %s \n", ptr_stu->subjects[i]);
    }

    // signal to operating system program ran fine
    return 0;
}

预期输出:

Accessing members using structure variable:

Name: Lucy
Age: 25
Program enrolled: CS
Subject : CS-01
Subject : CS-02
Subject : CS-03
Subject : CS-04
Subject : CS-05
Accessing members using pointer variable:

Name: Lucy
Age: 25
Program enrolled: CS
Subject : CS-01
Subject : CS-02
Subject : CS-03
Subject : CS-04
Subject : CS-05

工作原理:

在第 3-9 行中,声明了一个结构student,它有四个成员,即:nameageprogramsubjects。成员类型如下:

名字 类型
name 指向char的指针
age int
program 指向char的指针
subjects 指向char5指针数组

在第 13-18 行中,声明并初始化了类型为struct student的变量stu。因为nameprogram是指向字符的指针,所以我们可以直接给它们分配字符串。类似地,subjects是一个由 5 个指向 char 的指针组成的数组,因此它可以容纳 5 个字符串。

在第 20 行中,声明了类型为struct student的指针变量ptr_stu,并使用&运算符为其分配了stu的地址。

从第 25-27 行,使用结构变量stu打印三个printf()语句nameageprogram

在第 29-32 行,一个 for 循环被用来循环指针数组*subjects[5]的所有元素。并使用结构变量打印主题名称。

从第 36-38 行开始,使用指针变量ptr_stu,使用三个printf()语句打印nameageprogram

在第 40-43 行中,一个 for 循环用于循环指针数组*subjects[5]的所有元素。并使用指针变量打印主题的名称。



C 语言中的结构和函数

原文:https://overiq.com/c-programming-101/structures-and-functions-in-c/

最后更新于 2020 年 7 月 27 日


像所有其他类型一样,我们可以将结构作为参数传递给函数。事实上,我们可以将单个成员、结构变量、结构指针等传递给函数。类似地,函数可以返回单个成员或结构变量或指向该结构的指针。

让我们从将单个成员作为参数传递给函数开始。

将结构成员作为参数传递给函数

我们可以像普通变量一样将单个成员传递给函数。

下面的程序演示了如何将结构成员作为参数传递给函数。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct student
{
    char name[20];
    int roll_no;
    int marks;
};

void print_struct(char name[], int roll_no, int marks);

int main()
{
    struct student stu = {"Tim", 1, 78};
    print_struct(stu.name, stu.roll_no, stu.marks);
    return 0;
}

void print_struct(char name[], int roll_no, int marks)
{
    printf("Name: %s\n", name);
    printf("Roll no: %d\n", roll_no);
    printf("Marks: %d\n", marks);
    printf("\n");
}

预期输出:

Name: Tim
Roll no: 1
Marks: 78

工作原理:

在第 7-12 行,一个结构学生被声明有三个成员,即nameroll_nomarks

在第 14 行中,声明了函数print_struct()的原型,它接受三个参数,即指向char的类型指针名称、int的类型roll_noint的类型marks

在第 18 行中,声明并初始化了类型为struct student的结构变量stu

在第 19 行,结构变量stu的所有三个成员都被传递给print_struct()函数。print_struct()函数的形式参数用实际参数的值初始化。

从第 25-27 行,三个printf()语句打印出学生的nameroll_nomarks

这个程序最需要注意的是stu.name是作为引用传递的,因为数组的名字是一个常量指针。所以print_struct()函数的形式参数,即名称和stu.name都指向同一个数组。因此,函数print_struct()所做的任何更改都会影响原始数组。我们可以通过对我们的程序进行以下修改来验证这一事实。

  1. 在主函数中,在对 print_struct()函数的调用后添加以下一行。

    printf("New name: %s", stu.name);
    
    
  2. print_struct()函数中,在最后一条printf()语句之前添加以下两行。

    printf("\nChanging name ... \n"); 
    strcpy(name, "Jack");
    
    

现在我们的程序应该是这样的:

#include<stdio.h>
#include<string.h>

/*
structure is defined above all functions so it is global.
*/

struct student
{
    char name[20];
    int roll_no;
    int marks;
};

void print_struct(char name[], int roll_no, int marks);

int main()
{
    struct student stu = {"Tim", 1, 78};
    print_struct(stu.name, stu.roll_no, stu.marks);

    printf("New name: %s", stu.name);

    return 0;
}

void print_struct(char name[], int roll_no, int marks)
{
    printf("Name: %s\n", name);
    printf("Roll no: %d\n", roll_no);
    printf("Marks: %d\n", marks);

    printf("\nChanging name ... \n");
    strcpy(name, "Jack");

    printf("\n");
}

预期输出:

Name: Tim
Roll no: 1
Marks: 78

Changing name ...

New name: Jack

这验证了print_struct()函数所做的更改会影响原始数组的事实。

将结构变量作为参数传递给函数

在前面的部分中,我们已经学习了如何将结构成员作为参数传递给函数。如果一个结构包含 2-3 个成员,那么我们可以很容易地将它们传递给函数,但是如果有 9-10 个或更多的成员呢?当然,通过 9-10 名成员是一个令人厌倦且容易出错的过程。所以在这种情况下,我们可以传递结构变量本身,而不是单独传递成员。

下面的程序演示了如何将结构变量作为参数传递给函数。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct student
{
    char name[20];
    int roll_no;
    int marks;
};

void print_struct(struct student stu);

int main()
{
    struct student stu = {"George", 10, 69};
    print_struct(stu);
    return 0;
}

void print_struct(struct student stu)
{
    printf("Name: %s\n", stu.name);
    printf("Roll no: %d\n", stu.roll_no);
    printf("Marks: %d\n", stu.marks);
    printf("\n");
}

预期输出:

Name: George
Roll no: 10
Marks: 69

工作原理:

在第 7-12 行,一个结构student被声明有三个成员,即:nameroll_nomarks

在第 14 行中,声明了接受类型为struct student的参数的函数print_struct()的原型。

在第 18 行中,声明并初始化了类型为struct student的结构变量stu

在第 19 行中,print_struct()函数与参数stu一起被调用。与数组不同,结构变量的名称不是指针,所以当我们将结构变量传递给函数时,print_struct()的形式参数被赋予了原始结构的副本。这两种结构位于不同的存储位置,因此它们彼此完全独立。函数print_struct()所做的任何更改都不会影响main()函数中的原始结构变量。

第 25-27 行的printf()语句打印学生的详细信息。

将结构指针作为参数传递给函数

虽然将结构变量作为参数传递允许我们将结构的所有成员传递给一个函数,但是这种操作有一些缺点。

  1. 回想一下,结构的副本被传递给了形式参数。如果结构很大,并且您经常传递结构变量,那么它会花费相当多的时间,这使得程序效率低下。
  2. 需要额外的内存来保存结构的每个副本。

下面的程序演示了如何将结构指针作为参数传递给函数。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct employee
{
    char name[20];
    int age;
    char doj[10]; // date of joining
    char designation[20];
};

void print_struct(struct employee *);

int main()
{
    struct employee dev = {"Jane", 25, "25/2/2015", "Developer"};
    print_struct(&dev);

    return 0;
}

void print_struct(struct employee *ptr)
{
    printf("Name: %s\n", ptr->name);
    printf("Age: %d\n", ptr->age);
    printf("Date of joining: %s\n", ptr->doj);
    printf("Age: %s\n", ptr->designation);
    printf("\n");
}

预期输出:

Name: Jin
Age: 25
Date of joining: 25/2/2015
Age: Developer

工作原理:

在第 7-13 行中,结构employee由四个成员声明,即nameagedoj(加入日期)和designation

在第 15 行,声明了函数print_struct()的原型,它接受指向struct student的类型指针的参数。

在第 19 行,声明并初始化了类型为struct employee的结构变量 dev。

在第 20 行中,print_struct()与变量dev的地址一起被调用。print_struct()的形式变元被赋予变量dev的地址。现在ptr指向的是原始结构,因此函数内部的任何变化都会影响原始结构。

第 27-30 行的printf()语句打印了开发者的详细信息。

将结构指针传递给函数的缺点是函数可以修改原始结构。如果那是你有意想要的,那没关系。但是,如果不希望函数修改原始结构,请使用const关键字。回想一下const关键字应用于变量时使其成为只读的。

让我们使用const关键字重写之前的程序。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct employee
{
    char name[20];
    int age;
    char doj[10]; // date of joining
    char designation[20];
};

void print_struct(const struct employee *);

int main()
{
    struct employee dev = {"Jane", 25, "25/2/2015", "Developer"};
    print_struct(&dev);

    return 0;
}

void print_struct(const struct employee *ptr)
{
    printf("Name: %s\n", ptr->name);
    printf("Age: %d\n", ptr->age);
    printf("Date of joining: %s\n", ptr->doj);
    printf("Age: %s\n", ptr->designation);

    //ptr->age = 11;

    printf("\n");
}

现在,即使我们将结构指针传递给print_struct()函数,任何修改结构值的尝试都会导致编译错误。试着注释掉第 32 行的代码,自己看看。

作为函数参数的结构数组

我们已经看到了如何将整数数组传递给函数。同样,我们可以将结构数组传递给函数。

下面的程序演示了如何将结构数组传递给函数。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct company
{
    char name[20];
    char ceo[20];
    float revenue; // in $
    float pps; // price per stock in $
};

void print_struct(const struct company str_arr[]);

int main()
{

    struct company companies[3] = {
                           {"Country Books", "Tim Green", 999999999, 1300 },
                           {"Country Cooks", "Jim Green", 9999999, 700 },
                           {"Country Hooks", "Sim Green", 99999, 300 },
                   };
    print_struct(companies);

    return 0;
}

void print_struct(struct company str_arr[])
{
    int i;

    for(i= 0; i<3; i++)
    {
        printf("Name: %s\n", str_arr[i].name);
        printf("CEO: %d\n", str_arr[i].ceo);
        printf("Revenue: %.2f\n", str_arr[i].revenue);
        printf("Price per stock : %.2f\n", str_arr[i].pps);
        printf("\n");
    }
}

预期输出:

Name: Country Books
CEO: 2686660
Revenue: 1000000000.00
Price per stock : 1300.00

Name: Country Cooks
CEO: 2686708
Revenue: 9999999.00
Price per stock : 700.00

Name: Country Hooks
CEO: 2686756
Revenue: 99999.00
Price per stock : 300.00

工作原理:

在第 7-13 行中,一个结构公司被声明有四个成员,即nameceorevenuepps

在第 15 行中,声明了函数print_struct()的原型,它接受结构类型数组的参数。

在第 20-24 行中,声明并初始化了类型为struct company的名为companies的结构数组。

在第 25 行中,print_struct()与参数公司一起被调用。由于数组的名称是指向数组第 0 个元素的常量指针,print_struct()的形式参数被分配了变量公司的地址。所以现在str_arr指向的是原始数组的结构,函数内部所做的任何改变都会影响到原始结构。如果不想调用函数修改原始结构,使用关键字const

在第 32 行,变量i被声明来控制 for 循环。

在第 34-41 行中,一个 for 循环用于循环结构数组,并打印每个公司的详细信息。

然后,控制转到main()函数,该函数终止。

print_struct()的形式论据也可以声明如下:

void print_struct(struct company *str_arr)
{
    int i;
    ...
}

但是为什么呢?

回想一下数组的名称,即companies是指向数组第 0 个元素的常量指针。在这种情况下,第 0 个元素属于 struct company 类型。所以公司的类型是指向struct company(struct company*)的指针。这就是为什么str_arr被声明为指向struct company(struct company*)的指针。

从函数返回结构

正如我们可以返回基本类型和数组一样,我们也可以从函数返回结构。要从函数返回结构,我们必须在函数定义和声明中指定适当的返回类型。考虑以下示例:

struct player check_health(struct player p);
{
    ...
}

该函数接受类型为struct player的参数,并返回类型为struct player的参数。

下面的程序演示了如何从函数返回结构。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct player
{
    char name[20];
    float height;
    float weight;
    float fees;
};

void print_struct(struct player p);
struct player deduct_fees(struct player p);

int main()
{
    struct player p = {"Joe", 5.9, 59, 5000 };
    print_struct(p);
    p = deduct_fees(p);
    print_struct(p);

    return 0;
}

struct player deduct_fees(struct player p)
{
    p.fees -= 1000;
    return p;
}

void print_struct(const struct player p)
{
    printf("Name: %s\n", p.name);
    printf("Height: %.2f\n", p.height);
    printf("Weight: %.2f\n", p.weight);
    printf("Fees: %.2f\n", p.fees);

    printf("\n");
}

预期输出:

Name: Joe
Height: 5.90
Weight: 59.00
Fees: 5000.00

Name: Joe
Height: 5.90
Weight: 59.00
Fees: 4000.00

工作原理:

在第 7-13 行中,类型为player的结构声明有 4 个成员,即nameheightweightfees

在第 15 行中,声明了print_struct()的原型,它接受类型为struct player的参数,但不返回任何内容。

在第 16 行中,声明了deduct_fees()的原型,它接受类型为struct player的参数,并返回类型为struct player的结构。

在第 20 行,声明并初始化了类型为struct player的结构变量 p。

在第 21 行中,print_struct()函数被传递了一个类型为struct player的参数。该函数打印玩家的详细信息,并将控制权交还给main()函数。

在第 22 行中,使用类型为struct player的参数调用deduct_fees()函数。该函数通过使用语句1000来减少玩家的费用。

p.fees -= 1000;

然后将结构变量p返回到被调用的函数即main(),在那里它被分配回变量p

在第 23 行中,再次调用print_struct(),参数与之前相同,以检查细节是否被deduct_fees()修改。

打印函数细节后,控制返回到main()函数,程序终止。

从函数返回结构指针

在最后一节中,我们学习了函数可以返回结构变量。所以它也可以返回一个指向结构变量的指针也就不足为奇了。要从函数返回结构指针,我们需要做的就是在函数定义和函数声明中指定合适的返回类型。例如:

struct *movie add_rating(struct movie *p);
{
    ...
}

该函数接受一个指向struct movie的类型指针参数,并返回一个指向struct movie的类型指针。

下面的程序演示了如何从函数返回结构指针。

#include<stdio.h>

/*
structure is defined above all functions so it is global.
*/

struct movie
{
    char title[20];
    char language[20];
    char director[20];
    int year;
    int rating;
};

void print_struct(const struct movie *p);
struct movie *add_rating(struct movie *p);

int main()
{
    struct movie m = {"The Accountant", "English" , "Gavin O'Connor", 2016, 1000};
    struct movie *ptr_m1 = &m, *ptr_m2;

    print_struct(ptr_m1);
    ptr_m2 = add_rating(ptr_m1);
    print_struct(ptr_m2);

    return 0;
}

struct movie *add_rating(struct movie *p)
{
    p->rating++; // increment rating by 1
    return p;
}

void print_struct(const struct movie *p)
{
    printf("Title: %s\n", p->title);
    printf("Language: %s\n", p->language);
    printf("Director: %s\n", p->director);
    printf("Year: %d\n", p->year);
    printf("Rating: %d\n", p->rating);

    printf("\n");
}

预期输出:

Title: The Accountant
Language: English
Director: Gavin O'Connor
Year: 2016
Rating: 1000

Title: The Accountant
Language: English
Director: Gavin O'Connor
Year: 2016
Rating: 1001

工作原理:

在第 7-14 行,一部结构电影由 5 个成员组成,即titlelanguagedirectoryearrating

在第 16 行,函数print_struct()的原型被声明为接受指向struct movie的类型指针的参数,并且不返回任何东西。

在第 17 行,声明了函数add_rating()的另一个原型,它接受指向struct movie的类型指针的参数,并且还返回类型为struct movie的指针。

在第 21 行中,声明并初始化了类型为struct movie的结构变量m

在第 22 行中,声明了两个类型为struct movie的指针变量ptr_m1ptr_m2,并为ptr_m1分配了m的地址。

第 24 行,调用print_struct()打印电影的细节。

在第 25 行中,add_rating()函数与变量m的地址一起被调用。该函数修改等级的值,并将指针返回到被调用的函数,在那里它被分配给指针变量ptr_m2

在第 26 行中,再次调用print_struct(),但这次ptr_m2被传递给它。打印详细信息后,控制权转移回main(),程序终止。



C 语言中的联合基础

原文:https://overiq.com/c-programming-101/union-basics-in-c/

最后更新于 2020 年 7 月 27 日


假设您正在创建一个程序来记录不同商品的名称和数量,其中数量可能是计数、重量或体积。解决这个问题的一种方法是创建如下结构:

struct goods
{
    char name[20];
    int count;
    float weight;
    float volume;
};

struct goods balls = {"balls", 10};

众所周知,球的数量是用计数来衡量的。所以,在这种情况下,不需要重量和体积。

同样在下面的陈述中:

struct goods flour = {"balls", 0, "20"};

因为面粉的数量是用重量来衡量的。所以,在这种情况下,不需要存储计数和体积。

从这些观察中,我们可以得出结论,一个特定类型的商品一次只能用数量中的一个来衡量,要么是数量,要么是重量,要么是体积。

在这一点上,我们的计划有以下限制:

  • 它占用的空间比要求的多,因此效率较低。
  • 有人可能设置了多个值。

如果我们可以用计数、重量或体积来记录数量,那就更有用了。这样我们可以节省很多内存。

在 C 语言中,联合允许我们这样做。

什么是联合?

像结构一样,联合用于创建新的数据类型。它也可以像结构一样包含成员。定义联合、创建联合变量和访问联合成员的语法与结构相同,唯一的区别是使用了联合关键字而不是结构。

结构和联合之间的重要区别在于,在结构中,每个成员都有自己的记忆,而联合中的成员共享相同的记忆。当声明类型为 union 的变量时,编译器会分配足够的内存来容纳 union 的最大成员。由于所有成员共享相同的内存,所以一次只能使用联合的一个成员,因此联合用于节省内存。声明联合的语法如下:

语法:

union tagname
{
    data_type member_1;
    data_type member_2;
    data_type member_3;
    ...
    data_type member_N;
};

就像结构一样,您可以用 union 定义或单独声明 union 变量。

union tagname
{
    data_type member_1;
    data_type member_2;
    data_type member_3;
    ... 
    data_type member_N;
} var_union;

union tagname var_union_2;

如果我们有一个联合变量,那么我们可以使用点运算符(.)访问联合的成员,同样,如果我们有指向联合的指针,那么我们可以使用箭头运算符(->)访问联合的成员。

下面的程序演示了如何使用联合。

#include<stdio.h>

/*
union is defined above all functions so it is global.
*/

union data
{
    int var1;
    double var2;
    char var3;
};

int main()
{
    union data t;

    t.var1 = 10;
    printf("t.var1 = %d\n", t.var1);

    t.var2 = 20.34;
    printf("t.var2 = %f\n", t.var2);

    t.var3 = 'a';
    printf("t.var3 = %c\n", t.var3);

    printf("\nSize of structure: %d", sizeof(t));

    return 0;
}

预期输出:

t.var1 = 10
t.var2 = 20.340000
t.var3 = a

Size of structure: 8

工作原理:

在第 7-12 行中,宣布了一个有三个成员的联盟data,即int类型的var1double类型的var2char类型的var3。当编译器看到联合的定义时,它将分配足够的内存来容纳联合的最大成员。在这种情况下,最大的成员是double,所以它将分配8字节的内存。如果上面的定义被声明为一个结构,编译器会分配13字节(8+4+2)的内存(这里我们忽略了漏洞,点击这里了解更多信息)。

在第 16 行,声明了类型为union data的联合变量t

在第 18 行中,t的第一个成员,即var1用值10初始化。需要注意的重要一点是,此时另外两个成员包含垃圾值。

在第 19 行中,t.var1的值是使用printf()语句打印的。

在第 21 行中,t的第二个成员,即var2被赋值为20.34。此时,另外两个成员包含垃圾值。

在第 22 行中,使用printf()语句打印t.var2的值。

在第 24 行中,t的第三个成员,即var3被赋值为'a'。此时,另外两个成员包含垃圾值。

在第 25 行中,使用printf()语句打印t.var3的值。

在第 27 行中,sizeof()运算符用于打印联合的大小。因为我们知道,在联合的情况下,编译器分配足够的内存来容纳最大的成员。联合data的最大成员是var2,因此sizeof()运算符返回8字节,然后使用printf()语句打印该字节。

初始化联合变量

在上面的程序中,我们已经看到了如何初始化联合变量的单个成员。我们也可以在声明时初始化 union 变量,但是有一个限制。由于联合共享相同的内存,所有成员不能同时持有这些值。所以我们只能在声明时初始化联盟的一个成员,这个特权属于第一个成员。例如:

union data
{
    int var1;
    double var2;
    char var3;
};

union data j = {10};

该语句初始化联合变量j,或者换句话说,它只初始化联合变量j的第一个成员。

指定的初始值设定项

指定的初始值设定项允许我们设置一个成员的值,而不是联合的第一个成员。假设我们要在声明时初始化联合数据的var2成员。我们可以这样做。

union data k = {.var2 = 9.14 };

这将把var2的值设置为9.14。同样,我们可以在声明时初始化第三个成员的值。

union data k = { .var3 = 'a' };

下面的程序演示了结构和指针之间的区别。

#include<stdio.h>
/*
union is defined above all functions so it is global.
*/

struct s
{
    int var1;
    double var2;
    char var3;
};

union u
{
    int var1;
    double var2;
    char var3;
};

int main()
{
    struct s a;
    union u b;

    printf("Information about structure variable \n\n");

    printf("Address variable of a = %u\n", &a);
    printf("Size of variable of a = %d\n", sizeof(a));

    printf("Address of 1st member i.e var1 = %u\n", &a.var1);
    printf("Address of 2nd member i.e var2 = %u\n", &a.var2);
    printf("Address of 3rd member i.e var3 = %u\n", &a.var3);

    printf("\n");

    printf("Information about union variable \n\n");

    printf("Address of variable of b = %u\n", &b);
    printf("Size of variable of b = %d\n", sizeof(b));

    printf("Address of 1st member i.e var1 = %u\n", &b.var1);
    printf("Address of 2nd member i.e var2 = %u\n", &b.var2);
    printf("Address of 3rd member i.e var3 = %u\n", &b.var3);
    printf("\n\n");

    return 0;
}

预期输出:

Address variable of a = 2686728
Size of variable of a = 24
Address of 1st member i.e var1 = 2686728
Address of 2nd member i.e var2 = 2686736
Address of 3rd member i.e var3 = 2686744

Information about union variable

Address of variable of b = 2686720
Size of variable of b = 8
Address of 1st member i.e var1 = 2686720
Address of 2nd member i.e var2 = 2686720
Address of 3rd member i.e var3 = 2686720

工作原理:

在第 6-11 行中,类型为s的结构由三个成员声明,即类型为intvar1floatvar2和类型为charvar3

在第 13-18 行中,类型为u的联合声明有三个成员,即类型为intvar1floatvar2和类型为charvar3

第 22 行和第 23 行分别声明了类型为struct s的结构变量a和类型为union u的联合变量b

在第 27 行,使用&运算符打印结构变量a的地址。

在第 28 行,使用sizeof()运算符打印结构变量的大小。

类似地,第 38 行和第 39 行的printf()语句分别打印联合变量b的地址和大小。

联合的所有成员共享相同的内存,这就是为什么接下来的三个printf()语句打印相同的地址。

请注意,联盟的成员共享相同的地址,而结构的成员不共享。结构和联合变量大小的差异也表明,在某些情况下,联合可以提供更经济的内存使用。我想强调的另一个要点是,由于前面讨论的边界对齐,结构的大小可能大于成员的总和,联合也是如此。

一个结构可以是联盟的成员。类似地,联合可以是结构的成员。

现在让我们把注意力转移回我们在引入联合时讨论的问题。

在了解了联合之后,我们知道一次只有联合变量的一个成员可用,这意味着联合是定义数量的完美选择。因此,如果将不同的数量存储为结构的成员,为什么不创建一个数量联盟,这样,对于任何商品,联盟中只有一个成员可用。

struct goods
{
    char name[20];

    union quantity
    {
        int count;
        float weight;
        float volume;
    } quant;
} g;

我们可以在商品结构之外定义联合数量,而不是嵌套数量。

union quantity
{
    int count;
    float weight;
    float volume;
};

struct goods
{
    char name[20];
    union quantity quant;
} g;

如果我们想访问 count 的值,我们可以写:

g.quant.count

类似地,为了获得权重值,我们可以写:

g.quant.weight

下面的程序演示了如何使用联合作为结构的成员。

#include<stdio.h>

/*
union is defined above all functions so it is global.
*/

union quantity
{
    int count;
    float weight;
    float volume;
};

struct goods
{
    char name[20];
    union quantity q;
};

int main()
{
    struct goods g1 = { "apple", {.weight=2.5} };
    struct goods g2 = { "balls", {.count=100} };

    printf("Goods name: %s\n", g1.name);
    printf("Goods quantity: %.2f\n\n", g1.q.weight);

    printf("Goods name: %s\n", g2.name);
    printf("Goods quantity: %d\n\n", g2.q.count);

    return 0;
}

预期输出:

Goods name: apple
Goods quantity: 2.50

Goods name: balls
Goods quantity: 100

工作原理:

在第 7-12 行中,宣布了一个有三个成员的联盟quantity,即int类型的countfloat类型的weightfloat类型的volume

在第 14-18 行中,结构goods2成员声明,即由字符组成的名称和类型union数量的w

在第 22 行,结构变量g1被声明和初始化。重要的是要注意如何使用指定的初始化器来初始化联合的weight成员。如果我们想要初始化第一个元素,我们应该这样做:

struct goods g1 = { "apple", {112} };

或者

struct goods g1 = { "apple", 112 };

在第 23 行,结构变量g2被声明和初始化。

在第 25 行和第 26 行,第一批货物的nameweight使用printf()语句打印。

类似地,在第 28 行和第 29 行中,第二货的nameweight使用printf()语句打印。



C 语言中的typedef语句

原文:https://overiq.com/c-programming-101/typedef-statement-in-c/

最后更新于 2020 年 7 月 27 日


typedef是 C 语言中的一个高级特性,它允许我们为现有类型或用户定义的类型创建别名或新名称。typedef 的语法如下:

语法: typedef data_type new_name;

typedef:是关键字。
data_type:是使用结构/联合创建的任何现有类型或用户定义类型的名称。
new_name:您要为任何现有类型或用户定义类型指定的别名或新名称。

让我们举个例子:

typedef int myint;

现在myintint的别名。从现在开始,我们可以使用myint而不是int关键字来声明新的int变量。

myint i = 0; // this statement is equivalent to int i = 0;

此语句声明并初始化类型为int的变量i

我们甚至可以为同一类型创建多个别名。例如:

typedef int myint, integer;

该语句为类型int创建两个别名,即myint和整数。

以下是更多的例子:

typedef unsigned long int ulint;
typedef float real;

这两个声明之后,ulintunsigned long int的别名,realfloat的别名。

我们可以在任何允许其他声明的地方写typedef声明。但是,需要注意的是,声明的范围取决于typedef语句的位置。如果定义放在所有函数之外,那么作用域是全局的,任何函数都可以使用别名而不是原始名称。另一方面,如果定义是在函数内部声明的,那么作用域是局部的,并且只有包含typedef语句的函数可以使用别名。请考虑以下示例:

示例 1:使用 typedef 声明本地别名

#include<stdio.h>
void foo(void);

int main()
{
    typedef unsigned char uchar;
    uchar ch = 'a';
    printf("ch inside main() : %c\n", ch);
    foo();
    return 0;
}

void foo(void)
{
    // uchar ch = 'a'; // Error
    unsigned char ch = 'z';
    printf("ch inside foo() : %c\n", ch);
}

预期输出:

ch inside main() : a
ch inside foo() : z

这里typedef的定义在main()函数里面,所以我们只能在main()里面使用别名uchar。尝试取消第 15 行的注释并编译程序,您将从编译器得到一个错误,因为别名ucharfoo()函数中不可用。

示例 2:使用 typedef 声明全局别名

#include<stdio.h>

typedef unsigned char uchar;
void foo(void);

int main()
{
    uchar ch = 'a';
    printf("ch inside main() : %c\n", ch);
    foo();
    return 0;
}

void foo(void)
{
    uchar ch = 'z';
    printf("ch inside foo() : %c\n", ch);
}

预期输出:

ch inside main() : a
ch inside foo() : z

这里typedef声明高于所有函数,所以任何函数都可以使用别名uchar声明类型为unsigned char的变量。

我们已经在下面的小节中看到了如何为简单类型声明别名,我们将学习如何为指针、函数、结构和联合定义别名。

带指针的 typedef

typedef int * iptr;

在这个语句之后iptr是指向int(int*)的指针的别名。下面是我们如何使用iptr声明一个整数指针:

iptr p;

该声明与以下声明相同:

int *p;

以下是更多的例子:

iptr a, *b; // same as int *a, **b;
iptr arr[10]; // same as int *arr[10];

在第一个声明中,a是指向int的指针,b是指向int的指针。在第二个声明中,arr是一个由10整数指针组成的数组。

这里有一个例子:

#include<stdio.h>
typedef int * iptr;

int main()
{
    int a = 99;
    iptr p; // same as int *p
    p = &a;

    printf("%u\n", p);
    printf("%d\n", *p);

    return 0;
}

预期输出:

2686788
99

带数组的 typedef

typedef int iarr[10];

在这个声明之后,iarr10整数元素数组的别名。

iarr a, b, c[5]; // same as int a[10], b[10], c[10][5];

在这个声明中,ab10整数数组,c是二维数组10*5

这里有一个例子:

#include<stdio.h>
typedef int iarr[10];

int main()
{
    int i;

    // same as int a[10] = {12,43,45,65,67,87,89,91,14,19}
    iarr a = {12,43,45,65,67,87,89,91,14,19}; 

    for(i = 0; i < 10; i++)
    {
        printf("a[%d] = %d\n",i ,a[i]);
    }
    return 0;
}

预期输出:

a[0] = 12
a[1] = 43
a[2] = 45
a[3] = 65
a[4] = 67
a[5] = 87
a[6] = 89
a[7] = 91
a[8] = 14
a[9] = 19

具有结构的类型定义

struct book
{
    char title[20];
    char publisher[20];
    char author[20];
    int year;
    int pages;
};

typedef struct book Book;

本次申报后,Bookstruct book的别名。所以我们可以不用struct book来声明新的结构变量,而只用Book

Book b1 = {"The Alchemist", "TDM Publication" , "Paulo Coelho", 1978, 331 };

我们也可以结合结构定义和typedef声明。这样做的语法是:

typedef struct tagname 
{
    data_type member1;
    data_type member1;
    ...
} newname;

让我们使用typedef的新语法重写结构书定义。

typedef struct book
{
    char title[20];
    char publisher[20];
    char author[20];
    int year;
    int pages;
} Book;

下面是演示如何将typedef用于结构的程序。

#include<stdio.h>

typedef struct book
{
    char title[20];
    char publisher[20];
    char author[20];
    int year;
   int pages;
} Book;

int main()
{

    Book b1 = {
                "The Zahir",
                "Harper Perennial" ,
                "Paulo Coelho",
                 2005,
                 336
              };

    printf("Title: %s\n", b1.title);
    printf("Author: %s\n", b1.author);

    return 0;
}

预期输出:

Title: The Zahir
Author: Paulo Coelho

同样,我们可以用typedef搭配联合。

typedef 和#define

值得一提的是typedef不是预处理器指令,所以它的解释是由编译器处理的,而不是预处理器。回想一下#define指令允许我们为任何文本定义扩展,另一方面typedef用于为任何数据类型创建别名。

但是也有#definetypedef产生相同结果的情况。

以下是一个这样的例子:

#定义指令 typedef 声明
#define uchar unsigned char typedef unsigned char uchar;
测试语句 uchar ch; uchar ch;
翻译后 unsigned char ch; unsigned char ch;

这里是#definetypedef产生不同结果的情况。

#定义指令 typedef 声明
#define fp float * typedef float * fp;
测试语句 fp a, b, c; fp a, b, c;
翻译后 float *a, b, c; float *a, *b, *c;

在第二种情况下,只要预处理器看到该语句。

fp a, b, c;

它用float *代替fp的出现。所以上面的声明就变成了。

float *a, b, c;

另一方面,typedef有更多的语义含义,所以编译器不会像预处理器那样只是替换。

下面的程序演示了#definetypedef的区别。

#include<stdio.h>
#define ptr int * // replace occurence of ptr by int *
typedef int * iptr; // iptr is an alias of pointer to int or int*

int main()
{
    ptr a, b, c; // same as int *a, b, c;
    iptr p1, p2, p3; // same as int *p1, *p2, *p3

    b = 10;
    c = 20;

    a = &b;
    p1 = &b;

    p2 = &c;
    p3 = &c;

    printf("Value at a = %d\n", *a); // print value of b
    printf("Value at p2 = %d\n", *p2); // print value of b

    return 0;
}

预期输出:

Value at a = 10
Value at p2 = 20

工作原理:

当预处理器浏览程序并看到声明时:

ptr a, b, c;

它将ptr替换为int *,这样上面的声明就变成了:

int *a, b, c;

其中只有a是指向int的指针,bc只是int类型的变量。

相反在下面的声明中。

iptr p1, p2, p3;

编译器知道iptr是指向int的指针的别名,所以p1p2p3是类型为int的指针变量。

typedef 的优点

它使程序更易读。当然,Book b1比写struct book b1更易读直观。

它使程序可移植。让我解释一下。看看sizeof()运算符和malloc()函数的原型。

size_t sizeof(type);

void *malloc(size_t size);

如你所见,这两个原型都使用类型size_t,我们已经告诉你把size_t当作unsigned int,但这并不完全正确。C 标准规定sizeof()必须返回一个整数,但是由实现来决定返回哪种类型。这样做的原因是,C 标准委员会决定没有选择可能是每个平台的最佳选择。因此,他们创建了一个新的类型,如size_ttime_t等,并让实现使用一个typedef将名称设置为某个特定的类型。所以size_t的一个系统类型可以是unsigned int,另一个系统类型可以是unsigned long int



文件处理

C 语言文件处理基础

原文:https://overiq.com/c-programming-101/basics-of-file-handling-in-c/

最后更新于 2020 年 7 月 27 日


到目前为止,我们创建的所有程序都受到严重限制,因为它们不能读写文件中的数据。大多数真实世界的程序都需要在文件中读写数据。在本章中,我们将学习如何在文件中执行输入和输出操作。c 在头文件 stdio.h 中提供了广泛的函数,用于在文件中读写数据。文本和二进制模式我们可以通过两种方式将数据存储到文件中:

  1. 文本模式。
  2. 二元模式。

在文本模式下,数据存储为以换行符(' \n ')结尾的一行字符,其中每个字符占用1字节。在文本文件中存储1234需要4字节,每个字符需要1字节。在文本模式下需要注意的重要事情是存储在内存中的是字符的二进制等价 ASCII 码。以下是123456如何以文本模式存储在文件中。

如您所见,在文本模式下存储123456需要 6 个字节。

在二进制模式下,数据存储在磁盘上的方式与存储在计算机内存中的方式相同。因此,以二进制模式存储123456只需要2字节。在 Linux 系统中,文本模式和二进制模式没有区别。以下是 123456 如何以二进制模式存储在文件中。

因此,通过使用二进制模式,我们可以节省大量磁盘空间。

在文本文件中,每行以一个或两个特殊字符结尾。在 Windows 系统中,每一行都以回车('\r')结束,后跟换行符('\n')。另一方面,在 Unix、Linux 和 Mac 操作系统中,每行都以一个换行符('\n')结尾。

文本文件和二进制文件跟踪文件的长度,也跟踪文件的结尾。但是在文本文件中,还有一种方法可以检测文件的结尾。文本文件可能包含一个特殊的文件结束标记,其 ASCII 值为26。当遇到该字符时,所有输入/输出功能停止从文件中读取,并向程序返回文件结束信号。没有输入/输出功能会自动插入该字符。在 Windows 中,可以使用 Ctrl+Z 将该字符插入文件中,需要注意的是,并没有要求 Ctrl+Z 必须存在,但如果存在,则认为是文件的结尾。Ctrl+Z 之后的任何字符都不会被读取。另一方面,基于 Unix 的操作系统具有如此特殊的文件结尾特征。

你需要记住的另一件重要的事情是文本文件是可移植的。这意味着您可以使用 Windows 创建一个文本文件,并在 Linux 系统中打开它,没有任何问题。另一方面,数据类型的大小和字节顺序可能因系统而异,因此二进制文件不可移植。

缓冲存储器

与读写存储在内存中的数据相比,读写存储在磁盘中的文件是一个相对较慢的过程。因此,所有标准的输入/输出函数都使用一种叫做缓冲存储器的东西来临时存储数据。

缓冲存储器是一种在数据写入文件之前临时存储数据的存储器。当缓冲存储器已满时,数据被写入(刷新)文件。此外,当文件关闭时,即使缓冲区已满或未满,缓冲存储器中的数据也会自动写入文件。这个过程称为刷新缓冲区。

您不必做任何事情来创建缓冲内存。一旦你打开一个文件,后台会自动为你创建一个缓冲存储器。但是,在一些罕见的情况下,您必须手动刷新缓冲区。如果是这样,您可以调用使用fflush()函数,如下所述。

打开文件

在对文件执行任何输入/输出(缺少输入/输出)之前,您必须先打开文件。fopen()功能用于打开一个文件。

语法: FILE *fopen(const char *filename, const char *mode);

filename:包含文件名的字符串。
mode:它指定你想对文件做什么,即读、写、追加。

成功时fopen()函数返回一个指向FILE类型结构的指针。FILE结构在stdio.h中定义,包含关于文件的信息,如名称、大小、缓冲区大小、当前位置、文件结尾等。

出错时fopen()功能返回NULL

模式的可能值是:

  1. "w"(写入)-该模式用于将数据写入文件。如果文件不存在,此模式会创建一个新文件。如果文件已经存在,那么这种模式首先清除文件中的数据,然后再写入任何内容。
  2. "a"(追加)-该模式称为追加模式。如果文件不存在,此模式会创建一个新文件。如果文件已经存在,那么这种模式会在文件末尾追加新数据。
  3. "r"(读取)-该模式打开文件进行读取。要以这种模式打开文件,文件必须已经存在。无论如何,此模式不会修改中的文件内容。如果您只想读取文件的内容,请使用此模式。
  4. "w+"(写+读)-这个模式和"w"是一个相同的模式,但是在这个模式下,你也可以读取数据。如果文件不存在,此模式会创建一个新文件。如果该文件已经存在,则在写入新数据之前,会删除以前的数据。
  5. "r+"(读+写)-该模式与"r"模式相同,但也可以修改文件内容。要以这种模式打开文件,文件必须已经存在。您可以在此模式下修改数据,但文件的先前内容不会被删除。这种模式也称为更新模式。
  6. "a+"(追加+读取)-该模式与"a"模式相同,但在该模式下,您也可以从文件中读取数据。如果文件不存在,则会创建一个新文件。如果文件已经存在,那么新数据将被附加到文件的末尾。请注意,在这种模式下,您可以追加数据,但不能修改现有数据。

要以二进制模式打开文件,您需要将"b"附加到如下模式:

方式 描述
"wb" 以二进制模式打开文件
"a+b""ab+" 以追加+二进制读取模式打开文件

要以文本模式打开文件,您可以将"t"附加到该模式。但是由于文本模式是默认模式"t"一般省略。

方式 描述
"w""wt" 以文本模式打开文件进行写入
"r""rt" 以文本模式打开文件进行阅读。

注意:模式是一个字符串,所以必须始终用双引号括起来。

fopen("somefile.txt", 'r'); // Error
fopen("somefile.txt", "r"); // Ok

如果您想同时打开几个文件,那么它们必须有自己的文件指针变量,该变量是使用对fopen()函数的单独调用创建的。

File fp1 = fopen("readme1.txt", 'r');
File fp2 = fopen("readme2.txt", 'r');
File fp3 = fopen("readme3.txt", 'r');

这里我们创建了 3 个文件指针,用于读取三个文件。

检查错误

如前所述,当打开文件时遇到错误时fopen()返回NULL。因此,在对文件执行任何操作之前,您必须始终检查错误。

// Checking for errors

File *fp;
fp = fopen("somefile.txt", "r");

if(fp == NULL)
{
    // if error opening the file show error message and exit the program
    printf("Error opening a file");
    exit(1);
}

我们也可以给fopen()函数取完整的路径名。假设我们要打开位于/home/downloads/的文件list.txt。以下是如何在fopen()中指定路径名。

fp = fopen("/home/downloads/list.txt", "r");

需要注意的是,Windows 使用反斜杠字符('\')作为目录分隔符,但是由于 C 将反斜杠作为转义序列的开始,所以我们不能在字符串内部直接使用'\'字符。要解决这个问题,用两个'\'代替一个'\'

fp = fopen("C:\home\downloads\list.txt", "r");    // Error
fp = fopen("C:\\home\\downloads\\list.txt", "r"); // ok

另一种技术是使用正斜杠('/')代替反斜杠('\')。

fp = fopen("C:/home/downloads/list.txt", "r");

关闭文件

当你处理完一个文件后,你应该立即使用fclose()功能关闭它。

语法: int fclose(FILE *fp);

当一个文件关闭时,与之相关的所有缓冲区都会被刷新,即缓冲区中的数据会被写入文件。

通过fclose()关闭文件并不是强制性的,因为程序一结束,所有文件都会立即关闭。但是,最好在处理完文件后立即将其关闭。否则,在大型程序中,您将浪费大量空间给未使用的文件指针。

成功时fclose()返回0,出错时返回EOF,即stdio.h中定义的常量文件结束,其值为-1

如果您打开了多个文件,那么您必须像这样分别关闭它们:

fclose(fp1);
fclose(fp2);

也可以使用fcloseall()功能一次关闭所有打开的文件。

成功时fcloseall()返回关闭的文件数,出错时返回EOF

下面是我们如何检查是否所有文件都已关闭。

// check whether all files are closed or not

n = fcloseall();

if(n == EOF)
{
    printf("Error in closing some files \n");
}

else
{
    printf("%d files closed \n", n);
}

文件结束-电渗流

文件读取功能需要知道文件何时到达末尾。因此,当到达文件结尾时,操作系统向程序发送文件结尾或EOF信号以停止读取。当程序收到信号时,文件读取功能停止读取文件并返回EOF。如前所述EOF是 stdio.h 头文件中定义的常量,数值为-1。重要的是要理解EOF不是出现在文件末尾的字符,而是在到达文件末尾时由文件读取功能返回。

一个 C 语言文件程序的基本工作流程

// Basic workflow of a file program in C

int main()
{
    FILE *fp; // declare file pointer variable
    fp = fopen("somefile.txt", "w"); // fopen() function called

    /*
       do something here
    */

    fclose(fp); // close the file
}



C 语言中的fputc()函数

原文:https://overiq.com/c-programming-101/fputc-function-in-c/

最后更新于 2020 年 7 月 27 日


fputc()函数的语法如下:

语法: int fputc(int ch, FILE *fp);

fputc()函数用于将第一个参数指定的单个字符写入 fp 指针指向的文本文件。将字符写入文本文件后,它会递增内部位置指针。如果写入成功,它将返回所写入字符的 ASCII 值。出错时,返回EOF,即-1

虽然fputc()的正式定义说“它向文件中写入单个字符”,但这不是它的实现方式。实际上,一个一个地写一个字符是非常低效和缓慢的。相反,如果将字符一个接一个地写入文件,它们会累积在缓冲存储器中。一旦字符数达到一个合理的数字,它们就会一次性写入文件。

举个例子吧。

下面的程序演示了如何使用fputc()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int ch;
    FILE *fp;
    fp = fopen("myfile.txt", "w");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Press Ctrl+Z in DOS and Ctrl+D\n\
    in Linux to stop reading more characters\n\n");

    printf("Enter text: ");

    while( (ch=getchar()) != EOF )
    {
        fputc(ch, fp);
    }

    fclose(fp);

    return 0;

}

预期输出:

Press Ctrl+Z in DOS and Ctrl+D
in Linux to stop reading more characters

Enter text: Testing fputc()
function
^D

工作原理:

在第 6 行,声明了类型为int的变量ch

在第 7 行,声明了类型为struct FILE的结构指针变量fp

在第 8 行中,fopen()函数用两个参数调用,即"myfile.txt""w"。成功后,它返回一个指向文件myfile.txt的指针,并以只写模式打开文件"myfile.txt"。故障时,返回NULL

在第 10 行,if 语句用于测试 fp 的值。如果是NULLprintf()语句打印错误信息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 16 行和第 19 行中,printf()语句将字符串打印到控制台。

在第 21 行,while 循环与getchar()一起使用。while 循环从标准输入中读取字符并将其写入文件。请注意 while 循环的条件:

(ch=getchar()) != EOF

ch=getchar()周围的括号是必要的,因为!=运算符的优先级大于=运算符。

while 循环将继续从标准输入中读取字符,直到在 Windows 中输入 Ctrl+Z 或在 Linux 中输入 Ctrl+D。回想一下getchar()函数返回刚从标准输入中读取的字符的 ASCII 值,当遇到文件结束字符时返回EOF。一旦您输入文件结尾字符,条件就被评估为 false,并且控件会跳出 while 循环。

在第 26 行,调用fclose()函数关闭文件。

需要注意的是字符^D没有写入文件。



C 中的fgetc()函数

原文:https://overiq.com/c-programming-101/fgetc-function-in-c/

最后更新于 2020 年 7 月 27 日


fgetc()函数的语法是:

语法: int fgetc(FILE *fp);

该功能与fputc()功能互补。它从文件中读取一个字符,并递增文件位置指针。要使用此功能,文件必须以读取模式打开。成功后,它会返回字符的 ASCII 值,但您也可以将结果分配给类型为char的变量。在失败或文件结束时,它返回EOF-1

正如fputc()这个函数也使用缓冲存储器。因此,不是从文件中逐个读取单个字符,而是将文件中的整个字符块读入缓冲区。然后一次将一个字符交给功能fgetc(),直到缓冲区为空。如果文件中仍有一些字符需要读取,则再次将一个字符块读入缓冲区。

下面的程序演示了如何使用fgetc()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int ch;
    FILE *fp;
    fp = fopen("myfile.txt", "r");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Reading contents of myfile.txt: \n\n");

    while( (ch=fgetc(fp)) != EOF )
    {
        printf("%c", ch, ch);
    }

    fclose(fp);
    return 0;
}

预期输出:

Reading contents of myfile.txt:

Testing fputc() function

工作原理:

在第 6 行,声明了类型为int的变量ch

在第 7 行,声明了类型为struct FILE的结构指针变量fp

在第 8 行中,fopen()函数用两个参数调用,即"myfile.txt""r"。成功后,它返回一个指向文件"myfile.txt"的指针,并以只读模式打开文件"myfile.txt"。失败或文件结束时,返回NULL

第 10 行,if 语句用于测试fp的值。如果是NULLprintf()语句打印错误信息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 16 行中,printf()语句将"Reading contents of myfile.txt: \n\n"打印到控制台。

在第 18-21 行,使用 while 循环从文件中逐个读取字符,并使用printf()语句将其打印到控制台(也可以使用 putchar()函数)。ch = fgetc(fp)周围的括号是必要的,因为!=运算符的优先级大于=运算符。

第 23 行,fclose()功能用于关闭文件。



C 中的fputs()函数

原文:https://overiq.com/c-programming-101/fputs-function-in-c/

最后更新于 2020 年 7 月 27 日


fputs()函数的语法是:

语法: int fputc(const char *str, FILE *fp);

该函数用于将字符串打印到文件中。它接受指向字符串和文件指针的两个参数。它将由str指向的空终止字符串写入文件。空字符不会写入文件。成功后,返回0。出错时,返回EOF-1

下面的程序演示了如何使用fputs()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    char str[50];
    FILE *fp;
    fp = fopen("myfile2.txt", "w");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fputs() function: \n\n");
    printf("To stop reading press Ctrl+Z in windows and Ctrl+D in Linux :");

    while( gets(str) != NULL )
    {
        fputs(str, fp);
    }

    fclose(fp);
    return 0;
}

预期输出:

Testing fputs() function:

To stop reading press Ctrl+Z in windows and Ctrl+D in Linux :

The first line
The second line
Third line
^D

工作原理:

在第 6 行,声明了一个大小为50的字符数组str

在第 7 行,声明了类型为struct FILE的结构指针变量fp

在第 8 行中,fopen()函数用两个参数调用,即"myfile2.txt""w"。成功后,它返回一个指向文件myfile2.txt的指针,并以只写模式打开文件myfile.txt。失败或文件结束时,返回NULL

第 10-14 行,if 语句用于测试fp的值。如果是NULLprintf()语句打印错误信息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 16 行和第 17 行,两个printf()语句将"Testing fputs() function: \n\n""To stop reading press Ctrl+Z in windows and Ctrl+D in Linux : \n\n"串到控制台。

在第 19-22 行,我们有 while 循环和gets()函数。while 循环将继续请求更多的字符串,直到它计数一个文件结束字符。关于 get()函数,需要记住两件重要的事情:

  1. gets()函数将输入的换行符转换为空字符('\0')。
  2. 当遇到文件结束字符时gets()返回NULL

以下是 while 循环的工作原理:

当输入第一行"The first line"后接换行符时,gets()函数将换行符('\n')转换为空字符('\0')。此时,str包含"The first line\0",然后写入文件。然后使用fputs()功能将字符串写入文件。需要注意的是fputs()函数不会将空字符'\0'写入文件。当gets()函数遇到文件结束字符时,while 条件变为假,控制退出循环。

在第 24 行,fclose()函数关闭文件指针。

put()和 fputs()之间的差异

回想一下,在前面的章节中,我们已经多次使用puts()函数将字符串打印到控制台上。fputs()puts()的重要区别在于puts()将字符串中的空字符('\0')转换为换行符('\n'),而fputs()则没有。



C 中的fgets()函数

原文:https://overiq.com/c-programming-101/fgets-function-in-c/

最后更新于 2020 年 7 月 27 日


fgets()函数的语法是:

语法: char *fgets(char *str, int n, FILE *fp);

该函数从fp指向的文件中读取一个字符串到str指向的内存中。该函数从文件中读取字符,直到读取换行符('\n')或读取n-1字符或遇到文件结尾,以先发生的为准。读取字符串后,它会附加空字符('\0')来终止字符串。成功后,它会返回一个指向str的指针。出错或文件结束时,它返回NULL

下面的程序演示了如何使用fgets()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    char str[50];
    FILE *fp;
    fp = fopen("myfile2.txt", "r");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fgets() function: \n\n");
    printf("Reading contents of myfile.txt: \n\n");

    while( fgets(str, 30, fp) != NULL )
    {
        puts(str);
    }

    fclose(fp);

    return 0;
}

预期输出:

Testing fgets() function:

Reading contents of myfile.txt:

The first lineSecond lineThis
is thirdand this is fourth

工作原理:

在第 6 行,声明了一个大小为 50 的字符数组str

在第 7 行,声明了类型为struct FILE的结构指针变量fp

在第 8 行中,fopen()函数用两个参数调用,即"myfile2.txt""r"。成功后,它返回一个指向文件myfile2.txt的指针,并以只读模式打开文件myfile.txt。失败或文件结束时,返回NULL

第 10-14 行,if 语句用于测试fp的值。如果是NULLprintf()语句打印错误信息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 16 行和第 17 行,两个 printf()语句将"Testing fgets() function: \n\n""Reading contents of myfile.txt: \n\n"串到控制台。

在第 19-22 行,while 循环用于读取 myfile2.txt 的内容。

调用函数fgets()的参数为 30,因此它从文件中读取 29 个字符,并通过在末尾添加空字符将其存储在数组字符串中。然后调用puts()功能显示字符串的内容。puts()函数通过将字符串末尾的空字符(“\0”)转换为换行符(“\n”)将字符串打印到控制台。这就是为什么每次迭代后都会打印换行符的原因。第二次迭代fgets()读取 27 个字符后遇到文件结尾。再次调用puts()函数,打印这 27 个字符以及末尾的换行符(' \n ')。第三次再次调用fgets()函数,但是由于没有剩余的字符可以读取,它返回NULL,while 条件变为假,控制从 while 循环中出来。

第 24 行,fclose()功能用于关闭指向myfile2.txt的文件指针。

fgets()gets()功能的区别

gets()从标准输入中读取输入,而fgets()从文件中读取。除此之外,最重要的区别是:

gets()从标准输入中读取输入时,它会将换行符(' \n ')转换为空字符(' \0 ');另一方面,当fgets()从文件中读取换行符(' \n ')时,它不会将其转换为空字符(' \ 0 '),而是保持不变。



C 语言中的fprintf()函数

原文:https://overiq.com/c-programming-101/fprintf-function-in-c/

最后更新于 2020 年 7 月 27 日


格式化文件输入和输出

至此,我们已经看到了如何读写文件中的字符和字符串。在现实世界中,数据由许多不同的类型组成。在本章中,我们将学习如何以格式化的方式输入和输出不同类型的数据。当我们想要以特定格式读取或写入数据时,我们使用格式化的输入和输出。

fprintf()函数

语法: int fprintf(FILE *fp, const char *format [, argument, ...] );

fprintf()功能与printf()相同,但它不是将数据写入控制台,而是将格式化数据写入文件。fprintf()函数的几乎所有参数都与printf()函数相同,除了它有一个额外的参数,该参数是指向格式化输出将被写入的文件的文件指针。成功后,它返回写入文件的字符总数。出错时,返回EOF

下面的程序演示了如何使用fprintf()功能。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    FILE *fp;
    char name[50];
    int roll_no, chars, i, n;
    float marks;

    fp = fopen("records.txt", "w");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fprintf() function: \n\n");

    printf("Enter the number of records you want to enter: ");
    scanf("%d", &n);

    for(i = 0; i < n; i++)
    {
        fflush(stdin);
        printf("\nEnter the details of student %d \n\n", i +1);

        printf("Enter name of the student: ");
        gets(name);

        printf("Enter roll no: ");
        scanf("%d", &roll_no);

        printf("Enter marks: ");
        scanf("%f", &marks);

        chars = fprintf(fp, "Name: %s\t\tRoll no: %d\t\tMarks: %.2f\n",
            name, roll_no, marks);
       printf("\n%d characters successfully written to the file\n\n", chars);
    }

    fclose(fp);
    return 0;
}

预期输出:

Testing fprintf() function:

Enter the number of records you want to enter: 5

Enter the details of student 1

Enter name of the student: Tina
Enter roll no: 1
Enter marks: 45

37 characters successfully written to the file

Enter the details of student 2

Enter name of the student: Nina
Enter roll no: 5
Enter marks: 89

37 characters successfully written to the file

Enter the details of student 3

Enter name of the student: Tim
Enter roll no: 2
Enter marks: 49

36 characters successfully written to the file

Enter the details of student 4

Enter name of the student: Jim
Enter roll no: 8
Enter marks: 41

36 characters successfully written to the file

Enter the details of student 5

Enter name of the student: King
Enter roll no: 9
Enter marks: 59

37 characters successfully written to the file

工作原理:

在第 6 行,结构指针变量 fp 被声明为 struct FILE 类型。

在第 7 行,声明了一个大小为 50 的字符名称数组。

在第 8 行中,声明了四个变量,即 roll_no、chars、I 和 int 类型的 n。

在第 9 行中,声明了 float 类型的变量标记。

在第 11 行,fopen()函数用两个参数调用,即“records.txt”和“w”。成功后,它返回一个指向 file records.txt 的指针,并以只写模式打开 file records.txt。失败时,它返回空值。

在第 13-17 行,if 语句用于测试 fp 的值。如果为空,printf()语句将打印错误消息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 19 行,printf()语句将“Testing fprintf()函数:\n\n”打印到控制台。

在第 21-22 行,程序要求用户输入他想输入其记录的学生人数。

在第 24-41 行,for 循环要求用户输入三条信息名称、roll_no 和相应学生的标记。在第 26 行中,我们正在刷新(删除)标准输入的内容,这一行是必要的,否则第 30 行中的 get()函数将读取换行符(在询问学生人数时输入),并且不会等待用户输入学生的姓名。

在第 38 行,调用 fprintf()函数和 5 个参数,将格式化数据写入文件。如果数据已成功写入文件,它将返回写入文件的字符数,然后将其分配给可变字符。在第 40 行中,printf()语句打印由 fprintf()函数的前一次调用写入文件的字符总数。循环将继续要求更多的学生记录,直到 i < n。一旦 n 变得大于 I,控制就从 for 循环中出来。

在第 43 行,fclose()函数关闭文件。



C 语言中的fscanf()函数

原文:https://overiq.com/c-programming-101/fscanf-function-in-c/

最后更新于 2020 年 7 月 27 日


该函数的语法是:

语法: int fscanf(FILE *fp, const char *format [, argument, ...] );

fscanf()功能用于从文件中读取格式化的输入。它的工作原理与scanf()功能类似,但它不是从标准输入中读取数据,而是从文件中读取数据。事实上,fscanf()函数的大多数参数与scanf()函数相同,只是它需要一个额外的参数,显然足够一个文件指针。成功时,此函数返回读取的值的数量,出错时或文件结束时,它返回EOF-1

下面的程序演示了如何使用fscanf()函数从文件中读取格式化数据。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    FILE *fp;
    char name[50];
    int roll_no, chars;
    float marks;

    fp = fopen("records.txt", "r");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fscanf() function: \n\n");
    printf("Name:\t\tRoll\t\tMarks\n");

    while( fscanf(fp, "Name: %s\t\tRoll no: %d\t\tMarks: %f\n"
                    , name, &roll_no, &marks) != EOF )
    {
        printf("%s\t\t%d\t\t%.2f\n", name, roll_no ,marks);
    }

    fclose(fp);
    return 0;
}

预期输出:

Name: Tina      Roll no: 1       Marks: 45.00
Name: Nina      Roll no: 5       Marks: 89.00
Name: Tim       Roll no: 2       Marks: 49.00
Name: Jim       Roll no: 8       Marks: 41.00
Name: King      Roll no: 9       Marks: 59.00

工作原理:

在第 6 行中,声明了类型为struct FILE的结构指针fp

在第 7 行中,声明了一个大小为 50 的字符名称数组。

在第 8 行,声明了两个 int 变量 roll_no 和 chars。

在第 9 行中,声明了 float 类型的变量标记。

在第 11 行,fopen()函数用两个参数调用,即“records.txt”和“r”。成功后,它返回一个指向文件 records.txt 的指针,并以只读模式打开文件 records.txt。失败时,它返回空值。

在第 13-17 行,if 语句用于测试 fp 的值。如果为空,printf()语句将打印错误消息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 19 行和第 20 行,我们有两个 printf()语句,将“Testing fscanf()函数:\n\n”和“Name:\ t \ tRoll \ t \ t 说明\n”打印到控制台。

在第 22-26 行中,while 循环与 fscanf()函数一起用来读取文件中的格式化数据,并将它们存储在变量名 roll_no 和 marks 中。printf()语句然后被用户用来打印从文件中读取的数据。fscanf()一直读取,直到遇到 EOF。当条件变为假并且控制脱离循环时,遇到文件结尾。

在第 28 行,调用 fclose()函数关闭文件。



C 中的fwrite()函数

原文:https://overiq.com/c-programming-101/fwrite-function-in-c/

最后更新于 2020 年 7 月 27 日


二进制输入和输出

到目前为止,我们一直使用文本模式来读写文件中的数据。在本章中,我们将学习如何使用二进制模式在文件中读写数据。回想一下,在二进制模式下,数据存储在文件中的方式与存储在内存中的方式相同,因此在二进制模式下不会发生数据转换。由于没有转换发生,二进制模式比文本模式快得多。

[img]

fread()fwrite()函数通常分别用于向文件读写二进制数据。虽然我们也可以在文本模式下使用它们。

先说fwrite()功能。

fwrite()函数

语法: size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);

fwrite()函数将 void 指针ptr指定的数据写入文件。

ptr:指向包含待写数据项的内存块。

size:指定每个要写入的项目的字节数。

n:是要写的项目数。

fp:是指向将要写入数据项的文件的指针。

成功时,它返回成功写入文件的项目数。出错时,它返回一个小于n的数字。注意两个参数(sizen)和fwrite()的返回值属于size_t类型,在大多数系统上是unsigned int

为了更好地理解fwrite()函数,请考虑以下示例:

例 1:写变量

float *f = 100.13;

fwrite(&p, sizeof(f), 1, fp);

这会将变量f的值写入文件。

例 2:写数组

int arr[3] = {101, 203, 303};

fwrite(arr, sizeof(arr), 1, fp);

这会将整个数组写入文件。

例 3:写数组的一些元素

int arr[3] = {101, 203, 303};

fwrite(arr, sizeof(int), 2, fp);

这只会将数组的前两个元素写入文件。

例 4:书写结构

struct student
{
    char name[10];
    int roll;
    float marks;
};

struct student student_1 = {"Tina", 12, 88.123};

fwrite(&student_1, sizeof(student_1), 1, fp);

这会将变量student_1的内容写入文件。

例 5:写入结构数组

struct student
{
    char name[10];
    int roll;
    float marks;
};

struct student students[3] = {
                                 {"Tina", 12, 88.123},
                                 {"Jack", 34, 71.182},
                                 {"May", 12, 93.713}
                             };

fwrite(students, sizeof(students), 1, fp);

这会将整个数组的学生写入文件。

假设我们不想将数组的所有元素都写入文件,而是只将数组的第 0 个和第 1 个元素写入文件。

fwrite(students, sizeof(struct student), 2, fp);

现在你已经明白fwrite()函数是如何工作的了。让我们使用fwrite()功能创建一个程序。

下面的程序演示了如何使用fwrite()功能。

#include<stdio.h>
#include<stdlib.h>

struct employee
{
    char name[50];
    char designation[50];
    int age;
    float salary
} employee;

int main()
{
    int n, i, chars;
    FILE *fp;

    fp = fopen("employee.txt", "wb");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fwrite() function: \n\n");

    printf("Enter the number of records you want to enter: ");
    scanf("%d", &n);

    for(i = 0; i < n; i++)
    {
        printf("\nEnter details of employee %d \n", i + 1);

        fflush(stdin);

        printf("Name: ");
        gets(employee.name);

        printf("Designation: ");
        gets(employee.designation);

        printf("Age: ");
        scanf("%d", &employee.age);

        printf("Salary: ");
        scanf("%f", &employee.salary);

        chars = fwrite(&employee, sizeof(employee), 1, fp);
        printf("Number of items written to the file: %d\n", chars);
    }

    fclose(fp);
    return 0;
}

预期输出:

Testing fwrite() function:

Enter the number of records you want to enter: 2

Enter details of employee 1
Name: Bob
Designation: Manager
Age: 29
Salary: 34000
Number of items written to the file: 1

Enter details of employee 2
Name: Jake
Designation: Developer
Age: 34
Salary: 56000
Number of items written to the file: 1

工作原理:

在第 4-10 行中,声明了一个结构雇员,它有四个成员,即名称是一个字符数组,名称也是一个字符数组,年龄是 int 类型,工资是 float 类型。除了结构定义,还声明了一个类型为 struct employee 的变量 emp。

在第 14 行,三个变量 n、I 和 chars 被声明为 int 类型。

在第 15 行,声明了一个 struct FILE 类型的结构指针 fp。

在第 17 行,fopen()函数用两个参数调用,即“employee.txt”和“wb”。成功后,它返回一个指向文件 employee.txt 的指针,并以只写模式打开文件 employee.txt。失败时,它返回空值。

在第 19-23 行,if 语句用于测试 fp 的值。如果为空,printf()语句将打印错误消息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 27-28 行,程序询问用户他/她想要输入多少条记录,并将数字存储在变量 n 中。

在第 30-50 行,for 循环中的语句要求用户输入四条信息,即姓名、职务、年龄和工资。请注意,在第 34 行中,调用 fflush()函数来刷新(删除)在第 28 行输入记录数时输入的标准输入中的换行符。如果没有对 fflush(stdin)的调用,那么第 37 行中的 get()函数将从标准输入中读取换行符,而不等待用户输入。在第 48 行,调用 fwrite()函数以二进制模式将结构变量 emp 写入文件。我们已经知道,在成功时,fwrite()返回写入文件的项目数。这里我们写的是单个结构变量的数据,所以 fwrite()将返回 1。出错时,它将返回一个小于 1 的数字。然后,fwrite()的返回值被赋给 chars 变量。在第 49 行,printf()语句打印成功写入文件的项目数。

第 52 行,fclose()功能用于关闭文件。



C 语言中的fread()函数

原文:https://overiq.com/c-programming-101/fread-function-in-c/

最后更新于 2020 年 7 月 27 日


fread()功能是fwrite()功能的补充。fread()函数常用于读取二进制数据。它接受与fwrite()函数相同的参数。fread()函数的语法如下:

语法: size_t fread(void *ptr, size_t size, size_t n, FILE *fp);

ptr是从文件中读取数据后将存储数据的存储块的起始地址。该函数从文件中读取n项,其中每个项占用第二个参数中指定的字节数。成功后,它从文件中读取n项并返回n。在错误或文件结束时,它返回一个小于n的数字。

让我们举一些例子:

示例 1:从文件中读取浮点值

int val;

fread(&val, sizeof(int), 1, fp);

这将从文件中读取一个float值,并将其存储在变量val中。

例 2:从文件中读取数组

int arr[10];

fread(arr, sizeof(arr), 1, fp);

这将从文件中读取一个10整数数组,并将其存储在变量arr中。

示例 3:读取数组的前 5 个元素

int arr[10];

fread(arr, sizeof(int), 5, fp);

这将从文件中读取5整数,并将其存储在变量arr中。

示例 4:读取数组的前 5 个元素

int arr[10];

fread(arr, sizeof(int), 5, fp);

这将从文件中读取5整数,并将其存储在变量arr中。

例 5:读取结构变量

struct student
{
    char name[10];
    int roll;
    float marks;
};

struct student student_1;

fread(&student_1, sizeof(student_1), 1, fp);

这将从文件中读取结构变量的内容,并将其存储在变量student_1中。

例 6:读取结构的数组

struct student
{
    char name[10];
    int roll;
    float marks;
};

struct student arr_student[100];

fread(&arr_student, sizeof(struct student), 10, fp);

这首先从文件中读取 student 类型的10元素,并将它们存储在变量arr_student中。

下面的程序演示了我们如何使用fread()功能。

#include<stdio.h>
#include<stdlib.h>

struct employee
{
    char name[50];
    char designation[50];
    int age;
    float salary
} emp;

int main()
{
    FILE *fp;
    fp = fopen("employee.txt", "rb");

    if(fp == NULL)
    {
        printf("Error opening file\n");
        exit(1);
    }

    printf("Testing fread() function: \n\n");

    while( fread(&emp, sizeof(emp), 1, fp) == 1 )
    {
        printf("Name: %s \n", emp.name);
        printf("Designation: %s \n", emp.designation);
        printf("Age: %d \n", emp.age);
        printf("Salary: %.2f \n\n", emp.salary);
    }

    fclose(fp);
    return 0;
}

预期输出:

Testing fread() function:

Name: Bob
Designation: Manager
Age: 29
Salary: 34000.00

Name: Jake
Designation: Developer
Age: 34
Salary: 56000.00

工作原理:

在第 4-10 行中,结构雇员和变量emp一起被声明。结构员工有四个成员,即:姓名、职务、年龄和工资。

在第 14 行,声明了类型为struct FILE的结构指针fp

在第 15 行中,fopen()函数用两个参数调用,即"employee.txt""rb"。成功后,它返回一个指向文件employee.txt的指针,并以只读模式打开文件employee.txt。故障时,返回NULL

第 17-21 行,if 语句用于测试fp的值。如果是NULLprintf()语句打印错误信息,程序终止。否则,程序继续执行 if 语句后面的语句。

在第 25-31 行中,while 循环与fread()一起用于读取文件的内容。fread()函数逐个读取存储在文件中的记录,并将其存储在结构变量emp中。fread()功能会一直返回 1,直到文件中有记录为止。一旦遇到文件的结尾fread()将返回一个小于 1 的值,while 循环中的条件变为假,控制从 while 循环中出来。

第 33 行,fclose()功能用于关闭文件。



C 编程示例

C 程序:求一个数的十进制位总和

原文:https://overiq.com/c-examples/c-program-to-find-the-sum-of-digits-of-a-number/

最后更新于 2020 年 9 月 23 日


下面的 C 程序计算一个数字的十进制位总和。

/************************************************
 * Program to find the sum of the digits of a number
*************************************************/

#include<stdio.h> // include stdio.h

int main()
{
    int n, remainder, sum = 0;

    printf("Enter a number: ");
    scanf("%d", &n);

    while(n != 0)
    {
        remainder = n % 10;
        sum += remainder;
        n = n / 10;
    }

    printf("sum = %d", sum);

    return 0;
}

预期输出:运行 1:

Enter a number: 12345
sum = 15

运行 2:

Enter a number: 99999   
sum = 45

现在试试

工作原理

下表演示了我们用来查找给定数字的十进制位总和的算法:

循环 剩余物 总额 n
第一次迭代后 remainder = 12345%10 = 5 sum = 0+5 = 5 n = 12345/10 = 1234
第二次迭代后 remainder = 1234%10 = 4 sum = 5+4 = 9 n = 1234/10 = 123
第三次迭代后 remainder = 123%10 = 3 sum = 9+3 = 12 n = 123/10 = 12
第四次迭代后 remainder = 12%10 = 2 sum = 12+2 = 14 n = 12/10 = 1
第五次迭代后 remainder = 1%10 = 1 sum = 14+1 = 15 n = 1/10 = 0

推荐阅读:



C 程序:计算一个数的阶乘

原文:https://overiq.com/c-examples/c-program-to-find-the-factorial-of-a-number/

最后更新于 2020 年 9 月 23 日


下面的 C 程序求一个数的阶乘:

/*******************************************************
 * Program to find the factorial of a number
********************************************************/

#include <stdio.h>

int main() 
{
    int n, i;
    unsigned long int fact = 1;

    printf("Enter a number: ");
    scanf("%d", &n);

    for(i = n; i > 0; i--)
    {        
        fact = fact * i;                
    }

    printf("%d! = %d", n, fact);

    return 0;
}

现在试试

预期输出第一次运行:

Enter a number: 5
5! = 120

第二次运行:

Enter a number: 8
8! = 40320

工作原理:

一个数的阶乘n定义为:

n! = n * (n-1) * (n-2) * (n-3) * ..... * 2 * 1

例如:

5! = 5 * 4 * 3 * 2 * 1 = 120
9! = 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 = 362880

下表演示了在循环的每次迭代中会发生什么(假设n = 5):

循环 事实
第一次迭代后 fact = 1 * 5 = 5 5
第二次迭代后 fact = 5 * 4 = 20 4
第三次迭代后 fact = 20 * 3 = 60 3
第四次迭代后 fact = 60 * 2 = 120 2
第五次迭代后 fact = 120 * 1 = 120 1

推荐阅读:



C 程序:寻找阿姆斯特朗数

原文:https://overiq.com/c-examples/c-program-to-find-armstrong-numbers/

最后更新于 2020 年 9 月 23 日


阿姆斯特朗的号码是多少?

阿姆斯特朗数是一个 3 位数的数字,因此它的每个数字的立方之和等于这个数字本身。例如:

153 = 1^3 + 5^3 + 3^3 = 153
370 = 3^3 + 7^3 + 0^3 = 370
371 = 3^3 + 7^3 + 1^3 = 371
407 = 4^3 + 0^3 + 7^3 = 407

下面是一个查找100999之间所有阿姆斯特朗数字的 C 程序:

/**************************************************************
 * Program to find the Armstrong numbers between 100 and 999 
***************************************************************/

#include <stdio.h>

int main() 
{

    int num, sum, rem;

    for(int n = 100; n < 999; n++)
    {
        num = n;
        sum = 0;
        while(num != 0)
        {
            rem = num % 10;     // get the last digit  |  sum += rem * rem * rem;  // cube the remainder and add it to the sum  |  num = num / 10;    // remove the last digit
        }

        if(n == sum)
        {
            printf("%d is an Armstrong number\n", n);
        }
    }

    return 0;
}

现在试试

预期输出:

153 is an Armstrong number
370 is an Armstrong number
371 is an Armstrong number
407 is an Armstrong number

工作原理:

下表演示了 while 循环(假设n = 153)每次迭代时发生的情况:

循环 雷姆 总额 数字
第一次迭代后 rem = 153%10 = 3 sum = 0 + 3^3 = 27 num = 153 / 10 = 15
第二次迭代后 rem = 15%10 = 5 sum = 27 + 5^3 = 152 num = 15 / 10 = 1
第三次迭代后 rem = 1%10 = 1 sum = 152 + 1^3 = 153 num = 1 / 10 = 0

推荐阅读:



C 程序:寻找素数

原文:https://overiq.com/c-examples/c-program-to-find-prime-numbers/

最后更新于 2020 年 9 月 23 日


什么是质数?

可被1整除或自身可整除的数称为质数。例如,11是素数,因为它只能被111整除。然而,数字6不是质数,因为它可以被23整除。以下是打印250之间质数的 C 程序。

/**************************************************************
 * Program to find the Prime numbers between 2 and 50
***************************************************************/

#include <stdio.h>

int main() 
{

    int is_prime = 1, n;

    for(int n = 2; n < 50; n++)
    {
        is_prime = 1;

        for(int i = 2; i < n; i++)
        {
            if(n % i == 0)
            {
                is_prime = 0;            
                break;
            }
        }

        if(is_prime)
        {
           printf("%d is prime\n", n);
        }   
    }

    return 0;
}

现在试试

预期输出:

2 is prime
3 is prime
5 is prime
7 is prime
11 is prime
13 is prime
17 is prime
19 is prime
23 is prime
29 is prime
31 is prime
37 is prime
41 is prime
43 is prime
47 is prime

工作原理:

为了检查一个质数,我们依次将一个数n2除以n-1。如果n不能被这个范围内的任何数整除,那么它就是一个质数。

例 1:5 是素数吗?

问题 声明 结果
Is 5 可被 2 整除 5 % 2 == 0 0(假)
Is 5 可被 3 整除 5 % 3 == 0 0(假)
Is 5 可以被 4 整除 5 % 4 == 0 0(假)

因此,5是一个质数。

例 2:6 是素数吗

问题 声明 结果
Is 6 可被 2 整除 6 % 2 == 0 1(真)

数字6可被 2 整除。因此,它不是质数。

使用用户定义的函数检查素数

下面是一个使用用户定义函数检查素数的 C 程序。

/**************************************************************
 * Program to check whether the number entered by the user 
 * is prime or not
***************************************************************/

#include <stdio.h>
int check_prime_number(int);

int main() 
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    if(check_prime_number(n))
    {
        printf("%d is prime", n);
    }
    else
    {
        printf("%d is not prime", n);
    }

    return 0;
}

int check_prime_number(int n)
{
    int is_prime = 1;

    if(n == 1)
    {
        return 0;
    }

    for(int i = 2; i < n; i++)
    {
        if(n % i == 0)
        {
            is_prime = 0;            
            break;
        }
    }

    return is_prime;
}

预期输出:第一次运行:

Enter a number: 11
11 is prime

第二次运行:

Enter a number: 45
45 is not prime

推荐阅读:



C 程序:生成斐波那契数列

原文:https://overiq.com/c-examples/c-program-to-generate-fibonacci-sequence/

最后更新于 2020 年 9 月 23 日


什么是斐波那契数列?

斐波那契数列是一个数列,其中每个连续的数字是前两个数字的和。例如:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144

下面是一个 C 程序,根据用户输入的项数生成斐波那契数列。

/*******************************************************
 * Program to generate Fibonacci sequence
********************************************************/

#include<stdio.h> // include stdio.h

int main()
{
    long int x = 0, y = 1, z;       
    int terms, i = 1;

    printf("Enter no. of terms: ");
    scanf("%d", &terms);   // to suppress the compiler warning in Linux replace %d with %zu

    printf("%d ", y);

    while(i <= terms - 1)
    {
        z = x + y;
        x = y;
        y = z;
        printf("%d ", z);  // to suppress the compiler warning in Linux replace %d with %zu
        i++;
    }

    return 0;
}

现在试试

它是如何工作的

下表演示了循环每次迭代时发生的情况(假设terms = 5)

循环 z x y
第一次迭代后 z = x + y = 0 + 1 = 1 x = y = 1 y = z = 1 i = 2
第二次迭代后 z = 1 + 1 = 2 x = 1 y = 2 i = 3
第三次迭代后 z = 1 + 2 = 3 x = 2 y = 3 i = 4
第四次迭代后 z = 2 + 3 = 5 x = 3 y = 5 i = 5

推荐阅读:



C 程序:计算一个数的十进制位总和,直到总和减少到一位数

原文:https://overiq.com/c-examples/c-program-to-find-the-sum-of-the-digits-of-a-number-untill-the-sum-is-reduced-to-a-single-digit/

最后更新于 2020 年 9 月 23 日


下面是一个 C 程序,用来寻找数字的总和,直到总和减少到一个位数。

/**************************************************************************************************
 * Program to find the sum of the digits of a number till the sum is reduced to a single digit
***************************************************************************************************/

#include<stdio.h> // include stdio.h

int main()
{    
    long int num;
    int sum = 0, rem;

    printf("Enter a number: ");
    scanf("%ld", &num);   

    while(num / 10 != 0)
    {
        sum = 0;
        while(num != 0) 
        {
            rem = num % 10;  // get the last digit of num
            sum += rem;      // add rem to sum
            num = num / 10;  // remove the last digit from num
        }

        num = sum;
    }

    printf("%d", sum);

    return 0;
}

现在试试

预期输出:第一次运行:

Enter a number: 12345
6

第二次运行:

Enter a number: 749
2

它是如何工作的

下表演示了假设num = 12345,内部 while 循环的每次迭代会发生什么:

循环 数字 总额 雷姆
第一次迭代后 remainder = 12345%10 = 5 sum = 0+5 = 5 num = 12345/10 = 1234
第二次迭代后 remainder = 1234%10 = 4 sum = 5+4 = 9 num = 1234/10 = 123
第三次迭代后 remainder = 123%10 = 3 sum = 9+3 = 12 num = 123/10 = 12
第四次迭代后 remainder = 12%10 = 2 sum = 12+2 = 14 num = 12/10 = 1
第五次迭代后 remainder = 1%10 = 1 sum = 14+1 = 15 num = 1/10 = 0

这就完成了外部 while 循环的第一次迭代。由于 num 仍未减少到一位数,条件num / 10 != 0评估为真(1)并再次执行内部 while 循环的主体。

循环 数字 总额 雷姆
第一次迭代后 remainder = 15%10 = 5 sum = 0+5 = 5 num = 15/10 = 1
第二次迭代后 remainder = 1%10 = 1 sum = 5+1 = 6 num = 1/10 = 0

推荐阅读:



C 程序:计算数字位数

原文:https://overiq.com/c-examples/c-program-to-count-number-of-digits-in-a-number/

最后更新于 2020 年 9 月 23 日


下面是一个计算数字位数的 C 程序。

/****************************************************
 * Program to count the number of digits in a number
*****************************************************/

#include<stdio.h> // include stdio.h

int main() 
{
    long int num;
    int count = 0, rem;

    printf("Enter a number: ");
    scanf("%ld", &num);

    while (num != 0) 
    {
        rem = num % 10;  // get the last digit of num
        num = num / 10;  // remove the last digit from num
        count++;         // increment count by 1
    }

    printf("%d", count);

    return 0;
}

现在试试

预期输出:第一次运行:

Enter a number: 123456
6

第二次运行:

Enter a number: 25
2

它是如何工作的

下表演示了在循环的每次迭代中发生的情况,假设num = 123456

循环 rem num count
第一次迭代后 rem = 123456%10 = 6 n = 123456/10 = 12345 count = 1
第二次迭代后 rem = 12345%10 = 5 n = 12345/10 = 1234 count = 2
第三次迭代后 rem = 1234%10 = 4 n = 1234/10 = 123 count = 3
第四次迭代后 rem = 123%10 = 3 n = 123/10 = 12 count = 4
第五次迭代后 rem = 12%10 = 2 n = 12/10 = 1 count = 5
第 6 次迭代后 rem = 1%10 = 1 n = 1/10 = 0 count = 6

推荐阅读:



C 程序:反转数字十进制位

原文:https://overiq.com/c-examples/c-program-to-reverse-the-digits-of-a-number/

最后更新于 2020 年 9 月 23 日


下面是一个反转数字的 C 程序:

/********************************************
 * Program to reverse the digits of a number
*********************************************/

#include<stdio.h> // include stdio.h

int main() 
{
    long int num, rev = 0;
    int rem;

    printf("Enter a number: ");
    scanf("%ld", &num);

    while(num != 0)
    {
        rem = num % 10;        // get the last digit of num
        rev = rev * 10 + rem;  // reverse the number
        num = num / 10;        // remove the last digit from num
    }

    printf("%ld", rev);

    return 0;
}

现在试试

预期输出:第一次运行:

Enter a number: 1728
8271

第二次运行:

Enter a number: 456123
321654

它是如何工作的

下表演示了我们用来反转给定数字位数的算法,假设n = 1728:

循环 remainder rev num
第一次迭代后 rem = 1728%10 = 8 rev = 0*10+8 = 8 num = 1728/10 = 172
第二次迭代后 rem = 172%10 = 2 rev = 8*10+2 = 82 num = 172/10 = 17
第三次迭代后 rem = 17%10 = 7 rev = 82*10+7 = 827 num = 17/10 = 1
第四次迭代后 rem = 1%10 = 1 rev = 827*10+1 = 8271 num = 1/10 = 0

推荐阅读:



C 程序:计算自然数N项之和

原文:https://overiq.com/c-examples/c-program-to-find-the-sum-of-natural-numbers-upto-n-terms/

最后更新于 2020 年 9 月 23 日


什么是自然数?

用于计数的整数称为自然数。自然数从1开始,一直到无穷大。例如:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10 . . .

下面是一个计算自然数之和直到n项的 C 程序:

/**********************************************************
 * Program to find the sum of natural numbers upto n terms
***********************************************************/

#include<stdio.h> // include stdio.h

int main() 
{
    int terms, i;
    unsigned long int sum = 0;

    printf("Enter the number of terms: ");
    scanf("%d", &terms);

    for(int i = 1 ; i <= terms; i++)
    {
        sum += i;                
    }

    printf("%ld", sum);

    return 0;
}

现在试试

预期输出:

第一次运行:

Enter the number of terms: 4 
10

第二次运行:

Enter the number of terms: 50000
1250025000

工作原理

下表演示了在 for 循环的每次迭代中发生的情况,假设terms = 4

循环 sum “我
第一次迭代后 sum = sum + i = 0+1 = 1 i = 2
第二次迭代后 sum = 1+2 = 3 i = 3
第三次迭代后 sum = 3+3 = 6 i = 4
第四次迭代后 sum = 6+4 = 10 i = 5

推荐阅读:



C 程序:检查数字是偶数还是奇数

原文:https://overiq.com/c-examples/c-program-to-check-whether-the-number-is-even-or-odd/

最后更新于 2020 年 7 月 27 日


什么是偶数或奇数:

偶数:能被2整除(即不留余数)的数称为偶数。

奇数:不能被2整除的数叫做奇数。

下面是一个检查一个数字是偶数还是奇数的 C 程序。

/*************************************************************
 * Program to check whether the entered number is even or odd.
**************************************************************/

#include<stdio.h> // include stdio.h

int main() 
{
    int n;

    printf("Enter a number: ");
    scanf("%d", &n);

    if(n % 2 == 0)
    {
        printf("%d is even", n);
    }
    else
    {
        printf("%d is odd", n);
    }

    return 0;
}

预期输出:第一次运行:

Enter a number: 18
18 is even

第二次运行:

Enter a number: 5555
5555 is odd

它是如何工作的

一个数即使能被 2 整除也是偶数。要检查偶数,我们只需使用余数运算符%。如果该数字为偶数,则余数运算符将返回0,否则为非零值。例如:

4 % 2 = 0 => 4 is even
11 % 2 = 1 => 11 is odd


相关计划



C 程序:寻找二次方程根

原文:https://overiq.com/c-examples/c-program-to-find-the-roots-of-a-quadratic-equation/

最后更新于 2020 年 7 月 27 日


什么是二次方程?

以下形式的方程:

\开始{聚集}
a x^{2}+b x+c = 0
\结束{聚集
}

被称为二次方程,其中abc是数字,a不等于0。满足方程的x的值称为方程的根。

为了计算二次方程的根,我们使用以下公式:

\ begin {聚首}
x=\frac{-b^2 \ pm \sqrt{b^2-4ac}}{2a}
\ end {聚首
}

其中(\sqrt{b^2 - 4ac})被称为判别式。

如果discriminant > 0,那么方程有两个截然不同的实根。
如果discriminant = 0,那么方程的根是实的,相同的。
如果discriminant < 0,那么方程的根是虚的。

下面是一个计算二次方程根的 C 程序:

/***********************************************************
 * C Program to find the roots of a Quadratic equation
************************************************************/

#include<stdio.h> // include stdio.h
#include<math.h> // include math.h for mathematical functions

int main() 
{
    float a, b, c, discriminant, root1, root2;    

    printf("Enter coefficient of x^2: ");
    scanf("%f", &a);

    printf("Enter coefficient of x: ");
    scanf("%f", &b);

    printf("Enter constant term: ");
    scanf("%f", &c);

    discriminant = sqrt( b*b - 4*a*c );        

    if(discriminant >= 0)
    {
        root1 = ( -b + discriminant ) / (2.0*a);
        root2 = ( -b - discriminant ) / (2.0*a);
        printf("\nFirst root: %.2f\n", root1);
        printf("Second root: %.2f\n", root2);
    }

    else
    {
        printf("\nRoots are imaginary");
    }

    return 0;
}

预期输出:第一次运行:

Enter coefficient of x^2: 1
Enter coefficient of x: 7
Enter constant term: 12

First root: -3.00
Second root: -4.00

第二次运行:

Enter coefficient of x^2: 1
Enter coefficient of x: 4
Enter constant term: 5

Roots are imaginary


相关节目:



C 程序:打印三元组数字

原文:https://overiq.com/c-examples/c-program-to-print-triad-numbers/

最后更新于 2020 年 9 月 23 日


什么是三和弦号码?

三元组数字是满足以下条件的三个数字的集合:

  1. 每个数字必须是 3 位数
  2. 数字中的所有数字必须是唯一的。
  3. 第二个数字必须是第一个数字的两倍,第三个数字必须是第一个数字的三倍。

以下是尝试过的数字示例:

192 384 576
219 438 657
267 534 801
273 546 819
327 654 981

下面是一个 C 程序,用来打印 100 到 999 之间的所有三元组数字。

/*******************************************
 * C Program to print all the triad numbers
 ********************************************/

#include<stdio.h> // include stdio.h

int main() 
{

    int d1, d2, d3, tmp;

    // generate three digit numbers
    for (int num = 100; num <= 333; num++) 
    {
        // check for unique digits in the first number
        for (int i = num; i <= num * 3; i += num) 
        {
            tmp = i;
            d1 = tmp % 10;

            tmp = tmp / 10;
            d2 = tmp % 10;

            tmp = tmp / 10;
            d3 = tmp % 10;

            if (d1 == d2 || d2 == d3 || d3 == d1) {
                goto next;
            }
        }

        // check whether the given three numbers have unique digits or not
        for (int a = num; a > 0; a /= 10) 
        {
            d1 = a % 10;
            for (int b = num * 2; b > 0; b /= 10) 
            {
                d2 = b % 10;
                for (int c = num * 3; c > 0; c /= 10) 
                {
                    d3 = c % 10;

                    if (d1 == d2 || d2 == d3 || d3 == d1) 
                    {
                        goto next;
                    }
                }
            }
        }

        printf("%d %d %d\n", num, num * 2, num * 3);

        next:;
    }

    return 0;
}

现在试试

预期输出:

192 384 576
219 438 657
267 534 801
273 546 819
327 654 981

它是如何工作的

关于该计划,需要注意以下几点:

  1. 第一个循环用于生成三位数。请注意,我们正在运行从 100 到 333 的循环,这是因为任何大于 333 的数字都会导致大于 999 的数字。
  2. 嵌套 for 循环(第 16 行)用于检查第一个数字中的数字是否唯一。如果数字不是唯一的,我们使用goto语句(第 28 行)从嵌套的 for 循环中出来,并继续下一个数字。
  3. 如果第一个数字中的数字是唯一的,那么控制进入第 33 行定义的 for 循环。这个 for 循环检查给定的三个数字是否有唯一的数字。如果数字是唯一的,那么这些数字就是三元组数字,否则,我们使用goto语句从嵌套的 for 循环中出来,并检查下一个数字。


C 程序:使用俄国农夫法相乘两个数

原文:https://overiq.com/c-examples/c-program-to-multiply-two-numbers-using-russian-peasant-method/

最后更新于 2020 年 9 月 23 日


什么是俄国农夫法?

俄国农夫法让我们不用乘法表就能求出两个数的乘法。在这种方法中,我们将数字除一半,然后相加。以下是使用俄罗斯农民方法寻找乘法的步骤。

  1. 想出两个数字,把它们写在专栏的开头。
  2. 第一个数乘以 2,第二个数除以 2(取整数除法)。
  3. 继续重复步骤 2,直到第二个数字减少到 1。
  4. 划掉第二个数字为偶数的整行。
  5. 取第一列中剩余数字的总和。这个和等于两个原始数字的乘积。

我们举个例子:例 1:用俄国农夫法乘以 12 * 13

第一步:在列首写数字:

第 1 栏 第 2 栏
Twelve Thirteen

第二步:第一个数乘以 2,第二个数除以 2(取整数除法)

第 1 栏 第 2 栏
Twelve Thirteen
Twenty-four six

第三步:继续重复第二步,直到第二个数字减少到 1。

第 1 栏 第 2 栏
Twelve Thirteen
Twenty-four six
Forty-eight three
Ninety-six one

第四步:划掉第二个数字为偶数的整行。

第 1 栏 第 2 栏
Twelve Thirteen
Twenty-four six
Forty-eight three
Ninety-six one

第五步:取第一列剩余数字的和。

第 1 栏 第 2 栏
Twelve Thirteen
Twenty-four six
Forty-eight three
Ninety-six one

12 * 13 的乘积= 12 + 48 + 96 = 156。

下面是一个用俄国农夫法将两个数字相乘的 C 程序:

/*************************************************************
 Program to multiply two numbers using Russian peasant method
 *************************************************************/

#include <stdio.h>

int main() 
{

    int a, b, result = 0, m, n;   

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    m = a;
    n =  b;

    while(n > 0)
    {                
        if(n % 2 != 0)
        {
            result += m;
        }    

        m *= 2;  // double the first number         
        n /= 2;  // half the second number
    }

    printf("%d * %d = %d", a, b, result);

    return 0;
}

现在试试

预期输出:第一次运行:

Enter two numbers: 12 13
12 * 13 = 156

第二次运行:

Enter two numbers: 10 96
10 * 96 = 960

它是如何工作的

下表演示了 while 循环每次迭代时发生的情况,假设(a = 12b = 13):

循环 if condition result m n
第一次迭代后 13%2!=0=>1 result=0+12=12 m=12*2=24 n=13/2=6
第二次迭代后 6%2!=0=>0(横排) result=12 m=24*2=48 n=6/2=3
第三次迭代后 3%2!=0=>1 result=12+48=60 m=48*2=96 n=3/2=1
第四次迭代后 1%2!=0=>1 result=60+96=156 m=96*2=192 n=1/2=0

推荐阅读:



C 程序:计算给定金额面额

原文:https://overiq.com/c-examples/c-program-to-find-the-number-of-denominations-for-a-given-amount/

最后更新于 2020 年 9 月 23 日


下面的程序要求用户输入金额,并打印要分发的纸币(面额为 500、100、50、20、10、1)的数量。例如,如果用户输入$451,则需要 4 张 100 的纸币、1 张 50 的纸币和 1 张 1 的纸币。

/***************************************************************
 Program to find the number of denominations for a given amount
 ***************************************************************/

#include <stdio.h>
#define SIZE 6

int main() 
{    
    int amount, notes;   

    // currency denominations
    int denominations[SIZE] = {500, 100, 50, 20, 10, 1};

    printf("Enter amount: ");
    scanf("%d", &amount);

    printf("\n");

    for (int i = 0; i < SIZE; i++) 
    {
        notes = amount / denominations[i];

        if (notes)
        {
            amount = amount % denominations[i]; // remaining money

            printf("%d * %d = %d \n", notes, denominations[i], 
                notes * denominations[i]);
        }                
    }

    return 0;
}

现在试试

预期输出:第一次运行:

Enter amount: 642

1 * 500 = 500 
1 * 100 = 100 
2 * 20 = 40 
2 * 1 = 2

第二次运行:

Enter amount: 9241

18 * 500 = 9000 
2 * 100 = 200 
2 * 20 = 40 
1 * 1 = 1


推荐节目



C 程序:检查数字是否是回文

原文:https://overiq.com/c-examples/c-program-to-check-whether-the-number-is-a-palindrome/

最后更新于 2020 年 9 月 23 日


什么是回文数?

当数字反转时保持不变的数字称为回文数字。比如 555、10101、45654 等;是回文数字。然而,123,300 等;不是。

下面是一个检查输入的数字是否回文的 C 程序:

/****************************************************
 Program to check whether the number is a palindrome
 ****************************************************/

#include<stdio.h> // include stdio.h library

int main(void)
{   
    int num, tmp_num, rev = 0, rem;

    printf("Enter a number: ");
    scanf("%d", &num);

    tmp_num = num;

    while(tmp_num != 0)
    {
        rem = tmp_num % 10;  // get the last digit from tmp_num
        rev = rev * 10 + rem;  
        tmp_num /= 10;  // remove the last digit from tmp_num
    }       

    if(num == rev)
    {
        printf("%d is a palindrome number.", num);
    }

    else
    {
        printf("%d is not palindrome number.", num);
    }

    return 0; // return 0 to operating system
}

现在试试

预期输出:第一次运行:

Enter a number: 1551
1551 is a palindrome number.

第二次运行:

Enter a number: 123
123 is not palindrome number.

它是如何工作的

下表演示了 while 循环每次迭代时发生的情况,假设num = 1551

循环 rem rev tmp_num
第一次迭代后 rem=1551%10=1 rev=0*10+1=1 tmp_num=1551/10=155
第二次迭代后 rem=155%10=5 rev=1*10+5=15 tmp_num=155/10=15
第三次迭代后 rem=15%10=5 rev=15*10+5=155 tmp_num=15/10=1
第四次迭代后 rem=1%10=1 rev=155*10+1=1551 tmp_num=15/10=0

推荐阅读:



C 程序:确定三角形的类型和面积

原文:https://overiq.com/c-examples/c-program-to-determine-the-type-and-area-of-a-triangle/

最后更新于 2020 年 7 月 27 日


下面是一个确定三角形的类型和面积的 C 程序:

/*****************************************************
 Program to determine the type and area of a triangle
 *****************************************************/

#include<stdio.h> // include stdio.h library
#include<math.h> // include math.h library

int main(void)
{   
    float a, b, c, s, area;

    printf("Enter sides of a triangle: \n");

    printf("a: ");
    scanf("%f", &a);

    printf("b: ");
    scanf("%f", &b);

    printf("c: ");
    scanf("%f", &c);

    // sum of any two sides must be greater than the third side    
    if( (a + b > c) && (b + c > a) && (c + a > b) )
    {
        //  three sides are equal
        if( (a == b) && (b == c) )
        {
            printf("Triangle is equilateral.\n");
        }

        //  two sides are equal
        else if( (a == b) || (b == c) || (a == c) )
        {
            printf("Triangle is isosceles.\n");
        }

        // no sides are equal
        else
        {
            printf("Triangle is scalene.\n");
        }

        //  area of triangle using heron's formula https://en.wikipedia.org/wiki/Heron's_formula

        s = (a + b + c) / 2;  //semi perimeter

        area = sqrt(  s * (s - a) * (s - b) * (s - c) ); // area

        printf("Area of triangle %.2f.", area);

    }

    else
    {
        printf("Sides don't make a triangle.");
    }

    return 0; // return 0 to operating system
}

预期输出:

第一次运行:

Enter sides of a triangle: 
a: 3
b: 4
c: 5
Triangle is scalene.
Area of triangle 6.00.

第二次运行:

Enter sides of a triangle: 
a: 4
b: 5
c: 1
Sides don't make a triangle.

它是如何工作的

上面的程序使用了两个定理:

  • 三角形不等式定理
  • 赫伦公式

三角形不等式定理

三角形不等式定理指出三角形的两条边之和必须大于第三条边。

ab c成为三角形的三条边,然后根据三角形不等式定理:

a + b > c
b + c > a
c + a > b

我们还可以利用三角不等式定理来判定给定的三条线段是否可以用来构造三角形。

为了使三条线段形成三角形的边,必须满足所有三个条件。

如果任何一个条件失败了,那么给定的线段就不能用来构建三角形。例如:

例 1:我们能用以下长度构造一个三角形吗:7,3,2?

7 + 3 > 2 => true 
3 + 2 > 7 => false
7 + 2 > 3 => true

第二个条件是假的。因此,长度 7、3 和 2 不能构成三角形。

例 2:我们能用以下长度构造一个三角形吗:3,4,5?

3 + 4 > 5 => true  
4 + 5 > 3 => true
5 + 3 > 4 => true

三个条件都是真的。因此,长度 3、4、5 可以用来构建三角形。

赫伦公式

Heron 公式允许我们利用三条边的长度来找到三角形的面积。

\ begin { collect * }
Area = \ sqrt { s(s-a)(s-b)(s-c)}
\ end { collect * }

其中s称为三角形的半周长,计算如下:

\ begin { collect * }
s = \ frac { a+b+c } { 2 }
\ end { collect * }



C 程序:打印两个范围之间的孪生素数

原文:https://overiq.com/c-examples/c-program-to-print-twin-prime-numbers-between-two-ranges/

最后更新于 2020 年 9 月 23 日


什么是孪生素数?

相差 2 的一对素数叫做孪生素数。例如,前 4 个孪生素数是:

(3, 5), (11, 13), (17, 19), (29, 31)

以下是打印两个范围之间的孪生素数的 C 程序:

/*******************************************************
 Program to print Twin prime numbers between two ranges
 *******************************************************/

#include<stdio.h> // include stdio.h library
int check_prime(int n);

int main(void)
{   

    int start, end;

    printf("Enter start: ");
    scanf("%d", &start);

    printf("Enter end: ");
    scanf("%d", &end);

    for(int i = start; i < end; i++)
    {
        if(check_prime(i) && check_prime(i + 2))
        {            
            printf("{%d, %d}\n", i, i + 2);
            i = i + 1; 
        }                
    }    

    return 0; // return 0 to operating system
}

int check_prime(int n)
{
    if(n == 1)
    {
        return 0;
    }

    for(int i = 2; i < n; i++)
    {
        if(n % i == 0)
        {
            // number is not prime
            return 0;
        }
    }

    // number is prime
    return 1;
}

现在试试

预期输出:第一次运行:

Enter start: 1
Enter end: 50
{3, 5}
{5, 7}
{11, 13}
{17, 19}
{29, 31}
{41, 43}

第二次运行:

Enter start: 500
Enter end: 600
{521, 523}
{569, 571}
{599, 601}

它是如何工作的

在第 19 行,我们有一个 for 循环,它迭代指定范围之间的数字。

在第 21 行,我们两次呼叫check_prime()号。一次带号i,一次带号i + 2

如果条件满足,那么ii + 2是孪生素数。在第 23 行,我们打印双素数,在第 24 行,我们将计数器i增加1,以检查下一个数。


相关阅读:



C 程序:使用单词打印两位数

原文:https://overiq.com/c-examples/c-program-to-print-the-two-digit-number-in-words/

最后更新于 2020 年 7 月 27 日


下面是一个用单词打印两位数的 C 程序:

/***********************************************
 Program to print the two digit number in words
 ***********************************************/

#include <stdio.h>  // include stdio.h library

int main(void)
{
    int num1, num2;

    printf("Enter a two-digit number: ");
    scanf("%1d%1d", &num1, &num2);

    printf("You have entered: ");

    // print word for the first digit
    switch (num1)
    {
        case 1:
            // special case for numbers between 11-19
            switch (num2)
            {
                case 0:
                    printf("ten"); 
                    return 0;
                case 1:
                    printf("eleven"); 
                    return 0;
                case 2:
                    printf("twelve"); 
                    return 0;
                case 3:
                    printf("thirteen"); 
                    return 0;
                case 4:
                    printf("fourteen"); 
                    return 0;
                case 5:
                    printf("fifteen"); 
                    return 0;
                case 6:
                    printf("sixteen"); 
                    return 0;
                case 7:
                    printf("seventeen"); 
                    return 0;
                case 8:
                    printf("eigthteen"); 
                    return 0;
                case 9:
                    printf("nineteen"); 
                    return 0;
            }
        case 2:
            printf("twenty"); 
            break;
        case 3:
            printf("thirty"); 
            break;
        case 4:
            printf("forty"); 
            break;
        case 5:
            printf("fifty"); 
            break;
        case 6:
            printf("sixty"); 
            break;
        case 7:
            printf("seventy"); 
            break;
        case 8:
            printf("eighty"); 
            break;
        case 9:
            printf("ninety"); 
            break;
    }

    // print word for the second digit
    switch (num2)
    {
        case 1:
            printf("-one"); 
            break;
        case 2:
            printf("-two"); 
            break;
        case 3:
            printf("-three"); 
            break;
        case 4:
            printf("-four"); 
            break;
        case 5:
            printf("-five"); 
            break;
        case 6:
            printf("-six"); 
            break;
        case 7:
            printf("-seven"); 
            break;
        case 8:
            printf("-eight"); 
            break;
        case 9:
            printf("-nine"); 
            break;
    }

    return 0;
}

预期输出:

第一次运行:

Enter a two-digit number: 19
You have entered: nineteen

第二次运行:

Enter a two-digit number: 95
You have entered: ninety-five

它是如何工作的

该程序由两个外部switch语句组成。第一个定义在第 17-78 行,第二个定义在第 81-110 行。第一个 switch 语句打印第一个数字的单词,第二个 switch 语句打印第二个数字的单词。从 11 到 19 的数字需要特殊处理,并由第 21-53 行中定义的嵌套 switch 语句处理。


推荐阅读



C 程序:计算一个数的幂

原文:https://overiq.com/c-examples/c-program-to-calculate-the-power-of-a-number/

最后更新于 2020 年 9 月 23 日


下面是计算一个数的幂的 C 程序:

/*******************************************
 Program to calculate the power of a number
 *******************************************/

#include<stdio.h> // include stdio.h library

int main(void)
{       
    int base, exponent, result = 1;

    printf("Enter base: ");
    scanf("%d", &base);

    printf("Enter exponent: ");
    scanf("%d", &exponent);

    int i = 1;

    while(i <= exponent)
    {
        result *= base;
        i++;
    }

    printf("%d^%d = %d", base, exponent, result);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

第一次运行:

Enter base: 21
Enter exponent: 2
21^2 = 441

第二次运行:

Enter base: 25
Enter exponent: 4
25^4 = 390625

它是如何工作的

下表演示了 while 循环每次迭代时发生的情况,假设base = 21exponent = 2

循环 result i
第一次迭代后 result = 1 * 21 = 21 2
第二次迭代后 result = 21 * 21 = 441 4

使用幂()函数计算幂

上面的程序只能在指数为正时计算幂。使用pow()函数计算任意实数指数的幂。

要使用pow()功能,请确保在程序顶部包含math.h头文件。

/********************************************************************
 Program to calculate the power of a number using pow() the function
 ********************************************************************/

#include<stdio.h> // include stdio.h library
#include<math.h> // include math.h library

int main(void)
{       
    double base, exponent;       

    printf("Enter base: ");
    scanf("%lf", &base);

    printf("Enter exponent: ");
    scanf("%lf", &exponent);       

    printf("%.2f^%.2f = %.2f", base, exponent, pow(base, exponent));

    return 0; // return 0 to operating system
}

现在试试

预期输出:

第一次运行:

Enter base: 4.5
Enter exponent: 1.2
4.50^1.20 = 6.08

第二次运行:

Enter base: 20
Enter exponent: 2.5
20.00^2.50 = 1788.85


推荐阅读



C 程序:寻找三个数字中最大值

原文:https://overiq.com/c-examples/c-program-to-find-the-largest-of-three-numbers/

最后更新于 2020 年 9 月 23 日


下面是一个寻找三个数字中最大的 C 程序:

/*********************************************
 Program to find the largest of three numbers
 **********************************************/

#include<stdio.h> // include stdio.h library

int main(void)
{       
    int a, b, c;

    printf("Enter a: ");
    scanf("%d", &a);

    printf("Enter b: ");
    scanf("%d", &b);

    printf("Enter c: ");
    scanf("%d", &c);

    if(a > b)
    {
        if(a > c)
        {
            printf("a is largest.");
        }
        else
        {
            printf("c is largest.");
        }
    }
    else
    {
        if(b > c)
        {
            printf("b is largest.");
        }
        else
        {
            printf("c is largest.");
        }
    }

    return 0; // return 0 to operating system
}

现在试试

预期输出:第一次运行:

Enter a: 45
Enter b: 100
Enter c: 20
b is largest.

第二次运行:

Enter a: -10 
Enter b: -81
Enter c: -5
c is largest.

它是如何工作的

程序首先要求用户输入三个数字(abc)。

在第 20 行,我们检查a是否大于b。如果条件a > b评估为真(1),程序控制进入第 22 行的嵌套 If 语句并检查a > c。如果条件a > c评估为真(1,则a为最大值。否则c是最大的数字。

如果第 20 行的条件评估为假,那么我们可以肯定地说b > a

第 33 行,我们检查b > c是否。如果是,那么b就是最大的数字。如果条件失败,那么我们可以断定c > b

从,b > ac > b开始。因此,c > b > a。因此,c是最大的数字。


推荐阅读:



C 程序:寻找数字的乘积

原文:https://overiq.com/c-examples/c-program-to-find-the-product-of-digits-of-a-number/

最后更新于 2020 年 9 月 23 日


下面是一个求数字的乘积的 C 程序:

/**************************************************
 Program to find the product of digits of a number
 * 
 * Enter a number: 456
 * 120
 **************************************************/

#include<stdio.h> // include stdio.h library

int main(void)
{       
    int num, rem, prod = 1;

    printf("Enter a number: ");
    scanf("%d", &num);

    while(num != 0)
    {
        rem = num % 10; // get the right-most digit
        prod *= rem; // calculate product of digits
        num /=  10;  // remove the right-most digit
    }

    printf("%d", prod);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

第一次运行:

Enter a number: 234
24

第二次运行:

Enter a number: 444
64

它是如何工作的

下表演示了 while 循环每次迭代时发生的情况,假设num = 234

循环 rem prod num
第一次迭代后 rem=234%10=4 prod=1*4=4 num=234/10=23
第二次迭代后 rem=23%10=3 prod=4*3=12 num=23/10=2
第三次迭代后 rem=2%10=2 prod=12*2=24 num=2/10=0

推荐阅读:



C 程序:计算排列组合

原文:https://overiq.com/c-examples/c-program-to-calculate-permutation-and-combination/

最后更新于 2020 年 9 月 23 日


下面是一个计算排列组合的 C 程序:

/**************************************************
 Program to calculate Permutation and Combination
 * 
 * Enter n: 10
 * Enter r: 4
 * Permutation = 5040
 * Combination = 210
 ***************************************************/

#include<stdio.h> // include stdio.h library
long permutation(int n, int r);
long combination(int n, int r);
long factorial(int num);

int main(void)
{    
    int n, r;

    printf("Enter n: ");
    scanf("%d", &n);

    printf("Enter r: ");
    scanf("%d", &r);

    printf("Permutation = %ld\n", permutation(n, r));    
    printf("Combination = %ld", combination(n, r));

    return 0; // return 0 to operating system
}

long permutation(int n, int r)
{
    return factorial(n) / factorial(n-r);
}

long combination(int n, int r)
{
    return permutation(n, r) / factorial(r);
}

long factorial(int num)
{
    long long fact = 1;

    while(num > 0)
    {
        fact *= num;
        num--;
    }

    return fact;
}

现在试试

预期输出:第一次运行:

Enter n: 5
Enter r: 3
Permutation = 60
Combination = 10

第二次运行:

Enter n: 5
Enter r: 0
Permutation = 1
Combination = 1

它是如何工作的

这里没什么神奇的,我们只是用公式来计算排列组合。

一次拍摄的不同物体的排列由下式给出:

\ begin { collect * }
NPr = \ frac { n!}{(n-r)!}
\ end { collect * }

一次拍摄的不同物体的组合由下式给出:

\ begin { collect * }
NCr = \ frac { n!}{r!(n-r)!}
\ end { collect * }


推荐阅读:



C 程序:求两个数的 LCM 和 HCF

原文:https://overiq.com/c-examples/c-program-to-find-lcm-and-hcf-of-two-numbers/

最后更新于 2020 年 9 月 23 日


什么是 LCM 和 HCF?

两个数的最小公倍数是能被这两个数整除的最小数:例如:4,10 的最小公倍数是 20,4,3 的最小公倍数是 12。

两个数的最大公因数,也称为最大公约数,是能被这两个数整除的最高数。比如:HCF 210,45 是 20,HCF 6,18 是 6。

欧几里德算法寻找人类合作框架

以下是使用欧几里德算法计算 HCF 的步骤:

  1. 输入两个正整数ab
  2. 如果a < b,则交换ab的值。
  3. a除以b得到余数。如果余数为0,则b为 HCF,否则转到步骤 4。
  4. b的值赋给a,将余数赋给b,然后再次进入步骤 3。

一旦我们计算了 HCF,就可以使用以下关系式轻松计算 LCM:

\ begin { collect * }
LCM(A,B) = \frac{A*B}{HCF(A,B)}
\ end { collect * }

下面是一个计算两个数的 LCM 和 HCF 的 C 程序:

/***********************************************
 * C Program to find LCM and HCF of two numbers
************************************************/

#include<stdio.h> // include stdio.h

int main() 
{
    int a, b;

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    printf("HCF = %d\n", calculate_hcf(a, b));
    printf("LCM = %d\n", calculate_lcm(a, b));

    return 0;
}

int calculate_hcf(int smaller, int larger)
{
    //  Finding HCF using Euclid's Algorithm
    //  https://en.wikipedia.org/wiki/Euclidean_algorithm

    int rem, tmp;

    if(larger < smaller)
    {
        tmp = larger;
        larger = smaller;
        smaller = tmp;
    }

    while(1)
    {
        rem = larger % smaller;
        if(rem == 0)
        {
            return smaller;
        }

        larger = smaller;
        smaller = rem;        
    }

}

int calculate_lcm(int a, int b)
{
    // lcm = product of two numbers / hcf
    return (a * b) / calculate_hcf(a, b);
}

现在试试

预期输出:第一次运行:

Enter two numbers: 3 4
HCF = 1
LCM = 12

第二次运行:

Enter two numbers: 210 45
HCF = 15
LCM = 630

工作原理

下表演示了在calculate_hcf()函数中 while 循环的每次迭代中发生的情况,假设larger = 210smaller = 45:

循环 rem sum n
第一次迭代后 rem = larger % smaller = 210%45 = 30 larger = 45 smaller = 30
第二次迭代后 rem = 45%30 = 15 larger = 30 smaller = 15
第三次迭代后 rem = 30%15 = 0 larger = 15 smaller = 0

相关节目:



C 程序:寻找数组中最大和最小元素

原文:https://overiq.com/c-examples/c-program-to-find-the-maximum-and-minimum-element-in-the-array/

最后更新于 2020 年 9 月 23 日


下面是一个寻找数组中最大和最小元素的 C 程序:

/*********************************************************
 Program to find maximum and minimum element in the array 
 *********************************************************/

#include<stdio.h> // include stdio.h library
#define MAX 5

int main(void)
{    
    int arr[MAX] = {50, -100, 20, 245, 0},
            min, max;

    min = max = arr[0]; // assign the first element to max and min

    for(int i = 0; i < MAX; i++)
    {
        if(arr[i] < min)
        {
            min = arr[i];
        }

        if(arr[i] > max)
        {
            max = arr[i];
        }
    }        

    printf("Min = %d\n", min);
    printf("Max = %d", max);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

Min = -100
Max = 245

它是如何工作的

我们使用 for 循环迭代数组中的元素。如果数组中的当前元素小于min,我们将该值赋给min。同样,如果当前元素大于max,我们将该值赋给max。当循环终止时,我们打印minmax变量的值:

下表演示了 for 循环每次迭代时发生的情况:

循环 Condition 1 Condition 2 min max
one arr[0]<min=>50<50=>0 arr[0]>max=>50>50=>0 min=50 max=50
Two arr[1]<min=>-100<50=>1 arr[1]>max=>-100>50=>0 min=-100 max=50
three arr[2]<min=>20<-100=>0 arr[2]>max=>20>50=>0 min=-100 max=50
four arr[3]<min=>245<-100=>0 arr[2]>max=>245>50=>1 min=-100 max=245
five arr[4]<min=>0<-100=>0 arr[4]>max=>0>50=>0 min=-100 max=245


C 程序:反转数组元素

原文:https://overiq.com/c-examples/c-program-to-reverse-the-elements-of-an-array/

最后更新于 2020 年 9 月 23 日


下面是一个反转数组元素的 C 程序:

/**********************************************
 Program to reverse the elements of an array 
 **********************************************/

#include<stdio.h> // include stdio.h library
#define MAX 5

int main(void)
{    
    int arr[MAX] = {10, 20, 30, 40, 50},
            i, j, tmp;

    i = 0;
    j = MAX - 1; // assign the last valid index 

    while(i < j)
    {
        // swap the elements
        tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp; 

        i++;  
        j--;
    }

    //  print the reversed array
    for(int k = 0; k < MAX; k++)
    {
        printf("%d ", arr[k]);
    }

    return 0; // return 0 to operating system
}

现在试试

预期输出:

50 40 30 20 10

它是如何工作的

为了反转数组的元素,我们用最后一个元素交换数组的第一个元素,用第二个元素交换最后一个元素,以此类推。我们不断重复这个过程,直到到达阵列的一半。

下图展示了运行中的流程:



C 程序:对数组元素求和

原文:https://overiq.com/c-examples/c-program-to-sum-the-elements-of-an-array/

最后更新于 2020 年 9 月 23 日


下面是一个求数组元素之和的 C 程序:

/**********************************************
 Program to sum the elements of an array
 **********************************************/

#include<stdio.h> // include stdio.h library
#define MAX 5

int main(void)
{    
    int arr[MAX];
    int sum = 0;  // accumulate sum in this variable

    // read input
    for(int i = 0; i < MAX; i++)
    {
        printf("Enter a[%d]: ", i);
        scanf("%d", &arr[i]);
    }

    // loop from index 0 to MAX
    for(int i = 0; i < MAX; i++)
    {
        sum += arr[i];  // add the current element to sum
    }

    printf("\nSum = %d", sum);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

Enter a[0]: 1
Enter a[1]: 2
Enter a[2]: 3
Enter a[3]: 4
Enter a[4]: 5

Sum = 15

它是如何工作的

  1. 我们从声明变量arrsum开始,分别存储数组和和。注意变量sum被初始化为0
  2. 在第 14-18 行,我们有一个循环,提示用户向数组中输入元素。
  3. 在第 21-24 行,我们有第二个 for 循环来求和数组的元素。
  4. 在第 25 行,我们使用 print 语句打印总和。

推荐阅读:



C 程序:计算数组中奇数和偶数元素个数

原文:https://overiq.com/c-examples/c-program-to-find-the-count-of-even-and-odd-elements-in-the-array/

最后更新于 2020 年 9 月 23 日


下面是一个计算数组中偶数和奇数元素的 C 程序:

/***************************************************************
 Program to find the count of even or odd elements in the array
 ***************************************************************/

#include<stdio.h> // include stdio.h library
#define MAX 5

int main(void)
{    
    int arr[MAX] = {1, 5, 9, 14, 200};
    int even_count = 0, odd_count = 0;   // variables to store even or odd count

    // iterate over the arrays
    for(int i = 0; i < MAX; i++)
    {
        // check for even number
        if(arr[i] % 2 == 0)
        {
            even_count++;
        }

        else
        {
            odd_count++;
        }
    }

    printf("Even elements = %d\n", even_count);
    printf("Odd elements = %d", odd_count);

    return 0; // return 0 to operating system
}

现在试试

预期输出:

Even elements = 2
Odd elements = 3

它是如何工作的

  • 在第 10 行,我们声明并初始化一个名为arr的数组。
  • 在第 11 行,我们声明并初始化变量even_countodd_count,分别存储偶数和奇数元素的计数。
  • 在第 14-27 行,我们让 for 循环迭代数组中的项。
  • 在第 17 行,我们有一个 if 语句来检查偶数。如果条件评估为真,我们将even_count增加 1。否则,元素是奇数,我们将odd_count增加 1。当数组中的元素用完时,for 循环终止。
  • 在第 28 行和第 29 行,我们使用打印语句打印even_countodd_count的值。

推荐阅读:



C 程序:相加两个矩阵

原文:https://overiq.com/c-examples/c-program-to-add-two-matrices/

最后更新于 2020 年 9 月 23 日


下面是一个 C 程序,它要求用户输入两个矩阵,然后将它们相加。

/******************************************
* Program to add two add matrices
******************************************/

#include<stdio.h> // include stdio.h
#define ROW 2
#define COL 3

int main()
{
    int i, j, arr1[ROW][COL], arr2[ROW][COL];

    printf("Enter first matrix: \n");

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            scanf("%d", &arr1[i][j]);
        }        
    }

    printf("\nEnter second matrix: \n");

    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {         
            scanf("%d", &arr2[i][j]);
        }                
    }

    printf("\narr1 + arr2 = \n");

    // add two matrices
    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            printf("%5d ", arr1[i][j] + arr2[i][j]);            
        }        
        printf("\n");
    }       

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Enter first matrix: 
1 2 3
4 5 6

Enter second matrix: 
2 4 6
8 10 12

arr1 + arr2 = 
    3     6     9 
   12    15    18

它是如何工作的

要加或减矩阵,我们只需分别加或减每个矩阵中相应的条目。

[
\左(\ begin { array } { cc } A _ { 11 }&A _ { 12 } \ \ A _ { 21 }&A _ { 22 } \ end { array } \右)+\左(\ begin { array } { cc } B _ { 11 }&B _ { 12 } \ \ B _ { 21 }&B _ { 22 } \ end { array } \右)= \左(\ begin array { cc } A _ { 11 }

请注意,只有当两个矩阵大小相同时,矩阵加法或减法才是可能的。

以下是上述程序的工作原理:

  • 第 15-22 行的第一个 for 循环要求用户输入第一个矩阵。
  • 第 26-33 行的第二个 for 循环要求用户输入第二个矩阵。
  • 第三个 for 循环(第 38-45 行)通过在每个矩阵中添加相应的条目来显示结果矩阵。

推荐阅读:



C 程序:相乘两个矩阵

原文:https://overiq.com/c-examples/c-program-to-multiply-two-matrices/

最后更新于 2020 年 9 月 23 日


下面是一个 C 程序,用来相乘两个矩阵:

/******************************************
 Program to multiply two matrices
******************************************/

#include<stdio.h> // include stdio.h
#define ROW1 2
#define COL1 2
#define ROW2 COL1
#define COL2 3

int main()
{
    int i, j, arr1[ROW1][COL1],
              arr2[ROW2][COL2],
              arr3[ROW1][COL2];

    printf("Enter first matrix (%d x %d): \n", ROW1, COL1);

    // input first matrix
    for(i = 0; i < ROW1; i++)
    {
        for(j = 0; j < COL1; j++)
        {            
            scanf("%d", &arr1[i][j]);
        }                
    }

    printf("\nEnter second matrix (%d x %d): \n", ROW2, COL2);

    // input second matrix
    for(i = 0; i < ROW2; i++)
    {
        for(j = 0; j < COL2; j++)
        {            
            scanf("%d", &arr2[i][j]);
        }                
    }

    printf("\narr1 * arr2 = ");        

    // multiply two matrices
    for(i = 0; i < ROW1; i++)
    {
        for(j = 0; j < COL2; j++)
        {
            arr3[i][j] = 0;

            for(int k = 0; k < COL1; k++)
            {
                arr3[i][j] += arr1[i][k] * arr2[k][j];            
            }                                    
        }                

        printf("\n");
    }       

    // print the result
    for(i = 0; i < ROW2; i++)
    {
        for(j = 0; j < COL2; j++)
        {
            printf("%d ", arr3[i][j]);
        }              
        printf("\n");
    }

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Enter first matrix (2 x 2): 
2 3
4 5

Enter second matrix (2 x 3): 
6 4 2
7 8 9

arr1 * arr2 =

33 32 31 
59 56 53

它是如何工作的

只有当第一个矩阵的列数等于第二个矩阵的行数时,两个矩阵才能相乘。

设,A为大小为2x3的矩阵,B为大小为3x2的矩阵,则,A * B由下式给出:

[
\左(\ begin { array } { CCC } a&b&c \ \ d&e&f \ end { array } \右)* \左(\ begin { array } { cc } g&h \ \ k&l \ \ o&p \ end { array } \右)= \左(\ begin { array } { cc } a * g+b * k+c * o&a * h+b * l+c * p \ \ d * g+o

一般来说,如果矩阵A的大小是m x nB的大小是n x p,那么矩阵A * B的大小就是m x p

以下是该程序的工作原理:

  1. 第一个 for 循环(第 20-26 行)要求用户输入第一个矩阵。
  2. 第二个 for 循环(第 31-37 行)要求用户输入第二个矩阵。
  3. 第三个 for 循环(第 42-55 行)将矩阵相乘。
  4. 第四个 for 循环(第 58-65 行)打印矩阵乘法的结果。

推荐阅读:



C 程序:寻找矩阵转置

原文:https://overiq.com/c-examples/c-program-to-find-the-transpose-of-a-matrix/

最后更新于 2020 年 9 月 23 日


下面是一个求矩阵转置的 C 程序:

/********************************************
* Program to find the transpose of a matrix
********************************************/

#include<stdio.h> // include stdio.h
#define ROW 2
#define COL 4

int main()
{
    int i, j, mat[ROW][COL], trans_mat[COL][ROW];

    printf("Enter matrix: \n");

    // input matrix
    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {            
            scanf("%d", &mat[i][j]);
        }        
    }

    /* create transpose matrix by  
     * switch entries
     */ 
    for(i = 0; i < ROW; i++)
    {
        for(j = 0; j < COL; j++)
        {
            trans_mat[j][i] = mat[i][j];
        }                
    }       

    printf("\nTranspose matrix: \n");

    // print transpose matrix
    for(i = 0; i < COL; i++)
    {
        for(j = 0; j < ROW; j++)
        {
            printf("%d ", trans_mat[i][j]);
        }        

        printf("\n");
    }

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:

Enter matrix: 
1 2 3 4
5 6 7 8

Transpose matrix: 
    1     5 
    2     6 
    3     7 
    4     8

它是如何工作的

A是一个大小为m x n的矩阵,那么通过行列互换得到的矩阵称为【T2 的转置】。

矩阵的转置由(A^T\表示)。例如:

[
A = \左(\开始{ array } { cc } 1&2 \ \ 3&4 \ \ 5&6 \结束{ array } \右)
]

然后

[
a^t = \左(\开始{ array } { CCC } 1&3&5 \ \ 2&4&6 \结束{ array } \右)
]

以下是该程序的工作原理:

  1. 第一个 for 循环(第 16-22 行)要求用户输入矩阵。
  2. 第二个 for 循环(第 27-33 行)通过交换行和列来创建转置矩阵。
  3. 第三个 for 循环(第 38-46 行)打印矩阵的转置。

推荐阅读:



C 程序:使用线性搜索搜索项目

原文:https://overiq.com/c-examples/c-program-to-search-for-an-item-using-linear-search/

最后更新于 2020 年 9 月 23 日


线性搜索

在线性搜索中,我们从数组的开头开始搜索目标项。如果目标等于索引 0 处的元素,那么我们就找到了目标。否则,我们继续在数组中一个接一个地搜索目标,直到找到匹配。线性搜索有时也称为顺序搜索。

当数组的元素没有排序时,我们通常使用线性搜索。

让我们举个例子:

假设,我们有一个数组arr声明并初始化为:

int arr[] = {100, 50, 99, 44, 12};

以下是在数组中搜索值44的步骤。

  1. 在索引0处搜索44。从,44 != arr[0]开始,我们进入下一个指数。
  2. 在索引1处搜索44。从,44 != arr[1]开始,我们进入下一个指数。
  3. 在索引2处搜索44。从,44 != arr[2]开始,我们进入下一个指数。
  4. 在索引4处搜索44。从,44 == arr[2]开始,我们就找到了目标。此时,我们不需要继续下一个指数。所以,我们的搜索到此结束。

时间复杂性

现在让我们根据大 0 符号来检查线性搜索的效率。

在最坏的情况下,如果数组中有 100 个元素,那么线性搜索将需要 100 步。同样,如果数组中有 1000 万个元素,那么线性搜索将需要 1000 万步。

:最坏情况下,我们指的是在数组末尾找到目标。

一般来说,我们可以说,在最坏的情况下,线性搜索将采取与数组中元素一样多的步骤。因此,如果数组中有N个元素,那么线性搜索将采取N步。

上述陈述可以用大 O 符号表示如下:

O(N)

这被解读为 n 的大 O。

请记住,简单来说,O(N)仅仅意味着N个元素,一个算法需要N个步骤。

线性搜索 C 程序

下面是一个使用线性搜索算法搜索目标的 C 程序:

/***************************************************
* Program to search for an item using Linear Search 
****************************************************/

#include<stdio.h> // include stdio.h
#define SIZE 10

int main()
{
    int arr[SIZE] = {100, 91, 22, 52, 71, 9, 11, 24, 2, 80}, is_found = 0;
    int target; // number to be searched

    printf("Enter element to search: ");
    scanf("%d", &target);

    // search for the target sequentially
    for(int i = 0; i < SIZE; i++)
    {
        if(target == arr[i])
        {
            // if target is found stop the search and break out
            is_found = 1;
            break;
        }
    }

    if(is_found)
    {
        printf("Item %d found.", target);
    }
    else
    {
        printf("Item %d not found.", target);
    }

    // signal to operating system everything works fine
    return 0;
}

现在试试

预期输出:第一次运行:

Enter element to search: 80
Item 80 found.

第二次运行:

Enter element to search: 200 
Item 200 not found.

它是如何工作的

在第 13 行,我们要求用户输入一个要搜索的数字。

第 14 行的scanf()函数从键盘读取输入,并将其存储在名为target的变量中。

在第 17-25 行,我们使用 for 循环迭代数组中的元素。如果目标等于数组中的当前元素,我们将is_found设置为1,并使用break语句退出 for 循环。否则,我们会一直寻找目标,直到到达数组的末尾。

第 27-34 行的 if-else 语句检查is_found变量的值,以确定我们是否找到了目标,并显示适当的消息。


推荐阅读:



C 程序:使用二分搜索搜索项目

原文:https://overiq.com/c-examples/c-program-to-search-for-an-item-using-binary-search/

最后更新于 2020 年 9 月 23 日


线性搜索允许我们在未排序的数组中搜索目标项目。

然而,如果数组是排序的,那么我们可以使用一个更有效的算法,叫做二分搜索法。

二进位检索

在二分搜索法,我们从检查数组中间元素的目标值开始。如果目标值等于中间元素,我们的搜索就成功了,我们就完成了。如果目标值与中间元素不同,并且由于数组是排序的,我们可以自动移除数组中一半可能的元素。

如果目标值小于中间元素,那么我们可以得出结论,目标值必须位于数组的下半部分。否则,目标值必须位于数组的上半部分。

假设目标值小于中间元素,因此我们继续在数组的下半部分搜索,再次获取目标值并将其与中间元素进行比较。我们不断重复这个过程,直到找到目标值,或者没有更多的元素可以搜索。

举个例子吧。

假设,我们有一个数组arr声明并初始化为:

#define SIZE 10
int arr[SIZE] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; // sorted array

为了跟踪我们将在数组中搜索的索引位置,我们需要三个额外的变量:lowupmidpoint

low变量是指数组的下界。最初将设置为0

low = 0

up变量是指数组的上限。最初,它将被设置为比数组小一。

up = SIZE - 1

midpoint变量是指数组下限和上限之间的中点。midpoint的值计算如下:

midpoint = (low + up) / 2

如果target > arr[midpoint],那么我们可以断定目标值位于中间元素右侧的某个地方。为了开始在数组的右边部分搜索,我们将low的值更新为大于midpoint1,即low = midpoint + 1。上界(up)的值还是一样的,不需要改动。

如果target < arr[midpoint],那么我们可以断定目标值位于中间元素左侧的某个地方。为了开始在数组的左边搜索,我们将up的值更新为1小于midpoint,即up = midpoint - 1。下界(low)的值还是一样的,不需要改动。

如果target == arr[midpoint],找到目标值,我们的搜索成功。

如果low > up,则没有找到目标值,因为没有更多的元素可以搜索。

以下是在排序数组中搜索目标值60的步骤。

第一步:

low = 0, up = 9,midpoint = 4

60 > arr[4] => 60 > 50,所以搜索将进行到数组的右边部分。

low = midpoint + 1 => low = 5
midpoint = (low + up) / 2 => m = (5 + 9) / 2 => m = 7

第二步:

low = 5, up = 9, midpoint = 7

60 < arr[7] => 60 < 80,所以搜索将进行到数组的左边部分。

up = midpoint - 1 => 6
midpoint = (5 + 6) / 2 => m = 5

第三步:

low = 5, up = 6, midpoint = 5

60 == arr[5] => 60 == 60,找到目标值。我们的搜索成功了。

请注意,我们已经通过 3 个步骤找到了目标值。使用线性搜索算法的相同搜索需要 6 个步骤。

现在,让我们看看如果在数组中找不到目标值会发生什么。

以下是在排序数组中搜索目标值95的步骤。

第一步:

low = 0, up = 9, midpoint = 4

95 > arr[4] => 95 > 50,所以搜索将进行到数组的右边部分。

low = midpoint + 1 => low = 5
midpoint = (low + up) / 2 => midpoint = (5 + 9) / 2 => midpoint = 7

第二步:

low = 5, up = 9, midpoint = 7

95 > arr[7] => 95 > 80,所以搜索将进行到数组的右边部分。

low = midpoint + 1 => 8
midpoint = (8 + 9) / 2 => midpoint = 8

第三步:

low = 8, up = 9, midpoint = 8

95 > arr[8] => 95 > 90,因此搜索将再次进行到数组的右边部分。

low = midpoint + 1 => 9 
midpoint = (9 + 9) / 2 => midpoint = 9

第四步:

low = 9, up = 9, midpoint = 9

95 < arr[9] => 95 < 100,因此搜索将再次进行到数组的右边部分。

up = midpoint - 1 => 8 
midpoint = (10 + 9) / 2 => midpoint = 8

注意第一次low ( 9)的值大于up ( 8)的值。因此,数组中不存在我们的目标值。

时间复杂性

现在让我们用大 O 符号来检验二分搜索法的效率。

如果数组的大小是3,那么找到目标值所需要的最大步数是 2。

同样,如果数组的大小是8,那么找到目标值所需要的最大步数是 3。

下表列出了数组的大小以及查找目标值所需的最大步骤数。

大小 步伐
three Two
eight three
Sixteen four
Thirty-two five
Sixty-four six

一般来说,我们可以说,对于大小为N的数组,找到目标值所需的最大步骤数是(\log_2N)或简单地(\log{}N)。

上述陈述可以用大 O 符号表示如下:

[\ math { o }(\ log { } n } ]

简单来说,(\ mathcal { O }(\ log { N }))仅仅意味着对于N个元素,一个算法需要(\ log { N } )个步骤

二分搜索法碳计划

下面是一个使用二进制搜索算法搜索目标的 C 程序:

/*****************************************************
 Program to search for an item using binary search
 *****************************************************/

#include <stdio.h>
#define SIZE 10

int binary_search(int arr[], int target); // function declaration

int main() 
{

    int arr[SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // sorted array
    int target; // number to be searched
    int index;  // index position of the target

    printf("Enter number to search: ");
    scanf("%d", &target);

    index = binary_search(arr, target);  

    if(index !=  -1)
    {
        printf("Item %d found at index %d.", target, index);
    }
    else
    {
        printf("Item %d not found.", target);    
    }   

    return 0;
}

/*
 * binary_search() returns index position of the 
 * target item. Returns -1, if the target item is not found.
 */

int binary_search(int arr[], int target)
{
    /* 
       variables to keep track of the index positions
       where we will search in the array
    */
    int low = 0, up = SIZE - 1, midpoint;    

    while(low <= up)
    {
        midpoint = (low + up) / 2;  // calculate midpoint

        if(target > arr[midpoint])
        {
            // proceed search to the right portion
            low = midpoint + 1;
        }

        else if(target < arr[midpoint])
        {
            // proceed search to the left portion
            up = midpoint - 1;
        }

        else if(target == arr[midpoint])
        {
            // target value found. Return the index and exit the function
            return midpoint;
        }        
    }

    // target value not found, return -1 and exit the function
    return -1;
}

现在试试

预期输出:第一次运行:

Enter number to search: 5
Item 5 found at index 4.

第二次运行:

Enter number to search: 101
Item 101 not found.

它是如何工作的

程序的核心在于binary_search()功能。

binary_search()函数以数组和目标值为自变量,返回目标值的索引位置。如果没有找到目标值,则返回-1

在第 45 行,我们声明变量来跟踪我们将在数组中搜索的索引位置。

在第 47-68 行中,我们有一个 while 循环,它一直运行到下限(low)的值小于或等于上限(up)的值。

在第 51-67 行,我们有一个 if-else 语句,它将目标值与中间元素进行比较,并相应地更新下限(low)和上限(up)的值。在目标值等于中间元素的情况下,我们返回中间元素的索引位置并退出函数。

当循环终止时,我们返回一个值-1,表示目标值不在数组中。


推荐阅读:



C 程序:使用冒泡排序法对数组升序排序

原文:https://overiq.com/c-examples/c-program-to-sort-an-array-in-ascending-order-using-bubble-sort/

最后更新于 2020 年 9 月 23 日


冒泡排序

冒泡排序是一种简单的方法,它将数组的元素按升序或降序排序。它的工作原理是比较相邻的元素,如果它们顺序不对,就交换它们。需要多次通过阵列。

以下是使用冒泡排序以升序对大小为N的数组进行排序的步骤:

通过#1 :

  1. arr[0]arr[1]进行比较。如果arr[0] > arr[1],交换他们。
  2. arr[1]arr[2]进行比较。如果arr[1] > arr[2],交换他们。
  3. 最后将a[N-2]arr[N-1]进行对比,如果arr[N-2] > arr[N-1]的话,互换。

这完成了第一次通过。

在第一次通过之后,数组中的最高值将位于末尾。

通过#2 :

  1. arr[0]arr[1]进行比较。如果arr[0] > arr[1],交换他们。
  2. arr[1]arr[2]进行比较。如果arr[1] > arr[2],交换他们。
  3. 最后将a[N-3]arr[N-2]进行对比,如果arr[N-3] > arr[N-2]的话,互换。

这完成了第二次通过。在此传递之后,第二高的元素将位于数组中第二高的索引处。

请注意,在第二次通过的最后一步中,我们没有将第二个最后元素即a[N-2]与最后一个元素即arr[N-1]进行比较,这是因为最后一个元素已经处于其正确位置。

通过#3 :

  1. arr[0]arr[1]进行比较。如果arr[0] > arr[1],交换他们。
  2. arr[1]arr[2]进行比较。如果arr[1] > arr[2],交换他们。
  3. 最后将a[N-4]arr[N-3]进行对比,如果arr[N-4] > arr[N-3]的话,互换。

这完成了第三次通过。在此传递之后,第三高的元素将位于数组中第三高的索引处。

就这样,我们不停地穿过阵列。当我们遇到一个没有交换任何元素的通道时,我们就停下来。

举个例子吧。

假设,我们有一个数组arr声明并初始化为:

#define SIZE 5
int arr[SIZE] = {80, 60, 90, 10, 40}; // unsorted array

以下是使用冒泡排序按升序对该数组进行排序的步骤。

通过#1 :

第一步:比较806080 > 60自,互换它们:

| 60 | 80 | 90 | 10 | 40 |

第二步:比较809080 < 90自,我们什么都不做:

| 60 | 80 | 90 | 10 | 40 |

第三步:比较901090 > 10自,互换它们:

| 60 | 80 | 10 | 90 | 40 |

第四步:比较904090 > 40自,互换它们:

| 60 | 80 | 10 | 40 | 90 |

这完成了第一次通过。最高的元素,即90,现在在数组的末尾。在这个通道中,我们进行了三次互换。所以,我们需要进行另一次穿越。请记住,我们会一直穿过数组,直到遇到一个没有交换任何元素的通道。

通过#2 :

第一步:比较608060 < 80自,我们什么都不做:

| 60 | 80 | 10 | 40 | 90 |

第二步:比较801080 > 10自,互换它们:

| 60 | 10 | 80 | 40 | 90 |

第三步:比较804080 > 40自,互换它们:

| 60 | 10 | 40 | 80 | 90 |

这完成了第二次通过。第二高的元素,即80,现在位于数组中第二高的索引处。另外,请注意,我们没有将8090进行比较。这是因为元件90已经处于从通道#1 开始的正确位置。

我们在这个通道中进行了两次交换。所以,我们需要再表演一个。

通过#3 :

第一步:比较601060 > 10自,互换它们:

| 10 | 60 | 40 | 80 | 90 |

第二步:比较604060 > 40自,互换它们:

| 10 | 40 | 60 | 80 | 90 |

这完成了第三次通过。第三高的元素,即60,现在位于数组中第三高的索引处。另外,请注意,我们没有将6080进行比较。这是因为元件80已经从通道#2 处于其正确位置。

我们在这个通道中进行了两次交换。所以,我们需要再表演一个。

通过#4 :

第一步:比较104010 < 40自,我们什么都不做:

| 10 | 40 | 60 | 80 | 90 |

这完成了第四次通过。我们没有在这个通道里交换任何东西。所以,我们需要不需要再表演一个。数组中的所有元素现在都按升序排序。

冒泡排序程序

下面是一个使用冒泡排序算法对数组进行升序排序的 C 程序:

/****************************************************************
 * Program to sort an array in ascending order using Bubble sort  
 ****************************************************************/

#include<stdio.h> // include stdio.h library
#define MAX 5
void bubble_sort(int arr[]); // function declaration

int main(void)
{        
    int arr[MAX]; 

    // input array
    for(int i = 0; i < MAX; i++)
    {
        printf("arr[%d] = ", i);    
        scanf("%d", &arr[i]);
    }

    printf("\nUnsorted array: \n");

    // print unsorted array
    for(int i = 0; i < MAX; i++)
    {
        printf("%d ", arr[i]);    
    }

    // sort array
    bubble_sort(arr);

    printf("\n\nSorted array: \n");

    // print sorted array
    for(int i = 0; i < MAX; i++)
    {
        printf("%d ", arr[i]);    
    }

    return 0; // return 0 to operating system
}

/*
 *  bubble_sort() takes an array and sorts it
 *  in the ascending order
 */

void bubble_sort(int arr[])
{
    int tmp,  // temporary variable to hold one of the values while swapping
        is_swapped; // variable to indicate whether we have made any swaps during the passthrough

    for(int i = 0; i < MAX; i++)
    {
        // re-initialize is_swapped to 0 after every passthrough       
        is_swapped = 0;  

        for(int j = 0; j < MAX - 1 - i; j++)
        {            
            if(arr[j] > arr[j+1]) // compare adjacent elements
            {
                // swap adjacent elements
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;

                // set is_swapped to 1, to indicate
                // that we have made at least one 
                // swap during the passthrough
                is_swapped = 1;               
            }     
        }        

        // if no swaps are made in the last passthrough,
        // exit the outer for loop

        if (!is_swapped)
        {
            break;
        }
    }        
}

现在试试

预期输出:

Enter array: 
arr[0] = 80
arr[1] = 60
arr[2] = 90
arr[3] = 10
arr[4] = 40

Unsorted array: 
80 60 90 10 40 

Sorted array: 
10 40 60 80 90

它是如何工作的

所有工作都在bubble_sort()功能中完成:

以下是它的工作原理:

在第 50 行和第 51 行,我们已经声明了两个变量:tmpis_swapped。在交换元素时,tmp变量将保存其中一个值,is_swapped用作一个标志,指示我们在传递过程中是否进行了任何交换。

在第 53-81 行,我们有一个外部 for 循环,它一直运行到数组没有被排序。

第 58-72 行的内部 for 循环在传递过程中交换无序元素。它从数组的开头开始,一直到还没有排序的索引。

如果我们在通过期间至少进行了一次交换,我们将is_swapped设置为1(第 70 行)。

最后,第 77-80 行的 if 语句检查is_swapped的值,以确定是否脱离外部 for 循环。当我们遇到一个没有交换任何元素的通道时,我们脱离了外部 for 循环。

请记住,上面的函数按升序对数组进行排序。要按降序排列元素,只需将第 60 行的 if 条件从arr[j] > arr[j+1]更改为arr[j] < arr[j+1]


推荐阅读:



C 程序:检查一个字符串是否是回文

原文:https://overiq.com/c-examples/c-program-to-check-whether-a-string-is-palindrome-or-not/

最后更新于 2020 年 7 月 27 日


[no_toc]

什么是回文?

一个数字或一个单词即使被颠倒了也保持不变,叫做回文。比如妈妈,雷达,或者数字 45654,都是回文。

下面是一个判断一个字符串是否是回文的 C 程序。

/*******************************************************
 Program to check whether a string is palindrome or not
 * 
 * Enter a word: racecar
 * racecar is palindrome
 *******************************************************/

#include<stdio.h> // include stdio.h library
#include<string.h> // include stdio.h library

int main(void)
{       

    int len, i= 0, j, is_palindrome = 1;

    char word[50];

    printf("Enter a word: ");
    scanf("%s", word);

    j = strlen(word) - 1;  // get the last valid index

    while(i <= j)
    {
        if (word[i] != word[j])
        {
            is_palindrome = 0;
            break;
        }

        i++;  
        j--;
    }

    if(is_palindrome)
    {
        printf("%s is palindrome", word);
    }
    else
    {
        printf("%s is not palindrome", word);
    }

    return 0;
}

预期输出:第一次运行:

Enter a word: racecar
racecar is palindrome

第二次运行:

Enter a word: netbeans
netbeans is not palindrome

相关程序: C 程序检查数字是否为回文

它是如何工作的

下表演示了 while 循环每次迭代时发生的情况,假设word = radar

循环 情况 j
第一次迭代后 word[0]!=word[1]=>'r'!='r'=>0 i=1 j=3
第二次迭代后 word[1]!=word[3]=>'a'!='a'=>0 i=2 j=2
第三次迭代后 word[2]!=word[2]=>'d'!='d'=>0 i=3 j=1

因此,字符串radar是回文。


推荐阅读:



posted @ 2024-11-02 15:52  绝不原创的飞龙  阅读(4)  评论(0编辑  收藏  举报