0%

C语言键盘输入采集scanf函数

前面说过C语言的额printf函数,那么这一次说说scanf函数。

这两个函数像两兄弟,比如在C++里面经常遇到setget,就差不到就是scanfprintf的意思,printf用来通过终端输出一些内容,scanf用来通过终端输入一些内容。

先看看这个函数的原型:

1
2
3
# include <stdio.h>

int scanf(const char *format, ...);

哇哦,这么多没有见过的,貌似不懂什么意思,没问题,一步一步来理解。

先来个好理解的,这个函数的意思就是:

1
scanf("输入控制符", 输入参数);

就是将键盘输入的参数转换为输入控制符规定格式的数据,然后保存到输入参数地址所表示的变量里面。

举个简单的例子:

1
2
int i;
scanf("%d", &i);

这个小段的意思就是输入一个数字,把这个数字作为传入参数赋值给变量i。

这个有个问题需要特别注意,就是&i的使用,很多人刚开始都会只输入i,实际需要传入的是一个地址,特别留意。

来个简单的小例子:

1
2
3
4
5
6
7
8
9
10
11
/*beginner/scanf/scanf1.c*/

#include <stdio.h>

int main(void)
{
int i;
i = 1314;
printf("i = %d\n", i);
return 0;
}

这个我们最开始编写的程序,如果需要打印一个我们需要的,就要每次修改源码,然后再编译运行,比如刚开始我们要一生一世1314,后来一想不对呀,要生生世世3344方显诚心吗,改过来,如此这般。

但是如果碰到一个变化不断的数字怎么办,比如让你说如N个学生的成绩,用来判断是否达到老师的期望,这个时候再改源码就不合适了,怎么办呢,就需要使用scanf函数了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
/*beginner/scanf/scanf2.c*/

#include <stdio.h>

int main(void)
{
int i;
scanf("%d", &i);
printf("i = %d\n", i);
return 0;
}

不错,已经每次可以通过终端输入打印出来不同的值了。

但是最后有个提示对不对,好的,大部分人会这么写,但是就会出现各种各样的问题:

1
2
3
4
5
6
7
8
9
10
11
12
/*beginner/scanf/scanf3.c*/

#include <stdio.h>

int main(void)
{
int i;
scanf("please input i %d", &i);
printf("i = %d\n", i);
return 0;
}

正确的写法是怎么样的呢,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*beginner/scanf/scanf4.c*/

#include <stdio.h>

int main(void)
{
int i;
printf("please input i :");
scanf("%d", &i);
printf("i = %d\n", i);
return 0;
}

看到区别了吗,区别就是把要显示的东西还是使用printf来,只有最简单的输入用scanf来,其实用scanf也是可以的,如果按照scanf3的代码来,你需要在终端中输入的语句是:please input i 1314,这样才能满足你的需求,但显然这不符合你的初衷,对吧。

所以这也说明了另一个问题,那就是scanf的格式与输入需要保持严格一致。

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#beginner/scanf/Makefile
ALL : scanf1 scanf2

scanf1: scanf1.c
gcc -o scanf1 scanf1.c

scanf2: scanf2.c
gcc -o scanf2 scanf2.c

scanf3: scanf3.c
gcc -o scanf3 scanf3.c

scanf4: scanf4.c
gcc -o scanf4 scanf4.c

.PHONY : clean

clean:
rm -f scanf1 scanf2 scanf3 scanf4

运行自行琢磨。

C语言的switch分支选择语句

switch本身作为分支选择语句主要是为了多个分支选择,其实它的语句用前面提到的if..else if..else if...else也可以解决,不过总归会比较冗长,相比较而言,switch语句看起来短小精悍,容易理解。

​ 最容易理解用来使用switch语句的为计算学生分数,比如输入A就证明分数在85~100分,输入B就证明分数在70-85,输入C勉强通过为60-70分,输入D,不好意思,还不到60分了。

swtich分支选择语句

一般形式如下:

1
2
3
4
5
6
7
8
9
switch(表达式)
{
case 表达式1:语句1
case 表达式2:语句2
...
case 表达式n-1:语句n-1
case 表达式n:语句n;
default:语句n+1
}

这个语句的具体含义为:

  1. 首先判断表达式;
  2. 如果表达式为表达式1,就执行语句1;
  3. 如果表达式为表达式2,就执行语句2;
  4. 以此类推
  5. default语句为默认不满足任何语句的时候执行的语句

这里有一个特别需要注意的,就是每一个语句最后都要加上break,切记,不然执行的结果并不一定是你认为的那样,因为在每个表达式,它没有跳出来。

简单举几个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*beginner/switch/switch1.c*/
#include <stdio.h>

int main()
{
int score;
score = 'A';

switch (score)
{
case 'A':
printf("Wow, Awesome!!\n");
case 'B':
printf("Yeah, Great!!\n");
case 'C':
printf("Ok, passed!!\n");
case 'D':
printf("Sorry, you lose it!!\n");
}

return 0;
}

这个例子有一个最大的不足在于如果不满足上面说的四种情况,需要一个default语句来处理异常情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*beginner/switch/switch2.c*/
#include <stdio.h>

int main()
{
int score;
score = 'A';

switch (score)
{
case 'A':
printf("Wow, Awesome!!\n");
case 'B':
printf("Yeah, Great!!\n");
case 'C':
printf("Ok, passed!!\n");
case 'D':
printf("Sorry, you lose it!!\n");
default:
printf("BAD input\n");
}

return 0;
}

这个例子的问题就在于每一个语句最后没有跟break语句,导致每个执行都会有输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*beginner/switch/switch3.c*/
#include <stdio.h>

int main()
{
int score;
score = 'A';

switch (score)
{
case 'A':
printf("Wow, Awesome!!\n");
break;
case 'B':
printf("Yeah, Great!!\n");
break;
case 'C':
printf("Ok, passed!!\n");
break;
case 'D':
printf("Sorry, you lose it!!\n");
break;
default:
printf("BAD input\n");
break;
}

return 0;
}

完美的switch就应该是这个样子了,😁。

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#beginner/switch/Makefile

ALL : switch1 switch2 switch3

switch1 : switch1.c
gcc -o switch1 switch1.c

switch2 : switch2.c
gcc -o switch2 switch2.c

switch3 : switch3.c
gcc -o switch3 switch3.c

.PHONY : clean

clean:
rm -f switch1 switch2 switch3

输入make,然后执行各个程序输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./switch1
Wow, Awesome!!
Yeah, Great!!
Ok, passed!!
Sorry, you lose it!!

$ ./switch2
Wow, Awesome!!
Yeah, Great!!
Ok, passed!!
Sorry, you lose it!!
BAD input

$ ./switch3
Wow, Awesome!!

如何选择C语言的循环语句

对于C语言的三种语句而言,其实彼此基本可以实现功能,也就是说用for能解决的,用while也能解决,反之也是。

再看一下,两种循环结构:

  1. for循环
  2. while、do-while循环

那么是如何来进行选择呢,什么时候用哪种循环呢,这就要看具体情况,叫做it depends,^_^。

一般而言,_选用的原则_如下所示:

  1. 如果循环次数在执行循环体之前就已确定,一般用for语句。如果循环次数是由循环体的执行情况确定的,一般用 while语句或者do- while语句;
  2. 当循环体至少执行一次时,用do-while语句,反之,如果循环体可能一次也不执行,则选用while语句
    C++/C循环语句中,for语句使用频率最高,while语句其次,do语句很少用。

C语言的while循环语句

​ C语言本身有3种程序结构,分别是:

  1. 顺序结构:语句一句一句执行,从头执行到结尾;
  2. 选择结构:可以理解为是分支结构,根据判断结构来选择向哪个方向进行,类似前面说的if、else语句;
  3. 循环结构:循环结构是这3个里面最复杂的,因为它自身有一个循环体,结构本身也是根据判断的结构,来决定循环体到底执行多少次,只要条件满足,循环体可以执行到天荒地老,此恨绵绵。主要的有上一次的for循环,还有今天说的while循环

while循环有两个,分别为while循环和do-while循环

while循环

一般形式如下:

1
2
3
4
while(表达式)
{
语句;
}

其中执行过程如下:

  1. 计算while后面括号里表达式的值,若其结果非0,则转入2,否则转3
  2. 执行循环体,转到1
  3. 退出循环,执行循环体下面的语句

由于是先执行判断后执行循环体,所以循环体可能一次都不执行。

循环体可以为空语句;,不过这个基本没有什么卵用。

do-while循环

while循环类似,还有一个do-while循环,一般形式如下:

1
2
3
4
5
do
{
语句;
}
while(表达式);

执行过程如下所示:

  1. 执行循环体,转2
  2. 计算while后面括号里表达式的值,若其结果非0,则转入1,否则转3
  3. 退出循环,执行循环体下面的语句。

注意:do……while语句最后的分号(;)不可少,否则提示出错。

这里可以看到,两种循环的区别是do-while至少执行一次。

简单举几个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*beginner/while/while.c*/
#include <stdio.h>

int main()
{
int i = 0;
int sum = 0;

while (i < 101)
{
sum += i;
i++;
}

printf("1 + ... + 100 = %d\n", sum);

i = 0;
sum = 0;
do
{
sum += i;
i++;
} while (i < 101);

printf("1 + ... + 100 = %d\n", sum);

return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
#beginner/for/Makefile

ALL : while

while : while.c
gcc -o while while.c

clean:
rm -f while

输入make,然后./while输出如下:

1
2
3
$./while
1 + ... + 100 = 5050
1 + ... + 100 = 5050

描述
r{c} 使用字符{c}来替换单个字符(当前光标所在的位置上)
R 替换字符直到你按下注意:当移动到一行的最后时,该动作如同A一样,而不是回绕替换下一行的字符。

示例一:整个文件替换

:%s/old-text/new-text/g 这条命令将把old-text替换为new-text。

示例二:单行替换并提示

:s/old-text/new-text/gi 这条命令将把当前行的old-text替换为new-text并在替换前提示确认;

示例三:指定行内替换

:N,Ms/old-text/new-text/g 这条指令将把第N行到第M行的old-text替换为new-text。

示例四:指定可视块内替换

:’<,’>s/old-text/new-text/g 这条指令将选定可视区域内的old-text替换为new-text。

示例五:替换从当前行开始的N行文本

:s/old-text/new-text/g N 这条指令将替换从当前行开始的N行之内的old-text为new-text。

示例六:替换全匹配单词

对于前面的技巧,可能会在his替换为her的时候,This也替换为Ther,这就不是我们的本意了,此时我们可以使用:

:s/<old-text>/new-text/ 来替换整个单词,而不会截取其中的一部分。

示例七:使用正则表达式同时替换两个单词

:s/(old-text1|old-text2)/new-text/g 这条指令将会同时替换old-text1和old-text2为new-text。

示例八:交互式的查找替换

:s/old-text/new-text/gc 这条指令将会在替换每一个单词的时候提示时候替换,这对于只是希望替换其中一部分的还是蛮有用处的。

C语言的for循环语句

​ C语言的for循环语句用的那是相当的多,等等,这话我好像说了好几次了,哈哈。

​ 在小时候爸爸妈妈为我,从1加到100,最后值是多少呢,然后从早到晚就算起来了;再后来知道的神通高斯;再后来知道了C语言的for语句,😁。

​ for循环的结构比较简单,如下所示:

1
2
3
4
for (表达式1;表达式2;表达式3)
{
//do something
}

它的执行过程如下所示:

  1. 先求解表达式1;
  2. 求解表达式2,若其值为真(非0),则执行循环体,否则结束循环;
  3. 执行完循环体,再求解表达式3。
  4. 重复执行步骤 2) 和 3),直到循环结束。

按照上面的例子,简单举几个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*beginner/for/for1.c*/
#include <stdio.h>

int main()
{
int i;
int sum = 0;

for (i = 0; i < 101; i++)
sum += i;
printf("1 + ... + 100 = %d\n", sum);

return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
#beginner/for/Makefile

ALL : for1

for1 : for1.c
gcc -o for1 for1.c

clean:
rm -f for1

输入make,然后./operator4输出为:

1
2
$ ./for1
1 + ... + 100 = 5050

C语言的if语句

​ C语言里面有一个问题,就是虽然对于逻辑运算符而言,存在真假,不过C语言里面是没有这个概念的,不想Python里面有True和False,对于C而言,非假即为真,也就是如果值不是0,那么就认为是真的。

此处为何非假即为真,而不是非真即为假呢,因为只有0为假,其他均为真

那么什么时候会用到if语句呢,答案是很多时候,但凡开始结构化语句判断就会涉及到,比如:

  1. 判断是正数 如示例if1.c

  2. 判断成绩:不及格或及格 如示例if2.c

  3. 判断年龄段:婴儿、儿童、青少年、成人等, 如示例if3.c

这三种情况从简单到复杂覆盖了if语句的情况。

  1. 最简单的就是判断是否真假:
1
2
3
4
if (true)
{
//do something
}

这里假定定义了true为真,比如#define true 1

  1. 第二种情况为判断真假
1
2
3
4
5
6
7
8
if (true)
{
// do something
}
else
{
// do something else
}
  1. 第三种情况为判断各种情况的真假
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (a)
{
// a do something
}
else if (b)
{
// b do something
}
else if (c)
{
// c do something
}
else
{
// do something else
}

按照上面的例子,简单举几个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
/*beginner/if/if1.c*/
#include <stdio.h>

int main()
{
int value = 5;

if (value > 0)
printf("Yes, positive number\n");

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*beginner/if/if2.c*/
#include <stdio.h>

int main()
{
int score = 75;

if (score < 60)
printf("Not passed\n");
else
printf("Passed\n");

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*beginner/if/if3.c*/
#include <stdio.h>

int main()
{
int age = 16;

if (age < 3)
printf("Baby\n");
else if (age < 5)
printf("preschooler\n");
else if (age < 10)
printf("schoolchild\n");
else if (age < 12)
printf("preteen\n");
else if (age < 18)
printf("teenager\n");
else
printf("adult\n");

return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#beginner/if/Makefile

ALL : if1 if2 if3

if1 : if1.c
gcc -o if1 if1.c

if2 : if2.c
gcc -o if2 if2.c

if3 : if3.c
gcc -o if3 if3.c

.PHONY : clean

clean:
rm -f if1 if2 if3

输入make,然后./operator4输出为:

1
2
3
4
5
6
$ ./if1
Yes, positive number
$ ./if2
Passed
$ ./if3
teenager

Vim书签命令的快速总结

  • ma – 创建一个书签a
  • `a –跳转到书签a的精确位置(行和列)
  • ‘a – 调整到书签a所在行的起始位置
  • :marks — 显示所有的书签;
  • :marks a – 显示名称为a书签的详细信息;
  • `. – 跳转到最后一次执行改变的精确位置(行和列)。
  • ‘. – 跳转到最后一次执行改变的行起始位置。

如果书签名称为大写字符,那么它就是一个全局书签。

如何显示所有书签

如果你创建几个书签,忘记了它们的名称,你可以很容易的获得书签列表,输入:marks,显示如下:

:marks

除了局部书签、全局书签以外,在Vim内部任何时候输入:marks,你可以获得下面几行。这些标识‘单引号,”双引号,[,],^和.点号由Vim创建和管理。你不需要直接控制它们。

:marks

你可以使用上面显示的缺省标识,如下:

缺省标识 描述
`” 到退出之前最后一次编辑的位置
`[ 到先前改变或者复制文本的第一个字符
`] 到先前改变或复制文本的最后一个字符
‘< 到先前选择可视化区域的第一行
‘> 到先前选择可视化区域的最后一行
‘. 到最后一次该标的位置
‘^ 到最后一次插入模式停止的光标所在位置

C语言++i和i++的区别

​ 对于一个如此细节的问题,其实没有很大的必要来讨论,毕竟正儿八经的程序里面,按照前面说的,形成了自己的风格,基本上已经不用太多估计i++++i的区别了。

​ 但是既然聊起这个问题,最简单的区别方法相信很多人已经了然于胸了,那就是:

  • i++:先用i的值然后再加1 所以a=++i等价于:i=i+1;a=i;
  • ++ii先加1,然后在用i的值 所以a=i++等价于:a=i;i=i+1;

比如下面的代码:

1
2
3
4
5
6
   int a, b;
int i = 10;
int j = 10;

a = i++;
b = ++j;

毋庸置疑,最后的结果是a=10,b=11。这个没有问题,那么下面的这个问题呢:

1
2
3
4
int i = 10;
int j = 10;
a = (i++) + (i++) + (i++);
b = (++j) + (++j) + (++j);

你是不是也可以立即给出答案呢,但是你确定结果是这样的吗,或许你实际执行了一下,结果在不同的平台竟然不一样,比如:Linux下面得到的结果是a=30,b=37,而MacOSX编译后的结果竟然是:a=33,b=36,同一个代码,竟然有不同的结果,甚至同一个平台不同的编辑器也会有不同的结果,引起这个的原因就是C语言里的副作用,所谓的副作用(side effect)是指对数据对象或文件的修改,而这个修改可能不同的编辑器有不同的顺序,详情且听下回分解。

建议如下,对于这两个的区别,实在是如果能不用到多个++i/i++就不要用,或者等你去参加面试高速面试官这个有多个结果,分别是为何为何,亦或者是你面试别人,出个考题,保证只有一个答案的新手们,妥妥被教育,暂无他用。

举个例子源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*beginner/operator/operator4.c*/
#include <stdio.h>

int main()
{
int a, b;
int i = 10, j = 10;

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

i = 10;
j = 10;
a = (i++) + (i++) + (i++);
b = (++j) + (++j) + (++j);
printf("a : %d\n", a);
printf("b : %d\n", b);

return 0;
}

相应地Makefile如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#beginner/operator/Makefile

ALL : operator1 operator2 operator3 operator4

operator1: operator1.c
gcc -o operator1 operator1.c

operator2: operator2.c
gcc -o operator2 operator2.c

operator3: operator3.c
gcc -o operator3 operator3.c

operator4: operator4.c
gcc -o operator4 operator4.c

.PHONY : clean

clean:
rm -f operator1 operator2 operator3 operator4


输入make,然后./operator4输出为:

1
2
3
4
a : 10
b : 11
a : xx
b : xx

第二个结果给的是xx,因为我目前有多个答案,随你去实验吧,等下次来讲讲为何会有这几个答案。

abort函数

函数abort,需要头文件:#include <stdlib.h>,异常终止一个进程。
中止当前进程,返回一个错误代码。错误代码的缺省值是3。

该函数产生SIGABRT信号并发送给自己。