2019物聯網科協第四次軟件培訓——指針

指針

指針是什麼

指針 是一種數據變量類型,它儲存了一個寄存器地址,這個地址的值指向儲存在該地址的對象的值。

指針
名爲 a 的指針,指向一個存儲器地址,當中的值爲 b。
圖片來自Wikipedia.org

指針的優點

指針相對於值傳遞的速度更快(在相應變量佔用儲存空間較多的情況下),值傳遞在C/C++中是將已有對象複製一份到一個新的內存空間中;而指針則是給出儲存數據的位置和數據類型,對數據直接進行操作。

回到目錄

定義指針變量

定義指針變量比較簡單,如下

int *a;     //定義整型指針變量a
char *b;    //定義char類型指針變量
int **c;    //定義(int *)類型指針變量,所謂的二級指針
char ***d;  //定義(char **)類型的指針變量,所謂的三級指針

//特殊的指針,void型指針,可以指向任何數據類型
void* ptr = a;  //OK  指向int類型的數據
ptr = b;        //OK  指向char類型的數據

指針變量的值中有一個非常特殊的值: NULL,它指向系統中的任何變量或者函數。一般,我們使用它作爲一個標誌(返回指針的函數沒有正確執行、到達鏈表末尾等等)

回到目錄

指針的運算符

指針的常用運算符有兩個,一個是 &,一個是 *

首先說說 &,它是取地址運算符(在這裏它是單目運算符,區別於按位與),使用它可以得到相應變量的儲存地址。

其次說說 *,它是指針運算符(或稱間接訪問運算符,區別於乘號),使用它可以得到指針指向空間的實際值,也可以修改指針指向空間的值。(需要區別於定義變量時,在變量名前寫的星號)

舉幾個例子

int a = 0;
int *b = &a;
int **c = &b; //多級指針賦值
*b = 1;       //a == 1, 完全等價於 a = 1;
*(&a) = 2;    //a == 2, 完全等價於 a = 2;

&(*b) == b;   //先間接訪問a,再取出其地址, true
*(&a) == a;   //先取a的地址,在通過指針間接訪問a, true

"&*(接地址)"連用表示什麼都沒幹
"*&(接變量名)"連用也表示什麼都沒幹

回到目錄

const修飾

在定義指針的時候,可以使用const對其進行修飾,const修飾有兩種寫法

int a[2] = {1, 2};
const int *b = a;   //不能修改指針指向內容的值
b++;       //合法的
*b = 2;    //非法的
int *const c = a;   //不能修改指針變量的值
*c = 4;    //合法的
c++;       //非法的

兩種const修飾也可以一起用

int a[2] = {1, 2};
const int *const b = a;   //既不能修改指針變量的值,也不能修改指針指向內容的值
b++;      //非法的
*b = 3;   //非法的

回到目錄

coding環節

例一

觀察下面兩種方法的輸出結果有何不同

例一值傳遞

#include <stdio.h>

void swap(int a, int b) {
    int c = a;
    a = b;
    b = c;
}

int main() {
    int a = 2, b = 3;

    swap(a, b);

    printf("a=%d\tb=%d\n", a, b);
    return 0;
}

例一指針傳遞

#include <stdio.h>

void swap(int* pa, int* pb) {
    int c = *pa;
    *pa = *pb;
    *pb = c;
}

int main() {
    int a = 2, b = 3;

    swap(&a, &b);

    printf("a=%d\tb=%d\n", a, b);
    return 0;
}

例一總結

值傳遞只是拷貝了一份變量的值到新的內存空間,對新內存空間中的變量進行操作,而不會對原有變量造成任何影響。指針操作則直接對原有變量內存空間中的數據進行了修改,從而正確地完成了交換數據的功能。

回到目錄

例二

對比兩種方法的運行時間

例二值傳遞

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//定義一個佔用空間比較多的變量類型
typedef struct {
    int a;
    float b;
    double c;
    double d;
    double e;
    double f;
    double g;
    double h;
} comp;

int main() {
    comp a, b = {};
    getchar();               //排除執行過程對運行時間產生的影響
    clock_t start = clock(); //記錄開始時間
    int i = 0;
    for (; i < 100000000; i++) {
        b.a = i;
        a = b;   //每次執行此條語句都是將comp中的所有變量一個一個的從b賦值到a
    }
    clock_t end = clock();   //記錄結束時間
    printf("Used time=%dms\n", end - start);
    return 0;
}

例二指針傳遞

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//定義一個佔用空間比較多的變量類型
typedef struct {
    int a;
    float b;
    double c;
    double d;
    double e;
    double f;
    double g;
    double h;
} comp;

int main() {
    comp *a, b = {};
    getchar();               //排除編譯過程對運行時間產生的影響
    clock_t start = clock(); //記錄開始時間
    int i = 0;
    for (; i < 100000000; i++) {
        b.a = i;
        a = &b;   //只取出b的地址
    }
    clock_t end = clock();   //記錄結束時間
    printf("Used time=%dms\n", end - start);
    return 0;
}

例二總結

在變量佔用空間較多的情況下,採用指針傳值會比普通的值傳遞快很多

回到目錄

總結1

上面的兩個例子說明了在一些情況下,使用指針賦值來代替傳統的賦值操作會讓程序運行得更快
而且我們對指針的調用也能夠實現對兩個變量的值進行交換,或是讀取相應變量的值。

而這時有人或許會對指針爲什麼還要有不同的類型有所疑問。下面就讓我們嘗試着運行一下下面
的這串代碼。

#include <stdio.h>

int main() {
    int array1[10];
    char array2[10];
    printf("數組元素中,第0個元素的地址 %d\n", &array1[0]);
    printf("整型數組的首地址是 %d\n數組中第一個元素的地址是 %d\n", array1, &array1[1]);
    //使用指針加1的效果:
    printf("int型指針加1: %d\n", array1 + 1);
    printf("字符數組的首地址是 %d\n數組中第一個元素的地址是 %d\n", array2, &array2[1]);
    //使用指針加1的效果:
    printf("char型指針加1:%d\n", array2 + 1);
    return 0;
}

C\C++使用指針的類型來確定一次指針變量 +1 需要在內存空間向後移動多少個字節,同時
指針的類型也使得程序能夠正確地完成指針–>值的轉換(這裏的"值"指的是指針指向的內存
空間中儲存的值)。

其次,上面的程序也證明了數組的首地址與數組名中儲存的地址相同!

下圖是一個字符數組在內存空間內的存儲結構圖,對於字符數組,其指針每加“1”在內存空間內就是將指向的地址向後移動了一個字節(8位)的長度。

字符數組

下圖是一個整型數組在內存空間內的儲存結構圖,對於整型數組,其指針每加“1”在內存空間內就是將指向的地址向後移動了四個字節(32位)的長度。

整型數組

回到目錄

二維數組的指針

運行一下試試

#include <stdio.h>

int main() {
    int array[3][10];
    printf("二維數組首地址 %d\n", array);
    printf("第一行一維數組首地址 %d\n", *array);
    printf("二維數組第二行地址 %d\n", array + 1);
    printf("第二行一維數組首地址 %d\n", *(array + 1));
    return 0;
}

是不是覺得二維數組的指針還是挺好理解的
那下面來看看提高部分吧

Error code

#include <stdio.h>

int main() {
    int array[3][10] = {};
    int **p = array;
    printf("第1行第0列的值 %d\n", **(p1+1));
    return 0;
}

好吧,這樣居然就報錯!我強制轉換總行了吧!

#include <stdio.h>

int main() {
    int array[3][10] = {};
    int **p = (void**)array;
    return 0;
}

還錯,那怎樣才正確嘛!

Correct code

#include <stdio.h>

int main() {
    int array[10][10] = {};
    int(*p1)[10] = array;
    printf("第1行第0列的值 %d\n", **(p1+1));
    return 0;
}

二維數組指針總結

二維數組指針和二級指針的差異來自於內存中儲存數組方式的不同
理想的情況下,二維數組儲存的方式如下圖所示

理想的二維數組儲存方式

而在計算機的內存中,考慮到內存空間的總量有限,二維數組是以下圖的形式儲存的
也就是說,在C\C++中,二維數組是一個僞二維數組,它本質上就是一個一維數組

實際的二維數組儲存方式

回到目錄

函數指針

函數指針,函數指針,看名字就能看出來,它是一個指向函數的指針。定義方法爲:
<Typename1> (*funcPointer)(<Typename2>, …)
Typename1 爲函數返回值類型
Typename2… 爲函數的形參表

下面提供一個使用函數指針的例子

例三

#include <stdio.h>

int func1(int a) {
    return a / 2;
}

int func2(int a) {
    return a * 2;
}

int main() {
    int (*funcPointer)(int);     //定義函數指針
    funcPointer = func1;
    printf("func1: %d\n", funcPointer(10));   //函數指針的調用
    funcPointer = func2;
    printf("func1: %d\n", funcPointer(10));   //函數指針的調用
    return 0;
}

例三總結

函數指針是一個非常非常有用的工具,下面我們就舉一個例子。

回到目錄

例四

在下面的這個例子中,我們將使用函數指針來指定數組的排序方法(升序 or 降序)

//請如果現在打開的是C語言的項目,請先新建一個C++的項目
#include <iostream>
#include <algorithm>
using namespace std;

int array[1000];

bool cmp(int front, int behind) {  //比較函數(現在是升序)
    return front < behind;
}

int main() {
    int n;
    //*****輸入數組 Start******
    cin >> n;
    int i = 0;
    for (; i < n; i++) {
        cin >> array[i];
    }
    //****輸入數組 End*********
    sort(array, array+n, cmp);  //傳入比較函數,告訴sort這個數組從小到大排
    
    for (i = 1; i < n; i++) {   //測試數組排序是否正確
        if (array[i] < array[i - 1]) {
            cout << "Error!\n";
            return -1;
        }
    }
    return 0;
}

回到目錄

例五

使用指針進行數組元素交換

啥,怎麼又是使用指針交換數組元素????

這次是數組元素萬能交換~~~

#include <stdio.h>

void swp(void*, unsigned);

int main() {
    int a[2] = {1, 2};
    double b[2] = {2.4, 3.4};
    swp(a, sizeof(int));
    printf("int數組 %d %d\n", a[0], a[1]);
    swp(b, sizeof(double));
    printf("double數組 %f %f", b[0], b[1]);
    return 0;
}

/*******萬能數組元素交換器************
* 傳入參數ptr(任意類型指針),size指針對應的變量所佔用的字節數(可使用sizeof(類型名))
*/
void swp(void* ptr, unsigned size) {
    char a;
    unsigned i;
    char *first, *second;
    first = (char *)ptr;
    second = first + size;
    for (i = 0; i < size; i++) {
        a = first[i];
        first[i] = second[i];
        second[i] = a;
    }
}
//**************end*****************

回到目錄

例六

使用qsort對結構體進行排序

#include <stdlib.h>
#include <stdio.h>

int array[1000];
/**
* 比較函數類型 int funcName(const void *, const void *)
* 使用const修飾以避免對數組元素值的意外修改
*/
int cmp(const void *front, const void *behind) {
    return  *(const int *)front - *(const int *)behind;      //將void *指針轉換爲int *(使用const修飾以避免對數組元素值的意外修改)
}

int main() {
    int n;
    scanf("%d", &n);
    int i = 0;
    for (; i < n; i++) {
        scanf("%d", array + i);
    }
    qsort(array, n, sizeof(int), cmp);  //傳入四個參數,第一個是數組首地址,第二個是數組長度,第三個是數組中一個元素所佔用的字節數,第四個是比較函數
    for (i = 1; i < n; i++) {
        if (array[i] < array[i-1]) {
            printf("Error!\n");      //未正確排序,輸出Error,並退出程序
            return -1;
        }
    }
    return 0;
}

回到目錄

總結2

指針作爲一種高效的傳遞參數的變量類型,正確地使用可以加快程序運行速度。在C/C++中也有許多用到了指針的地方。例如:qsort,sort函數 cout操作符等。

回到目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章