=============================================================================
涉及到的知识点有:1、C语言库函数、字符输入函数:gets和fgets、字符输出函数:puts和fputs、
求字符串长度函数strlen、字符串追加函数strcat、字符串有限追加函数strncat、字符串比较函数strcmp、
字符串有限比较函数strcmp、字符串拷贝函数strcpy、字符串有限拷贝函数strncpy、
格式化字符串函数sprintf(输出)、格式化字符串函数sscanf(读取输入)、解析一个字符串、
字符串查找字符函数strchr、字符串查找子串函数strstr、字符串分割函数strtok、
atoi函数、atof函数、atol函数、解析一个字符串的高级应用。
2、函数的定义和声明、函数的形式参数(形参)与实际参数(实参)、函数的返回值类型和返回值、
return函数与exit函数(exit更猛,不受位置限制)、自定义一个函数,实现大小写字母的互相转换功能、
自定义一个函数,实现atoi的功能。
3、函数的递归、递归例子:有n个人排成一队、递归例子:将10进制数转化为二进制数、
递归例子:将10进制数转化为16进制、递归例子:菲波那切数列、递归的优点与缺点。
4、多个源代码文件程序如何编译、头文件的使用、解决预编译时会出现多次函数声明问题。
=============================================================================
C语言库函数

1、字符串处理函数:字符输入函数和字符输出函数

字符输入函数:gets和fgets

通过scanf输入的时候,最后按的是一个什么键?答:回车键,scanf会把回车键认为是输入完成,而不是字符串的内容。
而且scanf认为回车和空格都代表输入完成哦。

当字符数组的成员数量小于用户在键盘输入字符的数量之后,scanf并不会自动处理,而是把用户输入的所有字符都放入了数组,导致了数组溢出了,内存出错,程序崩溃。
-----------------------------------------------------------------------------
gets认为只有回车代表输入完成,空格只是字符串中的一部分而已。
gets和scanf一样有缓冲区溢出的危险。
-----------------------------------------------------------------------------
字符数组 的英文名字是 char []

gets()函数的基本用法为:
char *gets(char *s);
该函数的参数是一个字符数组,该函数的返回值也是一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5    char a[100] = { 0 };
6    gets(a);
7    printf("%s\n", a);
8    return 0;
9 }
-----------------------------------------------------------------------------
编译时会出现一个warning,建议我们不要使用gets函数了。我们暂时不管他,先直接运行看结果。
--------------------------------------
警告如下:
a2.c:6:5: warning: implicit declaration of function ‘gets’ [-Wimplicit-function-declaration]
  gets(a);
  ^
/tmp/cceyMQ7u.o: In function `main':
a2.c:(.text+0x41): warning: the `gets' function is dangerous and should not be used.
--------------------------------------
原因:
问题出在程序中使用了 gets是非常不安全的。这是对程序产生BUG,出现不可靠性的一个描述,
有些函数在某些意外情况会导致程序陷入不可控状态,仅仅是PC上运行最多也就是退出而已,
但是如果是运行在飞机等系统里的话,就会有大麻烦,说危险也不为过。因为英文文献里描述为dangerous,所以也就翻译为危险。

函数执行需要一个栈空间,但这个栈空间容量是有限的,而且栈里存放了函数返回的地址。
gets()函数在获取输入时,如果无限输入会造成栈空间溢出,在程序返回时,不能正常的找到返回地址,程序将发生不可预测行为。
--------------------------------------
解决:
解决办法是使用 fgets,但由于fgets函数是为读取文件设计的,所以读取键盘是没有gets那么方便。

fgets(char *s, int size, FILE *stream);
第一个参数是:字符类型的数组,第二个参数是:标明这个数组的大小,第三个参数是:如果总是通过键盘输入的话,可以固定写为stdin。

fgets()函数的基本用法为:

-------------------------------------
示例程序:
/*代码实现01_使用fputs函数打印输出*/

#include <stdio.h>

int main ( )
{
  char name[20] = { 0 };
  fgets(name, sizeof(name), stdin); //stdin 意思是键盘输入
  fputs(name, stdout); //stdout 意思是通过打印设备输出
  return 0;
}
--------------------------------------
/*代码实现02_使用printf函数打印输出*/
#include <stdio.h>

int main ( )
{
  char name[20] = { 0 };
  fgets(name, sizeof(name), stdin); //stdin 意思是键盘输入
  printf("%s", name); //这边输出不需要 \n 了,实际操作时,fgets会认为用户输入的回车也是字符串的一部分内容。即输出的内容中间接地带了 \n 了。
  return 0;
}

-----------------------------------------------------------------------------
fgets会认为用户输入的回车也是字符串的一部分内容。
fgets是安全的,不会因为用户恶意的输入过长的字符串导致溢出。因为它只接受它能存的最大的字符数,其余的舍掉!

=============================================================================
字符输出函数:puts和fputs

puts函数打印字符串时,与printf不同,puts会在最后自动增加一个换行字符 '\n'。
puts不支持各种转义字符,如%d,%s都不支持,puts只能简单直接地输出一个字符串,而不能输出char类型(字符类型)、int类型(整型)、double类型(双精度浮点类型)等其他类型。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5   char a[] = "hello world";
6   puts(a);
7   return 0;
8 }

--------------------------------------
puts函数内部可能是如下这样实现的:
int puts(const char *s)
{
  int index = 0;
  while(s[index])
  {
    putchar(s[index]);
    index++;
  }
  putchar('\n');
  return index;
}

putchar函数的作用:向终端输出一个字符。
(1)putchar函数只能用于单个字符的输出,且一次只能输出一个字符。
(2)在程序中使用putchar函数,务必牢记:在程序(或文件)的开头加上编译预处理命令(也称包含命令),即:#include "stdio.h"。

//思考
用putchar就可以实现puts,printf等输出函数。
用getchar就可以实现scanf,gets等输入函数。

-----------------------------------------------------------------------------
fputs函数是puts函数的文件操作版本。

int fputs(const char *s, FILE *stream);    //注意:const用来修饰char *s, *s的内容不可变。
第一个参数是一个char的数组;第二个参数是:如果总是通过屏幕进行输出的话,可以固定写为stdout。

fputs并不会像puts那样输出的时候自动结尾加 \n 。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5    char a[] = "hello world";
6    //puts(a);       //hello world
            //root@iZ2zeeailqvwws5dcuivdbZ:~/1/01#

7    //printf(a);          //hello worldroot@iZ2zeeailqvwws5dcuivdbZ:~/1/01# 编译有警告哦
8    fputs(a, stdout);   //hello worldroot@iZ2zeeailqvwws5dcuivdbZ:~/1/01#
9    return 0;
10 }
--------------------------------------
1 #include <stdio.h>
2
3 int main()
4 {
5    char a[] = "hello world\n";
6    //puts(a);
7    //printf(a);
8    fputs(a, stdout);   //hello world
9    return 0;          //root@iZ2zeeailqvwws5dcuivdbZ:~/1/01#
10 }

=============================================================================
求字符串长度函数strlen

使用这个库函数时需要包含头文件<string.h>

"abc",注意:用strlen时返回的结果是3,而不是4。

strlen函数的基本用法为:
size_t strlen(const char *s);
参数是:一个字符数组。
返回值是:不包含字符串结尾'\0'的字符串的长度(注意:是字符串字节的总数哦!不同的系统下是不同的)。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6    char a[] = "hello"; //char a[] = "hello传智";
7    unsigned int index = 0;
8    /*
9    while (a[index])
10   {
11     index++;
12   }
13   */
14   index = strlen(a);
15      printf("%u\n",index);//5 //11
16   return 0;
17 }

=============================================================================
字符串追加函数strcat

strcat函数的基本用法为:
char *strcat(char *dest, const char *src);
参数是:第一个参数是一个字符数组,第二个参数是静态的字符数组。
返回值是:一个字符数组。

用strcat的时候要注意,第一个字符串一定要有足够的空间容纳第二个字符串。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6    char a[100] = "abc";   //a不是空串时
7    char b[100] = "hello";
8    strcat(a,b);       //将a和b合并为一个字符串,合并的结果放入a。
9    printf("%s\n", a);   //abchello
10    return 0;
11 }
--------------------------------------
1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = { 0 }; //a为空串时 或者 char a[100] = "";
7   char b[100] = "hello";
8   strcat(a, b);
9   printf("%s\n", a); //hello
10   return 0;
11 }
-----------------------------------------------------------------------------
字符串有限追加函数strncat

strncat函数的基本用法为:
char *strncat(char *dest, const char *src, size_t n);
参数是:第一个参数是一个字符数组,第二个参数是静态的字符数组,第三个参数代表最多追加几个字符。
返回值是:一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6    char a[100] = "abc";
7    char b[100] = "hello123456789";
8    strncat(a, b, 5);
9    printf("%s\n", a);   //abchello
10    return 0;
11 }

=============================================================================
字符串比较函数strcmp

strcmp函数的基本用法为:
int strcmp(const char *s1, const char *s2); //比较两个字符串是否相等
参数是:第一个参数是一个静态的字符数组,第二个参数是静态的字符数组。
返回值是:int。相等返回0;不相等返回非0.

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6    char a[100] = "abc";
7    char b[100] = "hel";
8    if (strcmp(a, b) == 0)
9    {
10     printf("相同\n");
11   }
12  else
13     {
14     printf("不相同\n");
15   }
16    return 0;
17 }

--------------------------------------
strcmp函数内部可能是如下这样实现的:
char a[100] = "abc";
char b[100] = "abc123";
int status = 0;//0代表这两个字符串相等;

//代码先加一个判断,两个字符串如果长度不一样,直接设置status等于1。

int index = 0;
while(a[index])
{
  if (a[index] != b[index])
  {
    status = 1;
    break;
  }
  index++;
}
//这样就可以通过status的值来判断两个字符串是否相等了。

-----------------------------------------------------------------------------
字符串有限比较函数strcmp

strncmp函数的基本用法为:
int strncmp(const char *s1, const char *s2, size_t n);
参数是:第一个参数是一个静态的字符数组,第二个参数是静态的字符数组,第三个参数代表比较几个字符。
返回值是:int。相等返回0;不相等返回非0.

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "abc";
7   char b[100] = "hel";
8   if (strncmp(a, b, 2) == 0)
9   {
10     printf("相同\n");
11    }
12    else
13    {
14       printf("不相同\n");
15  }
16    return 0;
17 }

=============================================================================
字符串拷贝函数strcpy

strcpy函数的基本用法为:
char *strcpy(char *dest, const char *src);
参数是:第一个参数是一个字符数组,第二个参数是静态的字符数组。
返回值是:一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "abc";
7   char b[100] = "1234";
8    /*
9    //把b的内容拷贝到a里面,不使用库函数
10  int index = 0;
11     while (b[index])
12  {
13     a[index] = b[index];
14     index++;
15   }
16   */
17  //把b的内容拷贝到a里面,使用库函数
18  strcpy(a, b);
19  printf("%s\n", a); //1234
20    return 0;
21 }
-----------------------------------------------------------------------------
字符串有限拷贝函数strncpy

strncpy函数的基本用法为:
char *strncpy(char *dest, const char *src, size_t n);
参数是:第一个参数是一个静态的字符数组,第二个参数是静态的字符数组,第三个参数代表比较几个字符。
返回值是:一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "abc";
7   char b[100] = "1234";
8   //把b的有限内容拷贝到a里面,使用库函数
9   strncpy(a, b, 2); //strncpy(a, b, sizeof(a) - 1);
10    printf("%s\n", a); //12c //123
11    return 0;
12 }

=============================================================================
格式化字符串函数sprintf(输出)

printf是向标准输出设备输出一个字符串。
sprintf向一个char的数组输出一个字符串。

超级特别注意:可以使用sprintf将一个int或者其他类型转化为一个字符串。

和printf函数功能类似,printf函数格式化结果输出到屏幕(或标准输出设备),
sprintf将格式化结果输出到字符串,并不会将结果打印到标准输出设备上去。

sprintf使用方法与printf类似,唯一的区别是多了第一个参数,第一个参数是一个char的数组。

sprintf的使用方法和printf基本一致,

特别注意:所有printf的转义符对于sprintf是一样的。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5   char a[100] = { 0 };
6     sprintf(a, "%s\n", "hello");    //不加下一句printf,屏幕上什么也不会输出。
7   printf("%s", a);//hello
8   return 0;
9 }
-----------------------------------------------------------------------------
格式化字符串函数sscanf(读取输入)

scanf从键盘读取用户输入数据,sscanf从指定格式化字符串读取输入。
即sscanf从某一个格式化字符串中读取到我们想要的东西,找到后通过转义的方式取出来,取出来后我们就可以继续进行想要的处理了。
sscanf函数类似于scanf函数,唯一的区别是多了第一个参数,第一个参数是一个char的数组。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5   char a[100] = "56+72";
6   int i;
7   int j;
8   sscanf(a, "%d+%d", &i, &j);    //sscanf从某一个格式化字符串中读取到我们想要的东西,找到后通过转义的方式取出来。
9   printf("%d+%d=%d\n", i, j, i + j);  //取出来后我们就可以继续进行想要的处理了。
10   return 0;
11 }

=============================================================================
课堂小练习:解析一个字符串

例如:
有一个字符数组,char a[100] = "43+56="; 整数是任意的,中间可能是 + - * / 任意一个。
现在写一个程序,将计算的结果追加到字符串a的后面。也即:程序执行完成后a的值是"43+56=99"。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int main()
4 {
5   char a[100] = "56+72=";
6   int i;
7   int j;
8   char c;
9   //printf("%d, %c, %d\n",i, c, j);//打印看看结果
10    sscanf(a, "%d%c%d", &i, &c, &j);//从字符串里面把想要的字符提取出来。
11
12     int res = 0;
13   switch(c)
14   {
15     case '+':
16       res = i + j;
17       break;
18     case '-':
19       res = i - j;
20       break;
21     case '*':
22       res = i * j;
23       break;
24     case '/':
25       res = i / j;
26          break;
27     default:
28       res = 0;
29   }
30   sprintf(a, "%d%c%d=%d", i, c, j, res);
31   printf("%s\n", a);
32   return 0;
33 }

=============================================================================
字符串查找字符函数strchr

strchr函数的基本用法为:
char *strchr(const char *s, int c); //功能:在指定的字符串里面找指定的相关字符的子串。
参数是:第一个参数是一个静态的字符数组,第二个参数是int(其实是一个字符的ASCII)。
返回值是:一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "hello world";
7   char *s;//定义了一个char类型的指针变量
8   s = strchr(a, 'l'); //注意是一个字符
9
10    if (s != NULL)
11     {
12     printf("%s\n", s); //llo world
13   }
14   return 0;
15 }
-----------------------------------------------------------------------------
字符串查找子串函数strstr

strstr函数的基本用法为:
char *strstr(const char *haystack, const char *needle);
参数是:第一个参数是一个静态的字符数组,第二个参数也是静态的字符数组(其实是一个字符串)。
返回值是:一个字符数组。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "hello world";
7   char *s;//定义了一个char类型的指针变量
8   s = strstr(a, "wo"); //注意是一个字符串
9
10  if (s != NULL)
11   {
12      printf("%s\n", s); //world
13   }
14   return 0;
15 }

strchr与strstr如果找不到,返回NULL(空指针)。

=============================================================================
字符串分割函数strtok

strtok函数的基本用法为:
char *strtok(char *str, const char *delim);
参数是:第一个参数是一个字符数组;第二个参数也是一个静态的字符数组(其实是一个字符串)。
返回值是:一个字符数组。

注意:

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "abc_565656_asadsd";
7   char *s;  //定义一个char的指针变量
8   s = strtok(a, "_");
9   printf("%s\n", s); //abc
10
11    s = strtok(NULL, "_");   //注意:在第二次至以后调用strtok函数时,第一个参数写NULL。
12    printf("%s\n", s); //565656
13
14    s = strtok(NULL, "_");
15    printf("%s\n", s); //asadsd
16
17    s = strtok(NULL, "_");
18    printf("%s\n", s);   //Segmentation fault(分段故障)
19       return 0;
20
21 }

如果要分割的字符串已经到了字符串结尾,若继续调用strtok则返回Segmentation fault(分段故障)。
-----------------------------------------------------------------------------
上面代码的简化代码如下:

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "abc_565656_asadsd";
7   char *s;//定义一个char的指针变量
8   s = strtok(a, "_");
9
10    while (s) //当s = NULL时,就是0,0就是假。
11      {
12     printf("%s\n", s);
13     s = strtok(NULL, "_");
14    }
15     return 0;
16
17 }

=============================================================================
atoi函数

atoi函数的功能是:把一个char的数组转化为一个int,需要头文件stdlib.h。

int atoi(const char *nptr);

linux下示例代码如下:

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main()
5 {
6   char a[100] = "123";  //如何把一个字符串转化为一个整数?
7   char b[100] = "500";
8
9   int i = 0;
10   i = atoi(a) + atoi(b);
11   printf("%d\n", i); //623
12   return 0;
13 }
-----------------------------------------------------------------------------
atof函数

atof函数的功能是:把一个小数形式的字符转化为一个浮点数。

double atof(const char *nptr);

linux下示例代码如下:

1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main()
5 {
6   char a[100] = "123.5";
7   char b[100] = "500.489";
8
9   double i = 0;
10    i = atof(a) + atof(b);
11    printf("%f\n", i);
12    return 0;
13 }
-----------------------------------------------------------------------------
atol函数

atol函数的功能是:把一个字符串转化为long类型。

long atol(const char *nptr);
long long atoll(const char *nptr);

-----------------------------------------------------------------------------
在c语言里面提供了把字符串转化为整数的函数,但并没有提供把整数转化为字符串的函数,
即:atoi是标准的c语言库函数,itoa不是c语言标准的库函数。(itoa可以在vs2017下编译,但在其他系统下就未知了。)

所以不要尝试使用itoa这种函数,可以使用sprintf将一个int或者其他类型转化为一个字符串。
即:sprintf可以实现将数字转化为字符串的功能。

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6   char a[100] = "1234";
7   char b[100] = "abc";
8   int c = 45;
9   sprintf(a, "%d", c);  //a[100] = "45";
10    strcat(b, a);
11    printf("%s\n", b);  //abc45
12    return 0;
13 }

=============================================================================
课堂小练习:解析一个字符串的高级应用

例如:
有一个字符数组 char a[100] = "12+5=;45-2=;34*2=;54/3=";
这个字符数组到底有多少分号是不确定的,
写个程序,执行后"="号后面自动添加计算结果。如下:
“12+5=17;45-2=43;34*2=68;54/3=18”

linux下示例代码如下:

1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int main()
6 {
7    char a[100] = "12+5=;45-2=;34*2=;54/3=";
8    char b[100] = { 0 }; //注意:b是空串啊!!!
9    char *s = strtok(a, ";");
10   while (s)
11    {
12       //printf("%s\n", s);
13       int i, j;
14       char c;
15       sscanf(s, "%d%c%d=", &i, &c, &j);
16
17      int res = 0;
18      switch (c)
19     {
20        case '+':
21          res = i + j;
22         break;
23        case '-':
24          res = i - j;
25          break;
26        case '*':
27          res = i * j;
28          break;
29        case '/':
30          res = i / j;
31          break;
32        defalut:
33        res = 0;
34      }
35      //printf("%s%d\n", s, res);
36      //printf("%s%d", s, res);
37      char tmp[10] = { 0 };
38      sprintf(tmp, "%s%d;", s, res);
39      strcat(b, tmp);
40      s =strtok(NULL, ";");
41   }
42    strcpy(a, b);
43    printf("%s\n", a);
44    return 0;
45 }

=============================================================================
函数的定义和声明

前几节学习c语言库函数的使用,而实际呢?我们有必要去自己写函数,因为c语言库函数并不能满足我们所有的应用。即自定义函数。

在使用函数前必须要定义或者声明函数或者就把整个函数写在main函数的上面。

特备注意:自己定义的函数有两种声明有两种格式。

linux下示例代码如下:

1 #include <stdio.h>
2
3 //调用函数的声明。
4 void test1();
5
6 //这是一个自定义的函数,函数名叫add,返回值类型是int类型,函数有两个参数,分别是a和b。
7 int add(int a, int b)
8 {
9    return a + b;
10 }
11
12 //这是一个自定义的函数,函数名叫test,没有返回值,没有参数。
13 void test()
14 {
15    printf("test\n");
16 }
17
18 void test2(int a) //a是形参。
19 {
20    printf("a = %d\n", a);
21 }
22
23 int main()
24 {
25    int i = 2;
26    int j = 5;
27    int c = add(i, j); //调用一个有参数,有返回值的函数。
28    printf("%d\n", c);
29    test(); //调用一个没有参数,没有返回值的函数。
30    test1(); //调用一个没有参数,没有返回值的函数。注意这种调用方式需要在前面进行声明。
31    test2(j); //j是实参。  //test2(8);  //test2(20 + 4);  //test(i + j);
32    return 0;
33 }
-----------------------------------------------------------------------------
函数的形式参数(形参)与实际参数(实参)

在调用函数的时候,函数大多数都有参数,主调函数和被调函数之间需要传递数据。

在定义函数时函数名后面括弧中的变量名称为“形式参数”,简称形参。
在调用函数时,函数名后面的括弧中的变量或者表达式称为“实际参数”,简称实参。

注意几点:
1、形参在未出现函数调用的时,他们并不占用内存单元,只有在发生函数调用的时候形参才被分配内存,函数调用完成后,形参所占用的内存被释放。

2、实参可以使变量、常量或者表达式。

3、在定义函数时,一定要指定形参的数据类型。

4、形参与实参的数据类型一定可兼容。

5、在c语言中,实参与形参的数据传递是“值的传递”,即单向传递,即只由实参传递给形参,而不能有形参传递给实参。

即:
实参的值单向的给形参,但形参的值不会传递给实参。
形参的值来自于实参,但形参的值改变后并不会改变实参的值。

linux下示例代码如下:

1 #include <stdio.h>
2
3 void test2(int a)
4 {
5    ++a;
6    printf("a = %d\n", a);
7 }
8
9 int main()
10 {
11    int i = 5;
12    test2(i);
13    printf("i = %d\n", i); //输出:a = 6 i = 5
14    return 0;
15 }
-------------------------------------
小知识复习:
b = a++; //先计算表达式的值,即先把a赋值给了b;然后a再自加1。
b = ++a; //先a自加1后;然后把a自加后得到的赋值给b。
小结:谁在前面先计算谁!!!
-------------------------------------
-----------------------------------------------------------------------------
函数的返回值类型和返回值

1、函数的返回值通过函数中的return获得的,如果函数的返回值为void,则不需要return语句。

2、函数ruturn语句中的返回值数据类型应该与定义函数时相同。

3、如果函数中没有return语句,那么函数将返回一个不确定的值。
-----------------------------------------------------------------------------
return函数与exit函数(exit更猛,不受位置限制)

exit是c语言的库函数,有一个整型的参数,代表进程终止。使用这个函数需要stdlib.h这个头文件。

在函数中写return只是代表函数终止了,但不管在程序的任何位置调用exit,那么整个程序马上终止了。

在main函数中执行return语句,程序终止,但在子函数中执行return只是子函数终止了,但main依旧运行。

在main函数中执行return或者调用exit结果是一样的。

=============================================================================
课堂小练习:
自定义一个函数,实现大小写字母的互相转换功能。
例如:小写的a的ASCII是97,大写的A的ASCII是65。
其实大小写字母的ASCII相差是32哦,但空格的ASCII是32。

linux下示例代码如下:

1 #include <stdio.h>
2
3 char trans(char c)
4 {
5    if (c >= 'a' && c <= 'z')
6    {
7      return c - ' ';
8    }
9    if (c >= 'A' && c <= 'Z')
10   {
11     return c + ' ';
12    }
13 }
14
15 int main()
16 {
17    char a = 'r';
18    printf("%c\n", trans(a));
19    return 0;
20 }
-----------------------------------------------------------------------------
c语言有个库函数,名字叫atoi,将一个字符串转化为整数。
自定义一个函数,实现atoi的功能,要求是不能使用任何c语言已有的库函数。

"123"
'1' '2''3' '\0'

步骤:
0、首先知道这个字符串有多长。(如果字符串是3位长,那么第一位 * 10 2次方。)
1、遍历这个字符串。
2、将第一个'1'取出来,然后把'1'转化为整数1,把1 * 100。
  将'2'取出来,转化为2,2 * 10。
  将'3'取出来,转化为3。
3、1 * 100 + 2 * 10 + 3 = 123;

注意:'0'的ASCII的值是48,'1'的ASCII的值是49,'2'的ASCII的值是50等等。
所以字符1('1')转换为数字1(1)就是让字符1减去字符0可得,

0 = '0' - '0',
1 = '1' - '0',
2 = '2' - '0',
......

linux下示例代码如下:

1 #include <stdio.h>
2
3 //得到一个字符串长度的函数
4 int my_strlen(const char *s)
5 {
6    int len = 0;
7    while (s[len])
8    {
9      len++;
10   }
11   return len;
12 }
13
14 //得到10的n次方的函数
15 int my_pow10(int n)
16 {
17    if (n == 0) //10的0次方
18    return 1;
19    if (n == 1) //10的1次方
20    return 10;
21
22    int base = 10;
23    int i;
24    for (i = 1; i < n; i++)
25    {
26      base *= 10;
27    }
28   return base;
29 }
30
31 //把一个字符转换为一个0到9整数的函数
32 int my_chartoint(char c)
33 {
34    return c - '0';
35 }
36
37 //把一个字符串转化为整数的函数
38 int my_atoi(const char *nptr)
39 {
40    int len = my_strlen(nptr);
41
42    int value = 0;
43    int i;
44    for (i = 0; i < len; i++)
45   {
46      value += my_chartoint(nptr[i]) * my_pow10(len - 1 - i);
47    }
48    return value;
49 }
50
51 int main()
52 {
53    char a[] = "123";
54    int i = my_atoi(a);
55    printf("%d\n", i);
56    return 0;
57 }

=============================================================================
函数的递归

函数可以自己调用自己,这就叫函数的递归。

linux下示例代码如下:

典型的递归代码:

1 #include <stdio.h>
2
3 void test(int n)
4 {
5    printf("n = %d\n", n);
6    if (n < 3)
7    {
8      test(n + 1);
9   }
10 }
11
12 int main()
13 {
14    int a = 0;
15    test(a);
16    return 0;
17 }

输出
n = 0
n = 1
n = 2
n = 3
-----------------------------------------------------------------------------

1 #include <stdio.h>
2
3 void test(int n)
4 {
5    //printf("n = %d\n", n);
6    if (n < 3)
7    {
8      test(n + 1);
9    }
10       printf("n = %d\n", n);
11 }
12
13 int main()
14 {
15    int a = 0;
16    test(a);
17    return 0;
18 }

输出
n = 3
n = 2
n = 1
n = 0
-----------------------------------------------------------------------------
即:

test(0)
{
  test(1)
  {
    test(2)
    {
      test(3)
      {
        不符合条件退出if判断语句了。
        printf("n = %d\n", n);//第一次输出:n = 3
      }
      printf("n = %d\n", n);//第二次输出:n = 2
    }
    printf("n = %d\n", n);//第三次输出:n = 1
  }
  printf("n = %d\n", n);//第四次输出:n = 0
}
-----------------------------------------------------------------------------

1 #include <stdio.h>
2
3 void test(int n)
4 {
5    printf("n = %d\n", n); //把代码放到递归的前面,叫做先序递归。
6    if (n < 3) //递归一定要有个终止条件。
7    {
8      test(n + 1);
9    }
10      printf("n = %d\n", n); //把代码放到递归的后面,叫做后序递归。
11 }
12
13 int main()
14 {
15    int a = 0;
16    test(a);
17    return 0;
18 }

输出
n = 0
n = 1
n = 2
n = 3
n = 3
n = 2
n = 1
n = 0
-----------------------------------------------------------------------------
递归例子:
有n个人排成一队,
问第n个人多少岁,他回答比前面一个人大2岁,
再问前面一个人多少岁,他回答比前面一个人大2岁,
一直问到最后面的一个人,他回答他是10岁。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int age(int n)
4 {
5    if (n == 1) //终止条件
6    {
7      return 10;
8    }
9    else
10    {
11     return age(n - 1) + 2;
12    }
13 }
14
15 int main()
16 {
17    int a = 5;
18    printf("%d\n", age(a)); //18
19 }
-----------------------------------------------------------------------------
递归例子:
将10进制数转化为二进制数。

例如:求十进制数13的二进制数。

2 13
  6    1
  3    0
  1    1
  0    1
 商数   余数
倒过来看余数得:
(13)10 = (1101)2

linux下示例代码如下:

1 #include <stdio.h>
2
3 void to_bin(unsigned int n)
4 {
5    int i = n % 2;//得到余数
6        //printf("%d\n", i);//看一下,输出的余数。发现是先序递归。而我们需要的二进制需要倒过来,该如何呢?用后序递归。
7
8    if (n > 2)
9    {
10      to_bin(n / 2);
11   }
12
13    printf("%d", i);
14
15 }
16
17 int main()
18 {
19    int a = 1300;
20    scanf("%d", &a);
21    to_bin(a);
22    printf("\n");
23    return 0;
24 }
-----------------------------------------------------------------------------
递归例子:
写一个函数,将10进制数转化为16进制,不能使用c语言库函数。

不能如下这样啊:
void to_hex(int n)
{
printf("%x", n);
}

例如:求十进制数130的十六进制数。

16 130
  8   2
  0   8
     商数   余数
倒过来看余数得:
(130)10 = (82)16

linux下示例代码如下:

1 #include <stdio.h>
2
3 char hex_char(unsigned int n)
4 {
5    switch (n)
6    {
7      case 0:
8        return '0';
9      case 1:
10         return '1';
11     case 2:
12         return '2';
13     case 3:
14         return '3';
15     case 4:
16       return '4';
17     case 5:
18       return '5';
19     case 6:
20       return '6';
21     case 7:
22       return '7';
23     case 8:
24      return '8';
25     case 9:
26       return '9';
27     case 10:
28         return 'a';
29       case 11:
30         return 'b';
31     case 12:
32         return 'c';
33     case 13:
34          return 'd';
35     case 14:
36       return 'e';
37     case 15:
38       return 'f';
39   }
40   return '0';
41 }
42
43 void to_hex(unsigned int n)
44 {
45    int i = n % 16;//得到余数
46    //printf("%d\n", i);//看一下,输出的余数。发现是先序递归。而我们需要的二进制需要倒过来,该如何呢?用后序递归。
47
48    if (n > 16)
49   {
50      to_hex(n / 16);
51    }
52
53    printf("%c",hex_char(i));
54
55 }
56
57 int main()
58 {
59   int a = 1300;
60    scanf("%d", &a);
61    to_hex(a);
62    printf("\n");
63    return 0;
64 }
-----------------------------------------------------------------------------
递归例子:
菲波那切数列:
0,1,1,2,3,5,8,13,21,34,55,89,144,......
第零项是0;
第一项是1;
第二项是1;
第三项是2;
第四项是3;
......
该数列从第二项开始,每一项等于前两项之和。

linux下示例代码如下:

1 #include <stdio.h>
2
3 int fib(int n)
4 {
5    if (n == 0)
6       return 0;
7    if (n == 1)
8       return 1;
9    if (n > 1)
10      return fib(n - 1) + fib(n - 2);
11 }
12
13 int main()
14 {
15   int i;
16   for (i = 0; i < 20; i++)
17    {
18      printf("%d\n", fib(i));
19    }
20    return 0;
21 }
-----------------------------------------------------------------------------
递归的优点:
递归给某些编程问题提供了简单的方法。

递归缺点:
一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。

笔试的时候很可能考递归哦!考验的是智力和思维的能力。
因为他的那个图想起来很费劲!需要多加训练。

联想到我们小时候听说的一个故事:从前有座山,山里有座庙......
或者是画中画的放大与缩小的效应。
=============================================================================
多个源代码文件程序如何编译?

1、头文件的使用

如何把我们的代码分解为多个函数,如何把函数放进不同的文件里面。
因为实际中我们的函数是散落在多个文件里面的。
-----------------------------------------------------------------------------
方法一:
如果把main函数放在第一个文件中,而把自定义的函数放在第二个文件中,
那么调用第二个文件中的自定义函数时就要在第一个文件中声明该函数原型。

方法二:
如果把很多个函数原型包含在一个头文件里,那么就不必每次使用自定义的函数的时候去声明函数原型了。
把函数声明放入头文件是个很好的习惯!!!
-----------------------------------------------------------------------------

方法一:
如果把main函数放在第一个文件中,而把自定义的函数放在第二个文件中,
那么调用第二个文件中的自定义函数时就要在第一个文件中声明该函数原型。
(即自定义函数的申明放在有main函数的第一个文件中,自定义函数的定义放在第二个文件中)
需要在编译的时候对二者进行一起编译才行哦!
------------------------------------
例如:
第一个文件func9.c:
#include <stdio.h>

int max(int a, int b);
int add(int a, int b);

int main()
{
  int a = 10;
  int b = 20;
  printf("%d\n", max(a, b));
  printf("%d\n", add(a, b));
  return 0;
}
------------------------------------
第二个文件aa.c:

int max(int a, int b)
{
  return (a > b) ? a : b;
}

int add(int a, int b)
{
  return a + b;
}

一起进行编译:gcc -o f9 func9.c aa.c

-----------------------------------------------------------------------------
上面方法一在主函数里面声明函数原型比较啰嗦,我们一般不这么做!我们一般用方法二:

方法二:
如果把很多个函数声明原型包含在一个头文件里,那么就不必每次使用自定义的函数的时候去声明函数原型了。
把函数声明放入头文件是个很好的习惯!!!
------------------------------------
第一个文件func9.c:
#include <stdio.h>
#include "aa.h" //双引号表示在当前目录下

int main()
{
  int a = 10;
  int b = 20;
  printf("%d\n", max(a, b));
  printf("%d\n", add(a, b));
  return 0;
}
------------------------------------
第二个文件aa.c:
#include <stdio.h>

int max(int a, int b)
{
  return (a > b) ? a : b;
}

int add(int a, int b)
{
  return a + b;
}
------------------------------------
第三个文件aa.h:

int max(int a, int b);
int add(int a, int b);

一起进行编译:gcc -o f9 func9.c aa.c
-----------------------------------------------------------------------------
其实方法二也有些问题!

如果第一个文件func9.c中多次出现include,如下这一句:
#include "a.h"
#include "a.h"
#include "a.h"

进行预编译:gcc -o ff func9.c -E
则预编译时会出现多次函数声明!那么如何避免多次出现include呢?
(问题如下图)


-----------------------------------------------------------------------------
#include 是预编译指令,代表头文件包含。
#define 定义一个宏常量。

学习 #ifdef 与 #ifndef。
#ifdef 是个预编译指令,代表只要定义了一个宏常量,那么就预编译下面的代码。
#ifndef 是个预编译指令,代表只要没有定义了一个宏常量,那么就预编译下面的代码。

格式如下:

#define 宏

#ifdef 宏

代码

#endif

演示如下图:

还有另一种写法哦,这里不再赘述!
------------------------------------
鉴于此,我们对aa.h进行改造。

#ifndef _A_H
#define _A_H

int max(int a, int b);
int add(int a, int b);

#endif

//这样写的效果是:不管这个头文件被包含多少次,只有一次生效。
//这是c语言大多数的头文件都会写成这个样子,这样会避免被多次预编译。

.c文件里面放的是函数的定义。
.h文件里面放的是函数的声明。

=============================================================================

c语言基础学习06的更多相关文章

  1. D02-R语言基础学习

    R语言基础学习——D02 20190423内容纲要: 1.前言 2.向量操作 (1)常规操作 (2)不定长向量计算 (3)序列 (4)向量的删除与保留 3.列表详解 (1)列表的索引 (2)列表得元素 ...

  2. D01-R语言基础学习

    R语言基础学习——D01 20190410内容纲要: 1.R的下载与安装 2.R包的安装与使用方法 (1)查看已安装的包 (2)查看是否安装过包 (3)安装包 (4)更新包 3.结果的重用 4.R处理 ...

  3. D03——C语言基础学习PYTHON

    C语言基础学习PYTHON——基础学习D03 20180804内容纲要: 1 函数的基本概念 2 函数的参数 3 函数的全局变量与局部变量 4 函数的返回值 5 递归函数 6 高阶函数 7 匿名函数 ...

  4. D03-R语言基础学习

    R语言基础学习——D03 20190423内容纲要: 1.导入数据 (1)从键盘输入 (2)从文本文件导入 (3)从excel文件导入 2.用户自定义函数   3.R访问MySQL数据库 (1)安装R ...

  5. Golang 汇编asm语言基础学习

    Golang 汇编asm语言基础学习 一.CPU 基础知识 cpu 内部结构 cpu 内部主要是由寄存器.控制器.运算器和时钟四个部分组成. 寄存器:用来暂时存放指令.数据等对象.它是一个更快的内存. ...

  6. go语言基础学习

    go基础学习,面向对象-方法在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法 使用= 和:=的区别: // = 使用必须使用先var声明例如: var a a=100 ...

  7. C# 语言基础学习路线图

    一直以来,对于很多知识点都是存于收藏夹中,随着时间地变更,收藏夹中链接也起来越多,从未进行整理,也很零散,所以想对曾经遇到并使用过的一些知识形成文档,作为个人知识库的一部分. 就从C# 语言基础开始, ...

  8. C语言基础学习基本数据类型-变量的命名

    变量的命名 变量命名规则是为了增强代码的可读性和容易维护性.以下为C语言必须遵守的变量命名规则: 1. 变量名只能是字母(A-Z,a-z),数字(0-9)或者下划线(_)组成. 2. 变量名第一个字母 ...

  9. 从零开始系列-R语言基础学习笔记之二 数据结构(二)

    在上一篇中我们一起学习了R语言的数据结构第一部分:向量.数组和矩阵,这次我们开始学习R语言的数据结构第二部分:数据框.因子和列表. 一.数据框 类似于二维数组,但不同的列可以有不同的数据类型(每一列内 ...

随机推荐

  1. AVL树的单双旋转操作

    把必须重新平衡的节点称为å.对于二叉树,å的两棵子树的高度最多相差2,这种不平衡可能有四种情况: 对å的左儿子的左子树进行插入节点(左-左) 对å的左儿子的右子树进行插入节点(左-右) 对å的右儿子的 ...

  2. Equilibrium point

    Given an array A your task is to tell at which position the equilibrium first occurs in the array. E ...

  3. 程序员的自我救赎---11.1:RPC接口使用规范

    <前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...

  4. bzoj 3575: [Hnoi2014]道路堵塞

    Description A 国有N座城市,依次标为1到N.同时,在这N座城市间有M条单向道路,每条道路的长度是一个正整数.现在,A国交通部指定了一条从城市1到城市N的路径, 并且保证这条路径的长度是所 ...

  5. 阅读MDN文档之层叠与继承(二)

    目录 The cascade Importance Specificity Source order A note on rule mixing Inheritance Controlling inh ...

  6. c#发展前景

    根据育龙网资料评价显示:C#几乎集中了所有关于软件开发和软件工程研究的最新成果:面向对象.类型安全.组件技术.自动内存管理.跨平台异常处理.版本控制.代码安全管理…….尽管像很多人注意到的一样,罗列上 ...

  7. Disruptor并发框架(一)简介&上手demo

    框架简介 Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易.这个系统是建立在JVM平台上,其核心是一 ...

  8. MongoDB数据库详解

    第1章 数据库管理系统 1.1 前言 01.数据的定义:文字.图像.地理位置信息(坐标.经纬度)等 02.数据库管理系统的定义:建立.存取和管理数据,保证数据安全和完整性的软件 03.常见的数据库管理 ...

  9. java多线程(五)-访问共享资源以及加锁机制(synchronized,lock,voliate)

    对于单线程的顺序编程而言,每次只做一件事情,其享有的资源不会产生什么冲突,但是对于多线程编程,这就是一个重要问题了,比如打印机的打印工作,如果两个线程都同时进行打印工作,那这就会产生混乱了.再比如说, ...

  10. 针对单个 js 文件禁用 ESLint 语法校验

    问题描述: 在 Vue-cli 创建的项目中,使用了 ESLint 规范代码的项目中 如何针对单个 js 文件禁用 ESLint 语法校验,但整个项目依然保留 ESLint 的校验规则? 解决方案: ...