在上一篇有關指針的博客中,我們介紹了指針的基礎知識,如:內存與地址,解引用操作符,野指針等,今天我們將更加深入的學習指針的其他知識。
1.指針的使用和傳址調用
1.1strlen的模擬實現
庫函數strlen的功能是求字符串長度,統計的是字符串中\0之前的字符個數
?函數原型如下:
size_t strlen (const char* str);
?參數str接收一個字符串的起始地址,然后開始統計字符串中\0之前的字符個數,最終返回長度。關于strlen函數的詳細介紹網頁:
strlen - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen我們要實現該函數的模擬實現,就要從字符串起始地址開始逐個遍歷字符,只要不是\0字符,計數器就加一,?直到\0結束。
#include<stdio.h>
#include<assert.h>int my_strlen(char* str)
{assert(str != NULL);char* pz = str;while (*pz != '\0'){pz++;}return pz - str;
}int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}
?在代碼里,我們用到了assert斷言,他幫助我們判斷str是否為空指針,如果是,程序會報錯。
1.2傳值調用和傳址調用
?
void Swap(int x, int y)
{int temp = x;x = y;y = temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交換前:num1 = %d,num2 = %d\n", num1, num2);Swap(num1, num2);printf("交換后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}
?代碼運行結果:
?
我們發現沒有產生預期的效果,為什么呢?調試觀察一下:
?
實參傳遞給形參的時候,形參會單獨創建?份臨時空間來接收實參,對形參的修改不影響實參。
?所以上述代碼實際是不符合題目要求的。
該怎樣設計代碼呢,既然我們把交換數值傳到函數內無法實現交換,那如果傳輸的是地址呢,同一份內存空間還會不會失敗?
void Swap(int* px, int* py)
{int* temp = px;*px = *py;*py = *temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交換前:num1 = %d,num2 = %d\n", num1, num2);Swap(&num1, &num2);printf("交換后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}
查看結果,我們發現交換成功了。
?調用Swap函數的時候是將變量的地址傳遞給了函數,這種函數調用方法叫:傳址調用。
傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量;所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采用傳值調用。如果函數內部要修改主調函數中的變量的值,就需要傳址調用。
?2.數組名的理解
在上篇博客我們在使用指針訪問數組的內容時,有這樣的代碼:
?
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
?這里我們使用arr的方式拿到了數組第一個元素的地址,有些人可能會有疑惑?不應該&arr[0]才是數組第一個元素的地址嗎,其實,大部分情況這兩種寫法是一樣的意思,并沒有含義的不同,我們來做一個測試。
我們發現數組名和數組首元素的地址打印出的結果一模一樣?,數組名就是數組首元素的地址。
這時候可能有同學有疑問?數組名如果是數組首元素的地址,那下面的代碼又該如何理解?
?我們看到上述代碼的結果是40,如果arr是數組首元素的地址,其結果應該是4/8才對。
其實數組名就是數組首元素的地址是對的,但是有兩個例外:
? sizeof(數組名),sizeof中單獨放數組名,這里的數組名表示整個數組,計算的是整個數組的大小,單位是字節? &數組名,這里的數組名表示整個數組,取出的是整個數組的地址(整個數組的地址和數組首元素的地址是有區別的)除此之外,任何地方使用數組名,數組名都表示首元素的地址。
?這時有好奇的同學,嘗試了如下代碼:
三個打印結果一模一樣,他就不會區分了,明明結果是一樣,含義卻不同嗎?
我們再看下面這個代碼:
?
3.使用指針訪問數組
int main()
{int arr[10] = {0};int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){scanf("%d", p + i);}for (int i = 0; i < sz; i++){printf("%d\n", *(p + i));printf("%d\n", p[i]);//這一行代碼效果與上一行代碼效果完全相等}return 0;
}
在該代碼中,將*(p+i)換成p[i]也是能夠正常打印的,所以本質上p[i] 是等價于 *(p+i)。
4.一維數組傳參的本質
我們發現在函數內部無法正確獲取數組的元素個數。這是為什么呢?
這時候就要學習數組傳參的本質了 ,之前說過,數組名是數組首元素的地址,那么在數組傳參的時候,傳遞的是數組名,本質上傳遞的其實是數組首元素地址,并不會傳遞整個數組,這兩者是有區別的。
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}void test(int* p)
{int sz2 = sizeof(p) / sizeof(p[0]);printf("sz2 = %d\n", sz2);
}
這兩者是等效的。
總結:?維數組傳參,形參的部分可以寫成數組的形式,也可以寫成指針的形式。
?5.二級指針

?如上圖,a是整型變量,他的地址存放在pa中,pa的類型是int*,pa的地址又存放在ppa中,ppa的類型是int**,這就是二級指針。
對于二級指針的運算有:
int a = 20;
*ppa = &a;
?*ppa 通過對ppa中的地址進?解引用,這樣找到的是 pa , *ppa 其實訪問的就是 pa。
**ppa = 30;
//等價于*pa = 30
//等價于a = 30
?**ppa 先通過 *ppa 找到 pa ,然后對 pa 進?解引?操作: *pa ,那找到的是 a 。
6.指針數組
6.1指針數組概念
?這是一個典型的指針數組,他的每一個元素都是地址,只想某一塊區域。
6.2指針數組模擬二維數組
int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 2,3,4,5,6 };int arr3[5] = { 3,4,5,6,7 };int* arr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", arr[i][j]);//printf("%d ", *(*(arr+i)+j));//兩行效果一致}printf("\n");}return 0;
}
arr[i]是訪問arr數組中的元素,arr[i]找到的數組元素指向了一個一維整型數組,所以arr[i][j]就是一維整型數組的元素。