C语言深度解剖
1、定義和聲明
定義:創(chuàng)建了對象并且為該對象分配內(nèi)存并給該對象取上名字(該名字就是變量名或者對象名),注意:名字一旦和這塊內(nèi)存匹配起來,就同生共死,不離不棄,并且這塊內(nèi)存的位置也不能被改變。 聲明:沒有分配內(nèi)存,只是告訴編譯器,這個名字已經(jīng)被預(yù)定了,別的地方不能再使用它作為變量名和對象名。extern int a; //聲明 int a; //定義2、皇帝身邊的小太監(jiān)——寄存器register
這個關(guān)鍵字請求編譯器盡可能地將變量存在CPU內(nèi)部寄存器中,而不是通過“內(nèi)存尋址訪問”,因此可以提高效率。 一個CPU的寄存器數(shù)量有限,也就幾個或者幾十個,因此不可能把所有變量全放入到寄存器。【舉例】
小太監(jiān)是皇帝和大臣之間數(shù)據(jù)傳遞的橋梁。 寄存器是CPU和內(nèi)存之間數(shù)據(jù)傳遞的橋梁。 數(shù)據(jù)從內(nèi)存里拿出來先放到CPU內(nèi)部寄存器,然后CPU再從寄存器里讀取數(shù)據(jù)來處理,CPU處理完數(shù)據(jù)后同樣把數(shù)據(jù)通過寄存器存放到內(nèi)存中,CPU不直接和內(nèi)存打交道。 寄存器其實(shí)就是一塊一塊小的存儲空間,只不過存取速度要比內(nèi)存快得多。【注意】
因?yàn)?span id="ze8trgl8bvbq" class="hljs-keyword">register變量可能不存放在內(nèi)存中,所以不能用取址運(yùn)算符&來獲取register變量的地址3、static在C中的幾個含義
(1)修飾變量:
__________________________作用域
static全局變量:從定義之處開始,到文件結(jié)尾處結(jié)束(限制在定義的文件中,即使使用關(guān)鍵字extern也不能把作用域擴(kuò)展到別的文件);在定義之處前面的代碼行也不能使用它,但是可以用extern擴(kuò)展到前面的某個代碼行處。
static局部變量:在函數(shù)體里面定義,就只能在該函數(shù)體里使用。
(2)修飾函數(shù)
static不是指存儲方式,而是對函數(shù)的作用域僅局限于本文件,該函數(shù)不能被其他文件訪問。
4、考點(diǎn):與“零值”進(jìn)行比較
① bool變量與“零值”進(jìn)行比較 if(bool == 0) if(bool == 1)//不好,讓人誤以為是整型變量 if(bool == FALSE) if(bool == TRUE)//TRUE的值不一定都是1 if(!bool) if(bool) //最好的寫法 ②float變量與“零值”進(jìn)行比較 if(float == 0.0) if(float != 0.0)//不好,沒有精度 if((float >= -EPSION)&&(float <= EPSION))//EPSION為定義好的精度,好 ③指針變量與“零值”進(jìn)行比價 if(p == 0) if(p != 0) //不好,讓人誤以為是整型變量 if(!p) if(p) //不好,讓人誤以為是bool變量 if(NULL ==p ) if(NULL != p) //好5、switch
每個case不要忘記加break switch case禁止使用return語句 case后面只能是整型、字符型、枚舉類型。6、在多層循環(huán)中,長的循環(huán)的放在內(nèi)部,短的循環(huán)放在外部。
7、void關(guān)鍵字
8、return語句注意事項(xiàng)
return語句不可返回指向“棧內(nèi)存”的“指針”,因?yàn)樵搩?nèi)存在函數(shù)體結(jié)束時被自動釋放。 例如:char* Fun(){char str[100];... ...return str; //在函數(shù)結(jié)束后,str會自動釋放,導(dǎo)致未知的錯誤,程序崩潰}9、const修飾只讀的變量必須在定義時進(jìn)行初始化
const int aa; //編譯失敗,必須初始化 const int aa = 100;//編譯成功 const與#define的區(qū)別 (1)const是在編譯期間確定值,#define是在預(yù)處理階段 (2)const有類型 (3)const在程序運(yùn)行時只有一個備份,而#define在內(nèi)存中有若干備份,因此const更加節(jié)省空間。10、柔性數(shù)組
(1)結(jié)構(gòu)體中的柔性數(shù)組成員前面必須至少有一個其他成員struct st{int a;char b;char ch[]; //等價于char ch[0];} (2)柔性數(shù)組不屬于sizeof(結(jié)構(gòu)體)的一部分11、enum枚舉類型
enum 枚舉類型名稱 {枚舉常量1;... ...枚舉常量n; }枚舉變量名;枚舉類型和#define的區(qū)別 (1)枚舉類型是在編譯時候確定其值 (2)可以調(diào)試枚舉常量,但是不能調(diào)試#define常量 (3)sizeof(枚舉類型)相當(dāng)于sizeof(int) #include <iostream> using namespace std; int main() {enum foo{};enum gender{male, female};enum season{spring, summer, autumn, winter};enum week{sunday, monday, tuesday, wednesday, thursday, friday, saturday};printf("%d\n",sizeof(foo));//結(jié)果為4,foo是枚舉類型printf("%d\n",sizeof(gender));//結(jié)果為4,gender是枚舉類型printf("%d\n",sizeof(season));//結(jié)果為4,season是枚舉類型printf("%d\n",sizeof(week));//結(jié)果為4,week是枚舉類型printf("%d\n",sizeof(male));//結(jié)果為4,male是枚舉常量printf("%d\n",sizeof(monday)); //結(jié)果為4,monday是枚舉常量 }12、typedef給已經(jīng)存在的數(shù)據(jù)類型取別名,而不是定義一個新的數(shù)據(jù)類型。
13、++、–運(yùn)算符
14、如何將數(shù)值存儲到指定的內(nèi)存地址
int *p = (int*)0x12ff7c;*p = 100;等價于*(int*)0x12ff7c = 100;15、判斷下面錯誤么?為什么?
int a[4]; cout<<sizeof(a[5])<<endl; //正確,結(jié)果是sizeof(int) = 4;雖然a[5]發(fā)生越界訪問,但是在sizeof(a[5])并沒有真正的訪問a[5],只是根據(jù)數(shù)組元素的類型來確定其值,因此并不會出錯。16、數(shù)組名a與&a的含義
a代表數(shù)組首元素的地址,即&a[0] &a代表數(shù)組的首地址【注】 首元素的地址與首地址的值相同,但是意義不同。17、數(shù)組名的含義
數(shù)組名不能作為左值數(shù)組名作為右值的含義 : 與&a[0]一樣,代表的是數(shù)組的首元素的地址(而不是數(shù)組的首地址)18、數(shù)組指針的初始化 格式
char ch[5] = "ABCD"; char ch[5] = "ABCDE"; //編譯失敗,因?yàn)槿萘渴?,此時"ABCDE"大小是6int (*p1)[5] = ch; //編譯失敗,不能將ch[5]轉(zhuǎn)換成char(*)[5] int (*p2)[5] = &ch; //編譯成功,p2指向數(shù)組ch的首地址(不是首元素的地址)19、二維數(shù)組/二級指針/指向二維數(shù)組的指針
(1) int a[5][5]; int (*p)[4]; p = a; 問: &p[4][2] - &a[4][2]; 的值為多少 ? 答: -4. 【解析】 &a[4][2]表示的是&a[0][0] + (5*sizeof(int))*4 + 2*sizeof(int) &p[4][2]表示的是&a[0][0] + (4*sizeof(int))*4 + 2*sizeof(int) p指向數(shù)組容量為4的數(shù)組,因此p+1的地址增量為sizeof(int)*4 (2)預(yù)備知識 char** p;//二級指針指向指針 char (*p)[4]; //p指向二維數(shù)組【總結(jié)】 多維數(shù)組作為形參等價于對應(yīng)的指針數(shù)組 例如:char a[3][4]————char (*p)[4] char a[3][4][5]————char (*p)[4][5] 指針數(shù)組作為形參等價于對應(yīng)的多級指針 例如:char *a[5]————char **P20、
(一) #include <iostream> using namespace std; void f(char a[10]) //或者void f(char *a) {a = "AAAAAAA"; } int main() {char a[10] = "BB";f(a);cout<<a<<endl; //輸出結(jié)果 BB } (二) #include <iostream> using namespace std; void f(char a[10]) //或者void f(char *a) {a = "AAAAAAA"; } int main() {char *a = "BB";f(a);cout<<a<<endl; //輸出結(jié)果 BB } //總結(jié):無論是main函數(shù)中的a無論是char字符數(shù)組還是指向字符串常量的指針,想調(diào)用函數(shù)f改變a的內(nèi)容都實(shí)現(xiàn)不成功。因?yàn)樾螀⑹莊中的局部變量,在f調(diào)用完成后釋放,相當(dāng)于沒有調(diào)用f對a進(jìn)行處理。21、怎么為stu結(jié)構(gòu)體中的name指針分配內(nèi)存?
struct student {char *name;int age; }stu,*pstu; int main() {stu.name = (char*)malloc(sizeof(char)*100); //正確pstu = (struct student*)malloc(sizeof(struct student)); //錯誤,【記住】此處只是給結(jié)構(gòu)體分配了內(nèi)存,并沒有給結(jié)構(gòu)體中的指針分配內(nèi)存。 } 【小知識點(diǎn)】malloc函數(shù)分配sizeof(0)的返回值是NULL指針么? 答:不是,返回一個正常的內(nèi)存地址,但是你卻無法使用這個塊大小為0的內(nèi)存。22、
void fun() {cout<<"FUN"<<endl; } int main() {void (*p)(); //定義一個p函數(shù)指針*(int*)&p = (int)fun; //將函數(shù)的入口地址賦值給指針變量p(*p)(); } *(int*)&p = (int)fun;的含義: (int*)&p 表示取出p的地址&p,再強(qiáng)制轉(zhuǎn)換成int*類型的指針。 *(int*)&p 即是取出p指向的int類型的數(shù)據(jù) (int)fun 表示將fun的入口地址轉(zhuǎn)換成int類型的數(shù)據(jù)23、signed、unsigned關(guān)鍵字
#include <iostream> using namespace std;int main() {char a[1000]; //char的范圍 [-128,127],超出這個范圍的值會產(chǎn)生溢出int i;for(i=0; i<1000; i++){ //a[0]到a[254]里面的值都不為0,而a[255]的值為0。strlen 函數(shù)是計(jì)//算字符串長度的,并不包含字符串最后的‘\0’。而判斷一個字符串是否//結(jié)束的標(biāo)志就是看是否遇到‘\0’。如果遇到‘\0’,則認(rèn)為本字符串結(jié)束。a[i] = -1-i; }cout<<strlen(a)<<endl; //因此strlen(a) = 255int k = -20;unsigned j = 10;cout<<k+j<<endl; //k先從int轉(zhuǎn)為uint,再與j相加unsigned m ;for (m=9;m>=0;m--){printf("%u\n",m); //死循環(huán),m為uint,m>=0恒成立}return 0; }例:#include <iostream> using namespace std; int array[] = {23,32,11,12,35}; #define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0])) //注意:sizeof返回的是unsigned int類型 int main() {int d = -1,x;if(d <= TOTAL_ELEMENTS - 2) //因此d和TOTAL_ELEMENTS - 2進(jìn)行比較時//先把d從int轉(zhuǎn)換成unsignedint類型,在進(jìn)行比較//d轉(zhuǎn)換完后是個很大的數(shù),比較結(jié)果不是想要的x = array[d+1]; }24、大端小端
25、地址強(qiáng)制轉(zhuǎn)換
26、
int main() {int a[4]={1,2,3,4};int *ptr1=(int *)(&a+1);int *ptr2=(int *)((int)a+1);printf("%x,%x",ptr1[-1],*ptr2);return 0; } ptr1:將&a+1 的值強(qiáng)制轉(zhuǎn)換成int*類型,賦值給int* 類型的變量ptr,ptr1 肯定指到數(shù)組a 的下一個int 類型數(shù)據(jù)了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 個byte。所以其值為0x4。 ptr2:按照上面的講解,(int)a+1 的值是元素a[0]的第二個字節(jié)的地址。然后把這個地址強(qiáng)制轉(zhuǎn)換成int*類型的值賦給ptr2,也就是說*ptr2 的值應(yīng)該為元素a[0]的第二個字節(jié)開始的連續(xù)4 個byte 的內(nèi)容。 【問題來了】這連續(xù)4 個byte 里到底存了什么東西呢?也就是說元素a[0],a[1]里面的值到底怎么存儲的。這就涉及到系統(tǒng)的大小端模式了! 如果是小端模式的話,*ptr2 的值為0x2000000。 如果是大端模式的話,*ptr2 的值為0x100。27、
(1) ++i+++i+++i;解讀 ((++(i++))+(i++))+i;(2) ++i+++++i+++i;解讀 (++((i++)++))+(i++)+i//優(yōu)先級:后自增 > 前自增27、幾個容易被誤解的運(yùn)算符優(yōu)先級
28、宏定義
總結(jié)
- 上一篇: 高级(复杂)指针的含义
- 下一篇: 改善程序的55个具体做法