C語言之漫談指針(下)


C語言之漫談指針(下)

在上節我們講到了一些關於指針的基礎知識:

詳見:C語言之漫談指針(上)

本節大綱:

  • 零.小tips
  • 一.字符指針
  • 二.指針數組數組指針
  • 三.數組傳參與指針傳參
  • 四.函數指針及函數指針數組
  • 五.回調函數
  • 六.例題講解

 

零.小tips

在正式開始下節之前,我們先來穿插兩個小tips:

1.打印函數哪家強!

//假設有下面兩個打印函數,我們應該首選哪個?
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):參數是整個結構體

void print_2(struct person* peo):參數是該結構體的地址

我們不妨想一想當我們要傳遞的值非常多時,如果采用傳遞變量自身的模式,

變量傳遞到函數里將會產生一份臨時拷貝,那會將使用多少的內存,且該內存的使用毫無意義,僅僅只是為了打印,這並不划來

而方式二采用傳地址的方式,內存不僅使用的更少了一些,而且效率也變得更高!

2.  *  與  [ ] 的優先級關系:

如圖:

 

 

 我們看到 [ ]運算符的優先級是高於 * 的!

如 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

我們來驗證一下:

 

 

 

二.指針數組與數組指針

1.指針數組

在上節中我們便提到了指針數組的概念,我們再次來復習一下

字符數組:存放字符的數組

整形數組:存放整形的數組

指針數組:存放指針的數組

比如:

int main()
{
    int a = 1; int b = 2; int c = 3; int* arr[3] = { &a,&b,&c };//arr便是一個指針數組 return 0; }

對於一個單純的指針數組並沒有太多的知識

2.數組指針

數組指針,末尾兩字為“指針”,所以它就是個指針,用來指向數組的指針。

那它怎么表示呢? int (*p2)[10]; 

我們可以在這解釋一下:

 

 

 我們還可以這樣理解:

 

 

 3.數組名與&數組名

在上節課中,我們舉過這樣的一個例子:

#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個字節

所以:到這大家明白它倆的區別了嗎?

4.指針[整數]與  *(指針±整數)

對此我們看一個例子:

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語言之數據在內存中的存儲

 

5.數組指針的使用

再學習了概念之后,我們就開始正式使用了:

#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] 

 

三.數組傳參與指針傳參

接下來就是傳參了,

要是只在主函數里這樣改改去去,那多沒意義;我們要做的就是寫一個函數,在函數里進行改變。

1.一維數組傳參

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

 

 

2.二維數組傳參

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

 

3.一級指針傳參

一級指針傳參主要是用來傳數組首元素的地址,以及需要改變的值(如之前提到的swap()交換倆整數的值)

所以我們遇到函數時就要想想它都可以傳什么值進去

void test1(int* p)
{}
//test1函數能接收什么參數?
void test2(char* p) {} //test2函數能接收什么參數?

 

4.二級指針傳參

 同樣二級指針也是如此;

平常寫函數時就要想想這種函數除了可以傳我現在需要的變量類型,還可以傳什么類型的變量,如此下去我們對於傳參的理解肯定會愈來愈高!

四.函數指針及函數指針數組

1.函數指針

1.函數指針的定義

正所謂函數指針:那就是指向函數的指針變量

我們先來看一個函數指針長什么樣:

char* (*fun1)(char* p1, char* p2);

fun是它的名字

char* 是它所指向函數的返回類型

(char* p1, char* p2)是它所指向的函數參數

 

2.函數指針的使用

現在我們知道了它長什么樣子,那我們現在來使用一下它

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);//用上述類型,聲明了一個函數

這樣是不是明了了許多

2.函數指針數組

函數指針數組那然是儲存函數指針的數組了啊

我們來看一個例子:

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

這樣是不是簡潔了不少呢

3.指向函數指針數組的指針

指向函數指針數組的指針,說白了,它就是個指針,所指向的內容為函數指針數組

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

注:

  函數指針不允許 ± 運算

五.回調函數

1.回調函數

  回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一
個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該
函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或
條件進行響應。

 

如果學習過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; }

 

 

 

 

2.qsort

  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的地址

解析如圖:注意前置++是先加后用

 

 

 

 所以,最后的結果為:

 

 

 

 

 

 

 

|---------------------------------------------------

 到此,對於指針的講解便結束了!

若有錯誤之處,還望指正!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM