今天我們繼續來學習C語言的入門知識點,第一課:C/C++編程筆記:C語言入門知識點(二),請收藏C語言最全筆記! 21. 輸入 & 輸出 當我們提到輸入時,這意味著要向程式填充一些數據。輸入可以是以文件的形式或從命令行中進行。C 語言提供了一系列內置的函數來讀取給定的輸入,並根據需要填充到程式中。 ...
今天我們繼續來學習C語言的入門知識點,第一課:C/C++編程筆記:C語言入門知識點(二),請收藏C語言最全筆記!

21. 輸入 & 輸出
當我們提到輸入時,這意味著要向程式填充一些數據。輸入可以是以文件的形式或從命令行中進行。C 語言提供了一系列內置的函數來讀取給定的輸入,並根據需要填充到程式中。
當我們提到輸出時,這意味著要在屏幕上、印表機上或任意文件中顯示一些數據。C 語言提供了一系列內置的函數來輸出數據到電腦屏幕上和保存數據到文本文件或二進位文件中。
標準輸出
C 語言把所有的設備都當作文件。所以設備(比如顯示器)被處理的方式與文件相同。以下三個文件會在程式執行時自動打開,以便訪問鍵盤和屏幕。

文件指針是訪問文件的方式,本節將講解如何從屏幕讀取值以及如何把結果輸出到屏幕上。
C 語言中的 I/O (輸入/輸出) 通常使用 printf() 和 scanf() 兩個函數。
scanf() 函數用於從標準輸入(鍵盤)讀取並格式化, printf() 函數發送格式化輸出到標準輸出(屏幕)。
例子:
void main(){
float f;
printf("Enter a float number: \n");
// %f 匹配浮點型數據
scanf("%f",&f);
printf("Value = %f", f);
}
輸出:
Enter a float number:
12.3
Value = 12.300000
getchar()&putchar() 函數
int getchar(void)函數從屏幕讀取下一個可用的字元,並把它返回為一個整數。這個函數在同一個時間內只會讀取一個單一的字元。您可以在迴圈內使用這個方法,以便從屏幕上讀取多個字元。
int putchar(int c)函數把字元輸出到屏幕上,並返回相同的字元。這個函數在同一個時間內只會輸出一個單一的字元。您可以在迴圈內使用這個方法,以便在屏幕上輸出多個字元。
void main(){
int c;
printf( "\nEnter a value :");
//函數從屏幕讀取下一個可用的字元,並把它返回為一個整數。這個函數在同一個時間內只會讀取一個單一的字元。您可以在迴圈內使用這個方法,以便從屏幕上讀取多個字元。
c = getchar( );
printf( "\nYou entered: ");
//讀取第一個字元
putchar( c );
}
輸出:
Enter a value :abcdef
You entered: a
gets() & puts() 函數
char *gets(char *s)函數從stdin讀取一行到s所指向的緩衝區,直到一個終止符或 EOF。
int puts(const char *s)函數把字元串 s 和一個尾隨的換行符寫入到stdout。
void main(){
char str[100];
printf( "\nEnter a value :");
//讀取一行
gets( str );
printf( "\nYou entered: ");
puts( str );
}
輸出:
Enter a value :大家好,才是真的好!
You entered: 大家好,才是真的好!
22. 文件讀寫
上一節我們講解了 C 語言處理的標準輸入和輸出設備。本章我們將介紹 C 程式員如何創建、打開、關閉文本文件或二進位文件。
一個文件,無論它是文本文件還是二進位文件,都是代表了一系列的位元組。C 語言不僅提供了訪問頂層的函數,也提供了底層(OS)調用來處理存儲設備上的文件。本章將講解文件管理的重要調用。
打開文件
您可以使用fopen( )函數來創建一個新的文件或者打開一個已有的文件,這個調用會初始化類型FILE的一個對象,類型FILE包含了所有用來控制流的必要的信息。下麵是這個函數調用的原型:
FILE *fopen( const char * filename, const char * mode );
在這裡,filename 是字元串,用來命名文件,訪問模式 mode 的值可以是下列值中的一個:

如果處理的是二進位文件,則需要使用下麵的訪問模式來取代上面的訪問模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
關閉文件
為了關閉文件,請使用 fclose( ) 函數。函數的原型如下:
int fclose( FILE *fp );
如果成功關閉文件,fclose( )函數返回零,如果關閉文件時發生錯誤,函數返回EOF。這個函數實際上,會清空緩衝區中的數據,關閉文件,並釋放用於該文件的所有記憶體。EOF 是一個定義在頭文件stdio.h中的常量。
C 標準庫提供了各種函數來按字元或者以固定長度字元串的形式讀寫文件。
寫入文件
下麵是把字元串寫入到流中的最簡單的函數:
int fputc(int c,FILE *fp);
函數 fputc() 把參數 c 的字元值寫入到 fp 所指向的輸出流中。如果寫入成功,它會返回寫入的字元,如果發生錯誤,則會返回 EOF。您可以使用下麵的函數來把一個以 null 結尾的字元串寫入到流中:
int fputs( const char *s, FILE *fp );
函數fputs()把字元串s寫入到 fp 所指向的輸出流中。如果寫入成功,它會返回一個非負值,如果發生錯誤,則會返回EOF。您也可以使用int fprintf(FILE *fp,const char *format, ...)函數來寫把一個字元串寫入到文件中。嘗試下麵的實例:

void main(){
//定義一個空指針文件
FILE *fp = NULL;
//打開文件,打開一個文本文件,允許讀寫文件。
// 如果文件不存在,則會創建一個新文件。
// 讀取會從文件的開頭開始,寫入則只能是追加模式。
fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","a+");
fprintf(fp, " fprintf 我是添加進來的1\n");
fprintf(fp, "fprintf 我是添加進來的2\n");
fputs("fputs 我是添加進來的1\n", fp);
fputs("fputs 我是添加進來的2\n", fp);
fclose(fp);
}
讀取文件
下麵是從文件讀取單個字元的最簡單的函數:
int fgetc( FILE * fp );
fgetc() 函數從 fp 所指向的輸入文件中讀取一個字元。返回值是讀取的字元,如果發生錯誤則返回 EOF。下麵的函數允許您從流中讀取一個字元串:
char *fgets( char *buf, int n, FILE *fp );
函數fgets()從 fp 所指向的輸入流中讀取 n - 1 個字元。它會把讀取的字元串複製到緩衝區buf,併在最後追加一個null字元來終止字元串。
如果這個函數在讀取最後一個字元之前就遇到一個換行符 '\n' 或文件的末尾 EOF,則只會返回讀取到的字元,包括換行符。您也可以使用int fscanf(FILE *fp, const char *format, ...)函數來從文件中讀取字元串,但是在遇到第一個空格和換行符時,它會停止讀取。

void main(){
FILE *fp = NULL;
//讀取文件
char buff[255];
fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","r");
fscanf(fp,"%s",buff);
printf("1: %s\n", buff);
fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff);
fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);
}
23. 預處理器
C 預處理器不是編譯器的組成部分,但是它是編譯過程中一個單獨的步驟。簡言之,C 預處理器只不過是一個文本替換工具而已,它們會指示編譯器在實際編譯之前完成所需的預處理。我們將把 C 預處理器(C Preprocessor)簡寫為 CPP。
所有的預處理器命令都是以井號(#)開頭。它必須是第一個非空字元,為了增強可讀性,預處理器指令應從第一列開始。下麵列出了所有重要的預處理器指令:

例子:
分析下麵的實例來理解不同的指令。
#define MAX_ARRAY_LENGTH 20
(1)這個指令告訴 CPP 把所有的 MAX_ARRAY_LENGTH 替換為 20。使用 #define 定義常量來增強可讀性。
#include <stdio.h>
#include "utils.h"
(2)這些指令告訴 CPP 從系統庫中獲取 stdio.h,並添加文本到當前的源文件中。下一行告訴 CPP 從本地目錄中獲取 utils.h,並添加內容到當前的源文件中。
#undef FILE_SIZE
#define FILE_SIZE 42
(3)這個指令告訴 CPP 取消已定義的 FILE_SIZE,並定義它為 42。
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
這個指令告訴 CPP 只有當 MESSAGE 未定義時,才定義 MESSAGE。
#ifdef DEBUG
/* Your debugging statements here */
#endif
這個指令告訴 CPP 如果定義了 DEBUG,則執行處理語句。在編譯時,如果您向 gcc 編譯器傳遞了-DDEBUG開關量,這個指令就非常有用。它定義了 DEBUG,您可以在編譯期間隨時開啟或關閉調試。
預定義巨集
ANSI C 定義了許多巨集。在編程中您可以使用這些巨集,但是不能直接修改這些預定義的巨集。

預處理器運算符
C 預處理器提供了下列的運算符來幫助您創建巨集:
巨集延續運算符()
一個巨集通常寫在一個單行上。但是如果巨集太長,一個單行容納不下,則使用巨集延續運算符(\)。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
字元串常量化運算符(#)
在巨集定義中,當需要把一個巨集的參數轉換為字元串常量時,則使用字元串常量化運算符(#)。在巨集中使用的該運算符有一個特定的參數或參數列表。例如:
#include <stdio.h>
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void)
{
message_for(Carole, Debra);
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Carole and Debra: We love you!
標記粘貼運算符(##)
巨集定義內的標記粘貼運算符(##)會合併兩個參數。它允許在巨集定義中兩個獨立的標記被合併為一個標記。例如:
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
token34 = 40
這是怎麼發生的,因為這個實例會從編譯器產生下列的實際輸出:
printf ("token34 = %d", token34);
這個實例演示了 token##n 會連接到 token34 中,在這裡,我們使用了字元串常量化運算符(#)和標記粘貼運算符(##)。
defined() 運算符
預處理器defined運算符是用在常量表達式中的,用來確定一個標識符是否已經使用 #define 定義過。如果指定的標識符已定義,則值為真(非零)。如果指定的標識符未定義,則值為假(零)。下麵的實例演示了 defined() 運算符的用法:
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void)
{
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Here is the message: You wish!

參數化的巨集
CPP 一個強大的功能是可以使用參數化的巨集來模擬函數。例如,下麵的代碼是計算一個數的平方:
int square(int x) {
return x * x;
}
我們可以使用巨集重寫上面的代碼,如下:
#define square(x) ((x) * (x))
在使用帶有參數的巨集之前,必須使用 #define 指令定義。參數列表是括在圓括弧內,且必須緊跟在巨集名稱的後邊。巨集名稱和左圓括弧之間不允許有空格。例如:
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
當上面的代碼被編譯和執行時,它會產生下列結果:
Max between 20 and 10 is 20
24. 頭文件
頭文件是擴展名為.h的文件,包含了 C 函數聲明和巨集定義,被多個源文件中引用共用。有兩種類型的頭文件:程式員編寫的頭文件和編譯器自帶的頭文件。
在程式中要使用頭文件,需要使用 C 預處理指令#include來引用它。前面我們已經看過stdio.h頭文件,它是編譯器自帶的頭文件。
引用頭文件相當於複製頭文件的內容,但是我們不會直接在源文件中複製頭文件的內容,因為這麼做很容易出錯,特別在程式是由多個源文件組成的時候。
A simple practice in C 或 C++ 程式中,建議把所有的常量、巨集、系統全局變數和函數原型寫在頭文件中,在需要的時候隨時引用這些頭文件。
引用頭文件的語法
使用預處理指令#include可以引用用戶和系統頭文件。它的形式有以下兩種:
#include <file>
這種形式用於引用系統頭文件。它在系統目錄的標準列表中搜索名為 file 的文件。在編譯源代碼時,您可以通過 -I 選項把目錄前置在該列表前。
#include "file"
這種形式用於引用用戶頭文件。它在包含當前文件的目錄中搜索名為 file 的文件。在編譯源代碼時,您可以通過 -I 選項把目錄前置在該列表前。
引用頭文件的操作
#include指令會指示 C 預處理器瀏覽指定的文件作為輸入。預處理器的輸出包含了已經生成的輸出,被引用文件生成的輸出以及#include指令之後的文本輸出。例如,如果您有一個頭文件 char_manger.h,如下:
char *test(void);
和一個使用了頭文件的主程式 char_manager.c,如下:
#include "char_manger.h"
int x;
int main (void)
{
puts (test ());
}
編輯器會看到如下的代碼信息:
char *test (void);
int x;
int main (void)
{
puts (test ());
}
只引用一次頭文件
如果一個頭文件被引用兩次,編譯器會處理兩次頭文件的內容,這將產生錯誤。為了防止這種情況,標準的做法是把文件的整個內容放在條件編譯語句中,如下:
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
這種結構就是通常所說的包裝器#ifndef。當再次引用頭文件時,條件為假,因為 HEADER_FILE 已定義。此時,預處理器會跳過文件的整個內容,編譯器會忽略它。
有條件引用
有時需要從多個不同的頭文件中選擇一個引用到程式中。例如,需要指定在不同的操作系統上使用的配置參數。您可以通過一系列條件來實現這點,如下:
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
但是如果頭文件比較多的時候,這麼做是很不妥當的,預處理器使用巨集來定義頭文件的名稱。這就是所謂的有條件引用。它不是用頭文件的名稱作為 #include 的直接參數,您只需要使用巨集名稱代替即可:
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
25. 強制類型轉換
強制類型轉換是把變數從一種類型轉換為另一種數據類型。例如,如果您想存儲一個 long 類型的值到一個簡單的整型中,您需要把 long 類型強制轉換為 int 類型。您可以使用強制類型轉換運算符來把值顯式地從一種類型轉換為另一種類型,如下所示:
(type_name) expression
請看下麵的實例,使用強制類型轉換運算符把一個整數變數除以另一個整數變數,得到一個浮點數:
void main(){
void main(){
int sum = 20,count = 3;
double value,value2;
value = (double)sum / count;
value2 = sum / count;
printf("Value 強轉 : %f Value2 wei強轉 : %f\n ", value ,value2);
}
}
輸出:
Value 強轉 : 6.666667 Value2 wei強轉 : 6.000000
整數提升
整數提升是指把小於 int 或 unsigned int 的整數類型轉換為 int 或 unsigned int 的過程。請看下麵的實例,在 int 中添加一個字元:
void main(){
//整數提升
int i= 17;
char c = 'c'; //在 ascii 中的值表示 99
int sum2;
sum2 = i + c;
printf("Value of sum : %d\n", sum2 );
}
輸出:
Value of sum : 116
在這裡,sum 的值為 116,因為編譯器進行了整數提升,在執行實際加法運算時,把 'c' 的值轉換為對應的 ascii 值。
26. 錯誤處理
C 語言不提供對錯誤處理的直接支持,但是作為一種系統編程語言,它以返回值的形式允許您訪問底層數據。在發生錯誤時,大多數的 C 或 UNIX 函數調用返回 1 或 NULL,同時會設置一個錯誤代碼errno,該錯誤代碼是全局變數,表示在函數調用期間發生了錯誤。您可以在 errno.h 頭文件中找到各種各樣的錯誤代碼。
所以,C 程式員可以通過檢查返回值,然後根據返回值決定採取哪種適當的動作。開發人員應該在程式初始化時,把 errno 設置為 0,這是一種良好的編程習慣。0 值表示程式中沒有錯誤。
errno、perror() 和 strerror()
C 語言提供了perror()和strerror()函數來顯示與errno相關的文本消息。
(1)perror()函數顯示您傳給它的字元串,後跟一個冒號、一個空格和當前 errno 值的文本表示形式。
(2)strerror()函數,返回一個指針,指針指向當前 errno 值的文本表示形式。
讓我們來模擬一種錯誤情況,嘗試打開一個不存在的文件。您可以使用多種方式來輸出錯誤消息,在這裡我們使用函數來演示用法。另外有一點需要註意,您應該使用stderr文件流來輸出所有的錯誤。
例子:
void main(){
int dividend = 20;
int divsor = 0;
int quotient;
if (divsor == 0){
fprintf(stderr,"除數為 0 退出運行。。。\n");
exit(EXIT_FAILURE);
}
quotient = dividend / divsor;
fprintf(stderr,"quotient 變數的值為 : %d\n", quotient);
exit(EXIT_SUCCESS);
}
輸出:除數為 0 退出運行。。。
27. 遞歸
遞歸指的是在函數的定義中使用函數自身的方法。
語法格式如下:
void recursion()
{
statements;
... ... ...
recursion(); /* 函數調用自身 */
... ... ...
}
int main()
{
recursion();
}
數的階乘
double factorial(unsigned int i){
if (i <= 1){
return 1;
}
return i * factorial(i - 1);
}
void main(){
int i = 15;
printf("%d 的階乘 %ld \n",i ,factorial(i));
}
輸出:15 的階乘 140732727129776
斐波拉契數列
//斐波拉契數列
int fibonaci(int i){
if (i == 0){
return 0;
}
if (i == 1){
return 1;
}
return fibonaci(i - 1) + fibonaci( i -2);
}
void main(){
for (int j = 0; j < 10; j++) {
printf("%d\t\n", fibonaci(j));
}
}
輸出:
0
1
1
2
3
5
8
13
21
34
28. 可變參數
有時,您可能會碰到這樣的情況,您希望函數帶有可變數量的參數,而不是預定義數量的參數。C 語言為這種情況提供了一個解決方案,它允許您定義一個函數,能根據具體的需求接受可變數量的參數。下麵的實例演示了這種函數的定義。
int func(int, ... )
{
.
.
.
}
int main()
{
func(2, 2, 3);
func(3, 2, 3, 4);
}
請註意,函數func()最後一個參數寫成省略號,即三個點號(...),省略號之前的那個參數是int,代表了要傳遞的可變參數的總數。為了使用這個功能,您需要使用stdarg.h頭文件,該文件提供了實現可變參數功能的函數和巨集。具體步驟如下:
(1)定義一個函數,最後一個參數為省略號,省略號前面可以設置自定義參數。
(2)在函數定義中創建一個va_list類型變數,該類型是在 stdarg.h 頭文件中定義的。
(3)使用int參數和va_start巨集來初始化va_list變數為一個參數列表。巨集 va_start 是在 stdarg.h 頭文件中定義的。
(4)使用va_arg巨集和va_list變數來訪問參數列表中的每個項。
(5)使用巨集va_end來清理賦予va_list變數的記憶體。
現在讓我們按照上面的步驟,來編寫一個帶有可變數量參數的函數,並返回它們的平均值:
double average(int num,...){
va_list vaList;
double sum = 0.0;
int i ;
//為 num 個參數初始化 valist
va_start(vaList,num);
//訪問所有賦給 vaList 的參數
for (int j = 0; j < num; j++) {
sum += va_arg(vaList, int);
}
//清理為valist 保留的記憶體
va_end(vaList);
return sum/num;
}
void main(){
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
輸出:
Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000
29. 記憶體管理
本章將講解 C 中的動態記憶體管理。C 語言為記憶體的分配和管理提供了幾個函數。這些函數可以在 <stdlib.h> 頭文件中找到。

**註意: ** void * 類型表示未確定類型的指針。C、C++ 規定 void * 類型可以通過類型轉換強制轉換為任何其它類型的指針。
動態分配記憶體
編程時,如果您預先知道數組的大小,那麼定義數組時就比較容易。例如,一個存儲人名的數組,它最多容納 100 個字元,所以您可以定義數組,如下所示:
char name[100];
但是,如果您預先不知道需要存儲的文本長度,例如您向存儲有關一個主題的詳細描述。在這裡,我們需要定義一個指針,該指針指向未定義所需記憶體大小的字元,後續再根據需求來分配記憶體,如下所示:
void main() {
char name[100];
char *description;
//將字元串 copy 到 name 中
strcpy(name, "迎娶白富美!");
//開始動態分配記憶體
description = (char *) malloc(200 * sizeof(char));
if (description == NULL) {
fprintf(stderr, "Error - unable to allocate required memory\n");
} else {
strcpy(description, "開始添加數據到 description 中");
}
printf("Name = %s\n", name );
printf("Description: %s sizeOf 大小 :%d\n", description , sizeof(description));
// 使用 free() 函數釋放記憶體
free(description);
}
輸出:
Name = 迎娶白富美!
Description: 開始添加數據到 description 中 sizeOf 大小 :8
30. 命令行參數
執行程式時,可以從命令行傳值給 C 程式。這些值被稱為命令行參數,它們對程式很重要,特別是當您想從外部控製程序,而不是在代碼內對這些值進行硬編碼時,就顯得尤為重要了。
命令行參數是使用 main() 函數參數來處理的,其中,argc是指傳入參數的個數,argv[]是一個指針數組,指向傳遞給程式的每個參數。下麵是一個簡單的實例,檢查命令行是否有提供參數,並根據參數執行相應的動作:
void main(int argc , char *argv[]){
if (argc ==1){
printf("argv[%d] == %d",0,*argv[0]);
}
else if (argc ==2){
printf("argv[%d] == %d",1,*argv[1]);
} else{
printf("匹配失敗...");
}
}
輸出:argv[0] == 47
總結
到了這裡,我們連續三天的C語言入門知識點分享,到這裡就結束了,你們收穫了多少呢?筆者也知道你們如果初學的話肯定一時間也記不住什麼,所以還是要記得收藏哈!這個很重要的。
不知道大家在看完 C 基礎內容之後在對比下 Java 語法,是不是大部分都差不多,之前有的人說學了 C 在學其它語言都是小菜一碟,現在看來好像是這麼回事。個人覺得其實只要會編程語言中的任何一門在學其它語言都會比較容易上手。
自學C/C++編程難度很大,不妨和一些志同道合的小伙伴一起學習成長!
C語言C++編程學習交流圈子,【點擊進入】微信公眾號:C語言編程學習基地
有一些源碼和資料分享,歡迎轉行也學習編程的伙伴,和大家一起交流成長會比自己琢磨更快哦!