C語言筆記2
變數定義並且賦值
extern int d = 3, f = 5; // d 和 f 的宣告與初始化
int d = 3, f = 5; // 定義並初始化 d 和 f
byte z = 22; // 定義並初始化 z
char x = 'x'; // 變數 x 的值為 'x'
變數宣告
extern int i; //宣告,不是定義,呼叫外部變數
int i; //宣告,也是定義
變數:全域性變數、區域性變數、靜態全域性變數、靜態區域性變數
靜態的含義:不會隨著函式的呼叫和退出而發生變化。
1、從作用域看:
1>全域性變數具有全域性作用域。全域性變數只需在一個原始檔中定義,就可以作用於所有的原始檔。當然,其他不包含全域性變數定義的原始檔需要用extern 關鍵字再次宣告這個全域性變數。
2>靜態區域性變數具有區域性作用域,它只被初始化一次,自從第一次被初始化直到程式執行結束都一直存在,它和全域性變數的區別在於全域性變數對所有的函式都是可見的,而靜態區域性變數只對定義自己的函式體始終可見。
3>區域性變數也只有區域性作用域,它是自動物件(auto),它在程式執行期間不是一直存在,而是隻在函式執行期間存在,函式的一次呼叫執行結束後,變數被撤銷,其所佔用的記憶體也被收回。
4>靜態全域性變數也具有全域性作用域,它與全域性變數的區別在於如果程式包含多個檔案的話,它作用於定義它的檔案裡,不能作用到其它檔案裡,即被static關鍵字修飾過的變數具有檔案作用域。這樣即使兩個不同的原始檔都定義了相同名字的靜態全域性變數,它們也是不同的變數。
2.從分配記憶體空間看:
1>全域性變數,靜態區域性變數,靜態全域性變數都在靜態儲存區分配空間,而區域性變數在棧裡分配空間
2>全域性變數本身就是靜態儲存方式, 靜態全域性變數當然也是靜態儲存方式。這兩者在儲存方式上並無不同。這兩者的區別雖在於非靜態全域性變數的作用域是整個源程式,當一個源程式由多個原始檔組成時,非靜態的全域性變數在各個原始檔中都是有效的。而靜態全域性變數則限制了其作用域,即只在定義該變數的原始檔內有效,在同一源程式的其它原始檔中不能使用它。由於靜態全域性變數的作用域侷限於一個原始檔內,只能為該原始檔內的函式公用,因此可以避免在其它原始檔中引起錯誤。
1)靜態變數會被放在程式的靜態資料儲存區(全域性可見)中,這樣可以在下一次呼叫的時候還可以保持原來的賦值。這一點是它與堆疊變數和堆變數的區別。
2)變數用static告知編譯器,自己僅僅在變數的作用範圍內可見。這一點是它與全域性變數的區別。
摘自 https://blog.csdn.net/yl970302/article/details/89104592
例子:兩個檔案。
1 //test.c
2
3 #include <stdio.h>
4 extern int global_var;
5
6 void test_global_var()
7 {
8 global_var++;
9 printf("global_var = %d\n", global_var);
10 }
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 void test_static_local_variable();
6
7 int global_var = 1; //普通全域性變數,隨著整個程式的結束而消亡。可以在整個程式方法問
8 //可以在其他.c檔案中訪問
9 static int static_global_var = 1; //靜態全域性變數,限定只能在本檔案內部訪問
10
11 int main(int argc, char** argv)
12 {
13 int a = 3; //普通區域性變數,只能在main函式內部使用,隨著main函式的結束而消亡
14
15 for (int i = 0; i < a; i++) //複合語句中定義,隨著for迴圈的結束而消亡
16 {
17 printf("i = %d\n", i);
18 }
19
20 test_static_local_variable(); //local_var = 1
21 test_static_local_variable(); //local_var = 2
22 test_static_local_variable(); //local_var = 3
23
24 printf("global_var = %d\n", global_var); //global_var = 1
25 test_global_var(); //global_var = 2
26 test_global_var(); //global_var = 3
27
28 system("pause");
29 return 0;
30 }
31
32 void test_static_local_variable()
33 {
34 static int local_var = 0; //靜態區域性變數,只能在函式test_static_local_variable內部使用
35 //生命週期為整個程式,隨著程式的結束而消亡
36 local_var++;
37 printf("local_var = %d\n", local_var);
38 }
void 函式:無型別函式
·函式返回值
下面給出void關鍵字的使用規則:
規則一
如果函式沒有返回值,那麼應宣告為void型別
在C語言中,凡不加返回值型別限定的函式,就會被編譯器作為返回整型值處理。但是許多程式設計師卻誤以為其為void型別。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( “2 + 3 = %d”, add ( 2, 3) );
}
程式執行的結果為輸出:
2 + 3 = 5
這說明不加返回值說明的函式的確為int函式。
·函式引數
·空指標宣告
void * p1;
int * p2;
p1 = p2;
而void *則不同,任何型別的指標都可以直接賦值給它,無需進行強制型別轉換:
規則二
如果函式無引數,那麼應宣告其引數為void。
在C++語言中宣告一個這樣的函式:
int function(void)
{
return1;
}
則進行下面的呼叫是不合法的:
function(2);
因為在C++中,函式引數為void的意思是這個函式不接受任何引數。
在Turbo C 2.0中編譯:
#include “stdio.h”
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
編譯正確且輸出1,這說明,在C語言中,可以給無引數的函式傳送任意型別的引數,但是在C++編譯器中編譯同樣的程式碼則會出錯。在C++中,不能向無引數的函式傳送任何引數,出錯提示“‘fun’ : function does not take 1 parameters”。
所以,無論在C還是C++中,若函式不接受任何引數,一定要指明引數為void。
規則三
小心使用void指標型別
按照ANSI(American National Standards Institute)標準,不能對void指標進行演算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
//ANSI標準之所以這樣認定,是因為它堅持:進行演算法操作的指標必須是確定知道其指向資料型別大小的。
//例如:
int *pint;
pint++; //ANSI:正確
pint++的結果是使其增大sizeof(int)。( 在VC6.0上測試是sizeof(int)的倍數)
但是大名鼎鼎的GNU(GNU’s Not Unix的縮寫)則不這麼認定,它指定void *的演算法操作與char *一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++; //GNU:正確
pvoid += 1; //GNU:正確
pvoid++的執行結果是其增大了1。( 在VC6.0上測試是sizeof(int)的倍數)
在實際的程式設計中,為迎合ANSI標準,並提高程式的可移植性,我們可以這樣編寫實現同樣功能的程式碼:
void * pvoid;
(char *)pvoid++; //ANSI:正確;GNU:正確
(char )pvoid += 1; //ANSI:錯誤;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支援。但是我們在真實設計時,還是應該儘可能地迎合ANSI標準。
規則四
如果函式的引數可以是任意型別指標,那麼應宣告其引數為void *
典型的如記憶體操作函式memcpy和memset的函式原型分別為:
void * memcpy(void dest, const void src,size_tlen);
void * memset ( void * buffer, int c, size_t num );
這樣,任何型別的指標都可以傳入memcpy和memset中,這也真實地體現了記憶體操作函式的意義,因為它操作的物件僅僅是一片記憶體,而不論這片記憶體是什麼型別。如果memcpy和memset的引數型別不是void ,而是char ,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個“純粹的,脫離低階趣味的”函式!
下面的程式碼執行正確:
//示例:memset接受任意型別指標
int intarray[100];
memset ( intarray, 0, 100sizeof(int) ); //將intarray清0
//示例:memcpy接受任意型別指標
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100sizeof(int) ); //將intarray2拷貝給intarray1
有趣的是,memcpy和memset函式返回的也是void 型別,標準庫函式的編寫者是多麼地富有學問啊!
規則五
void不能代表一個真實的變數
下面程式碼都企圖讓void代表一個真實的變數,因此都是錯誤的程式碼:
void a; //錯誤
function(void a); //錯誤
void體現了一種抽象,這個世界上的變數都是“有型別”。
void的出現只是為了一種抽象的需要,如果你正確地理解了面向物件中“抽象基類”的概念,也很容易理解void資料型別。正如不能給抽象基類定義一個例項,我們也不能定義一個void(讓我們類比的稱void為“抽象資料型別”)變數。
總結
小小的void蘊藏著很豐富的設計哲學,作為一名程式設計人員,對問題進行深一個層次的思考必然使我們受益匪淺。
不論什麼型別的指標(void, char, int, float…)在Debug模式編譯時,預設初始值都是0xCCCCCCCC(是由編譯器決定的,主要目的是為了新增除錯的輔助程式碼用於及時發現錯誤),在Release模式編譯,則是不確定的值。
#include
#include
//#include
using namespace std;
void main()
{
void *p1;
int a = 10;
int *p2 = &a;
cout << p1 << endl;
cout << (int)*p2 << endl;
p1 = p2;
cout << (int)p1 << endl;//!!! 用空型別操作輸出值!
cout << (int)p2 << endl;
}
/ 輸出:
0xCCCCCCCC
10
10
10
*/
在宣告同時賦值NULL,在delete後立即設定為NULL。
在debug版本下指標預設初始值為0xCCCCCCCC,在Release版本下初始值為0x0000000A,(在我電腦上VC6.0)。對於指標如果暫時沒有合適的初始化值,就應該把它置為NULL(0)。
對於好的程式設計習慣來說,declare一個指標,則初始化為NULL,如果是類成員 則在建構函式中initialize,當對指標使用delete時候,則置它為NULL。
0xCCCCCCCC只是在debug狀態下VC生成的未定義過的指標值,用來提示這個指標是未被初始化的,在release狀態下不會等於這個值(除非巧合)。對於指標如果暫時沒有合適的初始化值,就應該把它置為NULL(0)。
範例 https://zhuanlan.zhihu.com/p/259898135
字元常量
字元常量是括在單引號中,例如,‘x’ 可以儲存在 char 型別的簡單變數中。
字元常量可以是一個普通的字元(例如 ‘x’)、一個轉義序列(例如 ‘\t’),或一個通用的字元(例如 ‘\u02C0’)。
在 C 中,有一些特定的字元,當它們前面有反斜槓時,它們就具有特殊的含義,被用來表示如換行符(\n)或製表符(\t)等。下表列出了一些這樣的轉義序列碼:
轉義序列 含義
\ \ 字元
’ ’ 字元
" " 字元
? ? 字元
\a 警報鈴聲
\b 退格鍵
\f 換頁符
\n 換行符
\r 回車
\t 水平製表符
\v 垂直製表符
\ooo 一到三位的八進位制數
\xhh . . . 一個或多個數字的十六進位制數