1. 程式人生 > 其它 >對指標的詳細認識(二)

對指標的詳細認識(二)

技術標籤:指標c++c語言

文章目錄


對指標的詳細認識(一)中我們已經知道:

  1. 指標就是一個用於存放地址的變數,地址唯一標識一塊記憶體空間。
  2. 指標的大小是固定的4/8個位元組(32位平臺/64位平臺)。
  3. 指標是有型別的,指標的型別決定了指標±整數的步長和指標解引用操作時的許可權大小。
  4. 指標的運算。

在本篇部落格中我們將繼續探討指標的高階內容。

字元指標

我們知道,在指標的型別中有一種指標型別叫字元指標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 陣列名

對於一個數組的陣列名,它什麼時候代表陣列首元素的地址,什麼時候又代表整個陣列的地址,這一直是很多人的疑惑。在這裡我給出大家準確的答案:
陣列名代表整個陣列的地址的情況其實只有兩種:

  1. &陣列名。
  2. 陣列名單獨放在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);
}

整型陣列:
當向函式傳入整型陣列的陣列名時,我們有以下幾種引數可供接收:

  1. 陣列傳引數組接收,我們傳入的是整型陣列,那我們就用整型陣列接收。
  2. 傳入的陣列名本質上是陣列首元素地址,所以我們可以用指標接收。陣列的元素型別是整型,我們接收整型元素的地址用int * 的指標即可。

整型指標陣列:
當向函式傳入整型指標陣列的陣列名時,我們有以下幾種引數可供接收:

  1. 陣列傳引數組接收,我們傳入的是整型指標陣列,那我們就用整型指標陣列接收。
  2. 指標接收,陣列的元素是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);
}

當向函式傳入二維陣列的陣列名時,我們有以下幾種引數可供接收:

  1. 二維陣列傳參二維陣列接收。
  2. 指標接收,二維陣列的首元素是二維陣列第一行的地址,即一維陣列的地址,我們用陣列指標接收即可。

注意:二維陣列傳參,函式形參的設計只能省略第一個[ ]內的數字。

一級指標傳參

#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;
}

總而言之,只要傳入的表示式最終的型別是二級指標型別即可傳入。