堅持就是勝利
文章目錄
- 一、字符指針
- 1、面試題
- 二、指針數組
- 三、數組指針
- 1、數組指針的定義
- 2、&數組名 VS 數組名
- 3、數組指針的使用
- (1)二維數組傳參,形參是 二維數組 的形式
- (2)二維數組傳參,形參是 指針 的形式
- (3)總結
- 四、數組傳參和指針傳參
- 1、一維數組傳參
- 2、二維數組傳參
- 3、一級指針傳參
- 4、二級指針傳參
- 五、函數指針
- 1、舉例理解:
- 2、分析兩段有趣的代碼
- (1)代碼1
- (2)代碼2
- 學習 typedef 類型重命名
- 代碼2太復雜,簡化為如下形式
初級階段:
1、指針就是個變量,用來存放地址,地址唯一標識一塊內存空間。
2、指針的大小是固定的 4/8 個字節(32位平臺 / 64位平臺)。
3、指針是有類型,指針的類型決定了指針的 + - 整數的步長,指針解引用操作的時候的權限。
4、指針的運算。
一、字符指針
在指針的類型中,我們知道有一種指針類型為字符類型 char*
一般使用:
#include <stdio.h>int main()
{char ch = 'w';char* ps = &ch;*ps = 'w';return 0;
}
還有一種使用方式如下:
#include <stdio.h>int main()
{char arr[] = "abcde";//[a b c d e \0]const char* p = "abcde"; //本質是:指針變量p 指向 首字符 a 的地址//并且 "abcde" 是 "常量字符串",得加上 constprintf("%s\n", p); //結果:abcde printf("%c\n", *p); //結果:areturn 0;
}
代碼:const char* p = "abcde";
特別容易以為是 把字符串 “abcde” 放到字符指針 p 中,
但是本質是把 字符串 abcde 首字符 的地址放到了 p 中。
1、面試題
#include <stdio.h>int main()
{char str1[] = "abcde";char str2[] = "abcde";const char* str3 = "abcde";const char* str4 = "abcde";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* arr1[10]; //整型指針的數組
char* arr2[4]; //一級字符指針的數組
char** arr3[5]; //二級字符指針的數組
//使用 指針數組 模擬實現 二維數組#include <stdio.h>int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* arr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}
三、數組指針
1、數組指針的定義
類比:
整型指針——指向整型變量的指針,存放整型變量的地址的指針變量。
字符指針——指向字符變量的指針,存放字符變量的地址的指針變量。
數組指針——指向數組的指針,存放數組的地址的指針變量。
2、&數組名 VS 數組名
int arr[10];
arr 和 &arr 分別是什么?
arr 是 數組名,數組名 表示 數組首元素 的地址。
那 &arr 數組名 到底是什么?
#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 的類型是:int(*)[10],是一種數組指針類型
數組的地址 + 1,跳過整個數組的大小,所以,&arr +1 相對于 &arr 的差值是40。
#include <stdio.h>int main()
{int arr[10] = { 0 };printf("%p\n", arr); //類型:int *printf("%p\n", &arr[0]); //類型:int *printf("%p\n", &arr); //類型:int(*)[10] 數組指針類型return 0;
}
電腦中 int[10] * ,這么寫是錯誤的,是為了方便你理解,其實應該寫成 int( * )[10] , 數組指針類型
#include <stdio.h>int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%d\n", sizeof(arr));return 0;
}
總結:(數組名的理解)
數組名是數組首元素的地址。
有2個例外:
(1)sizeof(數組名),這里的數組名不是數組首元素的地址,數組名表示整個數組,sizeof(數組名) 計算的是 整個數組的大小,單位是字節。
(2)&數組名,這里的數組名表示整個數組,&數組名 取出的是 整個數組的地址。
(3)除此之外,所有的地方的數組名都是數組首元素的地址。
3、數組指針的使用
#include <stdio.h>int main()
{char arr[6] = "abcde";char(*p)[6] = &arr;printf("%c \n", *(*p)); //p 中放的是 &arr , *p 就是 arr(數組名), *(*p) 就是 *(arr),就是首字母,就是對首字母地址解引用printf("%s", p);return 0;
}
#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));}putchar('\n');for (i = 0; i < 10; i++){printf("%d ", *(arr + i));}return 0;
}
用指針數組,來遍歷輸出數組的每個元素,感覺使用起來很變扭!
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*p)[10] = &arr; //*&arr 那么*&就互相抵消,*&arr 就是 arr(數組名)int i = 0;for (i = 0; i < 10; i++){ //p 中存放的就是 &arr//由于*&arr 就是 arr(數組名)//*p 就是 *(&arr) 就是 arr(數組名)printf("%d ", *((*p) + i)); //(*p) 就是 arr(數組名) //(*p)+i 就是 arr + i //*(arr + i) 就是 遍歷數組的每一個元素putchar('\n');printf("%d ", (*p)[i]); //arr[i]//效果一樣}return 0;
}
所以,數組指針 不是像 上面的代碼那樣使用,這樣反而弄巧成拙了。
問:數組指針怎么使用呢?
答:一般在 二維數組 上才方便。
(1)二維數組傳參,形參是 二維數組 的形式
//二維數組傳參,形參是二維數組的形式#include <stdio.h>void Print(int arr[3][5], int r, int c)
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[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;
}
(2)二維數組傳參,形參是 指針 的形式
//二維數組傳參,形參是指針的形式#include <stdio.h>void Print(int(*p)[5], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; 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 };//arr 是數組名,數組名表示數組首元素的地址Print(arr, 3, 5);return 0;
}
(3)總結
一維數組傳參,形參的部分可以是數組(本質還是指針),也可以是指針
//一維數組傳參,形參的部分可以是數組(本質還是指針),也可以是指針void test1(int arr[5], int sz) //int arr[5] 本質還是指針,只是寫成了數組的形式
{}void test2(int* p, int sz)
{}int main()
{int arr[5] = { 0 };test1(arr, 5);test2(arr, 5);return 0;
}
二維數組傳參,形參的部分可以是數組,也可以是指針
//二維數組傳參,形參的部分可以是數組,也可以是指針void test3(char arr[3][5], int r, int c)
{}void test4(char(*p)[5], int r, int c)
{}int main()
{char arr[3][5] = { 0 };test3(arr, 3, 5);test4(arr, 3, 5);return 0;
}
學了指針數組和數組指針,我們來一起回顧并看看下面的代碼的意思:
四、數組傳參和指針傳參
在寫代碼的時候,難免要把【數組】或者【指針】傳給函數,那函數的參數該如何設計?
1、一維數組傳參
#include <stdio.h>void test(int arr[]) //ok? 答:可行
{} void test(int arr[10]) //ok? 答:可行
{} //[10]中的10,可以寫成100、1000都行,反正 int arr[10] 本質就是指針void test(int *arr) //ok? 答:可行
{}void test2(int *arr[20]) //ok? 答:可行
{} //原因:形參實參類型保持一致void test2(int** arr) //ok? 答:可行
{} //int *arr2[20] 就是數組20個元素,每個元素類型都是 int * //一級指針的地址,就是 二級指針
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);return 0;
}
2、二維數組傳參
void test(int arr[3][5]) //ok? 答:可行
{}void test(int arr[][]) //ok? 答:不可行
{} //不可以省略行void test(int arr[][5]) //ok? 答:可行
{}//總結:二維數組傳參,函數形參的設計只能省略第一個 [ ] 的數字
//因為對一個二維數組,可以不知道有多少行,但是必須知道一行多少元素
//這樣才方便運算void test(int* arr) //ok? 答:不可行
{} //arr 是數組指針的地址,arr 是數組第一行的地址 arr == &arr[0]void test(int* arr[5]) //ok? 答:不可行
{} //int* arr[5] 是指針數組,有5個元素,每個元素的類型是 int*void test(int(*arr)[5]) //ok? 答:可行
{} //int (*arr)[5]數組指針,元素類型是 int[5],是數組某一行的指針void test(int **arr) //ok? 答:不可行
{} //arr的類型是 int*,明顯錯誤//二級指針 是 用來接收一級指針的地址的
int main()
{int arr[3][5] = { 0 };test(arr);
}
3、一級指針傳參
#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,10 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一級指針p,傳給函數print(p, sz);return 0;
}
當一個函數的參數部分為一級指針的時候,函數能接受什么參數?
void test(char* p)
{}char ch = '2';
char* ptr = &ch;
char arr[] = "abcde";test(&ch); //第一種
test(ptr); //第二種
test(arr); //第三種
4、二級指針傳參
區別:
二維數組傳參,形參既可以是數組,也可以是指針。
二級指針傳參,形參只可以是指針。
#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]; //指針數組,類型是 char*//二級指針,是一級指針的地址test(&pc);test(ppc);test(arr); //是數組元素類型 char* 的地址return 0;
}
五、函數指針
函數指針——指向函數的指針
函數名 是 函數的地址
&函數名 也是 函數的地址
int (*pf)(int , int) 去掉 指針名 pf ,剩下的就是指針類型 int ( * )(int , int)
1、舉例理解:
2、分析兩段有趣的代碼
(1)代碼1
//代碼1
( *( void (*)() )0 )();
1、將 0 強制轉換成 void (*)() 類型的函數指針
2、調用 0 地址處的這個函數
//0x0012ff40 可以理解為:int
// 也可以理解為:int*//同理://0 可以理解為:int
// 也可以理解為:int*//void (*p)() -- p是函數指針
//void (*)() 是函數指針類型//從0開始切入分析
//0 被強制類型轉換為 void (*)() 類型的函數指針
// 0 此時變成了函數指針,也就是說 0 變成 地址了
//* 解引用 ()代表 函數
// 函數指針就是指針,指針就是地址
//調用 0 地址 處的這個函數//代碼1
( *( void (*)() )0 )();
我們自己寫的應用程序是不可以訪問 0 地址的。
舉例的是 系統程序。
(2)代碼2
//代碼2
void (*signal(int , void(*)(int)))(int);
//signal 是一個函數聲明
//signal 函數有 2 個參數
//第 1 個參數的類型是 int
//第 2 個參數的類型是 void(*)(int) 函數指針類型,該函數指針指向的函數有一個 int 類型的參數,返回類型是 void
//signal 函數的返回類型也是 void(*)(int) 函數指針類型,該函數指針指向的函數有一個 int 類型的參數,返回類型是 void //代碼2
void (*signal(int , void(*)(int)))(int);
學習 typedef 類型重命名
typedef unsigned int uint; //將 unsigned int 重命名為 uint //正確
typedef int* ptr_r; //將 int* 重命名為 ptr_r //正確
數組指針,這種書寫格式是錯誤的
//對 數組指針 進行 類型重命名typedef int(*)[10] parr_t; //這么寫就是錯誤的,不能以這種方式來書寫
應該改為這樣
//對 數組指針 進行 類型重命名//錯誤的書寫格式
//typedef int(*)[10] parr_t; //這么寫就是錯誤的,不能以這種方式來書寫//正確的書寫格式
typedef int(*parr_t)[10];
函數指針類型 跟上面的書寫格式一致
代碼2太復雜,簡化為如下形式
原代碼:
void (*signal(int , void(*)(int)))(int);
簡化后:
typedef void(*pfun_t)(int); //將 void (*)(int) 重命名為 pfun_t
pfun_t signal(int, pfun_t);
微軟雅黑字體
黑體
3號字
4號字
紅色
綠色
藍色