主頁 >  其他 > 10W+字C語言硬核總結(二),值得閱讀收藏!

10W+字C語言硬核總結(二),值得閱讀收藏!

2021-07-26 07:06:35 其他

0.為什么使用指標

假如我們定義了 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,1] 為命令號, buf[2,3] 為資料型別, buf[4~7] 為該型別的數值,型別為 int ,使用如下陳述句進行賦值:

*(short*)&buf[0]=DataId;
*(short*)&buf[2]=DataType;
*(int*)&buf[4]=DataValue;
  • 資料轉換,利用指標的靈活的型別轉換,可以用來做資料型別轉換,比較常用于通訊緩沖區的填充,

  • 指標的機制比較簡單,其功能可以被集中重新實作成更抽象化的參考資料形式

  • 函式指標,形如: #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

歡迎一鍵三連!!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/290025.html

標籤:其他

上一篇:推薦一些python學習的書籍,python入門新手必看,少走一半彎路

下一篇:Hadoop Yarn 創建Application

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more