c語言基礎總結

零:
	聲明、定義、初始化:

	聲明:前面加上 extern, 表示變量或函數在其他文件中定義了。
	extern int a; 	// 前面加上了extern
	extern int func(int a, double b);	
	// extern 可置於變量和函數前面,表示變量或者函數的定義是在其他文件中,
	// 提示編譯器遇到這個變量或者函數的時候,在其他文件/模塊中尋找它的定義
	// 如果在聲明函數的時候,extern 是可以被省略

	定義:只要前面沒有 extern 的,就屬於定義,定義有存儲空間,可以獲取地址,但是地址內沒有合法的值
	int a;
	int a[5];

	賦值:對已定義的變量進行賦值操作
	a = 10;

	初始化:定義變量的同時進行賦值操作,地址和值都可以被獲取。
	int a = 3;
	int a[5] = { 0 };


一、字符串函數

int strlen(const char* str);
	int a = strlen(str):返回不包括字符串結尾'\0'的字符串長度,即有效字符長度

int strnlen(const char* str, int maxlen);
	int a = strlen(str, len) : 返回len個char長度以內,不包括字符串結尾'\0'的字符串長度。

int strcat(char* str1, const char* str2);
	strcat(str1, str2): 將參數str2 追加到 str1裏;

int strncat(char* str1, const char* str2, int len);
	strncat(str1, str2, len):字符串有限追加len個char字節長度的字符串

...(此處省略很多字);

char* strtok(char* str, const char* delim);
	strtok(str, delim): 分解字符串str爲一組字符串子串,str就是要被分隔的字符串,delim分隔符
						strtok()函數每次會把分隔符所在的位置置爲'\0',調用前和調用後的str已經不一樣了;
	/*
	char s[20] = "abcsabscccs";

	strtok(s, "s");
	printf("%s\n", s);

	strtok(NULL, "s");
	printf("%s\n", s);

	strtok(NULL, "s");
	printf("%s\n", s);
	*/



二、函數參數的 進棧順序 和 參數計算順序

1. 大端對齊 和 小端對齊
	unsigned int a = 0x12345678;

	大端:就是把數值的高位數值放在內存的低位字節,把數值的低位數值放在內存的高位字節

		地址:	0x00c1		0x00c2		0x00c3		0x00c4
	 
		數值:	  0x12        0x34        0x56        0x78

	小端:就是把數值的高位數值放在內存的高位字節,把數值的低位數值放在內存的低位字節

		地址:	0x00c1		0x00c2		0x00c3		0x00c4
	 
		數值:	  0x78        0x56        0x34        0x12


	大端:IBM、Sun的服務器是大端,初代的蘋果電腦PowerMac也是大端。
	小端:x86架構的CPU是小端,ARM(英國)架構大多也是小端。


2.	函數參數的進棧順序

#include <stdio.h>
void func(int a, int b, int c)
{
	printf("a = %d : [%p]\n", a, &a);
	printf("b = %d : [%p]\n", b, &b);
	printf("c = %d : [%p]\n", c, &c);
}

int main(void)
{
	func(100,200,300);
	return 0;
}

//Win32下的編譯結果
a = 100 : [0x5fcad0] +4		
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0]

//Ubuntu Linux下編譯結果
a = 100 : [0x5fcad0] +4
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0] 

C程序在執行時:"先入棧的數據在棧底,爲高地址;後入棧的數據在棧頂,爲低地址。"
				"先進後出,後進先出"

上面的例子可以看出:函數參數的進棧順序是"從右往左"。

// Mac OS X下的編譯結果
a = 100 : [0x5fcae0] -4
b = 200 : [0x5fcad8] -4
c = 300 : [0x5fcad0] 

Mac OS X 使用的編譯器是 LLVM Clang,這個編譯器會優化一些代碼,
導致函數參數的進棧順序是"從左往右"。


3.	函數參數計算順序
#include <stdio.h>
int main(void)
{
	int a = 10, b = 20, c = 30;
	printf("%d, %d, %d\n", a + b + c, (b = b * 2), (c = c * 2));
	return 0;
}

//Win32 MSVC 下編譯結果是
110, 40, 60

//Ubuntu Linux GUN GCC下編譯結果是
110, 40, 60

//Mac OS X LLVM Clang下編譯結果
60, 40, 60

由此可知:
在MSVC和GCC編譯器下,參數的計算順序是"從右往左",和其參數進棧順序相同;
在LLVM Clang編譯器下,參數的計算順序是"從左往右",和其參數進棧順序相同。

所以,當一個函數參數有多個的時候,C/C++語言設計者沒有規定函數參數的進棧順序和計算順序是哪一種,
這些是有編譯器廠商自己規定的。


4.	函數的默認參數
#include <stdio.h>
void func(int a, int b, int c = 300)
{
	printf("a = %d : [%p]\n", a, &a);
	printf("b = %d : [%p]\n", b, &b);
	printf("c = %d : [%p]\n", c, &c);
}

int main(void)
{
	func(100,200);
	return 0;
}

上面的寫法,在Ubuntu Linux和MSVC下是不允許,會報語法錯誤,實參數量要匹配型參數量,
也不允許函數參數在參數列表內賦值。
但是在LLVM Clang下是允許的,這是由於編譯器支持這麼幹。

C編譯器有很多,除了GCC、MSVC、Clang外,
還有一種編譯叫ICC,這個編譯器是Intel公司做的,會優化代碼在Intel CPU上的效率。
還有Turbo C,是美國borland公司的產品,現在估計已經被淘汰了。TC

重點:
由於C語言編譯器廠商規範不統一,所以我們在寫代碼的時候,不要寫類似的"UB語句"。
"Undefined Behavior" 代碼行爲未定義:
如果你的程序違反了C標準的某些規則,那麼具體會發生什麼,C語言沒有定義,
也就是說會得到一些奇怪的結果,都是有可能的。比如說整數溢出就是這種情況。

"Unspeakable Behavior"方案未定義:
C標準可能給你的代碼提供了好幾種可選方案,但是具體用哪一種,C標準沒有定義。
比如說上面的例子,函數參數的計算順序和進棧順序不一致,而且任何計算方式都行。


寫程序的態度要嚴謹,我們的代碼是給人看的,所以爲了將來的代碼維護和升級,儘量不要用表達過於籠統的語句。

// 我的代碼,只有上帝和我能懂。
// 我是你之前的維護人員,經過測試這裏的代碼最好不要亂動。

#include <stdio.h>
int main(void)
{
	int i = 0;
	int a = i + ++i;
	printf("%d\n", a);
	return 0;
}
//反面教材,這種情況,最後的值會因爲編譯器不同而不同。


三、一級指針
1.	指針的基本用法:
		"32位系統下是4個字節,64位系統下是8個字節"

	1) 在配合數據類型定義變量的時候使用*號,是代表這個變量的類型是指針類型
		int a = 10;		// 定義一個整型變量,並賦值10
		int *p = &a;	// 定義一個整型指針變量,指向 a 的地址

	2) 在配合變量進行操作的時候使用*號,是取值運算符,意思是可以取出這塊內存空間的值、
	
		printf("%d\n", *p);			//取出p指向的內存空間的值
		printf("%d\n", *p += 1);	//取出p指向的內存空間的值。並自增1
		printf("%d\n", a);			// a的值也會被修改,是 11


2.	指針的幾種特殊定義方式:
	
	1) int *const p;
		指針常量:p是 int*類型,const 修飾的是p,所以p是常量。則p指向的地址不可修改,也就是說不能再指向其他地方了。
					但是,可以修改他所指向的地址裏的值。
		舉例:
		int a = 10;
		int b = 20;
		int *const p = &a;
		p = &b;			//錯誤
		*p = 100;		//允許


	2) const int *p;
	   int const *p;
	   	常量指針:p是 int*類型,const 修飾的是 *p,所以*p是常量。則p是一個指向常量的指針,也就是說他也可以指向其他地方,
	   				但是,他所指向的地址裏的值不能修改。

	   	舉例:
	   	int a = 10;
	   	int b = 20;
	   	const int *p = &a;
	   	p = &b;			// 允許
	   	*p = 100;		// 錯誤


	3) const int *const p;
		常量指針常量:p是 int*類型,const 分別修飾了p 和 *p,所以p和*p都是常量,則p是一個指向常量的指針常量,
					也就是說這個指針指向的地址不可修改,地址裏的內容也不可修改。

		舉例:
		int a = 10;
		int b = 20;
		const int* const p = &a;
		p = &b;			// 錯誤
		*p = 100;		// 錯誤


切記:如果你定義了一個指針,那麼就一定要知道這個指針指向了什麼地方,而且你要保證你定義的這個指針是正確且有效的,
		如果你亂用,我就用程序崩潰來懲罰你!

C語言之所以強大、自由性、高執行效率,很大部分是體現在指針的靈活運用上的。

 《C Primer Plus》第五版 很多頁
  入門書、參考書、

 《C和指針》

 《C專家編程》《C陷阱與缺陷》《C沉思錄》

 《The C Programming Language》 K&R C, ANSI C標準出臺前,這本書第一版就是當時的C標準。


四、多級指針

#include <stdio.h>
int main(void)
{
	int a = 10;			// 定義一個整型變量 a,值爲 10
	int *p = &a;		// 定義一個一級整型指針變量p,值爲 a的地址
	int **pp = &p;		// 定義一個二級整型指針變量pp,值是 p的地址
	int ***ppp = &pp;	// 定義一個三級整型指針變量ppp,值是 pp 的地址

	// %p 打印地址, %d 打印整數, %f 打印浮點數, %s 打印字符串,%c 打印字符
	// 在配合數據類型定義變量的時候使用*號,是代表這個變量的類型是指針類型
	// 在配合變量進行操作的時候使用*號,是取值運算符,意思是可以取出這塊內存空間的值、

	printf("%p, %p, %p, %p\n", &a, &p, &pp, &ppp);
	// 取出各個變量自身在內存的地址

	printf("%p, %p, %p, %p\n", &a, p, pp, ppp);
	// 除了 &a之外,都是打印各個指針變量存儲的值

	printf("%p, %p, %p, %p\n", &a, p, *pp, **ppp);
	// %p打印地址(其實是按十六進制方式打印出)
	// 1. 利用取地址運算符&,打印變量 a 的地址
	// 2. 直接打印p的值,也就是 a 的地址
	// 3. 利用取值運算符*,取出二級指針變量pp的值,也就是一級指針p的地址,在通過%p打印出p的值,也就是 a 的地址
	// 4. 利用取值運算符*,取出三級指針變量ppp的值,也就是二級指針pp的地址,再通過取值運算符*,取出二級指針pp的值
	//		也就是p的地址,再通過%p打印出p的的值,也就是 a 的地址

	printf("%d, %d, %d, %d\n", a, *p, **pp, ***ppp);
	// 如果要取出變量a的值,那麼用了多少級指針,就用多少個取值運算符,
	// 在取出最後的值之前,各級指針取出的值都是其上一級指針的地址

	return 0;
}


















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章