在上節咱們講到了一些關於指針的基礎知識:html
詳見:C語言之漫談指針(上)python
本節大綱:數組
在正式開始下節以前,咱們先來穿插兩個小tips:app
//假設有下面兩個打印函數,咱們應該首選哪一個?
struct person
{
char name[20]; int age; char sex[5]; char tele[13]; char addr[20]; }; void print_1(struct person peo) { printf("This is print_1\n"); printf("%s\n", peo.name); printf("%d\n", peo.age); printf("%s\n", peo.sex); printf("%s\n", peo.tele); printf("%s\n", peo.addr); printf("\n"); } void print_2(struct person* peo) { printf("This is print_2\n"); printf("%s\n", peo->name); printf("%d\n", peo->age); printf("%s\n", peo->sex); printf("%s\n", peo->tele); printf("%s\n", peo->addr); printf("\n"); } int main() { struct person peo = { "zhangsan",18,"male","12345678","hualalalala" }; print_1(peo); print_2(&peo); return 0; }
在上述兩個打印函數,咱們應該首選 print_2() 函數函數
咱們先來看看兩個函數的傳參是什麼:學習
void print_1(struct person peo):參數是整個結構體spa
void print_2(struct person* peo):參數是該結構體的地址設計
咱們不妨想想當咱們要傳遞的值很是多時,若是採用傳遞變量自身的模式,3d
變量傳遞到函數裏將會產生一份臨時拷貝,那會將使用多少的內存,且該內存的使用毫無心義,僅僅只是爲了打印,這並不划來指針
而方式二採用傳地址的方式,內存不只使用的更少了一些,並且效率也變得更高!
如圖:
咱們看到 [ ]運算符的優先級是高於 * 的!
如 int* arr[3] 這個arr首先於[3]結合再與*結合。
在上節的指針類型中,咱們見到了一種爲char*的指針,他指向的空間內容爲 char 型,且解引用時只能解引用1個字節。
而且咱們通常的用法以下:
int main()
{
char ch = 'a'; char* p = &ch; *p = 'w'; return 0; }
可是若是咱們這樣寫 char* p = "abcdef"; 它算不算字符指針呢?
咱們能夠解引用打印一下: printf("%c\n", *p); 咱們會發現它打印了一個字符 a
因此 char* p = "abcdef"; 這種類型也算字符指針,它指向的是首元素的地址
也就是說把 a 的地址放在了 p 中
可是咱們發現當咱們這樣寫的時候 printf("%s\n", p); 運行結果會出現整個字符串,那這是爲何呢?
那咱們繼續回到剛纔的話題,既然 char* p = "abcdef"; 是一個字符指針,那咱們可不能夠對放在裏面的值進行修改呢?
咱們試試看:
int main()
{
char* p = "abcdef"; printf("%s\n", p); *p = 'c'; return 0; }
咱們運行時會發現編譯器運行到一半會崩,並彈出: 寫入訪問權限衝突。的錯誤。
這又是爲何呢?
這又得回到咱們上節所提到的計算機儲存器的一些知識了
見圖:
因此咱們要想修改,應該怎麼作?
若是想要修改字符串的內容,就須要對它的副本進行操做。若是在存儲器的非只讀區域建立了字符串的副本,就能夠修改它的字母了。
簡而言之:建立一個字符數組來接收它便可
int main()
{
char ch[] = "abcdef"; printf("%s\n", ch); *ch = 'c'; printf("%s\n", ch); return 0; }
咱們會發現運行結果爲:
abcdef
cbcdef
咱們一樣再來看一下原理:
[tips]:
因此若咱們要寫出 char* p = "abcdef"; 這樣的字符指針,最好在前面加一個 const 修飾符
即: const char* p = "abcdef"; ,由於 p 指向的內容不可修改。
有了以上的知識,咱們來看一道題:
#include <stdio.h>
int main()
{
char str1[] = "hello world."; char str2[] = "hello world."; char* str3 = "hello world."; char* str4 = "hello world."; 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
咱們來驗證一下:
在上節中咱們便提到了指針數組的概念,咱們再次來複習一下
字符數組:存放字符的數組
整形數組:存放整形的數組
指針數組:存放指針的數組
好比:
int main()
{
int a = 1; int b = 2; int c = 3; int* arr[3] = { &a,&b,&c };//arr即是一個指針數組 return 0; }
對於一個單純的指針數組並無太多的知識
數組指針,末尾兩字爲「指針」,因此它就是個指針,用來指向數組的指針。
那它怎麼表示呢? int (*p2)[10];
咱們能夠在這解釋一下:
咱們還能夠這樣理解:
在上節課中,咱們舉過這樣的一個例子:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0; }
咱們當時發現他們倆的地址相同,因而咱們下了一個結論:數組名錶示的就是數組首元素地址
那麼數組名與&數組名呢,咱們看看下面這個例子
#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 = %li\n", arr); printf("&arr= %li\n", &arr); printf("arr+1 = %li\n", arr + 1); printf("&arr+1= %li\n", &arr + 1); return 0; }
注:爲了方便觀察地址值的差別,筆者在這用 %li 來打印
咱們會發現,結果並不同,這就說明,它們倆並非一個東西;
那它倆究竟有何不一樣呢?
&arr 表示的是數組的地址,而不是數組首元素的地址,加1就跳過整個數組
arr表示的是數組首元素的地址,加1跳過1個元素,到數組的第2個元素
因此咱們來看看結果:
arr與arr+1恰好差4個字節
而&arr與&arr+1恰好差 咱們所定義的 一個有10個整形的數組的大小即40個字節
因此:到這你們明白它倆的區別了嗎?
對此咱們看一個例子:
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 }; int* p = arr; int i = 0; printf("指針[整數]---- *(指針±整數)\n"); for (i = 0; i < 10; i++) { printf(" %d ---- %d \n",p[i],*(p+i)); } return 0; }
運行結果:
咱們發現 指針[整數] 與 *(指針±整數) 的效果相同
此外再提一點,指針地址的強制類型轉換:
咱們都知道,指針的類型決定了指針一次能夠解引用幾個字節,因此指針的強制類型轉換隻是改變了該指針一次能夠解引用幾個字節!
如:
#include<stdio.h> int main() { double a = 1;//首先 dp ip cp 儲存的地址相同 double* dp = &a;//只是 dp 解引用能夠訪問8個字節,因此dp+1跳過8個字節 int* ip = (int*)&a;// ip 解引用能夠訪問4個字節,ip+1跳過4個字節 char* cp = (char*)&a;// cp 解引用能夠訪問1個字節,cp+1跳過一個字節 return 0;// 此外,並沒有區別 }
對上述知識咱們先來看一道題:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); printf("%d,%d",*(a + 1), *(ptr - 1)); return 0; } //程序的結果是什麼?
1.首先a是一個數組名,也就是數組的首元素地址,加一,跳過一個元素,指向數組的第二個元素,此時再進行解引用,所得到的值就是數組的第二個元素
因此*(a+1)的值就是 2;
2.ptr=int*(&a+1),首先這個數組名加了 & 符號,因此它加一就跳過整個數組而後將其轉爲int*,最後再減一,此時指針的類型爲 int * ,因此指向的位置就再往前走一個int 的位置,
指向數組的最後一個元素,因此*(ptr - 1)的值就爲 5;
而後,咱們再來看這一道題:
int main()
{
int a[4] = { 1,2,3,4 }; int* ptr1 = (int*)(&a + 1); int* ptr2 = (int*)((int)a + 1); printf("%x %x\n", ptr1[-1], *ptr2); return 0; }
它的結果是什麼呢?
1.首先&a+1就跳過整個數組,而後ptr[-1]就是指跳過整個數組以後,再向前移一位,也就是數組元素4,
因此ptr[-1]就是 4
2.對於ptr2來講,咱們先看 int(a) + 1,這部分,首先a是一個數組名,那它就是數組首元素的地址,總之就是一個地址,如今將他強制類型轉換爲 int ,再加一,此時就是簡簡單單的加一
而後又將其強制類型轉換爲 int* 型,應爲再強轉以前加了一,因此如今指向的是數組首元素的第二個字節,而後按照你編譯器的大小端模式所儲存的內存,再日後讀取3個字節,
再按找大小端模式拿出來,就是*ptr2的值
對於第二點的一些疑問,筆者整理了一張圖:
這樣就應該容易理解第二點了。對於大小端的儲存模式詳見:C語言之數據在內存中的存儲
再學習了概念以後,咱們就開始正式使用了:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int(*p)[10] = &arr;//把數組arr的地址賦值給數組指針變量p //可是咱們通常不多這樣寫代碼 return 0; }
使用,這裏咱們用到的例子是:依據 C語言之三字棋的實現及擴展 改編的一些函數;
如咱們的棋盤打印函數:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)//寫成這種形式,你們確定都能明白,可是寫成下面那樣呢?
{
int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int(*arr)[5], int row, int col)//首先arr是一個數組指針,它指向的是一個有五個 int 元素的數組,因此如今的arr就至關於二維數組第一行的地址 {//那麼,arr+1 便表示第二行首元素的地址,以此類推 int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { //printf("%d ", *(*(arr+i)+j));//他們倆效果相同 printf("%d ", arr[i][j]);//這裏以前已經說了,指針[整數] = *(指針±整數) } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); //數組名arr,表示首元素的地址 //可是二維數組的首元素是二維數組的第一行 //因此這裏傳遞的arr,其實至關於第一行的地址,是一維數組的地址 //能夠數組指針來接收 print_arr2(arr, 3, 5); return 0; }
在此以後,咱們在辨認一下下面分別都是聲明類型
int arr[5];//數組,大小爲5,內容爲int int* parr1[10];//數組,大小爲10,內容爲int* int(*parr2)[10];//指針,指向一個大小爲10,內容爲int的數組 int(*parr3[10])[5];//對於這個,彷佛就麻煩了一點 //咱們把它分開來看,int(*)[5] 是類型,指元素爲5個元素指針數組 // parr3[10]就是它的名稱 //換種寫法就是
//typedef int(*foo)[5];
//foo parr3[10];
並且,在這有一個問題:咱們能夠這樣寫嗎? parr2 = &parr1;
咱們再來看一下:
parr1裏放的是int*,而parr2裏放的是int,二者類型不同,因此固然不能夠這樣寫
要是parr2這樣寫即可以了 int*(*parr2)[10]
接下來就是傳參了,
要是隻在主函數裏這樣改改去去,那多沒意義;咱們要作的就是寫一個函數,在函數裏進行改變。
C 語言中,當一維數組做爲函數參數的時候,編譯器老是把它解析成一個指向其首元素首地址的指針。
注意數組傳參主函數裏要傳數組名
函數裏能夠寫成指針的形式也能夠寫爲數組的形式,注意[ ]裏能夠不寫大小,
實際傳遞的數組大小與函數形參指定的數組大小沒有關係。
例:
#include <stdio.h>
void test(int arr[])//√
{}
void test(int arr[10])//√
{} void test(int* arr)//√ {} void test2(int* arr[20])//√ {} void test2(int** arr)//√ {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
C 語言中,當一維數組做爲函數參數的時候,編譯器老是把它解析成一個指向其首元素首地址的指針。
這條規則並非遞歸的,也就是說只有一維數組纔是如此,當數組超過一維時,將第一維改寫爲指向數組首元素首地址的指針以後,後面的維不再可改寫。
好比:a[3][4][5]做爲參數時能夠被改寫爲(*p)[4][5]。二維數組傳參是要注意函數形參的設計只能省略第一個[]的數字。
void test(int arr[3][5])//√
{}
void test(int arr[][])//×
{} void test(int arr[][5])//√ {} //總結:二維數組傳參,函數形參的設計只能省略第一個[]的數字。 //由於對一個二維數組,能夠不知道有多少行,可是必須知道一行多少元素。 //這樣才方便運算。 void test(int* arr)//× {} void test(int* arr[5])//× {} void test(int(*arr)[5])//√ {} void test(int** arr)//× {} int main() { int arr[3][5] = { 0 }; test(arr); }
一級指針傳參主要是用來傳數組首元素的地址,以及須要改變的值(如以前提到的swap()交換倆整數的值)
因此咱們遇到函數時就要想一想它均可以傳什麼值進去
如
void test1(int* p)
{}
//test1函數能接收什麼參數?
void test2(char* p) {} //test2函數能接收什麼參數?
一樣二級指針也是如此;
日常寫函數時就要想一想這種函數除了能夠傳我如今須要的變量類型,還能夠傳什麼類型的變量,如此下去咱們對於傳參的理解確定會越來越高!
正所謂函數指針:那就是指向函數的指針變量
咱們先來看一個函數指針長什麼樣:
char* (*fun1)(char* p1, char* p2);
fun是它的名字
char* 是它所指向函數的返回類型
(char* p1, char* p2)是它所指向的函數參數
如今咱們知道了它長什麼樣子,那咱們如今來使用一下它
void print()
{
printf("hehe\n"); } int main() { void(*p)() = &print;//p是一個函數指針,所指向的函數無參無返回值 (*p)(); return 0; }
咱們發現,屏幕上出現了 hehe ,這就是它的一個基本使用
固然在這 void(*p)() = &print; 賦值的時候,能夠沒必要寫&號;
這是由於函數名被編譯以後其實就是一個地址,因此這裏兩種用法沒有本質的差異。
在這看一個例子:
void test()
{
printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
咱們發現他倆打印以後的結果相同;
一樣,在函數調用的時候, (*p)(); 也能夠不用寫 * ;
(你見過誰調用函數的時候還帶個 * )
即咱們在寫的時候,這兩種其實均可以
void print()
{
printf("hehe\n"); } int main() { void(*p)() = &print; void(*ch)() = print; p(); (*ch)(); return 0; }
接下來咱們再來看一個函數指針:
(*(void(*) ())0)()
乍一看,好複雜,咱們來仔細分析一下
這樣是否是就清楚了許多呢
咱們再來看一個:
void(*signal(int, void(*)(int)))(int);
咱們再來分析一下:
這是一個函數聲明
聲明的函數叫signal,signal函數有2個參數,第一個參數類型是int, 第二個參數類型是一個函數指針,
該函數指針指向的函數參數是int,返回類型是void
signal函數的返回來類型是一個函數指針,該函數指針指向的函數參數是int,返回類型是void
咱們也能夠把上面那個函數聲明這樣寫:
typedef void(*pfun_t)(int);//類型爲指向一個參數爲int,無返回值的函數指針 pfun_t signal(int, pfun_t);//用上述類型,聲明瞭一個函數
這樣是否是明瞭了許多
函數指針數組那然是儲存函數指針的數組了啊
咱們來看一個例子:
int Add(int x, int y)
{
return x + y; } int Sub(int x, int y) { return x - y; } int main() { //函數指針的數組 int (*pf1)(int, int) = Add; int (*pf2)(int, int) = Sub; int (*pf)(int, int);//函數指針 int(* pfA[4])(int, int);//函數指針的數組 //函數指針數組 //pfArr2就是函數指針數組,數組的類型爲 int(*)(int,int) int (* pfArr[2])(int, int) = { Add, Sub }; return 0; }
在此之上,咱們再來回憶一下用多分支寫出的一個簡易計算器
//計算器 - 加、減、乘、除
void menu()
{
printf("****************************\n"); printf("**** 1. add 2. sub ****\n"); printf("**** 3. mul 4. div ****\n"); printf("**** 0. exit ****\n"); printf("****************************\n"); } int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } //函數傳參-函數指針 //回調函數 void calc(int (*p)(int, int)) { int x = 0; int y = 0; int ret = 0; printf("請輸入2個操做數:>"); scanf("%d%d", &x, &y); ret = p(x, y); printf("ret = %d\n", ret); } int main() { int input = 0; do { menu(); printf("請選擇:>"); scanf("%d", &input); switch (input) { case 1: calc(Add);//計算器 break; case 2: calc(Sub);//計算器 break; case 3: calc(Mul); break; case 4: calc(Div); break; case 0: printf("退出計算器\n"); break; default: printf("選擇錯誤\n"); break; } } while (input); return 0; }
咱們會發現,在switch下出現了許多贅餘的語句,咱們來用函數指針來改寫一下:
void menu()
{
printf("****************************\n"); printf("**** 1. add 2. sub ****\n"); printf("**** 3. mul 4. div ****\n"); printf("**** 0. exit ****\n"); printf("****************************\n"); } int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; //函數指針數組 - 轉移表 int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div }; do { menu(); printf("請選擇:>"); scanf("%d", &input);//1 if (0 == input) { printf("退出程序\n"); break; } else if (input>=1 && input<=4) { printf("請輸入2個操做數:>"); scanf("%d%d", &x, &y); ret = pfArr[input](x, y); printf("ret = %d\n", ret); } else { printf("選擇錯誤\n"); } } while (input); return 0; }
這樣是否是簡潔了很多呢
指向函數指針數組的指針,說白了,它就是個指針,所指向的內容爲函數指針數組
int main()
{
//函數指針
int(*p)(int, int); //函數指針的數組,數組元素類型爲 int(*)(int, int) int(*pArr[4])(int, int); //ppArr是指向函數指針數組的指針 int(*(*ppArr)[4])(int, int) = &pArr; return 0; }
這裏給一個使用
//這裏的函數做用都是講傳遞的字符串給打印出來
char* fun1(char* p)
{
printf("%s\n", p); return p; } char* fun2(char* p) { printf("%s\n", p); return p; } char* fun3(char* p) { printf("%s\n", p); return p; } int main() { char* (*a[3])(char* p);//定義一個函數指針數組a,數組元素類型爲char(*)(char*p) char* (*(*pf)[3])(char* p);//定義一個指向函數指針數組的指針,所指向的數組類型爲 char*(*)(char*p) pf = &a;//將a的地址賦予pf //分別賦值 a[0] = fun1; a[1] = &fun2; a[2] = &fun3; //分別使用 (*pf)[0]("fun1"); pf[0][1]("fun2"); pf[0][2]("fun3"); return 0; }
注:
函數指針不容許 ± 運算
回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一
個函數,當這個指針被用來調用其所指向的函數時,咱們就說這是回調函數。回調函數不是由該
函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或
條件進行響應。
若是學習過python裝飾器的同窗,或許對這個概念有點熟悉
咱們先用python來寫一個裝飾器:
import time
def timing_func(f): def wrapper(): start = time.time() f() stop = time.time() return (stop - start) return wrapper def fun1(): print("lalala") time.sleep(1) fun1=timing_func(fun1) @timing_func def fun2(): print("hehehe") time.sleep(1) print(fun1()) print(fun2())
接下來咱們再看 c 的回調函數的一個例子
#include<stdio.h>
#include<time.h>
#include <Windows.h>
void print() { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", i); Sleep(100); } printf("\n"); } void print_time(void (*fun)()) { SYSTEMTIME tm; GetLocalTime(&tm); printf("函數開始前的時間爲:\n%d-%d-%d %d:%d:%d:%d\n", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds); printf("\n函數執行中……\n"); fun(); GetLocalTime(&tm); printf("\n函數結束後的時間爲:\n%d-%d-%d %d:%d:%d:%d\n", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds); } int main() { print_time(print); return 0; }
qsort即是一個用到了回調函數的庫函數
首先qsort是一個用來排序的函數:
咱們來看看人家的聲明:
最後的 int(*compar)(const void*, const void*)即是一個咱們使用時,須要傳遞的函數指針
它的返回值爲int ,參數爲兩個void* ,使用咱們設置函數是,要和它的類型一致
接下來咱們繼續來看看它各個參數的含義:
以及要注意的一點:
使用時要包含 stdlib.h 這個頭文件
接下來看他的一個使用:
#include <stdio.h>
#include <stdlib.h>
int values[] = { 40, 10, 100, 90, 20, 25 }; int compare(const void* a, const void* b) { return (*(int*)a - *(int*)b); } int main() { int n; qsort(values, 6, sizeof(int), compare); for (n = 0; n < 6; n++) printf("%d ", values[n]); return 0; }
在這個例子中,咱們要注意的就是在compare所指向的函數中,咱們要將兩個參數進行強制類型轉換爲咱們要排序的類型
接下來就舉幾個例子:
#include<string.h>
struct stu //假定一個結構體,來寫它各成員類型的排序函數
{
int num; char name[20]; int score; }; int int_compare(const void* _1, const void* _2)//進行 int 型的比較 { return (*(int*)_1) - (*(int*)_2); } int char_compare(const void* _1, const void* _2)// 進行char型的比較 { return (*(char*)_1) - (*(char*)_2); } //進行我所自定義結構體各成員元素的排序 int stu_cmp_num(const void* _1, const void* _2)//按照 num 來排序 { return ((struct stu*)_1)->num - ((struct stu*)_2)->num; } int stu_cmp_score(const void* _1, const void* _2) //按照 score 來排序 { return ((struct stu*)_1)->score - ((struct stu*)_2)->score; } int stu_cmp_name(const void* _1, const void* _2) // 按照 name 來排序 { return strcmp(((struct stu*)_1)->name, ((struct stu*)_2)->name); }
接下來就到了咱們的例題環節了,咱們再來複習一下剛剛學過的東西
struct Test
{
int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p;
//假設p 的值爲0x100000。 以下表表達式的值分別爲多少? int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }
在這用到告終構體內存對齊的知識,詳見:C語言之結構體內存的對齊
簡單得知該結構體在32位機器上的大小爲20個字節
首先p 是一個結構體指針 p+1 = 0x100000+20 = 0x100014
(unsigned long)p + 0x1 = 0x100000 + 1 = 0x100001
(unsigned int*)p + 0x1 = 0x100000 + 4 = 0x100004
2.
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int* p; p = a[0]; printf("%d", p[0]); return 0; }
這要注意的就是數組元素爲逗號表達式,逗號表達式的值爲最後一個值
因此數組初始化後的值爲 {1,3,5,0,0,0}
又由於p指向的是a[0]的地址,也就是a[0]這一行的首元素地址
因此p[0]最後的值爲 1
3.
int main()
{
int a[5][5]; int(*p)[4]; p = a; printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; }
這裏要注意的就是p所指向的是一個數組大小爲4的整形數組
而咱們的arr[5][5]是一行爲5個整形元素的數組,共五行
又由於數組在內存中是順序存儲的
因此畫出圖:
因此最後的結果爲4
4.
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }
首先a裏面分別存着三串字符串首字母的地址,如今pa指向了a,而後pa+1就指向了a首元素的下一位,也就是at中的a的地址
因此最後打印出來是at
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int* ptr1 = (int*)(&aa + 1); int* ptr2 = (int*)(*(aa + 1)); printf("%d,%d",*(ptr1 - 1), *(ptr2 - 1)); return 0; }
這裏的&aa是直接跳過了一整個數組,而aa+1是跳過了一行也就是5個元素
因此最後的結果爲 10,5
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" }; char** cp[] = { c + 3,c + 2,c + 1,c }; char*** cpp = cp; printf("%s\n", **++cpp); printf("%s\n", *-- * ++cpp + 3); printf("%s\n", *cpp[-2] + 3); printf("%s\n", cpp[-1][-1] + 1); return 0; }
對於這道題就顯得複雜了一些
c 裏面存着,E、N、P、R的地址
cp 與 c 反了過來,存的是R、P、N、R的地址
cpp 指向了 cp ,cpp裏存的是cp的首地址,即R的地址
解析如圖:注意前置++是先加後用
因此,最後的結果爲:
|---------------------------------------------------
到此,對於指針的講解便結束了!
如有錯誤之處,還望指正!