零:
聲明、定義、初始化:
聲明:前面加上 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;
}
c語言基礎總結
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.