前文補充
#include <iostream>
using namespace std;int main()
{int a = 10;int c = 20; // 將變量c定義在switch語句之前switch(a){case 1:{cout << ".........." << endl;cout << c << endl;}break;default:cout << ".........." << endl;break;}return 0;
}
這段代碼存在一些問題,以下是代碼的分析和問題說明:
代碼分析
switch
語句的結構問題switch
語句的語法要求case
標簽必須位于switch
語句塊的最外層,而不能嵌套在其他語句(如printf
)之后。在你的代碼中,printf("..........\n");
和int c = 20;
以及printf("..........\n");
出現在case
標簽之前,這是不符合C++語法的。switch
語句的執行流程是從匹配的case
標簽開始執行,直到遇到break
或switch
語句結束。因此,case
標簽不能嵌套在其他語句中。
變量
c
的作用域問題在
switch
語句中,case
標簽后的代碼塊(用大括號{}
括起來的部分)會創建一個新的作用域。在你的代碼中,int c = 20;
定義在switch
語句塊的外面,但在case 1
中嘗試訪問c
。由于c
的作用域問題,這會導致編譯錯誤。
修改后的代碼
以下是修改后的代碼,修復了上述問題:
#include <iostream>
using namespace std;int main()
{int a = 10;int c = 20; // 將變量c定義在switch語句之前switch(a){case 1:{cout << ".........." << endl;cout << c << endl;}break;default:cout << ".........." << endl;break;}return 0;
}
修改說明
變量
c
的定義位置將
int c = 20;
定義在switch
語句之前,確保在switch
語句中可以正確訪問c
。
switch
語句的結構將
printf
語句和case
標簽的順序調整為符合C++語法的結構。case
標簽必須位于switch
語句塊的最外層。
使用
cout
代替printf
為了保持代碼風格的一致性,將
printf
替換為C++標準庫中的cout
。
輸出結果
假設a
的值為10,程序的輸出將是:
..........
如果將a
的值改為1,程序的輸出將是:
..........
20
本章重點
循環的語法理解
break
和continue
的作用goto
真的沒人用嗎void
的理解void
修飾函數和函數參數的作用void
指針的用途
1.9 do、while、for 關鍵字
三種循環各自語法 - 補充內容
while
循環
// while
條件初始化
while(條件判定){// 業務更新條件更新
}
for
循環
// for
for(條件初始化;條件判定;條件更新){// 業務代碼
}
do while
循環
條件判定是放在后面的,是直接先執行代碼塊中的代碼邏輯的!--- 至少會跑一遍
// do while
條件初始化
do{條件更新
}while(條件判定);do{}while(0);
三種循環對應的死循環寫法 - 補充內容
while(1){
}for(;;){}do{}while(1);
書中代碼,優化后
#include <stdio.h>
#include <windows.h>int main()
{while (1){char c = getchar();if ('#' == c){break;}printf("echo: %c\n", c);}system("pause");return 0;
}
在C語言中,使用 int c
而不是 char c
來接收 getchar()
的返回值是一個常見的最佳實踐,原因主要與 getchar()
函數的返回值類型以及字符和整數的表示范圍有關。下面詳細解釋為什么使用 int c
更好:
getchar()
?的返回值類型
getchar()
函數的返回值類型是 int
,而不是 char
。其原型定義為:
int getchar(void);
這意味著 getchar()
返回的是一個整數(int
類型),而不是一個字符(char
類型)。返回的整數值可以表示一個字符的ASCII碼,也可以是一個特殊的值 EOF
(表示文件結束)。
EOF
?的值
EOF
是一個特殊的值,用于表示輸入流的結束。在標準C中,EOF
的值通常定義為 -1
。由于 char
類型的范圍通常是 0 到 255(對于無符號字符)或 -128 到 127(對于有符號字符),而 EOF
的值是 -1
,這可能會導致問題。
如果使用 char c
來接收 getchar()
的返回值,可能會出現以下問題:
有符號字符問題 :如果
char
是有符號類型(這是默認情況,但具體取決于編譯器和平臺),EOF
的值-1
可以被正確存儲在char
中。但是,當char
被用作條件表達式時,可能會出現意外的行為。例如,if (c == EOF)
可能不會按預期工作,因為c
的值可能會被隱式轉換為int
,導致比較失敗。無符號字符問題 :如果
char
是無符號類型(可以通過編譯器選項或平臺設置來改變),EOF
的值-1
會被解釋為一個非常大的正數(通常是 255)。這會導致if (c == EOF)
的比較永遠失敗,因為c
的值永遠不會等于-1
。
使用?int c
?的好處
使用 int c
來接收 getchar()
的返回值可以避免上述問題。int
類型的范圍足夠大,可以正確表示所有可能的字符值以及 EOF
。這樣可以確保 if (c == EOF)
的比較總是按預期工作。
以下是修改后的代碼,使用 int c
來接收 getchar()
的返回值:
#include <stdio.h>
#include <windows.h>int main()
{int c; // 使用int來接收getchar()的返回值while (1){c = getchar();if ('#' == c){break;}printf("echo: %c\n", c);}system("pause");return 0;
}
使用 int c
而不是 char c
來接收 getchar()
的返回值,可以確保程序能夠正確處理 EOF
的值,避免因字符類型范圍問題導致的錯誤。這是C語言編程中的一個常見最佳實踐,有助于提高代碼的健壯性和可移植性。
我們的1234的數字,可以打印出來,我們也就可以發現 printf 的價值,有作用,就是將一個整型轉化成一個一個字符,顯示到顯示器當中(程序運行會默認打開三個文件:輸入輸出錯誤)
對于 ASCLL碼表,在后面,我們會談論到,這也就是機器不是只認識二進制嗎?為什么還認識abcd這樣的字符,更多詳情,后續會介紹!
補充:
與getchar:從鍵盤上獲取一個字符的操作相對應的是putchar,往標準輸出上顯示一個字符!
1.9.1?break
?&?continue
?區別
在C語言中,break
和 continue
都是用于控制循環流程的語句,但它們的作用和使用場景有很大區別。
1.?break
作用 :用于完全終止最內層的循環(
for
、while
、do-while
),跳出循環體,繼續執行循環體之后的代碼。使用場景 :當你在循環中遇到了某種條件,使得你不再需要繼續執行循環,就可以使用
break
。比如在一個查找算法中,一旦找到了目標元素,就可以用break
跳出循環,避免無意義的繼續循環。示例代碼 :
#include <stdio.h>int main() {for (int i = 0; i < 10; i++) {if (i == 5) {break; // 當i等于5時,跳出循環}printf("%d ", i);}printf("\nLoop ended.\n");return 0;
}
0 1 2 3 4
Loop ended.
因為當 i
等于 5 時,break
語句執行,循環被終止,直接跳到循環體之后的代碼。
2.?continue
作用 :用于跳過當前循環體中的剩余代碼,直接進入下一次循環迭代。也就是說,
continue
只是中斷當前這一次循環的執行,而不是整個循環。直接跳到條件判定,而不是內部代碼塊!do while 也是如此!!!也是先到while的條件判定!都是跳轉到條件判定!!!
但是對于for來說:我們有一個示例代碼:也是證明代碼:
int main()
{int i = 10;for( ; i < 10; ++i){printf("continue before\n");if(i == 5){printf("continue\n");continue;}printf("continue after\n");}return 0;
}
這就不是跳轉到1了,想想,如果跳轉到1,就會造成死循環了,所以,這個應該是跳到2,即++i!
#include <stdio.h>int main()
{int i = 0;for( ; i < 10; ){if(i == 5){printf("%d\n", i);continue;}++i;}return 0;
}
但是我們這么寫的話就是會造成死循環!
continue
?語句在?for
?循環中的行為是:跳過當前迭代中?continue
?之后的所有代碼,然后執行?for
?循環的第三部分(即增量表達式?++i
),最后再進行條件判斷開始下一次循環;而在您第二個沒有增量表達式的?for
?循環中,由于?continue
?跳過了?++i
?語句,導致?i
?的值永遠停留在 5 無法增加,從而造成了死循環。
簡單來說:
有增量表達式:
continue
?→ 執行?++i
?→ 條件判斷 → 下一次循環無增量表達式:
continue
?→ 條件判斷 → 下一次循環(i
?不變)→ 死循環
使用場景 :當你在循環中遇到某些情況,不想執行當前循環體中的剩余代碼,但仍然希望循環能夠繼續進行,就可以使用
continue
。例如在篩選數據時,跳過不符合條件的數據,繼續處理符合條件的數據。示例代碼 :
#include <stdio.h>int main() {for (int i = 0; i < 10; i++) {if (i % 2 == 0) {continue; // 當i為偶數時,跳過當前循環的剩余代碼}printf("%d ", i);}printf("\nLoop ended.\n");return 0;
}
輸出結果為:
1 3 5 7 9
Loop ended.
當 i
為偶數時,continue
語句執行,跳過了當前循環的 printf
語句,直接進入下一次循環迭代。
作用范圍 :
break
是終止整個循環,而continue
只是中斷當前這一次循環的執行。代碼執行流程 :使用
break
后,循環直接結束,執行循環體之后的代碼;使用continue
后,會跳過當前循環的剩余代碼,直接進入下一次循環迭代。適用場景 :
break
適用于你確定不需要繼續循環的情況;continue
適用于你只是不想執行當前循環的某些代碼,但仍然希望循環繼續進行的情況。
break
?的細節補充
在嵌套循環中的表現 :
break
只能終止它所在的最內層循環。如果你有嵌套循環(即一個循環體內部還有另一個循環),break
只會終止最內層的循環,不會影響外層循環。例如:
#include <stdio.h>int main() {for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (j == 1) {break; // 只終止內層循環}printf("i=%d, j=%d\n", i, j);}printf("Inner loop ended.\n");}printf("Outer loop ended.\n");return 0;
}
輸出結果為:
i=0, j=0
Inner loop ended.
i=1, j=0
Inner loop ended.
i=2, j=0
Inner loop ended.
Outer loop ended.
可以看到,break
只終止了內層循環,外層循環仍然繼續執行。
在
switch
語句中的使用 :break
也可以用在switch
語句中,用于防止代碼的“穿透”。在switch
語句中,如果沒有break
,程序會從匹配的case
開始執行,直到遇到break
或者switch
語句結束。例如:
#include <stdio.h>int main() {int num = 2;switch (num) {case 1:printf("One\n");case 2:printf("Two\n");break;case 3:printf("Three\n");break;default:printf("Default\n");}return 0;
}
輸出結果為:
Two
如果沒有在 case 2
后面加 break
,程序會繼續執行 case 3
和 default
,這就是所謂的“穿透”。
continue
?的細節補充
對循環控制變量的影響 :
continue
語句不會影響循環控制變量的更新。例如在for
循環中,continue
之后循環控制變量仍然會按照正常的邏輯更新。例如:
#include <stdio.h>int main() {for (int i = 0; i < 5; i++) {if (i == 2) {continue; // 跳過當前循環的剩余代碼}printf("i = %d\n", i);}return 0;
}
輸出結果為:
i = 0
i = 1
i = 3
i = 4
可以看到,i
仍然按照正常的邏輯從 0 增加到 4,continue
只是跳過了 i == 2
時的 printf
語句。
在嵌套循環中的表現 :和
break
一樣,continue
也只影響它所在的最內層循環。例如:
#include <stdio.h>int main() {for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (j == 1) {continue; // 只影響內層循環}printf("i=%d, j=%d\n", i, j);}}return 0;
}
輸出結果為:
i=0, j=0
i=0, j=2
i=1, j=0
i=1, j=2
i=2, j=0
i=2, j=2
可以看到,當 j == 1
時,continue
語句跳過了內層循環中 j == 1
的那次迭代,但外層循環仍然正常進行。
3. 其他細節
可讀性 :在使用
break
和continue
時,要注意代碼的可讀性。過度使用這些語句可能會使代碼邏輯變得復雜,難以理解。例如,如果一個循環中有多個break
或continue
,可能會讓人難以跟蹤程序的執行流程。在一些情況下,可以通過調整循環條件或者使用標志變量來避免使用break
和continue
,使代碼更加清晰。性能影響 :在大多數情況下,
break
和continue
對性能的影響可以忽略不計。它們只是改變了程序的控制流,并沒有進行復雜的計算。但是,在一些極端情況下(例如在非常大的循環中頻繁使用break
或continue
),可能會對性能產生一定的影響。不過,這種影響通常比代碼的可讀性和邏輯正確性要次要得多。與其他控制語句的配合 :
break
和continue
可以與其他控制語句(如if
、else
、switch
等)配合使用,以實現復雜的控制邏輯。在使用時,要注意它們的優先級和作用范圍,確保程序的邏輯符合預期。例如,在一個if
語句中使用break
或continue
,要清楚它們會終止或跳過的是哪個循環。
總之,break
和 continue
是C語言中非常有用的控制語句,但要根據具體的情況合理使用,避免濫用導致代碼難以理解和維護。
1.9.2 循環語句的注意點
推薦書中的內容
[規則1 - 36] 思想推薦,但是不強制。另外書中代碼嚴重不推薦,外小內大,一般效率是會高一些,但是差別不會特別大,實際測試的時候,出現效率現象出現反直覺現象,也不要意外。
[建議1 - 37] 推薦,給兩個理由:循環次數明確,便于進行十數計算
[規則1 - 38] 推薦,代碼量問題要結合場景
[規則1 - 39] 部分推薦,代碼量問題要結合場景
[規則1 - 40] 推薦,實際工作中,也基本很少遇到
[規則1 - 41] 推薦
1.10?goto
?關鍵字
基本使用
// 使用goto模擬實現循環
#include <stdio.h>
#include <windows.h>int main()
{int i = 0;START:printf("[\xad]goto running .... \n", i);Sleep(1000);++i;if (i < 10){goto START;}printf("goto end ....\n");system("pause");return 0;
}
可以根據goto關鍵字,直接跳轉到goto后面所指定的標簽所對應的位置處,也就是說如果START:放在后面,goto到標簽的中間的部分就不會執行了!其實我們想要怎么跳轉就怎么跳轉!
goto只能在本代碼塊內進行使用,函數的都不能跨越,文件間就更不用說了!
有什么問題
[規則1 - 42] 推薦
我的建議
很多公司確實禁止使用goto
,不過,這個問題我們還是靈活對待,goto
在解決很多問題是有有效的。我們可以認為goto
引用場景較少,一般不使用,但是必須得知道goto
,需要的時候,也必須會用
有人用嗎
Linux內核源代碼中充滿了大量的goto
,只能說我們目前,或者很多公司的業務邏輯不是那么復雜
1.11?void
?關鍵字
1.11.1?void a
// 問是否可以定義變量
#include <stdio.h>
#include <windows.h>int main()
{void a;system("pause");return 0;
}
為何 void
不能定義變量
定義變量的本質:開辟空間
而void
作為空類型,理論上是不應該開辟空間的。即使開了空間,也僅僅作為一個占位符看待
所以,既然無法開辟空間,那么也就無法作為正常變量使用,既然無法使用,編譯器干脆就不讓他定義變量了。
- 在vs2013中,
sizeof(void)=1
(但編譯器依舊理解成,無法定義變量) - 在Linux中,
sizeof(void)=1
(但編譯器依舊理解成,無法定義變量)
1.11.2 void修飾函數返回值和參數
場景1:void作為函數返回值
// void修飾函數返回值和參數
#include <stdio.h>
#include <windows.h>void show()
{printf("no return value!\n");
}
int main()
{show();system("pause");return 0;
}
如果自定義函數,或者庫函數不需要返回值,那么就可以寫成void
那么問題來了,可以不寫嗎?不可以,自定義函數的默認返回值是int!!!
所以,沒有返回值,如果不是void,會讓閱讀你代碼的人產生誤解:他是不是忘了寫,還是想默認int?
結論:void作為函數返回值,代表不需要,這里是一個“占位符”的概念,是告知編譯器和給閱讀源代碼的工程師看的。
【規則1 - 43】推薦
場景2:void 作為函數參數
// void 作為函數參數
// 如果一個函數沒有參數,我們可以不寫,如test10
#include <stdio.h>
#include <windows.h>int test1() // 函數默認不需要參數
{return 1;
}
int test2(void) // 明確函數不需要參數
{return 1;
}
int main()
{printf("%d\n", test1(10)); // 依舊傳入參數,編譯器不會告警或者報錯printf("%d\n", test2(10)); // 依舊傳入參數,編譯器會告警(vs)或者報錯(gcc)system("pause");return 0;
}
結論:如果一個函數沒有參數,將參數列表設置成void,是一個不錯的習慣,因為可以將錯誤明確提前發現
另外,閱讀你代碼的人,也一眼看出,不需要參數。相當于“自解釋”。
閱讀書中內容,【規則1-44】推薦
題外話,盡管如此,如果這點你不習慣,也不勉強。
1.11.3 void指針
void不能定義變量,那么void*呢?
#include <stdio.h>#include <windows.h>int main()
{void *p = NULL; // 可以system("pause");return 0;
}
為什么void可以呢?因為void是指針,是指針,空間大小就能明確出來
場景:void* 能夠接受任意指針類型
#include <stdio.h>
#include <windows.h>int main()
{void *p = NULL;int *x = NULL;double *y = NULL;p = x; // 同上p = y; // 同上system("pause");return 0;
}
反過來,在vs/gcc中也沒有報錯。書中編譯器很老了,我們嚴重不推薦
結論:但是我們依舊認為,void*的作用是用來接受任意指針類型的。這塊在后面如果想設計出通用接口,很有用
比如:
void *memset ( void * ptr, int value, size_t num );
void * 定義的指針變量可以進行運算操作嗎
【規則1 - 45】現場驗證,各種標準我們了解一下
//在vs2013中
#include <stdio.h>
#include <windows.h>int main()
{void *p = NULL;p++; // 報錯p += 1; // 報錯system("pause");return 0;
}
//在gcc4.8.5中
#include <stdio.h>
#include <stdio.h>int main()
{void *p = NULL; // NULL在數值層面,就是0p++; // 能通過printf("%d\n", p); // 輸出1p += 1; // 能通過printf("%d\n", p); // 輸出2return 0;
}
為什么在不同的平臺下,編譯器會表現出不同的現象呢?
根本原因是因為使用的c標準版本的問題。具體閱讀書。
void * 用來設計通用接口
【規則1 - 46】推薦
1.11.4 void不能代表一個真實的變量
已經講過,此處略過