9-3變量生命週期和修飾符
生命週期
函數的聲明週期
起於調用,結束於調用結束
局部變量的生命週期
起於調用,結束於調用結束
main函數的生命週期
main開始 == 進程的開始 main函數的結束 == 進程的結束
全局變量的生命週期
起於main調用,結束於main調用結束
修飾符
auto
含義:只能用於修飾局部變量,表示該變量存儲於棧stack
特點: 隨用隨開,用完消失
C中默認的局部變量,就是auto類型,所以通常將其省略 c++ auto自動類型
register
含義:只能修飾局部變量,原則上,將內存中的變量升級到CPU寄存器中存儲,這樣訪問速度會更快,但由於CPU寄存器數量有限,通常會在程序優化階段,被優化爲普通auto類型,可以通過彙編代碼來查看,優化過程(與平臺和編譯有關)
特點:可以避免cpu與內存的頻繁交互
一般程序從內存讀取數據 放到寄存器 進行運算 運算結果寫入到內存
關於各個存儲部件中讀寫速度
硬盤:7200轉
內存:頻率1333MHZ 1600MHZ 帶寬 = 頻率*64/8
緩存:
CPU:
extern
含義:只能用於修飾全局變量,全局變量本身是全局可用的,但是由於文件是單個完成編譯,
並且編譯是自上而下的,
所以說,對於不是在本範圍內定義的全局變量,要想使用必須用extern 進行申明,
如果不加上可能會造成重定義。
特點:
1.可以省略,聲明在前,定義在後,不論局部還是全局。
main.c : int a; other.c : int a =200;編譯不會報錯
2.外延性 某文件定義的全局變量 可在其他文件使用
c語言編譯是跨文件的
編譯過程:單文件編譯 單文件每個編譯成xxx.o
鏈接所有.o文件和lib.so文件
生成可執行文件xxx.out
9-4 static
修飾局部變量
特點:1.初始化值爲0,且只初始化一次
2.生命週期等同於程序
修飾全局變量
特點:1.全局的外延性消失,變成文件內部的全局變量 也適用於函數
2.存儲在data段
小結問題:
-
extern int a;
int a= 200;
是不是同一個a? -
局部變量和全局變量儲存的位置有什麼不同?
-
static修飾的變量值所保存的位置在哪裏 才導致是累計變化的?
保存在data段中
9-5 字符串常量——9-7字符串的輸入輸出
定義
是雙引號括起的任意字符序列
字符串大小
看到的大小,比我們實際字面量要多一個,
最後的字符’\0’,我們稱爲字符串結束字符,是系統對雙引號引起的字符串自動加設 的,而非手動干預。
字符串存儲
字符串的存儲,不像其它普通類型的常量自變量的存儲一樣,普通類型自變量通常存儲 在代碼段(text),
而字符串,則存儲在數據段,且是隻讀數據段。
也就是通常意義上的常量區,但是常量區這種說法,是不太精確的,也不提倡。
拓展
棧區(stack):臨時變量,局部變量,生命週期結束即銷燬
堆區(heap):一些new出來的變量 存儲的地方 特定銷燬時間統一銷燬,析構函數
數據段(data):用來存放程序中已初始化的全局變量的一塊內存區域,屬於靜態內存分配。
代碼段(text):用來存放程序執行代碼的一塊內存區域,只讀,且不可修改。可能包含一些只讀的常數變量,例如字符串常量等。
bbs段:bss段(bss segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域
字符數組和字符串區別
1.字符串數組(棧區)是將text區的常量字符串 拷貝到棧區 字符串值的改變實際上是棧的值改變
字符串指針 是直接指向 text區常量字符串地址 故不能改變text區的值
2.相同點:字符數組長度大於等於字符串長度
字符串的輸入輸出
puts函數:printf的自帶換行
scanf函數:默認遇到空格結束輸入,scanf("%[\n]s")遇到回車結束輸入,但不檢查輸入長度,不安全
gets函數:空格也可以讀入,但不檢查輸入長度,不安全。
fgets函數:參數列表(接收變量,長度,輸入流)
tips
1.printf(“”) 空串有換行功能
2.gets();不檢查預留存儲區是否能夠容納實際輸入的數據,
換句話說,如果輸入的字符數目大於數組的長度,gets 無法檢測到這個問題,就會發生內存越界,所以編程時建議使用 fgets()。
char *fgets(char *s, int size, FILE *stream);
fgets() 雖然比 gets() 安全,但安全是要付出代價的,
代價就是它的使用比 gets() 要麻煩一點,有三個參數。
它的功能是從 stream 流中讀取 size 個字符存儲到字符指針變量 s 所指向的內存空間。它的返回值是一個指針,指向字符串中第一個字符的地址。
3. c中允許 c++不允許
char a[2] = "china";
printf("%s",a);
輸出結果:ch
4.char * p = "china";
printf("p = %p p+1 = %p p[0]=%c 0[p] =%c\n", p, p + 1, p[0], 0[p]);
printf(" = %p = %p =%c =%c\n", "china", "china"+1, "china"[0], 0["china"]);
輸出結果:地址不相等 與視頻不符合
問題?
1.視頻說 " 字符串,則存儲在數據段"
而 這篇文章說了
在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等
2.字符串指針和字符數組的區別?
字符數組可修改內容,有個拷貝過程,data段常量區拷貝到棧區,字符數組實際是棧區
字符串指針如果指向data段字符串常量 是不可修改地址的,若是指向字符數組反而可以
10-2 從字符串常量到字符數組
10-3 原生字符串處理
strlen(char *) 字符串長度
strcat(char *head,char *tail) 連接兩個字符串
注意:被連接的串必須有足夠空間
but vs 可行…
char firstName[] = "jim";
char familyName[30] = "tim";
strcat(firstName, familyName);
printf("%s", firstName);
輸出:jimtim
優化操作:
原本代碼
while(*p) p++;
char *p,*q;
while(1){
p * =q*;
if(p* == '\0'){
break;
}
p++;q++;
}
優化後:
while(*p) p++;
while(*p++ = *q++);
Tip
- int* 和 char* ,float * 長度 是多少?
指針統一長度爲4byte
int a = 1;
int* ap = &a;
printf("%d \n", sizeof(ap));
char * c = "123";
printf("%d \n", sizeof(c));
float d = 1.0f;
float * dp = &d;
printf("%d \n", sizeof(dp));
輸出:4
4
4
-
char *ac = “123”;
printf("%p \n", &ac);
printf("%p \n", “123”);爲什麼輸出地址不同?
3.字符串長度和大小是不同的
長度是指字符長度且不包含\0,大小是指字節大小包含\0
10-7 字符串指針數組入門
指針數組的本質是數組,數組指針的本質是指針。
Tips
1.運行這段代碼 設置字符串池優化
char * pa = "china"; char *pb = "america"; char*pc = "canada"; char*pd = "japan";
char * cpArr[4] = { pa,pb,pc,pd };
printf("pa = %p \n", pa);
printf("pb = %p \n", pb);
printf("pc = %p \n", pc);
printf("pd = %p \n", pd);
for (int i = 0; i < 4; i++)
{
printf("%p \n",cpArr[i]);
}
printf("----------------\n", pd);
char * cpArr2[4] = {"china","america","canada", "japan" };
for (int i = 0; i < 4; i++)
{
printf("%p \n", cpArr2[i]);
}
- NULL ,nullptr,0區別
11-1 棧內存和堆內存的基本概念
概念
源程序:源代碼
程序:可執行文件
進程:時間概念可執行性文件被拉起,到結束的這一段過程,稱爲進程
進程空間:可執行文件 被拉起以後 在內存中的分佈情況
棧內存
棧存儲的特點
棧中存放任意類型的變量,但必須是 **auto** 類型修飾的,即自動類型的局部變量,隨用隨開,用完即消。
內存的分配和銷燬系統自動完成,不需要人工干預。
分配順序,由高地址到低地址。
棧的大小
棧的大小並不大,他的意義並不在於存儲大數據,而在於數據交換。
常見棧溢出
局部變量申請過多,過大,char[1024*1024*10];
遞歸層數太多 例如10000層遞歸
堆內存
棧存儲的特點
堆內存可以存放任意類型的數據,但需要自己申請與釋放。
分配順序,由低地址到高地址。
堆大小
堆大小,想像中的無窮大,對於棧來說,大空間申請,唯此,無它耳。但實際使用中,受限於實際內存的大小和內存是否連續性。(基本最大爲用戶空間大小)
Tips
1.memset(pm,1,10*sizeof(int);
爲每個字節賦值爲1;一個int 實際4個字節 4個0x01
printf("%#x");
%#表示的輸出提示方式,如果是8進制,在前面加0,如果是十進制,不加任何字符,如果是十六進制,會加上0x
2.calloc和malloc
calloc在動態分配完內存後,自動初始化該內存空間爲零,而malloc不做初始化,分配到的空間中的數據是隨機數據
3.realloc
void *realloc(void *ptr, size_t size);
功能:擴容(縮小)原有內存的大小。通常用於擴容,縮小會會導致內存縮去的部分數據丟失。
參數:
void *ptr:ptr 表示待擴容(縮小)的指針, ptr 爲之前用 malloc 或 者 calloc 分配的內存地址。或 ptr==NULL,則該函數等同於 malloc。
size_t:size 表示擴容(縮小)後內存的大小。
返回值:
成功返回非空指針指向申請的空間 ,失敗返回 NULL。返回的指針,可能與 ptr 的值相同,也有可能不同。若相同,則說明在原空間後面申請,否則,則可能後續空間不足,重新申請的新的連續空間,原數據拷貝到新空間,原有空間自動釋放。
原理:先判斷當前的指針是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_address返回,如果空間不夠,先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而後釋放原來mem_address所指內存區域(注意:原來指針是自動釋放,不需要使用free),同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。
malloc 使用流程 申請 判空 使用 釋放 置空
問題1:指針接收 創建的內存空間(malloc),釋放時怎麼知道釋放(free)多少內存空間的
heap的每個塊兒頭有保存本塊的大小以及本塊分配的大小。
free並不知道那個指針所指空間的大小,它要先查找當前heap的頭部,然後整個塊兒釋放掉。
問題2:如何用malloc/new 定義一維,二維,三維數組?用free/delete銷燬呢?malloc/free和new/delete區別?
問題3:malloc/calloc/realloc 區別與不同?
問題4:常見的內存泄露和檢測內存泄露的常見方式?內存泄漏和野指針?
12-1 結構體 共用體 枚舉
typedef深入分析
定義
用自定義名字爲已有數據類型命名。其實叫 typerename 更合適。
形如:typedef 現在類型名 新類型名;
typedef 和#define 的區別
typedef 是以;號結尾的 C 語言語句。而#define 則是預處理階段的文本替換。有時
他們是可以互換的,但有時不可以。
typedef a b;
#define a b
Tips
1.typedef char *pChar; pChar a; a等同於?
試着預測輸出並調試以下代碼:
{
char *p,q;
printf("sizeof(p) = %d sizeof(q) = %d \n",sizeof(p),sizeof(q));// 4,1
typedef char *pChar;
pChar a,b;
printf("sizeof(a) = %d sizeof(b) = %d \n",sizeof(a),sizeof(b));// 4, 4
#define DpChar char*;
DpChar m,n;
printf("sizeof(m) = %d sizeof(n) = %d \n",sizeof(m),sizeof(n));// 4 ,1
}
2.總結
新類型名一般用大寫表示,以便於區別。
用 typedef 只能聲明新的類型名,不能創造新的類型,只是爲已經存在的類型起
一個別名,也不能用來定義變量,即只能用其聲明的類型來定義變量;
有時也可用宏定義來代替 typedef 的功能,但是宏定義是由預處理完成的,而
typedef 則是在編譯時完成的,更爲靈活方便。
typedef 可以讓類型更見名知意,更便於移值。
結構體的初始化
初始化及成員訪問
點成員運算符(.) 優先級等同-> 但比*高
Tips
問題一
初始化和賦值在c++中實際含義是?
問題二
形參和實參之間傳遞是什麼關係?
問題三
typedef和#define 本質區別在哪裏?
問題四
結構體作形參爲什麼要傳指針?
結構體數組及應用
結構體嵌套和結構體大小
結構體嵌套
結構體中,嵌套結構體,稱爲結構體嵌套。結構體中,既可以嵌套結構體類型變量,
也可以嵌套結構體類型,後一種方式不推薦。
結構體類型大小
結構體成員內存分佈
首成員在低地址,尾成員在高地址。
內存對齊
對齊規則
目的是解決:一個成員變量需要多個機器週期去讀的現象,稱爲內存不對齊。爲什麼要對齊
呢?本質是犧牲空間,換取時間的方法。
不同的編譯器和處理器,其結構體內部的成員有不同的對齊方式
x86(linux 默認#pragma pack(4), window 默認#pragma pack(8))。linux 最大支持 4 字節對齊。
方法:
①取 pack(n)的值(n= 1 2 4 8--),取結構體中類型最大值 m。兩者取小即爲外對齊大 小 Y= (m<n?m:n)。
②將每一個結構 體的成員大小與 Y 比較取小者爲 X,作爲內對齊大小.
③所謂按 X 對齊,即爲地址(設起始地址爲 0)能被 X 整除的地方開始存放數據。
④外部對齊原則是依據 Y 的值(Y 的最小整數倍),進行補空操作。
外對齊和內對齊:
外對齊Y:保證讀取結構體的起始地址到結束地址,表示結構體之間的對齊
內對齊X:保證從結構體內變量起始的地址到結束的地址 正好是該變量的長度,結構體內成員變量之間的對齊
結構體中指針使用注意事項
1.向結構體內未初始化的指針拷貝
結構體中,包含指針,注意指針的賦值,切不可向未知區域拷貝。
struct student
{
char*name;
int score;
}stu;
int main()
{
strcpy(stu.name,"Jimy");
stu.score=99;
return 0;
}
name 指針並沒有指向一個合法的地址,這時候其內部存的只是一些亂碼。所以在
調用 strcpy 函數時,會將字符串 “Jimy” 往亂碼所指的內存上拷貝,內存 name 指針根
本就無權訪問,導致出錯。 同樣stu.name = “Jimy”;可以的,name指向常量區,但是將來name不可改
int main()
{
struct student *pstu;
pstu = (struct student*)malloc(sizeof(struct student));
strcpy(pstu->name,"Jimy");
pstu->score=99;
free(pstu);
return 0;
}
爲指針變量 pstu 分配了內存,但是同樣沒有給 name 指針分配內存。錯誤與上面
第一種情況一樣,解決的辦法也一樣。這裏用了一個 malloc 給人一種錯覺,以爲也給
name 指針分配了內存。
2.未釋放結構體內指針所指向的空間
從內向外依次釋放空間。
Tip
1.結構體中嵌套構造類型成員的對齊(數組、結構體成員)
2.深拷貝和淺拷貝
深拷貝:拷貝內存的內容,結構體之間互不影響。
淺拷貝:直接地址賦值,指針共享一片內存。一個結構體發生變化,另一個結構體也會發生變化。
13-2 單鏈表
14-2 文本文件和二進制文件
文件流
C 語言把文件看作是一個字符的序列,即文件是由一個一個字符組成的字符流,因 此 c 語言將文件也稱之爲文件流。即,當讀寫一個文件時,可以不必關心文件的格式或
結構。
文件類型
文件,物理上是二進制,所以文本文件與二進制文件的區別並不是物理上的,而是邏輯上的。
文本文件是基於字符編碼的文件,常見的編碼有 ASCII 編碼,二進制文件是基於值編碼
的文件。
文本文件
以 ASCII 碼格式存放,一個字節存放一個字符。 文本文件的每一個
字節存放一個 ASCII 碼,代表一個字符。這便於對字符的逐個處理,但佔用存儲空間
較多,而且要花費時間轉換。
二進制文件
以值(補碼)編碼格式存放。二進制文件是把數據以二進制數的格
式存放在文件中的,其佔用存儲空間較少。數據按其內存中的存儲形式原樣存放。
用例:
int main() {
short a = 10000;
FILE * fp = fopen("ascii.txt", "w");
fprintf(fp, "%d", a);//文本寫
fclose(fp);
FILE *fp2 = fopen("bin.txt", "w");
char buf[] = "abcd";
fwrite(&a, 2, 1, fp2);//字節寫
//fwrite(buf, 4, 1, fp2);//字節寫
fclose(fp2);
return 0;
}
文件緩存
爲什麼要有緩衝區(buffer) 原因爲多種,有兩個重點:
1 從內存中讀取數據比從文件中讀取數據要快得多。
2 對文件的讀寫需要用到 open、read、write 等系統底層函數,而用戶進程每調用
一次系統函數都要從用戶態切換到內核態,等執行完畢後再返回用戶態,這種切
換要花費一定時間成本(對於高併發程序而言,這種狀態的切換會影響到程序性
能)。
文件的打開和關閉
FILE 結構體
FILE 結構體是對緩衝區和文件讀寫狀態的記錄者,所有對文件的操作,都是通過
FILE 結構體完成的。
typedef struct {
short level; /* 緩衝區滿/空程度 */
unsigned flags; /* 文件狀態標誌 */
char fd; /* 文件描述符 */
unsigned char hold; /* 若無緩衝區不讀取字符 */
short bsize; /* 緩衝區大小 */
unsigned char *buffer; /* 數據傳送緩衝區位置 */
unsigned char *curp; /* 當前讀寫位置 */
unsigned istemp; /* 臨時文件指示 */
short token; /* 用作無效檢測 */
} FILE ; /* 結構體類型名 FILE */
在開始執行程序的時候,將自動打開 3 個文件和相關的流:標準輸入流(stdin)、標
準輸出流(stdout)和標準錯誤(stderr),它們都是 FIEL*型的指針。流提供了文件和程序的
通信通道。
fopen
如果讀寫的是二進制文件,則還要加 b,比如 rb, r+b 等。 unix/linux 不區分文本和
二進制文件。
fclose
作用:強制輸出緩存內容 然後關閉FILE*
文件的讀和寫
一次讀一個字符
fputc
fgetc
feof
特點:feof 這個函數,是去讀標誌位判斷文件是否結束的。即在讀到文件結尾的時候再
去讀一次,標誌位纔會置位,此時再來作判斷文件處理結束狀態,文件到結尾。如果用
於打印,則會出現多打一次的的現象
一次讀一行字符
windows 換行符 ‘\n’ = 0x0d 0a;
linux 換行符 ‘\n’ = 0x 0a;
tips
1.fprintf(fp,fmt,buff) 文本寫出
fwrite(buff,size,count,fp) 字節寫出
2.亂碼原由
二進制文件讀取由acsii碼的方式讀取
3.文件緩存win和linux區別
win會立即輸出,linux會等待緩存滿了再輸出,加上\n會立刻輸出緩存
4.rewind(fp) 將文件指針重置到文件頭
位運算符
預處理
預處理操作,不是 c 語言語句,故語句末尾沒有分號,在預處理階段完成,本質是替
換操作。
發生時段:
宏定義變量
不帶參宏:
#define 定義的宏,只能在一行內表達(換行符表示結束而非空格),如果想多行表
達,則需要加續行符。
#define PI 3.14\
15926
宏常量,常被 const/ enum 變量取代,用於定義文件路徑則被常用。
#define FILEPATH "E:\\English\\listen_to_this\\listen_to_this_3"
#define ERR_EXIT(m)\
do\ {\
printf("Err:%s",m);\
exit(-1);\
}while(0) //此處的分號,可有可無
宏常量的缺陷 解決這一些問題,要不吝惜使用括號。
#define N 2+3 // #define N (2+3)
int main(void)
{
int num = N*2;
return 0;
}
宏類型:
宏可以給類型起別名,因其缺點,常被 typedef 取代
#define CHARP char *
int main(void)
{
CHARP p,q;
printf("p = %d q = %d\n",sizeof(p),sizeof(q));
return 0;
}
帶參宏(宏函數)
#define str(x) x
#define str(x) “aa”#x"bb" //#字符串化
#define str(x) x*2 \
x+x // ‘’ 續行符號
#undef MAX
條件編譯
#ifdef #ifndef
#elif
#endif
預定義宏
頭文件包含的意義
全寫入,被包含的文件中。包含是支持嵌套的。
方式<>
#include<stdio.h>,從系統指定路徑中搜索包含頭文件,linux 中的系統路徑爲
(/usr/include)
方式" "
#include"myString.h",從工程當前路徑中搜索包含頭文件,如果當前工程路徑下
沒有的話,則到系統路徑下搜索包含。
其他
#運算符 利用宏創建字符串
將替換符 字符串化,解決字符串中,不可被替換的參數問題。字符串如下的書寫
也是合理的。
//#define str(x) #x
//#define str(x) "aaaaaaaaxaaaaaaaaa"
#define str(x) "aaaaaaaa"#x"aaaaaaaaa"
int main()
{
printf("%s\n",str(100));
return 0;
}
##運算符 預處理的粘和劑
#解決了雙引號中無法替換問題,##解決了非雙引號中粘連無法替換的問題。
//#define sum(a,b) (aa+bb)
#define sum(a,b) (a##a+b##b)
int main()
{
printf("%d\n",sum(2,3));
return 0;
}
預定義宏
DATE 進行預處理的日期(“MMmm dd yyyy"形式的字符串文字)
FILE 代表當前源代碼文件名的字符串文字
LINE 代表當前源代碼中的行號的整數常量
TIME 源文件編譯時間,格式"hh:mm:ss”
func 當前所在函數名
在打印調試信息時打印這兩
個宏 FILE LINE 可以給開發者非常有用的提示
變參函數
參數的個數也是可變的,也就是說,在形參表中可以不明確指定傳遞參數的個數和類型,一個常見的庫函數 printf()就是如此。這種函數稱之爲變參函數。
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
//num 個 int 型數相乘
int mul(int num, int data1, ... )
{
int total = data1;
int arg, i;
va_list ap;
va_start(ap, data1);
for(i = 1; i < num; i++)
{
arg = va_arg(ap, int);
total *= arg;
}
va_end(ap);
return total;
}
//i 個 int 型數相乘
long mul2(int i, ...)
{
int *p, j;
p = &i + 1; //p 指向參數列表下一個位置
long s = *p;
for (j = 1; j < i; j++)
s *= p[j];
return s;
}
int main()
{
printf("%d\n", mul(3, 2, 3, 5));
printf("%d\n", mul2(3, 2, 3, 5));
return 0;
}
變參宏
VA_ARGS 是一個可變參數的宏,這個可變參數的宏是新的 C99 規範中新增
的,目前似乎只有 gcc 支持(VC6.0 的編譯器不支持)。宏前面加上##的作用在於,當
可變參數的個數爲 0 時,這裏的##起到把前面多餘的","去掉的作用,否則會編譯出錯, 你
可以試試。
#define debug(...) printf(__VA_ARGS__)
#define dgbmsg(fmt,...) printf(fmt,__VA_ARGS__)
#define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__)
#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)
#define debug(format, args...) fprintf (stderr, format, args)
在標準 C 裏,你不能省略可變參數,但是你卻可以給它傳遞一個空的參數。例如,
下面的宏調用在 ISO C 裏是非法的,因爲字符串後面沒有逗號:
debug (“A message”)
GNU CPP 在這種情況下可以讓你完全的忽略可變參數。在上面的例子中,編譯器仍
然會有問題(complain),因爲宏展開後,裏面的字符串後面會有個多餘的逗號。
CPP 使用一個特殊的’##’操作。書寫格式爲:
#define debug(format, …) fprintf (stderr, format, ## VA_ARGS)
這裏,如果可變參數被忽略或爲空,’##’操作將使預處理器(preprocessor)去除掉
它前面的那個逗號。如果你在宏調用時,確實提供了一些可變參數,GNU CPP 也會工作
正常,它會把這些可變參數放到逗號的後面。象其它的 pasted macro 參數一樣,這些參
數不是宏的擴展。
進階
數據類型
1.補碼
有符號數正負數二進制排列規律:
負數爲對應的正數 取反+1
0的補碼是0
2. int 和 long ,long long 區別
編譯器不同 int和long的含義可能是不同的
而 long long類型是 8個字節,64位
unsigned long long:0~2^64-1
long long:-2^63~ 2^63-1
3.類型轉化
顯示轉化
1>小數據賦給大變量 不會造成數據的丟失,系統爲了保證數據的完整性,還提供了符號擴充行爲。
2>大數據賦給小變量 會發生截斷行爲,有可能會造成數據丟失
隱式轉化
1>整體提升 32位機中 所有低於32位的整型數據 在運算過程中先要轉化爲 32 位的
整型數據,然後才參與運算
2>混合提升
(1)有long double 類型參與運算,先全部轉化爲long double
沒有(1)則(2)有double 類型參與運算,先全部轉化爲double類型
沒有(2)則(3)有unsign int 類型參與運算,先全部轉化爲unsign int類型
沒有(3)則(4)有 int 類型參與運算,先全部轉化爲int類型
3>強制轉化
進程空間:
壓棧和出棧
每次壓棧前會保存當前函數環境包括變量值,然後新創建一塊棧區來進行操作。
每次出棧會銷燬當前棧頂,最後return到上一個函數所在地址。
一維數組
本質
數組是用於存儲相同數據類型數據,且在內存空間連續的一種數據結構類型。數組 三要素。
類型:type [N]
定義:type name[N]
大小:sizeof(type [N]) 或 sizeof(name)
初始化
int array[10] = {1,2,3}; //部分初始化
int array2[10] = {[3] = 10};
int array1[10] = {0}; //清零
int array3[10] = {1,2,3,4,5,6,7,8,9,0,1,2};//越界不檢
訪問
數組名是數組的唯一標識符,數組的每一個元素都是沒有名字的。數據名有兩重含 義:
(1)數組名作整體訪問
int main(void) {
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
//int[10] arr;
printf("sizeof(arr) = %d\n",sizeof(arr));
printf("&arr = %p\n",&arr);
printf("&arr+1 = %p\n",&arr+1);
int(*pa)[10] = &arr;
printf("pa = %p\n",pa);
printf("pa+1 = %p\n",pa+1);
return 0;
}
輸出結果:
sizeof(arr) = 40
&arr = 007AF9DC
&arr+1 = 007AFA04
pa = 007AF9DC
pa+1 = 007AFA04
pa +1 爲數組最後一個元素的後的第一個地址
(2)數組名作起始地址訪問成員
name[i] == *(name+i) == i[name]
int main(void) {
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("arr[0] = %d\n",arr[0]);
printf("*(arr+0) = %d\n",*(arr+0));
printf("arr = %p\n",arr);
printf("arr +1 = %p\n",arr+1);
printf("&arr[0] = %p\n",&arr[0]);
printf("&arr[0] +1 = %p\n",&arr[0]+1);
return 0;
}
輸出結果:
arr[0] = 1
*(arr+0) = 1
arr = 010FF7E4
arr +1 = 010FF7E8
&arr[0] = 010FF7E4
&arr[0] +1 = 010FF7E8
作參數傳遞
參數傳遞,旨在傳遞三要素(起始地址,步長, 範圍)
void selectSort(int *p,int n)// int 表示步長,p表示起始地址,n表示範圍
返回堆中一維數組
返回值返回(一級指針)
char * allocMem(int n) {
char *p = (char*)malloc(n);
return p;
}
參數返回(二級指針)
int allocMem(char **p,int n)
{
*p = (char*)malloc(n);
return *p== NULL?-1:1;
}
二維數組
本質
二維數組的本質是一維數組,只不過,一維數組的成員又是一個一維數組而己。三 要素:
type name[M][N] == type[N] name[M]
int arr[3][4] == int[4] arr[3] // 步長爲int[4] 起始地址爲arr 範圍3
初始化
行可以省,列不可以省,部分初始化和清零依然適用
int array[2][3] = {[1][2]=3}; //c99
int array[][3];=> int[3] arry[];//行可以省 列不可以省 行 初始化 自動賦值
訪問
數組名,是數組的唯一標識符。
(1)數組名作爲整體訪問
int main(void) {
int arr[3];
//int[3] arr;
printf("sizeof(arr) = %d sizeof(int[3]) = %d\n",sizeof(arr),sizeof(int[3]));
printf("&arr = %p\n",&arr);
printf("&arr+1 = %p\n",&arr+1);
int array[3][4];
//int[4] array[3] type array[3]; type[3] array;
printf("sizeof(array) = %d sizeof(int[3][4]) = %d\n",sizeof(array),sizeof(int[3][4]));
printf("&array = %p\n",&array);
printf("&array +1 = %p\n",&array+1);
return 0;
}
輸出結果:
sizeof(arr) = 12 sizeof(int[3]) = 12
&arr = 005CFDF4
&arr+1 = 005CFE00
sizeof(array) = 48 sizeof(int[3][4]) = 48
&array = 005CFDC0
&array +1 = 005CFDF0
&array+1 同一維數組 數組結尾地址
(2)數組名作起始地址訪問成員
(3)結論
a 表示第 0 行首地址,a+i 表示第 i 行首地址
*(a+i), a[i], &a[i][0]表示第 i 行第 0 個元素地址
*(a+i)+j, a[i]+j, &a[i][j]表示第 i 行第 j 個元素地址
*(*(a+i)+j) *(a[i]+j), a[i][j]表示第 i 行第 j 個元素
線性存儲
二維數組在邏輯上是二維的,但是在存儲上卻是一維的。正是這個特點,也可以用 一維數組的方式去訪問二維數組的。
int main() {
int array[2][3] = {9,8,7,6,5,4};
for(int i=0;i<2; i++) {
for(int j=0;j<3;j++) {
printf("%d ",array[i][j]);
}
putchar(10);
}
int *p = (int *)array;
for(int i=0; i<6;i++) {
printf("%d ",p[i]);
}
return 0;
}
參數傳遞
二維數組本質是常量數組指針,所以跟其對應的形參也應該是數組指針類型的變 量
void displayArray(int(*p)[4],int n) //步長int[4] 首地址p,範圍n
數組指針
一次可以移動一個字節指針,稱爲 char 類型的指針,
一次可以移動二個字節的指 針,稱爲 short類型的指針。
一次可以移動一個數組大小的指針,是什麼類型的指針的,又該如何稱謂呢?
比如 二維數組名: arr[3][4]; arr+1 一次加 1 的大小,就是 int[4]類型的大小,即一個數組 的大小。
定義
int [N] *pName; =>int (*pName)[N];
語法解析:“()”的優先級比“[]”高, “*”號和 pName 構成一個指針的定義, 指針的類型爲 int [N]。
別名
typedef int (*TYPE)[N];
TYPE a;//等價於 int (*a)[N];
數組指針與數組名
解析 | 一維數組名 | 二維數組名 |
---|---|---|
示例 | int arr[4] | int arr[3][4] |
本質 | 一級指針 | 數組指針 |
引用 | &arr 數組指針 | &arr 數組指針 |
應用
(1)二維數組傳參
void displayArray(int(*p)[4],int n)
(2)一維空間的二維訪問
int main() {
int arr[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[2] = (int(*)[2])arr;
for(int i=0; i< sizeof(arr)/sizeof(int[2]);i++) {
for(int j=0; j<2; j++) {
printf("%d\t",p[i][j]);
}
putchar(10);
}
}
輸出結果:
1 2
3 4
5 6
7 8
9 10
11 12
多維指針
本質分析
type name[x][y][z] == type [y][z] name[x]; //步長爲 type[][],首地址name,範圍 x
指針
編址
變量地址
對變量取地址,取得是最低位字節的地址。32 位機下,大小均爲 4。
int main(void) {
char a;
short b;
int c;
printf("&a = %p\n",&a);
printf("&b = %p\n",&b);
printf("&c = %p\n",&c);
printf("sizeof(&a) = %d\n",sizeof(&a));
printf("sizeof(&b) = %d\n",sizeof(&b));
printf("sizeof(&c) = %d\n",sizeof(&c));
return 0;
}
輸出結果:
&a = 00CFF897
&b = 00CFF890
&c = 00CFF88C
sizeof(&a) = 4
sizeof(&b) = 4
sizeof(&c) = 4
本質
指針的本質,就一個有類型的地址。
int main(void) {
int a = 0x12345678;
printf("%p\n",&a);
printf("%d\n",*(&a));
//printf("%d\n",a);
//printf("%x\n",*((int*)0x0060FEAC));
return 0;
}
指針變量
內存的地址,即指針(常量),存放該地址的變量就是指針變量。此變量,必須滿足 3 個條件,大小爲 4,有類型,區別於其它變量。
type *var;
type 決定了類型(步長),* 表示該變量是指針,var 用於存儲地址。
變量大小
指針變量 大小始終爲4字節
數組變量 大小爲 類型(即步長)* 範圍 字節
int main(void) {
char *pa;
int *pi;
printf("sizeof(pa) = %d sizeof(pb) = %d\n", sizeof(pa),sizeof(pi));
printf("sizeof(char*) = %d,sizeof(int*) = %d\n",sizeof(char*),sizeof(int*));
char *pm = (char*)malloc(100);
printf("sizeof(pm) = %d\n",sizeof(pm));
int (*parr)[10];
printf("sizeof(parr) = %d\n",sizeof(parr));
int arr[100];
printf("sizeof(arr) = %d\n",sizeof(arr));
printf("sizeof(&arr) = %d\n",sizeof(&arr));
return 0;
}
輸出結果:
sizeof(pa) = 4 sizeof(pb) = 4
sizeof(char*) = 4,sizeof(int*) = 4
sizeof(pm) = 4
sizeof(parr) = 4
sizeof(arr) = 400
sizeof(&arr) = 4
變量類型
類型,決定了,從 var 存放的地址開始的尋址能力。
int main(void) {
int a = 0x12345678;
char *pa = &a;
printf("%x\n",*pa);
short *ps = &a;
printf("%x\n",*ps);
int *pi = &a;
printf("%x\n",*pi);
return 0;
}
輸出結果:
78
5678
12345678
注意:值的存儲 由低到高
引用與解引用
int main(void) {
int a = 0x12345678;
printf("%x\n",*&a);
int arr[3];
printf("arr = %p\n",arr);
printf("&arr = %p\n",&arr);
printf("arr+1 = %p\n",arr+1);
printf("&arr+1 = %p\n",&arr+1);
printf("*&arr = %p\n",*&arr);
printf("*&arr + 1 = %p\n",*&arr+1);
return 0;
}
輸出結果:
12345678
arr = 006FFAC4
&arr = 006FFAC4
arr+1 = 006FFAC8
&arr+1 = 006FFAD0
*&arr = 006FFAC4
*&arr + 1 = 006FFAC8
在地址 方面,
數組arr => &arr ,也有arr => *&arr
但arr+1 !=> &arr+1 (步長不一樣)
運算
指針的運算是,地址值+類型的運算。
int main(void) {
int a[10];
printf("a = %p\n",a);
printf("a+1 = %p\n",a+1);
printf("&a[9] - &a[4] = %d\n",&a[9] - &a[4]);
printf("(int)&a[9]-(int)&a[4] = %d\n",(int)&a[9] -(int)&a[4]);
return 0;
}
輸出結果:
a = 0116FAE8
a+1 = 0116FAEC
&a[9] - &a[4] = 5
(int)&a[9]-(int)&a[4] = 20
解釋幾點:
a和a+1是 運算是以步長(此時爲int)爲單位 +1即移動一個Int長度
&a[9],&a[5]都是轉爲指針int* 也是以int爲步長單位的,所以9-5+1=5步
(int)&a[9],(int)&a[4] 被強轉爲字節爲單位的了 所以結果爲 5*4 =20字節
補充幾個例子:
例1
int main() {
int a[5] = {1,2,3,4,5};
int *ptr1 = (int *)(&a + 1);//此時ptr1指向a末尾後面一個int
int *ptr2 = (int *)((int)a + 1);//此時ptr2指向a起始地址後面一個字節
printf("%x, %x",ptr1[-1], *ptr2);//ptr前移一個int 即a[5],ptr2取一個int長度包含了a[0]後三個字節和a[1]第一個字節
return 0;
}
輸出結果:
5, 2000000
解釋
int *ptr1 = (int *)(&a + 1);//此時ptr1指向a末尾後面一個int
int *ptr2 = (int *)((int)a + 1);//此時ptr2指向a起始地址後面一個字節
printf("%x, %x",ptr1[-1], *ptr2);//ptr前移一個int 即a[5],ptr2取一個int長度包含了a[0]後三個字節和a[1]第一個字節
例2
int main(int argc, char **argv) {
int a;
int *p = &a;
printf("%x, %x\n",p,p+1);
printf("%x, %x",(int)p,(int)p+1);
//printf("%x, %x",(void*)p,(void*)p+1);
return 0;
}
輸出結果:
84fd44, 84fd48
84fd44, 84fd45
同理,(int *)和(int)步長區別
二級指針
定義:
二級指針,是一種指向指針的指針。我們可以通過它實現間接訪問數據,和改變一級
指針的指向問題。
初始化:
二級指針 實際上存儲着一級指針的地址
作用:間接數據訪問
(1)改變一級指針指向的內容
(2)改變一級指針指向
同理 N級指針 是爲了間接訪問N-1級指針數據
FILE與 sqlite3 函數設計的比較:
FILE* file = fopen(filePathStr,modeStr); // 返回句柄 不利於錯誤判斷
if()file == NULL) return -1;
sqlite3 *db = NULL;
int rc = sqlite_open(&db,sql,NULL,NULL); //錯誤碼 和句柄分開返回
if(rc == SUCCESS)
FILE 和 sqlite3 均是描述資源的句柄(即一個結構體)。獲取文件句柄的方式是通過
返回值,而獲取數據庫句柄的方式是參數。
FILE *的設計方式,並不是很好。原因是,返回值中要容錯出錯碼。而最合理的
方式,是將出錯碼和返回值分開。
步長
步長爲一級指針長度 即4 bytes
二級指針和指針數組
const修飾符修飾指針
作用增強程序的鍵壯性。但凡被修飾過的變量,其值不可修改
const 修飾指針
const 修飾變量
const 修飾變量,此時稱爲常變量。常變量,有常量的屬性,比用宏定義的常量有 了類型的屬性
特點:聲明時就必須定義,且定義之後不可修改。且發生在編譯時期。
可以通過指針修改const修飾的局部變量,但不可以修改const全局變量
const int b = 100;
int main(void)
{
const int a = 100;
printf("temp a = %d\n", a);
int *pa = &a;
*pa = 300;
printf("after temp a = %d\n",a);
int *pb = &b;
printf("global b = %d\n", b);
pb = 250;
printf("after global b = %d\n", b);
system("pause");
return 0;
}
輸出結果:
temp a = 100
after temp a = 300
global b = 100
after global b = 100
請按任意鍵繼續. . .
const 修飾符
(1) const 修飾指針,表示指針的指向是恆定的,不可更改。
int * const p = &a;
const修飾p,指針p是不可變的,也就是p指向的內存單元不可變。即p的指向不可變,p指向的內存單元的內容可以變。
(2) const 修飾指針指向的值 不能改變
const int *p = a;
a的值不能改變
(3) const int* const p = &a;
*p和p都被const修飾了,所以p指向的內存單元,和p指向內存單元中存放的內容都是不可變的。
函數指針與回調函數
函數的本質
函數的本質是一段可執行性代碼段。函數名,則是指向這段代碼段的首地址。
回調函數
回調(函數作參數)
回調函數,本質也是一種函數調用,先將函數以指針的方式傳入,然後,調用。這種寫法的好處是,對外提供函數類型,而不是函數定義。這樣我們只需要依據函數類型 和函數功能提供函數就可以了。給程序的書寫帶來了很大的自由。
練習
解釋((void() ()) 0)();
地址爲0的函數強轉爲(void(*)())函數的調用