C語言進階

指針

指針也是變量,在C語言中扮演者重要的角色懊悯。有許多使用指針的理由凛捏,比如:

  • 字符串(string)
  • 函數(shù)里按引用傳遞參數(shù)
  • 創(chuàng)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
  • 指向函數(shù)
  • 等等

什么是指針吞瞪?

指針可以說是一種簡單的整型變量箩朴,存儲著某個值的內(nèi)存地址岗喉,而不是存儲值本身。

計算機的內(nèi)存是一系列序列化的數(shù)據(jù)炸庞,一個指針指向內(nèi)存特定的部分钱床。我們的程序可以使用指針來指向一個很大的內(nèi)存,這取決于我們怎么看待要存的數(shù)據(jù)埠居。

字符串指針

我們已經(jīng)討論了string查牌,但現(xiàn)在我們可以更加深入一些,了解C中的string實際上是什么滥壕。

下面這行代碼:

char * name = "John";

做了兩件事情:

  • 分配一個叫name的局部變量纸颜,name是一個指針,指向字符串John的第一個字符绎橘;也就是說胁孙,name變量存儲的是字符J的內(nèi)存地址。
  • John分配到程序內(nèi)存的某個地方(當(dāng)然称鳞,在需要編譯和執(zhí)行之后)涮较。

如果我們嘗試將name作為數(shù)組來訪問,這也是可以的冈止,name是字符J狂票,因為它存放著字符串John的首個字符的地址。

既然我們已經(jīng)知道內(nèi)存單元是有順序的熙暴,這意味著我們可以像數(shù)組一樣來訪問string的每一個字符闺属,直到到達最后一個終止字符\0

解引用

解引用是指我們可以獲取一個指針變量的實際值周霉,而不只是其內(nèi)存地址掂器。其實,之前我們已經(jīng)在數(shù)組中使用了解引用诗眨,只是我們不知道而已唉匾。比如,中括號[0]操作符匠楚,訪問數(shù)組中的第一個元素巍膘。并且,數(shù)組實際上是指針芋簿,訪問第一個元素相當(dāng)于解引用峡懈。相應(yīng)地,數(shù)組的名字存放的是數(shù)組的第一個元素的內(nèi)存地址与斤。我們可以使用星號(*)來解引用肪康。

如果我們想創(chuàng)建一個數(shù)組指向存放在棧中的不同元素荚恶,我們可以這樣寫:

/* define a local variable a */
int a = 1;

/* define a pointer variable, and point it to a using the & operator */
int * pointer_to_a = &a;

printf("The value a is %d\n", a);
printf("The value of a is also %d\n", *pointer_to_a);

注意:我們使用&操作符來指向變量a

然后我們使用解引用操作符來獲取指針變量的內(nèi)容磷支;我們也可以改變所引用的內(nèi)容谒撼。

int a = 1;
int * pointer_to_a = &a;

/* let's change the variable a */
a += 1;

/* we just changed the variable again! */
*pointer_to_a += 1;

/* will print out 3 */
printf("The value of a is now %d\n", a);

結(jié)構(gòu)體

使用

C中的結(jié)構(gòu)體是一種特殊的、大型的變量雾狈,可以存儲不同類型的各種變量廓潜。結(jié)構(gòu)體是C中類和對象的基礎(chǔ),結(jié)構(gòu)體用來:

  • 序列化數(shù)據(jù)
  • 在函數(shù)中通過一個參數(shù)傳遞多個值
  • 構(gòu)建數(shù)據(jù)結(jié)構(gòu)善榛,比如鏈表辩蛋、二叉樹等

結(jié)構(gòu)體中最簡單的例子就是point了,point包含了兩個變量xy移盆,分別是點的橫縱坐標悼院。讓我們定義point

struct point {
    int x;
    int y;
};

現(xiàn)在,我們來定義每一個點并使用它咒循。假設(shè)函數(shù)draw接收一個點的xy坐標据途,并將其顯示在屏幕上。如果沒有struct剑鞍,我們就必須指定兩個參數(shù)昨凡,每個坐標需要一個爽醋。

/* draws a point at 10, 5 */
int x = 10;
int y = 5;
draw(x, y);

使用struct蚁署,我們可以使用指針來傳遞參數(shù):

/* draws a point at 10, 5 */
struct point p;
p.x = 10;
p.y = 5;
draw(p);

為了訪問point的內(nèi)部變量,我們使用.操作符蚂四。

typedef

typedef允許我們將類型重命名光戈,這樣,我們就可以擺脫很長的struct類型的定義遂赠。typedef定義結(jié)構(gòu)體示例如下:

typedef struct {
    int x;
    int y;
} point;

這允許我們像這樣定義一個point

point p;

結(jié)構(gòu)體內(nèi)部也可以包含指針久妆,也就能夠包含string,或者包含其他結(jié)構(gòu)體跷睦,這就是struct的魅力所在筷弦。比如,我們?nèi)缦露xvehicle結(jié)構(gòu)體:

typedef struct {
    char * brand;
    int model;
} vehicle;

因為brand是一個字符指針抑诸,vehicle類型可以包含string烂琴。

vehicle mycar;
mycar.brand = "Ford";
mycar.model = 2007;

函數(shù)按照引用傳遞

含義

如果函數(shù)按值傳遞,那么實際參數(shù)只是原來的值的一份拷貝蜕乡。但如果我們拷貝引用而不是值呢奸绷?這就能夠讓我們控制傳來的參數(shù),相應(yīng)地层玲,對原值的修改也會生效号醉。

讓我們實現(xiàn)一個函數(shù)addone反症,將一個值加一,按照以下方式寫則不會生效畔派。

void addone(int n) {
    n++;
}

int n;
printf("Before: %d\n", n);
addone(n);
printf("After: %d\n", n);

然而铅碍,這樣卻是可行的。

void addone(int * n) {
    (*n)++;
}

int n;
printf("Before: %d\n", n);
addone(&n);
printf("After: %d\n", n);

第二個版本的addone不同之處在于线椰,它接收了一個指針變量n作為參數(shù)该酗,然后直接操縱它,因為它知道n在內(nèi)存中的地址士嚎。

注意:當(dāng)調(diào)用addone函數(shù)時呜魄,我們我們必須傳遞一個引用的值,而不是變量本身莱衩。當(dāng)函數(shù)知道了變量的地址爵嗅,它就不會拷貝變量本身。

指向結(jié)構(gòu)體的指針

讓我們創(chuàng)建一個函數(shù)叫call笨蚁,它使xy坐標都向正方向移動睹晒。注意,我們不是傳遞兩個參數(shù)括细,而是只傳遞一個結(jié)構(gòu)體的指針伪很。

void move(point * p) {
    (*p).x++;
    (*p).y++;
}

然而,如果我們希望解引用結(jié)構(gòu)體并訪問其內(nèi)部的成員時奋单,我們有一種更加簡明的語法锉试,這種方法在結(jié)構(gòu)體中很常用。我們可以像下面那樣重寫函數(shù):

void move(point * p) {
    p->x++;
    p->y++;
}

動態(tài)內(nèi)存分配

動態(tài)內(nèi)存分配是C的一個重要主題览濒。它允許創(chuàng)建諸如鏈表之類的復(fù)雜數(shù)據(jù)結(jié)構(gòu)呆盖。動態(tài)分配內(nèi)存允許我們無需在程序中指定變量的初始存儲空間。

為了動態(tài)地分配一塊內(nèi)存贷笛,我們需要指定一個指針來存放新分配的內(nèi)存的首地址应又。這樣,我們就可以通過這個指針來訪問這塊內(nèi)存乏苦,并且一旦我們不再需要這塊內(nèi)存株扛,我們將使用同樣的指針來釋放它。

讓我們假設(shè)我們想要分配一段person結(jié)構(gòu)體的內(nèi)存汇荐。person定義如下:

typedef struct {
    char * name;
    int age;
} person;

使用下面的語法洞就,來分配一個新的person實例,并將地址傳遞給myperson變量拢驾。

person * myperson = malloc(sizeof(person));

這就告訴編譯器我們想要動態(tài)地分配一段內(nèi)尋奖磁,只是用來存放person這個結(jié)構(gòu)體,然會返回一個指向該結(jié)構(gòu)體的指針繁疤。

我們可以使用->表示法來訪問person的成員咖为。

myperson->name = "John";
myperson->age = 27;

在我們使用完所分配的struct變量之后秕狰,我們可以使用free來釋放那段內(nèi)存。

free(myperson);

注意:free并不刪除myperson本身躁染,它刪除的是myperson指向的變量所在的內(nèi)存占用鸣哀。myperson變量仍然指向那段內(nèi)存,我們不再使用那個內(nèi)存區(qū)域了吞彤,并且我們應(yīng)該確保我們不會再使用那段內(nèi)存區(qū)域我衬,不然可能會有問題,因為那段區(qū)域可能已經(jīng)是其他數(shù)據(jù)了饰恕。

數(shù)組和指針

在先前指針的章節(jié)中挠羔,你已經(jīng)知道了指針可以用來存放變量的地址,比如埋嵌,在下面的代碼中破加,pc存放字符變量c的地址。

char c = 'A';
char *pc = &c;

這里雹嗦,c是一個標量范舀,可以存放單一的值。然而了罪,你已經(jīng)熟悉了數(shù)組可以存放多個相同類型的值锭环,并且在連續(xù)分配的內(nèi)存空間中。所以泊藕,你可能會考慮辅辩,我們能不能使用指針?實際上吱七,我們可以汽久。

讓我們從簡單的代碼開始看它輸出什么。我們稍后討論其每一個行為踊餐。

char vowels[] = {'A', 'E', 'I', 'O', 'U'};
char *pvowels = &vowels;
int i;

// Print the addresses
for (i = 0; i < 5; i++) {
    printf("&vowels[%d]: %u, pvowels + %d: %u, vowels + %d: %u\n", i, &vowels[i], i, pvowels + i, i, vowels + i);
}

// Print the values
for (i = 0; i < 5; i++) {
    printf("vowels[%d]: %c, *(pvowels + %d): %c, *(vowels + %d): %c\n", i, vowels[i], i, *(pvowels + i), i, *(vowels + i));
}

上面代碼的典型輸出如下:(不同機器會存在差別)

&vowels[0]: 4287605531, pvowels + 0: 4287605531, vowels + 0: 4287605531

&vowels[1]: 4287605532, pvowels + 1: 4287605532, vowels + 1: 4287605532

&vowels[2]: 4287605533, pvowels + 2: 4287605533, vowels + 2: 4287605533

&vowels[3]: 4287605534, pvowels + 3: 4287605534, vowels + 3: 4287605534

&vowels[4]: 4287605535, pvowels + 4: 4287605535, vowels + 4: 4287605535

vowels[0]: A, *(pvowels + 0): A, *(vowels + 0): A

vowels[1]: E, *(pvowels + 1): E, *(vowels + 1): E

vowels[2]: I, *(pvowels + 2): I, *(vowels + 2): I

vowels[3]: O, *(pvowels + 3): O, *(vowels + 3): O

vowels[4]: U, *(pvowels + 4): U, *(vowels + 4): U

你可能已經(jīng)猜到了,&vowels[i]給出了vowels數(shù)組中的第i+1個元素臀稚,并且吝岭,因為是一個字符數(shù)組,每一個元素占一個字節(jié)吧寺,所以其內(nèi)存區(qū)域就簡單地以字節(jié)分隔窜管。我們創(chuàng)建一個指針pvowels然后將數(shù)組的地址賦給它。pvowels+i是一個合法的操作稚机,即使在正常的情況下這看起來沒什么意義幕帆。特別地,在上面的輸出中赖条,&vowels[i]pvowels+i是相同的失乾。所以在使用這種方法輸出上不要有疑慮常熙。

如果你仔細看一下之前的代碼,你就會發(fā)現(xiàn)碱茁,我們還使用了另外一種更加奇怪的表示方法:vowels+i裸卫,這也是一樣的。另一方面纽竣,pvowels+ivowels+i同樣是返回數(shù)組vowels的第i+1個元素墓贿。為什么會這樣?

這是因為數(shù)組本身也是一個常指針蜓氨,指向數(shù)組的第一個元素聋袋。換句話說,vowels穴吹、&vowels[0]舱馅、和vowels+0都指向同樣的位置。

數(shù)組的動態(tài)內(nèi)存分配

到現(xiàn)在為止刀荒,我們已經(jīng)知道了可以使用指針來訪問數(shù)組代嗤。除此之外,我們還可以使用塊指針動態(tài)分配內(nèi)存缠借。這兩個方面可以結(jié)合在一起干毅,正如下面的代碼所闡述的。

// Allocate memory to store five characters
int n = 5;
char *pvowels = (char *) malloc(n * sizeof(char));
int i;

pvowels[0] = 'A';
pvowels[1] = 'E';
*(pvowels + 2) = 'I';
pvowels[3] = 'O';
*(pvowels + 4) = 'U';

for (i = 0; i < n; i++) {
    printf("%c ", pvowels[i]);
}

printf("\n");

free(pvowels);

在上面的代碼中泼返,我們分配了連續(xù)的5個字節(jié)去存儲字符數(shù)據(jù)硝逢。我們使用數(shù)組下標遍歷數(shù)組仿佛pvowels是一個數(shù)組。然后绅喉,注意pvowels實際上是一個指針渠鸽。就像指針和數(shù)組一節(jié)所說的,數(shù)組和指針實際上都是一樣的事情柴罐。

所以這有什么用徽缚?記住當(dāng)定義數(shù)組的時候,數(shù)組的大小是必須知道的革屠。但是凿试,在一些場景下,我們并不知道數(shù)組的所需要的實際長度似芝,我們可能就盡可能大地定義數(shù)組那婉,這樣會造成內(nèi)存浪費。然而党瓮,使用動態(tài)分配內(nèi)存的時候详炬,我們可以將程序的內(nèi)存按需分配。并且寞奸,不再使用的內(nèi)存也可以使用free函數(shù)來釋放呛谜。在不好的方面在跳,如果使用了動態(tài)內(nèi)存分配,我們就必須顯式地調(diào)用free函數(shù)來釋放內(nèi)存呻率,否則將有可能發(fā)生內(nèi)存泄漏硬毕。

舉一反三,我們可以按照同樣的方式為二維數(shù)組動態(tài)分配內(nèi)存礼仗,這也可以拓展到多維數(shù)組吐咳。不像一維數(shù)組,我們在這里需要使用指針的指針元践,正如下面的代碼所示韭脊。

int nrows = 2;
int ncols = 5;
int i, j;

// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char *));

// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));

pvowels[0][0] = 'A';
pvowels[0][1] = 'E';
pvowels[0][2] = 'I';
pvowels[0][3] = 'O';
pvowels[0][4] = 'U';

pvowels[1][0] = 'a';
pvowels[1][1] = 'e';
pvowels[1][2] = 'i';
pvowels[1][3] = 'o';
pvowels[1][4] = 'u';

for (i = 0; i < nrows; i++) {
    for(j = 0; j < ncols; j++) {
        printf("%c ", pvowels[i][j]);
    }

    printf("\n");
}

// Free individual rows
free(pvowels[0]);
free(pvowels[1]);

// Free the top-level pointer
free(pvowels);

遞歸

函數(shù)調(diào)用自身時將發(fā)生遞歸現(xiàn)象。遞歸可以使代碼更加整潔单旁、優(yōu)雅沪羔。但當(dāng)遞歸樹較深時,內(nèi)存占用將很嚴重象浑,時間復(fù)雜度也不佳蔫饰。

使用遞歸的情況:

  • 遍歷遞歸的數(shù)據(jù)結(jié)構(gòu),比如鏈表愉豺、二叉樹等
  • 探索某些可能存在的路徑

遞歸總是可以分為兩個部分的篓吁。終止部分指示了遞歸到什么程度為止,而遞歸部分則解決子問題蚪拦。

比如杖剪,下面的例子將使用遞歸加法來代替乘法:

#include <stdio.h>

unsigned int multiply(unsigned int x, unsigned int y)
{
    if (x == 1)
    {
        /* Terminating case */
        return y;
    }
    else if (x > 1)
    {
        /* Recursive step */
        return y + multiply(x-1, y);
    }

    /* Catch scenario when x is zero */
    return 0;
}

int main() {
    printf("3 times 5 is %d", multiply(3, 5));
    return 0;
}

鏈表

簡介

鏈表是最好最簡單的動態(tài)數(shù)據(jù)結(jié)構(gòu)的例子,使用指針來實現(xiàn)驰贷。然而盛嘿,理解指針對于理解鏈表如何工作是很關(guān)鍵的。

一般地括袒,鏈表作為一種線性表次兆,可以在鏈表的任何地方,按需增長和收縮箱熬。

  • 數(shù)據(jù)項可以在鏈表的中間增加或者移除
  • 沒有必要定義初始化大小

然而类垦,鏈表也有一些缺點:

  • 不能隨機訪問,鏈表的迭代必須從第一個元素開始城须,按順序存取。
  • 需要動態(tài)第分配內(nèi)存米苹,增加了代碼的復(fù)雜度糕伐,處理不好可能會造成內(nèi)存泄漏
  • 鏈表比數(shù)組有更大的頭部,因為每一個節(jié)點都必須包含一個指針域蘸嘶,增加了空間的使用良瞧。

什么是鏈表陪汽?

鏈表是一系列動態(tài)分配的節(jié)點的集合,每個節(jié)點都包含一個值與一個指針说订。指針總是指向鏈表的下一個成員至壤,做后一個成員指向為Null利诺。

通常使用一個局部的指針變量來指向鏈表的第一個元素,如果指針為NULL训挡,那就認為鏈表為空。

linked_list.jpeg

讓我們來定義鏈表的節(jié)點:

typedef struct node {
    int val;
    struct node * next;
} node_t;

注意:我們使用struct來實現(xiàn)數(shù)據(jù)上的遞歸歧强,即node內(nèi)部的next依然是node類型澜薄。我們將節(jié)點命名為node_t

現(xiàn)在我們可以使用節(jié)點了摊册。讓我么創(chuàng)建一個局部變量指向鏈表的第一個節(jié)點肤京。(叫head

node_t * head = NULL;
head = malloc(sizeof(node_t));
if (head == NULL) {
    return 1;
}

head->val = 1;
head->next = NULL;

我們剛才只是創(chuàng)建了鏈表的第一個變量,我們還必須設(shè)置它的值茅特,并且其下一個節(jié)點應(yīng)該為空忘分,如果我們的鏈表只有這個元素的話。注意:我們總是檢查malloc分配內(nèi)存是否成功白修。

在鏈表的尾部增加一個節(jié)點妒峦,我們只需要更改next域指向這個新的節(jié)點。

node_t * head = NULL;
head = malloc(sizeof(node_t));
head->val = 1;
head->next = malloc(sizeof(node_t));
head->next->val = 2;
head->next->next = NULL;

我們可以按照這樣的方式不斷地增加元素熬荆,直到最后一個元素的next域為NULL舟山。

遍歷鏈表

讓我們創(chuàng)建一個打印鏈表所有節(jié)點的函數(shù)。使用current指針來追蹤當(dāng)前要打印的節(jié)點卤恳。在打印玩一個節(jié)點之后累盗,將current的指針域指向下一個節(jié)點,然后再次打印突琳,直到到達鏈表的結(jié)尾若债。(next域為空)

void print_list(node_t * head) {
    node_t * current = head;

    while (current != NULL) {
        printf("%d\n", current->val);
        current = current->next;
    }
}

在表尾增加元素

為了迭代整個鏈表,我們使用一個叫做current的指針拆融。我們在每一次循環(huán)中都把它推向下一個節(jié)點蠢琳,直到我們到達最后。然后我們就可以在current插入一個節(jié)點镜豹。

void push(node_t * head, int val) {
    node_t * current = head;
    while (current->next != NULL) {
        current = current->next;
    }

    /* now we can add a new variable */
    current->next = malloc(sizeof(node_t));
    current->next->val = val;
    current->next->next = NULL;
}

在表頭增加元素

要在表頭添加元素傲须,我們只需要按照以下的步驟來做:

  • 創(chuàng)建一個節(jié)點并賦值
  • 將新節(jié)點的next域指向表頭
  • 修改表頭指針

我們將使用函數(shù)來完成這個操作,為了修改表頭的指針域趟脂,我們還需要傳遞一個指針的指針泰讽,這樣才能修改next域本身。

void push(node_t ** head, int val) {
    node_t * new_node;
    new_node = malloc(sizeof(node_t));

    new_node->val = val;
    new_node->next = *head;
    *head = new_node;
}

移除表頭元素

要移除變量,我們只需要進行反操作:

  • 新建一個臨時變量存放頭指針的下一個元素的信息
  • 釋放頭指針指向的節(jié)點
  • 將臨時節(jié)點設(shè)置為頭節(jié)點

以下是代碼:

int pop(node_t ** head) {
    int retval = -1;
    node_t * next_node = NULL;

    if (*head == NULL) {
        return -1;
    }

    next_node = (*head)->next;
    retval = (*head)->val;
    free(*head);
    *head = next_node;

    return retval;
}

移除表尾元素

移除鏈表的最后一個元素需要遍歷整個鏈表已卸,找到最后一個元素的地址佛玄,然后直接釋放其內(nèi)存空間,將上一個節(jié)點的next域修改為NULL累澡。注意:要找到最后一個元素梦抢,我們還必須額外設(shè)置一個變量來檢查是否到達倒數(shù)第二個,這是關(guān)鍵愧哟。

int remove_last(node_t * head) {
    int retval = 0;
    /* if there is only one item in the list, remove it */
    if (head->next == NULL) {
        retval = head->val;
        free(head);
        return retval;
    }

    /* get to the second to last node in the list */
    node_t * current = head;
    while (current->next->next != NULL) {
        current = current->next;
    }

    /* now current points to the second to last item of the list, so let's remove current->next */
    retval = current->next->val;
    free(current->next);
    current->next = NULL;
    return retval;

}

移除特定的元素

無論是通過索引還是節(jié)點值的方式給出要刪除的節(jié)點奥吩,我們都必須遍歷整個鏈表,找到我們想要刪除的元素翅雏,然后刪除它圈驼。移除的過程只需要修改指針域。

這是刪除的算法:

  • 迭代鏈表望几,找到要刪除元素的前一個元素绩脆,使用一個臨時指針來指向它
  • 使用一個額外指針指向待刪除的節(jié)點
  • 將臨時指針指向的節(jié)點的next域修改為下一個節(jié)點的下一個節(jié)點的地址
  • 釋放待刪除節(jié)點的地址

以下是一些邊界案例,在刪除的時候需要注意橄抹。

int remove_by_index(node_t ** head, int n) {
    int i = 0;
    int retval = -1;
    node_t * current = *head;
    node_t * temp_node = NULL;

    if (n == 0) {
        return pop(head);
    }

    for (i = 0; i < n-1; i++) {
        if (current->next == NULL) {
            return -1;
        }
        current = current->next;
    }

    temp_node = current->next;
    retval = temp_node->val;
    current->next = temp_node->next;
    free(temp_node);

    return retval;

}

聯(lián)合體

C中的聯(lián)合體和結(jié)構(gòu)體比較像靴迫,結(jié)構(gòu)體中的每個變量都有自己的存儲空間,但聯(lián)合體是多個變量使用同一個內(nèi)存空間楼誓,因此玉锌,聯(lián)合體的大小取決與最大的變量的占用空間大小。

所以疟羹,如果你想以不同的方式讀取一個變量主守,比如,讀取一個整數(shù)的每一個字節(jié)榄融,你可以這樣做:

union intParts {
  int theInt;
  char bytes[sizeof(int)];
};

這樣参淫,你就可以看到一個int類型的每個字節(jié)的內(nèi)容。

union intParts parts;
parts.theInt = 5968145; // arbitrary number > 255 (1 byte)

printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
parts.theInt, parts.bytes[0], parts.bytes[1], parts.bytes[2], parts.bytes[3]);

// vs

int theInt = parts.theInt;
printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
theInt, *((char*)&theInt+0), *((char*)&theInt+1), *((char*)&theInt+2), *((char*)&theInt+3));

// or with array syntax which can be a tiny bit nicer sometimes

printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
    theInt, ((char*)&theInt)[0], ((char*)&theInt)[1], ((char*)&theInt)[2], ((char*)&theInt)[3]);

比如愧杯,你可能有一個struct類型的數(shù)字涎才,但你不想像下面那樣使用:

struct operator {
    int intNum;
    float floatNum;
    int type;
    double doubleNum;
};

因為你的程序可能還有很多這樣的變量,這種存儲方式顯然會浪費內(nèi)存空間力九。所以耍铜,你可以這樣做:

struct operator {
    int type;
    union {
      int intNum;
      float floatNum;
      double doubleNum;
    } types;
};

operator這個結(jié)構(gòu)體現(xiàn)在的大小是type的大小再加上types聯(lián)合體的最大的變量的大小。

用法:

operator op;
op.type = 0; // int, probably better as an enum or macro constant
op.types.intNum = 352;

而且跌前,如果你不指定聯(lián)合體的名字棕兼,你甚至可以直接通過結(jié)構(gòu)體訪問!

struct operator {
    int type;
    union {
        int intNum;
        float floatNum;
        double doubleNum;
    }; // no name!
};

operator op;
op.type = 0; // int
// intNum is part of the union, but since it's not named you access it directly off the struct itself
op.intNum = 352;

另外抵乓,一種可能更加有用的特征是程储,當(dāng)你有很多和相同類型的變量的時候蹭沛,你想同時使用名稱(可讀性)和索引(迭代需要)臂寝,在這種情況下章鲤,你可以這樣做:

union Coins {
    struct {
        int quarter;
        int dime;
        int nickel;
        int penny;
    }; // anonymous struct acts the same way as an anonymous union, members are on the outer container
    int coins[4];
};

聯(lián)合體變量是共享內(nèi)存的!

union Coins change;
for(int i = 0; i < sizeof(change) / sizeof(int); ++i)
{
    scanf("%i", change.coins + i); // BAD code! input is always suspect!
}
printf("There are %i quarters, %i dimes, %i nickels, and %i pennies\n",
    change.quarter, change.dime, change.nickel, change.penny);

指針運算

如前所述咆贬,指針其實是一個整型的變量败徊,因此,可以對其進行基本的運算掏缎。

指針自增

就像任何變量一樣皱蹦,++操作符對那個變量執(zhí)行增加1的操作。在這里指針變量的自增將會使指針指向下一個內(nèi)存地址眷蜈。結(jié)合數(shù)組的操作沪哺,我們來看一下是如何實現(xiàn)的。

#include <stdio.h>

int main()
{
    int intarray[5] = {10,20,30,40,50};

    int i;
    for(i = 0; i < 5; i++)
        printf("intarray[%d] has value %d - and address @ %x\n", i, intarray[i], &intarray[i]);

    int *intpointer = &intarray[3]; //point to the 4th element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 4th element

    intpointer++; //now increase the pointer's address so it points to the 5th elemnt in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element

    return 0;
}

指針自減

--操作實現(xiàn)指針指向上一個內(nèi)存地址酌儒。

#include <stdio.h>

int main()
{
    int intarray[5] = {10,20,30,40,50};

    int i;
    for(i = 0; i < 5; i++)
        printf("intarray[%d] has value %d - and address @ %x\n", i, intarray[i], &intarray[i]);

    int *intpointer = &intarray[4]; //point to the 5th element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element

    intpointer--; //now decrease the point's address so it points to the 4th element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 4th element

    return 0;
}

指針加法

我們可以將一個整數(shù)值加到一個指針變量上辜妓,就像下面這樣。

#include <stdio.h>

int main()
{
    int intarray[5] = {10,20,30,40,50};

    int i;
    for(i = 0; i < 5; i++)
        printf("intarray[%d] has value: %d - and address @ %x\n", i, intarray[i], &intarray[i]);

    int *intpointer = &intarray[1]; //point to the 2nd element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 2nd element

    intpointer += 2; //now shift by two the point's address so it points to the 4th element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the addres of the 4th element

    return 0;
}

注意:這里的內(nèi)存地址一下子就改變了8忌怎。你可能會感到奇怪籍滴?答案很簡單,因為我們的指針是一個int類型的指針榴啸,而int類型的大小是4個字節(jié)孽惰,所以指針每增加1,內(nèi)存地址就增加4鸥印。

指針減法

同樣地勋功,我們可以對指針進行減法操作。

#include <stdio.h>

int main()
{
    int intarray[5] = {10,20,30,40,50};

    int i;
    for(i = 0; i < 5; i++)
        printf("intarray[%d] has value: %d - and address @ %x\n", i, intarray[i], &intarray[i]);

    int *intpointer = &intarray[4]; //point to the 5th element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element

    intpointer -= 2; //now shift by two the point's address so it points to the 3rd element in the array
    printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 3rd element

    return 0;
}

其他操作

還有更多的操作库说,比如比較>狂鞋,<==璃弄。這個和常規(guī)變量的比較基本相同要销,只不過在這里比較的是內(nèi)存地址而已。

函數(shù)指針

還記得指針嗎夏块?我們用它來指向一個字符數(shù)組和表示string疏咐。當(dāng)我們能靈活地操縱是真的時候,事情就變得有趣脐供。我們現(xiàn)在就做一些有趣的事情浑塞,嘗試將指針指向一個函數(shù)。

為什么指向一個函數(shù)政己?

可能這時你的腦海里第一個想法是酌壕,我可以簡單地通過函數(shù)名來調(diào)用函數(shù),為什么要使用一個指針來指向一個函數(shù)?卵牍!好問題」郏現(xiàn)在想一下你使用一個sort函數(shù)來對數(shù)組進行排序,有時你想對其升序有時降序糊昙,你該如何選擇辛掠?函數(shù)指針!

函數(shù)指針語法

void (*pf)(int);

我開始同意你開始的想法了释牺。這樣定義實在太復(fù)雜了萝衩!或者你可能會重新讀代碼,并且嘗試去理解指針到底指向哪里没咙。從外面開始讀猩谊,*pf是一個指向了一個函數(shù)。void是函數(shù)的返回值祭刚。最后牌捷,int是函數(shù)的參數(shù)。明白了袁梗?很好宜鸯!

讓我們向函數(shù)里面插入指針,然后再次讀代碼遮怜。

char* (*pf)(int*)

再次淋袖,*pf是函數(shù)指針,char*是函數(shù)的返回值锯梁,我們甚至猜到這是string即碗,int*是參數(shù)的類型。

好了陌凳,理論就到此為止剥懒。讓我們?nèi)タ纯磳嶋H環(huán)境的一些惡心代碼吧!以下是一個例子合敦。

#include <stdio.h>
void someFunction(int arg)
{
    printf("This is someFunction being called and arg is: %d\n", arg);
    printf("Whoops leaving the function now!\n");
}

int main()
{
    void (*pf)(int);
    pf = &someFunction;
    printf("We're about to call someFunction() using a pointer!\n");
    (pf)(5);
    printf("Wow that was cool. Back to main now!\n\n");
}

記得我們之前所說的sort函數(shù)嗎初橘?我們做一些相同的事情。這次我們不是對集合進行升序排序充岛,我們來定義自己的排序規(guī)則保檐。

#include <stdio.h>
#include <stdlib.h> //for qsort()

int compare(const void* left, const void* right)
{
    return (*(int*)right - *(int*)left);
//go back to ref if this seems complicated:                http://www.cplusplus.com/reference/cstdlib/qsort/
}
main()
{
    int (*cmp) (const void* , const void*);
    cmp = &compare;

    int iarray[] = {1,2,3,4,5,6,7,8,9};
    qsort(iarray, sizeof(iarray)/sizeof(*iarray), sizeof(*iarray), cmp);

    int c = 0;
    while (c < sizeof(iarray)/sizeof(*iarray))
    {
        printf("%d \t", iarray[c]);
        c++;
    }
}

現(xiàn)在,我們反思一下崔梗,為什么我們使用函數(shù)指針夜只?因為靈活。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒜魄,一起剝皮案震驚了整個濱河市扔亥,隨后出現(xiàn)的幾起案子场躯,更是在濱河造成了極大的恐慌,老刑警劉巖旅挤,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踢关,死亡現(xiàn)場離奇詭異,居然都是意外死亡谦铃,警方通過查閱死者的電腦和手機耘成,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驹闰,“玉大人,你說我怎么就攤上這事撒会∴诶剩” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵诵肛,是天一觀的道長屹培。 經(jīng)常有香客問我,道長怔檩,這世上最難降的妖魔是什么褪秀? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮薛训,結(jié)果婚禮上媒吗,老公的妹妹穿的比我還像新娘。我一直安慰自己乙埃,他們只是感情好闸英,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著介袜,像睡著了一般甫何。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遇伞,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天辙喂,我揣著相機與錄音,去河邊找鬼鸠珠。 笑死巍耗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跳芳。 我是一名探鬼主播芍锦,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼飞盆!你這毒婦竟也來了娄琉?” 一聲冷哼從身側(cè)響起次乓,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孽水,沒想到半個月后票腰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡女气,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年杏慰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炼鞠。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缘滥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谒主,到底是詐尸還是另有隱情朝扼,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布霎肯,位于F島的核電站擎颖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏观游。R本人自食惡果不足惜搂捧,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懂缕。 院中可真熱鬧允跑,春花似錦、人聲如沸提佣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拌屏。三九已至潮针,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間倚喂,已是汗流浹背每篷。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留端圈,地道東北人焦读。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像舱权,于是被迫代替她去往敵國和親矗晃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型宴倍。 運用指針編程是C語言最主要的風(fēng)格之一张症。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu)仓技; ...
    朱森閱讀 3,424評論 3 44
  • 指針 指針就是保存地址的變量,想一下俗他,無論什么類型的指針脖捻,因為地址的所占用的空間是一樣的,所以指針所占用的字節(jié)應(yīng)該...
    Jameslong閱讀 562評論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,089評論 1 32
  • 《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》--第六章 第40條(ps:此乃讀書筆記兆衅,加深記憶地沮,僅供大家參考...
    z_zero閱讀 293評論 0 0
  • 成見:可以變化的,成見會成為類似本能的東西羡亩,容易改變摩疑,可防范,寫下來夕春,討論未荒。 本能:一種結(jié)果,很難改變及志,但可有機制...
    A00小淺閱讀 267評論 2 2