熬夜整理的c/c++萬字總結(二),值得收藏!

0.爲何使用指針

假如咱們定義了 char a=’A’ ,當須要使用 ‘A’ 時,除了直接調用變量 a ,還能夠定義 char *p=&a ,調用 a 的地址,即指向 a 的指針 p ,變量 a( char 類型)只佔了一個字節,指針自己的大小由可尋址的字長來決定,指針 p 佔用 4 個字節。html

這必定是你須要的電子書資源,全!點擊查看!ios

程序員書籍資源,點擊查看!程序員

但若是要引用的是佔用內存空間比較大東西,用指針也仍是 4 個字節便可。編程

使用指針型變量在不少時候佔用更小的內存空間。 變量爲了表示數據,指針能夠更好的傳遞數據,舉個例子:數組

第一節課是 1 班語文, 2 班數學,第二節課顛倒過來, 1 班要上數學, 2 班要上語文,那麼第一節課下課後須要怎樣做調整呢?方案一:課間 1 班學生全都去 2 班, 2 班學生全都來 1 班,固然,走的時候要攜帶上書本、筆紙、零食……場面一片狼藉;方案二:兩位老師課間互換教室。安全

顯然,方案二更好一些,方案二相似使用指針傳遞地址,方案一將內存中的內容從新「複製」了一份,效率比較低。數據結構

  • 在數據傳遞時,若是數據塊較大,可使用指針傳遞地址而不是實際數據,即提升傳輸速度,又節省大量內存。函數

一個數據緩衝區 char buf[100] ,若是其中 buf[0,1] 爲命令號, buf[2,3] 爲數據類型, buf[4~7] 爲該類型的數值,類型爲 int ,使用以下語句進行賦值:測試

*(short*)&buf[0]=DataId;
*(short*)&buf[2]=DataType;
*(int*)&buf[4]=DataValue;
  • 數據轉換,利用指針的靈活的類型轉換,能夠用來作數據類型轉換,比較經常使用於通信緩衝區的填充。this

  • 指針的機制比較簡單,其功能能夠被集中從新實現成更抽象化的引用數據形式

  • 函數指針,形如: #define PMYFUN (void*)(int,int) ,能夠用在大量分支處理的實例當中,如某通信根據不一樣的命令號執行不一樣類型的命令,則能夠創建一個函數指針數組,進行散轉。

  • 在數據結構中,鏈表、樹、圖等大量的應用都離不開指針。

1. 指針強化

1.1 指針是一種數據類型

操做系統將硬件和軟件結合起來,給程序員提供的一種對內存使用的抽象,這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操做和使用真實存在的物理存儲器。全部的虛擬地址造成的集合就是虛擬地址空間。

內存是一個很大的線性的字節數組,每一個字節固定由 8 個二進制位組成,每一個字節都有惟一的編號,以下圖,這是一個 4G 的內存,他一共有 4x1024x1024x1024 = 4294967296 個字節,那麼它的地址範圍就是 0 ~ 4294967296 ,十六進制表示就是 0x00000000~0xffffffff ,當程序使用的數據載入內存時,都有本身惟一的一個編號,這個編號就是這個數據的地址。指針就是這樣造成的。

1.1.1 指針變量

指針是一種數據類型,佔用內存空間,用來保存內存地址。

void test01(){
 
 int* p1 = 0x1234;
 int*** p2 = 0x1111;

 printf("p1 size:%d\n",sizeof(p1));
 printf("p2 size:%d\n",sizeof(p2));


 //指針是變量,指針自己也佔內存空間,指針也能夠被賦值
 int a = 10;
 p1 = &a;

 printf("p1 address:%p\n", &p1);
 printf("p1 address:%p\n", p1);
 printf("a address:%p\n", &a);

}

1.1.2 野指針和空指針

1.1.2.1 空指針

標準定義了NULL指針,它做爲一個特殊的指針變量,表示不指向任何東西。要使一個指針爲NULL,能夠給它賦值一個零值。爲了測試一個指針百年來那個是否爲NULL,你能夠將它與零值進行比較。

對指針解引用操做能夠得到它所指向的值。但從定義上看,NULL指針並未指向任何東西,由於對一個NULL指針因引用是一個非法的操做,在解引用以前,必須確保它不是一個NULL指針。

若是對一個NULL指針間接訪問會發生什麼呢?結果因編譯器而異。 不容許向NULL和非法地址拷貝內存:

void test(){
 char *p = NULL;
 //給p指向的內存區域拷貝內容
 strcpy(p, "1111"); //err

 char *q = 0x1122;
 //給q指向的內存區域拷貝內容
 strcpy(q, "2222"); //err  
}

1.1.2.2 野指針

在使用指針時,要避免野指針的出現:

野指針指向一個已刪除的對象或未申請訪問受限內存區域的指針。與空指針不一樣,野指針沒法經過簡單地判斷是否爲 NULL避免,而只能經過養成良好的編程習慣來盡力減小。對野指針進行操做很容易形成程序錯誤。

什麼狀況下會致使野指針?

  • 指針變量未初始化

任何指針變量剛被建立時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。因此,指針變量在建立的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它指向合法的內存。

  • 指針釋放後未置空

有時指針在free或delete後未賦值 NULL,便會令人覺得是合法的。別看free和delete的名字(尤爲是delete),它們只是把指針所指的內存給釋放掉,但並無把指針自己幹掉。此時指針指向的就是「垃圾」內存。釋放後的指針應當即將指針置爲NULL,防止產生「野指針」。

  • 指針操做超越變量做用域

不要返回指向棧內存的指針或引用,由於棧內存在函數結束時會被釋放。

void test(){
 int* p = 0x001; //未初始化
 printf("%p\n",p);
 *p = 100;
}

操做野指針是很是危險的操做,應該規避野指針的出現:

  • 初始化時置 NULL

指針變量必定要初始化爲NULL,由於任何指針變量剛被建立時不會自動成爲NULL指針,它的缺省值是隨機的。

  • 釋放時置 NULL

當指針p指向的內存空間釋放時,沒有設置指針p的值爲NULL。delete和free只是把內存空間釋放了,可是並無將指針p的值賦爲NULL。一般判斷一個指針是否合法,都是使用if語句測試該指針是否爲NULL。

1.1.2.3 void*類型指針

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是空類型,只保存了指針的值,而丟失了類型信息,咱們不知道他指向的數據是什麼類型的,只指定這個數據在內存中的起始地址,若是想要完整的提取指向的數據,程序員就必須對這個指針作出正確的類型轉換,而後再解指針。

1.1.2.4 void*數組和指針

  • 同類型指針變量能夠相互賦值,數組不行,只能一個一個元素的賦值或拷貝

  • 數組在內存中是連續存放的,開闢一塊連續的內存空間。數組是根據數組的下進行訪問的。指針很靈活,它能夠指向任意類型的數據。指針的類型說明了它所指向地址空間的內存。

  • 數組所佔存儲空間的內存:sizeof(數組名) 數組的大小:sizeof(數組名)/sizeof(數據類型),在32位平臺下,不管指針的類型是什麼,sizeof(指針名)都是 4 ,在 64 位平臺下,不管指針的類型是什麼,sizeof(指針名)都是 8 。

  • 數組名做爲右值的時候,就是第一個元素的地址

int main(void)
{
    int arr[5] = {1,2,3,4,5};

    int *p_first = arr;
    printf("%d",*p_first);  //1
    return 0;
}
  • 指向數組元素的指針 支持 遞增 遞減 運算。p= p+1意思是,讓p指向原來指向的內存塊的下一個相鄰的相同類型的內存塊。在數組中相鄰內存就是相鄰下標元素。

1.1.3 間接訪問操做符

經過一個指針訪問它所指向的地址的過程叫作間接訪問,或者叫解引用指針,這個用於執行間接訪問的操做符是*。

注意:對一個int類型指針解引用會產生一個整型值,相似地,對一個float指針解引用會產生了一個float類型的值。

int arr[5];
int *p = * (&arr);
int arr1[5][3] arr1 = int(*)[3]&arr1

1)在指針聲明時,* 號表示所聲明的變量爲指針

2)在指針使用時,* 號表示操做指針所指向的內存空間

  • *至關經過地址(指針變量的值)找到指針指向的內存,再操做內存

  • *放在等號的左邊賦值(給內存賦值,寫內存)

  • *放在等號的右邊取值(從內存中取值,讀內存)

//解引用
void test01(){

 //定義指針
 int* p = NULL;
 //指針指向誰,就把誰的地址賦給指針
 int a = 10;
 p = &a;
 *p = 20;//*在左邊當左值,必須確保內存可寫
 //*號放右面,從內存中讀值
 int b = *p;
 //必須確保內存可寫
 char* str = "hello world!";
 *str = 'm';

 printf("a:%d\n", a);
 printf("*p:%d\n", *p);
 printf("b:%d\n", b);
}

1.1.4 指針的步長

指針是一種數據類型,是指它指向的內存空間的數據類型。指針所指向的內存空間決定了指針的步長。指針的步長指的是,當指針+1時候,移動多少字節單位。

思考以下問題:

int a = 0xaabbccdd;
unsigned int *p1 = &a;
unsigned char *p2 = &a;

//爲何*p1打印出來正確結果?
printf("%x\n", *p1);
//爲何*p2沒有打印出來正確結果?
printf("%x\n", *p2);

//爲何p1指針+1加了4字節?
printf("p1  =%d\n", p1);
printf("p1+1=%d\n", p1 + 1);
//爲何p2指針+1加了1字節?
printf("p2  =%d\n", p2);
printf("p2+1=%d\n", p2 + 1);

1.1.5 函數與指針

1.1.5.1 函數的參數和指針

C語言中,實參傳遞給形參,是按值傳遞的,也就是說,函數中的形參是實參的拷貝份,形參和實參只是在值上面同樣,而不是同一個內存數據對象。這就意味着:這種數據傳遞是單向的,即從調用者傳遞給被調函數,而被調函數沒法修改傳遞的參數達到回傳的效果。

void change(int a)
{
    a++;      //在函數中改變的只是這個函數的局部變量a,而隨着函數執行結束,a被銷燬。age仍是原來的age,紋絲不動。
}
int main(void)
{
    int age = 60;
    change(age);
    printf("age = %d",age);   // age = 60
    return 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 = 61
    return 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=%d\n",a,b);
    swap(&a,&b);
    printf("a=%d,b=%d\n",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;
}

1.1.5.2 函數的指針

一個函數老是佔用一段連續的內存區域,函數名在表達式中有時也會被轉換爲該函數所在內存區域的首地址。咱們能夠把函數的這個首地址賦予一個指針變量,使指針變量指向函數所在的內存區域,而後經過指針變量就能夠找到並調用該函數。這種指針就是函數指針。

函數指針的定義形式爲:

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: %d\n", maxval);
    return 0;
}

1.1.5.3 結構體和指針

結構體指針有特殊的語法: -> 符號

若是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;
}

1.2 指針的意義_間接賦值

1.2.1 間接賦值的三大條件

經過指針間接賦值成立的三大條件:

  • 2個變量(一個普通變量一個指針變量、或者一個實參一個形參)

  • 創建關係

  • 經過 * 操做指針指向的內存

void test(){
 int a = 100; //兩個變量
 int *p = NULL;
 //創建關係
 //指針指向誰,就把誰的地址賦值給指針
 p = &a;
 //經過*操做內存
 *p = 22;
}

1.2.2 如何定義合適的指針變量

void test(){
 int b;  
 int *q = &b; //0級指針
 int **t = &q;
 int ***m = &t;
}

1.2.3 間接賦值:從0級指針到1級指針

int func1(){ return 10; }

void func2(int a){
 a = 100;
}
//指針的意義_間接賦值
void test02(){
 int a = 0;
 a = func1();
 printf("a = %d\n", a);

 //爲何沒有修改?
 func2(a);
 printf("a = %d\n", a);
}

//指針的間接賦值
void func3(int* a){
 *a = 100;
}

void test03(){
 int a = 0;
 a = func1();
 printf("a = %d\n", a);

 //修改
 func3(&a);
 printf("a = %d\n", a);
}

1.2.4 間接賦值:從1級指針到2級指針

void AllocateSpace(char** p){
 *p = (char*)malloc(100);
 strcpy(*p, "hello world!");
}

void FreeSpace(char** p){

 if (p == NULL){
  return;
 }
 if (*p != NULL){
  free(*p);
  *p = NULL;
 }

}

void test(){
 
 char* p = NULL;

 AllocateSpace(&p);
 printf("%s\n",p);
 FreeSpace(&p);

 if (p == NULL){
  printf("p內存釋放!\n");
 }
}

1.2.4 間接賦值的推論

  • 用1級指針形參,去間接修改了0級指針(實參)的值。

  • 用2級指針形參,去間接修改了1級指針(實參)的值。

  • 用3級指針形參,去間接修改了2級指針(實參)的值。

  • 用n級指針形參,去間接修改了n-1級指針(實參)的值。

1.3 指針作函數參數

指針作函數參數,具有輸入和輸出特性:

  • 輸入:主調函數分配內存

  • 輸出:被調用函數分配內存

1.3.1 輸入特性

void fun(char *p /* in */)
{
 //給p指向的內存區域拷貝內容
 strcpy(p, "abcddsgsd");
}

void test(void)
{
 //輸入,主調函數分配內存
 char buf[100] = { 0 };
 fun(buf);
 printf("buf  = %s\n", buf);
}

1.3.2 輸出特性

void fun(char **p /* out */, int *len)
{
 char *tmp = (char *)malloc(100);
 if (tmp == NULL)
 {
  return;
 }
 strcpy(tmp, "adlsgjldsk");

 //間接賦值
 *p = tmp;
 *len = strlen(tmp);
}

void test(void)
{
 //輸出,被調用函數分配內存,地址傳遞
 char *p = NULL;
 int len = 0;
 fun(&p, &len);
 if (p != NULL)
 {
  printf("p = %s, len = %d\n", p, len);
 }
}

1.4 字符串指針強化

1.4.1 字符串指針作函數參數

1.4.1.1 字符串基本操做

//字符串基本操做
//字符串是以0或者'\0'結尾的字符數組,(數字0和字符'\0'等價)
void test01(){

 //字符數組只能初始化5個字符,當輸出的時候,從開始位置直到找到0結束
 char str1[] = { 'h''e''l''l''o' };
 printf("%s\n",str1);

 //字符數組部分初始化,剩餘填0
 char str2[100] = { 'h''e''l''l''o' };
 printf("%s\n", str2);

 //若是以字符串初始化,那麼編譯器默認會在字符串尾部添加'\0'
 char str3[] = "hello";
 printf("%s\n",str3);
 printf("sizeof str:%d\n",sizeof(str3));
 printf("strlen str:%d\n",strlen(str3));

 //sizeof計算數組大小,數組包含'\0'字符
 //strlen計算字符串的長度,到'\0'結束

 //那麼若是我這麼寫,結果是多少呢?
 char str4[100] = "hello";
 printf("sizeof str:%d\n", sizeof(str4));
 printf("strlen str:%d\n", strlen(str4));

 //請問下面輸入結果是多少?sizeof結果是多少?strlen結果是多少?
 char str5[] = "hello\0world"; 
 printf("%s\n",str5);
 printf("sizeof str5:%d\n",sizeof(str5));
 printf("strlen str5:%d\n",strlen(str5));

 //再請問下面輸入結果是多少?sizeof結果是多少?strlen結果是多少?
 char str6[] = "hello\012world";
 printf("%s\n", str6);
 printf("sizeof str6:%d\n", sizeof(str6));
 printf("strlen str6:%d\n", strlen(str6));
}

八進制和十六進制轉義字符:

在C中有兩種特殊的字符,八進制轉義字符和十六進制轉義字符,八進制字符的通常形式是'\ddd',d是0-7的數字。十六進制字符的通常形式是'\xhh',h是0-9A-F內的一個。八進制字符和十六進制字符表示的是字符的ASCII碼對應的數值。

好比 :

  • '\063'表示的是字符'3',由於'3'的ASCII碼是30(十六進制),48(十進制),63(八進制)。

  • '\x41'表示的是字符'A',由於'A'的ASCII碼是41(十六進制),65(十進制),101(八進制)。

1.4.1.2 字符串拷貝功能實現

//拷貝方法1
void copy_string01(char* dest, char* source ){

 for (int i = 0; source[i] != '\0';i++){
  dest[i] = source[i];
 }

}

//拷貝方法2
void copy_string02(char* dest, char* source){
 while (*source != '\0' /* *source != 0 */){
  *dest = *source;
  source++;
  dest++;
 }
}

//拷貝方法3
void copy_string03(char* dest, char* source){
 //判斷*dest是否爲0,0則退出循環
 while (*dest++ = *source++){}
}

1.4.1.3 字符串反轉模型

void reverse_string(char* str){

 if (str == NULL){
  return;
 }

 int begin = 0;
 int end = strlen(str) - 1;
 
 while (begin < end){
  
  //交換兩個字符元素
  char temp = str[begin];
  str[begin] = str[end];
  str[end] = temp;

  begin++;
  end--;
 }

}

void test(){
 char str[] = "abcdefghijklmn";
 printf("str:%s\n", str);
 reverse_string(str);
 printf("str:%s\n", str);
}

1.4.2 字符串的格式化

1.4.2.1 sprintf

#include <stdio.h>
int sprintf(char *str, const char *format, ...);

功能:根據參數format字符串來轉換並格式化數據,而後將結果輸出到str指定的空間中,直到 出現字符串結束符 '\0' 爲止。

參數

  • str:字符串首地址

  • format:字符串格式,用法和printf()同樣

返回值

  • 成功:實際格式化的字符個數

  • 失敗: - 1

void test(){
 
 //1. 格式化字符串
 char buf[1024] = { 0 };
 sprintf(buf, "你好,%s,歡迎加入咱們!""John");
 printf("buf:%s\n",buf);

 memset(buf, 0, 1024);
 sprintf(buf, "我今年%d歲了!", 20);
 printf("buf:%s\n", buf);

 //2. 拼接字符串
 memset(buf, 0, 1024);
 char str1[] = "hello";
 char str2[] = "world";
 int len = sprintf(buf,"%s %s",str1,str2);
 printf("buf:%s len:%d\n", buf,len);

 //3. 數字轉字符串
 memset(buf, 0, 1024);
 int num = 100;
 sprintf(buf, "%d", num);
 printf("buf:%s\n", buf);
 //設置寬度 右對齊
 memset(buf, 0, 1024);
 sprintf(buf, "%8d", num);
 printf("buf:%s\n", buf);
 //設置寬度 左對齊
 memset(buf, 0, 1024);
 sprintf(buf, "%-8d", num);
 printf("buf:%s\n", buf);
 //轉成16進制字符串 小寫
 memset(buf, 0, 1024);
 sprintf(buf, "0x%x", num);
 printf("buf:%s\n", buf);

 //轉成8進制字符串
 memset(buf, 0, 1024);
 sprintf(buf, "0%o", num);
 printf("buf:%s\n", buf);
}

1.4.2.2 sscanf

#include <stdio.h>
int sscanf(const char *str, const char *format, ...);

功能:從str指定的字符串讀取數據,並根據參數format字符串來轉換並格式化數據。

參數

  • str:指定的字符串首地址

  • format:字符串格式,用法和scanf()同樣

返回值

  • 成功:成功則返回參數數目,失敗則返回-1

  • 失敗: - 1

//1. 跳過數據
void test01(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 //匹配第一個字符是不是數字,若是是,則跳過
 //若是不是則中止匹配
 sscanf("123456aaaa""%*d%s", buf); 
 printf("buf:%s\n",buf);
}

//2. 讀取指定寬度數據
void test02(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 sscanf("123456aaaa""%7s", buf);
 printf("buf:%s\n", buf);
}

//3. 匹配a-z中任意字符
void test03(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 //先匹配第一個字符,判斷字符是不是a-z中的字符,若是是匹配
 //若是不是中止匹配
 sscanf("abcdefg123456""%[a-z]", buf);
 printf("buf:%s\n", buf);
}

//4. 匹配aBc中的任何一個
void test04(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 //先匹配第一個字符是不是aBc中的一個,若是是,則匹配,若是不是則中止匹配
 sscanf("abcdefg123456""%[aBc]", buf);
 printf("buf:%s\n", buf);
}

//5. 匹配非a的任意字符
void test05(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 //先匹配第一個字符是不是aBc中的一個,若是是,則匹配,若是不是則中止匹配
 sscanf("bcdefag123456""%[^a]", buf);
 printf("buf:%s\n", buf);
}

//6. 匹配非a-z中的任意字符
void test06(){
 char buf[1024] = { 0 };
 //跳過前面的數字
 //先匹配第一個字符是不是aBc中的一個,若是是,則匹配,若是不是則中止匹配
 sscanf("123456ABCDbcdefag""%[^a-z]", buf);
 printf("buf:%s\n", buf);
}

1.5 一級指針易錯點

1.5.1 越界

void test(){
 char buf[3] = "abc";
 printf("buf:%s\n",buf);
}

1.5.2 指針疊加會不斷改變指針指向

void test(){
 char *p = (char *)malloc(50);
 char buf[] = "abcdef";
 int n = strlen(buf);
 int i = 0;

 for (i = 0; i < n; i++)
 {
  *p = buf[i];
  p++; //修改原指針指向
 }

 free(p);
}

1.5.3 返回局部變量地址

char *get_str()
{
 char str[] = "abcdedsgads"; //棧區,
 printf("[get_str]str = %s\n", str);
 return str;
}

1.5.4 同一塊內存釋放屢次(不能夠釋放野指針)

void test(){ 
 char *p = NULL;

 p = (char *)malloc(50);
 strcpy(p, "abcdef");

 if (p != NULL)
 {
  //free()函數的功能只是告訴系統 p 指向的內存能夠回收了
  // 就是說,p 指向的內存使用權交還給系統
  //可是,p的值仍是原來的值(野指針),p仍是指向原來的內存
  free(p); 
 }

 if (p != NULL)
 {
  free(p);
 }
}

1.6 const使用

//const修飾變量
void test01(){
 //1. const基本概念
 const int i = 0;
 //i = 100; //錯誤,只讀變量初始化以後不能修改

 //2. 定義const變量最好初始化
 const int j;
 //j = 100; //錯誤,不能再次賦值

 //3. c語言的const是一個只讀變量,並非一個常量,可經過指針間接修改
 const int k = 10;
 //k = 100; //錯誤,不可直接修改,咱們可經過指針間接修改
 printf("k:%d\n", k);
 int* p = &k;
 *p = 100;
 printf("k:%d\n", k);
}

//const 修飾指針
void test02(){

 int a = 10;
 int b = 20;
 //const放在*號左側 修飾p_a指針指向的內存空間不能修改,但可修改指針的指向
 const int* p_a = &a;
 //*p_a = 100; //不可修改指針指向的內存空間
 p_a = &b; //可修改指針的指向

 //const放在*號的右側, 修飾指針的指向不能修改,可是可修改指針指向的內存空間
 int* const p_b = &a;
 //p_b = &b; //不可修改指針的指向
 *p_b = 100; //可修改指針指向的內存空間

 //指針的指向和指針指向的內存空間都不能修改
 const int* const p_c = &a;
}
//const指針用法
struct Person{
 char name[64];
 int id;
 int age;
 int score;
};

//每次都對對象進行拷貝,效率低,應該用指針
void printPersonByValue(struct Person person){
 printf("Name:%s\n", person.name);
 printf("Name:%d\n", person.id);
 printf("Name:%d\n", person.age);
 printf("Name:%d\n", person.score);
}

//可是用指針會有反作用,可能會不當心修改原數據
void printPersonByPointer(const struct Person *person){
 printf("Name:%s\n", person->name);
 printf("Name:%d\n", person->id);
 printf("Name:%d\n", person->age);
 printf("Name:%d\n", person->score);
}
void test03(){
 struct Person p = { "Obama", 1101, 23, 87 };
 //printPersonByValue(p);
 printPersonByPointer(&p);
}

2. 指針的指針(二級指針)

2.1 二級指針基本概念

這裏讓咱們花點時間來看一個例子,揭開這個即將開始的序幕。考慮下面這些聲明:

int a = 12;
int *b = &a;

它們以下圖進行內存分配:

假定咱們又有了第3個變量,名叫c,並用下面這條語句對它進行初始化:

c = &b;

它在內存中的大概模樣大體以下:

c的類型是什麼?顯然它是一個指針,但它所指向的是什麼?

變量b是一個「指向整型的指針」,因此任何指向b的類型必須是指向「指向整型的指針」的指針,更通俗地說,是一個指針的指針。

它合法嗎?

是的!指針變量和其餘變量同樣,佔據內存中某個特定的位置,因此用&操做符取得它的地址是合法的。

那麼這個變量的聲明是怎樣的聲明的呢?

int **c = &b;

那麼這個**c如何理解呢?操做符具備從右想作的結合性,因此這個表達式至關於(*c),咱們從裏向外逐層求職。*c訪問c所指向的位置,咱們知道這是變量b.第二個間接訪問操做符訪問這個位置所指向的地址,也就是變量a.指針的指針並不難懂,只須要留心全部的箭頭,若是表達式中出現了間接訪問操做符,你就要隨箭頭訪問它所指向的位置。

2.2 二級指針作形參輸出特性

二級指針作參數的輸出特性是指由被調函數分配內存。

//被調函數,由參數n肯定分配多少個元素內存
void allocate_space(int **arr,int n){
 //堆上分配n個int類型元素內存
 int *temp = (int *)malloc(sizeof(int)* n);
 if (NULL == temp){
  return;
 }
 //給內存初始化值
 int *pTemp = temp;
 for (int i = 0; i < n;i ++){
  //temp[i] = i + 100;
  *pTemp = i + 100;
  pTemp++;
 }
 //指針間接賦值
 *arr = temp;
}
//打印數組
void print_array(int *arr,int n){
 for (int i = 0; i < n;i ++){
  printf("%d ",arr[i]);
 }
 printf("\n");
}
//二級指針輸出特性(由被調函數分配內存)
void test(){
 int *arr = NULL;
 int n = 10;
 //給arr指針間接賦值
 allocate_space(&arr,n);
 //輸出arr指向數組的內存
 print_array(arr, n);
 //釋放arr所指向內存空間的值
 if (arr != NULL){
  free(arr);
  arr = NULL;
 }
}

2.3 二級指針作形參輸入特性

二級指針作形參輸入特性是指由主調函數分配內存。

//打印數組
void print_array(int **arr,int n){
 for (int i = 0; i < n;i ++){
  printf("%d ",*(arr[i]));
 }
 printf("\n");
}
//二級指針輸入特性(由主調函數分配內存)
void test(){
 
 int a1 = 10;
 int a2 = 20;
 int a3 = 30;
 int a4 = 40;
 int a5 = 50;

 int n = 5;

 int** arr = (int **)malloc(sizeof(int *) * n);
 arr[0] = &a1;
 arr[1] = &a2;
 arr[2] = &a3;
 arr[3] = &a4;
 arr[4] = &a5;

 print_array(arr,n);

 free(arr);
 arr = NULL;
}

2.4 強化訓練_畫出內存模型圖

void mian()
{
 //棧區指針數組
 char *p1[] = { "aaaaa""bbbbb""ccccc" };

 //堆區指針數組
 char **p3 = (char **)malloc(3 * sizeof(char *)); //char *array[3];

 int i = 0;
 for (i = 0; i < 3; i++)
 {
  p3[i] = (char *)malloc(10 * sizeof(char)); //char buf[10]
  sprintf(p3[i], "%d%d%d", i, i, i);
 }
}

2.4 多級指針

將堆區數組指針案例改成三級指針案例:

//分配內存
void allocate_memory(char*** p, int n){

 if (n < 0){
  return;
 }

 char** temp = (char**)malloc(sizeof(char*)* n);
 if (temp == NULL){
  return;
 }

 //分別給每個指針malloc分配內存
 for (int i = 0; i < n; i++){
  temp[i] = malloc(sizeof(char)* 30);
  sprintf(temp[i], "%2d_hello world!", i + 1);
 }

 *p = temp;
}

//打印數組
void array_print(char** arr, int len){
 for (int i = 0; i < len; i++){
  printf("%s\n", arr[i]);
 }
 printf("----------------------\n");
}

//釋放內存
void free_memory(char*** buf, int len){
 if (buf == NULL){
  return;
 }

 char** temp = *buf;

 for (int i = 0; i < len; i++){
  free(temp[i]);
  temp[i] = NULL;
 }

 free(temp);
}

void test(){

 int n = 10;
 char** p = NULL;
 allocate_memory(&p, n);
 //打印數組
 array_print(p, n);
 //釋放內存
 free_memory(&p, n);
}

2.5 深拷貝和淺拷貝

若是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!!!複製給B
  cout <<"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;
}

根據上面實例能夠看到,淺複製僅複製對象自己(其中包括是指針的成員),這樣不一樣被複制對象的成員中的對應非空指針會指向同一對象,被成員指針引用的對象成爲共享的,沒法直接經過指針成員安全地刪除(由於若直接刪除,另外對象中的指針就會無效,造成所謂的野指針,而訪問無效指針是危險的;

除非這些指針有引用計數或者其它手段確保被指對象的全部權);而深複製在淺複製的基礎上,連同指針指向的對象也一塊兒複製,代價比較高,可是相對容易管理。

參考資料

  1. C Primer Plus(第五版)中文版

  2. https://www.cnblogs.com/lulipro/p/7460206.html

相關文章
相關標籤/搜索