《21天学通C语言》总结(2)
-《21天學通C語言》 第六版
第8天:數組
聲明數組
int array[10];c語言中數組從0開始,并且c語言編譯器不對程序使用的下標是否越界進行判斷,程序會順利編譯和鏈接,但下標越界通常導致錯誤的結果。
多維數組
#define MONTHS 12 int array[MONTHS];使用常量定義數組,方便修改,規定常量名大寫。
 無法使用const定義的常量:
 const int MONTHS = 12;
 int array[MONTHS] // 錯誤
 數組維數盡可能不要超過三維。
多維數組初始化
#include <stdio.h> #include <stdlib.h>int x, y, randarray[5][4];int main(void) {for (x = 0; x < 5; x++){for (y = 0; y < 4; y++){randarray[x][y] = rand();}}for (x = 0; x < 5; x++){for (y = 0; y < 4; y++){printf("\t%d", randarray[x][y]);}printf("\n");}getchar(); //暫停return 0; }上面程序初始化數組中的元素為隨機值并打印,stdlib.h包含隨機函數rand()。
 最后使用getchar()可以暫停程序,直到用戶輸入。
 沒有初始化數組時,如果數組是全局變量則會被設置為0,而局部變量為隨機值。
可以使用sizeof()運算符獲得數組的大小,也可以使用size()獲得大小后除以數組類型對應的元素長度確定包含的元素數。
c語言中數組無法直接相加,但可以寫一個函數讓它們直接相加。
第9天:指針
指針是一個變量,其值為另一個變量的地址。
指針的聲明
 typename *ptrname ( * )為間接運算符,表明ptrname是一個指向typename類型變量的指針。編譯器根據上下文判斷 * 為乘法還是間接運算符。
初始化指針
 使用地址運算符初始化指針,pointer = &variable
直接存取,間接存取
 printf("%d", rate); printf("%d", *p_rate)
 間接運算符 * 提供存儲在指針指向位置的值(稱為解除引用)
指針處理多字節變量時,變量的地址為它占用的第一個字節的地址。
指針和數組
不帶方括號的數組名是一個常量指針,指向數組第一個元素,即date &date[0],等價。
 常量指針無法修改,將其賦值給一個指針即可。
 指針遞增,遞減。pointer++會自動指向下一個元素,而不是加一個字節。
如果數組類型為int,則pointer+4,而pointer += 4則將pointer加16。對指向數組元素的指針遞增或遞減時,編譯器不會跟蹤數組開始和結束的位置,因此需要注意指針指向的位置。
可以對兩個指針相減,返回兩個元素之間相差的元素數,而不是字節差。
 ptr1 - ptr2
 還可以對指針進行比較,==,<,>,<=,>=,!=等。
 無法對指針進行乘除運算。
數組下標法和指針解除引用
 下面關系成立:
將數組傳遞給函數
函數無法之間接受數組,因此可以通過傳遞指針和數組的長度給函數,或者在數組最后面加一個標志表示數組結束。
 例如:int function(int [], int);,int function(int *, int);。
 上面兩個等價,int [],int *,表示該參數是一個指針,推薦使用第一個形式。
fflush(stdin)會把存進緩沖區的數據刷掉,以便正確輸入
#include <stdio.h> #include <conio.h>void main( void ) {int integer;char string[81];/* Read each word as a string. */printf( "Enter a sentence of four words with scanf: " );for( integer = 0; integer < 4; integer++ ){scanf( "%s", string );printf( "%s\n", string );}/* You must flush the input buffer before using gets. */fflush( stdin );printf( "Enter the same sentence with gets: " );gets( string );printf( "%s\n", string ); }第10天:字符和字符串
單字符和字符數組
 char code = 'x'
 char string[10]
 字符初始化時只能用單引號,字符串初始化時用雙引號。字符數組能夠存儲的最長字符串包含的字符數比數組元素數少1,因為字符串以空字符結尾,/0,其acsii碼為0。
 字面字符串:用雙引號括起來的一個或多個字符。
 數組字符串:用數組表示的字符串。
不存儲在數組中的字符串
 char *message = "Great Caesar's Ghost ! ";
 char message[] = "Great Caesar's Ghost ! ";
 使用指針初始化字符數組,編譯時自動分配存儲空間。*message與message[]等價,此時message未被初始化,會導致錯誤??
malloc()函數
 返回一個void類型的指針,void類型的指針能與所有的數據類型兼容。頭文件stdlib.h,無法分配內存時返回NULL。應該總是檢查malloc()的返回值。
使用malloc(number * sizeof(type));sizeof可以確保代碼的可移植性。
 不應將過大的字符串賦給數組,例如:
 char a_string[] = 'NO';
 如果將’YES’賦給該數組,第4個字符會覆蓋后面的內容。
puts函數,printf函數
 puts接受一個參數——指向要顯示字符串的指針,打印結束后自動換行。
 printf使用格式化字符串和轉換說明符來格式化其輸出。%s,參數為指向字符串的指針。
gets函數,scanf函數
 gets遇到換行符結束,scanf遇到空白字符(空格、制表符、換行符)結束。
gets從鍵盤讀取一個字符串。直到遇到換行符,丟棄換行符添加一個空字符,字符串存儲到gets()的char指針指向的位置,返回一個指向開始位置的指針。
 可以使用該指針判斷是否輸入空字符串:
 while ( *(ptr = gets(input) ) != NULL )
 使用gets()或其他指針來存儲數據的函數時,指針一定要指向已分配空間。
scanf()使用地址作為參數,數組名即為地址因此不需要&,常規變量則需要&。
 使用%s第一個非空白字符為開始,下一個空白字符(空格,制表符或換行符)之前結束。
 使用%ns則讀取n個字符或遇到下一個空白字符。
有些程序員使用gets()輸入所以數據,然后讓程序將數組分離出來,并將其轉換為數值。
- 使用指針初始化數組時,可以把所指向的地址賦值給另一個指針,但不能把地址賦值給另一個數組。
 char *m = "asdfgh";// 實際使用需要初始化。
 char *m1;
 char m2[];
 m1 = m; 正確
 m2 = m;錯誤,應使用數組賦值函數,例如:strcpy()。
- char *ptr; gets(ptr);如果這樣,該字符串可能覆蓋重要信息! 因此必須初始化。
第11天:結構、共用體和TypeDef
簡單結構
struct tag{structure_member(s); }instance;可以結構的定義和聲明同時進行,也可以分開,只有聲明才會為數據對象預留內存空間,例如:
 struct coord{
 int x;
 int y;
 };
 struct coord first, second;
 coord即結構的名稱叫做結構的標記(tag),存取結構使用句點運算符(.),例如:
結構的優點在于可以使用簡單的賦值語句在同類型的結構直接復制信息:
 first = second;
 等價于:
 first.x = second.x;
 first.y = second.y;
 結構體是為易于操作而被組合在一起的一個或多個變量,其中變量的數據類型不要求相同,也不要求是簡單變量,可以包含數組、指針和其他結構體。
復雜結構
包含其他結構體的結構體
#include <stdio.h>int length, width; int area;struct coord{int x;int y; };struct rectangle{struct coord topleft;struct coord bottomrt; } mybox;mybox的兩個成員均為coord結構,因此包含x,y兩個成員。
包含數組的結構體
#include <stdio.h>struct data{float amount;char fname[30];char lname[30]; };結構數組
包含結構體的數組
/* 使用包含數組的結構體 */ #include <stdio.h>struct entry{char fname[20];char lname[20];char phone[10]; };struct entry list[4];初始化結構
可以在定義結構體時進行初始化:
struct customer{char firm[20];char contact[25]; };struct sale{struct customer buyer;char item[20];float amount; };struct sale y1990[1000] = {{ { "Ac bb", "gg"},"LEFTDDADA",100.00}{ {"albb", "may"},"qwerttt";4458.98} };結構和指針
- 將指針作為結構成員
 struct data
 {
 char *p1;
 char *p2;
 }myptrs;
 char s1[] = "avcs", s2[] = "sdasdq";
 myptrs.p1 = s1;
 myptrs.p2 = s2;
 使用指針時必須先對其初始化!
- 指向結構的指針
而以下用指針表示成員的表達式等價:
gizmo.number; // str.memb (*p_part).number; // (*p_str.memb) p_part->number // p_str.memb其中第二個括號必須加,因為成員運算符(.)的優先級比間接運算符(*)高。
-使用指針的結構數組
 接著上面的代碼
p_part即指向數組第一個結構的指針,p_part++即指向數組的下一個結構。
 p_part++與p_part += sizeof(obj)不等價,書上似乎錯了。
- 將結構作為參數傳遞給函數
直接將實例名傳遞給函數即可。
共用體
類似于結構體,特點是同一時間只有一個成員可用,長度為最長成員的長度。
#include <stdio.h>#define CHARACTER 'c' #define INTEGRAL 'i' #define FLOAT 'f'struct generic_tag{char type;union shared_tag {char c;int i;float f;} shared; };... struct generic_tag var; var.type = CHARACTER; var.shared.c = '^';初始化共用體時只能初始其第一個成員。
使用typedef給結構創建別名
typedef struct{int x;int y; } coord;coord topleft, bottomright; struct coord{int x;int y; };struct coord topleft, bottomright;二者等價,但使用typedef時不需要關鍵字struct,不過此時標志符coord不是結構名。
第12天:變量作用域
外部變量(全局變量):
 程序中大部分或全部函數都需要使用的變量,應將其聲明為外部變量。
- 外部靜態變量:外部靜態變量值位于它所在的文件中,且在它的定義之后的函數中可見。
- 常規外部變量:對于其所在文件中所有函數都可見,且其他文件中的函數也可以使用。
- 外部變量不初始化則默認初始化為0。
- extern關鍵字
 使用外部變量時,用extern來聲明是個好習慣。
 int x = 999;
 int main(void)
 {
 extern int x;
 }
局部變量:
-  靜態變量:static int x,只在首次調用時被初始化1次,之后會保留前一次退出時的值。 
 不要在main函數中定義靜態局部變量,這樣毫無意義。
-  動態變量:auto int x,auto可以不加,只有在調用的時候存在。 
-  局部變量不初始化其值不確定。 
寄存器變量:register int x;對于頻繁使用的變量,有極大的好處。
- register關鍵字只是建議,而不是命令
- register只能用于簡單的數值變量,不能用于數組和結構,也不能用于靜態變量和外部變量,不能定義指向寄存器變量的指針。
局部變量和代碼塊
可以在代碼塊(用花括號括起來的代碼段)中聲明局部變量,變量的聲明必須在開頭位置,對函數也一樣。
#include <stdio.h> int main(void) {int count = 0;{int count = 999;} }很少用,常見用途是隔離程序的問題,并在其中創建局部變量來查找問題。
總結
- 通過在函數中聲明大多數變量,可以提高函數間的獨立性,除非有特殊的原因要求將變量聲明為外部的或靜態的,否則應將變量聲明為動態局部。
- 定義結構時且沒有聲明實例,則不會占用內存,因此,最好將常用的結構定義放在外面,一般程序員都放在頭文件中。
- 可以聲明名稱相同但類型不同的局部變量和全局變量,有些程序員在命名全局變量時,總是以g打頭(例如gCount),這樣全局和局部一目了然。
- 局部變量和全局變量重名時會覆蓋全局變量。
練習10
 下面的程序若把ctr定義為全局變量,則會無限循環
第13天:高級程序流程控制
提早結束循環
只能位于for、while和do…while中。
break語句:跳出break所在的那一層循環。
 continue語句:進入continue所在那層循環的下一次循環。
putchar();將一個字符顯示到屏幕上。
goto語句
無條件跳轉語句,goto語句和它要跳轉的語句必須位于同一函數中,但可以位于不同的代碼塊中。
 不要用goto語句。
死循環
while (1) 在循環內判斷條件,用break跳出循環。
switch語句
switch是c語言中最靈活的程序控制語句。
- case: statement; break;如果想執行一個case后結束switch語句,需要添加break,否則會一直執行到下一個break或最后一個case(或defalut)。與matlab不同。
- 如果case之后想結束程序,使用exit(0)。
- 如果要檢查同一變量的兩種以上的值,使用switch而不是if。
- 對齊case,一定要使用default。
表達式為多個值的任意一個時,都執行相同的語句塊,可以利用break。例如:
puts("\nenter number 1-5 0 for exit:");scanf("%d", &reply);switch (reply){case 0:exit(0);case 1:case 2:case 3:case 4:case 5:{printf("congradulation!!!oumeidaitou!\n");break;}default:puts("please be serious!\n");退出程序
正常執行到main()函數的右花括號時結束,但可以隨時調用庫函數exit()來終止程序,也可以指定一個或多個在程序結束時自動執行的函數。
 exit必須要包含頭文件stdlib.h,它定義了兩個符號常量,作為exit()的參數。
 #define EXIT_SUCCESS 0
 #defube EXIT_FAILURE 1
 正常終止0,錯誤終止1。
程序中執行操作系統命令
c語言提供一個system()函數,可以在一個正在運行的程序中執行操作系統命令。可以無需退出程序就能讀取磁盤的目錄列表或對磁盤進行格式化。頭文件stdlib.h。
system(command);
 獲得目錄列表:
使用system的程序示例:
... int main(void) {char input[40];while (1){puts("enter command . blank to exit: ");gets(input);if (input[0] == 0)exit(0);system(input);}return 0;}總結
goto語句(不用),switch語句,case后面加break,否則會一直執行到下一個break。死循環。exit()。system()。
- case可以檢查long,int,char型變量,char型加單引號’’。
第14天:操縱屏幕、打印機和鍵盤
流和c語言
c語言采用流的方式執行輸入和輸出操作。
- 流是一個字符序列,準確的說,數據字節序列。
- 流的優點是,輸入/輸出編程是獨立于設備的。
- 每個流都與一個文件相連,并不是磁盤文件,是程序處理的流和用于輸入/輸出的設備之間的橋梁。
流的模式有兩種:文本流和二進制流。
ANSI預定義的流:stdin:鍵盤,stdout:屏幕,stderr:屏幕。
標準輸入流會被自動打開,如gets(),scanf(),其他流(如操縱磁盤中信息的流)必須顯示的打開。
while ((ch = getchar()) != '\n' && x < MAX)buffer[x++] = ch; while ((ch = getchar()) != '\n' && x < MAX)buffer[++x] = ch;c語言的流函數
c語言包含很多處理流輸入和輸出的函數,主要分為兩個版本一個使用標準流,另一個需要指定一個流。
- printf, vprintf, puts, putchar, scanf, vscanf, gets, getchar, perror使用標準流。
- fprintf, vfp…, fputs, putc, fputc, fsc…, vfsc…, fgets, getc, fgetc 等需要指定流。
讀取屏幕輸入
c程序從鍵盤(stdin流)讀取輸入,輸入函數分為三類:字符輸入、行輸入和格式化輸入。
字符輸入
字符輸入每次從流中讀取一個字符,這些函數被調用時,返回下一個字符或EOF(到達文件末尾或出錯)。
 可以根據是否緩存與回顯對字符輸入進行分類。緩存指所有字符保存在一個臨時存儲空間中,按下Enter后再發送到stdin。回顯指的是是否將輸入的字符回顯到stdout(即屏幕)中。
getchar()
 int getchar(void);
 具備緩沖和回顯功能。
 while ( (ch = getchar() ) != '\n' )
 putchar(ch);
 上面輸入一串字符并回車后會顯示兩行字符。
getch() //不是ANSI標準的一部分
 int getch(void);
 不緩沖也不回顯。
 while( (ch = getch() ) != '\r') //;
 putchar(ch);
 上面輸入只顯示一行,回車后結束。’\r’是轉義字符,表示回車。對輸入進行緩存的函數會把回車轉換為換行,不緩存的回車仍為’\r’。
getche() //不是ANSI標準
 類似于getch(),不過會回顯。
getc()和fgetc()函數
 不會自動使用stdin,讓程序指定輸入流,主要是從磁盤文件讀取字符。
ungetc()恢復一個字符
 原型為:
 int ungetc(int ch, FILE *fp);
 ch:要恢復的字符。*fp:指定將字符歸還到哪個流。FILE *fp用于與磁盤文件相關聯的流。
 恢復成功返回ch;否則返回EOF(stdio.h中定義的符號常量,值為-1)。
行輸入
gets()
 之前說過,講一個char指針作為參數,返回一個char指針。讀取stdin中的字符,直到到達換行符(\n)或文件末尾,并將換行符替換為空字符,之后存儲到str指定的位置。原型為:
 char *gets(char *str);
 返回值是指向字符串的指針(str), 發生錯誤或讀取字符前到達文件末尾,返回空指針。
fgets()
 可以指定輸入流和最多讀取的字符數。
 char *fgets(char *str, int n, FILE *fp);
 str指定存儲位置,n指定最多輸入字符數(n-1),FILE *fp指定輸入流。
 讀取字符直到,遇到換行符、文件末尾或n-1個字符。
格式化輸入
輸入數字時,需要用格式化輸入。
scanf("%d", &x);
- 可以再兩個%d后面添加字符(%除外),%d qwer %d則兩次輸入的字符直接必須加qwer。
- 添加的空格和制表符會被忽略。
- 常用轉換說明符(%+),d:十進制整數,u:無符號十進制整數,f:浮點數,[…]:只讀取方括號內列出的字符,[^…]:只讀取方括號沒列出的字符。
- 精度限定符,改變它后面的類型說明符的含義,常用:%lld:long long 。 %llu:unsigned long long。 %lf:double。%Lf:long double。
- %%讀取%且不執行任何操作。
處理多余的字符
防止多余的輸入留在stdin中,導致問題。
 1.使用gets()。
 2.使用fflush()。
 int fflush(FILE *stream);,stream是要清洗的流。
控制屏幕輸出
同樣分為字符輸出、行輸出和格式化輸出三類。
字符輸出
putchar()、putc()、fputc()。
 int putchar(int c);將變量c中字符寫入stdout,返回寫入的字符,錯誤返回EOF。
 putc()以宏的方式實現fputs()。
 int fputc(intc, FILE *fp);如果指定的流為stdout,則fputc與putchar相同。
行輸出
puts()、fputs()。
 int puts(char *cp);
 fputs可以指定流。
格式化輸出
printf()、fprintf()。
 fprintf()可以指定輸出流,通常用于將數據輸出到磁盤文件。
 printf必須包含格式化字符。格式化可以是一個字符串的指針:
格式化字符中除轉換命令以外的字符將按原樣顯示。
轉化命令的組成部分如下:
 %[flag][field_width][.[precision]][l/ll]conversion_char
 常用轉換字符包括:d, u, o, x, c, e, f, s ,%。
-  l:指定參數為long或double。ll:指定為long long。 
-  精度指定小數位數或輸出的字符數,如果只包含小數點,則精度為0。 
-  *,用于指定字段寬度。例如: printf("%*f", n, d1); 
-  flag有四種:-、+、’ '、#。 
 -:輸出左對齊
 +:有符號數顯示符號
 ’ ':正數前加空格
 #:%-#15o %-#15x分別是顯示八進制和十六進制。
格式化字符串中可以包含轉義序列:\n等。
重新定向輸入/輸出
可以在命令提示符里輸入指令,使:
- 發送到stdout的輸出重新定向到磁盤或打印機
- 讓stdin的程序輸入來自磁盤文件而不是鍵盤。
例如:
 redirection > test.txt
 redirection >> test.txt
 后者>>為附加模式,>為覆蓋模式。
 redirection < INPUT.TXT
 redirecton < INPUT.TXT > junk.txt
因為在vscode的poweshell里輸入出錯,查了一下原來和cmd不是一個同東西。
使用fprintf()
fprintf()主要用于處理磁盤文件。也可以重新指定到別的輸出流。例如:
 fprintf(stderr, "error");
 因為stdout的輸出可被重新定向,stderr不能把重新定向,因此應保證錯誤信息總是顯示在屏幕上,所以此時使用fprintf()。
fprintf(stdprn, "xxx");
 輸出發送到打印機。
總結
- 學習了流,ANSI C有三個預定義的流,stdin,stdout,stderr。
- 讀取字符分為字符輸入,行輸入和格式化輸入,字符輸入根據是否緩沖和回顯分類
- 輸出同樣分為三類。printf中%后面可以加[flag] [field_width] [.[precision]] [l/ll]轉換說明符
- 錯誤消息應發給stderr。
發現這種總結一本書的學習方式還是比較浪費時間的,并且也沒有多么大的意義,寫完這個21天c之后我打算換一種方式,不把每一個細節都寫上來。只寫重點,關鍵的理解。
總結
以上是生活随笔為你收集整理的《21天学通C语言》总结(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 白话经典算法系列之一 冒泡排序的三种实现
- 下一篇: python中的命名空间指什么_13.P
