c语言高低位拷贝_C语言指针详解
1為什么使用指針
假如我們定義了 char a=’A’ ,當需要使用 ‘A’ 時,除了直接調用變量 a ,還可以定義 char *p=&a ,調用 a 的地址,即指向 a 的指針 p ,變量 a( char 類型)只占了一個字節,指針本身的大小由可尋址的字長來決定,指針 p 占用 4 個字節。
但如果要引用的是占用內存空間比較大東西,用指針也還是 4 個字節即可。
- 使用指針型變量在很多時候占用更小的內存空間。
變量為了表示數據,指針可以更好的傳遞數據,舉個例子:
第一節課是 1 班語文, 2 班數學,第二節課顛倒過來, 1 班要上數學, 2 班要上語文,那么第一節課下課后需要怎樣作調整呢?方案一:課間 1 班學生全都去 2 班, 2 班學生全都來 1 班,當然,走的時候要攜帶上書本、筆紙、零食……場面一片狼藉;方案二:兩位老師課間互換教室。
顯然,方案二更好一些,方案二類似使用指針傳遞地址,方案一將內存中的內容重新“復制”了一份,效率比較低。
- 在數據傳遞時,如果數據塊較大,可以使用指針傳遞地址而不是實際數據,即提高傳輸速度,又節省大量內存。
一個數據緩沖區 char buf[100] ,如果其中 buf[0,3] 為命令號, buf[4,5] 為類型, buf[6~10] 為該類型的數值,類型為 int ,使用如下語句進行賦值:
*(short*)&buf[0]=DataId; *(short*)&buf[4]=DataType; *(int*)&buf[6]=DataValue;- 數據轉換,利用指針的靈活的類型轉換,可以用來做數據類型轉換,比較常用于通訊緩沖區的填充。
- 指針的機制比較簡單,其功能可以被集中重新實現成更抽象化的引用數據形別
- 函數指針,形如: #define PMYFUN (void*)(int,int) ,可以用在大量分支處理的實例當中,如某通訊根據不同的命令號執行不同類型的命令,則可以建立一個函數指針數組,進行散轉。
- 在數據結構中,鏈表、樹、圖等大量的應用都離不開指針。
2 指針是什么?
操作系統將硬件和軟件結合起來,給程序員提供的一種對內存使用的抽象,這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作和使用真實存在的物理存儲器。所有的虛擬地址形成的集合就是虛擬地址空間。
內存是一個很大的線性的字節數組,每個字節固定由 8 個二進制位組成,每個字節都有唯一的編號,如下圖,這是一個 4G 的內存,他一共有 4x1024x1024x1024 = 4294967296 個字節,那么它的地址范圍就是 0 ~ 4294967296 ,十六進制表示就是 0x00000000~0xffffffff ,當程序使用的數據載入內存時,都有自己唯一的一個編號,這個編號就是這個數據的地址。指針就是這樣形成的。
#include <stdio.h>int main(void) {char ch = 'a';int num = 97;printf("ch 的地址:%pn",&ch); //ch 的地址:00BEFDF7printf("num的地址:%pn",&num); //num的地址:00BEFDF8return 0; }指針不僅可以表示變量的地址,還可以存儲各種類型數據的地址,指針變量是用來保存這些地址的變量,與數組類似,依據地址存放的數據類型,指針也分為 int 指針類型, double 指針類型, char 指針類型等等。
綜上,指針的實質就是數據在內存中的地址,而指針變量是用來保存這些地址的變量。
指針變量 和 指向關系
用來保存 指針 的變量,就是指針變量。如果指針變量p保存了變量 num的地址,則就說:p指向了變量num,也可以說p指向了num所在的內存塊,指針變量pp指向了p所在的內存塊,以下面為例:
#include <stdio.h>int main(void) {int num = 97;char ch = 'a';int *p = & num;int **pp = &p;char *p1 = & ch;printf("num 的地址:%pn",&num); printf("指針p的值:%pn",p); printf("指針p的地址:%pn",&p); printf("指針pp的值:%pn",pp); printf("ch 的地址:%pn",&ch); return 0; }- int型的num值為97占4個字節,內存地址為:0113F924,char 型的ch('a')值為97占1個字節,內存地址為:0113F91B。
- num的地址為:0113F924,num的值為 97 ,指針 p 指向 num 的內存塊,指針 p 地址為:0113F90C,p的內存保存的值就是num的地址0113F924。
- 指針變量 pp 指向 指針 p,指針 pp 內存值為 指針 p 的地址:0113F90C,形成了只想指針的指針。
定義指針變量
C語言中,定義變量時,在變量名 前 寫一個 * 星號,這個變量就變成了對應變量類型的指針變量。必要時要加( ) 來避免優先級的問題。
引申:C語言中,定義變量時,在定義的最前面寫上typedef ,那么這個變量名就成了一種類型,即這個類型的同義詞。
int a ; //int類型變量 a int *a ; //int* 變量a int arr[3]; //arr是包含3個int元素的數組 int (* arr )[3]; //arr是一個指向包含3個int元素的數組的指針變量int* p_int; //指向int類型變量的指針 double* p_double; //指向idouble類型變量的指針 struct Student *p_struct; //結構體類型的指針 int(*p_func)(int,int); //指向返回類型為int,有2個int形參的函數的指針 int(*p_arr)[3]; //指向含有3個int元素的數組的指針 int** p_pointer; //指向 一個整形變量指針的指針取地址
既然有了指針變量,那就得讓他保存其它變量的地址,使用& 運算符取得一個變量的地址。
int add(int a , int b) {return a + b; }int main(void) {int num = 97;float score = 10.00F;int arr[3] = {1,2,3};int* p_num = #float* p_score = &score;int (*p_arr)[3] = &arr; int (*fp_add)(int ,int ) = add; //p_add是指向函數add的函數指針return 0; }特殊的情況,他們并不一定需要使用&取地址:
- 數組名的值就是這個數組的第一個元素的地址。
- 函數名的值就是這個函數的地址。
- 字符串字面值常量作為右值時,就是這個字符串對應的字符數組的名稱,也就是這個字符串在內存中的地址。
解地址
對一個指針解地址,就可以取到這個內存數據,解地址 的寫法,就是在指針的前面加一個 * 號。
解指針的實質是:從指針指向的內存塊中取出這個內存數據。
int main(void) {int age = 19;int*p_age = &age;*p_age = 20; //通過指針修改指向的內存數據printf("age = %d",*p_age); //通過指針讀取指向的內存數據printf("age = %d",age);return 0; }空指針
空指針在概念上不同于未初始化的指針。空指針可以確保不指向任何對象或函數;而未初始化的指針則可能指向任何地方。空指針不是野指針。
在C語言中,我們讓指針變量賦值為NULL表示一個空指針,而C語言中,NULL實質是 ((void*)0) , 在C++中,NULL實質是0。
#ifdef __cplusplus#define NULL 0 #else #define NULL ((void *)0) #endifvoid*類型指針
void是一種特殊的指針類型,可以用來存放任意對象的地址。一個void指針存放著一個地址,這一點和其他指針類似。不同的是,我們對它到底儲存的是什么對象的地址并不了解。
double a=2.3; int b=5; void *p=&a; cout<<p<<endl; //輸出了a的地址p=&b; cout<<p<<endl; //輸出了b的地址//cout<<*p<<endl;這一行不可以執行,void*指針只可以儲存變量地址,不可以直接操作它指向的對象由于void是空類型,只保存了指針的值,而丟失了類型信息,我們不知道他指向的數據是什么類型的,只指定這個數據在內存中的起始地址,如果想要完整的提取指向的數據,程序員就必須對這個指針做出正確的類型轉換,然后再解指針。
數組和指針
- 同類型指針變量可以相互賦值,數組不行,只能一個一個元素的賦值或拷貝
- 數組在內存中是連續存放的,開辟一塊連續的內存空間。數組是根據數組的下進行訪問的。指針很靈活,它可以指向任意類型的數據。指針的類型說明了它所指向地址空間的內存。
- 數組所占存儲空間的內存:sizeof(數組名) 數組的大小:sizeof(數組名)/sizeof(數據類型),在32位平臺下,無論指針的類型是什么,sizeof(指針名)都是 4 ,在 64 位平臺下,無論指針的類型是什么,sizeof(指針名)都是 8 。
- 數組名作為右值的時候,就是第一個元素的地址
- 指向數組元素的指針 支持 遞增 遞減 運算。p= p+1意思是,讓p指向原來指向的內存塊的下一個相鄰的相同類型的內存塊。在數組中相鄰內存就是相鄰下標元素。
函數與指針
函數的參數和指針
C語言中,實參傳遞給形參,是按值傳遞的,也就是說,函數中的形參是實參的拷貝份,形參和實參只是在值上面一樣,而不是同一個內存數據對象。這就意味著:這種數據傳遞是單向的,即從調用者傳遞給被調函數,而被調函數無法修改傳遞的參數達到回傳的效果。
void change(int a) {a++; //在函數中改變的只是這個函數的局部變量a,而隨著函數執行結束,a被銷毀。age還是原來的age,紋絲不動。 } int main(void) {int age = 60;change(age);printf("age = %d",age); // age = 60return 0; }有時候我們可以使用函數的返回值來回傳數據,在簡單的情況下是可以的,但是如果返回值有其它用途(例如返回函數的執行狀態量),或者要回傳的數據不止一個,返回值就解決不了了。
傳遞變量的指針可以輕松解決上述問題。
void change(int* pa) {(*pa)++; //因為傳遞的是age的地址,因此pa指向內存數據age。當在函數中對指針pa解地址時,//會直接去內存中找到age這個數據,然后把它增1。 } int main(void) {int age = 160;change(&age);printf("age = %d",age); // age = 61return 0; }比如指針的一個常見的使用例子:
#include <stdio.h> #include <stdlib.h> #include <string.h>void swap(int *,int *); int main() {int a=5,b=10;printf("a=%d,b=%dn",a,b);swap(&a,&b);printf("a=%d,b=%dn",a,b);return 0; } void swap(int *pa,int *pb) {int t=*pa;*pa=*pb;*pb=t; }在以上的例子中,swap函數的兩個形參pa和pb可以接收兩個整型變量的地址,并通過間接訪問的方式修改了它指向變量的值。在main函數中調用swap時,提供的實參分別為&a,&b,這樣就實現了pa=&a,pb=&b的賦值過程,這樣在swap函數中就通過*pa修改了 a 的值,通過*pb修改了 b 的值。因此,如果需要在被調函數中修改主調函數中變量的值,就需要經過以下幾個步驟:
- 定義函數的形參必須為指針類型,以接收主調函數中傳來的變量的地址;
- 調用函數時實參為變量的地址;
- 在被調函數中使用*間接訪問形參指向的內存空間,實現修改主調函數中變量值的功能。
指針作為函數的形參的另一個典型應用是當函數有多個返回值的情形。比如,需要在一個函數中統計一個數組的最大值、最小值和平均值。當然你可以編寫三個函數分別完成統計三個值的功能。但比較啰嗦,如:
int GetMax(int a[],int n) {int max=a[0],i;for(i=1;i<n;i++){if(max<a[i]) max=a[i];}return max; } int GetMin(int a[],int n) {int min=a[0],i;for(i=1;i<n;i++){if(min>a[i]) min=a[i];}return min; } double GetAvg(int a[],int n) {double avg=0;int i;for(i=0;i<n;i++){avg+=a[i];}return avg/n; }其實我們完全可以在一個函數中完成這個功能,由于函數只能有一個返回值,可以返回平均值,最大值和最小值可以通過指針類型的形參來進行實現:
double Stat(int a[],int n,int *pmax,int *pmin) {double avg=a[0];int i;*pmax=*pmin=a[0];for(i=1;i<n;i++){avg+=a[i];if(*pmax<a[i]) *pmax=a[i];if(*pmin>a[i]) *pmin=a[i];}return avg/n; }函數的指針
一個函數總是占用一段連續的內存區域,函數名在表達式中有時也會被轉換為該函數所在內存區域的首地址。我們可以把函數的這個首地址賦予一個指針變量,使指針變量指向函數所在的內存區域,然后通過指針變量就可以找到并調用該函數。這種指針就是函數指針。
函數指針的定義形式為:
returnType (*pointerName)(param list);
returnType 為函數返回值類型,pointerNmae 為指針名稱,param list 為函數參數列表。參數列表中可以同時給出參數的類型和名稱,也可以只給出參數的類型,省略參數的名稱,這一點和函數原型非常類似。
用指針來實現對函數的調用:
#include <stdio.h> //返回兩個數中較大的一個 int max(int a, int b) {return a>b ? a : b; } int main() {int x, y, maxval;//定義函數指針int (*pmax)(int, int) = max; //也可以寫作int (*pmax)(int a, int b)printf("Input two numbers:");scanf("%d %d", &x, &y);maxval = (*pmax)(x, y);printf("Max value: %dn", maxval);return 0; }結構體和指針
結構體指針有特殊的語法: -> 符號
如果p是一個結構體指針,則可以使用 p ->【成員】 的方法訪問結構體的成員
typedef struct {char name[31];int age;float score; }Student;int main(void) {Student stu = {"Bob" , 19, 98.0};Student*ps = &stu;ps->age = 20;ps->score = 99.0;printf("name:%s age:%d ",ps->name,ps->age);return 0; }const 和 指針
- 指向常量的指針,值不能改變,指向可改變
- 常指針值能改變,指向不可改變
- 指向常量的常指針,都不能改變
深拷貝和淺拷貝
如果2個程序單元(例如2個函數)是通過拷貝 他們所共享的數據的 指針來工作的,這就是淺拷貝,因為真正要訪問的數據并沒有被拷貝。如果被訪問的數據被拷貝了,在每個單元中都有自己的一份,對目標數據的操作相互 不受影響,則叫做深拷貝。
#include <iostream> using namespace std;class CopyDemo { public:CopyDemo(int pa,char *cstr) //構造函數,兩個參數{this->a = pa;this->str = new char[1024]; //指針數組,動態的用new在堆上分配存儲空間strcpy(this->str,cstr); //拷貝過來}//沒寫,C++會自動幫忙寫一個復制構造函數,淺拷貝只復制指針,如下注釋部分//CopyDemo(CopyDemo& obj) //{// this->a = obj.a;// this->str = obj.str; //這里是淺復制會出問題,要深復制//}CopyDemo(CopyDemo& obj) //一般數據成員有指針要自己寫復制構造函數,如下{this->a = obj.a;// this->str = obj.str; //這里是淺復制會出問題,要深復制this->str = new char[1024];//應該這樣寫if(str != 0)strcpy(this->str,obj.str); //如果成功,把內容復制過來}~CopyDemo() //析構函數{delete str;}public:int a; //定義一個整型的數據成員char *str; //字符串指針 };int main() {CopyDemo A(100,"hello!!!");CopyDemo B = A; //復制構造函數,把A的10和hello!!!復制給Bcout <<"A:"<< A.a << "," <<A.str << endl;//輸出A:100,hello!!!cout <<"B:"<< B.a << "," <<B.str << endl;//輸出B:100,hello!!!//修改后,發現A,B都被改變,原因就是淺復制,A,B指針指向同一地方,修改后都改變B.a = 80;B.str[0] = 'k';cout <<"A:"<< A.a << "," <<A.str << endl;//輸出A:100,kello!!!cout <<"B:"<< B.a << "," <<B.str << endl;//輸出B:80,kello!!!return 0; }根據上面實例可以看到,淺復制僅復制對象本身(其中包括是指針的成員),這樣不同被復制對象的成員中的對應非空指針會指向同一對象,被成員指針引用的對象成為共享的,無法直接通過指針成員安全地刪除(因為若直接刪除,另外對象中的指針就會無效,形成所謂的野指針,而訪問無效指針是危險的;
除非這些指針有引用計數或者其它手段確保被指對象的所有權);而深復制在淺復制的基礎上,連同指針指向的對象也一起復制,代價比較高,但是相對容易管理。
總結
以上是生活随笔為你收集整理的c语言高低位拷贝_C语言指针详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux c段错误,Linux C中段
- 下一篇: linux 32bit swt,无法在W