这是一篇C语言程序设计入门的教程,我寄希望于通过这篇简单的入门教程让你理解C语言以及程序设计基础。或许有人会嘲笑我,人家最短的C语言入门也得21天,你怎么就能够通过一篇文章,讲透C语言呢?emmm,话说我这里想要讲的是C语言的基本用法。有些功能会很简单,而不去过多的牵扯太多语法上的细节。

0x00 写在前面的话

这篇文章可以算是学习 C 语言的一个引子,不太明白的地方可以发送邮件至笔者邮箱,笔者将通过邮件方式进行回复,或者更新博客文章以更加详细的完善本文。本文尽量做到语言简洁明了,通俗易懂。正因为如此,一些概念上的描述应该会与教科书不同。

0x01 基础知识

什么是程序设计?

让我们先从程序设计开始说起吧。说起程序设计,一个最简单,也是大家应该都听说过的例子就是:“如何将大象放进冰箱。” 这个例子就是一个程序设计的问题。那么以下我们给出部分网友给出的方法:

1. 打开冰箱门
2. 将大象放进冰箱
3. 关上冰箱门

从上面这个例子,我们能够看出:程序就是将一系列具体明确的动作按照规定好的顺序依次执行。这里我们给出了程序的两个特点:1. 动作具体明确;2. 动作执行具有一定的顺序。

解释一下:

  1. 动作具体明确:网络上经常开玩笑说程序员都是钢铁直男。确实,不得不说,程序员在有些时候遇到问题就是这个样子的,因为程序员每天的日常工作都是跟计算机打交道,程序员需要将需求(需要完成的任务、实现的功能)转化成计算机可以执行的程序代码,而由于计算机只能够执行一些非常简单且动作明确的指令。(比如:计算1+2的结果等等。)日常工作导致了我们程序员在思考问题的时候容易具有这样的特点。当然,大部分程序员在工作以外,也还是会比较懂情调的,不会显得那么木讷。
  2. 规定好的顺序:什么叫规定好的顺序呢?就像上面的例子中,只有先打开冰箱门这个动作完成了之后,才能将大象放进冰箱,当执行完将大象放进冰箱这个动作后,最后才能去关上冰箱门。这样的例子现实生活中到处都是。(比如:在某宝上购物,你首先需要打开某宝APP,然后找到想要购买的商品,接着下单,付款,在付款之后卖家收到商品购买通知,卖家才会发货。)

以上两点,是笔者对于学习程序设计的人们一个最基本的思维要求。也就是要求大家在设计程序的时候要想清楚:程序里哪些动作是具体明确的,哪些动作先执行,哪些动作后执行。

那么,讲了这么多我们还是没有讲到程序设计到底是个啥。这里我们要说的是计算机编程中的程序设计,程序设计是指通过计算机编程语言设计一个让计算机按照规定好的顺序执行动作具体明确的指令以帮助我们解决问题。

什么是 C 语言?

那么,什么是C语言呢?C语言就是一种计算机语言。因为计算机只能够执行由01组成的二进制机器指令,而直接用01直接编写计算机能够执行的机器代码,对人类来说是一件非常困难的事情,于是人们设计了各种各样的程序设计语言帮助人们编写计算机程序,然后将人们根据该语言规定好的语法规则写出来的程序代码转化成计算机可以直接执行的机器代码。而C语言则是这些设计出来帮助大家编写程序的计算机程序设计语言中的一种。

每种语言都会有自己规定好的语法规则,这也就意味着,如果你不按照规定好的语法规则去编写代码的话,那么该语言的编译器或者解释器则不能够将代码转化为机器能够执行的机器代码,即:编译失败!!!所以,学习程序设计语言,一定要记住该语言的语法规则。

0x02 C 语言程序设计环境安装

环境安装部分,大家可以执行网上寻找解决方案。

Windows 上笔者推荐 “MinGW + Sublime Text 3 + 命令行” 的方式。

macOS 和 Linux 上环境则简单一些,应该都是可以直接使用 gcc 编译器的。

0x03 数据类型

计算机处理数据都是01的二进制数据,但是我们在进行程序设计的时候,遇到的各种问题,我们会需要特定的数据类型来表示我们的数据。比如:在计数的时候我们需要用到整数;在处理金额数值的时候我们需要用到小数;在处理文本内容的时候我们需要用到字符串;等等。

接下来我们对数据类型进行分类:

整型:int

浮点类型:float, double

字符类型:char

空类型:void

以上是我们最常见的数据类型。

类型是用来对变量进行描述的。比如:我们要创建一个变量用来存放计数器的数值信息,则我们可以通过 int count;声明这个变量。(变量就是存放在内存中的一个名称标记,不然怎么才能知道内存应该保存在哪里、从哪里取这个数值呢?变量之所以称之为变量,是因为其值是可以改变的,我们可以通过程序指令对其存储的值进行修改。与变量相对还有叫常量,其值是不可以改变的,常量的作用是用来便于表示代码中一些特定的数值的。)

常量的作用。比如粉红色对应的RGB的值是255,192,203,其对应十六进制值就是#FFC0CB,在程序中出现这样的数值,我们称之为魔法数字。那么如果程序员直接在程序中给一个变量赋值为粉红色的值的话,代码 color_val = 0xFFC0CB; 则显得不方便阅读。于是我们可以通过宏定义去定义常量并在需要使用的地方去使用:

#define COLOR_PINK 0xFFC0CB

...

color_val = COLOR_PINK;

对了,这里有一点要明白的:变量只有在声明了之后才可以使用。如:

int count;
count = 0;
count = count + 1;
printf("%d\n", count);

以上四行代码依次为:

  1. 声明 int 类型的变量 count ;
  2. count 类型赋值为 0 ;
  3. count 的值增加1;
  4. 输出 count 的值。

0x04 控制流

程序设计的控制流即控制程序执行的顺序。三大控制流:顺序结构选择结构循环结构。这部分知识点的难点在于理解循环结构。

顺序结构:程序按照代码排列的顺序从上到下依次执行。比如:

x = 4;
y = x * x;

以上例子先执行 x = 4; 然后执行 y = x * x;

这部分非常容易理解,对吧。

选择结构:通过判断来决定程序接下来执行哪部分代码块。比如:

if (表达式)
{
    语句块1;
}

解释:程序执行到 if 这个部分时,先执行表达式进行判断,如果判断成立则执行里面的”语句块1”,然后从 } 后继续执行;否则直接从 } 后面继续执行。

循环结构: 通过循环结构来让程序不断执行某一部分代码,直到达到某种条件并跳出此结构。循环结构控制有:while, do~while, for 三种。

以上我们说的顺序结构和选择结构,其对应的每一条语句都只能够执行一次,然后程序就继续往后面的执行了。我们一起来思考一个问题,假如我们要计算 1+2+3+4+5 这样的问题,当然这里不考虑等差数列公式哈:)。那么我们在没有循环的情况下,代码应该是下面这样的:

int sum;
sum = 0;
sum += 1;
sum += 2;
sum += 3;
sum += 4;
sum += 5;

这还是少的,那如果是 1+2+3+...+20000 呢?你还能愉快的写代码吗?我看不能!而且,当我最后这个是个变量呢?因为我想让这个程序更加通用,让这个程序允许用户输入一个数字,然后计算从 1 开始加一直加到这个数字的和。

那么这里我们就可以通过 while 语句来解决以上问题:

int sum, i;
sum = 0;
i = 1;
while (i <= 5)
{
    sum += i;
    i++;
}

解释一下以上的 while 结构,当程序执行到 while 时,程序接下来对 i <= 5 部分进行判断,如果成立则执行 {} 里面的,在执行完 {} 里面的跳回 while 这个地方;否则,直接从 } 后面开始执行。好,我们来执行一下程序看看:

1: sum = 0;
2: i = 1;

3: i <= 5? 成立
4: sum += i; (sum值为1)
5: i++; (i值为2)

6: i <= 5? 成立
7: sum += i; (sum值为3)
8: i++; (i值为3)

9: i <= 5? 成立
10: sum += i; (sum值为6)
11: i++; (i值为4)

12: i <= 5? 成立
13: sum += i; (sum值为10)
14: i++; (i值为5)

15: i <= 5? 成立
16: sum += i; (sum值为15)
17: i++; (i值为6)

18: i <= 5? 不成立
19: 执行 } 后面的代码

跟着以上步骤执行一下,我觉得差不多应该明白循环结构 while 是怎么执行的了。

那么 do~while 结构呢?由于我们并不常用到它,所以这里就不再过多去浪费篇幅去说了。

接下来我们重点讲一讲 for 结构。其实 forwhile 是一样的。for 算是对 while 结构的改进。我们下面的代码,再来对比地看一下上面 while 写的代码。我们发现,其实就是将 i = 1i++ 这两个语句放到 for 整个大括号里面去了。而这两个程序执行的过程是完全一样的。笔者把for 结构理解成三个部分 for (初始;条件;自增) 。这也是控制一个循环结构的三个要素,进入循环之前,我们需要设置一些变量的初始值;然后控制条件,再满足条件的情况下让循环里面的 {} 代码块部分执行,然后不断通过 “自增” 去改变变量的值,直至变量在条件判断的时候不成立,使得程序跳出循环结构。

int sum, i;
sum = 0;
for (i = 1; i <= 5; i++)
{
    sum += i;
}

注:学习循环结构的时候,一定要注意循环变量开始值和终止值,也就是从哪个值开始,到那个值结束。

0x05 数组

现实生活中就有很多关于数组的例子,比如:名单,购物列表,成绩单,等等。将一些具有同样属性类型的元素按照一定的顺序放在一起,就构成了一个列表,也就是数组了。对于数组的声明,我们需要三个要素:数组存储数据元素的类型数组名称数组大小。声明数组的格式为:

类型 名称[大小];

比如一个班级一次数学考试的成绩(假设成绩都是整数),班级有50位学生,那么用于存储学生成绩的数组则可以如下声明:

int math_score[50];

那么对于数组中元素应该如何引用呢?看下文中的例子就明白了:

math_score[0] = 100; // 对 math_score 这个数组中,下标为 0 的元素,赋值为 100
val = math_score[1]; // 将 math_score 这个数组中,下标为 1 的元素的值取出来,赋值给 val 这个变量

这里有一点需要交待一下的,也是数组中非常重要的一点,那就是:数组的引用下标是从 $0$ 开始的。也就是说数组第一个元素,应该是下标为 $0$ 的元素,即 math_score[0] ,那么对于上面声明的这个数组来说,可用的下标元素就有:

math_score[0], math_score[1], math_score[2], … , math_score[48], math_score[49]

这个部分关于数组下标的内容是比较重要的,需要花时间去理解的,这跟我们日常计数有点不一样。

下面我们来写一个例子:从键盘上输入 10 个整数到一个数组中,并计算这些数字的总和。

#include <stdio.h>

int main() {
    int a[10];
    int sum = 0;

    /* 1. 输入 10 个整数,请注意循环中 i 的初始值以及终止值 */
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    /* 2. 通过循环将每个元素的值累加到 sum 上 */
    for (int i = 0; i < 10; i++) {
        sum += a[i];
    }

    /* 3. 输出累加结果 */
    printf("%d\n", sum);

    return 0;
}

从上面的例子中,我们可以看到,有了循环操作,数组才变得更加灵活。

0x06 函数

函数,是一个数学的概念。比如:$f(x) = x^2$ 就是一个函数。这是一个抽象的概念,是一个统一概括的概念。当然,我们都知道这是一个计算平方值的函数,当我们说到这个函数的时候,我们并没有说这是计算某个值的平方值。这时我们就是一个统一概括的概念,表示这个函数具有计算某一类数值平方值的功能。当我们计算 $10$ 的平方的时候,也就是 $f(10)$ 时,这时就是我们去调用这个函数进行计算的一个过程了。

在 C 语言中,函数会非常的常见,我们可以通过函数,将各种各样的操作抽象出来,然后去描述我们所要解决的那些问题。比如计算平方值。在定义一个函数的时候,我们需要知道这个函数所拥有的几个属性:函数参数函数返回值是什么类型的函数的名称

我们可以从下面这个例子来看一下函数定义是什么样子的。

int f(int x) {
    return x * x;
}

其实,函数本质上就是一个代码片段,这个代码片段是从哪里开始的呢?答案是:从函数参数开始的,当我们调用这个函数的时候,就会从函数参数部分开始执行,将我们调用函数所使用的参数,将值给函数代码片段的 x . 然后开始执行……

所以,函数就是一个代码片段,当你调用这个函数的时候,这部分代码才会开始执行,最后将执行的结果返回给你。当你调用 f(10) 这部分最终会返回 $100$ 给你,当你调用 f(8) 则会返回 $64$ 给你。

那我们接下来看下一个例子:计算一组整数的和

int calc_arr_sum(int a[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += a[i];
    }
    return sum;
}

对于这样一个函数,需要两个参数,数组 a 的名称 和 数组大小值。因为函数传输过来的是数组 a 的首地址,而这个地址中并不包含这个数组有多大的信息,所以需要另一个参数值来传输数组大小的信息。

那么我们在数组这一节最后的例子就可以用以下完整代码来编写了:

#include <stdio.h>

int calc_arr_sum(int a[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += a[i];
    }
    return sum;
}

int main() {
    int a[10];
    int sum = 0;

    /* 1. 输入 10 个整数,请注意循环中 i 的初始值以及终止值 */
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    /* 2. 调用计算数组元素和的函数进行计算 */
    sum = calc_arr_sum(a, 10);

    /* 3. 输出累加结果 */
    printf("%d\n", sum);

    return 0;
}

注意:这里 main() 函数和 calc_arr_sum() 两个函数中的 a 并不是同一个,在 calc_arr_sum() 函数中的 a 是一个抽象的概念,并不是具体的哪一个数组,我们在编写这个函数的时候也是假设其为任意数组而已,很多教材上称之为形式参数。而在 main() 函数中的 a 则是通过具体的声明的,这里面存储着从键盘输入的 10 个数值,很多教材上称之为实际参数。这部分内容需要着重去理解,理解函数参数中的变量名称的意义。

0x07 指针

这部分内容是很多初学者最为头大的部分了。

0xFF C语言编程练习题

敬请期待……