0%

C语言的共用体union

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。

什么意思呢,就是在同一块内存存储可以定义多个数据类型,但是在使用的时候,只有一个变量有效。

这里就有一个问题,变量有大有小呀,对的,所以这个时候共用体的空间为内部变量最大占用空间的值。

如此这般,共用体就可以通过共享存储空间,来避免当前没有被使用的变量所造成的存储空间的浪费。

共用体的成员可以使用任何数据类型,但是一个共用体所占用的存储空间的字节总数,必须保证至少足以能够容纳其占用空间字节数最大的成员。并且共用体每次只允许访问一个成员,也就是一种数据类型,确保按照正确的数据类型来访问共用体中的数据,就是你的责任了。

先看看union的格式:

1
2
3
4
5
6
7
union [tag]
{
member definition;
member definition;
...
member definition;
} [variables];

其中:

  • union为类型变量;
  • tag为共用体的标记;
  • member definition为变量的定义;

举个例子:

1
2
3
4
5
6
7
union test
{
int i;
float f;
double d;
char str[20];
} data;

通过这个例子可以看到,这个结构体的大小是多少呢?可以通过程序来确认一下。

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>

union test
{
int i;
float f;
double d;
char str[20];
};

int main( )
{
union test data;

printf( "data size : %d\n", sizeof(data));

return 0;
}

C语言的结构体

前面说到了结构体的使用方法和访问方法。

这一次讲点高级的结构体用法。

从前面的说法可以看到每次初始的方法为:

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

struct Student lilei = {1, "Li Lei", 18, "male"};

是不是感觉很啰嗦,是的,每次都要使用struct关键字来新定义一个变量,程序员最有名的特点就是懒,所以这问题必须解决,这就是关键字typedef的妙用了。

typedef的作用就是用来创建新类型,看看用法:

1
2
3
4
5
6
7
8
9
typedef struct 
{
int id;
char name[15];
int age;
char sex[10];
}Student;

Student lilei = {1, "Li Lei", 18, "male"};

是的,只需要在struct前面加上typedef这个关键字,把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
25
26
27
28
/*beginner/struct/struct5.c*/
#include <stdio.h>

int main()
{

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

Student lilei = {1, "Li Lei", 18, "male"};
Student hanmeimei = {2, "Han Meimei", 17, "female"};
Student weihua1 = {3, "Wei Hua", 18, "male"};
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;
}

编译运行

直接输入make就可以了。

1
gcc -o struct5 struct5.c

运行输出如下:

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

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

.. _linux_alias_beginner:

.. 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即可。