第一節 地址運算符、間接運算符、指針初識
&
地址運算符 由地址運算符所組成的表達式稱爲地址表達式*
間接運算符 由間接運算符所組成的表達式稱爲間接表達式- 共同特點:
- 一元運算符,只需要一個右操作數
- 優先級相同,從右向左結合
int main(){
int a = 100;
&a; //地址 --> 指針
*&a; // *地址 --> 指針所指向的值
int b = 100, c = 100;
b + c; //表達式的值爲200,是一個"int"類型的整數
&b; //表達式的值爲(int*)0x22fe44,是一個"指向int"類型的指針
/*
long int b = 100, c = 100;
&b; //一個指向“long int”類型的指針
*/
return 0;
}
&a;
: 地址表達式,變量a的內存地址爲地址表達式的值,地址又被稱作“指針”
*&a;
:間接表達式,地址所指向的值(100)爲間接表達式的值,地址所指向的值又被稱爲“指針所指向的值”
- 間接表達式的運算順序爲從右向左,在此表達式裏,地址運算符的優先級高於間接運算符
第二節 聲明存儲指針的變量–整數
-
如何聲明一個指針類型的變量?
- 在聲明的變量前加一個
*
- 在聲明的變量前加一個
-
int main(){ int a; //變量a,用來存儲"int類型"的整數 int * b; //變量b,聲明用來存儲"指向int類型"的指針 b = &a; //將指向"int類型"的指針賦值給b *b = 100; /* *b爲間接運算符,優先級高於賦值運算符 先將變量b進行左值轉換,*b的值則爲變量a的值 */ return 0; }
-
int * b; //變量b,用來存儲"指向int類型"的指針
b = &a; //將指向"int類型"的指針賦值給b,由於上面一行代碼聲明瞭變量b爲指針類型,故此行代碼才能正確運行 -
*
: 根據地址對變量進行還原,*b = = a
*b
的值等於變量a的值*b = 100;
< == >a = 100;
- 通過
*
運算符和變量b,間接影響變量a的值,所以將*
運算符稱爲間接運算符 *&a
: 先執行&a
取出地址(指針),再執行*&a
對取出的指針進行還原*&a < == > a
第三節 函數名-指針轉換、聲明存儲指針的變量–函數、使用變量調用函數
void swap(int * a, int * b){
int temp = * b;
* b = * a;
* a = temp;
}
int main(void){
int m = 10086, n = 10010;
swap(& m, & n);
void (* pf)(int *, int *) = swap;
pf(& m, & n); //處理器在進行處理的時候,會先將函數調用表達式`pf()中存儲的是一個指向swap的指針(地址)`進行一個左值轉換,通過變量pf調用swap函數
//(*pf)(& m, & n); (&*pf)(& m, & n); (*&*pf)(& m, & n); (&*&*pf)(& m, & n);
return 0;
}
- 在main函數中,函數名(swap)會被隱式的轉換爲函數的地址(“指向函數的指針”),處理器會根據地址定位到swap函數所在的位置並執行,
swap (& m, & n) < == > (&swap)(& m, & n);
- 聲明pf變量,用於存儲“指向函數swap”的指針,只要符合聲明格式的函數,pf都可以進行存儲
第四節 類型匹配、數值的類型、整型常量的:前綴、後綴、類型判定方法
int main()
{
//“變量”在聲明的時候,我們規定了它的類型
int a; //變量a爲int類型
//在給變量進行賦值的時候,必須賦值給它相對應類型的數值
a = 1.5; //類型不匹配,存儲就會出現問題
//“常量”也是有類型的
//整數類型的常量 --> 簡稱整型常量
//1.整型常量3種進制的寫法(前綴)
a = 125; //十進制寫法:以非0的數字開頭
a = 0125; //八進制寫法:以0開頭
a = 0x125; //十六進制寫法:以0x/0X開頭
a = 0b00001; //二進制寫法:以0b/0B開頭
//注意”C語言並沒有規定二進制的寫法,這是編譯器自行添加的
//所以,更換編譯器的時候,程序有可能會報錯
//2.整型常量的3個後綴:u(unsigned)、l(long )、ll(long long)(不分大小寫)
a = 125u;
a = 125L;
a = 125LL;
a = 125ul; //u和l:誰前睡後都可以
a = 125ull; //u和ll:誰前誰後都可以
//怎樣判定常量是什麼類型?
//前綴 + 後綴 + 具體的C實現 這三個因素決定了一個常量的類型
//具體判定方法:C語言提供了整型常量類型對照表,詳見下表
return 0;
}
- 整型常量對照表
第五節 整數類型轉換爲_Bool類型、隱式類型轉換
int main(void){
_Bool a, b, c, d;
a = 0; //int類型的0 ----> _Bool類型的0
b = 1; //int類型的1 ----> _Bool類型的1
c = 100; //int類型的100 ----> _Bool類型的1
d = 100000000000; //long long int類型的100000000000 --> _Bool類型的1
return 0;
}
- 由於a,b,c,d都是int或long long int型的數據,與_Bool類型不一致,故需要進行類型轉換。
- 類型轉換不需要人爲進行參與,自動進行,所以也稱爲隱式類型轉換,轉換的結果爲上述代碼段中的註釋
- 將“整數類型”轉換爲“_Bool類型”時:
- 如果數值在_Bool類型存儲範圍以內,只有類型會發生改變,數值將不會發生改變
- 如果數值不在_Bool類型存儲範圍以內,類型和數值將全部發生改變,數值將被轉換爲1
第六節 整數類型轉換爲非_Bool整數類型、隱式類型轉換
int main(void){
short int a = 100; //將int類型的100 --> 隱式轉換爲short int類型的100
int b = 3700u; //將unsigned int類型的3700 ----> 隱式轉換爲int類型的3700
//由於a和b的類型不一致,需要進行隱式類型轉換
//新類型爲無符號整數
unsigned char c = -1; //int類型的-1隱式轉換爲unsigned char類型的255
//由於-1太小,不在新類型的存儲範圍之內,所以需要加上一個數(256)得到一個新值
//256由來:新類型(char)的最大存儲值爲1個字節(255),進行加1 ----> -1 + 256 = 255
unsigned char d = 257; //將int類型的257隱式轉換爲unsigned char類型的1
//由於257太大,不在新類型的存儲範圍之內,所以需要減去一個數(256)得到一個新值
//256由來:新類型(char)的最大存儲值爲1個字節(255),進行加1後得到一個新值,做減法運算 ----> 257 - 256 = 1
//有符號整數 --> 不同的C實現會有不同的處理結果
return 0;
}
- 由於類型的不一致,需要進行隱式類型轉換
- 將“整數類型”轉換爲“非_Bool整數類型”時:
- 如果數值在新類型的存儲範圍以內,只有類型會發生改變,數值將不會發生改變
- 如果數值不在新類型的存儲範圍之內,類型和數值都將全部發生改變
- 數值的改變分爲兩種情況:
- 新類型是無符號整數、新類型是有符號整數
第七節 表達式值的類型
int max(int a, int b)
{
if(a >= b)
//關係表達式的類型 --> int(固定)
return a;
return b;
}
int main(void)
{
//每個表達式都有一個值,這個值也是有類型的
//"表達式值的類型",簡稱"表達式的類型"
int x = 1, y = 1;
unsigned char z;
x += z = 56;
//整型常量表達式:56 --> int類型(判斷方法見本章第三節)
//賦值表達式:z = 56: 值 -> 56、類型 -> unsigned char
//複合表達式的值: x += z = 56:值 -> 57、類型 -> int
//賦值表達式 --> 值 -> 左操作數的值、類型 -> 左操作數的類型
z += max(x, y);
//函數調用表達式:max(x, y):值 -> 函數的返回值、類型 -> 函數的返回類型
//複合賦值表達式: z += max(x, y):類型 -> unsigned char
y = ++ x;
//前綴遞增表達式: ++ x:類型 -> int -> 操作數的原型
//賦值表達式: y = ++ x: 類型 -> int
return 0;
}
第八節 隱式類型轉換和運算符的關係、整型轉換階、整型提升
int main(void)
{
//類型的轉換是由雲端夫來發起的
//賦值運算符:將右操作數轉換爲和左操作數一樣的類型
singned char a = 1, b = 2;
//遞增運算符:不改變操作數的類型
++ a;
/*
其他的大多數運算符都需要將操作數轉換爲相同的類型,然後再進行運算
具體的轉換規則:以加性運算符爲例
*/
a + 380L;
//第一步:整型提升 --> 將轉換階低於int或者unsigned int的類型轉換爲int或者unsigned int類型
//第二步:整型提升後,如果操作數類型不一致 --> 將轉換階低的操作數轉換爲和轉換階高的操作數一樣的類型
//詳細轉換見整型的轉換階
/*
注意:
1.整型的提升是對第一步的描述,和第二步無關
2.整型提升以int類型爲首選
3.當進行整型提升時,如果數值不在int類型的存儲範圍之內,那麼就將它轉換爲unsigned int --> 當short int 和 int的存儲範圍相同時
*/
a + b;
//整型提升,只要在int或unsigned int類型以下的,都需要進行整型提升
int c = -1;
unsigned int d = 100;
c + d;
//整型提升後,兩個類型在同一階
//將“有符號類型”轉換爲“無符號類型”
return 0;
}
- 整型的轉換階
第九節 負號運算符、負號表達式
int main(void)
{
unsigned char a;
a = -1; //1爲int型,4個字節"0000 0000 0000 0000 0000 0000 0000 0001",在此處,a的值爲255
// - : 負號運算符 --> 負號表達式 --> 在C語言中,負數是通過運算所得到的
// 一元運算符,只需要一個右操作數
//運算規則:
// 1.如果操作數是一個整數,負號運算符會發起類型轉換 --> 對操作數進行整型提升
// 2.對操作數進行“取補碼”操作,得到 "1111 1111 1111 1111 1111 1111 1111 1111"
signed char b = - a ++; //b的值爲1
return 0;
}
第十節 顯式類型轉換、轉型運算符、轉型表達式、()的多種用法
int main(void)
{
//顯式/強制類型轉換
//語法規則:(類型名)表達式
//() --> 轉型運算符 --> 將表達式的類型轉換爲括號中所指定的類型
long long a;
a = 33; //處理器在執行運算時會先進行隱式的運算,將int型的33轉換成long long int型的33並賦值給a
a = (long long int)33; //int --> long long int
a = (long long)33 + 100;
a = (long long)(33 + 100); //(33 + 100) --> 基本表達式
//()運算符的三種用法
//1.函數調用運算符
//2.轉型運算符
//3.構成基本表達式
return 0;
}
第十一節 整數-指針的轉換、間接運算符的性質、再識指針
int main(void)
{
//間接運算符 --> 根據指針(地址)對變量進行還原 --> 它的操作數必須是指針,並且它不會發起類型轉換
int a = 0;
int b = (int) &a;//&a的值爲變量a的地址,類型爲指向int類型的指針;此行代碼的作用是將變量a的地址存儲到變量b中
/*
錯誤:間接運算符的操作數類型錯誤
* b = 100;
*/
*(int *)b = 100; //(int *)b先將b進行左值轉換,此時b中存儲的是a的地址,類型爲指向int類型的指針
//此行代碼執行完以後,a的值變爲100
//*(int *)b < == > a
return 0;
}
第十二節 研究指針三劍客、類型的本質
int main(void)
{
int a = 0; //假設:變量a的內存地址爲123456
&a;
int b = (int)&a;
return 0;
}
&a
: 值爲123456,類型爲指向int類型的指針- b : 值爲123456,類型爲int整型
&a和變量b
: 數值相同,類型不同- 分爲不同類型的原因:
- 不同類型,對應着不同的使用規則
* b
----> 編譯器會報錯* &a
----> 將&a
的值解析爲地址,然後訪問這個地址所對應的內存單元- C語言爲什麼要引入
地址運算符&
、間接運算符*
、指針
這幾種類型?- 讓它們互相配合,使操作者可以通過地址來訪問內存
&
: 獲取一個數值,並將這個數值的類型規定爲指針*
: 通過指針類型的數值,來訪問內存單元
- 彙編語言可以通過地址來任意訪問內存 ----> 強大
- 高級語言把內存訪問的細節屏蔽掉 ----> 簡單
- C語言 ----> 既強大又簡單
第十三節 指針-指針轉換、複雜表達式分析
int abc(int a, int b)
{
return 0;
}
int main(void)
{
int a, (* b)(int, int) = abc;
// 變量 ----> 存儲數據
//變量a ----> int整型
//變量b ----> 指針 ----> 指向函數 ----> 兩個int類型的參數,一個int類型的返回值
/*
abc ----> 函數名 ----> 函數名-指針轉換 ----> 值:函數abc的地址 ----> 類型 ----> 指針 ----> 指向函數 ----> 兩個int類型的參數,一個int類型的返回值
*/
//變量b中的值爲多少? ----> 函數abc在內存中的地址
//變量b中的值是什麼類型? ----> int (*) (int , int)
void (* c)(void) = (void (*) (void)) b;
//變量 ----> 存儲數據
//變量c ----> 指針 ----> 指向函數 ----> 沒有返回值,沒有參數
//b ----> 變量 ----> 左值轉換 ----> 值:函數abc的地址、類型:int (*) (int, int)
//變量c中的值是多少? ----> 函數abc在內存中的地址
//變量c中的值是什麼類型? ----> void (*) (void)
a = ((int (*)(int , int )) c)(1,2);//函數調用表達式,調用函數abc,並傳入值1和2
//((int (*)(int , int )) c)最外層()是爲了讓基本表達式作爲一個整體(轉型運算符)參與運算
//基本表達式:(int (*)(int , int )) c
//處理器會將c的值進行左值轉換,將c的原類型void (*) (void)強制轉換爲int (*)(int , int )
return 0;
}
第十四節 從內存的角度搞定 – 多級指針
int main(void)
{
int i, * pi = &i, * * ppi = & pi;
* * ppi = 1;
* ppi = 1;
//--------------------------------------------
int j;
pi = & j;
** ppi = 3;
return 0;
}