0%

C语言的枚举

枚举这个类型,其实不是很常见,因为使用define基本都可以搞定,不过存在即是合理,枚举可以让某些数据组合更加简洁、易读、规范。

举个用的最多的例子,如果我们定义一个星期,可以使用define,如下所示:

1
2
3
4
5
6
7
#define MONDAY 1
#define TUESDAY 2
#define WEDNESDAY 3
#define THURSDAY 4
#define FRIDAY 5
#define SATURDAY 6
#defind SUNDAY 7

看着还是很不错的表达方式,然后我们在看看枚举变量,表达如下:

1
2
3
4
enum DAY
{
MONDAY = 1, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
};

看起来貌似更加简洁,明了。

OK,现在来看看枚举的定义方式:

1
enum enum_name {enum_element1, enum_element2, enum_element3...enum_elementN};

其中:

  • enum_name为枚举的名字
  • enum_element为枚举的元素

NOTE : 枚举的元素,默认第一个为0,如果需要改变,需要显式初始。

看一下enum的使用吧:

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

int main()
{
int i;

enum DAY
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};

for (i = MONDAY; i <= SUNDAY; i++)
printf("TODAY is %d\n", i);

return 0;
}

可以看到默认MONDAY为0,与我们的习惯不同,我们显式初始化为1:

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

int main()
{
int i;

enum DAY
{
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};

for (i = MONDAY; i <= SUNDAY; i++)
printf("TODAY is %d\n", i);

return 0;
}

看看enum和switch的配合使用:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*beginner/enum/enum3.c*/
#include <stdio.h>

int main()
{
int i;

enum DAY
{
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};

enum DAY which_day;

printf("Which day is today?[1-7]: ");
scanf("%d", &which_day);

switch (which_day)
{
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
printf("OMG, Working day\n");
break;
case SATURDAY:
case SUNDAY:
printf("Oh, yeah, weekend\n");
break;
default:
printf("range (1-7)");
break;
}

return 0;
}

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#beginner/enum/Makefile
ALL : enum1 enum2 enum3

enum1: enum1.c
gcc -o enum1 enum1.c

enum2: enum2.c
gcc -o enum2 enum2.c

enum3: enum3.c
gcc -o enum3 enum3.c

.PHONY : clean

clean:
rm -f enum1 enum2 enum3

运行输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$./enum1
TODAY is 0
TODAY is 1
TODAY is 2
TODAY is 3
TODAY is 4
TODAY is 5
TODAY is 6

$ ./enum2
TODAY is 1
TODAY is 2
TODAY is 3
TODAY is 4
TODAY is 5
TODAY is 6
TODAY is 7

$ ./enum3
Which day is today?[1-7]: 3
OMG, Working day
$ ./enum3
Which day is today?[1-7]: 6
Oh, yeah, weekend

C语言中的常量

C语言里面,一般有2个常量的使用,一个是说过的define另外一个就是前两次用到的const,下面就分别来说说。

const

constC/C++中的一个关键字(修饰符), const一般用来定义一个常量, 既然叫做常量, 即就意味着这个值后面就不能修改了。

举个简单的计算面积的例子:

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

int main()
{
const int LENGTH = 5;
const int WIDTH = 4;

printf("The area is %d\n", LENGTH * WIDTH);

return 0;
}

下面在看看define这个常量定义。

#define

define,其实正常的称呼应该叫做宏定义,是一条预编译指令, 编译器在编译阶段会将所有使用到宏的地方简单地进行替换。如下图所示 :

比较

所以const define 都能定义一个常量,都能实现修改值修改一次, 则所有用上该常量的地方都同步改值,一句代码都不用改。

这样就可以得到下面的优点:

  • 使代码更易维护
  • 提高代码的效率

不过除了这些相同点,还是区别的,听我慢慢道来。

下面的有点小超纲,看不懂也没有关系的,🆙。

const定义常量是汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率比宏定义要高。既然宏定义能做的事const都能做, 那宏还有什么存在的必要么?

存在即合理, 既然宏定义还没被淘汰, 那必然有它存在的道理.

宏能做到const不能办到的事.

  • 宏能定义函数
  • 宏还能根据传入的参数生成字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
/*beginner/constant/constant3.c*/
#include <stdio.h>

#define STRINGCAT(x,y) #x#y
#define TOSTRING(x) #x

int main()
{
printf("%s\n", STRINGCAT(Hello, WORLD));
printf("%s\n", TOSTRING(1234));

return 0;
}

这个功能相当的赞,可以替换很多string的函数了。

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#beginner/constant/Makefile
ALL : constant1 constant2 constant3

constant1: constant1.c
gcc -o constant1 constant1.c

constant2: constant2.c
gcc -o constant2 constant2.c

constant3: constant3.c
gcc -o constant3 constant3.c

.PHONY : clean

clean:
rm -f constant1 constant2 constant3

运行输出如下:

1
2
3
4
5
6
7
8
9
$ ./constant1
The area is 20

$ ./constant2
The area is 20

$ ./constant3
HelloWORLD
1234

C语言的字符串

何为字符串,就是将字符串起来,哈哈,跟羊肉串一样。

这次说说早就应该介绍的字符串,其实字符串主要坑比较多,刚开始用还没啥,越用越心虚。

那咱们就从不心虚的地方说起吧。

来个官方定义:

字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。
所以一个以 null 结尾的字符串,包含了组成该字符串的字符。

是个什么意思呢,来看个实例了解下。

比如我们声明和初始化创建了一个 “Hello” 字符串。

1
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

这里就有疑问了,本着勤俭节约的精神,吃饭不要铺张浪费,吃饱吃好就行,不耍排场。
这里也应该这样呀,你的Hello只有5个字符,为什么要用6个字符数组???

这是字符串的第一个坑,建议的解决方法是尽可能比你需要的大一些字符,在目前内存与房价齐飞的阶段,不多你几个字符,原因如下:

由于在数组的末尾存储了空字符,所以字符数组的大小比单词 “Hello” 的字符数多一个。

还有一个疑问就是,这样写字符串也太-太-太啰嗦了吧,是的,所以另外一种的使用方法为:

1
char greeting[] = "Hello";

整齐划一有内涵。

这两个值倒是在内存里面如何存储呢,我们来个表格看看:

下标 0 1 2 3 4 5
变量 H e l l o \0

看到没有,最后一位就是null,即\0

看个简单的代码:

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

int main()
{
char s1[6] = "Hello";
char s2[6] = {'H', 'e', 'l', 'l', 'o'};

printf("s1 is %s\n", s1);
printf("s2 is %s\n", s2);

return 0;
}

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
#beginner/string/Makefile
ALL : string1

string1: string1.c
gcc -o string1 string1.c


.PHONY : clean

clean:
rm -f string1

运行输出如下:

1
2
3
$ ./string1
s1 is Hello
s2 is Hello

C语言的结构体访问方式

本学期来了一个新学生,名字叫魏华,在上一个程序的基础上,我们使用指向结构的指针来访问试试看。

先看代码:

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/struct/struct4.c*/
#include <stdio.h>

int main()
{

struct Student
{
int id;
char name[15];
int age;
char sex[10];
};

struct Student lilei = {1, "Li Lei", 18, "male"};
struct Student hanmeimei = {2, "Han Meimei", 17, "female"};
struct Student weihua1 = {3, "Wei Hua", 18, "male"};
struct Student *weihua = &weihua1;

printf("The student information :\n");
printf("ID \t | Name \t| Age\t| Sex \n");
printf("%d \t | %s \t| %d \t| %s \n", lilei.id, lilei.name, lilei.age, lilei.sex);
printf("%d \t | %s \t| %d \t| %s \n", hanmeimei.id, hanmeimei.name, hanmeimei.age, hanmeimei.sex);
printf("%d \t | %s \t| %d \t| %s \n", weihua->id, weihua->name, weihua->age, weihua->sex);

return 0;
}

可以看到与上一个程序的区别就是如下的代码段:

1
2
3
4
5
struct Student weihua1 = {3, "Wei Hua", 18, "male"};
struct Student *weihua = &weihua1;

printf("%d \t | %s \t| %d \t| %s \n", weihua->id, weihua->name, weihua->age, weihua->sex);

这里说一下另立独行,希望引起注意的魏华同学,先定义了weihua1这个结构体,然后定义了一个执行该结构的指针,你应该还记得&是什么意思,对的,是取指针地址的意思。

另外需要注意的是,在原来定义结构体变量的时候,访问方式为.,但是在更改为指针后,访问方式为->

这个记住就好了,如果不记得,在编译的时候看一下报错信息。

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#beginner/struct/Makefile
ALL : struct1 struct2 struct3 struct4

struct1: struct1.c
gcc -o struct1 struct1.c

struct2: struct2.c
gcc -o struct2 struct2.c

struct3: struct3.c
gcc -o struct3 struct3.c

struct4: struct4.c
gcc -o struct4 struct4.c

.PHONY : clean

clean:
rm -f struct1 struct2 struct3 struct4

运行输出如下:

1
2
3
4
5
6
$ ./struct4
The student information :
ID | Name | Age | Sex
1 | Li Lei | 18 | male
2 | Han Meimei | 17 | female
3 | Wei Hua | 18 | male

C语言的数组

先说了结构体,可以存储不同的类型,比如描述一个学生,有学号、姓名、成绩等。

那么我们现在只想了解某个班级的平均分数,这是就是同一类型的组合了,当然也可以使用结构体搞定,不过就大材小用了,我们这次就看看能存储相同类型的数组。

先看看数组的定义方式:

1
type array_name [array_size];

其中:

  • type为数组的类型,可以是我们前面说过的各种类型,比如int,float,double等
  • array_name为数组的名字
  • array_size为数组的元素数量

下面来看看如何用,比如我们希望初始化5个学生的成绩,首先需要定义一个含有5个元素的数组:

1
float score[5];

初始化以后需要赋值,如下所示:

1
2
3
4
5
score[0] = 76.0;
score[1] = 77.0;
score[2] = 89.0;
score[3] = 98.0;
score[4] = 99.0;

不知你有没有注意到,数组的下标是从0开始的,所以下标的范围为0~array_size-1。

这种初始化方式有点繁琐,还有一种声明初始化一起的语句为:

1
float score[size] = {76.0, 77.0, 89.0, 98.0, 99.0};

上面的两个方法效果是一样的。

好的,开始算算这5个学生的平均成绩吧。

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

int main()
{
const int size = 5;
float score[size] = {76.0, 77.0, 89.0, 98.0, 99.0};
float average_score;
float sum = 0;
int i;

for (i = 0; i < size; i++)
sum += score[i];

average_score = sum / size;

printf("The average score is %f\n", average_score);

return 0;
}

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
#beginner/array/Makefile
ALL : array1

struct1: array1.c
gcc -o array1 array1.c

.PHONY : clean

clean:
rm -f array1

运行输出如下:

1
2
$ ./array1
The average score is 87.800003

C语言的结构体

OK,这次我们来聊聊结构体。

任务来了,我想让你给学生建立一个数据库,该怎么来做。

这个学生包含的信息如下:

  • ID:也就是学号,唯一区别码,用整型表示
  • Name:姓名,用字符串表示
  • Age:年龄,用整型表示
  • Sex:性别,用字符串表示

按照目前学过的知识我们的代码如下,比如先来一个李雷同学的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*beginner/struct/struct1.c*/
#include <stdio.h>

int main()
{
int lilei_id = 1;
char lilei_name[15] = "Li Lei";
int lilei_age = 18;
char lilei_sex[10] = "male";

printf("The student information :\n");
printf("ID \t | Name \t| Age\t| Sex \n");
printf("%d \t | %s \t| %d \t| %s \n", lilei_id, lilei_name, lilei_age, lilei_sex);

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/struct/struct2.c*/
#include <stdio.h>

int main()
{
int lilei_id = 1;
char lilei_name[15] = "Li Lei";
int lilei_age = 18;
char lilei_sex[10] = "male";

int hanmeimei_id = 2;
char hanmeimei_name[15] = "Han Meimei";
int hanmeimei_age = 17;
char hanmeimei_sex[10] = "female";

printf("The student information :\n");
printf("ID \t | Name \t| Age\t| Sex \n");
printf("%d \t | %s \t| %d \t| %s \n", lilei_id, lilei_name, lilei_age, lilei_sex);
printf("%d \t | %s \t| %d \t| %s \n", hanmeimei_id, hanmeimei_name, hanmeimei_age, hanmeimei_sex);

return 0;
}

发现了什么问题,是不是跟自定义函数类似,代码是简单,不过大部分都是重复的工作,没有显示度,没有创新。

如何办,C语言提供了一个结构体,允许用户自己建立由不同类型数据组成的组合型的数据结构,注意是不同类型不同类型

重要的事情说三遍。

先看一下结构体的声明吧,看看是何方神圣:

1
2
3
4
5
6
struct tag { 
member1
member2
member3
...
} list ;

具体是个什么含义:

  • struct:是结构体的声明类型
  • tag:是这个结构体的标记;
  • member:就是结构体的内容
  • list:是变量的列表

来个实例化吧,比如学生的定义就可以写成如下:

1
2
3
4
5
6
struct Student{
int id;
char name[10];
int age;
char sex[10];
}student;

从这个代码可以看出,结构体的tag首字母一般大写,结构体的内容类型不一样,结构体有个列表叫做student。

下面我们使用结构体来重构代码如下:

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/struct/struct3.c*/
#include <stdio.h>

int main()
{

struct Student
{
int id;
char name[15];
int age;
char sex[10];
};

struct Student lilei = {1, "Li Lei", 18, "male"};
struct Student hanmeimei = {2, "Han Meimei", 17, "female"};

printf("The student information :\n");
printf("ID \t | Name \t| Age\t| Sex \n");
printf("%d \t | %s \t| %d \t| %s \n", lilei.id, lilei.name, lilei.age, lilei.sex);
printf("%d \t | %s \t| %d \t| %s \n", lilei.id, lilei.name, lilei.age, lilei.sex);

return 0;
}

有没有感觉,神清气爽,两袖清风,原来8行的代码,2行搞定,也就多了一个结构体的定义,在学生数以万计的时候,代码量减少了四分之三,这是在只有4个参数的情况下,参数越多越给力。

有几点说明:

  • 注意结构体的定义,有大括号,有分号
  • 注意结构体的初始化,需要使用struct Student s1这样的表示方式;
  • 注意结构体的初始化也是使用大括号,对应每个变量

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#beginner/struct/Makefile
ALL : struct1 struct2 struct3

struct1: struct1.c
gcc -o struct1 struct1.c

struct2: struct2.c
gcc -o struct2 struct2.c

struct3: struct3.c
gcc -o struct3 struct3.c

.PHONY : clean

clean:
rm -f struct1 struct2 struct3

运行输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ./struct1
The student information :
ID | Name | Age | Sex
1 | Li Lei | 18 | male

$ ./struct2
The student information :
ID | Name | Age | Sex
1 | Li Lei | 18 | male
2 | Han Meimei | 17 | female

$ ./struct3
The student information :
ID | Name | Age | Sex
1 | Li Lei | 18 | male
2 | Han Meimei | 17 | female

自定义函数是写大程序的前提

最开始写程序的时候,我们都会全部一股脑地写在main函数中,导致有的main函数有成千上万行,要调试起来,这就尴尬了。

这就引出了我们需要定义一些自定义函数,只需要将这些函数写在主函数就可以了。

是个什么意思呢,就是你秀给客户或者测试人员的main函数,是这个样子的(看一下百万量级规模的内核启动函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(void)
{
printf("Linux/AXP bootloader for Linux \n");

pal_init();

openboot();

printf("Loading vmlinux ...");
load(dev, START_ADDR, KERNEL_SIZE);
close(dev);

callback_getenv(ENV_BOOTED_OSFLAGS, envval, sizeof(envval));

runkernel();

//__halt();
}

虽然有些东西看不懂什么意思(因为我精简和修改了部分代码),没有问题,我们需要理解的就是Linux内核有百万行量级,而它的主函数也就短短的十几行,其他的就放在了自定义函数里实现,也就上面的openboot,runkernel等。


OK,任务来了,我想让你写个程序,计算出摄氏度和华氏度的转换,

两个的转换关系如下:

  • 华氏度 = 32°F+ 摄氏度 × 1.8
  • 摄氏度 = (华氏度 - 32°F) ÷ 1.8

这个简单呀,前面就写过(刷刷刷,码出如下代码):

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/function/function1.c*/
# include <stdio.h>

int main()
{
float f; // Fahrenheit temperature
float c; // Celsius temperature

f = 80.0;
c = 27.0;

float ff;
float cc;

printf ("Fahrenheit <=> Celsius\n");
ff = 32 + c * 1.8;
cc = (f - 32) / 1.8;

printf("%f <=> %f\n",f, cc);
printf("%f <=> %f\n",ff, c);


return 0;
}

不错,孺子可教也,在加深一步,又来了一组数据,也是求两者的转换关系,代码继续累加,如下:

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
30
31
32
33
34
35
/*beginner/function/function2.c*/
# include <stdio.h>

int main()
{
float f; // Fahrenheit temperature
float f1;
float c; // Celsius temperature
float c1;

f = 80.0;
f1 = 90.0;
c = 27.0;
c1 = 36.0;

float ff;
float ff1;
float cc;
float cc1;

printf ("Fahrenheit <=> Celsius\n");
ff = 32 + c * 1.8;
cc = (f - 32) / 1.8;

printf("%f <=> %f\n",f, cc);
printf("%f <=> %f\n",ff, c);

ff1 = 32 + c1 * 1.8;
cc1 = (f1 - 32) / 1.8;

printf("%f <=> %f\n",f1, cc1);
printf("%f <=> %f\n",ff1, c1);

return 0;
}

如果我在来一组数据,是不是同样还需要在原来的代码基础上累加,我们就会发现:main函数越来越庞大,但是有效的代码就那么几行,其余的工作都是在重复叠加,这就引出一个问题,我们的华氏转摄氏度是比较简单的公示,如果算个麦克斯韦方程:

$\oiint_{\partial V}\mathrm{E}\cdot d \mathrm{a} = \frac{Q_V}{\epsilon_0}\ \oiint_{\partial S}\mathrm{E}\cdot d \mathrm{l} = \frac{d}{dt} \int_S \mathrm{B} \cdot d\mathrm{a}\ \oiint_{\partial V}\mathrm{B}\cdot d \mathrm{a} = 0 \ \oiint_{\partial S}\mathrm{B}\cdot d \mathrm{l} = \mu_0\epsilon_0\frac{d}{dt} \int_S \mathrm{E} \cdot d\mathrm{a} $$

其中$\oiint$为环路积分的符号

那么每一次计算都要重复这些操作,有没有一种方法可以简化呢,妥妥滴,这就带来了自定义函数。

我们以华氏度和摄氏度的转换为例,可以定义两个函数:

1
2
float c2f(float c); // Celsius to Fahrenheit
float f2c(float f); // Fahrenheit to Celsius

两个函数的含义为c2f就是将摄氏度c转换为华氏度返回,相反f2c为将华氏度f转换为摄氏度返回。

具体的代码如下:

1
2
3
4
5
6
7
8
9
float c2f(float c)
{
return 32 + c * 1.8;
}

float f2c(float f)
{
return (f - 32) / 1.8;
}

我们把这两个代码片段加到程序中,最后的效果如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*beginner/function/function2.c*/
# include <stdio.h>

float c2f(float c); // Celsius to Fahrenheit
float f2c(float f); // Fahrenheit to Celsius

int main()
{
float f; // Fahrenheit temperature
float f1;
float c; // Celsius temperature
float c1;

f = 80.0;
f1 = 90.0;
c = 27.0;
c1 = 36.0;

float ff;
float ff1;
float cc;
float cc1;

printf ("Fahrenheit <=> Celsius\n");
cc = f2c(f);
ff = c2f(c);

printf("%f <=> %f\n",f, cc);
printf("%f <=> %f\n",ff, c);

cc1 = f2c(f1);
ff1 = c2f(c1);

printf("%f <=> %f\n",f1, cc1);
printf("%f <=> %f\n",ff1, c1);

return 0;
}

float c2f(float c)
{
return 32 + c * 1.8;
}
float f2c(float f)
{
return (f - 32) / 1.8;
}

这个程序新加的东西有点多,不过可以清晰的看到,主函数通过使用自定义的函数已经将重复的东西结构化了,这是编写大程序的基础,希望你能掌握。

编译运行

直接输入make就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#beginner/function/Makefile
ALL : function1 function2 function3

function1: function1.c
gcc -o function1 function1.c

function2: function2.c
gcc -o function2 function2.c

function3: function3.c
gcc -o function3 function3.c

.PHONY : clean

clean:
rm -f function1 function2 function3

运行输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./function1
Fahrenheit <=> Celsius
80.000000 <=> 26.666666
80.599998 <=> 27.000000

$ ./function2
Fahrenheit <=> Celsius
80.000000 <=> 26.666666
80.599998 <=> 27.000000
90.000000 <=> 32.222221
96.800003 <=> 36.000000

$ ./function3
Fahrenheit <=> Celsius
80.000000 <=> 26.666666
80.599998 <=> 27.000000
90.000000 <=> 32.222221
96.800003 <=> 36.000000

别有一番风趣的alias

.. note::
寒蝉凄切,对长亭晚,骤雨初歇。
柳永《雨霖铃》

Linux alias命令用于设置指令的别名,可以将比较长的命令进行简化。

默认情况下会输出当前的设置:

1
2
3
4
5
$ alias
l='ls -lah'
la='ls -lAh'
ll='ls -lh'
ls='ls --color=tty'

所以此时输入ll以后,就相当于输入了ls -lh

给命令设置别名也很简单,方法为:

1
$ alias newcommand='command setting'

比如:

1
$ alias ll='ls -lh' # 相当的实用

不过需要注意的时,这个命令如果在终端操作,关闭后并不会保持。

如果需要每次都可以使用,需要将这个命令输入到.bashrc中。

比较常用的一些为:

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
$ alias
alias cp='cp -i'
alias l.='ls -d .* --color=tty'
alias ll='ls -l --color=tty'
alias ls='ls --color=tty'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde

#对路径切换很有用
$ alias ..='cd ..'
$ alias ...='cd ../../../'
$ alias ....='cd ../../../../'
$ alias .....='cd ../../../../'
$ alias .4='cd ../../../../'
$ alias .5='cd ../../../../..'


#获取disk的信息##
$ alias df='df -H'
$ alias du='du -ch'

#设置一些系统信息
$ alias cpuinfo='lscpu'
$ alias meminfo='free -h'

在比如一个稍微复杂一点的:

1
$ alias lt='ls --human-readable --size -1 -S --classify'

lt将排序并显示一个总的文件大小。

当前,可以设定alias,也可以清除,只要使用unalias即可。

可看黄道吉日的 cal

.. note::
但愿人长久,千里共婵娟。
苏轼《水调歌头·明月几时有》

cal用于显示当前日历信息或者指定日期的公历信息。

cal的官方定义为:

cal, ncal — displays a calendar and the date of Easter

cal也是来自于calendar的前三个字母。

其用法有好几种,比如可以为:

1
2
3
4
5
6
$ cal [-31jy] [-A number] [-B number] [-d yyyy-mm] [[month] year]
$ cal [-31j] [-A number] [-B number] [-d yyyy-mm] -m month [year]
$ ncal [-C] [-31jy] [-A number] [-B number] [-d yyyy-mm] [[month] year]
$ ncal [-C] [-31j] [-A number] [-B number] [-d yyyy-mm] -m month [year]
$ ncal [-31bhjJpwySM] [-A number] [-B number] [-H yyyy-mm-dd] [-d yyyy-mm] [-s country_code] [[month] year]
$ ncal [-31bhJeoSM] [-A number] [-B number] [-d yyyy-mm] [year]

cal可以没有参数,也可以多个参数组合。

[[month] year]的含义是,比如有year这个参数,然后可以出现month year两个参数。

主要使用的参数为:

  • -3 :显示前后和当前3个月的日历
  • -y :显示一年的日历,此时不要指定月份参数
  • -j :显示在当年中的第几天(儒略日)

显示当前月份的日历

默认无参数会显示当前的月份等信息

1
2
3
4
5
6
7
8
$ cal
February 2011
Su Mo Tu We Th Fr Sa
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

显示指定年月的日历

比如希望看看2012年12月份,可以运行如下命令:

1
2
3
4
5
6
7
8
9
$ cal 12 2012
December 2012
Su Mo Tu We Th Fr Sa
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
30 31

显示3个月的日历

-3将显示当前月份、前一个月、后一个月,共计3个月的日历。

1
2
3
4
5
6
7
8
9
10
$ cal -3
2011
January February March
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 1 2 3 4 5 1 2 3 4 5
2 3 4 5 6 7 8 6 7 8 9 10 11 12 6 7 8 9 10 11 12
9 10 11 12 13 14 15 13 14 15 16 17 18 19 13 14 15 16 17 18 19
16 17 18 19 20 21 22 20 21 22 23 24 25 26 20 21 22 23 24 25 26
23 24 25 26 27 28 29 27 28 27 28 29 30 31
30 31

显示一年的日历

使用-y参数,可以查看一年的日历。

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
30
31
32
33
34
35
36
37
$ cal -y
2011
January February March
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 1 2 3 4 5 1 2 3 4 5
2 3 4 5 6 7 8 6 7 8 9 10 11 12 6 7 8 9 10 11 12
9 10 11 12 13 14 15 13 14 15 16 17 18 19 13 14 15 16 17 18 19
16 17 18 19 20 21 22 20 21 22 23 24 25 26 20 21 22 23 24 25 26
23 24 25 26 27 28 29 27 28 27 28 29 30 31
30 31

April May June
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 7 1 2 3 4
3 4 5 6 7 8 9 8 9 10 11 12 13 14 5 6 7 8 9 10 11
10 11 12 13 14 15 16 15 16 17 18 19 20 21 12 13 14 15 16 17 18
17 18 19 20 21 22 23 22 23 24 25 26 27 28 19 20 21 22 23 24 25
24 25 26 27 28 29 30 29 30 31 26 27 28 29 30


July August September
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 1 2 3
3 4 5 6 7 8 9 7 8 9 10 11 12 13 4 5 6 7 8 9 10
10 11 12 13 14 15 16 14 15 16 17 18 19 20 11 12 13 14 15 16 17
17 18 19 20 21 22 23 21 22 23 24 25 26 27 18 19 20 21 22 23 24
24 25 26 27 28 29 30 28 29 30 31 25 26 27 28 29 30
31

October November December
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 1 2 3 4 5 1 2 3
2 3 4 5 6 7 8 6 7 8 9 10 11 12 4 5 6 7 8 9 10
9 10 11 12 13 14 15 13 14 15 16 17 18 19 11 12 13 14 15 16 17
16 17 18 19 20 21 22 20 21 22 23 24 25 26 18 19 20 21 22 23 24
23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31
30 31

显示儒略日

-j用于显示儒略日,这里的儒略日的概念为从1月1日开始计算的多少天,这个在倒计时的时候挺好用的。

1
2
3
4
5
6
7
8
9
$ cal -j 
cal 02 2011 -j
February 2011
Su Mo Tu We Th Fr Sa
32 33 34 35 36
37 38 39 40 41 42 43
44 45 46 47 48 49 50
51 52 53 54 55 56 57
58 59

查看庐山真面貌的cat

.. note::
此去经年,应是良辰好景虚设
宋 柳永《雨霖铃》

cat命令可用于输出文件的内容到标准输出。

cat的官方定义为:

concatenate files and print on the standard output

翻译过来就是:把档案串连接后传到基本输出

其用法一般为:

1
$ cat [OPTION]... [FILE]...

cat命令的可选参数[OPTION]如下所示:

  • -n--number: 由 1 开始对所有输出的行数编号
  • -b--number-nonblank: 和 -n 相似,只不过对于空白行不编号
  • -s --squeeze-blank :当遇到有连续两行以上的空白行,就代换为一行的空白行
  • -T--show-tabs:显示TAB字符,显示为^I
  • -E--show-ends:显示行末符号,字符为$
  • -A--show-all:显示所有的信息

此时假定我们的文件为hello.c,内容为最经典的:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char * argv[])
{
printf("Hello World\n");

return 0;
}

接下来的实例全部根据这个文件展开,Hello World. Hello Linux

实例 :简单显示内容

1
2
3
4
5
6
7
8
9
10
11
$ cat hello.c 

#include <stdio.h>

int main(int argc, char * argv[])
{
printf("Hello World\n");

return 0;
}

实例 :显示行号 -n

1
2
3
4
5
6
7
8
9
10
11
$ cat -n hello.c 

1 #include <stdio.h>
2
3 int main(int argc, char * argv[])
4 {
5 printf("Hello World\n");
6
7 return 0;
8 }

实例 : 显示行末

1
2
3
4
5
6
7
8
9
$ cat -E hello.c 
#include <stdio.h>$
$
int main(int argc, char * argv[])$
{$
printf("Hello World\n");$
$
return 0;$
}$

实例:显示空白字符

1
2
3
4
5
6
7
8
9
cat -T hello.c 
#include <stdio.h>

int main(int argc, char * argv[])
{
^Iprintf("Hello World\n");
^I
^Ireturn 0;
}

此时可以看到^I,which means Tab charcter.

加一个管道

比如,此时希望看到你的源码文件一共多少行,每行代表什么意思,就可以把含有行号的输入通过管道发送到另外一个文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat -n hello.c > hello_number.c

$ cat hello_number.c

1 #include <stdio.h>
2
3 int main(int argc, char * argv[])
4 {
5 printf("Hello World\n");
6
7 return 0;
8 }

其他的一些选项可以自行尝试。