指針是分類型的:
指針++根據類型不同,偏移值也不同。指針和數組,如何讓指針指向數組?
①用數組名 :int array[10],int* p,p=array
②用第一個元素的地址:int array[10],int* p,p=&array[0]
注意:
不要讓指針的偏移位置超出了數組,否則將看到亂碼。
#include <stdio.h>
#include <stdlib.h>
void arrayAdrePrint(int datas[],int size)
{int i;for(i=0;i<size;i++){printf("第%d個元素的地址是:%p\n",i+1,&datas[i]); }
}
void arrayPrint1(int datas[],int size)
{int i;for(i=0;i<size;i++){printf("arrayPrint1打印第%d個元素的地址是:%p\n",i+1,datas);datas++; }
}
void arrayPrint2(int* p,int size)
{int i;for(i=0;i<size;i++){printf("arrayPrint2打印第%d個元素的地址是:%p\n",i+1,p);p++; }
}
int main()
{int *p1;//整型類型只能存放整數的地址char *p2;//字符型只能存放字符數據的地址int a=10;char c='A';p1=&a;p2=&c;//指針++printf("a的地址打印:%p\n",p1);printf("a的地址++打印:%p\n",++p1);printf("c的地址打印:%p\n",p2);printf("c的地址++打印:%p\n",++p2);//結果是:a的地址++后,向后偏移了四個字節,c的地址++后向后偏移了1個字節//原因是:整型占4個字節,字符型占一個字節int len;int array[3]={1,2,3};len=sizeof(array)/sizeof(array[0]);arrayAdrePrint(array,len);//打印數組的地址,由程序結果可知數組元素的地址是連續的。int* parray;parray=array;arrayPrint1(array,len);arrayPrint2(parray,len);//通過指針++的方式訪問數組system("pause");return 0;
}
指針數組函數的綜合:
#include <stdio.h>
#include <stdlib.h>
void initScores(int *datas,int size)
{int i;for(i=0;i<size;i++){printf("請輸入第%d個學生的分數\n",i+1);scanf("%d",datas);if(*datas<0 || *datas>100){printf("輸入錯誤,強制退出\n");system("pause");exit(-1);}datas++;}
}
void arrayPrint(int *datas,int size)
{int i;for(i=0;i<size;i++){printf("第%d個學生的成績是%d\n",i+1,*datas++);}
}
int findMax(int *datas,int size)
{int i;int max=0;for(i=0;i<size;i++){if(*datas>max){max=*datas; }datas++;}return max;
}
int findMin(int *datas,int size)
{int i;int min=*datas;for(i=0;i<size;i++){if(*datas<=min){min=*datas; }*datas++;}return min;
}
float findAverge(int *datas,int size)
{int i;int sum=0;float ave;for(i=0;i<size;i++){sum=*datas+sum;datas++;}ave=(float)sum/size;return ave;
}
void printRet(int data1,int data2,float data3)
{printf("最高分是:%d,最低分是:%d,平均分是:%f\n",data1,data2,data3);
}
int main()
{int scores[3];int len;int max;int min;float averge;len=sizeof(scores)/sizeof(scores[0]);//1、初始化數組initScores(scores,len);//函數傳參的過程實際上就是賦值的過程//1.1打印數組arrayPrint(scores,len);//2、找到最高分max=findMax(scores,len);//3、找到最低分min=findMin(scores,len);//4、算出平均分averge=findAverge(scores,len);//5、輸出結果printRet(max,min,averge);system("pause");return 0;
}
為什么要用指針:
①可以將數值存放在固定的地址中
#include <stdio.h>
#include <stdlib.h>
//0060FEF8這個是我的電腦a的地址
//能不能讓a也就是10,強制的保存在我要的地址 比如將a放在0060FEF4地址是十六進制的數
int main()
{int a;//a的地址肯定是系統隨機分配a=10;int *p;p=&a;printf("a's address is %p\n",p);//(int *)0060FEF4表示將這個整型數強制轉化為一個整型數的地址int *p2=(int*)0x0060FEF4;//這種寫法在以后arm架構,裸機編程·,arm驅動可能用到*p2=10;printf("在內存的%p位置,存放值是%d\n",p2,*p2);volatile int *p2=(volatile int*)0x0060FEF4;//volatile是類型修飾符/*寄存器比內存更靠近CPU,所以數據在內存中訪問的更快,當前我們寫的都是單線程的程序從main函數進來,我們定義一個變量是存放在內存中的,這時候我們對變量的訪問是將變量拷貝一份到寄存器,下次cpu訪問的時候直接從寄存器訪問,這樣效率更高,若改變變量的值,它會自動的將變量的值更新到寄存器。若是多線程編程,同樣是變量存放在內存中,兩個線程都可以對變量進行修改(除非對內存做了一些限定),若線程二改變變量的值,他不會通知線程一將變量的值更新到寄存器,這樣CPU用的只就不是最新的值,程序的結果就可能會有問題。而volatile的作用是讓CPU直接訪問內存,不通過寄存器,這樣提高了正確性,但是降低了效率*/system("pause");return 0;
}
volatile和編譯器的優化有關:
編譯器的優化:
在本次線程內,當讀取一個變量時,為了提高讀取速度,編譯器進行優化時會把變量讀取到一個寄存器中,以后再讀取變量值時,就直接從寄存器中讀出;當變量在本線程里改變時,會同時把變量的新值copy到該寄存器中,以保持一致。
當變量因別的線程值發生改變,上面寄存器的值不會相應的改變,從而造成應用程序讀取的值和實際的變量值不一致。
當寄存器因別的線程改變了值,原變量的值也不會改變,也會造成應用程序讀取的值和實際的變量值不一樣。
volatile詳解:博客
②可以通過地址改變變量的值
在這里插入代碼片#include <stdio.h>
#include <stdlib.h>
/*void swap(int a,int b)
{int tmp;tmp=a;a=b;b=tmp;
}這個函數不能交換a和b的值*/
//指針變量本身也有地址,指針變量的值是其他變量的地址
void swap(int *a,int *b)
{int tmp;tmp=*a;*a=*b;*b=tmp;
}
int main()
{int a=5;int b=10;swap(&a,&b);printf("a=%d,b=%d\n",a,b);system("pause");return 0;
}
指針數組(好多個指針放在一個數組里):
#include <stdio.h>
#include <stdlib.h>
void int1Print(int** array)//因為實參本身就是一級指針//所以要想讓數組的首地址作為參數就要用二級指針
{int i;for(i=0;i<3;i++){printf("指針偏移方法:第%d個元素是;%d\n",i+1,**array);//array本身是一級指針//加一個*代表是取指針數組中所存放的地址//加二個*代表取第一個元素地址所存放的值*array++;//表示將指針后移指向數組中下一個地址}
}
void int2Print(int** array)//array是一級指針數組的首地址,它的地址是二級指針
{int i;for(i=0;i<3;i++){printf("數組元素++方法:第%d個元素是;%d\n",i+1,*array[i]);}
}
void address1Print(int** array)
{int i;for(i=0;i<3;i++){printf("第%d個元素是:%p\n",i+1,*array);array++;}
}
void address2Print(int** array)
{int i;for(i=0;i<3;i++){printf("第%d個元素是:%p\n",i+1,array[i]);}
}
int main()
{int a=1;int b=2;int c=3;int i;int array[3];//多個整數組成的數組叫做整數數組int* p;//整形指針變量//定義指針數組格式:類型名* 數組名[] int* parray[3];//指針數組就是多個指針組成的數組,數組中元素是指針變量//指針變量是存放地址的變量parray[0]=&a;parray[1]=&b;parray[2]=&c;//三個普通沒有任何關系的整型變量的地址存入數組int1Print(parray);//通過指針打印數組中地址所存放的數據int2Print(parray);address1Print(parray);//輸出指針數組中的地址address2Print(parray);system("pause");return 0;
}
數組指針(數組的指針):
#include <stdio.h>
#include <stdlib.h>
//上節課說的是“指針數組”,是一個數組里有多個指針
//本次課說的是“數組指針”,一個指針
//不管是何種類型的指針變量,都是存放別人地址的變量
void addressPrint(int* data)
{printf("函數內輸出array2++的地址:%p\n",++data);
}
int main()
{int i;int (*array1)[3];//數組指針的定義,整型數組指針!=整型指針,因為不是一個類型的指針//比如:int* p,int (*array)[],在linux環境下不能p=array//這個數組指針指向的是整個數組,若array1++,偏移的是整個數組所占的字節=3*4=12.//定義格式:數組的類型 (*數組的名字)[]int array2[3]={1,2,3};int* p;//此指針并非是數組指針//僅僅是一個普通的整形指針,剛好指向了數組的首元素地址arrayarray1=array2;p=array2;printf("元素名稱打印數組2的地址是:%p\n",array2);printf("首元素地址打印數組2的地址是:%p\n",&array2[0]);printf("用數組指針賦值打印數組2的地址是:%p\n",array1);printf("用整型指針賦值打印數組2的地址是:%p\n",p);//這幾種方式輸出的地址是一樣的0060FEDCprintf("=================區別如下================\n");//printf("array2++的結果是:%p\n",++p);// printf("array2++的結果是:%p\n",++array2);因為array2是數組名是常量,不能用來++輸出地址,可以將它賦給指針變量++輸出如:printf("array2++的結果是:%p\n",++p);//但是如果將數組名作為參數傳入函數,則可以用++來輸出整個數組的偏移值。如函數addressPrint()addressPrint(array2);//輸出結果:0060FEE0 偏移值:0060FEE0-0060FEDC=4,是一個數組內元素的大小printf("array1++的結果是:%p\n",++array1);//輸出結果:0060FEE8 偏移值:0060FEE8-0060FEDC=12,正好是整個數組的大小system("pause");return 0;
}
函數指針:
#include <stdio.h>
#include <stdlib.h>
//函數指針,顧名思義它是存放一個函數的地址
//如何定義?如何通過指針調用該函數?
void printfWelcome()
{printf("歡迎來到啊哈C\n");
}
int add(int a,int b)
{return a+b;
}
int main()
{int i=10;printf("變量名:i=%d\n",i);//通過變量名來訪問一個變量int* p=&i;printf("指針:i=%d\n",*p);//通過指針訪問變量printfWelcome();//通過函數名來調用函數//如何定義函數指針void (*p2)();// 1、如何表示指針:用* 2、如何知道是函數:用()表示 3、函數指針是專用的,格式要求很強:參數類型,個數,返回值,就像數組指針一樣//如何給函數指針賦值p2=printfWelcome;//這里不能寫括號,函數名就像是地址,就像數組名一樣就是地址//如何通過函數指針調用函數p2();//直接通過指針名字調用(*p2)();//取內容再調用,(*指針名字)()int (*padd)(int a,int b);//定義函數指針,形參可以省略padd=add;printf("結果是:%d\n",padd(1,2));printf("結果是:%d\n",(*padd)(1,2));system("pause");return 0;
}
無類型的指針:
這里引入malloc函數
不對指針進行寫操作就不用malloc給他開辟空間
給新定義的一個指針賦值(指針),也不用給新定義的指針malloc開辟空間
malloc的函數原型是:
extern void *malloc(unsigned int num_bytes);
這個函數返回的是無類型的指針。
頭文件:#include <malloc.h> 或 #include <alloc.h> (注意:alloc.h 與 malloc.h 的內容是完全一致的。)功能:分配長度為num_bytes字節的內存塊說明:如果分配成功則返回指向被分配內存的指針,否則返回空指針NULL。當內存不再使用時,應使用free()函數將內存塊釋放。num_bytes表示大小是為無符號整型一、函數聲明(函數原型):void *malloc(int size);
說明:malloc 向系統申請分配指定size個字節的內存空間。
返回類型是 void* 類型,void* 表示未確定類型的指針。
C,C++規定,void* 類型可以強制轉換為任何其它類型的指針。二、malloc與new的不同點
從函數聲明上可以看出。malloc 和 new 至少有兩個不同:new 返回指定類型的指針,并且可以自動計算所需要大小。
比如:
int *p;p = new int; //返回類型為int* 類型(整數型指針),分配大小為 sizeof(int);或:int* parr;parr = new int [100]; //返回類型為 int* 類型(整數型指針),分配大小為 sizeof(int) * 100;而 malloc 則必須由我們計算要字節數,并且在返回后強行轉換為實際類型的指針。int* p;p = (int *) malloc (sizeof(int));int* p; p++偏移4個字節
char* p; p++偏移1個字節
void* p; p++不知道偏移幾個字節/*int返回的是整型,void并不是沒有返回值,
而是他的返回值是:無類型的,void本身也是一個類型(無類型)*/
第一、malloc 函數返回的是 void * 類型,如果你寫成:p = malloc (sizeof(int)); 則程序無法通過編譯,報錯:“不能將 void* 賦值給 int * 類型變量”。所以必須通過 (int ) 來將強制轉換。
第二、函數的實參為 sizeof(int) ,用于指明一個整型數據需要的大小。如果你寫成:int p = (int ) malloc (1);代碼也能通過編譯,但事實上只分配了1個字節大小的內存空間,當你往里頭存入一個整數,就會有3個字節無家可歸,而直接“住進鄰居家”!造成的結果是后面的內存中原有數據內容全部被清空。malloc 也可以達到 new [] 的效果,申請出一段連續的內存,方法無非是指定你所需要內存大小。比如想分配100個int類型的空間:int p = (int *) malloc ( sizeof(int) * 100 ); //分配可以放得下100個整數的內存空間。另外有一點不能直接看出的區別是,malloc 只管分配內存,并不能對所得的內存進行初始化,所以得到的一片新內存中,其值將是隨機的。除了分配及最后釋放的方法不一樣以外,通過malloc或new得到指針,在其它操作上保持一致。
總結:
malloc()函數其實就在內存中找一片指定大小的空間,然后將這個空間的首地址范圍給一個指針變量,這里的指針變量可以是一個單獨的指針,也可以是一個數組的首地址,這要看malloc()函數中參數size的具體內容。我們這里malloc分配的內存空間在邏輯上連續的,而在物理上可以連續也可以不連續。對于我們程序員來說,我們關注的是邏輯上的連續,因為操作系統會幫我們安排內存分配,所以我們使用起來就可以當做是連續的。
實例演示(內含用戶輸入數組元素個數指針方式):
#include <stdio.h>
#include <stdlib.h>
//指針可以當做數組名來用,作為數組的首地址,嚴格的說不等于數組,但是可以認為它是個數組一樣的使用而不產生任何問題。
//不過既然這樣,那它應該算是個數組吧。所以,一般我們都用“動態數組”這種名字來稱呼這種東西。
//malloc能操作的是程序中的堆空間,而普通的數組則是存放在棧空間里面的。
/* 堆空間是系統內存中的可用區域,和普遍意義上的“堆(Heap)”不同,基本上可以看作是由空閑內存組成的大鏈表。
嗯,操作系統怎么處理這東西不管了,反正你就可以認為堆空間是可用內存里的一片連續區域。malloc函數的作用就是從這一片內存中劃出一塊空間來。
你可以認為是malloc從內存中找到了一片可以安全存放數據的可用空間,這樣你的數據就可以放在這片空間里面。這片空間的大小是你自己指定的。
通過malloc(字節數)這樣簡單的方法。為了找到這片空間,malloc函數會告訴你這片空間開頭的地址,你可以把它賦值給一個變量存放起來。*/
int main()
{//int a[3];/* int* a=(int*)malloc(3*sizeof(int));//通過malloc開辟內存空間,并強制轉換為int類型的指針//a指向開辟連續內存空間的首地址//a僅僅代表開辟的12字節的整型內存空間的首地址,所以可以作為數組名來為內存寫值,如下:int i;for(i=0;i<3;i++){a[i]=i;//i是整型,占用三個字節,malloc開辟的是連續的空間,}for(i=0;i<3;i++){printf("數組打印:%d\n",a[i]);}*/int n,i;printf("請輸入數組的個數:\n");scanf("%d",&n);printf("n=%d\n",n);int* a=(int*)malloc(n*sizeof(int));if(a==NULL){printf("開辟失敗\n");}for(i=0;i<n;i++){printf("請輸入第%d個學生的成績:\n",i+1);scanf("%d",&a[i]);//注意要取地址}for(i=0;i<n;i++){printf("第%d個學生的成績是:%d \n",i+1,*a++);}system("pause");return 0;
}
詳解malloc函數的博客:malloc
什么是內存泄漏?如何避免?
內存泄漏:主要體現是程序剛跑起來很好,跑幾個小時或者幾天或者幾周程序崩潰。
比如:
while(1){sleep(1);//每隔一秒int* p=(int*)malloc(1024);//開辟1M空間,malloc申請的空間,程序不會主動釋放//linux中的話,程序結束后,系統會回收空間,程序不結束,不釋放如何避免:1、注意,循環中有沒有一直申請開辟內存2、及時合理的釋放 free(p) p=NULL;若不寫p=NULL可能會使p變成野指針野指針:是指在定義指針的時候沒有將指針初始化,比如:int* p,p就是野指針,將p=NULL后就不是了}
指針收官之面試題小測:
#include <stdio.h>
#include <stdlib.h>
int main()
{int a;//定義整形變量int* p;//定義p為指向整型數據的指針變量p=NULL;int a[5];//定義數組a,它有5個元素int* p[4];//定義指針數組p,它由4個指向整形數組的指針元素組成int (*p)[4];//p為指向包含4個元素的一維數組的指針變量void* p;//p是一個指針變量,基本類型為void(空類型),不指向具體的對象int add(int,int);//add為返回整型的函數int (*p)(int a);//p為指向數組的指針,該函數返回一個整型int** p;//p是一個指針變量,他指向整型數據的指針變量int* p();//p為一個指針的函數,該指針指向整型數據system("pause");return 0;
}
int add(int a,int b)//返回值是整型的函數
{return a+b;
}