@toc 面試
前言數組
在C語言基礎階段,咱們學習過指針相關的一些基礎內容,好比說:markdown
1.指針是一個變量,用來存放地址,地址是惟一標識一塊內存空間
2.指針的大小是固定的4 / 8個字節(32位平臺 / 64位平臺)
3.指針是由類型,指針的類型決定了指針的 + -整數的步長,指針解引用操做時候的權限
4.指針的運算數據結構
本篇文章及後面的幾篇文章將會更加詳細的去介紹和學習指針的進階部分。(指針的內容在數據結構中會常常用到,因此必定要好好學習,打好基礎~)ide
接下來,咱們繼續探討指針的高級使用函數
在指針的類型中咱們知道有一種指針類型爲字符指針 char*
通常使用方式:學習
還有使用方式以下:設計
注意觀察區別:%C 與 %S :3d
這種方式是將字符串的首地址放到指針中,經過指針能夠找到該字符串(千萬不要理解成將字符串放到指針裏面去,這是不可能的)
。(相似與數組名就是首元素地址,可是跟數組仍是有所區別的,這個字符串是一個常量字符串,沒法被改變,以下圖:)指針
常量字符串不能改變
若是說咱們想修改這個字符串,須要將其放入數組中,而後再去修改:
擴展:在C語言中,內存能夠被劃分爲棧區、堆區、靜態區、常量區。
棧區:局部變量,函數形參,函數調用
堆區:動態內存如malloc等申請使用
靜態區:全局變量,static修飾的局部變量
常量區:常量字符串
常量區中的內容在整個程序的執行期間是不容許被修改的,且同一份常量字符串只會建立一份,不會重複建立存儲。
看一面試題,輸出什麼?
#include <stdio.h> int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; char *str3 = "hello bit."; char *str4 = "hello bit."; if(str1 ==str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 ==str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
結果展現:
const修飾常量
分析:
建立數組須要開闢空間,數組arr1和arr2在內存空間所在位置是不一樣的,因此arr1 != arr2;
char p1 = 「abcdef」; char p2 = 「abcdef」; "abcdef"是常量字符串,不能被修改,在內存空間所佔位置固定,char p1 = 「abcdef」; 是將該常量字符串的首地址放到字符指針p1中,char p2 = 「abcdef」;
是將該常量字符串的首地址放到字符指針p2中。
也就是說p1和p2存放都是常量字符串"abcdef"的首地址,因此p1 ==p2
。(注意:一樣的常量字符串只會存一份,不會同時存兩份,因此不會開闢不一樣的空間來存儲。)
總結:
這裏arr1和arr2指向的是一個同一個常量字符串。
C /C++會把常量字符串存儲到單獨的一個內存區域當幾個指針。指向同一個字符串的時候,他們實際會指向同一塊內存。可是用相同的常量字符串去初始化不一樣的數組的時候就會開闢出不一樣的內存塊。因此arr1和arr2不一樣,p1和p2相同。
經過前面的學習,咱們能夠知道形如int arr1[5] = {0};的是整形數組,數組存放的是整形,形如char arr2[10] = {0};的是字符數組,數組存放的是字符。
同理,指針數組應該是數組,數組存放的是指針。
//指針數組 - 數組 - 存放指針的數組 int* arr[4];//存放 整形指針的數組 - 指針數組 char* ch[5];//存放 字符指針的數組 - 指針數組
指針數組的簡單使用
看如下代碼,猜猜結果是什麼?
//指針數組的簡單使用 int main() { int a = 10; int b = 20; int c = 30; int d = 40; int* arr[4] = {&a, &b, &c, &d}; int i = 0; for (i = 0; i < 4; i++) { printf( "%d ", *(arr[i]) ); } return 0; }
咱們知道數組名能夠表明首元素的地址,請看下面這段代碼:
#include<stdio.h> int main() { int arr1[] = { 1,2,3,4,5 }; //arr1--int* int arr2[] = { 2,3,4,5,6 }; //arr2--int* int arr3[] = { 3,4,5,6,7 }; //arr3--int* int* parr[] = { arr1,arr2,arr3 }; //存入每一個數組首元素數組 //經過arr數組打印arr1,arr2,arr3三個數組中的元素 int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *( (parr[i] + j) ); } printf("\n"); }
上面舉例都是用整型指針數組,接下來咱們來看一個字符指針數組的例子:
#include<stdio.h> int main() { char* p1 = "student_zhang"; char* p2 = "guofucheng"; char* p3 = "liudehua"; char* ch[3] = { p1,p2,p3 }; int i = 0; for (i = 0; i < 3; i++) { printf("%s\n", ch[i]); } return 0; }
這裏咱們再複習一下,下面指針數組是什麼意思 ?
int* arr1[10]; //整型指針的數組 char* ch[5]; //字符指針的數組 char* arr2[4]; // 一級字符指針的數組 char** arr3[5]; //二級字符指針的數組
數組指針是指針仍是數組 ?
答案是∶指針
咱們已經熟悉︰
整形指針 : int p ; 可以指向整形數據的指針。
浮點型指針 : float pf ; 可以指向浮點型數據的指針。
那數組指針應該是︰可以指向數組的指針。數組指針和指針數組要區分開來。
整型指針 ---> 指向整型的指針
int a = 10; int* pa = &a;
字符指針 ---> 指向字符的指針
char ch = 'w'; char* pc = &ch;
數組指針---> 指向數組的指針----->eg : int (*p)[10]=NAll ;
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //int* p = arr;//數組名是首元素地址 //數組指針 存放數組的指針變量 int(*p)[10] = &arr;//(*p)表明p是指針變量 //該指針指向了一個數組,數組10個元素,每一個元素的類型是int //若是不用括號將*p括起來,寫成int* p[10],這是指針數組 return 0; }
首先,咱們要知道[]的優先級是比 要高的,對於形式1,p1會先與[ ]結合,在與 結合,因此形式1是指針數組,()的優先級又比[]高,因此p2會先於 * 結合,在與[ ]結合,因此形式2是數組指針。
到這裏,相信你對數組指針有了必定了解,哪麼怎麼創建數組指針呢
?
✳
例如:給定char arr[5],請寫出用來表達char arr[5]的數組指針
先分析char arr[5],很明顯,這是一個指針數組,數組名是arr,有五個元素,數組的類型是char,咱們已知數組指針 - 指向數組的指針 - 存放數組的地址,因此應該對數組取地址,即&arr,如何應該定義一個有五個元素的指針存放數組的地址,即(p)[5],指針類型爲char,因此數組指針是char (p)[5] = &arr
不成熟的說指針的指針至關於二級指針,確定有兩個 「 * 」
因此咱們如今會寫數組指針了。
來試一試!
例如:
char( pa)[10] = &arr
int( pa)[2] = &par
咱們看到打印的結果都是同樣的,那麼數組名arr和數組的地址 & arr是同樣的嗎?
從地址值來看,二者是同樣的,可是二者的含義和使用是不一樣的:
int p1; p1+1 表示跳過一個int類型的長度,也就是4個字節
char p2; p2+1表示跳過一個char類型的長度,也就是1個字節
int(p3)[10]; p3+1表示跳過一個具備10個整型長度的數組,也就是410=40個字節
咱們先看這個例子:
經過數組指針解引用找到數組,再用方括號[ ],去找到數組中的每一個元素。
這種並不是數組指針的經常使用方式,由於用起來很「彆扭」。
這種方式不如首元素地址 + i 流暢:
數組指針的使用,通常常見於二維數組及其以上
當咱們在談首元素的時候,一維數組的首元素就是第一個元素,二維數組的首元素要先將二維數組看做一維數組(該數組中每個元素都是一個一維數組),那二維數組的首元素就是第一個一維數組。那麼二維數組的首元素地址就是第一個一維數組的地址!(不是第一個一維數組中第一個元素的地址,雖然值相同,但含義和使用不一樣)
#include<stdio.h> //常見的方式 void print_arr1(int arr[3][5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } //數組指針方式 void print_arr2(int(*p)[5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", (*(p + i))[j]); printf("%d ", *(*p + i)+j); printf("%d ", *(p[i]+j); printf("%d ", p[i][j]); //四個都等價 } printf("\n"); } } int main() { int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} }; //打印這個二維數組 print_arr1(arr,3,5);//數組名,行,列也要傳參 print_arr2(arr, 3, 5); //數組名-首元素地址 return 0; }
結果展現:
圖解:
注意:對一個存放數組地址的指針進行解引用操做,找到的是這個數組,也就是這個數組的數組名,數組名這時候又表示數組首元素地址!
*( p + i ):至關於拿到了一行<br/>至關於這一行的數組名<br/>( *p + i )[ j ] <===> *(*(p + i ) + j )
爲了更好的理解這一點,咱們來看這個例子:
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); //方式一:經過指針找到與首元素偏移i個元素的地址, //再對齊解引用操做,找到這個元素 printf("%d ", *(arr + i)); //方式二:既然能夠將arr賦值給p,說明arr與p等價 //那麼就能夠直接用arr替代p進行相應的解引用操做 printf("%d ", arr[i]); //方式三:經過數組名+[下標]訪問數組元素 //即arr+[下標i]訪問下標爲i的元素,也就是第i+1個元素 printf("%d ", p[i]); //方式四:既然arr與p等價, //那麼也能夠直接用p+[下標]的方式訪問數組的元素 //上述四種方式實際結果徹底相同,實際上也能夠互相轉換使用 } return 0; }
恍然大悟
總結:咱們對一個數組指針變量進行解引用操做,好比int(*p)[10],獲得的是一個數組,或者說是這個數組的數組名,而數組名又能夠表示該數組首元素的地址。若是要找到該數組中的每個元素,就須要對這個數組元素的地址進行解引用操做。
簡單點來講就是,對一個數組指針類型進行解引用操做,獲得的仍是地址,對這個地址在進行相應的解引用操做,才能獲得數組中的具體的元素。
下面這些代碼的含義是什麼?
int arr[5]; int* parr1[10]; int(*parr2)[10]; int(*parr3[10])[5];
解析:
int arr[5]; //arr是一個數組,數組有5個元素,每一個元素類型是int //arr類型是 int [5] --- 去掉變量名,剩下的就是變量的類型 int* parr1[10]; //parr1是一個數組,數組有10個元素,每一個元素類型是int* //parr1是指針數組,類型是 int* [10] int(*parr2)[10]; //parr2是一個指針,指針指向一個數組,數組有10個元素,每一個元素的類型是int //parr2是數組指針,類型是 int(*)[10] int(*parr3[10])[5]; //parr3是一個數組,數組有10個元素,每一個元素都是一個指 針 //指針指向一個數組,數組有5個元素,每一個元素類型是int //parr3是一個指向數組指針的數組,本質上仍是數組 //parr3類型是 int(*[10])[5]
int( parr3 [10])[ 5 ]; 拿掉數組名後,剩下 int()[5]就是這個數組的類型
問題1:parr2 = &parr1;//可否將數組parr1的地址放到parr2中呢?
答:不能,由於類型不匹配,parr2指向的類型應該是 int[10] parr1 是 int* [10];
#include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10]) //ok? {} void test(int* arr)//ok? {} void test2(int* arr[20])//ok? {} void test2(int** arr) // ok ? {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
答案:以上五種傳參方式均ok
注意:一維數組傳參能夠傳數組形式,也能夠傳指針形式,傳數組形式的時候數組元素的個數能夠不寫,也能夠寫,傳指針的時候要注意指針的類型,也就是指針指向什麼類型的元素,
好比說指針指向int類型元素,那麼指針的類型就是 int*
void test(int arr[3][5])//ok ? {} //能夠 void test(int arr[][])//ok ? {} //不能夠,行能夠省略,列不能夠,第一個[ ]內容能夠不寫,第二個[ ]要寫 void test(int arr[][5])//ok ? {} //能夠 void test(int* arr)// ok ? {} //不能夠 void test(int* arr[5])//ok ? {} //不能夠 void test(int(*arr)[5])//ok ? {} //能夠 void test(int** arr)// ok ? {} //不能夠 int main() { int arr[3][5] = { 0 }; test(arr); }
總結 : 二維數組傳參,函數形參的設計只能省略第一個[ ]的數字。
由於對一個二維數組,能夠不知道有多少行,可是必須知道一行多少元素。
這樣才方便運算。
二維數組傳參也能寫成指針的形式,指針的類型應該是數組指針。
思考1:這裏的指針傳參能夠用數組去接收嗎?
通過實驗咱們能夠看到這樣作是沒問題的,指針傳參能夠用數組去接收!
思考2:當一個函數的參數部分爲一級指針的時候,函數能接收什麼參數 ?
例如:int * p
void test1(int* p) { } int main() { int a = 10; int* pa = &a; int arr[10] = { 0 }; test1(&a); test1(pa); test1(arr); return 0; }
再例如:char* p
void test2(char* p) { } int main() { char a = 'W'; char* pa = &a; char arr[10] = { 0 }; test2(&a); test2(pa); test2(arr); return 0; }
注意:
int p1;
int p2;
*靠近int 或者靠近變量p實際的意義和效果沒區別,是同樣的。 通常咱們習慣用int p2這種寫法,這樣能夠明確表示指針變量p2的類型。**
void test(int** p) { } int main() { int* p1; int** ptr; int* arr[5]; test(&p1);//一級指針取地址 test(ptr);//二級指針 test2(arr);//一級指針數組的首元素 return 0; }