指針
指針也是變量,在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
包含了兩個變量x
和y
移盆,分別是點的橫縱坐標悼院。讓我們定義point
:
struct point {
int x;
int y;
};
現(xiàn)在,我們來定義每一個點并使用它咒循。假設(shè)函數(shù)draw
接收一個點的x
和y
坐標据途,并將其顯示在屏幕上。如果沒有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
笨蚁,它使x
和y
坐標都向正方向移動睹晒。注意,我們不是傳遞兩個參數(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+i
和vowels+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
训挡,那就認為鏈表為空。
讓我們來定義鏈表的節(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ù)指針夜只?因為靈活。