淺談C的應用與常見error
我下面所寫的都是用“.c”後綴的。“.c”後綴是c源文件的後綴,“.cpp”後綴是c++源文件的後綴。c++繼承了c語言的一些特性,所以有些bug在“.cpp”裡是可以通過的。
1、scanf()你真的了解了麼?;
scanf()是有返回值的,返回值類型是int,這個你知道麼?它返回了成功讀入輸入信息的個數。
那麼我們可以利用它來做什麼呢?
非零即真,這句話你一定聽說過,而且不止一次。那麼我們看一下下面的代碼。
int a;
while (scanf("%d", &a) == 0)
{
printf("請輸入合法字符:");
}
這樣寫代碼是不是好一點,再輸入的同時判斷了輸入的是否正確。但這會使你進入一個死循環。為什麼會這樣呢?
我們在輸入時,我們的鍵值需要一塊緩沖區在存放鍵值,如果輸入的鍵值不被scanf()接受,那麼它會一直在緩沖區中,那麼再仔細看一看上面的代碼,你是不是明白了呢。
解決這個問題並不難,只要將輸入的鍵值接受,他就不會在緩沖區中影響你的程序了。看看下面的代碼,有沒有豁然開朗,哇,scanf()還可以這麼使用!
int a;
while (scanf("%d", &a) == 0)
{
getchar();
printf("請輸入合法字符:");
}
2、你還在糾結while(),for();
初學者可能會糾結什麼場合用while(),什麼場合用for()。
在我看來,這根本不存不值得我去糾結,我們應該想的是使用哪個可以使程序更簡單,別人更容易看懂。
while(),for()的本質都是循環,只是形式不一樣而已。
int i = 0; int i;
while (i<10) for (i=0; i<10; i++)
{ {
i++;
} }
上面的代碼當然是for()更簡明一寫,但1、中的例子卻是使用while()。for()比while()在語句要多一些,這是你需要的麼?
根據每個人的代碼風格不同,不能統一要求使用for()或者while()。建議初學者可以兩個都試一下,感覺他們之間的不同,寫出漂亮的代碼。
3、switch()你會使用麼?
switch(n)
{
case 1:
break;
case 2:
break;
default:
}
你一直這麼使用麼?
我們都知道case 後面不加break,會一直執行下面的代碼,直到代碼全不執行完或者遇到break。
利用這個特性,我們是不是可以做一些其他的事,比如加法,一年有十二個月,我想算出4月6號是今年的第幾天,你想到怎麼使用了麼?我們看一下下面的代碼。
switch (month-1)
{
case 11: sumMonth += 30;
case 10: sumMonth += 31;
case 9: sumMonth += 30;
case 8: sumMonth += 31;
case 7: sumMonth += 31;
case 6: sumMonth += 30;
case 5: sumMonth += 31;
case 4: sumMonth += 30;
case 3: sumMonth += 31;
case 2:
if ((i%4==0 && i%100!=0) || i%400==0)
{
sumMonth += 29;
}
else
{
sumMonth += 28;
}
case 1: sumMonth += 31;
}
你看懂了麼?只是提供一個思路,將我們思維打開,你會看見另一片天空。
你在case中定義過變量麼?
case中為什麼不能定義變量呢?
int a;
switch (a)
{
case 1:
int b;
break;
case 2:
b = 0;
break;
}
你這麼任性,編譯器怎麼辦!
那我們為什麼不規定case 1定義的變量只能在case 1中使用呢?在6、變量作用域中你將會找到答案。
當然,我們不應該只局限於switch(),if() else也一樣可以完成多重選擇的任務,不過switch()內置3-999個標簽,使用switch()的程序運行速度可能稍快一些,代碼也更簡潔。
4、if()的使用
我們都知道if()用於判斷。那麼當你判斷兩個字符相等時,你會怎麼寫?
int a;
if (a == 5)
如果是我,我會寫成
int a;
if (5 == a)
功能是一樣的,何必這麼麻煩?我們在做一個項目時,不可能十行代碼調試半個小時,當我們思維在鍵盤上飛舞時,我才體會到一個程序員的快樂。在我們正在體驗這種快樂時,手誤在所難免,如果寫成if(a=5),這個錯誤夠你找兩天的。但是if(5=a),編譯器就可以幫你找到。
當你想判斷兩個字符串是否相等時,你會不會這樣寫?
char c_c1[10] = "acdefg";
char c_c2[10] = "qwerty";
if (c_c1 == c_c2)或者if (c_c1[10] == c_c2[10])
總之都是不對的。字符串,數組,指針後面還會有所涉及,這裡不做詳談。
比較兩個字符串我們用到了C函數庫中提供的strcmp()函數,它包含在<string.h>頭文件中。strcmp()返回了一個int類型的值,-1,0,1。0代表兩個字符串相等,-1,1是由於字符之間的比較(ASCII)決定的。下面我看一個例子。
char c_ch[10] = "qwertyui" //最多可以存放9個字符,最後一位是”\0“
char *p_ch = "adfgf"
strcmp(c_ch, p_ch); //strcmp()中的兩個參數是兩個字符串的地址
strcmp("qwertyui", "adfgf"); //這樣也是可以的,字符串存放的也是首字母的地址,後面會提到。
5、你知道頭文件的作用麼?
我們寫代碼都習慣把#include <stdio.h>最先寫上,但你知道它的作用麼?
#include :文件包含
當預處理器發現#include 指令後,就會尋找 <>中的文件名並把這個文件包含到你寫的程序代碼,替換源文件(就是你寫的文件)中的#include。雖然你只寫了一行代碼,但是編譯器卻給你添加了很多代碼。
<stido.h>包含了輸入輸出函數,所以在程序的調試階段,他是不可少的。
#include "gaozy" 這類頭文件你見過麼?它與#include <stdio.h>又有哪些區別呢?<>包含的頭文件,編譯器會在系統目錄中搜索。“”包含的頭文件,編譯器會先在當前工作目錄中搜索,如果沒有,再去系統目錄中搜索。也可以簡單的理解成C函數庫裡的函數在<>包含的頭文件裡,而我們自己寫的函數則放在“”文件中。
6、變量作用域
int i;
while (1)
{
int i = 0;
printf("%d", i);
}
這樣會報錯麼?當然不會,因為兩個int i;並不在同一個作用域。
那麼什麼是作用域,怎麼分辨作用域呢?
我們通常把一個{}內的代碼看成在一個作用域。這也是為什麼case裡不可以定義變量的原因。
只有{}才可以分辨作用域麼?
當然不是
while (1)
int i;
printf("hellow world!");
int i;自己單獨在一個作用域中,printf()只會執行一次,當然這是一個死循環,不會執行到printf()。現在你有點略懂了麼?跟while()相同的還有for(),if()
還有個問題你想過麼?第一次循環定義一個 i,第二次循環又定義一個 i,這不重復定義了麼?
當然不是,作用域內的變量是有生命周期的。也就是說,當第一次循環結束,你所定義的 i 的內存空間已經被釋放。
7、傷腦筋的bug
你寫過這樣的程序麼?
int i;
while (1)
{
int i = 0;
printf("%d", i);
int j;
}
編譯器是這樣報錯的::\c\淺談c.c(49) : error C2143: syntax error : missing ';' before 'type'
我英文不好,不理解這句話是什麼意思,但是這段代碼裡根本不存在缺少;的情況。
我將代碼粘到vs2010中,它是可以運行的。我一度懷疑是編譯器出錯了。但事實上編譯器是很少出錯的,糾結了很久,我突然想起來,c語言中規定,變量是必須在作用域的開始定義。vs210中,我用文件是後綴是.cpp的,就是是說c++允許在其他函數之後定義變量,而c對此則表示不支持。
你寫的代碼有沒有之前運行正常,你只是修改了一點,語法上別沒有出錯。但編譯器卻報錯了呢?
LINK : fatal error LNK1168: cannot open Debug/SMIS.exe for writing
之前遇見這個問題也是糾結很久,為什麼一會兒可以編譯,一會兒又讓我等待呢?
這也不是編譯器出錯了。而是你執行了程序,沒有關閉,對代碼進行了修改,又要執行程序。
編譯器如果會說話:“小子,你拿我開心呢吧,還能不能一起玩耍了。”
當你malloc時,要記得free啊,還有malloc之後跟上一個判斷是有必要的。
p_head = (P_STUDENT)malloc(sizeof(STUDENT));
if (NULL == p_head)
{
printf("動態內存分配失敗,程序結束!");
exit(-1);
}
如果沒有if()判斷的話,在內存分配失敗時,運行程序會出錯,而且很難找到錯誤原因。
8、字符串,數組,指針
談到字符串、數組、指針,那麼我們就不得不談內存了。我們可以把內存理解成一排很長很長的房子,它是一個線性結構。房子裡住著不同的人家,每個房子有不同編號。
人家對應的是計算機裡定義的變量,房子編號對應的是變量所在地址。所以,我們每定義一個變量,都會占用一套房子,當房子被全部占用時,你的電腦就崩潰了。當然,我們電腦的內存還是很大的。下面來看看字符串、數組、指針之間的關系。
字符串以“/0”結束,所以字符串長度要在實際長度上+1
數組名就是數組首元素的地址
char ch[10] = "asdfg"; //ch[10]是定長數組
char ch[] = "asdfg"; //ch[]沒有規定長度,但是在定義時必須初始化
char *p_ch = "asdfg"; //指針指向字符串的首地址
這三種定義字符串的方法差不多。ch和p_ch都是地址。
數組定義之後不可改變,指針還可以指向其他地址,但要注意內存洩露。
9、指針與地址
有些書上說指針就是地址,其實這種說法並不准確。
那麼指針是什麼,地址又是什麼?
首先地址就是地址,物理內存的編號。
指針是一個變量,這個變量存放的內容是內存的物理地址。就像int i; i 是一個變量,存放一個 i 值。指針變量也存放一個指針值,這個值是一塊物理內存的地址。
其實指針是地址還是變量並不影響我們對指針的使用。
10、你用過宏麼?
#define MAX 60
就是MAX = 60;的意思,之後的代碼中MAX都等於60。宏使我們的程序維護更簡單,也更容易理解。
11、malloc與free
有人說new跟delete更強大,但我並這麼認為,new和delete是c++中的運算符,提供了對對象的操作。而malloc和free是c語言中用來進行動態內存分配的,不具備可比性。
C語言是面向過程的程序設計,根本沒有對象的概念。