對指標的詳細認識(二)
文章目錄
在 對指標的詳細認識(一)中我們已經知道:
- 指標就是一個用於存放地址的變數,地址唯一標識一塊記憶體空間。
- 指標的大小是固定的4/8個位元組(32位平臺/64位平臺)。
- 指標是有型別的,指標的型別決定了指標±整數的步長和指標解引用操作時的許可權大小。
- 指標的運算。
在本篇部落格中我們將繼續探討指標的高階內容。
字元指標
我們知道,在指標的型別中有一種指標型別叫字元指標char * 。
字元指標的一般使用方法為:
#include<stdio.h>
int main()
{
char ch = 'w';
char* p = &ch;
return 0;
}
程式碼中,將字元變數ch的地址存放在了字元指標p中。
其實,字元指標還有另一種使用方式:
#include<stdio.h>
int main()
{
char* p = "hello csdn.";
printf("%c\n", *p);//列印字元'h'
printf("%s\n", p);//列印字串"hello csdn."
return 0;
}
程式碼中,字元指標p中存放的並非字串"hello csdn.",字元指標p中存放的是字串"hello csdn.“的首元素地址,即字元’h’的地址。
所以,當對字元指標p進行解引用操作並以字元的形式列印時只能列印字元’h’。我們知道,列印一個字串只需要提供字串的首元素地址即可,既然字元指標p中存放的是字串的首元素地址,那麼我們只要提供p(字串首地址)並以字串的形式列印,便可以列印字串"hello csdn.”。
注意:程式碼中的字串"hello csdn."是一個常量字串。
這裡有一道題目,可以幫助我們更好的理解字元指標和常量字串:
#include <stdio.h>
int main()
{
char str1[] = "hello csdn.";
char str2[] = "hello csdn.";
char *str3 = "hello csdn.";
char *str4 = "hello csdn.";
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;
}
題目最後列印的結果是:
str1 and str2 are not same
str3 and str4 are same
題目中str1和str2是兩個字元陣列,比較str1和str2時,相當於比較陣列str1和陣列str2的首元素地址,而str1與str2是兩個不同的字元陣列,建立陣列str1和陣列str2是會開闢兩塊不同的空間,它們的首元素地址當然不同。
而str3和str4是兩個字元指標,它們指向的都是常量字串"hello csdn."的首元素地址,所以str3和str4指向的是同一個地方。
注意:常量字串與普通字串最大的區別是,常量字串是不可被修改的字串,既然不能被修改,那麼在記憶體中沒有必要存放兩個一模一樣的字串,所以在記憶體中相同的常量字串只有一個。
指標陣列
我們已經知道了整型陣列、字元陣列等。整型陣列是用於存放整型的陣列,字元陣列是用於存放字元的陣列。
int arr1[4];
char arr2[5];
陣列arr1包含4個元素,每個元素的型別是整型;陣列arr2包含5個元素,每個元素的型別是字元型。
指標陣列也是陣列,是用於存放指標的陣列。
int* arr3[5];
陣列arr3包含5個元素,每個元素是一個一級整型指標。
以此類推:
char* arr4[10];//陣列arr4包含10個元素,每個元素是一個一級字元型指標。
char** arr5[5];//陣列arr5包含5個元素,每個元素是一個二級字元型指標。
陣列arr4包含10個元素,每個元素是一個一級字元型指標;陣列arr5包含5個元素,每個元素是一個二級字元型指標。
陣列指標
陣列指標的定義
我們已經知道了,整型指標是指向整型的指標,字元指標是指向字元的指標,那麼陣列指標應該就是指向陣列的指標了。
整型指標和字元指標,在使用時只需取出某整型/字元型的資料的地址,並將地址存入整型/字元型指標即可。
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;//取出a的地址存入整型指標中
char ch = 'w';
char* pc = &ch;//取出ch的地址存入字元型指標中
return 0;
}
陣列指標也是一樣,我們只需取出陣列的地址,並將其存入陣列指標即可。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
//&arr - 陣列的地址
return 0;
}
那麼陣列指標的指標型別是如何寫出來的呢?
首先我們應該知道的是:
1. [ ]的優先順序要高於 * 。
2. 一個變數除去了變數名,便是它的變數型別。
比如:
int a = 10;//除去變數名a,變數型別為int
char ch = 'w';//除去變數名ch,變數型別為char
int* p = NULL;//除去變數名p,變數型別為int*
陣列也可以這樣理解:
int arr[10] = { 0 };除去變數名arr,變數型別為int [10]
int* arr[10] = { 0 };除去變數名arr,變數型別為int* [10]
陣列的變數型別說明了陣列的元素個數和每個元素的型別:
3.一個指標變數除去了變數名和 * ,便是指標指向的內容的型別。
比如:
int a = 10;
int* p = &a;//除去變數名(p)和*,便是P指向的內容(a)的型別->int
char ch = 'w';
char* pc = &ch;//除去變數名(pc)和*,便是pc指向的內容(ch)的型別->char
接下來我們就可以來寫陣列指標的指標型別了:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
//&arr - 陣列的地址
return 0;
}
首先p是一個指標,所以p必須要先與 * 結合,而[ ]的優先順序要高於 * ,所以我們要加上( )以便讓p與 * 先結合。
指標p指向的內容,即陣列型別是int [10],所以陣列指標p就變成了int(*p)[10]。
去掉變數名p後,便是該陣列指標的變數型別int( * )[10]。
如果我們不加( ),那麼就變成了int *p[10],因為[ ]的優先順序要高於 * ,所以p先與[ ]結合,這時p就變成了一個數組,而陣列中每個元素的的型別是int * ,這就是前面說到的指標陣列。
&陣列名 VS 陣列名
對於一個數組的陣列名,它什麼時候代表陣列首元素的地址,什麼時候又代表整個陣列的地址,這一直是很多人的疑惑。在這裡我給出大家準確的答案:
陣列名代表整個陣列的地址的情況其實只有兩種:
- &陣列名。
- 陣列名單獨放在sizeof內部,即sizeof(陣列名)。
除此之外,所有的陣列名都是陣列首元素地址。
比如:
int arr[5] = { 1, 2, 3, 4, 5 };
對於該陣列arr,只有以下兩種情況陣列名代表整個陣列的地址:
&arr;
sizeof(arr);//arr單獨放在sizeof內部
除此之外,所以的arr都代表陣列首元素地址,即1的地址。
將其與指標聯絡起來:
#include<stdio.h>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int* p1 = arr;//陣列首元素的地址
int(*p2)[5] = &arr;//陣列的地址
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p1+1);
printf("%p\n", p2+1);
return 0;
}
因為程式碼中的arr是陣列首元素地址,所以要用int * 的指標接收。而&arr是整個陣列的地址,所以要用陣列指標進行接收。
雖然一個是陣列首元素地址,一個是陣列的地址,但是它們存放的都是陣列的起始位置的地址,所以將p1和p2以地址的形式打印出來發現它們的值一樣。
陣列首元素地址和陣列的地址的區別在於,陣列首元素地址+1只能跳過一個元素指向下一個元素,而陣列的地址+1能跳過整個陣列指向陣列後面的記憶體空間。
陣列指標的使用
陣列指標有一個簡單的使用案例,那就是列印二維陣列:
#include<stdio.h>
void print(int(*p)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)//行數
{
int j = 0;
for (j = 0; j < col; 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(arr, 3, 5);//傳入二維陣列名,即二維陣列首元素地址,即二維陣列第一行的地址
return 0;
}
在這裡我們列印一個三行五列的二維陣列。傳參時我們傳入二維陣列的陣列名,明確列印的起始位置;傳入行數和列數,明確列印的資料範圍。
通過上面對&陣列名和陣列名的認識,我們知道了這裡傳入的陣列名代表的是二維陣列的首元素地址,而二維陣列的首元素第一行的元素,即傳入的是一維陣列的地址,所以我們必須用陣列指標進行接收。
列印時,通過表示式 * (*(p+i)+j ) 鎖定列印目標:
陣列引數、指標引數
一維陣列傳參
#include<stdio.h>
void test1(int arr[10])//陣列接收
{}
void test1(int *arr)//指標接收
{}
void test2(int *arr[20])//陣列接收
{}
void test2(int **arr)//指標接收
{}
int main()
{
int arr1[10] = { 0 };//整型陣列
int *arr2[20] = { 0 };//整型指標陣列
test1(arr1);
test2(arr2);
}
整型陣列:
當向函式傳入整型陣列的陣列名時,我們有以下幾種引數可供接收:
- 陣列傳引數組接收,我們傳入的是整型陣列,那我們就用整型陣列接收。
- 傳入的陣列名本質上是陣列首元素地址,所以我們可以用指標接收。陣列的元素型別是整型,我們接收整型元素的地址用int * 的指標即可。
整型指標陣列:
當向函式傳入整型指標陣列的陣列名時,我們有以下幾種引數可供接收:
- 陣列傳引數組接收,我們傳入的是整型指標陣列,那我們就用整型指標陣列接收。
- 指標接收,陣列的元素是int * 型別的,我們接收int * 型別元素的地址用二級指標int ** 即可。
注意:一維陣列傳參,函式形參設計時[ ]內的數字可省略。
二維陣列傳參
#include<stdio.h>
void test(int arr[][5])//陣列接收
{}
void test(int(*arr)[5])//指標接收
{}
int main()
{
int arr[3][5] = { 0 };//二維陣列
test(arr);
}
當向函式傳入二維陣列的陣列名時,我們有以下幾種引數可供接收:
- 二維陣列傳參二維陣列接收。
- 指標接收,二維陣列的首元素是二維陣列第一行的地址,即一維陣列的地址,我們用陣列指標接收即可。
注意:二維陣列傳參,函式形參的設計只能省略第一個[ ]內的數字。
一級指標傳參
#include<stdio.h>
void print(int* p, int sz)//一級指標接收
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;//一級指標
print(p, sz);
return 0;
}
當我們傳入的引數為一級指標時,我們可以用一級指標的形參對其進行接收,那麼當函式形參為一級指標的時候,我們可以傳入什麼樣的引數呢?
#include<stdio.h>
void test(int* p)
{}
int main()
{
int a = 10;
test(&a);//可以傳入變數的地址
int* p = &a;
test(p);//可以傳入一級指標
int arr[10] = { 0 };
test(arr);//可以傳入一維陣列名
//...
return 0;
}
總而言之,只要傳入的表示式最終的型別是一級指標型別即可傳入。
二級指標傳參
#include<stdio.h>
void test(int** p)//二級指標接收
{}
int main()
{
int a = 10;
int* pa = &a;
int** paa = &pa;
test(paa);//二級指標
return 0;
}
當我們傳入的引數為二級指標時,我們可以用二級指標的形參對其進行接收,那麼當函式形參為二級指標的時候,我們可以傳入什麼樣的引數呢?
#include<stdio.h>
void test(int** p)
{}
int main()
{
int a = 10;
int* pa = &a;
test(&pa);//可以傳入一級指標的地址
int** paa = &pa;
test(paa);//可以傳入二級指標
int* arr[10];
test(arr);//可以傳入一級指標陣列的陣列名
//...
return 0;
}
總而言之,只要傳入的表示式最終的型別是二級指標型別即可傳入。