有關指標的那些事兒《一》

想去吹吹海風發表於2020-11-14

指標是什麼?

	指標在C語言中的含義就是地址,而存放地址的變數叫做指標(變數)。
	指標在32位作業系統下,都是 四位元組,64位為8位元組。
	不太理解?舉個例子吧。

什麼是指標,什麼是指標變數,解引用是訪問什麼?

指標解引用
在這裡插入圖片描述
可以看到a的地址和p的內容 是相同的。

指標怎麼用?

	有地址的資料都可以用指標來指向它,來完成一些操作。
  1. 指標型別

    根據資料型別,指標也分許多型別。(字元指標、整型指標、陣列指標、函式指標、結構體指標、等等)
    指標的型別決定了,對指標解引用的時候有多大的許可權(能操作幾個位元組)。 比如: char* 的指標解引用就只能訪問一個位元組,而 int* 的指標的解引用就能訪問四個位元組。
    
  2. 野指標

    野指標概念: 野指標就是指標指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
    野指標成因
    1. 指標未初始化
    2. 2. 指標越界訪問。
    何規避野指標:
    3. 指標初始化
    4. 小心指標越界
    5. . 指標指向空間釋放即使置NULL
    6.  指標使用之前檢查有效
    
  3. 指標運算

    指標+- 整數:
    指標-指標:
    指標的關係運算:		
    
  4. 二級指標:

    指標變數也是變數,是變數就有地址,那指標變數的地址存放在哪裡? 這就是 二級指標 。
    

字元指標

字元指標的要點!
1.字串的名字代表字串首元素的地址。
2.字元指標不可以修改字元內容。因為這裡字元的地址在系統記憶體字元常量區,只可讀,不可修改。
注意註釋裡的內容!

`	char* arr = "hello.c" ;//*arr是字元指標
	printf("%d\n", sizeof(arr));//sizeof(arr)==sizeof(type of arr)這裡字串名字arr的資料型別為char*,即字元型指標。32位作業系統下,指標都是4位元組。64位:8位元組。
	printf("%d\n", sizeof(*arr));//sizeof(*arr)==sizeof(type of *arr)這裡*arr是指標的解引用,“arr”字串名代表首元素的地址,所以sizeof(*arr)==sizeof(h),
	                           //“h”是一個字元,但它的資料型別是前面開闢空間時所決定的,這裡是char型,所以結果為1。

	/**arr = *(arr+1);*/ //可以看到指標字串是不能被修改的,因為指標字串中的字元是在系統記憶體中的字元常量區,只可讀取,不可被修改。
                         //我們使用指標字串的時候,只是使用了字元存在系統記憶體中的地址,通過地址來訪問和排序的。
	printf("%c\n", *arr + 1);//由於“*”優先順序高於“+”。所以先解引用再加1。解引用後是“h”,“h+1”是什麼呢?是系統字元常量區中“h”的下一個字元!
	printf("%c\n", *(arr + 1));//這裡解引用的是“arr+1”。arr代表首元素地址,+1是第二個元素的地址!再解引用就是第二個元素‘e’。
	                         //所以我們在使用指標的時候要多注意優先順序的問題,為了確保無誤可以加()。

	//---------------------------------------------------------------------------

`
字元指標不能修改字元的執行結果圖。
至於為什麼也在註釋裡寫了。
在這裡插入圖片描述

陣列名相關

這裡是存放在陣列裡的字串相關。
要點有兩個:
1.sizeof(陣列名)和 &陣列名 中的陣列名代表整個陣列,其他情況都是指首元素地址。
2.為什麼陣列字串可以修改呢?因為陣列是在棧幀上開闢空間,並且對字元常量區進行拷貝放入陣列開闢的記憶體中。所以可以被修改。

//---------------------------------------------------------------------------
	//陣列字串的資料型別大小,以及可否被修改驗證。
    char arr2[] = { "hello world!" };
	printf("%d\n", sizeof(arr2));//sizeof(陣列名)和 &陣列名 中的陣列名代表整個陣列,其他情況都是指首元素地址。所以“arr2”的大小,是char型別所佔位元組大小×陣列元素個數。字串最後系統給一個\0
	printf("%d\n", sizeof(*arr2));//第一個元素的資料型別所佔位元組數。
	
	arr2[0] = arr2[11];//用陣列下標修改陣列字串中的字元。
	char* p = arr2;//這是一個指向陣列內部首元素的指標,因為陣列名在這裡代表首元素地址。
	*(p + 2) = arr2[11];//用指標修改陣列字串中的字元。
	printf("%s\n", arr2);//為什麼陣列字串可以修改呢?因為陣列是在棧幀上開闢空間,並且對字元常量區進行拷貝放入陣列開闢的記憶體中。所以可以被修改。
	//---------------------------------------------------------------------------

一道面試題目:

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

答案:
這裡str3和str4指向的是一個同一個常量字串。C/C++會把常量字串儲存到單獨的一個記憶體區域,當幾個指標。指向同一個字串的時候,他們實際會指向同一塊記憶體。但是用相同的常量字串去初始化不同的陣列的時候就會開闢出不同的記憶體塊。所以str1和str2不同,str3和str4相同。

指標陣列和陣列指標

指標陣列:
指標陣列是指標還是陣列?
答案:是陣列。是存放指標的陣列。

int* arr3[5];//是什麼?

arr3是一個陣列,有五個元素,每個元素是一個整形指標。

陣列指標:

陣列指標是指標?還是陣列?
答案是:指標。
我們已經熟悉: 整形指標: int * pint; 能夠指向整形資料的指標。
浮點型指標: float * pf; 能夠指向浮點型資料的指標。
那陣列指標應該是:能夠指向陣列的指標。
下面程式碼哪個是陣列指標?

int *p1[10];
int (*p2)[10];
//p1, p2分別是什麼?

解釋:因為操作符” [ “的優先順序大於” * “所以p1先和” [ “結合,
說明p1是一個陣列,是一個存放int 型別的陣列。即int型指標陣列
p2先和
結合,說明p是一個指標變數,然後指著指向的是一個大小為10個整型的陣列。
所以p2是一個指標,指向一個陣列,叫陣列指標。指向一個int[10]的指標。
//這裡要注意:[]的優先順序要高於號的,所以必須加上()來保證p先和結合

&陣列名VS陣列名
對於下面的陣列
int ar[10];
arr&arr 分別是啥?
我們知道arr是陣列名,陣列名錶示陣列首元素的地址。
&arr到底是啥?
我們看一段程式碼:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

可見陣列名和&陣列名列印的地址是一樣的。
難道兩個是一樣的嗎?
我們再看一段程式碼:

#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0;
}

根據上面的程式碼我們發現,其實&arr和arr,雖然值是一樣的,但是意義應該不一樣的。
實際上: &arr 表示的是陣列的地址,而不是陣列首元素的地址。
&arr+1,跳過整個陣列的大小,所以 &arr+1 相對於 &arr 的差值是40.
而給arr+1,arr代表首元素的地址,+1只是指向下一個陣列內的元素。

陣列傳參

	不論是陣列傳參還是指標傳參,其實傳的都是地址。
	我們知道,指標變數本身存放的就是地址,那麼陣列傳參傳的是什麼呢? 

記住!!!
陣列傳參時,發生降維,傳的是降維之後第一個元素的地址。

這裡的元素指的是:
在這裡插入圖片描述
一維陣列傳參:

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

1、2、3、4、5均可以。
arr是一個int陣列,降維後是一個int*。
arr2是一個存放int型別的陣列[20],降維後是一個int**
1、2、原因是陣列傳參時降維,傳的都是首元素的地址,型別為int
。所以編譯器不關心[ ]裡面填寫的數字,寫多少都可以。所以1、2、正確。
3、中, arr是首元素的地址,型別為int*,自然可以。
4、中降維後傳的是指標,只要資料型別匹配,int**=int就可以。
5、可以,是因為傳引數據型別不匹配,傳的是int
,而test2接受的是int**。也正確!
結論:傳參時候,資料型別匹配即可!名字不重要!

二維陣列傳參

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//總結:二維陣列傳參,函式形參的設計只能省略第一個[]的數字。
//因為對一個二維陣列,可以不知道有多少行,但是必須知道一行多少元素。
//這樣才方便運算。

根據總結,1、3可以。2不行。

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

根據一維陣列傳參得出來的結論,降維後傳的是int (* )arr[5].//陣列指標
1中接收的型別為int* 。錯誤
2中接收的型別為int * arr[5]//指標陣列。錯誤
3中接收的型別為int(*)[5].正確
4中接收的型別為int * arr[5],錯誤。這裡的 **arr看作 *(*arr)。arr代表首元素地址。解引用後(*arr)=arr[5]。所以接收型別為int * [5]

指標傳參

一級指標傳參

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一級指標p,傳給函式
 print(p, sz);
 return 0;
}

思考:
當一個函式的引數部分為一級指標的時候,函式能接收什麼引數?
比如:

void test1(int *p)
{}
//test1函式能接收什麼引數?
void test2(char* p)
{}
//test2函式能接收什麼引數?

test1可以接收
1:一級in型指標*p
2:int型一維陣列(陣列降維後就是一級指標)
test2可以接收
1:一級char 型指標
2:char型一維陣列
3:char
字串名字。
二級指標傳參

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr);
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

思考:
當函式的引數為二級指標的時候,可以接收什麼引數?
以下三種!

void test(char **p)
{

}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//Ok?
 return 0;
}
  1. 字元指標
  2. 陣列指標
  3. 指標陣列
  4. 陣列傳參和指標傳參
  5. 函式指標
  6. 函式指標陣列
  7. 指向函式指標陣列的指標
  8. 回撥函式

這篇文章主要講述了1、2、3、4。
剩下的5、6、7、8。下篇文章見!

相關文章