1. 程式人生 > >c語言基礎學習06

c語言基礎學習06

cas clu 小知識 fputs 轉義字符 而已 參與 子串 翻譯

=============================================================================
涉及到的知識點有: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 }

-----------------------------------------------------------------------------
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>

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

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:
#include <stdio.h>

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